3D Projection System
PinePaper includes a standalone 3D projection library for rendering 3D primitives on the 2D canvas. The library is zero-dependency (~18KB minified), outputs plain polygon descriptors, and can be used independently of Paper.js.
Architecture
js/3d/
core/ Point3D, Matrix4, Quaternion (math primitives)
geometry/ Face, Mesh, Primitives (cube, sphere, cylinder, torus, cone)
projection/ Projections (5 types), Camera3D (orbital camera)
pipeline/ Scene3D (orchestrator), Lighting, DepthSorter
gpu/ ProjectionGPU (WebGPU/WebGL2 acceleration)
index.js Barrel export for standalone usage
Design Principles:
- Zero DOM or Paper.js dependencies in the 3D library itself
- All data structures are plain JavaScript objects (no classes) for serialization
- Consumer-agnostic polygon output: any 2D renderer can consume the results
- GPU acceleration is optional with automatic fallback to CPU
PinePaper API
Access 3D features through the PinePaper instance:
createObject3D(type, params)
Create a 3D primitive and register it as a canvas item.
const cubeId = app.createObject3D('cube', {
size: 1.5,
color: '#3b82f6',
x: 0, y: 0, z: 0,
rotationY: 45,
animationType: 'rotate',
animationSpeed: 0.5
});
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
type |
string | — | 'cube', 'sphere', 'cylinder', 'torus', 'cone' |
size |
number | 1 | Cube size (cube only) |
radius |
number | 0.5 | Radius (sphere, cylinder, torus, cone) |
height |
number | 1 | Height (cylinder, cone) |
tube |
number | 0.2 | Tube radius (torus only) |
color |
string | ‘#cccccc’ | Face color |
x, y, z |
number | 0 | World position |
rotationX/Y/Z |
number | 0 | Rotation in degrees |
scale |
number | 1 | Uniform scale |
widthSegments |
number | 12 | Longitude divisions (sphere) |
heightSegments |
number | 8 | Latitude divisions (sphere) |
segments |
number | 16 | Circumference divisions (cylinder, cone) |
radialSegments |
number | 12 | Around tube (torus) |
tubularSegments |
number | 16 | Around ring (torus) |
animationType |
string | — | 'rotate' for auto-rotation |
animationSpeed |
number | 1 | Rotation speed multiplier |
Returns: string — Item registry ID (e.g., 'item_42')
createGlossySphere(params)
Create a glossy 2D sphere effect using layered radial gradients with shadow.
const sphere = app.createGlossySphere({
x: 400, y: 300,
radius: 60,
color: '#F97316',
lightDirection: 'top-left',
glossiness: 0.8,
castShadow: true
});
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
x |
number | center | X position |
y |
number | center | Y position |
radius |
number | 50 | Sphere radius |
color |
string | ‘#4a9eff’ | Base color |
lightDirection |
string | ‘top-left’ | Light source direction |
glossiness |
number | 0.7 | Glossiness level (0-1) |
castShadow |
boolean | true | Whether to cast shadow |
shadowBlur |
number | 15 | Shadow blur amount |
Light Directions: top-left, top-right, top, left, right, bottom-left, bottom-right
Returns: paper.Group — Group containing sphere elements
Primitives
| Primitive | Faces (default) | Key Params |
|---|---|---|
cube |
6 quads | size |
sphere |
~96 | radius, widthSegments, heightSegments |
cylinder |
~34 | radius, height, segments |
torus |
~192 | radius, tube, radialSegments, tubularSegments |
cone |
~17 | radius, height, segments |
Standalone Usage
The 3D library can be used independently of PinePaper. Import from the barrel export:
import * as Scene3D from './js/3d/pipeline/Scene3D.js';
import * as Primitives from './js/3d/geometry/Primitives.js';
import * as Camera3D from './js/3d/projection/Camera3D.js';
// Create a scene
const scene = Scene3D.createScene({
width: 800,
height: 600,
projection: 'perspective'
});
// Add primitives
const cube = Primitives.cube({ size: 1, color: '#3b82f6' });
Scene3D.addMesh(scene, cube);
const sphere = Primitives.sphere({ radius: 0.5, color: '#ef4444' });
Scene3D.addMesh(scene, sphere);
// Set camera
const camera = Camera3D.createCamera({
position: { x: 3, y: 2, z: 5 },
target: { x: 0, y: 0, z: 0 }
});
Scene3D.setCamera(scene, camera);
// Render — returns plain polygon descriptors
const polygons = Scene3D.render(scene);
// Each polygon: { vertices2D, color, depth, meshIndex }
// Render with any 2D system (Canvas2D, SVG, Paper.js, etc.)
for (const poly of polygons) {
ctx.beginPath();
poly.vertices2D.forEach((v, i) => {
i === 0 ? ctx.moveTo(v.x, v.y) : ctx.lineTo(v.x, v.y);
});
ctx.closePath();
ctx.fillStyle = poly.color;
ctx.fill();
}
Render Pipeline
The rendering pipeline follows these stages:
- Transform — Apply mesh world transform (4x4 matrix) to face vertices and normals
- Back-face Cull — Skip faces where the normal points away from the camera
- Shade — Apply directional lighting with ambient floor (
brightness = ambient + max(0, N·L) * intensity) - Project — Convert 3D vertices to 2D screen coordinates using the active projection
- Depth Sort — Painter’s algorithm (back-to-front) for correct z-ordering
Module Map
| Module | Purpose |
|---|---|
Point3D |
3D vector math (add, sub, dot, cross, normalize, lerp, transformMat4) |
Matrix4 |
4x4 matrix operations (multiply, invert, translate, scale, rotate, lookAt, perspective) |
Quaternion |
Rotation representation and interpolation |
Face |
Triangle/quad face with auto-computed normal |
Mesh |
Collection of faces with world transform matrix |
Primitives |
Geometry generators (cube, sphere, cylinder, torus, cone) |
Projections |
5 projection types (perspective, orthographic, isometric, cabinet, cavalier) |
Camera3D |
LookAt camera with orbital navigation |
Scene3D |
Pipeline orchestrator (transform, cull, shade, project, sort) |
Lighting |
Directional light with ambient floor |
DepthSorter |
Painter’s algorithm depth sorting |
ProjectionGPU |
GPU-accelerated vertex projection with automatic fallback |
Scene Statistics
const stats = Scene3D.getStats(scene);
// { meshes: 2, faces: 102, vertices: 306 }
// Via PinePaper bridge:
const stats = app.threeD.getStats();
// { meshes: 2, faces: 102, vertices: 306, gpuMode: 'webgpu', objectCount: 2 }
Paper.js Community Usage
The 3D library outputs renderer-agnostic polygon descriptors:
{
vertices2D: [{x: 100, y: 50}, {x: 200, y: 50}, {x: 150, y: 150}],
color: '#5588cc', // Pre-shaded CSS color
depth: 3.14, // For painter's algorithm sorting
meshIndex: 0 // Source mesh identifier
}
Any Paper.js project can consume these directly:
import * as Scene3D from 'pinepaper-3d';
const polygons = Scene3D.render(scene);
for (const poly of polygons) {
new paper.Path({
segments: poly.vertices2D.map(v => new paper.Point(v.x, v.y)),
fillColor: poly.color,
closed: true
});
}