I have added a (slow) hatching option to the polygon routines of Polygon clipping.
#polygons #Escher
Log in to post a comment.
// Polygon hatching. Created by Reinder Nijhoff 2018
// @reindernijhoff
//
// https://turtletoy.net/turtle/92ebe08d89
//
Canvas.setpenopacity(.6);
const turtle = new Turtle();
const polygons = [];
function walk(i) {
const s = 25;
const sp = 12.5;
// first an invisible cube is added to the clip list.
if (i ==0) drawCube((3-8/2)*(s+sp),2*s-(s+sp)*(-8+9)/Math.sqrt(1.25),s,0,false);
if (i < 4) drawCube((i-1.5)*(s+sp),2*s,s);
else if (i < 7) drawCube((3-i/2)*(s+sp),2*s-(s+sp)*(i-3)/Math.sqrt(1.25),s);
else if (i < 9) drawCube((3-i/2)*(s+sp),2*s-(s+sp)*(-i+9)/Math.sqrt(1.25),s,9);
return i < 9;
}
function drawCube(x,y,r,skip=0,draw=true) {
const p = [[x,y]];
for (let i=0; i<6; i++) {
p.push([Math.cos(i*Math.PI/3)*r+x, Math.sin(i*Math.PI/3)*r+y]);
}
// create 3 visible polygons for cube
const ps = [];
for (let i=0; i<3; i++) {
const c = new Polygon();
c.cp = [p[0],p[i*2+1],p[i*2+2],p[(i*2+3)%6]];
c.addOutline();
c.addHatching((i-1/3)*Math.PI/2, .3+i*.5);
ps.push(c);
}
// draw cube, clip polygons to clip list.
for (let j=0; j<ps.length; j++) {
const c = ps[j];
let vis = true;
for (let i=skip, l=polygons.length; i<l; i++) {
if(!c.boolean(polygons[i])) {
vis = false;
break;
}
}
if (vis) {
if (draw) c.draw(turtle, 0.3);
polygons.push(c);
}
}
}
// 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() {
for (let i=0, 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.addHatching = function(a,d) {
// find number of intersections from p to far away - if even you're outside
const tp = new Polygon();
tp.createPoly(0,0,4,200,Math.PI*.5);
const dx = Math.sin(a)*d, dy = Math.cos(a)*d;
const cx = Math.sin(a)*300, cy = Math.cos(a)*300;
for (let i = .5; i<300/d; i++) {
tp.dp.push(new LineSegment([dx*i+cy,dy*i-cx], [dx*i-cy,dy*i+cx]));
tp.dp.push(new LineSegment([-dx*i+cy,-dy*i-cx], [-dx*i-cy,-dy*i+cx]));
}
tp.boolean(this, false);
this.dp = this.dp.concat(tp.dp);
}
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, -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;
}
// vec2 functions
function vec2_equal(a,b) {
return vec2_dist_sqr(a,b) < 0.001;
}
function vec2_dot(a, b) {
return a[0]*b[0]+a[1]*b[1];
}
function vec2_dist_sqr(a, b) {
return (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;
}