006 - Motion System

The Motion System provides reusable movement patterns based on deltaTime.

Why Motion AND Animation?

At first glance, animation and motion seem to be the same thing – both move things. But they solve different problems:

Animation: "In 3 seconds, the size should go from 80% to 120%."

  • Has defined duration
  • Interpolates between start and end
  • Needs a clock

Motion: "The object moves at 50 pixels per second to the right."

  • Runs endlessly (or until boundary)
  • Based on velocity, not time
  • Only needs deltaTime

Practical Example: A ball animates its pulsing (Breathe Animation). At the same time, it moves across the screen (VelocityMotion). Both together: Animation for values, Motion for position.

Animation vs. Motion

Aspect Animation Motion
Time Basis Normalized (0-1) deltaTime (seconds)
Purpose Interpolate value Move position
Duration Defined Endless/continuous
Clock Requires AnimationClock Not required

Components

Class File Function
Motion Motion.ts Base Class
LinearMotion LinearMotion.ts A → B
VelocityMotion VelocityMotion.ts Constant Velocity
NoiseMotion NoiseMotion.ts Organic Drift
WaveMotion WaveMotion.ts Sine Oscillation
PathMotion PathMotion.ts Along Path
OrbitMotion OrbitMotion.ts Orbit
BoundaryBehavior BoundaryBehavior.ts Boundary Behavior

Motion Base Class

Interface:

interface MotionContext {
  deltaTime: number;    // Time since last frame
  time: number;         // Total time since start
  canvasSize?: Size;    // For boundary calculations
}

abstract class Motion<T extends MotionOptions> {
  apply(position: Vector, context: MotionContext): Vector;
  getOffset(context: MotionContext): Vector;
}

LinearMotion

Linear movement between two points.

Options:

Option Type Default Description
from Vector (0.1, 0.5) Start Point (normalized)
to Vector (0.9, 0.5) Target Point (normalized)
mode string 'pingpong' Movement Mode
speed number 0.5 Speed

Modes:

Mode Behavior
once A → B, then stop
loop A → B, jump to A, repeat
pingpong A → B → A → B...

VelocityMotion

Constant velocity in one direction.

Options:

Option Type Default Description
velocity Vector (1, 0) Velocity Vector
friction number 1.0 Friction (1 = none)

NoiseMotion

Organic, random movement (Perlin Noise).

Options:

Option Type Default Description
amplitude number 50 Movement Strength
frequency number 0.01 Noise Frequency
speed number 1 Time Factor

WaveMotion

Sine-based oscillation.

Options:

Option Type Default Description
amplitude number 50 Deflection
frequency number 1 Frequency (Hz)
axis WaveAxis 'y' 'x', 'y', or 'both'
waveform WaveformType 'sine' Waveform

Waveforms:

Waveform Curve
sine Sine
triangle Triangle
square Square
sawtooth Sawtooth

PathMotion

Movement along a defined path.

Path Types:

Class Description
CirclePath Circular Path
EllipsePath Elliptical Path
LinePath Straight Path
PolygonPath Path along Polygon Vertices
BezierPath Bézier Curve

Options:

Option Type Description
path Path The Path
speed number Speed
loop boolean Repeat

OrbitMotion

Orbit around a center point.

Options:

Option Type Default Description
center Vector Canvas Center Center
radius number 100 Orbit Radius
speed number 1 Orbit Speed
startAngle number 0 Start Angle (rad)

Boundary Behaviors

Behavior at canvas boundaries.

                                         ┌─────────────────────┐
                                    ┌───▶│   BounceStrategy    │
                                    │    └─────────────────────┘
                                    │    ┌─────────────────────┐
                                    ├───▶│   WrapStrategy      │
                                    │    └─────────────────────┘
                                    │    ┌─────────────────────┐
┌──────────┐    ┌─────────┐   Yes   │    │   ClampStrategy     │
│ Position │───▶│ Boundary│────────▶├───▶└─────────────────────┘
└──────────┘    └─────────┘         │    ┌─────────────────────┐
                    │               ├───▶│   FadeStrategy      │
                    │ No            │    └─────────────────────┘
                    ▼               │    ┌─────────────────────┐
              ┌──────────┐         ├───▶│   ShrinkFadeStrategy│
              │ Continue │         │    └─────────────────────┘
              └──────────┘         │    ┌─────────────────────┐
                                   ├───▶│   DissolveStrategy  │
                                   │    └─────────────────────┘
                                   │    ┌─────────────────────┐
                                   ├───▶│   SendStrategy      │
                                   │    └─────────────────────┘
                                   │    ┌─────────────────────┐
                                   └───▶│   CompositeStrategy │
                                        └─────────────────────┘

