に似ている再帰的ツリージェネレーター、しかしもう少し有機的です。 この実装では、ベジェ曲線を使用して幹、枝、小枝の輪郭を描きます。 小枝を細かく分けすぎると、葉の塊が追加されます。 JSL path(...) 関数を使用してベジェ幹と葉を描画しますが、葉は単なる八角形です。 パスが塗りつぶされ、それ自体が交差する場合、偶数と奇数のカウントを実行して、塗りつぶしの色が適用されているかどうかを判断します。 小枝の場合、これはあまり望ましいことではありませんが、葉の場合は素晴らしい効果が得られます。
Lots of leaf octagons with odd/even overlapLots of leaf octagons with odd/even overlapLots of leaf octagons with odd/even overlapLots of leaf octagons with odd/even overlap奇数/偶数が重なった葉の八角形がたくさんあります
内部的には、アルゴリズムはランダムな形状の木を作成し、各幹/枝/小枝の中央の脊椎をたどり、葉に向かう途中の左側のベジェ ポイントと、葉に戻る途中の右側のベジェ ポイントを記録します。トランク。 セグメントの中心にある点は描画された点です。両側の 2 つの制御点は、セグメントが他のセグメントと出会う場所です。
Spine, bark, control points for BezierSpine, bark, control points for BezierSpine, bark, control points for BezierSpine, bark, control points for Bezierスパイン、樹皮、ベジェのコントロール ポイント
最小のリム サイズを変更すると、再帰の深さが制御されます。わかりやすくするために、これらの一部は葉を消しています。
Just a trunk. The end cap is rounded with a Bezier too.Just a trunk. The end cap is rounded with a Bezier too.Just a trunk. The end cap is rounded with a Bezier too.Just a trunk. The end cap is rounded with a Bezier too.ただのトランク。 エンドキャップもベジェで丸みを帯びています。
This branch point chose a three-way split.This branch point chose a three-way split.This branch point chose a three-way split.This branch point chose a three-way split.この分岐点は三者分岐を選択しました。
Add the octagon leaves. They don't overlap because the size was adjusted.Add the octagon leaves. They don't overlap because the size was adjusted.Add the octagon leaves. They don't overlap because the size was adjusted.Add the octagon leaves. They don't overlap because the size was adjusted.八角形の葉を加えます。 サイズを調整してあるので重なりません。
One more level.One more level.One more level.One more level.もう 1 つのレベル。
The overlapping leaves show the odd/even effect of crossing paths.The overlapping leaves show the odd/even effect of crossing paths.The overlapping leaves show the odd/even effect of crossing paths.The overlapping leaves show the odd/even effect of crossing paths.重なり合う葉は、パスが交差することによる奇数/偶数の効果を示します。
Tiny leaves on a big tree.Tiny leaves on a big tree.Tiny leaves on a big tree.Tiny leaves on a big tree.大きな木に小さな葉っぱ。
Tiny, still more branchingTiny, still more branchingTiny, still more branchingTiny, still more branching小さくて、まだ枝分かれしている
Big leaves, lots of branches.Big leaves, lots of branches.Big leaves, lots of branches.Big leaves, lots of branches.大きな葉、たくさんの枝。
// Recursive routine builds a random tree using Bezier curves in path(...) command
TreePath = Function( {x0, y0, size0, angle},// parameters for recursion
{x0L, x0R, y0L, y0R, x1, y1, size1, x1L, x1R, y1L, y1R, nbranches, leftangle, rightangle, ibranch, nbranches, angles, length, biggest, biggestv,
middle, middlev, cx0, cy0, cx1, cy1, cxc, cyc, xcleaf, ycleaf, radleaf, xleaf, yleaf, diameter, leafangle}, // local variables
// x0,y0 is the center of this trunk segment, starting point. angle is the direction, and size0 the radius.
leftangle = angle + Pi() / 2; // find two points at right angles to the direction of growth
rightangle = angle - Pi() / 2; // by adding +/- 90 degrees ... this is all radians, so PI/2
x0L = x0 + size0 * Cos( leftangle ); // size0 is the radius
y0L = y0 + size0 * Sin( leftangle );
x0R = x0 + size0 * Cos( rightangle );
y0R = y0 + size0 * Sin( rightangle );
length = size0 ^ 1 * Abs( Random Normal( 30, 2 ) );// scaling constants...play with these
size1 = .95 * size0;// .95 is a limb shrinkage constant. Not sure this is enough to notice the taper.
x1 = x0 + length * Cos( angle ); // x1,y1 is the other endpoint of this trunk segment
y1 = y0 + length * Sin( angle );
x1L = x1 + size1 * Cos( leftangle ); // and similar right angle calculation
y1L = y1 + size1 * Sin( leftangle ); // to find the bark at the other end
x1R = x1 + size1 * Cos( rightangle );
y1R = y1 + size1 * Sin( rightangle );
// the center of the trunk segment is ON the drawn curve. the endpoints become
// control points that pull the curve toward the joints, partway, leaving smooth bends.
Insert Into( global:pathTrunk, Eval List( {Eval List( {x0L, y0L, 0 /*control*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x0L + x1L) / 2, (y0L + y1L) / 2, 3/*bezier*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1L, y1L, 0 /*control*/} )} ) );
If( size0 > .5, // the recursion stops when the branch becomes twigs; leaves are added then <GRIN>
nbranches = Random Integer( 2, 3 ); // experiment with number of branches here
angles = J( nbranches, 1, Abs( Random Normal( 4, 1 ) ) );
// I decided the trees generally looked better if the bigger branch was centered...
biggest = Loc Max( angles );
biggestv = angles[biggest];
middle = Ceiling( N Rows( angles ) / 2 );
middlev = angles[middle];
angles[middle] = biggestv;
angles[biggest] = middlev;
angles = angles / Sum( angles ) * Pi();//normalize, radians
// repurpose leftangle and rightangle, using previous leftangle as a starting point:
// there is a semicircle at the end of this branch, pi() radians (180 deg), going through
// x1L,y1L and x1R,y1R which form a diameter. The first branch leaves from x1L,y1L and
// the rightangle on the semicircle (computed inside the loop.) The angle is half way,
// and the x0y0 is on the chord, halfway. When this routine is recursively called,
// it will recompute the two points on the chord from the angle and size, which is half the chord length.
For( ibranch = 1, ibranch <= nbranches, ibranch += 1,
// working on a branch...figure out its base
rightangle = leftangle - angles[ibranch];
// x1,y1 is the semicircle center. get the chord endpoints at the left and right angles and radius size.
cx0 = x1 + size1 * Cos( leftangle );
cy0 = y1 + size1 * Sin( leftangle );
cx1 = x1 + size1 * Cos( rightangle );
cy1 = y1 + size1 * Sin( rightangle );
// and the midpoint of the chord
cxc = (cx0 + cx1) / 2;
cyc = (cy0 + cy1) / 2;
// and the diameter of the branch (length of chord)
diameter = Sqrt( (cx0 - cx1) ^ 2 + (cy0 - cy1) ^ 2 );
// here it is! do the child branch (which will do its child...)
Recurse( cxc, cyc, diameter / 2, (leftangle + rightangle) / 2 );
// set up for next branch...
leftangle = rightangle;
);
, //else cap this branch and add the leaves
// the cap is a rounded line across the end instead of more branching
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1L, y1L, 0 /*control*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x1L + x1R) / 2, (y1L + y1R) / 2, 3/*bezier*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1R, y1R, 0 /*control*/} )} ) );
// add leaves to the twigs...circle diameter twig length, centered on twig...
// the single path with all these circles makes an interesting pattern when the circles overlap
// and cancel each other out in an even/odd way. The trunk does that too, and there seems to be no way to prevent it.
xcleaf = (x1L + x1R) / 2; // center a sphere of leaves over the cap segment
ycleaf = (y1L + y1R) / 2;
radleaf = 100 * size1; // constant to play with - radius of leaf ball around cap's size
For( leafangle = 0, leafangle < 2 * Pi(), leafangle += Pi() / 4,
xleaf = xcleaf + radleaf * Cos( leafangle );
yleaf = ycleaf + radleaf * Sin( leafangle );
// move to the new leaf ball, then draw the ball
Insert Into( global:pathLeaves, Eval List( {Eval List( {xleaf, yleaf, If( leafangle == 0, 1/*move*/, 2/*draw*/ )} )} ) );
);
);
// ditto, x0R,y0R to x1R,y1R is the trailing (right side) edge. But reversed.
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1R, y1R, 0 /*control*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x0R + x1R) / 2, (y0R + y1R) / 2, 3/*bezier*/} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x0R, y0R, 0 /*control*/} )} ) );
);
// these two globals are filled in by the recursive routine
global:pathTrunk = {};
global:pathLeaves = {};
size = 11; // of the trunk, radius really
treepath( 0, 0, size, Pi() / 2 ); // do the recursion
// leading move, control...this adds the left-side ground
Insert Into( global:pathTrunk, {{-size, 0, 0}}, 1 );//push control
Insert Into( global:pathTrunk, {{-size - 5, 0, 1}}, 1 );//push move
// trailing control, draw...this adds the right-side ground
Insert Into( global:pathTrunk, {{size, 0, 0}} );//push control
Insert Into( global:pathTrunk, {{size + 5, 0, 3}} );//push move
// convert the lists into matrices. It was easier to add to the lists during recursion
// but path() needs the matrix
matTrunk = Matrix( global:pathTrunk );
matLeaves = Matrix( global:pathLeaves );
New Window( "Example",
Graph Box(
X Scale( -600, 600 ),
Y Scale( -100, 1100 ),
framesize( 950, 950 ),
<<BACKGROUNDCOLOR></BACKGROUNDCOLOR></GRIN>path() 関数がベジェ曲線に対して何を期待しているかを理解するのは少し難しいですが、実行すると意味がわかります。 リストの最初の項目は移動 (1)、最後の項目はベジェ点 (3) である必要があります。ベジェ ポイントの各ペアの間、および最初の動きとベジェの間に 2 つのコントロール ポイント (0) が表示されます。 描かれた線は移動点 (1) とベジェ点 (3) を通過します。 制御点 (0) は曲線の形状を決定します。線はそれらを通過しませんが、それらに向かって進みます。 上記のコードは、制御点のペアを両方とも同じ点 (青色、かなり上) にします。 まだ2つあるはずです。
見るこの答え動作を実験するコード用。
It is a graph.It is a graph.It is a graph.It is a graph.グラフです。
ベジエが正しい綴りです。 発音は私にとってもっと難しいです。 https://en.wikipedia.org/wiki/B%C3%A9zier_curve