insomni 0.2.0-alpha.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/LICENSE.md +674 -0
- package/README.md +234 -0
- package/dist/advanced.d.mts +76 -0
- package/dist/advanced.mjs +81 -0
- package/dist/assemble-BT3CXbSx.mjs +1574 -0
- package/dist/camera-view-DHmMiKvP.d.mts +326 -0
- package/dist/frame-mHNdKRpF.mjs +135 -0
- package/dist/index-CmMZCMJT.d.mts +39 -0
- package/dist/index-DkJfpntS.d.mts +2417 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6612 -0
- package/dist/internal.d.mts +892 -0
- package/dist/internal.mjs +566 -0
- package/dist/logger-DSyBF3Y_.mjs +15 -0
- package/dist/particles.d.mts +816 -0
- package/dist/particles.mjs +4804 -0
- package/dist/pipeline-BWCAZTKx.mjs +470 -0
- package/dist/pipeline-DE3a1Pnk.d.mts +115 -0
- package/dist/reactivity-B7I0pvzm.mjs +191 -0
- package/dist/reactivity.d.mts +2 -0
- package/dist/reactivity.mjs +2 -0
- package/dist/renderer-DzZqd1bY.d.mts +4566 -0
- package/dist/root-CHradZKM.mjs +30 -0
- package/dist/shape-DfZP9Jdk.mjs +349 -0
- package/dist/space-CeDnj6eu.mjs +11240 -0
- package/dist/spatial-Bd3Ay8I2.d.mts +85 -0
- package/dist/spatial-hash-C1crBjTo.mjs +77 -0
- package/dist/spatial.d.mts +2 -0
- package/dist/spatial.mjs +121 -0
- package/dist/text-font-D7GGDtTK.d.mts +185 -0
- package/dist/text-ttf.d.mts +91 -0
- package/dist/text-ttf.mjs +298 -0
- package/dist/texture-dABoqFoP.mjs +131 -0
- package/dist/viewport.d.mts +2 -0
- package/dist/viewport.mjs +274 -0
- package/package.json +69 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { n as createFrame } from "./frame-mHNdKRpF.mjs";
|
|
2
|
+
//#region src/viewport.ts
|
|
3
|
+
function resolveAxisMargin(spec) {
|
|
4
|
+
if (spec === void 0) return {
|
|
5
|
+
x: 0,
|
|
6
|
+
y: 0
|
|
7
|
+
};
|
|
8
|
+
if (typeof spec === "number") return {
|
|
9
|
+
x: spec,
|
|
10
|
+
y: spec
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
x: spec.x ?? 0,
|
|
14
|
+
y: spec.y ?? 0
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function resolveMargins(opts) {
|
|
18
|
+
return {
|
|
19
|
+
overshoot: resolveAxisMargin(opts.overshoot),
|
|
20
|
+
drift: resolveAxisMargin(opts.drift)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Shift the visible range along one axis so the content range stays in (or
|
|
25
|
+
* near) view. Returns the delta to apply to the visible endpoints.
|
|
26
|
+
*/
|
|
27
|
+
function clampOffset(s0, V, c0, L, overshoot, drift) {
|
|
28
|
+
const offset = s0 - c0;
|
|
29
|
+
let lo;
|
|
30
|
+
let hi;
|
|
31
|
+
if (V < L) {
|
|
32
|
+
const m = Math.abs(overshoot) * V;
|
|
33
|
+
lo = -m;
|
|
34
|
+
hi = L - V + m;
|
|
35
|
+
} else {
|
|
36
|
+
const m = Math.abs(drift) * V;
|
|
37
|
+
lo = L - V - m;
|
|
38
|
+
hi = m;
|
|
39
|
+
}
|
|
40
|
+
if (offset < lo) return lo - offset;
|
|
41
|
+
if (offset > hi) return hi - offset;
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
function clampCameraState(state) {
|
|
45
|
+
const bounds = state.panBounds;
|
|
46
|
+
if (!bounds) return false;
|
|
47
|
+
const z = state.camera.zoom;
|
|
48
|
+
if (!Number.isFinite(z) || z <= 0) return false;
|
|
49
|
+
const fw = state.frame.width;
|
|
50
|
+
const fh = state.frame.height;
|
|
51
|
+
const Vx = fw / z;
|
|
52
|
+
const Vy = fh / z;
|
|
53
|
+
const c = bounds.content;
|
|
54
|
+
const m = bounds.margins;
|
|
55
|
+
let changed = false;
|
|
56
|
+
if (Vx > 0) {
|
|
57
|
+
const dx = clampOffset(state.camera.x - Vx / 2, Vx, c.x, c.width, m.overshoot.x, m.drift.x);
|
|
58
|
+
if (dx !== 0) {
|
|
59
|
+
state.camera.x += dx;
|
|
60
|
+
changed = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (Vy > 0) {
|
|
64
|
+
const dy = clampOffset(state.camera.y - Vy / 2, Vy, c.y, c.height, m.overshoot.y, m.drift.y);
|
|
65
|
+
if (dy !== 0) {
|
|
66
|
+
state.camera.y += dy;
|
|
67
|
+
changed = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return changed;
|
|
71
|
+
}
|
|
72
|
+
function createViewport(options) {
|
|
73
|
+
const frame = options.frame;
|
|
74
|
+
const parent = options.parent ?? null;
|
|
75
|
+
const init = options.camera ?? {};
|
|
76
|
+
const camera = {
|
|
77
|
+
x: init.x ?? 0,
|
|
78
|
+
y: init.y ?? 0,
|
|
79
|
+
zoom: init.zoom ?? 1,
|
|
80
|
+
rotation: init.rotation ?? 0
|
|
81
|
+
};
|
|
82
|
+
const state = {
|
|
83
|
+
mode: "camera",
|
|
84
|
+
frame,
|
|
85
|
+
parent,
|
|
86
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
87
|
+
notifying: false,
|
|
88
|
+
clipRect: {
|
|
89
|
+
x: frame.x,
|
|
90
|
+
y: frame.y,
|
|
91
|
+
width: frame.width,
|
|
92
|
+
height: frame.height
|
|
93
|
+
},
|
|
94
|
+
camera,
|
|
95
|
+
baseCamera: { ...camera },
|
|
96
|
+
panBounds: options.panBounds ? {
|
|
97
|
+
content: options.panBounds.content,
|
|
98
|
+
margins: resolveMargins(options.panBounds)
|
|
99
|
+
} : null
|
|
100
|
+
};
|
|
101
|
+
clampCameraState(state);
|
|
102
|
+
state.baseCamera.x = state.camera.x;
|
|
103
|
+
state.baseCamera.y = state.camera.y;
|
|
104
|
+
const notify = () => notifyListeners(state);
|
|
105
|
+
return {
|
|
106
|
+
mode: "camera",
|
|
107
|
+
get frame() {
|
|
108
|
+
return state.frame;
|
|
109
|
+
},
|
|
110
|
+
get absoluteFrame() {
|
|
111
|
+
return absoluteFrame(state);
|
|
112
|
+
},
|
|
113
|
+
get parent() {
|
|
114
|
+
return state.parent;
|
|
115
|
+
},
|
|
116
|
+
get clipRect() {
|
|
117
|
+
return refreshClipRect(state);
|
|
118
|
+
},
|
|
119
|
+
get camera() {
|
|
120
|
+
return { ...state.camera };
|
|
121
|
+
},
|
|
122
|
+
panBy(dxPx, dyPx) {
|
|
123
|
+
if (dxPx === 0 && dyPx === 0) return;
|
|
124
|
+
const z = state.camera.zoom;
|
|
125
|
+
if (z === 0) return;
|
|
126
|
+
state.camera.x -= dxPx / z;
|
|
127
|
+
state.camera.y -= dyPx / z;
|
|
128
|
+
clampCameraState(state);
|
|
129
|
+
notify();
|
|
130
|
+
},
|
|
131
|
+
zoomAt(anchorSx, anchorSy, factor) {
|
|
132
|
+
const fx = typeof factor === "number" ? factor : factor.x ?? 1;
|
|
133
|
+
const fy = typeof factor === "number" ? factor : factor.y ?? 1;
|
|
134
|
+
const factorValue = fx === fy ? fx : Math.sqrt(fx * fy);
|
|
135
|
+
if (!Number.isFinite(factorValue) || factorValue <= 0 || factorValue === 1) return;
|
|
136
|
+
const abs = absoluteFrame(state);
|
|
137
|
+
const lx = anchorSx - abs.x;
|
|
138
|
+
const ly = anchorSy - abs.y;
|
|
139
|
+
const px = lx - abs.width / 2;
|
|
140
|
+
const py = ly - abs.height / 2;
|
|
141
|
+
const curZoom = state.camera.zoom;
|
|
142
|
+
const newZoom = curZoom * factorValue;
|
|
143
|
+
if (newZoom === curZoom) return;
|
|
144
|
+
const dz = 1 / curZoom - 1 / newZoom;
|
|
145
|
+
state.camera.x += px * dz;
|
|
146
|
+
state.camera.y += py * dz;
|
|
147
|
+
state.camera.zoom = newZoom;
|
|
148
|
+
clampCameraState(state);
|
|
149
|
+
notify();
|
|
150
|
+
},
|
|
151
|
+
reset() {
|
|
152
|
+
const b = state.baseCamera;
|
|
153
|
+
const c = state.camera;
|
|
154
|
+
if (c.x === b.x && c.y === b.y && c.zoom === b.zoom && c.rotation === b.rotation) return;
|
|
155
|
+
c.x = b.x;
|
|
156
|
+
c.y = b.y;
|
|
157
|
+
c.zoom = b.zoom;
|
|
158
|
+
c.rotation = b.rotation;
|
|
159
|
+
notify();
|
|
160
|
+
},
|
|
161
|
+
setFrame(nextFrame) {
|
|
162
|
+
if (sameFrame(state.frame, nextFrame)) {
|
|
163
|
+
state.frame = nextFrame;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
state.frame = nextFrame;
|
|
167
|
+
refreshClipRect(state);
|
|
168
|
+
clampCameraState(state);
|
|
169
|
+
notify();
|
|
170
|
+
},
|
|
171
|
+
setCamera(next) {
|
|
172
|
+
const c = state.camera;
|
|
173
|
+
const nx = next.x ?? c.x;
|
|
174
|
+
const ny = next.y ?? c.y;
|
|
175
|
+
const nz = next.zoom ?? c.zoom;
|
|
176
|
+
const nr = next.rotation ?? c.rotation;
|
|
177
|
+
if (nx === c.x && ny === c.y && nz === c.zoom && nr === c.rotation) return;
|
|
178
|
+
c.x = nx;
|
|
179
|
+
c.y = ny;
|
|
180
|
+
c.zoom = nz;
|
|
181
|
+
c.rotation = nr;
|
|
182
|
+
clampCameraState(state);
|
|
183
|
+
notify();
|
|
184
|
+
},
|
|
185
|
+
jumpCamera(next) {
|
|
186
|
+
this.setCamera(next);
|
|
187
|
+
},
|
|
188
|
+
screenToWorld(sx, sy) {
|
|
189
|
+
const abs = absoluteFrame(state);
|
|
190
|
+
const z = state.camera.zoom || 1;
|
|
191
|
+
const px = sx - (abs.x + abs.width / 2);
|
|
192
|
+
const py = sy - (abs.y + abs.height / 2);
|
|
193
|
+
return {
|
|
194
|
+
x: px / z + state.camera.x,
|
|
195
|
+
y: py / z + state.camera.y
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
worldToScreen(wx, wy) {
|
|
199
|
+
const abs = absoluteFrame(state);
|
|
200
|
+
const z = state.camera.zoom || 1;
|
|
201
|
+
return {
|
|
202
|
+
x: (wx - state.camera.x) * z + abs.x + abs.width / 2,
|
|
203
|
+
y: (wy - state.camera.y) * z + abs.y + abs.height / 2
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
setPanBounds(panBounds) {
|
|
207
|
+
state.panBounds = panBounds ? {
|
|
208
|
+
content: panBounds.content,
|
|
209
|
+
margins: resolveMargins(panBounds)
|
|
210
|
+
} : null;
|
|
211
|
+
if (clampCameraState(state)) notify();
|
|
212
|
+
},
|
|
213
|
+
onChange(cb) {
|
|
214
|
+
state.listeners.add(cb);
|
|
215
|
+
return () => {
|
|
216
|
+
state.listeners.delete(cb);
|
|
217
|
+
};
|
|
218
|
+
},
|
|
219
|
+
_state: state
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function absoluteFrame(state) {
|
|
223
|
+
if (!state.parent) return state.frame;
|
|
224
|
+
const parentAbs = state.parent.absoluteFrame;
|
|
225
|
+
if (parentAbs.x === 0 && parentAbs.y === 0) return state.frame;
|
|
226
|
+
return createFrame({
|
|
227
|
+
x: state.frame.x + parentAbs.x,
|
|
228
|
+
y: state.frame.y + parentAbs.y,
|
|
229
|
+
width: state.frame.width,
|
|
230
|
+
height: state.frame.height
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function refreshClipRect(state) {
|
|
234
|
+
const abs = absoluteFrame(state);
|
|
235
|
+
const rect = state.clipRect;
|
|
236
|
+
rect.x = abs.x;
|
|
237
|
+
rect.y = abs.y;
|
|
238
|
+
rect.width = abs.width;
|
|
239
|
+
rect.height = abs.height;
|
|
240
|
+
return rect;
|
|
241
|
+
}
|
|
242
|
+
function notifyListeners(state) {
|
|
243
|
+
if (state.notifying) return;
|
|
244
|
+
state.notifying = true;
|
|
245
|
+
try {
|
|
246
|
+
for (const cb of state.listeners) cb();
|
|
247
|
+
} finally {
|
|
248
|
+
state.notifying = false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function sameFrame(a, b) {
|
|
252
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Compute the world-space AABB visible within the camera viewport's frame
|
|
256
|
+
* (or a sub-rect of it). Useful for hit-testing and manual culling logic —
|
|
257
|
+
* `Renderer2D` applies render-time cull automatically.
|
|
258
|
+
*
|
|
259
|
+
* Rotation is ignored — the returned rect is the non-rotated AABB, which is
|
|
260
|
+
* a conservative over-approximation when the camera is rotated.
|
|
261
|
+
*/
|
|
262
|
+
function worldCullBounds(viewport, rect) {
|
|
263
|
+
const r = rect ?? viewport.absoluteFrame;
|
|
264
|
+
const tl = viewport.screenToWorld(r.x, r.y);
|
|
265
|
+
const br = viewport.screenToWorld(r.x + r.width, r.y + r.height);
|
|
266
|
+
return {
|
|
267
|
+
x: Math.min(tl.x, br.x),
|
|
268
|
+
y: Math.min(tl.y, br.y),
|
|
269
|
+
width: Math.abs(br.x - tl.x),
|
|
270
|
+
height: Math.abs(br.y - tl.y)
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
export { createViewport, worldCullBounds };
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "insomni",
|
|
3
|
+
"version": "0.2.0-alpha.0",
|
|
4
|
+
"description": "A WebGPU rendering engine.",
|
|
5
|
+
"license": "GPL-3.0",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.mts",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./advanced": {
|
|
16
|
+
"types": "./dist/advanced.d.mts",
|
|
17
|
+
"default": "./dist/advanced.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./internal": {
|
|
20
|
+
"types": "./dist/internal.d.mts",
|
|
21
|
+
"default": "./dist/internal.mjs"
|
|
22
|
+
},
|
|
23
|
+
"./particles": {
|
|
24
|
+
"types": "./dist/particles.d.mts",
|
|
25
|
+
"default": "./dist/particles.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./reactivity": {
|
|
28
|
+
"types": "./dist/reactivity.d.mts",
|
|
29
|
+
"default": "./dist/reactivity.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./spatial": {
|
|
32
|
+
"types": "./dist/spatial.d.mts",
|
|
33
|
+
"default": "./dist/spatial.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./text-ttf": {
|
|
36
|
+
"types": "./dist/text-ttf.d.mts",
|
|
37
|
+
"default": "./dist/text-ttf.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./viewport": {
|
|
40
|
+
"types": "./dist/viewport.d.mts",
|
|
41
|
+
"default": "./dist/viewport.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@chenglou/pretext": "^0.0.5",
|
|
50
|
+
"@dirtytalk/engine": "^0.0.3",
|
|
51
|
+
"@dirtytalk/spatial": "^0.0.3",
|
|
52
|
+
"earcut": "^2.2.4",
|
|
53
|
+
"typegpu": "^0.10.2",
|
|
54
|
+
"insomni-msdf-text": "0.1.0-alpha.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/earcut": "^2.1.4",
|
|
58
|
+
"@vitest/browser-playwright": "^4.0.0",
|
|
59
|
+
"playwright": "^1.56.0",
|
|
60
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@^0.1.23"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "vp pack",
|
|
64
|
+
"dev": "vp pack --watch",
|
|
65
|
+
"test": "vp test",
|
|
66
|
+
"test:browser": "vp test -c vite.browser-test.config.ts",
|
|
67
|
+
"check": "vp check"
|
|
68
|
+
}
|
|
69
|
+
}
|