incanto 0.4.3 → 0.6.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/2d.d.ts +80 -6
- package/dist/2d.js +4 -4
- package/dist/3d.d.ts +200 -15
- package/dist/3d.js +4 -4
- package/dist/{audio-player-DqUR3XFs.d.ts → audio-player-DkBqRTs4.d.ts} +1 -1
- package/dist/{behavior-BAQq7HGM.d.ts → behavior-CWhW3oa6.d.ts} +54 -0
- package/dist/{create-game-CZHROKcT.js → create-game-B_sW_eiE.js} +59 -27
- package/dist/{create-game-DiTq3-fQ.js → create-game-DkbZCNaV.js} +292 -14
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-DP2WPYom.js → duplicate-BEvGBtb_.js} +1 -1
- package/dist/{gameplay-Ccruc3Wd.js → gameplay-CDFgSG6z.js} +293 -24
- package/dist/gameplay.d.ts +113 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +139 -5
- package/dist/index.js +59 -6
- package/dist/{loader-CGs_G-r0.js → loader-B4OEXDZ8.js} +60 -0
- package/dist/{loader-Mo0KghCv.d.ts → loader-D2u0fVW2.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +1 -1
- package/dist/{particle-sim-DYuSUxvK.js → particle-sim-CFkILGwh.js} +84 -3
- package/dist/{particle-sim-CbN4YUuH.d.ts → particle-sim-CwJ5rI_P.d.ts} +3 -0
- package/dist/{physics-2d-KuMWPTf6.js → physics-2d-Cgli1aju.js} +95 -10
- package/dist/{physics-3d-DmNCeh58.js → physics-3d-CBAQ12LY.js} +121 -43
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-DPEV9_9t.js → register-BJCfuuZ2.js} +79 -5
- package/dist/{register-B0gq63VW.js → register-C35HDZpm.js} +2 -2
- package/dist/{register-BRy8FNox.js → register-Dd7Juujf.js} +389 -49
- package/dist/{register-BuUV1_KB.js → register-Dl_ixIJe.js} +275 -2
- package/dist/test.d.ts +2 -2
- package/dist/test.js +10 -10
- package/editor/assets/{agent8-DryTXVd4.js → agent8-MciFwn0v.js} +1 -1
- package/editor/assets/index-mofVSmuA.js +7417 -0
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/schemas/scene.schema.json +980 -126
- package/skills/incanto-3d-models.md +24 -1
- package/skills/incanto-building-2d-games.md +6 -0
- package/skills/incanto-building-3d-games.md +46 -1
- package/skills/incanto-gameplay-behaviors.md +60 -0
- package/skills/incanto-hud.md +58 -0
- package/skills/incanto-node-reference.md +148 -0
- package/skills/incanto-physics-and-input.md +47 -0
- package/editor/assets/index-N8se-PhP.js +0 -7365
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { f as Node } from "./loader-
|
|
2
|
-
import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-
|
|
1
|
+
import { f as Node } from "./loader-B4OEXDZ8.js";
|
|
2
|
+
import { i as applyParticlePreset, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-CFkILGwh.js";
|
|
3
3
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
4
4
|
import { t as Rng } from "./rng-DP-SR7eg.js";
|
|
5
|
-
import { t as registerCoreNodes } from "./register-
|
|
5
|
+
import { t as registerCoreNodes } from "./register-Dl_ixIJe.js";
|
|
6
6
|
import { i as getNodeSchema, l as registerNode } from "./registry-BVJ2HbCn.js";
|
|
7
7
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
8
8
|
import { n as splatWeights, t as buildHeightmap } from "./heightmap-CroQPEER.js";
|
|
9
|
-
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, Color, ConeGeometry, CubeCamera, CylinderGeometry, DataTexture, DepthTexture, DirectionalLight, DoubleSide, Euler, FloatType, Frustum, Group, HalfFloatType, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, MathUtils, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NormalBlending, Object3D, PerspectiveCamera, Plane, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, Sphere, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector2, Vector3, Vector4, VectorKeyframeTrack, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
|
|
9
|
+
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, Color, ConeGeometry, CubeCamera, CylinderGeometry, DataTexture, DepthTexture, DirectionalLight, DoubleSide, Euler, FloatType, Frustum, Group, HalfFloatType, IcosahedronGeometry, ImageBitmapLoader, InstancedBufferAttribute, InstancedMesh, LinearFilter, LinearMipmapLinearFilter, LoopOnce, LoopRepeat, MathUtils, Matrix4, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshPhysicalMaterial, MeshStandardMaterial, NearestFilter, NoBlending, NormalBlending, Object3D, PerspectiveCamera, Plane, PlaneGeometry, PointLight, Points, PointsMaterial, Quaternion, QuaternionKeyframeTrack, RGBADepthPacking, RGBAFormat, RepeatWrapping, SRGBColorSpace, ShaderChunk, ShaderMaterial, Sphere, SphereGeometry, Texture, TextureLoader, UniformsLib, UniformsUtils, Vector2, Vector3, Vector4, VectorKeyframeTrack, WebGLCubeRenderTarget, WebGLRenderTarget } from "three";
|
|
10
10
|
import { clone } from "three/addons/utils/SkeletonUtils.js";
|
|
11
11
|
//#region src/3d/camera-rig.ts
|
|
12
12
|
const QUARTER_PITCH = Math.atan(1 / Math.SQRT2);
|
|
@@ -203,6 +203,7 @@ var Node3D = class extends Node {
|
|
|
203
203
|
0,
|
|
204
204
|
0
|
|
205
205
|
] },
|
|
206
|
+
static: { default: false },
|
|
206
207
|
scale: { default: [
|
|
207
208
|
1,
|
|
208
209
|
1,
|
|
@@ -222,6 +223,15 @@ var Node3D = class extends Node {
|
|
|
222
223
|
0,
|
|
223
224
|
0
|
|
224
225
|
];
|
|
226
|
+
/**
|
|
227
|
+
* This subtree never changes after load: the renderer syncs it ONCE and
|
|
228
|
+
* skips it every frame after (transforms, materials, animations frozen).
|
|
229
|
+
* The per-frame walk is O(nodes) — marking terrain/buildings/decor static
|
|
230
|
+
* removes most of a big scene from it. Set `static = false` to resume
|
|
231
|
+
* live syncing. Do NOT mark animated nodes (Water3D, Particles3D,
|
|
232
|
+
* AnimatedSprite3D, ModelInstance3D with clips) or cameras static.
|
|
233
|
+
*/
|
|
234
|
+
static = false;
|
|
225
235
|
scale = [
|
|
226
236
|
1,
|
|
227
237
|
1,
|
|
@@ -286,7 +296,21 @@ var PhysicsBody3D = class extends Node3D {
|
|
|
286
296
|
static props = { collider: { default: {} } };
|
|
287
297
|
/** The unified collision model: every collider participant can emit these. */
|
|
288
298
|
static signals = ["triggerEnter", "triggerExit"];
|
|
289
|
-
|
|
299
|
+
_collider = {};
|
|
300
|
+
/** @internal Physics resync flag — raised when `collider` is REPLACED. */
|
|
301
|
+
_colliderChanged = false;
|
|
302
|
+
/**
|
|
303
|
+
* Collider shape prop. Change it by REPLACING the object
|
|
304
|
+
* (`body.collider = { shape: 'box', size: [...] }`) — physics rebuilds the
|
|
305
|
+
* body on replacement; in-place mutation of the old object is not watched.
|
|
306
|
+
*/
|
|
307
|
+
get collider() {
|
|
308
|
+
return this._collider;
|
|
309
|
+
}
|
|
310
|
+
set collider(value) {
|
|
311
|
+
this._collider = value;
|
|
312
|
+
this._colliderChanged = true;
|
|
313
|
+
}
|
|
290
314
|
/** Loader hook: bad collider shapes fail at LOAD, not at physics start. */
|
|
291
315
|
static validateJson(node) {
|
|
292
316
|
const body = node;
|
|
@@ -373,6 +397,78 @@ var CharacterBody3D = class extends PhysicsBody3D {
|
|
|
373
397
|
}
|
|
374
398
|
};
|
|
375
399
|
//#endregion
|
|
400
|
+
//#region src/3d/nodes/joint-3d.ts
|
|
401
|
+
/**
|
|
402
|
+
* A physics joint linking its PARENT body to `target` (a node path to the
|
|
403
|
+
* other body). Godot-style placement: the joint lives as a child of body A.
|
|
404
|
+
*
|
|
405
|
+
* { "name": "Hinge", "type": "Joint3D",
|
|
406
|
+
* "props": { "type": "spherical", "target": "%Anchor", "anchor": [0, -20] } }
|
|
407
|
+
*
|
|
408
|
+
* Types: `fixed` welds the bodies rigidly · `spherical` is a ball joint at the
|
|
409
|
+
* anchors · `rope` caps the anchor distance at `length` (m; 0 = measured at
|
|
410
|
+
* creation) · `spring` pulls toward `length` with `stiffness`/`damping`.
|
|
411
|
+
* Anchors are LOCAL meter offsets on each body.
|
|
412
|
+
*/
|
|
413
|
+
var Joint3D = class extends Node3D {
|
|
414
|
+
static typeName = "Joint3D";
|
|
415
|
+
static props = {
|
|
416
|
+
type: {
|
|
417
|
+
default: "spherical",
|
|
418
|
+
options: [
|
|
419
|
+
"fixed",
|
|
420
|
+
"spherical",
|
|
421
|
+
"rope",
|
|
422
|
+
"spring"
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
target: { default: "" },
|
|
426
|
+
anchor: { default: [
|
|
427
|
+
0,
|
|
428
|
+
0,
|
|
429
|
+
0
|
|
430
|
+
] },
|
|
431
|
+
targetAnchor: { default: [
|
|
432
|
+
0,
|
|
433
|
+
0,
|
|
434
|
+
0
|
|
435
|
+
] },
|
|
436
|
+
length: { default: 0 },
|
|
437
|
+
stiffness: { default: 50 },
|
|
438
|
+
damping: { default: 5 }
|
|
439
|
+
};
|
|
440
|
+
type = "spherical";
|
|
441
|
+
target = "";
|
|
442
|
+
anchor = [
|
|
443
|
+
0,
|
|
444
|
+
0,
|
|
445
|
+
0
|
|
446
|
+
];
|
|
447
|
+
targetAnchor = [
|
|
448
|
+
0,
|
|
449
|
+
0,
|
|
450
|
+
0
|
|
451
|
+
];
|
|
452
|
+
/** rope/spring rest length in m; 0 = measure body distance at creation. */
|
|
453
|
+
length = 0;
|
|
454
|
+
stiffness = 50;
|
|
455
|
+
damping = 5;
|
|
456
|
+
onEnterTree() {
|
|
457
|
+
if (!(this.parent instanceof PhysicsBody3D)) throw new IncantoError("TREE_VIOLATION", `Joint3D '${this.name}' must be a child of a physics body (its body A); parent is '${this.parent?.name ?? "none"}'.`);
|
|
458
|
+
if (this.target === "") throw new IncantoError("BAD_FORMAT", `Joint3D '${this.name}' needs a "target" node path to the body it links to.`);
|
|
459
|
+
}
|
|
460
|
+
/** @internal The two bodies, resolved (throws on a bad target at step time). */
|
|
461
|
+
_resolveBodies() {
|
|
462
|
+
const a = this.parent;
|
|
463
|
+
const b = this.getNodeOrNull(this.target);
|
|
464
|
+
if (!(b instanceof PhysicsBody3D)) throw new IncantoError("NODE_NOT_FOUND", `Joint3D '${this.name}': target '${this.target}' is not a physics body.`);
|
|
465
|
+
return {
|
|
466
|
+
a,
|
|
467
|
+
b
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
//#endregion
|
|
376
472
|
//#region src/3d/terrain/splat-material.ts
|
|
377
473
|
/** Splice `code` immediately AFTER `marker` — a missing marker is a hard error
|
|
378
474
|
* (a silently unpatched terrain shader renders weight-colored vertex soup). */
|
|
@@ -4885,6 +4981,9 @@ const MATERIAL_KEYS = [
|
|
|
4885
4981
|
"wireframe",
|
|
4886
4982
|
"flatShading",
|
|
4887
4983
|
"opacity",
|
|
4984
|
+
"clearcoat",
|
|
4985
|
+
"clearcoatRoughness",
|
|
4986
|
+
"envMapIntensity",
|
|
4888
4987
|
"depthTest",
|
|
4889
4988
|
"depthWrite",
|
|
4890
4989
|
"map",
|
|
@@ -4897,7 +4996,8 @@ const MATERIAL_KEYS = [
|
|
|
4897
4996
|
* - `size [x,y,z]`: box extents; sphere/gem radius = x; capsule radius = x,
|
|
4898
4997
|
* height = y; plane = x·z ground plane (laid flat); cylinder radius = x, height = y
|
|
4899
4998
|
* - `material`: `{color, metalness, roughness, wireframe, flatShading, opacity,
|
|
4900
|
-
*
|
|
4999
|
+
* clearcoat, clearcoatRoughness, envMapIntensity, depthTest, depthWrite,
|
|
5000
|
+
* emissive, emissiveIntensity, map, normalMap, repeat}` —
|
|
4901
5001
|
* `map`/`normalMap` are texture URLs loaded lazily (headless-safe: geometry and
|
|
4902
5002
|
* physics never wait on them), tiled by `repeat: [u, v]`. `gem` + `flatShading:
|
|
4903
5003
|
* true` + a low `roughness` reads as a sparkling faceted jewel (spin it for the
|
|
@@ -4938,13 +5038,13 @@ var MeshInstance3D = class extends Node3D {
|
|
|
4938
5038
|
receiveShadow = false;
|
|
4939
5039
|
/** Loader hook: malformed materials fail at LOAD time, not at render. */
|
|
4940
5040
|
static validateJson(node) {
|
|
4941
|
-
|
|
5041
|
+
validateMeshMaterial(node.material, node.name);
|
|
4942
5042
|
}
|
|
4943
5043
|
geometryKey = "";
|
|
4944
5044
|
textureKey = "";
|
|
4945
5045
|
loadedTextures = [];
|
|
4946
5046
|
_createObject3D() {
|
|
4947
|
-
return new Mesh(new BoxGeometry(1, 1, 1), new
|
|
5047
|
+
return new Mesh(new BoxGeometry(1, 1, 1), new MeshPhysicalMaterial());
|
|
4948
5048
|
}
|
|
4949
5049
|
_syncObject3D() {
|
|
4950
5050
|
super._syncObject3D();
|
|
@@ -4956,22 +5056,7 @@ var MeshInstance3D = class extends Node3D {
|
|
|
4956
5056
|
this.geometryKey = key;
|
|
4957
5057
|
}
|
|
4958
5058
|
const mat = m.material;
|
|
4959
|
-
mat
|
|
4960
|
-
mat.metalness = this.material.metalness ?? 0;
|
|
4961
|
-
mat.roughness = this.material.roughness ?? 1;
|
|
4962
|
-
mat.wireframe = this.material.wireframe ?? false;
|
|
4963
|
-
mat.emissive.set(this.material.emissive ?? "#000000");
|
|
4964
|
-
mat.emissiveIntensity = this.material.emissiveIntensity ?? 1;
|
|
4965
|
-
const opacity = this.material.opacity ?? 1;
|
|
4966
|
-
mat.opacity = opacity;
|
|
4967
|
-
mat.transparent = opacity < 1;
|
|
4968
|
-
mat.depthTest = this.material.depthTest ?? true;
|
|
4969
|
-
mat.depthWrite = this.material.depthWrite ?? true;
|
|
4970
|
-
const flat = this.material.flatShading ?? false;
|
|
4971
|
-
if (mat.flatShading !== flat) {
|
|
4972
|
-
mat.flatShading = flat;
|
|
4973
|
-
mat.needsUpdate = true;
|
|
4974
|
-
}
|
|
5059
|
+
applyMeshMaterial(mat, this.material);
|
|
4975
5060
|
this.syncTextures(mat);
|
|
4976
5061
|
m.castShadow = this.castShadow;
|
|
4977
5062
|
m.receiveShadow = this.receiveShadow;
|
|
@@ -5014,11 +5099,42 @@ var MeshInstance3D = class extends Node3D {
|
|
|
5014
5099
|
super.free();
|
|
5015
5100
|
}
|
|
5016
5101
|
};
|
|
5102
|
+
/**
|
|
5103
|
+
* Apply the shared material-prop pipeline (everything except textures) with
|
|
5104
|
+
* reset-to-default semantics: the JSON object IS the material state — a
|
|
5105
|
+
* replaced `{color}` must not keep a stale wireframe from a previous value.
|
|
5106
|
+
* Shared by every node that takes a `MeshMaterialProps` (MeshInstance3D,
|
|
5107
|
+
* LoftMesh3D).
|
|
5108
|
+
*/
|
|
5109
|
+
function applyMeshMaterial(mat, props) {
|
|
5110
|
+
mat.color.set(props.color ?? "#ffffff");
|
|
5111
|
+
mat.metalness = props.metalness ?? 0;
|
|
5112
|
+
mat.roughness = props.roughness ?? 1;
|
|
5113
|
+
mat.wireframe = props.wireframe ?? false;
|
|
5114
|
+
mat.emissive.set(props.emissive ?? "#000000");
|
|
5115
|
+
mat.emissiveIntensity = props.emissiveIntensity ?? 1;
|
|
5116
|
+
const opacity = props.opacity ?? 1;
|
|
5117
|
+
mat.opacity = opacity;
|
|
5118
|
+
mat.transparent = opacity < 1;
|
|
5119
|
+
const clearcoat = props.clearcoat ?? 0;
|
|
5120
|
+
if (mat.clearcoat > 0 !== clearcoat > 0) mat.needsUpdate = true;
|
|
5121
|
+
mat.clearcoat = clearcoat;
|
|
5122
|
+
mat.clearcoatRoughness = props.clearcoatRoughness ?? 0;
|
|
5123
|
+
mat.envMapIntensity = props.envMapIntensity ?? 1;
|
|
5124
|
+
mat.depthTest = props.depthTest ?? true;
|
|
5125
|
+
mat.depthWrite = props.depthWrite ?? true;
|
|
5126
|
+
const flat = props.flatShading ?? false;
|
|
5127
|
+
if (mat.flatShading !== flat) {
|
|
5128
|
+
mat.flatShading = flat;
|
|
5129
|
+
mat.needsUpdate = true;
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5017
5132
|
/** Hard-fail on malformed material JSON — typos and bad shapes surface at
|
|
5018
|
-
* load with the valid options, instead of silently rendering defaults.
|
|
5019
|
-
|
|
5133
|
+
* load with the valid options, instead of silently rendering defaults.
|
|
5134
|
+
* Exported for nodes that reuse `MeshMaterialProps` (LoftMesh3D). */
|
|
5135
|
+
function validateMeshMaterial(material, name, typeName = "MeshInstance3D") {
|
|
5020
5136
|
const fail = (message) => {
|
|
5021
|
-
throw new IncantoError("BAD_FORMAT",
|
|
5137
|
+
throw new IncantoError("BAD_FORMAT", `${typeName} '${name}' ${message}`, {
|
|
5022
5138
|
prop: "material",
|
|
5023
5139
|
validOptions: [...MATERIAL_KEYS]
|
|
5024
5140
|
});
|
|
@@ -5037,11 +5153,14 @@ function validateMaterial(material, name) {
|
|
|
5037
5153
|
for (const key of [
|
|
5038
5154
|
"metalness",
|
|
5039
5155
|
"roughness",
|
|
5040
|
-
"opacity"
|
|
5156
|
+
"opacity",
|
|
5157
|
+
"clearcoat",
|
|
5158
|
+
"clearcoatRoughness"
|
|
5041
5159
|
]) {
|
|
5042
5160
|
const value = material[key];
|
|
5043
5161
|
if (value !== void 0 && !(typeof value === "number" && value >= 0 && value <= 1)) fail(`material.${key} must be a number in 0..1, got ${JSON.stringify(value)}.`);
|
|
5044
5162
|
}
|
|
5163
|
+
if (material.envMapIntensity !== void 0 && !(typeof material.envMapIntensity === "number" && Number.isFinite(material.envMapIntensity) && material.envMapIntensity >= 0)) fail(`material.envMapIntensity must be a number >= 0, got ${JSON.stringify(material.envMapIntensity)}.`);
|
|
5045
5164
|
for (const key of [
|
|
5046
5165
|
"wireframe",
|
|
5047
5166
|
"flatShading",
|
|
@@ -5071,6 +5190,185 @@ function buildGeometry(kind, size) {
|
|
|
5071
5190
|
}
|
|
5072
5191
|
}
|
|
5073
5192
|
//#endregion
|
|
5193
|
+
//#region src/3d/nodes/loft-mesh-3d.ts
|
|
5194
|
+
/**
|
|
5195
|
+
* A smooth swept hull ("loft"): superellipse cross-sections interpolated
|
|
5196
|
+
* along Z with rounded end caps and per-vertex normals — curved highlights
|
|
5197
|
+
* and sky reflections sweep across it like sheet metal, which no box/capsule
|
|
5198
|
+
* assembly can do. THE node for vehicle bodies, glass canopies, boat hulls,
|
|
5199
|
+
* fuselages.
|
|
5200
|
+
*
|
|
5201
|
+
* - `sections`: ≥2 stations front→back (see LoftSection)
|
|
5202
|
+
* - `slices`: vertices around each ring (8..64, default 24)
|
|
5203
|
+
* - `smooth`: interpolated rings per section gap (1..16, default 4)
|
|
5204
|
+
* - `material`: same object as MeshInstance3D **minus textures** (a loft has
|
|
5205
|
+
* no UVs — map/normalMap/repeat hard-fail). Pair `clearcoat` paint or a
|
|
5206
|
+
* high `envMapIntensity` glass with the smooth normals for the automotive
|
|
5207
|
+
* look.
|
|
5208
|
+
*/
|
|
5209
|
+
var LoftMesh3D = class extends Node3D {
|
|
5210
|
+
static typeName = "LoftMesh3D";
|
|
5211
|
+
static props = {
|
|
5212
|
+
sections: { default: [] },
|
|
5213
|
+
slices: { default: 24 },
|
|
5214
|
+
smooth: { default: 4 },
|
|
5215
|
+
material: { default: {} },
|
|
5216
|
+
castShadow: { default: false },
|
|
5217
|
+
receiveShadow: { default: false }
|
|
5218
|
+
};
|
|
5219
|
+
sections = [];
|
|
5220
|
+
slices = 24;
|
|
5221
|
+
smooth = 4;
|
|
5222
|
+
material = {};
|
|
5223
|
+
castShadow = false;
|
|
5224
|
+
receiveShadow = false;
|
|
5225
|
+
/** Loader hook: malformed sections/materials fail at LOAD time. */
|
|
5226
|
+
static validateJson(node) {
|
|
5227
|
+
const loft = node;
|
|
5228
|
+
validateSections(loft.sections, node.name);
|
|
5229
|
+
validateMeshMaterial(loft.material, node.name, "LoftMesh3D");
|
|
5230
|
+
for (const key of [
|
|
5231
|
+
"map",
|
|
5232
|
+
"normalMap",
|
|
5233
|
+
"repeat"
|
|
5234
|
+
]) if (loft.material[key] !== void 0) throw new IncantoError("BAD_FORMAT", `LoftMesh3D '${node.name}' material does not support '${key}' — a loft has no UVs; use color/metalness/roughness/clearcoat/envMapIntensity instead.`, { prop: "material" });
|
|
5235
|
+
}
|
|
5236
|
+
geometryKey = "";
|
|
5237
|
+
_createObject3D() {
|
|
5238
|
+
return new Mesh(new BufferGeometry(), new MeshPhysicalMaterial());
|
|
5239
|
+
}
|
|
5240
|
+
_syncObject3D() {
|
|
5241
|
+
super._syncObject3D();
|
|
5242
|
+
const m = this._ensureObject3D();
|
|
5243
|
+
const key = JSON.stringify([
|
|
5244
|
+
this.sections,
|
|
5245
|
+
this.slices,
|
|
5246
|
+
this.smooth
|
|
5247
|
+
]);
|
|
5248
|
+
if (key !== this.geometryKey && this.sections.length >= 2) {
|
|
5249
|
+
m.geometry.dispose();
|
|
5250
|
+
m.geometry = buildLoftGeometry(this.sections, this.slices, this.smooth);
|
|
5251
|
+
this.geometryKey = key;
|
|
5252
|
+
}
|
|
5253
|
+
applyMeshMaterial(m.material, this.material);
|
|
5254
|
+
m.castShadow = this.castShadow;
|
|
5255
|
+
m.receiveShadow = this.receiveShadow;
|
|
5256
|
+
}
|
|
5257
|
+
};
|
|
5258
|
+
function validateSections(sections, name) {
|
|
5259
|
+
const fail = (message) => {
|
|
5260
|
+
throw new IncantoError("BAD_FORMAT", `LoftMesh3D '${name}' ${message}`, { prop: "sections" });
|
|
5261
|
+
};
|
|
5262
|
+
if (!Array.isArray(sections) || sections.length < 2) fail(`sections needs at least 2 stations, got ${JSON.stringify(sections)}.`);
|
|
5263
|
+
for (const [i, s] of sections.entries()) {
|
|
5264
|
+
if (typeof s !== "object" || s === null) fail(`sections[${i}] must be an object.`);
|
|
5265
|
+
for (const k of [
|
|
5266
|
+
"z",
|
|
5267
|
+
"width",
|
|
5268
|
+
"height",
|
|
5269
|
+
"y"
|
|
5270
|
+
]) if (typeof s[k] !== "number" || !Number.isFinite(s[k])) fail(`sections[${i}].${k} must be a finite number, got ${JSON.stringify(s[k])}.`);
|
|
5271
|
+
if (s.width <= 0 || s.height <= 0) fail(`sections[${i}] width/height must be positive, got ${s.width}×${s.height}.`);
|
|
5272
|
+
if (s.corner !== void 0 && !(typeof s.corner === "number" && s.corner >= 0 && s.corner <= 1)) fail(`sections[${i}].corner must be in 0..1, got ${JSON.stringify(s.corner)}.`);
|
|
5273
|
+
if (i > 0 && s.z <= sections[i - 1].z) fail(`sections[].z must be strictly ascending (sections[${i}].z=${s.z}).`);
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5276
|
+
/** Endpoint-clamped Catmull-Rom through scalar samples. */
|
|
5277
|
+
function catmullRom(p0, p1, p2, p3, t) {
|
|
5278
|
+
const t2 = t * t;
|
|
5279
|
+
const t3 = t2 * t;
|
|
5280
|
+
return .5 * (2 * p1 + (p2 - p0) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + (-p0 + 3 * p1 - 3 * p2 + p3) * t3);
|
|
5281
|
+
}
|
|
5282
|
+
/** Sample the section list into (K-1)*smooth+1 interpolated rings. */
|
|
5283
|
+
function sampleRings(sections, smooth) {
|
|
5284
|
+
const get = (i) => {
|
|
5285
|
+
const s = sections[Math.max(0, Math.min(sections.length - 1, i))];
|
|
5286
|
+
return {
|
|
5287
|
+
z: s.z,
|
|
5288
|
+
width: s.width,
|
|
5289
|
+
height: s.height,
|
|
5290
|
+
y: s.y,
|
|
5291
|
+
corner: s.corner ?? .7
|
|
5292
|
+
};
|
|
5293
|
+
};
|
|
5294
|
+
const rings = [];
|
|
5295
|
+
for (let i = 0; i < sections.length - 1; i++) {
|
|
5296
|
+
const a = get(i - 1);
|
|
5297
|
+
const b = get(i);
|
|
5298
|
+
const c = get(i + 1);
|
|
5299
|
+
const d = get(i + 2);
|
|
5300
|
+
const steps = i === sections.length - 2 ? smooth + 1 : smooth;
|
|
5301
|
+
for (let s = 0; s < steps; s++) {
|
|
5302
|
+
const t = s / smooth;
|
|
5303
|
+
rings.push({
|
|
5304
|
+
z: catmullRom(a.z, b.z, c.z, d.z, t),
|
|
5305
|
+
width: Math.max(1e-4, catmullRom(a.width, b.width, c.width, d.width, t)),
|
|
5306
|
+
height: Math.max(1e-4, catmullRom(a.height, b.height, c.height, d.height, t)),
|
|
5307
|
+
y: catmullRom(a.y, b.y, c.y, d.y, t),
|
|
5308
|
+
corner: Math.min(1, Math.max(0, catmullRom(a.corner, b.corner, c.corner, d.corner, t)))
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
return rings;
|
|
5313
|
+
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Build the swept surface: ring grid + two cap-centre vertices, indexed
|
|
5316
|
+
* without a seam so `computeVertexNormals` shades the hull perfectly smooth.
|
|
5317
|
+
*/
|
|
5318
|
+
function buildLoftGeometry(sections, slices, smooth) {
|
|
5319
|
+
const S = Math.max(8, Math.min(64, Math.round(slices)));
|
|
5320
|
+
const rings = sampleRings(sections, Math.max(1, Math.min(16, Math.round(smooth))));
|
|
5321
|
+
const R = rings.length;
|
|
5322
|
+
const positions = new Float32Array((R * S + 2) * 3);
|
|
5323
|
+
const first = rings[0];
|
|
5324
|
+
const last = rings[R - 1];
|
|
5325
|
+
positions.set([
|
|
5326
|
+
0,
|
|
5327
|
+
first.y,
|
|
5328
|
+
first.z
|
|
5329
|
+
], 0);
|
|
5330
|
+
positions.set([
|
|
5331
|
+
0,
|
|
5332
|
+
last.y,
|
|
5333
|
+
last.z
|
|
5334
|
+
], 3);
|
|
5335
|
+
for (let i = 0; i < R; i++) {
|
|
5336
|
+
const ring = rings[i];
|
|
5337
|
+
const nTop = 8 - 6 * ring.corner;
|
|
5338
|
+
const nBottom = Math.max(nTop, 5);
|
|
5339
|
+
for (let j = 0; j < S; j++) {
|
|
5340
|
+
const theta = j / S * Math.PI * 2;
|
|
5341
|
+
const c = Math.cos(theta);
|
|
5342
|
+
const sn = Math.sin(theta);
|
|
5343
|
+
const n = sn >= 0 ? nTop : nBottom;
|
|
5344
|
+
const x = ring.width / 2 * Math.sign(c) * Math.abs(c) ** (2 / n);
|
|
5345
|
+
const yy = ring.height / 2 * Math.sign(sn) * Math.abs(sn) ** (2 / n);
|
|
5346
|
+
const v = (2 + i * S + j) * 3;
|
|
5347
|
+
positions[v] = x;
|
|
5348
|
+
positions[v + 1] = ring.y + yy;
|
|
5349
|
+
positions[v + 2] = ring.z;
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
const indices = [];
|
|
5353
|
+
const at = (i, j) => 2 + i * S + j % S;
|
|
5354
|
+
for (let i = 0; i < R - 1; i++) for (let j = 0; j < S; j++) {
|
|
5355
|
+
const a = at(i, j);
|
|
5356
|
+
const b = at(i, j + 1);
|
|
5357
|
+
const c = at(i + 1, j);
|
|
5358
|
+
const d = at(i + 1, j + 1);
|
|
5359
|
+
indices.push(a, b, c, b, d, c);
|
|
5360
|
+
}
|
|
5361
|
+
for (let j = 0; j < S; j++) {
|
|
5362
|
+
indices.push(0, at(0, j + 1), at(0, j));
|
|
5363
|
+
indices.push(1, at(R - 1, j), at(R - 1, j + 1));
|
|
5364
|
+
}
|
|
5365
|
+
const geo = new BufferGeometry();
|
|
5366
|
+
geo.setAttribute("position", new BufferAttribute(positions, 3));
|
|
5367
|
+
geo.setIndex(indices);
|
|
5368
|
+
geo.computeVertexNormals();
|
|
5369
|
+
return geo;
|
|
5370
|
+
}
|
|
5371
|
+
//#endregion
|
|
5074
5372
|
//#region src/3d/animation/rigmap.ts
|
|
5075
5373
|
/**
|
|
5076
5374
|
* Mixamo rig name → VRM humanoid bone name (the vibe-starter-3d /
|
|
@@ -5227,7 +5525,9 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5227
5525
|
castShadow: { default: false },
|
|
5228
5526
|
animationLoop: { default: true },
|
|
5229
5527
|
receiveShadow: { default: false },
|
|
5230
|
-
tint: { default: "" }
|
|
5528
|
+
tint: { default: "" },
|
|
5529
|
+
metalness: { default: -1 },
|
|
5530
|
+
roughness: { default: -1 }
|
|
5231
5531
|
};
|
|
5232
5532
|
model = "";
|
|
5233
5533
|
animation = "";
|
|
@@ -5239,6 +5539,12 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5239
5539
|
/** Hex colour multiplied into every material — RESKIN one shared GLB into many
|
|
5240
5540
|
* variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
|
|
5241
5541
|
tint = "";
|
|
5542
|
+
/** Override every material's metalness (0..1); -1 keeps the authored value.
|
|
5543
|
+
* Low-poly kits often ship fully rough — 0.5–0.8 turns flat paint into a
|
|
5544
|
+
* glinting clear-coat under the scene's IBL. Cloned per instance like tint. */
|
|
5545
|
+
metalness = -1;
|
|
5546
|
+
/** Override every material's roughness (0..1); -1 keeps the authored value. */
|
|
5547
|
+
roughness = -1;
|
|
5242
5548
|
store = null;
|
|
5243
5549
|
entry = null;
|
|
5244
5550
|
mountedRef = "";
|
|
@@ -5252,11 +5558,25 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5252
5558
|
* frames after `this.animation` already moved on). */
|
|
5253
5559
|
actionKeys = /* @__PURE__ */ new WeakMap();
|
|
5254
5560
|
warnedClaim = false;
|
|
5255
|
-
/** Per-mesh original (shared) materials, captured at mount so
|
|
5256
|
-
* re-derive from the source. Our clones are tracked for disposal. */
|
|
5561
|
+
/** Per-mesh original (shared) materials, captured at mount so tint/overrides
|
|
5562
|
+
* can always re-derive from the source. Our clones are tracked for disposal. */
|
|
5257
5563
|
tintTargets = [];
|
|
5258
5564
|
tintedClones = [];
|
|
5259
|
-
|
|
5565
|
+
appliedMods = "";
|
|
5566
|
+
/** Named sub-node poses (degrees), re-applied every sync — drive wheels,
|
|
5567
|
+
* turrets, doors from behaviors without touching three directly. */
|
|
5568
|
+
nodePoses = /* @__PURE__ */ new Map();
|
|
5569
|
+
/**
|
|
5570
|
+
* Rotate a named sub-node of the loaded model (local Euler XYZ, degrees) —
|
|
5571
|
+
* e.g. spin a GLB car's `wheel-front-left` every frame, aim a turret, swing a
|
|
5572
|
+
* door. Poses persist and re-apply on every sync (safe to set before the
|
|
5573
|
+
* model finishes loading). Unknown names are ignored. `npx incanto-model
|
|
5574
|
+
* <file>` lists a model's node names.
|
|
5575
|
+
*/
|
|
5576
|
+
poseNode(name, rotation) {
|
|
5577
|
+
this.nodePoses.set(name, rotation);
|
|
5578
|
+
this.applyPoses();
|
|
5579
|
+
}
|
|
5260
5580
|
_createObject3D() {
|
|
5261
5581
|
return new Group();
|
|
5262
5582
|
}
|
|
@@ -5310,7 +5630,7 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5310
5630
|
});
|
|
5311
5631
|
this.tintTargets = [];
|
|
5312
5632
|
this.tintedClones = [];
|
|
5313
|
-
this.
|
|
5633
|
+
this.appliedMods = "";
|
|
5314
5634
|
instance.traverse((obj) => {
|
|
5315
5635
|
const mesh = obj;
|
|
5316
5636
|
if (mesh.isMesh && mesh.material) this.tintTargets.push({
|
|
@@ -5339,34 +5659,51 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5339
5659
|
mesh.receiveShadow = this.receiveShadow;
|
|
5340
5660
|
}
|
|
5341
5661
|
});
|
|
5342
|
-
this.
|
|
5662
|
+
this.applyMaterialMods();
|
|
5663
|
+
this.applyPoses();
|
|
5343
5664
|
this.syncAnimation(assets);
|
|
5344
5665
|
}
|
|
5345
|
-
/**
|
|
5346
|
-
*
|
|
5347
|
-
* mutates the shared source), disposing prior clones on change. */
|
|
5348
|
-
|
|
5349
|
-
|
|
5666
|
+
/** Apply per-instance material mods — `tint` (multiplied into colour) and the
|
|
5667
|
+
* `metalness`/`roughness` overrides — by cloning the captured originals
|
|
5668
|
+
* (never mutates the shared source), disposing prior clones on change. */
|
|
5669
|
+
applyMaterialMods() {
|
|
5670
|
+
const key = `${this.tint}|${this.metalness}|${this.roughness}`;
|
|
5671
|
+
if (key === this.appliedMods) return;
|
|
5350
5672
|
for (const c of this.tintedClones) c.dispose();
|
|
5351
5673
|
this.tintedClones = [];
|
|
5352
5674
|
const tint = this.tint ? new Color(this.tint) : null;
|
|
5675
|
+
const hasMods = tint !== null || this.metalness >= 0 || this.roughness >= 0;
|
|
5353
5676
|
for (const t of this.tintTargets) {
|
|
5354
|
-
if (!
|
|
5677
|
+
if (!hasMods) {
|
|
5355
5678
|
t.mesh.material = t.original;
|
|
5356
5679
|
continue;
|
|
5357
5680
|
}
|
|
5358
|
-
const
|
|
5681
|
+
const modOne = (src) => {
|
|
5359
5682
|
const clone = src.clone();
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5683
|
+
if (tint) {
|
|
5684
|
+
const cm = clone;
|
|
5685
|
+
const sc = src.color;
|
|
5686
|
+
if (cm.color) cm.color = sc ? sc.clone().multiply(tint) : tint.clone();
|
|
5687
|
+
}
|
|
5688
|
+
const pbr = clone;
|
|
5689
|
+
if (this.metalness >= 0 && typeof pbr.metalness === "number") pbr.metalness = this.metalness;
|
|
5690
|
+
if (this.roughness >= 0 && typeof pbr.roughness === "number") pbr.roughness = this.roughness;
|
|
5363
5691
|
clone.userData.incantoTinted = true;
|
|
5364
5692
|
this.tintedClones.push(clone);
|
|
5365
5693
|
return clone;
|
|
5366
5694
|
};
|
|
5367
|
-
t.mesh.material = Array.isArray(t.original) ? t.original.map(
|
|
5695
|
+
t.mesh.material = Array.isArray(t.original) ? t.original.map(modOne) : modOne(t.original);
|
|
5696
|
+
}
|
|
5697
|
+
this.appliedMods = key;
|
|
5698
|
+
}
|
|
5699
|
+
/** Re-apply every queued named-node pose onto the mounted instance. */
|
|
5700
|
+
applyPoses() {
|
|
5701
|
+
if (!this.fitGroup || this.nodePoses.size === 0) return;
|
|
5702
|
+
const DEG = Math.PI / 180;
|
|
5703
|
+
for (const [name, rot] of this.nodePoses) {
|
|
5704
|
+
const obj = this.fitGroup.getObjectByName(name);
|
|
5705
|
+
if (obj) obj.rotation.set(rot[0] * DEG, rot[1] * DEG, rot[2] * DEG);
|
|
5368
5706
|
}
|
|
5369
|
-
this.appliedTint = this.tint;
|
|
5370
5707
|
}
|
|
5371
5708
|
syncAnimation(assets) {
|
|
5372
5709
|
if (!this.mixer || !this.entry || this.playing === this.animation) return;
|
|
@@ -5426,7 +5763,7 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5426
5763
|
for (const c of this.tintedClones) c.dispose();
|
|
5427
5764
|
this.tintedClones = [];
|
|
5428
5765
|
this.tintTargets = [];
|
|
5429
|
-
this.
|
|
5766
|
+
this.appliedMods = "";
|
|
5430
5767
|
this.fitGroup = null;
|
|
5431
5768
|
this.mixer = null;
|
|
5432
5769
|
this.currentAction = null;
|
|
@@ -5573,7 +5910,7 @@ var Particles3D = class Particles3D extends Node3D {
|
|
|
5573
5910
|
gravity: [
|
|
5574
5911
|
(this.gravity[0] ?? 0) * PX_TO_M,
|
|
5575
5912
|
(this.gravity[1] ?? 0) * PX_TO_M,
|
|
5576
|
-
0
|
|
5913
|
+
(this.gravity[2] ?? 0) * PX_TO_M
|
|
5577
5914
|
],
|
|
5578
5915
|
drag: this.drag,
|
|
5579
5916
|
maxParticles: this.maxParticles,
|
|
@@ -5584,6 +5921,7 @@ var Particles3D = class Particles3D extends Node3D {
|
|
|
5584
5921
|
}
|
|
5585
5922
|
update(dt) {
|
|
5586
5923
|
const sim = this._ensureSim();
|
|
5924
|
+
sim.setRate(this.emitting ? this.rate : 0);
|
|
5587
5925
|
if (this.burst > 0 && !this.bursted) {
|
|
5588
5926
|
this.bursted = true;
|
|
5589
5927
|
sim.burst(this.burst);
|
|
@@ -11095,11 +11433,13 @@ function registerNodes3D() {
|
|
|
11095
11433
|
registerNode(Node3D);
|
|
11096
11434
|
registerNode(Particles3D);
|
|
11097
11435
|
registerNode(MeshInstance3D);
|
|
11436
|
+
registerNode(LoftMesh3D);
|
|
11098
11437
|
registerNode(Sprite3D);
|
|
11099
11438
|
registerNode(AnimatedSprite3D);
|
|
11100
11439
|
registerNode(Billboard3D);
|
|
11101
11440
|
registerNode(CharacterController3D);
|
|
11102
11441
|
registerNode(VoxelGrid3D);
|
|
11442
|
+
registerNode(Joint3D);
|
|
11103
11443
|
registerNode(Terrain3D);
|
|
11104
11444
|
registerNode(Water3D);
|
|
11105
11445
|
registerNode(Foliage3D);
|
|
@@ -11115,4 +11455,4 @@ function registerNodes3D() {
|
|
|
11115
11455
|
registerNode(CharacterBody3D);
|
|
11116
11456
|
}
|
|
11117
11457
|
//#endregion
|
|
11118
|
-
export {
|
|
11458
|
+
export { StaticBody3D as A, TERRAIN_THEMES as C, CharacterBody3D as D, Area3D as E, keyboardIntensity as F, movementState as I, rigPose as L, validateCollider3D as M, QUARTER_PITCH as N, PhysicsBody3D as O, cameraRelative as P, DEFAULT_TERRAIN_TEXTURE_BASE as S, Joint3D as T, FLOWER_VARIETIES as _, VoxelGrid3D as a, Billboard3D as b, ModelInstance3D as c, DirectionalLight3D as d, OmniLight3D as f, resolveFlowerDensity as g, Flowers3D as h, VOXEL_PALETTE as i, Node3D as j, RigidBody3D as k, LoftMesh3D as l, DENSITY_PRESETS as m, Water3D as n, Tree3D as o, Foliage3D as p, WATER_MAX_RIPPLES as r, Particles3D as s, registerNodes3D as t, MeshInstance3D as u, CharacterController3D as v, terrainThemeLayers as w, Terrain3D as x, Camera3D as y };
|