Floating pyramid segments 🎈

Not much to add to the title except that Reinder's Polygon Clipping thing is quite nice...

Log in to post a comment.

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

const thickness = .2; // min=.1 max=1 step=.1
const margin = .1; // min=.1 max=1 step=.1

// Global code will be evaluated once.
const turtle = new Turtle();
const polygons = new Polygons();
const text = new Text();
turtle.radians();

//plotHelperGrid();
let v = new Vec2(25, -90);
let d1 = new Vec2(15, 130);
let d2 = new Vec2(-70, 90);
let d3 = new Vec2(30, 70);

let topHatch = {angle: 1, distance: .2}
let leftHatch = {angle: 2, distance: .5}
let rightHatch = {angle: 3, distance: .35}

// The walk function will be called until it returns false.
function walk(i) {
    let s = i * thickness + i * margin;
    let t = s + thickness;
    
    ptsTop = [
        v.clone().add(d1.clone().scale(s)),
        v.clone().add(d2.clone().scale(s)),
        v.clone().add(d3.clone().scale(s))
    ];
    ptsLeft = [
        v.clone().add(d1.clone().scale(s)),
        v.clone().add(d2.clone().scale(s)),
        v.clone().add(d2.clone().scale(t)),
        v.clone().add(d1.clone().scale(t))
    ];
    ptsRight = [
        v.clone().add(d1.clone().scale(s)),
        v.clone().add(d3.clone().scale(s)),
        v.clone().add(d3.clone().scale(t)),
        v.clone().add(d1.clone().scale(t))
    ];
    drawVecs(ptsTop, topHatch);
    drawVecs(ptsLeft, leftHatch);
    drawVecs(ptsRight, rightHatch);
    
    return t < 1.2;
}

function write(x, y, txt, scale, heading) {
    let h = turtle.h();
    turtle.seth(heading);
    turtle.jump(x, y);
    text.print(turtle, txt, scale); 
    turtle.seth(h);
}

function drawVecs(vecs, hatch = null) {
    let points = vecs.map(v => [v.x, v.y]);
    
    let p = polygons.create();
    p.addPoints(...points);
    p.addOutline();
    if(hatch != null) {
        p.addHatching(hatch.angle, hatch.distance);
    }
    polygons.draw(turtle, p, true);
    
}

function plotHelperGrid(d = 10) {
    let start = (Math.floor(100 / d) * d) + (d / 2);

    for(let x = -start; x < 100; x+=d) {
        turtle.jump(x, -100);
        turtle.goto(x, 100);
        turtle.jump(-100, x);
        turtle.goto(100, x);
    }
    for(let x = -.1; x < .2; x+=.1) {
        turtle.jump(x, -100);
        turtle.goto(x, 100);
        turtle.jump(-100, x);
        turtle.goto(100, x);
    }
}
////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){let t=[];const s=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,h=1e5,i=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),h=Math.min(h,t[1]),i=Math.max(i,t[1])}),this.aabb=[(s+e)/2,(h+i)/2,(e-s)/2,(i-h)/2]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t,e){const h=new s;h.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const i=Math.sin(t)*e,n=Math.cos(t)*e,a=200*Math.sin(t),p=200*Math.cos(t);for(let t=.5;t<150/e;t++)h.dp.push([i*t+p,n*t-a],[i*t-p,n*t+a]),h.dp.push([-i*t+p,-n*t-a],[-i*t-p,-n*t+a]);h.boolean(this,!1),this.dp=[...this.dp,...h.dp]}inside(t){let s=0;for(let e=0,h=this.cp.length;e<h;e++)this.segment_intersect(t,[.13,-1e3],this.cp[e],this.cp[(e+1)%h])&&s++;return 1&s}boolean(t,s=!0){if(s&&Math.abs(this.aabb[0]-t.aabb[0])-(t.aabb[2]+this.aabb[2])>=0&&Math.abs(this.aabb[1]-t.aabb[1])-(t.aabb[3]+this.aabb[3])>=0)return this.dp.length>0;const e=[];for(let h=0,i=this.dp.length;h<i;h+=2){const i=this.dp[h],n=this.dp[h+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const h=this.segment_intersect(i,n,t.cp[s],t.cp[(s+1)%e]);!1!==h&&a.push(h)}if(0===a.length)s===!t.inside(i)&&e.push(i,n);else{a.push(i,n);const h=n[0]-i[0],p=n[1]-i[1];a.sort((t,s)=>(t[0]-i[0])*h+(t[1]-i[1])*p-(s[0]-i[0])*h-(s[1]-i[1])*p);for(let h=0;h<a.length-1;h++)(a[h][0]-a[h+1][0])**2+(a[h][1]-a[h+1][1])**2>=.001&&s===!t.inside([(a[h][0]+a[h+1][0])/2,(a[h][1]+a[h+1][1])/2])&&e.push(a[h],a[h+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,h){const i=(h[1]-e[1])*(s[0]-t[0])-(h[0]-e[0])*(s[1]-t[1]);if(0===i)return!1;const n=((h[0]-e[0])*(t[1]-e[1])-(h[1]-e[1])*(t[0]-e[0]))/i,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/i;return n>=0&&n<=1&&a>=0&&a<=1&&[t[0]+n*(s[0]-t[0]),t[1]+n*(s[1]-t[1])]}};return{list:()=>t,create:()=>new s,draw:(s,e,h=!0)=>{for(let s=0;s<t.length&&e.boolean(t[s]);s++);e.draw(s),h&&t.push(e)}}}

