描画関数抽象化ver1.0

processing4を用います。

processingの基本的な描画関数。

line(x1, y1, x2, y2);
circle(x, y, extent);

それをループを用いて使用する場合の例。

 static void DrawPoints(PGraphics pg, Collection<PVector> points, float radius)
 {
   for(PVector v : points)
   {
       pg.ellipse(v.x,v.y,radius,radius);
   }
 }

//折線
 static void DrawLiner(PGraphics pg, List<PVector> vertices)
 {
   for(int i = 0; i<vertices.size()-1; i++)
   {
     PVector sv = vertices.get(i);
     PVector ev = vertices.get(i+1);
     pg.line(sv.x, sv.y, ev.x, ev.y);
   }
 }
 
 //閉多角形
 static void DrawLinerClose(PGraphics pg, List<PVector> vertices)
 {  
   if(vertices==null){return;}  
   for(int i = 0; i<vertices.size(); i++)
   {    
     PVector sv = vertices.get(i);
     PVector ev = vertices.get((i+1)%vertices.size());
     pg.line(sv.x, sv.y, ev.x, ev.y);
   }  
 }
 

この辺りを抽象化することを考える。
staticは関数型インターフェースを利用しやすくするために用いてある。関数型インターフェースとは、他の言語でいう関数ポインタやデリゲートに相当し、関数を変数にブチ込んで換装可能にするタイプのやつである。
staticとprocessingは食い合わせがあまり良くない。staticな関数内ではlineだのcircleだのrandomだのnoiseだのはそのままでは使えなくなる(これらの関数はstaticで定義されてない)。この辺りを使うためには通常processingが隠しておいてくれているもろもろを全てほじくり返していく必要があり、やり過ぎるとprocessingでやる意味自体が薄れていく。

line,circle,ellipseなどはprocessingのPGraphics内部で定義され、javaのGraphics2Dをラップして(内部でpublic Graphics2D g2;として保持して)使用している。javaのGraphics2DはOSと相談してビデオカードに処理を任せた上でディスプレイやらプリンタやらに描画画像を出力する。

詰まる所プログラマは使用している言語及びOS、ないしAPIに規定された描画関数以上のことはできないのであるから、processingにおいてはPGraphicsを引数に受けてしまえばprocessing上で可能なだいたいのことはできる。

通常processingでlineだのcircleだのはメインとなるPGraphicsをあらかじめnewして保持して使っているのであり、これを任意の関数に渡したい時はthis.getGraphics()などして取得する。


抽象化してみた結果

static class StaticLooting
{
 //sampling:全体集合から部分集合を生成
 //unitting:部分集合から最小draw単位を生成
 //draw:
 static void DrawLine(PGraphics pg, List<PVector> source)
 {
   LinerStructure(pg, source, StaticLooting::SamplingLine, StaticLooting::PickNext, StaticLooting::DrawLine);    
 }
 static void DrawLineClose(PGraphics pg, List<PVector> source)
 {
   LinerStructure(pg, source, StaticLooting::SamplingAll, StaticLooting::PickNext, StaticLooting::DrawLine);    
 }
  static void DrawDash(PGraphics pg, List<PVector> source)
 {
   LinerStructure(pg, source, StaticLooting::SamplingSkip1, StaticLooting::PickNext, StaticLooting::DrawLine); 
 }     
}

Structure

LinerStructureはforループ構造のことである。

  static void LinerStructure(PGraphics pg, List<PVector> source, SamplingMethodS sampling, UnittingMethodSi unitting, DrawMethod drawer)
 {
   var sub = sampling.exe(source);    
   for(int i = 0; i<sub.size(); i++)
   {
     IDrawingUnit vert = unitting.exe(source,source.indexOf(sub.get(i)));
     drawer.exe(pg, vert);
   }    
 }

Sampling,Looting

SamplingMethodは与えられたListからループする要素を選択する。

  interface SamplingMethodS
 {
   List<PVector> exe(List<PVector> source);
 }

これの意味する所はループの形式を全部、単純な、

for(int i = 0; i<sub.size(); i++)

という形式に直すということである。
これは以下のような些末だが重要な制御の違いを抽象化する。

for(int i = 0; i<sub.size()-1; i++)
for(int i = 0; i<sub.size(); i+=2)

i<List.size();とi<List.size()-1の差は、折線か閉多角形かの違うを生む。すなわち始点と終点の間を繋ぐか繋がないかである。
i++とi+=2の差は破線を生む。

SamplingMethodの例

  //全部返す
 static List<PVector> SamplingAll(List<PVector> source)
 {
   return source.stream().map(v->v.copy()).collect(Collectors.toList());
 }

 //末尾以外返す
 static List<PVector> SamplingLine(List<PVector> source)
 {
   if(source==null||source.size()<=0){return null;}    
   var ret = source.stream().map(v->v.copy()).collect(Collectors.toList());
   if(source.size()<=1){return ret;}   
   ret.remove(ret.size()-1);
   return ret;
 }
 
 //1個飛ばしで返す
 static List<PVector> SamplingSkip1(List<PVector> source)
 {
   if(source==null||source.size()<=0){return null;}    
   var ret = new ArrayList<PVector>();    
   for(int i = 0; i<source.size(); i+=2)
   {
     ret.add(source.get(i).copy());
   }
   return ret;
 }  

Unitting

UnittingMethodによってprocessing描画関数の引数を生成する

  interface UnittingMethodSi
 {
   IDrawingUnit exe(List<PVector> source, int index);
 }

