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:
- Broad-Phase (AABB) - Fast bounding box check
- Narrow-Phase (SAT) - Precise polygon overlap
- 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]
endPresets:
| 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
}
}