incanto 0.27.0 → 0.29.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/README.md +44 -0
- package/bin/incanto-editor.mjs +118 -1
- package/dist/2d.d.ts +38 -5
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +496 -150
- package/dist/3d.js +5 -5
- package/dist/{behavior-CPibUfnH.d.ts → behavior-BAc0erXF.d.ts} +21 -0
- package/dist/{create-game-DUAtTqID.js → create-game-CNKXGfpr.js} +270 -47
- package/dist/{create-game-C0bPoURs.js → create-game-CbuLWorm.js} +53 -14
- package/dist/debug.d.ts +51 -6
- package/dist/debug.js +242 -19
- package/dist/editor-switch-B0wB_DSr.d.ts +59 -0
- package/dist/editor-switch-CAKlJMIY.js +161 -0
- package/dist/editor.d.ts +49 -0
- package/dist/editor.js +8439 -0
- package/dist/{gameplay-Ddk13pQ6.js → gameplay-L05WgWd1.js} +220 -37
- package/dist/gameplay.d.ts +1 -1
- package/dist/gameplay.js +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.js +2 -2
- package/dist/{loader-DILt9PGC.d.ts → loader-B242FF6N.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +2 -2
- package/dist/{pathfinding-RWYkNKx9.d.ts → pathfinding-BwD974Ss.d.ts} +1 -1
- package/dist/{physics-2d-DiVFFlH3.js → physics-2d-CCVTrKOd.js} +62 -2
- package/dist/{physics-3d-4mGzOZ2J.js → physics-3d-BZZLtwJu.js} +439 -8
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-nObreUQR.js → register-BTg0EM7s.js} +35 -3
- package/dist/{register-DcHXS1MA.js → register-C44aSduO.js} +5694 -5108
- package/dist/{register-CvpSUU3O.js → register-DWcWq4QG.js} +22 -2
- package/dist/{register-BFFE1Mh1.js → register-MelqEdza.js} +1 -1
- package/dist/test.d.ts +35 -3
- package/dist/test.js +104 -10
- package/dist/vite.d.ts +53 -1
- package/dist/vite.js +94 -2
- package/editor/assets/{agent8-DElIPozC.js → agent8-DEVkEa3d.js} +1 -1
- package/editor/assets/debug-zGAtpDF0.js +2 -0
- package/editor/assets/index-Bx4UtWYY.js +10586 -0
- package/editor/index.html +3 -157
- package/package.json +3 -2
- package/schemas/scene.schema.json +110 -4
- package/skills/incanto-3d-models.md +38 -0
- package/skills/incanto-assets.md +13 -0
- package/skills/incanto-building-3d-games.md +83 -6
- package/skills/incanto-editor.md +151 -0
- package/skills/incanto-environment.md +88 -1
- package/skills/incanto-node-reference.md +35 -1
- package/skills/incanto-physics-and-input.md +45 -6
- package/skills/incanto-verifying-your-game.md +51 -2
- package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/beacon-isle-3d/package.json +1 -1
- package/templates-app/tps-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/tps-3d/package.json +1 -1
- package/templates-app/village-quest-3d/docs/project-3d-rules.md +5 -0
- package/templates-app/village-quest-3d/package.json +1 -1
- package/editor/assets/index-BrneVqN0.js +0 -8330
- package/editor/assets/index-D8QvwvOm.css +0 -1
|
@@ -413,6 +413,13 @@ void main() {
|
|
|
413
413
|
* Foliage/flowers additionally BELONG out of the depth pass: alpha-cutout
|
|
414
414
|
* quads depth-write their full rectangle and wind-displaced blades render
|
|
415
415
|
* unposed, so their depth was always a lie the foam mask had to survive.
|
|
416
|
+
*
|
|
417
|
+
* Individual OBJECTS opt out the same way by setting
|
|
418
|
+
* `userData.incantoWaterPassSkip = true` — for a node that is only PARTLY
|
|
419
|
+
* unfit, like a tree whose trunk is honest geometry but whose leaves are the
|
|
420
|
+
* same alpha-cutout lie (a depth-only override drops both their alphaTest and
|
|
421
|
+
* their wind displacement, so every leaf card stamps a solid unposed
|
|
422
|
+
* rectangle; a canopy became a wall of near-depth over the water behind it).
|
|
416
423
|
*/
|
|
417
424
|
const SKIPPED_IN_WATER_PASSES = new Set([
|
|
418
425
|
"Foliage3D",
|
|
@@ -430,6 +437,11 @@ function hideForWaterPass(root, out) {
|
|
|
430
437
|
if (typeof root?.traverse !== "function") return out;
|
|
431
438
|
root.traverse((obj) => {
|
|
432
439
|
if (!obj.visible) return;
|
|
440
|
+
if (obj.userData?.incantoWaterPassSkip === true) {
|
|
441
|
+
obj.visible = false;
|
|
442
|
+
out.push(obj);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
433
445
|
const type = (obj.userData?.incantoNode)?.constructor?.typeName;
|
|
434
446
|
if (type && SKIPPED_IN_WATER_PASSES.has(type)) {
|
|
435
447
|
obj.visible = false;
|
|
@@ -1280,6 +1292,24 @@ float rippleFoam(vec2 p) {
|
|
|
1280
1292
|
}
|
|
1281
1293
|
|
|
1282
1294
|
// Enhanced depth-based foam effect calculation
|
|
1295
|
+
/**
|
|
1296
|
+
* Linear scene depth at uv, cross-filtered over the pre-pass' own texels.
|
|
1297
|
+
* The pre-pass runs at HALF resolution: a single tap quantises the shoreline
|
|
1298
|
+
* to half-res steps, and any camera motion — even sub-pixel — reshuffles which
|
|
1299
|
+
* texel wins, so every depth-driven band (foam, contact line) BOILS while you
|
|
1300
|
+
* move. Averaging the 4-neighbour cross moves smoothly instead. The shoreline
|
|
1301
|
+
* fade has filtered this way all along (waterColumnAt callers); the foam and
|
|
1302
|
+
* edge bands were still on one tap each.
|
|
1303
|
+
*/
|
|
1304
|
+
float filteredSceneDepth(vec2 uv) {
|
|
1305
|
+
float c = linearizeDepth(texture2D(uDepthTexture, uv).r, uCameraNear, uCameraFar);
|
|
1306
|
+
float l = linearizeDepth(texture2D(uDepthTexture, uv - vec2(uScenePassTexel.x, 0.0)).r, uCameraNear, uCameraFar);
|
|
1307
|
+
float r = linearizeDepth(texture2D(uDepthTexture, uv + vec2(uScenePassTexel.x, 0.0)).r, uCameraNear, uCameraFar);
|
|
1308
|
+
float d = linearizeDepth(texture2D(uDepthTexture, uv - vec2(0.0, uScenePassTexel.y)).r, uCameraNear, uCameraFar);
|
|
1309
|
+
float u = linearizeDepth(texture2D(uDepthTexture, uv + vec2(0.0, uScenePassTexel.y)).r, uCameraNear, uCameraFar);
|
|
1310
|
+
return c * 0.4 + (l + r + d + u) * 0.15;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1283
1313
|
float calculateFoam() {
|
|
1284
1314
|
// incanto v2: foam rides the always-on scene depth pass — both gates
|
|
1285
1315
|
if (uEnableFoam < 0.5 || uUseSceneDepth < 0.5) return 0.0;
|
|
@@ -1287,12 +1317,10 @@ float calculateFoam() {
|
|
|
1287
1317
|
// Convert screen coordinates to texture coordinates
|
|
1288
1318
|
vec2 screenUV = vScreenPosition.xy / vScreenPosition.w * 0.5 + 0.5;
|
|
1289
1319
|
|
|
1290
|
-
// Sample scene depth value from depth texture
|
|
1291
|
-
float sceneDepth = texture2D(uDepthTexture, screenUV).r;
|
|
1292
1320
|
float waterDepth = gl_FragCoord.z;
|
|
1293
1321
|
|
|
1294
|
-
// Convert to linear depth
|
|
1295
|
-
float linearSceneDepth =
|
|
1322
|
+
// Convert to linear depth (scene side cross-filtered, see above)
|
|
1323
|
+
float linearSceneDepth = filteredSceneDepth(screenUV);
|
|
1296
1324
|
float linearWaterDepth = linearizeDepth(waterDepth, uCameraNear, uCameraFar);
|
|
1297
1325
|
|
|
1298
1326
|
// Calculate depth difference (distance between water and other objects)
|
|
@@ -1344,10 +1372,9 @@ float calculateEdgeBlending() {
|
|
|
1344
1372
|
if (uEnableFoam < 0.5 || uUseSceneDepth < 0.5 || uEdgeIntensity <= 0.0) return 0.0;
|
|
1345
1373
|
|
|
1346
1374
|
vec2 screenUV = vScreenPosition.xy / vScreenPosition.w * 0.5 + 0.5;
|
|
1347
|
-
float sceneDepth = texture2D(uDepthTexture, screenUV).r;
|
|
1348
1375
|
float waterDepth = gl_FragCoord.z;
|
|
1349
1376
|
|
|
1350
|
-
float linearSceneDepth =
|
|
1377
|
+
float linearSceneDepth = filteredSceneDepth(screenUV); // cross-filtered — one tap boils
|
|
1351
1378
|
float linearWaterDepth = linearizeDepth(waterDepth, uCameraNear, uCameraFar);
|
|
1352
1379
|
|
|
1353
1380
|
float depthDiff = abs(linearSceneDepth - linearWaterDepth);
|
|
@@ -1379,7 +1406,24 @@ void main() {
|
|
|
1379
1406
|
linearSceneDepth = linearizeDepth(sceneDepth, uCameraNear, uCameraFar);
|
|
1380
1407
|
|
|
1381
1408
|
float depthDiff = linearWaterDepth - linearSceneDepth;
|
|
1382
|
-
|
|
1409
|
+
|
|
1410
|
+
// The depth texture DELIBERATELY holds above-water geometry too (the
|
|
1411
|
+
// depth-completion pass, for cutouts) — so a sample NEARER than this
|
|
1412
|
+
// fragment cannot be a bed, and treating it as "column 0" rendered the
|
|
1413
|
+
// water as clear glass over the bare bed. Behind a tree the half-res
|
|
1414
|
+
// pre-pass smears the canopy across the gaps between its own leaves, and
|
|
1415
|
+
// whole stretches of pond turned to glass — "the water isn't drawn". The
|
|
1416
|
+
// column reads the DEEPEST of the 5-tap neighborhood instead; when even
|
|
1417
|
+
// that is contaminated, depth is UNKNOWN (-1) and the flat-ramp fallback
|
|
1418
|
+
// paints honest murk.
|
|
1419
|
+
float seamL = linearizeDepth(texture2D(uDepthTexture, screenUV - vec2(uScenePassTexel.x, 0.0)).r, uCameraNear, uCameraFar);
|
|
1420
|
+
float seamR = linearizeDepth(texture2D(uDepthTexture, screenUV + vec2(uScenePassTexel.x, 0.0)).r, uCameraNear, uCameraFar);
|
|
1421
|
+
float seamD = linearizeDepth(texture2D(uDepthTexture, screenUV - vec2(0.0, uScenePassTexel.y)).r, uCameraNear, uCameraFar);
|
|
1422
|
+
float seamU = linearizeDepth(texture2D(uDepthTexture, screenUV + vec2(0.0, uScenePassTexel.y)).r, uCameraNear, uCameraFar);
|
|
1423
|
+
float seamFarthest = max(max(seamL, seamR), max(seamD, max(seamU, linearSceneDepth)));
|
|
1424
|
+
if (seamFarthest > linearWaterDepth + 0.02) {
|
|
1425
|
+
waterViewDepth = seamFarthest - linearWaterDepth;
|
|
1426
|
+
}
|
|
1383
1427
|
|
|
1384
1428
|
// waterline dither: on a near-flat beach one half-res depth texel spans a
|
|
1385
1429
|
// wide screen block at grazing view, so these thresholds cut staircase
|
|
@@ -1390,10 +1434,21 @@ void main() {
|
|
|
1390
1434
|
float seamJitter =
|
|
1391
1435
|
(noise(vWorldPosition.xz * 3.1) * 0.65 + noise(vWorldPosition.xz * 8.7) * 0.35 - 0.5) * 0.035;
|
|
1392
1436
|
|
|
1437
|
+
// This discard exists for ONE thing: dithering the waterline seam — an
|
|
1438
|
+
// organic jittered edge in the few centimetres where half-res depth
|
|
1439
|
+
// quantisation would carve staircase notches. TRUE occlusion is the
|
|
1440
|
+
// hardware depth test's job (depthTest is on, and anything opaque wrote
|
|
1441
|
+
// full-res depth): a water fragment that reached this shader is, by the
|
|
1442
|
+
// full-res buffer's word, VISIBLE. So a pre-pass sample far in front of it
|
|
1443
|
+
// can only be the half-res lie — a tree canopy smearing near-depth across
|
|
1444
|
+
// the gaps between its own leaves — and acting on it erased the whole pond
|
|
1445
|
+
// when viewed through or past a tree. Only the seam band may discard.
|
|
1446
|
+
bool seamEligible = depthDiff < 0.45;
|
|
1447
|
+
|
|
1393
1448
|
// 🎯 Two-step approach
|
|
1394
1449
|
// Step 1: Discard completely overlapping parts
|
|
1395
1450
|
float hardDiscardThreshold = 0.1 + seamJitter;
|
|
1396
|
-
if (depthDiff > hardDiscardThreshold) {
|
|
1451
|
+
if (seamEligible && depthDiff > hardDiscardThreshold) {
|
|
1397
1452
|
discard; // Completely remove definitively overlapping pixels
|
|
1398
1453
|
}
|
|
1399
1454
|
|
|
@@ -1401,7 +1456,7 @@ void main() {
|
|
|
1401
1456
|
float softDiscardStart = -0.05 + seamJitter; // Start fade slightly ahead
|
|
1402
1457
|
float softDiscardEnd = hardDiscardThreshold;
|
|
1403
1458
|
|
|
1404
|
-
if (depthDiff > softDiscardStart) {
|
|
1459
|
+
if (seamEligible && depthDiff > softDiscardStart) {
|
|
1405
1460
|
depthMask = 1.0 - smoothstep(softDiscardStart, softDiscardEnd, depthDiff);
|
|
1406
1461
|
// At border: 0 (fully transparent) ~ 1 (fully opaque)
|
|
1407
1462
|
}
|
|
@@ -2160,18 +2215,22 @@ function validateCollider3D(collider, nodeName) {
|
|
|
2160
2215
|
return;
|
|
2161
2216
|
}
|
|
2162
2217
|
if (shape === "heightfield") return;
|
|
2163
|
-
|
|
2218
|
+
if (shape === "auto") return;
|
|
2219
|
+
throw new IncantoError("BAD_FORMAT", `Collider on '${nodeName}': "shape" must be 'auto', 'box', 'sphere', 'capsule', 'trimesh', or 'heightfield', got ${JSON.stringify(shape)}.`);
|
|
2164
2220
|
}
|
|
2165
2221
|
//#endregion
|
|
2166
2222
|
//#region src/3d/nodes/node-3d.ts
|
|
2167
|
-
/**
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2223
|
+
/** The first ground-answering node in the tree, searched depth-first. */
|
|
2224
|
+
function firstGroundIn(root) {
|
|
2225
|
+
const probe = root;
|
|
2226
|
+
if (probe.constructor?.typeName === "Terrain3D" && typeof probe.heightAt === "function") return root;
|
|
2227
|
+
for (const child of root.children) {
|
|
2228
|
+
const found = firstGroundIn(child);
|
|
2229
|
+
if (found) return found;
|
|
2230
|
+
}
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
var Node3D = class Node3D extends Node {
|
|
2175
2234
|
static typeName = "Node3D";
|
|
2176
2235
|
static props = {
|
|
2177
2236
|
position: { default: [
|
|
@@ -2202,7 +2261,8 @@ var Node3D = class extends Node {
|
|
|
2202
2261
|
"effects",
|
|
2203
2262
|
"overlay"
|
|
2204
2263
|
]
|
|
2205
|
-
}
|
|
2264
|
+
},
|
|
2265
|
+
snapToGround: { default: null }
|
|
2206
2266
|
};
|
|
2207
2267
|
position = [
|
|
2208
2268
|
0,
|
|
@@ -2236,6 +2296,23 @@ var Node3D = class extends Node {
|
|
|
2236
2296
|
renderOrder = 0;
|
|
2237
2297
|
/** Named draw-order band; `renderOrder` is the fine offset inside it. */
|
|
2238
2298
|
orderGroup = "default";
|
|
2299
|
+
/**
|
|
2300
|
+
* PUT THIS ON THE GROUND. `true` sets the node's Y from the terrain under it
|
|
2301
|
+
* at load; a number does the same and lifts it by that many metres (a post
|
|
2302
|
+
* whose origin sits at its middle wants half its height).
|
|
2303
|
+
*
|
|
2304
|
+
* Computing a Y by hand is the single most repeated mistake in scene
|
|
2305
|
+
* authoring, and it is not a careless one: the number is right when it is
|
|
2306
|
+
* written and wrong the moment anything reshapes the ground — a river carving
|
|
2307
|
+
* its bed, a basin, a different seed, a generator that mirrors the carve
|
|
2308
|
+
* slightly differently. Every "floating boulder", "buried player" and
|
|
2309
|
+
* "bridge posts in mid-air" in this engine's history is that drift.
|
|
2310
|
+
*
|
|
2311
|
+
* With this the ground is asked at load, in the same frame the terrain is
|
|
2312
|
+
* built, and there is nothing to drift from. `null` (default) leaves `y`
|
|
2313
|
+
* exactly as authored — for anything that genuinely hangs in the air.
|
|
2314
|
+
*/
|
|
2315
|
+
snapToGround = null;
|
|
2239
2316
|
/** What three actually sorts by: band base + renderOrder. */
|
|
2240
2317
|
get effectiveRenderOrder() {
|
|
2241
2318
|
return effectiveOrder(this.orderGroup, this.renderOrder);
|
|
@@ -2249,6 +2326,47 @@ var Node3D = class extends Node {
|
|
|
2249
2326
|
*/
|
|
2250
2327
|
_interpPrev = null;
|
|
2251
2328
|
_interpCurr = null;
|
|
2329
|
+
onReady() {
|
|
2330
|
+
this.applyGroundSnap();
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Resolve {@link snapToGround} against the terrain under this node.
|
|
2334
|
+
*
|
|
2335
|
+
* Ancestor transforms are translation-only here, the same contract heightAt
|
|
2336
|
+
* and the physics heightfield already follow, so the world height converts
|
|
2337
|
+
* back to a local Y by subtracting the parents' offset.
|
|
2338
|
+
*/
|
|
2339
|
+
applyGroundSnap() {
|
|
2340
|
+
if (this.snapToGround === null || this.snapToGround === false) return;
|
|
2341
|
+
const lift = this.snapToGround === true ? 0 : this.snapToGround;
|
|
2342
|
+
const terrain = firstGroundIn(this.getRoot());
|
|
2343
|
+
if (!terrain) return;
|
|
2344
|
+
let ox = this.position[0] ?? 0;
|
|
2345
|
+
let oy = 0;
|
|
2346
|
+
let oz = this.position[2] ?? 0;
|
|
2347
|
+
for (let n = this.parent; n; n = n.parent) {
|
|
2348
|
+
if (!(n instanceof Node3D)) continue;
|
|
2349
|
+
const yaw = (n.rotation[1] ?? 0) * Math.PI / 180;
|
|
2350
|
+
if (yaw !== 0) {
|
|
2351
|
+
const c = Math.cos(yaw);
|
|
2352
|
+
const sn = Math.sin(yaw);
|
|
2353
|
+
const rx = ox * c + oz * sn;
|
|
2354
|
+
const rz = -ox * sn + oz * c;
|
|
2355
|
+
ox = rx;
|
|
2356
|
+
oz = rz;
|
|
2357
|
+
}
|
|
2358
|
+
ox += n.position[0] ?? 0;
|
|
2359
|
+
oy += n.position[1] ?? 0;
|
|
2360
|
+
oz += n.position[2] ?? 0;
|
|
2361
|
+
}
|
|
2362
|
+
const worldX = ox;
|
|
2363
|
+
const worldZ = oz;
|
|
2364
|
+
this.position = [
|
|
2365
|
+
this.position[0] ?? 0,
|
|
2366
|
+
terrain.heightAt(worldX, worldZ) + lift - oy,
|
|
2367
|
+
this.position[2] ?? 0
|
|
2368
|
+
];
|
|
2369
|
+
}
|
|
2252
2370
|
/** @internal The backing three object (lazily created). */
|
|
2253
2371
|
_ensureObject3D() {
|
|
2254
2372
|
if (!this._object3D) {
|
|
@@ -2291,7 +2409,38 @@ var Node3D = class extends Node {
|
|
|
2291
2409
|
* `{shape:'capsule', radius, height}` (meters, y-up).
|
|
2292
2410
|
*/
|
|
2293
2411
|
var PhysicsBody3D = class extends Node3D {
|
|
2294
|
-
static props = { collider: {
|
|
2412
|
+
static props = { collider: {
|
|
2413
|
+
default: {},
|
|
2414
|
+
variants: {
|
|
2415
|
+
tag: "shape",
|
|
2416
|
+
byTag: {
|
|
2417
|
+
auto: { shape: "auto" },
|
|
2418
|
+
box: {
|
|
2419
|
+
shape: "box",
|
|
2420
|
+
size: [
|
|
2421
|
+
1,
|
|
2422
|
+
1,
|
|
2423
|
+
1
|
|
2424
|
+
]
|
|
2425
|
+
},
|
|
2426
|
+
sphere: {
|
|
2427
|
+
shape: "sphere",
|
|
2428
|
+
radius: .5
|
|
2429
|
+
},
|
|
2430
|
+
capsule: {
|
|
2431
|
+
shape: "capsule",
|
|
2432
|
+
radius: .35,
|
|
2433
|
+
height: 1
|
|
2434
|
+
},
|
|
2435
|
+
trimesh: {
|
|
2436
|
+
shape: "trimesh",
|
|
2437
|
+
vertices: [],
|
|
2438
|
+
indices: []
|
|
2439
|
+
},
|
|
2440
|
+
heightfield: { shape: "heightfield" }
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
} };
|
|
2295
2444
|
/** The unified collision model: every collider participant can emit these. */
|
|
2296
2445
|
static signals = ["triggerEnter", "triggerExit"];
|
|
2297
2446
|
_collider = {};
|
|
@@ -2561,24 +2710,24 @@ const WATER_PRESETS = {
|
|
|
2561
2710
|
}
|
|
2562
2711
|
},
|
|
2563
2712
|
pond: {
|
|
2564
|
-
waveHeight: .
|
|
2713
|
+
waveHeight: .006,
|
|
2565
2714
|
swell: 0,
|
|
2566
2715
|
whitecaps: 0,
|
|
2567
|
-
absorption: .
|
|
2568
|
-
opacity: .
|
|
2569
|
-
detailStrength: .
|
|
2570
|
-
sunIntensity: .
|
|
2716
|
+
absorption: .85,
|
|
2717
|
+
opacity: .93,
|
|
2718
|
+
detailStrength: .14,
|
|
2719
|
+
sunIntensity: .7,
|
|
2571
2720
|
colors: {
|
|
2572
|
-
trough: "#
|
|
2573
|
-
surface: "#
|
|
2574
|
-
peak: "#
|
|
2721
|
+
trough: "#1c2e1e",
|
|
2722
|
+
surface: "#39543c",
|
|
2723
|
+
peak: "#6f8a68"
|
|
2575
2724
|
},
|
|
2576
|
-
color: "#
|
|
2725
|
+
color: "#39543c",
|
|
2577
2726
|
causticsAbove: 0,
|
|
2578
2727
|
reflectionInterval: 800,
|
|
2579
2728
|
_look: {
|
|
2580
|
-
fresnelScale: 1.
|
|
2581
|
-
reflectivityMax: .
|
|
2729
|
+
fresnelScale: 1.05,
|
|
2730
|
+
reflectivityMax: .78,
|
|
2582
2731
|
edgeFade: false
|
|
2583
2732
|
}
|
|
2584
2733
|
}
|
|
@@ -2731,6 +2880,7 @@ var Water3D = class Water3D extends Node3D {
|
|
|
2731
2880
|
mirrorInterval: { default: 33 },
|
|
2732
2881
|
swellSteepness: { default: .35 },
|
|
2733
2882
|
foam: { default: true },
|
|
2883
|
+
reflectivity: { default: null },
|
|
2734
2884
|
interaction: { default: true },
|
|
2735
2885
|
splash: { default: true },
|
|
2736
2886
|
sunDirection: { default: [
|
|
@@ -2790,6 +2940,16 @@ var Water3D = class Water3D extends Node3D {
|
|
|
2790
2940
|
reflectionInterval = 1e3;
|
|
2791
2941
|
/** Depth-texture shoreline foam — rides the always-on fancy scene pass. */
|
|
2792
2942
|
foam = true;
|
|
2943
|
+
/**
|
|
2944
|
+
* How much sky the surface throws back at grazing angles, 0..1 — `null`
|
|
2945
|
+
* keeps the preset's own ceiling.
|
|
2946
|
+
*
|
|
2947
|
+
* Turn it DOWN when the sky is blown out: a bright overcast reflected at 0.78
|
|
2948
|
+
* is a sheet of white, and a pool doing that next to a River3D (which paints
|
|
2949
|
+
* an AUTHORED sky palette and so never blows out) reads as two unrelated
|
|
2950
|
+
* materials meeting at a line. Turn it up for a mirror-calm lake.
|
|
2951
|
+
*/
|
|
2952
|
+
reflectivity = null;
|
|
2793
2953
|
/** Bodies crossing the surface emit signals and raise ripples. */
|
|
2794
2954
|
interaction = true;
|
|
2795
2955
|
/** Splash WHITEWATER: the shaders paint expanding surface foam where bodies
|
|
@@ -2880,9 +3040,10 @@ var Water3D = class Water3D extends Node3D {
|
|
|
2880
3040
|
/** Per-preset internal look constants (fresnel curve, reflection ceiling). */
|
|
2881
3041
|
presetLook() {
|
|
2882
3042
|
const look = WATER_PRESETS[this.preset]?._look;
|
|
3043
|
+
const asked = this.rp("reflectivity");
|
|
2883
3044
|
return {
|
|
2884
3045
|
fresnelScale: look?.fresnelScale ?? FANCY_LOOK.fresnelScale,
|
|
2885
|
-
reflectivityMax: look?.reflectivityMax ?? .6,
|
|
3046
|
+
reflectivityMax: typeof asked === "number" ? Math.min(Math.max(asked, 0), 1) : look?.reflectivityMax ?? .6,
|
|
2886
3047
|
edgeFade: look?.edgeFade ?? true,
|
|
2887
3048
|
foamRangeScale: look?.foamRangeScale ?? 1,
|
|
2888
3049
|
foamIntensity: look?.foamIntensity ?? FOAM_LOOK.intensity
|
|
@@ -2962,6 +3123,9 @@ var Water3D = class Water3D extends Node3D {
|
|
|
2962
3123
|
mirrorCamera = new PerspectiveCamera();
|
|
2963
3124
|
mirrorMatrix = new Matrix4();
|
|
2964
3125
|
lastMirrorAt = 0;
|
|
3126
|
+
/** Camera pose the current mirror image was rendered from (see redraw). */
|
|
3127
|
+
lastMirrorEye = new Vector3(Infinity, Infinity, Infinity);
|
|
3128
|
+
lastMirrorQuat = new Quaternion();
|
|
2965
3129
|
_createObject3D() {
|
|
2966
3130
|
return new Mesh();
|
|
2967
3131
|
}
|
|
@@ -3044,7 +3208,8 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3044
3208
|
if (!this.mirrorTarget) this.mirrorTarget = new WebGLRenderTarget(mirrorW, mirrorH, {
|
|
3045
3209
|
depthBuffer: true,
|
|
3046
3210
|
stencilBuffer: false,
|
|
3047
|
-
type: HalfFloatType
|
|
3211
|
+
type: HalfFloatType,
|
|
3212
|
+
samples: 4
|
|
3048
3213
|
});
|
|
3049
3214
|
const surfaceY = worldTranslationOf(this).y;
|
|
3050
3215
|
const cam = ctx.camera;
|
|
@@ -3065,11 +3230,15 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3065
3230
|
mirrorCam.lookAt(mirrorTargetScratch);
|
|
3066
3231
|
mirrorCam.updateMatrixWorld(true);
|
|
3067
3232
|
mirrorCam.updateProjectionMatrix();
|
|
3068
|
-
|
|
3069
|
-
this.
|
|
3070
|
-
this.
|
|
3071
|
-
if (this.lastMirrorAt === 0 || now - this.lastMirrorAt >= this.rp("mirrorInterval")) {
|
|
3233
|
+
cam.getWorldPosition(mirrorPrevEyeCheck);
|
|
3234
|
+
const moved = mirrorPrevEyeCheck.distanceToSquared(this.lastMirrorEye) > 1e-6 || Math.abs(mirrorQuatScratch.dot(this.lastMirrorQuat)) < .9999995;
|
|
3235
|
+
if (this.lastMirrorAt === 0 || moved || now - this.lastMirrorAt >= this.rp("mirrorInterval")) {
|
|
3072
3236
|
this.lastMirrorAt = now;
|
|
3237
|
+
this.lastMirrorEye.copy(mirrorPrevEyeCheck);
|
|
3238
|
+
this.lastMirrorQuat.copy(mirrorQuatScratch);
|
|
3239
|
+
this.mirrorMatrix.set(.5, 0, 0, .5, 0, .5, 0, .5, 0, 0, .5, .5, 0, 0, 0, 1);
|
|
3240
|
+
this.mirrorMatrix.multiply(mirrorCam.projectionMatrix);
|
|
3241
|
+
this.mirrorMatrix.multiply(mirrorCam.matrixWorldInverse);
|
|
3073
3242
|
const prevMirrorTarget = ctx.gl.getRenderTarget();
|
|
3074
3243
|
const prevMirrorClip = ctx.gl.clippingPlanes;
|
|
3075
3244
|
const wasMirrorVisible = mesh.visible;
|
|
@@ -3565,6 +3734,19 @@ var Water3D = class Water3D extends Node3D {
|
|
|
3565
3734
|
const center = worldTranslationOf(this, this._scratchA);
|
|
3566
3735
|
return center.y + this.waveOffsetAt(x, z, center);
|
|
3567
3736
|
}
|
|
3737
|
+
/**
|
|
3738
|
+
* @internal Water surface height over a world point, or null outside this
|
|
3739
|
+
* surface's footprint — what scattered vegetation asks before it plants a
|
|
3740
|
+
* blade (see {@link WetSurface}). The STILL level, not the wave: grass does
|
|
3741
|
+
* not come and go with the swash.
|
|
3742
|
+
*/
|
|
3743
|
+
_waterYAt(x, z) {
|
|
3744
|
+
const center = worldTranslationOf(this, this._scratchA);
|
|
3745
|
+
const halfX = (this.size[0] ?? 0) / 2;
|
|
3746
|
+
const halfZ = (this.size[1] ?? 0) / 2;
|
|
3747
|
+
if (Math.abs(x - center.x) > halfX || Math.abs(z - center.z) > halfZ) return null;
|
|
3748
|
+
return center.y;
|
|
3749
|
+
}
|
|
3568
3750
|
/** Base wave height at world (x, z) — the CPU twin of the active shader.
|
|
3569
3751
|
* Ripples are excluded on purpose: a splash must not re-trigger itself. */
|
|
3570
3752
|
waveOffsetAt(x, z, center) {
|
|
@@ -3684,6 +3866,7 @@ const UNDERWATER_MARGIN = 3;
|
|
|
3684
3866
|
/** Keep-below clip plane for the scene pre-pass (constant set per render):
|
|
3685
3867
|
* normal (0,−1,0) keeps y < constant — see the grab-clip note in _onRender3D. */
|
|
3686
3868
|
const mirrorEyeScratch = new Vector3();
|
|
3869
|
+
const mirrorPrevEyeCheck = new Vector3();
|
|
3687
3870
|
const mirrorTargetScratch = new Vector3();
|
|
3688
3871
|
const mirrorUpScratch = new Vector3();
|
|
3689
3872
|
const mirrorQuatScratch = new Quaternion();
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue, s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-
|
|
2
|
+
import { $ as Node, l as PropSchema, n as BehaviorCtor, t as Behavior, v as Engine } from "./behavior-BAc0erXF.js";
|
|
3
3
|
|
|
4
4
|
//#region src/gameplay/chase.d.ts
|
|
5
5
|
/**
|
package/dist/gameplay.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-
|
|
1
|
+
import { C as FollowCamera, D as Health, E as DamageOnContact, O as Collector, S as restartScene, T as phaseOf, _ as hitStop, a as Wander, b as GameFlow, c as Projectile, d as PathFollow, f as Oscillate, g as Cooldown, h as CameraShake, i as WaveSpawner, k as Chase, l as Pickup, m as Lifetime, n as registerGameplayBehaviors, o as Spawner, p as MoveTo, r as ZombieAI, s as ScoreKeeper, t as GAMEPLAY_BEHAVIORS, u as Patrol, v as screenFlash, w as DayNight, x as goToScene, y as Interactable } from "./gameplay-L05WgWd1.js";
|
|
2
2
|
export { CameraShake, Chase, Collector, Cooldown, DamageOnContact, DayNight, FollowCamera, GAMEPLAY_BEHAVIORS, GameFlow, Health, Interactable, Lifetime, MoveTo, Oscillate, PathFollow, Patrol, Pickup, Projectile, ScoreKeeper, Spawner, Wander, WaveSpawner, ZombieAI, goToScene, hitStop, phaseOf, registerGameplayBehaviors, restartScene, screenFlash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as Rng, c as JsonValue, d as jsonKind, i as SceneJson, l as jsonClone, n as NodeJson, o as JsonKind, r as SCENE_FORMAT, s as JsonObject, t as ConnectionJson, u as jsonEquals } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as Node, A as SfxPlayOptions, B as spatialPan, C as RendererStats, D as LogManager, E as LogLevel, F as ROLLOFF_MODELS, G as SynthOptions, H as SFX_PRESET_NAMES, I as RolloffModel, J as MusicManager, K as synthSfx, L as SpatialParams, M as Voice, N as VoicePreset, O as InputMap, P as Listener, Q as BusName, R as Vec3, S as GameStats, T as LogEntry, U as SfxParams, V as SFX_PRESETS, W as SfxWave, X as PlayMusicOptions, Y as MusicTrack, Z as AudioBuses, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as NodeLifecycle, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as isAudioContextAvailable, k as SfxEngine, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as SignalListener, o as registeredBehaviors, p as getNodeSignals, q as MusicBackend, r as clearBehaviors, rt as SceneTree, s as NodeCtor, t as Behavior, tt as Signal, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as spatialGain } from "./behavior-
|
|
3
|
-
import { n as loadScene, t as LoadSceneOptions } from "./loader-
|
|
2
|
+
import { $ as Node, A as SfxPlayOptions, B as spatialPan, C as RendererStats, D as LogManager, E as LogLevel, F as ROLLOFF_MODELS, G as SynthOptions, H as SFX_PRESET_NAMES, I as RolloffModel, J as MusicManager, K as synthSfx, L as SpatialParams, M as Voice, N as VoicePreset, O as InputMap, P as Listener, Q as BusName, R as Vec3, S as GameStats, T as LogEntry, U as SfxParams, V as SFX_PRESETS, W as SfxWave, X as PlayMusicOptions, Y as MusicTrack, Z as AudioBuses, _ as registeredTypes, a as registerBehavior, b as Scheduler, c as PropDef, d as createNode, et as NodeLifecycle, f as getNodeSchema, g as registerNode, h as mergeStaticSignals, i as getBehavior, j as isAudioContextAvailable, k as SfxEngine, l as PropSchema, m as getNodeType, n as BehaviorCtor, nt as SignalListener, o as registeredBehaviors, p as getNodeSignals, q as MusicBackend, r as clearBehaviors, rt as SceneTree, s as NodeCtor, t as Behavior, tt as Signal, u as clearRegistry, v as Engine, w as Scene, x as EngineStats, y as EngineOptions, z as spatialGain } from "./behavior-BAc0erXF.js";
|
|
3
|
+
import { n as loadScene, t as LoadSceneOptions } from "./loader-B242FF6N.js";
|
|
4
4
|
import { n as ParticleSimConfig, r as ParticleView, t as ParticleSim } from "./particle-sim-CwJ5rI_P.js";
|
|
5
|
-
import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-
|
|
5
|
+
import { a as AudioElementLike, i as gridFromRows, n as PathGrid, o as AudioPlayer, r as findPath, t as FindPathOptions } from "./pathfinding-BwD974Ss.js";
|
|
6
6
|
import { i as auditScene, n as IncantoErrorCode, r as IncantoErrorDetails, t as IncantoError } from "./errors-1dXlIwoR.js";
|
|
7
7
|
|
|
8
8
|
//#region src/core/audio/crossfade.d.ts
|
|
@@ -49,6 +49,13 @@ interface DebugLineSource {
|
|
|
49
49
|
readonly dimension: "2d" | "3d";
|
|
50
50
|
/** Toggle at runtime: `physics.debugDraw = true`. */
|
|
51
51
|
debugDraw: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Draw only the colliders under this node (its subtree, or the nearest body
|
|
54
|
+
* above it when the node itself carries none). Null draws every collider in
|
|
55
|
+
* the world — which on a real map is a terrain grid burying the one shape you
|
|
56
|
+
* meant to look at.
|
|
57
|
+
*/
|
|
58
|
+
debugScope: object | null;
|
|
52
59
|
/** Flat segment vertices (xy pairs in 2D px, xyz triples in 3D meters), or null while off. */
|
|
53
60
|
debugLines(): Float32Array | null;
|
|
54
61
|
}
|
|
@@ -108,6 +115,18 @@ declare class HudLayer extends Node {
|
|
|
108
115
|
_element: HTMLElement | null;
|
|
109
116
|
private readonly slots;
|
|
110
117
|
override onEnterTree(): void;
|
|
118
|
+
/**
|
|
119
|
+
* Put the overlay where the page's owner says UI goes: `engine.uiHost`, or
|
|
120
|
+
* the window when nobody claims it (a game — a HUD covers the screen).
|
|
121
|
+
*
|
|
122
|
+
* Re-checked every frame because a scene's tree is BUILT before it is handed
|
|
123
|
+
* to an engine: at onEnterTree there is no engine to ask yet, so the first
|
|
124
|
+
* update is where the answer actually arrives. appendChild moves the element
|
|
125
|
+
* if it already landed somewhere else, so the HUD follows the host across a
|
|
126
|
+
* scene swap into a differently-hosted engine (game → editor) instead of
|
|
127
|
+
* being stranded on document.body over the inspector.
|
|
128
|
+
*/
|
|
129
|
+
private _mount;
|
|
111
130
|
override onExitTree(): void;
|
|
112
131
|
override update(): void;
|
|
113
132
|
/** @internal Widgets mount into per-anchor flex columns. */
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { _ as registerBehavior, a as SCENE_FORMAT, c as SceneTree, d as resolveConstants, f as Node, g as getBehavior, h as clearBehaviors, i as serializeNode, l as CONST_REF_KEY, m as Behavior, n as loadScene, o as computeViewport, p as parseNodePath, r as Scene, s as resolveViewport, u as isConstRef, v as registeredBehaviors, y as Signal } from "./loader-BZqOKfI2.js";
|
|
2
|
-
import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-
|
|
2
|
+
import { C as crossfadeGains, S as WebAudioMusicBackend, T as AudioBuses, _ as spatialPan, a as HudLayer, b as synthSfx, c as UiText, d as LogManager, f as InputMap, g as spatialGain, h as ROLLOFF_MODELS, i as UiDialogue, l as AudioPlayer, m as isAudioContextAvailable, n as Timer, o as UiBanner, p as SfxEngine, r as UiButton, s as UiBar, t as registerCoreNodes, u as Engine, v as SFX_PRESETS, w as fadeGain, x as MusicManager, y as SFX_PRESET_NAMES } from "./register-BTg0EM7s.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
5
|
import { t as auditScene } from "./audit-C6rMyict.js";
|
|
@@ -295,6 +295,6 @@ function newUid() {
|
|
|
295
295
|
//#endregion
|
|
296
296
|
//#region src/index.ts
|
|
297
297
|
/** Engine version. Kept in sync with package.json by the release pipeline. */
|
|
298
|
-
const VERSION = "0.
|
|
298
|
+
const VERSION = "0.29.0";
|
|
299
299
|
//#endregion
|
|
300
300
|
export { AudioBuses, AudioPlayer, Behavior, CONST_REF_KEY, Engine, HudLayer, IncantoError, InputMap, LogManager, MusicManager, Node, ORDER_GROUP_BASE, PARTICLE_PRESETS, PARTICLE_PRESET_NAMES, ParticleSim, ROLLOFF_MODELS, Rng, SCENE_FORMAT, SFX_PRESETS, SFX_PRESET_NAMES, Scene, SceneTree, SfxEngine, Signal, Timer, TouchControls, UiBanner, UiBar, UiButton, UiDialogue, UiText, VERSION, WebAudioMusicBackend, applyParticlePreset, assetUrls, attachTouchControls, auditScene, clearBehaviors, clearRegistry, computeViewport, createNode, createNoise2D, createSaveStore, crossfadeGains, duplicateNode, effectiveOrder, fadeGain, findPath, getBehavior, getNodeSchema, getNodeSignals, getNodeType, gridFromRows, isAudioContextAvailable, isConstRef, joystickVector, jsonClone, jsonEquals, jsonKind, loadScene, mergeStaticSignals, newUid, parseNodePath, preloadUrls, registerBehavior, registerCoreNodes, registerNode, registeredBehaviors, registeredTypes, replay, resolveConstants, resolveRendering, resolveViewport, serializeNode, spatialGain, spatialPan, startRecording, synthSfx };
|
package/dist/net.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as JsonValue, i as SceneJson, s as JsonObject } from "./schema-CcoWb32N.js";
|
|
2
|
-
import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-
|
|
2
|
+
import { $ as Node, l as PropSchema, tt as Signal, v as Engine } from "./behavior-BAc0erXF.js";
|
|
3
3
|
|
|
4
4
|
//#region src/net/types.d.ts
|
|
5
5
|
type Unsubscribe = () => void;
|
package/dist/net.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as loadScene } from "./loader-BZqOKfI2.js";
|
|
2
|
-
import { u as Engine } from "./register-
|
|
2
|
+
import { u as Engine } from "./register-BTg0EM7s.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as jsonClone } from "./json-BLk7H2Qa.js";
|
|
5
5
|
import { n as createAgent8Server } from "./agent8-CvsfVskX.js";
|
|
6
|
-
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-
|
|
6
|
+
import { i as applySyncPatch, n as NetworkSpawner, r as NetworkManager, t as registerNodesNet } from "./register-MelqEdza.js";
|
|
7
7
|
//#region src/net/loopback.ts
|
|
8
8
|
/**
|
|
9
9
|
* In-memory multiplayer hub implementing the SAME kernel contract as
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
2
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
3
|
-
import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-
|
|
3
|
+
import { _ as RigidBody2D, b as validateCollider2D, g as PhysicsBody2D, h as CharacterBody2D, m as Area2D, p as Joint2D, y as Node2D } from "./register-DWcWq4QG.js";
|
|
4
4
|
import { n as registerDebugSource } from "./debug-draw-CZmOYjL2.js";
|
|
5
5
|
//#region src/2d/physics/physics-2d.ts
|
|
6
6
|
var physics_2d_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -28,6 +28,8 @@ var Physics2D = class {
|
|
|
28
28
|
engine;
|
|
29
29
|
/** Render collider outlines in the GAME view (renderers pick this up). */
|
|
30
30
|
debugDraw = false;
|
|
31
|
+
/** Narrow those outlines to one node's subtree (see DebugLineSource). */
|
|
32
|
+
debugScope = null;
|
|
31
33
|
dimension = "2d";
|
|
32
34
|
unregisterDebug;
|
|
33
35
|
warnedNoCollider = /* @__PURE__ */ new WeakSet();
|
|
@@ -157,11 +159,69 @@ var Physics2D = class {
|
|
|
157
159
|
/** Rapier debug segments, scaled back to pixels. Null while debugDraw is off. */
|
|
158
160
|
debugLines() {
|
|
159
161
|
if (!this.debugDraw) return null;
|
|
160
|
-
const raw = this.world.debugRender().vertices;
|
|
162
|
+
const raw = this.debugScope ? this.scopedLines(this.debugScope) : this.world.debugRender().vertices;
|
|
161
163
|
const px = new Float32Array(raw.length);
|
|
162
164
|
for (let i = 0; i < raw.length; i++) px[i] = raw[i] * SCALE;
|
|
163
165
|
return px;
|
|
164
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Only the selected node's colliders (meters — the caller scales to pixels).
|
|
169
|
+
* Rapier draws the whole world or nothing, so a chosen few are outlined from
|
|
170
|
+
* the same shape data the solver holds. 2D colliders are rect/circle/capsule,
|
|
171
|
+
* which is the whole switch.
|
|
172
|
+
*/
|
|
173
|
+
scopedLines(node) {
|
|
174
|
+
const out = [];
|
|
175
|
+
const bodies = [];
|
|
176
|
+
const collect = (n) => {
|
|
177
|
+
const entry = this.entries.get(n);
|
|
178
|
+
if (entry) bodies.push(entry.body);
|
|
179
|
+
for (const child of n.children) collect(child);
|
|
180
|
+
};
|
|
181
|
+
collect(node);
|
|
182
|
+
for (let p = node.parent; p && bodies.length === 0; p = p.parent) {
|
|
183
|
+
const entry = this.entries.get(p);
|
|
184
|
+
if (entry) bodies.push(entry.body);
|
|
185
|
+
}
|
|
186
|
+
const RING = 24;
|
|
187
|
+
for (const body of bodies) for (let i = 0; i < body.numColliders(); i++) {
|
|
188
|
+
const collider = body.collider(i);
|
|
189
|
+
if (!collider) continue;
|
|
190
|
+
const t = collider.translation();
|
|
191
|
+
const angle = collider.rotation();
|
|
192
|
+
const cos = Math.cos(angle);
|
|
193
|
+
const sin = Math.sin(angle);
|
|
194
|
+
const put = (x, y) => {
|
|
195
|
+
out.push(t.x + x * cos - y * sin, t.y + x * sin + y * cos);
|
|
196
|
+
};
|
|
197
|
+
const seg = (ax, ay, bx, by) => {
|
|
198
|
+
put(ax, ay);
|
|
199
|
+
put(bx, by);
|
|
200
|
+
};
|
|
201
|
+
const shape = collider.shape;
|
|
202
|
+
if (shape.halfExtents) {
|
|
203
|
+
const { x, y } = shape.halfExtents;
|
|
204
|
+
seg(-x, -y, x, -y);
|
|
205
|
+
seg(x, -y, x, y);
|
|
206
|
+
seg(x, y, -x, y);
|
|
207
|
+
seg(-x, y, -x, -y);
|
|
208
|
+
} else if (shape.radius !== void 0) {
|
|
209
|
+
const r = shape.radius;
|
|
210
|
+
const half = shape.halfHeight ?? 0;
|
|
211
|
+
for (let k = 0; k < RING; k++) {
|
|
212
|
+
const a = k / RING * Math.PI * 2;
|
|
213
|
+
const b = (k + 1) / RING * Math.PI * 2;
|
|
214
|
+
const lift = (v) => v >= 0 ? half : -half;
|
|
215
|
+
seg(Math.cos(a) * r, Math.sin(a) * r + lift(Math.sin(a)), Math.cos(b) * r, Math.sin(b) * r + lift(Math.sin(b)));
|
|
216
|
+
}
|
|
217
|
+
if (half > 0) {
|
|
218
|
+
seg(-r, -half, -r, half);
|
|
219
|
+
seg(r, -half, r, half);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return new Float32Array(out);
|
|
224
|
+
}
|
|
165
225
|
/** Create newly-arrived joints; tear down departed ones. */
|
|
166
226
|
syncJoints(structureChanged) {
|
|
167
227
|
const R = this.R;
|