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/edit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dt as e, Et as t, Ot as n, et as r, jt as i, kt as a, rt as o, u as s, ut as c, v as l, wt 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
|
|
29
|
+
node = new o({
|
|
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(
|
|
47
|
-
if (n("TransformHandle",
|
|
48
|
-
n("TransformHandle",
|
|
49
|
-
let
|
|
50
|
-
let
|
|
51
|
-
return
|
|
46
|
+
constructor(r) {
|
|
47
|
+
if (n("TransformHandle", r, d, f), r?.view?.scene || i("TransformHandle", "'view' is required - the View it is used in"), this.#e = r.view, r.move !== void 0 && (this.move = r.move !== !1 && e("TransformHandle", "move", r.move, ["xz", "xyz"])), r.turn !== void 0 && (this.turn = _("turn", r.turn)), r.dragTarget !== void 0 && (this.dragTarget = _("dragTarget", r.dragTarget)), r.size !== void 0 && (this.size = t("TransformHandle", "size", r.size), this.size > 0 || i("TransformHandle", `'size' is in pixels, above 0, got ${this.size}`)), r.snap != null) {
|
|
48
|
+
n("TransformHandle", r.snap, ["move", "turn"], {}, "snap");
|
|
49
|
+
let e = (e) => {
|
|
50
|
+
let t = r.snap?.[e];
|
|
51
|
+
return t === void 0 ? 0 : (typeof t == "number" && t >= 0 || i("TransformHandle", `'snap.${e}' must be a step of 0 or more, got ${a(t)}`), t);
|
|
52
52
|
};
|
|
53
53
|
this.snap = {
|
|
54
|
-
move:
|
|
55
|
-
turn:
|
|
54
|
+
move: e("move"),
|
|
55
|
+
turn: e("turn")
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
|
-
for (let
|
|
59
|
-
let
|
|
60
|
-
|
|
58
|
+
for (let e of ["constrain", "onChange"]) {
|
|
59
|
+
let t = r[e];
|
|
60
|
+
t != null && (typeof t != "function" && i("TransformHandle", `'${e}' must be a function, got ${a(t)}`), this[e] = t);
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
r.history != null && (typeof r.history.transform != "function" && i("TransformHandle", `'history' must be a History, got ${a(r.history)}`), this.history = r.history), this.#a(), this.node.visible = !1, this.node.onUpdate(() => this.#o()), this.#e.scene.add(this.node), r.target != null && (this.target = r.target);
|
|
63
63
|
}
|
|
64
64
|
get target() {
|
|
65
65
|
return this.#t;
|
|
@@ -80,22 +80,22 @@ var d = [
|
|
|
80
80
|
depthTest: !1,
|
|
81
81
|
depthWrite: !1,
|
|
82
82
|
opacity: .95
|
|
83
|
-
}), t = (t, n,
|
|
83
|
+
}), t = (t, n, i, a = [
|
|
84
84
|
0,
|
|
85
85
|
0,
|
|
86
86
|
0
|
|
87
|
-
],
|
|
88
|
-
let s = this.node.add(new
|
|
87
|
+
], o = t) => {
|
|
88
|
+
let s = this.node.add(new r({
|
|
89
89
|
name: t,
|
|
90
90
|
geometry: n,
|
|
91
|
-
material: e(
|
|
92
|
-
position:
|
|
91
|
+
material: e(i),
|
|
92
|
+
position: a,
|
|
93
93
|
renderOrder: m,
|
|
94
94
|
castShadow: !1
|
|
95
95
|
}));
|
|
96
|
-
return s.onDrag = (e) => this.#s(
|
|
96
|
+
return s.onDrag = (e) => this.#s(o, e), this.#r[t] = s, s;
|
|
97
97
|
};
|
|
98
|
-
t("ring",
|
|
98
|
+
t("ring", l({
|
|
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
|
-
t(`${e}-shaft`,
|
|
118
|
+
t(`${e}-shaft`, s({
|
|
119
119
|
radius: .035,
|
|
120
120
|
height: .4,
|
|
121
121
|
segments: 10,
|
|
122
122
|
axis: e
|
|
123
|
-
}), n, r(1.3), e), t(`${e}-tip`,
|
|
123
|
+
}), n, r(1.3), e), t(`${e}-tip`, s({
|
|
124
124
|
radius: .1,
|
|
125
125
|
radiusTop: 0,
|
|
126
126
|
height: .22,
|
|
@@ -128,7 +128,7 @@ var d = [
|
|
|
128
128
|
axis: e
|
|
129
129
|
}), n, r(1.6), e);
|
|
130
130
|
};
|
|
131
|
-
n("x", p.x), n("z", p.z), n("y", p.y), t("disc",
|
|
131
|
+
n("x", p.x), n("z", p.z), n("y", p.y), t("disc", s({
|
|
132
132
|
radius: .16,
|
|
133
133
|
height: .02,
|
|
134
134
|
segments: 32
|
|
@@ -151,85 +151,85 @@ var d = [
|
|
|
151
151
|
let n = this.#e, r = n.camera, i = n.canvas.clientHeight || 1, a = r.constructor.type === "orthographic-camera" ? r.height / i : 2 * r.position.distanceTo(t) * Math.tan(r.fov * Math.PI / 360) / i, o = e.bounds, s = o.isEmpty() ? 0 : Math.hypot(o.max[0] - o.min[0], o.max[2] - o.min[2]) / 2, c = Math.max(this.size / 2 * a, s * 1.1);
|
|
152
152
|
this.node.scale.set(c, c, c);
|
|
153
153
|
}
|
|
154
|
-
#s(e,
|
|
155
|
-
let
|
|
156
|
-
if (!
|
|
157
|
-
if (
|
|
158
|
-
let
|
|
154
|
+
#s(e, t) {
|
|
155
|
+
let n = this.#t;
|
|
156
|
+
if (!n) return;
|
|
157
|
+
if (t.phase === "start") {
|
|
158
|
+
let r = n.worldPosition;
|
|
159
159
|
if (e === "y") {
|
|
160
|
-
let e = this.#e.camera.position,
|
|
161
|
-
|
|
162
|
-
normal:
|
|
163
|
-
point:
|
|
160
|
+
let e = this.#e.camera.position, n = new u(e[0] - r[0], 0, e[2] - r[2]);
|
|
161
|
+
t.plane = {
|
|
162
|
+
normal: n.len() > 1e-6 ? n.normalize() : new u(0, 0, 1),
|
|
163
|
+
point: r.clone()
|
|
164
164
|
};
|
|
165
|
-
} else
|
|
166
|
-
normal: new
|
|
167
|
-
point:
|
|
165
|
+
} else t.plane = {
|
|
166
|
+
normal: new u(0, 1, 0),
|
|
167
|
+
point: r.clone()
|
|
168
168
|
};
|
|
169
|
-
let
|
|
169
|
+
let i = t.ray.intersectPlane(t.plane.normal, t.plane.point), a = i === null ? r : t.ray.at(i);
|
|
170
170
|
this.#i = {
|
|
171
|
-
position:
|
|
172
|
-
rotation: g(
|
|
173
|
-
quaternion:
|
|
174
|
-
angle: Math.atan2(
|
|
175
|
-
done: this.history ? this.history.transform(
|
|
176
|
-
}, this.onChange?.(
|
|
171
|
+
position: n.position.clone(),
|
|
172
|
+
rotation: g(n.quaternion),
|
|
173
|
+
quaternion: n.quaternion.clone(),
|
|
174
|
+
angle: Math.atan2(a[2] - r[2], a[0] - r[0]),
|
|
175
|
+
done: this.history ? this.history.transform(n) : null
|
|
176
|
+
}, this.onChange?.(n, "start");
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
|
-
let
|
|
180
|
-
if (!
|
|
181
|
-
if (
|
|
182
|
-
|
|
179
|
+
let r = this.#i;
|
|
180
|
+
if (!r) return;
|
|
181
|
+
if (t.phase === "end") {
|
|
182
|
+
r.done?.(e === "ring" ? "Turn" : "Move"), this.#i = null, this.onChange?.(n, "end");
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
|
-
if (!
|
|
186
|
-
let
|
|
187
|
-
position:
|
|
188
|
-
rotation:
|
|
185
|
+
if (!t.point) return;
|
|
186
|
+
let i = {
|
|
187
|
+
position: r.position.clone(),
|
|
188
|
+
rotation: r.rotation
|
|
189
189
|
};
|
|
190
190
|
if (e === "ring") {
|
|
191
|
-
let e = this.node.position,
|
|
192
|
-
this.snap?.turn && (
|
|
191
|
+
let e = this.node.position, n = Math.atan2(t.point[2] - e[2], t.point[0] - e[0]), a = r.rotation - (n - r.angle);
|
|
192
|
+
this.snap?.turn && (a = Math.round(a / this.snap.turn) * this.snap.turn), i.rotation = a;
|
|
193
193
|
} else {
|
|
194
|
-
let
|
|
195
|
-
|
|
194
|
+
let n = t.delta, r = e === "x" ? [
|
|
195
|
+
n[0],
|
|
196
196
|
0,
|
|
197
197
|
0
|
|
198
198
|
] : e === "z" ? [
|
|
199
199
|
0,
|
|
200
200
|
0,
|
|
201
|
-
|
|
201
|
+
n[2]
|
|
202
202
|
] : e === "y" ? [
|
|
203
203
|
0,
|
|
204
|
-
|
|
204
|
+
n[1],
|
|
205
205
|
0
|
|
206
206
|
] : [
|
|
207
|
-
|
|
207
|
+
n[0],
|
|
208
208
|
0,
|
|
209
|
-
|
|
209
|
+
n[2]
|
|
210
210
|
];
|
|
211
|
-
|
|
212
|
-
let
|
|
213
|
-
if (
|
|
211
|
+
i.position.add(r);
|
|
212
|
+
let a = this.snap?.move;
|
|
213
|
+
if (a) for (let e = 0; e < 3; e++) r[e] !== 0 && (i.position[e] = Math.round(i.position[e] / a) * a);
|
|
214
214
|
}
|
|
215
|
-
let
|
|
216
|
-
if (
|
|
217
|
-
let
|
|
218
|
-
|
|
219
|
-
let
|
|
215
|
+
let a = this.constrain ? this.constrain(i, n) : i;
|
|
216
|
+
if (a === null) return;
|
|
217
|
+
let o = a ?? i;
|
|
218
|
+
n.position.copy(o.position);
|
|
219
|
+
let s = new c().setFromAxisAngle([
|
|
220
220
|
0,
|
|
221
221
|
1,
|
|
222
222
|
0
|
|
223
|
-
],
|
|
224
|
-
|
|
223
|
+
], o.rotation - r.rotation);
|
|
224
|
+
n.quaternion.copy(s.multiply(r.quaternion)), this.#o(), this.onChange?.(n, "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 _(
|
|
232
|
-
return typeof
|
|
231
|
+
function _(e, t) {
|
|
232
|
+
return typeof t != "boolean" && i("TransformHandle", `'${e}' must be true or false, got ${a(t)}`), t;
|
|
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(
|
|
245
|
-
n("History",
|
|
244
|
+
constructor(e = {}) {
|
|
245
|
+
n("History", e, v, y), e.limit !== void 0 && ((!Number.isInteger(e.limit) || e.limit < 1) && i("History", `'limit' must be a whole number 1 or more, got ${a(e.limit)}`), this.limit = e.limit), e.onChange != null && (typeof e.onChange != "function" && i("History", `'onChange' must be a function, got ${a(e.onChange)}`), this.onChange = e.onChange);
|
|
246
246
|
}
|
|
247
|
-
push(
|
|
248
|
-
return
|
|
247
|
+
push(e) {
|
|
248
|
+
return e && typeof e.undo == "function" && typeof e.redo == "function" || i("History", `push() takes { label, undo, redo } with undo and redo functions, got ${a(e)}`), this.#e.push(e), 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":["fail","Vec3","checkOptions","checkOneOf","describe","checkNumber","Mesh","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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"}
|
|
1
|
+
{"version":3,"file":"edit.js","names":["checkOneOf","checkNumber","checkOptions","Mesh","fail","describe","Group","cylinder","Quat","torus","Vec3","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 {
|
|
1
|
+
import { St 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) {
|