moebius gears

getting the turtle into gears... ;-)
derived from "moebius gears" shaderoo.org/?shader=3gqsg3
by (sloppily) sorting polys in z-direction
and then using reinder's occlusion magic from "Cubic space division #2"

Log in to post a comment.

// created by florian berger (flockaroo) - 2018
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// derived from  "moebius gears" https://www.shaderoo.org/?shader=3GQsG3
// by (sloppily) sorting polys in z-direction (center only)
// and then using reinder's occlusion magic from  "Cubic space division #2"

Canvas.setpenopacity(1.);

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

const PI2 = Math.PI*2.0;
const GEAR_NUM = 9;
const TOOTH_NUM = 15;
const GEAR_W = .25;
const GEAR_H = .2;
const GEAR_INNER = .65;
const MOEB_R = 2.;
const TSPEED = 1.;
const RSPEED = 1.;
const iTime = 0.011; // hmm, some occlusion errors occur on some angles in iTime=0

function mcos(x) {
    return Math.cos(x);
}

function msin(x) {
    return Math.sin(x);
}

function cos2(x) {
    return [Math.cos(x[0]),Math.cos(x[1])];
}

function sin2(x) {
    return [Math.sin(x[0]),Math.sin(x[1])];
}

function SC(x) {
    return [Math.sin(x),Math.cos(x)];
}

function add3(a,b) {
    return [a[0]+b[0],a[1]+b[1],a[2]+b[2]];
}

function sub3(a,b) {
    return [a[0]-b[0],a[1]-b[1],a[2]-b[2]];
}

function dot3(a,b) {
    return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
}

function scale3(a,b) {
    return [a[0]*b,a[1]*b,a[2]*b];
}

function mymix(a,b,f) {
    return a*(1.0-f)+b*f;
}

function length3(a) {
    return Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);
}

function normalize3(a) {
    return scale3(a,1.0/length3(a));
}

function cross(a,b) {
    return [
        a[1]*b[2]-b[1]*a[2],
        a[2]*b[0]-b[2]*a[0],
        a[0]*b[1]-b[0]*a[1]
    ];
}

function inverseQuat(q)
{
    //return vec4(-q.xyz,q.w)/length(q);
    // if already normalized this is enough
    return [-q[0],-q[1],-q[2],q[3]];
}

function multQuat(a,b)
{
    //return vec4(cross(a.xyz,b.xyz) + a.xyz*b.w + b.xyz*a.w, a.w*b.w - dot(a.xyz,b.xyz));
    var v=add3(add3(cross(a,b), scale3(a,b[3])), scale3(b,a[3]));
    var w=a[3]*b[3]-dot3(a,b);
    return [v[0],v[1],v[2],w];
}

function transformVecByQuat( v, q )
{
    //return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w*v );
    return add3(v, scale3(cross( q, add3(cross( q, v ) , scale3(v,q[3]) )) ,2.0));
}

function axAng2Quat(ax, ang)
{
    //return vec4(normalize(ax),1)*sin(vec2(ang*.5)+vec2(0,PI2*.25)).xxxy;
    var s=sin2([ang*0.5,ang*0.5+PI2*0.25]);
    var nax=normalize3(ax);
    return [nax[0]*s[0],nax[1]*s[0],nax[2]*s[0],s[1]];
}

const GEAR_SEG_NUM = 60;
const GEAR_TRI_NUM = (GEAR_SEG_NUM*8);

const inuAll=[
        [1,0,1],
        [1,1,1],
        [0,0,1],
        [1,1,1],
        [0,1,1],
        [0,0,1],
        
        [0,0,1],
        [0,1,1],
        [0,0,0],
        [0,1,1],
        [0,1,0],
        [0,0,0],
        
        [0,0,0],
        [0,1,0],
        [1,0,0],
        [0,1,0],
        [1,1,0],
        [1,0,0],
        
        [1,0,0],
        [1,1,0],
        [1,0,1],
        [1,1,0],
        [1,1,1],
        [1,0,1]
        ];

function gearPos(idx)
{
    var idx24=idx%(8*3);
    var ang0=PI2/(GEAR_SEG_NUM)*Math.floor(idx/24);
    var ang1=PI2/(GEAR_SEG_NUM)*Math.floor(idx/24+1);
    var ri  = GEAR_INNER;
    var rin = GEAR_INNER;
    var ro  = 1.0+GEAR_H*.5*Math.cos(ang0*(TOOTH_NUM));
    var ron = 1.0+GEAR_H*.5*Math.cos(ang1*(TOOTH_NUM));
    var w = GEAR_W;
    var quadIdx=Math.floor(idx24/6);
    var inu=inuAll[idx24];
    var r   = mymix(mymix(ro,ron,inu[1]),mymix(ri,rin,inu[1]),inu[0]);
    var ang = mymix(ang0,ang1,inu[1]);
    var z   = mymix(-w,w,inu[2]);
    return [msin(ang)*r,mcos(ang)*r,z];
}

