xxxxxxxxxx
424
let world;
const pts = [
[1, 1],
[-1, 1],
[-1, -1],
[1, -1],
];
let start = false;
let dbg = false;
let paused = false;
function makeSquare(side) {
// A square at the origin, of side length side, pointing
// in the +Z direction
return {
frame: math.transpose(
math.matrix([
[0, 0, 1, 0],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
])
),
side: side,
};
}
function makeCube(side) {
// An axis-aligned cube of side length side, centered
// at the origin
return [
{
frame: math.transpose(
math.matrix([
[0, 0, 1, 0],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, side / 2, 1],
])
),
side: side,
},
{
frame: math.transpose(
math.matrix([
[0, 0, -1, 0],
[-1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, -side / 2, 1],
])
),
side: side,
},
{
frame: math.transpose(
math.matrix([
[0, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 1, 0],
[0, side / 2, 0, 1],
])
),
side: side,
},
{
frame: math.transpose(
math.matrix([
[0, -1, 0, 0],
[-1, 0, 0, 0],
[0, 0, 1, 0],
[0, -side / 2, 0, 1],
])
),
side: side,
},
{
frame: math.transpose(
math.matrix([
[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 1, 0, 0],
[side / 2, 0, 0, 1],
])
),
side: side,
},
{
frame: math.transpose(
math.matrix([
[-1, 0, 0, 0],
[0, 0, -1, 0],
[0, 1, 0, 0],
[-side / 2, 0, 0, 1],
])
),
side: side,
},
];
}
function makeCamera(eye, view, up) {
let right = math.cross(view, up);
up = math.cross(right, view);
right = math.multiply(right, 1.0 / math.norm(right));
up = math.multiply(up, 1.0 / math.norm(up));
view = math.multiply(view, 1.0 / math.norm(view));
return math.transpose(
math.matrix([
[right[0], right[1], right[2], 0],
[up[0], up[1], up[2], 0],
[view[0], view[1], view[2], 0],
[eye[0], eye[1], eye[2], 1],
])
);
}
function makeTranslate(x, y, z) {
return math.transpose(
math.matrix([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[x, y, z, 1],
])
);
}
function makeRotateX(ang) {
const c = cos(ang);
const s = sin(ang);
return math.matrix([
[c, -s, 0, 0],
[s, c, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]);
}
function makeRotateY(ang) {
const c = cos(ang);
const s = sin(ang);
return math.matrix([
[c, 0, s, 0],
[0, 1, 0, 0],
[-s, 0, c, 0],
[0, 0, 0, 1],
]);
}
function makeRotateZ(ang) {
const c = cos(ang);
const s = sin(ang);
return math.matrix([
[1, 0, 0, 0],
[0, c, -s, 0],
[0, s, c, 0],
[0, 0, 0, 1],
]);
}
function applyTransform(face, T) {
// console.log( face );
// console.log( T );
return {
frame: math.multiply(T, face.frame),
side: face.side,
};
}
function evalPt(face, x, y) {
const ret = math.multiply(
face.frame,
math.transpose([0, (x * face.side) / 2, (y * face.side) / 2, 1])
);
return ret;
}
function asvec(v) {
return [v.get([0]), v.get([1]), v.get([2]), v.get([3])];
}
function makeWorld(n) {
let ret = [];
ret.push(applyTransform(makeSquare(15), makeRotateZ(-HALF_PI)));
circs = [];
function isOK(x, y, r) {
for (let c of circs) {
if (dist(x, y, c[0], c[1]) < r + c[2]) {
return false;
}
}
return true;
}
let rad = 3;
while (circs.length < n) {
let found = false;
for (let jdx = 0; jdx < 100; ++jdx) {
const x = random(-5 + rad, 5 - rad);
const y = random(-5 + rad, 5 - rad);
if (isOK(x, y, rad)) {
circs.push([x, y, rad]);
found = true;
break;
}
}
if (!found) {
rad = rad * 0.95;
}
}
for (let c of circs) {
const ang = random(HALF_PI);
const R = makeRotateY(ang);
const sz = sqrt(2) * c[2] * random(0.75, 1.0);
const T = makeTranslate(c[0], sz / 2, c[1]);
ret = ret.concat(
makeCube(sz)
.map((f) => applyTransform(f, R))
.map((f) => applyTransform(f, T))
);
}
return ret;
}
function maxDepth(face) {
const ds = pts.map((p) => asvec(evalPt(face, p[0], p[1]))[2]);
return Math.max(ds);
}
function maxDepthFrom(face, eye) {
function getDepth(p) {
const tp = evalPt(face, p[0], p[1]);
return math.distance(tp, eye);
}
const ds = pts.map(getDepth);
return Math.max(ds);
}
function computeShadows(world, light) {
for (let face of world) {
const n = math.flatten(math.column(face.frame, 0));
const ppt = math.flatten(math.column(face.frame, 3));
const l = math.subtract(light, ppt);
const d = math.multiply(l, n);
if (d < -1e-7) {
face.outline = { regions: [], inverted: false };
}
}
for (let idx = 0; idx < world.length; ++idx) {
for (let jdx = 0; jdx < idx; ++jdx) {
let fclose = world[idx];
let ffar = world[jdx];
if (maxDepthFrom(fclose, light) > maxDepthFrom(ffar, light)) {
const tmp = fclose;
fclose = ffar;
ffar = tmp;
}
/*
const cn = math.flatten( math.column( fclose.frame, 0 ) );
const cpt = math.flatten( math.column( fclose.frame, 3 ) );
if( abs( math.multiply( cn, math.subtract( cpt, light ) ) ) < 1e-7 ) {
continue;
}*/
// Project from fclose onto ffar.
const poly = [];
const n = math.flatten(math.column(ffar.frame, 0));
const ppt = math.flatten(math.column(ffar.frame, 3));
const iframe = math.inv( ffar.frame );
for (let p of pts) {
const sp = evalPt(fclose, p[0], p[1]);
// project ray from light through sp onto support
// plane of ffar.
const l = math.subtract(sp, light);
const den = math.multiply(l, n);
if (abs(den) < 1e-7) {
continue;
}
const t = math.multiply(math.subtract(ppt, light), n) / den;
const fpt = asvec(
math.multiply(
iframe,
math.add(light, math.multiply(l, t))
)
);
poly.push([fpt[1], fpt[2]]);
if (start) {
console.log("light: " + light);
console.log("sp: " + sp);
console.log("l: " + l);
console.log("n: " + n);
console.log("ppt: " + ppt);
console.log("den: " + den);
console.log("sub: " + math.subtract(ppt, light));
console.log("mult: " + math.multiply(math.subtract(ppt, light), n));
console.log("t: " + t);
console.log("fpt: " + fpt);
}
}
if (start) {
start = false;
console.log("" + poly);
}
ffar.outline = PolyBool.difference(ffar.outline, {
regions: [poly],
inverted: false,
});
}
}
}
function initFaces(world) {
for (let face of world) {
const hs = face.side / 2;
face.outline = {
regions: [
[
[hs, hs],
[-hs, hs],
[-hs, -hs],
[hs, -hs],
],
],
inverted: false,
};
}
}
function setup() {
createCanvas(512, 512);
world = makeWorld(5);
}
function draw() {
if( paused ) {
return;
}
background(255);
strokeJoin(ROUND);
const eye = [8, 10, 17];
const ang = map(frameCount%200,0,200,0,TWO_PI);
const light = [10 * cos(ang), 8, 10 * sin(ang)];
let c = makeCamera(eye, [-eye[0], -eye[1], -eye[2]], [0, 1, 0]);
let ci = math.inv(c);
const tlight = asvec(math.multiply(ci, math.transpose([light, 1])));
push();
translate(width / 2, height / 2);
scale(7, -7);
let count = 0;
const tfaces = world.map((f) => applyTransform(f, ci));
tfaces.sort((a, b) => maxDepth(b) - maxDepth(a));
initFaces(tfaces);
computeShadows(tfaces, tlight);
for (let face of tfaces) {
const n = math.flatten(math.column(face.frame, 0));
const o = math.flatten(math.column(face.frame, 3));
const d = math.multiply(n, o);
if (d > 0) {
continue;
}
fill(0);
noStroke();
beginShape();
for (let cc of pts) {
let pt = asvec(evalPt(face, cc[0], cc[1]));
vertex((pt[0] / pt[2]) * 100, (pt[1] / pt[2]) * 100);
}
endShape(CLOSE);
if (dbg) {
fill(200);
} else {
fill(255);
}
stroke(255);
strokeWeight(0.25);
let dd = 0.5 * face.side;
for (let pgon of face.outline.regions) {
beginShape();
for (let p of pgon) {
let pt = asvec(evalPt(face, p[0] / dd, p[1] / dd));
vertex((pt[0] / pt[2]) * 100, (pt[1] / pt[2]) * 100);
}
endShape(CLOSE);
}
}
pop();
}
function keyPressed() {
if (key == " ") {
dbg = !dbg;
}else if( key == 'g' ) {
saveGif("output", 200, { units: "frames", delay: 0 });
} else if( key == 'p' ) {
paused = !paused;
}
}