重心座標の恐怖


重心座標と逆変換

三角形の内側の座標v[x,y]は[0-1]のパラメータ[t1,t2,t3]を3つ用いることで表せます。すなわち入力[t1,t2,t3]であって、出力v[x,y]です。正確に言うと三角形を構成する座標値V1,V2,V3も必要ですが、これは既知、すでにあきらかであるとします。つまり画面上に最初からあれということです。

画像1

画像2

重心座標は入力v[x,y]として、出力[t1,t2,t3]とすることもできます。wikipediaを丸写ししたのがこちら。

画像3

これをコードにしてみるとこんな感じ。言語によってはもう少し綺麗に書けるでしょう。

※ちなみにですがcopy()なんちゃらは参照をたたっ切るための操作です。processingにおかれましてはv1.mult(v2)とPVector.mult(v1,v2)には明確な違いがあり、それはv1インスタンスに対する操作であるか、v1,v2を用いた新たなベクトルの生成であるかの違いとなります。本来PVector.mult(v1,v2)と書くべきものを、タイピングのめんどくささからcopy()にしているだけです。

//重心座標(t1,t2,t3)->(x,y)
PVector Barycentric(PVector v1, PVector v2, PVector v3, float t1, float t2, float t3)
{
 PVector num = v1.copy().mult(t1).add(v2.copy().mult(t2)).add(v3.copy().mult(t3));
 return num.div(t1+t2+t3);  
}

PVector Barycentric(PVector v1, PVector v2, PVector v3, PVector t)
{
 PVector num = v1.copy().mult(t.x).add(v2.copy().mult(t.y)).add(v3.copy().mult(t.z));
 return num.div(t.x+t.y+t.z);  
}

//重心座標逆変換
PVector InvBarycentric(PVector v1, PVector v2, PVector v3, PVector v)
{
 float num1 = (v2.y-v3.y)*(v.x-v3.x)+(v3.x-v2.x)*(v.y-v3.y);
 float denom1 = (v2.y-v3.y)*(v1.x-v3.x)+(v3.x-v2.x)*(v1.y-v3.y);
 float t1 = num1/denom1;
 
 float num2 = (v3.y-v1.y)*(v.x-v3.x)+(v1.x-v3.x)*(v.y-v3.y);
 float denom2 = (v2.y-v3.y)*(v1.x-v3.x)+(v3.x-v2.x)*(v1.y-v3.y);
 float t2 = num2/denom2;
 
 float t3 = 1-t1-t2;
 
 return new PVector(t1,t2,t3);  
}

一度[t1,t2,t3]に変換してしまえば、三角形中にあるいかなる座標も別の三角形にいい感じに写せます。

画像4

さっそく強引に応用します。以下では4つの座標からなる四角形が渡された時、その内1辺(の半分)を使って正方形のテクスチャソース空間を作り、その空間内の座標を最初に入力された4つの座標の4隅に張り込んでます。

当然のことながら、テクスチャ空間に相当する部分はあらかじめ別でつくっといてもよい物です。

void DrawCircle(PVector v1, PVector v2, PVector v3, PVector v4, float resolution)
{  
 PVector texture_edge = v2.copy().sub(v1);
 PVector texture_v1 = v1.copy();
 PVector texture_v2 = v2.copy();
 PVector texture_v3 = v2.copy().add(TurnR(texture_edge));
 PVector texture_v4 = v1.copy().add(TurnR(texture_edge));
 PVector texture_cv = GetIntersectionSeg2Seg(texture_v1,texture_v3,texture_v2,texture_v4);
 float texture_radius = PVector.lerp(texture_v1,texture_v2,0.5).sub(texture_cv).mag();
 
 PVector texture_em1 = PVector.lerp(texture_v1,texture_v2,0.5);
 PVector texture_em2 = PVector.lerp(texture_v2,texture_v3,0.5);
 PVector texture_em3 = PVector.lerp(texture_v3,texture_v4,0.5);
 PVector texture_em4 = PVector.lerp(texture_v4,texture_v1,0.5);    
 PVector rv1 = texture_em1.copy().sub(texture_cv).normalize().mult(texture_radius);   
 
 ArrayList<PVector> tex1 = new ArrayList<PVector>();
 for(float i = 0; i<PI/2; i+=resolution)
 {
   PVector temp1 = RotRadian(rv1, i);  
   tex1.add(texture_cv.copy().add(temp1));
 } 
 
 //↑↑↑ここまでテクスチャ役の座標群作る
 //ここからテクスチャ役の座標群張り込む↓↓↓

 PVector edge_mv1 = PVector.lerp(v1,v2,0.5);
 PVector edge_mv2 = PVector.lerp(v2,v3,0.5);
 PVector edge_mv3 = PVector.lerp(v3,v4,0.5);
 PVector edge_mv4 = PVector.lerp(v4,v1,0.5);

 ArrayList<PVector> target1 = new ArrayList<PVector>();
 ArrayList<PVector> target2 = new ArrayList<PVector>();
 ArrayList<PVector> target3 = new ArrayList<PVector>();
 ArrayList<PVector> target4 = new ArrayList<PVector>();
 for(int i = 0; i<tex1.size(); i++)
 {
   PVector tar1 = tex1.get(i);   
   target1.add(Barycentric(edge_mv1,v2,edge_mv2,InvBarycentric(texture_em1,texture_v2,texture_em2,tar1)));
   target2.add(Barycentric(edge_mv2,v3,edge_mv3,InvBarycentric(texture_em1,texture_v2,texture_em2,tar1)));
   target3.add(Barycentric(edge_mv3,v4,edge_mv4,InvBarycentric(texture_em1,texture_v2,texture_em2,tar1)));
   target4.add(Barycentric(edge_mv4,v1,edge_mv1,InvBarycentric(texture_em1,texture_v2,texture_em2,tar1)));    
 }  
 
 DrawLiner(target1);
 DrawLiner(target2);    
 DrawLiner(target3);
 DrawLiner(target4);
}

