phoria2 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +9 -6
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +436 -436
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1223 -862
  7. package/dist/core.js.map +1 -1
  8. package/dist/debug.js +297 -0
  9. package/dist/debug.js.map +1 -0
  10. package/dist/edit.js +40 -40
  11. package/dist/edit.js.map +1 -1
  12. package/dist/explain.js +32 -13
  13. package/dist/explain.js.map +1 -1
  14. package/dist/gltf.js +128 -128
  15. package/dist/gltf.js.map +1 -1
  16. package/dist/keyboard.js +19 -19
  17. package/dist/keyboard.js.map +1 -1
  18. package/dist/motion.js +35 -35
  19. package/dist/motion.js.map +1 -1
  20. package/dist/particles.js +36 -36
  21. package/dist/particles.js.map +1 -1
  22. package/dist/passes.js +116 -104
  23. package/dist/passes.js.map +1 -1
  24. package/dist/phoria2.js +1 -1
  25. package/dist/picking.js +88 -71
  26. package/dist/picking.js.map +1 -1
  27. package/dist/snapshot.js +1 -1
  28. package/dist/tooltip.js +1 -1
  29. package/dist/types/View.d.ts +16 -0
  30. package/dist/types/debug.d.ts +74 -0
  31. package/dist/types/explain.d.ts +4 -0
  32. package/dist/types/frame.d.ts +5 -0
  33. package/dist/types/material/Material.d.ts +52 -0
  34. package/dist/types/watch.d.ts +8 -1
  35. package/dist/types/webgl/hooks.d.ts +48 -0
  36. package/dist/types/webgl/passes.d.ts +4 -0
  37. package/dist/types/webgl/programs.d.ts +15 -5
  38. package/dist/types/webgl/shaders.d.ts +36 -5
  39. package/llms-full.txt +227 -0
  40. package/llms.txt +1 -1
  41. package/package.json +1 -1
  42. package/schema/scene.schema.json +27 -0
  43. package/src/View.js +105 -7
  44. package/src/debug.js +283 -0
  45. package/src/explain.js +19 -0
  46. package/src/frame.js +7 -2
  47. package/src/material/Material.js +121 -2
  48. package/src/scene/drawables.js +21 -4
  49. package/src/watch.js +18 -2
  50. package/src/webgl/WebGLRenderer.js +19 -2
  51. package/src/webgl/hooks.js +121 -0
  52. package/src/webgl/passes.js +17 -2
  53. package/src/webgl/picking.js +23 -2
  54. package/src/webgl/programs.js +20 -9
  55. package/src/webgl/resources.js +1 -1
  56. package/src/webgl/shaders.js +443 -35
