{"id":"bouncing-balls","version":"1.1.0","title":"Bouncing Balls (Real Physics)","summary":"Balls drop into a box, bounce off each other and the walls, roll and settle — an actual physics simulation.","kind":"physics","target":"js","collections":["satisfying","bouncing-falling"],"tags":["physics","bounce","bouncing","balls","falling","collision","simulation","rigid body","gravity","satisfying","marbles"],"loopable":false,"motion":"simulation","durationSeconds":8,"status":"published","media":{"poster":"https://aipixelwizard.com/media/bouncing-balls/1.1.0/poster.webp","preview":"https://aipixelwizard.com/media/bouncing-balls/1.1.0/preview.mp4","sample":"https://aipixelwizard.com/media/bouncing-balls/1.1.0/sample.mp4","sampleBytes":800300,"sampleNote":"Pre-rendered with the default settings. Customised settings must be exported locally (npm run render)."},"urls":{"page":"https://aipixelwizard.com/visuals/bouncing-balls","manifest":"https://aipixelwizard.com/api/v1/visuals/bouncing-balls/versions/1.1.0","agentMd":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/agent.md","files":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/","demo":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/index.html"},"description":"A small, real rigid-body simulation: spheres with mass, bounciness and friction collide with each other and the walls of a box, spin as they roll, and come to rest. It is calculated once at a fixed step, then played back frame by frame, so the same seed always gives the same motion and video export is exact. The result is just numbers (positions and spins), so you can draw the balls any way you like: flat 2D, glossy 3D, chrome or glass.","role":"Motion source: per-frame positions and orientations of colliding spheres in a box.","useCases":["Satisfying falling-and-settling clips","Balls poured into a container","Chrome or glass marbles (combine with Chrome Material and Glass Container)"],"synonyms":["bouncy balls","balls falling","drop balls","balls in a box","marbles","ball pit","physics balls","objects fall bounce settle","shiny balls bouncing"],"digest":"25a29def87560c0a12e03dfed36f2fe090df37af0480a1579d2d5f7597521a89","implementation":{"target":"js","runtime":"any","language":"javascript","module":"esm","entry":"src/bouncing-balls.js","exports":[{"name":"simulateBouncingBalls","signature":"simulateBouncingBalls({ count?, seed?, box?, radius?, restitution?, friction?, gravity?, fps?, durationSeconds?, stepHz?, spawnHeight? }) => { count, radii, frameCount, positionAt(frame, i), quaternionAt(frame, i), positions, quaternions, box, stats }","description":"Runs the fixed-timestep simulation once and returns baked per-frame transforms."},{"name":"rotateByQuaternion","signature":"rotateByQuaternion([x, y, z], [qx, qy, qz, qw]) => [x, y, z]","description":"Helper for drawing spin in 2D."}],"reusable":["src/bouncing-balls.js"],"demo":"demo.js","harness":"index.html"},"dependencies":[{"id":"apw-runtime","version":"1.0.0","scope":"demo","reason":"Demo harness and colour helpers. src/bouncing-balls.js has no dependencies."}],"uses":[],"usedBy":[],"params":[{"key":"count","label":"Number of balls","type":"integer","min":1,"max":40,"step":1,"default":16},{"key":"bounciness","label":"Bounciness","help":"Low is heavy and dead; high is super-bouncy.","type":"number","min":0.3,"max":0.92,"step":0.01,"default":0.72},{"key":"ballSize","label":"Ball size","type":"number","min":0.6,"max":1.5,"step":0.05,"default":1},{"key":"gravity","label":"Gravity","help":"9.81 is Earth. Lower feels floaty, like slow motion.","type":"number","min":2,"max":20,"step":0.1,"default":9.81},{"key":"seed","label":"Variation","help":"Each number gives a different but repeatable drop.","type":"integer","min":1,"max":9999,"step":1,"default":7},{"key":"palette","label":"Colours","type":"select","default":"candy","options":[{"value":"candy","label":"Candy"},{"value":"sunset","label":"Sunset"},{"value":"mono","label":"Monochrome"}]},{"key":"background","label":"Background","type":"color","default":"#0d0d12"}],"presets":[{"id":"default","label":"Candy drop","params":{}},{"id":"floaty","label":"Floaty & bouncy","params":{"gravity":3.5,"bounciness":0.88,"count":10}},{"id":"pile","label":"Heavy pile","params":{"count":34,"bounciness":0.4,"ballSize":0.8,"palette":"mono"}}],"output":{"width":1080,"height":1080,"fps":30,"durationSeconds":8,"posterSeconds":1.6,"alpha":false,"motion":"simulation","loop":{"seamless":false,"method":"Physics runs forward and settles; it does not return to the starting state."},"seed":{"param":"seed","behavior":"Same seed and settings produce identical motion on every run."}},"composition":{"inputs":"Options (count, seed, box size, bounciness, gravity) and the clip's fps/duration.","outputs":"positionAt(frame, i) → [x, y, z] and quaternionAt(frame, i) → [x, y, z, w] for each ball; radii per ball.","coordinates":"Y-up. Box floor at y = 0, centred on x = 0, z = 0. Default inner box 4 × 3 × 4 units. Walls are infinitely tall.","lighting":"None (motion only).","timing":"Bake once with the clip's fps and duration, then look up by frame. Frames are random-access.","compatibleWith":["chrome-material","glass-container","orbit-camera"],"notes":["Match the box option to any container you draw (Glass Container uses the same 4 × 3 × 4 default).","Re-bake when count, seed, bounciness, gravity, fps or duration change."]},"reuse":["Draw the balls as chrome spheres inside a glass box in Three.js (see the Bouncing Marbles in a Box example).","Use positions to drive 2D emoji, logos or product photos falling into a pile.","Use only the first second (balls still in the air) for a drop-in transition."],"integration":{"newProject":"Call simulateBouncingBalls({ fps, durationSeconds, ...options }) once, then each frame read positionAt(frame, i) and draw ball i at that position with radius radii[i].","existingProject":"Copy src/bouncing-balls.js. Bake once where your scene is created, then update the transforms of your existing ball objects from positionAt/quaternionAt in your frame update. Keep your renderer, materials and camera."},"performance":{"notes":"Baking 8 s of 40 balls at 240 Hz takes about 50 ms in Node 22 on a server CPU. Playback is a lookup.","limits":"Up to 60 balls. Collision checking is all-pairs, so cost grows with count²."},"limitations":["Spheres only, inside an axis-aligned box. Not a general physics engine.","During hard impacts balls can overlap by up to about 0.05 units for a step before correction (measured ≤ 0.05 with default sizes).","Slow contacts are treated as resting (no bounce) so piles settle instead of jittering."],"license":{"spdx":"MIT","notice":"Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.","thirdParty":[]},"rights":{"approvedForPublicRelease":true},"provenance":"Original code written for AI Pixel Wizard in October 2026. Demo scene rebuilt in October 2026 to the catalog quality bar (docs/QUALITY.md); the reusable source is unchanged.","changelog":[{"version":"1.1.0","date":"2026-10-03","notes":"New demo: lit studio tray with candy-coated spheres (key, rim, softbox highlight), soft contact shadows and a cove backdrop. Physics unchanged."},{"version":"1.0.0","date":"2026-10-02","notes":"Initial release."}],"verification":{"testedOn":"2026-10-03","inputs":{"bouncing-balls@1.1.0":"25a29def87560c0a12e03dfed36f2fe090df37af0480a1579d2d5f7597521a89","apw-runtime@1.0.0":"5be859cdac74823ba0bcc32c6daf06688a871104d24db5d4ceaffffd84d7c40d"},"environment":"Chrome 154.0.8037.97 headless on Linux 7.0.0-38-generic (x64), Node 22.14.0","renderer":"Canvas 2D","width":1080,"height":1080,"fps":30,"frames":240,"renderSeconds":27.5,"loopCheck":{"checked":false,"seamless":null,"maxDifference":null,"note":"Asset does not claim a seamless loop."},"determinism":{"checked":true,"identical":true},"errors":[],"media":{"poster":"/media/bouncing-balls/1.1.0/poster.webp","preview":"/media/bouncing-balls/1.1.0/preview.mp4","sample":"/media/bouncing-balls/1.1.0/sample.mp4","sampleBytes":800300}},"files":[{"path":"LICENSE","size":1072,"sha256":"8a5f3a96f07e11522ec4bdd0a583356f99bad1245920f9709cee09b995a45153","contentType":"text/plain; charset=utf-8","url":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/LICENSE"},{"path":"asset.json","size":7993,"sha256":"c974900d8e7615f43a66e29f827f4f6ca85a8b4f859b93ef1529299af248731d","contentType":"application/json; charset=utf-8","url":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/asset.json"},{"path":"demo.js","size":9164,"sha256":"c8f248c0fb2181784e87b4cfda72ffa6b9d52035a2172fae1faffb18ed6a11ba","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/demo.js"},{"path":"index.html","size":543,"sha256":"0cf0d68235d4558308f065ccf65ae6f8aec965bf390caa784ca5d75d5b16fc89","contentType":"text/html; charset=utf-8","url":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/index.html"},{"path":"src/bouncing-balls.js","size":12785,"sha256":"8397b7d56a2655293a7e44d923c8090fde60b18ec70cb3e9f6cdf2de53c10597","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/bouncing-balls/1.1.0/src/bouncing-balls.js"}],"warnings":[]}