GUI作る
はじめに
※processingです。
※出典の無い独自研究です。
※最下部にコピペ用のコードを追加しました。
以下、基本度の高い機能。
interface GUI
{
void draw();
void draw(float x, float y);
}
interface IHit
{
boolean Hit(float x, float y);
}
interface ITranslate
{
void Translate(PVector dv);
}
interface HasOrigin
{
PVector GetOrigin();
}
interface IClickable
{
void mousePressed();
void mouseReleased();
}
interface IDraggable
{
void mouseDragged();
}
Draw() : GUIを描画する機能。GUIがGraphics User Interfaceを名乗るからには自身を表示する能力は欠くことができない。引数無しDraw()関数は、表示に必要なすべてのパラメータが既にGUIクラスに含まれていることを意味する。
Draw(x,y) : 表示位置を外部から指定する場合。例えば親子関係にあるGUIにおいて、親GUIが子GUIの表示位置を管理したい場合などに用いる。(x,y)が単純に子GUIの原点を指し示すこともあれば、子GUIの全座標に対する平行移動の量(dx,dy)とみなすこともできる。
Draw(params) : 表示に必要な全てのパラメータを外部から入力する場合もあり得る。
これらのDraw関数を抽象化することはありうる。すなわちinterfaceとして分離することはありうる。
Hit : ある座標がGUIに定義された領域に含まれるか否か。ひらたく言うとユーザーがクリックした時にそのGUIに触れたか否か。ユーザーの入力に対して反応しないGUIはありうるため、interfaceで抽象化する。ただし入力に反応しないGUIは常にfalseを返すHitを実装するという選択肢もある。必ずしも必要な能力とは限らない。
衝突判定の最も単純なケースとみなすことができる。抽象化すると図形対図形の衝突判定となりうる。
Translate : 入力されたベクトルでもって平行移動できる能力。ひらたく言うとドラッグした時に移動できる能力。必ずしも必要な能力とは限らない。
Move : 平行移動に比べて、図形の原点に対する瞬間移動のような挙動をする。図形が原点以外の座標を有する場合は、原点の移動量分、その他の座標は平行移動する。それゆえ、図形に設定した原点によって最終的な移動場所は変わる。
Origin : 原点を持つ。GUIを代表する一点を持つ。子GUIは親GUIの原点を基準として自身の位置を決定する。すなわち子GUIの絶対座標は親Origin+子Originと言った感じである。
矩形の場合、左上を原点とした方が良い場合もあれば、中心を原点とした方が良い場合もある。すなわち原点は好みに応じて変更できることが好ましい。必ずしも必要な能力とは限らない。
mousePressed();
mouseReleased();
mouseDragged();
processingのイベントに対応する能力。processingのイベント内部で機械的にループ処理する。
ArrayList<GUI> GUIs = new ArrayList<GUI>();
void mousePressed()
{
for(int i = 0; i<this.GUIs.size(); i++)
{
GUI gui = this.GUIs.get(i);
if(gui instanceof IClickable)
{
((IClickable)gui).mousePressed();
}
}
}
ボタンGUIを想像し、可能な限り基本的なものを組み立てようと試みる。
ただしここにあるものは下書きのようなものであり、過程である。なぜならば過剰な整理整頓は過程が喪失する傾向にあり、その結果がこの備忘録であるからである。
Coordinates : GUIを代表する座標群。表示に使ったり、衝突に使ったりするが、この座標群にはある種の忖度が働く。例えば円オブジェクトの場合、その代表する座標を近似の円周ととる場合や円の中心ととる場合が考えられる。すなわちこの座標群の定義はいい加減である。
//GUI Base
class GObject implements GUI, HasOrigin, IHit, ITranslate, IClickable, IDraggable
{
GUI Parent;
//代表する座標を全部
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
ArrayList<PVector> GetCoords()
{
return Coordinates;
}
//origin x,y
PVector Origin;
PVector GetOrigin()
{
return this.Origin;
}
boolean Hit(float x, float y){return false;}
void Translate(PVector dv){}
void mousePressed(){}
void mouseDragged(){}
void mouseReleased(){}
void draw(){}
//他から描画位置を指定される場合
//つまり実際の位置と描画位置がズレる可能性がある場合に使う
void draw(float x, float y){}
}//GUIBase
矩形GUIは良く使うので具体的に組み立てる。
これはGUIに形状情報を与えるに等しい。同レベルのGUIにLineGUIやCircleGUIが考えられる。抽象化するとHasShapeといった形で、別途形状情報の系統を作成して、それを所持する形となるであろう。
//+幾何
class RectGUIBase extends GObject
{
@Override
ArrayList<PVector> GetCoords()
{
this.Coordinates.clear();
this.Coordinates.add(new PVector(this.Origin.x, this.Origin.y));
this.Coordinates.add(new PVector(this.Origin.x+w, this.Origin.y));
this.Coordinates.add(new PVector(this.Origin.x+w, this.Origin.y+h));
this.Coordinates.add(new PVector(this.Origin.x, this.Origin.y+h));
return this.Coordinates;
}
float w;
float h;
@Override
void draw(float x_,float y_)
{
stroke(0);
rect(x_,y_,w,h);
}
@Override
void draw()
{
this.draw(this.Origin.x, this.Origin.y);
}
@Override
boolean Hit(float x, float y)
{
if(this.Origin.x<x&&x<this.Origin.x+this.w)
{
if(this.Origin.y<y&&y<this.Origin.y+this.h)
{
return true;
}
}
return false;
}
@Override
void Translate(PVector dv)
{
this.Origin.add(dv);
}
}
矩形のボタンを作成
//ボタン基礎
//機能(ハンドラ)を実装するとは限らない
//継承して直にハンドラを書くという使い方もする
class Button extends RectGUIBase
{
String ID = "RectGUIBase";
String Text;
@Override
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,w,h);
noFill();
stroke(0);
rect(x_,y_,w,h);
}
@Override
void draw()
{
this.draw(this.Origin.x, this.Origin.y);
}
}
ボタン押した時に関数が起動するようにする。
//機能(ハンドラ)を所持する
class FunctionButton extends Button
{
String ID = "FunctionButton";
GUIFunction GUIFunction;
@Override
void mousePressed()
{
if(!this.Hit(mouseX, mouseY)){return;}
if(GUIFunction!=null){GUIFunction.func();}
}
FunctionButton(float x, float y, float w, float h, String str, GUIFunction func)
{
this.Origin = new PVector(x,y);
this.w=w;
this.h=h;
this.GUIFunction = func;
this.Text = str;
}
FunctionButton(float x, float y, float w, float h, String str)
{
this.Origin = new PVector(x,y);
this.w=w;
this.h=h;
this.GUIFunction = new NULLFUNC();
this.Text = str;
}
}
interface GUIFunction
{
void func();
}
//何もしない
class NULLFUNC implements GUIFunction
{
void func()
{
}
}
このボタンをどこかに保存し、processingのmousePressed()イベントに合わせてボタンのmousePressed()イベントを実行すれば良い。
例:ただしあまり整理されてはいない。例えばHit判定とmousePressedを分断しているのは無駄である。例えばGUIManagerクラスを作成し、そのクラスがCurrentGUI(現在操作対象のGUI)などを管理すれなどすれば整理できる。
ただし繰り返すが、整理しすぎると頭を打って記憶を失った時や、紛争地帯を旅して帰ってきた時などにコードを見返すと、なにをやっているのか訳が分からんなる。
PVector pprev;
PVector prev;
PVector current;
PVector dv = new PVector(0,0);
ArrayList<GUI> GUIs = new ArrayList<GUI>();
GUI CurrentGUI;
int current_gui_index = -1;
void setup()
{
FunctionButton button = new FunctionButton(10,10,100,20,"test");
this.GUIs.add(button);
}
void draw()
{
background(255);
for(GUI gui : this.GUIs)
{
gui.draw();
}
}
boolean flag = false;
void mousePressed()
{
pprev = new PVector(mouseX,mouseY);
prev = new PVector(mouseX,mouseY);
current = new PVector(mouseX,mouseY);
for(int i = 0; i<this.GUIs.size(); i++)
{
GUI gui = this.GUIs.get(i);
if(gui instanceof IHit)
{
if(((IHit)gui).Hit(mouseX,mouseY))
{
current_gui_index = i;
CurrentGUI = gui;
}
}
if(gui instanceof IClickable)
{
((IClickable)gui).mousePressed();
}
}
}
void mouseDragged()
{
pprev = prev.copy();
prev = current.copy();
current.x = mouseX;
current.y = mouseY;
dv = PVector.sub(current,prev);
if(CurrentGUI!=null)
{
if(CurrentGUI instanceof IDraggable)
{
((IDraggable)CurrentGUI).mouseDragged();
}
}
}
void mouseReleased()
{
pprev = null;
prev = null;
current = null;
current_pobj_index=-1;
current_gui_index=-1;
for(int i = 0; i<this.GUIs.size(); i++)
{
GUI gui = this.GUIs.get(i);
if(gui instanceof IClickable)
{
((IClickable)gui).mouseReleased();
}
}
}
GUIの移動
以下、なんでもない適当な矩形。
//test用
class BlankRect extends RectGUIBase
{
String ID = "RectGUIBase";
BlankRect(float x, float y, float w, float h)
{
this.Origin = new PVector(x,y);
this.w=w;
this.h=h;
}
}
なんでもない適当な矩形を操作することを考える。例えば矩形のフチにわずかばかりのスペースを設けて、そこをドラッグすると矩形が移動するようにする。この矩形のフチのわずかばかりのスペースをフレームと名付ける。
このフレームGUIはGUIBaseたるGObjectを継承しない。一般的なGUIでもないし基本的なGUIでもない。GUIを自作することの意味はこのような一般的でもなければ基本的でもない、適当に思いついたものをいい加減に実装できることにある。
//端から端まで幅を持つ
//Origin及びTranslateを持たない
//他のGUIに寄生する、あるいは拡張するGUI
class Frame implements GUI
{
String ID = "Frame";
@Override
void draw(float x_,float y_)
{
}
@Override
void draw()
{
}
}
class RectTopFrame extends Frame implements IHit
{
String ID = "RectTopFrame";
RectGUIBase Target;
float Wide;
boolean Hit(float x, float y)
{
if(Target.Origin.x<x&&x<Target.Origin.x+Target.w)
{
if(Target.Origin.y<y&&y<Target.Origin.y+Wide)
{
return true;
}
}
return false;
}
RectTopFrame(RectGUIBase target, float wide)
{
Target=target;
Wide=wide;
}
@Override
void draw()
{
rect(Target.Origin.x,Target.Origin.y,Target.w,this.Wide);
}
}
このような、だからなんやねんのGUIに機能を追加する。
class MovalTopFrame extends RectTopFrame implements IHit, IClickable, IDraggable
{
String ID = "MovalTopFrame";
boolean pressed = false;
void mousePressed()
{
if(this.Hit(mouseX, mouseY))
{
pressed = true;
}
}
void mouseDragged()
{
if(pressed)
{
if(Target instanceof ITranslate)
{
((ITranslate)Target).Translate(dv);
}
}
}
void mouseReleased()
{
pressed = false;
}
MovalTopFrame(RectGUIBase target, float wide)
{
super(target, wide);
}
}
これでフレーム内部をドラッグすると、捕捉対象のなんでもない矩形が移動する。
void setup()
{
//FunctionButton button = new FunctionButton(10,10,100,20,"test");
//this.GUIs.add(button);
//同じリストに追加しておけば拡張が発動する
BlankRect rect = new BlankRect(10,100,200,200);
this.GUIs.add(rect);
//RectTopFrame frame = new RectTopFrame(rect, 20);
//this.GUIs.add(frame);
MovalTopFrame frame = new MovalTopFrame(rect, 20);
this.GUIs.add(frame);
}
このフレームGUIは、いわゆる一般的なGUIによくある、親子関係などの構造に含まない。それゆえに面倒な設定をしなくて良い。作成時に捕捉対象への参照を取り込み、ただ単純にGUIのリストに放り込んでおくだけで機能を発揮する。
ただしGUIリストのループには注意が必要である。例えば自身もドラッグ機能を持つ矩形にたいしてフレームを取り付けた場合、フレームのドラッグ機能を実行するのか、矩形のドラッグ機能を実行するのか、あるいは両方のドラッグ機能を実行するのか、それらはループの走査方法とGUIの格納順番によって変化する。
また、フレームにフレームを接続するなどの拡張を繰り返すと最終的に訳の分からない異形のGUIができあがる。
上記のフレームは矩形に対し、矩形の上辺から内側に生成されるが、生成の辺を変えたり向きを変えたりは、あるいはそれらを一般化したクラスを作成したりは自由である。
寄生タイプの適当な拡張GUIをもう一つ。
//Translateできるとは限らない
class Label implements GUI
{
String ID = "Label";
String Text;
PVector Origin;
float w;
float h;
//boolean Hit(float x, float y){return false;}
@Override
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,w,h);
noFill();
stroke(0);
rect(x_,y_,w,h);
}
@Override
void draw()
{
this.draw(this.Origin.x, this.Origin.y);
}
}
//独立したOriginを持たないためLabelとは別の系統
class OffsetLabel implements GUI, IHit, ITranslate, IClickable, IDraggable
{
String ID = "OffsetLabel";
GUI Target;
String Text = "label";
PVector MarkTo = new PVector(0,0);//指し示す場所へのオフセット
PVector Offset = new PVector(0,0);//MarkToからLabelのLeftTopまでのオフセット
float w;
float h;
PVector AbsOrigin()
{
PVector v = new PVector(0,0);
if(Target instanceof HasOrigin)
{
HasOrigin o = (HasOrigin)Target;
v.add(o.GetOrigin());
}
return v;
}
PVector AbsMarkTo()
{
PVector v = AbsOrigin();
v.add(MarkTo);
return v;
}
PVector AbsCoord()
{
PVector v = AbsMarkTo();
v.add(Offset);
return v;
}
boolean Hit(float x, float y)
{
if(!(Target instanceof HasOrigin)){return false;}
HasOrigin o = (HasOrigin)Target;
if(AbsCoord().x<x&&x<AbsCoord().x+w)
{
if(AbsCoord().y<y&&y<AbsCoord().y+h)
{
return true;
}
}
return false;
}
boolean pressed = false;
void mousePressed()
{
if(this.Hit(mouseX, mouseY))
{
pressed = true;
}
}
void mouseDragged()
{
if(pressed)
{
this.Translate(dv);
}
}
void mouseReleased()
{
pressed = false;
}
void Translate(PVector dv)
{
this.Offset.add(dv);
}
@Override
void draw(float x_,float y_)
{
if(!(Target instanceof HasOrigin)){return;}
fill(0);
textSize(16);
text(Text,AbsMarkTo().x+x_,AbsMarkTo().y+y_,w,h);
noFill();
line(AbsMarkTo().x,AbsMarkTo().y,AbsMarkTo().x+x_,AbsMarkTo().y+y_);
stroke(0);
rect(AbsMarkTo().x+x_,AbsMarkTo().y+y_,w,h);
}
@Override
void draw()
{
this.draw(this.Offset.x, this.Offset.y);
}
OffsetLabel(GUI target, float x_, float y_, float w_, float h_ )
{
this.Target = target;
this.Offset = new PVector(x_,y_);
this.w=w_;
this.h=h_;
}
}
このラベルGUIは注釈を付けたり、パラメータを表示したりする時に使用することができる。ラベル自体ドラッグできる。また、寄生タイプなので寄生先が移動しても追跡する。
このラベルに対して、寄生先の操作機能を持たせることも、関数実行機能を持たせることも当然可能である。
以下のような感じで適当に用いられる。
void setup()
{
//FunctionButton button = new FunctionButton(10,10,100,20,"test");
//this.GUIs.add(button);
//同じリストに追加しておけば拡張が発動する
BlankRect rect = new BlankRect(10,100,200,200);
this.GUIs.add(rect);
//RectTopFrame frame = new RectTopFrame(rect, 20);
//this.GUIs.add(frame);
MovalTopFrame frame = new MovalTopFrame(rect, 20);
this.GUIs.add(frame);
OffsetLabel offset = new OffsetLabel(rect,40,-30,100,20);
//offset.MarkTo = new PVector(100,100);
this.GUIs.add(offset);
}
TextBox
class TextBox extends RectGUIBase implements IKeyInputtable
{
String Text = "";
char keydata = ' ';
void keyPressed()
{
keydata = key;
Text += keydata;
}
void keyReleased()
{
}
@Override
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,w,h);
noFill();
stroke(0);
rect(x_,y_,w,h);
}
@Override
void draw()
{
this.draw(this.Origin.x, this.Origin.y);
}
TextBox(float x, float y, float w, float h)
{
this.Origin = new PVector(x,y);
this.w=w;
this.h=h;
}
}
それに伴って追加されたinterface
interface IKeyInputtable
{
void keyPressed();
void keyReleased();
}
それに伴って追加されたprocessingのイベントループ
void keyPressed()
{
for(int i = 0; i<this.GUIs.size(); i++)
{
GUI gui = this.GUIs.get(i);
if(gui instanceof IKeyInputtable)
{
((IKeyInputtable)gui).keyPressed();
}
}
}
void keyReleased()
{
for(int i = 0; i<this.GUIs.size(); i++)
{
GUI gui = this.GUIs.get(i);
if(gui instanceof IKeyInputtable)
{
((IKeyInputtable)gui).keyReleased();
}
}
}
TextBoxが矩形の左上を(0,0)として文字列を描画するため、適当に作ったフレームクラスが矩形の内側に入り込むと邪魔なので外側に拡張するようにする。
class RectTopFrame extends Frame implements IHit
{
String ID = "RectTopFrame";
RectGUIBase Target;
float Wide;
boolean Up = false;
boolean Hit(float x, float y)
{
if(Up){return UpHit(x,y);}
else{return DownHit(x,y);}
}
boolean UpHit(float x, float y)
{
if(Target.Origin.x<x&&x<Target.Origin.x+Target.w)
{
if(Target.Origin.y-Wide<y&&y<Target.Origin.y)
{
return true;
}
}
return false;
}
boolean DownHit(float x, float y)
{
if(Target.Origin.x<x&&x<Target.Origin.x+Target.w)
{
if(Target.Origin.y<y&&y<Target.Origin.y+Wide)
{
return true;
}
}
return false;
}
RectTopFrame(RectGUIBase target, float wide)
{
Target=target;
Wide=wide;
}
@Override
void draw()
{
if(!Up){rect(Target.Origin.x,Target.Origin.y,Target.w,this.Wide);}
else{rect(Target.Origin.x,Target.Origin.y,Target.w,-this.Wide);}
}
}
スライダーバー
最終形としてこんなものを作る。
このGUIはMin値とMax値の間にある、Knobの位置に応じたパラメータを出力できる。Knobを動かすと出力がMin-Max間で変化する。
これを実現するためにはKnob用のGUIが必要である。CircleGUIBaseはRectGUIBaseと似たような機能を持つ。ここでは半径情報と単位ラジアン情報を用いて円の近似を生成していることに注意を要する。要するに良く見ると多角形であることが分かる。
class CircleGUIBase extends GObject implements HasOrigin, IDrawableXY
{
PVector Center;
PVector GetOrigin()
{
return this.Center;
}
@Override
ArrayList<PVector> GetCoords()
{
this.Coordinates.clear();
this.Coordinates.add(this.Center);
return this.Coordinates;
}
float Radius;
float UnitRadian;
//円周
ArrayList<PVector> CalcuCircle(PVector center, float radius, float u_radian)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for(float radian = 0; radian<2*PI; radian+=u_radian)
{
float nx = center.x+radius*cos(radian);
float ny = center.y+radius*sin(radian);
ret.add(new PVector(nx,ny));
}
float nx = center.x+radius*cos(2*PI);
float ny = center.y+radius*sin(2*PI);
ret.add(new PVector(nx,ny));
return ret;
}
@Override
void draw(float x_,float y_)
{
stroke(0);
DrawLiner(this.CalcuCircle(new PVector(x_,y_),this.Radius,this.UnitRadian));
}
@Override
void draw()
{
this.draw(this.Center.x, this.Center.y);
}
@Override
boolean Hit(float x, float y)
{
float dx = x-this.Center.x;
float dy = y-this.Center.y;
if(dx*dx+dy*dy<this.Radius*this.Radius)
{
return true;
}
return false;
}
@Override
void Translate(PVector dv)
{
this.Center.add(dv);
}
}
CircleGUIBaseを継承して、なにもしないBlankCircle クラスを生成する。このクラスは描画能力を持ち、一応イベントに反応するための関数を持ってはいるが(GObjectとして)、関数の中で何も実行していないのでユーザーの入力には反応できない。このクラスはBlankRectクラスに相当する。
//Translate無し
class BlankCircle extends CircleGUIBase
{
String ID = "BlankCircle";
BlankCircle(float x, float y, float r_, float u_radian)
{
this.Center = new PVector(x,y);
this.Radius=r_;
this.UnitRadian=u_radian;
}
BlankCircle(PVector center, float r_, float u_radian)
{
this.Center = center.copy();
this.Radius=r_;
this.UnitRadian=u_radian;
}
}
このGUIをユーザー入力で動かせるようにする。GObjectの各種イベントハンドラを上書きする。
//動かすことができるCircleGUI
//他のGUIのつまみボタン的なものとして
class MovalCircle extends BlankCircle
{
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged() {if(pressed){this.Translate(dv);} }
void mouseReleased() { pressed = false; }
MovalCircle(float x, float y, float r_, float u_radian)
{
super(x,y,r_,u_radian);
}
MovalCircle(PVector center, float r_, float u_radian)
{
super(center,r_,u_radian);
}
}
Rect形状のKnobも同様にして作ることができる。ただし機能的には先に作ったラベルGUIとそこそこ被っている。これは思いつくままに適当に作っているから起こる現象である。
//動かすことができるCircleGUI
//他のGUIのつまみボタン的なものとして
//既にラベルと機能が被っている
class MovalRect extends BlankRect
{
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged() {if(pressed){this.Translate(dv);} }
void mouseReleased() { pressed = false; }
MovalRect(float x, float y, float w, float h)
{
super(x,y,w,h);
}
}
Knobを所有するための線分型のGUIが必要であろう。線分を表現するには始点と終点を示すベクトルが必要である。ここでは始点も終点もGUIを当てはめ、適当に動かせるようにした。
このことによりBarクラスは、ある種の子GUIを持った、親子型の構造になった言える。メインで回すGUIリストにはBarクラスしか登録しないため、子GUIの挙動は全てこのクラスで管理する必要がある。
メインのループではメインのGUIリストしか走査しないので、そのリストに登録されていないGUIは、いかにイベントに対応する能力を所持していたとしても、その機能を実行されない。
逆に、親GUIで子GUIの動作を記述しているにもかかわらず、子GUIをメインのGUIリストに登録してしまうと、イベント発生時にハンドラが二重実行される可能性がある。というかされる。
このBarクラスを座標的に動かすためには、
・1つのKnob、及び2つの始点終点を各々独立に動かす
・1つの線分、ないし2つの始点終点を同時に動かす
・全てのGUIを同時に動かす
などの選択肢があるが、ここでは『3つのKnobを各々独立に動かす』ことしか考慮に入れていない。
※そこそこ重要なこと
Knob移動時、最近接点を計算してKnobの座標を修正。Knobが必ず線分上に位置するようにした。
※細かいこと
矩形や円と違って、線分に関してはOriginが定義しにくい(もちろんできないことはない)。ゆえに矩形はLeftTopに相当するベクトル、円には中点に相当するベクトルを用意し、interface HasOrigin に関して修正を加えた。
以上のことから、BarクラスはHasOriginとITranslateを持たない(持たせることはできる)。RectGUIBaseとCircleGUIBaseはHasOriginとITranslateを持つ。
この辺の細かい所は適当に修正されることが十分にあり得る。
class Bar implements GUI, IHit, IClickable, IDraggable//ITranslate
{
//この3つのGUIはメインのリストには乗らない
//なのでこのGUIの管理は全てこのクラスで行わなければならない
MovalCircle Knob;
MovalCircle Start;
MovalCircle End;
PVector GetSV()
{
return this.Start.Center;
}
PVector GetEV()
{
return this.End.Center;
}
PVector GetLV()
{
return PVector.sub(GetEV(),GetSV());
}
PVector GetTV()
{
return PVector.sub(Knob.Center,GetSV());
}
//boolean pressed = false;
void mousePressed()
{
if(Knob.Hit(mouseX, mouseY)){Knob.pressed=true;}
else if(Start.Hit(mouseX, mouseY)){Start.pressed=true;}
else if(End.Hit(mouseX, mouseY)){End.pressed=true;}
}
void mouseDragged()
{
if(Knob.pressed){Knob.Translate(dv);}
else if(Start.pressed){Start.Translate(dv);}
else if(End.pressed){End.Translate(dv);}
Knob.Center = NearestOnSegment(this.GetSV(),GetEV(),Knob.Center).copy();
}
void mouseReleased()
{
Knob.pressed = false;
Start.pressed = false;
End.pressed = false;
}
@Override
boolean Hit(float x, float y)
{
if(Knob.Hit(x,y)){return true;}
else if(Start.Hit(x,y)){return true;}
else if(End.Hit(x,y)){return true;}
return false;
}
@Override
void draw()
{
this.Knob.draw();
this.Start.draw();
this.End.draw();
line(GetSV().x,GetSV().y,GetEV().x,GetEV().y);
}
Bar(PVector sv, PVector ev)
{
this.Start = new MovalCircle(sv,20,2*PI/36);
this.End = new MovalCircle(ev,20,2*PI/36);
this.Knob = new MovalCircle(new PVector((sv.x+ev.x)/2, (sv.y+ev.y)/2),20,2*PI/36);
}
}
これだけだと、ただ単に線分上を移動するKnobがあるだけである。現在の値を計算できるようにすると
class PBar extends Bar
{
float Min;
float Max;
float Value()
{
return Min+(Max-Min)*t();
}
float t()
{
return GetTV().mag()/GetLV().mag();
}
void SetKnob(float t)
{
PVector tv = PVector.add(this.GetSV(), GetLV().mult(t));
this.Knob.Center.x = tv.x;
this.Knob.Center.y = tv.y;
}
PBar(PVector sv, PVector ev, float min, float max, float t)
{
super(sv,ev);
Min = min;
Max = max;
SetKnob(t);
}
@Override
void draw()
{
this.Knob.draw();
this.Start.draw();
this.End.draw();
line(GetSV().x,GetSV().y,GetEV().x,GetEV().y);
fill(0);
text(str(Min), this.Start.Center.x, this.Start.Center.y);
text(str(Max), this.End.Center.x, this.End.Center.y);
text(str(Value()), this.Knob.Center.x, this.Knob.Center.y);
}
}
あとは適当な操作対象クラスを参照に持つようなGUIを作れば、Knobの動きに応じて対象クラスのパラメータを操作できる。
Rails
スライダーバーの話をもう少し抽象化する。例えばKnobが線分上ではなく円周上、あるいはArrayList<PVector>で表されるような折れ線、ないし閉曲線である場合を考える。
Knobが移動可能な線型及び曲線系の領域をRailsと称する。また、Knob的な可動GUIをTrackerと称する。
interface IRails
{
ArrayList<PVector> GetCoordinates();
PVector Nearest(PVector tracker);
}
円周上の場合。
Trakerは適当に動いた後、RailsのNearest関数を実行することで自分の位置を修正し、領域に拘束される。
この円周Railsは中点をドラッグすればRails自身が移動できる。ただしTrackerはおいてけぼりにされる。なぜならTrackerはRailsのことを知っているが、Railsは今現在Trackerのことを知らないからである。修正するには互いに参照を確保するか、別にUpdate関数を設けて逐次位置を修正するなどしなければならない。
//円周
class CircumferenceRails implements GUI, IRails, IHit, IClickable, IDraggable
{
PVector Center;
float CenterR = 10;
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.Center);
return ret;
}
PVector GetOrigin()
{
return this.Center;
}
float Radius;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircumference(this.Center, this.Radius, tracker);
return near;
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
//中心点に触ったか否か
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
CircumferenceRails(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
その上を移動するTracker
class Tracker extends MovalCircle
{
IRails Rails;
boolean pressed = false;
@Override
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
@Override
void mouseDragged()
{
if(pressed)
{
this.Translate(dv);
PVector near = Rails.Nearest(this.Center);
this.Center.x = near.x;
this.Center.y = near.y;
}
}
@Override
void mouseReleased() { pressed = false; }
Tracker(IRails rail)
{
super(rail.GetCoordinates().get(0));
this.Rails = rail;
}
}
円周に関する最近接点を求める場合
//円周上の最近接点
PVector NearestOnCircumference(PVector center, float radius, PVector v)
{
PVector toV = PVector.sub(v,center);
toV.normalize().mult(radius);
return toV.add(center);
}
円周上だけでなく、円の内部エリアも移動可能である場合
class CircleRails implements GUI, IRails, IHit, IClickable, IDraggable
{
PVector Center;
float CenterR = 10;
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.Center);
return ret;
}
PVector GetOrigin()
{
return this.Center;
}
float Radius;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircle(this.Center, this.Radius, tracker);
return near;
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
CircleRails(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
その判定
//円周上を含まないPointIn
boolean PointInCircle(PVector center, float radius, PVector v)
{
float dx = v.x-center.x;
float dy = v.y-center.y;
if(dx*dx+dy*dy<radius*radius){return true;}
return false;
}
//領域に対して最も近い点
PVector NearestOnCircle(PVector center, float radius, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(PointInCircle(center,radius,v)){return v;}
else{return NearestOnCircumference(center,radius,v);}
}
この方式はGUIのリストに適当に放り込んで回せばよい。
CircleRails rail = new CircleRails(new PVector(200,200),100);
this.GUIs.add(rail);
Tracker tracker = new Tracker(rail);
this.GUIs.add(tracker);
折れ線領域、あるいは閉多角形上の領域の場合
このGUIは頂点をドラッグすると部分的に動かせる。判定、あるいはDraw関数をちょちょいと書き換えると、最後の頂点と始点間が閉じたり閉じなかったりとなる。
class LinerRails implements GUI, IRails, IHit, IClickable, IDraggable
{
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
ArrayList<PVector> GetCoordinates()
{
return this.Coordinates;
}
float r = 10;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnSegments(this.Coordinates, tracker);
//PVector near = NearestOnSegmentsClose(this.Coordinates, tracker);
return near;
}
int current_vertex_index = -1;
void mousePressed()
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(v.x-r<mouseX&&mouseX<v.x+r)
{
if(v.y-r<mouseY&&mouseY<v.y+r)
{
current_vertex_index = i;
return;
}
}
}
}
void mouseDragged()
{
if(0<=current_vertex_index&¤t_vertex_index<this.Coordinates.size())
{
PVector v = this.Coordinates.get(current_vertex_index);
v.add(dv);
}
}
void mouseReleased()
{
current_vertex_index=-1;
}
boolean Hit(float x, float y)
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(v.x-r<mouseX&&mouseX<v.x+r)
{
if(v.y-r<mouseY&&mouseY<v.y+r)
{
return true;
}
}
}
return false;
}
void draw()
{
DrawPoints(this.r, this.Coordinates);
DrawLiner(this.Coordinates);
}
LinerRails(ArrayList<PVector> coordinates)
{
this.Coordinates = coordinates;
}
}
その判定
//閉じない
PVector NearestOnSegments(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
PVector ret = vectors.get(0);
for(int i = 0; i<vectors.size()-1; i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get(i+1);
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = near;
}
}
return ret;
}
//閉じる
PVector NearestOnSegmentsClose(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
PVector ret = vectors.get(0);
for(int i = 0; i<vectors.size(); i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get((i+1)%vectors.size());
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = near;
}
}
return ret;
}
凸も凹も含む多角形領域をRailsとする場合
class PolygonRails implements GUI, IRails, IHit, IClickable, IDraggable
{
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
ArrayList<PVector> GetCoordinates()
{
return this.Coordinates;
}
float r = 10;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnPolygon(this.Coordinates, tracker);
return near;
}
int current_vertex_index = -1;
void mousePressed()
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(v.x-r<mouseX&&mouseX<v.x+r)
{
if(v.y-r<mouseY&&mouseY<v.y+r)
{
current_vertex_index = i;
return;
}
}
}
}
void mouseDragged()
{
if(0<=current_vertex_index&¤t_vertex_index<this.Coordinates.size())
{
PVector v = this.Coordinates.get(current_vertex_index);
v.add(dv);
}
}
void mouseReleased()
{
current_vertex_index=-1;
}
boolean Hit(float x, float y)
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(v.x-r<mouseX&&mouseX<v.x+r)
{
if(v.y-r<mouseY&&mouseY<v.y+r)
{
return true;
}
}
}
return false;
}
void draw()
{
DrawPoints(this.r, this.Coordinates);
DrawLinerClose(this.Coordinates);
}
PolygonRails(ArrayList<PVector> coordinates)
{
this.Coordinates = coordinates;
}
}
凹も含む多角形の内外判定はこちらのサイトを参考としたもの
https://www.nttpc.co.jp/technology/number_algorithm.html
//領域に対して最も近い点
PVector NearestOnPolygon(ArrayList<PVector> vectors, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(CrossingNumber(vectors,v)){return v;}
else{return NearestOnSegmentsClose(vectors,v);}
}
//参考:https://www.nttpc.co.jp/technology/number_algorithm.html
boolean CrossingNumber(ArrayList<PVector> vertex, PVector p)
{
int count = 0;
for(int i = 0; i < vertex.size(); i++)
{
PVector sv = vertex.get(i);
PVector ev = vertex.get((i+1)%vertex.size());
if((sv.y <= p.y && p.y < ev.y)||(sv.y > p.y && p.y >= ev.y))
{
float t = (p.y - sv.y) / (ev.y - sv.y);
if(p.x < (sv.x + (t * (ev.x - sv.x))))
{
count++;
}
}//if
}//for
if(count%2==1){return true;}
return false;
}//CrossingNumber
カラーピッカー
これまでの応用で死ぬ程強引に作ったカラーピッカー。
class ColorPicker implements GUI, IHit, IClickable, IDraggable
{
TetragonRails Tetragon;
Tracker Tracker;
MovalCircle V3;
float vr = 20;
int pressed_index = -1;
void mousePressed()
{
if(Tracker.Hit(mouseX,mouseY))
{
Tracker.mousePressed();
pressed_index=0;
}
else if(V3.Hit(mouseX,mouseY))
{
V3.mousePressed();
pressed_index=1;
}
else if(Tetragon.Hit(mouseX,mouseY))
{
//tetragonはその内側に操作可能GUIが複数ある
Tetragon.mousePressed();
pressed_index=2;
}
}
void mouseDragged()
{
if(0<=pressed_index && pressed_index<3)
{
if(pressed_index==0)
{
//this.Tracker.Translate(dv);
this.Tracker.mouseDragged();
}
if(pressed_index==1)
{
//this.V3.Translate(dv);
this.V3.mouseDragged();
}
if(pressed_index==2)
{
this.Tetragon.mouseDragged();
}
}
}
void mouseReleased()
{
if(pressed_index==0)
{
this.Tracker.mouseReleased();
}
if(pressed_index==1)
{
this.V3.mouseReleased();
}
if(pressed_index==2)
{
this.Tetragon.mouseReleased();
}
}
boolean Hit(float x, float y)
{
if(this.Tetragon.Hit(x,y)){return true;}
if(this.V3.Hit(x,y)){return true;}
if(this.Tracker.Hit(x,y)){return true;}
return false;
}
color CurrentColor()
{
PVector toV = PVector.sub(Tracker.Center,Tetragon.LTV);//現在の色
PVector proj_vx = Projection(Tetragon.VX,toV);
PVector proj_vy = Projection(Tetragon.VY,toV);
PVector toV3 = PVector.sub(V3.Center,Tetragon.LTV);
float t_vx = proj_vx.mag()/Tetragon.VX.mag();
float t_vy = proj_vy.mag()/Tetragon.VY.mag();
float t_v3 = toV3.mag()/255;
if(1<t_v3){t_v3=1;}
return color(255*t_vx,255*t_vy,255*t_v3);
}
void draw()
{
background(CurrentColor());
stroke(0);
this.Tetragon.draw();
loadPixels();
for(int i = 0; i<height;i++)
{
for(int j = 0; j<width; j++)
{
PVector v = new PVector(j,i);
if(PointInTetragon(Tetragon.LTV,Tetragon.VX,Tetragon.VY, v))
{
//PVector toV = PVector.sub(Tracker.Center,Tetragon.LTV);//現在の色
PVector toV = PVector.sub(v,Tetragon.LTV);
PVector proj_vx = Projection(Tetragon.VX,toV);
PVector proj_vy = Projection(Tetragon.VY,toV);
PVector toV3 = PVector.sub(V3.Center,Tetragon.LTV);
float t_vx = proj_vx.mag()/Tetragon.VX.mag();
float t_vy = proj_vy.mag()/Tetragon.VY.mag();
float t_v3 = toV3.mag()/255;
if(1<t_v3){t_v3=1;}
pixels[i*width+j] = color(255*t_vx,255*t_vy,255*t_v3);
}
}
}
updatePixels();
line(this.Tetragon.LTV.x,this.Tetragon.LTV.y,this.V3.Center.x,this.V3.Center.y);
this.V3.draw();
this.Tracker.draw();
}
ColorPicker(PVector ltv, PVector vx, PVector vy)
{
Tetragon = new TetragonRails(ltv,vx,vy);
Tracker = new Tracker(Tetragon);
V3 = new MovalCircle(PVector.add(vx,vy),vr);
}
}
Tetragonが名称として適当かどうかは不明。起点とベクトル2本から定義されるが、動作によっては対辺の平行性などは失われる。
class TetragonRails implements GUI, IRails, IHit, IClickable, IDraggable
{
PVector LTV;
PVector VX;
PVector VY;
//vertex radius
float vr = 20;
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.LTV.copy());
ret.add(PVector.add(this.LTV, VX));
ret.add(PVector.add(this.LTV, VX).add(VY));
ret.add(PVector.add(this.LTV, VY));
return ret;
}
PVector GetOrigin()
{
return this.LTV;
}
//void draw(float x_,float y_)
//{
// stroke(0);
// DrawLinerClose(GetCoordinates());
//}
@Override
void draw()
{
DrawLinerClose(GetCoordinates());
DrawPoints(vr, GetCoordinates());
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnTetragon(this.LTV, this.VX, this.VY, tracker);
return near;
}
int pressed_index = -1;
void mousePressed()
{
for(int i = 0; i<GetCoordinates().size(); i++)
{
PVector v = GetCoordinates().get(i);
if(PointInCircle(v,vr,new PVector(mouseX,mouseY)))
{
pressed_index=i;
return;
}
}
}
void mouseDragged()
{
if(0<=pressed_index&&pressed_index<this.GetCoordinates().size())
{
if(pressed_index==0){this.LTV.add(dv);}
if(pressed_index==1){this.VX.add(dv);}
if(pressed_index==2)
{
this.VX.add(dv);
this.VY.add(dv);
}
if(pressed_index==3){this.VY.add(dv);}
}
}
void mouseReleased(){pressed_index=-1;}
boolean Hit(float x, float y)
{
for(PVector v : GetCoordinates())
{
if(PointInCircle(v,vr,new PVector(x,y)))
{
return true;
}
}
return false;
}
void Translate(PVector dv)
{
this.LTV.add(dv);
}
TetragonRails(PVector ltv, PVector vx, PVector vy)
{
this.LTV = ltv.copy();
this.VX = vx.copy();
this.VY = vy.copy();
}
}
Binder
Railsの逆、勝手に動くTrackerを引きずり戻すもの。
//Railの逆
interface IBinder extends IUpdate
{
}
class CircumferenceBinder implements GUI, IBinder, IHit, IClickable, IDraggable
{
ArrayList<Tracker> Trackers = new ArrayList<Tracker>();
PVector Center;
float CenterR = 10;
PVector TrackerStartPoint()
{
return PVector.add(this.Center,new PVector(CenterR,0));
}
PVector GetOrigin()
{
return this.Center;
}
float Radius;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircumference(this.Center, this.Radius, tracker);
return near;
}
//強制的に所属するTrackerを軌道上に収容する
void Update()
{
for(Tracker tracker : this.Trackers)
{
PVector near = Nearest(tracker.GetOrigin());
tracker.Move(near.x,near.y);
}
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
//中心点に触ったか否か
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
CircumferenceBinder(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
ただしいくらかの細かい修正は必要となる。UpdateはprocessingのDraw中に実行される。TrackerはRailsがnullでも動くように修正せなければならない。
OriginはNearestの計算用としての意味合いを強く厳格に、RailsはTrackerの初期位置を設定、等。それゆえ、このあたりからコピペだとエラーでると思われる。
interface IMove
{
void Move(float x, float y);
}
interface IUpdate
{
void Update();
}
使い方。
//Binder型:両者ともメインのGUIのリストへの登録が必要だし
//BinderへのTrackerの登録も必要
CircumferenceBinder binder = new CircumferenceBinder(new PVector(250,250), 100);
this.GUIs.add(binder);
Tracker tracker = new Tracker(new PVector(250,250), 20);
this.GUIs.add(tracker);
binder.Trackers.add(tracker);
コピペ用コード
以下、コピペのためにこれまでの物を特に精査せずのせることとする。
import java.util.Arrays;
int window_width = 500;
int window_height = 500;
void settings()
{
size(window_width, window_height);
}
PVector pprev;
PVector prev;
PVector current;
PVector dv = new PVector(0,0);
ArrayList<IAObject> GUIs = new ArrayList<IAObject>();
IAObject CurrentGUI;
int current_gui_index = -1;
void setup()
{
//FunctionButton button = new FunctionButton(10,10,100,20,"test");
//this.GUIs.add(button);
////同じリストに追加しておけば拡張が発動する
//BlankRect rect = new BlankRect(10,100,200,200);
//this.GUIs.add(rect);
//RectTopFrame frame = new RectTopFrame(rect, 20);
//this.GUIs.add(frame);
////MovalTopFrame frame = new MovalTopFrame(rect, 20);
////this.GUIs.add(frame);
//OffsetLabel offset = new OffsetLabel(rect,40,-30,100,20);
////offset.MarkTo = new PVector(100,100);
//this.GUIs.add(offset);
//TextBox textbox = new TextBox(10,100,200,200);
//this.GUIs.add(textbox);
//MovalTopFrame frame = new MovalTopFrame(textbox, 20);
//frame.Up=true;
//this.GUIs.add(frame);
//描画のみ、特に役に立たず
//BlankCircle circle = new BlankCircle(100,100,20,2*PI/36);
//this.GUIs.add(circle);
//MovalCircle circle = new MovalCircle(100,100,20,2*PI/36);
//this.GUIs.add(circle);
//MovalRect rect = new MovalRect(10,100,200,200);
//this.GUIs.add(rect);
//Bar bar = new Bar(new PVector(100,100), new PVector(300,100));
//this.GUIs.add(bar);
//PBar pbar = new PBar(new PVector(100,100), new PVector(300,100), 0, 100, 0.5);
//this.GUIs.add(pbar);
//ArrayList<PVector> vs = new ArrayList<PVector>();
//vs.add(new PVector(100,100));
//vs.add(new PVector(200,240));
//vs.add(new PVector(300,100));
//vs = OSpline(vs,2,0.1);
//ArrayList<PVector> vs = new ArrayList<PVector>();
//vs.add(new PVector(100,100));
//vs.add(new PVector(200,200));
//vs.add(new PVector(300,100));
//vs.add(new PVector(300,300));
//vs.add(new PVector(200,240));
//vs.add(new PVector(100,300));
//LinerRails rail = new LinerRails(vs);
//this.GUIs.add(rail);
//Tracker tracker = new Tracker(rail);
//this.GUIs.add(tracker);
//MovalPolygon poly = new MovalPolygon(vs);
//this.GUIs.add(poly);
//CircumferenceRails rail = new CircumferenceRails(new PVector(200,200),100);
//this.GUIs.add(rail);
//Tracker tracker = new Tracker(rail);
//this.GUIs.add(tracker);
//CircleRails rail = new CircleRails(new PVector(200,200),100);
//this.GUIs.add(rail);
//Tracker tracker = new Tracker(rail);
//this.GUIs.add(tracker);
//PolygonRails poly = new PolygonRails(vs);
//this.GUIs.add(poly);
//Tracker tracker = new Tracker(poly);
//this.GUIs.add(tracker);
//RectRails rect = new RectRails(new PVector(200,200),100,100);
//this.GUIs.add(rect);
//Tracker tracker = new Tracker(rect);
//this.GUIs.add(tracker);
//TetragonRails tetra = new TetragonRails(new PVector(200,200),new PVector(200,20),new PVector(20,200));
//this.GUIs.add(tetra);
//Tracker tracker = new Tracker(tetra);
//this.GUIs.add(tracker);
//ColorPicker picker = new ColorPicker(new PVector(100,100),new PVector(200,20),new PVector(20,200));
//this.GUIs.add(picker);
////Binder型:両者ともメインのGUIのリストへの登録が必要だし
////BinderへのTrackerの登録も必要
//CircumferenceBinder binder = new CircumferenceBinder(new PVector(250,250), 100);
//this.GUIs.add(binder);
//Tracker tracker1 = new Tracker(new PVector(100,100), 20);
//this.GUIs.add(tracker1);
//Tracker tracker2 = new Tracker(new PVector(200,100), 20);
//this.GUIs.add(tracker2);
//Tracker tracker3 = new Tracker(new PVector(250,250), 20);
//this.GUIs.add(tracker3);
//binder.Trackers.add(tracker1);
//binder.Trackers.add(tracker2);
//binder.Trackers.add(tracker3);
//MovalCircle c = new MovalCircle(new PVector(250,250), 20);
//this.GUIs.add(c);
//VEngine transformer = new VEngine(c, new PVector(20,20));
//this.GUIs.add(transformer);
//OutOfField outoffield = new OutOfField(c, transformer);
//this.GUIs.add(outoffield);
//自走ピッカー
//ColorPicker picker = new ColorPicker(new PVector(100,100),new PVector(200,20),new PVector(20,200));
//this.GUIs.add(picker);
//this.GUIs.add(picker.Tetragon);//Binder
//VEngine transformer = new VEngine(picker.Tracker, new PVector(5,5));
//this.GUIs.add(transformer);
//OutOfField outoffield = new OutOfField(picker.Tracker, transformer);
//this.GUIs.add(outoffield);
}
void draw()
{
background(255);
for(IAObject gui : this.GUIs)
{
if(gui instanceof IUpdate)
{
((IUpdate)gui).Update();
}
if(gui instanceof IDrawable)
{
((IDrawable)gui).draw();
}
}
}
boolean flag = false;
void mousePressed()
{
pprev = new PVector(mouseX,mouseY);
prev = new PVector(mouseX,mouseY);
current = new PVector(mouseX,mouseY);
for(int i = 0; i<this.GUIs.size(); i++)
{
IAObject gui = this.GUIs.get(i);
if(gui instanceof IHit)
{
if(((IHit)gui).Hit(mouseX,mouseY))
{
current_gui_index = i;
CurrentGUI = gui;
}
}
if(gui instanceof IClickable)
{
((IClickable)gui).mousePressed();
}
}
}
void mouseDragged()
{
pprev = prev.copy();
prev = current.copy();
current.x = mouseX;
current.y = mouseY;
dv = PVector.sub(current,prev);
if(CurrentGUI!=null)
{
if(CurrentGUI instanceof IDraggable)
{
((IDraggable)CurrentGUI).mouseDragged();
}
}
}
void mouseReleased()
{
pprev = null;
prev = null;
current = null;
current_gui_index=-1;
for(int i = 0; i<this.GUIs.size(); i++)
{
IAObject gui = this.GUIs.get(i);
if(gui instanceof IClickable)
{
((IClickable)gui).mouseReleased();
}
}
}
void keyPressed()
{
for(int i = 0; i<this.GUIs.size(); i++)
{
IAObject gui = this.GUIs.get(i);
if(gui instanceof IKeyInputtable)
{
((IKeyInputtable)gui).keyPressed();
}
}
}
void keyReleased()
{
for(int i = 0; i<this.GUIs.size(); i++)
{
IAObject gui = this.GUIs.get(i);
if(gui instanceof IKeyInputtable)
{
((IKeyInputtable)gui).keyReleased();
}
}
}
interface IAObject
{
//void draw();
//void draw(float x, float y);
}
interface IGObject extends IAObject, IDrawable, IHit, ITranslate, IClickable, IDraggable
{
}
interface IDrawable
{
void draw();
}
interface IDrawableXY
{
void draw(float x, float y);
}
interface IHit
{
boolean Hit(float x, float y);
}
interface IMove
{
void Move(float x, float y);
}
interface ITranslate
{
void Translate(PVector dv);
}
interface HasOrigin
{
PVector GetOrigin();
}
interface HasParent
{
IGObject GetParent();
}
interface IUpdate
{
void Update();
}
interface IClickable
{
void mousePressed();
void mouseReleased();
}
interface IDraggable
{
void mouseDragged();
}
interface IKeyInputtable
{
void keyPressed();
void keyReleased();
}
interface ITransformer extends IUpdate
{
}
interface IModifier extends IUpdate
{
}
interface HasVelocity
{
PVector GetVelocity();
void SetVelocity(PVector v);
}
interface ILineSegmentGeometry
{
PVector GetSV();
PVector GetEV();
}
interface IRayGeometry
{
PVector GetSV();
PVector GetLV();
}
interface ICircleGeometry
{
PVector GetCenter();
float GetRadius();
}
boolean CircleHit(ICircleGeometry circle, float x, float y)
{
return PointInCircle(circle.GetCenter(), circle.GetRadius(), new PVector(x,y));
//float dx = x-circle.GetCenter().x;
//float dy = y-circle.GetCenter().y;
//if(dx*dx+dy*dy<circle.GetRadius()*circle.GetRadius())
//{
// return true;
//}
//return false;
}
interface IEllipseGeometry
{
PVector GetCenter();
float GetARadius();//長径
float GetBRadius();//短径
}
interface IRectGeometry
{
PVector GetLTV();
float GetWidth();
float GetHeight();
}
boolean RectHit(IRectGeometry rect, float x, float y)
{
if(rect.GetLTV().x<x&&x<rect.GetLTV().x+rect.GetWidth())
{
if(rect.GetLTV().y<y&&y<rect.GetLTV().y+rect.GetHeight())
{
return true;
}
}
return false;
}
interface IPolygonGeometry
{
ArrayList<PVector> GetCoordinates();
}
class ColorPicker implements IAObject, IDrawable, IHit, IClickable, IDraggable
{
TetragonBinder Tetragon;
//TetragonRails Tetragon;
Tracker Tracker;
MovalCircle V3;
float vr = 20;
int pressed_index = -1;
void mousePressed()
{
if(Tracker.Hit(mouseX,mouseY))
{
Tracker.mousePressed();
pressed_index=0;
}
else if(V3.Hit(mouseX,mouseY))
{
V3.mousePressed();
pressed_index=1;
}
else if(Tetragon.Hit(mouseX,mouseY))
{
//tetragonはその内側に操作可能GUIが複数ある
Tetragon.mousePressed();
pressed_index=2;
}
}
void mouseDragged()
{
if(0<=pressed_index && pressed_index<3)
{
if(pressed_index==0)
{
//this.Tracker.Translate(dv);
this.Tracker.mouseDragged();
}
if(pressed_index==1)
{
//this.V3.Translate(dv);
this.V3.mouseDragged();
}
if(pressed_index==2)
{
this.Tetragon.mouseDragged();
}
}
}
void mouseReleased()
{
if(pressed_index==0)
{
this.Tracker.mouseReleased();
}
if(pressed_index==1)
{
this.V3.mouseReleased();
}
if(pressed_index==2)
{
this.Tetragon.mouseReleased();
}
}
boolean Hit(float x, float y)
{
if(this.Tetragon.Hit(x,y)){return true;}
if(this.V3.Hit(x,y)){return true;}
if(this.Tracker.Hit(x,y)){return true;}
return false;
}
color CurrentColor()
{
PVector toV = PVector.sub(Tracker.Center,Tetragon.LTV);//現在の色
PVector proj_vx = Projection(Tetragon.VX,toV);
PVector proj_vy = Projection(Tetragon.VY,toV);
PVector toV3 = PVector.sub(V3.Center,Tetragon.LTV);
float t_vx = proj_vx.mag()/Tetragon.VX.mag();
float t_vy = proj_vy.mag()/Tetragon.VY.mag();
float t_v3 = toV3.mag()/255;
if(1<t_v3){t_v3=1;}
return color(255*t_vx,255*t_vy,255*t_v3);
}
void draw()
{
background(CurrentColor());
stroke(0);
this.Tetragon.draw();
loadPixels();
for(int i = 0; i<height;i++)
{
for(int j = 0; j<width; j++)
{
PVector v = new PVector(j,i);
if(PointInTetragon(Tetragon.LTV,Tetragon.VX,Tetragon.VY, v))
{
//PVector toV = PVector.sub(Tracker.Center,Tetragon.LTV);//現在の色
PVector toV = PVector.sub(v,Tetragon.LTV);
PVector proj_vx = Projection(Tetragon.VX,toV);
PVector proj_vy = Projection(Tetragon.VY,toV);
PVector toV3 = PVector.sub(V3.Center,Tetragon.LTV);
float t_vx = proj_vx.mag()/Tetragon.VX.mag();
float t_vy = proj_vy.mag()/Tetragon.VY.mag();
float t_v3 = toV3.mag()/255;
if(1<t_v3){t_v3=1;}
pixels[i*width+j] = color(255*t_vx,255*t_vy,255*t_v3);
}
}
}
updatePixels();
line(this.Tetragon.LTV.x,this.Tetragon.LTV.y,this.V3.Center.x,this.V3.Center.y);
this.V3.draw();
this.Tracker.draw();
}
ColorPicker(PVector ltv, PVector vx, PVector vy)
{
//Railsの場合
//Tetragon = new TetragonRails(ltv,vx,vy);
//Tracker = new Tracker(Tetragon);
//V3 = new MovalCircle(PVector.add(vx,vy),vr);
//Binderの場合
Tetragon = new TetragonBinder(ltv,vx,vy);
Tracker = new Tracker(ltv.copy(), 20);
Tetragon.Trackers.add(Tracker);
V3 = new MovalCircle(PVector.add(vx,vy),vr);
}
}
//GUI Base
class GObject implements IGObject //IAObject, IDrawable, IHit, ITranslate, IClickable, IDraggable
{
boolean Hit(float x, float y){return false;}
void Translate(PVector dv){}
void mousePressed(){}
void mouseDragged(){}
void mouseReleased(){}
void draw(){}
//他から描画位置を指定される場合
//つまり実際の位置と描画位置がズレる可能性がある場合に使う
//void draw(float x, float y){}
}//GUIBase
//+幾何情報
class PolygonGUIBase extends GObject implements IPolygonGeometry, HasOrigin, IDrawableXY
{
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
ArrayList<PVector> GetCoordinates()
{
return this.Coordinates;
}
PVector GetOrigin(){return this.Coordinates.get(0);}
void draw(float x_,float y_)
{
stroke(0);
beginShape();
for(PVector v : this.Coordinates)
{
vertex(x_+v.x,y_+v.y);
}
endShape(CLOSE);
}
@Override
void draw(){this.draw(0,0);}
@Override
boolean Hit(float x, float y)
{
//return PointInPolygon(this.Coordinates, new PVector(x,y));
return CrossingNumber(this.Coordinates, new PVector(x,y));
}
@Override
void Translate(PVector dv)
{
for(PVector v : this.Coordinates){v.add(dv);}
}
}
//+幾何
class RectGUIBase extends GObject implements IRectGeometry, HasOrigin, IDrawableXY
{
PVector LTV;
PVector GetLTV(){return this.LTV;}
float Width;
float GetWidth(){return this.Width;}
float Height;
float GetHeight(){return this.Height;}
PVector GetOrigin()
{
return this.LTV;
}
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.clear();
ret.add(new PVector(this.LTV.x, this.LTV.y));
ret.add(new PVector(this.LTV.x+Width, this.LTV.y));
ret.add(new PVector(this.LTV.x+Width, this.LTV.y+Height));
ret.add(new PVector(this.LTV.x, this.LTV.y+Height));
return ret;
}
void draw(float x_,float y_)
{
stroke(0);
rect(x_,y_,Width,Height);
}
@Override
void draw()
{
this.draw(this.LTV.x, this.LTV.y);
}
@Override
boolean Hit(float x, float y)
{
return RectHit(this,x,y);
}
@Override
void Translate(PVector dv)
{
this.LTV.add(dv);
}
}
class CircleGUIBase extends GObject implements ICircleGeometry, HasOrigin, IDrawableXY
{
String ID = "CircleGUIBase";
PVector Center;
PVector GetCenter(){return this.Center;}
float Radius;
float GetRadius(){return this.Radius;}
float UnitRadian;
PVector GetOrigin()
{
return this.Center;
}
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.Center);
return ret;
}
//円周
ArrayList<PVector> CalcuCircle(PVector center, float radius, float u_radian)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for(float radian = 0; radian<2*PI; radian+=u_radian)
{
float nx = center.x+radius*cos(radian);
float ny = center.y+radius*sin(radian);
ret.add(new PVector(nx,ny));
}
float nx = center.x+radius*cos(2*PI);
float ny = center.y+radius*sin(2*PI);
ret.add(new PVector(nx,ny));
return ret;
}
void draw(float x_,float y_)
{
stroke(0);
DrawLiner(this.CalcuCircle(new PVector(x_,y_),this.Radius,this.UnitRadian));
}
@Override
void draw()
{
this.draw(this.Center.x, this.Center.y);
}
@Override
boolean Hit(float x, float y)
{
return CircleHit(this,x,y);
}
@Override
void Translate(PVector dv)
{
this.Center.add(dv);
}
}
//Translate無し,全てのイベントハンドラが中身なし、Frameなんかを装着しなければ動かない
class BlankPolygon extends PolygonGUIBase
{
String ID = "BlankPolygon";
//コンストラクタ
BlankPolygon(PVector... vectors)
{
for(PVector v : vectors)
{
this.Coordinates.add(v.copy());
}
}
BlankPolygon(ArrayList<PVector> vectors)
{
for(PVector v : vectors)
{
this.Coordinates.add(v.copy());
}
}
}
//Translate無し,全てのイベントハンドラが中身なし、Frameなんかを装着しなければ動かない
class BlankRect extends RectGUIBase
{
String ID = "BlankRect";
BlankRect(float x, float y, float w, float h)
{
this.LTV = new PVector(x,y);
this.Width=w;
this.Height=h;
}
}
//Translate無し,全てのイベントハンドラが中身なし、Frameなんかを装着しなければ動かない
class BlankCircle extends CircleGUIBase
{
String ID = "BlankCircle";
BlankCircle(float x, float y, float r_, float u_radian)
{
this.Center = new PVector(x,y);
this.Radius=r_;
this.UnitRadian=u_radian;
}
BlankCircle(PVector center, float r_, float u_radian)
{
this.Center = center.copy();
this.Radius=r_;
this.UnitRadian=u_radian;
}
BlankCircle(PVector center, float r_)
{
this.Center = center.copy();
this.Radius=r_;
this.UnitRadian=2*PI/36.0;
}
BlankCircle(PVector center)
{
this.Center = center.copy();
this.Radius=20;
this.UnitRadian=2*PI/36.0;
}
}
//動かすことができるCircleGUI
//各種イベントを実装する
class MovalPolygon extends BlankPolygon
{
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged() {if(pressed){this.Translate(dv);} }
void mouseReleased() { pressed = false; }
MovalPolygon(PVector... vectors)
{
super(vectors);
}
MovalPolygon(ArrayList<PVector> vectors)
{
super(vectors);
}
}
//動かすことができるCircleGUI
//他のGUIのつまみボタン的なものとして
//既にラベルと機能が被っている
class MovalRect extends BlankRect
{
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged() {if(pressed){this.Translate(dv);} }
void mouseReleased() { pressed = false; }
MovalRect(float x, float y, float w, float h)
{
super(x,y,w,h);
}
}
//動かすことができるCircleGUI
//他のGUIのつまみボタン的なものとして
class MovalCircle extends BlankCircle
{
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged() {if(pressed){this.Translate(dv);} }
void mouseReleased() { pressed = false; }
MovalCircle(float x, float y, float r_, float u_radian)
{
super(x,y,r_,u_radian);
}
MovalCircle(PVector center, float r_, float u_radian)
{
super(center,r_,u_radian);
}
MovalCircle(PVector center, float r_)
{
super(center,r_);
}
MovalCircle(PVector center)
{
super(center);
}
}
interface ITracker extends HasOrigin, IMove
{
//最近接点を求められるため、代表する唯一の座標が必要
}
class Tracker extends MovalCircle implements ITracker
{
IRails Rails;
void Move(float x, float y)
{
this.Center.x = x;
this.Center.y = y;
}
boolean pressed = false;
@Override
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
@Override
void mouseDragged()
{
if(pressed)
{
this.Translate(dv);
if(Rails != null)
{
PVector near = Rails.Nearest(this.Center);
this.Center.x = near.x;
this.Center.y = near.y;
}
}
}
@Override
void mouseReleased() { pressed = false; }
Tracker(PVector center, float r_)
{
super(center,r_);
}
Tracker(IRails rail)
{
super(rail.TrackerStartPoint());
this.Rails = rail;
}
}
interface IRails
{
//Trackerの初期位置を指定する
PVector TrackerStartPoint();
//自身に対するTracker『の』最近接点を求める
PVector Nearest(PVector tracker);
}
//Railの逆
interface IBinder extends IUpdate
{
}
class LineBinder implements IAObject, IBinder, ILineSegmentGeometry, IDrawable, IHit, IClickable, IDraggable
{
ArrayList<Tracker> Trackers = new ArrayList<Tracker>();
PVector SV;
PVector GetSV(){return this.SV;}
PVector EV;
PVector GetEV(){return this.EV;}
float r = 20;
PVector TrackerStartPoint()
{
return this.SV;
}
//強制的に所属するTrackerを軌道上に収容する
void Update()
{
Bind();
}
void Bind()
{
for(Tracker tracker : this.Trackers)
{
PVector near = Nearest(tracker.GetOrigin());
tracker.Move(near.x,near.y);
}
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnSegment(SV, EV, tracker);
return near;
}
int pressed_index = -1;
void mousePressed()
{
if(PointInCircle(SV,r, new PVector(mouseX, mouseY))){pressed_index=0;}
else if(PointInCircle(EV,r, new PVector(mouseX, mouseY))){pressed_index=1;}
}
void mouseDragged()
{
if(0==pressed_index){this.SV.add(dv);}
else if(1==pressed_index){this.EV.add(dv);}
}
void mouseReleased(){pressed_index=-1;}
boolean Hit(float x, float y)
{
if(PointInCircle(SV,r, new PVector(mouseX, mouseY))){return true;}
else if(PointInCircle(EV,r, new PVector(mouseX, mouseY))){return true;}
return false;
}
void draw()
{
line(SV.x,SV.y,EV.x,EV.y);
}
void Translate(PVector dv)
{
this.SV.add(dv);
this.EV.add(dv);
}
LineBinder(PVector sv, PVector ev)
{
this.SV = sv.copy();
this.EV = ev.copy();
}
}
class LineRails implements IAObject, IRails, ILineSegmentGeometry, IDrawable, IHit, IClickable, IDraggable
{
PVector SV;
PVector GetSV(){return this.SV;}
PVector EV;
PVector GetEV(){return this.EV;}
float r = 20;
PVector TrackerStartPoint()
{
return this.SV;
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnSegment(SV, EV, tracker);
return near;
}
int pressed_index = -1;
void mousePressed()
{
if(PointInCircle(SV,r, new PVector(mouseX, mouseY))){pressed_index=0;}
else if(PointInCircle(EV,r, new PVector(mouseX, mouseY))){pressed_index=1;}
}
void mouseDragged()
{
if(0==pressed_index){this.SV.add(dv);}
else if(1==pressed_index){this.EV.add(dv);}
}
void mouseReleased(){pressed_index=-1;}
boolean Hit(float x, float y)
{
if(PointInCircle(SV,r, new PVector(mouseX, mouseY))){return true;}
else if(PointInCircle(EV,r, new PVector(mouseX, mouseY))){return true;}
return false;
}
void draw()
{
line(SV.x,SV.y,EV.x,EV.y);
}
void Translate(PVector dv)
{
this.SV.add(dv);
this.EV.add(dv);
}
LineRails(PVector sv, PVector ev)
{
this.SV = sv.copy();
this.EV = ev.copy();
}
}
//trackerの追跡対象
//tracker:ユーザーが動かすやつ
//tracer:自分で動くやつ
//折れ線、多角形
class LinerRails implements IAObject, IRails, IDrawable, IHit, IClickable, IDraggable
{
PVector TrackerStartPoint()
{
return this.Coordinates.get(0);
}
//折れ線、多角形
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
//ArrayList<PVector> GetCoordinates()
//{
// return this.Coordinates;
//}
float r = 10;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnSegments(this.Coordinates, tracker);
//PVector near = NearestOnSegmentsClose(this.Coordinates, tracker);
return near;
}
int current_vertex_index = -1;
void mousePressed()
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(PointInCircle(v,r,new PVector(mouseX,mouseY)))
{
current_vertex_index = i;
return;
}
}
}
void mouseDragged()
{
if(0<=current_vertex_index&¤t_vertex_index<this.Coordinates.size())
{
PVector v = this.Coordinates.get(current_vertex_index);
v.add(dv);
}
}
void mouseReleased(){current_vertex_index=-1;}
boolean Hit(float x, float y)
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(PointInCircle(v,r,new PVector(mouseX,mouseY)))
{
return true;
}
}
return false;
}
void draw()
{
DrawPoints(this.r, this.Coordinates);
DrawLiner(this.Coordinates);
}
LinerRails(ArrayList<PVector> coordinates)
{
this.Coordinates = coordinates;
}
}
class PolygonRails implements IAObject, IRails, IDrawable, IHit, IClickable, IDraggable
{
PVector TrackerStartPoint()
{
return this.Coordinates.get(0);
}
ArrayList<PVector> Coordinates = new ArrayList<PVector>();
//ArrayList<PVector> GetCoordinates()
//{
// return this.Coordinates;
//}
float r = 10;
PVector Nearest(PVector tracker)
{
PVector near = NearestOnPolygon(this.Coordinates, tracker);
return near;
}
int current_vertex_index = -1;
void mousePressed()
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(PointInCircle(v,r,new PVector(mouseX,mouseY)))
{
current_vertex_index = i;
return;
}
}
}
void mouseDragged()
{
if(0<=current_vertex_index&¤t_vertex_index<this.Coordinates.size())
{
PVector v = this.Coordinates.get(current_vertex_index);
v.add(dv);
}
}
void mouseReleased(){current_vertex_index=-1;}
boolean Hit(float x, float y)
{
for(int i = 0; i<Coordinates.size(); i++)
{
PVector v = Coordinates.get(i);
if(PointInCircle(v,r,new PVector(mouseX,mouseY)))
{
return true;
}
}
return false;
}
void draw()
{
DrawPoints(this.r, this.Coordinates);
DrawLinerClose(this.Coordinates);
}
PolygonRails(ArrayList<PVector> coordinates)
{
this.Coordinates = coordinates;
}
}
//円周
class CircumferenceBinder implements IAObject, IBinder, ICircleGeometry, IDrawable, IHit, IClickable, IDraggable
{
ArrayList<Tracker> Trackers = new ArrayList<Tracker>();
PVector Center;
PVector GetCenter(){return this.Center;}
float CenterR = 10;
float Radius;
float GetRadius(){return this.Radius;}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircumference(this.Center, this.Radius, tracker);
return near;
}
//強制的に所属するTrackerを軌道上に収容する
void Update()
{
Bind();
//OrderBind();
}
void Bind()
{
for(Tracker tracker : this.Trackers)
{
PVector near = Nearest(tracker.GetOrigin());
tracker.Move(near.x,near.y);
}
}
void OrderBind()
{
if(this.Trackers.size()<2)
{
Bind();
}
else
{
for(int i = 0; i<this.Trackers.size(); i++)
{
Tracker tracker = Trackers.get(i);
Tracker prev= Trackers.get((i-1+Trackers.size())%Trackers.size());
Tracker next= Trackers.get((i+1)%Trackers.size());
PVector near = Nearest(tracker.GetOrigin());
float radian = Get_radian(this.Center, this.Radius, near);
float radian_prev = Get_radian(this.Center, this.Radius, prev.GetOrigin());
float radian_next = Get_radian(this.Center, this.Radius, next.GetOrigin());
////clamp
//if(radian<radian_prev)
//{
// radian=radian_prev;
//}
//if(radian_next<radian)
//{
// radian=radian_next;
//}
//errが大の方が押せちゃう
//float errn = 0.1;
//float errp = 0.08;
if(i==0)
{
if(radian<-PI+0.1){radian=-PI+0.1;}
if(radian_next<radian){radian=radian_next;}
}
else if(i==this.Trackers.size()-1)
{
if(radian<radian_prev){radian=radian_prev;}
if(radian>PI-0.1){radian=PI-0.1;}
}
else
{
if(radian<radian_prev){radian=radian_prev;}
if(radian_next<radian){radian=radian_next;}
}
near = Set_radian(this.Center, this.Radius, radian);
tracker.Move(near.x,near.y);
}
}
}
//Binder自身の動作
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
//Binder自身の動作
//中心点に触ったか否か
boolean Hit(float x, float y)
{
return CircleHit(this,x,y);
//return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
//Binder自身の描画
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
//デバッグ用、Trackerの描画
//DrawTrackerIndex(this.Trackers);
}
//コンストラクタ
CircumferenceBinder(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
//円周
class CircumferenceRails implements IAObject, IRails, ICircleGeometry, IDrawable, IHit, IClickable, IDraggable
{
PVector Center;
PVector GetCenter(){return this.Center;}
float CenterR = 10;
float Radius;
float GetRadius(){return this.Radius;}
PVector TrackerStartPoint()
{
return PVector.add(this.Center,new PVector(CenterR,0));
}
PVector GetOrigin()
{
return this.Center;
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircumference(this.Center, this.Radius, tracker);
return near;
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
//中心点に触ったか否か
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
//コンストラクタ
CircumferenceRails(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
class CircleBinder implements IAObject, IBinder, ICircleGeometry, IDrawable, IHit, IClickable, IDraggable
{
ArrayList<Tracker> Trackers = new ArrayList<Tracker>();
PVector Center;
PVector GetCenter(){return this.Center;}
float CenterR = 10;
float Radius;
float GetRadius(){return this.Radius;}
//強制的に所属するTrackerを軌道上に収容する
void Update()
{
Bind();
}
void Bind()
{
for(Tracker tracker : this.Trackers)
{
PVector near = Nearest(tracker.GetOrigin());
tracker.Move(near.x,near.y);
}
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircle(this.Center, this.Radius, tracker);
return near;
}
//Binder自身の動作
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
//Binder自身の描画
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
CircleBinder(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
class CircleRails implements IAObject, IRails, ICircleGeometry, IDrawable, IHit, IClickable, IDraggable
{
PVector Center;
PVector GetCenter(){return this.Center;}
float CenterR = 10;
float Radius;
float GetRadius(){return this.Radius;}
PVector TrackerStartPoint()
{
return this.Center;
}
PVector GetOrigin()
{
return this.Center;
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnCircle(this.Center, this.Radius, tracker);
return near;
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.Center.add(dv);}}
void mouseReleased(){pressed=false;}
boolean Hit(float x, float y)
{
return PointInCircle(this.Center, this.CenterR, new PVector(x,y));
}
void draw()
{
stroke(0);
circle(this.Center.x,this.Center.y, this.Radius*2);
DrawPoints(this.CenterR, this.Center);
}
CircleRails(PVector center, float radius)
{
this.Center = center.copy();
this.Radius = radius;
}
}
class RectRails implements IAObject, IRails, IRectGeometry, IDrawable, IHit, IClickable, IDraggable
{
PVector LTV;
PVector GetLTV(){return this.LTV;}
float ltv_r=20;
float Width;
float GetWidth(){return this.Width;}
float Height;
float GetHeight(){return this.Height;}
PVector GetOrigin()
{
return this.LTV;
}
PVector TrackerStartPoint()
{
return this.LTV;
}
void draw(float x_,float y_)
{
stroke(0);
rect(x_,y_,Width,Height);
}
void draw()
{
this.draw(this.LTV.x, this.LTV.y);
DrawPoints(ltv_r, this.LTV);
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnRect(this.LTV, this.Width, this.Height, tracker);
return near;
}
boolean pressed = false;
void mousePressed(){if(Hit(mouseX,mouseY)){pressed=true;}}
void mouseDragged(){if(pressed){this.LTV.add(dv);}}
void mouseReleased(){pressed=false;}
boolean Hit(float x, float y)
{
//LTVのみ判定
return PointInCircle(this.LTV,this.ltv_r,new PVector(x,y));
//return PointInRect(this.LTV, this.Width, this.Height, new PVector(x,y));
}
void Translate(PVector dv)
{
this.LTV.add(dv);
}
RectRails(PVector ltv, float w, float h)
{
this.LTV = ltv.copy();
this.Width = w;
this.Height = h;
}
}
class TetragonRails implements IAObject, IRails, IDrawable, IHit, IClickable, IDraggable
{
PVector LTV;
PVector VX;
PVector VY;
//vertex radius
float vr = 20;
PVector TrackerStartPoint()
{
return this.LTV;
}
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.LTV.copy());
ret.add(PVector.add(this.LTV, VX));
ret.add(PVector.add(this.LTV, VX).add(VY));
ret.add(PVector.add(this.LTV, VY));
return ret;
}
PVector GetOrigin()
{
return this.LTV;
}
//void draw(float x_,float y_)
//{
// stroke(0);
// DrawLinerClose(GetCoordinates());
//}
//@Override
void draw()
{
DrawLinerClose(GetCoordinates());
DrawPoints(vr, GetCoordinates());
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnTetragon(this.LTV, this.VX, this.VY, tracker);
return near;
}
int pressed_index = -1;
void mousePressed()
{
for(int i = 0; i<GetCoordinates().size(); i++)
{
PVector v = GetCoordinates().get(i);
if(PointInCircle(v,vr,new PVector(mouseX,mouseY)))
{
pressed_index=i;
return;
}
}
}
void mouseDragged()
{
if(0<=pressed_index&&pressed_index<this.GetCoordinates().size())
{
if(pressed_index==0){this.LTV.add(dv);}
if(pressed_index==1){this.VX.add(dv);}
if(pressed_index==2)
{
this.VX.add(dv);
this.VY.add(dv);
}
if(pressed_index==3){this.VY.add(dv);}
}
}
void mouseReleased(){pressed_index=-1;}
boolean Hit(float x, float y)
{
for(PVector v : GetCoordinates())
{
if(PointInCircle(v,vr,new PVector(x,y)))
{
return true;
}
}
return false;
}
void Translate(PVector dv)
{
this.LTV.add(dv);
}
TetragonRails(PVector ltv, PVector vx, PVector vy)
{
this.LTV = ltv.copy();
this.VX = vx.copy();
this.VY = vy.copy();
}
}
class TetragonBinder implements IAObject, IBinder, IDrawable, IHit, IClickable, IDraggable
{
ArrayList<Tracker> Trackers = new ArrayList<Tracker>();
PVector LTV;
PVector VX;
PVector VY;
//vertex radius
float vr = 20;
PVector GetOrigin()
{
return this.LTV;
}
PVector TrackerStartPoint()
{
return this.LTV;
}
ArrayList<PVector> GetCoordinates()
{
ArrayList<PVector> ret = new ArrayList<PVector>();
ret.add(this.LTV.copy());
ret.add(PVector.add(this.LTV, VX));
ret.add(PVector.add(this.LTV, VX).add(VY));
ret.add(PVector.add(this.LTV, VY));
return ret;
}
void Update()
{
for(Tracker tracker : Trackers)
{
PVector near = NearestOnTetragon(this.LTV, this.VX, this.VY, tracker.GetOrigin());
tracker.GetOrigin().x=near.x;
tracker.GetOrigin().y=near.y;
}
}
//@Override
void draw()
{
DrawLinerClose(GetCoordinates());
DrawPoints(vr, GetCoordinates());
}
PVector Nearest(PVector tracker)
{
PVector near = NearestOnTetragon(this.LTV, this.VX, this.VY, tracker);
return near;
}
int pressed_index = -1;
void mousePressed()
{
for(int i = 0; i<GetCoordinates().size(); i++)
{
PVector v = GetCoordinates().get(i);
if(PointInCircle(v,vr,new PVector(mouseX,mouseY)))
{
pressed_index=i;
return;
}
}
}
void mouseDragged()
{
if(0<=pressed_index&&pressed_index<this.GetCoordinates().size())
{
if(pressed_index==0){this.LTV.add(dv);}
if(pressed_index==1){this.VX.add(dv);}
if(pressed_index==2)
{
this.VX.add(dv);
this.VY.add(dv);
}
if(pressed_index==3){this.VY.add(dv);}
}
}
void mouseReleased(){pressed_index=-1;}
boolean Hit(float x, float y)
{
for(PVector v : GetCoordinates())
{
if(PointInCircle(v,vr,new PVector(x,y)))
{
return true;
}
}
return false;
}
void Translate(PVector dv)
{
this.LTV.add(dv);
}
TetragonBinder(PVector ltv, PVector vx, PVector vy)
{
this.LTV = ltv.copy();
this.VX = vx.copy();
this.VY = vy.copy();
}
}
//端から端まで幅を持つ
//Origin及びTranslateを持たない
//他のGUIに寄生する、あるいは拡張するGUI
class Frame implements IAObject, IDrawable, IDrawableXY
{
String ID = "Frame";
void draw(float x_,float y_){}
void draw(){}
}
class RectTopFrame extends Frame implements IHit
{
String ID = "RectTopFrame";
RectGUIBase Target;
float Wide;
boolean Up = false;
boolean Hit(float x, float y)
{
if(Up){return UpHit(x,y);}
else{return DownHit(x,y);}
}
boolean UpHit(float x, float y)
{
if(Target.LTV.x<x&&x<Target.LTV.x+Target.Width)
{
if(Target.LTV.y-Wide<y&&y<Target.LTV.y)
{
return true;
}
}
return false;
}
boolean DownHit(float x, float y)
{
if(Target.LTV.x<x&&x<Target.LTV.x+Target.Width)
{
if(Target.LTV.y<y&&y<Target.LTV.y+Wide)
{
return true;
}
}
return false;
}
//コンストラクタ
RectTopFrame(RectGUIBase target, float wide)
{
Target=target;
Wide=wide;
}
@Override
void draw()
{
if(!Up){rect(Target.LTV.x,Target.LTV.y,Target.Width,this.Wide);}
else{rect(Target.LTV.x,Target.LTV.y,Target.Width,-this.Wide);}
}
}
class MovalTopFrame extends RectTopFrame implements IHit, IClickable, IDraggable
{
String ID = "MovalTopFrame";
boolean pressed = false;
void mousePressed() {if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged()
{
if(pressed)
{
if(Target instanceof ITranslate){((ITranslate)Target).Translate(dv); }
}
}
void mouseReleased(){pressed = false;}
MovalTopFrame(RectGUIBase target, float wide)
{
super(target, wide);
}
}
//Translateできるとは限らない
class Label implements IAObject ,IRectGeometry, IDrawableXY
{
String ID = "Label";
String Text;
PVector Origin;
PVector GetLTV(){return this.Origin;}
float Width;
float GetWidth(){return this.Width;}
float Height;
float GetHeight(){return this.Height;}
//boolean Hit(float x, float y){return false;}
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,Width,Height);
noFill();
stroke(0);
rect(x_,y_,Width,Height);
}
void draw()
{
this.draw(this.Origin.x, this.Origin.y);
}
}
//独立したOriginを持たないためLabelとは別の系統
class OffsetLabel implements IGObject, IRectGeometry, IDrawableXY//IGObject extends IAObject, IDrawable, IHit, ITranslate, IClickable, IDraggable
{
String ID = "OffsetLabel";
IAObject Target;
String Text = "label";
PVector MarkTo = new PVector(0,0);//指し示す場所へのオフセット
PVector Offset = new PVector(0,0);//MarkToからLabelのLeftTopまでのオフセット
PVector AbsOrigin()
{
PVector v = new PVector(0,0);
if(Target instanceof HasOrigin)
{
HasOrigin o = (HasOrigin)Target;
v.add(o.GetOrigin());
}
return v;
}
PVector AbsMarkTo()
{
PVector v = AbsOrigin();
v.add(MarkTo);
return v;
}
PVector AbsCoordinate()
{
PVector v = AbsMarkTo();
v.add(Offset);
return v;
}
PVector GetLTV(){return this.AbsCoordinate();}
float Width;
float GetWidth(){return this.Width;}
float Height;
float GetHeight(){return this.Height;}
boolean Hit(float x, float y)
{
return RectHit(this,x,y);
}
boolean pressed = false;
void mousePressed(){if(this.Hit(mouseX, mouseY)){pressed = true;}}
void mouseDragged(){if(pressed){this.Translate(dv);}}
void mouseReleased(){pressed = false;}
void Translate(PVector dv)
{
this.Offset.add(dv);
}
void draw(float x_,float y_)
{
if(!(Target instanceof HasOrigin)){return;}
fill(0);
textSize(16);
text(Text,AbsMarkTo().x+x_,AbsMarkTo().y+y_,Width,Height);
noFill();
line(AbsMarkTo().x,AbsMarkTo().y,AbsMarkTo().x+x_,AbsMarkTo().y+y_);
stroke(0);
rect(AbsMarkTo().x+x_,AbsMarkTo().y+y_,Width,Height);
}
void draw()
{
this.draw(this.Offset.x, this.Offset.y);
}
//コンストラクタ
OffsetLabel(IAObject target, float x_, float y_, float w_, float h_ )
{
this.Target = target;
this.Offset = new PVector(x_,y_);
this.Width=w_;
this.Height=h_;
}
}
class TextBox extends RectGUIBase implements IKeyInputtable
{
String Text = "";
char keydata = ' ';
void keyPressed()
{
keydata = key;
Text += keydata;
}
void keyReleased()
{
}
@Override
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,Width,Height);
noFill();
stroke(0);
rect(x_,y_,Width,Height);
}
@Override
void draw()
{
this.draw(this.LTV.x, this.LTV.y);
}
//コンストラクタ
TextBox(float x, float y, float w, float h)
{
this.LTV = new PVector(x,y);
this.Width=w;
this.Height=h;
}
}
//ボタン基礎
//機能(ハンドラ)を実装するとは限らない
//継承して直にハンドラを書くという使い方もする
class Button extends RectGUIBase
{
String ID = "RectGUIBase";
String Text;
@Override
void draw(float x_,float y_)
{
fill(0);
textSize(16);
text(Text,x_,y_,Width,Height);
noFill();
stroke(0);
rect(x_,y_,Width,Height);
}
@Override
void draw()
{
this.draw(this.LTV.x, this.LTV.y);
}
}
//機能(ハンドラ)を所持する
class FunctionButton extends Button
{
String ID = "FunctionButton";
GUIFunction GUIFunction;
@Override
void mousePressed()
{
if(!this.Hit(mouseX, mouseY)){return;}
if(GUIFunction!=null){GUIFunction.func();}
}
FunctionButton(float x, float y, float w, float h, String str, GUIFunction func)
{
this.LTV = new PVector(x,y);
this.Width=w;
this.Height=h;
this.GUIFunction = func;
this.Text = str;
}
FunctionButton(float x, float y, float w, float h, String str)
{
this.LTV = new PVector(x,y);
this.Width=w;
this.Height=h;
this.GUIFunction = new NULLFUNC();
this.Text = str;
}
}
interface GUIFunction
{
void func();
}
//何もしない
class NULLFUNC implements GUIFunction
{
void func()
{
}
}
class Bar implements IAObject, ILineSegmentGeometry, IDrawable, IHit, IClickable, IDraggable //ITranslate
{
//この3つのGUIはメインのリストには乗らない
//なのでこのGUIの管理は全てこのクラスで行わなければならない
MovalCircle Knob;
MovalCircle Start;
MovalCircle End;
PVector GetSV(){return this.Start.Center;}
PVector GetEV(){return this.End.Center;}
PVector GetLV(){return PVector.sub(GetEV(),GetSV());}
PVector GetTV(){return PVector.sub(Knob.Center,GetSV());}
//boolean pressed = false;
void mousePressed()
{
if(Knob.Hit(mouseX, mouseY)){Knob.pressed=true;}
else if(Start.Hit(mouseX, mouseY)){Start.pressed=true;}
else if(End.Hit(mouseX, mouseY)){End.pressed=true;}
}
void mouseDragged()
{
if(Knob.pressed){Knob.Translate(dv);}
else if(Start.pressed){Start.Translate(dv);}
else if(End.pressed){End.Translate(dv);}
Knob.Center = NearestOnSegment(this.GetSV(),GetEV(),Knob.Center).copy();
}
void mouseReleased()
{
Knob.pressed = false;
Start.pressed = false;
End.pressed = false;
}
@Override
boolean Hit(float x, float y)
{
if(Knob.Hit(x,y)){return true;}
else if(Start.Hit(x,y)){return true;}
else if(End.Hit(x,y)){return true;}
return false;
}
//@Override
void draw()
{
this.Knob.draw();
this.Start.draw();
this.End.draw();
line(GetSV().x,GetSV().y,GetEV().x,GetEV().y);
}
Bar(PVector sv, PVector ev)
{
this.Start = new MovalCircle(sv,20,2*PI/36);
this.End = new MovalCircle(ev,20,2*PI/36);
this.Knob = new MovalCircle(new PVector((sv.x+ev.x)/2, (sv.y+ev.y)/2),20,2*PI/36);
}
}
class PBar extends Bar
{
float Min;
float Max;
float Value()
{
return Min+(Max-Min)*t();
}
float t()
{
return GetTV().mag()/GetLV().mag();
}
void SetKnob(float t)
{
PVector tv = PVector.add(this.GetSV(), GetLV().mult(t));
this.Knob.Center.x = tv.x;
this.Knob.Center.y = tv.y;
}
@Override
void draw()
{
this.Knob.draw();
this.Start.draw();
this.End.draw();
line(GetSV().x,GetSV().y,GetEV().x,GetEV().y);
fill(0);
text(str(Min), this.Start.Center.x, this.Start.Center.y);
text(str(Max), this.End.Center.x, this.End.Center.y);
text(str(Value()), this.Knob.Center.x, this.Knob.Center.y);
}
PBar(PVector sv, PVector ev, float min, float max, float t)
{
super(sv,ev);
Min = min;
Max = max;
SetKnob(t);
}
PBar(PVector sv, PVector ev, float min, float max)
{
super(sv,ev);
Min = min;
Max = max;
SetKnob(0.5);
}
PBar(PVector sv, PVector ev, float max)
{
super(sv,ev);
Min = 0;
Max = max;
SetKnob(0.5);
}
}
PVector vadd(PVector v1, PVector v2)
{
return PVector.add(v1,v2);
}
PVector vsub(PVector v1, PVector v2)
{
return PVector.sub(v1,v2);
}
PVector vmult(PVector v, float value)
{
return PVector.mult(v,value);
}
PVector vdiv(PVector v, float value)
{
return PVector.div(v,value);
}
PVector vrot(PVector v, float radian)
{
return RotRadian(v,radian);
}
//体積を求める
public float CalcuArea(ArrayList<PVector> vertex)
{
float sum = 0;
for(int i = 0; i<vertex.size()-1; i++)
{
PVector v1 = vertex.get(i);
PVector v2 = vertex.get(i+1);
float c = Cross(v1,v2);
sum+=c;
}
return (Math.abs(sum))/2.0;
}
public PVector Nagate(PVector v)
{
return new PVector(-v.x,-v.y);
}
// 内積
public float Dot(PVector v1, PVector v2)
{
return (v1.x * v2.x + v1.y * v2.y);
}
//外積、疑似外積、疑スカラー、パープ内積
public float Cross(PVector v1, PVector v2)
{
//+ならv2がv1の右側
return (v1.x * v2.y - v1.y * v2.x);
}
//重心
public PVector CenterOfGravity(ArrayList<PVector> vertexes)
{
PVector sum = new PVector(0,0);
for(int i = 0; i<vertexes.size(); i++)
{
PVector v = vertexes.get(i);
sum.add(v);
}
sum.div(vertexes.size());
return sum;
}
public ArrayList<PVector> RotRadian(ArrayList<PVector> vertex, double radian)
{
PVector cog = CenterOfGravity(vertex);
return RotRadian(vertex, cog, radian);
}
public ArrayList<PVector> RotDegree(ArrayList<PVector> vertex, double degree)
{
PVector cog = CenterOfGravity(vertex);
return RotDegree(vertex, cog, degree);
}
public ArrayList<PVector> RotRadian(ArrayList<PVector> vertex, PVector center, double radian)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
PVector dv = PVector.sub(v,center);
dv = RotRadian(dv, radian);
ret.add(PVector.add(center,dv));
}
return ret;
}
public ArrayList<PVector> RotDegree(ArrayList<PVector> vertex, PVector center, double degree)
{
if (degree == 0) { return vertex; }
double radian = (degree * Math.PI) / 180;
return RotRadian(vertex, center, radian);
}
public PVector RotRadian(PVector v, double radian)
{
float x = v.x * cos((float)radian) - v.y * sin((float)radian);
float y = v.x * sin((float)radian) + v.y * cos((float)radian);
return new PVector(x,y);
}
public PVector RotDegree(PVector v, double degree)
{
if (degree == 0) { return new PVector(v.x,v.y); }
double radian = (degree * Math.PI) / 180;
return RotRadian(v, radian);
}
public void RotRadianRef(ArrayList<PVector> vertex, PVector center, double radian)
{
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
PVector dv = PVector.sub(v,center);
RotRadianRef(dv, radian);
vertex.get(i).x = center.x+dv.x;
vertex.get(i).y = center.y+dv.y;
}
}
public void RotDegreeRef(ArrayList<PVector> vertex, PVector center, double degree)
{
if (degree == 0) { return ; }
double radian = (degree * Math.PI) / 180;
RotRadianRef(vertex, center, radian);
}
public void RotRadianRef(PVector v, double radian)
{
float x = v.x * cos((float)radian) - v.y * sin((float)radian);
float y = v.x * sin((float)radian) + v.y * cos((float)radian);
v.x = x;
v.y = y;
}
public void RotDegreeRef(PVector v, double degree)
{
if (degree == 0) { return; }
double radian = (degree * Math.PI) / 180;
RotRadianRef(v, radian);
}
//Christer p40
//正射影ベクトル(2点u,vが与えられた場合)
public PVector Projection(PVector u, PVector v)
{
//u:axis
return PVector.mult(u,(PVector.dot(u, v) / PVector.dot(u, u)));
}
//正射影ベクトル(直線上に焼き付ける場合)
public PVector Projection(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
//このベクトルの先端を絶対座標として取得するためにはsvを足さなければならない
return PVector.mult(lv,(PVector.dot(toV, lv) / PVector.dot(lv, lv)));
}
//垂直射影ベクトル(2点u,vが与えられた場合)
public PVector Perpendicular(PVector u, PVector v)
{
//u:axis
return PVector.sub(v, Projection(u, v));
}
//垂直射影ベクトル(直線上から射出する場合)
public PVector Perpendicular(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
//このベクトルの先端を絶対座標として取得するためにはsv+projを足さなければならない
return PVector.sub(toV, Projection(lv, toV));
}
//uの垂直軸に対してvを鏡映変換
public PVector RefrectV(PVector u, PVector v)
{
//v-2*parpendicular
return PVector.sub(v,PVector.mult(Perpendicular(u,v),2));
}
public PVector RefrectV(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
//v-2*parpendicular
return PVector.sub(toV,PVector.mult(Perpendicular(lv,toV),2));
}
//uに対するvの鏡映変換
public PVector RefrectH(PVector u, PVector v)
{
//2*parpendicular-v
return PVector.sub(PVector.mult(Perpendicular(u,v),2), v);
}
public PVector RefrectH(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
//2*parpendicular-v
return PVector.sub(PVector.mult(Perpendicular(lv,toV),2), toV);
}
class VectorLine2D
{
PVector SV;
PVector EV;
PVector LV()
{
return PVector.sub(this.EV,this.SV);
}
VectorLine2D(PVector sv, PVector ev)
{
this.SV = sv.copy();
this.EV = ev.copy();
}
VectorLine2D(float sx, float sy, float ex, float ey)
{
this.SV = new PVector(sx,sy);
this.EV = new PVector(ex,ey);
}
}
//複数線分の交点座標を取得
public ArrayList<PVector> GetIntersection(ArrayList<VectorLine2D> l1, ArrayList<VectorLine2D> l2)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for (int i = 0; i < l1.size(); i++)
{
ArrayList<PVector> vs = GetIntersection(l1.get(i), l2);
if(vs!=null&&vs.size()<1)
{
ret.addAll(vs);
}
}
return ret;
}
public ArrayList<PVector> GetIntersection(VectorLine2D l1, ArrayList<VectorLine2D> l2)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for (int i = 0; i < l2.size(); i++)
{
PVector v = GetIntersection(l1, l2.get(i));
if(v!=null)
{
ret.add(v);
}
}
return ret;
}
public PVector GetIntersection(PVector sv1, PVector ev1, PVector sv2, PVector ev2)
{
return GetIntersection(new VectorLine2D(sv1,ev1), new VectorLine2D(sv2, ev2));
}
public PVector GetIntersection(VectorLine2D l1, VectorLine2D l2)
{
if(IsIntersect(l1,l2))
{
PVector v = l1.LV();
PVector w = l2.LV();
PVector u = PVector.sub(l2.SV, l1.SV);
float t1 = (float)(Cross(v, u)/ Cross(w, v));
return PVector.add(l2.SV, PVector.mult(l2.LV(), t1));
}
return null;
}
//線分は交差するか
public boolean IsIntersect(VectorLine2D l1, VectorLine2D l2)
{
if (IsOnStraddle(l1, l2))
{
if (IsOnStraddle(l2, l1))
{
return true;
}
}
return false;
}
//線分が直線を跨ぐ(直線から線分を見る)
//お互いにお互いを見ると, 平行でない限り必ずどこかで跨ぐ
public boolean IsOnStraddle(VectorLine2D l1, VectorLine2D l2)
{
PVector lv1 = l1.LV();
PVector l1SVtol2SV = PVector.sub(l2.SV, l1.SV);
PVector l1SVtol2EV = PVector.sub(l2.EV, l1.SV);
//OnLine(外積=0)を入念にとる
if ((int)Cross(lv1, l1SVtol2SV) == 0 || (int)Cross(lv1, l1SVtol2EV) == 0)
{
return true;
}
//片方がプラマイどちらかで、片方が外積ゼロだと反応しないケースがでる気がする(未確認)
if (((int)Cross(lv1, l1SVtol2SV) ^ (int)Cross(lv1, l1SVtol2EV)) < 0)
{
return true;
}
return false;
}
//線分が直線を跨ぐ(直線から線分を見る)
//お互いにお互いを見ると, 平行でない限り必ずどこかで跨ぐ
public boolean IsStraddle(VectorLine2D l1, VectorLine2D l2)
{
PVector lv1 = l1.LV();
PVector l1SVtol2SV = PVector.sub(l2.SV, l1.SV);
PVector l1SVtol2EV = PVector.sub(l2.EV, l1.SV);
//OnLineはとらない(false)
if ((int)Cross(lv1, l1SVtol2SV) == 0 || (int)Cross(lv1, l1SVtol2EV) == 0)
{
return false;
}
//これだけだとOnLineをとってしまうことがある
if (((int)Cross(lv1, l1SVtol2SV) ^ (int)Cross(lv1, l1SVtol2EV)) < 0)
{
return true;
}
return false;
}
//Line
float Get_t(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
float t = Dot(toV,lv)/Dot(lv,lv);
return t;
}
PVector Set_t(PVector sv, PVector ev, float t)
{
PVector lv = PVector.sub(ev,sv);
return lv.normalize().mult(t);
}
//区間を1とする
float Get_t_Liner(ArrayList<PVector> vertices, PVector v)
{
int index = NearestOnSegmentsIndex(vertices,v);
PVector sv = vertices.get(index);
PVector ev = vertices.get(index+1);
return index+Get_t(sv,ev,v);
}
PVector Set_t_Liner(ArrayList<PVector> vertices, float t)
{
int index = (int)t;
PVector sv = vertices.get(index);
PVector ev = vertices.get(index+1);
return Set_t(sv,ev,t-index);
}
//区間を1とする
float Get_t_LinerClose(ArrayList<PVector> vertices, PVector v)
{
int index = NearestOnSegmentsIndexClose(vertices,v);
PVector sv = vertices.get(index);
PVector ev = vertices.get(index+1);
return index+Get_t(sv,ev,v);
}
PVector Set_t_LinerClose(ArrayList<PVector> vertices, float t)
{
int index = (int)t;
PVector sv = vertices.get(index);
PVector ev = vertices.get(index+1%vertices.size());
return Set_t(sv,ev,t-index);
}
float Get_radian(PVector cv, float radius, PVector v)
{
PVector lv = PVector.sub(v,cv);
double radian = Math.atan2(lv.y, lv.x);
//radian += PI;
//if(radian<0){radian=0;}
//if(2*PI<radian){radian=2*PI;}
return (float)radian;
}
PVector Set_radian(PVector cv, float radius, float radian)
{
PVector v = new PVector(radius,0);
RotRadianRef(v,radian);
return PVector.add(cv,v);
}
//Christer p128
//直線に一番近い点の絶対座標:Projection先端の絶対座標に等しい
PVector NearestOnLine(PVector sv, PVector to, PVector v)
{
//PVector perp = Perpendicular(sv,to,v);
//perp = Nagate(perp);
//PVector d = PVector.add(v,perp);
PVector proj = Projection(sv,to,v);
PVector d = PVector.add(sv,proj);
return d;
}
//Christer p128
//半直線に一番近い点の絶対座標
PVector NearestOnRay(PVector sv, PVector to, PVector v)
{
PVector lv = PVector.sub(to,sv);
PVector toV = PVector.sub(v,sv);
float t = Dot(toV,lv)/Dot(lv,lv);
if(t<0){t=0;}
return PVector.add(sv, lv.mult(t));
}
//Christer p128
//線分に一番近い点の絶対座標
PVector NearestOnSegment(PVector sv, PVector ev, PVector v)
{
PVector lv = PVector.sub(ev,sv);
PVector toV = PVector.sub(v,sv);
float t = Dot(toV,lv)/Dot(lv,lv);
if(t<0){t=0;}
if(1<t){t=1;}
return PVector.add(sv, lv.mult(t));
}
//折れ線
//閉じない
PVector NearestOnSegments(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
PVector ret = vectors.get(0);
for(int i = 0; i<vectors.size()-1; i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get(i+1);
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = near;
}
}
return ret;
}
int NearestOnSegmentsIndex(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
int ret = 0;
for(int i = 0; i<vectors.size()-1; i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get(i+1);
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = i;
}
}
return ret;
}
//閉じる
PVector NearestOnSegmentsClose(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
PVector ret = vectors.get(0);
for(int i = 0; i<vectors.size(); i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get((i+1)%vectors.size());
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = near;
}
}
return ret;
}
int NearestOnSegmentsIndexClose(ArrayList<PVector> vectors, PVector v)
{
float min_distance = Float.MAX_VALUE;
int ret = 0;
for(int i = 0; i<vectors.size(); i++)
{
PVector v0 = vectors.get(i);
PVector v1 = vectors.get((i+1)%vectors.size());
PVector near = NearestOnSegment(v0,v1,v);
PVector n2v = PVector.sub(v, near);
float d = n2v.mag();
if( d<min_distance)
{
min_distance = d;
ret = i;
}
}
return ret;
}
//領域に対して最も近い点
PVector NearestOnPolygon(ArrayList<PVector> vectors, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(CrossingNumber(vectors,v)){return v;}
else{return NearestOnSegmentsClose(vectors,v);}
}
//■円周
//円周上の最近接点
PVector NearestOnCircumference(PVector center, float radius, PVector v)
{
PVector toV = PVector.sub(v,center);
toV.normalize().mult(radius);
return toV.add(center);
}
//円周上を含まないPointIn
boolean PointInCircle(PVector center, float radius, PVector v)
{
float dx = v.x-center.x;
float dy = v.y-center.y;
if(dx*dx+dy*dy<radius*radius){return true;}
return false;
}
//領域に対して最も近い点
PVector NearestOnCircle(PVector center, float radius, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(PointInCircle(center,radius,v)){return v;}
else{return NearestOnCircumference(center,radius,v);}
}
//■矩形
//矩形外周上
PVector NearestOnCircumference(PVector ltv, float w, float h, PVector v)
{
ArrayList<PVector> rect = new ArrayList<PVector>();
rect.add(new PVector(ltv.x, ltv.y));
rect.add(new PVector(ltv.x+w,ltv.y));
rect.add(new PVector(ltv.x+w,ltv.y+h));
rect.add(new PVector(ltv.x, ltv.y+h));
PVector near = NearestOnSegmentsClose(rect,v);
return near;
}
boolean PointInRect(PVector ltv, float w, float h, PVector v)
{
if(ltv.x<v.x&&v.x<ltv.x+w)
{
if(ltv.y<v.y&&v.y<ltv.y+h)
{
return true;
}
}
return false;
}
//領域に対して最も近い点
PVector NearestOnRect(PVector ltv, float w, float h, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(PointInRect(ltv,w,h,v)){return v;}
else{return NearestOnCircumference(ltv,w,h,v);}
}
//■Tetragon
PVector NearestOnCircumference(PVector ltv, PVector vx, PVector vy, PVector v)
{
ArrayList<PVector> tetragon = new ArrayList<PVector>();
tetragon.add(ltv.copy());
tetragon.add(PVector.add(ltv,vx));
tetragon.add(PVector.add(ltv,vx).add(vy));
tetragon.add(PVector.add(ltv,vy));
PVector near = NearestOnSegmentsClose(tetragon,v);
return near;
}
boolean PointInTetragon(PVector ltv, PVector vx, PVector vy, PVector v)
{
ArrayList<PVector> tetragon = new ArrayList<PVector>();
tetragon.add(ltv.copy());
tetragon.add(PVector.add(ltv,vx));
tetragon.add(PVector.add(ltv,vx).add(vy));
tetragon.add(PVector.add(ltv,vy));
return CrossingNumber(tetragon,v);
}
PVector NearestOnTetragon(PVector ltv, PVector vx, PVector vy, PVector v)
{
//多角形の内側ならそのまま
//多角形の外にでたら最も近い外周上の1点を返す
if(PointInTetragon(ltv,vx,vy,v)){return v;}
else{return NearestOnCircumference(ltv,vx,vy,v);}
}
//凸のみPointInPolygonの部品
boolean PIP_IsRight(PVector sv, PVector ev, PVector p)
{
PVector lv = ev.copy().sub(sv);
PVector p_ = p.copy().sub(sv);
if(0<Cross(lv,p_)){return true;}
return false;
}
//凸のみPointInPolygon
boolean PointInPolygon(ArrayList<PVector> vertex, PVector p)
{
//最初の一回
boolean is_right = PIP_IsRight(vertex.get(0),vertex.get(1),p);
//中間
for(int i = 1; i<vertex.size(); i++)
{
//向き変わった
if(is_right!=PIP_IsRight(vertex.get(i),vertex.get((i+1)%vertex.size()),p))
{
return false;
}
}
return true;
}
boolean PointInPolygon_origin(ArrayList<PVector> vertex, PVector p)
{
//最初の一回
PVector sv = vertex.get(0).copy();
PVector ev = vertex.get(1).copy();
PVector lv = ev.sub(sv);
PVector p_ = p.copy().sub(sv);
//右である
boolean is_right = false;
if(0<Cross(lv,p_)){is_right=true;}
//中間
for(int i = 1; i<vertex.size()-1; i++)
{
sv = vertex.get(i).copy();
ev = vertex.get(i+1).copy();
lv = ev.sub(sv);
p_ = p.copy().sub(sv);
if(0<Cross(lv,p_))
{
//向き変わった
if(is_right==false){return false;}
}
else
{
//向き変わった
return false;
}
}
//最後の一回
sv = vertex.get(vertex.size()-1).copy();
ev = vertex.get(0).copy();
lv = ev.sub(sv);
p_ = p.copy().sub(sv);
if(0<Cross(lv,p_))
{
//向き変わった
if(is_right==false){return false;}
}
else
{
//向き変わった
return false;
}
return true;
}
//参考:https://www.nttpc.co.jp/technology/number_algorithm.html
boolean CrossingNumber(ArrayList<PVector> vertex, PVector p)
{
int count = 0;
for(int i = 0; i < vertex.size(); i++)
{
PVector sv = vertex.get(i);
PVector ev = vertex.get((i+1)%vertex.size());
//PVector lv = PVector.sub(ev,sv);
//PVector toP = PVector.sub(p,sv);
//ルール1,2
//上向き(sv.y<ev.y 終点含まず、始点ならカウント候補)||下向き(sv.y>ev.y 始点含まず、終点ならカウント候補)
//ルール3
//sv.y==ev.yはカウントしない
if((sv.y <= p.y && p.y < ev.y)||(sv.y > p.y && p.y >= ev.y))
{
//ルール4
//辺は点pよりも絶対座標で右側にあり、辺上ではない
//点pは辺よりも絶対座標で左側にあり、辺上ではない
//点p.xと交点.xの比較
float t = (p.y - sv.y) / (ev.y - sv.y);
if(p.x < (sv.x + (t * (ev.x - sv.x))))
{
count++;
}
//交点が辺の左
//外積計算は相対位置で左右を取るため成り立たない
//上向きの時に入力点を左を取り、下向きの時は入力点を右を取らなければならない
//というよりも外積計算で左右とったら凸多角形の内外判定と変わらない
//if(0>Cross(lv,toP)){count++;}
}//if
}//for
if(count%2==1){return true;}
return false;
}//CrossingNumber
ArrayList<PVector> MakeCopy(ArrayList<PVector> source)
{
if(source==null||source.size()<1){return null;}
ArrayList<PVector> ret = new ArrayList<PVector>();
for(int i = 0; i<source.size(); i++)
{
if(source.get(i)==null){continue;}
ret.add(source.get(i).copy());
}
return ret;
}
//ArrayList<PVector> Copy(ArrayList<PVector> vectors)
//{
// ArrayList<PVector> ret = new ArrayList<PVector>();
// for(int i = 0; i<vectors.size(); i++)
// {
// ret.add(vectors.get(i).copy());
// }
// return ret;
//}
ArrayList<PVector> MakeCopy(PVector[] source)
{
if(source==null||source.length<1){return null;}
ArrayList<PVector> ret = new ArrayList<PVector>();
for(int i = 0; i<source.length; i++)
{
if(source[i]==null){continue;}
ret.add(source[i]);
}
return ret;
}
PVector[][] MakeCopy(PVector[][] grid)
{
int m = grid.length;
int n = grid[0].length;
PVector[][] ret = new PVector[m][n];
for(int i = 0; i<m; i++)
{
for(int j = 0; j<n; j++)
{
ret[i][j]=grid[i][j].copy();
}
}
return ret;
}
void CopyVector(ArrayList<PVector> source, ArrayList<PVector> target)
{
if(source==null||source.size()<1){return ;}
if(target==null||target.size()<1){return ;}
for(int i = 0; i<source.size(); i++)
{
target.get(i).x=source.get(i).x;
target.get(i).y=source.get(i).y;
}
}
void CopyVector(PVector[] source, ArrayList<PVector> target)
{
//if(source==null||source.length<1){return ;}
//if(target==null||target.size()<1){return ;}
target.clear();
for(int i = 0; i<source.length; i++)
{
target.add(source[i].copy());
}
}
PVector[] GetRow(PVector[][] grid, int row_index)
{
int n = grid[0].length;
PVector[] ret = new PVector[n];
for(int i = 0; i<n; i++)
{
ret[i]=grid[row_index][i];
}
return ret;
}
PVector[] GetCol(PVector[][] grid, int col_index)
{
int m = grid.length;
PVector[] ret = new PVector[m];
for(int i = 0; i<m; i++)
{
ret[i]=grid[i][col_index];
}
return ret;
}
void DrawTrackerIndex(ArrayList<Tracker> Trackers)
{
fill(0);
for(int i = 0; i<Trackers.size(); i++)
{
Tracker t = Trackers.get(i);
text(str(i), t.Center.x, t.Center.y);
}
noFill();
}
void DrawIndex(ArrayList<PVector> vertex)
{
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
text(str(i), v.x, v.y);
}
}
void DrawPoints(ArrayList<PVector> vertex)
{
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
ellipse(v.x, v.y, 20, 20);
}
}
void DrawPoints(float radius, ArrayList<PVector> points)
{
for(PVector p : points)
{
ellipse(p.x,p.y,radius,radius);
}
}
void DrawPoints(float radius, PVector... points)
{
for(PVector p : points)
{
ellipse(p.x,p.y,radius,radius);
}
}
void DrawLine(PVector sp, PVector ep)
{
line(sp.x, sp.y, ep.x,ep.y);
}
void DrawLinerCloseAAP(ArrayList<ArrayList<PVector>> vertexes)
{
for(ArrayList<PVector> vertex : vertexes)
{
DrawLinerClose(vertex);
}
}
//閉じる、塗らない
void DrawLinerClose(PVector... vertex)
{
DrawLinerClose(MakeCopy(vertex));
}
//閉じない、塗らない
void DrawLiner(PVector... vertex)
{
DrawLiner(MakeCopy(vertex));
}
//閉じる、塗らない
void DrawLinerClose(ArrayList<PVector> vertex)
{
if(vertex==null){return;}
for(int i = 0; i<vertex.size(); i++)
{
PVector sv = vertex.get((i+vertex.size()-1)%vertex.size());
PVector ev = vertex.get(i);
line(sv.x, sv.y, ev.x, ev.y);
}
//for(int i = 0; i<vertex.size()-1; i++)
//{
// PVector sv = vertex.get(i);
// PVector ev = vertex.get(i+1);
// line(sv.x, sv.y, ev.x, ev.y);
//}
//PVector sv = vertex.get(vertex.size()-1);
//PVector ev = vertex.get(0);
//line(sv.x, sv.y, ev.x, ev.y);
}
//閉じない、塗らない
void DrawLiner(ArrayList<PVector> vertex)
{
for(int i = 0; i<vertex.size()-1; i++)
{
PVector sv = vertex.get(i);
PVector ev = vertex.get(i+1);
line(sv.x, sv.y, ev.x, ev.y);
}
}
void DrawLiner(PVector sv, PVector ev)
{
line(sv.x, sv.y, ev.x, ev.y);
}
//閉じる、塗る
void FillLinerClose(PVector... vertex)
{
beginShape();
for(int i = 0; i<=vertex.length; i++)
{
PVector v = vertex[i%vertex.length];
vertex(v.x,v.y);
}
endShape();
}
//閉じる、塗る
void FillLinerClose(ArrayList<PVector> vertex)
{
beginShape();
for(int i = 0; i<=vertex.size(); i++)
{
PVector v = vertex.get(i%vertex.size());
vertex(v.x,v.y);
}
endShape();
}
//閉じない、塗る
void FillLiner(PVector... vertex)
{
beginShape();
for(int i = 0; i<vertex.length; i++)
{
PVector v = vertex[i];
vertex(v.x,v.y);
}
endShape();
}
//閉じない、塗る
void FillLiner(ArrayList<PVector> vertex)
{
beginShape();
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
vertex(v.x,v.y);
}
endShape();
}
//閉じない、塗る
void FillLiner(PGraphics pg, ArrayList<PVector> vertex)
{
pg.beginShape();
for(int i = 0; i<vertex.size(); i++)
{
PVector v = vertex.get(i);
pg.vertex(v.x,v.y);
}
pg.endShape();
}
void DrawArrow(PVector sv, PVector ev)
{
DrawArrow_t(sv,ev,0.2,60);
}
//比率に応じた矢じり t:0-1
void DrawArrow_t(PVector sv, PVector ev, float t, float degree)
{
PVector rlv = PVector.sub(sv,ev);
rlv.mult(t);
//矢じりの左端と右端
PVector lv = RotDegree(rlv, degree/2);
PVector rv = RotDegree(rlv, -degree/2);
line(sv.x,sv.y,ev.x,ev.y);
beginShape();
vertex(ev.x,ev.y);
vertex(ev.x+rv.x,ev.y+rv.y);
vertex(ev.x+lv.x,ev.y+lv.y);
endShape(CLOSE);
}
//矢じりの大きさが一定
void DrawArrow(PVector sv, PVector ev, float l, float degree)
{
PVector rlv = PVector.sub(sv,ev);
PVector nrlv = rlv.normalize();
nrlv.mult(l);
//矢じりの左端と右端
PVector lv = RotDegree(nrlv, degree/2);
PVector rv = RotDegree(nrlv, -degree/2);
//PVector b = PVector.add(ev,nrlv);
line(sv.x,sv.y,ev.x,ev.y);
beginShape();
vertex(ev.x,ev.y);
vertex(ev.x+rv.x,ev.y+rv.y);
vertex(ev.x+lv.x,ev.y+lv.y);
endShape(CLOSE);
}
この記事が気に入ったらサポートをしてみませんか?