Log in to post a comment.
// Polygon Utility. Created by Reinder Nijhoff 2022 - @reindernijhoff
// The MIT License
//
// https://turtletoy.net/turtle/108305d431
//
let seed = 1; // min=1, max=100, step=1
const drawMode = 0; // min=0, max=1, step=1 (Circles, Polygons)
Canvas.setpenopacity(drawMode == 1 ? 1 : .3);
const turtle = new Turtle();
const ps = [];
// create random polygons
for (let i=0; i<9; i++) {
const w = 50 + 50 * random(), h = 50 + 50 * random(), x = 200 * random() - 100, y = 200 * random() - 100;
// create random polygon p
let p = [[x-w, y-h], [x-w, y+h], [x+w, y+h], [x+w, y-h]];
// split edges. After the split, all edges will have a lenth of ~ 2
p = pSplitEdges(p, 2);
// transform the polygon by adding sin(y) to the x-coordinate of all vertices
p = pTransform(p, c => [c[0] + Math.sin(c[1]*.1)*2, c[1]]);
// smooth polygon
p = pSmooth(p, 50*(random()**2) | 0 + 1);
if (drawMode == 1) {
pDraw(p, turtle);
}
ps.push(p);
}
function walk(i) {
if (drawMode == 1) {
return false;
}
// create a random point
const [x,y] = [Math.random()*200-100, Math.random()*200-100];
let dist = 1000;
let inside = 0;
ps.forEach(p => {
// find minimal distance from point to nearest polygon
dist = Math.min(dist, pDistance(p, [x,y]));
// check if point is inside polygon
if (pPointInPolygon(p, [x,y])) {
inside++;
}
});
// draw a circle with a radius equal to the nearest distance to a polygon
if (Math.random() > 0.5 * Math.sqrt(dist / 25) + (inside % 2 ? .5 : 0)) {
turtle.jump(x, y-dist);
turtle.circle(dist);
}
return i < 20000;
}
function random() {
let r = 1103515245 * (((seed+=12345) >> 1) ^ (seed));
r = 1103515245 * (r ^ (r >> 3));
r = r ^ (r >> 16);
const mod = 1 << 20;
return (r % mod) / mod;
}
////////////////////////////////////////////////////////////////
// Polygon Utility code
// https://turtletoy.net/turtle/108305d431
//
// A collection of functions that can be used to work with polygons.
// A polygon is defined by an array of vertices [v0, v1, v2, v3, ...].
//
////////////////////////////////////////////////////////////////
// draw a polygon p using turtle t
function pDraw(p, t) {
t.jump(p[p.length-1]);
p.forEach(c => t.goto(c));
}
// smooth a polygon by averaging all vertices with its neighbors. Repeat r times.
function pSmooth(p, r=1) {
let smooth = [];
for(;r;r--) {
smooth = [];
for (let i=0; i<p.length; i++) {
const pm = p[(i-1+p.length) % p.length];
const p0 = p[(i+0) % p.length];
const pp = p[(i+1) % p.length];
smooth.push(lrp2(lrp2(pm, pp, .5), p0, 1/3));
}
p = [...smooth];
}
return smooth;
}
// split all edges of polygon in segments with length ~ d
function pSplitEdges(p, d = 2) {
const sub = [];
for (let i=0; i<p.length; i++) {
const p0 = p[(i+0) % p.length];
const p1 = p[(i+1) % p.length];
const l = Math.ceil(dist2(p0, p1)/d);
for (let j=0; j<l; j++) {
sub.push(lrp2(p0, p1, j/l));
}
}
return sub;
}
// expand polygon p with distance d
function pExpand(p, d = 1) {
const lines = [];
const scaled = [];
for (let i=0; i<p.length; i++) {
const i0 = (i+0) % p.length;
const i1 = (i+1) % p.length;
const p0 = p[i0];
const p1 = p[i1];
let n = nrm2(sub2(p1, p0));
n = [-n[1], n[0]];
lines.push([...n, -dot2(n, sub2(p0, scl2(n, -d)))]);
}
// find intersection points
for (let i=0; i<lines.length; i++) {
const i0 = (i+0) % lines.length;
const i1 = (i+1) % lines.length;
const pp = cross3(lines[i0], lines[i1]);
scaled[i] = Math.abs(pp[2]) < 0.0001 ? add2(p[i], scl2(lines[i], d)) : scl2(pp, 1/pp[2]);
}
return scaled;
}
// calculate bouding box (aabb) of p
function pAABB(p) {
let min = [...p[0]], max = [...p[0]];
p.forEach(c => {
min = [ Math.min(min[0],c[0]), Math.min(min[1],c[1]) ];
max = [ Math.max(max[0],c[0]), Math.max(max[1],c[1]) ];
});
return [min, max];
}
// point in aabb ?
function pointInAABB(p, aabb) {
const [min, max] = aabb;
return min[0] <= p[0] && max[0] >= p[0] && min[1] <= p[1] && max[1] >= p[1];
}
// check if point is inside polygon p
function pPointInPolygon(p, point, aabb = undefined) {
if (aabb && !pointInAABB(point, aabb || pAABB(p))) return false;
let int = 0; // find number of i ntersection points from p to far away
for (let i = 0, l = p.length; i < l; i++) {
if (segmentIntersect(point, [0.1, -1000], p[i], p[(i + 1) % l])) {
int++;
}
}
return int & 1; // if even your outside
}
// check if sphere ([x,y,radius]) is inside polygon p
function pSphereInPolygon(p, sphere, aabb = undefined) {
if (!pPointInPolygon(p, sphere, aabb || pAABB(p))) return false;
return pDistance(p, sphere) > sphere[2];
}
// find distance from point to polygon p
function pDistance(p, point) {
let dist = 100000;
for (let i=0; i<p.length; i++) {
const a = p[i], b = p[(i+1) % p.length];
const ba = sub2(b, a);
const pa = sub2(point, a);
const h = Math.max(0, Math.min(1, dot2(pa,ba)/dot2(ba,ba)));
dist = Math.min(dist, dist2(pa, scl2(ba, h)));
}
return dist;
}
// transform vertices of polygon p by function t
function pTransform(p, t) {
const ret = [];
for (let i=0; i<p.length; i++) {
ret.push(t(p[i]));
}
return ret;
}
//port of http://paulbourke.net/geometry/pointlineplane/Helpers.cs
function segmentIntersect(l1p1, l1p2, l2p1, l2p2) {
const d = (l2p2[1] - l2p1[1]) * (l1p2[0] - l1p1[0]) - (l2p2[0] - l2p1[0]) * (l1p2[1] - l1p1[1]);
if (d === 0) return false;
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]);
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;
}
//
// math functions
//
function scl2(a,b) { return [a[0]*b, a[1]*b]; }
function add2(a,b) { return [a[0]+b[0], a[1]+b[1]]; }
function sub2(a,b) { return [a[0]-b[0], a[1]-b[1]]; }
function dot2(a,b) { return a[0]*b[0] + a[1]*b[1]; }
function cross2(a,b) { return a[0]*b[1] - a[1]*b[0]; }
function len2(a) { return Math.sqrt(a[0]**2 + a[1]**2); }
function dist2(a, b) { return len2(sub2(a,b)); }
function nrm2(a) { return scl2(a, 1/len2(a)); }
function lrp2(b,a,f) { return f = Math.max(0, Math.min(1, f)), [a[0]*f+b[0]*(1-f), a[1]*f+b[1]*(1-f)]; }
function cross3(a, b) { return [a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]]; }