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)
}