例えば
line(PVector v1, PVector v2)
circle(PVector cv, float radius)
ellipse(PVector cv, float a, float b)
なる描画関数に必要な入力パラメータを生成する。必要なパラメータが、入力された最初の領域(ここではList<PVector>)から生成可能ならば、そのUnittingMethodの引数は最初に与えられた領域となる。

 static Vert2 PickNext(List<PVector> source, int index)
 {
   return new Vert2(source.get(index),source.get((index+1)%source.size()));
 }

この関数は

     PVector sv = vertices.get(i);
     PVector ev = vertices.get(i+1);

やら

     PVector v0 = vertices.get(i);
     PVector v1 = vertices.get(i+1);
     PVector v2 = vertices.get(i+2);     

やらを抽象化する。

線分や三角形を描画する場合、それが入力された最初の領域List<PVector>から生成されるのであれば、この関数は強力に働く。
逆に、『なんでもいいが、とりあえず半径20の円』というような場合、半径20なるパラメータを最初の領域から生成する手段がない場合、そのパラメータはUnitttingMethod内部でその場で作るか、引数から与える以外に方法はない。

UnittingMethodの引数は、描画に必要なパラメータを生成するのに十分な情報が必要となる。足らなければ引数を増やし、UnittingMethodを用いるStructure関数も新たに生成する。あるいはUnittingMethodArgsとして引数をまとめる。

  interface UnittingMethodSi
 {
   IDrawingUnit exe(List<PVector> source, int index);
 }

以下は描画関数に放り込まれる引数となるクラス。
List<PVector>,List<Float>なるを保持するクラスをつくればある程度まとめることもできる。

  interface IDrawingUnit{}
 
 //円
 static class Vert1t implements IDrawingUnit
 {
   PVector V0;
   float t0;
   Vert1t(PVector v0, float t)
   {
     this.V0=v0.copy();
     t0=t;
   }
 }
 //楕円
 static class Vert1tt implements IDrawingUnit
 {
   PVector V0;
   float t0;
   float t1;
   
   Vert1tt(PVector v0, float t0_, float t1_)
   {
     this.V0=v0.copy();
     t0=t0_;
     t1=t1_;
     
   }
 }
 
 static class Vert2 implements IDrawingUnit
 {
   PVector V0;PVector V1;
   Vert2(PVector v0, PVector v1){V0=v0.copy();V1=v1.copy();}
 }
 static class Vert3 implements IDrawingUnit
 {
   PVector V0;PVector V1;PVector V2;
   Vert3(PVector v0, PVector v1, PVector v2){V0=v0.copy();V1=v1.copy();V2=v2.copy();}    
 }
 static class Vert4 implements IDrawingUnit
 {
   PVector V0;  PVector V1;  PVector V2;  PVector V3;
   Vert4(PVector v0, PVector v1, PVector v2, PVector v3){V0=v0.copy();V1=v1.copy();V2=v2.copy();V3=v3.copy();}       
 }  
 static class VertL implements IDrawingUnit
 {
   List<PVector> Verts;
   VertL(List<PVector> verts)
   {
     Verts=verts.stream().map((v)->v.copy()).collect(Collectors.toList());
   }       
 }    

Drawing

描画関数の例。

  interface DrawMethod
 {
   void exe(PGraphics pg, IDrawingUnit unit);
 }
 

 static void DrawLine(PGraphics pg, IDrawingUnit unit)
 {
   if(!(unit instanceof Vert2)){return;}
   var vert2 = (Vert2)unit;
   pg.line(vert2.V0.x,vert2.V0.y,vert2.V1.x,vert2.V1.y);
 }   
 
 //Vert3
 static void DrawTri(PGraphics pg, IDrawingUnit unit)
 {
   if(!(unit instanceof Vert3)){return;}
   var vert3 = (Vert3)unit;
   pg.line(vert3.V0.x,vert3.V0.y,vert3.V1.x,vert3.V1.y);
   pg.line(vert3.V1.x,vert3.V1.y,vert3.V2.x,vert3.V2.y);
   pg.line(vert3.V2.x,vert3.V2.y,vert3.V0.x,vert3.V0.y);    
 }     
 static void FillTri(PGraphics pg, IDrawingUnit unit)
 {
   if(!(unit instanceof Vert3)){return;}
   var vert3 = (Vert3)unit;
   pg.beginShape();
   pg.vertex(vert3.V0.x,vert3.V0.y);
   pg.vertex(vert3.V1.x,vert3.V1.y);
   pg.vertex(vert3.V2.x,vert3.V2.y);    
   pg.endShape();
 }    
 
 //Vert4
 static void DrawSpline(PGraphics pg, IDrawingUnit unit)
 {
   if(!(unit instanceof Vert4)){return;}
   var vert4 = (Vert4)unit;
   var sp = SplineMethod.CalcuOpenSpline(List.of(vert4.V0,vert4.V1,vert4.V2,vert4.V3),3,0.1);
   DrawLine(pg,sp);
 }    
 static void DrawCyclicSpline(PGraphics pg, IDrawingUnit unit)
 {
   if(!(unit instanceof Vert4)){return;}
   var vert4 = (Vert4)unit;
   var sp = SplineMethod.CalcuCyclicSpline(List.of(vert4.V0,vert4.V1,vert4.V2,vert4.V3),3,0.1);
   DrawLine(pg,sp);
 }   





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