Root system via dynamic L-system
A significantly dumbed-down 2D version of RootBox
Leitner et al (2010) The algorithmic beauty of plant roots – an L-System model for dynamic root growth simulation doi.org/10.1080/13873954.2010.491360
Leitner et al (2010) A dynamic root system growth model based on L-Systems doi.org/10.1007/s11104-010-0284-7
RootBox csc.univie.ac.at/rootbox/
Log in to post a comment.
Canvas.setpenopacity(-1); const top_h = -60; const t = new Turtle(); function draw_dashed_line(y, Ndash) { const dash = 200 / (2 * Ndash - 1) t.jump(-100, y) for (let i = 0; i < Ndash; i++) { t.forward(dash) t.penup() t.forward(dash) t.pendown() } } draw_dashed_line(top_h, 100) function random_normal(mu, sigma) { let u = Math.random(), v = Math.random() let z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v) // Box–Muller transform //z = Math.min(Math.max(z, -3.0), 3.0) // clip to 3 sigma return mu + sigma * z } // function check_block(draw_step) { // sphere // let cy = -30 // if (t.x()**2 + (t.y() - cy) **2 < 20**2) { // t.back(draw_step) // let new_dir = Math.atan2(t.x(), -(t.y() - cy)) / Math.PI * 180 // if (new_dir < 0) { // new_dir += 180 // } // else if (new_dir > 180) { // new_dir -= 180 // } // t.setheading(new_dir) // } // } let cf_y1 = top_h + 25, cf_y2 = cf_y1 + 10, cf_y3 = cf_y2 + 10 let cf_w = 15 // draw coffin t.jump(-cf_w, cf_y1) t.goto(cf_w, cf_y1) t.goto(cf_w + (cf_y2 - cf_y1), cf_y2) t.goto(cf_w + (cf_y2 - cf_y1) - (cf_y3 - cf_y2), cf_y3) t.goto(-cf_w - (cf_y2 - cf_y1) + (cf_y3 - cf_y2), cf_y3) t.goto(-cf_w + -(cf_y2 - cf_y1), cf_y2) t.goto(-cf_w, cf_y1) t.jump(-cf_w - (cf_y2 - cf_y1), cf_y2) t.goto(cf_w + (cf_y2 - cf_y1), cf_y2) function check_block(draw_step) { // coffin if ((t.y() < cf_y1) || (t.y() > cf_y3)) { return } else if ((t.y() < cf_y2) && (Math.abs(t.x()) > cf_w + (t.y() - cf_y1))) { return } else if ((t.y() > cf_y2) && (Math.abs(t.x()) > cf_w + (cf_y2 - cf_y1) - (t.y() - cf_y2))) { return } else { t.back(draw_step) if (t.y() < cf_y2) { if (t.x() < -cf_w) { t.setheading(136) } else if (t.x() < 0) { t.setheading(180) } else if (t.x() < cf_w) { t.setheading(0) } else { t.setheading(45) } } else { t.setheading(90) } } } function draw_root(x0, y0, angle0, max_angle, l_base, l_end, l_mid, l_mid_sd, n_branches, N_try_grav, try_grav_depth, angle_sigma, draw_step, br_theta, br_theta_sd, br_l, br_l_sd) { let L_max = l_base + l_end let L_mid = [] for (let i = 0; i < n_branches; i++) { Lm = random_normal(l_mid, l_mid_sd) if (i == 0) { L_mid[0] = l_base + Lm } else { L_mid[i] = L_mid[i - 1] + Lm } L_max += Lm } t.jump(x0, y0) t.setheading(angle0) let prev_heading = angle0 let L = 0.0 let n_steps = 0 let n_br = 0 while (L < L_max) { t.forward(draw_step) L += draw_step if (t.y() < y0) { t.sety(y0) t.setheading(0) } check_block(draw_step) if ((n_br < n_branches) && (L > L_mid[n_br])) { let mem_x = t.x(), mem_y = t.y(), mem_h = t.heading() let dtheta = random_normal(br_theta, br_theta_sd) let angle_br = (Math.random() < 0.5) ? (mem_h + dtheta) : (mem_h - dtheta) let L_br = random_normal(br_l, br_l_sd) draw_root(mem_x, mem_y, angle_br, 360, L_br, 0, 0, 0, 0, 1, 0, 18, draw_step, 0, 0, 0, 0) t.jump(mem_x, mem_y) t.setheading(mem_h) n_br++ } let N = 1 if ((t.y() - y0) > try_grav_depth) { N = N_try_grav } else if (Math.random() <= 0.3) { N = Math.floor(1 + N_try_grav * Math.random()) } let best_angle = -90 for (let i = 0; i < N; i++) { let new_angle = t.heading() + random_normal(0, Math.sqrt(draw_step) * angle_sigma) if (Math.abs(new_angle - 90) < Math.abs(best_angle - 90)) { best_angle = new_angle } } best_angle = Math.min(Math.max(best_angle, 90 - max_angle), 90 + max_angle) prev_heading = t.heading() t.setheading(best_angle) } } const R = 3 t.jump(-R, top_h) t.goto(-R, -100) t.jump(R, top_h) t.goto(R, -100) const d = 2.0, Nd = 60 for (let i = 0; i < Nd; i++) { let y = -100 + (top_h + 100) * Math.random() let dd = d * Math.random() let x = (Math.random() <= 0.25) ? -R : ((Math.random() <= 0.33) ? (R - dd) : -R + (2 * R - dd) * Math.random()) t.jump(x, y) t.goto(x + dd, y) } const s = 1.5 const N0 = 30 const mod = 0.5 for (let i = 0; i < N0; i++) { let angle = 180 / (N0 + 2) * (1 + i + Math.random() - 0.5) let x = (R + 1) * (-1 + 2 * Math.random()) draw_root(x, top_h + 0.5, angle, 110, 0.1 * s, 18 * s, 0.6 * s / mod, 0.06 * s / mod, 120 * mod, 2, 30 * s, 9, 0.25, 70, 15, 1.5 * s, 0.5 * s) }