Skip to main content
Canvas Cloth Simulation new

Cloth Simulation

Verlet integration cloth physics with interactive tearing

Statistics
Points
0
Constraints
0
Iterations
0
Physics
Wind

Drag to pull cloth · Shift+click to tear

Verlet Integration

This simulation uses Verlet integration for cloth physics, a numerical method that is both stable and efficient for constrained particle systems.

Position Update

Instead of tracking velocity explicitly, Verlet integration derives it from the difference between current and previous positions:

x(t+dt) = 2 * x(t) - x(t-dt) + a * dt²

This implicit velocity makes the system naturally damped and resistant to energy gain.

Constraint Relaxation

Points are connected by distance constraints. Each frame, constraints are solved iteratively:

  1. Calculate current distance between two points
  2. Compare to rest length
  3. Move each point by half the difference
  4. Repeat for multiple iterations (more = stiffer)

correction = (restLength - distance) / distance * 0.5

Tearing

When a constraint stretches beyond a threshold (200% of rest length), it breaks permanently. This simulates cloth tearing under excessive force.

Interaction

Dragging a point moves it directly, and Verlet integration naturally propagates the motion through the constraint network, creating realistic cloth behavior.

© 2013 - 2026 Cylian 🤖 Claude
Instructions Claude

Prompt utilisé pour régénérer cette page :

Page: Cloth Simulation
Description: "Verlet integration cloth physics with interactive tearing"
Category: canvas
Icon: grid
Tags: physics, simulation, verlet
Status: new

Front matter (index.md):
  title: "Cloth Simulation"
  description: "Verlet integration cloth physics with interactive tearing"
  icon: "grid"
  tags: ["physics", "simulation", "verlet"]
  status: ["new"]

HTML structure (index.md):
  <section class="container visual size-800 ratio-1-1 canvas-contain">
    <canvas id="cloth-canvas"></canvas>
  </section>

Widget files:
- _stats.right.md (weight: 10): div.cloth-stats with:
  ##### Statistics — <dl> with Points/Constraints/Iterations stats
  Stat IDs: stat-points, stat-constraints, stat-iterations

- _controls.right.md (weight: 20): Contains controls, options, and help:
  div.cloth-controls with 3 native <button> elements (not shortcode buttons):
    <button id="btn-play" class="item button is-play"> with {{< icon name="play" >}}
    <button id="btn-pause" class="item button is-pause"> with {{< icon name="pause" >}}
    <button id="btn-reset" class="item button"> with {{< icon name="refresh" >}}
  div.cloth-options with 2 fieldsets:
    Physics fieldset:
      Gravity: slider-gravity (0-2, step 0.05, value 0.5), display span#display-gravity
      Stiffness: slider-stiffness (0.1-1, step 0.05, value 0.8), display span#display-stiffness
      Iterations: slider-iterations (1-15, step 1, value 5), display span#display-iterations
    Wind fieldset:
      Enable wind: checkbox toggle-wind
      Strength: slider-wind (0-1, step 0.05, value 0.3), display span#display-wind
  div.cloth-help: "**Drag** to pull cloth · **Shift+click** to tear"

- _algorithm.after.md (weight: 90): Explains Verlet integration formula (x(t+dt) = 2*x(t) - x(t-dt) + a*dt²), constraint relaxation with correction formula, tearing threshold at 200%, interaction propagation.

Architecture (single file default.js):
- IIFE, imports panic from /_lib/panic_v3.js
- No other external dependencies

SCSS file (default.scss):
- CSS custom properties: --cloth-color-normal (var(--draw-color-primary)), --cloth-color-stress (var(--color-red)), --cloth-color-pin (var(--color-yellow))
- .cloth-controls: flex row, .is-play/.is-pause toggled by .is-running, native button reset styling
- .cloth-options: flex column with fieldset/legend sections, .option with label+range/checkbox, label has span with monospace+primary color for value display
- .cloth-stats: dl grid (1fr auto), dt secondary color, dd monospace right-aligned
- .cloth-help: centered secondary text, margin-top

CONFIG defaults:
  canvasSize: 800, gridCols: 30, gridRows: 25, spacing: 15,
  gravity: 0.5, stiffness: 0.8, damping: 0.99,
  constraintIterations: 5, tearThreshold: 2.0,
  windEnabled: false, windStrength: 0.3, mouseRadius: 30

