Game Feel · Motion · common
Squash and Stretch
deform along the motion, keep the volume constant
Stretch an object along its velocity and squash it on impact, scaling the other axes so volume stays constant. Motion reads as fast and elastic.
The orange ball stretches along its velocity as it falls and rises, and squashes flat for a moment after each bounce. The cyan wireframe is the undeformed ball for comparison. Turn off volume preservation to see it bloat, or set the amount to 0 for a rigid ball.
What it is
Squash and stretch is one of the classic principles of animation. A fast-moving object is drawn longer along its direction of travel, and it flattens when it hits something. The eye reads this as speed, weight and elasticity, even on a plain sphere or cube.
In a game it can be fully procedural. Each frame, point the object's local up axis along its velocity and scale that axis by s = 1 + k·|v| (clamped). Scale the two perpendicular axes by 1/√s so the product of the three scales stays 1. Preserving volume is what makes it look like rubber rather than an object that grows and shrinks.
On landing, switch to a squash along the contact normal (s < 1) and spring back over ~0.1 s. Keep the bottom of the object on the ground by offsetting its position by the scaled radius. Apply the deformation to a visual child node only, never to the collider or the physics body.
Key formulas
- Stretch along velocity
- Volume preservation (3D)
For a 2D sprite use s⊥ = 1 / s∥ instead.
Rules of thumb
- Keep stretch under ~1.5–1.8× and impact squash above ~0.6× unless the style is very cartoony.
- Pick
kso the stretch reaches its limit at your fastest common speed (e.g. falling at terminal velocity). - Recover from a squash in 0.08–0.15 s. Ease it with a spring or
1 − e^(−k·t)for a slight overshoot. - Deform a child mesh, not the root, so the collider and children such as weapons stay unscaled.
Implementation
const UP = new THREE.Vector3(0, 1, 0)
const dir = new THREE.Vector3()
const K = 0.05, MAX_STRETCH = 1.6
function applyStretch(visual: THREE.Object3D, velocity: THREE.Vector3) {
const speed = velocity.length()
if (speed < 1e-3) {
visual.quaternion.identity()
visual.scale.set(1, 1, 1)
return
}
const along = Math.min(MAX_STRETCH, 1 + K * speed)
const across = 1 / Math.sqrt(along) // along * across² = 1
dir.copy(velocity).divideScalar(speed)
visual.quaternion.setFromUnitVectors(UP, dir) // local Y → velocity
visual.scale.set(across, along, across)
}Common mistakes
- Scaling only one axis, so the object visibly gains and loses mass.
- Scaling the physics body or collider and getting tunneling or jitter at contacts.
- Stretching in world Y regardless of direction, so a ball moving sideways looks wrong.
- Snapping between stretch and squash with no recovery time, which reads as flicker rather than elasticity.
Prompt
Add procedural squash and stretch to a bouncing Three.js object in TypeScript. Keep physics on the root and deform only a child mesh. Each frame: if speed > 0.001, rotate the child so local +Y points along the velocity and scale it (1/sqrt(s), s, 1/sqrt(s)) with s = min(1.6, 1 + 0.05 * speed). On a ground impact, squash along the contact normal to s = 1 - 0.35 * impactSpeed / maxImpactSpeed (min 0.6) and recover to 1 over 0.12 s with an exponential ease, keeping the bottom on the floor. Write a Vitest test that the product of the three scale factors is always 1 within 1e-9.
Related
Further reading
Updated 2026-11-01