@weasel-js/kernel3d 1.18.2 → 1.19.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/dist/index.d.ts +12 -7
- package/dist/index.js +65 -35
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -53,14 +53,16 @@ declare const DEFAULT_ORBIT_LIMITS: OrbitLimits;
|
|
|
53
53
|
* itself when it already holds. */
|
|
54
54
|
declare function clampCamera(camera: Camera3d, limits?: Partial<OrbitLimits>): Camera3d;
|
|
55
55
|
/** The camera turned about its target by the given radians, pitch held inside
|
|
56
|
-
* `limits`. Yaw is not wrapped. */
|
|
56
|
+
* `limits`. Yaw is not wrapped. A turn that is not finite leaves it as it is. */
|
|
57
57
|
declare function orbitBy(camera: Camera3d, deltaYaw: number, deltaPitch: number, limits?: Partial<OrbitLimits>): Camera3d;
|
|
58
|
-
/** The camera with its distance multiplied by `factor`, held inside `limits`.
|
|
58
|
+
/** The camera with its distance multiplied by `factor`, held inside `limits`.
|
|
59
|
+
* A `NaN` factor leaves it as it is. */
|
|
59
60
|
declare function dollyBy(camera: Camera3d, factor: number, limits?: Partial<OrbitLimits>): Camera3d;
|
|
60
61
|
/**
|
|
61
62
|
* The camera with its target slid across the screen plane, so the point at the
|
|
62
63
|
* target follows a pointer that moved `dx`, `dy` CSS pixels in a pane
|
|
63
|
-
* `paneHeight` pixels tall. Neither angle nor distance changes.
|
|
64
|
+
* `paneHeight` pixels tall. Neither angle nor distance changes. A pan that
|
|
65
|
+
* carries the target past the double range leaves the camera as it is.
|
|
64
66
|
*/
|
|
65
67
|
declare function panBy(camera: Camera3d, dx: number, dy: number, paneHeight: number): Camera3d;
|
|
66
68
|
/**
|
|
@@ -219,8 +221,8 @@ type Point$1 = {
|
|
|
219
221
|
x: number;
|
|
220
222
|
y: number;
|
|
221
223
|
};
|
|
222
|
-
/** Where `p` draws in the viewport, or null when it is behind the near plane
|
|
223
|
-
* or projects to nothing finite. */
|
|
224
|
+
/** Where `p` draws in the viewport, or null when it is behind the near plane,
|
|
225
|
+
* the pane has no area, or it projects to nothing finite. */
|
|
224
226
|
declare function projectPoint(p: Vec3, viewport: Viewport3d): Point$1 | null;
|
|
225
227
|
|
|
226
228
|
/** The 3D world every dep is built from: core's scene with a `Pose3`, the
|
|
@@ -368,6 +370,8 @@ interface Footprint {
|
|
|
368
370
|
*/
|
|
369
371
|
declare function createInsert(opts: {
|
|
370
372
|
viewport: ViewportSource;
|
|
373
|
+
/** The view whose camera a drag is cast through, for a `<CanvasView deps>` of its own. Default the surface's. */
|
|
374
|
+
viewId?: string | null;
|
|
371
375
|
up?: Vec3;
|
|
372
376
|
/** Smallest footprint a click-sized drag produces. Default 0.25. */
|
|
373
377
|
minExtent?: number;
|
|
@@ -439,8 +443,9 @@ interface GroundGridSourceOptions {
|
|
|
439
443
|
/**
|
|
440
444
|
* A grid on the ground plane as snap targets: for each pointer or geometry
|
|
441
445
|
* probe on the ground, the grid line on either side of it along each of the
|
|
442
|
-
* ground's two axes
|
|
443
|
-
*
|
|
446
|
+
* ground's two axes, none along an axis where the probe lies so far out that
|
|
447
|
+
* lines a step apart are no longer two numbers. Its reach is unlimited and it
|
|
448
|
+
* is a fallback, so it holds every axis no target claims and never displaces one.
|
|
444
449
|
*/
|
|
445
450
|
declare function groundGridSource(opts: GroundGridSourceOptions): SnapSource<Vec3>;
|
|
446
451
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, quatMultiply, quatConjugate, quatRotate, sub, invert, transformPoint, normalize, transformPoint4, distance,
|
|
1
|
+
import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, quatMultiply, quatConjugate, quatRotate, sub, invert, transformPoint, normalize, cross, dot, transformPoint4, distance, len, aabbOfPoints, intersectRayPlane, pointOnRay, intersectRayAabb } from '@weasel-js/geom/3d';
|
|
2
2
|
import { clamp, lengthTable, curveSpan, rectsOverlap } from '@weasel-js/geom';
|
|
3
|
-
import { scenePoseFrame, createSnapOutcomeStore, assertSnapSourceIds, DEFAULT_SNAP_PX, resolveOverlays, snapSourcesFor, resolvedSnapMarks, SNAP_AXIS_STEP, gatherSnapCandidates, scenePoseAdapter, composeWorldPose } from '@weasel-js/core/engine';
|
|
3
|
+
import { scenePoseFrame, createSnapOutcomeStore, assertSnapSourceIds, DEFAULT_SNAP_PX, resolveOverlays, snapSourcesFor, resolveHolding, resolvedSnapMarks, SNAP_AXIS_STEP, gatherSnapCandidates, scenePoseAdapter, composeWorldPose } from '@weasel-js/core/engine';
|
|
4
4
|
import { isDev } from '@weasel-js/react';
|
|
5
5
|
import { closestOnFlat, resolveSnap, createBvh, stepLine, createSettledCache } from '@weasel-js/geom/nd';
|
|
6
6
|
import { overlaysOf, defineTool } from '@weasel-js/routing';
|
|
@@ -45,19 +45,23 @@ var DEFAULT_ORBIT_LIMITS = {
|
|
|
45
45
|
};
|
|
46
46
|
function clampCamera(camera, limits = {}) {
|
|
47
47
|
const l = { ...DEFAULT_ORBIT_LIMITS, ...limits };
|
|
48
|
-
const pitch = clamp(camera.pitch,
|
|
48
|
+
const pitch = clamp(camera.pitch, clamp(l.minPitch, -MAX_PITCH, MAX_PITCH), clamp(l.maxPitch, -MAX_PITCH, MAX_PITCH));
|
|
49
49
|
const distance2 = clamp(camera.distance, l.minDistance, l.maxDistance);
|
|
50
50
|
if (pitch === camera.pitch && distance2 === camera.distance) return camera;
|
|
51
51
|
return { ...camera, pitch, distance: distance2 };
|
|
52
52
|
}
|
|
53
|
+
function heldFinite(camera, moved) {
|
|
54
|
+
const { target: t, yaw, pitch, distance: distance2 } = moved;
|
|
55
|
+
return Number.isFinite(t.x + t.y + t.z + yaw + pitch + distance2) ? moved : camera;
|
|
56
|
+
}
|
|
53
57
|
function orbitBy(camera, deltaYaw, deltaPitch, limits) {
|
|
54
|
-
return clampCamera(
|
|
58
|
+
return heldFinite(camera, clampCamera(
|
|
55
59
|
{ ...camera, yaw: camera.yaw + deltaYaw, pitch: camera.pitch + deltaPitch },
|
|
56
60
|
limits
|
|
57
|
-
);
|
|
61
|
+
));
|
|
58
62
|
}
|
|
59
63
|
function dollyBy(camera, factor, limits) {
|
|
60
|
-
return clampCamera({ ...camera, distance: camera.distance * factor }, limits);
|
|
64
|
+
return heldFinite(camera, clampCamera({ ...camera, distance: camera.distance * factor }, limits));
|
|
61
65
|
}
|
|
62
66
|
function panBy(camera, dx, dy, paneHeight) {
|
|
63
67
|
if (!(paneHeight > 0)) return camera;
|
|
@@ -70,7 +74,7 @@ function panBy(camera, dx, dy, paneHeight) {
|
|
|
70
74
|
z: -Math.cos(yaw) * Math.sin(pitch)
|
|
71
75
|
};
|
|
72
76
|
const shift = add(scale(right, -dx * perPixel), scale(up, dy * perPixel));
|
|
73
|
-
return { ...camera, target: add(camera.target, shift) };
|
|
77
|
+
return heldFinite(camera, { ...camera, target: add(camera.target, shift) });
|
|
74
78
|
}
|
|
75
79
|
function dollyToward(camera, factor, at, pane, limits) {
|
|
76
80
|
const dollied = dollyBy(camera, factor, limits);
|
|
@@ -81,14 +85,14 @@ function dollyToward(camera, factor, at, pane, limits) {
|
|
|
81
85
|
-kept * (at.y - pane.height / 2),
|
|
82
86
|
pane.height
|
|
83
87
|
);
|
|
84
|
-
return { ...dollied, target: moved.target };
|
|
88
|
+
return heldFinite(camera, { ...dollied, target: moved.target });
|
|
85
89
|
}
|
|
86
90
|
var UNIT_CUBE = {
|
|
87
91
|
min: { x: -0.5, y: -0.5, z: -0.5 },
|
|
88
92
|
max: { x: 0.5, y: 0.5, z: 0.5 }
|
|
89
93
|
};
|
|
90
|
-
function pose3(position,
|
|
91
|
-
return { position, rotation, scale:
|
|
94
|
+
function pose3(position, scale7 = { x: 1, y: 1, z: 1 }, rotation = quatIdentity()) {
|
|
95
|
+
return { position, rotation, scale: scale7 };
|
|
92
96
|
}
|
|
93
97
|
function poseMatrix(pose) {
|
|
94
98
|
return compose(pose.position, pose.rotation, pose.scale);
|
|
@@ -97,10 +101,14 @@ function aabbOfPose(pose, local = UNIT_CUBE) {
|
|
|
97
101
|
return transformAabb(poseMatrix(pose), local);
|
|
98
102
|
}
|
|
99
103
|
var times = (a, b) => ({ x: a.x * b.x, y: a.y * b.y, z: a.z * b.z });
|
|
104
|
+
var overAxis = (a, s, zero) => {
|
|
105
|
+
const q = a / s;
|
|
106
|
+
return s === 0 || !Number.isFinite(q) ? zero : q;
|
|
107
|
+
};
|
|
100
108
|
var over = (a, s, zero) => ({
|
|
101
|
-
x:
|
|
102
|
-
y:
|
|
103
|
-
z:
|
|
109
|
+
x: overAxis(a.x, s.x, zero.x),
|
|
110
|
+
y: overAxis(a.y, s.y, zero.y),
|
|
111
|
+
z: overAxis(a.z, s.z, zero.z)
|
|
104
112
|
});
|
|
105
113
|
var ORIGIN = { x: 0, y: 0, z: 0 };
|
|
106
114
|
var POSE3_COMPOSITION = {
|
|
@@ -129,7 +137,8 @@ function ndcToScreen(ndc, rect) {
|
|
|
129
137
|
};
|
|
130
138
|
}
|
|
131
139
|
function rayThroughScreenPoint(point, rect, viewProjection) {
|
|
132
|
-
const
|
|
140
|
+
const eye = eyeOf(viewProjection) ?? ORIGIN2;
|
|
141
|
+
const inv = invert(multiply(viewProjection, compose(eye, quatIdentity(), UNIT)));
|
|
133
142
|
if (!inv) return null;
|
|
134
143
|
const ndc = screenToNdc(point, rect);
|
|
135
144
|
const near = transformPoint(inv, { x: ndc.x, y: ndc.y, z: -1 });
|
|
@@ -137,7 +146,19 @@ function rayThroughScreenPoint(point, rect, viewProjection) {
|
|
|
137
146
|
const direction = sub(far, near);
|
|
138
147
|
const unit = normalize(direction);
|
|
139
148
|
if (unit.x === 0 && unit.y === 0 && unit.z === 0) return null;
|
|
140
|
-
return { origin: near, direction };
|
|
149
|
+
return { origin: add(eye, near), direction };
|
|
150
|
+
}
|
|
151
|
+
var ORIGIN2 = { x: 0, y: 0, z: 0 };
|
|
152
|
+
var UNIT = { x: 1, y: 1, z: 1 };
|
|
153
|
+
function eyeOf(m) {
|
|
154
|
+
const row = (r) => ({ x: m[r], y: m[4 + r], z: m[8 + r] });
|
|
155
|
+
const [a, b, c] = [row(0), row(1), row(3)];
|
|
156
|
+
const bc = cross(b, c);
|
|
157
|
+
const ca = cross(c, a);
|
|
158
|
+
const ab = cross(a, b);
|
|
159
|
+
const det = dot(a, bc);
|
|
160
|
+
const eye = scale(add(add(scale(bc, m[12]), scale(ca, m[13])), scale(ab, m[15])), -1 / det);
|
|
161
|
+
return Number.isFinite(eye.x + eye.y + eye.z) ? eye : null;
|
|
141
162
|
}
|
|
142
163
|
var CLIP_SIDES = [
|
|
143
164
|
(p) => p[2] + p[3],
|
|
@@ -153,6 +174,7 @@ function lerpClip(a, b, t) {
|
|
|
153
174
|
}
|
|
154
175
|
var EDGE_BITS = [1, 2, 4];
|
|
155
176
|
function projectAabbToScreen(box, viewProjection, rect) {
|
|
177
|
+
if (!(rect.width > 0 && rect.height > 0)) return null;
|
|
156
178
|
const corners = [];
|
|
157
179
|
for (let i = 0; i < 8; i++) {
|
|
158
180
|
const corner = {
|
|
@@ -186,7 +208,7 @@ function projectAabbToScreen(box, viewProjection, rect) {
|
|
|
186
208
|
for (const side of CLIP_SIDES) {
|
|
187
209
|
const da = side(a);
|
|
188
210
|
const db = side(b);
|
|
189
|
-
if (da < 0 && db < 0) {
|
|
211
|
+
if (Number.isNaN(da) || Number.isNaN(db) || da < 0 && db < 0) {
|
|
190
212
|
t0 = 1;
|
|
191
213
|
t1 = 0;
|
|
192
214
|
break;
|
|
@@ -200,7 +222,8 @@ function projectAabbToScreen(box, viewProjection, rect) {
|
|
|
200
222
|
}
|
|
201
223
|
}
|
|
202
224
|
if (!any) return null;
|
|
203
|
-
|
|
225
|
+
const out = { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
226
|
+
return Number.isFinite(out.x + out.y + out.width + out.height) ? out : null;
|
|
204
227
|
}
|
|
205
228
|
function rectOf(viewport) {
|
|
206
229
|
return {
|
|
@@ -217,8 +240,9 @@ function rayAt(viewport, point) {
|
|
|
217
240
|
return rayThroughScreenPoint(point, rectOf(viewport), viewProjectionOf(viewport));
|
|
218
241
|
}
|
|
219
242
|
function projectPoint(p, viewport) {
|
|
243
|
+
if (!(viewport.width > 0 && viewport.height > 0)) return null;
|
|
220
244
|
const [x, y, z, w] = transformPoint4(viewProjectionOf(viewport), p);
|
|
221
|
-
if (!(w > 0) || z
|
|
245
|
+
if (!(w > 0) || !(z >= -w)) return null;
|
|
222
246
|
const screen = ndcToScreen({ x: x / w, y: y / w }, rectOf(viewport));
|
|
223
247
|
return Number.isFinite(screen.x) && Number.isFinite(screen.y) ? screen : null;
|
|
224
248
|
}
|
|
@@ -257,8 +281,8 @@ function worldPoses(scene, reading) {
|
|
|
257
281
|
return (id) => frame.world(id);
|
|
258
282
|
}
|
|
259
283
|
const adapter = scenePoseAdapter(untyped);
|
|
260
|
-
const
|
|
261
|
-
return (id) => composeWorldPose(adapter, id,
|
|
284
|
+
const compose3 = scene.composition.compose;
|
|
285
|
+
return (id) => composeWorldPose(adapter, id, compose3);
|
|
262
286
|
}
|
|
263
287
|
function boxAt(world, node, pose) {
|
|
264
288
|
if (world.bounds) return world.bounds(pose === node.pose ? node : { ...node, pose });
|
|
@@ -532,16 +556,17 @@ function createSession3d(inputs) {
|
|
|
532
556
|
store.set([]);
|
|
533
557
|
} else {
|
|
534
558
|
const probes = frame.probes(anchor);
|
|
535
|
-
const
|
|
536
|
-
|
|
559
|
+
const at = probes.map((q) => ({ id: q.id, at: tuple(q.at), role: q.role }));
|
|
560
|
+
const res = resolveHolding(candidatesOf(inputs, sources, probes, delta0, vp, origin, up), (kept) => resolveSnap({
|
|
561
|
+
probes: at,
|
|
537
562
|
input,
|
|
538
563
|
delta0: tuple(delta0),
|
|
539
|
-
candidates:
|
|
564
|
+
candidates: kept,
|
|
540
565
|
toScreen: (w) => {
|
|
541
566
|
const s = projectPoint(vec3Of(w), vp);
|
|
542
567
|
return s ? [s.x, s.y] : [NaN, NaN];
|
|
543
568
|
}
|
|
544
|
-
});
|
|
569
|
+
}), vec3Of);
|
|
545
570
|
delta = vec3Of(res.delta);
|
|
546
571
|
held = res.members.length > 0;
|
|
547
572
|
store.set(resolvedSnapMarks(res.satisfied, delta));
|
|
@@ -724,8 +749,8 @@ function createPoseDescriptor(world, viewId) {
|
|
|
724
749
|
const ky = src.height === 0 ? 1 : dst.height / src.height;
|
|
725
750
|
const from = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
|
|
726
751
|
const to = {
|
|
727
|
-
x: dst.x + (from.x - src.x) * kx,
|
|
728
|
-
y: dst.y + (from.y - src.y) * ky
|
|
752
|
+
x: from.x + (dst.x - src.x) + (from.x - src.x) * (kx - 1),
|
|
753
|
+
y: from.y + (dst.y - src.y) + (from.y - src.y) * (ky - 1)
|
|
729
754
|
};
|
|
730
755
|
const plane = viewPlaneAt(vp.camera, pose.position);
|
|
731
756
|
const before = onPlane(vp, plane, from);
|
|
@@ -829,7 +854,7 @@ function createInsert(opts) {
|
|
|
829
854
|
const { u, v } = groundAxes(up);
|
|
830
855
|
return {
|
|
831
856
|
commit(bounds, extras, ends, select) {
|
|
832
|
-
const vp = opts.viewport();
|
|
857
|
+
const vp = opts.viewport(opts.viewId);
|
|
833
858
|
const hits = footprintPoints(bounds, ends, (p) => groundHit(vp, p, up));
|
|
834
859
|
if (hits.length === 0) return null;
|
|
835
860
|
let minU = Infinity, maxU = -Infinity, minV = Infinity, maxV = -Infinity;
|
|
@@ -945,7 +970,7 @@ function aimSpace3d(viewport, viewId, opts = {}) {
|
|
|
945
970
|
},
|
|
946
971
|
unproject(px) {
|
|
947
972
|
const hit = groundHit(viewport, { x: px.x + ox, y: px.y + oy }, up);
|
|
948
|
-
return hit && toAimPoint(hit, axes);
|
|
973
|
+
return hit && { ...toAimPoint(hit, axes), z: 0 };
|
|
949
974
|
}
|
|
950
975
|
};
|
|
951
976
|
}
|
|
@@ -954,8 +979,8 @@ function cameraOfAim3d(aim) {
|
|
|
954
979
|
}
|
|
955
980
|
var AXES = ["x", "y", "z"];
|
|
956
981
|
var BITS = [1, 2, 4];
|
|
957
|
-
var
|
|
958
|
-
var ACROSS = { x: [
|
|
982
|
+
var UNIT2 = { x: { x: 1, y: 0, z: 0 }, y: { x: 0, y: 1, z: 0 }, z: { x: 0, y: 0, z: 1 } };
|
|
983
|
+
var ACROSS = { x: [UNIT2.y, UNIT2.z], y: [UNIT2.x, UNIT2.z], z: [UNIT2.x, UNIT2.y] };
|
|
959
984
|
function boxesNear(world, q, r) {
|
|
960
985
|
return snapBoxes(world, q.exclude, q.probes.map(({ at: p }) => padQuery([p.x - r, p.y - r, p.z - r, p.x + r, p.y + r, p.z + r])));
|
|
961
986
|
}
|
|
@@ -1072,7 +1097,9 @@ function groundGridSource(opts) {
|
|
|
1072
1097
|
const up = normalize(opts.up ?? DEFAULT_UP);
|
|
1073
1098
|
const [u, v] = planeBasis(up);
|
|
1074
1099
|
const lines = (c) => {
|
|
1075
|
-
const
|
|
1100
|
+
const k = Math.round(c / opts.step);
|
|
1101
|
+
if (!Number.isSafeInteger(k)) return [];
|
|
1102
|
+
const near = k * opts.step;
|
|
1076
1103
|
return [near, near + (c >= near ? opts.step : -opts.step)];
|
|
1077
1104
|
};
|
|
1078
1105
|
return {
|
|
@@ -1139,9 +1166,10 @@ var HEAD = { lenRatio: 8, maxShare: 0.5, aspect: 7 / 16 };
|
|
|
1139
1166
|
var HEAD_OVERDRAW = 0.1;
|
|
1140
1167
|
function headFor(shaftHalf, linePx = Number.POSITIVE_INFINITY, head = HEAD) {
|
|
1141
1168
|
const wanted = shaftHalf * head.lenRatio;
|
|
1142
|
-
const
|
|
1169
|
+
const cap = head.maxShare === 0 ? 0 : head.maxShare * linePx;
|
|
1170
|
+
const headLen = Math.max(0, Math.min(wanted, cap));
|
|
1143
1171
|
const headHalf = headLen * head.aspect;
|
|
1144
|
-
const shrunk = wanted > 0 ? shaftHalf * headLen / wanted : shaftHalf;
|
|
1172
|
+
const shrunk = wanted > 0 ? shaftHalf * (headLen / wanted) : shaftHalf;
|
|
1145
1173
|
return { headLen, headHalf, shaftHalf: wanted > 0 ? Math.min(shrunk, headHalf) : shrunk };
|
|
1146
1174
|
}
|
|
1147
1175
|
function layoutLine(shaftHalf, length, pxPerUnit, head) {
|
|
@@ -1319,7 +1347,7 @@ void main() {
|
|
|
1319
1347
|
if (wanted > 0.0 && density > 0.0) {
|
|
1320
1348
|
headPx = max(0.0, min(wanted, a_shape.z * density * total));
|
|
1321
1349
|
headHalf = headPx * a_shape.w;
|
|
1322
|
-
shaft = min(shaftHalf * headPx / wanted, headHalf);
|
|
1350
|
+
shaft = min(shaftHalf * (headPx / wanted), headHalf);
|
|
1323
1351
|
shaftEnd = total - headPx / density * (1.0 - OVERDRAW);
|
|
1324
1352
|
}
|
|
1325
1353
|
|
|
@@ -1479,10 +1507,11 @@ function cameraStepper(dep, zero, relative, step) {
|
|
|
1479
1507
|
let reached = zero;
|
|
1480
1508
|
let written = origin;
|
|
1481
1509
|
let prior = origin;
|
|
1510
|
+
let caughtUp = false;
|
|
1482
1511
|
return (at) => {
|
|
1483
1512
|
if (!dep || !origin) return;
|
|
1484
1513
|
const now = dep.get();
|
|
1485
|
-
if (now !== written && now !== prior) {
|
|
1514
|
+
if (now !== written && (caughtUp || now !== prior)) {
|
|
1486
1515
|
origin = now;
|
|
1487
1516
|
from = reached;
|
|
1488
1517
|
}
|
|
@@ -1490,6 +1519,7 @@ function cameraStepper(dep, zero, relative, step) {
|
|
|
1490
1519
|
written = step(origin, relative(at, from), dep);
|
|
1491
1520
|
reached = at;
|
|
1492
1521
|
dep.set(written);
|
|
1522
|
+
caughtUp = dep.get() === written;
|
|
1493
1523
|
};
|
|
1494
1524
|
}
|
|
1495
1525
|
function dragCamera(ctx, kind, step) {
|