Quadmandeltree - debug

Mandelbrot rendered as a quad tree.
Debug version.

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 = 10000, 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 = 7      // min = 1, max = 12, 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)
{
    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
    }
}

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)
    {
        const density = (this.value * (1 - min_factor) + min_factor)
        const grid = Math.round(4 * density) / 4
        const step = 0.5 / grid
        for (var f=0; f<this.size; f+=step)
        {
            turtle.penup()
            turtle.goto(this.x + f, this.y)
            turtle.pendown()
            turtle.goto(this.x + this.size, this.y + this.size - f)

            if (f > 0)
            {
                turtle.penup()
                turtle.goto(this.x, this.y + f)
                turtle.pendown()
                turtle.goto(this.x + this.size - f, this.y + this.size)
            }
        }

        var center_x = this.x + this.size / 2
        var center_y = this.y + this.size / 2
        var size = this.size / 2
        
        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)
        turtle.goto(center_x - size, center_y + size)
        turtle.goto(center_x - size, center_y - size)

        turtle.penup()
        const string = `${this.value}`
        const text_size = 0.4 / octree_depth
        turtle.goto(this.x + this.size / 2 - string.length * 10 * text_size, this.y + this.size / 2)
        text.print(turtle, string, text_size)

        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)

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
////////////////////////////////////////////////////////////////

function Text() {
    class Text {
        print (t, str, scale = 1, italic = 0, kerning = 1) {
            t.radians();
            let pos = [t.x(), t.y()], h = t.h(), o = pos;
            str.split('').map(c => {
                const i = c.charCodeAt(0) - 32;
                if (i < 0 ) {
                    pos = o = this.rotAdd([0, 48*scale], o, h);
                } else if (i > 96 ) {
                    pos = this.rotAdd([16*scale, 0], o, h);
                } else {
                    const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2];
                    paths.map( p => {
                        t.up();
                    	p.map( s=> {
                        	t.goto(this.rotAdd([(s[0]-s[1]*italic)*scale - lt, s[1]*scale], pos, h));
                        	t.down();
                        });
                    });
                    pos = this.rotAdd([(rt - lt)*kerning, 0], pos, h);
                }
            });
        }
        rotAdd (a, b, h) {
            return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], 
                    Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];
        }
    }
    
const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefg'+
'gmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfph'+
'nmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>c'+
'qfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti'+
'^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrk'+
'shserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlping'+
'kfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjd'+
'hcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wag'+
'sg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmh'+
'n<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o'+
'_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnop'+
'mrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<'+
'q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c'+
'^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcnd'+
'pfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqpo'+
'rlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs'+
'<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkc'+
'ieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfeh'+
'dkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_j'+
'bjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^'+
'k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofp'+
'ips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkd'+
'mepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsks'+
'mrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj['+
'i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjci'+
'eigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhill'+
'nmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map(
    r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret 
    = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0)
    -106));} return ret; })]; });

    return new Text();
}

const text = new Text();