トランスフォーマー

processing4.0をもちいます。

適当に作った概念や名称が当然のように登場することがあります。
他所で使うとポカンとされます。

基本的な考え方。

変換対象(図形、パラメータ)と、変換変形操作を分離する。その後レゴみたいに組み立てる。

変換変形操作とは、平行移動、拡大縮小、回転などといったアフィン変換。補間、クリッピング、ブーリアン演算、あるいは3次元ベクトルを2次元平面に焼き付けるような座標変換、はたまた単純な1次元パラメータをちょん切るようなクランプ処理。そういうのひっくるめた全部です。

また、以下のものごともひっくるめて捉えております。

Transformer

Transformer(変形変換対象を有する)
TransformerTarget(被変変換形対象となるパラメータを有する)

以下はTransformerの変形対象となる単純な図形の例。

interface HasCoordinates{List<PVector> GetCoordinates();}
interface IGeometry2D extends IGeometry,HasCoordinates{}
interface IVectorRectGeometry2D extends IGeometry{PVector GetV1();PVector GetV2(); PVector GetV3(); PVector GetV4();}


class VectorRect2D implements IVectorRectGeometry2D, IGeometry2D
{
 List<PVector> GetCoordinates()
 {
   return List.of(V1,V2,V3,V4);
 }
 
 PVector V1;
 PVector V2;
 PVector V3;
 PVector V4;
 PVector GetV1(){return this.V1;}
 PVector GetV2(){return this.V2;}
 PVector GetV3(){return this.V3;}
 PVector GetV4(){return this.V4;}
 
 VectorRect2D(PVector v1, PVector v2, PVector v3, PVector v4)
 {
   this.V1 = v1.copy();this.V2 = v2.copy();
   this.V3 = v3.copy();this.V4 = v4.copy();
 }
 VectorRect2D(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4)
 {
   this.V1 = new PVector(x1,y1);this.V2 = new PVector(x2,y2);
   this.V3 = new PVector(x3,y3);this.V4 = new PVector(x4,y4);   
 }  
}

Rotater,Rotor

ほとんど抽象化していないTransformerの単純な例。
変換対象の参照を保持し、Updateを回すことで変形、即座に反映する。

回転だけなら回転原点と単位回転角のみ必要。その他のパラメータはオプション。あったほうが楽だが、ごてごてつけてくとどんどん面倒になる。

//参照TargetがGeometry2DであればUpdate毎に回転させる
//このRotater自身もTransformerによってパラメータを調整可能
class Geometry2DRotater implements ITransformer, ITransformerTarget, HasRadian, HasOrigin
{
 ITransformerTarget target;
 
 PVector origin;//回転原点
 PVector GetOrigin(){return this.origin;}
 void SetOrigin(PVector v){this.origin.set(v);}
 
 float init_unit_radian;//option
 float unit_radian;
 float GetRadian(){return this.unit_radian;}
 void SetRadian(float val){this.unit_radian=val;}
 
 float accum_radian;  //option 外付けのLoggerにしても良いもの
   
 Geometry2DRotater(ITransformerTarget target_, PVector origin_, float radian_)
 {
   this.target=target_;
   this.origin = origin_.copy();
   this.init_unit_radian=radian_;
   this.unit_radian=radian_;
 }
 
 void Update()
 {
   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.RotRadianRef(g2d.GetCoordinates(), this.origin, this.unit_radian);
   this.accum_radian+=unit_radian;
 }
}

  static 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;
 }

Update内の変形処理は関数型インターフェース、あるいはデリゲート、あるいは関数ポインタなどとして換装可能にすべきもの。

   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.RotRadianRef(g2d.GetCoordinates(), this.origin, this.unit_radian);
   this.accum_radian+=unit_radian;


Modifier

Update毎にターゲットのパラメータを修正する。
パラメータを1次の幾何と見るならModifierも次元が低いだけのTransformerである。

//まだ抽象化できる
interface IScalarOperation{float exe(float v1, float v2);}
interface IScalarGetter{float get(ITransformerTarget target);}
interface IScalarSetter{void set(ITransformerTarget target, float value);}

