如同递归树生成器,但更有机一点。 该实现使用贝塞尔曲线来勾勒出树干、四肢和树枝的轮廓。 当树枝划分得太细时,就会添加一丛叶子。 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