@@ -1 +1 @@
1
- {"version":3,"file":"motion.js","names":["checkOneOf","checkNumber","transformMat4","checkOptions","EASES","multiply","fail","describe","setAxisAngle","Curve","Vec3","OPTIONS","HINTS","STEP_OPTIONS","STEP_HINTS","Timeline","options","duration","run","ease","#steps","#apply","last","#time","#current","#playing","time","i","step","s","target","fn","#onStep","dt","steps","now","current","raw","FORWARD","FACE_OPTIONS","FACE_HINTS","FOLLOW_OPTIONS","FOLLOW_HINTS","TO_MINUS_Z","face","forward","ahead","node","direction","up","parent","m","turn","q","follow","path","t","reveal","g","lines","pieces"],"sources":["../src/motion/Timeline.js","../src/motion/follow.js"],"sourcesContent":["// A timeline of named steps played one after another - for stories, explainers and guided tours.\n// Each step is a function given its own progress (0 before it starts, 0-1 while it plays, 1 once\n// it's done), every frame. Steps set the scene from their progress rather than nudging it, so\n// seeking, stepping back and looping always leave the scene right.\n\nimport { checkNumber, checkOptions, describe, fail } from '../validate.js'\nimport { EASES } from './ease.js'\n\n/** @import { EaseName } from './ease.js' */\n\n/**\n * @typedef {object} StepOptions\n * @property {string} [label] what the step shows, for captions - timeline.label is the current one\n * @property {EaseName | ((t: number) => number)} [ease] how progress runs through the step, default 'linear'\n */\n\n/**\n * @typedef {object} TimelineOptions\n * @property {boolean} [loop] start again at the end, default false\n * @property {number} [speed] 1 is real time, default 1\n */\n\n/** @typedef {{ start: number, duration: number, run: (t: number) => void, label: string, ease: (t: number) => number }} Step */\n\n/**\n * Steps played in order, each a function of its progress.\n *\n * const story = new Timeline({ loop: true })\n * story.add(3, (t) => follow(rocket, ascent, t), { label: 'Launch' })\n * story.add(2, (t) => { capsule.visible = t > 0 }, { label: 'Separation' })\n * view.start((dt) => story.update(dt))\n */\nconst OPTIONS = ['loop', 'speed']\nconst HINTS = { repeat: \"use 'loop: true'\", autoplay: 'a timeline plays from the start - call pause() to hold it' }\n/** What add() takes, and hints for names people reach for instead. */\nexport const STEP_OPTIONS = ['label', 'ease']\nexport const STEP_HINTS = { name: \"use 'label'\", easing: \"use 'ease'\" }\n\nexport class Timeline {\n /** The options it takes, and hints for names people reach for instead. */\n static OPTIONS = OPTIONS\n static HINTS = HINTS\n /** @type {Step[]} */ #steps = []\n #time = 0\n #playing = true\n #current = -1\n /** @type {((index: number, label: string) => void)[]} */ #onStep = []\n loop = false\n speed = 1\n\n /** @param {TimelineOptions} [options] */\n constructor(options = {}) {\n checkOptions('Timeline', options, OPTIONS, HINTS)\n if (options.loop !== undefined) this.loop = !!options.loop\n if (options.speed !== undefined) this.speed = checkNumber('Timeline', 'speed', options.speed)\n }\n\n /**\n * Add a step after the last one.\n * @param {number} duration seconds; 0 for an instant change\n * @param {(t: number) => void} run called every frame with this step's progress, 0-1\n * @param {StepOptions} [options]\n * @returns {this}\n */\n add(duration, run, options = {}) {\n checkNumber('Timeline.add()', 'duration', duration)\n if (duration < 0) fail('Timeline.add()', `'duration' is seconds, 0 or more, got ${duration}`)\n if (typeof run !== 'function') fail('Timeline.add()', `takes (duration, (t) => { ... }), got ${describe(run)} for the step`)\n checkOptions('Timeline.add()', options, STEP_OPTIONS, STEP_HINTS)\n const ease = typeof options.ease === 'function' ? options.ease : EASES[options.ease ?? 'linear']\n if (!ease) fail('Timeline.add()', `ease '${options.ease}' - expected ${Object.keys(EASES).map((e) => `'${e}'`).join(', ')}, or a function`)\n this.#steps.push({ start: this.duration, duration, run, label: options.label ?? '', ease })\n this.#apply()\n return this\n }\n\n /** Total length in seconds. */\n get duration() {\n const last = this.#steps[this.#steps.length - 1]\n return last ? last.start + last.duration : 0\n }\n\n /** Seconds since the start. */\n get time() {\n return this.#time\n }\n\n /** The step playing now, by index - -1 before any are added. */\n get step() {\n return this.#current\n }\n\n /** The label of the step playing now. */\n get label() {\n return this.#steps[this.#current]?.label ?? ''\n }\n\n /** The steps, in order, with their start times and labels. */\n get steps() {\n return this.#steps.map(({ start, duration, label }) => ({ start, duration, label }))\n }\n\n get playing() {\n return this.#playing\n }\n\n /** @returns {this} */\n play() {\n if (this.#time >= this.duration && !this.loop) this.#time = 0\n this.#playing = true\n return this\n }\n\n /** @returns {this} */\n pause() {\n this.#playing = false\n return this\n }\n\n /**\n * Jump to a time in seconds, setting the scene to match.\n * @param {number} time @returns {this}\n */\n seek(time) {\n this.#time = Math.min(this.duration, Math.max(0, checkNumber('Timeline.seek()', 'time', time)))\n this.#apply()\n return this\n }\n\n /**\n * Jump to the start of a step, by index or label.\n * @param {number | string} step @returns {this}\n */\n seekStep(step) {\n const i = typeof step === 'string' ? this.#steps.findIndex((s) => s.label === step) : step\n const target = this.#steps[i]\n if (!target) fail('Timeline.seekStep()', `no step ${describe(step)} - there are ${this.#steps.length}${typeof step === 'string' ? `: ${this.#steps.map((s) => `'${s.label}'`).join(', ')}` : ''}`)\n return this.seek(target.start)\n }\n\n /**\n * Call with the step playing now, and again whenever it changes: (index, label). Called straight\n * away too, so a caption set up after the steps starts right.\n * @param {(index: number, label: string) => void} fn @returns {this}\n */\n onStep(fn) {\n if (typeof fn !== 'function') fail('Timeline.onStep()', `takes (index, label) => { ... }, got ${describe(fn)}`)\n this.#onStep.push(fn)\n if (this.#current >= 0) fn(this.#current, this.label)\n return this\n }\n\n /**\n * Move time on by dt seconds (while playing) and set the scene - call every frame.\n * @param {number} dt @returns {this}\n */\n update(dt) {\n if (this.#playing && this.#steps.length) {\n let t = this.#time + dt * this.speed\n if (t >= this.duration) {\n if (this.loop && this.duration > 0) t %= this.duration\n else {\n t = this.duration\n this.#playing = false\n }\n }\n this.#time = t\n }\n this.#apply()\n return this\n }\n\n // set every step from the time: later steps back to their start, latest first, then each step\n // up to now in order - so whatever a step controls ends up as the latest one to touch it says\n #apply() {\n const steps = this.#steps, now = this.#time\n let current = steps.length - 1\n for (let i = 0; i < steps.length; i++) {\n if (now < steps[i].start + steps[i].duration || (steps[i].duration === 0 && now <= steps[i].start)) {\n current = i\n break\n }\n }\n for (let i = steps.length - 1; i > current; i--) steps[i].run(0)\n for (let i = 0; i <= current; i++) {\n const s = steps[i]\n const raw = s.duration > 0 ? Math.min(1, Math.max(0, (now - s.start) / s.duration)) : (now >= s.start ? 1 : 0)\n s.run(s.ease(raw))\n }\n if (current !== this.#current) {\n this.#current = current\n for (const fn of this.#onStep) fn(current, steps[current]?.label ?? '')\n }\n }\n}\n","// Moving things along curves, and drawing curves in as they're travelled.\n\nimport { transformMat4 } from '../math/vec3.js'\nimport { multiply, setAxisAngle } from '../math/quat.js'\nimport { Curve } from '../geometry/Curve.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkOneOf, checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n/** @import { Lines } from '../scene/drawables.js' */\n\n/** @import { Vec3Input } from '../types.js' */\n\n/**\n * Which of a model's own axes is its nose. Cameras, lights and lookAt use -Z; a model built\n * upright - a rocket along +Y - says '+y'.\n * @typedef {'-z' | '+z' | '-y' | '+y' | '-x' | '+x'} Forward\n */\n\n/**\n * @typedef {object} FaceOptions\n * @property {Forward} [forward] the model's nose axis, default '-z'\n * @property {Vec3Input} [up] which way is up as it turns, in the parent's space, default [0, 1, 0].\n * Heading straight along it leaves the roll undefined, so a craft that turns through every\n * heading in the x-y plane (seen face on) needs one out of the plane: [0, 0, 1]\n */\n\n/**\n * @typedef {object} FollowOptions\n * @property {boolean} [orient] turn the node to face along the curve, default true\n * @property {Forward} [forward] the model's nose axis, default '-z'\n * @property {Vec3Input} [up] which way is up as it turns, default [0, 1, 0] - see face()\n */\n\nconst FORWARD = ['-z', '+z', '-y', '+y', '-x', '+x']\n/** What face() and follow() take, and hints for names people reach for instead. */\nexport const FACE_OPTIONS = ['forward', 'up']\nexport const FACE_HINTS = { nose: \"use 'forward' - which of the model's axes is its nose\" }\nexport const FOLLOW_OPTIONS = ['orient', 'forward', 'up']\nexport const FOLLOW_HINTS = { rotate: \"use 'orient'\", lookAhead: 'the node faces along the curve - orient: false leaves it as it is', nose: \"use 'forward' - which of the model's axes is its nose\" }\n// the turn that takes each nose axis onto -Z, as [axis, angle]\nconst TO_MINUS_Z = {\n '-z': null,\n '+z': [[0, 1, 0], Math.PI],\n '-y': [[1, 0, 0], Math.PI / 2],\n '+y': [[1, 0, 0], -Math.PI / 2],\n '-x': [[0, 1, 0], -Math.PI / 2],\n '+x': [[0, 1, 0], Math.PI / 2],\n}\n\n/**\n * Turn a node so its nose points along a direction, in its parent's space - for a model built\n * along another axis, say which with forward.\n *\n * face(lander, [0, 1, 0], { forward: '+y' }) // stand it upright\n * @param {Node} node\n * @param {Vec3Input} direction\n * @param {FaceOptions} [options]\n * @returns {Node}\n */\nexport function face(node, direction, options = {}) {\n checkOptions('face()', options, FACE_OPTIONS, FACE_HINTS)\n const forward = /** @type {Forward} */ (options.forward === undefined ? '-z' : checkOneOf('face()', 'forward', options.forward, FORWARD))\n const ahead = node.position.clone().add(Vec3.fromInput(direction, 'face()'))\n let up = options.up === undefined ? undefined : Vec3.fromInput(options.up, 'face() up')\n const parent = node.parent\n if (parent) {\n const m = parent.updateWorld().worldMatrix\n if (up) up = transformMat4(up, up.add(node.position), m).sub(transformMat4(new Vec3(), node.position, m))\n transformMat4(ahead, ahead, m)\n }\n node.lookAt(ahead, up)\n const turn = TO_MINUS_Z[forward]\n if (turn) {\n const q = setAxisAngle(new Float32Array(4), turn[0], turn[1])\n node.quaternion.copy(multiply(new Float32Array(4), node.quaternion, q))\n }\n return node\n}\n\n/**\n * Put a node t of the way along a curve (0 start, 1 end, by distance), facing the way it runs.\n * The curve is in the node's parent's space - the same space as its position. A model built along\n * another axis than -Z says which is its nose with forward.\n *\n * story.add(4, (t) => follow(lander, transfer, t))\n * follow(rocket, ascent, t, { forward: '+y' }) // a rocket modelled upright\n * @param {Node} node\n * @param {Curve} path\n * @param {number} t\n * @param {FollowOptions} [options]\n * @returns {Node}\n */\nexport function follow(node, path, t, options = {}) {\n if (!(path instanceof Curve)) fail('follow()', `takes (node, curve, t) - make the curve with curve([...points]) or ellipse({...}), got ${describe(path)}`)\n checkOptions('follow()', options, FOLLOW_OPTIONS, FOLLOW_HINTS)\n node.position.copy(path.at(t))\n if (options.orient !== false) face(node, path.tangentAt(t), { forward: options.forward, up: options.up })\n return node\n}\n\n/**\n * Draw only the first part of a line - t of it (0 nothing, 1 all) - so a path draws in as it's\n * travelled. For Lines made from curve.line(): its pieces run in order from the start.\n *\n * story.add(4, (t) => { follow(lander, transfer, t); reveal(transferLine, t) })\n * @param {Lines} lines\n * @param {number} t\n * @returns {Lines}\n */\nexport function reveal(lines, t) {\n const g = lines?.geometry\n if (!g || /** @type {any} */ (lines).drawMode !== 'lines') fail('reveal()', `takes a Lines node, got ${describe(lines)}`)\n const pieces = g.vertexCount / 2\n g.drawCount = Math.round(Math.min(1, Math.max(0, t)) * pieces) * 2\n lines.visible = g.drawCount > 0\n return lines\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"motion.js","names":["checkNumber","fail","transformMat4","checkOptions","describe","Vec3","EASES","multiply","checkOneOf","Curve","setAxisAngle","OPTIONS","HINTS","STEP_OPTIONS","STEP_HINTS","Timeline","options","duration","run","ease","#steps","#apply","last","#time","#current","#playing","time","i","step","s","target","fn","#onStep","dt","steps","now","current","raw","FORWARD","FACE_OPTIONS","FACE_HINTS","FOLLOW_OPTIONS","FOLLOW_HINTS","TO_MINUS_Z","face","forward","ahead","node","direction","up","parent","m","turn","q","follow","path","t","reveal","g","lines","pieces"],"sources":["../src/motion/Timeline.js","../src/motion/follow.js"],"sourcesContent":["// A timeline of named steps played one after another - for stories, explainers and guided tours.\n// Each step is a function given its own progress (0 before it starts, 0-1 while it plays, 1 once\n// it's done), every frame. Steps set the scene from their progress rather than nudging it, so\n// seeking, stepping back and looping always leave the scene right.\n\nimport { checkNumber, checkOptions, describe, fail } from '../validate.js'\nimport { EASES } from './ease.js'\n\n/** @import { EaseName } from './ease.js' */\n\n/**\n * @typedef {object} StepOptions\n * @property {string} [label] what the step shows, for captions - timeline.label is the current one\n * @property {EaseName | ((t: number) => number)} [ease] how progress runs through the step, default 'linear'\n */\n\n/**\n * @typedef {object} TimelineOptions\n * @property {boolean} [loop] start again at the end, default false\n * @property {number} [speed] 1 is real time, default 1\n */\n\n/** @typedef {{ start: number, duration: number, run: (t: number) => void, label: string, ease: (t: number) => number }} Step */\n\n/**\n * Steps played in order, each a function of its progress.\n *\n * const story = new Timeline({ loop: true })\n * story.add(3, (t) => follow(rocket, ascent, t), { label: 'Launch' })\n * story.add(2, (t) => { capsule.visible = t > 0 }, { label: 'Separation' })\n * view.start((dt) => story.update(dt))\n */\nconst OPTIONS = ['loop', 'speed']\nconst HINTS = { repeat: \"use 'loop: true'\", autoplay: 'a timeline plays from the start - call pause() to hold it' }\n/** What add() takes, and hints for names people reach for instead. */\nexport const STEP_OPTIONS = ['label', 'ease']\nexport const STEP_HINTS = { name: \"use 'label'\", easing: \"use 'ease'\" }\n\nexport class Timeline {\n /** The options it takes, and hints for names people reach for instead. */\n static OPTIONS = OPTIONS\n static HINTS = HINTS\n /** @type {Step[]} */ #steps = []\n #time = 0\n #playing = true\n #current = -1\n /** @type {((index: number, label: string) => void)[]} */ #onStep = []\n loop = false\n speed = 1\n\n /** @param {TimelineOptions} [options] */\n constructor(options = {}) {\n checkOptions('Timeline', options, OPTIONS, HINTS)\n if (options.loop !== undefined) this.loop = !!options.loop\n if (options.speed !== undefined) this.speed = checkNumber('Timeline', 'speed', options.speed)\n }\n\n /**\n * Add a step after the last one.\n * @param {number} duration seconds; 0 for an instant change\n * @param {(t: number) => void} run called every frame with this step's progress, 0-1\n * @param {StepOptions} [options]\n * @returns {this}\n */\n add(duration, run, options = {}) {\n checkNumber('Timeline.add()', 'duration', duration)\n if (duration < 0) fail('Timeline.add()', `'duration' is seconds, 0 or more, got ${duration}`)\n if (typeof run !== 'function') fail('Timeline.add()', `takes (duration, (t) => { ... }), got ${describe(run)} for the step`)\n checkOptions('Timeline.add()', options, STEP_OPTIONS, STEP_HINTS)\n const ease = typeof options.ease === 'function' ? options.ease : EASES[options.ease ?? 'linear']\n if (!ease) fail('Timeline.add()', `ease '${options.ease}' - expected ${Object.keys(EASES).map((e) => `'${e}'`).join(', ')}, or a function`)\n this.#steps.push({ start: this.duration, duration, run, label: options.label ?? '', ease })\n this.#apply()\n return this\n }\n\n /** Total length in seconds. */\n get duration() {\n const last = this.#steps[this.#steps.length - 1]\n return last ? last.start + last.duration : 0\n }\n\n /** Seconds since the start. */\n get time() {\n return this.#time\n }\n\n /** The step playing now, by index - -1 before any are added. */\n get step() {\n return this.#current\n }\n\n /** The label of the step playing now. */\n get label() {\n return this.#steps[this.#current]?.label ?? ''\n }\n\n /** The steps, in order, with their start times and labels. */\n get steps() {\n return this.#steps.map(({ start, duration, label }) => ({ start, duration, label }))\n }\n\n get playing() {\n return this.#playing\n }\n\n /** @returns {this} */\n play() {\n if (this.#time >= this.duration && !this.loop) this.#time = 0\n this.#playing = true\n return this\n }\n\n /** @returns {this} */\n pause() {\n this.#playing = false\n return this\n }\n\n /**\n * Jump to a time in seconds, setting the scene to match.\n * @param {number} time @returns {this}\n */\n seek(time) {\n this.#time = Math.min(this.duration, Math.max(0, checkNumber('Timeline.seek()', 'time', time)))\n this.#apply()\n return this\n }\n\n /**\n * Jump to the start of a step, by index or label.\n * @param {number | string} step @returns {this}\n */\n seekStep(step) {\n const i = typeof step === 'string' ? this.#steps.findIndex((s) => s.label === step) : step\n const target = this.#steps[i]\n if (!target) fail('Timeline.seekStep()', `no step ${describe(step)} - there are ${this.#steps.length}${typeof step === 'string' ? `: ${this.#steps.map((s) => `'${s.label}'`).join(', ')}` : ''}`)\n return this.seek(target.start)\n }\n\n /**\n * Call with the step playing now, and again whenever it changes: (index, label). Called straight\n * away too, so a caption set up after the steps starts right.\n * @param {(index: number, label: string) => void} fn @returns {this}\n */\n onStep(fn) {\n if (typeof fn !== 'function') fail('Timeline.onStep()', `takes (index, label) => { ... }, got ${describe(fn)}`)\n this.#onStep.push(fn)\n if (this.#current >= 0) fn(this.#current, this.label)\n return this\n }\n\n /**\n * Move time on by dt seconds (while playing) and set the scene - call every frame.\n * @param {number} dt @returns {this}\n */\n update(dt) {\n if (this.#playing && this.#steps.length) {\n let t = this.#time + dt * this.speed\n if (t >= this.duration) {\n if (this.loop && this.duration > 0) t %= this.duration\n else {\n t = this.duration\n this.#playing = false\n }\n }\n this.#time = t\n }\n this.#apply()\n return this\n }\n\n // set every step from the time: later steps back to their start, latest first, then each step\n // up to now in order - so whatever a step controls ends up as the latest one to touch it says\n #apply() {\n const steps = this.#steps, now = this.#time\n let current = steps.length - 1\n for (let i = 0; i < steps.length; i++) {\n if (now < steps[i].start + steps[i].duration || (steps[i].duration === 0 && now <= steps[i].start)) {\n current = i\n break\n }\n }\n for (let i = steps.length - 1; i > current; i--) steps[i].run(0)\n for (let i = 0; i <= current; i++) {\n const s = steps[i]\n const raw = s.duration > 0 ? Math.min(1, Math.max(0, (now - s.start) / s.duration)) : (now >= s.start ? 1 : 0)\n s.run(s.ease(raw))\n }\n if (current !== this.#current) {\n this.#current = current\n for (const fn of this.#onStep) fn(current, steps[current]?.label ?? '')\n }\n }\n}\n","// Moving things along curves, and drawing curves in as they're travelled.\n\nimport { transformMat4 } from '../math/vec3.js'\nimport { multiply, setAxisAngle } from '../math/quat.js'\nimport { Curve } from '../geometry/Curve.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkOneOf, checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n/** @import { Lines } from '../scene/drawables.js' */\n\n/** @import { Vec3Input } from '../types.js' */\n\n/**\n * Which of a model's own axes is its nose. Cameras, lights and lookAt use -Z; a model built\n * upright - a rocket along +Y - says '+y'.\n * @typedef {'-z' | '+z' | '-y' | '+y' | '-x' | '+x'} Forward\n */\n\n/**\n * @typedef {object} FaceOptions\n * @property {Forward} [forward] the model's nose axis, default '-z'\n * @property {Vec3Input} [up] which way is up as it turns, in the parent's space, default [0, 1, 0].\n * Heading straight along it leaves the roll undefined, so a craft that turns through every\n * heading in the x-y plane (seen face on) needs one out of the plane: [0, 0, 1]\n */\n\n/**\n * @typedef {object} FollowOptions\n * @property {boolean} [orient] turn the node to face along the curve, default true\n * @property {Forward} [forward] the model's nose axis, default '-z'\n * @property {Vec3Input} [up] which way is up as it turns, default [0, 1, 0] - see face()\n */\n\nconst FORWARD = ['-z', '+z', '-y', '+y', '-x', '+x']\n/** What face() and follow() take, and hints for names people reach for instead. */\nexport const FACE_OPTIONS = ['forward', 'up']\nexport const FACE_HINTS = { nose: \"use 'forward' - which of the model's axes is its nose\" }\nexport const FOLLOW_OPTIONS = ['orient', 'forward', 'up']\nexport const FOLLOW_HINTS = { rotate: \"use 'orient'\", lookAhead: 'the node faces along the curve - orient: false leaves it as it is', nose: \"use 'forward' - which of the model's axes is its nose\" }\n// the turn that takes each nose axis onto -Z, as [axis, angle]\nconst TO_MINUS_Z = {\n '-z': null,\n '+z': [[0, 1, 0], Math.PI],\n '-y': [[1, 0, 0], Math.PI / 2],\n '+y': [[1, 0, 0], -Math.PI / 2],\n '-x': [[0, 1, 0], -Math.PI / 2],\n '+x': [[0, 1, 0], Math.PI / 2],\n}\n\n/**\n * Turn a node so its nose points along a direction, in its parent's space - for a model built\n * along another axis, say which with forward.\n *\n * face(lander, [0, 1, 0], { forward: '+y' }) // stand it upright\n * @param {Node} node\n * @param {Vec3Input} direction\n * @param {FaceOptions} [options]\n * @returns {Node}\n */\nexport function face(node, direction, options = {}) {\n checkOptions('face()', options, FACE_OPTIONS, FACE_HINTS)\n const forward = /** @type {Forward} */ (options.forward === undefined ? '-z' : checkOneOf('face()', 'forward', options.forward, FORWARD))\n const ahead = node.position.clone().add(Vec3.fromInput(direction, 'face()'))\n let up = options.up === undefined ? undefined : Vec3.fromInput(options.up, 'face() up')\n const parent = node.parent\n if (parent) {\n const m = parent.updateWorld().worldMatrix\n if (up) up = transformMat4(up, up.add(node.position), m).sub(transformMat4(new Vec3(), node.position, m))\n transformMat4(ahead, ahead, m)\n }\n node.lookAt(ahead, up)\n const turn = TO_MINUS_Z[forward]\n if (turn) {\n const q = setAxisAngle(new Float32Array(4), turn[0], turn[1])\n node.quaternion.copy(multiply(new Float32Array(4), node.quaternion, q))\n }\n return node\n}\n\n/**\n * Put a node t of the way along a curve (0 start, 1 end, by distance), facing the way it runs.\n * The curve is in the node's parent's space - the same space as its position. A model built along\n * another axis than -Z says which is its nose with forward.\n *\n * story.add(4, (t) => follow(lander, transfer, t))\n * follow(rocket, ascent, t, { forward: '+y' }) // a rocket modelled upright\n * @param {Node} node\n * @param {Curve} path\n * @param {number} t\n * @param {FollowOptions} [options]\n * @returns {Node}\n */\nexport function follow(node, path, t, options = {}) {\n if (!(path instanceof Curve)) fail('follow()', `takes (node, curve, t) - make the curve with curve([...points]) or ellipse({...}), got ${describe(path)}`)\n checkOptions('follow()', options, FOLLOW_OPTIONS, FOLLOW_HINTS)\n node.position.copy(path.at(t))\n if (options.orient !== false) face(node, path.tangentAt(t), { forward: options.forward, up: options.up })\n return node\n}\n\n/**\n * Draw only the first part of a line - t of it (0 nothing, 1 all) - so a path draws in as it's\n * travelled. For Lines made from curve.line(): its pieces run in order from the start.\n *\n * story.add(4, (t) => { follow(lander, transfer, t); reveal(transferLine, t) })\n * @param {Lines} lines\n * @param {number} t\n * @returns {Lines}\n */\nexport function reveal(lines, t) {\n const g = lines?.geometry\n if (!g || /** @type {any} */ (lines).drawMode !== 'lines') fail('reveal()', `takes a Lines node, got ${describe(lines)}`)\n const pieces = g.vertexCount / 2\n g.drawCount = Math.round(Math.min(1, Math.max(0, t)) * pieces) * 2\n lines.visible = g.drawCount > 0\n return lines\n}\n\n"],"mappings":"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"}
package/dist/particles.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Et as e, It as t, Ot as n, at as r, jt as i, kt as a, lt as o, ot as s, tt as c, vt as l, wt as u } from "./core.js";
1
+ import { At as e, Bt as t, Ft as n, Mt as r, Nt as i, Ot as a, St as o, at as s, lt as c, pt as l, ut as u } from "./core.js";
2
2
  // src/particles/Emitter.js
3
3
  var d = [
4
4
  "origin",
@@ -18,7 +18,7 @@ var d = [
18
18
  "opacity",
19
19
  "spin"
20
20
  ], p = [
21
- ...c.OPTIONS,
21
+ ...s.OPTIONS,
22
22
  ...d,
23
23
  "max",
24
24
  ...f
@@ -32,23 +32,23 @@ var d = [
32
32
  vertexColors: !0,
33
33
  blending: "additive",
34
34
  depthWrite: !1
35
- }, g = /* @__PURE__ */ new Float32Array(16), _ = class extends c {
35
+ }, g = /* @__PURE__ */ new Float32Array(16), _ = class extends s {
36
36
  static type = "emitter";
37
37
  static get OPTIONS() {
38
38
  return p;
39
39
  }
40
40
  static get HINTS() {
41
41
  return {
42
- ...c.HINTS,
42
+ ...s.HINTS,
43
43
  ...m
44
44
  };
45
45
  }
46
46
  rate = 100;
47
- gravity = new u();
47
+ gravity = new a();
48
48
  drag = 0;
49
- origin = new u();
49
+ origin = new a();
50
50
  originSpread = 0;
51
- velocity = new u(0, 1, 0);
51
+ velocity = new a(0, 1, 0);
52
52
  velocitySpread = 0;
53
53
  lifetime = [1, 1];
54
54
  follow = null;
@@ -75,33 +75,33 @@ var d = [
75
75
  #h = null;
76
76
  #g = null;
77
77
  #_ = {
78
- position: new u(),
79
- velocity: new u(),
78
+ position: new a(),
79
+ velocity: new a(),
80
80
  age: 0,
81
81
  lifetime: 0
82
82
  };
83
83
  constructor(e = {}) {
84
- e?.geometry !== void 0 && i("Emitter", "an Emitter makes its own geometry - size it with 'max'");
84
+ e?.geometry !== void 0 && n("Emitter", "an Emitter makes its own geometry - size it with 'max'");
85
85
  let t = e?.max ?? 1e3;
86
- (!Number.isInteger(t) || t < 1) && i("Emitter", `'max' must be a whole number of 1 or more, got ${a(t)}`);
87
- let n = e?.material;
88
- n === void 0 || n instanceof s || n && typeof n == "object" && !Array.isArray(n) || i("Emitter", `'material' is a Material or material options such as { size: 0.2 }, got ${a(n)}`);
89
- let r = n instanceof s ? n : {
86
+ (!Number.isInteger(t) || t < 1) && n("Emitter", `'max' must be a whole number of 1 or more, got ${i(t)}`);
87
+ let r = e?.material;
88
+ r === void 0 || r instanceof u || r && typeof r == "object" && !Array.isArray(r) || n("Emitter", `'material' is a Material or material options such as { size: 0.2 }, got ${i(r)}`);
89
+ let a = r instanceof u ? r : {
90
90
  ...h,
91
- ...n
92
- }, c = new o({
91
+ ...r
92
+ }, o = new l({
93
93
  positions: new Float32Array(t * 3),
94
94
  colors: new Float32Array(t * 3)
95
95
  });
96
- c.drawCount = 0, super({
96
+ o.drawCount = 0, super({
97
97
  ...e,
98
- geometry: c,
99
- material: r
98
+ geometry: o,
99
+ material: a
100
100
  }), this.#e = t, this.#r = new Float32Array(t * 3), this.#i = new Float32Array(t), this.#a = new Float32Array(t), this.#c = new Uint8Array(t), e.frustumCulled === void 0 && (this.frustumCulled = !1), this.#w(e), this.#C(e), this.onUpdate((e, t) => this.step(t));
101
101
  }
102
102
  set(e) {
103
103
  let t = [...d, ...f];
104
- for (let n of Object.keys(e ?? {})) t.includes(n) || i("Emitter", `set() doesn't take '${n}' - it takes ${t.join(", ")}`);
104
+ for (let r of Object.keys(e ?? {})) t.includes(r) || n("Emitter", `set() doesn't take '${r}' - it takes ${t.join(", ")}`);
105
105
  return this.#w(e), this.#C(e), this;
106
106
  }
107
107
  get max() {
@@ -141,9 +141,9 @@ var d = [
141
141
  return [...this.#p];
142
142
  }
143
143
  burst(e, t = {}) {
144
- for (let e of Object.keys(t)) d.includes(e) || i("Emitter", `burst() doesn't take '${e}' - it takes ${d.join(", ")}`);
145
- let { color: n, ...r } = t, a = this.#D();
146
- this.#C(r), n !== void 0 && (this.#l = this.#O(w(n)));
144
+ for (let e of Object.keys(t)) d.includes(e) || n("Emitter", `burst() doesn't take '${e}' - it takes ${d.join(", ")}`);
145
+ let { color: r, ...i } = t, a = this.#D();
146
+ this.#C(i), r !== void 0 && (this.#l = this.#O(w(r)));
147
147
  let o = this.origin.clone();
148
148
  for (let t = 0; t < e; t++) this.#v(o, o, 0);
149
149
  return this.#C(a), this.#l = 0, this.#x(), this;
@@ -206,7 +206,7 @@ var d = [
206
206
  velocity: this.velocity.toArray(),
207
207
  velocitySpread: typeof this.velocitySpread == "number" ? this.velocitySpread : this.velocitySpread.toArray(),
208
208
  lifetime: [...this.lifetime],
209
- color: this.#o[0].map((e) => new l().setSRGB(e[0], e[1], e[2]).toJSON()),
209
+ color: this.#o[0].map((e) => new o().setSRGB(e[0], e[1], e[2]).toJSON()),
210
210
  ...this.#E()
211
211
  };
212
212
  }
@@ -259,7 +259,7 @@ var d = [
259
259
  let n = "Emitter";
260
260
  if (t.origin !== void 0 && this.origin.copy(t.origin, n), t.originSpread !== void 0 && (this.originSpread = Math.max(0, e(n, "originSpread", t.originSpread))), t.velocity !== void 0 && this.velocity.copy(t.velocity, n), t.velocitySpread !== void 0) {
261
261
  let r = t.velocitySpread;
262
- typeof r == "number" ? this.velocitySpread = e(n, "velocitySpread", r) : this.velocitySpread instanceof u ? this.velocitySpread.copy(r, n) : this.velocitySpread = u.fromInput(r, n);
262
+ typeof r == "number" ? this.velocitySpread = e(n, "velocitySpread", r) : this.velocitySpread instanceof a ? this.velocitySpread.copy(r, n) : this.velocitySpread = a.fromInput(r, n);
263
263
  }
264
264
  t.lifetime !== void 0 && (this.lifetime = y(t.lifetime)), t.color !== void 0 && (this.#o[0] = w(t.color));
265
265
  }
@@ -302,15 +302,15 @@ var d = [
302
302
  }
303
303
  };
304
304
  function v(e) {
305
- return e === null || e instanceof r ? e : i("Emitter", `'follow' must be a node, got ${a(e)}`);
305
+ return e === null || e instanceof c ? e : n("Emitter", `'follow' must be a node, got ${i(e)}`);
306
306
  }
307
307
  function y(e) {
308
308
  let t = typeof e == "number" ? [e, e] : e;
309
- return Array.isArray(t) && t.length === 2 && t.every((e) => typeof e == "number" && e > 0) || i("Emitter", `'lifetime' must be seconds above 0, or a [min, max] range, got ${a(e)}`), [Math.min(t[0], t[1]), Math.max(t[0], t[1])];
309
+ return Array.isArray(t) && t.length === 2 && t.every((e) => typeof e == "number" && e > 0) || n("Emitter", `'lifetime' must be seconds above 0, or a [min, max] range, got ${i(e)}`), [Math.min(t[0], t[1]), Math.max(t[0], t[1])];
310
310
  }
311
- function b(e, t, n) {
312
- let r = Array.isArray(t) ? t : [t];
313
- return r.length && r.every((e) => typeof e == "number" && Number.isFinite(e) && e >= 0) || i("Emitter", `'${e}' must be a number of 0 or more, or a list of them a particle goes through, got ${a(t)}`), r.map((e) => Math.min(e, n));
311
+ function b(e, t, r) {
312
+ let a = Array.isArray(t) ? t : [t];
313
+ return a.length && a.every((e) => typeof e == "number" && Number.isFinite(e) && e >= 0) || n("Emitter", `'${e}' must be a number of 0 or more, or a list of them a particle goes through, got ${i(t)}`), a.map((e) => Math.min(e, r));
314
314
  }
315
315
  function x(e) {
316
316
  return e.length === 1 ? e[0] : [...e];
@@ -322,11 +322,11 @@ function S(e, t) {
322
322
  }
323
323
  function C(e) {
324
324
  let t = typeof e == "number" ? [-Math.abs(e), Math.abs(e)] : e;
325
- return Array.isArray(t) && t.length === 2 && t.every((e) => typeof e == "number" && Number.isFinite(e)) || i("Emitter", `'spin' must be radians per second, or a [min, max] range, got ${a(e)}`), [Math.min(t[0], t[1]), Math.max(t[0], t[1])];
325
+ return Array.isArray(t) && t.length === 2 && t.every((e) => typeof e == "number" && Number.isFinite(e)) || n("Emitter", `'spin' must be radians per second, or a [min, max] range, got ${i(e)}`), [Math.min(t[0], t[1]), Math.max(t[0], t[1])];
326
326
  }
327
327
  function w(e) {
328
328
  let t = Array.isArray(e) && !(e.length === 3 && e.every((e) => typeof e == "number")) ? e : [e];
329
- return t.length || i("Emitter", "'color' needs at least one colour"), t.map((e) => l.fromInput(e, "Emitter").toSRGB());
329
+ return t.length || n("Emitter", "'color' needs at least one colour"), t.map((e) => o.fromInput(e, "Emitter").toSRGB());
330
330
  }
331
331
  function T(e) {
332
332
  if (!e) return [
@@ -355,14 +355,14 @@ var E = [
355
355
  }, O = class {
356
356
  static OPTIONS = E;
357
357
  static HINTS = D;
358
- velocity = new u();
359
- gravity = new u();
358
+ velocity = new a();
359
+ gravity = new a();
360
360
  drag = 0;
361
361
  constructor(t = {}) {
362
- n("Body", t, E, D), t.velocity !== void 0 && this.velocity.copy(t.velocity, "Body"), t.gravity !== void 0 && this.gravity.copy(t.gravity, "Body"), t.drag !== void 0 && (this.drag = Math.max(0, e("Body", "drag", t.drag)));
362
+ r("Body", t, E, D), t.velocity !== void 0 && this.velocity.copy(t.velocity, "Body"), t.gravity !== void 0 && this.gravity.copy(t.gravity, "Body"), t.drag !== void 0 && (this.drag = Math.max(0, e("Body", "drag", t.drag)));
363
363
  }
364
364
  impulse(e) {
365
- return this.velocity.add(u.fromInput(e, "Body")), this;
365
+ return this.velocity.add(a.fromInput(e, "Body")), this;
366
366
  }
367
367
  update = (e, t) => {
368
368
  let n = this.velocity, r = Math.exp(-this.drag * t);
@@ -1 +1 @@
1
- {"version":3,"file":"particles.js","names":["OPTIONS","HINTS","mat4.invert","checkNumber","invert","checkOptions","Node","fail","describe","Geometry","Material","Points","Color","Vec3","SPAWN_OPTIONS","BEHAVIOUR_OPTIONS","OPTIONS$1","HINTS$1","DEFAULT_MATERIAL","tmpMat","Emitter","options","max","given","material","geometry","#max","#velocities","#ages","#lifetimes","#palette","#setBehaviour","#setSpawn","dt","known","key","#alive","#palettes","colorStops","v","s","#setLooks","#size","#sizeSpread","#opacity","#spin","spawn","saved","#saveSpawn","color","#spawnPalette","#paletteFor","origin","i","count","#spawn","#upload","#due","p","ages","lives","g","keep","hook","particle","#particle","angles","spins","#spins","age","#remove","i3","k","#paint","#followedOrigin","from","#lastOrigin","born","n","rest","#looks","t","o","randomInBall","dv","a","b","head","min","#sizeFactors","last","c","sizes","along","opacities","stops","x","f","world","owner","lifetimeRange","checkFollow","lookStops","spinRange","plain","unstop","id","#paletteIds","range","list","name","most","r","y","z","OPTIONS","HINTS","Body"],"sources":["../src/particles/Emitter.js","../src/particles/Body.js"],"sourcesContent":["// A particle system as one Points node: particles are born at the emitter's origin, move under\n// velocity, gravity and drag, fade through a list of colours and die. The simulation runs in the\n// emitter's own update handler, so it follows the scene clock (frame-rate independent) and its\n// arrays go to the GPU as one buffer refill per frame. Particles that grow, fade or turn give the\n// geometry per-point sizes, opacities or angles, and are drawn as squares.\nimport * as mat4 from '../math/mat4.js'\nimport { Geometry } from '../geometry/Geometry.js'\nimport { Material } from '../material/Material.js'\nimport { Points } from '../scene/drawables.js'\nimport { Node } from '../scene/Node.js'\nimport { Color } from '../value/Color.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkNumber, describe, fail } from '../validate.js'\n\n/** @import { MaterialOptions } from '../material/Material.js' */\n/** @import { NodeOptions } from '../scene/Node.js' */\n/** @import { ColorInput, Vec3Input } from '../types.js' */\n\n/**\n * A live particle, as passed to `onParticle`. Change `position` or `velocity` and the changes stick.\n * @typedef {object} Particle\n * @property {Vec3} position in the emitter's space\n * @property {Vec3} velocity units per second\n * @property {number} age seconds since birth\n * @property {number} lifetime seconds it lives for\n */\n\n/**\n * How particles are born. Each can also be passed to burst() for that burst alone.\n * @typedef {object} SpawnOptions\n * @property {Vec3Input} [origin] where particles start, in the emitter's space (default [0, 0, 0])\n * @property {number} [originSpread] start anywhere within this radius of the origin (default 0)\n * @property {Vec3Input} [velocity] starting velocity, units per second (default [0, 1, 0])\n * @property {number | Vec3Input} [velocitySpread] random extra velocity: a number spreads evenly in every\n * direction (a sphere - explosions), [x, y, z] spreads up to ± that much per axis (default 0)\n * @property {number | [number, number]} [lifetime] seconds, or a [min, max] range (default 1)\n * @property {ColorInput | ColorInput[]} [color] a colour, or colours each particle fades through over its\n * life - end on black to fade out with additive blending (default white)\n */\n\n/**\n * @typedef {NodeOptions & SpawnOptions & {\n * max?: number,\n * rate?: number,\n * gravity?: Vec3Input,\n * drag?: number,\n * follow?: Node | null,\n * material?: Material | MaterialOptions,\n * frustumCulled?: boolean,\n * renderOrder?: number,\n * onParticle?: ((p: Particle) => boolean | void) | null,\n * sort?: boolean,\n * } & LookOptions} EmitterOptions\n * max: most live particles at once (default 1000). rate: particles born per second (default 100).\n * gravity: acceleration, e.g. [0, -9.8, 0] (default none). drag: share of velocity lost per second\n * (default 0). follow: a node whose position becomes the origin each frame - a trail that stays\n * behind it. onParticle: called for every live particle each frame after it moves; return false to\n * remove it (a particle hitting something). sort: draw furthest first, for soft particles with\n * blending 'normal' (smoke) that would otherwise layer wrongly.\n */\n\n/**\n * How particles look over their lives, beyond colour.\n * @typedef {object} LookOptions\n * @property {number | number[]} [size] a multiple of the material's size, or several each particle goes\n * through over its life - [0.5, 3] for smoke that billows out (default 1)\n * @property {number} [sizeSpread] each particle's size varies by up to this share either way, 0-1 (default 0)\n * @property {number | number[]} [opacity] 0-1, or several each particle goes through - [0, 1, 0] fades in\n * and out (default 1)\n * @property {number | [number, number]} [spin] radians per second: a number turns each particle up to that\n * fast either way, [min, max] a range. Spinning particles start at a random angle (default 0)\n */\n\nconst SPAWN_OPTIONS = ['origin', 'originSpread', 'velocity', 'velocitySpread', 'lifetime', 'color']\nconst LOOK_OPTIONS = ['size', 'sizeSpread', 'opacity', 'spin']\nconst BEHAVIOUR_OPTIONS = ['rate', 'gravity', 'drag', 'follow', 'onParticle', ...LOOK_OPTIONS]\nconst OPTIONS = [...Points.OPTIONS, ...SPAWN_OPTIONS, 'max', ...BEHAVIOUR_OPTIONS]\nconst HINTS = {\n count: \"use 'max' for the most particles alive at once, 'rate' for how many are born per second\",\n lifetimeRnd: \"give 'lifetime' a [min, max] range\",\n velocityRnd: \"use 'velocitySpread'\",\n positionRnd: \"use 'originSpread'\",\n}\n// the look most particle effects want: glowing dots that don't hide each other\n/** @type {MaterialOptions} */\nconst DEFAULT_MATERIAL = { size: 0.1, vertexColors: true, blending: 'additive', depthWrite: false }\n\nconst tmpMat = new Float32Array(16)\n\n/**\n * Particles drawn as points. Add it to a scene and it runs itself.\n *\n * const sparks = scene.add(new Emitter({ rate: 200, velocitySpread: 2, gravity: [0, -9.8, 0], color: ['#ffd27f', '#000'] }))\n * sparks.burst(500, { velocitySpread: 8 })\n */\nexport class Emitter extends Points {\n static type = 'emitter'\n static get OPTIONS() { return OPTIONS }\n static get HINTS() { return { ...Points.HINTS, ...HINTS } }\n\n /** Particles born per second; 0 stops new ones (existing particles live out their lives). */\n rate = 100\n /** Acceleration on every particle, units per second². */\n gravity = new Vec3()\n /** Share of velocity lost per second. */\n drag = 0\n /** Where particles start, in the emitter's space. */\n origin = new Vec3()\n originSpread = 0\n velocity = new Vec3(0, 1, 0)\n /** @type {number | Vec3} */ velocitySpread = 0\n /** @type {[number, number]} */ lifetime = [1, 1]\n /** A node whose position becomes the origin each frame. @type {Node | null} */\n follow = null\n /** @type {((p: Particle) => boolean | void) | null} */\n onParticle = null\n\n #max\n #alive = 0\n #due = 0\n /** @type {Float32Array} */ #velocities\n /** @type {Float32Array} */ #ages\n /** @type {Float32Array} */ #lifetimes\n // Colour stop lists, sRGB like geometry colours. Palette 0 is the emitter's own colour; bursts with\n // their own colours add more, and each particle remembers which one it fades through.\n /** @type {number[][][]} */ #palettes = [[[1, 1, 1]]]\n /** @type {Map<string, number>} */ #paletteIds = new Map()\n /** @type {Uint8Array} */ #palette\n #spawnPalette = 0\n // the looks: stops over each particle's life, and each one's own size factor and turn rate\n /** @type {number[]} */ #size = [1]\n #sizeSpread = 0\n /** @type {number[]} */ #opacity = [1]\n /** @type {[number, number]} */ #spin = [0, 0]\n /** @type {Float32Array | null} */ #sizeFactors = null\n /** @type {Float32Array | null} */ #spins = null\n /** Last frame's origin, so a moving source leaves an even trail rather than clumps. */\n #lastOrigin = /** @type {Vec3 | null} */ (null)\n #particle = /** @type {Particle} */ ({ position: new Vec3(), velocity: new Vec3(), age: 0, lifetime: 0 })\n\n /** @param {EmitterOptions} [options] */\n constructor(options = {}) {\n if (/** @type {any} */ (options)?.geometry !== undefined) fail('Emitter', \"an Emitter makes its own geometry - size it with 'max'\")\n const max = options?.max ?? 1000\n if (!Number.isInteger(max) || max < 1) fail('Emitter', `'max' must be a whole number of 1 or more, got ${describe(max)}`)\n const given = options?.material\n if (given !== undefined && !(given instanceof Material) && (!given || typeof given !== 'object' || Array.isArray(given))) {\n fail('Emitter', `'material' is a Material or material options such as { size: 0.2 }, got ${describe(given)}`)\n }\n const material = given instanceof Material ? given : { ...DEFAULT_MATERIAL, ...given }\n const geometry = new Geometry({ positions: new Float32Array(max * 3), colors: new Float32Array(max * 3) })\n geometry.drawCount = 0\n super({ ...options, geometry, material })\n this.#max = max\n this.#velocities = new Float32Array(max * 3)\n this.#ages = new Float32Array(max)\n this.#lifetimes = new Float32Array(max)\n this.#palette = new Uint8Array(max)\n // particles spread anywhere, and stale slots would make bounds meaningless\n if (options.frustumCulled === undefined) this.frustumCulled = false\n\n this.#setBehaviour(options)\n this.#setSpawn(options)\n this.onUpdate((_, dt) => this.step(dt))\n }\n\n /**\n * Change several settings at once - a preset, say. Vectors are copied into the emitter's own,\n * so anything bound to `emitter.gravity` and friends stays bound. Live particles carry on.\n * @param {SpawnOptions & LookOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null,\n * onParticle?: ((p: Particle) => boolean | void) | null }} options\n * @returns {this}\n */\n set(options) {\n const known = [...SPAWN_OPTIONS, ...BEHAVIOUR_OPTIONS]\n for (const key of Object.keys(options ?? {})) {\n if (!known.includes(key)) fail('Emitter', `set() doesn't take '${key}' - it takes ${known.join(', ')}`)\n }\n this.#setBehaviour(options)\n this.#setSpawn(options)\n return this\n }\n\n /** Most live particles at once. */\n get max() { return this.#max }\n\n /** Particles alive now. */\n get count() { return this.#alive }\n\n /** The colours particles fade through - one or several. @param {ColorInput | ColorInput[]} v */\n set color(v) { this.#palettes[0] = colorStops(v) }\n /** @returns {[number, number, number][]} */\n get color() { return /** @type {[number, number, number][]} */ (this.#palettes[0].map((s) => [...s])) }\n\n /** Each particle's size over its life, as multiples of the material's. @param {number | number[]} v */\n set size(v) { this.#setLooks({ size: v }) }\n /** @returns {number[]} */\n get size() { return [...this.#size] }\n /** How much each particle's size varies, 0-1 either way. @param {number} v */\n set sizeSpread(v) { this.#setLooks({ sizeSpread: v }) }\n get sizeSpread() { return this.#sizeSpread }\n /** Each particle's opacity over its life. @param {number | number[]} v */\n set opacity(v) { this.#setLooks({ opacity: v }) }\n /** @returns {number[]} */\n get opacity() { return [...this.#opacity] }\n /** How fast particles turn, radians per second, as a [min, max] range. @param {number | [number, number]} v */\n set spin(v) { this.#setLooks({ spin: v }) }\n /** @returns {[number, number]} */\n get spin() { return [...this.#spin] }\n\n /**\n * Birth `count` particles at once - explosions, splashes. Options override the emitter's own\n * spawn settings for this burst only.\n * @param {number} count\n * @param {SpawnOptions} [options]\n * @returns {this}\n */\n burst(count, options = {}) {\n for (const key of Object.keys(options)) {\n if (!SPAWN_OPTIONS.includes(key)) fail('Emitter', `burst() doesn't take '${key}' - it takes ${SPAWN_OPTIONS.join(', ')}`)\n }\n const { color, ...spawn } = options\n const saved = this.#saveSpawn()\n this.#setSpawn(spawn)\n if (color !== undefined) this.#spawnPalette = this.#paletteFor(colorStops(color))\n const origin = this.origin.clone()\n for (let i = 0; i < count; i++) this.#spawn(origin, origin, 0)\n this.#setSpawn(saved)\n this.#spawnPalette = 0\n this.#upload()\n return this\n }\n\n /** Remove every particle. @returns {this} */\n clear() {\n this.#alive = 0\n this.#due = 0\n this.#upload()\n return this\n }\n\n /**\n * Advance the simulation by dt seconds. The emitter's update handler calls this each frame; call\n * it yourself only for an emitter outside a running scene.\n * @param {number} dt\n */\n step(dt) {\n if (dt <= 0) return\n const p = this.geometry.positions\n const v = this.#velocities, ages = this.#ages, lives = this.#lifetimes\n const g = this.gravity, keep = Math.exp(-this.drag * dt)\n const hook = this.onParticle, particle = this.#particle\n const angles = this.geometry.angles, spins = this.#spins\n\n for (let i = 0; i < this.#alive;) {\n const age = ages[i] += dt\n if (age >= lives[i]) {\n this.#remove(i)\n continue\n }\n const i3 = i * 3\n for (let k = 0; k < 3; k++) {\n v[i3 + k] = (v[i3 + k] + g[k] * dt) * keep\n p[i3 + k] += v[i3 + k] * dt\n }\n if (angles && spins) angles[i] += spins[i] * dt\n this.#paint(i, age / lives[i])\n if (hook) {\n particle.position.set(p[i3], p[i3 + 1], p[i3 + 2])\n particle.velocity.set(v[i3], v[i3 + 1], v[i3 + 2])\n particle.age = age\n particle.lifetime = lives[i]\n if (hook(particle) === false) {\n this.#remove(i)\n continue\n }\n p.set(particle.position, i3)\n v.set(particle.velocity, i3)\n }\n i++\n }\n\n // new particles, spread along the path the origin took this frame\n const origin = this.#followedOrigin()\n const from = this.#lastOrigin ?? origin\n this.#due += this.rate * dt\n const born = Math.floor(this.#due)\n this.#due -= born\n for (let n = 0; n < born; n++) this.#spawn(from, origin, (n + Math.random()) / born * dt)\n this.#lastOrigin = origin.clone()\n this.#upload()\n }\n\n /** @internal @returns {Record<string, unknown>} */\n cloneOptions() {\n // an emitter makes its own geometry, so neither form carries it\n const rest = super.cloneOptions()\n delete rest.geometry\n return { ...rest, ...this.#saveSpawn(), max: this.#max, rate: this.rate, gravity: this.gravity.clone(),\n drag: this.drag, follow: this.follow, onParticle: this.onParticle, color: this.color, ...this.#looks() }\n }\n\n /** @internal @returns {Record<string, unknown>} */\n jsonOptions() {\n const rest = super.jsonOptions()\n delete rest.geometry\n return { ...rest, max: this.#max, rate: this.rate, gravity: this.gravity.toArray(), drag: this.drag,\n origin: this.origin.toArray(), originSpread: this.originSpread, velocity: this.velocity.toArray(),\n velocitySpread: typeof this.velocitySpread === 'number' ? this.velocitySpread : this.velocitySpread.toArray(),\n lifetime: [...this.lifetime], color: this.#palettes[0].map((s) => new Color().setSRGB(s[0], s[1], s[2]).toJSON()),\n ...this.#looks() }\n }\n\n // birth one particle somewhere between a and b, already `head` seconds into its life\n /** @param {Vec3} a @param {Vec3} b @param {number} head */\n #spawn(a, b, head) {\n if (this.#alive >= this.#max) return\n const i = this.#alive++, i3 = i * 3\n const p = this.geometry.positions, v = this.#velocities\n const t = Math.random()\n const o = randomInBall(this.originSpread)\n const s = this.velocitySpread\n const dv = typeof s === 'number' ? randomInBall(s) : [(Math.random() * 2 - 1) * s[0], (Math.random() * 2 - 1) * s[1], (Math.random() * 2 - 1) * s[2]]\n for (let k = 0; k < 3; k++) {\n v[i3 + k] = this.velocity[k] + dv[k]\n p[i3 + k] = a[k] + (b[k] - a[k]) * t + o[k] + v[i3 + k] * head\n }\n const [min, max] = this.lifetime\n this.#lifetimes[i] = min + Math.random() * (max - min)\n this.#ages[i] = head\n this.#palette[i] = this.#spawnPalette\n if (this.#sizeFactors) this.#sizeFactors[i] = 1 + (Math.random() * 2 - 1) * this.#sizeSpread\n const angles = this.geometry.angles, spins = this.#spins\n if (angles && spins) {\n spins[i] = this.#spin[0] + Math.random() * (this.#spin[1] - this.#spin[0])\n angles[i] = Math.random() * Math.PI * 2 + spins[i] * head\n }\n this.#paint(i, head / this.#lifetimes[i])\n }\n\n // move the last particle into slot i\n /** @param {number} i */\n #remove(i) {\n const last = --this.#alive\n if (i === last) return\n const p = this.geometry.positions, c = /** @type {Float32Array} */ (this.geometry.colors), v = this.#velocities\n p.copyWithin(i * 3, last * 3, last * 3 + 3)\n c.copyWithin(i * 3, last * 3, last * 3 + 3)\n v.copyWithin(i * 3, last * 3, last * 3 + 3)\n this.#ages[i] = this.#ages[last]\n this.#lifetimes[i] = this.#lifetimes[last]\n this.#palette[i] = this.#palette[last]\n for (const a of [this.geometry.sizes, this.geometry.opacities, this.geometry.angles, this.#sizeFactors, this.#spins]) {\n if (a) a[i] = a[last]\n }\n }\n\n /** @param {number} i @param {number} t 0-1 through its life */\n #paint(i, t) {\n const { sizes, opacities } = this.geometry\n if (sizes) sizes[i] = along(this.#size, t) * (this.#sizeFactors ? this.#sizeFactors[i] : 1)\n if (opacities) opacities[i] = along(this.#opacity, t)\n const c = /** @type {Float32Array} */ (this.geometry.colors), stops = this.#palettes[this.#palette[i]]\n if (stops.length === 1) {\n c.set(stops[0], i * 3)\n return\n }\n const x = Math.min(Math.max(t, 0), 1) * (stops.length - 1)\n const s = Math.min(Math.floor(x), stops.length - 2), f = x - s\n const a = stops[s], b = stops[s + 1]\n c[i * 3] = a[0] + (b[0] - a[0]) * f\n c[i * 3 + 1] = a[1] + (b[1] - a[1]) * f\n c[i * 3 + 2] = a[2] + (b[2] - a[2]) * f\n }\n\n #upload() {\n this.geometry.drawCount = this.#alive\n this.geometry.needsUpdate()\n }\n\n /** The followed node's position in the emitter's space, or the origin. @returns {Vec3} */\n #followedOrigin() {\n if (!this.follow) return this.origin\n const world = this.follow.worldPosition\n if (!mat4.invert(tmpMat, this.worldMatrix)) return this.origin\n return this.origin.copy(world).applyMatrix4(tmpMat)\n }\n\n /** @param {SpawnOptions} o */\n #setSpawn(o) {\n const owner = 'Emitter'\n // vectors are copied in place, so references to them stay live\n if (o.origin !== undefined) this.origin.copy(o.origin, owner)\n if (o.originSpread !== undefined) this.originSpread = Math.max(0, checkNumber(owner, 'originSpread', o.originSpread))\n if (o.velocity !== undefined) this.velocity.copy(o.velocity, owner)\n if (o.velocitySpread !== undefined) {\n const s = o.velocitySpread\n if (typeof s === 'number') this.velocitySpread = checkNumber(owner, 'velocitySpread', s)\n else if (this.velocitySpread instanceof Vec3) this.velocitySpread.copy(s, owner)\n else this.velocitySpread = Vec3.fromInput(s, owner)\n }\n if (o.lifetime !== undefined) this.lifetime = lifetimeRange(o.lifetime)\n if (o.color !== undefined) this.#palettes[0] = colorStops(o.color)\n }\n\n /** @param {LookOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null, onParticle?: ((p: Particle) => boolean | void) | null }} o */\n #setBehaviour(o) {\n this.#setLooks(o)\n const owner = 'Emitter'\n if (o.rate !== undefined) this.rate = Math.max(0, checkNumber(owner, 'rate', o.rate))\n if (o.gravity !== undefined) this.gravity.copy(o.gravity, owner)\n if (o.drag !== undefined) this.drag = Math.max(0, checkNumber(owner, 'drag', o.drag))\n if (o.follow !== undefined) this.follow = checkFollow(o.follow)\n if (o.onParticle !== undefined) this.onParticle = o.onParticle\n }\n\n // Sizes, opacities and angles go on the geometry when first wanted (the points are then drawn as\n // squares) and stay; live particles get the plain values until they are next painted.\n /** @param {LookOptions} o */\n #setLooks(o) {\n const owner = 'Emitter', g = this.geometry, max = this.#max\n if (o.size !== undefined) this.#size = lookStops('size', o.size, Infinity)\n if (o.sizeSpread !== undefined) this.#sizeSpread = Math.min(Math.max(checkNumber(owner, 'sizeSpread', o.sizeSpread), 0), 1)\n if (o.opacity !== undefined) this.#opacity = lookStops('opacity', o.opacity, 1)\n if (o.spin !== undefined) this.#spin = spinRange(o.spin)\n const plain = (/** @type {number[]} */ stops) => stops.length === 1 && stops[0] === 1\n if (!g.sizes && (!plain(this.#size) || this.#sizeSpread)) g.sizes = new Float32Array(max).fill(1)\n if (!this.#sizeFactors && this.#sizeSpread) this.#sizeFactors = new Float32Array(max).fill(1)\n if (!g.opacities && !plain(this.#opacity)) g.opacities = new Float32Array(max).fill(1)\n if (!g.angles && (this.#spin[0] || this.#spin[1])) {\n g.angles = new Float32Array(max)\n this.#spins = new Float32Array(max)\n }\n }\n\n /** The looks, as options. @returns {LookOptions} */\n #looks() {\n return { size: unstop(this.#size), sizeSpread: this.#sizeSpread, opacity: unstop(this.#opacity),\n spin: /** @type {[number, number]} */ ([...this.#spin]) }\n }\n\n /** The spawn settings other than colour, as options. @returns {SpawnOptions} */\n #saveSpawn() {\n return { origin: this.origin.clone(), originSpread: this.originSpread, velocity: this.velocity.clone(),\n velocitySpread: typeof this.velocitySpread === 'number' ? this.velocitySpread : this.velocitySpread.clone(),\n lifetime: /** @type {[number, number]} */ ([...this.lifetime]) }\n }\n\n // The palette id for a burst's colours, reused when the same colours come again. Ids are a byte\n // per particle, so past 255 distinct burst palettes the oldest is overwritten.\n /** @param {number[][]} stops @returns {number} */\n #paletteFor(stops) {\n const key = stops.flat().join()\n let id = this.#paletteIds.get(key)\n if (id === undefined) {\n id = this.#palettes.length < 256 ? this.#palettes.length : 1 + (this.#paletteIds.size % 255)\n for (const [k, v] of this.#paletteIds) if (v === id) this.#paletteIds.delete(k)\n this.#paletteIds.set(key, id)\n }\n this.#palettes[id] = stops\n return id\n }\n}\n\n/** @param {unknown} v @returns {Node | null} */\nfunction checkFollow(v) {\n if (v === null || v instanceof Node) return /** @type {Node | null} */ (v)\n return fail('Emitter', `'follow' must be a node, got ${describe(v)}`)\n}\n\n/** @param {number | [number, number]} v @returns {[number, number]} */\nfunction lifetimeRange(v) {\n const range = typeof v === 'number' ? [v, v] : v\n if (!Array.isArray(range) || range.length !== 2 || !range.every((n) => typeof n === 'number' && n > 0)) {\n fail('Emitter', `'lifetime' must be seconds above 0, or a [min, max] range, got ${describe(v)}`)\n }\n return [Math.min(range[0], range[1]), Math.max(range[0], range[1])]\n}\n\n/** @param {string} name @param {number | number[]} v @param {number} most @returns {number[]} */\nfunction lookStops(name, v, most) {\n const list = Array.isArray(v) ? v : [v]\n if (!list.length || !list.every((n) => typeof n === 'number' && Number.isFinite(n) && n >= 0)) {\n fail('Emitter', `'${name}' must be a number of 0 or more, or a list of them a particle goes through, got ${describe(v)}`)\n }\n return list.map((n) => Math.min(n, most))\n}\n\n/** One stop as a number, several as a list. @param {number[]} stops @returns {number | number[]} */\nfunction unstop(stops) {\n return stops.length === 1 ? stops[0] : [...stops]\n}\n\n/** The value t (0-1) of the way through evenly spaced stops. @param {number[]} stops @param {number} t */\nfunction along(stops, t) {\n if (stops.length === 1) return stops[0]\n const x = Math.min(Math.max(t, 0), 1) * (stops.length - 1)\n const s = Math.min(Math.floor(x), stops.length - 2)\n return stops[s] + (stops[s + 1] - stops[s]) * (x - s)\n}\n\n/** @param {number | [number, number]} v @returns {[number, number]} */\nfunction spinRange(v) {\n const range = typeof v === 'number' ? [-Math.abs(v), Math.abs(v)] : v\n if (!Array.isArray(range) || range.length !== 2 || !range.every((n) => typeof n === 'number' && Number.isFinite(n))) {\n fail('Emitter', `'spin' must be radians per second, or a [min, max] range, got ${describe(v)}`)\n }\n return [Math.min(range[0], range[1]), Math.max(range[0], range[1])]\n}\n\n/** @param {ColorInput | ColorInput[]} v @returns {number[][]} */\nfunction colorStops(v) {\n // a single [r, g, b] array is one colour, not three\n const list = Array.isArray(v) && !(v.length === 3 && v.every((n) => typeof n === 'number')) ? v : [v]\n if (!list.length) fail('Emitter', \"'color' needs at least one colour\")\n return list.map((c) => Color.fromInput(/** @type {ColorInput} */ (c), 'Emitter').toSRGB())\n}\n\n/** A random point within a ball of radius r, evenly spread. @param {number} r @returns {number[]} */\nfunction randomInBall(r) {\n if (!r) return [0, 0, 0]\n let x, y, z\n do {\n x = Math.random() * 2 - 1\n y = Math.random() * 2 - 1\n z = Math.random() * 2 - 1\n } while (x * x + y * y + z * z > 1)\n return [x * r, y * r, z * r]\n}\n","// Simple physics as a behaviour rather than a base class: any node moves with velocity, gravity\n// and drag once its body's update is attached - node.onUpdate(body.update).\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkNumber, checkOptions } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n/** @import { Vec3Input } from '../types.js' */\n\n/**\n * @typedef {object} BodyOptions\n * @property {Vec3Input} [velocity] units per second (default [0, 0, 0])\n * @property {Vec3Input} [gravity] acceleration, e.g. [0, -9.8, 0] (default none)\n * @property {number} [drag] share of velocity lost per second (default 0)\n */\n\n/**\n * Velocity, gravity and drag for a node.\n *\n * const body = new Body({ velocity: [0, 20, 0], gravity: [0, -9.8, 0] })\n * rocket.onUpdate(body.update)\n */\nconst OPTIONS = ['velocity', 'gravity', 'drag']\nconst HINTS = {\n impulse: 'call body.impulse([x, y, z]) after creating it',\n mass: 'a Body has no mass - impulse() changes velocity directly',\n}\n\nexport class Body {\n /** The options it takes, and hints for names people reach for instead. */\n static OPTIONS = OPTIONS\n static HINTS = HINTS\n velocity = new Vec3()\n gravity = new Vec3()\n drag = 0\n\n /** @param {BodyOptions} [options] */\n constructor(options = {}) {\n checkOptions('Body', options, OPTIONS, HINTS)\n if (options.velocity !== undefined) this.velocity.copy(options.velocity, 'Body')\n if (options.gravity !== undefined) this.gravity.copy(options.gravity, 'Body')\n if (options.drag !== undefined) this.drag = Math.max(0, checkNumber('Body', 'drag', options.drag))\n }\n\n /** Add to the velocity at once - a kick. @param {Vec3Input} v @returns {this} */\n impulse(v) {\n this.velocity.add(Vec3.fromInput(v, 'Body'))\n return this\n }\n\n /**\n * Move a node one step. Bound, so it can be handed straight to node.onUpdate().\n * @param {Node} node\n * @param {number} dt seconds\n */\n update = (node, dt) => {\n const v = this.velocity, keep = Math.exp(-this.drag * dt)\n for (let k = 0; k < 3; k++) {\n v[k] = (v[k] + this.gravity[k] * dt) * keep\n node.position[k] += v[k] * dt\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"particles.js","names":["OPTIONS","HINTS","mat4.invert","checkNumber","invert","fail","checkOptions","describe","Vec3","Color","Points","Node","Geometry","Material","SPAWN_OPTIONS","BEHAVIOUR_OPTIONS","OPTIONS$1","HINTS$1","DEFAULT_MATERIAL","tmpMat","Emitter","options","max","given","material","geometry","#max","#velocities","#ages","#lifetimes","#palette","#setBehaviour","#setSpawn","dt","known","key","#alive","#palettes","colorStops","v","s","#setLooks","#size","#sizeSpread","#opacity","#spin","spawn","saved","#saveSpawn","color","#spawnPalette","#paletteFor","origin","i","count","#spawn","#upload","#due","p","ages","lives","g","keep","hook","particle","#particle","angles","spins","#spins","age","#remove","i3","k","#paint","#followedOrigin","from","#lastOrigin","born","n","rest","#looks","t","o","randomInBall","dv","a","b","head","min","#sizeFactors","last","c","sizes","along","opacities","stops","x","f","world","owner","lifetimeRange","checkFollow","lookStops","spinRange","plain","unstop","id","#paletteIds","range","list","name","most","r","y","z","OPTIONS","HINTS","Body"],"sources":["../src/particles/Emitter.js","../src/particles/Body.js"],"sourcesContent":["// A particle system as one Points node: particles are born at the emitter's origin, move under\n// velocity, gravity and drag, fade through a list of colours and die. The simulation runs in the\n// emitter's own update handler, so it follows the scene clock (frame-rate independent) and its\n// arrays go to the GPU as one buffer refill per frame. Particles that grow, fade or turn give the\n// geometry per-point sizes, opacities or angles, and are drawn as squares.\nimport * as mat4 from '../math/mat4.js'\nimport { Geometry } from '../geometry/Geometry.js'\nimport { Material } from '../material/Material.js'\nimport { Points } from '../scene/drawables.js'\nimport { Node } from '../scene/Node.js'\nimport { Color } from '../value/Color.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkNumber, describe, fail } from '../validate.js'\n\n/** @import { MaterialOptions } from '../material/Material.js' */\n/** @import { NodeOptions } from '../scene/Node.js' */\n/** @import { ColorInput, Vec3Input } from '../types.js' */\n\n/**\n * A live particle, as passed to `onParticle`. Change `position` or `velocity` and the changes stick.\n * @typedef {object} Particle\n * @property {Vec3} position in the emitter's space\n * @property {Vec3} velocity units per second\n * @property {number} age seconds since birth\n * @property {number} lifetime seconds it lives for\n */\n\n/**\n * How particles are born. Each can also be passed to burst() for that burst alone.\n * @typedef {object} SpawnOptions\n * @property {Vec3Input} [origin] where particles start, in the emitter's space (default [0, 0, 0])\n * @property {number} [originSpread] start anywhere within this radius of the origin (default 0)\n * @property {Vec3Input} [velocity] starting velocity, units per second (default [0, 1, 0])\n * @property {number | Vec3Input} [velocitySpread] random extra velocity: a number spreads evenly in every\n * direction (a sphere - explosions), [x, y, z] spreads up to ± that much per axis (default 0)\n * @property {number | [number, number]} [lifetime] seconds, or a [min, max] range (default 1)\n * @property {ColorInput | ColorInput[]} [color] a colour, or colours each particle fades through over its\n * life - end on black to fade out with additive blending (default white)\n */\n\n/**\n * @typedef {NodeOptions & SpawnOptions & {\n * max?: number,\n * rate?: number,\n * gravity?: Vec3Input,\n * drag?: number,\n * follow?: Node | null,\n * material?: Material | MaterialOptions,\n * frustumCulled?: boolean,\n * renderOrder?: number,\n * onParticle?: ((p: Particle) => boolean | void) | null,\n * sort?: boolean,\n * } & LookOptions} EmitterOptions\n * max: most live particles at once (default 1000). rate: particles born per second (default 100).\n * gravity: acceleration, e.g. [0, -9.8, 0] (default none). drag: share of velocity lost per second\n * (default 0). follow: a node whose position becomes the origin each frame - a trail that stays\n * behind it. onParticle: called for every live particle each frame after it moves; return false to\n * remove it (a particle hitting something). sort: draw furthest first, for soft particles with\n * blending 'normal' (smoke) that would otherwise layer wrongly.\n */\n\n/**\n * How particles look over their lives, beyond colour.\n * @typedef {object} LookOptions\n * @property {number | number[]} [size] a multiple of the material's size, or several each particle goes\n * through over its life - [0.5, 3] for smoke that billows out (default 1)\n * @property {number} [sizeSpread] each particle's size varies by up to this share either way, 0-1 (default 0)\n * @property {number | number[]} [opacity] 0-1, or several each particle goes through - [0, 1, 0] fades in\n * and out (default 1)\n * @property {number | [number, number]} [spin] radians per second: a number turns each particle up to that\n * fast either way, [min, max] a range. Spinning particles start at a random angle (default 0)\n */\n\nconst SPAWN_OPTIONS = ['origin', 'originSpread', 'velocity', 'velocitySpread', 'lifetime', 'color']\nconst LOOK_OPTIONS = ['size', 'sizeSpread', 'opacity', 'spin']\nconst BEHAVIOUR_OPTIONS = ['rate', 'gravity', 'drag', 'follow', 'onParticle', ...LOOK_OPTIONS]\nconst OPTIONS = [...Points.OPTIONS, ...SPAWN_OPTIONS, 'max', ...BEHAVIOUR_OPTIONS]\nconst HINTS = {\n count: \"use 'max' for the most particles alive at once, 'rate' for how many are born per second\",\n lifetimeRnd: \"give 'lifetime' a [min, max] range\",\n velocityRnd: \"use 'velocitySpread'\",\n positionRnd: \"use 'originSpread'\",\n}\n// the look most particle effects want: glowing dots that don't hide each other\n/** @type {MaterialOptions} */\nconst DEFAULT_MATERIAL = { size: 0.1, vertexColors: true, blending: 'additive', depthWrite: false }\n\nconst tmpMat = new Float32Array(16)\n\n/**\n * Particles drawn as points. Add it to a scene and it runs itself.\n *\n * const sparks = scene.add(new Emitter({ rate: 200, velocitySpread: 2, gravity: [0, -9.8, 0], color: ['#ffd27f', '#000'] }))\n * sparks.burst(500, { velocitySpread: 8 })\n */\nexport class Emitter extends Points {\n static type = 'emitter'\n static get OPTIONS() { return OPTIONS }\n static get HINTS() { return { ...Points.HINTS, ...HINTS } }\n\n /** Particles born per second; 0 stops new ones (existing particles live out their lives). */\n rate = 100\n /** Acceleration on every particle, units per second². */\n gravity = new Vec3()\n /** Share of velocity lost per second. */\n drag = 0\n /** Where particles start, in the emitter's space. */\n origin = new Vec3()\n originSpread = 0\n velocity = new Vec3(0, 1, 0)\n /** @type {number | Vec3} */ velocitySpread = 0\n /** @type {[number, number]} */ lifetime = [1, 1]\n /** A node whose position becomes the origin each frame. @type {Node | null} */\n follow = null\n /** @type {((p: Particle) => boolean | void) | null} */\n onParticle = null\n\n #max\n #alive = 0\n #due = 0\n /** @type {Float32Array} */ #velocities\n /** @type {Float32Array} */ #ages\n /** @type {Float32Array} */ #lifetimes\n // Colour stop lists, sRGB like geometry colours. Palette 0 is the emitter's own colour; bursts with\n // their own colours add more, and each particle remembers which one it fades through.\n /** @type {number[][][]} */ #palettes = [[[1, 1, 1]]]\n /** @type {Map<string, number>} */ #paletteIds = new Map()\n /** @type {Uint8Array} */ #palette\n #spawnPalette = 0\n // the looks: stops over each particle's life, and each one's own size factor and turn rate\n /** @type {number[]} */ #size = [1]\n #sizeSpread = 0\n /** @type {number[]} */ #opacity = [1]\n /** @type {[number, number]} */ #spin = [0, 0]\n /** @type {Float32Array | null} */ #sizeFactors = null\n /** @type {Float32Array | null} */ #spins = null\n /** Last frame's origin, so a moving source leaves an even trail rather than clumps. */\n #lastOrigin = /** @type {Vec3 | null} */ (null)\n #particle = /** @type {Particle} */ ({ position: new Vec3(), velocity: new Vec3(), age: 0, lifetime: 0 })\n\n /** @param {EmitterOptions} [options] */\n constructor(options = {}) {\n if (/** @type {any} */ (options)?.geometry !== undefined) fail('Emitter', \"an Emitter makes its own geometry - size it with 'max'\")\n const max = options?.max ?? 1000\n if (!Number.isInteger(max) || max < 1) fail('Emitter', `'max' must be a whole number of 1 or more, got ${describe(max)}`)\n const given = options?.material\n if (given !== undefined && !(given instanceof Material) && (!given || typeof given !== 'object' || Array.isArray(given))) {\n fail('Emitter', `'material' is a Material or material options such as { size: 0.2 }, got ${describe(given)}`)\n }\n const material = given instanceof Material ? given : { ...DEFAULT_MATERIAL, ...given }\n const geometry = new Geometry({ positions: new Float32Array(max * 3), colors: new Float32Array(max * 3) })\n geometry.drawCount = 0\n super({ ...options, geometry, material })\n this.#max = max\n this.#velocities = new Float32Array(max * 3)\n this.#ages = new Float32Array(max)\n this.#lifetimes = new Float32Array(max)\n this.#palette = new Uint8Array(max)\n // particles spread anywhere, and stale slots would make bounds meaningless\n if (options.frustumCulled === undefined) this.frustumCulled = false\n\n this.#setBehaviour(options)\n this.#setSpawn(options)\n this.onUpdate((_, dt) => this.step(dt))\n }\n\n /**\n * Change several settings at once - a preset, say. Vectors are copied into the emitter's own,\n * so anything bound to `emitter.gravity` and friends stays bound. Live particles carry on.\n * @param {SpawnOptions & LookOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null,\n * onParticle?: ((p: Particle) => boolean | void) | null }} options\n * @returns {this}\n */\n set(options) {\n const known = [...SPAWN_OPTIONS, ...BEHAVIOUR_OPTIONS]\n for (const key of Object.keys(options ?? {})) {\n if (!known.includes(key)) fail('Emitter', `set() doesn't take '${key}' - it takes ${known.join(', ')}`)\n }\n this.#setBehaviour(options)\n this.#setSpawn(options)\n return this\n }\n\n /** Most live particles at once. */\n get max() { return this.#max }\n\n /** Particles alive now. */\n get count() { return this.#alive }\n\n /** The colours particles fade through - one or several. @param {ColorInput | ColorInput[]} v */\n set color(v) { this.#palettes[0] = colorStops(v) }\n /** @returns {[number, number, number][]} */\n get color() { return /** @type {[number, number, number][]} */ (this.#palettes[0].map((s) => [...s])) }\n\n /** Each particle's size over its life, as multiples of the material's. @param {number | number[]} v */\n set size(v) { this.#setLooks({ size: v }) }\n /** @returns {number[]} */\n get size() { return [...this.#size] }\n /** How much each particle's size varies, 0-1 either way. @param {number} v */\n set sizeSpread(v) { this.#setLooks({ sizeSpread: v }) }\n get sizeSpread() { return this.#sizeSpread }\n /** Each particle's opacity over its life. @param {number | number[]} v */\n set opacity(v) { this.#setLooks({ opacity: v }) }\n /** @returns {number[]} */\n get opacity() { return [...this.#opacity] }\n /** How fast particles turn, radians per second, as a [min, max] range. @param {number | [number, number]} v */\n set spin(v) { this.#setLooks({ spin: v }) }\n /** @returns {[number, number]} */\n get spin() { return [...this.#spin] }\n\n /**\n * Birth `count` particles at once - explosions, splashes. Options override the emitter's own\n * spawn settings for this burst only.\n * @param {number} count\n * @param {SpawnOptions} [options]\n * @returns {this}\n */\n burst(count, options = {}) {\n for (const key of Object.keys(options)) {\n if (!SPAWN_OPTIONS.includes(key)) fail('Emitter', `burst() doesn't take '${key}' - it takes ${SPAWN_OPTIONS.join(', ')}`)\n }\n const { color, ...spawn } = options\n const saved = this.#saveSpawn()\n this.#setSpawn(spawn)\n if (color !== undefined) this.#spawnPalette = this.#paletteFor(colorStops(color))\n const origin = this.origin.clone()\n for (let i = 0; i < count; i++) this.#spawn(origin, origin, 0)\n this.#setSpawn(saved)\n this.#spawnPalette = 0\n this.#upload()\n return this\n }\n\n /** Remove every particle. @returns {this} */\n clear() {\n this.#alive = 0\n this.#due = 0\n this.#upload()\n return this\n }\n\n /**\n * Advance the simulation by dt seconds. The emitter's update handler calls this each frame; call\n * it yourself only for an emitter outside a running scene.\n * @param {number} dt\n */\n step(dt) {\n if (dt <= 0) return\n const p = this.geometry.positions\n const v = this.#velocities, ages = this.#ages, lives = this.#lifetimes\n const g = this.gravity, keep = Math.exp(-this.drag * dt)\n const hook = this.onParticle, particle = this.#particle\n const angles = this.geometry.angles, spins = this.#spins\n\n for (let i = 0; i < this.#alive;) {\n const age = ages[i] += dt\n if (age >= lives[i]) {\n this.#remove(i)\n continue\n }\n const i3 = i * 3\n for (let k = 0; k < 3; k++) {\n v[i3 + k] = (v[i3 + k] + g[k] * dt) * keep\n p[i3 + k] += v[i3 + k] * dt\n }\n if (angles && spins) angles[i] += spins[i] * dt\n this.#paint(i, age / lives[i])\n if (hook) {\n particle.position.set(p[i3], p[i3 + 1], p[i3 + 2])\n particle.velocity.set(v[i3], v[i3 + 1], v[i3 + 2])\n particle.age = age\n particle.lifetime = lives[i]\n if (hook(particle) === false) {\n this.#remove(i)\n continue\n }\n p.set(particle.position, i3)\n v.set(particle.velocity, i3)\n }\n i++\n }\n\n // new particles, spread along the path the origin took this frame\n const origin = this.#followedOrigin()\n const from = this.#lastOrigin ?? origin\n this.#due += this.rate * dt\n const born = Math.floor(this.#due)\n this.#due -= born\n for (let n = 0; n < born; n++) this.#spawn(from, origin, (n + Math.random()) / born * dt)\n this.#lastOrigin = origin.clone()\n this.#upload()\n }\n\n /** @internal @returns {Record<string, unknown>} */\n cloneOptions() {\n // an emitter makes its own geometry, so neither form carries it\n const rest = super.cloneOptions()\n delete rest.geometry\n return { ...rest, ...this.#saveSpawn(), max: this.#max, rate: this.rate, gravity: this.gravity.clone(),\n drag: this.drag, follow: this.follow, onParticle: this.onParticle, color: this.color, ...this.#looks() }\n }\n\n /** @internal @returns {Record<string, unknown>} */\n jsonOptions() {\n const rest = super.jsonOptions()\n delete rest.geometry\n return { ...rest, max: this.#max, rate: this.rate, gravity: this.gravity.toArray(), drag: this.drag,\n origin: this.origin.toArray(), originSpread: this.originSpread, velocity: this.velocity.toArray(),\n velocitySpread: typeof this.velocitySpread === 'number' ? this.velocitySpread : this.velocitySpread.toArray(),\n lifetime: [...this.lifetime], color: this.#palettes[0].map((s) => new Color().setSRGB(s[0], s[1], s[2]).toJSON()),\n ...this.#looks() }\n }\n\n // birth one particle somewhere between a and b, already `head` seconds into its life\n /** @param {Vec3} a @param {Vec3} b @param {number} head */\n #spawn(a, b, head) {\n if (this.#alive >= this.#max) return\n const i = this.#alive++, i3 = i * 3\n const p = this.geometry.positions, v = this.#velocities\n const t = Math.random()\n const o = randomInBall(this.originSpread)\n const s = this.velocitySpread\n const dv = typeof s === 'number' ? randomInBall(s) : [(Math.random() * 2 - 1) * s[0], (Math.random() * 2 - 1) * s[1], (Math.random() * 2 - 1) * s[2]]\n for (let k = 0; k < 3; k++) {\n v[i3 + k] = this.velocity[k] + dv[k]\n p[i3 + k] = a[k] + (b[k] - a[k]) * t + o[k] + v[i3 + k] * head\n }\n const [min, max] = this.lifetime\n this.#lifetimes[i] = min + Math.random() * (max - min)\n this.#ages[i] = head\n this.#palette[i] = this.#spawnPalette\n if (this.#sizeFactors) this.#sizeFactors[i] = 1 + (Math.random() * 2 - 1) * this.#sizeSpread\n const angles = this.geometry.angles, spins = this.#spins\n if (angles && spins) {\n spins[i] = this.#spin[0] + Math.random() * (this.#spin[1] - this.#spin[0])\n angles[i] = Math.random() * Math.PI * 2 + spins[i] * head\n }\n this.#paint(i, head / this.#lifetimes[i])\n }\n\n // move the last particle into slot i\n /** @param {number} i */\n #remove(i) {\n const last = --this.#alive\n if (i === last) return\n const p = this.geometry.positions, c = /** @type {Float32Array} */ (this.geometry.colors), v = this.#velocities\n p.copyWithin(i * 3, last * 3, last * 3 + 3)\n c.copyWithin(i * 3, last * 3, last * 3 + 3)\n v.copyWithin(i * 3, last * 3, last * 3 + 3)\n this.#ages[i] = this.#ages[last]\n this.#lifetimes[i] = this.#lifetimes[last]\n this.#palette[i] = this.#palette[last]\n for (const a of [this.geometry.sizes, this.geometry.opacities, this.geometry.angles, this.#sizeFactors, this.#spins]) {\n if (a) a[i] = a[last]\n }\n }\n\n /** @param {number} i @param {number} t 0-1 through its life */\n #paint(i, t) {\n const { sizes, opacities } = this.geometry\n if (sizes) sizes[i] = along(this.#size, t) * (this.#sizeFactors ? this.#sizeFactors[i] : 1)\n if (opacities) opacities[i] = along(this.#opacity, t)\n const c = /** @type {Float32Array} */ (this.geometry.colors), stops = this.#palettes[this.#palette[i]]\n if (stops.length === 1) {\n c.set(stops[0], i * 3)\n return\n }\n const x = Math.min(Math.max(t, 0), 1) * (stops.length - 1)\n const s = Math.min(Math.floor(x), stops.length - 2), f = x - s\n const a = stops[s], b = stops[s + 1]\n c[i * 3] = a[0] + (b[0] - a[0]) * f\n c[i * 3 + 1] = a[1] + (b[1] - a[1]) * f\n c[i * 3 + 2] = a[2] + (b[2] - a[2]) * f\n }\n\n #upload() {\n this.geometry.drawCount = this.#alive\n this.geometry.needsUpdate()\n }\n\n /** The followed node's position in the emitter's space, or the origin. @returns {Vec3} */\n #followedOrigin() {\n if (!this.follow) return this.origin\n const world = this.follow.worldPosition\n if (!mat4.invert(tmpMat, this.worldMatrix)) return this.origin\n return this.origin.copy(world).applyMatrix4(tmpMat)\n }\n\n /** @param {SpawnOptions} o */\n #setSpawn(o) {\n const owner = 'Emitter'\n // vectors are copied in place, so references to them stay live\n if (o.origin !== undefined) this.origin.copy(o.origin, owner)\n if (o.originSpread !== undefined) this.originSpread = Math.max(0, checkNumber(owner, 'originSpread', o.originSpread))\n if (o.velocity !== undefined) this.velocity.copy(o.velocity, owner)\n if (o.velocitySpread !== undefined) {\n const s = o.velocitySpread\n if (typeof s === 'number') this.velocitySpread = checkNumber(owner, 'velocitySpread', s)\n else if (this.velocitySpread instanceof Vec3) this.velocitySpread.copy(s, owner)\n else this.velocitySpread = Vec3.fromInput(s, owner)\n }\n if (o.lifetime !== undefined) this.lifetime = lifetimeRange(o.lifetime)\n if (o.color !== undefined) this.#palettes[0] = colorStops(o.color)\n }\n\n /** @param {LookOptions & { rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null, onParticle?: ((p: Particle) => boolean | void) | null }} o */\n #setBehaviour(o) {\n this.#setLooks(o)\n const owner = 'Emitter'\n if (o.rate !== undefined) this.rate = Math.max(0, checkNumber(owner, 'rate', o.rate))\n if (o.gravity !== undefined) this.gravity.copy(o.gravity, owner)\n if (o.drag !== undefined) this.drag = Math.max(0, checkNumber(owner, 'drag', o.drag))\n if (o.follow !== undefined) this.follow = checkFollow(o.follow)\n if (o.onParticle !== undefined) this.onParticle = o.onParticle\n }\n\n // Sizes, opacities and angles go on the geometry when first wanted (the points are then drawn as\n // squares) and stay; live particles get the plain values until they are next painted.\n /** @param {LookOptions} o */\n #setLooks(o) {\n const owner = 'Emitter', g = this.geometry, max = this.#max\n if (o.size !== undefined) this.#size = lookStops('size', o.size, Infinity)\n if (o.sizeSpread !== undefined) this.#sizeSpread = Math.min(Math.max(checkNumber(owner, 'sizeSpread', o.sizeSpread), 0), 1)\n if (o.opacity !== undefined) this.#opacity = lookStops('opacity', o.opacity, 1)\n if (o.spin !== undefined) this.#spin = spinRange(o.spin)\n const plain = (/** @type {number[]} */ stops) => stops.length === 1 && stops[0] === 1\n if (!g.sizes && (!plain(this.#size) || this.#sizeSpread)) g.sizes = new Float32Array(max).fill(1)\n if (!this.#sizeFactors && this.#sizeSpread) this.#sizeFactors = new Float32Array(max).fill(1)\n if (!g.opacities && !plain(this.#opacity)) g.opacities = new Float32Array(max).fill(1)\n if (!g.angles && (this.#spin[0] || this.#spin[1])) {\n g.angles = new Float32Array(max)\n this.#spins = new Float32Array(max)\n }\n }\n\n /** The looks, as options. @returns {LookOptions} */\n #looks() {\n return { size: unstop(this.#size), sizeSpread: this.#sizeSpread, opacity: unstop(this.#opacity),\n spin: /** @type {[number, number]} */ ([...this.#spin]) }\n }\n\n /** The spawn settings other than colour, as options. @returns {SpawnOptions} */\n #saveSpawn() {\n return { origin: this.origin.clone(), originSpread: this.originSpread, velocity: this.velocity.clone(),\n velocitySpread: typeof this.velocitySpread === 'number' ? this.velocitySpread : this.velocitySpread.clone(),\n lifetime: /** @type {[number, number]} */ ([...this.lifetime]) }\n }\n\n // The palette id for a burst's colours, reused when the same colours come again. Ids are a byte\n // per particle, so past 255 distinct burst palettes the oldest is overwritten.\n /** @param {number[][]} stops @returns {number} */\n #paletteFor(stops) {\n const key = stops.flat().join()\n let id = this.#paletteIds.get(key)\n if (id === undefined) {\n id = this.#palettes.length < 256 ? this.#palettes.length : 1 + (this.#paletteIds.size % 255)\n for (const [k, v] of this.#paletteIds) if (v === id) this.#paletteIds.delete(k)\n this.#paletteIds.set(key, id)\n }\n this.#palettes[id] = stops\n return id\n }\n}\n\n/** @param {unknown} v @returns {Node | null} */\nfunction checkFollow(v) {\n if (v === null || v instanceof Node) return /** @type {Node | null} */ (v)\n return fail('Emitter', `'follow' must be a node, got ${describe(v)}`)\n}\n\n/** @param {number | [number, number]} v @returns {[number, number]} */\nfunction lifetimeRange(v) {\n const range = typeof v === 'number' ? [v, v] : v\n if (!Array.isArray(range) || range.length !== 2 || !range.every((n) => typeof n === 'number' && n > 0)) {\n fail('Emitter', `'lifetime' must be seconds above 0, or a [min, max] range, got ${describe(v)}`)\n }\n return [Math.min(range[0], range[1]), Math.max(range[0], range[1])]\n}\n\n/** @param {string} name @param {number | number[]} v @param {number} most @returns {number[]} */\nfunction lookStops(name, v, most) {\n const list = Array.isArray(v) ? v : [v]\n if (!list.length || !list.every((n) => typeof n === 'number' && Number.isFinite(n) && n >= 0)) {\n fail('Emitter', `'${name}' must be a number of 0 or more, or a list of them a particle goes through, got ${describe(v)}`)\n }\n return list.map((n) => Math.min(n, most))\n}\n\n/** One stop as a number, several as a list. @param {number[]} stops @returns {number | number[]} */\nfunction unstop(stops) {\n return stops.length === 1 ? stops[0] : [...stops]\n}\n\n/** The value t (0-1) of the way through evenly spaced stops. @param {number[]} stops @param {number} t */\nfunction along(stops, t) {\n if (stops.length === 1) return stops[0]\n const x = Math.min(Math.max(t, 0), 1) * (stops.length - 1)\n const s = Math.min(Math.floor(x), stops.length - 2)\n return stops[s] + (stops[s + 1] - stops[s]) * (x - s)\n}\n\n/** @param {number | [number, number]} v @returns {[number, number]} */\nfunction spinRange(v) {\n const range = typeof v === 'number' ? [-Math.abs(v), Math.abs(v)] : v\n if (!Array.isArray(range) || range.length !== 2 || !range.every((n) => typeof n === 'number' && Number.isFinite(n))) {\n fail('Emitter', `'spin' must be radians per second, or a [min, max] range, got ${describe(v)}`)\n }\n return [Math.min(range[0], range[1]), Math.max(range[0], range[1])]\n}\n\n/** @param {ColorInput | ColorInput[]} v @returns {number[][]} */\nfunction colorStops(v) {\n // a single [r, g, b] array is one colour, not three\n const list = Array.isArray(v) && !(v.length === 3 && v.every((n) => typeof n === 'number')) ? v : [v]\n if (!list.length) fail('Emitter', \"'color' needs at least one colour\")\n return list.map((c) => Color.fromInput(/** @type {ColorInput} */ (c), 'Emitter').toSRGB())\n}\n\n/** A random point within a ball of radius r, evenly spread. @param {number} r @returns {number[]} */\nfunction randomInBall(r) {\n if (!r) return [0, 0, 0]\n let x, y, z\n do {\n x = Math.random() * 2 - 1\n y = Math.random() * 2 - 1\n z = Math.random() * 2 - 1\n } while (x * x + y * y + z * z > 1)\n return [x * r, y * r, z * r]\n}\n","// Simple physics as a behaviour rather than a base class: any node moves with velocity, gravity\n// and drag once its body's update is attached - node.onUpdate(body.update).\nimport { Vec3 } from '../value/Vec3.js'\nimport { checkNumber, checkOptions } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n/** @import { Vec3Input } from '../types.js' */\n\n/**\n * @typedef {object} BodyOptions\n * @property {Vec3Input} [velocity] units per second (default [0, 0, 0])\n * @property {Vec3Input} [gravity] acceleration, e.g. [0, -9.8, 0] (default none)\n * @property {number} [drag] share of velocity lost per second (default 0)\n */\n\n/**\n * Velocity, gravity and drag for a node.\n *\n * const body = new Body({ velocity: [0, 20, 0], gravity: [0, -9.8, 0] })\n * rocket.onUpdate(body.update)\n */\nconst OPTIONS = ['velocity', 'gravity', 'drag']\nconst HINTS = {\n impulse: 'call body.impulse([x, y, z]) after creating it',\n mass: 'a Body has no mass - impulse() changes velocity directly',\n}\n\nexport class Body {\n /** The options it takes, and hints for names people reach for instead. */\n static OPTIONS = OPTIONS\n static HINTS = HINTS\n velocity = new Vec3()\n gravity = new Vec3()\n drag = 0\n\n /** @param {BodyOptions} [options] */\n constructor(options = {}) {\n checkOptions('Body', options, OPTIONS, HINTS)\n if (options.velocity !== undefined) this.velocity.copy(options.velocity, 'Body')\n if (options.gravity !== undefined) this.gravity.copy(options.gravity, 'Body')\n if (options.drag !== undefined) this.drag = Math.max(0, checkNumber('Body', 'drag', options.drag))\n }\n\n /** Add to the velocity at once - a kick. @param {Vec3Input} v @returns {this} */\n impulse(v) {\n this.velocity.add(Vec3.fromInput(v, 'Body'))\n return this\n }\n\n /**\n * Move a node one step. Bound, so it can be handed straight to node.onUpdate().\n * @param {Node} node\n * @param {number} dt seconds\n */\n update = (node, dt) => {\n const v = this.velocity, keep = Math.exp(-this.drag * dt)\n for (let k = 0; k < 3; k++) {\n v[k] = (v[k] + this.gravity[k] * dt) * keep\n node.position[k] += v[k] * dt\n }\n }\n}\n"],"mappings":"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"}