Built-in Relations

PinePaper includes several pre-defined relation types for common behaviors.

orbits

Circular motion around a target.

app.addRelation(moonId, earthId, 'orbits', {
  radius: 100,
  speed: 0.5,
  direction: 'counterclockwise',
  phase: 0
});
Parameter Type Default Description
radius number 100 Orbit radius
speed number 1 Rotation speed
direction string 'counterclockwise' 'clockwise' or 'counterclockwise'
phase number 0 Starting angle offset

follows

Move toward target with smoothing.

app.addRelation(labelId, playerId, 'follows', {
  offset: [0, -50],
  smoothing: 0.1,
  delay: 0
});
Parameter Type Default Description
offset array [0, 0] Position offset from target
smoothing number 0.1 Movement smoothing (0-1)
delay number 0 Follow delay in seconds

attached_to

Move with target (parent-child relationship).

app.addRelation(hatId, characterId, 'attached_to', {
  offset: [0, -30],
  inherit_rotation: true
});
Parameter Type Default Description
offset array [0, 0] Fixed offset from target
inherit_rotation boolean false Also rotate with target

maintains_distance

Stay at fixed distance from target.

app.addRelation(satelliteId, stationId, 'maintains_distance', {
  distance: 200,
  strength: 0.8
});
Parameter Type Default Description
distance number 100 Target distance
strength number 1 How strongly to maintain distance

points_at

Rotate to face target.

app.addRelation(arrowId, targetId, 'points_at', {
  offset_angle: -90,
  smoothing: 0.2
});
Parameter Type Default Description
offset_angle number 0 Angle offset in degrees
smoothing number 0 Rotation smoothing

mirrors

Mirror position across axis.

app.addRelation(reflectionId, originalId, 'mirrors', {
  axis: 'vertical',
  center: [400, 300]
});
Parameter Type Default Description
axis string 'vertical' 'vertical', 'horizontal', or 'both'
center array canvas center Mirror center point

parallax

Move relative to target by depth factor.

app.addRelation(backgroundId, cameraId, 'parallax', {
  depth: 0.5,
  origin: [400, 300]
});
Parameter Type Default Description
depth number 0.5 Depth factor (0-1)
origin array [0, 0] Parallax origin point

bounds_to

Stay within target’s bounds.

app.addRelation(playerId, arenaId, 'bounds_to', {
  padding: 20,
  bounce: true
});
Parameter Type Default Description
padding number 0 Inner padding
bounce boolean false Bounce off edges

Manim-Inspired Animation Relations

These relations are inspired by the Manim Community animation library, providing similar animation capabilities in PinePaper’s declarative relation system.

grows_from

Item scales from zero to full size, emanating from a point. Similar to Manim’s GrowFromPoint and GrowFromCenter.

app.addRelation(itemId, null, 'grows_from', {
  origin: 'center',
  duration: 0.5,
  delay: 0,
  easing: 'easeOut'
});
Parameter Type Default Description
origin string 'center' Origin point: 'center', 'top', 'bottom', 'left', 'right', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
duration number 0.5 Growth duration in seconds
delay number 0 Delay before growth starts
easing string 'easeOut' Easing function

staggered_with

Staggered animation timing for groups. Similar to Manim’s LaggedStart.

// Apply to each item in a group with increasing index
app.addRelation(item1Id, leaderId, 'staggered_with', { index: 0, stagger: 0.1, effect: 'popIn' });
app.addRelation(item2Id, leaderId, 'staggered_with', { index: 1, stagger: 0.1, effect: 'popIn' });
app.addRelation(item3Id, leaderId, 'staggered_with', { index: 2, stagger: 0.1, effect: 'popIn' });
Parameter Type Default Description
index number 0 Position in stagger sequence (0-based)
stagger number 0.1 Delay between each item in seconds
effect string 'fadeIn' Effect type: 'fadeIn', 'fadeOut', 'growIn', 'slideIn', 'popIn'

indicates

Temporary highlight effect with scale and color pulse. Similar to Manim’s Indicate.

app.addRelation(itemId, null, 'indicates', {
  scale: 1.2,
  color: '#fbbf24',
  duration: 0.5,
  repeat: 2
});
Parameter Type Default Description
scale number 1.2 Maximum scale during indication
color string '#fbbf24' Highlight color (null to skip color)
duration number 0.5 Duration of one indication cycle
delay number 0 Delay before indication starts
repeat number 1 Number of times to repeat

circumscribes

Draw a temporary shape around the target item. Similar to Manim’s Circumscribe.