function gearsTri(idx)
{
    var gear=Math.floor(idx/(GEAR_TRI_NUM*3));
    var gidx=Math.floor(idx%(GEAR_TRI_NUM*3));
    var dang=PI2/(GEAR_NUM-.5);
    var R=MOEB_R;

        var ang=gear*dang;
        if(ang>PI2*2.) ang-=PI2*2.;
        if(ang<0.0) ang+=PI2*2.;
        var s=(Math.floor(ang/dang+.01)%2)*2.-1.;
	    var r=R*Math.tan(dang*.5);
        // radial offset of gears is different depending if lying or upright
        //R*(1+tan(dang*.5)*GEAR_W*.5) in upright case
        //R/cos(dang*.5) in lying case
        var pos=scale3([Math.cos(ang),Math.cos(ang+PI2*.25),0],
                   mymix(R*(1.+Math.tan(dang*.5)*GEAR_W*.5),
                     R/Math.cos(dang*.5),
                     -Math.cos(ang*.5+iTime*TSPEED)*.5+.5
                    )*1.02);
        var quat=[0,.707107,0,.707107];
        quat=multQuat(quat,axAng2Quat([1,0,0],2.*Math.sin(iTime*1.5*RSPEED)*s));
        quat=multQuat(quat,axAng2Quat([0,1,0],ang*.25+iTime*.5*TSPEED));
        quat=multQuat(quat,axAng2Quat([0,0,1],ang));
        
    if(gear>GEAR_NUM*2) return [0,0,0];
    
    return add3(pos,transformVecByQuat(scale3(gearPos(gidx),r),inverseQuat(quat)));
}

function rotX(ph,v) {
    return [ v[0],v[1]*mcos(ph)+v[2]*msin(ph), v[2]*mcos(ph)-v[1]*msin(ph) ];
}

function project(p)
{
    p[2]+=180;
    return [p[0]/p[2]*180.,p[1]/p[2]*180.,p[2]];
}

function insertQuad(p0,p1,p2,p3)
{
    var z = p0[2]+p1[2]+p2[2]+p3[2];
    var idx=0;
    for(idx=0;idx<quads.length && quads[idx+8]<z;idx+=9);
    // hmm, why is the one below not working... !?
    //for(var i=0;i<quads.length;i+=9) {
    //    if(quads[i+8]>z) { idx=i; break; }
    //}
    quads.splice(idx, 0, p0[0], p0[1], p1[0], p1[1], p2[0], p2[1], p3[0], p3[1], z);
}

function walk(i) {
    var num = (2*GEAR_NUM-1)*15*16;
    if(i==0){
        for(let j=0;j<num;j++) {
            var p0=gearsTri(j*6);
            var p1=gearsTri(j*6+1);
            var p2=gearsTri(j*6+2);
            var p3=gearsTri(j*6+4);
            p0=scale3(p0,28.0);
            p1=scale3(p1,28.0);
            p2=scale3(p2,28.0);
            p3=scale3(p3,28.0);
            p0=rotX(1.,p0);
            p1=rotX(1.,p1);
            p2=rotX(1.,p2);
            p3=rotX(1.,p3);
            p0=project(p0);
            p1=project(p1);
            p2=project(p2);
            p3=project(p3);
    
            if(cross(sub3(p1,p0),sub3(p2,p0))[2]<0.0)
            {
                insertQuad(p0,p2,p3,p1);
            }
        }
    }
    
    var p0=[quads[i*9+0],quads[i*9+1]];
    var p1=[quads[i*9+2],quads[i*9+3]];
    var p2=[quads[i*9+4],quads[i*9+5]];
    var p3=[quads[i*9+6],quads[i*9+7]];
    const p = new Polygon();
    p.cp.push([p0[0], p0[1]]);
    p.cp.push([p1[0], p1[1]]);
    p.cp.push([p2[0], p2[1]]);
    p.cp.push([p3[0], p3[1]]);
    p.addOutline(0);
    drawPolygon(turtle, p);
    /*turtle.penup(p0);
    turtle.goto(p0);
    turtle.pendown(p0);
    turtle.goto(p1);
    turtle.goto(p2);
    turtle.goto(p3);
    turtle.goto(p0);*/
    return i <= num/1.8;
}

////////////////////////////
// reinder's occlusion code parts from "Cubic space division #2"
////////////////////////////

function drawPolygon(turtle, p) {
    let vis = true;
    for (let j=0; j<polygonList.length; j++) {
        if(!p.boolean(polygonList[j])) {
            vis = false;
            break;
        }
    }
    if (vis) {
        p.draw(turtle, 0);
        polygonList.push(p);
    }
}

