Log in to post a comment.

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(1);

// Global code will be evaluated once.
const turtle = new Turtle();


let a1 = 0; // min = -720, max=720, step=1
let a2 = 46; // min = -90, max=90, step=1
let cd = 71.9; // min = 0, max=360, step=0.1
let perspective = 1; // min=0, max=1, step=1,  (off, on)
let viewSize = 200; // min=10, max=400, step=1
let fov = 90; // min=10, max=160, step=0.1

let radius = 109; // min = 10, max=200, step=1
let amount = 9800; // min=0, max=50000, step=1
let rainLen = 2.2; // min=0, max=100, step=0.1
let circleSize = 3.8; // min=0, max=30, step=0.1

let boxSize = 27.6; // min=1, max=50, step=0.1

function circle(p, r) {
    let loop = [];
    const STEPS =32;
    for (let i=0; i<STEPS; i++) {
        let a = Math.PI * 2 * i / STEPS;
        let p2 = new V3(Math.sin(a), Math.cos(a));
        loop.push(p2.mul(r).add(p));
    }
    return Ob.fromLoop(loop);
}

// The walk function will be called until it returns false.
function walk(i) {
    initlib();
    let scene = new Scene();
    if (perspective > 0) {
        scene.fov = [fov, fov];
        scene.w = scene.h = viewSize;
        scene.setPerspective(new V3(0, 0, 0), new V3(0, 0, 0));    
    } else {
        scene.setOrthographic(1, new V3(0,0,0), new V3(0, 0, 0));  
    }
    scene.camera_pos = Scene.worldCameraOrbit(new V3(0, 0, 0), cd, a1, a2);
    //
    
    let cube = new Ob([
            //[0, 0, 0, 20, 0, 0],
            [0, 0, 0, 10, 0, 0],
            [11, 0, 0, 20, 0, 0],
            //[0, 0, 0, 0, 20, 0],
            [0, 0, 0, 0, 10, 0],
            [0, 11, 0, 0, 20, 0],
            [0, 0, 0, 0, 0, 20],
    ]);

    //scene.addOb(cube);
    /*scene.addLine(new V3(50, 5, 0), new V3(50, 0, 0));
    scene.addLine(new V3(0, 0, 0), new V3(0, 50, 0));
    scene.addLine(new V3(0, 0, 0), new V3(0, 0, 25));*/
    let sdFunc = SDF.move(SDF.sphere(V(0, 0, 0), boxSize), V(0, 0, boxSize) );// SDF.box(V(20, 10, 10));
    
    for (let i =0; i<amount; i++) {
        let p = new V3(Util.rndRange(-radius, radius), Util.rndRange(-radius, radius), Util.rndRange(-0.1*radius, radius));
        if (p.magnitude() > radius) {
            continue;
        }
        let posXY = p.changez(0);
        let p2 = p.add(new V3(0, 0, Util.rndRange(rainLen, rainLen * 1.05)));
        const [hit, travel] = SDF.runRay(sdFunc, posXY.changez(radius), V(0, 0, -1), radius);
        let h = hit.z;
        
        
        
        if (p.z < h && h > 0.01) { 
            if (p.z < h && h-p.z < circleSize ) {
                let r=  h-p.z;
                if (h > 0.01) {
                    r = 0.5;
                }
                scene.addOb(circle(p.changez(h), r));
                p.z = Math.max(h, p.z);
                p2.z = Math.max(h, p2.z);
                scene.addLine(p2, p);  
            } else {
               let d = Math.random() * Math.PI * 2;
               let dv = V(Math.cos(d), Math.sin(d));
               let l=0, r = radius;
               for (let i=0; i<20; i++) {
                   let m = (l+r)/2;
                   let pSide = posXY.changez(radius).add(dv.mul(m));
                   let [hit2, travel2] = SDF.runRay(sdFunc, pSide, V(0, 0, -1), radius);
                   if (hit2.z <= 0.01) {
                       r = m;
                   } else {
                       l = m;
                   }
               }
               let pnew = posXY.add(dv.mul(l));
               p = pnew.changez(Math.max(0, p.z));
               p2 = pnew.changez(Math.max(0, p2.z));
               scene.addLine(p2, p);
            }
        } else {
           if (p.z < h && h-p.z < circleSize ) {
                let r=  h-p.z;
                if (h > 0.01) {
                    r = 2;
                }
                scene.addOb(circle(p.changez(h), r));
            } 
            p.z = Math.max(h, p.z);
            p2.z = Math.max(h, p2.z);
            scene.addLine(p2, p);    
        }
        
        
        
        
        
    }
    /*let o1 = new Ob();
    scene.addLine(new V3(0, 0, 0), new V3(50, 0, 0));
    scene.addLine(new V3(50, 5, 0), new V3(50, 0, 0));
    scene.addLine(new V3(0, 0, 0), new V3(0, 50, 0));
    scene.addLine(new V3(0, 0, 0), new V3(0, 0, 25));
    
    
    //o1.transform(M4.translate(new V3(-10, -10, -10)));
    scene.addOb(o1);*/
    scene.draw();
    return false;
}

