xxxxxxxxxx
const r = 150
const numSplits = 20
const generate = () => {
// Each object is an array of faces. Start with a cube
const objects = [
// makeCubeFaces implementation in makeCubeFaces.js
makeCubeFaces(r)
];
const measure = (faces) => {
const minVert = faces[0][0].copy()
const maxVert = faces[0][0].copy()
for (const face of faces) {
for (const vert of face) {
minVert.x = min(minVert.x, vert.x)
minVert.y = min(minVert.y, vert.y)
minVert.z = min(minVert.z, vert.z)
maxVert.x = max(maxVert.x, vert.x)
maxVert.y = max(maxVert.y, vert.y)
maxVert.z = max(maxVert.z, vert.z)
}
}
const center = maxVert.copy().add(minVert).mult(0.5)
const maxR = 0.5 * maxVert.dist(minVert)
// Approximate weight using a sphere
const weight = (4 / 3) * PI * pow(maxR / r, 3)
return { weight, maxR, center }
}
for (let i = 0; i < numSplits; i++) {
// Remove a random object, weighted approximately by its size so that
// big ones get cut more times overall. We're going to split it into
// two pieces then add both back
const objWeights = objects.map((obj) => measure(obj).weight + 50)
const sumWeights = objWeights.reduce((acc, next) => acc + next)
let pickedWeight = random(sumWeights)
let idxToSplit = 0
for (let i = 0; i < objWeights.length; i++) {
pickedWeight -= objWeights[i]
if (pickedWeight <= 0) {
idxToSplit = i
break
}
}
const [objToSplit] = objects.splice(idxToSplit, 1)
// Pick a random point on a random edge of a random face to start the cut
const faceToSplit = random(objToSplit)
const splitPtIdx = random(faceToSplit.length)
const a = floor(splitPtIdx)
const b = ceil(splitPtIdx) % faceToSplit.length
const mix = splitPtIdx - a
const splitPt = p5.Vector.lerp(
faceToSplit[a],
faceToSplit[b],
mix
)
// Get a random normal for the cut
// p5.Vector.random3D() doesn't respect randomSeed() calls, which makes
// debugging very annoying :(
const splitNormal = createVector(
random(-1, 1),
random(-1, 1),
random(-1, 1)
).normalize()
// Cutting implementation lives in cutObject.js
const [rawLeft, rawRight] = cutObject(objToSplit, splitPt, splitNormal)
const left = rawLeft.filter((f) => f.length > 0)
const right = rawRight.filter((f) => f.length > 0)
if (left.length > 0) {
objects.push(left)
}
if (right.length > 0) {
objects.push(right)
}
}
return objects.map((faces) => {
const { weight, maxR, center } = measure(faces)
// Translate this piece outwards. Bigger pieces move less
const direction = center
.copy()
.mult(0.05)
.mult(30 / (0.005 * weight + 1))
// Rotate this piece:
// - smaller pieces rotate more
// - pieces rotate more the farther they've translated out (I don't
// check collisions, and there's less likelihood of intersections
// the farther away it moves)
const dMag = direction.mag() / 100
const rotX = dMag * random(-PI, PI) / (0.5 * weight + 1)
const rotY = dMag * random(-PI, PI) / (0.5 * weight + 1)
const normalizedFaces = faces.map(
(face) => face.map((pt) => pt.copy().sub(center))
)
return {
faces: normalizedFaces,
center,
direction,
rotX,
rotY,
}
})
}
let parts = []
let bgColors = []
let flashColor
let material
function setup() {
createCanvas(400, 400, WEBGL);
_renderer.GL.getExtension('OES_standard_derivatives')
material = createShader(vert, frag)
bgColors = [
'#355070',
'#6d597a',
'#b56576',
'#e56b6f',
'#eaac8b',
].map((c) => color(c))
flashColor = color('#fffded')
}
let lastItem = -1
let bgColor
let shadowColor
let materialColor
function draw() {
const timePerItem = 3000
const item = floor(millis() / timePerItem)
const t = (millis() % timePerItem) / timePerItem
if (item !== lastItem) {
parts = generate()
bgColor = random(bgColors)
shadowColor = lerpColor(bgColor, color('#96113b'), 0.5)
materialColor = lerpColor(bgColor, color('#ffffff'), 0.85)
}
lastItem = item
let progress = 0
const percentExpanding = 0.5
const percentHolding = 0.6
if (t < percentExpanding) {
progress = Ease.easeOutQuart(map(t, 0, percentExpanding, 0, 1))
} else if (t < percentHolding) {
progress = 1
} else {
progress = Ease.easeInOutCubic(map(t, percentHolding, 1, 1, 0))
}
background(lerpColor(flashColor, bgColor, map(t, 0, 0.3, 0, 1, true)))
noStroke()
shader(material)
material.setUniform('shadowColor', [
red(shadowColor) / 255,
green(shadowColor) / 255,
blue(shadowColor) / 255,
])
material.setUniform('materialColor', [
red(materialColor) / 255,
green(materialColor) / 255,
blue(materialColor) / 255,
])
material.setUniform('lightColor', [1, 1, 0.9])
orbitControl()
scale(0.3)
rotateY(millis() * 0.0005)
for (const part of parts) {
push()
translate(part.center.x, part.center.y, part.center.z)
translate(
progress*part.direction.x,
progress*part.direction.y,
progress*part.direction.z
)
rotateX(progress*part.rotX)
rotateY(progress*part.rotY)
for (const face of part.faces) {
push()
// Create triangle fan. This only works because every face is
// guaranteed to be convex (would be different if I didn't start
// from a cube)
beginShape(TRIANGLES)
const p0 = face[0]
for (let i = 1; i < face.length; i++) {
const j = (i + 1) % face.length
const p1 = face[i]
const p2 = face[j]
vertex(p0.x, p0.y, p0.z)
vertex(p1.x, p1.y, p1.z)
vertex(p2.x, p2.y, p2.z)
}
endShape()
pop()
}
pop()
}
}