004 - Shapes

The Shape System provides a modular, extensible architecture for geometric shapes with physics support.

The Problem

Geometric shapes are the basic vocabulary of generative art: circles, rectangles, polygons, stars. But:

  • Each shape has its own parameters – Circle has radius, Rectangle has width/height, Star has points
  • Some shapes can be deformed – Soft shapes with jelly effect
  • Physics doesn't fit all – A circle can bounce, but a line?

The Solution

The Shape System uses a Registry pattern with decorator-based auto-registration. Each shape registers itself with its metadata – name, category, physics capabilities. The ShapeRegistry is then the central factory: "Give me a shape called 'Circle'" → done.

Why Decorator? Because the information is directly at the code where it belongs. No central config file that gets out of sync. Write a new shape class, put @registerShape on it, done.

Why Physics Capabilities? Not every shape can do everything. A rigid circle can bounce as a rigid body. A soft circle can deform. A line has no meaningful physics. The system knows this and prevents nonsensical combinations.

Architecture

┌──────────────────────────────────────────────────────────────────────┐
│  Registration                                                        │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │              @registerShape Decorator                          │  │
│  └────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────┐
│  Registry                                                            │
│  ┌────────────────────────────────────────────────────────────────┐  │
│  │                      ShapeRegistry                             │  │
│  └────────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌──────────────────────────────────────────────────────────────────────┐
│  Base                                                                │
│  ┌───────────────────────┐    ┌───────────────────────┐              │
│  │      ShapeBase        │───▶│   IShape Interface    │              │
│  └───────────────────────┘    └───────────────────────┘              │
└──────────────────────────────────────────────────────────────────────┘
                                    ▲
          ┌─────────────────────────┼─────────────────────────┐
          │                         │                         │
┌─────────┴──────────┐   ┌──────────┴─────────┐   ┌──────────┴─────────┐
│   Basic Shapes     │   │    Soft Shapes     │   │      Custom        │
├────────────────────┤   ├────────────────────┤   ├────────────────────┤
│ Circle, Rect       │   │ SoftCircle         │   │ Character          │
│ Ellipse, Polygon   │   │ SoftRect           │   │ CustomSVG          │
│ Star, Line         │   │ Blob               │   │                    │
│ Rhombus, Trapez    │   │                    │   │                    │
│ Triangle           │   │                    │   │                    │
└────────────────────┘   └────────────────────┘   └────────────────────┘

Components

Class Path Function
ShapeRegistry shapes/ShapeRegistry.ts Factory & Registration
ShapeBase shapes/ShapeBase.ts Base Class
@registerShape shapes/ShapeRegistry.ts Auto-Registration Decorator

ShapeRegistry

Central factory for all shapes.

API:

Method Description
create(name, props) Create Shape
has(name) Shape exists?
getNames() All Shape Names
getMetadata(name) Shape Metadata
getByCategory(cat) Filter by Category
getByPhysicsCapability(cap) Filter by Physics

Categories:

Category Shapes
basic Circle, Rect, Ellipse, Line, Polygon, Star, Rhombus, Trapez, Triangle
soft SoftCircle, SoftRect, Blob
custom Character, CustomSVG

Basic Shapes

Basic geometric shapes with rigid body physics.

Circle

Property Type Default Description
radius number 50 Radius

Rect

Property Type Default Description
width number 100 Width
height number 100 Height
cornerRadius number 0 Corner Radius

Ellipse

Property Type Default Description
radiusX number 50 Horizontal Radius
radiusY number 30 Vertical Radius

Polygon

Property Type Default Description
sides number 6 Number of Sides
radius number 50 Outer Radius

Star

Property Type Default Description
points number 5 Number of Points
outerRadius number 50 Outer Radius
innerRadius number 25 Inner Radius

Line

Property Type Default Description
x1, y1 number 0 Start Point
x2, y2 number 100 End Point

Rhombus, Trapez, Triangle

Specialized polygons with corresponding properties.

Soft Shapes

Deformable shapes with internal Verlet simulation.

Two approaches for soft bodies:

Approach Description Application
SoftCircle, SoftRect, Blob Specialized soft shape classes When you need a dedicated soft shape
ShapeStateManager Makes ANY shape soft Flexible - toggle between rigid and soft

SoftCircle

Property Type Default Description
radius number 50 Base Radius
vertices number 24 Number of Vertices
stiffness number 0.5 Stiffness (0-1)
damping number 0.9 Damping

SoftRect

Property Type Default Description
width number 100 Base Width
height number 100 Base Height
segments number 4 Segments per Side
stiffness number 0.5 Stiffness

Blob

Property Type Default Description
radius number 50 Base Radius
irregularity number 0.3 Irregularity
vertices number 12 Number of Vertices
stiffness number 0.3 Stiffness

ShapeStateManager - Universal Soft Bodies