class ScalarModifier implements IModifier, ITransformerTarget
{
 //最大限に抽象化
 //ここでやってることを2D変形に適用したい
 
 ITransformerTarget target;
 IScalarGetter getter;//修正対象のパラメータを取得する
 IScalarSetter setter;//修正対象のパラメータを適用する
 
 IScalarOperation operation;
 
 float value;  
 
 ScalarModifier(
   ITransformerTarget target_, IScalarGetter getter_, IScalarSetter setter_,
   IScalarOperation operation_, float value_)
 {
   target=target_;
   setter=setter_;
   getter=getter_;
   operation=operation_;
   value=value_;
 }
   
 void Update()
 {
   //Modifier Method(target,getter,setter,operation,value)
   setter.set(target, operation.exe(getter.get(target), this.value));
 }
}

この段階で変換対象、その変換対象から修正したいパラメータを取り出す方法、その設定方法、変更時の二項演算などが設定可能。

設定可能な二項演算とは+,-,*,/,%などが想定されるが、powでもlogでもシグネチャ(戻り値と引数の型)さえ揃えば何でもよい。

Modifier設定例

//参考用
ScalarModifier MakeRadianModifier(ITransformerTarget target_, IScalarOperation op, float dvalue_)
{
 if(!(target_ instanceof HasRadian)){return null;}  
 var sm = new ScalarModifier(
   target_,
   ((tar)->((HasRadian)tar).GetRadian()),        //getter
   ((tar,val)->((HasRadian)tar).SetRadian(val)),//setter
   op,//((v1,v2)->v1+v2)など,//operation
   dvalue_);
 return sm;
}
ScalarModifier MakeMinModifier(ITransformerTarget target_, IScalarOperation op, float dvalue_)
{
 if(!(target_ instanceof HasMin)){return null;}  
 var sm = new ScalarModifier(
   target_,
   ((tar)->((HasMin)tar).GetMin()),        //getter
   ((tar,val)->((HasMin)tar).SetMin(val)),//setter
   op, dvalue_);
 return sm;
}  
ScalarModifier MakeMaxModifier(ITransformerTarget target_, IScalarOperation op, float dvalue_)
{
 if(!(target_ instanceof HasMax)){return null;}  
 var sm = new ScalarModifier(
   target_,
   ((tar)->((HasMax)tar).GetMax()),        //getter
   ((tar,val)->((HasMax)tar).SetMax(val)),//setter
   op, dvalue_);
 return sm;
}  

この例はパラメータを狙い打っているため、新しいパラメータが現れる度にクラスなりインターフェースなりが増えていく。ゆえに文字列と数値のキーペアにすることなどが考えられるが、それはそれで面倒であるため、心が折れるまではパラメータ毎にインターフェースを増殖させる。

Satisfier

if文の抽象化。条件を満たした場合発火する。以下の例では発火した後に関数を起動しているが、抽象化したおすなら発火したことをイベントなりで発行する。あるいはSatisfier:Reactorのペアで保持するなどする。

以下の例においては、条件を満たした場合のみ発火するTransformerである。

//functional
interface ISatisfierMethod
{
 boolean Satisfy(ITransformerTarget target);
}
//functional
interface TransformMethod
{
 void Transform(ITransformerTarget target);
}

//このSatisfierは条件を満たした場合、
//targetに対して変換(Transform)ないし修正(Modify)を実行するのでTransformerである
class Satisfier implements ITransformer, ISatisfier
{
 ITransformerTarget target;
 
 ISatisfierMethod satisfy_method;
   
 ITransformerTarget reaction_target;
 
 //Transformerを保持する場合
 //ITransformer reactor;
 //メソッドのみ保持する場合
 TransformMethod reaction;
 
 Satisfier(
   ITransformerTarget target_, ISatisfierMethod satisfy_method_, 
   ITransformerTarget reaction_target_, TransformMethod reaction_)
 {
   target=target_;
   satisfy_method=satisfy_method_;
   reaction_target=reaction_target_;
   reaction=reaction_;
 }
 
 boolean Satisfy(){return satisfy_method.Satisfy(target);}
 
