Show code
```html
<!DOCTYPE html>
<html><head><title>L-System Tree</title></head><style>body{margin:0;overflow:hidden;background:#111}{canvas{display:block}}#status{position:absolute;top:10px;left:10px;color:#ccc;font-family:monospace;padding:5px;background:#000b1133;border-radius:5px}</style><body><canvas id="treeCanvas"></canvas><div id="status">Generating...</div><script>
const canvas = document.getElementById('treeCanvas');
const ctx = canvas.getContext('2d');
let W, H;
function resize() {W=canvas.width=window.innerWidth;H=canvas.height=window.innerHeight;ctx.fillStyle='#000';ctx.fillRect(0,0,W,H); }
window.addEventListener('resize',resize);
resize();
const L_RULES = ['F', 'FF+[+F%-]']; // A classic fractal rule
let currentString = '';
let generation = 0;
let maxGenerations = 6;
let branchesDrawn = 0;
let isBuilding = true;
let animationTime = 0;
struct State {
x: number, y: number, angle: number, length: number, stack: string[]
}
function initializeSystem() {
currentString = L_RULES[0];
generation = 0;
branchesDrawn = 0;
isBuilding = true;
animationTime = 0;
document.getElementById('status').innerHTML = `Generation: ${generation}/6 | Branches: 0`;
}
function expand() {
if (generation >= maxGenerations) return false;
currentString = "";
for (const char of currentString) {
if (char === 'F') { // Simple rewrite for this example
currentString += L_RULES[1].replace(/[+%-]/g, '').split('').join('');
} else if (L_RULES.includes(char)) {
currentString = char;
}
}
generation++;
document.getElementById('status').innerHTML = `Generation: ${generation}/${maxGenerations} | Branches: ${branchesDrawn}`;
if (generation < maxGenerations) {
requestAnimationFrame(buildTree);
} else {
setTimeout(() => {
isBuilding = false;
document.getElementById('status').innerHTML = `Complete! Total Generations: ${generation}. Swaying...`;
animateWind();
}, 100);
}
return true;
}
function drawBranch(s) {
const state = [];
let p = { x: W/2, y: H-50 };
let angle = -Math.PI / 2;
state.push({...p, angle, length: 1}); // Initial state (root)
for (let i = 0; i < s.length; i++) {
const char = s[i];
if (char === 'F') {
const nextP = { x: p.x + Math.cos(angle) * p.length, y: p.y + Math.sin(angle) * p.length };
p = nextP;
ctx.strokeStyle = '#1a3300';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(state[state.length - 1].x, state[state.length - 1].y);
ctx.lineTo(p.x, p.y);
ctx.stroke();
} else if (char === '+') {
// Increase angle (right)
angle += Math.PI / 4;
} else if (char === '-') {
// Decrease angle (left)
angle -= Math.PI / 4;
} else if (char === '[') {
state.push({...p, angle: angle, length: p.length}); // Push state
} else if (char === ']') {
const lastState = state.pop();
p = {...lastState};
angle = lastState.angle;
// Recalculate position based on the popped state's origin
state[state.length - 1].x = p.x + Math.cos(angle) * 0.5; // Simple correction for segment start
state[state.length - 1].y = p.y + Math.sin(angle) * 0.5;
} else if (char === '|') {
// This character is used as a marker for new segments/branches, handled by structure instead of simple append
}
}
}
let segments_in_progress = [];
function buildTree() {
if (!isBuilding) return;
const chars = currentString.split('');
segments_in_progress = [];
// Simplified interpretation focusing on segment drawing logic rather than complex stack traversal for size constraints
// We iterate through the string and store potential branches
let p = { x: W/2, y: H-50 };
let angle = -Math.PI / 2;
const segments = [];
for (let i = 0; i < chars.length; i++) {
const char = chars[i];
if (char === 'F') {
segments.push({x: p.x, y: p.y, length: 10}); // Store start point and basic segment data
p.x += Math.cos(angle) * 10;
p.y += Math.sin(angle) * 10;
} else if (char === '+') {
angle += Math.PI / 4;
} else if (char === '-') {
angle -= Math.PI / 4;
} else if (char === '[') {
// In a real L-system, this would push the full state. Here we just track depth for branch counting.
segments_in_progress.push(true);
} else if (char === ']') {
segments_in_progress.pop();
}
}
if (!segments[0]) { // Initial draw phase needs to happen once per generation step
ctx.clearRect(0, 0, W, H);
ctx.save();
// Simple drawing of the current state's path (approximation)
let x = W/2; let y = H-50; let a = -Math.PI / 2;
ctx.strokeStyle = '#1a3300';
ctx.lineWidth = 4 * (generation > 0 ? 0.8 : 1);
ctx.beginPath();
ctx.moveTo(x, y);
for(let i=0; i<chars.length; i++){
const char = chars[i];
if(char === 'F'){
x += Math.cos(a) * 10;
y += Math.sin(a) * 10;
ctx.lineTo(x, y);
} else if (char === '+') { a += Math.PI/4; }
else if (char === '-') { a -= Math.PI/4; }
}
ctx.stroke();
}
// Increment branches drawn based on the complexity of the current structure
branchesDrawn = Math.min(100 + generation * 5, 300);
}
function animateWind() {
let t = animationTime;
const windStrength = 0.02 + 0.01 * Math.sin(t / 400);
ctx.clearRect(0, 0, W, H);
ctx.save();
ctx.translate(W/2, H - 150); // Base anchor point
ctx.rotate(-Math.PI / 2); // Start pointing up
// Recursive function to draw and apply wind physics
function drawAndSway(segment, angleOffset, scale) {
if (scale < 0.1) return;
const length = segment.length * scale;
ctx.strokeStyle = `rgba(26, 51, 0, ${scale*0.8 + 0.2})`;
ctx.lineWidth = Math.max(1, 3 * scale);
// Apply wind influence (horizontal force proportional to time and scale)
const swayX = length * Math.sin(angleOffset * 0.5 + t / 400 * windStrength);
const swayY = length * Math.cos(angleOffset * 0.5 + t / 400 * windStrength);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(swayX, swayY);
ctx.stroke();
// Recursive calls (simplified tree branching structure)
if (segment.branches && segment.branches.length > 0) {
for (let i = 0; i < segment.branches.length; i++) {
const b = segment.branches[i];
drawAndSway(b, angleOffset + b.angle * scale / Math.max(1, scale), scale * 0.6);
}
}
}
// Since we don't store the full tree structure for physics in this size-constrained version,
// we just draw a simplified visualization based on generation count.
const simSegments = [];
for(let i=0; i<generation; i++){
simSegments.push({length: Math.pow(10, -i*0.2), branches: Array(Math.min(5, 3+i)).fill({})});
}
drawAndSway(simSegments[0] || {length: 10}, 0, 1);
ctx.restore();
animationTime += 16; // Assuming ~60fps frame rate
requestAnimationFrame(animateWind);
}
function run() {
initializeSystem();
// Step-by-step building animation loop
const buildTree = () => {
if (generation < maxGenerations) {
buildTree();
} else {
setTimeout(() => {
isBuilding = false;
animateWind();
}, 100);
}
};
requestAnimationFrame(buildTree);
}
run();
</script></body></html>
```