The Motto: "My world, my rules!" - ANY shape can become soft.

With the ShapeStateManager you can transform normal shapes (Rect, Circle, Star, Polygon...) into soft bodies.

Basic Usage

import { ShapeStateManager } from '@carstennichte/cc-toolbox';

// 1. Create StateManager
const stateManager = new ShapeStateManager();

// 2. Initialize shape geometry
stateManager.initializeFromGeometry({
  type: 'rect',
  center: new Vector(200, 200),
  width: 100,
  height: 80,
});

// 3. Apply preset
stateManager.applyPreset('jelly');

// 4. In the animation loop
function animate(deltaTime: number) {
  stateManager.update(deltaTime);
  const vertices = stateManager.getOutlineVertices();
  renderer.drawPolygon(vertices, brush);
}

Available Presets

Preset Effect Description
default Rigid No soft body physics
soft Gently soft Balanced values
cloth Fabric Pin top, follows gravity
balloon Inflated With pressure simulation
jelly Jelly Very elastic, bounces back
slime Slime Very soft, sluggish
rubber Rubber Fast springing
droplet Water droplet Surface tension
organic Organic Natural looking
stiff Almost rigid Minimal deformation

Interaction

// Apply forces
stateManager.applyForce(new Vector(0, 0.5));  // Gravity
stateManager.applyExplosion(mousePos, 5, 200); // Explosion
stateManager.repelFrom(mousePos, 0.5, 100);   // Repulsion

// Drag vertex (Drag & Drop)
stateManager.dragVertex(targetVertex, mousePos);

// Reset
stateManager.resetPositions();

Pin Modes

Fix certain points:

Mode Description
none All freely movable
top Top edge fixed
bottom Bottom edge fixed
left / right Side fixed
corners All corners fixed
center Center point fixed

Collision Detection

Soft bodies can collide with each other:

// Create multiple shapes
const shapes: ShapeStateManager[] = [];
for (let i = 0; i < 5; i++) {
  const sm = new ShapeStateManager();
  sm.initializeFromGeometry({ type: 'circle', center: ..., radius: 50 });
  sm.applyPreset('jelly');
  shapes.push(sm);
}

// Activate collision - each knows all others
const allDecorators = shapes.map(s => s.getDecorator());
for (const shape of shapes) {
  shape.setCollisionBodies(allDecorators);
}

// Update - collisions are checked automatically
function animate(dt: number) {
  for (const shape of shapes) {
    shape.update(dt);
  }
}

Collision Algorithm:

  1. Broad-Phase (AABB) - Fast bounding box check
  2. Narrow-Phase (SAT) - Precise polygon overlap
  3. Response - Shapes are separated + elastic bounce applied

Custom Shapes

Character

Renders single characters in various fonts.

Property Type Default Description
char string 'A' The Character
fontSize number 48 Font Size
fontFamily string 'Arial' Font Family

CustomSVG

Renders SVG paths.

Property Type Description
svgPath string SVG Path String
viewBox {x,y,w,h} Viewport

Physics Capabilities

Each shape declares its physics capabilities.

graph LR
    subgraph "Capability Flags"
        RB[supportsRigidBody]
        SB[hasSoftBody]
        CP[supportsCenterParticle]
    end
    
    subgraph "Presets"
        NO[NO_PHYSICS]
        RIGID[RIGID_BODY_PHYSICS]
        SOFT[SOFT_BODY_PHYSICS]
    end

Presets:

Preset RigidBody SoftBody CenterParticle
NO_PHYSICS
RIGID_BODY_PHYSICS
SOFT_BODY_PHYSICS

Shape Assignment:

Shape Physics
Circle, Rect, Polygon, Star, etc. RIGID_BODY_PHYSICS
SoftCircle, SoftRect, Blob SOFT_BODY_PHYSICS
Line, Character, CustomSVG NO_PHYSICS

ShapePhysicsAdapter

Connects shapes with PhysicsWorld.

Functions:

Method Description
attachToWorld(world) Bind Shape to PhysicsWorld
setPosition(vec) Set Position
applyForce(vec) Apply Force
applyImpulse(vec) Apply Impulse
setLocked(bool) Lock/Unlock

Behavior:

  • Rigid Body Shapes: One particle represents the shape
  • Soft Body Shapes: Optionally a center particle for global forces
  • No Physics Shapes: Adapter not applicable

Creating Custom Shapes

import { ShapeBase, registerShape, RIGID_BODY_PHYSICS } from '@carstennichte/cc-toolbox';

@registerShape({
  name: 'hexagon',
  category: 'basic',
  description: 'Regular hexagon',
  physics: RIGID_BODY_PHYSICS
})
export class Hexagon extends ShapeBase {
  getPath(): Path2D {
    // ... generate path
  }
  
  getBounds(): { x, y, width, height } {
    // ... calculate bounds
  }
}