 void Update()
 {
   if(this.Satisfy())
   {
     //reactor.Transform(reaction_target);//Transformerの場合 
     reaction.Transform(reaction_target);      
   }
 }
}

その設定例

まだ極めて強引

SatisfierMethod(関数型インターフェース)は引数の数および種類だけ新規に作成するか、SatisfierArgsでまとめるなりする。関数に必要なパラメータを引数で受けないのであればラムダで直に書く。

//設定例
Satisfier MakeStopper(Geometry2DRotater rotater, ScalarModifier modifier)
{
 float threshold = 0;
 var satisfier = new Satisfier(
   rotater,
   ((target)->((Geometry2DRotater)target).unit_radian<=threshold ? true : false),
   modifier,
   ((rtarget)->{
     ((ScalarModifier)rtarget).value=0;
     //((ScalarModifier)modifier).value=0;//上と同じ意味
     ((Geometry2DRotater)rotater).unit_radian=0;//直に書き換え
     })
     );
 return satisfier;  
}

Satisfier MakeBackReverse(Geometry2DRotater rotater, ScalarModifier modifier)
{
 float threshold = 10;
 var satisfier = new Satisfier(
   rotater,
   ((target)->{
     if(((Geometry2DRotater)target).accum_radian>=threshold)
     {return true;}
     else if (((Geometry2DRotater)target).accum_radian<=-threshold)
     {
       return true;
     }
     else
     {return false;}
   }),
   modifier,
   ((rtarget)->{
     ((ScalarModifier)modifier).value=-((ScalarModifier)modifier).value;//直に書き換え
     ((Geometry2DRotater)rotater).accum_radian=0;//直に書き換え
     ((Geometry2DRotater)rotater).unit_radian=((Geometry2DRotater)rotater).init_unit_radian;
     })
     );
 return satisfier;  
}


Translater

Rotaterと同レベルのTansformer。

Geometryを平行移動させる。

class Geometry2DTranslaterDV implements ITransformer, ITransformerTarget
{
 ITransformerTarget target;
 
 PVector dv;  
 
 //指標の一つとして
 float accum_dv_lenth = 0; 
   
 Geometry2DTranslaterDV(ITransformerTarget target_, PVector dv_)
 {
   target=target_;
   dv=dv_;
 }
 
 void Update()
 {
   //TransformMethodとして抽象化されるべきもの
   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.AddRef(g2d.GetCoordinates(), this.dv);
   this.accum_dv_lenth+=dv.mag();    
 }
}

Geometryを目的位置に移動させる場合

class Geometry2DTranslaterTo implements ITransformer, ITransformerTarget
{
 ITransformerTarget target;
 
 PVector to;
 
 float velocity = 10;
 
 PVector dv()
 {
   //if(!(target instanceof HasOrigin)){return null;}    
   //var ho = (HasOrigin)target;   
   //return to.copy().sub(ho.GetOrigin()).normalize().mult(velocity);
   
   if(!(target instanceof IGeometry2D)){return null;}    
   var g2d = (IGeometry2D)target;    
   return to.copy().sub(g2d.GetCoordinates().get(0)).normalize().mult(velocity);
 }
 
 //指標の一つとして
 float accum_dv_lenth = 0; 
 
 Geometry2DTranslaterTo(ITransformerTarget target_, PVector to_, float velocity_)
 {
   target=target_;
   to=to_;
   velocity=velocity_;
 }
 
 void Update()
 {
   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.AddRef(g2d.GetCoordinates(), this.dv());
   this.accum_dv_lenth+=this.dv().mag();    
 }
}

Outputer,Popupper,出力子

出力はするが、変形対象を保持しないのでTransformerではない。
しかしパラメータを有するならばTansformerTargetになりうる。

自ら可変なパラメータ。floatで保持する代わりに乱数を取得したい場合に用いる。

(0-1)出力、(0-2*PI)出力
パラメータを固定して単純化したもの。出力のみ。
パラメータを有しないのでTransformerTargetではない。