Basic Strategies

Strategy Behavior
BounceStrategy Bounce (reverse velocity)
WrapStrategy Pac-Man (opposite side)
ClampStrategy Stop at edge
FadeStrategy Reduce opacity when leaving

Advanced Strategies (NEW)

Strategy Behavior
ShrinkFadeStrategy Object shrinks AND fades out
DissolveStrategy Object dissolves into particles
SendStrategy Object is sent via Callback/Sockets
CompositeStrategy Combines multiple strategies

BoundaryResult

Property Type Description
position Vector New Position
triggered boolean Boundary reached?
edges BoundaryEdge[] Affected Edges
reflection Vector For Velocity Correction
distanceToEdge number 0-1 Distance to Edge
scale number For ShrinkFade (0-1)
remove boolean Remove object?
data any For Callbacks (Send)
particleBurst object For Dissolve Effect

ShrinkFade Example

const boundary = new ShrinkFadeStrategy({ 
  fadeDistance: 100, 
  minScale: 0.1,
  easing: 'easeOut'
});

const result = boundary.apply(position, canvasSize);

// Apply scale and alpha
ctx.globalAlpha = result.distanceToEdge ?? 1;
ctx.scale(result.scale ?? 1, result.scale ?? 1);

// Remove object when too small
if (result.remove) {
  entities.delete(entity.id);
}

Dissolve Example

const boundary = new DissolveStrategy({ particleCount: 30 });

const result = boundary.apply(position, canvasSize);

// Process particle burst
if (result.particleBurst) {
  particleSystem.emit({
    position: result.particleBurst.position,
    count: result.particleBurst.count,
    velocity: result.particleBurst.velocity,
  });
}

if (result.remove) {
  entities.delete(entity.id);
}

Send/Receive (Multi-Screen)

The SendStrategy integrates with the MultiScreenClient for real multi-screen installations:

import { 
  SendStrategy, 
  MultiScreenClient, 
  MultiScreenDebugPanel,
  calculateEntryPosition,
  calculateEntryVelocity,
  getOppositeEdge
} from '@carstennichte/cc-toolbox';

// 1. Set up MultiScreenClient
const client = new MultiScreenClient(socket, 
  { artworkId: 'item-001', instanceId: '0' },
  { room: 'exhibition:demo', autoJoin: true }
);

client.configureTopology({
  arrangement: '1x3',  // 3 Screens side by side
  position: 0,         // Left Screen
  totalScreens: 3,
  wrapAround: true
});

// 2. SendStrategy for boundary handling
const boundary = new SendStrategy({
  onExit: (pos, edge, data) => {
    // Send via MultiScreenClient to neighbor screen
    client.sendEntity(
      {
        type: 'Entity',
        exitPosition: { x: pos.x / canvasWidth, y: pos.y / canvasHeight },
        velocity: data.velocity,
        appearance: { color: data.color, size: data.size },
      },
      edge,
      'neighbor'  // Automatically to correct neighbor
    );
  },
  exitData: () => entity.toData(),
});

// 3. Receive entities
client.onEntityReceive((data) => {
  const entryPos = calculateEntryPosition(data.exitEdge, data.entity.exitPosition, { bounds: canvasSize });
  const entryVel = calculateEntryVelocity(data.exitEdge, data.entity.velocity);
  
  const newEntity = createEntity({
    position: entryPos,
    velocity: entryVel,
    color: data.entity.appearance.color,
  });
  entities.add(newEntity);
});

// 4. Debug panel (optional)
const debugPanel = new MultiScreenDebugPanel(client);
debugPanel.show();

Send Modes:

Mode Description
broadcast To all screens in room
neighbor To neighboring screen (based on topology)
target To specific ArtworkAddress
random To random screen

See also: Multi-Screen Communication

Composite (Combined Effects)

// Shrink + Send
const boundary = new CompositeStrategy({
  strategies: [
    new ShrinkFadeStrategy({ fadeDistance: 50 }),
    new SendStrategy({ onExit: sendToOtherScreen }),
  ],
  combineMode: 'merge',
});

Combining Motion

Motions can be combined:

// Orbit + Noise
const basePos = orbitMotion.apply(center, ctx);
const finalPos = noiseMotion.apply(basePos, ctx);

Tweakpane Integration

All motions have Tweakpane support.

Registration:

Function Description
registerLinearMotionTweakpane(module, motion) LinearMotion Blades
registerBoundaryTweakpane(module, boundary) Boundary Blades

Typical Blades:

Blade Control
enabled Checkbox
speed Slider
mode Dropdown
amplitude Slider
boundaryType Dropdown