phoria2 1.0.0 → 1.1.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 (46) hide show
  1. package/README.md +6 -6
  2. package/dist/bloom.js +4 -4
  3. package/dist/bloom.js.map +1 -1
  4. package/dist/charts.js +377 -377
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.js +1052 -558
  7. package/dist/core.js.map +1 -1
  8. package/dist/edit.js +48 -48
  9. package/dist/edit.js.map +1 -1
  10. package/dist/explain.js +1 -1
  11. package/dist/gltf.js +207 -162
  12. package/dist/gltf.js.map +1 -1
  13. package/dist/motion.js +27 -27
  14. package/dist/motion.js.map +1 -1
  15. package/dist/particles.js +50 -50
  16. package/dist/particles.js.map +1 -1
  17. package/dist/passes.js +199 -97
  18. package/dist/passes.js.map +1 -1
  19. package/dist/phoria2.js +1 -1
  20. package/dist/picking.js +16 -21
  21. package/dist/picking.js.map +1 -1
  22. package/dist/snapshot.js +1 -1
  23. package/dist/tooltip.js +3 -3
  24. package/dist/tooltip.js.map +1 -1
  25. package/dist/types/View.d.ts +5 -0
  26. package/dist/types/geometry/Geometry.d.ts +6 -0
  27. package/dist/types/material/Material.d.ts +125 -0
  28. package/dist/types/material/Texture.d.ts +37 -5
  29. package/dist/types/webgl/WebGLRenderer.d.ts +6 -0
  30. package/dist/types/webgl/passes.d.ts +37 -13
  31. package/dist/types/webgl/resources.d.ts +24 -2
  32. package/dist/types/webgl/shaders.d.ts +4 -2
  33. package/llms-full.txt +96 -7
  34. package/package.json +1 -1
  35. package/schema/scene.schema.json +127 -0
  36. package/src/View.js +4 -2
  37. package/src/geometry/Geometry.js +8 -1
  38. package/src/gltf/load.js +50 -6
  39. package/src/material/Material.js +104 -19
  40. package/src/material/Texture.js +116 -11
  41. package/src/watch.js +19 -0
  42. package/src/webgl/WebGLRenderer.js +51 -12
  43. package/src/webgl/passes.js +190 -32
  44. package/src/webgl/picking.js +0 -1
  45. package/src/webgl/resources.js +89 -8
  46. package/src/webgl/shaders.js +350 -48
package/dist/edit.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as e, Dt as t, Et as n, St as r, Tt as i, at as a, kt as o, nt as s, u as c, v as l, wt as u } from "./core.js";
1
+ import { At as e, C as t, Ct as n, Dt as r, Et as i, Ot as a, Tt as o, ot as s, rt as c, u as l, v as u } from "./core.js";
2
2
  // src/edit/TransformHandle.js