//->(0,1)出力
class RandomRadianGenerator implements IOutputer
{
 //Transformer(変形対象を有する)でも
 //TransformerTarget(被変形対象パラメータを有する)でもない
 //Updateもない(Updateでメンバにoutputを記録というやり方もできるが)
 //極度に単純化
 
 IOutput Output()
 {
   return new OutputValue(StaticRandom(0,2*PI));
 }
}

//->(0,2*PI)出力
class RandomTGenerator implements IOutputer
{
 IOutput Output()
 {
   return new OutputValue(StaticRandom());
 }      
}

Inputなる先を見据えすぎた関数がついているが使用しておらず。

(min-max)出力の例。このOutputterは出力のレンジを調整できるのでTransformerの変換対象になりえる。


interface IRandomArgs extends IInput{}
class RandomArgs implements IRandomArgs
{
 float min;
 float max;
 
 RandomArgs(float min_, float max_)
 {
   min=min_;
   max=max_;
 }
}

class RandomGenerator implements ITransformerTarget, IInputer, IOutputer
{
 //それなりに抽象化は効く
 Ifxy fxy;
   
 //input
 float min;
 float max;
 //output
 //float output;
 
 RandomGenerator(float min_, float max_)
 {
   this.min=min_;
   this.max=max_;      
   this.fxy = ((x,y)->StaticRandom(x,y));
 }
 
 void Input(IInput input)
 {
   if(!(input instanceof IRangeGeometry)){return;}
   var minmax = (IRangeGeometry)input;
   this.Input(minmax.GetMin(),minmax.GetMax());
 }
 
 void Input(float min_, float max_)
 {
   this.min=min_;
   this.max=max_;
 }
 
 IOutput Output()
 {
   return new OutputValue(fxy.exe(min,max));
 }
}

次の例。ランダムにベクトルを生成し、領域内ならば返す。
領域は凹凸問わない。
凸形ならば重心を用いたもう少し簡単な領域内ベクトルを生成できる。
だいぶ試験的な意味合いが強く、whileでバグる可能性がとてもある。

class RegionVGenerator implements ITransformerTarget, IInputer, IOutputer
{
 IGeometry2D region;

 RegionVGenerator(IGeometry2D geometry)
 {
   region = geometry;
 }
 
 void Input(IInput input)
 {
   if(!(input instanceof IGeometry2D)){return;}
   var geometry = (IGeometry2D)input;
   this.Input(geometry);
 }
 
 void Input(IGeometry2D geometry)
 {
   region = geometry;
 }
 void Input(IGeometryMaker maker, IGeometryArgs args)
 {
   region = (IGeometry2D)maker.Make(args);
 }
 
 IOutput Output()
 {
   return new OutputVector2D(RandomRegionV(this.region));
 }  
 
}

