a scriptable compass & rule drawing tool
Top-level code is setup (runs once). Return a function to create an update loop (runs every frame).
t is elapsed time in seconds since the script started.
const c = compass(150)
c.pen('#ff0000')
return t => {
c.radius = 100 + 50 * sin(t)
}
| Call | Returns | Description |
|---|---|---|
compass(radius?) | CompassNode | Circle tool at origin (default radius 100) |
ruler(angle?) | RulerNode | Line tool at origin (default angle 0) |
| Property | Description |
|---|---|
.x, .y | Position (world coords) |
.radius | Circle radius |
.angle | Rotation of the compass (rotates all pens and attached children) |
.speed | Auto-advance angle each frame (angle = t * speed). Default 0 |
.offset | Attachment offset on parent (angle in radians for compass, distance in pixels for ruler) |
.draggable | Allow user to drag the compass (default false) |
.resizable | Allow user to resize the compass (default false) |
.rotatable | Allow user to rotate the compass (default false) |
.showGuides | Show/hide guide circles (default true) |
.on(parent, offset?) | Attach to parent node at given offset (default 0). Returns this |
.off() | Detach. Returns this |
.pen(color?, size?, offset?) | Create pen on edge at offset (radians). Auto-activates. Returns PenNode |
| Property | Description |
|---|---|
.x, .y | Position (world coords) |
.angle | Ruler angle |
.speed | Auto-advance angle each frame (angle = t * speed). Default 0 |
.offset | Attachment offset on parent (angle in radians for compass, distance in pixels for ruler) |
.draggable | Allow user to drag the ruler (default false) |
.rotatable | Allow user to rotate the ruler (default false) |
.showGuides | Show/hide guide line (default true) |
.on(parent, offset?) | Attach to parent node at given offset (default 0). Returns this |
.off() | Detach. Returns this |
.pen(color?, size?, offset?) | Create pen on line at offset (pixels). Auto-activates. Returns PenNode |
| Property | Description |
|---|---|
.offset | Position parameter — angle in radians for compass, distance in pixels for ruler |
.angle | Rotation of the pen shape (radians) |
.color | Pen color (default black) |
.size | Pen size in pixels (default 1) |
.active | Whether pen is drawing (read/write — setting true activates, false deactivates) |
.drawOnPress | When true, pen only draws while mouse is held down (default false) |
.shape | Pen tip shape — 'circle' (default) or a single character from the loaded simplex font |
.shapeWidth | Read-only. Computed width of the current shape at the current size |
.filled | When true, shape stamps are filled instead of stroked (default false) |
.strokeSize | Stroke width when drawing shapes (default 1) |
.flow | Minimum distance in pixels between stamps (default 1) |
.flowGap | Extra gap between stamps in pixels, or null for default spacing |
.shapeSequence | Array of shape strings to cycle through, or null for single shape |
.sequenceLimit | Stop drawing after this many stamps per sequence cycle, or null for unlimited |
.sequenceIndex | Read-only. Current index within the shape sequence |
.sequenceCycles | Read-only. Number of complete sequence cycles so far |
.stampCount | Read-only. Total number of stamps drawn by this pen |
.lastStampWidth | Read-only. Width of the most recently drawn stamp |
.activate() | Start drawing |
.deactivate() | Stop drawing |
Math. prefix)sin, cos, tan, atan2, sqrt, abs, floor, ceil, round, min, max, pow, log, PI, TAU
lerp(a, b, t), clamp(v, lo, hi), easeInOut(t), easeIn(t), easeOut(t), degToRad(degrees), radToDeg(radians)
noise(x, y?) — 2D Perlin noise, returns -1 to 1
random(min?, max?) — no args: 0–1, one arg: 0–max, two args: min–max
hsl(h, s?, l?) (defaults: s=100, l=50), rgb(r, g, b), rgba(r, g, b, a)
hex '#ff0000', '#0af' — rgb 'rgb(255, 0, 0)' — rgba 'rgba(255, 0, 0, 0.5)' — hsl 'hsl(0, 100%, 50%)' — hsla 'hsla(0, 100%, 50%, 0.5)'
| Property | Description |
|---|---|
mouse.x, mouse.y | World coords, updated each frame |
mouse.pressed | Boolean, true while mouse button held |
mouse.velocity | Smoothed, 0–1 |
mouse.direction | Smoothed angle of movement in radians |
mouse.rawVelocity | Unsmoothed 0–1 |
mouse.rawDirection | Unsmoothed angle in radians |
mouse.pressure | Stylus pressure 0–1 |
mouse.tangentialPressure | Barrel pressure -1–1 |
mouse.tiltX, mouse.tiltY | Stylus tilt in degrees, -90–90 |
mouse.twist | Stylus twist in degrees, 0–359 |
One-liner:
compass(100).pen()
Spirograph with speed (no update fn needed):
const a = compass(120)
const b = compass(40).on(a)
a.speed = 2
b.speed = 3
b.pen(hsl(220))
Spirograph:
const a = compass(120)
const b = compass(40).on(a)
b.pen('#0000ff')
a.pen('#ff0000')
return t => {
a.angle = t * 2
b.angle = t * 3
}
Nested gears:
const a = compass(120)
const b = compass(40).on(a)
const c = compass(15).on(b)
c.pen('#ff0000')
return t => {
a.angle = t * 2
b.angle = t * 7
}
Deep nest with color cycling:
const a = compass(120)
const b = compass(60).on(a)
const c = compass(35).on(b)
const d = compass(20).on(c)
const e = compass(10).on(d)
const p = e.pen()
return t => {
a.angle = t * 1
b.angle = t * 3
c.angle = t * 7
d.angle = t * 13
e.angle = t * 21
p.color = hsl(t * 60)
}
Organic motion with noise:
const c = compass(80)
c.pen()
return t => {
c.x = 100 * noise(t * 0.5, 0)
c.y = 100 * noise(0, t * 0.5)
c.radius = 60 + 30 * noise(t)
}
Mouse follower:
const c = compass(80)
c.pen()
return t => {
c.x = mouse.x
c.y = mouse.y
}
Draw on press:
const c = compass(100)
const p = c.pen('#000')
p.drawOnPress = true
return t => { c.angle = t }
Ruler:
const r = ruler(0)
r.pen()
return t => {
r.angle = sin(t) * 0.5
}
| Key | Action |
|---|---|
1–9 | Toggle pen on/off |
h | Toggle handles |
s | Pause / resume script |
u / r | Undo / redo |
f | Switch to Fill tool |
Space + drag | Pan |
| Key | Action |
|---|---|
Tab / Shift+Tab | Cycle selected node |
a | Arc drawing mode |
l | Line drawing mode |
x | Delete selected node |
c | Clear ink |