{"id":"pendulum-wave","version":"1.0.0","title":"Pendulum Wave","summary":"A row of pendulums released together that drift through waves, snakes and chaos and line up again, as a perfect loop.","kind":"object","target":"three","collections":["satisfying","hypnotic-loops"],"tags":["pendulum","pendulum wave","satisfying","physics","wave","desk toy","kinetic sculpture","loop","chrome","3d"],"loopable":true,"motion":"procedural","durationSeconds":12,"status":"published","media":{"poster":"https://aipixelwizard.com/media/pendulum-wave/1.0.0/poster.webp","preview":"https://aipixelwizard.com/media/pendulum-wave/1.0.0/preview.mp4","sample":"https://aipixelwizard.com/media/pendulum-wave/1.0.0/sample.mp4","sampleBytes":5152505,"sampleNote":"Pre-rendered with the default settings. Customised settings must be exported locally (npm run render)."},"urls":{"page":"https://aipixelwizard.com/visuals/pendulum-wave","manifest":"https://aipixelwizard.com/api/v1/visuals/pendulum-wave/versions/1.0.0","agentMd":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/agent.md","files":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/","demo":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/index.html"},"description":"The classic pendulum-wave desk toy: a row of balls on V-shaped strings under a bar. Each one swings slightly faster than its neighbour, so the row ripples into travelling waves, a snake, splits into two and three groups, looks like chaos, and then falls back into one straight line exactly as the loop ends. Choose chrome, brass or two-colour gradient balls, how many there are, how fast the pattern plays and the camera view.","role":"Hero object: a satisfying pendulum-wave apparatus that loops exactly.","useCases":["A satisfying loop for a reel or an ad break","Background motion for science or education content","A calm, hypnotic idle screen"],"synonyms":["pendulum waves","swinging balls","harmonic pendulums","wave machine","newton cradle","satisfying loop","kinetic art","rhythm pendulums"],"digest":"baf4c5022b18a4b44ce8d0a0f1f3c5b6ecc118cc8baadbe9d28f5637383aa907","implementation":{"target":"three","runtime":"browser","language":"javascript","module":"esm","entry":"src/pendulum-wave.js","exports":[{"name":"createPendulumWave","signature":"createPendulumWave({ count?, swings?, spacing?, longest?, realism?, barHeight?, bobRadius?, vSpread?, bobMaterial?, bobColors?, bobFinish?, frameMaterial?, stringColor?, frame? }) => { group, lengths, update(progress, amplitude?), dispose() }","description":"The apparatus as a THREE.Group (row along X, swings toward ±Z, stands on y = 0). update(progress 0…1) poses every pendulum."},{"name":"pendulumAngles","signature":"pendulumAngles(progress, { count?, swings?, amplitude? }) => number[]","description":"Swing angles in radians; pendulum i makes swings + i whole swings per loop, so progress 0 and 1 match."},{"name":"pendulumLengths","signature":"pendulumLengths({ count?, swings?, longest?, realism? }) => number[]","description":"String lengths, longest first, graded like the real apparatus."},{"name":"gradeColors","signature":"gradeColors(count, stops) => THREE.Color[]","description":"Colours graded across the row through two or more stops."}],"reusable":["src/pendulum-wave.js"],"demo":"demo.js","harness":"index.html"},"dependencies":[{"id":"three","version":"0.185.1","scope":"source","reason":"Geometry, physical materials and shadows."},{"id":"chrome-material","version":"1.1.0","scope":"demo","reason":"Demo uses the shared studio (environment light, cove, floor) and the chrome material for the balls."},{"id":"apw-runtime","version":"1.0.0","scope":"demo","reason":"Demo harness."}],"uses":[],"usedBy":[],"params":[{"key":"balls","label":"Balls","type":"select","default":"chrome","options":[{"value":"chrome","label":"Chrome"},{"value":"brass","label":"Brass"},{"value":"gradient","label":"Two-colour gradient"}]},{"key":"colorA","label":"First ball colour","help":"Used by the two-colour gradient.","type":"color","default":"#ff6b5e"},{"key":"colorB","label":"Last ball colour","help":"Used by the two-colour gradient.","type":"color","default":"#6b5cff"},{"key":"count","label":"Pendulums","type":"integer","min":8,"max":24,"step":1,"default":15},{"key":"swings","label":"Pattern speed","help":"Swings the longest pendulum makes in one loop. More swings play the pattern faster.","type":"integer","min":6,"max":30,"step":1,"default":12},{"key":"amplitude","label":"Swing","help":"How far the balls swing, in degrees.","type":"number","min":8,"max":35,"step":1,"default":24},{"key":"view","label":"Camera","type":"select","default":"three","options":[{"value":"three","label":"Three-quarter"},{"value":"front","label":"Front"},{"value":"along","label":"Along the row"}]},{"key":"background","label":"Background","type":"color","default":"#0b0b10"}],"presets":[{"id":"default","label":"Chrome on black","params":{}},{"id":"brass","label":"Brass desk toy","params":{"balls":"brass","count":12,"background":"#17110c","view":"front"}},{"id":"candy","label":"Coral to violet","params":{"balls":"gradient","count":20,"swings":14,"background":"#ece7f4","view":"along"}}],"output":{"width":1080,"height":1080,"fps":30,"durationSeconds":12,"posterSeconds":3.4,"alpha":false,"motion":"procedural","loop":{"seamless":true,"cycleSeconds":12,"method":"Pendulum i makes a whole number of swings per loop (Pattern speed + i), all released together, so at the end every ball is back in line where it started; the camera drift is one whole sine cycle."},"seed":null},"composition":{"inputs":"Loop progress 0…1 (frame / total frames) and the swing amplitude in radians. Needs scene.environment for metal balls.","outputs":"A THREE.Group: the A-frame stand, the bar and the pendulums.","coordinates":"Row along X centred on the origin (width ≈ count × 0.26), bar at y = 2.5, balls swing toward ±Z, the stand's feet at y = 0 and z = ±0.62.","lighting":"Image-based (scene.environment) for chrome and brass; a shadow-casting directional light gives the swinging floor shadows.","timing":"update(progress) is a pure function of progress; frames render in any order, and any clip length loops.","compatibleWith":["chrome-material","orbit-camera","kinetic-title","gradient-background"],"notes":["Keep Pattern speed whole: the loop is exact only for whole numbers of swings.","At short loops the swings are faster than real pendulums of these lengths; lengths still grade like the real apparatus."]},"reuse":["Satisfying loop: render the chrome version on black for a reel; the line-up at the end makes a natural cut point.","Brand colours: set the two gradient colours to a brand palette for an idle or loading loop.","Science explainer: pair it with Kinetic Title to label the moments when the row splits into two and three groups."],"integration":{"newProject":"Create a Three.js scene with an environment map (createStudioEnvironment from Chrome Material), add createPendulumWave().group and call update(frame / totalFrames) every frame.","existingProject":"Copy src/pendulum-wave.js. Add wave.group to your scene, scale it to fit, and call wave.update(progress, amplitude) from your frame loop with progress running 0…1 over your loop."},"performance":{"notes":"About 90 meshes for 15 pendulums; nothing is computed per frame except one rotation per pendulum.","limits":"Shadow maps are the main cost; drop them or lower the map size on phones."},"limitations":["Not a physics simulation: the swings are exact closed-form motion, which is what makes the loop perfect, but the balls never collide or lose energy.","Each pendulum swings in one plane only, as on the real bifilar apparatus."],"license":{"spdx":"MIT","notice":"Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.","thirdParty":[{"name":"three.js","version":"0.185.1","license":"MIT","url":"https://github.com/mrdoob/three.js"}]},"rights":{"approvedForPublicRelease":true},"provenance":"Adapted from the owner's threejs-sketches/audio-musical/rhythm-pendulums (MIT), rebuilt for AI Pixel Wizard in October 2026 as the real bifilar pendulum-wave apparatus with exact whole-swing timing, the shared studio and the frame/time contract.","changelog":[{"version":"1.0.0","date":"2026-10-05","notes":"Initial release."}],"verification":{"testedOn":"2026-10-05","inputs":{"pendulum-wave@1.0.0":"baf4c5022b18a4b44ce8d0a0f1f3c5b6ecc118cc8baadbe9d28f5637383aa907","three@0.185.1":"664ca387e5fab46ff2daaa6895e1d015426850891af5ea5082cbbcc3985b068c","chrome-material@1.1.0":"ecb41776bfc1ee88997604f916a5ffe833f971d7edcc97c855697a5792d6f1dd","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":"WebGL 2 via SwiftShader (software)","width":1080,"height":1080,"fps":30,"frames":360,"renderSeconds":155.6,"loopCheck":{"checked":true,"seamless":true,"maxDifference":0,"note":"Frame 0 vs end state (progress 1): mean |Δ| 0.0000, max |Δ| 0 of 255."},"determinism":{"checked":true,"identical":true},"errors":[],"media":{"poster":"/media/pendulum-wave/1.0.0/poster.webp","preview":"/media/pendulum-wave/1.0.0/preview.mp4","sample":"/media/pendulum-wave/1.0.0/sample.mp4","sampleBytes":5152505}},"files":[{"path":"LICENSE","size":1072,"sha256":"8a5f3a96f07e11522ec4bdd0a583356f99bad1245920f9709cee09b995a45153","contentType":"text/plain; charset=utf-8","url":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/LICENSE"},{"path":"asset.json","size":9310,"sha256":"6f4f4319c7e0192f8fc8d8682ed955a97484932403cb4034739e2dfac3fa3f84","contentType":"application/json; charset=utf-8","url":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/asset.json"},{"path":"demo.js","size":4870,"sha256":"3885063a559d3f2dc1f2f6a5db1ccc39e38c0bc3d1b7925a12f39a44a53cbe8a","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/demo.js"},{"path":"index.html","size":527,"sha256":"2203a8271c79fa783ffe8569bdd70cf7f4b8a1275231ca5c901221049724f488","contentType":"text/html; charset=utf-8","url":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/index.html"},{"path":"src/pendulum-wave.js","size":7726,"sha256":"3bb1de1ff16c2fc1d5a41a2f6eedec7cb49a19f5cfce85ac353fdfbf53d8416d","contentType":"text/javascript; charset=utf-8","url":"https://aipixelwizard.com/registry/pendulum-wave/1.0.0/src/pendulum-wave.js"}],"warnings":[]}