004 - Shapes

Das Shape System bietet eine modulare, erweiterbare Architektur für geometrische Formen mit Physics-Unterstützung.

Das Problem

Geometrische Formen sind das Grundvokabular generativer Kunst: Kreise, Rechtecke, Polygone, Sterne. Aber:

  • Jede Form hat eigene Parameter – Kreis hat Radius, Rechteck hat Breite/Höhe, Stern hat Zacken
  • Manche Formen können deformiert werden – Soft Shapes mit Jelly-Effekt
  • Physik passt nicht zu allen – Ein Kreis kann abprallen, aber eine Linie?

Die Lösung

Das Shape System nutzt ein Registry-Pattern mit Decorator-basierter Auto-Registration. Jedes Shape registriert sich selbst mit seinen Metadaten – Name, Kategorie, Physics-Fähigkeiten. Die ShapeRegistry ist dann die zentrale Factory: "Gib mir ein Shape namens 'Circle'" → fertig.

Warum Decorator? Weil die Information direkt am Code steht, wo sie hingehört. Kein zentrales Config-File, das aus dem Sync gerät. Neue Shape-Klasse schreiben, @registerShape drauf, fertig.

Warum Physics-Capabilities? Nicht jedes Shape kann alles. Ein harter Kreis kann als Rigid Body abprallen. Ein Soft Circle kann sich verformen. Eine Linie hat keine sinnvolle Physik. Das System weiß das und verhindert unsinnige Kombinationen.

Architektur

┌──────────────────────────────────────────────────────────────────────┐
│  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           │   │                    │   │                    │
└────────────────────┘   └────────────────────┘   └────────────────────┘

Komponenten

Klasse Pfad Funktion
ShapeRegistry shapes/ShapeRegistry.ts Factory & Registration
ShapeBase shapes/ShapeBase.ts Basis-Klasse
@registerShape shapes/ShapeRegistry.ts Auto-Registration Decorator

ShapeRegistry

Zentrale Factory für alle Shapes.

API:

Methode Beschreibung
create(name, props) Shape erstellen
has(name) Shape existiert?
getNames() Alle Shape-Namen
getMetadata(name) Shape-Metadaten
getByCategory(cat) Nach Kategorie filtern
getByPhysicsCapability(cap) Nach Physics filtern

Kategorien:

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

Basic Shapes

Geometrische Grundformen mit Rigid-Body Physics.

Circle

Property Typ Default Beschreibung
radius number 50 Radius

Rect

Property Typ Default Beschreibung
width number 100 Breite
height number 100 Höhe
cornerRadius number 0 Eckenradius

Ellipse

Property Typ Default Beschreibung
radiusX number 50 Horizontaler Radius
radiusY number 30 Vertikaler Radius

Polygon

Property Typ Default Beschreibung
sides number 6 Anzahl Seiten
radius number 50 Außenradius

Star

Property Typ Default Beschreibung
points number 5 Anzahl Zacken
outerRadius number 50 Außenradius
innerRadius number 25 Innenradius

Line

Property Typ Default Beschreibung
x1, y1 number 0 Startpunkt
x2, y2 number 100 Endpunkt

Rhombus, Trapez, Triangle

Spezialisierte Polygone mit entsprechenden Eigenschaften.

Soft Shapes

Deformierbare Formen mit interner Verlet-Simulation.

Zwei Ansätze für Soft Bodies:

Ansatz Beschreibung Anwendung
SoftCircle, SoftRect, Blob Spezialisierte Soft-Shape Klassen Wenn du ein dediziertes weiches Shape brauchst
ShapeStateManager Macht JEDES Shape soft Flexibel - toggle zwischen starr und weich

SoftCircle

Property Typ Default Beschreibung
radius number 50 Basis-Radius
vertices number 24 Anzahl Vertices
stiffness number 0.5 Steifigkeit (0-1)
damping number 0.9 Dämpfung

SoftRect

Property Typ Default Beschreibung
width number 100 Basis-Breite
height number 100 Basis-Höhe
segments number 4 Segmente pro Seite
stiffness number 0.5 Steifigkeit

Blob

