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;
}