Similar to Recursive Tree Generator , but a bit more organic. This implementation uses Bezier curves to outline the trunk, limbs, and twigs. When the twigs are divided too finely, a clump of leaves is added. The JSL path(...) function is used to draw the Bezier trunk and the leaves, though the leaves are just an octagon. When a path is filled, and it crosses over itself, it does an even-odd count to determine if the fill color is applied. With the twigs this isn't really desirable, but with the leaves it makes a nice effect.
Lots of leaf octagons with odd/even overlap
Internally, the algorithm creates a random shaped tree, following the central spine of each trunk/limb/twig and recording the Bezier points for the left side on the way to the leaves and the Bezier points for the right side on the way back to the trunk. The point that is on the center of a segment is a drawn point; the two control points on each side are where the segment meets other segments.
Spine, bark, control points for Bezier
Changing the minimum limb size controls how deep the recursion goes; some of these turned off the leaves for clarity.
Just a trunk. The end cap is rounded with a Bezier too.
This branch point chose a three-way split.
Add the octagon leaves. They don't overlap because the size was adjusted.
One more level.
The overlapping leaves show the odd/even effect of crossing paths.
Tiny leaves on a big tree.
Tiny, still more branching
Big leaves, lots of branches.
TreePath = Function( {x0, y0, size0, angle},
{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},
leftangle = angle + Pi() / 2;
rightangle = angle - Pi() / 2;
x0L = x0 + size0 * Cos( leftangle );
y0L = y0 + size0 * Sin( leftangle );
x0R = x0 + size0 * Cos( rightangle );
y0R = y0 + size0 * Sin( rightangle );
length = size0 ^ 1 * Abs( Random Normal( 30, 2 ) );
size1 = .95 * size0;
x1 = x0 + length * Cos( angle );
y1 = y0 + length * Sin( angle );
x1L = x1 + size1 * Cos( leftangle );
y1L = y1 + size1 * Sin( leftangle );
x1R = x1 + size1 * Cos( rightangle );
y1R = y1 + size1 * Sin( rightangle );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x0L, y0L, 0 } )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x0L + x1L) / 2, (y0L + y1L) / 2, 3} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1L, y1L, 0 } )} ) );
If( size0 > .5,
nbranches = Random Integer( 2, 3 );
angles = J( nbranches, 1, Abs( Random Normal( 4, 1 ) ) );
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();
For( ibranch = 1, ibranch <= nbranches, ibranch += 1,
rightangle = leftangle - angles[ibranch];
cx0 = x1 + size1 * Cos( leftangle );
cy0 = y1 + size1 * Sin( leftangle );
cx1 = x1 + size1 * Cos( rightangle );
cy1 = y1 + size1 * Sin( rightangle );
cxc = (cx0 + cx1) / 2;
cyc = (cy0 + cy1) / 2;
diameter = Sqrt( (cx0 - cx1) ^ 2 + (cy0 - cy1) ^ 2 );
Recurse( cxc, cyc, diameter / 2, (leftangle + rightangle) / 2 );
leftangle = rightangle;
);
,
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1L, y1L, 0 } )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x1L + x1R) / 2, (y1L + y1R) / 2, 3} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1R, y1R, 0 } )} ) );
xcleaf = (x1L + x1R) / 2;
ycleaf = (y1L + y1R) / 2;
radleaf = 100 * size1;
For( leafangle = 0, leafangle < 2 * Pi(), leafangle += Pi() / 4,
xleaf = xcleaf + radleaf * Cos( leafangle );
yleaf = ycleaf + radleaf * Sin( leafangle );
Insert Into( global:pathLeaves, Eval List( {Eval List( {xleaf, yleaf, If( leafangle == 0, 1, 2 )} )} ) );
);
);
Insert Into( global:pathTrunk, Eval List( {Eval List( {x1R, y1R, 0 } )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {(x0R + x1R) / 2, (y0R + y1R) / 2, 3} )} ) );
Insert Into( global:pathTrunk, Eval List( {Eval List( {x0R, y0R, 0 } )} ) );
);
global:pathTrunk = {};
global:pathLeaves = {};
size = 11;
treepath( 0, 0, size, Pi() / 2 );
Insert Into( global:pathTrunk, {{-size, 0, 0}}, 1 );
Insert Into( global:pathTrunk, {{-size - 5, 0, 1}}, 1 );
Insert Into( global:pathTrunk, {{size, 0, 0}} );
Insert Into( global:pathTrunk, {{size + 5, 0, 3}} );
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( HLS Color( .66, .2, 1 ) ),
Fill Color( HLS Color( .16, .3, .5 ) );
Path( matTrunk, 1 );
Transparency( .75 );
Fill Color( HLS Color( .33, .6, .8 ) );
Path( matLeaves, 1 );
)
);
It is a little hard to figure out what the path() function expects for a Bezier curve, but it makes sense when it is done. The first item in the list should be a move (1), and the last item should be a Bezier point (3). Two control points (0) appear between each pair of Bezier points and between the initial move and Bezier. The drawn line goes through the move (1) and Bezier (3) points. The control points (0) determine the shape of the curve; the line does not go through them but does go toward them. The code above makes the pair of control points both the same point (blue, way above). There must still be two.
See this answer for code to experiment with behavior.
It is a graph.
Bézier is the correct spelling. Pronunciation is harder for me. https://en.wikipedia.org/wiki/B%C3%A9zier_curve