Combining 3D Primitives in p5.js with buildGeometry()
Understanding buildGeometry()
When building complex 3D scenes with multiple primitives like box() and sphere(), performance often becomes a bottleneck. The buildGeometry() function addresses this by acting as a geometry pre-fabrication system—it combines multiple simple shapes into a single p5.Geometry object that gets created once and reused efficient throughout your sketch.
This approach mirrors an assembly line: instead of manufacturing each component repeatedly, you pre-assemble everything upfront. The resulting geometry object can then be rendered instantly with the model() function, eliminating redundant calculations on every frame.
Basic Implementation
Here's how to create and render a packaged 3D shape:
let packagedGeometry;
function setup() {
createCanvas(400, 400, WEBGL);
// Build the geometry once during setup
packagedGeometry = buildGeometry(constructShape);
}
function draw() {
background(220);
orbitControl();
ambientLight(100);
directionalLight(255, 255, 255, 100, 100, 100);
model(packagedGeometry);
}
// Callback defines the shape assembly
function constructShape() {
box(60);
// Attach a smaller box on top
translate(0, 45, 0);
box(30);
}
The callback function constructShape defines the geometry once. In side draw(), calling model(packagedGeometry) renders this pre-built geometry without recreating it each frame.
Creating a Dynamic Geometric Structure
The real power of buildGeometry() emerges when building animated scenes. Below is a particle-field effect where 100 small spheres orbit around a central point:
let particleSystem;
function setup() {
createCanvas(600, 600, WEBGL);
particleSystem = buildGeometry(buildParticles);
}
function draw() {
background(30);
orbitControl();
// Rotation based on time for continuous animation
rotateY(millis() * 0.001);
// Dynamic color cycling
colorMode(HSB, 360, 100, 100);
let hue = (millis() * 0.05) % 360;
fill(hue, 85, 90);
// Specular highlight for shininess
specularMaterial(255);
shininess(50);
model(particleSystem);
}
function buildParticles() {
for (let i = 0; i < 100; i++) {
// Calculate orbital position
let angle = (TWO_PI / 100) * i;
let radius = 80;
push();
translate(
cos(angle) * radius,
sin(angle * 0.5) * 30,
sin(angle) * radius
);
rotateY(angle);
sphere(8);
pop();
}
}
This example demonstrates several key concepts:
- Geometry caching: All 100 spheres are computed once during
setup() - Material state preservation:
specularMaterial()andshininess()apply to all primitives within the geometry since the callback executes withinbuildGeometry's internal push/pop context - Transform accumulation: The nested
translateandrotateYcalls create the orbital arrangement
Performance Comparison
| Approach | setup() | draw() | Frame overhead |
|---|---|---|---|
| Raw primitives (100 spheres) | Minimal | 100 draw calls | High |
| buildGeometry() | O(n) construction | Single model() call | Constant |
For scenes with many repeated shapes or complex hierarchies, buildGeometry() reduces computational load to a one-time cost during initialization, making smooth 60fps rendering achievable even on less powerful hardware.
Practical Applications
Consider using buildGeometry() for:
- Level geometry: Pre-build environment assets loaded at startup
- Character components: Combine primitive body parts into articulated figures
- Particle systems: Assembly-style emitters with complex arrangements
- Architectural elements: Combine walls, floors, and fixtures into scene chunks
The function excels when geometry remains static but requires repeated rendering—the typical scenario for most 3D applications where the scene updates via transforms rather than structural changes.