class V3 {
    constructor(x=0, y=0, z=0) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
    static V0 = new V3(0, 0, 0);
    toString() { return `V3(${this.x}, ${this.y}, ${this.z})`; }
    add(b) { return new V3(this.x + b.x, this.y + b.y, this.z + b.z); }
    sub(b) { return new V3(this.x - b.x, this.y - b.y, this.z - b.z); }
    mul(b) { return new V3(this.x  * b, this.y * b, this.z * b); }
    scale(b) { return new V3(this.x  * b.x, this.y * b.y, this.z * b.z); }
    flipx() { return new V3(-this.x, this.y, this.z); }
    flipy() { return new V3(this.x, -this.y, this.z); }
    copy() { return new V3(this.x,this.y,this.z); }
    changex(v) { let res = this.copy(); res.x = v; return res;}
    changey(v) { let res = this.copy(); res.y = v; return res;}
    changez(v) { let res = this.copy(); res.z = v;    return res; }
    len2() { return this.x*this.x + this.y*this.y + this.z*this.z; }
    static lerp(a, b, x) { return a.mul(1-x).add(b.mul(x)); }
    magnitude() { return Math.sqrt(this.len2()); }
    normalized() { return this.mul(1/this.magnitude()); }
    xyzs() { return this.x + this.y + this.z }
    xyzMax() { return Math.max(this.x, this.y, this.z); }
    xyzMin() { return Math.min(this.x, this.y, this.z); }
    
    abs() { return new V3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)); }
    max(b) { return new V3(Math.max(this.x, b.x), Math.max(this.y, b.y), Math.max(this.z, b.z)); } 
    min(b) { return new V3(Math.min(this.x, b.x), Math.min(this.y, b.y), Math.min(this.z, b.z)); } 
    maxK(k) { return new V3(Math.max(this.x, k), Math.max(this.y, k), Math.max(this.z, k)); } 
    minK(k) { return new V3(Math.min(this.x, k), Math.min(this.y, k), Math.min(this.z, k)); } 
    dot(b) {
        return this.scale(b).xyzs();
    }
    rotDeg(deg) {
        const a = deg*Math.PI/180;
        const s = Math.sin(a);
        const c = Math.cos(a);
        return new V2(this.x*c-this.y*s, this.x*s+this.y*c);
    }
}
class M4 {
    constructor() {
        this.d = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]];
    }
    mul(b) {
        let res = new M4();
        for (let i=0; i<4; i++) {
            for (let j=0; j<4; j++) {
                let s = 0;
                for (let k=0; k<4; k++) {
                    s += this.d[i][k] * b.d[k][j]
                }
                res.d[i][j] = s;
            }
        }
        return res;
    }
    transpose() {
        let res = new M4();
        for (let i=0; i<4; i++) {
            for (let j=0; j<4; j++) {
                res.d[i][j] = this.d[j][i];
            }
        }
        return res;
    }
    mulv(v) {
        let vv = [v.x, v.y, v.z, 1];
        let res = [0, 0, 0, 0];
        for (let i=0; i<4; i++) {
            for (let k=0; k<4; k++) {
                res[i] += this.d[i][k] * vv[k];
            }
        }
        return new V3(res[0], res[1], res[2]);
    }
    static translate(v) {
        let r = new M4();
        r.d[0][3] = v.x;
        r.d[1][3] = v.y;
        r.d[2][3] = v.z;
        return r;
    }
    static euler(a,b,c) {
        let m1 = new M4();
        let m2 = new M4();
        let m3 = new M4();
        m1.d = [[1, 0, 0, 0],
                [0, Math.cos(a), -Math.sin(a), 0],
                [0, Math.sin(a), Math.cos(a), 0],
                [0, 0, 0, 1]];
        m2.d = [[Math.cos(b), 0, Math.sin(b), 0],
                [0, 1, 0, 0],
                [-Math.sin(b), 0, Math.cos(b), 0],
                [0, 0, 0, 1]];
        m3.d = [[Math.cos(c), -Math.sin(c), 0, 0],
                [Math.sin(c), Math.cos(c), 0, 0],
                [0, 0, 1, 0],
                [0, 0, 0, 1]];
        return m1.mul(m2).mul(m3);
    }
    static eulerv(v) {
        return euler(v.x, v.y, v.z);
    }
}
class Util {
    static rndRange(min, max) {
        return Math.random() * (max-min)+min;
    }
    static inverseLerp(a, b, x) {
        let d = b-a;
        if (d != 0) {
            return (x-a)/d;
        } else {
            return 0;
        }
    }
    static lerp(a, b, x) {
        return (1-x)*a + x*b;
    }
    static planeDistance(p0, pnorm, x) {
        pnorm = pnorm.normalized();
        let k = p0.dot(pnorm);
        return x.dot(pnorm) - k;
    }
    static rectContains(r, p) {
        return p.x >= r[0] && p.x <= r[1] && p.y >= r[2] && p.y <= r[3];
    }
    static planeClip(p0, pnorm, a, b) {
        let d1 = Util.planeDistance(p0, pnorm, a);
        let d2 = Util.planeDistance(p0, pnorm, b);
        if (d1 >= 0 && d2 >= 0) {
            return [a, b];
        }
        if (d1 < 0 && d2 < 0) {
            return null;
        }
        let m = Util.inverseLerp(d1, d2, 0);
        let p = V3.lerp(a, b, m);
        if (d1 < 0) {
            return [p, b];
        } else {
            return [a, p];
        }
    }
    
