p5.tree
v0.0.3
Published
Shader development and space transformations WEBGL addon library.
Maintainers
Readme
p5.tree
Shader development, camera keyframes interpolation and space transformations for WEBGL / WebGPU-ready p5.js v2.

- Keyframes interpolation
- Space transformations
- Utilities
- Drawing stuff
- Installation
- vs-code & vs-codium & gitpod hacking instructions
In p5.tree, matrix queries are immutable and cache-friendly: they never modify their parameters and always return new p5.Matrix instances. For example, iMatrix (inverse) does not modify its matrix argument:
let matrix = createMatrix(4)
// iMatrix doesn't modify its matrix param, it gives a new value
let i = iMatrix(matrix)
// i !== matrixMost functions are available both as p5 helpers (global-style) and as renderer methods (p5.RendererGL). Camera path methods are available on p5.Camera, and also as p5 helpers that forward to the current active camera.
Parameters for p5.tree functions can be provided in any order, unless specified otherwise. When a function takes an options/config object, it is always the last parameter.
Keyframes interpolation
p5.tree provides a small camera-path API built on top of p5.Camera.copy() snapshots and p5.Camera.slerp() interpolation.
The path lives in user-space as camera.path (an array of p5.Camera snapshots). You add keyframes, then play the path at a chosen speed and duration.
Recording keyframes
camera.addPath(...) appends a keyframe (camera snapshot) to camera.path.
Overloads
camera.addPath(eye, center, up, [opts])camera.addPath(view, [opts])camera.addPath([camera0, camera1, ...], [opts])camera.addPath([view0, view1, ...], [opts])camera.addPath([opts])
Notes
- In (1),
upis mandatory (no default is assumed). - In (2),
viewis ap5.Matrix(4)or a rawmat4[16]representing a world→camera transform. - (3) appends copies of existing camera snapshots.
- (4) appends copies of existing camera view matrices.
- (5) records a snapshot of the current camera at call time.
Where:
eye,center,uparep5.Vectoror[x, y, z].viewis ap5.Matrix(4x4) or a rawmat4[16]representing a world → camera transform (likecamera.cameraMatrix).opts.reset(boolean, defaultfalse) clears the path before appending.
Example: record 3 keyframes
let cam
function setup() {
createCanvas(600, 400, WEBGL)
cam = createCamera()
cam.addPath([400, 0, 0], [0, 0, 0], [0, 1, 0])
cam.addPath(other_cam)
cam.addPath(viewMatrix)
}p5 wrappers
addPath(...) is also available as a p5 helper; it forwards to the current active camera (the one used for rendering).
function setup() {
createCanvas(600, 400, WEBGL)
addPath([400, 0, 0], [0, 0, 0], [0, 1, 0])
addPath(cam)
addPath(viewMatrix)
}Playback
camera.playPath(rateOrOpts) starts (or updates) playback.
camera.playPath(rate)whererateis a speed multiplier:rate > 0plays forwardrate < 0plays reverserate === 0stops ticking (equivalent to stopping)
camera.playPath({ duration, loop, pingPong, onEnd, rate })
Options:
duration: frames per segment (default30)loop: wraps at ends (defaultfalse)pingPong: bounces at ends (defaultfalse)onEnd: callback when playback naturally ends (non-looping, non-pingPong)rate: speed multiplier (default1)
If both pingPong and loop are true, pingPong takes precedence.
Example: loop the path
function setup() {
createCanvas(600, 400, WEBGL)
addPath([400, 0, 0], [0, 0, 0], [0, 1, 0], { reset: true })
addPath(cam)
addPath(viewMatrix)
// 45 frames per segment, loop forever
playPath({ duration: 45, loop: true })
}
function draw() {
background(220)
box(80)
}Projection safety:
p5.Camera.slerp()requires all keyframes to use the same projection.p5.treeenforces this by checking projection matrix compatibility while recording.
Seek, stop, reset
camera.seekPath(t)jumps to a normalized timetin[0, 1]along the whole path.camera.stopPath()stops playback (keeps current camera pose).camera.resetPath()clears playback state (keeps keyframes unless you clear them).
The same methods exist as p5 helpers (seekPath, stopPath, resetPath) forwarding to the active camera.
Space transformations
This section covers matrix operations, matrix/frustum queries, and coordinate space conversions for 3D rendering.
Matrix operations
createMatrix(...args): Explicit wrapper aroundnew p5.Matrix(...args)(identity creation).tMatrix(matrix): Returns the transpose ofmatrix.iMatrix(matrix): Returns the inverse ofmatrix.axbMatrix(a, b): Returns the product of theaandbmatrices.
Observation: all returned matrices are p5.Matrix instances.
Matrix queries
pMatrix(): Returns the current projection matrix.mvMatrix([{ [vMatrix], [mMatrix] }]): Returns the modelview matrix.mMatrix(): Returns the model matrix (local → world), defined bytranslate/rotate/scaleand the currentpush/popstack.eMatrix(): Returns the current eye matrix (inverse ofvMatrix()). Also available onp5.Camera.vMatrix(): Returns the view matrix (inverse ofeMatrix()). Also available onp5.Camera.pvMatrix([{ [pMatrix], [vMatrix] }]): Returns projection × view.ipvMatrix([{ [pMatrix], [vMatrix], [pvMatrix] }]): Returns(pvMatrix)⁻¹.lMatrix([{ [from = createMatrix(4)], [to = this.eMatrix()], [matrix] }]): Returns the 4×4 matrix that transforms locations (points) fromfromtoto.dMatrix([{ [from = createMatrix(4)], [to = this.eMatrix()], [matrix] }]): Returns the 3×3 matrix that transforms directions (vectors) fromfromtoto(rotational part only).nMatrix([{ [vMatrix], [mMatrix], [mvMatrix] }]): Returns the normal matrix.
Observations
- All returned matrices are
p5.Matrixinstances. - Default values (
pMatrix,vMatrix,pvMatrix,eMatrix,mMatrix,mvMatrix) are those defined by the renderer at the moment the query is issued.
Frustum queries
lPlane(),rPlane(),bPlane(),tPlane()nPlane(),fPlane()fov(): vertical field-of-view (radians).hfov(): horizontal field-of-view (radians).isOrtho():truefor orthographic,falsefor perspective.
Coordinate space conversions
mapLocation(point = p5.Tree.ORIGIN, [{ [from = p5.Tree.EYE], [to = p5.Tree.WORLD], [pMatrix], [vMatrix], [eMatrix], [pvMatrix], [ipvMatrix] }])mapDirection(vector = p5.Tree._k, [{ [from = p5.Tree.EYE], [to = p5.Tree.WORLD], [vMatrix], [eMatrix], [pMatrix] }])
Pass matrix parameters when you have cached those matrices (see Matrix queries) to speed up repeated conversions:
let cachedPVI
function draw() {
cachedPVI = ipvMatrix() // compute once per frame
// many fast conversions using the cached matrix
const a = mapLocation([0, 0, 0], { from: p5.Tree.WORLD, to: p5.Tree.SCREEN, ipvMatrix: cachedPVI })
const b = mapLocation([100, 0, 0], { from: p5.Tree.WORLD, to: p5.Tree.SCREEN, ipvMatrix: cachedPVI })
// ...
}You can also convert between local spaces by passing a p5.Matrix as from / to:
let model
function draw() {
background(0)
push()
translate(80, 0, 0)
rotateY(frameCount * 0.01)
model = mMatrix()
box(40)
pop()
// screen projection of the model origin
const s = mapLocation(p5.Tree.ORIGIN, { from: model, to: p5.Tree.SCREEN })
beginHUD()
bullsEye({ x: s.x, y: s.y, size: 30 })
endHUD()
}Observations
- Returned vectors are
p5.Vectorinstances. fromandtomay be matrices or any of:p5.Tree.WORLD,p5.Tree.EYE,p5.Tree.SCREEN,p5.Tree.NDC,p5.Tree.MODEL.- When no matrix params are passed, current renderer values are used.
- The default
mapLocation()call (i.e. eye → world at origin) returns the camera world position. - The default
mapDirection()call returns the normalized camera viewing direction. - Useful vector constants:
p5.Tree.ORIGIN,p5.Tree._k,p5.Tree.i,p5.Tree.j,p5.Tree.k,p5.Tree._i,p5.Tree._j.
Heads Up Display
beginHUD(): begins HUD mode, so geometry specified betweenbeginHUD()andendHUD()is in window space.endHUD(): ends HUD mode.
In HUD space: (x, y) ∈ [0, width] × [0, height] (origin at the top-left, y down), matching image() / 2D drawing coordinates.
Utilities
A small collection of helpers commonly needed in interactive 3D sketches:
texOffset(image):[1 / image.width, 1 / image.height]mousePosition([flip = true]): pixel-density-aware mouse position (optionally flips Y).pointerPosition(pointerX, pointerY, [flip = true]): pixel-density-aware pointer position (optionally flips Y).resolution(): pixel-density-aware canvas resolution[pd * width, pd * height]pixelRatio(location): world-to-pixel ratio at a world location.mousePicking([{ ... }])andpointerPicking(pointerX, pointerY, [{ ... }]): hit-test a screen-space circle/square tied to a model matrix.bounds([{ [eMatrix], [vMatrix] }]): frustum planes in general formax + by + cz + d = 0.visibility({ ... }): returnsp5.Tree.VISIBLE,p5.Tree.INVISIBLE, orp5.Tree.SEMIVISIBLE.
Drawing stuff
Debug / teaching primitives for visualizing common 3D concepts:
axes({ size, colors, bits })grid({ size, subdivisions })cross({ mMatrix, x, y, size, ... })bullsEye({ mMatrix, x, y, size, shape, ... })viewFrustum({ pg, bits, viewer, eMatrix, pMatrix, vMatrix })
Releases
Latest (v0.0.3):
These links always point to the latest published version on npm.Current tagged version (v0.0.3):
Use these if you want to lock to a specific version.
Usage
The library works in two setups:
- CDN: Use the IIFE (Immediately Invoked Function Expression) format with
<script>tags directly in the browser, along with p5.js. - npm: Use the ES module version in modern projects with Vite or another bundler.
CDN
Include both libraries using <script> tags, which run in both global and instance mode.
<!-- index.html -->
<!-- Load p5.js first (required by p5.tree) -->
<script src="https://cdn.jsdelivr.net/npm/p5/lib/p5.min.js"></script>
<!-- Load p5.tree (latest stable version) -->
<script src="https://cdn.jsdelivr.net/npm/p5.tree/dist/p5.tree.js"></script>
<script>
function setup() {
createCanvas(600, 400, WEBGL)
// Example: draw world axes
axes(100)
}
function draw() {
background(0.15)
orbitControl()
}
</script>You can run the example, which uses global mode, by opening the index.html file in a browser, or by using VSCodium (recommended) or Visual Studio Code with the Live Server extension.
npm (ESM)
Install both p5 and p5.tree as dependencies:
npm i p5 p5.treeThen import them in your project’s entry file (e.g. main.js) using a modern bundler like Vite, which runs in instance mode only:
// main.js
import p5 from 'p5'
import 'p5.tree'
const sketch = p => {
p.setup = () => {
p.createCanvas(600, 400, p.WEBGL)
// Example: draw world axes
p.axes(100)
}
p.draw = () => {
p.background(0.15)
p.orbitControl()
}
}
new p5(sketch)This approach provides full modularity, clean instance isolation, and compatibility with modern JavaScript tooling.