3
3
  var d = [
4
4
  "view",
@@ -26,7 +26,7 @@ var d = [
26
26
  ring: "#ffcc33",
27
27
  disc: "#f2f2f2"
28
28
  }, m = 1e5, h = class {
29
- node = new s({
29
+ node = new c({
30
30
  name: "transform-handle",
31
31
  highlight: "#ffffff"
32
32
  });
@@ -43,23 +43,23 @@ var d = [
43
43
  #n = null;
44
44
  #r = {};
45
45
  #i = null;
46
- constructor(e) {
47
- if (n("TransformHandle", e, d, f), e?.view?.scene || o("TransformHandle", "'view' is required - the View it is used in"), this.#e = e.view, e.move !== void 0 && (this.move = e.move !== !1 && i("TransformHandle", "move", e.move, ["xz", "xyz"])), e.turn !== void 0 && (this.turn = _("turn", e.turn)), e.dragTarget !== void 0 && (this.dragTarget = _("dragTarget", e.dragTarget)), e.size !== void 0 && (this.size = u("TransformHandle", "size", e.size), this.size > 0 || o("TransformHandle", `'size' is in pixels, above 0, got ${this.size}`)), e.snap != null) {
48
- n("TransformHandle", e.snap, ["move", "turn"], {}, "snap");
49
- let r = (n) => {
50
- let r = e.snap?.[n];
51
- return r === void 0 ? 0 : (typeof r == "number" && r >= 0 || o("TransformHandle", `'snap.${n}' must be a step of 0 or more, got ${t(r)}`), r);
46
+ constructor(t) {
47
+ if (r("TransformHandle", t, d, f), t?.view?.scene || e("TransformHandle", "'view' is required - the View it is used in"), this.#e = t.view, t.move !== void 0 && (this.move = t.move !== !1 && i("TransformHandle", "move", t.move, ["xz", "xyz"])), t.turn !== void 0 && (this.turn = _("turn", t.turn)), t.dragTarget !== void 0 && (this.dragTarget = _("dragTarget", t.dragTarget)), t.size !== void 0 && (this.size = o("TransformHandle", "size", t.size), this.size > 0 || e("TransformHandle", `'size' is in pixels, above 0, got ${this.size}`)), t.snap != null) {
48
+ r("TransformHandle", t.snap, ["move", "turn"], {}, "snap");
49
+ let n = (n) => {
50
+ let r = t.snap?.[n];
51
+ return r === void 0 ? 0 : (typeof r == "number" && r >= 0 || e("TransformHandle", `'snap.${n}' must be a step of 0 or more, got ${a(r)}`), r);
52
52
  };
53
53
  this.snap = {
54
- move: r("move"),
55
- turn: r("turn")
54
+ move: n("move"),
55
+ turn: n("turn")
56
56
  };
57
57
  }
58
58
  for (let n of ["constrain", "onChange"]) {
59
- let r = e[n];
60
- r != null && (typeof r != "function" && o("TransformHandle", `'${n}' must be a function, got ${t(r)}`), this[n] = r);
59
+ let r = t[n];
60
+ r != null && (typeof r != "function" && e("TransformHandle", `'${n}' must be a function, got ${a(r)}`), this[n] = r);
61
61
  }
62
- e.history != null && (typeof e.history.transform != "function" && o("TransformHandle", `'history' must be a History, got ${t(e.history)}`), this.history = e.history), this.#a(), this.node.visible = !1, this.node.onUpdate(() => this.#o()), this.#e.scene.add(this.node), e.target != null && (this.target = e.target);
62
+ t.history != null && (typeof t.history.transform != "function" && e("TransformHandle", `'history' must be a History, got ${a(t.history)}`), this.history = t.history), this.#a(), this.node.visible = !1, this.node.onUpdate(() => this.#o()), this.#e.scene.add(this.node), t.target != null && (this.target = t.target);
63
63
  }
64
64
  get target() {
65
65
  return this.#t;
@@ -74,7 +74,7 @@ var d = [
74
74
  this.target = null, this.node.removeFromParent();
75
75
  }
76
76
  #a() {
77
- let t = (e) => ({
77
+ let e = (e) => ({
78
78
  color: e,
79
79
  unlit: !0,
80
80
  depthTest: !1,
@@ -85,17 +85,17 @@ var d = [
85
85
  0,
86
86
  0
87
87
  ], o = n) => {
88
- let s = this.node.add(new e({
88
+ let s = this.node.add(new t({
89
89
  name: n,
90
90
  geometry: r,
91
- material: t(i),
91
+ material: e(i),
92
92
  position: a,
93
93
  renderOrder: m,
94
94
  castShadow: !1
95
95
  }));
96
96
  return s.onDrag = (e) => this.#s(o, e), this.#r[n] = s, s;
97
97
  };
98
- n("ring", l({
98
+ n("ring", u({
99
99
  radius: 1,
100
100
  tube: .035,
101
101
  segments: 72,
@@ -115,12 +115,12 @@ var d = [
115
115
  0,
116
116
  t
117
117
  ];
118
- n(`${e}-shaft`, c({
118
+ n(`${e}-shaft`, l({
119
119
  radius: .035,
120
120
  height: .4,
121
121
  segments: 10,
122
122
  axis: e
123
- }), t, r(1.3), e), n(`${e}-tip`, c({
123
+ }), t, r(1.3), e), n(`${e}-tip`, l({
124
124
  radius: .1,
125
125
  radiusTop: 0,
126
126
  height: .22,
@@ -128,7 +128,7 @@ var d = [
128
128
  axis: e
129
129
  }), t, r(1.6), e);
130
130
  };
131
- r("x", p.x), r("z", p.z), r("y", p.y), n("disc", c({
131
+ r("x", p.x), r("z", p.z), r("y", p.y), n("disc", l({
132
132
  radius: .16,
133
133
  height: .02,
134
134
  segments: 32
@@ -152,44 +152,44 @@ var d = [
152
152
  this.node.scale.set(c, c, c);
153
153
  }
154
154
  #s(e, t) {
155
- let n = this.#t;
156
- if (!n) return;
155
+ let r = this.#t;
156
+ if (!r) return;
157
157
  if (t.phase === "start") {
158
- let i = n.worldPosition;
158
+ let i = r.worldPosition;
159
159
  if (e === "y") {
160
- let e = this.#e.camera.position, n = new r(e[0] - i[0], 0, e[2] - i[2]);
160
+ let e = this.#e.camera.position, r = new n(e[0] - i[0], 0, e[2] - i[2]);
161
161
  t.plane = {
162
- normal: n.len() > 1e-6 ? n.normalize() : new r(0, 0, 1),
162
+ normal: r.len() > 1e-6 ? r.normalize() : new n(0, 0, 1),
163
163
  point: i.clone()
164
164
  };
165
165
  } else t.plane = {
166
- normal: new r(0, 1, 0),
166
+ normal: new n(0, 1, 0),
167
167
  point: i.clone()
168
168
  };
169
169
  let a = t.ray.intersectPlane(t.plane.normal, t.plane.point), o = a === null ? i : t.ray.at(a);
170
170
  this.#i = {
171
- position: n.position.clone(),
172
- rotation: g(n.quaternion),
173
- quaternion: n.quaternion.clone(),
171
+ position: r.position.clone(),
172
+ rotation: g(r.quaternion),
173
+ quaternion: r.quaternion.clone(),
174
174
  angle: Math.atan2(o[2] - i[2], o[0] - i[0]),
175
- done: this.history ? this.history.transform(n) : null
176
- }, this.onChange?.(n, "start");
175
+ done: this.history ? this.history.transform(r) : null
176
+ }, this.onChange?.(r, "start");
177
177
  return;
178
178
  }
179
179
  let i = this.#i;
180
180
  if (!i) return;
181
181
  if (t.phase === "end") {
182
- i.done?.(e === "ring" ? "Turn" : "Move"), this.#i = null, this.onChange?.(n, "end");
182
+ i.done?.(e === "ring" ? "Turn" : "Move"), this.#i = null, this.onChange?.(r, "end");
183
183
  return;
184
184
  }
185
185
  if (!t.point) return;
186
- let o = {
186
+ let a = {
187
187
  position: i.position.clone(),
188
188
  rotation: i.rotation
189
189
  };
190
190
  if (e === "ring") {
191
191
  let e = this.node.position, n = Math.atan2(t.point[2] - e[2], t.point[0] - e[0]), r = i.rotation - (n - i.angle);
192
- this.snap?.turn && (r = Math.round(r / this.snap.turn) * this.snap.turn), o.rotation = r;
192
+ this.snap?.turn && (r = Math.round(r / this.snap.turn) * this.snap.turn), a.rotation = r;
193
193
  } else {
194
194
  let n = t.delta, r = e === "x" ? [
195
195
  n[0],
@@ -208,28 +208,28 @@ var d = [
208
208
  0,
209
209
  n[2]
210
210
  ];
211
- o.position.add(r);
211
+ a.position.add(r);
212
212
  let i = this.snap?.move;
213
- if (i) for (let e = 0; e < 3; e++) r[e] !== 0 && (o.position[e] = Math.round(o.position[e] / i) * i);
213
+ if (i) for (let e = 0; e < 3; e++) r[e] !== 0 && (a.position[e] = Math.round(a.position[e] / i) * i);
214
214
  }
215
- let s = this.constrain ? this.constrain(o, n) : o;
216
- if (s === null) return;
217
- let c = s ?? o;
218
- n.position.copy(c.position);
219
- let l = new a().setFromAxisAngle([
215
+ let o = this.constrain ? this.constrain(a, r) : a;
216
+ if (o === null) return;
217
+ let c = o ?? a;
218
+ r.position.copy(c.position);
219
+ let l = new s().setFromAxisAngle([
220
220
  0,
221
221
  1,
222
222
  0
223
223
  ], c.rotation - i.rotation);
224
- n.quaternion.copy(l.multiply(i.quaternion)), this.#o(), this.onChange?.(n, "move");
224
+ r.quaternion.copy(l.multiply(i.quaternion)), this.#o(), this.onChange?.(r, "move");
225
225
  }
226
226
  };
227
227
  function g(e) {
228
228
  let [t, n, r, i] = e;
229
229
  return Math.atan2(-2 * (t * r - n * i), 1 - 2 * (n * n + r * r));
230
230
  }
231
- function _(e, n) {
232
- return typeof n != "boolean" && o("TransformHandle", `'${e}' must be true or false, got ${t(n)}`), n;
231
+ function _(t, n) {
232
+ return typeof n != "boolean" && e("TransformHandle", `'${t}' must be true or false, got ${a(n)}`), n;
233
233
  }
234
234
  // src/edit/History.js
235
235
  var v = ["limit", "onChange"], y = {
@@ -241,11 +241,11 @@ var v = ["limit", "onChange"], y = {
241
241
  onChange = null;
242
242
  #e = [];
243
243
  #t = [];
244
- constructor(e = {}) {
245
- n("History", e, v, y), e.limit !== void 0 && ((!Number.isInteger(e.limit) || e.limit < 1) && o("History", `'limit' must be a whole number 1 or more, got ${t(e.limit)}`), this.limit = e.limit), e.onChange != null && (typeof e.onChange != "function" && o("History", `'onChange' must be a function, got ${t(e.onChange)}`), this.onChange = e.onChange);
244
+ constructor(t = {}) {
245
+ r("History", t, v, y), t.limit !== void 0 && ((!Number.isInteger(t.limit) || t.limit < 1) && e("History", `'limit' must be a whole number 1 or more, got ${a(t.limit)}`), this.limit = t.limit), t.onChange != null && (typeof t.onChange != "function" && e("History", `'onChange' must be a function, got ${a(t.onChange)}`), this.onChange = t.onChange);
246
246
  }
247
- push(e) {
248
- return e && typeof e.undo == "function" && typeof e.redo == "function" || o("History", `push() takes { label, undo, redo } with undo and redo functions, got ${t(e)}`), this.#e.push(e), this.#e.length > this.limit && this.#e.shift(), this.#t = [], this.onChange?.(this), this;
247
+ push(t) {
248
+ return t && typeof t.undo == "function" && typeof t.redo == "function" || e("History", `push() takes { label, undo, redo } with undo and redo functions, got ${a(t)}`), this.#e.push(t), this.#e.length > this.limit && this.#e.shift(), this.#t = [], this.onChange?.(this), this;
249
249
  }
250
250
  undo() {
251
251
  let e = this.#e.pop();
package/dist/edit.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"edit.js","names":["Mesh","describe","checkOptions","Vec3","checkOneOf","Quat","fail","Group","cylinder","torus","checkNumber","HANDLE_OPTIONS","HANDLE_HINTS","COLOURS","ON_TOP","TransformHandle","options","#view","bool","step","v","k","key","fn","#build","#follow","#target","node","#taken","#drag","drag","material","add","name","mesh","color","part","#parts","arrow","along","axis","d","target","at","view","cam","height","perPixel","box","size","r","center","eye","facing","start","p","#from","yawOf","from","proposal","angle","turn","move","result","placed","turned","x","y","z","w","q","HISTORY_OPTIONS","HINTS","History","entry","#done","#undone","list","nodes","before","placeOf","label","after","i","finalLabel","place","n"],"sources":["../src/edit/TransformHandle.js","../src/edit/History.js"],"sourcesContent":["// A move and turn handle for editors: arrows to slide what is selected along an axis, a disc to\n// slide it freely across the floor, and a ring round it to turn it - drawn over everything, the\n// same size on screen however far away, snapping to a grid and to angles, and recording undo.\nimport { Group } from '../scene/Node.js'\nimport { Mesh } from '../scene/drawables.js'\nimport { Quat } from '../value/Quat.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { cylinder, torus } from '../geometry/generators.js'\nimport { checkNumber, checkOneOf, checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node, Drag } from '../scene/Node.js' */\n/** @import { History } from './History.js' */\n/** @import { View } from '../View.js' */\n\n/**\n * A move or turn the handle proposes, for constrain() to check: where the target would be, and\n * its turn about the vertical.\n * @typedef {{ position: Vec3, rotation: number }} Placement\n */\n\n/**\n * @typedef {object} TransformHandleOptions\n * @property {View} view the view it is used in - it adds itself to the view's scene\n * @property {Node | null} [target] what it moves; change it later with handle.target\n * @property {'xz' | 'xyz' | false} [move] which ways it moves things: across the floor (default), up and down too, or not at all\n * @property {boolean} [turn] a ring to turn things about the vertical (default true)\n * @property {{ move?: number, turn?: number } | null} [snap] snap positions to a grid of this spacing, and turns to steps of\n * this many radians (default none)\n * @property {number} [size] how big it is on screen, in pixels across its ring (default 120); it grows to go round a big target\n * @property {boolean} [dragTarget] drag the target itself across the floor too, as well as by the handle (default true)\n * @property {History | null} [history] record each move and turn here, for undo (default none)\n * @property {((placement: Placement, target: Node) => Placement | null | void) | null} [constrain] check a move or turn before it\n * happens: return a changed placement (kept inside a room, out of other things), null to refuse it, or nothing to allow it\n * @property {((target: Node, phase: 'start' | 'move' | 'end') => void) | null} [onChange] told as the target is moved or turned\n */\n\nexport const HANDLE_OPTIONS = ['view', 'target', 'move', 'turn', 'snap', 'size', 'dragTarget', 'history', 'constrain', 'onChange']\nexport const HANDLE_HINTS = {\n object: \"use 'target'\",\n mode: \"use 'move' ('xz', 'xyz' or false) and 'turn' (true or false)\",\n rotate: \"use 'turn' - a ring for turning about the vertical\",\n translate: \"use 'move': 'xz', 'xyz' or false\",\n translationSnap: \"use snap: { move, turn }\",\n rotationSnap: \"use snap: { move, turn }\",\n onDrag: \"use 'onChange': (target, phase) => ...\",\n}\n\nconst COLOURS = { x: '#ff5a52', y: '#5ad07a', z: '#4f8cff', ring: '#ffcc33', disc: '#f2f2f2' }\n// drawn after everything, over it\nconst ON_TOP = 100000\n\nexport class TransformHandle {\n /** The handle's own nodes, in the view's scene. */\n node = new Group({ name: 'transform-handle', highlight: '#ffffff' })\n /** @type {'xz' | 'xyz' | false} */ move = 'xz'\n turn = true\n /** @type {{ move: number, turn: number } | null} */ snap = null\n size = 120\n dragTarget = true\n /** @type {History | null} */ history = null\n /** @type {((placement: Placement, target: Node) => Placement | null | void) | null} */ constrain = null\n /** @type {((target: Node, phase: 'start' | 'move' | 'end') => void) | null} */ onChange = null\n\n /** @type {View} */ #view\n /** @type {Node | null} */ #target = null\n /** The node whose onDrag the handle took over, and that onDrag, to give back. @type {{ node: Node, onDrag: ((drag: Drag) => void) | null } | null} */ #taken = null\n /** @type {Record<string, Mesh>} */ #parts = {}\n /** Where the target was as a drag began, and the undo it will record. @type {{ position: Vec3, rotation: number, quaternion: Quat, angle: number, done: ((label?: string) => boolean) | null } | null} */ #from = null\n\n /** @param {TransformHandleOptions} options */\n constructor(options) {\n checkOptions('TransformHandle', options, HANDLE_OPTIONS, HANDLE_HINTS)\n if (!options?.view?.scene) fail('TransformHandle', \"'view' is required - the View it is used in\")\n this.#view = options.view\n if (options.move !== undefined) this.move = options.move === false ? false : /** @type {'xz' | 'xyz'} */ (checkOneOf('TransformHandle', 'move', options.move, ['xz', 'xyz']))\n if (options.turn !== undefined) this.turn = bool('turn', options.turn)\n if (options.dragTarget !== undefined) this.dragTarget = bool('dragTarget', options.dragTarget)\n if (options.size !== undefined) {\n this.size = checkNumber('TransformHandle', 'size', options.size)\n if (!(this.size > 0)) fail('TransformHandle', `'size' is in pixels, above 0, got ${this.size}`)\n }\n if (options.snap != null) {\n checkOptions('TransformHandle', options.snap, ['move', 'turn'], {}, 'snap')\n const step = (k) => {\n const v = options.snap?.[k]\n if (v === undefined) return 0\n if (typeof v !== 'number' || !(v >= 0)) fail('TransformHandle', `'snap.${k}' must be a step of 0 or more, got ${describe(v)}`)\n return v\n }\n this.snap = { move: step('move'), turn: step('turn') }\n }\n for (const key of ['constrain', 'onChange']) {\n const fn = options[key]\n if (fn == null) continue\n if (typeof fn !== 'function') fail('TransformHandle', `'${key}' must be a function, got ${describe(fn)}`)\n this[key] = fn\n }\n if (options.history != null) {\n if (typeof options.history.transform !== 'function') fail('TransformHandle', `'history' must be a History, got ${describe(options.history)}`)\n this.history = options.history\n }\n this.#build()\n this.node.visible = false\n this.node.onUpdate(() => this.#follow())\n this.#view.scene.add(this.node)\n if (options.target != null) this.target = options.target\n }\n\n /** What the handle moves, or null to hide it. @returns {Node | null} */\n get target() { return this.#target }\n /** @param {Node | null} node */\n set target(node) {\n if (node === this.#target) return\n // give back the old target's own drag - whatever dragTarget says now\n if (this.#taken) this.#taken.node.onDrag = this.#taken.onDrag\n this.#taken = null\n this.#target = node ?? null\n this.node.visible = !!node\n if (node && this.dragTarget) {\n this.#taken = { node, onDrag: node.onDrag }\n node.onDrag = (drag) => this.#drag('free', drag)\n }\n if (node) this.#follow()\n this.#view.invalidate()\n }\n\n /** Take the handle out of the scene, giving the target back its own drag. */\n dispose() {\n this.target = null\n this.node.removeFromParent()\n }\n\n #build() {\n const material = (color) => ({ color, unlit: true, depthTest: false, depthWrite: false, opacity: 0.95 })\n /** @param {string} name @param {any} geometry @param {string} color @param {number[]} [position] @param {any} [part] */\n const add = (name, geometry, color, position = [0, 0, 0], part = name) => {\n const mesh = this.node.add(new Mesh({ name, geometry, material: material(color), position, renderOrder: ON_TOP, castShadow: false }))\n mesh.onDrag = (drag) => this.#drag(part, drag)\n this.#parts[name] = mesh\n return mesh\n }\n // the ring round it, for turning\n add('ring', torus({ radius: 1, tube: 0.035, segments: 72, tubeSegments: 8 }), COLOURS.ring)\n // arrows out beyond the ring along each way it moves, and a disc in the middle to move it freely\n const arrow = (axis, color) => {\n const along = (d) => /** @type {number[]} */ (axis === 'x' ? [d, 0, 0] : axis === 'y' ? [0, d, 0] : [0, 0, d])\n add(`${axis}-shaft`, cylinder({ radius: 0.035, height: 0.4, segments: 10, axis }), color, along(1.3), axis)\n add(`${axis}-tip`, cylinder({ radius: 0.1, radiusTop: 0, height: 0.22, segments: 16, axis }), color, along(1.6), axis)\n }\n arrow('x', COLOURS.x)\n arrow('z', COLOURS.z)\n arrow('y', COLOURS.y)\n add('disc', cylinder({ radius: 0.16, height: 0.02, segments: 32 }), COLOURS.disc, [0, 0, 0], 'free')\n }\n\n // sit on the target, the same size on screen at any distance - but always round it\n #follow() {\n const target = this.#target\n if (!target) return\n for (const [name, mesh] of Object.entries(this.#parts)) {\n const axis = name[0]\n mesh.visible = name === 'ring' ? this.turn : name === 'disc' ? !!this.move : !!this.move && (axis !== 'y' || this.move === 'xyz')\n }\n target.updateWorld()\n const at = target.worldPosition\n this.node.position.copy(at)\n const view = this.#view, cam = /** @type {any} */ (view.camera)\n const height = view.canvas.clientHeight || 1\n const perPixel = cam.constructor.type === 'orthographic-camera' ? cam.height / height\n : 2 * cam.position.distanceTo(at) * Math.tan(cam.fov * Math.PI / 360) / height\n // round the target's footprint, at least\n const box = target.bounds, size = box.isEmpty() ? 0 : Math.hypot(box.max[0] - box.min[0], box.max[2] - box.min[2]) / 2\n const r = Math.max(this.size / 2 * perPixel, size * 1.1)\n this.node.scale.set(r, r, r)\n }\n\n /** @param {'x' | 'y' | 'z' | 'free' | 'ring'} part @param {Drag} drag */\n #drag(part, drag) {\n const target = this.#target\n if (!target) return\n if (drag.phase === 'start') {\n const center = target.worldPosition\n // up and down: across an upright plane facing the camera\n if (part === 'y') {\n const eye = this.#view.camera.position\n const facing = new Vec3(eye[0] - center[0], 0, eye[2] - center[2])\n drag.plane = { normal: facing.len() > 1e-6 ? facing.normalize() : new Vec3(0, 0, 1), point: center.clone() }\n } else {\n // the rest across the floor the target stands on\n drag.plane = { normal: new Vec3(0, 1, 0), point: center.clone() }\n }\n const start = drag.ray.intersectPlane(drag.plane.normal, drag.plane.point)\n const p = start !== null ? drag.ray.at(start) : center\n this.#from = {\n position: target.position.clone(), rotation: yawOf(target.quaternion), quaternion: target.quaternion.clone(),\n angle: Math.atan2(p[2] - center[2], p[0] - center[0]),\n done: this.history ? this.history.transform(target) : null,\n }\n this.onChange?.(target, 'start')\n return\n }\n const from = this.#from\n if (!from) return\n if (drag.phase === 'end') {\n from.done?.(part === 'ring' ? 'Turn' : 'Move')\n this.#from = null\n this.onChange?.(target, 'end')\n return\n }\n if (!drag.point) return\n const proposal = { position: from.position.clone(), rotation: from.rotation }\n if (part === 'ring') {\n const center = this.node.position\n const angle = Math.atan2(drag.point[2] - center[2], drag.point[0] - center[0])\n // turning anticlockwise seen from above is a positive turn about y, which runs the other way to the angle in x, z\n let turn = from.rotation - (angle - from.angle)\n if (this.snap?.turn) turn = Math.round(turn / this.snap.turn) * this.snap.turn\n proposal.rotation = turn\n } else {\n const d = drag.delta\n const move = part === 'x' ? [d[0], 0, 0] : part === 'z' ? [0, 0, d[2]] : part === 'y' ? [0, d[1], 0] : [d[0], 0, d[2]]\n proposal.position.add(move)\n const step = this.snap?.move\n if (step) {\n // snap only the axes this drag moves, so the others stay where they were\n for (let k = 0; k < 3; k++) if (move[k] !== 0) proposal.position[k] = Math.round(proposal.position[k] / step) * step\n }\n }\n const result = this.constrain ? this.constrain(proposal, target) : proposal\n if (result === null) return\n const placed = /** @type {Placement} */ (result ?? proposal)\n target.position.copy(placed.position)\n // the turn about the vertical since the drag began, before the rotation it began with - so a\n // yaw past 90 degrees, or a tilt, comes through as it was\n const turned = new Quat().setFromAxisAngle([0, 1, 0], placed.rotation - from.rotation)\n target.quaternion.copy(turned.multiply(from.quaternion))\n this.#follow()\n this.onChange?.(target, 'move')\n }\n}\n\n/**\n * Which way a rotation faces about the vertical, in radians: where it turns the x axis. Euler y\n * can't say - a quaternion read back as Euler angles gives y between -90 and 90 degrees, with x and z\n * flipped to make up the rest.\n * @param {Quat} q\n * @returns {number}\n */\nfunction yawOf(q) {\n const [x, y, z, w] = q\n // the rotated x axis, (1 - 2(y² + z²), 2(xy + zw), 2(xz - yw)), seen from above\n return Math.atan2(-2 * (x * z - y * w), 1 - 2 * (y * y + z * z))\n}\n\n/** @param {string} name @param {unknown} v */\nfunction bool(name, v) {\n if (typeof v !== 'boolean') fail('TransformHandle', `'${name}' must be true or false, got ${describe(v)}`)\n return v\n}\n","// Undo and redo for editors: a list of what was done, each with how to undo and redo it.\nimport { checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n\n/**\n * Something done, that can be undone and done again.\n * @typedef {object} HistoryEntry\n * @property {string} [label] what it was, for an \"Undo move\" menu item\n * @property {() => void} undo\n * @property {() => void} redo\n */\n\n/**\n * @typedef {object} HistoryOptions\n * @property {number} [limit] how many steps back it keeps (default 100)\n * @property {((history: History) => void) | null} [onChange] called whenever what can be undone or redone changes\n */\n\nexport const HISTORY_OPTIONS = ['limit', 'onChange']\nconst HINTS = { max: \"use 'limit'\", size: \"use 'limit'\", onUpdate: \"use 'onChange'\" }\n\n/**\n * Undo and redo. Record each change once it has been made, with how to undo and redo it; or let\n * transform() work that out for moves, turns and resizes.\n *\n * const history = new History()\n * const done = history.transform([chair]) // before moving it\n * chair.position.x += 1\n * done('Move chair') // after: recorded, if it moved\n * history.undo()\n */\nexport class History {\n /** How many steps back are kept. */\n limit = 100\n /** @type {((history: History) => void) | null} */\n onChange = null\n /** @type {HistoryEntry[]} */ #done = []\n /** @type {HistoryEntry[]} */ #undone = []\n\n /** @param {HistoryOptions} [options] */\n constructor(options = {}) {\n checkOptions('History', options, HISTORY_OPTIONS, HINTS)\n if (options.limit !== undefined) {\n if (!Number.isInteger(options.limit) || options.limit < 1) fail('History', `'limit' must be a whole number 1 or more, got ${describe(options.limit)}`)\n this.limit = options.limit\n }\n if (options.onChange != null) {\n if (typeof options.onChange !== 'function') fail('History', `'onChange' must be a function, got ${describe(options.onChange)}`)\n this.onChange = options.onChange\n }\n }\n\n /**\n * Record a change already made. Anything undone before it can no longer be redone.\n * @param {HistoryEntry} entry\n * @returns {this}\n */\n push(entry) {\n if (!entry || typeof entry.undo !== 'function' || typeof entry.redo !== 'function') {\n fail('History', `push() takes { label, undo, redo } with undo and redo functions, got ${describe(entry)}`)\n }\n this.#done.push(entry)\n if (this.#done.length > this.limit) this.#done.shift()\n this.#undone = []\n this.onChange?.(this)\n return this\n }\n\n /** Undo the last change. @returns {boolean} whether there was one */\n undo() {\n const entry = this.#done.pop()\n if (!entry) return false\n entry.undo()\n this.#undone.push(entry)\n this.onChange?.(this)\n return true\n }\n\n /** Do again the last change undone. @returns {boolean} whether there was one */\n redo() {\n const entry = this.#undone.pop()\n if (!entry) return false\n entry.redo()\n this.#done.push(entry)\n this.onChange?.(this)\n return true\n }\n\n get canUndo() { return this.#done.length > 0 }\n get canRedo() { return this.#undone.length > 0 }\n /** How many steps undo() can go back - for a button that says \"Undo (3)\". */\n get undoCount() { return this.#done.length }\n /** How many steps redo() can go forward. */\n get redoCount() { return this.#undone.length }\n /** What undo() would undo, or null. @returns {string | null} */\n get undoLabel() { return this.#done.at(-1)?.label ?? null }\n /** What redo() would redo, or null. @returns {string | null} */\n get redoLabel() { return this.#undone.at(-1)?.label ?? null }\n\n /** Forget everything. */\n clear() {\n this.#done = []\n this.#undone = []\n this.onChange?.(this)\n }\n\n /**\n * Note where some nodes are now - position, rotation and scale - and get a function to call once\n * they have changed, which records undoing back to here. It records nothing if they haven't moved.\n * @param {Node | Node[]} nodes\n * @param {string} [label]\n * @returns {(label?: string) => boolean} records the change; true if there was one\n */\n transform(nodes, label) {\n const list = [nodes].flat()\n const before = list.map(placeOf)\n return (finalLabel = label) => {\n const after = list.map(placeOf)\n if (after.every((p, i) => p.every((v, k) => v === before[i][k]))) return false\n this.push({ label: finalLabel, undo: () => list.forEach((n, i) => place(n, before[i])), redo: () => list.forEach((n, i) => place(n, after[i])) })\n return true\n }\n }\n}\n\n/** A node's position, rotation (as a quaternion) and scale, as numbers. @param {Node} n */\nconst placeOf = (n) => [...n.position, ...n.quaternion, ...n.scale]\n\n/** @param {Node} n @param {number[]} p */\nfunction place(n, p) {\n n.position.set(p[0], p[1], p[2])\n n.quaternion.set(p[3], p[4], p[5], p[6])\n n.scale.set(p[7], p[8], p[9])\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"edit.js","names":["fail","Mesh","Vec3","checkOptions","checkOneOf","describe","checkNumber","Quat","Group","cylinder","torus","HANDLE_OPTIONS","HANDLE_HINTS","COLOURS","ON_TOP","TransformHandle","options","#view","bool","step","v","k","key","fn","#build","#follow","#target","node","#taken","#drag","drag","material","add","name","mesh","color","part","#parts","arrow","along","axis","d","target","at","view","cam","height","perPixel","box","size","r","center","eye","facing","start","p","#from","yawOf","from","proposal","angle","turn","move","result","placed","turned","x","y","z","w","q","HISTORY_OPTIONS","HINTS","History","entry","#done","#undone","list","nodes","before","placeOf","label","after","i","finalLabel","place","n"],"sources":["../src/edit/TransformHandle.js","../src/edit/History.js"],"sourcesContent":["// A move and turn handle for editors: arrows to slide what is selected along an axis, a disc to\n// slide it freely across the floor, and a ring round it to turn it - drawn over everything, the\n// same size on screen however far away, snapping to a grid and to angles, and recording undo.\nimport { Group } from '../scene/Node.js'\nimport { Mesh } from '../scene/drawables.js'\nimport { Quat } from '../value/Quat.js'\nimport { Vec3 } from '../value/Vec3.js'\nimport { cylinder, torus } from '../geometry/generators.js'\nimport { checkNumber, checkOneOf, checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node, Drag } from '../scene/Node.js' */\n/** @import { History } from './History.js' */\n/** @import { View } from '../View.js' */\n\n/**\n * A move or turn the handle proposes, for constrain() to check: where the target would be, and\n * its turn about the vertical.\n * @typedef {{ position: Vec3, rotation: number }} Placement\n */\n\n/**\n * @typedef {object} TransformHandleOptions\n * @property {View} view the view it is used in - it adds itself to the view's scene\n * @property {Node | null} [target] what it moves; change it later with handle.target\n * @property {'xz' | 'xyz' | false} [move] which ways it moves things: across the floor (default), up and down too, or not at all\n * @property {boolean} [turn] a ring to turn things about the vertical (default true)\n * @property {{ move?: number, turn?: number } | null} [snap] snap positions to a grid of this spacing, and turns to steps of\n * this many radians (default none)\n * @property {number} [size] how big it is on screen, in pixels across its ring (default 120); it grows to go round a big target\n * @property {boolean} [dragTarget] drag the target itself across the floor too, as well as by the handle (default true)\n * @property {History | null} [history] record each move and turn here, for undo (default none)\n * @property {((placement: Placement, target: Node) => Placement | null | void) | null} [constrain] check a move or turn before it\n * happens: return a changed placement (kept inside a room, out of other things), null to refuse it, or nothing to allow it\n * @property {((target: Node, phase: 'start' | 'move' | 'end') => void) | null} [onChange] told as the target is moved or turned\n */\n\nexport const HANDLE_OPTIONS = ['view', 'target', 'move', 'turn', 'snap', 'size', 'dragTarget', 'history', 'constrain', 'onChange']\nexport const HANDLE_HINTS = {\n object: \"use 'target'\",\n mode: \"use 'move' ('xz', 'xyz' or false) and 'turn' (true or false)\",\n rotate: \"use 'turn' - a ring for turning about the vertical\",\n translate: \"use 'move': 'xz', 'xyz' or false\",\n translationSnap: \"use snap: { move, turn }\",\n rotationSnap: \"use snap: { move, turn }\",\n onDrag: \"use 'onChange': (target, phase) => ...\",\n}\n\nconst COLOURS = { x: '#ff5a52', y: '#5ad07a', z: '#4f8cff', ring: '#ffcc33', disc: '#f2f2f2' }\n// drawn after everything, over it\nconst ON_TOP = 100000\n\nexport class TransformHandle {\n /** The handle's own nodes, in the view's scene. */\n node = new Group({ name: 'transform-handle', highlight: '#ffffff' })\n /** @type {'xz' | 'xyz' | false} */ move = 'xz'\n turn = true\n /** @type {{ move: number, turn: number } | null} */ snap = null\n size = 120\n dragTarget = true\n /** @type {History | null} */ history = null\n /** @type {((placement: Placement, target: Node) => Placement | null | void) | null} */ constrain = null\n /** @type {((target: Node, phase: 'start' | 'move' | 'end') => void) | null} */ onChange = null\n\n /** @type {View} */ #view\n /** @type {Node | null} */ #target = null\n /** The node whose onDrag the handle took over, and that onDrag, to give back. @type {{ node: Node, onDrag: ((drag: Drag) => void) | null } | null} */ #taken = null\n /** @type {Record<string, Mesh>} */ #parts = {}\n /** Where the target was as a drag began, and the undo it will record. @type {{ position: Vec3, rotation: number, quaternion: Quat, angle: number, done: ((label?: string) => boolean) | null } | null} */ #from = null\n\n /** @param {TransformHandleOptions} options */\n constructor(options) {\n checkOptions('TransformHandle', options, HANDLE_OPTIONS, HANDLE_HINTS)\n if (!options?.view?.scene) fail('TransformHandle', \"'view' is required - the View it is used in\")\n this.#view = options.view\n if (options.move !== undefined) this.move = options.move === false ? false : /** @type {'xz' | 'xyz'} */ (checkOneOf('TransformHandle', 'move', options.move, ['xz', 'xyz']))\n if (options.turn !== undefined) this.turn = bool('turn', options.turn)\n if (options.dragTarget !== undefined) this.dragTarget = bool('dragTarget', options.dragTarget)\n if (options.size !== undefined) {\n this.size = checkNumber('TransformHandle', 'size', options.size)\n if (!(this.size > 0)) fail('TransformHandle', `'size' is in pixels, above 0, got ${this.size}`)\n }\n if (options.snap != null) {\n checkOptions('TransformHandle', options.snap, ['move', 'turn'], {}, 'snap')\n const step = (k) => {\n const v = options.snap?.[k]\n if (v === undefined) return 0\n if (typeof v !== 'number' || !(v >= 0)) fail('TransformHandle', `'snap.${k}' must be a step of 0 or more, got ${describe(v)}`)\n return v\n }\n this.snap = { move: step('move'), turn: step('turn') }\n }\n for (const key of ['constrain', 'onChange']) {\n const fn = options[key]\n if (fn == null) continue\n if (typeof fn !== 'function') fail('TransformHandle', `'${key}' must be a function, got ${describe(fn)}`)\n this[key] = fn\n }\n if (options.history != null) {\n if (typeof options.history.transform !== 'function') fail('TransformHandle', `'history' must be a History, got ${describe(options.history)}`)\n this.history = options.history\n }\n this.#build()\n this.node.visible = false\n this.node.onUpdate(() => this.#follow())\n this.#view.scene.add(this.node)\n if (options.target != null) this.target = options.target\n }\n\n /** What the handle moves, or null to hide it. @returns {Node | null} */\n get target() { return this.#target }\n /** @param {Node | null} node */\n set target(node) {\n if (node === this.#target) return\n // give back the old target's own drag - whatever dragTarget says now\n if (this.#taken) this.#taken.node.onDrag = this.#taken.onDrag\n this.#taken = null\n this.#target = node ?? null\n this.node.visible = !!node\n if (node && this.dragTarget) {\n this.#taken = { node, onDrag: node.onDrag }\n node.onDrag = (drag) => this.#drag('free', drag)\n }\n if (node) this.#follow()\n this.#view.invalidate()\n }\n\n /** Take the handle out of the scene, giving the target back its own drag. */\n dispose() {\n this.target = null\n this.node.removeFromParent()\n }\n\n #build() {\n const material = (color) => ({ color, unlit: true, depthTest: false, depthWrite: false, opacity: 0.95 })\n /** @param {string} name @param {any} geometry @param {string} color @param {number[]} [position] @param {any} [part] */\n const add = (name, geometry, color, position = [0, 0, 0], part = name) => {\n const mesh = this.node.add(new Mesh({ name, geometry, material: material(color), position, renderOrder: ON_TOP, castShadow: false }))\n mesh.onDrag = (drag) => this.#drag(part, drag)\n this.#parts[name] = mesh\n return mesh\n }\n // the ring round it, for turning\n add('ring', torus({ radius: 1, tube: 0.035, segments: 72, tubeSegments: 8 }), COLOURS.ring)\n // arrows out beyond the ring along each way it moves, and a disc in the middle to move it freely\n const arrow = (axis, color) => {\n const along = (d) => /** @type {number[]} */ (axis === 'x' ? [d, 0, 0] : axis === 'y' ? [0, d, 0] : [0, 0, d])\n add(`${axis}-shaft`, cylinder({ radius: 0.035, height: 0.4, segments: 10, axis }), color, along(1.3), axis)\n add(`${axis}-tip`, cylinder({ radius: 0.1, radiusTop: 0, height: 0.22, segments: 16, axis }), color, along(1.6), axis)\n }\n arrow('x', COLOURS.x)\n arrow('z', COLOURS.z)\n arrow('y', COLOURS.y)\n add('disc', cylinder({ radius: 0.16, height: 0.02, segments: 32 }), COLOURS.disc, [0, 0, 0], 'free')\n }\n\n // sit on the target, the same size on screen at any distance - but always round it\n #follow() {\n const target = this.#target\n if (!target) return\n for (const [name, mesh] of Object.entries(this.#parts)) {\n const axis = name[0]\n mesh.visible = name === 'ring' ? this.turn : name === 'disc' ? !!this.move : !!this.move && (axis !== 'y' || this.move === 'xyz')\n }\n target.updateWorld()\n const at = target.worldPosition\n this.node.position.copy(at)\n const view = this.#view, cam = /** @type {any} */ (view.camera)\n const height = view.canvas.clientHeight || 1\n const perPixel = cam.constructor.type === 'orthographic-camera' ? cam.height / height\n : 2 * cam.position.distanceTo(at) * Math.tan(cam.fov * Math.PI / 360) / height\n // round the target's footprint, at least\n const box = target.bounds, size = box.isEmpty() ? 0 : Math.hypot(box.max[0] - box.min[0], box.max[2] - box.min[2]) / 2\n const r = Math.max(this.size / 2 * perPixel, size * 1.1)\n this.node.scale.set(r, r, r)\n }\n\n /** @param {'x' | 'y' | 'z' | 'free' | 'ring'} part @param {Drag} drag */\n #drag(part, drag) {\n const target = this.#target\n if (!target) return\n if (drag.phase === 'start') {\n const center = target.worldPosition\n // up and down: across an upright plane facing the camera\n if (part === 'y') {\n const eye = this.#view.camera.position\n const facing = new Vec3(eye[0] - center[0], 0, eye[2] - center[2])\n drag.plane = { normal: facing.len() > 1e-6 ? facing.normalize() : new Vec3(0, 0, 1), point: center.clone() }\n } else {\n // the rest across the floor the target stands on\n drag.plane = { normal: new Vec3(0, 1, 0), point: center.clone() }\n }\n const start = drag.ray.intersectPlane(drag.plane.normal, drag.plane.point)\n const p = start !== null ? drag.ray.at(start) : center\n this.#from = {\n position: target.position.clone(), rotation: yawOf(target.quaternion), quaternion: target.quaternion.clone(),\n angle: Math.atan2(p[2] - center[2], p[0] - center[0]),\n done: this.history ? this.history.transform(target) : null,\n }\n this.onChange?.(target, 'start')\n return\n }\n const from = this.#from\n if (!from) return\n if (drag.phase === 'end') {\n from.done?.(part === 'ring' ? 'Turn' : 'Move')\n this.#from = null\n this.onChange?.(target, 'end')\n return\n }\n if (!drag.point) return\n const proposal = { position: from.position.clone(), rotation: from.rotation }\n if (part === 'ring') {\n const center = this.node.position\n const angle = Math.atan2(drag.point[2] - center[2], drag.point[0] - center[0])\n // turning anticlockwise seen from above is a positive turn about y, which runs the other way to the angle in x, z\n let turn = from.rotation - (angle - from.angle)\n if (this.snap?.turn) turn = Math.round(turn / this.snap.turn) * this.snap.turn\n proposal.rotation = turn\n } else {\n const d = drag.delta\n const move = part === 'x' ? [d[0], 0, 0] : part === 'z' ? [0, 0, d[2]] : part === 'y' ? [0, d[1], 0] : [d[0], 0, d[2]]\n proposal.position.add(move)\n const step = this.snap?.move\n if (step) {\n // snap only the axes this drag moves, so the others stay where they were\n for (let k = 0; k < 3; k++) if (move[k] !== 0) proposal.position[k] = Math.round(proposal.position[k] / step) * step\n }\n }\n const result = this.constrain ? this.constrain(proposal, target) : proposal\n if (result === null) return\n const placed = /** @type {Placement} */ (result ?? proposal)\n target.position.copy(placed.position)\n // the turn about the vertical since the drag began, before the rotation it began with - so a\n // yaw past 90 degrees, or a tilt, comes through as it was\n const turned = new Quat().setFromAxisAngle([0, 1, 0], placed.rotation - from.rotation)\n target.quaternion.copy(turned.multiply(from.quaternion))\n this.#follow()\n this.onChange?.(target, 'move')\n }\n}\n\n/**\n * Which way a rotation faces about the vertical, in radians: where it turns the x axis. Euler y\n * can't say - a quaternion read back as Euler angles gives y between -90 and 90 degrees, with x and z\n * flipped to make up the rest.\n * @param {Quat} q\n * @returns {number}\n */\nfunction yawOf(q) {\n const [x, y, z, w] = q\n // the rotated x axis, (1 - 2(y² + z²), 2(xy + zw), 2(xz - yw)), seen from above\n return Math.atan2(-2 * (x * z - y * w), 1 - 2 * (y * y + z * z))\n}\n\n/** @param {string} name @param {unknown} v */\nfunction bool(name, v) {\n if (typeof v !== 'boolean') fail('TransformHandle', `'${name}' must be true or false, got ${describe(v)}`)\n return v\n}\n","// Undo and redo for editors: a list of what was done, each with how to undo and redo it.\nimport { checkOptions, describe, fail } from '../validate.js'\n\n/** @import { Node } from '../scene/Node.js' */\n\n/**\n * Something done, that can be undone and done again.\n * @typedef {object} HistoryEntry\n * @property {string} [label] what it was, for an \"Undo move\" menu item\n * @property {() => void} undo\n * @property {() => void} redo\n */\n\n/**\n * @typedef {object} HistoryOptions\n * @property {number} [limit] how many steps back it keeps (default 100)\n * @property {((history: History) => void) | null} [onChange] called whenever what can be undone or redone changes\n */\n\nexport const HISTORY_OPTIONS = ['limit', 'onChange']\nconst HINTS = { max: \"use 'limit'\", size: \"use 'limit'\", onUpdate: \"use 'onChange'\" }\n\n/**\n * Undo and redo. Record each change once it has been made, with how to undo and redo it; or let\n * transform() work that out for moves, turns and resizes.\n *\n * const history = new History()\n * const done = history.transform([chair]) // before moving it\n * chair.position.x += 1\n * done('Move chair') // after: recorded, if it moved\n * history.undo()\n */\nexport class History {\n /** How many steps back are kept. */\n limit = 100\n /** @type {((history: History) => void) | null} */\n onChange = null\n /** @type {HistoryEntry[]} */ #done = []\n /** @type {HistoryEntry[]} */ #undone = []\n\n /** @param {HistoryOptions} [options] */\n constructor(options = {}) {\n checkOptions('History', options, HISTORY_OPTIONS, HINTS)\n if (options.limit !== undefined) {\n if (!Number.isInteger(options.limit) || options.limit < 1) fail('History', `'limit' must be a whole number 1 or more, got ${describe(options.limit)}`)\n this.limit = options.limit\n }\n if (options.onChange != null) {\n if (typeof options.onChange !== 'function') fail('History', `'onChange' must be a function, got ${describe(options.onChange)}`)\n this.onChange = options.onChange\n }\n }\n\n /**\n * Record a change already made. Anything undone before it can no longer be redone.\n * @param {HistoryEntry} entry\n * @returns {this}\n */\n push(entry) {\n if (!entry || typeof entry.undo !== 'function' || typeof entry.redo !== 'function') {\n fail('History', `push() takes { label, undo, redo } with undo and redo functions, got ${describe(entry)}`)\n }\n this.#done.push(entry)\n if (this.#done.length > this.limit) this.#done.shift()\n this.#undone = []\n this.onChange?.(this)\n return this\n }\n\n /** Undo the last change. @returns {boolean} whether there was one */\n undo() {\n const entry = this.#done.pop()\n if (!entry) return false\n entry.undo()\n this.#undone.push(entry)\n this.onChange?.(this)\n return true\n }\n\n /** Do again the last change undone. @returns {boolean} whether there was one */\n redo() {\n const entry = this.#undone.pop()\n if (!entry) return false\n entry.redo()\n this.#done.push(entry)\n this.onChange?.(this)\n return true\n }\n\n get canUndo() { return this.#done.length > 0 }\n get canRedo() { return this.#undone.length > 0 }\n /** How many steps undo() can go back - for a button that says \"Undo (3)\". */\n get undoCount() { return this.#done.length }\n /** How many steps redo() can go forward. */\n get redoCount() { return this.#undone.length }\n /** What undo() would undo, or null. @returns {string | null} */\n get undoLabel() { return this.#done.at(-1)?.label ?? null }\n /** What redo() would redo, or null. @returns {string | null} */\n get redoLabel() { return this.#undone.at(-1)?.label ?? null }\n\n /** Forget everything. */\n clear() {\n this.#done = []\n this.#undone = []\n this.onChange?.(this)\n }\n\n /**\n * Note where some nodes are now - position, rotation and scale - and get a function to call once\n * they have changed, which records undoing back to here. It records nothing if they haven't moved.\n * @param {Node | Node[]} nodes\n * @param {string} [label]\n * @returns {(label?: string) => boolean} records the change; true if there was one\n */\n transform(nodes, label) {\n const list = [nodes].flat()\n const before = list.map(placeOf)\n return (finalLabel = label) => {\n const after = list.map(placeOf)\n if (after.every((p, i) => p.every((v, k) => v === before[i][k]))) return false\n this.push({ label: finalLabel, undo: () => list.forEach((n, i) => place(n, before[i])), redo: () => list.forEach((n, i) => place(n, after[i])) })\n return true\n }\n }\n}\n\n/** A node's position, rotation (as a quaternion) and scale, as numbers. @param {Node} n */\nconst placeOf = (n) => [...n.position, ...n.quaternion, ...n.scale]\n\n/** @param {Node} n @param {number[]} p */\nfunction place(n, p) {\n n.position.set(p[0], p[1], p[2])\n n.quaternion.set(p[3], p[4], p[5], p[6])\n n.scale.set(p[7], p[8], p[9])\n}\n"],"mappings":"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"}
package/dist/explain.js CHANGED
@@ -1,4 +1,4 @@
1
- import { bt as e } from "./core.js";
1
+ import { xt as e } from "./core.js";
2
2
  // src/explain.js
3
3
  var t = (e) => Math.abs(e) >= 1e4 || e !== 0 && Math.abs(e) < .001 ? e.toExponential(2) : String(+e.toFixed(2)), n = (e) => `[${t(e[0])}, ${t(e[1])}, ${t(e[2])}]`, r = (e, t) => `${e} ${t}${e === 1 ? "" : "s"}`;
4
4
  function i(t) {