@pluot/core 0.1.0 → 0.1.1
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 +1 -0
- package/dist/index.js +1109 -4101
- package/dist-tsc/functional-3d-view-controls.d.ts +11 -0
- package/dist-tsc/functional-3d-view-controls.d.ts.map +1 -0
- package/dist-tsc/functional-3d-view-controls.js +517 -0
- package/dist-tsc/functional-dom-2d-camera.d.ts +9 -0
- package/dist-tsc/functional-dom-2d-camera.d.ts.map +1 -0
- package/dist-tsc/functional-dom-2d-camera.js +178 -0
- package/dist-tsc/index.d.ts +2 -2
- package/dist-tsc/index.d.ts.map +1 -1
- package/dist-tsc/index.js +2 -2
- package/dist-tsc/lru-store.d.ts.map +1 -1
- package/dist-tsc/lru-store.js +3 -0
- package/dist-tsc/unrolled-3d-view-controls.d.ts +2 -0
- package/dist-tsc/unrolled-3d-view-controls.d.ts.map +1 -0
- package/dist-tsc/unrolled-3d-view-controls.js +637 -0
- package/dist-tsc/unrolled-dom-2d-camera.d.ts +2 -0
- package/dist-tsc/unrolled-dom-2d-camera.d.ts.map +1 -0
- package/dist-tsc/unrolled-dom-2d-camera.js +193 -0
- package/dist-tsc/viewport.d.ts +91 -0
- package/dist-tsc/viewport.d.ts.map +1 -1
- package/dist-tsc/viewport.js +91 -3
- package/dist-tsc/viewport.test.d.ts +2 -0
- package/dist-tsc/viewport.test.d.ts.map +1 -0
- package/dist-tsc/viewport.test.js +184 -0
- package/package.json +8 -2
- package/src/functional-3d-view-controls.ts +585 -0
- package/src/functional-dom-2d-camera.ts +214 -0
- package/src/index.ts +2 -2
- package/src/lru-store.ts +3 -0
- package/src/viewport.test.ts +262 -0
- package/src/viewport.ts +91 -3
- package/src/3d-view-controls.js +0 -271
- package/src/dom-2d-camera.js +0 -441
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Functional/stateless adaptation of dom-2d-camera.
|
|
2
|
+
//
|
|
3
|
+
// License copied from https://github.com/flekschas/dom-2d-camera/blob/master/LICENSE.md
|
|
4
|
+
//
|
|
5
|
+
// This software is released under the MIT license:
|
|
6
|
+
//
|
|
7
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
8
|
+
// this software and associated documentation files (the "Software"), to deal in
|
|
9
|
+
// the Software without restriction, including without limitation the rights to
|
|
10
|
+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
11
|
+
// the Software, and to permit persons to whom the Software is furnished to do so,
|
|
12
|
+
// subject to the following conditions:
|
|
13
|
+
//
|
|
14
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
// copies or substantial portions of the Software.
|
|
16
|
+
//
|
|
17
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
19
|
+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
20
|
+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
21
|
+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
22
|
+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
import { mat4, vec2 } from 'gl-matrix';
|
|
24
|
+
// Camera configuration constants (matching dom-2d-camera defaults)
|
|
25
|
+
const isFixed = false;
|
|
26
|
+
const isNdc = true;
|
|
27
|
+
const isPan = true;
|
|
28
|
+
const isPanInverted = [false, true];
|
|
29
|
+
const panSpeed = 1;
|
|
30
|
+
const isRotate = true;
|
|
31
|
+
const rotateSpeed = 1;
|
|
32
|
+
const defaultMouseDownMoveAction = "pan";
|
|
33
|
+
const mouseDownMoveModKey = "alt";
|
|
34
|
+
const isZoom = true;
|
|
35
|
+
const zoomSpeed = 1;
|
|
36
|
+
// Derived settings
|
|
37
|
+
const isPanX = Array.isArray(isPan) ? Boolean(isPan[0]) : Boolean(isPan);
|
|
38
|
+
const isPanY = Array.isArray(isPan) ? Boolean(isPan[1]) : Boolean(isPan);
|
|
39
|
+
const isPanXInverted = Array.isArray(isPanInverted) ? Boolean(isPanInverted[0]) : Boolean(isPanInverted);
|
|
40
|
+
const isPanYInverted = Array.isArray(isPanInverted) ? Boolean(isPanInverted[1]) : Boolean(isPanInverted);
|
|
41
|
+
const isZoomX = Array.isArray(isZoom) ? Boolean(isZoom[0]) : Boolean(isZoom);
|
|
42
|
+
const isZoomY = Array.isArray(isZoom) ? Boolean(isZoom[1]) : Boolean(isZoom);
|
|
43
|
+
const panOnMouseDownMove = defaultMouseDownMoveAction === "pan";
|
|
44
|
+
const KEY_MAP = {
|
|
45
|
+
alt: "altKey",
|
|
46
|
+
cmd: "metaKey",
|
|
47
|
+
ctrl: "ctrlKey",
|
|
48
|
+
meta: "metaKey",
|
|
49
|
+
shift: "shiftKey",
|
|
50
|
+
};
|
|
51
|
+
// --- Aspect ratio helpers ---
|
|
52
|
+
function computeAspectRatioFactors(vp) {
|
|
53
|
+
const aspectRatio = vp.width / vp.height;
|
|
54
|
+
let xFactor = 1.0;
|
|
55
|
+
let yFactor = 1.0;
|
|
56
|
+
if (vp.aspectRatioMode === "Contain") {
|
|
57
|
+
if (aspectRatio > 1.0)
|
|
58
|
+
xFactor = 1.0 / aspectRatio;
|
|
59
|
+
else if (aspectRatio < 1.0)
|
|
60
|
+
yFactor = aspectRatio;
|
|
61
|
+
}
|
|
62
|
+
else if (vp.aspectRatioMode === "Cover") {
|
|
63
|
+
if (aspectRatio > 1.0)
|
|
64
|
+
yFactor = aspectRatio;
|
|
65
|
+
else if (aspectRatio < 1.0)
|
|
66
|
+
xFactor = 1.0 / aspectRatio;
|
|
67
|
+
}
|
|
68
|
+
let xAlignTranslation = 0.0;
|
|
69
|
+
let yAlignTranslation = 0.0;
|
|
70
|
+
if (vp.aspectRatioAlignmentMode === "Start") {
|
|
71
|
+
xAlignTranslation = xFactor - 1.0;
|
|
72
|
+
yAlignTranslation = yFactor - 1.0;
|
|
73
|
+
}
|
|
74
|
+
else if (vp.aspectRatioAlignmentMode === "End") {
|
|
75
|
+
xAlignTranslation = 1.0 - xFactor;
|
|
76
|
+
yAlignTranslation = 1.0 - yFactor;
|
|
77
|
+
}
|
|
78
|
+
return { xFactor, yFactor, xAlignTranslation, yAlignTranslation };
|
|
79
|
+
}
|
|
80
|
+
// --- Event handlers ---
|
|
81
|
+
export function onWheel(viewportParams, prevCameraMatrix, event) {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
if ((!isZoomX && !isZoomY) || isFixed)
|
|
84
|
+
return prevCameraMatrix;
|
|
85
|
+
const { width: plotWidth, height: plotHeight, margins } = viewportParams;
|
|
86
|
+
const width = plotWidth - ((margins?.marginLeft ?? 0) + (margins?.marginRight ?? 0));
|
|
87
|
+
const height = plotHeight - ((margins?.marginBottom ?? 0) + (margins?.marginTop ?? 0));
|
|
88
|
+
const { xFactor, yFactor, xAlignTranslation, yAlignTranslation } = computeAspectRatioFactors(viewportParams);
|
|
89
|
+
const deltaModeScale = event.deltaMode === 1 ? 12 : 1;
|
|
90
|
+
const scrollDist = deltaModeScale * (event.deltaY || event.deltaX || 0);
|
|
91
|
+
if (!scrollDist)
|
|
92
|
+
return prevCameraMatrix;
|
|
93
|
+
const dZ = zoomSpeed * Math.exp(scrollDist / height);
|
|
94
|
+
const px = isNdc
|
|
95
|
+
? ((-1 + (event.offsetX / width) * 2) - xAlignTranslation) * (1.0 / xFactor)
|
|
96
|
+
: event.offsetX;
|
|
97
|
+
const py = isNdc
|
|
98
|
+
? ((1 - (event.offsetY / height) * 2) - yAlignTranslation) * (1.0 / yFactor)
|
|
99
|
+
: event.offsetY;
|
|
100
|
+
let dx = isZoomX ? 1 / dZ : 1;
|
|
101
|
+
let dy = isZoomY ? 1 / dZ : 1;
|
|
102
|
+
if (dx <= 0 || dy <= 0 || (dx === 1 && dy === 1))
|
|
103
|
+
return prevCameraMatrix;
|
|
104
|
+
const view = mat4.clone(prevCameraMatrix);
|
|
105
|
+
const s = mat4.fromScaling(mat4.create(), new Float32Array([dx, dy, 1]));
|
|
106
|
+
const p = new Float32Array([px, py, 0]);
|
|
107
|
+
const a = mat4.fromTranslation(mat4.create(), p);
|
|
108
|
+
const aInv = mat4.invert(mat4.create(), a);
|
|
109
|
+
// view = a * s * aInv * prevCameraMatrix (scale about mouse pivot)
|
|
110
|
+
mat4.multiply(view, aInv, view);
|
|
111
|
+
mat4.multiply(view, s, view);
|
|
112
|
+
mat4.multiply(view, a, view);
|
|
113
|
+
return view;
|
|
114
|
+
}
|
|
115
|
+
export function onMouseMove(viewportParams, prevCameraMatrix, event) {
|
|
116
|
+
const { width: plotWidth, height: plotHeight, margins } = viewportParams;
|
|
117
|
+
const width = plotWidth - ((margins?.marginLeft ?? 0) + (margins?.marginRight ?? 0));
|
|
118
|
+
const height = plotHeight - ((margins?.marginBottom ?? 0) + (margins?.marginTop ?? 0));
|
|
119
|
+
const { xFactor, yFactor } = computeAspectRatioFactors(viewportParams);
|
|
120
|
+
const isLeftMousePressed = (event.buttons & 1) !== 0;
|
|
121
|
+
const isMouseDownMoveModActive = Boolean(event[KEY_MAP[mouseDownMoveModKey]]);
|
|
122
|
+
const view = mat4.clone(prevCameraMatrix);
|
|
123
|
+
let changed = false;
|
|
124
|
+
// Pan
|
|
125
|
+
if ((isPanX || isPanY) &&
|
|
126
|
+
isLeftMousePressed &&
|
|
127
|
+
((panOnMouseDownMove && !isMouseDownMoveModActive) ||
|
|
128
|
+
(!panOnMouseDownMove && isMouseDownMoveModActive))) {
|
|
129
|
+
const dX = isPanXInverted ? -event.movementX : event.movementX;
|
|
130
|
+
const dY = isPanYInverted ? -event.movementY : event.movementY;
|
|
131
|
+
const tx = isPanX ? (isNdc ? ((panSpeed * dX) / width) * 2 * (1.0 / xFactor) : panSpeed * dX) : 0;
|
|
132
|
+
const ty = isPanY ? (isNdc ? ((panSpeed * dY) / height) * 2 * (1.0 / yFactor) : -(panSpeed * dY)) : 0;
|
|
133
|
+
if (tx !== 0 || ty !== 0) {
|
|
134
|
+
const t = mat4.fromTranslation(mat4.create(), new Float32Array([tx, ty, 0]));
|
|
135
|
+
mat4.multiply(view, t, view);
|
|
136
|
+
changed = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Rotate
|
|
140
|
+
if (isRotate &&
|
|
141
|
+
isLeftMousePressed &&
|
|
142
|
+
((panOnMouseDownMove && isMouseDownMoveModActive) ||
|
|
143
|
+
(!panOnMouseDownMove && !isMouseDownMoveModActive)) &&
|
|
144
|
+
(Math.abs(event.movementX) + Math.abs(event.movementY)) > 0) {
|
|
145
|
+
const wh = width / 2;
|
|
146
|
+
const hh = height / 2;
|
|
147
|
+
const x1 = (event.offsetX - event.movementX) - wh;
|
|
148
|
+
const y1 = hh - (event.offsetY - event.movementY);
|
|
149
|
+
const x2 = event.offsetX - wh;
|
|
150
|
+
const y2 = hh - event.offsetY;
|
|
151
|
+
if (x1 * x1 + y1 * y1 > 0 && x2 * x2 + y2 * y2 > 0) {
|
|
152
|
+
const radians = vec2.angle([x1, y1], [x2, y2]);
|
|
153
|
+
const cross = x1 * y2 - x2 * y1;
|
|
154
|
+
const rad = rotateSpeed * radians * Math.sign(cross);
|
|
155
|
+
if (rad !== 0) {
|
|
156
|
+
const r = mat4.fromRotation(mat4.create(), rad, new Float32Array([0, 0, 1]));
|
|
157
|
+
mat4.multiply(view, r, view);
|
|
158
|
+
changed = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return changed ? view : prevCameraMatrix;
|
|
163
|
+
}
|
|
164
|
+
// These handlers don't modify the camera matrix in the functional approach.
|
|
165
|
+
// Button state is read from event.buttons in onMouseMove; modifier key state
|
|
166
|
+
// is read from event.altKey/etc. in onMouseMove.
|
|
167
|
+
export function onMouseDown(_viewportParams, prevCameraMatrix, _event) {
|
|
168
|
+
return prevCameraMatrix;
|
|
169
|
+
}
|
|
170
|
+
export function onMouseUp(_viewportParams, prevCameraMatrix, _event) {
|
|
171
|
+
return prevCameraMatrix;
|
|
172
|
+
}
|
|
173
|
+
export function onKeyDown(_viewportParams, prevCameraMatrix, _event) {
|
|
174
|
+
return prevCameraMatrix;
|
|
175
|
+
}
|
|
176
|
+
export function onKeyUp(_viewportParams, prevCameraMatrix, _event) {
|
|
177
|
+
return prevCameraMatrix;
|
|
178
|
+
}
|
package/dist-tsc/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { initialize, getIsWasmReady, render_wasm, pick_wasm, setStore, setStoreByName, getStore, } from './core.js';
|
|
2
|
-
export { default as create2dCamera } from "./dom-2d-camera.js";
|
|
3
|
-
export { default as create3dCamera } from "./3d-view-controls.js";
|
|
4
2
|
export { checkWebGpuFeatureDetection } from './feature-detection.js';
|
|
5
3
|
export { getBounds, getCameraMatrixFromBounds } from './viewport.js';
|
|
4
|
+
export { onMouseMove as onMouseMove2d, onWheel as onWheel2d } from './functional-dom-2d-camera.js';
|
|
5
|
+
export { onMouseMove as onMouseMove3d, onWheel as onWheel3d } from './functional-3d-view-controls.js';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist-tsc/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,EACX,SAAS,EACT,QAAQ,EACR,cAAc,EACd,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,cAAc,EACd,WAAW,EACX,SAAS,EACT,QAAQ,EACR,cAAc,EACd,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,WAAW,IAAI,aAAa,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAC;AACnG,OAAO,EAAE,WAAW,IAAI,aAAa,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,kCAAkC,CAAC"}
|
package/dist-tsc/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { initialize, getIsWasmReady, render_wasm, pick_wasm, setStore, setStoreByName, getStore, } from './core.js';
|
|
2
|
-
export { default as create2dCamera } from "./dom-2d-camera.js";
|
|
3
|
-
export { default as create3dCamera } from "./3d-view-controls.js";
|
|
4
2
|
export { checkWebGpuFeatureDetection } from './feature-detection.js';
|
|
5
3
|
export { getBounds, getCameraMatrixFromBounds } from './viewport.js';
|
|
4
|
+
export { onMouseMove as onMouseMove2d, onWheel as onWheel2d } from './functional-dom-2d-camera.js';
|
|
5
|
+
export { onMouseMove as onMouseMove3d, onWheel as onWheel3d } from './functional-3d-view-controls.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lru-store.d.ts","sourceRoot":"","sources":["../src/lru-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,aAAa,EAAgB,MAAM,SAAS,CAAC;AAavE,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,IAEhD,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAmBhG;AAID,qBAAa,QAAQ,CAAC,CAAC,SAAS,aAAa,CAAE,YAAW,aAAa;;
|
|
1
|
+
{"version":3,"file":"lru-store.d.ts","sourceRoot":"","sources":["../src/lru-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAc,aAAa,EAAgB,MAAM,SAAS,CAAC;AAavE,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,IAEhD,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAmBhG;AAID,qBAAa,QAAQ,CAAC,CAAC,SAAS,aAAa,CAAE,YAAW,aAAa;;gBAYzD,KAAK,EAAE,CAAC,EAAE,OAAO,SAAM;IAW7B,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IA8BzE,OAAO,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS;IAQlF,QAAQ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAmChG,YAAY,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS;IAO/G,UAAU;CAUX;AAED,wBAAgB,GAAG,CAAC,WAAW,EAAE,aAAa,EAAE,OAAO,SAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,CAEtF"}
|
package/dist-tsc/lru-store.js
CHANGED
|
@@ -40,6 +40,9 @@ export class LruStore {
|
|
|
40
40
|
#cache;
|
|
41
41
|
// We need a way to synchronously peek at the promise state (a-la Bun's peek or Effect's Deferred.poll).
|
|
42
42
|
// We can probably do something more sophisticated but will try this first.
|
|
43
|
+
// TODO: should this map be stored on the Rust side instead, so that the peeking can be performed without
|
|
44
|
+
// the JS function call? Instead, JS would "push" the promise states by calling a Rust function upon any
|
|
45
|
+
// promise state change, via a new function exposed from Rust such as `wasm.push_promise_state(key, 'fulfilled')`
|
|
43
46
|
#promise_states;
|
|
44
47
|
constructor(store, maxSize = 100) {
|
|
45
48
|
this.#inner_store = store;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unrolled-3d-view-controls.d.ts","sourceRoot":"","sources":["../src/unrolled-3d-view-controls.js"],"names":[],"mappings":""}
|