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