app.addRelation(sourceId, targetId, 'circumscribes', {
  shape: 'rectangle',
  color: '#fbbf24',
  strokeWidth: 3,
  padding: 10,
  duration: 1.0,
  fadeOut: true
});
Parameter Type Default Description
shape string 'rectangle' Shape type: 'rectangle', 'circle', 'ellipse'
color string '#fbbf24' Stroke color
strokeWidth number 3 Stroke width in pixels
padding number 10 Padding around target item
duration number 1.0 Animation duration
fadeOut boolean true Fade out after drawing

wave_through

Send a wave distortion through the item. Similar to Manim’s ApplyWave.

app.addRelation(itemId, null, 'wave_through', {
  amplitude: 20,
  frequency: 2,
  direction: 'horizontal',
  duration: 1.0
});
Parameter Type Default Description
amplitude number 20 Wave amplitude in pixels
frequency number 2 Number of wave cycles
direction string 'horizontal' Wave direction: 'horizontal' or 'vertical'
duration number 1.0 Wave duration in seconds
delay number 0 Delay before wave starts

camera_follows

View pans to follow a target item. Similar to Manim’s MovingCameraScene.

app.addRelation(null, targetId, 'camera_follows', {
  smoothing: 0.1,
  offset: [0, 0],
  zoom: 1,
  deadzone: 50
});
Parameter Type Default Description
smoothing number 0.1 Follow smoothing (0 = instant, 1 = very slow)
offset array [0, 0] Offset from target center
zoom number 1 Zoom level (1 = normal, >1 = zoom in)
deadzone number 50 Pixels target can move before camera follows
bounds object null Camera bounds: {minX, maxX, minY, maxY}

camera_animates

Keyframe-based camera zoom and pan animation. Uses 'camera' as a virtual item ID.

// Zoom in, pan right, zoom out sequence
app.addRelation('camera', null, 'camera_animates', {
  duration: 6,
  loop: true,
  keyframes: [
    { time: 0, zoom: 1, center: [400, 300] },
    { time: 2, zoom: 2, center: [400, 300], easing: 'easeInOut' },
    { time: 4, zoom: 2, center: [600, 300], easing: 'easeOut' },
    { time: 6, zoom: 1, center: [400, 300], easing: 'easeInOut' }
  ]
});

// Or use the camera helper API
app.camera.zoomIn(2, 0.5);
app.camera.panTo(200, 200, 0.5);
app.camera.moveTo(200, 200, 2, 0.5);
app.camera.reset(0.5);
Parameter Type Default Description
keyframes array [] Array of {time, zoom, center, easing} objects
duration number 2 Total animation duration in seconds
loop boolean false Loop the animation
delay number 0 Delay before animation starts

Keyframe Properties:

Property Type Description
time number Time in seconds
zoom number Zoom level (1=normal, 2=2x zoom in, 0.5=zoom out)
center [x, y] View center coordinates
easing string Easing function for transition to this keyframe

Camera Helper Methods:

Method Description
app.camera.zoomIn(level, duration) Animate zoom in
app.camera.zoomOut(level, duration) Animate zoom out
app.camera.panTo(x, y, duration) Pan to coordinates
app.camera.panBy(dx, dy, duration) Pan by relative offset
app.camera.panLeft/Right/Up/Down(amount, duration) Directional pan
app.camera.moveTo(x, y, zoom, duration) Combined zoom and pan
app.camera.reset(duration) Reset to default state
app.camera.stop() Stop current animation
app.camera.getState() Get {zoom, center}
app.camera.isAnimating() Check if animating

morphs_to

Shape morphing animation. Similar to Manim’s Transform. Supports cross-type morphing (e.g., Path to PointText) using particle-based denoising effects.

app.addRelation(sourceId, targetId, 'morphs_to', {
  duration: 1.0,
  delay: 0,
  easing: 'easeInOut',
  morphColor: true,
  morphSize: true,
  hideTarget: true,
  removeTargetOnComplete: false
});
Parameter Type Default Description
duration number 1.0 Morph duration in seconds
delay number 0 Delay before morph starts
easing string 'easeInOut' Easing function
morphColor boolean true Also morph colors
morphSize boolean true Also morph size/scale
hideTarget boolean true Hide target item during morph (serves as shape reference)
removeTargetOnComplete boolean false Remove target item when morph completes

Morph Strategy by Type:

Source → Target Strategy Description
Path → Path Direct Point-by-point path interpolation
Circle → Circle Direct Radius and position interpolation
Text → Text Direct Font size, position, opacity blend
Path → Text Denoising Particle effect transition
Text → Path Denoising Particle effect transition
Group → Any Denoising Complex shape dissolution

Note: Path segment interpolation works best when source and target have the same number of segments. Cross-type morphs use a particle-based denoising effect for smooth transitions.