Cloth data structure:
- Points array: objects with {x, y, oldX, oldY, pinned}
- Constraints array: objects with {p1, p2, restLength, torn?}
- Grid: 30×25 points, top row pinned every 3rd point (col % 3 === 0)
- Horizontal constraints: between adjacent points in same row
- Vertical constraints: between adjacent points in same column
- Start position: centered horizontally ((canvasWidth - cols*spacing)/2), 50px from top

Verlet integration (verletIntegrate):
- Skips pinned points
- Implicit velocity from position difference: vx = (x - oldX) * damping (0.99)
- Store current as old, then: x += vx, y += vy + gravity
- Wind: sinusoidal force = windStrength * sin(time + y * 0.01), applied to x axis
- Boundary constraints: floor at h-5, left wall at x=5, right wall at x=w-5
- Time from Date.now() * 0.001

Constraint solver (solveConstraints):
- Iterates constraintIterations times (default 5)
- For each constraint (iterated in reverse):
  - Skip if already torn
  - Calculate distance between p1 and p2
  - If distance > restLength * tearThreshold (2.0): mark torn, skip
  - If distance === 0: skip (degenerate case)
  - Push/pull both points: diff = (restLength - dist) / dist * stiffness
  - Offset applied to each point by half (if not pinned)
- After all iterations: batch filter out torn constraints

Rendering:
- HD canvas: 800px × devicePixelRatio, ctx.scale(dpr, dpr)
- Background: cachedColors.background (from --background-color-surface)
- Constraints drawn as lines colored by stretch stress:
  - stretch > 1.3: stress color (red, cachedColors.stress)
  - stretch > 1.1 to 1.3: RGB interpolation from normal→stress (inline calc: t = (stretch-1.1)/0.2)
  - stretch <= 1.1: normal color (cachedColors.normal)
- Line width 1.5, round caps
- Pinned points: cachedColors.pin (yellow) dots (radius 3)

Mouse interaction:
- getCanvasCoords(e): converts event to canvas logical coordinates via getBoundingClientRect
- findNearestPoint(mx, my): finds closest point within mouseRadius (30px)
- Drag mode (default mousedown without shift):
  - mousedown: find nearest point, store as dragPoint
  - mousemove: if mouseDown and shiftDown, tear instead; otherwise drag updates position
  - dragPoint's x/y and oldX/oldY set to mouse position (prevents velocity accumulation)
- Tear mode (shift held):
  - tearAt(mx, my): removes constraints whose midpoint is within mouseRadius of mouse
  - Midpoint = average of p1 and p2 positions
  - Continuous tearing while shift+drag
- mouseup/mouseleave: release dragPoint, clear mouseDown
- Shift key tracked via document keydown/keyup events
- Context menu prevented on canvas

Animation loop:
- tick() runs continuously via requestAnimationFrame (always rendering)
- Drag interaction works even when physics is paused
- Physics (verletIntegrate + solveConstraints) only runs when isRunning = true
- Stats updated every frame via updateStats()

Controls architecture:
- start(): sets isRunning = true, updates button visibility
- pause(): sets isRunning = false, updates button visibility
- reset(): stops physics, clears dragPoint/mouseDown, recreates cloth from scratch
- updateControlState(): toggles .is-running on .cloth-controls element
- bindSlider(sliderId, displayId, setter, decimals): generic helper for all range inputs
- Gravity slider: updates gravity variable
- Stiffness slider: updates stiffness variable
- Iterations slider: updates constraintIterations (rounded to integer)
- Wind toggle: updates windEnabled boolean
- Wind strength slider: updates windStrength variable

Color caching:
- cachedColors: background, normal, stress, pin
- cacheColors() reads from CSS vars:
  - background: --background-color-surface (fallback #1a1a1a)
  - normal: --cloth-color-normal or --draw-color-primary (fallback #5eaadd)
  - stress: --cloth-color-stress or --color-red (fallback #e74c3c)
  - pin: --cloth-color-pin or --color-yellow (fallback #f1c40f)
- Refreshed on prefers-color-scheme change

Initialization:
- Gets cloth-canvas element
- Caches colors, sets up theme listener
- initCanvas(): sets physical resolution and DPI scaling
- createCloth(): generates point grid and constraints
- setupControls(): binds buttons and sliders
- setupMouse(): binds all mouse/keyboard events
- Starts render loop immediately (tick via rAF)
- Updates stats, logs initialization

Auto-init on DOM ready (DOMContentLoaded or immediate if already loaded).
Render loop starts immediately on init (animation always runs, physics toggled separately).

Page entièrement générée et maintenue par IA, sans intervention humaine.