@vibecook/ice 0.8.1 → 0.10.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/{chunk-VITC7U3J.js → chunk-5FQS7V2C.js} +82 -7
- package/dist/chunk-5FQS7V2C.js.map +1 -0
- package/dist/{chunk-M5SFRVMH.js → chunk-JWRHAM7L.js} +2 -2
- package/dist/{chunk-4BOR42FZ.js → chunk-MFTMPJUF.js} +9 -4
- package/dist/{chunk-4BOR42FZ.js.map → chunk-MFTMPJUF.js.map} +1 -1
- package/dist/devtools.js +1 -1
- package/dist/dom.js +2 -2
- package/dist/ground.js +646 -148
- package/dist/ground.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/r3f.js +3 -3
- package/dist/react.js +3 -3
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +11 -0
- package/dist/types/packages/core/src/behavior/types.d.ts +21 -0
- package/dist/types/packages/core/src/index.d.ts +1 -1
- package/dist/types/packages/core/src/settings/ground-config.d.ts +49 -1
- package/dist/types/packages/ground/src/index.d.ts +18 -1
- package/dist/types/packages/ground/src/passes/grid-classic.d.ts +30 -0
- package/dist/types/packages/ground/src/passes/grid-magnet.d.ts +31 -0
- package/dist/types/packages/ground/src/passes/grid.d.ts +19 -12
- package/dist/types/packages/ground/src/passes/magnet-collect.d.ts +63 -0
- package/dist/types/packages/ground/src/passes/wires-collect.d.ts +5 -1
- package/dist/types/packages/ground/src/poles.d.ts +47 -0
- package/dist/types/packages/react/src/infinite-canvas.d.ts +18 -1
- package/package.json +1 -1
- package/dist/chunk-VITC7U3J.js.map +0 -1
- /package/dist/{chunk-M5SFRVMH.js.map → chunk-JWRHAM7L.js.map} +0 -0
package/dist/ground.js
CHANGED
|
@@ -2,12 +2,17 @@ import {
|
|
|
2
2
|
Active,
|
|
3
3
|
Camera,
|
|
4
4
|
Culled,
|
|
5
|
+
CursorVisual,
|
|
5
6
|
DEFAULT_GRID_CONFIG,
|
|
7
|
+
DEFAULT_GRID_MAGNET_CONFIG,
|
|
6
8
|
DEFAULT_SNAP_GUIDES_CONFIG,
|
|
7
9
|
DEFAULT_WIRES_CONFIG,
|
|
8
10
|
Drag,
|
|
9
11
|
GuideLine,
|
|
12
|
+
LocalPointer,
|
|
10
13
|
MeasuredSize,
|
|
14
|
+
Pointer,
|
|
15
|
+
PointerWorld,
|
|
11
16
|
Port,
|
|
12
17
|
PortAnchor,
|
|
13
18
|
Position,
|
|
@@ -16,6 +21,7 @@ import {
|
|
|
16
21
|
Selected,
|
|
17
22
|
Size,
|
|
18
23
|
SpacingBar,
|
|
24
|
+
SpatialVersion,
|
|
19
25
|
Wire,
|
|
20
26
|
WireFrom,
|
|
21
27
|
WirePorts,
|
|
@@ -23,7 +29,7 @@ import {
|
|
|
23
29
|
defineQuery,
|
|
24
30
|
portSlots,
|
|
25
31
|
widgets
|
|
26
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-5FQS7V2C.js";
|
|
27
33
|
import {
|
|
28
34
|
portAnchor,
|
|
29
35
|
tessellateCubic,
|
|
@@ -136,6 +142,9 @@ function createLayer(host, world, renderer, passes) {
|
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
// ../ground/src/passes/grid.ts
|
|
145
|
+
import { Group as Group2 } from "three/webgpu";
|
|
146
|
+
|
|
147
|
+
// ../ground/src/passes/grid-classic.ts
|
|
139
148
|
import {
|
|
140
149
|
clamp,
|
|
141
150
|
float,
|
|
@@ -159,8 +168,7 @@ import {
|
|
|
159
168
|
Vector3
|
|
160
169
|
} from "three/webgpu";
|
|
161
170
|
var FULLSCREEN_TRIANGLE = new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]);
|
|
162
|
-
function
|
|
163
|
-
let cfg = { ...DEFAULT_GRID_CONFIG, ...initial };
|
|
171
|
+
function createClassicGrid(cfg) {
|
|
164
172
|
const uCamera = uniform(new Vector2(0, 0));
|
|
165
173
|
const uZoom = uniform(1);
|
|
166
174
|
const uDpr = uniform(1);
|
|
@@ -200,24 +208,20 @@ function createGridPass(initial = {}) {
|
|
|
200
208
|
mesh.frustumCulled = false;
|
|
201
209
|
mesh.renderOrder = 0;
|
|
202
210
|
return {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
arm: () => [],
|
|
206
|
-
// camera/resize dirt is layer-owned; config re-tunes call invalidate
|
|
207
|
-
collect(_world, frame) {
|
|
211
|
+
mesh,
|
|
212
|
+
setCamera(frame) {
|
|
208
213
|
uCamera.value.set(frame.camera.x, frame.camera.y);
|
|
209
214
|
uZoom.value = frame.camera.zoom;
|
|
210
215
|
uDpr.value = frame.dpr;
|
|
211
216
|
},
|
|
212
217
|
configure(next) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
uLevelWeight.value.set(...cfg.levelWeight);
|
|
218
|
+
uSpacings.value.set(...next.spacings);
|
|
219
|
+
uDotColor.value.set(...next.dotColor);
|
|
220
|
+
uDotAlpha.value = next.dotAlpha;
|
|
221
|
+
uFadeIn.value.set(...next.fadeIn);
|
|
222
|
+
uFadeOut.value.set(...next.fadeOut);
|
|
223
|
+
uDotRadius.value.set(...next.dotRadius);
|
|
224
|
+
uLevelWeight.value.set(...next.levelWeight);
|
|
221
225
|
},
|
|
222
226
|
dispose() {
|
|
223
227
|
geometry.dispose();
|
|
@@ -226,6 +230,45 @@ function createGridPass(initial = {}) {
|
|
|
226
230
|
};
|
|
227
231
|
}
|
|
228
232
|
|
|
233
|
+
// ../ground/src/passes/grid-magnet.ts
|
|
234
|
+
import {
|
|
235
|
+
Fn,
|
|
236
|
+
If,
|
|
237
|
+
Loop,
|
|
238
|
+
abs,
|
|
239
|
+
clamp as clamp2,
|
|
240
|
+
float as float2,
|
|
241
|
+
instanceIndex,
|
|
242
|
+
int,
|
|
243
|
+
length as length2,
|
|
244
|
+
max as max2,
|
|
245
|
+
min as min2,
|
|
246
|
+
mix as mix2,
|
|
247
|
+
mod,
|
|
248
|
+
positionGeometry as positionGeometry2,
|
|
249
|
+
select,
|
|
250
|
+
sign,
|
|
251
|
+
smoothstep as smoothstep2,
|
|
252
|
+
sqrt,
|
|
253
|
+
storage,
|
|
254
|
+
uniform as uniform2,
|
|
255
|
+
varying,
|
|
256
|
+
vec2,
|
|
257
|
+
vec4 as vec42,
|
|
258
|
+
Continue,
|
|
259
|
+
Discard
|
|
260
|
+
} from "three/tsl";
|
|
261
|
+
import {
|
|
262
|
+
BufferAttribute as BufferAttribute2,
|
|
263
|
+
Group,
|
|
264
|
+
InstancedBufferGeometry,
|
|
265
|
+
Mesh as Mesh2,
|
|
266
|
+
MeshBasicNodeMaterial as MeshBasicNodeMaterial2,
|
|
267
|
+
StorageBufferAttribute,
|
|
268
|
+
Vector2 as Vector22,
|
|
269
|
+
Vector3 as Vector32
|
|
270
|
+
} from "three/webgpu";
|
|
271
|
+
|
|
229
272
|
// ../ground/src/passes/soup-collect.ts
|
|
230
273
|
var SoupBuilder = class {
|
|
231
274
|
pos = [];
|
|
@@ -308,134 +351,6 @@ function parseCssColor(css) {
|
|
|
308
351
|
return [0.5, 0.5, 0.5, 1];
|
|
309
352
|
}
|
|
310
353
|
|
|
311
|
-
// ../ground/src/passes/guides-collect.ts
|
|
312
|
-
var guideQ = defineQuery([GuideLine]);
|
|
313
|
-
var barQ = defineQuery([SpacingBar]);
|
|
314
|
-
function collectGuides(world, frame, config = DEFAULT_SNAP_GUIDES_CONFIG) {
|
|
315
|
-
const soup = new SoupBuilder();
|
|
316
|
-
const cam = frame.camera;
|
|
317
|
-
const lw = config.lineWidth;
|
|
318
|
-
const guideColor = [config.color[0], config.color[1], config.color[2], config.guideAlpha];
|
|
319
|
-
const barColor = [config.color[0], config.color[1], config.color[2], config.spacingAlpha];
|
|
320
|
-
world.query(guideQ).each((b) => {
|
|
321
|
-
for (const r of b) {
|
|
322
|
-
const g = world.read(b.entity(r), GuideLine);
|
|
323
|
-
const fullSpan = g.from === g.to;
|
|
324
|
-
if (g.axis === "x") {
|
|
325
|
-
const sx = worldToScreen(g.at, 0, cam).x;
|
|
326
|
-
if (frame.width > 0 && (sx < -1 || sx > frame.width + 1)) continue;
|
|
327
|
-
const y0 = fullSpan ? 0 : worldToScreen(0, g.from, cam).y;
|
|
328
|
-
const y1 = fullSpan ? frame.height : worldToScreen(0, g.to, cam).y;
|
|
329
|
-
soup.rect(sx - lw / 2, Math.min(y0, y1), lw, Math.abs(y1 - y0), guideColor);
|
|
330
|
-
} else {
|
|
331
|
-
const sy = worldToScreen(0, g.at, cam).y;
|
|
332
|
-
if (frame.height > 0 && (sy < -1 || sy > frame.height + 1)) continue;
|
|
333
|
-
const x0 = fullSpan ? 0 : worldToScreen(g.from, 0, cam).x;
|
|
334
|
-
const x1 = fullSpan ? frame.width : worldToScreen(g.to, 0, cam).x;
|
|
335
|
-
soup.rect(Math.min(x0, x1), sy - lw / 2, Math.abs(x1 - x0), lw, guideColor);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
world.query(barQ).each((b) => {
|
|
340
|
-
for (const r of b) {
|
|
341
|
-
const s = world.read(b.entity(r), SpacingBar);
|
|
342
|
-
const t = config.tickPx;
|
|
343
|
-
if (s.axis === "x") {
|
|
344
|
-
const a = worldToScreen(s.from, s.perp, cam);
|
|
345
|
-
const bp = worldToScreen(s.to, s.perp, cam);
|
|
346
|
-
if (frame.width > 0 && (Math.max(a.x, bp.x) < 0 || Math.min(a.x, bp.x) > frame.width)) continue;
|
|
347
|
-
soup.rect(Math.min(a.x, bp.x), a.y - lw / 2, Math.abs(bp.x - a.x), lw, barColor);
|
|
348
|
-
soup.rect(a.x - lw / 2, a.y - t, lw, t * 2, barColor);
|
|
349
|
-
soup.rect(bp.x - lw / 2, bp.y - t, lw, t * 2, barColor);
|
|
350
|
-
} else {
|
|
351
|
-
const a = worldToScreen(s.perp, s.from, cam);
|
|
352
|
-
const bp = worldToScreen(s.perp, s.to, cam);
|
|
353
|
-
if (frame.height > 0 && (Math.max(a.y, bp.y) < 0 || Math.min(a.y, bp.y) > frame.height)) continue;
|
|
354
|
-
soup.rect(a.x - lw / 2, Math.min(a.y, bp.y), lw, Math.abs(bp.y - a.y), barColor);
|
|
355
|
-
soup.rect(a.x - t, a.y - lw / 2, t * 2, lw, barColor);
|
|
356
|
-
soup.rect(bp.x - t, bp.y - lw / 2, t * 2, lw, barColor);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
return soup.build();
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// ../ground/src/passes/soup-mesh.ts
|
|
364
|
-
import { attribute } from "three/tsl";
|
|
365
|
-
import { BufferAttribute as BufferAttribute2, BufferGeometry as BufferGeometry2, DoubleSide, Mesh as Mesh2, MeshBasicNodeMaterial as MeshBasicNodeMaterial2 } from "three/webgpu";
|
|
366
|
-
function createSoupMesh(renderOrder) {
|
|
367
|
-
const material = new MeshBasicNodeMaterial2();
|
|
368
|
-
material.fragmentNode = attribute("color", "vec4");
|
|
369
|
-
material.transparent = true;
|
|
370
|
-
material.depthTest = false;
|
|
371
|
-
material.depthWrite = false;
|
|
372
|
-
material.side = DoubleSide;
|
|
373
|
-
let geometry = new BufferGeometry2();
|
|
374
|
-
let capacity = 0;
|
|
375
|
-
const mesh = new Mesh2(geometry, material);
|
|
376
|
-
mesh.frustumCulled = false;
|
|
377
|
-
mesh.renderOrder = renderOrder;
|
|
378
|
-
mesh.visible = false;
|
|
379
|
-
return {
|
|
380
|
-
mesh,
|
|
381
|
-
update(soup) {
|
|
382
|
-
if (soup.vertexCount === 0) {
|
|
383
|
-
mesh.visible = false;
|
|
384
|
-
geometry.setDrawRange(0, 0);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (soup.vertexCount > capacity) {
|
|
388
|
-
capacity = Math.max(soup.vertexCount, capacity * 2, 256);
|
|
389
|
-
const old = geometry;
|
|
390
|
-
geometry = new BufferGeometry2();
|
|
391
|
-
const pos = new Float32Array(capacity * 3);
|
|
392
|
-
const col = new Float32Array(capacity * 4);
|
|
393
|
-
pos.set(soup.positions);
|
|
394
|
-
col.set(soup.colors);
|
|
395
|
-
geometry.setAttribute("position", new BufferAttribute2(pos, 3));
|
|
396
|
-
geometry.setAttribute("color", new BufferAttribute2(col, 4));
|
|
397
|
-
mesh.geometry = geometry;
|
|
398
|
-
old.dispose();
|
|
399
|
-
} else {
|
|
400
|
-
const pos = geometry.getAttribute("position");
|
|
401
|
-
const col = geometry.getAttribute("color");
|
|
402
|
-
pos.array.set(soup.positions);
|
|
403
|
-
col.array.set(soup.colors);
|
|
404
|
-
pos.needsUpdate = true;
|
|
405
|
-
col.needsUpdate = true;
|
|
406
|
-
}
|
|
407
|
-
geometry.setDrawRange(0, soup.vertexCount);
|
|
408
|
-
mesh.visible = true;
|
|
409
|
-
},
|
|
410
|
-
dispose() {
|
|
411
|
-
geometry.dispose();
|
|
412
|
-
material.dispose();
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ../ground/src/passes/guides.ts
|
|
418
|
-
function createGuidesPass(initial = {}) {
|
|
419
|
-
const config = { ...DEFAULT_SNAP_GUIDES_CONFIG, ...initial };
|
|
420
|
-
const soup = createSoupMesh(2);
|
|
421
|
-
return {
|
|
422
|
-
name: "snap-guides",
|
|
423
|
-
object: soup.mesh,
|
|
424
|
-
arm(world, wake) {
|
|
425
|
-
return [
|
|
426
|
-
world.reactive.observeQuery(guideQ, wake, { cols: [GuideLine] }),
|
|
427
|
-
world.reactive.observeQuery(barQ, wake, { cols: [SpacingBar] })
|
|
428
|
-
];
|
|
429
|
-
},
|
|
430
|
-
collect(world, frame) {
|
|
431
|
-
soup.update(collectGuides(world, frame, config));
|
|
432
|
-
},
|
|
433
|
-
dispose() {
|
|
434
|
-
soup.dispose();
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
354
|
// ../ground/src/passes/wires-collect.ts
|
|
440
355
|
var wireQ = defineQuery([Wire]);
|
|
441
356
|
var geometryQ = defineQuery([Position, Size]);
|
|
@@ -451,7 +366,7 @@ function outOfScope(world, e) {
|
|
|
451
366
|
var TESSELLATION_TOLERANCE_PX = 0.25;
|
|
452
367
|
var DASH_PX = 6;
|
|
453
368
|
var GAP_PX = 4;
|
|
454
|
-
function
|
|
369
|
+
function readWidgetRect(world, e) {
|
|
455
370
|
const p = world.get(e, Position);
|
|
456
371
|
if (p === void 0) return void 0;
|
|
457
372
|
const measured = world.get(e, MeasuredSize);
|
|
@@ -470,7 +385,7 @@ function slotOf(wt, portId) {
|
|
|
470
385
|
}
|
|
471
386
|
function endpointAnchor(world, widget, portId) {
|
|
472
387
|
if (widget === void 0) return void 0;
|
|
473
|
-
const rect =
|
|
388
|
+
const rect = readWidgetRect(world, widget);
|
|
474
389
|
if (rect === void 0) return void 0;
|
|
475
390
|
const type = world.get(widget, PrefabId)?.id;
|
|
476
391
|
if (typeof type !== "string") return void 0;
|
|
@@ -572,6 +487,540 @@ function collectWires(world, frame, config = DEFAULT_WIRES_CONFIG, preview) {
|
|
|
572
487
|
return soup.build();
|
|
573
488
|
}
|
|
574
489
|
|
|
490
|
+
// ../ground/src/passes/magnet-collect.ts
|
|
491
|
+
var MAGNET_SOURCE_FLOATS = 8;
|
|
492
|
+
var MAX_MAGNET_SOURCES = 256;
|
|
493
|
+
var MAX_MAGNET_INSTANCES = 22e4;
|
|
494
|
+
function resolveMagnet(cfg) {
|
|
495
|
+
return { ...DEFAULT_GRID_MAGNET_CONFIG, ...cfg.magnet };
|
|
496
|
+
}
|
|
497
|
+
function fadeOpacity(cssSpacing, fadeIn, fadeOut) {
|
|
498
|
+
const rise = (cssSpacing - fadeIn[0]) / Math.max(fadeIn[1] - fadeIn[0], 1e-4);
|
|
499
|
+
const fall = 1 - (cssSpacing - fadeOut[0]) / Math.max(fadeOut[1] - fadeOut[0], 1e-4);
|
|
500
|
+
return Math.min(Math.max(Math.min(rise, fall), 0), 1);
|
|
501
|
+
}
|
|
502
|
+
function magnetFieldScale(zoom, fadeZoom) {
|
|
503
|
+
if (fadeZoom <= 0) return 1;
|
|
504
|
+
const t = (zoom - 0.7 * fadeZoom) / (0.3 * fadeZoom);
|
|
505
|
+
return Math.min(Math.max(t, 0), 1);
|
|
506
|
+
}
|
|
507
|
+
function collectMagnetLevels(frame, cfg) {
|
|
508
|
+
const { camera } = frame;
|
|
509
|
+
const out = [];
|
|
510
|
+
const opacities = cfg.spacings.map((s) => fadeOpacity(s * camera.zoom, cfg.fadeIn, cfg.fadeOut));
|
|
511
|
+
for (let i = 0; i < cfg.spacings.length; i++) {
|
|
512
|
+
const spacing = cfg.spacings[i];
|
|
513
|
+
const opacity = opacities[i];
|
|
514
|
+
const weight = cfg.levelWeight[0] + i * cfg.levelWeight[1];
|
|
515
|
+
const alpha = opacity * weight * cfg.dotAlpha;
|
|
516
|
+
if (opacity < 0.01 || alpha <= 0) {
|
|
517
|
+
out.push({ i0: 0, j0: 0, cols: 0, rows: 0, count: 0, alpha: 0, skipModulo: 0 });
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const worldW = frame.width / camera.zoom;
|
|
521
|
+
const worldH = frame.height / camera.zoom;
|
|
522
|
+
const i0 = Math.floor(camera.x / spacing) - 1;
|
|
523
|
+
const j0 = Math.floor(camera.y / spacing) - 1;
|
|
524
|
+
const i1 = Math.ceil((camera.x + worldW) / spacing) + 1;
|
|
525
|
+
const j1 = Math.ceil((camera.y + worldH) / spacing) + 1;
|
|
526
|
+
const cols = Math.max(1, i1 - i0);
|
|
527
|
+
const rows = Math.max(1, j1 - j0);
|
|
528
|
+
const count = cols * rows;
|
|
529
|
+
if (count > MAX_MAGNET_INSTANCES) {
|
|
530
|
+
out.push({ i0: 0, j0: 0, cols: 0, rows: 0, count: 0, alpha: 0, skipModulo: 0 });
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
let skipModulo = 0;
|
|
534
|
+
const next = cfg.spacings[i + 1];
|
|
535
|
+
if (next !== void 0 && opacities[i + 1] >= 0.01) {
|
|
536
|
+
const ratio = next / spacing;
|
|
537
|
+
if (Number.isInteger(ratio) && ratio > 1) skipModulo = ratio;
|
|
538
|
+
}
|
|
539
|
+
out.push({ i0, j0, cols, rows, count, alpha, skipModulo });
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function collectMagnetSources(world, frame, magnet, fieldScale, poles, readSpatial, out) {
|
|
544
|
+
if (fieldScale <= 0) return 0;
|
|
545
|
+
const { camera } = frame;
|
|
546
|
+
const cap = Math.min(magnet.maxSources, MAX_MAGNET_SOURCES, Math.floor(out.length / MAGNET_SOURCE_FLOATS));
|
|
547
|
+
let n = 0;
|
|
548
|
+
for (const pole of poles) {
|
|
549
|
+
if (n >= cap) break;
|
|
550
|
+
if (!(pole.strength > 0)) continue;
|
|
551
|
+
const s = pole.space === "screen" ? pole : worldToScreen(pole.x, pole.y, camera);
|
|
552
|
+
const o = n * MAGNET_SOURCE_FLOATS;
|
|
553
|
+
out[o + 0] = s.x;
|
|
554
|
+
out[o + 1] = s.y;
|
|
555
|
+
out[o + 2] = 0;
|
|
556
|
+
out[o + 3] = 0;
|
|
557
|
+
out[o + 4] = 0;
|
|
558
|
+
out[o + 5] = pole.strength * fieldScale;
|
|
559
|
+
out[o + 6] = 0;
|
|
560
|
+
out[o + 7] = 0;
|
|
561
|
+
n++;
|
|
562
|
+
}
|
|
563
|
+
if (!magnet.widgets || readSpatial === void 0 || magnet.widgetStrength <= 0 || n >= cap) {
|
|
564
|
+
return n;
|
|
565
|
+
}
|
|
566
|
+
const haloCss = 5 * magnet.reach * Math.sqrt(Math.max(magnet.widgetStrength * fieldScale, 1e-4));
|
|
567
|
+
const pad = haloCss / camera.zoom;
|
|
568
|
+
const hits = readSpatial({
|
|
569
|
+
minX: camera.x - pad,
|
|
570
|
+
minY: camera.y - pad,
|
|
571
|
+
maxX: camera.x + frame.width / camera.zoom + pad,
|
|
572
|
+
maxY: camera.y + frame.height / camera.zoom + pad
|
|
573
|
+
});
|
|
574
|
+
const viewCx = frame.width / 2;
|
|
575
|
+
const viewCy = frame.height / 2;
|
|
576
|
+
const candidates = [];
|
|
577
|
+
for (const hit of hits) {
|
|
578
|
+
const rect = readWidgetRect(world, hit.id);
|
|
579
|
+
if (rect === void 0 || rect.width <= 0 || rect.height <= 0) continue;
|
|
580
|
+
const hx = rect.width / 2 * camera.zoom;
|
|
581
|
+
const hy = rect.height / 2 * camera.zoom;
|
|
582
|
+
const center = worldToScreen(rect.x + rect.width / 2, rect.y + rect.height / 2, camera);
|
|
583
|
+
candidates.push({
|
|
584
|
+
cx: center.x,
|
|
585
|
+
cy: center.y,
|
|
586
|
+
hx,
|
|
587
|
+
hy,
|
|
588
|
+
r: Math.min(magnet.widgetRadius * camera.zoom, hx, hy),
|
|
589
|
+
area: hx * hy,
|
|
590
|
+
centerDist: (center.x - viewCx) ** 2 + (center.y - viewCy) ** 2
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
const budget = cap - n;
|
|
594
|
+
if (candidates.length > budget) {
|
|
595
|
+
candidates.sort((a, b) => b.area - a.area || a.centerDist - b.centerDist);
|
|
596
|
+
candidates.length = budget;
|
|
597
|
+
}
|
|
598
|
+
const strength = magnet.widgetStrength * fieldScale;
|
|
599
|
+
for (const c of candidates) {
|
|
600
|
+
const o = n * MAGNET_SOURCE_FLOATS;
|
|
601
|
+
out[o + 0] = c.cx;
|
|
602
|
+
out[o + 1] = c.cy;
|
|
603
|
+
out[o + 2] = c.hx;
|
|
604
|
+
out[o + 3] = c.hy;
|
|
605
|
+
out[o + 4] = c.r;
|
|
606
|
+
out[o + 5] = strength;
|
|
607
|
+
out[o + 6] = 0;
|
|
608
|
+
out[o + 7] = 0;
|
|
609
|
+
n++;
|
|
610
|
+
}
|
|
611
|
+
return n;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ../ground/src/passes/grid-magnet.ts
|
|
615
|
+
var CORNERS = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0]);
|
|
616
|
+
var LEVELS = 3;
|
|
617
|
+
function createMagnetGrid() {
|
|
618
|
+
const sourceArray = new Float32Array(MAX_MAGNET_SOURCES * MAGNET_SOURCE_FLOATS);
|
|
619
|
+
const sourceAttr = new StorageBufferAttribute(sourceArray, 4);
|
|
620
|
+
const sourceBuf = storage(sourceAttr, "vec4", MAX_MAGNET_SOURCES * 2).setPBO(true).toReadOnly();
|
|
621
|
+
const uCam = uniform2(new Vector22(0, 0));
|
|
622
|
+
const uZoom = uniform2(1);
|
|
623
|
+
const uView = uniform2(new Vector22(1, 1));
|
|
624
|
+
const uDotMode = uniform2(0);
|
|
625
|
+
const uHalfLen = uniform2(5);
|
|
626
|
+
const uHalfWidth = uniform2(0.55);
|
|
627
|
+
const uRestRadius = uniform2(0.75);
|
|
628
|
+
const uK = uniform2(0);
|
|
629
|
+
const uPad = uniform2(0);
|
|
630
|
+
const uPolarity = uniform2(1);
|
|
631
|
+
const uAlwaysAlign = uniform2(0);
|
|
632
|
+
const uCount = uniform2(0, "int");
|
|
633
|
+
const uColor = uniform2(new Vector32(0.75, 0.77, 0.8));
|
|
634
|
+
const EPS = float2(25);
|
|
635
|
+
const REST = vec2(0, 1);
|
|
636
|
+
const sdRoundBox = Fn(([p, b, r]) => {
|
|
637
|
+
const q = abs(p).sub(b).add(vec2(r, r));
|
|
638
|
+
return length2(max2(q, vec2(0, 0))).add(min2(max2(q.x, q.y), 0)).sub(r);
|
|
639
|
+
});
|
|
640
|
+
const roundBoxNormal = Fn(([p, b, r]) => {
|
|
641
|
+
const s = sign(p);
|
|
642
|
+
const q = abs(p);
|
|
643
|
+
const inner = max2(b.sub(vec2(r, r)), vec2(0, 0));
|
|
644
|
+
const d = q.sub(inner);
|
|
645
|
+
const n = vec2(0, 0).toVar();
|
|
646
|
+
If(d.x.greaterThan(0).and(d.y.greaterThan(0)), () => {
|
|
647
|
+
const len = length2(d);
|
|
648
|
+
If(len.greaterThan(1e-4), () => {
|
|
649
|
+
n.assign(s.mul(d.div(len)));
|
|
650
|
+
}).Else(() => {
|
|
651
|
+
n.assign(vec2(s.x, 0));
|
|
652
|
+
});
|
|
653
|
+
}).ElseIf(d.x.greaterThan(d.y), () => {
|
|
654
|
+
n.assign(vec2(s.x, 0));
|
|
655
|
+
}).Else(() => {
|
|
656
|
+
n.assign(vec2(0, s.y));
|
|
657
|
+
});
|
|
658
|
+
return n;
|
|
659
|
+
});
|
|
660
|
+
const safeNormalize = Fn(([v, fallback]) => {
|
|
661
|
+
const n = length2(v);
|
|
662
|
+
return select(n.greaterThan(1e-4), v.div(max2(n, 1e-4)), fallback);
|
|
663
|
+
});
|
|
664
|
+
const group = new Group();
|
|
665
|
+
const geometries = [];
|
|
666
|
+
const materials = [];
|
|
667
|
+
const meshes = [];
|
|
668
|
+
const levelUniforms = Array.from({ length: LEVELS }, () => ({
|
|
669
|
+
origin: uniform2(new Vector22(0, 0)),
|
|
670
|
+
dims: uniform2(new Vector22(1, 1)),
|
|
671
|
+
spacing: uniform2(20),
|
|
672
|
+
alpha: uniform2(0),
|
|
673
|
+
skipMod: uniform2(0)
|
|
674
|
+
}));
|
|
675
|
+
for (let level = 0; level < LEVELS; level++) {
|
|
676
|
+
const lu = levelUniforms[level];
|
|
677
|
+
const vUv = varying(vec2(0, 0), `vMagnetUv${level}`);
|
|
678
|
+
const vAlpha = varying(float2(0), `vMagnetAlpha${level}`);
|
|
679
|
+
const vertexNode = Fn(() => {
|
|
680
|
+
const iid = float2(instanceIndex);
|
|
681
|
+
const cols = lu.dims.x;
|
|
682
|
+
const col = mod(iid, cols).floor();
|
|
683
|
+
const row = iid.div(cols).floor();
|
|
684
|
+
const world = lu.origin.add(vec2(col, row)).mul(lu.spacing);
|
|
685
|
+
const screen = world.sub(uCam).mul(uZoom);
|
|
686
|
+
const field = vec2(0, 0).toVar();
|
|
687
|
+
const minD = float2(1e9).toVar();
|
|
688
|
+
Loop({ start: int(0), end: uCount, type: "int", condition: "<" }, ({ i }) => {
|
|
689
|
+
const a = sourceBuf.element(i.mul(2));
|
|
690
|
+
const b = sourceBuf.element(i.mul(2).add(1));
|
|
691
|
+
const center = a.xy;
|
|
692
|
+
const half = a.zw;
|
|
693
|
+
const rad = b.x;
|
|
694
|
+
const strength = b.y;
|
|
695
|
+
const rel = screen.sub(center);
|
|
696
|
+
const reach = uPad.mul(sqrt(max2(strength, 1e-4)));
|
|
697
|
+
If(
|
|
698
|
+
abs(rel.x).greaterThan(half.x.add(reach)).or(abs(rel.y).greaterThan(half.y.add(reach))),
|
|
699
|
+
() => {
|
|
700
|
+
Continue();
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
const d = sdRoundBox(rel, half, rad);
|
|
704
|
+
minD.assign(min2(minD, d));
|
|
705
|
+
const dist = max2(d, 0);
|
|
706
|
+
const r2 = dist.mul(dist).add(EPS);
|
|
707
|
+
const outward = roundBoxNormal(rel, half, rad);
|
|
708
|
+
field.subAssign(outward.mul(uK.mul(strength).mul(uPolarity).div(r2)));
|
|
709
|
+
});
|
|
710
|
+
const fieldDir = safeNormalize(field, REST);
|
|
711
|
+
const mag = length2(field);
|
|
712
|
+
const influence = select(uDotMode.greaterThan(0.5), mag.div(mag.add(1)), clamp2(mag, 0, 1));
|
|
713
|
+
const corner = positionGeometry2.xy;
|
|
714
|
+
const dotRadius = mix2(uRestRadius, uHalfLen.mul(1.05), influence);
|
|
715
|
+
const dotLocal = corner.mul(dotRadius);
|
|
716
|
+
const dir = select(
|
|
717
|
+
uAlwaysAlign.greaterThan(0.5),
|
|
718
|
+
fieldDir,
|
|
719
|
+
safeNormalize(mix2(REST, fieldDir, influence), REST)
|
|
720
|
+
);
|
|
721
|
+
const nrm = vec2(dir.y.negate(), dir.x);
|
|
722
|
+
const nLen = mix2(uHalfLen.mul(0.55), uHalfLen.mul(1.35), influence);
|
|
723
|
+
const nWid = mix2(uHalfWidth.mul(0.85), uHalfWidth.mul(1.15), influence);
|
|
724
|
+
const needleLocal = nrm.mul(corner.x).mul(nWid).add(dir.mul(corner.y).mul(nLen));
|
|
725
|
+
const local = select(uDotMode.greaterThan(0.5), dotLocal, needleLocal);
|
|
726
|
+
const pos = screen.add(local);
|
|
727
|
+
const restAlpha = select(uDotMode.greaterThan(0.5), float2(0.72), float2(0.35));
|
|
728
|
+
const fieldAlpha = mix2(restAlpha, float2(1), influence);
|
|
729
|
+
const alpha = clamp2(lu.alpha.mul(fieldAlpha), 0, 1).toVar();
|
|
730
|
+
If(minD.lessThan(-0.5), () => {
|
|
731
|
+
alpha.assign(0);
|
|
732
|
+
});
|
|
733
|
+
const absI = lu.origin.x.add(col);
|
|
734
|
+
const absJ = lu.origin.y.add(row);
|
|
735
|
+
If(
|
|
736
|
+
lu.skipMod.greaterThan(0.5).and(mod(absI, lu.skipMod).lessThan(0.5)).and(mod(absJ, lu.skipMod).lessThan(0.5)),
|
|
737
|
+
() => {
|
|
738
|
+
alpha.assign(0);
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
vUv.assign(corner);
|
|
742
|
+
vAlpha.assign(alpha);
|
|
743
|
+
const ndc = vec2(
|
|
744
|
+
pos.x.div(uView.x).mul(2).sub(1),
|
|
745
|
+
float2(1).sub(pos.y.div(uView.y).mul(2))
|
|
746
|
+
);
|
|
747
|
+
return vec42(ndc, 0, 1);
|
|
748
|
+
})();
|
|
749
|
+
const fragmentNode = Fn(() => {
|
|
750
|
+
const dotEdge = float2(1).sub(smoothstep2(0.62, 1, length2(vUv)));
|
|
751
|
+
const ax = float2(1).sub(smoothstep2(0.55, 1, abs(vUv.x)));
|
|
752
|
+
const ay = float2(1).sub(smoothstep2(0.82, 1, abs(vUv.y)));
|
|
753
|
+
const edge = select(uDotMode.greaterThan(0.5), dotEdge, ax.mul(ay));
|
|
754
|
+
const a = vAlpha.mul(edge);
|
|
755
|
+
Discard(a.lessThan(0.01));
|
|
756
|
+
return vec42(uColor, a);
|
|
757
|
+
})();
|
|
758
|
+
const material = new MeshBasicNodeMaterial2();
|
|
759
|
+
material.vertexNode = vertexNode;
|
|
760
|
+
material.fragmentNode = fragmentNode;
|
|
761
|
+
material.transparent = true;
|
|
762
|
+
material.depthTest = false;
|
|
763
|
+
material.depthWrite = false;
|
|
764
|
+
const geometry = new InstancedBufferGeometry();
|
|
765
|
+
geometry.setAttribute("position", new BufferAttribute2(CORNERS, 3));
|
|
766
|
+
geometry.instanceCount = 0;
|
|
767
|
+
const mesh = new Mesh2(geometry, material);
|
|
768
|
+
mesh.frustumCulled = false;
|
|
769
|
+
mesh.renderOrder = 0;
|
|
770
|
+
mesh.visible = false;
|
|
771
|
+
geometries.push(geometry);
|
|
772
|
+
materials.push(material);
|
|
773
|
+
meshes.push(mesh);
|
|
774
|
+
group.add(mesh);
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
group,
|
|
778
|
+
update(frame, cfg, magnet, levels, sources, sourceCount) {
|
|
779
|
+
uCam.value.set(frame.camera.x, frame.camera.y);
|
|
780
|
+
uZoom.value = frame.camera.zoom;
|
|
781
|
+
uView.value.set(Math.max(1, frame.width), Math.max(1, frame.height));
|
|
782
|
+
uDotMode.value = magnet.glyph === "dot" ? 1 : 0;
|
|
783
|
+
uHalfLen.value = magnet.needleLength;
|
|
784
|
+
uHalfWidth.value = magnet.needleWidth;
|
|
785
|
+
uRestRadius.value = cfg.dotRadius[0];
|
|
786
|
+
const k = 0.5 * magnet.reach * magnet.reach;
|
|
787
|
+
uK.value = k;
|
|
788
|
+
uPad.value = Math.sqrt(Math.max(k, 1) / 0.02);
|
|
789
|
+
uPolarity.value = magnet.polarity;
|
|
790
|
+
uAlwaysAlign.value = magnet.alwaysAlign ? 1 : 0;
|
|
791
|
+
uCount.value = sourceCount;
|
|
792
|
+
uColor.value.set(...cfg.dotColor);
|
|
793
|
+
if (sourceCount > 0) {
|
|
794
|
+
sourceArray.set(sources.subarray(0, sourceCount * MAGNET_SOURCE_FLOATS));
|
|
795
|
+
sourceAttr.needsUpdate = true;
|
|
796
|
+
}
|
|
797
|
+
for (let level = 0; level < LEVELS; level++) {
|
|
798
|
+
const win = levels[level];
|
|
799
|
+
const lu = levelUniforms[level];
|
|
800
|
+
const geometry = geometries[level];
|
|
801
|
+
const mesh = meshes[level];
|
|
802
|
+
if (win === void 0 || win.count <= 0) {
|
|
803
|
+
geometry.instanceCount = 0;
|
|
804
|
+
mesh.visible = false;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
lu.origin.value.set(win.i0, win.j0);
|
|
808
|
+
lu.dims.value.set(win.cols, win.rows);
|
|
809
|
+
lu.spacing.value = cfg.spacings[level];
|
|
810
|
+
lu.alpha.value = win.alpha;
|
|
811
|
+
lu.skipMod.value = win.skipModulo;
|
|
812
|
+
geometry.instanceCount = win.count;
|
|
813
|
+
mesh.visible = true;
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
dispose() {
|
|
817
|
+
for (const g of geometries) g.dispose();
|
|
818
|
+
for (const m of materials) m.dispose();
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ../ground/src/passes/grid.ts
|
|
824
|
+
function mergeGridConfig(base, next) {
|
|
825
|
+
const merged = { ...base, ...next };
|
|
826
|
+
if (next.magnet !== void 0) merged.magnet = { ...base.magnet, ...next.magnet };
|
|
827
|
+
return merged;
|
|
828
|
+
}
|
|
829
|
+
function createGridPass(initial = {}, deps = {}) {
|
|
830
|
+
let cfg = mergeGridConfig(DEFAULT_GRID_CONFIG, initial);
|
|
831
|
+
const group = new Group2();
|
|
832
|
+
const classic = createClassicGrid(cfg);
|
|
833
|
+
group.add(classic.mesh);
|
|
834
|
+
let magnet = null;
|
|
835
|
+
const poles = deps.poles ?? [];
|
|
836
|
+
const sourceScratch = new Float32Array(MAX_MAGNET_SOURCES * MAGNET_SOURCE_FLOATS);
|
|
837
|
+
const poleScratch = [];
|
|
838
|
+
return {
|
|
839
|
+
name: "grid",
|
|
840
|
+
object: group,
|
|
841
|
+
arm(world, wake) {
|
|
842
|
+
const wakeIfWidgets = () => {
|
|
843
|
+
const m = resolveMagnet(cfg);
|
|
844
|
+
if (m.enabled && m.widgets && deps.readSpatial !== void 0) wake();
|
|
845
|
+
};
|
|
846
|
+
const wakeIfEnabled = () => {
|
|
847
|
+
if (resolveMagnet(cfg).enabled) wake();
|
|
848
|
+
};
|
|
849
|
+
const unsubs = [world.reactive.observeResource(SpatialVersion, wakeIfWidgets)];
|
|
850
|
+
for (const source of poles) unsubs.push(source.subscribe(world, wakeIfEnabled));
|
|
851
|
+
return unsubs;
|
|
852
|
+
},
|
|
853
|
+
collect(world, frame) {
|
|
854
|
+
const m = resolveMagnet(cfg);
|
|
855
|
+
if (!m.enabled) {
|
|
856
|
+
classic.mesh.visible = true;
|
|
857
|
+
if (magnet !== null) magnet.group.visible = false;
|
|
858
|
+
classic.setCamera(frame);
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (magnet === null) {
|
|
862
|
+
magnet = createMagnetGrid();
|
|
863
|
+
group.add(magnet.group);
|
|
864
|
+
}
|
|
865
|
+
classic.mesh.visible = false;
|
|
866
|
+
magnet.group.visible = true;
|
|
867
|
+
const fieldScale = magnetFieldScale(frame.camera.zoom, m.fadeZoom);
|
|
868
|
+
poleScratch.length = 0;
|
|
869
|
+
if (fieldScale > 0) {
|
|
870
|
+
for (const source of poles) poleScratch.push(...source.read(world));
|
|
871
|
+
}
|
|
872
|
+
const sourceCount = collectMagnetSources(
|
|
873
|
+
world,
|
|
874
|
+
frame,
|
|
875
|
+
m,
|
|
876
|
+
fieldScale,
|
|
877
|
+
poleScratch,
|
|
878
|
+
deps.readSpatial,
|
|
879
|
+
sourceScratch
|
|
880
|
+
);
|
|
881
|
+
const levels = collectMagnetLevels(frame, cfg);
|
|
882
|
+
magnet.update(frame, cfg, m, levels, sourceScratch, sourceCount);
|
|
883
|
+
},
|
|
884
|
+
configure(next) {
|
|
885
|
+
cfg = mergeGridConfig(cfg, next);
|
|
886
|
+
classic.configure(cfg);
|
|
887
|
+
},
|
|
888
|
+
dispose() {
|
|
889
|
+
classic.dispose();
|
|
890
|
+
magnet?.dispose();
|
|
891
|
+
magnet = null;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ../ground/src/passes/guides-collect.ts
|
|
897
|
+
var guideQ = defineQuery([GuideLine]);
|
|
898
|
+
var barQ = defineQuery([SpacingBar]);
|
|
899
|
+
function collectGuides(world, frame, config = DEFAULT_SNAP_GUIDES_CONFIG) {
|
|
900
|
+
const soup = new SoupBuilder();
|
|
901
|
+
const cam = frame.camera;
|
|
902
|
+
const lw = config.lineWidth;
|
|
903
|
+
const guideColor = [config.color[0], config.color[1], config.color[2], config.guideAlpha];
|
|
904
|
+
const barColor = [config.color[0], config.color[1], config.color[2], config.spacingAlpha];
|
|
905
|
+
world.query(guideQ).each((b) => {
|
|
906
|
+
for (const r of b) {
|
|
907
|
+
const g = world.read(b.entity(r), GuideLine);
|
|
908
|
+
const fullSpan = g.from === g.to;
|
|
909
|
+
if (g.axis === "x") {
|
|
910
|
+
const sx = worldToScreen(g.at, 0, cam).x;
|
|
911
|
+
if (frame.width > 0 && (sx < -1 || sx > frame.width + 1)) continue;
|
|
912
|
+
const y0 = fullSpan ? 0 : worldToScreen(0, g.from, cam).y;
|
|
913
|
+
const y1 = fullSpan ? frame.height : worldToScreen(0, g.to, cam).y;
|
|
914
|
+
soup.rect(sx - lw / 2, Math.min(y0, y1), lw, Math.abs(y1 - y0), guideColor);
|
|
915
|
+
} else {
|
|
916
|
+
const sy = worldToScreen(0, g.at, cam).y;
|
|
917
|
+
if (frame.height > 0 && (sy < -1 || sy > frame.height + 1)) continue;
|
|
918
|
+
const x0 = fullSpan ? 0 : worldToScreen(g.from, 0, cam).x;
|
|
919
|
+
const x1 = fullSpan ? frame.width : worldToScreen(g.to, 0, cam).x;
|
|
920
|
+
soup.rect(Math.min(x0, x1), sy - lw / 2, Math.abs(x1 - x0), lw, guideColor);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
world.query(barQ).each((b) => {
|
|
925
|
+
for (const r of b) {
|
|
926
|
+
const s = world.read(b.entity(r), SpacingBar);
|
|
927
|
+
const t = config.tickPx;
|
|
928
|
+
if (s.axis === "x") {
|
|
929
|
+
const a = worldToScreen(s.from, s.perp, cam);
|
|
930
|
+
const bp = worldToScreen(s.to, s.perp, cam);
|
|
931
|
+
if (frame.width > 0 && (Math.max(a.x, bp.x) < 0 || Math.min(a.x, bp.x) > frame.width)) continue;
|
|
932
|
+
soup.rect(Math.min(a.x, bp.x), a.y - lw / 2, Math.abs(bp.x - a.x), lw, barColor);
|
|
933
|
+
soup.rect(a.x - lw / 2, a.y - t, lw, t * 2, barColor);
|
|
934
|
+
soup.rect(bp.x - lw / 2, bp.y - t, lw, t * 2, barColor);
|
|
935
|
+
} else {
|
|
936
|
+
const a = worldToScreen(s.perp, s.from, cam);
|
|
937
|
+
const bp = worldToScreen(s.perp, s.to, cam);
|
|
938
|
+
if (frame.height > 0 && (Math.max(a.y, bp.y) < 0 || Math.min(a.y, bp.y) > frame.height)) continue;
|
|
939
|
+
soup.rect(a.x - lw / 2, Math.min(a.y, bp.y), lw, Math.abs(bp.y - a.y), barColor);
|
|
940
|
+
soup.rect(a.x - t, a.y - lw / 2, t * 2, lw, barColor);
|
|
941
|
+
soup.rect(bp.x - t, bp.y - lw / 2, t * 2, lw, barColor);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
return soup.build();
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ../ground/src/passes/soup-mesh.ts
|
|
949
|
+
import { attribute } from "three/tsl";
|
|
950
|
+
import { BufferAttribute as BufferAttribute3, BufferGeometry as BufferGeometry2, DoubleSide, Mesh as Mesh3, MeshBasicNodeMaterial as MeshBasicNodeMaterial3 } from "three/webgpu";
|
|
951
|
+
function createSoupMesh(renderOrder) {
|
|
952
|
+
const material = new MeshBasicNodeMaterial3();
|
|
953
|
+
material.fragmentNode = attribute("color", "vec4");
|
|
954
|
+
material.transparent = true;
|
|
955
|
+
material.depthTest = false;
|
|
956
|
+
material.depthWrite = false;
|
|
957
|
+
material.side = DoubleSide;
|
|
958
|
+
let geometry = new BufferGeometry2();
|
|
959
|
+
let capacity = 0;
|
|
960
|
+
const mesh = new Mesh3(geometry, material);
|
|
961
|
+
mesh.frustumCulled = false;
|
|
962
|
+
mesh.renderOrder = renderOrder;
|
|
963
|
+
mesh.visible = false;
|
|
964
|
+
return {
|
|
965
|
+
mesh,
|
|
966
|
+
update(soup) {
|
|
967
|
+
if (soup.vertexCount === 0) {
|
|
968
|
+
mesh.visible = false;
|
|
969
|
+
geometry.setDrawRange(0, 0);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (soup.vertexCount > capacity) {
|
|
973
|
+
capacity = Math.max(soup.vertexCount, capacity * 2, 256);
|
|
974
|
+
const old = geometry;
|
|
975
|
+
geometry = new BufferGeometry2();
|
|
976
|
+
const pos = new Float32Array(capacity * 3);
|
|
977
|
+
const col = new Float32Array(capacity * 4);
|
|
978
|
+
pos.set(soup.positions);
|
|
979
|
+
col.set(soup.colors);
|
|
980
|
+
geometry.setAttribute("position", new BufferAttribute3(pos, 3));
|
|
981
|
+
geometry.setAttribute("color", new BufferAttribute3(col, 4));
|
|
982
|
+
mesh.geometry = geometry;
|
|
983
|
+
old.dispose();
|
|
984
|
+
} else {
|
|
985
|
+
const pos = geometry.getAttribute("position");
|
|
986
|
+
const col = geometry.getAttribute("color");
|
|
987
|
+
pos.array.set(soup.positions);
|
|
988
|
+
col.array.set(soup.colors);
|
|
989
|
+
pos.needsUpdate = true;
|
|
990
|
+
col.needsUpdate = true;
|
|
991
|
+
}
|
|
992
|
+
geometry.setDrawRange(0, soup.vertexCount);
|
|
993
|
+
mesh.visible = true;
|
|
994
|
+
},
|
|
995
|
+
dispose() {
|
|
996
|
+
geometry.dispose();
|
|
997
|
+
material.dispose();
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// ../ground/src/passes/guides.ts
|
|
1003
|
+
function createGuidesPass(initial = {}) {
|
|
1004
|
+
const config = { ...DEFAULT_SNAP_GUIDES_CONFIG, ...initial };
|
|
1005
|
+
const soup = createSoupMesh(2);
|
|
1006
|
+
return {
|
|
1007
|
+
name: "snap-guides",
|
|
1008
|
+
object: soup.mesh,
|
|
1009
|
+
arm(world, wake) {
|
|
1010
|
+
return [
|
|
1011
|
+
world.reactive.observeQuery(guideQ, wake, { cols: [GuideLine] }),
|
|
1012
|
+
world.reactive.observeQuery(barQ, wake, { cols: [SpacingBar] })
|
|
1013
|
+
];
|
|
1014
|
+
},
|
|
1015
|
+
collect(world, frame) {
|
|
1016
|
+
soup.update(collectGuides(world, frame, config));
|
|
1017
|
+
},
|
|
1018
|
+
dispose() {
|
|
1019
|
+
soup.dispose();
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
575
1024
|
// ../ground/src/passes/wires.ts
|
|
576
1025
|
function createWiresPass(initial = {}, readPreview) {
|
|
577
1026
|
const config = { ...DEFAULT_WIRES_CONFIG, ...initial };
|
|
@@ -682,12 +1131,53 @@ function createGroundRenderer(doc, opts = {}) {
|
|
|
682
1131
|
};
|
|
683
1132
|
}
|
|
684
1133
|
|
|
1134
|
+
// ../ground/src/poles.ts
|
|
1135
|
+
var localPointerQ = defineQuery([Pointer, PointerWorld, LocalPointer]);
|
|
1136
|
+
function localPointerPoles(opts = {}) {
|
|
1137
|
+
const strength = opts.strength ?? 1;
|
|
1138
|
+
return {
|
|
1139
|
+
read(world) {
|
|
1140
|
+
const out = [];
|
|
1141
|
+
world.query(localPointerQ).each((b) => {
|
|
1142
|
+
for (const r of b) {
|
|
1143
|
+
const p = world.read(b.entity(r), PointerWorld);
|
|
1144
|
+
out.push({ x: p.x, y: p.y, strength, space: "world" });
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
return out;
|
|
1148
|
+
},
|
|
1149
|
+
subscribe(world, wake) {
|
|
1150
|
+
return world.reactive.observeQuery(localPointerQ, wake, { cols: [PointerWorld] });
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
var cursorVisualQ = defineQuery([Position, CursorVisual]);
|
|
1155
|
+
function cursorVisualPoles(opts = {}) {
|
|
1156
|
+
const strength = opts.strength ?? 1;
|
|
1157
|
+
return {
|
|
1158
|
+
read(world) {
|
|
1159
|
+
const out = [];
|
|
1160
|
+
world.query(cursorVisualQ).each((b) => {
|
|
1161
|
+
for (const r of b) {
|
|
1162
|
+
const p = world.read(b.entity(r), Position);
|
|
1163
|
+
out.push({ x: p.x, y: p.y, strength, space: "world" });
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
return out;
|
|
1167
|
+
},
|
|
1168
|
+
subscribe(world, wake) {
|
|
1169
|
+
return world.reactive.observeQuery(cursorVisualQ, wake, { cols: [Position] });
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
|
|
685
1174
|
// ../ground/src/index.ts
|
|
686
1175
|
function ground(opts = {}) {
|
|
687
1176
|
return (ctx) => {
|
|
688
1177
|
const doc = ctx.host.container.ownerDocument;
|
|
689
1178
|
const renderer = opts.rendererOverride ?? createGroundRenderer(doc, { forceWebGL: opts.forceWebGL === true });
|
|
690
|
-
const
|
|
1179
|
+
const poles = opts.poles === void 0 ? [] : Array.isArray(opts.poles) ? opts.poles : [opts.poles];
|
|
1180
|
+
const grid = createGridPass(opts.grid ?? {}, { poles, ...ctx.readSpatial !== void 0 ? { readSpatial: ctx.readSpatial } : {} });
|
|
691
1181
|
const wires = createWiresPass(opts.wires ?? {}, ctx.readWirePreview);
|
|
692
1182
|
const guides = createGuidesPass(opts.guides ?? {});
|
|
693
1183
|
const passes = [grid, wires, guides, ...opts.passes ?? []];
|
|
@@ -704,12 +1194,20 @@ function ground(opts = {}) {
|
|
|
704
1194
|
}
|
|
705
1195
|
export {
|
|
706
1196
|
DEFAULT_GRID_CONFIG,
|
|
1197
|
+
DEFAULT_GRID_MAGNET_CONFIG,
|
|
707
1198
|
DEFAULT_SNAP_GUIDES_CONFIG,
|
|
708
1199
|
DEFAULT_WIRES_CONFIG,
|
|
1200
|
+
MAX_MAGNET_SOURCES,
|
|
709
1201
|
SoupBuilder,
|
|
710
1202
|
collectGuides,
|
|
1203
|
+
collectMagnetLevels,
|
|
1204
|
+
collectMagnetSources,
|
|
711
1205
|
collectWires,
|
|
1206
|
+
cursorVisualPoles,
|
|
712
1207
|
ground,
|
|
713
|
-
|
|
1208
|
+
localPointerPoles,
|
|
1209
|
+
magnetFieldScale,
|
|
1210
|
+
parseCssColor,
|
|
1211
|
+
resolveMagnet
|
|
714
1212
|
};
|
|
715
1213
|
//# sourceMappingURL=ground.js.map
|