如同遞迴樹生成器,但更有機一點。 此實現使用貝塞爾曲線來勾勒出樹幹、四肢和樹枝的輪廓。 當樹枝劃分得太細時,就會添加一叢葉子。 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許多葉子八邊形具有奇數/偶數重疊
在內部,該演算法創建一棵隨機形狀的樹,沿著每個樹幹/樹枝/樹枝的中央脊柱,並記錄通往葉子的左側的貝塞爾點和返回到葉子的右側的貝塞爾點。樹幹。 線段中心的點是繪製點;每側的兩個控制點是該線段與其他線段相交的位置。
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.又一個層次。
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)。兩個控制點 (0) 出現在每對貝塞爾曲線點之間以及初始移動和貝塞爾曲線之間。 繪製的線穿過移動點 (1) 和貝塞爾點 (3)。 控制點(0)決定曲線的形狀;該線不會穿過它們,但確實朝向它們。 上面的程式碼使一對控制點成為同一點(藍色,位於上方)。 肯定還是有兩個。
看這個答案用於實驗行為的程式碼。
It is a graph.It is a graph.It is a graph.It is a graph.這是一個圖表。
Bézier 是正確的拼法。 發音對我來說更難。 https://en.wikipedia.org/wiki/B%C3%A9zier_curve