Relations
PinePaper includes a relation system for behavior-driven associations between items.
Core Concepts
What are Relations?
Relations are rules that describe how items should behave relative to each other.
// "The moon orbits the earth"
app.addRelation(moonId, earthId, 'orbits', { radius: 100, speed: 0.5 });
// The system automatically updates the moon's position each frame
Compositional Behavior
Relations are additive, not exclusive. An item can have multiple relations that compose together:
// Planet orbits star
app.addRelation(planetId, starId, 'orbits', { radius: 500, speed: 0.25 });
// Planet also constrained to canvas bounds
app.addRelation(planetId, canvasId, 'bounds_to', { padding: 20 });
// Both relations apply each frame:
// 1. orbits computes new orbital position
// 2. bounds_to constrains the position to stay within canvas
Worker-Based Computation
Relation math runs in Web Workers for performance. Heavy calculations (orbits, distance, color interpolation) happen off the main thread.
Adding Relations
addRelation(itemId, targetId, type, params)
Create a relationship between two items.
// Make moon orbit earth
app.addRelation(moonId, earthId, 'orbits', {
radius: 100,
speed: 0.5,
direction: 'counterclockwise'
});
// Make label follow player
app.addRelation(labelId, playerId, 'follows', {
offset: [0, -50], // 50px above
smoothing: 0.1
});
// Keep satellite at fixed distance
app.addRelation(satelliteId, stationId, 'maintains_distance', {
distance: 200,
strength: 0.8
});
Querying Relations
getRelations(itemId, type?)
Get all relations for an item.
// All relations
const relations = app.getRelations(itemId);
// Specific type
const orbits = app.getRelations(itemId, 'orbits');
queryByRelationTarget(targetId, type)
Find items that have a relation to a target.
// What items orbit the earth?
const orbiters = app.queryByRelationTarget(earthId, 'orbits');
orbiters.forEach(({ itemId, item, params }) => {
console.log(`${itemId} orbits at radius ${params.radius}`);
});
Advanced Queries
// Check if relation exists
if (app.hasRelation(moonId, earthId, 'orbits')) {
console.log('Moon orbits Earth');
}
// Find items that do NOT have a relation
const notOrbiting = app.queryNotRelation('orbits');
// Compound queries with AND logic
const results = app.queryCompound([
{ relation: 'orbits' }, // Must orbit something
{ relation: 'follows', not: true } // Must NOT follow anything
]);
// Chain query - follow relation chains
const chains = app.queryRelationChain(['follows', 'orbits']);
// Get all items with active relations
const active = app.queryActiveRelations();
// Get isolated items (no relations)
const isolated = app.queryIsolatedItems();
Removing Relations
// Remove specific relation
app.removeRelation(moonId, earthId, 'orbits');
// Remove all relations to target
app.removeRelation(moonId, earthId);
Built-in Relations
Core Relations
| Relation | Description |
|---|---|
orbits |
Circular motion around target |
follows |
Move toward target with smoothing |
attached_to |
Move with target (parent-child) |
maintains_distance |
Stay fixed distance from target |
points_at |
Rotate to face target |
mirrors |
Mirror position across axis |
parallax |
Move relative to target by depth |
bounds_to |
Stay within target’s bounds |
animates |
Keyframe animation over time |
Manim-Inspired Animation Relations
| Relation | Description | Manim Equivalent |
|---|---|---|
grows_from |
Scale from zero to full size from a point | GrowFromPoint/GrowFromCenter |
staggered_with |
Staggered animation timing for groups | LaggedStart |
indicates |
Temporary highlight with scale/color pulse | Indicate |
circumscribes |
Draw temporary shape around target | Circumscribe |
wave_through |
Send wave distortion through item | ApplyWave |
camera_follows |
View pans to follow target item | MovingCameraScene |
camera_animates |
Keyframe-based camera zoom and pan | Camera animation |
morphs_to |
Shape morphing animation | Transform |
See Built-in Relations for detailed documentation of each.
Statistics
const stats = app.getRelationStats();
// {
// activeItems: 5,
// ruleCount: 8,
// associationsByType: { orbits: 2, follows: 3 }
// }
Export/Import
// Export all relations
const relations = app.relationRegistry.exportForSave();
// Import relations (after scene load)
app.relationRegistry.importFromSave(relations);
See Also
- Built-in Relations - Detailed documentation for each relation type
- Relation Queries - Advanced query patterns
- Custom Relations - Create your own relation types
- Relation Registry - Registry API and training data export
- MCP Integration - Use relations in MCP tools
- MCP Tools Specification - Relation tool definitions