static PVector RandomRegionV(IGeometry2D region)
{
 var coordinates = region.GetCoordinates();
 
 //PVector
 var min_x = coordinates.stream().min((v1,v2)->(int)(v1.x-v2.x)).get();
 var max_x = coordinates.stream().max((v1,v2)->(int)(v1.x-v2.x)).get();
 var min_y = coordinates.stream().min((v1,v2)->(int)(v1.y-v2.y)).get();
 var max_y = coordinates.stream().max((v1,v2)->(int)(v1.y-v2.y)).get();  
 
 boolean in_region = false;
 while(in_region)
 {
   PVector v = new PVector(StaticRandom(min_x.x,max_x.x),StaticRandom(min_y.y,max_y.y));
   if(IntersectionMethod.CrossingNumber(coordinates, v)){return v;}    
 }
 return null;
}

  //参考:https://www.nttpc.co.jp/technology/number_algorithm.html
 static boolean CrossingNumber(List<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

ランダムfloat*2によるベクトル生成。矩形の範囲内で生成される。

class RandomVGenerator implements ITransformerTarget, IInputer, IOutputer
{
 //input
 float min;
 float max;//radius
 
 RandomVGenerator(float min_, float max_)
 {
   this.min=min_;
   this.max=max_;
 }    
 
 void Input(IInput input)
 {
   if(!(input instanceof IRangeGeometry)){return;}
   var minmax = (IRangeGeometry)input;
   this.Input(minmax.GetMin(),minmax.GetMax());
 }
 
 void Input(float min_, float max_)
 {
   this.min=min_;
   this.max=max_;
 }
   
 IOutput Output()
 {
   return new OutputVector2D(new PVector(StaticRandom(min,max),StaticRandom(min,max)));
 }      
}


RandomTranslater

パラメータにOutputerを持ち、ランダムウォーク的なことをさせるTranslater

class Geometry2DTranslaterRandom implements ITransformer, ITransformerTarget
{
 ITransformerTarget target;
 
 RegionDirectionGenerator dvgen;
 
 //指標の一つとして
 float accum_dv_lenth = 0; 
   
 Geometry2DTranslaterRandom(ITransformerTarget target_, float max)
 {
   target=target_;
   dvgen=new RegionDirectionGenerator(max);
 }
 Geometry2DTranslaterRandom(
   ITransformerTarget target_,
   float radian_min, float radian_max, float len_min, float len_max)
 {
   target=target_;
   dvgen=new RegionDirectionGenerator(radian_min,radian_max,len_min,len_max);
 }  
 
 void Update()
 {
   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.AddRef(g2d.GetCoordinates(), this.dvgen.OutputV());
   this.accum_dv_lenth+=this.dvgen.OutputV().mag();    
 }
}


RotaterのOriginパラメータを遅延計算にする。

このOriginはGetOriginMethod型インターフェースとしてさらに抽象化しても良いし、乱数出力のOutputterを接続しても良い。また、Outputterが与えられた幾何から重心を出力するようなものとして整備されても良い。

class Geometry2DCogRotater implements ITransformer, ITransformerTarget, HasRadian
{
 ITransformerTarget target;

 //遅延
 PVector CalcuOrigin()
 {
   if(!(target instanceof IGeometry2D)){return null;}    
   var g2d = (IGeometry2D)target;
   var cog = VectorMethod.CenterOfGravity(g2d.GetCoordinates());
   return cog;
 }
 
 float init_unit_radian;//option
 float unit_radian;
 float GetRadian(){return this.unit_radian;}
 void SetRadian(float val){this.unit_radian=val;}
   
 float accum_radian;  //option 外付けのLoggerにしても良いもの
   
 Geometry2DCogRotater(ITransformerTarget target_, float radian_)
 {
   this.target=target_;
   this.init_unit_radian=radian_;
   this.unit_radian=radian_;    
 }
 
 void Update()
 {
   if(!(target instanceof IGeometry2D)){return;}    
   var g2d = (IGeometry2D)target;
   VectorMethod.RotRadianRef(g2d.GetCoordinates(), this.CalcuOrigin(), this.unit_radian);
   this.accum_radian+=unit_radian;  
 }  
}

ここいらあたりまでをしぬ程抽象化することを考える。それはまた次回である。

ここいらまでのことは以下のような使い方をされる。updaterはdrawループで回される。図形の表示用の関数はまた別に自分で用意しなければならない。

  //①,2とセット
 //rotater = new Geometry2DRotater(rect,new PVector(width/2,height/2), 2*PI/36);
 //updater.add(rotater);  
 
 //②ModifierおよびSatisfierテスト
 //modifier = MakeRadianModifier(rotater,((v1,v2)->v1-v2),2*PI/360);
 //updater.add(modifier);
 //②
 //var stopper = MakeStopper(rotater,rm);
 //updater.add(stopper);
 //var backreverse = MakeBackReverse(rotater,modifier);
 //updater.add(backreverse);

 //③Translaterテスト
 //var tlto = new Geometry2DTranslaterTo(rect,new PVector(width/2,height/2),1);
 //updater.add(tlto);
 
 //④Random値Translaterテスト
 var tlr = new Geometry2DTranslaterRandom(rect,0,2*PI,0,10);
 updater.add(tlr);
 
 //⑤Origin遅延
 var rotaterCog = new Geometry2DRotater(rect,new PVector(width/2,height/2), 2*PI/36);
 rotaterCog.rotater_method=rotaterCog::RotFromCog;
 updater.add(rotaterCog); 

この記事が気に入ったらサポートをしてみませんか?