![見出し画像](https://assets.st-note.com/production/uploads/images/47117243/rectangle_large_type_2_fb68b37f79e1377138b2a678f8294584.png?width=1200)
プロシージャルな葉っぱの作り方。
世の中にはジェネレーティブアート、あるいはプロシージャル生成などといったジャンルがございます。これらは細かい説明を全部丸ごと叩いて砕いて抜きにすれば、プログラミングをしながら鼻歌交じりにお絵描きしたり、寝ている間にコンピューターが、ゲームに必要なデータを全部作って揃えてくれるといったような、驚嘆すべき夢の技術です。
なにやらAIというものがすごいらしいという昨今の風潮を待つまでもなく、コンピューターというのは算数をさせたら町内で右にでるものはいないと評判の機械でした。『結局最後は、人間の腕が勝つんでしょう?』などといった幻想は、もはや賞味期限が切れ過ぎて、逆に来月までもつと思ってた非常食のカップめんのごとく、捨てちゃえそんなの感にあふれています。
もはや人間がそれをやろうと思ったのなら、コンピューターにもそれはできる時代です。
人間が葉っぱを描こうと思ったのなら、コンピューターにも葉っぱは描けるのです。
問題があるとすれば、このような魔法をちょちょいと使うためには、数学、物理、プログラミング、その上にアートに通暁せねばならず、とりわけ真の意味で葉っぱと向き合おうと思ったなら、そこには植物学や自然に対する深い理解や洞察が不可欠なことです。
えんぴつがあれば3分くらいで描けそうな葉っぱを描くのに、カップめんの賞味期限が切れるレベルで時間を要することもあるでしょう。それだけのことをしてもなお葉っぱが描きたい。この記事はそういう人のためのものです。
Processingというものを使う。
Processingの解説は、既に良質なものが世にあふれておりましょうが、あえてここでも振り返りましょう。
ProcessingはJavaを用いたプログラミング言語であり開発環境です。世の中にはJavaという傲慢ちきで鼻持ちならないプログラミング言語があって、これは例えるならかつては悪の帝国に立ち向かった英雄だったけれども、最近は昼間っから何やってんだか良く分からないといったような言語なのですが、
Processingは、Javaで最初から作ってたら面倒な部分を全部すっ飛ばして、利用者がビジュアルアートを即座に体感し、遊び、学べるようになっておるすごいやつです。
最近ではJavaScriptをベースとしたp5.jsが勢いを増し、カジュアルさで言えば一時期のITベンチャーのオフィスもかくやというほど。もはやネットさえ繋がるのであれば開発環境すらいらない塩梅で、
それでもわたくしがProcessingを用いるのはデータ型があるからで、その上で開発環境もいまだに最強に手軽だからです。p5.jsにはTypeScriptといった強化外骨格や、VS Codeなる攻守最強開発環境が後ろに控えておりますが、Processingの場合はフォルダごとコピペしようがゴミ箱に捨てようが、例えそれがプログラムの実行中だろうがちょっと怒られるくらいで、
デバッガがさすがにちょっと頼りなかったり、Java自体がそもそも好きじゃなかったり、そういうのをしょっ引いても、なんだかんだで一番楽だなと。そういう次第なのであります。
葉っぱクラス
さて、わたくしはたかだか葉っぱを描画するために以下のようなクラスを用いております。なんでこんなことになってるかの解説を有料記事でします。
interface IParticle
{
void Update();
void Draw();
void Kill();
}
//原点はindex[0]
//線分としての側面が強い
//Axisの両サイドに基準となるコントロールポイントを生成できるが、
//必ずしも両側を同時使用する必要は無く、片側使用にも対応できる
class LinerHex implements IParticle
{
//コンストラクタ
LinerHex(ArrayList<IParticle> list, PVector pos, PVector line)
{
this.InitialPos = pos.copy();
this.InitialDiameterLV = line.copy();
this.Particles = list;//Particle管理用のリストへの参照
this.Hex.add(pos.copy());//H[0]を作成
this.DiameterLV = line.copy();
//Hex[0]から作成される
this.Hex = GetUnitHex();
//MakeLinerHexは座標を閉じるため始点と終点が重複する
this.Hex.remove(this.Hex.size()-1);
VertexL = CalcuOpenSpline(HexR(),3,0.01);
VertexR = CalcuOpenSpline(HexL(),3,0.01);
MakeVein((int)random(5,10));
}
//パーティクル管理リストへの参照
ArrayList<IParticle> Particles;
//■
//Particle用
PVector InitialPos;
//■データの本体1
PVector Pos()//index[0]
{
return this.H0();
}
void SetPos(PVector pos)
{
this.H0().x = pos.x;
this.H0().y = pos.y;
}
float UnitRadian = 2*PI/6;
//■
PVector InitialDiameterLV = new PVector(0,0);
//■データの本体2
PVector DiameterLV = new PVector(0,0);
//現在の直径軸
PVector CurrentAxis(){return DiameterLV;}
//現在の半径
float CurrentRadius(){return CurrentAxis().mag()/2;}
//現在の直径
float CurrentDiameter(){return CurrentAxis().mag();}
float CurrentAngle(){return CalcuAngle360(DiameterLV);}
PVector UnitHexCenter()
{
return Pos().copy().add(DiameterLV.copy().div(2));
}
ArrayList<PVector>GetUnitHex()
{
return MakeCircleHexLiner(this.Pos(),this.DiameterLV);
}
PVector GetUnitHexVertex(int index)
{
return GetCircleVertexLiner(this.Pos(),this.DiameterLV, this.UnitRadian, index);
}
//Axisに対して現在の基準円周上の座標が落とす射影ベクトル
PVector GetUnitHexProjection(int index)
{
if(index==0){return new PVector(0,0);}
if(index==3){return CurrentAxis();}
return Projection(CurrentAxis(), GetUnitHexVertex(index).sub(H0()));
}
//Axisから現在の基準円周上の座標への垂直ベクトル
PVector GetUnitHexPerpendicular(int index)
{
if(index==0){return new PVector(0,0);}
if(index==3){return new PVector(0,0);}
return Perpendicular(CurrentAxis(), GetUnitHexVertex(index).sub(this.H0()));
}
//現在のAxisから現在の基準円周上の座標への距離
float GetUnitPerpLength(int index){return GetUnitHexPerpendicular(index).mag();}
//■データの本体3
ArrayList<PVector> Hex = new ArrayList<PVector>();
PVector H0(){return this.Hex.get(0);}
PVector H1(){return this.Hex.get(1);}
PVector H2(){return this.Hex.get(2);}
PVector H3(){return this.Hex.get(3);}
PVector H4(){return this.Hex.get(4);}
PVector H5(){return this.Hex.get(5);}
ArrayList<PVector>Copy(ArrayList<PVector> vertices)
{
ArrayList<PVector> ret = new ArrayList<PVector>();
for(int i = 0; i<vertices.size(); i++)
{
ret.add(vertices.get(i).copy());
}
return ret;
}
ArrayList<PVector> HexR()
{
ArrayList<PVector>ret = new ArrayList<PVector>();
ret.add(H0());
ret.add(H1());
ret.add(H2());
ret.add(H3());
return ret;
}
ArrayList<PVector> HexL()
{
ArrayList<PVector>ret = new ArrayList<PVector>();
ret.add(H0());
ret.add(H5());
ret.add(H4());
ret.add(H3());
return ret;
}
//補間後
ArrayList<PVector> VertexR = new ArrayList<PVector>();
ArrayList<PVector> VertexL = new ArrayList<PVector>();
//絶対距離
//現在のAxisから現在の頂点までの距離
float GetCurrentPerpLength(int index){return Perpendicular(CurrentAxis(),Hex.get(index)).mag();}
//相対距離
//現在の基準円周上にある場合、dt=1と考える
//現在の基準円周内なら0-1
float perp_t1(){return GetCurrentPerpLength(1)/GetUnitPerpLength(1);}
float perp_t2(){return GetCurrentPerpLength(2)/GetUnitPerpLength(2);}
float perp_t4(){return GetCurrentPerpLength(3)/GetUnitPerpLength(4);}
float perp_t5(){return GetCurrentPerpLength(4)/GetUnitPerpLength(5);}
//t入力でHexの現在の座標を変更する
void SetPerp_t(int index, float t)
{
PVector proj = GetUnitHexProjection(index).copy();
PVector perp = GetUnitHexPerpendicular(index).copy();//Axisから基準円周上の座標への垂直ベクトル
PVector new_v = H0().copy().add(proj).add(perp.mult(t));
this.Hex.get(index).x=new_v.x;
this.Hex.get(index).y=new_v.y;
}
//葉脈パラメータ
ArrayList<Float> vein_ct = new ArrayList<Float>();//中心
ArrayList<Float> vein_rt = new ArrayList<Float>();//R円周
ArrayList<Float> vein_lt = new ArrayList<Float>();//L円周
void MakeVein(int vein_count)
{
float unit = 1.0/(float)vein_count;
for(int i = 2; i<vein_count-1; i++)
{
vein_ct.add(unit*i);
vein_rt.add(unit*i+unit);
vein_lt.add(unit*i+unit);
}
}
//t入力で領域上の絶対座標を求める
//中央脈MainVein
PVector GetMainVeinVertex(float t)
{
return H0().copy().add(CurrentAxis().copy().mult(t));
}
//葉縁(ようえん)Margin
PVector GetLeftMarginVertex(int degree, float t)
{
return Px0(this.HexR(),degree,t);
}
PVector GetRightMarginVertex(int degree, float t)
{
return Px0(this.HexL(),degree,t);
}
void DrawVein()
{
for(int i = 0; i<vein_ct.size(); i++)
{
PVector central = GetMainVeinVertex(vein_ct.get(i));
PVector lm = GetLeftMarginVertex(3,vein_lt.get(i));
PVector rm = GetRightMarginVertex(3,vein_rt.get(i));
line(central.x,central.y,lm.x,lm.y);
line(central.x,central.y,rm.x,rm.y);
}
}
PVector Velocity = new PVector(0,0);
void Update()
{
float d_air = random(-2,2);
Air.x=0.1*d_air;
Velocity.add(Air);
Velocity.y+=Gravity;
Translate(Velocity);
//Rot
float rot_t = 0.0001;
float rot = noise(this.Pos().x*rot_t,this.Pos().y*rot_t,millis()*rot_t);
rot = map(rot,0,1,-0.01,0.01);
this.Rot(rot);
//Hex変形
float hex_t = 0.001;
float hex_tt = 0.0001;
float t_min = -0.6;
float t_max = 0.2;
//基準円がt=1
SetPerp_t(1, 1 + map(noise(Hex.get(1).x*hex_t,Hex.get(1).y*hex_t, millis()*hex_tt),0,1,t_min,t_max));//index[1]
SetPerp_t(2, 1 + map(noise(Hex.get(2).x*hex_t,Hex.get(2).y*hex_t, millis()*hex_tt),0,1,t_min,t_max));//index[2]
SetPerp_t(4, 1 + map(noise(Hex.get(4).x*hex_t,Hex.get(4).y*hex_t, millis()*hex_tt),0,1,t_min,t_max));//index[4]
SetPerp_t(5, 1 + map(noise(Hex.get(5).x*hex_t,Hex.get(5).y*hex_t, millis()*hex_tt),0,1,t_min,t_max));//index[5]
//見た目更新
VertexL = CalcuOpenSpline(HexL(),3,0.01);
VertexR = CalcuOpenSpline(HexR(),3,0.01);
}
void Draw()
{
stroke(0);
fill(255);
//strokeWeight(2);
stroke(255,153,51);
//葉っぱの色
//fill(0,82,20);
//fill(0,255,255);
fill(102,153,255);
//fill(255,255,204);
FillLinerClose(this.VertexL);
//fill(8,112,12);
fill(102,255,204);
FillLinerClose(this.VertexR);
//葉脈
DrawVein();
//Test表示用
//index[0]
//fill(255);
//circle(Pos().x,Pos().y,20);
////CurrentHex
//fill(255);
//circle(H0().x,H0().y,20);
//circle(H1().x,H1().y,20);
//circle(H2().x,H2().y,20);
//circle(H3().x,H3().y,20);
//circle(H4().x,H4().y,20);
//circle(H5().x,H5().y,20);
//fill(0);
//text("H0",H0().x,H0().y);
//text("H1",H1().x,H1().y);
//text("H2",H2().x,H2().y);
//text("H3",H3().x,H3().y);
//text("H4",H4().x,H4().y);
//text("H5",H5().x,H5().y);
//fill(255);
////Projection
//fill(255);
//PVector p1 = GetUnitHexProjection(1).add(H0());
//PVector p2 = GetUnitHexProjection(2).add(H0());
//PVector p4 = GetUnitHexProjection(4).add(H0());
//PVector p5 = GetUnitHexProjection(5).add(H0());
//circle(p1.x,p1.y,20);
//circle(p2.x,p2.y,20);
//circle(p4.x,p4.y,20);
//circle(p5.x,p5.y,20);
////Perpendicular
//PVector pp1 = GetUnitHexPerpendicular(1).add(p1);
//PVector pp2 = GetUnitHexPerpendicular(2).add(p2);
//PVector pp4 = GetUnitHexPerpendicular(4).add(p4);
//PVector pp5 = GetUnitHexPerpendicular(5).add(p5);
//fill(0);
//text("pp1",pp1.x,pp1.y);
//text("pp2",pp2.x,pp2.y);
//text("pp4",pp4.x,pp4.y);
//text("pp5",pp5.x,pp5.y);
//fill(255);
////DrawPerpendicular
//line(p1.x,p1.y,pp1.x,pp1.y);
//line(p2.x,p2.y,pp2.x,pp2.y);
//line(p4.x,p4.y,pp4.x,pp4.y);
//line(p5.x,p5.y,pp5.x,pp5.y);
////基準円
//noFill();
//circle(UnitHexCenter().x,UnitHexCenter().y,CurrentDiameter());
//fill(255);
}
void Rot(float radian)
{
//LV更新(SV固定)
//これが無ければ基準Hexが回転しない
RotRadianRef(this.DiameterLV,radian);
//Hex更新
this.Hex = GetUnitHex();
//this.Hex = RotRadian(this.Hex, this.Pos(), radian);
}
void Move(PVector pos)
{
//SV更新(LV固定)
SetPos(pos);
//Hex更新
this.Hex = GetUnitHex();
//PVector dv = pos.copy().sub(this.Pos());
//Translate(dv);
}
void Translate(PVector dv)
{
//SV更新(LV固定)
this.Hex.get(0).add(dv);
//Hex更新
this.Hex = GetUnitHex();
//for(PVector v : this.Hex)
//{
// v.add(dv);
//}
}
void Kill()
{
if(Pos().y<0||height<Pos().y)
{
this.Particles.remove(this);
}
if(Pos().x<0||width<Pos().x)
{
this.Particles.remove(this);
}
}
}
ここから先は
¥ 280
この記事が気に入ったらチップで応援してみませんか?