////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
////////////////////////////////////////////////////////////////
function Text(){const s="br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefggmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfphnmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>cqfZh\\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^jfmfogphqkqmppnrkshserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlpingkfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjdhcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wagsg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmhn<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_oapcqfqkpnopmrjscs>`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_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqporlshsercp>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>fnkcieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfehdkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_jbjs<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<eihfjemeofpips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkdmepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsksmrpo<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`kbjcieigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhillnmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs".split(">").map(s=>[s.charCodeAt(0)-106,s.charCodeAt(1)-106,s.substr(2).split("<").map(s=>{const e=[];for(let p=0;p<s.length;p+=2)e.push(s.substr(p,2).split("").map(s=>s.charCodeAt(0)-106));return e})]);return new class{print(e,p,j=1,h=0,r=1){e.radians();let f=[e.x(),e.y()],o=e.h(),i=f;p.split("").map(p=>{const c=p.charCodeAt(0)-32;if(c<0)f=i=this.rotAdd([0,48*j],i,o);else if(c>96)f=this.rotAdd([16*j,0],i,o);else{const p=s[c],i=p[0]*j,d=p[1]*j;p[2].map(s=>{e.up(),s.map(s=>{e.goto(this.rotAdd([(s[0]-s[1]*h)*j-i,s[1]*j],f,o)),e.down()})}),f=this.rotAdd([(d-i)*r,0],f,o)}})}rotAdd(s,e,p){return[Math.cos(p)*s[0]-Math.sin(p)*s[1]+e[0],Math.cos(p)*s[1]+Math.sin(p)*s[0]+e[1]]}}}

