xxxxxxxxxx
/*
Randomly generated curve shapes inspired by Frank Gehry's buildings.
Each shape is a line swept along a curve. Each piece of the "building"
randomly spawns child pieces on top.
*/
let building = []
const regenerate = () => {
if (building) {
// Clean out old meshes
for (const { mesh } of building) {
_renderer._freeBuffers(mesh.gid)
}
}
building = []
let id = 0
// Create one curve sweep, which will be offsetted/scaled relative
// to a parent piece
const genPiece = (parent, depth, bounds) => {
const transforms = []
if (parent) {
transforms.push(parent.transforms)
}
const t = {}
for (const prop in bounds) {
t[prop] = random(bounds[prop])
}
transforms.push(() => {
translate(t.offX, t.offY, t.offZ)
rotateZ(t.rotZ)
rotateY(t.rotY)
scale(t.sX, t.sY, t.sZ)
})
const piece = {
mesh: makeBuildingPiece(id, depth, t.sX, t.sY, t.sZ),
transforms,
}
id++
building.push(piece)
let numChildren = depth < 2 ? random([0, 1, 2]) : 0
if (depth === 0) numChildren = max(numChildren, 1)
if (numChildren > 0) {
let positions = []
for (let offX = -40; offX <= 40; offX += 20) {
for (let offZ = -40; offZ <= 40; offZ += 20) {
positions.push({ offX, offZ })
}
}
for (let i = 0; i < numChildren; i++) {
const position = random(positions)
positions = positions.filter((p) => p !== position)
const { offX, offZ } = position
genPiece(piece, depth + 1, {
offY: [-70, -80],
offX: [offX - 10, offX + 10],
offZ: [offZ - 10, offZ + 10],
rotZ: [-PI/12, PI/12],
rotY: [0, TWO_PI],
sX: [0.25, 0.75 / numChildren],
sZ: [0.25, 0.75 / numChildren],
sY: [0.5, 0.9],
})
}
}
}
// Create the base piece and recursively generate children pieces
genPiece(undefined, 0, {
offY: [100, 100],
offX: [0, 0],
offZ: [0, 0],
rotZ: [0, 0],
rotY: [0, 0],
sX: [1, 1],
sY: [1, 1],
sZ: [1, 1],
})
}
let bgColors
function setup() {
createCanvas(500, 500, WEBGL)
frameRate(30)
setupRendering()
bgColors = ['#102436', '#4f2641'].map((c) => color(c))
}
function drawBuilding() {
for (const { mesh, transforms } of building) {
push()
transforms.forEach((t) => t())
model(mesh)
pop()
}
}
let lastId = -1
function draw() {
const period = 4000
background(
lerpColor(
bgColors[0],
bgColors[1],
map(cos(millis() / period * TWO_PI), -1, 1, 0, 1)
)
)
orbitControl()
const id = floor(millis() / period)
if (id !== lastId) {
regenerate()
}
lastId = id
// The shader does a reflection using a sphere map
shader(reflection)
reflection.setUniform('sphereMap', sphereMap)
reflection.setUniform('roughSphereMap', irradianceMap)
reflection.setUniform('texture', texture)
reflection.setUniform('bumpMap', bumpMap)
reflection.setUniform('bumpHeightScale', 1.5)
reflection.setUniform('time', millis())
noStroke()
scale(1.4)
rotateX(-PI * 0.02)
rotateY(millis() * 0.0003)
// In order for the reflections to work properly, I need the surface normal
// to be pointing out of the surface. Only the front face has the normal
// pointing in the right direction, but I want the back faces' reflections
// to work too, so I have to draw the front and back faces separately so
// I can give each one properly oriented normals
// Back faces
_renderer.GL.enable(_renderer.GL.CULL_FACE)
_renderer.GL.cullFace(_renderer.GL.FRONT)
reflection.setUniform('normalScale', -1)
drawBuilding()
// Front faces
_renderer.GL.cullFace(_renderer.GL.BACK)
reflection.setUniform('normalScale', 1)
drawBuilding()
}
// Function to sweep a line about a curve and save it to a P5 Geometry
// for efficient rendering
function makeBuildingPiece(id, depth, sx, sy, sz) {
return new p5.Geometry(1, 1, function() {
this.gid = `buildingPiece|${id}`
const numPoints = 10 - depth * 2
const r = 100
// Generate points in a circle with random offsets
const points = _.times(numPoints).map((i) => {
const angle = (i / numPoints) * TWO_PI
const pt = createVector(
r * cos(angle),
0,
r * sin(angle)
).add(createVector(
random(-20, 20),
0,
random(-20, 20)
))
return { pt }
})
// Add bezier tangent control points to make it smooth
for (let i = 0; i < points.length; i++) {
const prev = points[(i - 1 + points.length) % points.length]
const curr = points[i]
const next = points[(i + 1) % points.length]
// Make most points smooth, some are randomly cusps
if (random() < 0.8) {
const tangent = next.pt.copy().sub(prev.pt).mult(1/6)
curr.left = curr.pt.copy().sub(tangent)
curr.right = curr.pt.copy().add(tangent)
}
}
// Make it repeat
points.push(points[0])
// We're going to extrude the curve up, but instead of directly up,
// we will wobble the up vector a bit using theta (basically X rotation)
// and phi (basically Z rotation)
const thetaPeriod = random([1, 2, 3]) * TWO_PI
const thetaScale = random(0.1, 0.3) * PI / (1 + depth)
const thetaOffset = random(TWO_PI)
const phiPeriod = random([1, 2, 3]) * TWO_PI
const phiScale = random(-1, 1) * PI / 2
const phiOffset = random(TWO_PI)
const theta = (t) => thetaScale * sin(t * thetaPeriod + thetaOffset)
const phi = (t) => phiScale * sin(t * phiPeriod + phiOffset)
// The rest is just making mesh vertices and normals out of those curves
const path = createBezierPath(points)
const len = path.getTotalLength()
const samples = max(4, ceil((len * min(sx, sz)) / 4))
const bottomPts = []
const bitangents = []
for (let i = 0; i < samples; i++) {
const t = i / samples
const d = t * len
const { x, y, z } = path.getPointAtLength(d)
const pt = createVector(x, y, z)
bottomPts.push(pt)
bitangents.push(p5.Vector.fromAngles(theta(t), phi(t)))
}
const facesY = max(2, ceil(20 * sz))
for (let j = 0; j <= facesY; j++) {
const frac = j / facesY
const pts = bottomPts.map((pt, i) =>
pt.copy().add(bitangents[i].copy().mult(80*frac)))
for (let i = 0; i < pts.length; i++) {
const pt = pts[i]
const tangent = pts[(i + 1) % pts.length].copy().sub(pt).normalize()
const normal = tangent.cross(bitangents[i]).normalize().mult(-1)
this.vertexNormals.push(normal)
}
this.vertices.push(pts)
this.uvs.push(
pts.map((_, i) => [i / (samples - 1), 1 - frac]),
)
}
for (let j = 0; j < facesY; j++) {
const offset = samples * j
for (let i = 0; i < samples; i++) {
// +--+
// \ |
// +
this.faces.push([
offset + samples + i,
offset + samples + (i + 1) % samples,
offset + (i + 1) % samples,
]);
// +
// | \
// +--+
this.faces.push([
offset + samples + i,
offset + (i + 1) % samples,
offset + i,
]);
}
}
})
}