Mandelbrot rendered as a quad tree.
Log in to post a comment.
// LL 2021 const max_iterations = 25 // min = 1, max = 100, step = 1 const m_scale = 65 // min = 10, max = 1000, step = 0.01 const offset_x = 0.7 // min = -10, max = 10, step = 0.001 const offset_y = 0 // min = -10, max = 10, step = 0.001 const min_factor = 0.1 // min = 0, max = 1, step = 0.01 const octree_depth = 9 // min = 1, max = 12, step = 1 const draw_style = 0 // min = 0, max = 1, step = 1, (boxes, hatching) const hatching_density = 20 // min = 1, max = 100, step = 1 const canvas_size = 190 Canvas.setpenopacity(1) const turtle = new Turtle() function dot(x, y, r) { turtle.penup() turtle.goto(x + r, y + r) turtle.pendown() turtle.goto(x + r, y - r) turtle.goto(x - r, y - r) turtle.goto(x - r, y + r) turtle.goto(x + r, y + r) } function mandelbrot(x, y) { var cr = x var ci = y var zr = 0 var zi = 0 var iterations = 0 while (zr * zr + zi * zi < 4) { const new_zr = zr * zr - zi * zi + cr const new_zi = 2 * zr * zi + ci zr = new_zr zi = new_zi iterations += 1 if (iterations >= max_iterations) break; } return iterations } // The walk function will be called until it returns false. function walk(i) { if (i==0 && min_factor==1) { turtle.penup() turtle.goto(canvas_size/2, canvas_size/2) turtle.pendown() turtle.goto(-canvas_size/2, canvas_size/2) turtle.goto(-canvas_size/2, -canvas_size/2) } octree.draw(i) return (i+1) < octree.total_leaves } function Leaf(depth, x, y, size) { this.leaves = [] this.x = x this.y = y this.size = size this.value = 0 this.id = -1 this.min_range = -1 this.max_range = -1 var size2 = size / 2 if (depth > 0) { for (var yy = 0; yy <2; yy++) { for (var xx = 0; xx <2; xx++) { this.leaves.push(new Leaf(depth-1, x + xx * size2, y + yy * size2, size2)) } } } else { var center_x = this.x + this.size / 2 var center_y = this.y + this.size / 2 const m_x = (center_x) / m_scale - offset_x const m_y = (center_y) / m_scale - offset_y this.value = mandelbrot(m_x, m_y) / max_iterations } } function Point(x, y) { this.x = x this.y = y } function Line(p0, p1) { this.points = [] this.points.push(p0) this.points.push(p1) } Line.prototype.draw = function() { turtle.penup() turtle.goto(this.points[0].x, this.points[0].y) turtle.pendown() turtle.goto(this.points[1].x, this.points[1].y) } function Box(p0, p1, p2, p3) { this.points = [] this.points.push(p0) this.points.push(p1) this.points.push(p2) this.points.push(p3) } function LineLineIntersection(line1, line2) { var s1x = line1.points[1].x - line1.points[0].x var s1y = line1.points[1].y - line1.points[0].y var s2x = line2.points[1].x - line2.points[0].x var s2y = line2.points[1].y - line2.points[0].y var s = (-s1y * (line1.points[0].x - line2.points[0].x) + s1x * (line1.points[0].y - line2.points[0].y)) / (-s2x * s1y + s1x * s2y) var t = ( s2x * (line1.points[0].y - line2.points[0].y) - s2y * (line1.points[0].x - line2.points[0].x)) / (-s2x * s1y + s1x * s2y) if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { var x = line1.points[0].x + (t * s1x); var y = line1.points[0].y + (t * s1y); return new Point(x, y); } return null } function LineBoxIntersection(line, box) { var points = [] var point for (var i=0; i<4 && points.length < 2; i++) { var line2 = new Line(box.points[i], box.points[(i+1)%4]) point = LineLineIntersection(line, line2) if (point != null) points.push(point) } if (points.length == 2) return new Line(points[0], points[1]) return null } Leaf.prototype.draw = function(target) { if (target < this.min_range || target > this.max_range) return true if (this.leaves.length == 0 && this.value > 0 && this.id == target) { switch (draw_style) { case 0: // boxes var center_x = this.x + this.size / 2 var center_y = this.y + this.size / 2 var size = this.size / 2 * (this.value * (1 - min_factor) + min_factor) turtle.penup() turtle.goto(center_x - size, center_y - size) turtle.pendown() turtle.goto(center_x + size, center_y - size) turtle.goto(center_x + size, center_y + size) if (min_factor < 1) { turtle.goto(center_x - size, center_y + size) turtle.goto(center_x - size, center_y - size) } break case 1: // hatching var p0 = new Point(this.x, this.y) var p1 = new Point(this.x + this.size, this.y) var p2 = new Point(this.x + this.size, this.y + this.size) var p3 = new Point(this.x, this.y + this.size) var box = new Box(p0, p1, p2, p3); var density = (this.value * (1 - min_factor) + min_factor) density = Math.floor(density * 10) / 10 var frequency = Math.ceil(1 / density) for (var h=0; h<hatching.length; h++) { if ((h%frequency) > 0) continue var line = LineBoxIntersection(hatching[h], box) if (line != null) { line.draw() } } break } return false } else { for (var l=0; l<this.leaves.length; l++) { if (!this.leaves[l].draw(target)) return false } } return true } Leaf.prototype.prune = function() { if (this.leaves.length == 0) return 1 var leaf_count = 0 for (var l = 0; l < this.leaves.length; l++) { leaf_count += this.leaves[l].prune() } var ok_to_prune = true for (var l = 0; l < this.leaves.length; l++) { if (this.leaves[l].leaves.length > 0) ok_to_prune = false } var value = this.value if (ok_to_prune) { for (var l = 0; l < this.leaves.length; l++) { if (l==0) value = this.leaves[l].value; else if (value != this.leaves[l].value) ok_to_prune = false; } } if (ok_to_prune) { this.leaves = [] this.value = value leaf_count = 1 } return leaf_count } Leaf.prototype.set_ids = function() { this.min_range = next_id if (this.leaves.length == 0) { this.id = next_id next_id += 1 } else { for (var l = 0; l < this.leaves.length; l++) { this.leaves[l].set_ids() } } this.max_range = next_id } var next_id = 0 function Octree(depth, size) { this.root = new Leaf(depth-1, -size/2, -size/2, size) this.total_leaves = this.root.prune() console.log("Total leaf count: %d", this.total_leaves) this.root.set_ids() } Octree.prototype.draw = function(target) { this.root.draw(target) } var octree = new Octree(octree_depth, canvas_size) /// function build_hatching() { list = [] const steps = hatching_density * 10 for (var i=0; i<steps; i++) { { var p0 = new Point(-canvas_size / 2 + canvas_size / steps * i, -canvas_size / 2) var p1 = new Point(canvas_size / 2, canvas_size / 2 - canvas_size / steps * i) var line = new Line(p0, p1) list.push(line) } if (i>0) { var p0 = new Point(-canvas_size / 2, -canvas_size / 2 + canvas_size / steps * i) var p1 = new Point(canvas_size / 2 - canvas_size / steps * i, canvas_size / 2) var line = new Line(p0, p1) list.unshift(line) } } const hack = 0.00001 for (var i=0; i<steps; i++) { { var p0 = new Point(hack+canvas_size / 2 - canvas_size / steps * i, -canvas_size / 2) var p1 = new Point(-canvas_size / 2, hack+canvas_size / 2 - canvas_size / steps * i) var line = new Line(p0, p1) list.push(line) } if (i>0) { var p0 = new Point(canvas_size / 2, hack-canvas_size / 2 + canvas_size / steps * i) var p1 = new Point(hack-canvas_size / 2 + canvas_size / steps * i, canvas_size / 2) var line = new Line(p0, p1) list.unshift(line) } } return list } const hatching = build_hatching()