    static clipRect(a,b,r) {
        let ca = rectContains(r, a);
        let cb = rectContains(r, b);
        if (ca && cb) {
            return [a, b];
        }
    }
}
class Scene {
    constructor() {
        this.camera_pos = Scene.worldCameraM(new V3(0, 0, 0), new V3(1, 0, 0));
        this.camera = Scene.orthographic1();
        this.lines=[];
        this.ortho = true;
        this.w=100;
        this.h=100;
        this.fov = [90, 90];
    }
    addLine(a,b) {
        this.lines.push([a, b]);
    }
    addOb(x) {
        this.lines.push(...x.lines);
    }
    mapPoint(x) {
        let p = this.camera.mulv(x);
        if (p.z != 0) {
            p = p.mul(1/Math.abs(p.z));
        } else {
            p.x = 0; p.y = 0;
        }
        return [p.x, p.y];
    }
    draw() {
        this.lines.forEach((l) => {
            let debug=false;
            if (l[0].z > 0 && l[1].z > 0) {
                debug=true;
            }
           let p1 = this.camera_pos.mulv(l[0]);
           let p2 = this.camera_pos.mulv(l[1]);
           
           
           /*if (p1.z > 0 && p2.z > 0) {
               return;
           } else if (p1.z > 0 || p2.z > 0) {
               let x = Util.inverseLerp(p1.z, p2.z, 0.011);
               if (p1.z > 0) {
                   p1 = V3.lerp(p1, p2, x);
               } else {
                   p2 = V3.lerp(p1, p2, x);
               }
           }*/
           if (!this.ortho) {
               let p0 = new V3(0, 0, 0);
               let norm = new V3();
               let line = [p1, p2];
               let a1 = Math.PI * 0.5*this.fov[0]/180;
               let a2 = Math.PI * 0.5*this.fov[1]/180;
               if (line) {
                    norm = new V3(Math.cos(a1), 0, -Math.sin(a1));
                    line = Util.planeClip(p0, norm, line[0], line[1]);
               }
               if (line) {
                    norm = new V3(-Math.cos(a1), 0, -Math.sin(a1));
                    line = Util.planeClip(p0, norm, line[0], line[1]);
               }
               if (line) {
                    norm = new V3(0, -Math.cos(a2), -Math.sin(a2));
                    line = Util.planeClip(p0, norm, line[0], line[1]);
               }
               if (line) {
                    norm = new V3(0, Math.cos(a2), -Math.sin(a2));
                    line = Util.planeClip(p0, norm, line[0], line[1]);
               }
               if (line) {
                   p1 = line[0];
                   p2 = line[1];
               } else {
                   return;
               }
           }
           p1 = this.mapPoint(p1);
           p2 = this.mapPoint(p2);
           turtle.jump(p1)
           turtle.pendown();
           turtle.goto(p2);
        });
    }
    static worldCameraM(from, to) {
        let d = to.sub(from);
        let m1 = M4.translate(from.mul(-1));
        let a1 = Math.atan2(d.y, d.x);
        let xy = d.changez(0);
        
        let a2 = Math.atan2(d.z, xy.magnitude());
        //let m2 = (new M4()).mul(M4.euler(0, 0, +a1)).mul(M4.euler(-Math.PI/2 - a2, 0, 0));
        let m2 = (new M4()).mul(M4.euler(-a2, -a1, 0)).mul(M4.euler(Math.PI/2, Math.PI, -Math.PI/2));
        return m2.mul(m1);
    }
    static worldCameraOrbit(to, distance, z0, x0) {
        let x = x0* Math.PI / 180;
        let z = z0* Math.PI / 180;
        let p = M4.euler(0, 0, z).mul(M4.euler(0, x, 0)).mulv(new V3(-1, 0, 0));
        let p2 = p.mul(distance).add(to);
        if (x0 > -90 && x0 < 90) {
            return Scene.worldCameraM(p2, p2.sub(p));
        } else {
            let m1 = M4.translate(p2.mul(-1));
            if (x0 > 0) {
                return M4.euler(0, 0, 1*Math.PI/2-z).mul(m1);    
            } else {
                return M4.euler(0, Math.PI, -1*Math.PI/2-z).mul(m1);    
            }
            
        }
    }
    