// polygon functions
function LineSegment(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
}
function Polygon() {
    this.cp = []; // clip path: array of [x,y] pairs
    this.dp = []; // 2d line to draw: array of linesegments
}
Polygon.prototype.addOutline = function(s=0) {
    for (let i=s, l=this.cp.length; i<l; i++) {
        this.dp.push(new LineSegment(this.cp[i], this.cp[(i+1)%l]));
    }
}
Polygon.prototype.createPoly = function(x,y,c,r,a) {
    this.cp = [];
    for (let i=0; i<c; i++) {
        this.cp.push( [x + Math.sin(i*Math.PI*2/c+a) * r, y + Math.cos(i*Math.PI*2/c+a) * r] );
    }
}
Polygon.prototype.draw = function(t, inp=0) {
    if (this.dp.length ==0) {
        return;
    }
    for (let i=0, l=this.dp.length; i<l; i++) {
        const d = this.dp[i];
        if (!vec2_equal(d.p1, t.pos())) {
            t.penup();
            t.goto([d.p1[0]+inp*(Math.random()-.5), d.p1[1]+inp*(Math.random()-.5)]);
            t.pendown();   
        }
        t.goto([d.p2[0]+inp*(Math.random()-.5), d.p2[1]+inp*(Math.random()-.5)]);
    }
}
Polygon.prototype.inside = function(p) {
    // find number of i ntersection points from p to far away
    // if even your outside
    const p1 = [0.1, -1000];
    let int = 0;
    for (let i=0, l=this.cp.length; i<l; i++) {
        if (vec2_find_segment_intersect(p, p1, this.cp[i], this.cp[(i+1)%l])) {
            int ++;
        }    
    }
    return int & 1;
}
Polygon.prototype.boolean = function(p, diff = true) {
    // very naive polygon diff algorithm - made this up myself
    const ndp = [];
    for (let i=0, l=this.dp.length; i<l; i++) {
        const ls = this.dp[i];
        
        // find all intersections with clip path
        const int = [];
        for (let j=0, cl=p.cp.length; j<cl; j++) {
            const pint = vec2_find_segment_intersect(ls.p1,ls.p2,p.cp[j],p.cp[(j+1)%cl]);
            if (pint) {
                int.push(pint);
            }
        }
        if (int.length == 0) { // 0 intersections, inside or outside?
            if (diff == !p.inside(ls.p1)) {
                ndp.push(ls);
            }
        } else {
            int.push(ls.p1);
            int.push(ls.p2);
            // order intersection points on line ls.p1 to ls.p2
            const cmp = [ls.p2[0]-ls.p1[0], ls.p2[1]-ls.p1[1]];
            int.sort( (a,b) => {
                const db = vec2_dot([b[0]-ls.p1[0], b[1]-ls.p1[1]], cmp);
                const da = vec2_dot([a[0]-ls.p1[0], a[1]-ls.p1[1]], cmp);
                return da - db;
            });
            for (let j=0; j<int.length-1; j++) {
                if (!vec2_equal(int[j], int[j+1])) {
                    if (diff == !p.inside([(int[j][0]+int[j+1][0])/2,(int[j][1]+int[j+1][1])/2])) {
                        ndp.push(new LineSegment(int[j], int[j+1]));
                    }
                }
            }
        }
    }
    this.dp = ndp;
    return this.dp.length > 0;
}

// vec functions
const vec2_equal = (a,b) => vec2_dist_sqr(a,b) < 0.01;
const vec2_dot = (a, b) => a[0]*b[0]+a[1]*b[1];
const vec2_dist_sqr = (a, b) => (a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]);
//port of http://paulbourke.net/geometry/pointlineplane/Helpers.cs
function vec2_find_segment_intersect(l1p1, l1p2, l2p1, l2p2) {
    const d = (l2p2[1] - l2p1[1]) * (l1p2[0] - l1p1[0]) - (l2p2[0] - l2p1[0]) * (l1p2[1] - l1p1[1]);
    const n_a = (l2p2[0] - l2p1[0]) * (l1p1[1] - l2p1[1]) - (l2p2[1] - l2p1[1]) * (l1p1[0] - l2p1[0]);
    const n_b = (l1p2[0] - l1p1[0]) * (l1p1[1] - l2p1[1]) - (l1p2[1] - l1p1[1]) * (l1p1[0] - l2p1[0]);
    if (d == 0) {
        return false;
    }
    const ua = n_a / d;
    const ub = n_b / d;
    if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
        return [l1p1[0] + (ua * (l1p2[0] - l1p1[0])), l1p1[1] + (ua * (l1p2[1] - l1p1[1])) ];
    }
    return false;  
}