↑↑↑上で使ってる関数↓↓↓

class VectorLine2D
{
 PVector SV;
 PVector EV;
 
 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);
 }  
}

PVector GetIntersectionSeg2Seg(PVector sv1, PVector ev1, PVector sv2, PVector ev2)
{
 return GetIntersectionSeg2Seg(new VectorLine2D(sv1,ev1), new VectorLine2D(sv2, ev2));
}

PVector GetIntersectionSeg2Seg(VectorLine2D seg1, VectorLine2D seg2)
{
 if(IsIntersectSeg2Seg(seg1,seg2))
 {
   PVector lv1 = GetLV(seg1);
   PVector lv2 = GetLV(seg2);
   //toSeg2
   PVector v = PVector.sub(seg2.SV, seg1.SV);
   
   //lv1の交点までのt
   float t1 = (float)(Cross(v, lv2)/ Cross(lv1, lv2));    
   //lv2の交点までのt
   //float t2 = (float)(Cross(v, lv1)/ Cross(lv1, lv2));

   return PVector.add(seg1.SV, PVector.mult(lv1, t1));
 }
 return null;
}
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);
}
float Epsilon = 0.0001;
PVector GetLV(VectorLine2D line)
{
 PVector lv = PVector.sub(line.EV,line.SV);
 //線分が短すぎると計算が飛ぶ
 if(Math.abs(lv.x)<Epsilon){lv.x=0;}
 if(Math.abs(lv.y)<Epsilon){lv.y=0;}
 return lv;
}
public float Cross(PVector v1, PVector v2)
{
   //+ならv2がv1の右側
   return (v1.x * v2.y - v1.y * v2.x);
}
void DrawLiner(ArrayList<PVector> vertices)
{
 for(int i = 0; i<vertices.size()-1; i++)
 {
   PVector sv = vertices.get(i);
   PVector ev = vertices.get(i+1);
   line(sv.x, sv.y, ev.x, ev.y);
 }
}


画像5

画像6



図は重心座標(t1,t2,t3)と三角形の関係。
ゲームプログラミングのためのリアルタイム衝突判定p46参照
ただし座標の配置が違う為、indexはズラしております。

画像8

画像9

テクスチャマッピング

同じ様なのにテクスチャマッピングがあります。

参考図書は以下の本のp127

重心座標では三角形は3つの位置ベクトルで表されましたが、テクスチャマッピングの場合は原点v0と、そこから射出されるベクトルU,Vで表されます。比は[t1,t2,t3]から[u,v]になるため、一つ減ります。

PVector UV(PVector v0, PVector U, PVector V, PVector uv)
{
 return UV(v0,U,V,uv.x,uv.y);
}
PVector UV(PVector v0, PVector U, PVector V, float u, float v)
{
 float x = u*U.x+v*V.x+v0.x;
 float y = u*U.y+v*V.y+v0.y;
 return new PVector(x,y);
}

PVector InvUV(PVector v0, PVector U, PVector V, PVector xy)
{
 return InvUV(v0,U,V,xy.x,xy.y);
}
PVector InvUV(PVector v0, PVector U, PVector V, float x, float y)
{
 float num1 = (x-v0.x)*V.y-(y-v0.y)*V.x;
 float num2 = (y-v0.y)*U.x-(x-v0.x)*U.y;
 
 float denom = U.x*V.y-V.x*U.y;
 
 float u = num1/denom;
 float v = num2/denom;
 
 return new PVector(u,v);
}