   static orthographic1(scale=1) {
        let res = new M4();
        res.d = [
            [scale,0,0,0],
            [0,-scale,0,0],
            [0, 0, 0,1],
            [0,0,0,1],
        ];
        //res = res.mul(Scene.worldCameraM(from, to));
        res = res;
        return res;
    }
    setOrthographic(scale, from, to) {
        this.camera = Scene.orthographic1(scale);
        this.camera_pos = Scene.worldCameraM(from, to);
        this.ortho = true;
    }
    static perspective(scale) {
        let res = new M4();
        res.d = [
            [scale,0,0,0],
            [0, -scale,0,0],
            [0, 0, -1,0],
            [0,0,0,1],
        ];
        return res;
    }
    setPerspective(from, to) {
        
        this.camera = Scene.perspective(this.w*0.5 * Math.tan((90-this.fov[0]*0.5)/180*Math.PI));
        this.camera_pos = Scene.worldCameraM(from, to);
        this.ortho = false;
    }
}
class Ob {
    constructor(linesa=[]){
        this.lines=[];
        if (linesa) {
            this.addLines(linesa);    
        }
    }
    static fromChain(points) {
        let res = new Ob();
        for (let i=1; i<points.length; i++) {
            res.addLine(points[i-1], points[i]);
        }
        return res;
    }
    static fromLoop(points) {
        let res = Ob.fromChain(points);
        if (points.length > 1) {
            res.addLine(points[points.length-1], points[0]);
        }
        return res;
    }
    addLine(a,b) {
        this.lines.push([a, b]);
    }
    addLines(ar) {
        for (const x of ar) {
            this.addLine(new V3(x[0], x[1], x[2]), new V3(x[3], x[4], x[5]));
        }
    }
    transform(m) {
        this.lines.forEach((v, i) => {
            this.lines[i] = [m.mulv(v[0]), m.mulv(v[1])];
        });
    }
}
class SDF {
    static bind1(f, ...args) {
        return function(...x) {
            f.bind(null, ...x);
        }
    }
    // combinations
    static unionD(a, b, x) { return Math.min(a(x), b(x)); }
    static union(a, b) { return SDF.unionD.bind(null, a, b); }
    static diffD(a, b, x) { return Math.max(-a(x), b(x)); }
    static diff(a, b) { return SDF.diffD.bind(a, b); }
    static intersectionD(a, b, x) { return Math.max(a(x), b(x)); }
    static intersection(a, b) { return SDF.intersectionD.bind(a, b); }
    static xorD(a, b, x) { 
        let d1=a(x), d2=b(x);
        return Math.max(min(d1, d2), -max(d1, d2)); 
    }
    static xor(a, b) { return SDF.xorD.bind(a, b); }
    static moveD(f, p, x) {
        return f(x.sub(p));
    }
    static move(f, p) {
        return SDF.moveD.bind(null, f, p);
    }
    static rotateD(f, euler, x) {
        return f(M4.euler(euler).mulV(x));
    }
    static rotate(f, euler) { return SDF.rotateD.bind(f, euler); }
    
    // primitives
    static sphereD(p, r, x) {
        let dv = x.sub(p);
        return dv.magnitude() - r;
    }
    static sphere(p, r) {
        //SDF.bind1(SDF.sphereD);
        return SDF.sphereD.bind(null, p, r);
    }
    static boxD(s, x) {
        let q = x.abs().sub(s);
        return q.maxK(0).magnitude() + Math.min(q.xyzMax());
    }
    static box = SDF.bind1(SDF.boxD);
    // 
    static runRay(f, p0, dir, limit) {
        let travel = 0;
        let p = p0;
        while (travel < limit) {
            let distance = f(p);
            if (distance < 0.001) {
                break;
            }
            distance = Math.min(limit-travel, distance);
            p = p.add(dir.mul(distance));
            travel += distance;
        }
        return [p, travel];
    }
}
function initlib() {
    this.V3 = V3;
    this.V = (x,y,z)=>new V3(x, y, z);
    this.M4 = M4;
    this.Ob = Ob;
    this.Scene = Scene;
    this.SDF = SDF;
}