Property Typ Default Beschreibung
radius number 50 Basis-Radius
irregularity number 0.3 Unregelmäßigkeit
vertices number 12 Anzahl Vertices
stiffness number 0.3 Steifigkeit

ShapeStateManager - Universal Soft Bodies

Das Motto: "Meine Welt, meine Regeln!" - JEDES Shape kann soft werden.

Mit dem ShapeStateManager kannst du normale Shapes (Rect, Circle, Star, Polygon...) in Soft Bodies verwandeln.

Grundlegende Verwendung

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

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

// 2. Shape-Geometrie initialisieren
stateManager.initializeFromGeometry({
  type: 'rect',
  center: new Vector(200, 200),
  width: 100,
  height: 80,
});

// 3. Preset anwenden
stateManager.applyPreset('jelly');

// 4. In der Animations-Schleife
function animate(deltaTime: number) {
  stateManager.update(deltaTime);
  const vertices = stateManager.getOutlineVertices();
  renderer.drawPolygon(vertices, brush);
}

Verfügbare Presets

Preset Effekt Beschreibung
default Starr Keine Soft Body Physik
soft Sanft weich Ausgewogene Werte
cloth Stoff Pin top, folgt Gravitation
balloon Aufgeblasen Mit Druck-Simulation
jelly Wackelpudding Sehr elastisch, springt zurück
slime Schleim Sehr weich, träge
rubber Gummi Schnell federnd
droplet Wassertropfen Oberflächenspannung
organic Organisch Natürlich wirkend
stiff Fast starr Minimale Verformung

Interaktion

// Kräfte anwenden
stateManager.applyForce(new Vector(0, 0.5));  // Gravitation
stateManager.applyExplosion(mousePos, 5, 200); // Explosion
stateManager.repelFrom(mousePos, 0.5, 100);   // Abstoßung

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

// Zurücksetzen
stateManager.resetPositions();

Pin-Modi

Fixiere bestimmte Punkte:

Modus Beschreibung
none Alles frei beweglich
top Obere Kante fixiert
bottom Untere Kante fixiert
left / right Seite fixiert
corners Alle Ecken fixiert
center Mittelpunkt fixiert

Kollisionserkennung

Soft Bodies können miteinander kollidieren:

// Mehrere Shapes erstellen
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);
}

// Kollision aktivieren - jeder kennt alle anderen
const allDecorators = shapes.map(s => s.getDecorator());
for (const shape of shapes) {
  shape.setCollisionBodies(allDecorators);
}

// Update - Kollisionen werden automatisch geprüft
function animate(dt: number) {
  for (const shape of shapes) {
    shape.update(dt);
  }
}

Kollisions-Algorithmus:

  1. Broad-Phase (AABB) - Schneller Bounding-Box Check
  2. Narrow-Phase (SAT) - Präzise Polygon-Überlappung
  3. Response - Shapes werden separiert + elastischer Bounce

Custom Shapes

Character

Rendert Einzelzeichen in verschiedenen Fonts.

Property Typ Default Beschreibung
char string 'A' Das Zeichen
fontSize number 48 Schriftgröße
fontFamily string 'Arial' Schriftart

CustomSVG

Rendert SVG-Pfade.

Property Typ Beschreibung
svgPath string SVG Path-String
viewBox {x,y,w,h} Viewport

Physics Capabilities

Jedes Shape deklariert seine Physics-Fähigkeiten.

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-Zuordnung:

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

ShapePhysicsAdapter

Verbindet Shapes mit PhysicsWorld.

Funktionen:

Methode Beschreibung
attachToWorld(world) Shape an PhysicsWorld binden
setPosition(vec) Position setzen
applyForce(vec) Kraft anwenden
applyImpulse(vec) Impuls anwenden
setLocked(bool) Fixieren/Freigeben

Verhalten:

  • Rigid Body Shapes: Ein Particle repräsentiert das Shape
  • Soft Body Shapes: Optional ein Center-Particle für globale Kräfte
  • No Physics Shapes: Adapter nicht anwendbar

Eigene Shapes erstellen

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 {
    // ... Pfad generieren
  }
  
  getBounds(): { x, y, width, height } {
    // ... Bounds berechnen
  }
}