使い方は重心座標と同じ。ただしuv座標生成時の関数への入力は位置座標ではない。

void DrawCircleUV(PVector v1, PVector v2, PVector v3, PVector v4, float resolution)
{
 PVector texture_edge = v2.copy().sub(v1);
 PVector texture_v1 = v1.copy();
 PVector texture_v2 = v2.copy();
 PVector texture_v3 = v2.copy().add(TurnR(texture_edge));
 PVector texture_v4 = v1.copy().add(TurnR(texture_edge));
 PVector texture_cv = GetIntersectionSeg2Seg(texture_v1,texture_v3,texture_v2,texture_v4);
 float texture_radius = PVector.lerp(texture_v1,texture_v2,0.5).sub(texture_cv).mag();
 
 PVector texture_em1 = PVector.lerp(texture_v1,texture_v2,0.5);
 PVector texture_em2 = PVector.lerp(texture_v2,texture_v3,0.5);
 PVector texture_em3 = PVector.lerp(texture_v3,texture_v4,0.5);
 PVector texture_em4 = PVector.lerp(texture_v4,texture_v1,0.5);    
 PVector rv1 = texture_em1.copy().sub(texture_cv).normalize().mult(texture_radius);   
 PVector rv2 = texture_em2.copy().sub(texture_cv).normalize().mult(texture_radius); 
 PVector rv3 = texture_em3.copy().sub(texture_cv).normalize().mult(texture_radius); 
 PVector rv4 = texture_em4.copy().sub(texture_cv).normalize().mult(texture_radius);   
 
 ArrayList<PVector> tex1 = new ArrayList<PVector>();
 ArrayList<PVector> tex2 = new ArrayList<PVector>();
 ArrayList<PVector> tex3 = new ArrayList<PVector>();
 ArrayList<PVector> tex4 = new ArrayList<PVector>();
 for(float i = 0; i<PI/2; i+=resolution)
 {
   PVector temp1 = RotRadian(rv1, i);
   PVector temp2 = RotRadian(rv2, i);
   PVector temp3 = RotRadian(rv3, i);
   PVector temp4 = RotRadian(rv4, i);    
   tex1.add(texture_cv.copy().add(temp1));
   tex2.add(texture_cv.copy().add(temp2));   
   tex3.add(texture_cv.copy().add(temp3));
   tex4.add(texture_cv.copy().add(temp4));
 }

 //target
 PVector edge_mv1 = PVector.lerp(v1,v2,0.5);
 PVector edge_mv2 = PVector.lerp(v2,v3,0.5);
 PVector edge_mv3 = PVector.lerp(v3,v4,0.5);
 PVector edge_mv4 = PVector.lerp(v4,v1,0.5);

 ArrayList<PVector> target1 = new ArrayList<PVector>();
 ArrayList<PVector> target2 = new ArrayList<PVector>();
 ArrayList<PVector> target3 = new ArrayList<PVector>();
 ArrayList<PVector> target4 = new ArrayList<PVector>();
 for(int i = 0; i<tex1.size(); i++)
 {
   PVector tar1 = tex1.get(i);
   PVector tar2 = tex2.get(i);
   PVector tar3 = tex3.get(i);
   PVector tar4 = tex4.get(i);
   
   target1.add(UV(edge_mv1,v2.copy().sub(edge_mv1),edge_mv2.copy().sub(edge_mv1),InvUV(texture_em1,texture_v2.copy().sub(texture_em1),texture_em2.copy().sub(texture_em1),tar1)));
   target2.add(UV(edge_mv2,v3.copy().sub(edge_mv2),edge_mv3.copy().sub(edge_mv2),InvUV(texture_em2,texture_v3.copy().sub(texture_em2),texture_em3.copy().sub(texture_em2),tar2)));
   target3.add(UV(edge_mv3,v4.copy().sub(edge_mv3),edge_mv4.copy().sub(edge_mv3),InvUV(texture_em3,texture_v4.copy().sub(texture_em3),texture_em4.copy().sub(texture_em3),tar3)));
   target4.add(UV(edge_mv4,v1.copy().sub(edge_mv4),edge_mv1.copy().sub(edge_mv4),InvUV(texture_em4,texture_v1.copy().sub(texture_em4),texture_em1.copy().sub(texture_em4),tar4)));    
 }  
 DrawLiner(target1);
 DrawLiner(target2);    
 DrawLiner(target3);
 DrawLiner(target4);
}

画像7




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