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