/* Most methods by Jurgen, some are commodity, some from Reinder's vec2 functions, some methods implemented to comply with the Vector2 class of the Unity framework */
function Vec2(x, y) {if(typeof y == 'undefined') {this.x = x[0]; this.y = x[1];} else {this.x = x; this.y = y;}}
/* returns scalar: length of vector */
Vec2.prototype.length = function() {return Math.sqrt(this.dot(this));}
/* returns this after making this' length 1 */
Vec2.prototype.normalize = function() {var l = this.length(); this.x /= l; this.y /= l; return this;}
/* returns this after rotating this in radians in clockwise direction for positive rads */
Vec2.prototype.rotate = function(rads) {var x = (Math.cos(rads) * this.x) - (Math.sin(rads) * this.y); var y = (Math.sin(rads) * this.x) + (Math.cos(rads) * this.y); this.x = x; this.y = y; return this;}
/* returns Vec2: a copy of this */
Vec2.prototype.clone = function() {return new Vec2(this.x, this.y);}
/* returns this after adding parameter Vec2 */
Vec2.prototype.add = function(v) {this.x += v.x; this.y += v.y; return this;}
/* returns this after subtracting parameter Vec2 */
Vec2.prototype.subtract = function(v) {this.x -= v.x; this.y -= v.y; return this;}
/* returns scalar: dot product of this and parameter Vec2 */
Vec2.prototype.dot = function(v) {return (this.x * v.x) + (this.y * v.y);}
/* returns boolean: true if distance(squared) between this and parameter Vec2 v is smaller than optional parameter scalar margin */
Vec2.prototype.equals = function(v, margin = .001) {return this.distanceSquared(v) < margin;}
/* returns boolean: true if this and parameter Vec2 are on lines that are parallel to each other within scalar parameter margin */
Vec2.prototype.isParallel = function(v, margin = .001) { var thi = this.clone().normalize().scale(100); var tha = v.clone().normalize().scale(100); return thi.equals(tha, margin) || thi.equals(tha.scale(-1), margin);}
/* returns scalar: the squared distance between this an parameter Vec2 */
Vec2.prototype.distanceSquared = function(v) {return Math.pow(this.x - v.x, 2) + Math.pow(this.y - v.y, 2);}
/* returns scalar: the distance between this an parameter Vec2 */
Vec2.prototype.distance = function(v) {return Math.sqrt(this.distanceSquared(v));}
/* returns scalar: the angle in radians between this and parameter Vec2 */
Vec2.prototype.angle = function(v) {return Math.acos( this.dot(v) / (this.length() * v.length()) );}
/* returns Vec2: interpolated linearly between parameter t = 0 (this) and t = 1 (paramter Vec2) (e.g. t = .5 gives a Vec2 with minimum and equal distance to this and parameter Vec2 */
Vec2.prototype.lerp = function(v, t) {if(t < 0 || 1 < t) { throw new Error('Parameter t out of bounds for Vec2.lerp(v, t)'); } var dx = v.x - this.x; var dy = v.y - this.y; this.x += t * dx; this.y += t * dy; return this;}
/* returns Vec2: alias of lerp() */
Vec2.prototype.interpolateLinearly = function(v, t) {return this.lerp(v, t);}
/* returns this after multiplying itself with scalar parameter */
Vec2.prototype.scale = function(s) {this.x *= s; this.y *= s; return this;}
/* returns array[2]: an array representation of this as [x, y] */
Vec2.prototype.simple = function() {return [this.x, this.y];}
/* returns this after making this the length of parameter length */
Vec2.prototype.scaleAbsolute = function(length) {return this.scale(length / this.length());}
/* returns this: alias of scale() */
Vec2.prototype.scaleRelative = function(s) {return this.scale(s);}
/* returns Vec2: a vector of same magnitude as this, perpendicular to this. The result is always rotated 90-degrees in a counter-clockwise direction for a 2D coordinate system where the positive Y axis goes up. */
Vec2.prototype.getPerpendicular = function() {return new Vec2(-this.y, this.x);}
/* returns Vec2: a vector reflected by a surface perpendicular to parameter norm as if the surface is a perfect mirror */
Vec2.prototype.reflect = function(norm) {var n = norm.clone().normalize(); return this.clone().subtract( n.scale(2 * this.dot(n)) ).scale(-1);}
/* return scalar: the Z-component of the resulting 3D Vector would this and parameter Vec2 be in the 3D plane x = 0, y = 0 */
Vec2.prototype.cross = function(v) {return (this.x * v.y) - (this.y * v.x);}

/* Most methods by Jurgen, some are commodity, some methods implemented to comply with the Ray2D class of the Unity framework */
function Ray2(originVec2, directionVec2) { this.o = originVec2; this.d = directionVec2; }
/* returns Ray2: a copy of this */
Ray2.prototype.clone = function() { return new Ray2(this.o.clone(), this.d.clone()); }
/* returns this: draws this ray (from t = 0 to t = 1) on parameter turtle t */
Ray2.prototype.draw = function(t) { t.jump(this.o.x, this.o.y); t.goto(this.o.x + this.d.x, this.o.y + this.d.y); return this; }
/* returns this: draws this ray (from t = 0 to t = 1) on parameter turtle t with an arrow head of size parameter ratio */
Ray2.prototype.drawArrow = function(t, arrawHeadRatio=.3) { this.draw(t); var newO = this.o.clone().add(this.d); var c1 = this.d.clone().scale(-1); var c2 = c1.clone().rotate(Math.PI / 4).scale(arrawHeadRatio); c1.rotate(Math.PI / -4).scale(arrawHeadRatio); new Ray2(newO, c1).draw(t); new Ray2(newO, c2).draw(t); return this; }
/* returns Vec2: alias for getPointAbsolute */
Ray2.prototype.getPoint = function(distance) { return this.getPointAbsolute(distance); }
/* returns Vec2: A point on this ray when the direction is resized to length parameter distance */
Ray2.prototype.getPointAbsolute = function(distance) { return this.o.clone().add(this.d.clone().scaleAbsolute( distance )); }
/* returns Vec2: A point on this ray when the direction is multiplied by parameter t */
Ray2.prototype.getPointRelative = function(t) { return this.o.clone().add(this.d.clone().scale(t)); }
/* returns intersectioninfo or false: object with a point (Vec2) of intersection and a t1 and t2 representing the the scalar to apply to resp this' direction or parameter r's direction to get to that point */
Ray2.prototype.getIntersectionRay2 = function (r) { var deltaO = this.o.clone().subtract(r.o); var perp = this.d.getPerpendicular(); var det = r.d.dot(perp); if(det == 0) { return false; } var t1 = r.d.cross(deltaO) / det; var t2 = deltaO.dot(perp) / det; return { point: this.getPointRelative(t1), t1: t1, t2: t2 } }