phoria2 1.2.0 → 1.3.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.
- package/README.md +2 -3
- package/dist/charts.js +476 -459
- package/dist/charts.js.map +1 -1
- package/dist/core.js +1192 -963
- package/dist/core.js.map +1 -1
- package/dist/edit.js +75 -75
- package/dist/edit.js.map +1 -1
- package/dist/explain.js +1 -1
- package/dist/gltf.js +84 -84
- package/dist/gltf.js.map +1 -1
- package/dist/keyboard.js +188 -0
- package/dist/keyboard.js.map +1 -0
- package/dist/motion.js +35 -35
- package/dist/motion.js.map +1 -1
- package/dist/particles.js +191 -118
- package/dist/particles.js.map +1 -1
- package/dist/passes.js +16 -16
- package/dist/passes.js.map +1 -1
- package/dist/phoria2.js +1 -1
- package/dist/picking.js +30 -14
- package/dist/picking.js.map +1 -1
- package/dist/snapshot.js +1 -1
- package/dist/tooltip.js +1 -1
- package/dist/types/View.d.ts +11 -0
- package/dist/types/area.d.ts +18 -0
- package/dist/types/geometry/Geometry.d.ts +16 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/material/Material.d.ts +15 -0
- package/dist/types/material/Texture.d.ts +18 -2
- package/dist/types/particles/Emitter.d.ts +39 -2
- package/dist/types/scene/drawables.d.ts +23 -2
- package/dist/types/webgl/resources.d.ts +12 -2
- package/dist/types/webgl/shaders.d.ts +7 -2
- package/llms-full.txt +48 -1
- package/package.json +1 -1
- package/schema/scene.schema.json +48 -0
- package/src/View.js +16 -0
- package/src/area.js +131 -0
- package/src/charts/marks.js +29 -2
- package/src/geometry/Geometry.js +16 -1
- package/src/index.js +1 -0
- package/src/material/Material.js +9 -2
- package/src/material/Texture.js +55 -2
- package/src/particles/Emitter.js +121 -10
- package/src/scene/drawables.js +68 -4
- package/src/watch.js +8 -1
- package/src/webgl/WebGLRenderer.js +36 -5
- package/src/webgl/picking.js +22 -1
- package/src/webgl/resources.js +168 -14
- package/src/webgl/shaders.js +110 -11
package/dist/particles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"particles.js","names":["OPTIONS","HINTS","mat4.invert","fail","Vec3","checkOptions","invert","describe","checkNumber","Color","Material","Geometry","Node","Points","SPAWN_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","spawn","saved","#saveSpawn","color","#spawnPalette","#paletteFor","origin","i","count","#spawn","#upload","#due","p","ages","lives","g","keep","hook","particle","#particle","age","#remove","i3","k","#paint","#followedOrigin","from","#lastOrigin","born","n","rest","t","o","randomInBall","dv","a","b","head","min","last","c","stops","x","f","world","owner","lifetimeRange","checkFollow","id","#paletteIds","range","list","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.\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 * }} 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).\n */\n\nconst SPAWN_OPTIONS = ['origin', 'originSpread', 'velocity', 'velocitySpread', 'lifetime', 'color']\nconst OPTIONS = [...Points.OPTIONS, ...SPAWN_OPTIONS,\n 'max', 'rate', 'gravity', 'drag', 'follow', 'onParticle']\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 /** 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 & { 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, 'rate', 'gravity', 'drag', 'follow', 'onParticle']\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 /**\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\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 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 }\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 }\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 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 }\n\n /** @param {number} i @param {number} t 0-1 through its life */\n #paint(i, 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 {{ rate?: number, gravity?: Vec3Input, drag?: number, follow?: Node | null, onParticle?: ((p: Particle) => boolean | void) | null }} o */\n #setBehaviour(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 /** 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 {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","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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"}
|
package/dist/passes.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as e, C as t, E as n,
|
|
1
|
+
import { A as e, C as t, E as n, It as r, Lt as i, O as a, Pt as o, S as s, T as c, bt as l, w as u, x as d, xt as f, yt as p } from "./core.js";
|
|
2
2
|
// src/irradiance.js
|
|
3
3
|
function m({ data: e, width: t, height: n }) {
|
|
4
4
|
let r = /* @__PURE__ */ new Float64Array(27), i = Math.max(1, Math.floor(t / 128)), a = Math.max(1, Math.floor(n / 64)), o = 2 * Math.PI * i / t, s = Math.PI * a / n;
|
|
@@ -56,7 +56,7 @@ uniform float uLod;
|
|
|
56
56
|
uniform float uIntensity;
|
|
57
57
|
uniform int uHdr;
|
|
58
58
|
out vec4 fragColor;
|
|
59
|
-
${
|
|
59
|
+
${u}
|
|
60
60
|
${c}
|
|
61
61
|
${e}
|
|
62
62
|
void main() {
|
|
@@ -151,7 +151,7 @@ fragColor = widened(vUv);
|
|
|
151
151
|
in vec2 vUv;
|
|
152
152
|
uniform sampler2D uMask;
|
|
153
153
|
out vec4 fragColor;
|
|
154
|
-
${
|
|
154
|
+
${u}
|
|
155
155
|
${y}
|
|
156
156
|
void main() {
|
|
157
157
|
if (texture(uMask, vUv).a > 0.0) discard;
|
|
@@ -204,7 +204,7 @@ fragColor = found ? vec4(float(at + 32) / 255.0, 0.0, 0.0, 1.0) : vec4(0.0);
|
|
|
204
204
|
in vec2 vUv;
|
|
205
205
|
uniform sampler2D uNearest;
|
|
206
206
|
out vec4 fragColor;
|
|
207
|
-
${
|
|
207
|
+
${u}
|
|
208
208
|
${C}
|
|
209
209
|
void main() {
|
|
210
210
|
float here = texture(uCoverage, vUv).r;
|
|
@@ -229,7 +229,7 @@ float alpha = clamp(ring.a - nearest + 0.5, 0.0, 1.0) * (1.0 - here);
|
|
|
229
229
|
if (alpha <= 0.0) discard;
|
|
230
230
|
fragColor = vec4(linearToSrgb(ring.rgb) * alpha, alpha);
|
|
231
231
|
}
|
|
232
|
-
`, E = [
|
|
232
|
+
`, E = [a, _], D = [n, b], O = [n, x], k = [a, S], A = [n, w], j = [n, T], M = [a, t], N = [n, h], P = [n, g], F = 512, I = 6, L = class {
|
|
233
233
|
#e = /* @__PURE__ */ new WeakMap();
|
|
234
234
|
#t = null;
|
|
235
235
|
#n = null;
|
|
@@ -270,16 +270,16 @@ fragColor = vec4(linearToSrgb(ring.rgb) * alpha, alpha);
|
|
|
270
270
|
}, this.#e.set(e, t), t;
|
|
271
271
|
}
|
|
272
272
|
background(e, t, n) {
|
|
273
|
-
let i = this.gl,
|
|
274
|
-
if (
|
|
273
|
+
let i = this.gl, a = t.isHDR, s = a ? this.environment(t) : null;
|
|
274
|
+
if (a && !s) return;
|
|
275
275
|
let c = this.programs.ready("background", [], N);
|
|
276
276
|
i.useProgram(c.program);
|
|
277
277
|
let l = Math.min(Math.max(e.backgroundBlur, 0), 1);
|
|
278
|
-
s ? (c.set("uBackground", l > 0 ? s.prefiltered : s.source), c.set("uLod", l * s.maxLod), c.set("uIntensity", e.environment === t ? e.environmentIntensity : 1)) : (c.set("uBackground", this.resources.texture(t)), c.set("uLod", l * 6), c.set("uIntensity", 1)), c.set("uHdr", +!!
|
|
278
|
+
s ? (c.set("uBackground", l > 0 ? s.prefiltered : s.source), c.set("uLod", l * s.maxLod), c.set("uIntensity", e.environment === t ? e.environmentIntensity : 1)) : (c.set("uBackground", this.resources.texture(t)), c.set("uLod", l * 6), c.set("uIntensity", 1)), c.set("uHdr", +!!a), c.set("uExposure", n.exposure), c.set("uToneMapping", n.toneMapping), c.set("uInverseViewProjection", r(o(), e.camera.viewProjection)), c.set("uCameraPosition", e.camera.position), i.disable(i.DEPTH_TEST), i.disable(i.BLEND), i.disable(i.CULL_FACE), i.depthMask(!1), this.programs.drawFullscreen(), i.depthMask(!0), i.enable(i.DEPTH_TEST);
|
|
279
279
|
}
|
|
280
280
|
shadowMap(e) {
|
|
281
281
|
let t = this.gl;
|
|
282
|
-
this.#t ??= this.#l(
|
|
282
|
+
this.#t ??= this.#l(l), t.bindFramebuffer(t.FRAMEBUFFER, this.#t.framebuffer), t.viewport(0, 0, l, l), this.#a(), this.#s(e.casters, e.viewProjection), this.#o();
|
|
283
283
|
let n = new Float32Array([
|
|
284
284
|
.5,
|
|
285
285
|
0,
|
|
@@ -304,15 +304,15 @@ fragColor = vec4(linearToSrgb(ring.rgb) * alpha, alpha);
|
|
|
304
304
|
texture: this.#t.texture,
|
|
305
305
|
uniforms: {
|
|
306
306
|
uShadowMatrix: i(/* @__PURE__ */ new Float32Array(16), n, e.viewProjection),
|
|
307
|
-
uShadowTexel: 1 /
|
|
307
|
+
uShadowTexel: 1 / l,
|
|
308
308
|
uShadowBias: e.texelSize * 1.5
|
|
309
309
|
}
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
spotShadowMaps(e) {
|
|
313
313
|
let t = this.gl;
|
|
314
|
-
this.#n ??= this.#l(
|
|
315
|
-
let n = /* @__PURE__ */ new Float32Array(64), r = /* @__PURE__ */ new Float32Array(16), a = /* @__PURE__ */ new Float32Array(4), o =
|
|
314
|
+
this.#n ??= this.#l(f), t.bindFramebuffer(t.FRAMEBUFFER, this.#n.framebuffer), t.viewport(0, 0, f, f), this.#a();
|
|
315
|
+
let n = /* @__PURE__ */ new Float32Array(64), r = /* @__PURE__ */ new Float32Array(16), a = /* @__PURE__ */ new Float32Array(4), o = f / 2;
|
|
316
316
|
for (let s of e) {
|
|
317
317
|
let e = s.tile % 2, c = Math.floor(s.tile / 2);
|
|
318
318
|
t.viewport(e * o, c * o, o, o), this.#s(s.casters, s.viewProjection);
|
|
@@ -349,14 +349,14 @@ fragColor = vec4(linearToSrgb(ring.rgb) * alpha, alpha);
|
|
|
349
349
|
uSpotShadowMatrix: n,
|
|
350
350
|
uSpotShadowTile: r,
|
|
351
351
|
uSpotShadowBias: a,
|
|
352
|
-
uSpotShadowTexel: 1 /
|
|
352
|
+
uSpotShadowTexel: 1 / f
|
|
353
353
|
}
|
|
354
354
|
};
|
|
355
355
|
}
|
|
356
356
|
pointShadowMap(e) {
|
|
357
357
|
let t = this.gl;
|
|
358
|
-
return this.#r ??= this.#u(
|
|
359
|
-
t.bindFramebuffer(t.FRAMEBUFFER, this.#r.framebuffers[n]), t.viewport(0, 0,
|
|
358
|
+
return this.#r ??= this.#u(p), e.faces.forEach((e, n) => {
|
|
359
|
+
t.bindFramebuffer(t.FRAMEBUFFER, this.#r.framebuffers[n]), t.viewport(0, 0, p, p), this.#a(), this.#s(e.casters, e.viewProjection);
|
|
360
360
|
}), this.#o(), {
|
|
361
361
|
define: "POINT_SHADOW",
|
|
362
362
|
sampler: "uPointShadowMap",
|
|
@@ -387,7 +387,7 @@ fragColor = vec4(linearToSrgb(ring.rgb) * alpha, alpha);
|
|
|
387
387
|
if (n.useProgram(c.program), c.set("uViewProjection", t), c.set("uModel", r.worldMatrix), r.clip && c.set("uClipPlane", r.clip.plane), o) {
|
|
388
388
|
c.set("uOpacity", a.opacity), c.set("uAlphaTest", a.alphaTest);
|
|
389
389
|
let e = a.map;
|
|
390
|
-
e && (c.set("uMapUv",
|
|
390
|
+
e && (c.set("uMapUv", d(e)), c.set("uMap", this.resources.texture(e))), s && c.set("uTriplanar", a.triplanar);
|
|
391
391
|
}
|
|
392
392
|
this.resources.drawTriangles(r.geometry, e);
|
|
393
393
|
}
|
package/dist/passes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passes.js","names":["mat4.invert","mat4.create","mat4.multiply","TONE_MAP","CLIP_ONLY_FRAGMENT","FULLSCREEN_VERTEX","invert","multiply","
|
|
1
|
+
{"version":3,"file":"passes.js","names":["mat4.invert","mat4.create","mat4.multiply","TONE_MAP","CLIP_ONLY_FRAGMENT","FULLSCREEN_VERTEX","invert","multiply","MESH_VERTEX","create","CLIP","EQUIRECT","SHADOW_MAP_SIZE","COMMON","uvMatrix","SPOT_ATLAS_SIZE","POINT_SHADOW_SIZE","irradianceSH","sh","sx","width","sy","height","dPhi","dTheta","y","theta","sinT","dy","weight","x","phi","dx","dz","basis","i","k","w","data","norm","band","out","c","BACKGROUND_FRAGMENT","PREFILTER_FRAGMENT","OUTLINE_MASK_FRAGMENT","OUTLINE_MAX","WIDEN","OUTLINE_WIDEN_FRAGMENT","OUTLINE_COMPOSITE_FRAGMENT","OUTLINE_COVER_FRAGMENT","SMOOTH","OUTLINE_SMOOTH_WIDEN_FRAGMENT","OUTLINE_SMOOTH_COMPOSITE_FRAGMENT","OUTLINE_MASK","OUTLINE_WIDEN","OUTLINE_COMPOSITE","OUTLINE_COVER","OUTLINE_SMOOTH_WIDEN","OUTLINE_SMOOTH_COMPOSITE","DEPTH","BACKGROUND","PREFILTER","ENV_WIDTH","ENV_LEVELS","Passes","gl","programs","resources","#environments","#shadow","#spotShadow","#pointShadow","#outline","texture","env","e","image","source","#floatTexture","prefiltered","program","framebuffer","level","hdr","blur","frame","tone","#depthTarget","#beginDepth","#drawDepth","shadow","#endDepth","toTexture","matrices","tiles","bias","half","spot","spots","toTile","#depthCube","point","face","item","casters","instances","defines","material","cutout","triplanar","viewProjection","map","targets","#outlineTargets","smooth","radius","outline","outlines","px","pixelRatio","attachment","target","widen","t","buffer","make","format","msaa","samples","size","framebuffers","levels"],"sources":["../src/irradiance.js"],"sourcesContent":["// Diffuse light from an environment image, for the renderer: computed once per environment.\n\n/** @import { DataImage } from './material/Texture.js' */\n\n/**\n * Diffuse light from an equirectangular environment, as nine spherical harmonic coefficients (RGB\n * each, flattened), scaled so the shader evaluates them with plain polynomial terms and gets\n * irradiance divided by PI - the light a white matte surface facing that way reflects.\n * @param {DataImage} image linear RGBA floats\n * @returns {Float32Array} 27 numbers\n */\nexport function irradianceSH({ data, width, height }) {\n const sh = new Float64Array(27)\n // sample a grid of at most 128 x 64 - plenty for the lowest frequencies\n const sx = Math.max(1, Math.floor(width / 128)), sy = Math.max(1, Math.floor(height / 64))\n const dPhi = (2 * Math.PI * sx) / width, dTheta = (Math.PI * sy) / height\n for (let y = 0; y < height; y += sy) {\n const theta = ((y + 0.5 * sy) / height) * Math.PI\n const sinT = Math.sin(theta), dy = Math.cos(theta)\n const weight = dPhi * dTheta * sinT\n for (let x = 0; x < width; x += sx) {\n const phi = ((x + 0.5 * sx) / width - 0.5) * 2 * Math.PI\n const dx = Math.cos(phi) * sinT, dz = Math.sin(phi) * sinT\n const basis = [1, dy, dz, dx, dx * dy, dy * dz, 3 * dz * dz - 1, dx * dz, dx * dx - dy * dy]\n const i = (y * width + x) * 4\n for (let k = 0; k < 9; k++) {\n const w = basis[k] * weight\n sh[k * 3] += data[i] * w\n sh[k * 3 + 1] += data[i + 1] * w\n sh[k * 3 + 2] += data[i + 2] * w\n }\n }\n }\n // projection uses the normalised basis Y = norm * polynomial, and evaluation multiplies by the\n // cosine lobe's band factors (PI, 2PI/3, PI/4) and norm again; then divide by PI\n const norm = [0.282095, 0.488603, 0.488603, 0.488603, 1.092548, 1.092548, 0.315392, 1.092548, 0.546274]\n const band = [1, 2 / 3, 2 / 3, 2 / 3, 1 / 4, 1 / 4, 1 / 4, 1 / 4, 1 / 4]\n const out = new Float32Array(27)\n for (let k = 0; k < 9; k++) {\n for (let c = 0; c < 3; c++) out[k * 3 + c] = sh[k * 3 + c] * norm[k] * norm[k] * band[k]\n }\n return out\n}\n"],"mappings":";;AAWA,SAAgBiB,EAAa,EAAE,SAAM,UAAO,aAAU;CACpD,IAAMC,oBAAK,IAAI,aAAa,EAAE,GAExBC,IAAK,KAAK,IAAI,GAAG,KAAK,MAAMC,IAAQ,GAAG,CAAC,GAAGC,IAAK,KAAK,IAAI,GAAG,KAAK,MAAMC,IAAS,EAAE,CAAC,GACnFC,IAAQ,IAAI,KAAK,KAAKJ,IAAMC,GAAOI,IAAU,KAAK,KAAKH,IAAMC;CACnE,KAAK,IAAIG,IAAI,GAAGA,IAAIH,GAAQ,KAAKD,GAAI;EACnC,IAAMK,KAAUD,IAAI,KAAMJ,KAAMC,IAAU,KAAK,IACzCK,IAAO,KAAK,IAAID,CAAK,GAAGE,IAAK,KAAK,IAAIF,CAAK,GAC3CG,IAASN,IAAOC,IAASG;EAC/B,KAAK,IAAIG,IAAI,GAAGA,IAAIV,GAAO,KAAKD,GAAI;GAClC,IAAMY,MAAQD,IAAI,KAAMX,KAAMC,IAAQ,MAAO,IAAI,KAAK,IAChDY,IAAK,KAAK,IAAID,CAAG,IAAIJ,GAAMM,IAAK,KAAK,IAAIF,CAAG,IAAIJ,GAChDO,IAAQ;IAAC;IAAGN;IAAIK;IAAID;IAAIA,IAAKJ;IAAIA,IAAKK;IAAI,IAAIA,IAAKA,IAAK;IAAGD,IAAKC;IAAID,IAAKA,IAAKJ,IAAKA;GAAE,GACrFO,KAAKV,IAAIL,IAAQU,KAAK;GAC5B,KAAK,IAAIM,IAAI,GAAGA,IAAI,GAAG,KAAK;IAC1B,IAAMC,IAAIH,EAAME,KAAKP;IAGrB,AAFA,EAAGO,IAAI,MAAME,EAAKH,KAAKE,GACvB,EAAGD,IAAI,IAAI,MAAME,EAAKH,IAAI,KAAKE,GAC/B,EAAGD,IAAI,IAAI,MAAME,EAAKH,IAAI,KAAKE;GACjC;EACF;CACF;CAGA,IAAME,IAAO;EAAC;EAAU;EAAU;EAAU;EAAU;EAAU;EAAU;EAAU;EAAU;CAAQ,GAChGC,IAAO;EAAC;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;EAAG,IAAI;CAAC,GACjEC,oBAAM,IAAI,aAAa,EAAE;CAC/B,KAAK,IAAIL,IAAI,GAAGA,IAAI,GAAG,KACrB,KAAK,IAAIM,IAAI,GAAGA,IAAI,GAAG,KAAK,EAAIN,IAAI,IAAIM,KAAKxB,EAAGkB,IAAI,IAAIM,KAAKH,EAAKH,KAAKG,EAAKH,KAAKI,EAAKJ;CAExF,OAAOK;AACT"}
|
package/dist/phoria2.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { j as AmbientLight,
|
|
1
|
+
export { j as AmbientLight, St as Box3, H as CAMERA_PRESETS, W as Camera, _t as Clip, vt as Color, r as Curve, M as DirectionalLight, X as Drawable, gt as Euler, R as Fog, lt as Geometry, rt as Group, N as HemisphereLight, Z as InstancedMesh, J as LIGHT_LIMITS, Q as Label, P as Light, $ as Lines, Ct as Mat4, ot as Material, et as Mesh, at as Node, Y as NullRenderer, q as OrbitControl, G as OrthographicCamera, K as PerspectiveCamera, Tt as PhoriaError, F as PointLight, tt as Points, ut as Quat, B as Ray, z as Raycaster, L as Scene, I as SpotLight, st as Texture, wt as Vec3, y as View, b as WebGLRenderer, c as axes, l as box, U as cameraPreset, t as create, i as curve, u as cylinder, d as edges, a as ellipse, f as extrude, o as fromPolygons, p as grid, m as icosphere, h as lathe, ct as loadTexture, s as outline, g as plane, n as registerType, _ as sphere, v as torus } from "./core.js";
|
package/dist/picking.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { D as e,
|
|
1
|
+
import { D as e, Ft as t, Lt as n, O as r, S as i, k as a, w as o } from "./core.js";
|
|
2
2
|
// src/webgl/picking.js
|
|
3
3
|
var s = 33, c = 4, l = `precision highp float;
|
|
4
4
|
precision highp int;
|
|
5
5
|
flat in uint vPickElement;
|
|
6
|
+
#ifdef SPRITES
|
|
7
|
+
in vec2 vUv;
|
|
8
|
+
#endif
|
|
6
9
|
#ifdef CLIP
|
|
7
10
|
in vec3 vWorldPosition;
|
|
8
11
|
#endif
|
|
@@ -15,13 +18,17 @@ void main() {
|
|
|
15
18
|
clipAway(vWorldPosition);
|
|
16
19
|
#endif
|
|
17
20
|
#ifdef ROUND
|
|
21
|
+
#ifdef SPRITES
|
|
22
|
+
vec2 p = vUv * 2.0 - 1.0;
|
|
23
|
+
#else
|
|
18
24
|
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
|
25
|
+
#endif
|
|
19
26
|
if (dot(p, p) > 1.0) discard;
|
|
20
27
|
#endif
|
|
21
28
|
pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
22
29
|
}
|
|
23
30
|
`, u = {
|
|
24
|
-
mesh: [
|
|
31
|
+
mesh: [r, l],
|
|
25
32
|
points: [a, l],
|
|
26
33
|
lines: [e, l]
|
|
27
34
|
}, d = class {
|
|
@@ -56,7 +63,7 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
|
56
63
|
setTimeout(t, 0);
|
|
57
64
|
});
|
|
58
65
|
}
|
|
59
|
-
#s(e,
|
|
66
|
+
#s(e, t, r, i, a, o) {
|
|
60
67
|
let c = this.gl, l = f(e);
|
|
61
68
|
if (!l.length) return null;
|
|
62
69
|
let u = this.#d();
|
|
@@ -66,7 +73,7 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
|
66
73
|
0,
|
|
67
74
|
0
|
|
68
75
|
]), c.clearBufferfv(c.DEPTH, 0, [1]);
|
|
69
|
-
let d = s, p =
|
|
76
|
+
let d = s, p = t / i * 2 - 1, m = 1 - r / a * 2, h = new Float32Array([
|
|
70
77
|
i / d,
|
|
71
78
|
0,
|
|
72
79
|
0,
|
|
@@ -84,7 +91,7 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
|
84
91
|
0,
|
|
85
92
|
1
|
|
86
93
|
]);
|
|
87
|
-
return
|
|
94
|
+
return n(this.#n, h, e.camera.projection), n(this.#r, this.#n, e.camera.view), l.forEach((t, n) => this.#u(t, n + 1, e, a, o)), c.bindVertexArray(null), {
|
|
88
95
|
items: l,
|
|
89
96
|
cx: p,
|
|
90
97
|
cy: m,
|
|
@@ -120,19 +127,28 @@ pickOut = uvec4(uPickId, vPickElement, floatBitsToUint(gl_FragCoord.z), 1u);
|
|
|
120
127
|
let e = this.gl;
|
|
121
128
|
return this.#a = e.createBuffer(), e.bindBuffer(e.PIXEL_PACK_BUFFER, this.#a), e.bufferData(e.PIXEL_PACK_BUFFER, this.#t.byteLength, e.STREAM_READ), e.bindBuffer(e.PIXEL_PACK_BUFFER, null), this.#a;
|
|
122
129
|
}
|
|
123
|
-
#u(e,
|
|
130
|
+
#u(e, n, r, i, a) {
|
|
124
131
|
let o = this.gl, { node: c, geometry: l, material: d, worldMatrix: f, instances: p } = e, m = c.drawMode, h = m === "points" ? "points" : m === "lines" ? "lines" : "mesh", g = ["PICK"];
|
|
125
|
-
p && g.push("INSTANCED")
|
|
126
|
-
let _ =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
o.
|
|
132
|
+
p && g.push("INSTANCED");
|
|
133
|
+
let _ = h === "points" && c.drawsSprites;
|
|
134
|
+
h === "points" && (d.sizeAttenuation && g.push("SIZE_ATTENUATION"), d.map || g.push("ROUND"), _ && g.push("SPRITES")), h === "mesh" && e.clip && g.push("CLIP");
|
|
135
|
+
let v = this.programs.ready(`pick-${h}`, g, u[h]);
|
|
136
|
+
if (o.useProgram(v.program), v.set("uView", r.camera.view), v.set("uProjection", this.#n), v.set("uViewProjection", this.#r), v.set("uModel", f), v.set("uPickId", n), v.set("uHighlightIndex", -2), e.clip && v.set("uClipPlane", e.clip.plane), h === "lines") {
|
|
137
|
+
o.disable(o.CULL_FACE), v.set("uResolution", [s, s]), v.set("uWidth", d.width * a), v.set("uNear", r.camera.near);
|
|
138
|
+
let e = c.arrowBits ?? 0;
|
|
139
|
+
v.set("uArrows", e), v.set("uArrowSize", c.arrowPixels?.(d.width) * a || 0);
|
|
140
|
+
let t = this.resources.lines(l);
|
|
141
|
+
o.bindVertexArray(t.vao), o.drawArraysInstanced(o.TRIANGLES, 0, e ? 12 : 6, Math.min(t.count, Math.floor(l.drawnCount / 2)));
|
|
131
142
|
} else if (h === "points") {
|
|
132
|
-
o.disable(o.CULL_FACE),
|
|
143
|
+
if (o.disable(o.CULL_FACE), v.set("uSize", d.sizeAttenuation ? d.size : d.size * a), v.set("uScale", r.camera.projection[5] * i / 2), _) {
|
|
144
|
+
v.set("uResolution", [s, s]);
|
|
145
|
+
let e = this.resources.sprites(l);
|
|
146
|
+
o.bindVertexArray(e.vao), o.drawArraysInstanced(o.TRIANGLE_STRIP, 0, 4, e.count);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
133
149
|
let e = this.resources.mesh(l, null, !1);
|
|
134
150
|
o.bindVertexArray(e.vao), o.drawArrays(o.POINTS, 0, l.drawnVertices);
|
|
135
|
-
} else
|
|
151
|
+
} else v.set("uNormalMatrix", this.#i), d.depthTest ? o.enable(o.DEPTH_TEST) : o.disable(o.DEPTH_TEST), d.doubleSided || e.clip?.cap ? o.disable(o.CULL_FACE) : (o.enable(o.CULL_FACE), o.cullFace(o.BACK), o.frontFace(t(f) < 0 ? o.CW : o.CCW)), this.resources.drawTriangles(l, p);
|
|
136
152
|
}
|
|
137
153
|
#d() {
|
|
138
154
|
if (this.#e) return this.#e;
|
package/dist/picking.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"picking.js","names":["mat4.determinant","LINES_VERTEX","
|
|
1
|
+
{"version":3,"file":"picking.js","names":["mat4.determinant","LINES_VERTEX","determinant","multiply","MESH_VERTEX","CLIP","POINTS_VERTEX","COMMON","REGION","TOLERANCE","PICK_FRAGMENT","SOURCES","Picker","gl","programs","resources","#target","#pack","#waiting","region","#drawRegion","frame","x","y","width","height","pixelRatio","#pixels","#decode","buffer","#readBuffer","fence","poll","resolve","items","pickable","target","#ensureTarget","s","cx","cy","zoom","#projection","#viewProjection","#draw","item","i","p","at","py","px","best","bestX","bestY","bestD","dy","dx","d","o","id","depth","ndc","mode","node","kind","defines","instances","sprites","material","program","worldMatrix","arrows","buffers","geometry","#normal","color","framebuffer","seen","ok","n","v"],"sources":[],"mappings":""}
|
package/dist/snapshot.js
CHANGED
package/dist/tooltip.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jt as e, kt as t } from "./core.js";
|
|
2
2
|
// src/tooltip.js
|
|
3
3
|
var n = "\n:where(.phoria-tooltip) { position: fixed; left: 0; top: 0; z-index: 1000; pointer-events: none; max-width: 320px;\n padding: 6px 10px; border-radius: 6px; background: rgba(20, 20, 24, 0.92); color: #fff;\n font: 12px/1.45 system-ui, sans-serif; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); white-space: pre-line; }\n:where(.phoria-tooltip-title) { font-weight: 600; }\n:where(.phoria-tooltip-rows) { display: grid; grid-template-columns: auto auto; column-gap: 12px; }\n:where(.phoria-tooltip-rows > :nth-child(odd)) { opacity: 0.7; }\n:where(.phoria-tooltip-rows > :nth-child(even)) { text-align: right; font-variant-numeric: tabular-nums; }\n", r = 14, i = class {
|
|
4
4
|
el;
|
package/dist/types/View.d.ts
CHANGED
|
@@ -339,6 +339,17 @@ export declare class View {
|
|
|
339
339
|
width: number;
|
|
340
340
|
height: number;
|
|
341
341
|
} | null;
|
|
342
|
+
/**
|
|
343
|
+
* Everything inside an area of the canvas, nearest first: a box `{ x, y, width, height }` or a
|
|
344
|
+
* lasso of `[x, y]` points, CSS pixels from the canvas's top left. Each point, instance and line
|
|
345
|
+
* segment is its own hit, found by where it is (a mesh by its middle) - hidden behind others or
|
|
346
|
+
* not - so a box dragged over a scatter selects that region of it.
|
|
347
|
+
*
|
|
348
|
+
* const picked = view.pickArea({ x: 40, y: 30, width: 200, height: 120 }).map((h) => h.datum)
|
|
349
|
+
* @param {import('./area.js').PickArea} area
|
|
350
|
+
* @returns {Hit[]}
|
|
351
|
+
*/
|
|
352
|
+
pickArea(area: import('./area.js').PickArea): Hit[];
|
|
342
353
|
/** Every hit under a canvas position, nearest first. @param {number | MouseEvent} x @param {number} [y] @returns {Hit[]} */
|
|
343
354
|
pickAll(x: number | MouseEvent, y?: number): Hit[];
|
|
344
355
|
/**
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Hit } from './Raycaster.js';
|
|
2
|
+
import type { Node } from './scene/Node.js';
|
|
3
|
+
import type { Camera } from './scene/cameras.js';
|
|
4
|
+
export type PickArea = {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
} | [number, number][];
|
|
10
|
+
/**
|
|
11
|
+
* Everything in the scene the camera shows, pickable, whose place falls inside the area - nearest
|
|
12
|
+
* the camera first.
|
|
13
|
+
* @param {Node} scene @param {Camera} camera @param {Float32Array} viewProjection
|
|
14
|
+
* @param {number} width @param {number} height the canvas, CSS pixels
|
|
15
|
+
* @param {PickArea} area
|
|
16
|
+
* @returns {Hit[]}
|
|
17
|
+
*/
|
|
18
|
+
export declare function pickArea(scene: Node, camera: Camera, viewProjection: Float32Array, width: number, height: number, area: PickArea): Hit[];
|
|
@@ -21,6 +21,19 @@ export type GeometryOptions = {
|
|
|
21
21
|
* rgb per vertex, sRGB 0-1 like every colour input
|
|
22
22
|
*/
|
|
23
23
|
colors?: ArrayLike<number>;
|
|
24
|
+
/**
|
|
25
|
+
* points: one per vertex, a multiple of the material's size
|
|
26
|
+
*/
|
|
27
|
+
sizes?: ArrayLike<number>;
|
|
28
|
+
/**
|
|
29
|
+
* points: one per vertex, 0-1, multiplying the material's opacity
|
|
30
|
+
*/
|
|
31
|
+
opacities?: ArrayLike<number>;
|
|
32
|
+
/**
|
|
33
|
+
* points: one per vertex, radians anticlockwise on screen - turns
|
|
34
|
+
* each point's texture
|
|
35
|
+
*/
|
|
36
|
+
angles?: ArrayLike<number>;
|
|
24
37
|
/**
|
|
25
38
|
* vertex indices; triangles for meshes, pairs for lines
|
|
26
39
|
*/
|
|
@@ -38,6 +51,9 @@ export declare class Geometry {
|
|
|
38
51
|
/** @type {Float32Array | null} */ uvs: Float32Array | null;
|
|
39
52
|
/** A second UV set, for baked occlusion and light maps. @type {Float32Array | null} */ uvs2: Float32Array | null;
|
|
40
53
|
/** @type {Float32Array | null} */ colors: Float32Array | null;
|
|
54
|
+
/** Points: each one's size, as a multiple of the material's. @type {Float32Array | null} */ sizes: Float32Array | null;
|
|
55
|
+
/** Points: each one's opacity, 0-1, times the material's. @type {Float32Array | null} */ opacities: Float32Array | null;
|
|
56
|
+
/** Points: each one's turn on screen, in radians anticlockwise. @type {Float32Array | null} */ angles: Float32Array | null;
|
|
41
57
|
/** @type {Uint16Array | Uint32Array | null} */ indices: Uint16Array | Uint32Array | null;
|
|
42
58
|
/**
|
|
43
59
|
* Draw only the first `drawCount` vertices (indices, when indexed), or all of them when null -
|
package/dist/types/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type RenderMode = import('./View.js').RenderMode;
|
|
|
37
37
|
export type CameraPreset = import('./presets.js').CameraPreset;
|
|
38
38
|
export type TooltipContent = import('./tooltip.js').TooltipContent;
|
|
39
39
|
export type Hit = import('./Raycaster.js').Hit;
|
|
40
|
+
export type PickArea = import('./area.js').PickArea;
|
|
40
41
|
export type LabelPlacement = import('./scene/drawables.js').LabelPlacement;
|
|
41
42
|
export type LabelPlace = import('./scene/drawables.js').LabelPlace;
|
|
42
43
|
export type InstanceLabels = import('./scene/drawables.js').InstanceLabels;
|
|
@@ -49,6 +50,7 @@ export type ImageOptions = import('./snapshot.js').ImageOptions;
|
|
|
49
50
|
/** @typedef {import('./presets.js').CameraPreset} CameraPreset */
|
|
50
51
|
/** @typedef {import('./tooltip.js').TooltipContent} TooltipContent */
|
|
51
52
|
/** @typedef {import('./Raycaster.js').Hit} Hit */
|
|
53
|
+
/** @typedef {import('./area.js').PickArea} PickArea */
|
|
52
54
|
/** @typedef {import('./scene/drawables.js').LabelPlacement} LabelPlacement */
|
|
53
55
|
/** @typedef {import('./scene/drawables.js').LabelPlace} LabelPlace */
|
|
54
56
|
/** @typedef {import('./scene/drawables.js').InstanceLabels} InstanceLabels */
|
|
@@ -211,6 +211,18 @@ export type MaterialOptions = {
|
|
|
211
211
|
* Lines: width in pixels (default 1)
|
|
212
212
|
*/
|
|
213
213
|
width?: number;
|
|
214
|
+
/**
|
|
215
|
+
* Lines: dashed, with dashes this long in the model's units; 0 is solid (default 0)
|
|
216
|
+
*/
|
|
217
|
+
dash?: number;
|
|
218
|
+
/**
|
|
219
|
+
* Lines: the gaps between dashes; 0 is as long as the dashes (default 0)
|
|
220
|
+
*/
|
|
221
|
+
gap?: number;
|
|
222
|
+
/**
|
|
223
|
+
* Lines: moves the dashes along the lines - animate it for flow (default 0)
|
|
224
|
+
*/
|
|
225
|
+
dashOffset?: number;
|
|
214
226
|
};
|
|
215
227
|
/**
|
|
216
228
|
* The one material: what a surface looks like. Plain objects passed as a `material` become
|
|
@@ -275,6 +287,9 @@ export declare class Material {
|
|
|
275
287
|
/** @type {number} */ size: number;
|
|
276
288
|
/** @type {boolean} */ sizeAttenuation: boolean;
|
|
277
289
|
/** @type {number} */ width: number;
|
|
290
|
+
/** Lines: dash length in the model's units; 0 is solid. @type {number} */ dash: number;
|
|
291
|
+
/** Lines: gap length; 0 is the dash's. @type {number} */ gap: number;
|
|
292
|
+
/** Lines: how far the dashes have moved along. @type {number} */ dashOffset: number;
|
|
278
293
|
/** @param {MaterialOptions} [options] */
|
|
279
294
|
constructor(options?: MaterialOptions);
|
|
280
295
|
/** Base colour; assign any colour input. @returns {Color} */
|
|
@@ -64,9 +64,15 @@ export type TextureOptions = {
|
|
|
64
64
|
*/
|
|
65
65
|
flipY?: boolean;
|
|
66
66
|
};
|
|
67
|
+
export type TextureRegion = {
|
|
68
|
+
x: number;
|
|
69
|
+
y: number;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
};
|
|
67
73
|
/**
|
|
68
74
|
* An image for a material map. A video's frames upload by themselves as it plays; after drawing on
|
|
69
|
-
* a canvas, call `needsUpdate()
|
|
75
|
+
* a canvas, call `needsUpdate()` - with the rectangle drawn in, to send only that.
|
|
70
76
|
*/
|
|
71
77
|
export declare class Texture {
|
|
72
78
|
#private;
|
|
@@ -114,7 +120,17 @@ export declare class Texture {
|
|
|
114
120
|
/** 'srgb' for colour (decoded to linear by the GPU), 'linear' for data such as normals and roughness. Call needsUpdate() after changing it. @returns {'srgb' | 'linear'} */
|
|
115
121
|
get colorSpace(): 'srgb' | 'linear';
|
|
116
122
|
set colorSpace(v: 'srgb' | 'linear');
|
|
117
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Upload the image again, after drawing on it (a canvas) or changing its pixels. Give the
|
|
125
|
+
* rectangle that changed - in the image's pixels, from its top left - and only that is sent: for a
|
|
126
|
+
* big canvas drawn on a little at a time, far less to send each frame.
|
|
127
|
+
*
|
|
128
|
+
* ctx.fillRect(x, y, 20, 20)
|
|
129
|
+
* texture.needsUpdate({ x, y, width: 20, height: 20 })
|
|
130
|
+
* @param {TextureRegion} [region]
|
|
131
|
+
* @returns {this}
|
|
132
|
+
*/
|
|
133
|
+
needsUpdate(region?: TextureRegion): this;
|
|
118
134
|
/** Release the GPU copy. @returns {void} */
|
|
119
135
|
dispose(): void;
|
|
120
136
|
/** A description (url plus options that differ from the defaults), or null for an in-memory image. */
|