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.