@vibecook/ice 0.9.0 → 0.11.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-E33I7R2Z.js → chunk-2TNVB2ON.js} +2 -2
- package/dist/{chunk-HM25ULBP.js → chunk-Q665T4L7.js} +9 -4
- package/dist/{chunk-HM25ULBP.js.map → chunk-Q665T4L7.js.map} +1 -1
- package/dist/{chunk-LBRHND4G.js → chunk-VGVDJZSP.js} +16 -2
- package/dist/{chunk-LBRHND4G.js.map → chunk-VGVDJZSP.js.map} +1 -1
- package/dist/devtools.js +1 -1
- package/dist/dom.js +2 -2
- package/dist/ground.js +598 -205
- 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/index.d.ts +1 -1
- package/dist/types/packages/core/src/settings/ground-config.d.ts +47 -1
- package/dist/types/packages/ground/src/index.d.ts +18 -1
- package/dist/types/packages/ground/src/passes/grid-contract.d.ts +28 -0
- package/dist/types/packages/ground/src/passes/grid-magnet-pass.d.ts +2 -0
- package/dist/types/packages/ground/src/passes/grid-magnet.d.ts +31 -0
- package/dist/types/packages/ground/src/passes/grid.d.ts +7 -18
- 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-E33I7R2Z.js.map → chunk-2TNVB2ON.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-VGVDJZSP.js";
|
|
27
33
|
import {
|
|
28
34
|
portAnchor,
|
|
29
35
|
tessellateCubic,
|
|
@@ -135,96 +141,54 @@ function createLayer(host, world, renderer, passes) {
|
|
|
135
141
|
};
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
// ../ground/src/passes/grid.ts
|
|
144
|
+
// ../ground/src/passes/grid-contract.ts
|
|
145
|
+
function mergeGridConfig(base, next) {
|
|
146
|
+
const merged = { ...base, ...next };
|
|
147
|
+
if (next.magnet !== void 0) merged.magnet = { ...base.magnet, ...next.magnet };
|
|
148
|
+
return merged;
|
|
149
|
+
}
|
|
150
|
+
function resolveInitialGridConfig(initial) {
|
|
151
|
+
return mergeGridConfig(DEFAULT_GRID_CONFIG, initial);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ../ground/src/passes/grid-magnet.ts
|
|
139
155
|
import {
|
|
156
|
+
Fn,
|
|
157
|
+
If,
|
|
158
|
+
Loop,
|
|
159
|
+
abs,
|
|
140
160
|
clamp,
|
|
141
161
|
float,
|
|
142
|
-
|
|
162
|
+
instanceIndex,
|
|
163
|
+
int,
|
|
143
164
|
length,
|
|
144
165
|
max,
|
|
145
166
|
min,
|
|
146
167
|
mix,
|
|
168
|
+
mod,
|
|
147
169
|
positionGeometry,
|
|
148
|
-
|
|
170
|
+
select,
|
|
171
|
+
sign,
|
|
149
172
|
smoothstep,
|
|
173
|
+
sqrt,
|
|
174
|
+
storage,
|
|
150
175
|
uniform,
|
|
151
|
-
|
|
176
|
+
varying,
|
|
177
|
+
vec2,
|
|
178
|
+
vec4,
|
|
179
|
+
Continue,
|
|
180
|
+
Discard
|
|
152
181
|
} from "three/tsl";
|
|
153
182
|
import {
|
|
154
183
|
BufferAttribute,
|
|
155
|
-
|
|
184
|
+
Group,
|
|
185
|
+
InstancedBufferGeometry,
|
|
156
186
|
Mesh,
|
|
157
187
|
MeshBasicNodeMaterial,
|
|
188
|
+
StorageBufferAttribute,
|
|
158
189
|
Vector2,
|
|
159
190
|
Vector3
|
|
160
191
|
} from "three/webgpu";
|
|
161
|
-
var FULLSCREEN_TRIANGLE = new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]);
|
|
162
|
-
function createGridPass(initial = {}) {
|
|
163
|
-
let cfg = { ...DEFAULT_GRID_CONFIG, ...initial };
|
|
164
|
-
const uCamera = uniform(new Vector2(0, 0));
|
|
165
|
-
const uZoom = uniform(1);
|
|
166
|
-
const uDpr = uniform(1);
|
|
167
|
-
const uSpacings = uniform(new Vector3(...cfg.spacings));
|
|
168
|
-
const uDotColor = uniform(new Vector3(...cfg.dotColor));
|
|
169
|
-
const uDotAlpha = uniform(cfg.dotAlpha);
|
|
170
|
-
const uFadeIn = uniform(new Vector2(...cfg.fadeIn));
|
|
171
|
-
const uFadeOut = uniform(new Vector2(...cfg.fadeOut));
|
|
172
|
-
const uDotRadius = uniform(new Vector2(...cfg.dotRadius));
|
|
173
|
-
const uLevelWeight = uniform(new Vector2(...cfg.levelWeight));
|
|
174
|
-
const effZoom = uZoom.mul(uDpr);
|
|
175
|
-
const worldPos = screenCoordinate.xy.div(effZoom).add(uCamera);
|
|
176
|
-
let total = float(0);
|
|
177
|
-
for (let i = 0; i < 3; i++) {
|
|
178
|
-
const spacing = i === 0 ? uSpacings.x : i === 1 ? uSpacings.y : uSpacings.z;
|
|
179
|
-
const cssSpacing = spacing.mul(uZoom);
|
|
180
|
-
const rise = cssSpacing.sub(uFadeIn.x).div(uFadeIn.y.sub(uFadeIn.x));
|
|
181
|
-
const fall = float(1).sub(cssSpacing.sub(uFadeOut.x).div(uFadeOut.y.sub(uFadeOut.x)));
|
|
182
|
-
const opacity = clamp(min(rise, fall), 0, 1);
|
|
183
|
-
const f = fract(worldPos.div(spacing).add(0.5)).sub(0.5);
|
|
184
|
-
const dist = length(f).mul(spacing).mul(effZoom);
|
|
185
|
-
const t = clamp(cssSpacing.sub(uFadeIn.x).div(40), 0, 1);
|
|
186
|
-
const radius = mix(uDotRadius.x, uDotRadius.y, t).mul(uDpr);
|
|
187
|
-
const dot = smoothstep(radius.sub(0.5), radius.add(0.5), dist).oneMinus();
|
|
188
|
-
const weight = uLevelWeight.x.add(float(i).mul(uLevelWeight.y));
|
|
189
|
-
total = max(total, dot.mul(opacity).mul(weight));
|
|
190
|
-
}
|
|
191
|
-
const material = new MeshBasicNodeMaterial();
|
|
192
|
-
material.vertexNode = vec4(positionGeometry.xy, 0, 1);
|
|
193
|
-
material.fragmentNode = vec4(uDotColor, clamp(total.mul(uDotAlpha), 0, 1));
|
|
194
|
-
material.transparent = true;
|
|
195
|
-
material.depthTest = false;
|
|
196
|
-
material.depthWrite = false;
|
|
197
|
-
const geometry = new BufferGeometry();
|
|
198
|
-
geometry.setAttribute("position", new BufferAttribute(FULLSCREEN_TRIANGLE, 3));
|
|
199
|
-
const mesh = new Mesh(geometry, material);
|
|
200
|
-
mesh.frustumCulled = false;
|
|
201
|
-
mesh.renderOrder = 0;
|
|
202
|
-
return {
|
|
203
|
-
name: "grid",
|
|
204
|
-
object: mesh,
|
|
205
|
-
arm: () => [],
|
|
206
|
-
// camera/resize dirt is layer-owned; config re-tunes call invalidate
|
|
207
|
-
collect(_world, frame) {
|
|
208
|
-
uCamera.value.set(frame.camera.x, frame.camera.y);
|
|
209
|
-
uZoom.value = frame.camera.zoom;
|
|
210
|
-
uDpr.value = frame.dpr;
|
|
211
|
-
},
|
|
212
|
-
configure(next) {
|
|
213
|
-
cfg = { ...cfg, ...next };
|
|
214
|
-
uSpacings.value.set(...cfg.spacings);
|
|
215
|
-
uDotColor.value.set(...cfg.dotColor);
|
|
216
|
-
uDotAlpha.value = cfg.dotAlpha;
|
|
217
|
-
uFadeIn.value.set(...cfg.fadeIn);
|
|
218
|
-
uFadeOut.value.set(...cfg.fadeOut);
|
|
219
|
-
uDotRadius.value.set(...cfg.dotRadius);
|
|
220
|
-
uLevelWeight.value.set(...cfg.levelWeight);
|
|
221
|
-
},
|
|
222
|
-
dispose() {
|
|
223
|
-
geometry.dispose();
|
|
224
|
-
material.dispose();
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
192
|
|
|
229
193
|
// ../ground/src/passes/soup-collect.ts
|
|
230
194
|
var SoupBuilder = class {
|
|
@@ -308,134 +272,6 @@ function parseCssColor(css) {
|
|
|
308
272
|
return [0.5, 0.5, 0.5, 1];
|
|
309
273
|
}
|
|
310
274
|
|
|
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
275
|
// ../ground/src/passes/wires-collect.ts
|
|
440
276
|
var wireQ = defineQuery([Wire]);
|
|
441
277
|
var geometryQ = defineQuery([Position, Size]);
|
|
@@ -451,7 +287,7 @@ function outOfScope(world, e) {
|
|
|
451
287
|
var TESSELLATION_TOLERANCE_PX = 0.25;
|
|
452
288
|
var DASH_PX = 6;
|
|
453
289
|
var GAP_PX = 4;
|
|
454
|
-
function
|
|
290
|
+
function readWidgetRect(world, e) {
|
|
455
291
|
const p = world.get(e, Position);
|
|
456
292
|
if (p === void 0) return void 0;
|
|
457
293
|
const measured = world.get(e, MeasuredSize);
|
|
@@ -470,7 +306,7 @@ function slotOf(wt, portId) {
|
|
|
470
306
|
}
|
|
471
307
|
function endpointAnchor(world, widget, portId) {
|
|
472
308
|
if (widget === void 0) return void 0;
|
|
473
|
-
const rect =
|
|
309
|
+
const rect = readWidgetRect(world, widget);
|
|
474
310
|
if (rect === void 0) return void 0;
|
|
475
311
|
const type = world.get(widget, PrefabId)?.id;
|
|
476
312
|
if (typeof type !== "string") return void 0;
|
|
@@ -572,6 +408,514 @@ function collectWires(world, frame, config = DEFAULT_WIRES_CONFIG, preview) {
|
|
|
572
408
|
return soup.build();
|
|
573
409
|
}
|
|
574
410
|
|
|
411
|
+
// ../ground/src/passes/magnet-collect.ts
|
|
412
|
+
var MAGNET_SOURCE_FLOATS = 8;
|
|
413
|
+
var MAX_MAGNET_SOURCES = 256;
|
|
414
|
+
var MAX_MAGNET_INSTANCES = 22e4;
|
|
415
|
+
function resolveMagnet(cfg) {
|
|
416
|
+
return { ...DEFAULT_GRID_MAGNET_CONFIG, ...cfg.magnet };
|
|
417
|
+
}
|
|
418
|
+
function fadeOpacity(cssSpacing, fadeIn, fadeOut) {
|
|
419
|
+
const rise = (cssSpacing - fadeIn[0]) / Math.max(fadeIn[1] - fadeIn[0], 1e-4);
|
|
420
|
+
const fall = 1 - (cssSpacing - fadeOut[0]) / Math.max(fadeOut[1] - fadeOut[0], 1e-4);
|
|
421
|
+
return Math.min(Math.max(Math.min(rise, fall), 0), 1);
|
|
422
|
+
}
|
|
423
|
+
function magnetFieldScale(zoom, fadeZoom) {
|
|
424
|
+
if (fadeZoom <= 0) return 1;
|
|
425
|
+
const t = (zoom - 0.7 * fadeZoom) / (0.3 * fadeZoom);
|
|
426
|
+
return Math.min(Math.max(t, 0), 1);
|
|
427
|
+
}
|
|
428
|
+
function collectMagnetLevels(frame, cfg) {
|
|
429
|
+
const { camera } = frame;
|
|
430
|
+
const out = [];
|
|
431
|
+
const opacities = cfg.spacings.map((s) => fadeOpacity(s * camera.zoom, cfg.fadeIn, cfg.fadeOut));
|
|
432
|
+
for (let i = 0; i < cfg.spacings.length; i++) {
|
|
433
|
+
const spacing = cfg.spacings[i];
|
|
434
|
+
const opacity = opacities[i];
|
|
435
|
+
const weight = cfg.levelWeight[0] + i * cfg.levelWeight[1];
|
|
436
|
+
const alpha = opacity * weight * cfg.dotAlpha;
|
|
437
|
+
if (opacity < 0.01 || alpha <= 0) {
|
|
438
|
+
out.push({ i0: 0, j0: 0, cols: 0, rows: 0, count: 0, alpha: 0, skipModulo: 0 });
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const worldW = frame.width / camera.zoom;
|
|
442
|
+
const worldH = frame.height / camera.zoom;
|
|
443
|
+
const i0 = Math.floor(camera.x / spacing) - 1;
|
|
444
|
+
const j0 = Math.floor(camera.y / spacing) - 1;
|
|
445
|
+
const i1 = Math.ceil((camera.x + worldW) / spacing) + 1;
|
|
446
|
+
const j1 = Math.ceil((camera.y + worldH) / spacing) + 1;
|
|
447
|
+
const cols = Math.max(1, i1 - i0);
|
|
448
|
+
const rows = Math.max(1, j1 - j0);
|
|
449
|
+
const count = cols * rows;
|
|
450
|
+
if (count > MAX_MAGNET_INSTANCES) {
|
|
451
|
+
out.push({ i0: 0, j0: 0, cols: 0, rows: 0, count: 0, alpha: 0, skipModulo: 0 });
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
let skipModulo = 0;
|
|
455
|
+
const next = cfg.spacings[i + 1];
|
|
456
|
+
if (next !== void 0 && opacities[i + 1] >= 0.01) {
|
|
457
|
+
const ratio = next / spacing;
|
|
458
|
+
if (Number.isInteger(ratio) && ratio > 1) skipModulo = ratio;
|
|
459
|
+
}
|
|
460
|
+
out.push({ i0, j0, cols, rows, count, alpha, skipModulo });
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
function collectMagnetSources(world, frame, magnet, fieldScale, poles, readSpatial, out) {
|
|
465
|
+
if (fieldScale <= 0) return 0;
|
|
466
|
+
const { camera } = frame;
|
|
467
|
+
const cap = Math.min(magnet.maxSources, MAX_MAGNET_SOURCES, Math.floor(out.length / MAGNET_SOURCE_FLOATS));
|
|
468
|
+
let n = 0;
|
|
469
|
+
for (const pole of poles) {
|
|
470
|
+
if (n >= cap) break;
|
|
471
|
+
if (!(pole.strength > 0)) continue;
|
|
472
|
+
const s = pole.space === "screen" ? pole : worldToScreen(pole.x, pole.y, camera);
|
|
473
|
+
const o = n * MAGNET_SOURCE_FLOATS;
|
|
474
|
+
out[o + 0] = s.x;
|
|
475
|
+
out[o + 1] = s.y;
|
|
476
|
+
out[o + 2] = 0;
|
|
477
|
+
out[o + 3] = 0;
|
|
478
|
+
out[o + 4] = 0;
|
|
479
|
+
out[o + 5] = pole.strength * fieldScale;
|
|
480
|
+
out[o + 6] = 0;
|
|
481
|
+
out[o + 7] = 0;
|
|
482
|
+
n++;
|
|
483
|
+
}
|
|
484
|
+
if (!magnet.widgets || readSpatial === void 0 || magnet.widgetStrength <= 0 || n >= cap) {
|
|
485
|
+
return n;
|
|
486
|
+
}
|
|
487
|
+
const haloCss = 5 * magnet.reach * Math.sqrt(Math.max(magnet.widgetStrength * fieldScale, 1e-4));
|
|
488
|
+
const pad = haloCss / camera.zoom;
|
|
489
|
+
const hits = readSpatial({
|
|
490
|
+
minX: camera.x - pad,
|
|
491
|
+
minY: camera.y - pad,
|
|
492
|
+
maxX: camera.x + frame.width / camera.zoom + pad,
|
|
493
|
+
maxY: camera.y + frame.height / camera.zoom + pad
|
|
494
|
+
});
|
|
495
|
+
const viewCx = frame.width / 2;
|
|
496
|
+
const viewCy = frame.height / 2;
|
|
497
|
+
const candidates = [];
|
|
498
|
+
for (const hit of hits) {
|
|
499
|
+
const rect = readWidgetRect(world, hit.id);
|
|
500
|
+
if (rect === void 0 || rect.width <= 0 || rect.height <= 0) continue;
|
|
501
|
+
const hx = rect.width / 2 * camera.zoom;
|
|
502
|
+
const hy = rect.height / 2 * camera.zoom;
|
|
503
|
+
const center = worldToScreen(rect.x + rect.width / 2, rect.y + rect.height / 2, camera);
|
|
504
|
+
candidates.push({
|
|
505
|
+
cx: center.x,
|
|
506
|
+
cy: center.y,
|
|
507
|
+
hx,
|
|
508
|
+
hy,
|
|
509
|
+
r: Math.min(magnet.widgetRadius * camera.zoom, hx, hy),
|
|
510
|
+
area: hx * hy,
|
|
511
|
+
centerDist: (center.x - viewCx) ** 2 + (center.y - viewCy) ** 2
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const budget = cap - n;
|
|
515
|
+
if (candidates.length > budget) {
|
|
516
|
+
candidates.sort((a, b) => b.area - a.area || a.centerDist - b.centerDist);
|
|
517
|
+
candidates.length = budget;
|
|
518
|
+
}
|
|
519
|
+
const strength = magnet.widgetStrength * fieldScale;
|
|
520
|
+
for (const c of candidates) {
|
|
521
|
+
const o = n * MAGNET_SOURCE_FLOATS;
|
|
522
|
+
out[o + 0] = c.cx;
|
|
523
|
+
out[o + 1] = c.cy;
|
|
524
|
+
out[o + 2] = c.hx;
|
|
525
|
+
out[o + 3] = c.hy;
|
|
526
|
+
out[o + 4] = c.r;
|
|
527
|
+
out[o + 5] = strength;
|
|
528
|
+
out[o + 6] = 0;
|
|
529
|
+
out[o + 7] = 0;
|
|
530
|
+
n++;
|
|
531
|
+
}
|
|
532
|
+
return n;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ../ground/src/passes/grid-magnet.ts
|
|
536
|
+
var CORNERS = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0]);
|
|
537
|
+
var LEVELS = 3;
|
|
538
|
+
function createMagnetGrid() {
|
|
539
|
+
const sourceArray = new Float32Array(MAX_MAGNET_SOURCES * MAGNET_SOURCE_FLOATS);
|
|
540
|
+
const sourceAttr = new StorageBufferAttribute(sourceArray, 4);
|
|
541
|
+
const sourceBuf = storage(sourceAttr, "vec4", MAX_MAGNET_SOURCES * 2).setPBO(true).toReadOnly();
|
|
542
|
+
const uCam = uniform(new Vector2(0, 0));
|
|
543
|
+
const uZoom = uniform(1);
|
|
544
|
+
const uView = uniform(new Vector2(1, 1));
|
|
545
|
+
const uDotMode = uniform(0);
|
|
546
|
+
const uHalfLen = uniform(5);
|
|
547
|
+
const uHalfWidth = uniform(0.55);
|
|
548
|
+
const uRestRadius = uniform(0.75);
|
|
549
|
+
const uK = uniform(0);
|
|
550
|
+
const uPad = uniform(0);
|
|
551
|
+
const uPolarity = uniform(1);
|
|
552
|
+
const uAlwaysAlign = uniform(0);
|
|
553
|
+
const uCount = uniform(0, "int");
|
|
554
|
+
const uColor = uniform(new Vector3(0.75, 0.77, 0.8));
|
|
555
|
+
const EPS = float(25);
|
|
556
|
+
const REST = vec2(0, 1);
|
|
557
|
+
const sdRoundBox = Fn(([p, b, r]) => {
|
|
558
|
+
const q = abs(p).sub(b).add(vec2(r, r));
|
|
559
|
+
return length(max(q, vec2(0, 0))).add(min(max(q.x, q.y), 0)).sub(r);
|
|
560
|
+
});
|
|
561
|
+
const roundBoxNormal = Fn(([p, b, r]) => {
|
|
562
|
+
const s = sign(p);
|
|
563
|
+
const q = abs(p);
|
|
564
|
+
const inner = max(b.sub(vec2(r, r)), vec2(0, 0));
|
|
565
|
+
const d = q.sub(inner);
|
|
566
|
+
const n = vec2(0, 0).toVar();
|
|
567
|
+
If(d.x.greaterThan(0).and(d.y.greaterThan(0)), () => {
|
|
568
|
+
const len = length(d);
|
|
569
|
+
If(len.greaterThan(1e-4), () => {
|
|
570
|
+
n.assign(s.mul(d.div(len)));
|
|
571
|
+
}).Else(() => {
|
|
572
|
+
n.assign(vec2(s.x, 0));
|
|
573
|
+
});
|
|
574
|
+
}).ElseIf(d.x.greaterThan(d.y), () => {
|
|
575
|
+
n.assign(vec2(s.x, 0));
|
|
576
|
+
}).Else(() => {
|
|
577
|
+
n.assign(vec2(0, s.y));
|
|
578
|
+
});
|
|
579
|
+
return n;
|
|
580
|
+
});
|
|
581
|
+
const safeNormalize = Fn(([v, fallback]) => {
|
|
582
|
+
const n = length(v);
|
|
583
|
+
return select(n.greaterThan(1e-4), v.div(max(n, 1e-4)), fallback);
|
|
584
|
+
});
|
|
585
|
+
const group = new Group();
|
|
586
|
+
const geometries = [];
|
|
587
|
+
const materials = [];
|
|
588
|
+
const meshes = [];
|
|
589
|
+
const levelUniforms = Array.from({ length: LEVELS }, () => ({
|
|
590
|
+
origin: uniform(new Vector2(0, 0)),
|
|
591
|
+
dims: uniform(new Vector2(1, 1)),
|
|
592
|
+
spacing: uniform(20),
|
|
593
|
+
alpha: uniform(0),
|
|
594
|
+
skipMod: uniform(0)
|
|
595
|
+
}));
|
|
596
|
+
for (let level = 0; level < LEVELS; level++) {
|
|
597
|
+
const lu = levelUniforms[level];
|
|
598
|
+
const vUv = varying(vec2(0, 0), `vMagnetUv${level}`);
|
|
599
|
+
const vAlpha = varying(float(0), `vMagnetAlpha${level}`);
|
|
600
|
+
const vertexNode = Fn(() => {
|
|
601
|
+
const iid = float(instanceIndex);
|
|
602
|
+
const cols = lu.dims.x;
|
|
603
|
+
const col = mod(iid, cols).floor().toVar();
|
|
604
|
+
const row = iid.div(cols).floor().toVar();
|
|
605
|
+
const world = lu.origin.add(vec2(col, row)).mul(lu.spacing);
|
|
606
|
+
const screen = world.sub(uCam).mul(uZoom).toVar();
|
|
607
|
+
const field = vec2(0, 0).toVar();
|
|
608
|
+
const minD = float(1e9).toVar();
|
|
609
|
+
Loop({ start: int(0), end: uCount, type: "int", condition: "<" }, ({ i }) => {
|
|
610
|
+
const a = sourceBuf.element(i.mul(2));
|
|
611
|
+
const b = sourceBuf.element(i.mul(2).add(1));
|
|
612
|
+
const center = a.xy;
|
|
613
|
+
const half = a.zw;
|
|
614
|
+
const rad = b.x;
|
|
615
|
+
const strength = b.y;
|
|
616
|
+
const rel = screen.sub(center);
|
|
617
|
+
const reach = uPad.mul(sqrt(max(strength, 1e-4)));
|
|
618
|
+
If(
|
|
619
|
+
abs(rel.x).greaterThan(half.x.add(reach)).or(abs(rel.y).greaterThan(half.y.add(reach))),
|
|
620
|
+
() => {
|
|
621
|
+
Continue();
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
const d = sdRoundBox(rel, half, rad);
|
|
625
|
+
minD.assign(min(minD, d));
|
|
626
|
+
const dist = max(d, 0);
|
|
627
|
+
const r2 = dist.mul(dist).add(EPS);
|
|
628
|
+
const outward = roundBoxNormal(rel, half, rad);
|
|
629
|
+
field.subAssign(outward.mul(uK.mul(strength).mul(uPolarity).div(r2)));
|
|
630
|
+
});
|
|
631
|
+
const fieldDir = safeNormalize(field, REST);
|
|
632
|
+
const mag = length(field);
|
|
633
|
+
const influence = select(uDotMode.greaterThan(0.5), mag.div(mag.add(1)), clamp(mag, 0, 1));
|
|
634
|
+
const corner = positionGeometry.xy;
|
|
635
|
+
const dotRadius = mix(uRestRadius, uHalfLen.mul(1.05), influence);
|
|
636
|
+
const dotLocal = vec2(corner.x, corner.y.negate()).mul(dotRadius);
|
|
637
|
+
const dir = select(
|
|
638
|
+
uAlwaysAlign.greaterThan(0.5),
|
|
639
|
+
fieldDir,
|
|
640
|
+
safeNormalize(mix(REST, fieldDir, influence), REST)
|
|
641
|
+
);
|
|
642
|
+
const nrm = vec2(dir.y.negate(), dir.x);
|
|
643
|
+
const nLen = mix(uHalfLen.mul(0.55), uHalfLen.mul(1.35), influence);
|
|
644
|
+
const nWid = mix(uHalfWidth.mul(0.85), uHalfWidth.mul(1.15), influence);
|
|
645
|
+
const needleLocal = nrm.mul(corner.x).mul(nWid).add(dir.mul(corner.y).mul(nLen));
|
|
646
|
+
const local = select(uDotMode.greaterThan(0.5), dotLocal, needleLocal);
|
|
647
|
+
const pos = screen.add(local);
|
|
648
|
+
const restAlpha = select(uDotMode.greaterThan(0.5), float(0.72), float(0.35));
|
|
649
|
+
const fieldAlpha = mix(restAlpha, float(1), influence);
|
|
650
|
+
const alpha = clamp(lu.alpha.mul(fieldAlpha), 0, 1).toVar();
|
|
651
|
+
If(minD.lessThan(-0.5), () => {
|
|
652
|
+
alpha.assign(0);
|
|
653
|
+
});
|
|
654
|
+
const absI = lu.origin.x.add(col);
|
|
655
|
+
const absJ = lu.origin.y.add(row);
|
|
656
|
+
If(
|
|
657
|
+
lu.skipMod.greaterThan(0.5).and(mod(absI, lu.skipMod).lessThan(0.5)).and(mod(absJ, lu.skipMod).lessThan(0.5)),
|
|
658
|
+
() => {
|
|
659
|
+
alpha.assign(0);
|
|
660
|
+
}
|
|
661
|
+
);
|
|
662
|
+
vUv.assign(corner);
|
|
663
|
+
vAlpha.assign(alpha);
|
|
664
|
+
const ndc = vec2(
|
|
665
|
+
pos.x.div(uView.x).mul(2).sub(1),
|
|
666
|
+
float(1).sub(pos.y.div(uView.y).mul(2))
|
|
667
|
+
);
|
|
668
|
+
return vec4(ndc, 0, 1);
|
|
669
|
+
})();
|
|
670
|
+
const fragmentNode = Fn(() => {
|
|
671
|
+
const dotEdge = float(1).sub(smoothstep(0.62, 1, length(vUv)));
|
|
672
|
+
const ax = float(1).sub(smoothstep(0.55, 1, abs(vUv.x)));
|
|
673
|
+
const ay = float(1).sub(smoothstep(0.82, 1, abs(vUv.y)));
|
|
674
|
+
const edge = select(uDotMode.greaterThan(0.5), dotEdge, ax.mul(ay));
|
|
675
|
+
const a = vAlpha.mul(edge);
|
|
676
|
+
Discard(a.lessThan(0.01));
|
|
677
|
+
return vec4(uColor, a);
|
|
678
|
+
})();
|
|
679
|
+
const material = new MeshBasicNodeMaterial();
|
|
680
|
+
material.vertexNode = vertexNode;
|
|
681
|
+
material.fragmentNode = fragmentNode;
|
|
682
|
+
material.transparent = true;
|
|
683
|
+
material.depthTest = false;
|
|
684
|
+
material.depthWrite = false;
|
|
685
|
+
const geometry = new InstancedBufferGeometry();
|
|
686
|
+
geometry.setAttribute("position", new BufferAttribute(CORNERS, 3));
|
|
687
|
+
geometry.instanceCount = 0;
|
|
688
|
+
const mesh = new Mesh(geometry, material);
|
|
689
|
+
mesh.frustumCulled = false;
|
|
690
|
+
mesh.renderOrder = 0;
|
|
691
|
+
mesh.visible = false;
|
|
692
|
+
geometries.push(geometry);
|
|
693
|
+
materials.push(material);
|
|
694
|
+
meshes.push(mesh);
|
|
695
|
+
group.add(mesh);
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
group,
|
|
699
|
+
update(frame, cfg, magnet, levels, sources, sourceCount) {
|
|
700
|
+
uCam.value.set(frame.camera.x, frame.camera.y);
|
|
701
|
+
uZoom.value = frame.camera.zoom;
|
|
702
|
+
uView.value.set(Math.max(1, frame.width), Math.max(1, frame.height));
|
|
703
|
+
uDotMode.value = magnet.glyph === "dot" ? 1 : 0;
|
|
704
|
+
uHalfLen.value = magnet.needleLength;
|
|
705
|
+
uHalfWidth.value = magnet.needleWidth;
|
|
706
|
+
uRestRadius.value = cfg.dotRadius[0];
|
|
707
|
+
const k = 0.5 * magnet.reach * magnet.reach;
|
|
708
|
+
uK.value = k;
|
|
709
|
+
uPad.value = Math.sqrt(Math.max(k, 1) / 0.02);
|
|
710
|
+
uPolarity.value = magnet.polarity;
|
|
711
|
+
uAlwaysAlign.value = magnet.alwaysAlign ? 1 : 0;
|
|
712
|
+
uCount.value = sourceCount;
|
|
713
|
+
uColor.value.set(...cfg.dotColor);
|
|
714
|
+
if (sourceCount > 0) {
|
|
715
|
+
sourceArray.set(sources.subarray(0, sourceCount * MAGNET_SOURCE_FLOATS));
|
|
716
|
+
sourceAttr.needsUpdate = true;
|
|
717
|
+
}
|
|
718
|
+
for (let level = 0; level < LEVELS; level++) {
|
|
719
|
+
const win = levels[level];
|
|
720
|
+
const lu = levelUniforms[level];
|
|
721
|
+
const geometry = geometries[level];
|
|
722
|
+
const mesh = meshes[level];
|
|
723
|
+
if (win === void 0 || win.count <= 0) {
|
|
724
|
+
geometry.instanceCount = 0;
|
|
725
|
+
mesh.visible = false;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
lu.origin.value.set(win.i0, win.j0);
|
|
729
|
+
lu.dims.value.set(win.cols, win.rows);
|
|
730
|
+
lu.spacing.value = cfg.spacings[level];
|
|
731
|
+
lu.alpha.value = win.alpha;
|
|
732
|
+
lu.skipMod.value = win.skipModulo;
|
|
733
|
+
geometry.instanceCount = win.count;
|
|
734
|
+
mesh.visible = true;
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
dispose() {
|
|
738
|
+
for (const g of geometries) g.dispose();
|
|
739
|
+
for (const m of materials) m.dispose();
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ../ground/src/passes/grid-magnet-pass.ts
|
|
745
|
+
var createGridPass = (initial = {}, deps = {}) => {
|
|
746
|
+
let cfg = resolveInitialGridConfig(initial);
|
|
747
|
+
const renderer = createMagnetGrid();
|
|
748
|
+
const poles = deps.poles ?? [];
|
|
749
|
+
const sourceScratch = new Float32Array(MAX_MAGNET_SOURCES * MAGNET_SOURCE_FLOATS);
|
|
750
|
+
const poleScratch = [];
|
|
751
|
+
return {
|
|
752
|
+
name: "grid",
|
|
753
|
+
object: renderer.group,
|
|
754
|
+
arm(world, wake) {
|
|
755
|
+
const wakeIfWidgets = () => {
|
|
756
|
+
const magnet = resolveMagnet(cfg);
|
|
757
|
+
if (magnet.widgets && deps.readSpatial !== void 0) wake();
|
|
758
|
+
};
|
|
759
|
+
const unsubs = [world.reactive.observeResource(SpatialVersion, wakeIfWidgets)];
|
|
760
|
+
for (const source of poles) unsubs.push(source.subscribe(world, wake));
|
|
761
|
+
return unsubs;
|
|
762
|
+
},
|
|
763
|
+
collect(world, frame) {
|
|
764
|
+
const magnet = resolveMagnet(cfg);
|
|
765
|
+
const fieldScale = magnetFieldScale(frame.camera.zoom, magnet.fadeZoom);
|
|
766
|
+
poleScratch.length = 0;
|
|
767
|
+
if (fieldScale > 0) {
|
|
768
|
+
for (const source of poles) poleScratch.push(...source.read(world));
|
|
769
|
+
}
|
|
770
|
+
const sourceCount = collectMagnetSources(
|
|
771
|
+
world,
|
|
772
|
+
frame,
|
|
773
|
+
magnet,
|
|
774
|
+
fieldScale,
|
|
775
|
+
poleScratch,
|
|
776
|
+
deps.readSpatial,
|
|
777
|
+
sourceScratch
|
|
778
|
+
);
|
|
779
|
+
const levels = collectMagnetLevels(frame, cfg);
|
|
780
|
+
renderer.update(frame, cfg, magnet, levels, sourceScratch, sourceCount);
|
|
781
|
+
},
|
|
782
|
+
configure(next) {
|
|
783
|
+
cfg = mergeGridConfig(cfg, next);
|
|
784
|
+
},
|
|
785
|
+
dispose() {
|
|
786
|
+
renderer.dispose();
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// ../ground/src/passes/guides-collect.ts
|
|
792
|
+
var guideQ = defineQuery([GuideLine]);
|
|
793
|
+
var barQ = defineQuery([SpacingBar]);
|
|
794
|
+
function collectGuides(world, frame, config = DEFAULT_SNAP_GUIDES_CONFIG) {
|
|
795
|
+
const soup = new SoupBuilder();
|
|
796
|
+
const cam = frame.camera;
|
|
797
|
+
const lw = config.lineWidth;
|
|
798
|
+
const guideColor = [config.color[0], config.color[1], config.color[2], config.guideAlpha];
|
|
799
|
+
const barColor = [config.color[0], config.color[1], config.color[2], config.spacingAlpha];
|
|
800
|
+
world.query(guideQ).each((b) => {
|
|
801
|
+
for (const r of b) {
|
|
802
|
+
const g = world.read(b.entity(r), GuideLine);
|
|
803
|
+
const fullSpan = g.from === g.to;
|
|
804
|
+
if (g.axis === "x") {
|
|
805
|
+
const sx = worldToScreen(g.at, 0, cam).x;
|
|
806
|
+
if (frame.width > 0 && (sx < -1 || sx > frame.width + 1)) continue;
|
|
807
|
+
const y0 = fullSpan ? 0 : worldToScreen(0, g.from, cam).y;
|
|
808
|
+
const y1 = fullSpan ? frame.height : worldToScreen(0, g.to, cam).y;
|
|
809
|
+
soup.rect(sx - lw / 2, Math.min(y0, y1), lw, Math.abs(y1 - y0), guideColor);
|
|
810
|
+
} else {
|
|
811
|
+
const sy = worldToScreen(0, g.at, cam).y;
|
|
812
|
+
if (frame.height > 0 && (sy < -1 || sy > frame.height + 1)) continue;
|
|
813
|
+
const x0 = fullSpan ? 0 : worldToScreen(g.from, 0, cam).x;
|
|
814
|
+
const x1 = fullSpan ? frame.width : worldToScreen(g.to, 0, cam).x;
|
|
815
|
+
soup.rect(Math.min(x0, x1), sy - lw / 2, Math.abs(x1 - x0), lw, guideColor);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
world.query(barQ).each((b) => {
|
|
820
|
+
for (const r of b) {
|
|
821
|
+
const s = world.read(b.entity(r), SpacingBar);
|
|
822
|
+
const t = config.tickPx;
|
|
823
|
+
if (s.axis === "x") {
|
|
824
|
+
const a = worldToScreen(s.from, s.perp, cam);
|
|
825
|
+
const bp = worldToScreen(s.to, s.perp, cam);
|
|
826
|
+
if (frame.width > 0 && (Math.max(a.x, bp.x) < 0 || Math.min(a.x, bp.x) > frame.width)) continue;
|
|
827
|
+
soup.rect(Math.min(a.x, bp.x), a.y - lw / 2, Math.abs(bp.x - a.x), lw, barColor);
|
|
828
|
+
soup.rect(a.x - lw / 2, a.y - t, lw, t * 2, barColor);
|
|
829
|
+
soup.rect(bp.x - lw / 2, bp.y - t, lw, t * 2, barColor);
|
|
830
|
+
} else {
|
|
831
|
+
const a = worldToScreen(s.perp, s.from, cam);
|
|
832
|
+
const bp = worldToScreen(s.perp, s.to, cam);
|
|
833
|
+
if (frame.height > 0 && (Math.max(a.y, bp.y) < 0 || Math.min(a.y, bp.y) > frame.height)) continue;
|
|
834
|
+
soup.rect(a.x - lw / 2, Math.min(a.y, bp.y), lw, Math.abs(bp.y - a.y), barColor);
|
|
835
|
+
soup.rect(a.x - t, a.y - lw / 2, t * 2, lw, barColor);
|
|
836
|
+
soup.rect(bp.x - t, bp.y - lw / 2, t * 2, lw, barColor);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
return soup.build();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ../ground/src/passes/soup-mesh.ts
|
|
844
|
+
import { attribute } from "three/tsl";
|
|
845
|
+
import { BufferAttribute as BufferAttribute2, BufferGeometry, DoubleSide, Mesh as Mesh2, MeshBasicNodeMaterial as MeshBasicNodeMaterial2 } from "three/webgpu";
|
|
846
|
+
function createSoupMesh(renderOrder) {
|
|
847
|
+
const material = new MeshBasicNodeMaterial2();
|
|
848
|
+
material.fragmentNode = attribute("color", "vec4");
|
|
849
|
+
material.transparent = true;
|
|
850
|
+
material.depthTest = false;
|
|
851
|
+
material.depthWrite = false;
|
|
852
|
+
material.side = DoubleSide;
|
|
853
|
+
let geometry = new BufferGeometry();
|
|
854
|
+
let capacity = 0;
|
|
855
|
+
const mesh = new Mesh2(geometry, material);
|
|
856
|
+
mesh.frustumCulled = false;
|
|
857
|
+
mesh.renderOrder = renderOrder;
|
|
858
|
+
mesh.visible = false;
|
|
859
|
+
return {
|
|
860
|
+
mesh,
|
|
861
|
+
update(soup) {
|
|
862
|
+
if (soup.vertexCount === 0) {
|
|
863
|
+
mesh.visible = false;
|
|
864
|
+
geometry.setDrawRange(0, 0);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (soup.vertexCount > capacity) {
|
|
868
|
+
capacity = Math.max(soup.vertexCount, capacity * 2, 256);
|
|
869
|
+
const old = geometry;
|
|
870
|
+
geometry = new BufferGeometry();
|
|
871
|
+
const pos = new Float32Array(capacity * 3);
|
|
872
|
+
const col = new Float32Array(capacity * 4);
|
|
873
|
+
pos.set(soup.positions);
|
|
874
|
+
col.set(soup.colors);
|
|
875
|
+
geometry.setAttribute("position", new BufferAttribute2(pos, 3));
|
|
876
|
+
geometry.setAttribute("color", new BufferAttribute2(col, 4));
|
|
877
|
+
mesh.geometry = geometry;
|
|
878
|
+
old.dispose();
|
|
879
|
+
} else {
|
|
880
|
+
const pos = geometry.getAttribute("position");
|
|
881
|
+
const col = geometry.getAttribute("color");
|
|
882
|
+
pos.array.set(soup.positions);
|
|
883
|
+
col.array.set(soup.colors);
|
|
884
|
+
pos.needsUpdate = true;
|
|
885
|
+
col.needsUpdate = true;
|
|
886
|
+
}
|
|
887
|
+
geometry.setDrawRange(0, soup.vertexCount);
|
|
888
|
+
mesh.visible = true;
|
|
889
|
+
},
|
|
890
|
+
dispose() {
|
|
891
|
+
geometry.dispose();
|
|
892
|
+
material.dispose();
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ../ground/src/passes/guides.ts
|
|
898
|
+
function createGuidesPass(initial = {}) {
|
|
899
|
+
const config = { ...DEFAULT_SNAP_GUIDES_CONFIG, ...initial };
|
|
900
|
+
const soup = createSoupMesh(2);
|
|
901
|
+
return {
|
|
902
|
+
name: "snap-guides",
|
|
903
|
+
object: soup.mesh,
|
|
904
|
+
arm(world, wake) {
|
|
905
|
+
return [
|
|
906
|
+
world.reactive.observeQuery(guideQ, wake, { cols: [GuideLine] }),
|
|
907
|
+
world.reactive.observeQuery(barQ, wake, { cols: [SpacingBar] })
|
|
908
|
+
];
|
|
909
|
+
},
|
|
910
|
+
collect(world, frame) {
|
|
911
|
+
soup.update(collectGuides(world, frame, config));
|
|
912
|
+
},
|
|
913
|
+
dispose() {
|
|
914
|
+
soup.dispose();
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
575
919
|
// ../ground/src/passes/wires.ts
|
|
576
920
|
function createWiresPass(initial = {}, readPreview) {
|
|
577
921
|
const config = { ...DEFAULT_WIRES_CONFIG, ...initial };
|
|
@@ -682,12 +1026,53 @@ function createGroundRenderer(doc, opts = {}) {
|
|
|
682
1026
|
};
|
|
683
1027
|
}
|
|
684
1028
|
|
|
1029
|
+
// ../ground/src/poles.ts
|
|
1030
|
+
var localPointerQ = defineQuery([Pointer, PointerWorld, LocalPointer]);
|
|
1031
|
+
function localPointerPoles(opts = {}) {
|
|
1032
|
+
const strength = opts.strength ?? 1;
|
|
1033
|
+
return {
|
|
1034
|
+
read(world) {
|
|
1035
|
+
const out = [];
|
|
1036
|
+
world.query(localPointerQ).each((b) => {
|
|
1037
|
+
for (const r of b) {
|
|
1038
|
+
const p = world.read(b.entity(r), PointerWorld);
|
|
1039
|
+
out.push({ x: p.x, y: p.y, strength, space: "world" });
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
return out;
|
|
1043
|
+
},
|
|
1044
|
+
subscribe(world, wake) {
|
|
1045
|
+
return world.reactive.observeQuery(localPointerQ, wake, { cols: [PointerWorld] });
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
var cursorVisualQ = defineQuery([Position, CursorVisual]);
|
|
1050
|
+
function cursorVisualPoles(opts = {}) {
|
|
1051
|
+
const strength = opts.strength ?? 1;
|
|
1052
|
+
return {
|
|
1053
|
+
read(world) {
|
|
1054
|
+
const out = [];
|
|
1055
|
+
world.query(cursorVisualQ).each((b) => {
|
|
1056
|
+
for (const r of b) {
|
|
1057
|
+
const p = world.read(b.entity(r), Position);
|
|
1058
|
+
out.push({ x: p.x, y: p.y, strength, space: "world" });
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
return out;
|
|
1062
|
+
},
|
|
1063
|
+
subscribe(world, wake) {
|
|
1064
|
+
return world.reactive.observeQuery(cursorVisualQ, wake, { cols: [Position] });
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
685
1069
|
// ../ground/src/index.ts
|
|
686
1070
|
function ground(opts = {}) {
|
|
687
1071
|
return (ctx) => {
|
|
688
1072
|
const doc = ctx.host.container.ownerDocument;
|
|
689
1073
|
const renderer = opts.rendererOverride ?? createGroundRenderer(doc, { forceWebGL: opts.forceWebGL === true });
|
|
690
|
-
const
|
|
1074
|
+
const poles = opts.poles === void 0 ? [] : Array.isArray(opts.poles) ? opts.poles : [opts.poles];
|
|
1075
|
+
const grid = createGridPass(opts.grid ?? {}, { poles, ...ctx.readSpatial !== void 0 ? { readSpatial: ctx.readSpatial } : {} });
|
|
691
1076
|
const wires = createWiresPass(opts.wires ?? {}, ctx.readWirePreview);
|
|
692
1077
|
const guides = createGuidesPass(opts.guides ?? {});
|
|
693
1078
|
const passes = [grid, wires, guides, ...opts.passes ?? []];
|
|
@@ -704,12 +1089,20 @@ function ground(opts = {}) {
|
|
|
704
1089
|
}
|
|
705
1090
|
export {
|
|
706
1091
|
DEFAULT_GRID_CONFIG,
|
|
1092
|
+
DEFAULT_GRID_MAGNET_CONFIG,
|
|
707
1093
|
DEFAULT_SNAP_GUIDES_CONFIG,
|
|
708
1094
|
DEFAULT_WIRES_CONFIG,
|
|
1095
|
+
MAX_MAGNET_SOURCES,
|
|
709
1096
|
SoupBuilder,
|
|
710
1097
|
collectGuides,
|
|
1098
|
+
collectMagnetLevels,
|
|
1099
|
+
collectMagnetSources,
|
|
711
1100
|
collectWires,
|
|
1101
|
+
cursorVisualPoles,
|
|
712
1102
|
ground,
|
|
713
|
-
|
|
1103
|
+
localPointerPoles,
|
|
1104
|
+
magnetFieldScale,
|
|
1105
|
+
parseCssColor,
|
|
1106
|
+
resolveMagnet
|
|
714
1107
|
};
|
|
715
1108
|
//# sourceMappingURL=ground.js.map
|