incanto 0.12.0 → 0.13.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 +22 -0
- package/bin/incanto-new.mjs +87 -0
- package/dist/2d.d.ts +2 -2
- package/dist/2d.js +3 -3
- package/dist/3d.d.ts +194 -71
- package/dist/3d.js +74 -4
- package/dist/{behavior-Bod1AyJS.d.ts → behavior-CPibUfnH.d.ts} +4 -2
- package/dist/{create-game-C_xf4gm9.js → create-game-Ct86zczq.js} +5 -5
- package/dist/{create-game-B2fewJUy.js → create-game-J6g4QInv.js} +5 -5
- package/dist/debug.d.ts +1 -1
- package/dist/{duplicate-BEvGBtb_.js → duplicate-BINwP0WG.js} +1 -1
- package/dist/{gameplay-Dn3yidJw.js → gameplay-sSqrUcnz.js} +75 -3
- package/dist/gameplay.d.ts +37 -2
- package/dist/gameplay.js +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -4
- package/dist/{loader-B4OEXDZ8.js → loader-BZqOKfI2.js} +3 -1
- package/dist/{loader-BcUDfNHn.d.ts → loader-DILt9PGC.d.ts} +1 -1
- package/dist/net.d.ts +1 -1
- package/dist/net.js +3 -3
- package/dist/{pathfinding-BSiXjd6W.d.ts → pathfinding-RWYkNKx9.d.ts} +1 -1
- package/dist/{physics-2d-B6D864oZ.js → physics-2d-BiIdl51r.js} +2 -2
- package/dist/{physics-3d-RLNaxRyw.js → physics-3d-DhaAaKJo.js} +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{register-Cwc-yCYx.js → register-BFFE1Mh1.js} +2 -2
- package/dist/{register-ysLR3WQS.js → register-BFw4c83z.js} +508 -123
- package/dist/{register-BD6zgrv_.js → register-Djwckzgx.js} +2 -2
- package/dist/{register-DJZXxwAn.js → register-nObreUQR.js} +1 -1
- package/dist/test.d.ts +2 -2
- package/dist/test.js +10 -10
- package/editor/assets/{agent8-D_4QY8Aq.js → agent8-BF8Jfbkl.js} +1 -1
- package/editor/assets/{index-s0DZuroe.js → index-DSq0gdhE.js} +74 -74
- package/editor/index.html +1 -1
- package/package.json +4 -2
- package/schemas/scene.schema.json +263 -0
- package/skills/incanto-3d-character.md +35 -0
- package/skills/incanto-building-3d-games.md +48 -0
- package/skills/incanto-gameplay-behaviors.md +23 -1
- package/skills/incanto-node-reference.md +51 -0
- package/templates-app/beacon-isle-3d/PROJECT/Context.md +44 -0
- package/templates-app/beacon-isle-3d/PROJECT/Requirements.md +16 -0
- package/templates-app/beacon-isle-3d/PROJECT/Status.md +38 -0
- package/templates-app/beacon-isle-3d/PROJECT/Structure.md +24 -0
- package/templates-app/beacon-isle-3d/docs/project-3d-rules.md +38 -0
- package/templates-app/beacon-isle-3d/generate-world.ts +814 -0
- package/templates-app/beacon-isle-3d/index.html +68 -0
- package/templates-app/beacon-isle-3d/package.json +26 -0
- package/templates-app/beacon-isle-3d/src/behaviors.ts +570 -0
- package/templates-app/beacon-isle-3d/src/game.scene.json +2992 -0
- package/templates-app/beacon-isle-3d/src/main.ts +33 -0
- package/templates-app/beacon-isle-3d/tsconfig.json +13 -0
- package/templates-app/beacon-isle-3d/verify.ts +285 -0
- package/templates-app/beacon-isle-3d/vite.config.ts +5 -0
- package/templates-app/tps-3d/PROJECT/Context.md +49 -0
- package/templates-app/tps-3d/PROJECT/Requirements.md +38 -0
- package/templates-app/tps-3d/PROJECT/Status.md +41 -0
- package/templates-app/tps-3d/PROJECT/Structure.md +44 -0
- package/templates-app/tps-3d/docs/project-3d-rules.md +38 -0
- package/templates-app/tps-3d/index.html +244 -0
- package/templates-app/tps-3d/package.json +25 -0
- package/templates-app/tps-3d/src/behaviors.ts +405 -0
- package/templates-app/tps-3d/src/game.scene.json +677 -0
- package/templates-app/tps-3d/src/main.ts +64 -0
- package/templates-app/tps-3d/tsconfig.json +13 -0
- package/templates-app/tps-3d/verify.ts +167 -0
- package/templates-app/tps-3d/vite.config.ts +5 -0
- package/templates-app/village-quest-3d/PROJECT/Context.md +39 -0
- package/templates-app/village-quest-3d/PROJECT/Requirements.md +36 -0
- package/templates-app/village-quest-3d/PROJECT/Status.md +26 -0
- package/templates-app/village-quest-3d/PROJECT/Structure.md +30 -0
- package/templates-app/village-quest-3d/docs/project-3d-rules.md +38 -0
- package/templates-app/village-quest-3d/index.html +68 -0
- package/templates-app/village-quest-3d/package.json +25 -0
- package/templates-app/village-quest-3d/src/behaviors.ts +529 -0
- package/templates-app/village-quest-3d/src/grove.scene.json +826 -0
- package/templates-app/village-quest-3d/src/main.ts +35 -0
- package/templates-app/village-quest-3d/src/quest.ts +18 -0
- package/templates-app/village-quest-3d/src/village.scene.json +680 -0
- package/templates-app/village-quest-3d/tsconfig.json +13 -0
- package/templates-app/village-quest-3d/verify.ts +254 -0
- package/templates-app/village-quest-3d/vite.config.ts +5 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { f as Node } from "./loader-
|
|
2
|
-
import { t as registerCoreNodes } from "./register-
|
|
1
|
+
import { f as Node } from "./loader-BZqOKfI2.js";
|
|
2
|
+
import { t as registerCoreNodes } from "./register-nObreUQR.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 { i as getNodeSchema, l as registerNode } from "./registry-BVJ2HbCn.js";
|
|
6
6
|
import { t as createNoise2D } from "./noise-CGUMx44x.js";
|
|
7
7
|
import { i as applyParticlePreset, o as effectiveOrder, r as PARTICLE_PRESET_NAMES, t as ParticleSim } from "./particle-sim-CyUU7HVU.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, 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";
|
|
9
|
+
import { AdditiveBlending, AnimationClip, AnimationMixer, Box3, BoxGeometry, BufferAttribute, BufferGeometry, CanvasTexture, CapsuleGeometry, Color, ConeGeometry, CubeCamera, CylinderGeometry, DataTexture, DepthTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, 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);
|
|
@@ -1950,8 +1950,8 @@ var BoneAttachment3D = class extends Node3D {
|
|
|
1950
1950
|
boneObj.updateWorldMatrix(true, false);
|
|
1951
1951
|
parent.updateWorldMatrix(true, false);
|
|
1952
1952
|
obj.matrixAutoUpdate = false;
|
|
1953
|
-
boneObj.matrixWorld.decompose(bonePosScratch, boneQuatScratch, boneScaleScratch);
|
|
1954
|
-
boneScratch.compose(bonePosScratch, boneQuatScratch, unitScale);
|
|
1953
|
+
boneObj.matrixWorld.decompose(bonePosScratch$1, boneQuatScratch, boneScaleScratch);
|
|
1954
|
+
boneScratch.compose(bonePosScratch$1, boneQuatScratch, unitScale);
|
|
1955
1955
|
obj.updateMatrix();
|
|
1956
1956
|
offsetScratch.copy(obj.matrix);
|
|
1957
1957
|
obj.matrix.copy(parent.matrixWorld).invert().multiply(boneScratch).multiply(offsetScratch);
|
|
@@ -1963,11 +1963,125 @@ var BoneAttachment3D = class extends Node3D {
|
|
|
1963
1963
|
};
|
|
1964
1964
|
const offsetScratch = new Matrix4();
|
|
1965
1965
|
const boneScratch = new Matrix4();
|
|
1966
|
-
const bonePosScratch = new Vector3();
|
|
1966
|
+
const bonePosScratch$1 = new Vector3();
|
|
1967
1967
|
const boneQuatScratch = new Quaternion();
|
|
1968
1968
|
const boneScaleScratch = new Vector3();
|
|
1969
1969
|
const unitScale = new Vector3(1, 1, 1);
|
|
1970
1970
|
//#endregion
|
|
1971
|
+
//#region src/3d/nodes/bone-look-at-3d.ts
|
|
1972
|
+
/**
|
|
1973
|
+
* Look-at IK for one bone — the character's head (or chest, or turret) turns
|
|
1974
|
+
* toward a target node ON TOP of whatever the animation is doing. Runs after
|
|
1975
|
+
* the mixer each rendered frame, blends in/out smoothly, and DISENGAGES when
|
|
1976
|
+
* the target leaves the comfort cone (no owl necks).
|
|
1977
|
+
*
|
|
1978
|
+
* { "name": "HeadTrack", "type": "BoneLookAt3D",
|
|
1979
|
+
* "props": { "target": "../Skin", "bone": "Head", "lookAt": "%Player" } }
|
|
1980
|
+
*
|
|
1981
|
+
* `forwardAxis` names the bone's local facing axis (mixamo heads face +z,
|
|
1982
|
+
* the default). Purely visual — headless it's a no-op.
|
|
1983
|
+
*/
|
|
1984
|
+
var BoneLookAt3D = class extends Node3D {
|
|
1985
|
+
static typeName = "BoneLookAt3D";
|
|
1986
|
+
static props = {
|
|
1987
|
+
/** Node path to the ModelInstance3D whose bone to steer. */
|
|
1988
|
+
target: { default: "" },
|
|
1989
|
+
/** Bone name ("Head", "Neck", "Spine2" — Mixamo spellings matched). */
|
|
1990
|
+
bone: { default: "Head" },
|
|
1991
|
+
/** Node path to LOOK AT ('' = idle). */
|
|
1992
|
+
lookAt: { default: "" },
|
|
1993
|
+
/** Comfort cone half-angle, degrees — beyond it the head returns to the anim. */
|
|
1994
|
+
maxAngleDeg: { default: 75 },
|
|
1995
|
+
/** Blend weight 0..1 (how far toward the target the bone turns). */
|
|
1996
|
+
weight: { default: .85 },
|
|
1997
|
+
/** The bone's local facing axis. */
|
|
1998
|
+
forwardAxis: {
|
|
1999
|
+
default: "+z",
|
|
2000
|
+
options: [
|
|
2001
|
+
"+x",
|
|
2002
|
+
"-x",
|
|
2003
|
+
"+y",
|
|
2004
|
+
"-y",
|
|
2005
|
+
"+z",
|
|
2006
|
+
"-z"
|
|
2007
|
+
]
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
target = "";
|
|
2011
|
+
bone = "Head";
|
|
2012
|
+
lookAt = "";
|
|
2013
|
+
maxAngleDeg = 75;
|
|
2014
|
+
weight = .85;
|
|
2015
|
+
forwardAxis = "+z";
|
|
2016
|
+
boneObj = null;
|
|
2017
|
+
lookedUpFor = "";
|
|
2018
|
+
/** 0..1 engage blend — eases in/out so tracking never snaps. */
|
|
2019
|
+
engage = 0;
|
|
2020
|
+
/** Test hook. */
|
|
2021
|
+
get attachedBone() {
|
|
2022
|
+
return this.boneObj;
|
|
2023
|
+
}
|
|
2024
|
+
_onRender3D(_ctx) {
|
|
2025
|
+
if (this.target === "" || this.bone === "") return;
|
|
2026
|
+
const key = `${this.target} ${this.bone}`;
|
|
2027
|
+
if (this.lookedUpFor !== key || this.boneObj === null || !this.boneObj.parent) {
|
|
2028
|
+
this.lookedUpFor = key;
|
|
2029
|
+
this.boneObj = null;
|
|
2030
|
+
const source = this.getNodeOrNull(this.target);
|
|
2031
|
+
if (source && typeof source.findBone === "function") this.boneObj = source.findBone(this.bone);
|
|
2032
|
+
}
|
|
2033
|
+
const bone = this.boneObj;
|
|
2034
|
+
if (!bone?.parent) return;
|
|
2035
|
+
const targetNode = this.lookAt === "" ? null : this.getNodeOrNull(this.lookAt);
|
|
2036
|
+
let engaged = false;
|
|
2037
|
+
if (targetNode && Array.isArray(targetNode.position)) {
|
|
2038
|
+
bone.updateWorldMatrix(true, false);
|
|
2039
|
+
bone.getWorldPosition(bonePosScratch);
|
|
2040
|
+
targetScratch.set(targetNode.position[0] ?? 0, targetNode.position[1] ?? 0, targetNode.position[2] ?? 0);
|
|
2041
|
+
dirScratch$1.copy(targetScratch).sub(bonePosScratch);
|
|
2042
|
+
if (dirScratch$1.lengthSq() > 1e-6) {
|
|
2043
|
+
dirScratch$1.normalize();
|
|
2044
|
+
axisScratch.copy(AXES[this.forwardAxis] ?? PLUS_Z);
|
|
2045
|
+
animForwardScratch.copy(axisScratch).applyQuaternion(bone.getWorldQuaternion(quatScratch$2));
|
|
2046
|
+
if (animForwardScratch.angleTo(dirScratch$1) < this.maxAngleDeg * Math.PI / 180) {
|
|
2047
|
+
engaged = true;
|
|
2048
|
+
lookMatrixScratch.lookAt(bonePosScratch, targetScratch, UP$1);
|
|
2049
|
+
desiredScratch.setFromRotationMatrix(lookMatrixScratch);
|
|
2050
|
+
correctionScratch.setFromUnitVectors(axisScratch, MINUS_Z);
|
|
2051
|
+
desiredScratch.multiply(correctionScratch);
|
|
2052
|
+
bone.parent.getWorldQuaternion(parentScratch).invert();
|
|
2053
|
+
desiredScratch.premultiply(parentScratch);
|
|
2054
|
+
const k = this.weight * this.engage;
|
|
2055
|
+
bone.quaternion.slerp(desiredScratch, k);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
this.engage += ((engaged ? 1 : 0) - this.engage) * ENGAGE_RATE;
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
const ENGAGE_RATE = .18;
|
|
2063
|
+
const AXES = {
|
|
2064
|
+
"+x": new Vector3(1, 0, 0),
|
|
2065
|
+
"-x": new Vector3(-1, 0, 0),
|
|
2066
|
+
"+y": new Vector3(0, 1, 0),
|
|
2067
|
+
"-y": new Vector3(0, -1, 0),
|
|
2068
|
+
"+z": new Vector3(0, 0, 1),
|
|
2069
|
+
"-z": new Vector3(0, 0, -1)
|
|
2070
|
+
};
|
|
2071
|
+
const UP$1 = new Vector3(0, 1, 0);
|
|
2072
|
+
const PLUS_Z = new Vector3(0, 0, 1);
|
|
2073
|
+
const MINUS_Z = new Vector3(0, 0, -1);
|
|
2074
|
+
const bonePosScratch = new Vector3();
|
|
2075
|
+
const targetScratch = new Vector3();
|
|
2076
|
+
const dirScratch$1 = new Vector3();
|
|
2077
|
+
const axisScratch = new Vector3();
|
|
2078
|
+
const animForwardScratch = new Vector3();
|
|
2079
|
+
const lookMatrixScratch = new Matrix4();
|
|
2080
|
+
const desiredScratch = new Quaternion();
|
|
2081
|
+
const correctionScratch = new Quaternion();
|
|
2082
|
+
const parentScratch = new Quaternion();
|
|
2083
|
+
const quatScratch$2 = new Quaternion();
|
|
2084
|
+
//#endregion
|
|
1971
2085
|
//#region src/3d/nodes/camera-3d.ts
|
|
1972
2086
|
/**
|
|
1973
2087
|
* A perspective camera. Mark exactly one camera `current: true`; with none
|
|
@@ -2022,7 +2136,7 @@ const CAM_GROUND_CLEARANCE = .3;
|
|
|
2022
2136
|
* near-plane band around the boom (the Emberwood hedge-maze lesson). */
|
|
2023
2137
|
const CAM_PROBE_RADIUS = .25;
|
|
2024
2138
|
const eulerScratch = new Euler();
|
|
2025
|
-
const quatScratch = new Quaternion();
|
|
2139
|
+
const quatScratch$1 = new Quaternion();
|
|
2026
2140
|
const matrixScratch$3 = new Matrix4();
|
|
2027
2141
|
const camVecScratch = new Vector3();
|
|
2028
2142
|
const eyeVecScratch = new Vector3();
|
|
@@ -2309,12 +2423,12 @@ var CharacterController3D = class extends Node3D {
|
|
|
2309
2423
|
camVecScratch.set(camera.position[0] ?? 0, camera.position[1] ?? 0, camera.position[2] ?? 0);
|
|
2310
2424
|
eyeVecScratch.set(eye[0], eye[1], eye[2]);
|
|
2311
2425
|
matrixScratch$3.lookAt(camVecScratch, eyeVecScratch, UP_VEC);
|
|
2312
|
-
quatScratch.setFromRotationMatrix(matrixScratch$3);
|
|
2313
|
-
eulerScratch.setFromQuaternion(quatScratch, "XYZ");
|
|
2426
|
+
quatScratch$1.setFromRotationMatrix(matrixScratch$3);
|
|
2427
|
+
eulerScratch.setFromQuaternion(quatScratch$1, "XYZ");
|
|
2314
2428
|
} else {
|
|
2315
2429
|
eulerScratch.set(pose.rotation[0], pose.rotation[1], pose.rotation[2], "YXZ");
|
|
2316
|
-
quatScratch.setFromEuler(eulerScratch);
|
|
2317
|
-
eulerScratch.setFromQuaternion(quatScratch, "XYZ");
|
|
2430
|
+
quatScratch$1.setFromEuler(eulerScratch);
|
|
2431
|
+
eulerScratch.setFromQuaternion(quatScratch$1, "XYZ");
|
|
2318
2432
|
}
|
|
2319
2433
|
camera.rotation = [
|
|
2320
2434
|
eulerScratch.x * RAD2DEG,
|
|
@@ -3110,8 +3224,8 @@ var Flowers3D = class extends Node3D {
|
|
|
3110
3224
|
const p = plants[i];
|
|
3111
3225
|
positionScratch$2.set(p.x, this.bedY(p.x, p.z), p.z);
|
|
3112
3226
|
quaternionScratch$2.setFromAxisAngle(Y_AXIS$2, p.rotY);
|
|
3113
|
-
scaleScratch$
|
|
3114
|
-
matrixScratch$2.compose(positionScratch$2, quaternionScratch$2, scaleScratch$
|
|
3227
|
+
scaleScratch$3.setScalar(this.height * p.scale);
|
|
3228
|
+
matrixScratch$2.compose(positionScratch$2, quaternionScratch$2, scaleScratch$3);
|
|
3115
3229
|
structureMesh.setMatrixAt(i, matrixScratch$2);
|
|
3116
3230
|
petalMesh.setMatrixAt(i, matrixScratch$2);
|
|
3117
3231
|
const pick = Math.min(Math.floor(p.color01 * palette.length), palette.length - 1);
|
|
@@ -3183,7 +3297,7 @@ const Y_AXIS$2 = new Vector3(0, 1, 0);
|
|
|
3183
3297
|
const matrixScratch$2 = new Matrix4();
|
|
3184
3298
|
const positionScratch$2 = new Vector3();
|
|
3185
3299
|
const quaternionScratch$2 = new Quaternion();
|
|
3186
|
-
const scaleScratch$
|
|
3300
|
+
const scaleScratch$3 = new Vector3();
|
|
3187
3301
|
const colorScratch$1 = new Color();
|
|
3188
3302
|
/**
|
|
3189
3303
|
* Nearest-k benders whose influence circle touches the field rectangle,
|
|
@@ -4672,8 +4786,8 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
4672
4786
|
const bladeHeight = this.height * regional * (.55 + p.scale * .6);
|
|
4673
4787
|
positionScratch$1.set(p.x, this.bedY(p.x, p.z), p.z);
|
|
4674
4788
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, p.rotY);
|
|
4675
|
-
scaleScratch$
|
|
4676
|
-
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4789
|
+
scaleScratch$2.set(widthJitter, bladeHeight, widthJitter);
|
|
4790
|
+
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2));
|
|
4677
4791
|
const leanDir = clump.leanDir + rng.range(-.6, .6);
|
|
4678
4792
|
const lean = (MESH_LEAN_BASE + MESH_LEAN_RANGE * (.5 * clump.lean + .5 * rng.next())) * (1 + MESH_TALL_LEAN_BOOST * tall);
|
|
4679
4793
|
blade[i * 4] = Math.cos(leanDir) * lean;
|
|
@@ -4711,8 +4825,8 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
4711
4825
|
const stem = this.height * regional * (.85 + rng.next() * .35);
|
|
4712
4826
|
positionScratch$1.set(p.x, this.bedY(p.x, p.z), p.z);
|
|
4713
4827
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, p.rotY);
|
|
4714
|
-
scaleScratch$
|
|
4715
|
-
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4828
|
+
scaleScratch$2.set(1, stem, 1);
|
|
4829
|
+
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2));
|
|
4716
4830
|
const pick = rng.next();
|
|
4717
4831
|
const color = FLOWER_COLORS[pick < .45 ? 0 : pick < .75 ? 1 : 2];
|
|
4718
4832
|
mesh.setColorAt(i, colorScratchC$1.set(color));
|
|
@@ -4764,8 +4878,8 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
4764
4878
|
const cardW = cardH * TUFT_ASPECT * rng.range(TUFT_WIDTH_JITTER[0], TUFT_WIDTH_JITTER[1]);
|
|
4765
4879
|
positionScratch$1.set(p.x, this.bedY(p.x, p.z), p.z);
|
|
4766
4880
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, p.rotY);
|
|
4767
|
-
scaleScratch$
|
|
4768
|
-
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4881
|
+
scaleScratch$2.set(cardW, cardH, cardW);
|
|
4882
|
+
mesh.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2));
|
|
4769
4883
|
const clump = clumpAt(p.x, p.z);
|
|
4770
4884
|
const t = Math.min(1, Math.max(0, .5 + .35 * clump.hue + (rng.next() - .5) * .3));
|
|
4771
4885
|
const dry = Math.min(1, Math.max(0, dryFieldAt(p.x, p.z, this.seed) * .8 - .1));
|
|
@@ -4822,11 +4936,11 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
4822
4936
|
for (let i = 0; i < count; i++) {
|
|
4823
4937
|
const p = placements[i];
|
|
4824
4938
|
positionScratch$1.set(p.x, this.bedY(p.x, p.z), p.z);
|
|
4825
|
-
scaleScratch$
|
|
4939
|
+
scaleScratch$2.setScalar(p.scale);
|
|
4826
4940
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, p.rotY);
|
|
4827
|
-
first.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4941
|
+
first.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2));
|
|
4828
4942
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, p.rotY + Math.PI / 2);
|
|
4829
|
-
second.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4943
|
+
second.setMatrixAt(i, matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2));
|
|
4830
4944
|
}
|
|
4831
4945
|
first.instanceMatrix.needsUpdate = true;
|
|
4832
4946
|
second.instanceMatrix.needsUpdate = true;
|
|
@@ -4851,8 +4965,8 @@ var Foliage3D = class Foliage3D extends Node3D {
|
|
|
4851
4965
|
const sz = rng.range(-halfZ, halfZ);
|
|
4852
4966
|
positionScratch$1.set(sx, this.bedY(sx, sz), sz);
|
|
4853
4967
|
quaternionScratch$1.setFromAxisAngle(Y_AXIS$1, rng.range(0, Math.PI * 2));
|
|
4854
|
-
scaleScratch$
|
|
4855
|
-
matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$
|
|
4968
|
+
scaleScratch$2.set(1, this.height * rng.range(HEIGHT_JITTER$1[0], HEIGHT_JITTER$1[1]), 1);
|
|
4969
|
+
matrixScratch$1.compose(positionScratch$1, quaternionScratch$1, scaleScratch$2);
|
|
4856
4970
|
mesh.setMatrixAt(i, matrixScratch$1);
|
|
4857
4971
|
mesh.setColorAt(i, colorScratchC$1.copy(colorA).lerp(colorB, rng.next()));
|
|
4858
4972
|
}
|
|
@@ -5001,88 +5115,12 @@ const Y_AXIS$1 = new Vector3(0, 1, 0);
|
|
|
5001
5115
|
const matrixScratch$1 = new Matrix4();
|
|
5002
5116
|
const positionScratch$1 = new Vector3();
|
|
5003
5117
|
const quaternionScratch$1 = new Quaternion();
|
|
5004
|
-
const scaleScratch$
|
|
5118
|
+
const scaleScratch$2 = new Vector3();
|
|
5005
5119
|
const colorScratchA$1 = new Color();
|
|
5006
5120
|
const colorScratchB$1 = new Color();
|
|
5007
5121
|
const colorScratchC$1 = new Color();
|
|
5008
5122
|
const tuftDryScratch = new Color();
|
|
5009
5123
|
//#endregion
|
|
5010
|
-
//#region src/3d/nodes/lights-3d.ts
|
|
5011
|
-
/** Sun-style light. Direction comes from the node's rotation. */
|
|
5012
|
-
var DirectionalLight3D = class extends Node3D {
|
|
5013
|
-
static typeName = "DirectionalLight3D";
|
|
5014
|
-
static props = {
|
|
5015
|
-
color: { default: "#ffffff" },
|
|
5016
|
-
intensity: { default: 1 },
|
|
5017
|
-
castShadow: { default: false },
|
|
5018
|
-
shadowArea: { default: 75 },
|
|
5019
|
-
shadowMapSize: { default: 2048 },
|
|
5020
|
-
shadowFollowsCamera: { default: false }
|
|
5021
|
-
};
|
|
5022
|
-
color = "#ffffff";
|
|
5023
|
-
intensity = 1;
|
|
5024
|
-
castShadow = false;
|
|
5025
|
-
/** Half-extent of the directional shadow frustum (template default ±75). */
|
|
5026
|
-
shadowArea = 75;
|
|
5027
|
-
shadowMapSize = 2048;
|
|
5028
|
-
/**
|
|
5029
|
-
* Re-center the shadow frustum on the camera every frame. A directional sun's
|
|
5030
|
-
* shadow map covers a fixed ortho box; in a big roaming world that box must be
|
|
5031
|
-
* huge (and so low-resolution that the player's own shadow nearly vanishes).
|
|
5032
|
-
* With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
|
|
5033
|
-
* crisp wherever you go. The renderer supplies the camera position.
|
|
5034
|
-
*/
|
|
5035
|
-
shadowFollowsCamera = false;
|
|
5036
|
-
_createObject3D() {
|
|
5037
|
-
return new DirectionalLight();
|
|
5038
|
-
}
|
|
5039
|
-
_syncObject3D() {
|
|
5040
|
-
super._syncObject3D();
|
|
5041
|
-
const light = this._ensureObject3D();
|
|
5042
|
-
light.color.set(this.color);
|
|
5043
|
-
light.intensity = this.intensity;
|
|
5044
|
-
light.castShadow = this.castShadow;
|
|
5045
|
-
if (this.castShadow && "shadow" in light) {
|
|
5046
|
-
const cam = light.shadow.camera;
|
|
5047
|
-
if ("left" in cam) {
|
|
5048
|
-
cam.left = -this.shadowArea;
|
|
5049
|
-
cam.right = this.shadowArea;
|
|
5050
|
-
cam.top = this.shadowArea;
|
|
5051
|
-
cam.bottom = -this.shadowArea;
|
|
5052
|
-
cam.near = .5;
|
|
5053
|
-
cam.far = 500;
|
|
5054
|
-
cam.updateProjectionMatrix();
|
|
5055
|
-
}
|
|
5056
|
-
light.shadow.mapSize.set(this.shadowMapSize, this.shadowMapSize);
|
|
5057
|
-
light.shadow.bias = -2e-4;
|
|
5058
|
-
light.shadow.normalBias = .002;
|
|
5059
|
-
}
|
|
5060
|
-
}
|
|
5061
|
-
};
|
|
5062
|
-
/** Point light with a range (Godot's OmniLight3D). */
|
|
5063
|
-
var OmniLight3D = class extends Node3D {
|
|
5064
|
-
static typeName = "OmniLight3D";
|
|
5065
|
-
static props = {
|
|
5066
|
-
color: { default: "#ffffff" },
|
|
5067
|
-
intensity: { default: 1 },
|
|
5068
|
-
range: { default: 0 }
|
|
5069
|
-
};
|
|
5070
|
-
color = "#ffffff";
|
|
5071
|
-
intensity = 1;
|
|
5072
|
-
/** 0 = unlimited (three `distance` semantics). */
|
|
5073
|
-
range = 0;
|
|
5074
|
-
_createObject3D() {
|
|
5075
|
-
return new PointLight();
|
|
5076
|
-
}
|
|
5077
|
-
_syncObject3D() {
|
|
5078
|
-
super._syncObject3D();
|
|
5079
|
-
const light = this._ensureObject3D();
|
|
5080
|
-
light.color.set(this.color);
|
|
5081
|
-
light.intensity = this.intensity;
|
|
5082
|
-
light.distance = this.range;
|
|
5083
|
-
}
|
|
5084
|
-
};
|
|
5085
|
-
//#endregion
|
|
5086
5124
|
//#region src/3d/nodes/mesh-instance-3d.ts
|
|
5087
5125
|
/** Every key `material` accepts — validation lists these on a typo. */
|
|
5088
5126
|
const MATERIAL_KEYS = [
|
|
@@ -5165,7 +5203,7 @@ var MeshInstance3D = class extends Node3D {
|
|
|
5165
5203
|
const key = `${this.mesh}:${JSON.stringify(this.size)}`;
|
|
5166
5204
|
if (key !== this.geometryKey) {
|
|
5167
5205
|
m.geometry.dispose();
|
|
5168
|
-
m.geometry =
|
|
5206
|
+
m.geometry = buildMeshGeometry(this.mesh, this.size);
|
|
5169
5207
|
this.geometryKey = key;
|
|
5170
5208
|
}
|
|
5171
5209
|
const mat = m.material;
|
|
@@ -5284,7 +5322,8 @@ function validateMeshMaterial(material, name, typeName = "MeshInstance3D") {
|
|
|
5284
5322
|
if (value !== void 0 && typeof value !== "boolean") fail(`material.${key} must be a boolean, got ${JSON.stringify(value)}.`);
|
|
5285
5323
|
}
|
|
5286
5324
|
}
|
|
5287
|
-
|
|
5325
|
+
/** Geometry factory shared by MeshInstance3D and InstancedMesh3D. */
|
|
5326
|
+
function buildMeshGeometry(kind, size) {
|
|
5288
5327
|
const x = size[0] ?? 1;
|
|
5289
5328
|
const y = size[1] ?? 1;
|
|
5290
5329
|
const z = size[2] ?? 1;
|
|
@@ -5303,6 +5342,206 @@ function buildGeometry(kind, size) {
|
|
|
5303
5342
|
}
|
|
5304
5343
|
}
|
|
5305
5344
|
//#endregion
|
|
5345
|
+
//#region src/3d/nodes/instanced-mesh-3d.ts
|
|
5346
|
+
/**
|
|
5347
|
+
* Hundreds of copies of one mesh in ONE draw call — scattered rocks, fence
|
|
5348
|
+
* posts, pillars, crates, gravestones. Same `mesh`/`size`/`material` surface
|
|
5349
|
+
* as MeshInstance3D; `transforms` places the copies:
|
|
5350
|
+
*
|
|
5351
|
+
* { "name": "Rocks", "type": "InstancedMesh3D",
|
|
5352
|
+
* "props": { "mesh": "gem", "size": [0.7, 0.5, 0.7],
|
|
5353
|
+
* "material": { "color": "#8a8f98", "roughness": 0.9, "flatShading": true },
|
|
5354
|
+
* "transforms": [[12, 0.2, -8, 40, 1.3], [-6, 0.1, 14, 210, 0.8]] } }
|
|
5355
|
+
*
|
|
5356
|
+
* Each row is `[x, y, z, yawDeg?, scale?]` (yaw 0 and scale 1 when omitted) —
|
|
5357
|
+
* dense enough to generate hundreds inline. REPLACE the array to update
|
|
5358
|
+
* (mutations are not watched).
|
|
5359
|
+
*/
|
|
5360
|
+
var InstancedMesh3D = class extends Node3D {
|
|
5361
|
+
static typeName = "InstancedMesh3D";
|
|
5362
|
+
static props = {
|
|
5363
|
+
mesh: {
|
|
5364
|
+
default: "box",
|
|
5365
|
+
options: [
|
|
5366
|
+
"box",
|
|
5367
|
+
"sphere",
|
|
5368
|
+
"capsule",
|
|
5369
|
+
"plane",
|
|
5370
|
+
"cylinder",
|
|
5371
|
+
"gem"
|
|
5372
|
+
]
|
|
5373
|
+
},
|
|
5374
|
+
size: { default: [
|
|
5375
|
+
1,
|
|
5376
|
+
1,
|
|
5377
|
+
1
|
|
5378
|
+
] },
|
|
5379
|
+
material: { default: {} },
|
|
5380
|
+
/** `[x, y, z, yawDeg?, scale?]` per instance. */
|
|
5381
|
+
transforms: { default: [] },
|
|
5382
|
+
castShadow: { default: false },
|
|
5383
|
+
receiveShadow: { default: false }
|
|
5384
|
+
};
|
|
5385
|
+
mesh = "box";
|
|
5386
|
+
size = [
|
|
5387
|
+
1,
|
|
5388
|
+
1,
|
|
5389
|
+
1
|
|
5390
|
+
];
|
|
5391
|
+
material = {};
|
|
5392
|
+
castShadow = false;
|
|
5393
|
+
receiveShadow = false;
|
|
5394
|
+
_transforms = [];
|
|
5395
|
+
transformsDirty = true;
|
|
5396
|
+
get transforms() {
|
|
5397
|
+
return this._transforms;
|
|
5398
|
+
}
|
|
5399
|
+
set transforms(value) {
|
|
5400
|
+
this._transforms = value;
|
|
5401
|
+
this.transformsDirty = true;
|
|
5402
|
+
}
|
|
5403
|
+
instanced = null;
|
|
5404
|
+
builtKey = "";
|
|
5405
|
+
static validateJson(node) {
|
|
5406
|
+
const self = node;
|
|
5407
|
+
validateMeshMaterial(self.material, node.name);
|
|
5408
|
+
for (const [i, row] of self._transforms.entries()) if (!Array.isArray(row) || row.length < 3 || row.length > 5 || row.some((v) => typeof v !== "number")) throw new IncantoError("BAD_FORMAT", `InstancedMesh3D '${node.name}': transforms[${i}] must be [x, y, z, yawDeg?, scale?] (numbers), got ${JSON.stringify(row)}.`);
|
|
5409
|
+
}
|
|
5410
|
+
/** Test hook: the live instance count. */
|
|
5411
|
+
get instanceCount() {
|
|
5412
|
+
return this.instanced?.count ?? 0;
|
|
5413
|
+
}
|
|
5414
|
+
_syncObject3D(alpha) {
|
|
5415
|
+
super._syncObject3D(alpha);
|
|
5416
|
+
const root = this._ensureObject3D();
|
|
5417
|
+
const key = `${this.mesh} ${JSON.stringify(this.size)} ${this._transforms.length}`;
|
|
5418
|
+
if (key !== this.builtKey) {
|
|
5419
|
+
this.builtKey = key;
|
|
5420
|
+
if (this.instanced) {
|
|
5421
|
+
root.remove(this.instanced);
|
|
5422
|
+
this.instanced.geometry.dispose();
|
|
5423
|
+
this.instanced.material.dispose();
|
|
5424
|
+
this.instanced = null;
|
|
5425
|
+
}
|
|
5426
|
+
if (this._transforms.length > 0) {
|
|
5427
|
+
const geometry = buildMeshGeometry(this.mesh, this.size);
|
|
5428
|
+
const material = new MeshPhysicalMaterial();
|
|
5429
|
+
this.instanced = new InstancedMesh(geometry, material, this._transforms.length);
|
|
5430
|
+
this.instanced.instanceMatrix.setUsage(DynamicDrawUsage);
|
|
5431
|
+
root.add(this.instanced);
|
|
5432
|
+
}
|
|
5433
|
+
this.transformsDirty = true;
|
|
5434
|
+
}
|
|
5435
|
+
const inst = this.instanced;
|
|
5436
|
+
if (!inst) return;
|
|
5437
|
+
inst.castShadow = this.castShadow;
|
|
5438
|
+
inst.receiveShadow = this.receiveShadow;
|
|
5439
|
+
applyMeshMaterial(inst.material, this.material);
|
|
5440
|
+
if (this.transformsDirty) {
|
|
5441
|
+
this.transformsDirty = false;
|
|
5442
|
+
for (let i = 0; i < this._transforms.length; i++) {
|
|
5443
|
+
const row = this._transforms[i];
|
|
5444
|
+
posScratch.set(row[0] ?? 0, row[1] ?? 0, row[2] ?? 0);
|
|
5445
|
+
quatScratch.setFromAxisAngle(UP, (row[3] ?? 0) * Math.PI / 180);
|
|
5446
|
+
const s = row[4] ?? 1;
|
|
5447
|
+
scaleScratch$1.set(s, s, s);
|
|
5448
|
+
matScratch.compose(posScratch, quatScratch, scaleScratch$1);
|
|
5449
|
+
inst.setMatrixAt(i, matScratch);
|
|
5450
|
+
}
|
|
5451
|
+
inst.instanceMatrix.needsUpdate = true;
|
|
5452
|
+
inst.computeBoundingSphere();
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
free() {
|
|
5456
|
+
if (this.instanced) {
|
|
5457
|
+
this.instanced.geometry.dispose();
|
|
5458
|
+
this.instanced.material.dispose();
|
|
5459
|
+
}
|
|
5460
|
+
super.free();
|
|
5461
|
+
}
|
|
5462
|
+
};
|
|
5463
|
+
const UP = new Vector3(0, 1, 0);
|
|
5464
|
+
const posScratch = new Vector3();
|
|
5465
|
+
const quatScratch = new Quaternion();
|
|
5466
|
+
const scaleScratch$1 = new Vector3();
|
|
5467
|
+
const matScratch = new Matrix4();
|
|
5468
|
+
//#endregion
|
|
5469
|
+
//#region src/3d/nodes/lights-3d.ts
|
|
5470
|
+
/** Sun-style light. Direction comes from the node's rotation. */
|
|
5471
|
+
var DirectionalLight3D = class extends Node3D {
|
|
5472
|
+
static typeName = "DirectionalLight3D";
|
|
5473
|
+
static props = {
|
|
5474
|
+
color: { default: "#ffffff" },
|
|
5475
|
+
intensity: { default: 1 },
|
|
5476
|
+
castShadow: { default: false },
|
|
5477
|
+
shadowArea: { default: 75 },
|
|
5478
|
+
shadowMapSize: { default: 2048 },
|
|
5479
|
+
shadowFollowsCamera: { default: false }
|
|
5480
|
+
};
|
|
5481
|
+
color = "#ffffff";
|
|
5482
|
+
intensity = 1;
|
|
5483
|
+
castShadow = false;
|
|
5484
|
+
/** Half-extent of the directional shadow frustum (template default ±75). */
|
|
5485
|
+
shadowArea = 75;
|
|
5486
|
+
shadowMapSize = 2048;
|
|
5487
|
+
/**
|
|
5488
|
+
* Re-center the shadow frustum on the camera every frame. A directional sun's
|
|
5489
|
+
* shadow map covers a fixed ortho box; in a big roaming world that box must be
|
|
5490
|
+
* huge (and so low-resolution that the player's own shadow nearly vanishes).
|
|
5491
|
+
* With this on, a SMALL `shadowArea` tracks the camera, so contact shadows stay
|
|
5492
|
+
* crisp wherever you go. The renderer supplies the camera position.
|
|
5493
|
+
*/
|
|
5494
|
+
shadowFollowsCamera = false;
|
|
5495
|
+
_createObject3D() {
|
|
5496
|
+
return new DirectionalLight();
|
|
5497
|
+
}
|
|
5498
|
+
_syncObject3D() {
|
|
5499
|
+
super._syncObject3D();
|
|
5500
|
+
const light = this._ensureObject3D();
|
|
5501
|
+
light.color.set(this.color);
|
|
5502
|
+
light.intensity = this.intensity;
|
|
5503
|
+
light.castShadow = this.castShadow;
|
|
5504
|
+
if (this.castShadow && "shadow" in light) {
|
|
5505
|
+
const cam = light.shadow.camera;
|
|
5506
|
+
if ("left" in cam) {
|
|
5507
|
+
cam.left = -this.shadowArea;
|
|
5508
|
+
cam.right = this.shadowArea;
|
|
5509
|
+
cam.top = this.shadowArea;
|
|
5510
|
+
cam.bottom = -this.shadowArea;
|
|
5511
|
+
cam.near = .5;
|
|
5512
|
+
cam.far = 500;
|
|
5513
|
+
cam.updateProjectionMatrix();
|
|
5514
|
+
}
|
|
5515
|
+
light.shadow.mapSize.set(this.shadowMapSize, this.shadowMapSize);
|
|
5516
|
+
light.shadow.bias = -2e-4;
|
|
5517
|
+
light.shadow.normalBias = .002;
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
};
|
|
5521
|
+
/** Point light with a range (Godot's OmniLight3D). */
|
|
5522
|
+
var OmniLight3D = class extends Node3D {
|
|
5523
|
+
static typeName = "OmniLight3D";
|
|
5524
|
+
static props = {
|
|
5525
|
+
color: { default: "#ffffff" },
|
|
5526
|
+
intensity: { default: 1 },
|
|
5527
|
+
range: { default: 0 }
|
|
5528
|
+
};
|
|
5529
|
+
color = "#ffffff";
|
|
5530
|
+
intensity = 1;
|
|
5531
|
+
/** 0 = unlimited (three `distance` semantics). */
|
|
5532
|
+
range = 0;
|
|
5533
|
+
_createObject3D() {
|
|
5534
|
+
return new PointLight();
|
|
5535
|
+
}
|
|
5536
|
+
_syncObject3D() {
|
|
5537
|
+
super._syncObject3D();
|
|
5538
|
+
const light = this._ensureObject3D();
|
|
5539
|
+
light.color.set(this.color);
|
|
5540
|
+
light.intensity = this.intensity;
|
|
5541
|
+
light.distance = this.range;
|
|
5542
|
+
}
|
|
5543
|
+
};
|
|
5544
|
+
//#endregion
|
|
5306
5545
|
//#region src/3d/nodes/loft-mesh-3d.ts
|
|
5307
5546
|
/**
|
|
5308
5547
|
* A smooth swept hull ("loft"): superellipse cross-sections interpolated
|
|
@@ -5482,6 +5721,42 @@ function buildLoftGeometry(sections, slices, smooth) {
|
|
|
5482
5721
|
return geo;
|
|
5483
5722
|
}
|
|
5484
5723
|
//#endregion
|
|
5724
|
+
//#region src/3d/animation/layering.ts
|
|
5725
|
+
/**
|
|
5726
|
+
* Clip masking for two-layer animation (upper-body attack over lower-body
|
|
5727
|
+
* locomotion). three has no per-bone action weights, so layering is done at
|
|
5728
|
+
* the CLIP level: the upper action gets only the tracks under the layer root
|
|
5729
|
+
* (e.g. Spine), and the base action gets everything else — no track overlap,
|
|
5730
|
+
* no half-blended bones.
|
|
5731
|
+
*/
|
|
5732
|
+
/** Bone name a track drives — "mixamorigSpine.quaternion" → "mixamorigSpine". */
|
|
5733
|
+
function trackBone(trackName) {
|
|
5734
|
+
const dot = trackName.lastIndexOf(".");
|
|
5735
|
+
return dot === -1 ? trackName : trackName.slice(0, dot);
|
|
5736
|
+
}
|
|
5737
|
+
/**
|
|
5738
|
+
* A copy of `clip` with only the tracks whose bone IS (mode 'keep') or is
|
|
5739
|
+
* NOT (mode 'drop') in `bones`. Track arrays are shared (cheap); the clip
|
|
5740
|
+
* duration is preserved so layered actions stay time-aligned.
|
|
5741
|
+
*/
|
|
5742
|
+
function maskClip(clip, bones, mode) {
|
|
5743
|
+
const tracks = clip.tracks.filter((t) => {
|
|
5744
|
+
const inSet = bones.has(trackBone(t.name));
|
|
5745
|
+
return mode === "keep" ? inSet : !inSet;
|
|
5746
|
+
});
|
|
5747
|
+
return new AnimationClip(`${clip.name}#${mode}`, clip.duration, tracks);
|
|
5748
|
+
}
|
|
5749
|
+
/** Collect the bone-name subtree from `root` (inclusive) — the layer mask. */
|
|
5750
|
+
function boneSubtree(root) {
|
|
5751
|
+
const out = /* @__PURE__ */ new Set();
|
|
5752
|
+
const walk = (o) => {
|
|
5753
|
+
out.add(o.name);
|
|
5754
|
+
for (const c of o.children) walk(c);
|
|
5755
|
+
};
|
|
5756
|
+
walk(root);
|
|
5757
|
+
return out;
|
|
5758
|
+
}
|
|
5759
|
+
//#endregion
|
|
5485
5760
|
//#region src/3d/animation/rigmap.ts
|
|
5486
5761
|
/**
|
|
5487
5762
|
* Mixamo rig name → VRM humanoid bone name (the vibe-starter-3d /
|
|
@@ -5615,6 +5890,8 @@ function retargetClipToVrm(clip, sourceScene, vrm) {
|
|
|
5615
5890
|
//#region src/3d/nodes/model-instance-3d.ts
|
|
5616
5891
|
/** Crossfade duration (s) between animation clips — smooths idle↔walk↔run↔jump. */
|
|
5617
5892
|
const ANIM_CROSSFADE = .2;
|
|
5893
|
+
/** Upper-layer fade — snappier than locomotion blends (attacks must feel instant). */
|
|
5894
|
+
const UPPER_FADE = .12;
|
|
5618
5895
|
/**
|
|
5619
5896
|
* A GLB/glTF/VRM model. `model` is a `$key` into the scene's assets header
|
|
5620
5897
|
* ({type:"model", url}) or a direct URL. `targetHeight` (>0) uniformly scales
|
|
@@ -5637,6 +5914,12 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5637
5914
|
targetHeight: { default: 0 },
|
|
5638
5915
|
castShadow: { default: false },
|
|
5639
5916
|
animationLoop: { default: true },
|
|
5917
|
+
/** Upper-body layer clip ('' = off). One-shots auto-clear on finish. */
|
|
5918
|
+
animationUpper: { default: "" },
|
|
5919
|
+
/** Loop the upper layer (false = one-shot: attack, wave, throw). */
|
|
5920
|
+
animationUpperLoop: { default: false },
|
|
5921
|
+
/** Bone whose subtree the upper layer drives (forgiving lookup). */
|
|
5922
|
+
upperBodyRoot: { default: "Spine" },
|
|
5640
5923
|
receiveShadow: { default: false },
|
|
5641
5924
|
tint: { default: "" },
|
|
5642
5925
|
metalness: { default: -1 },
|
|
@@ -5648,6 +5931,16 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5648
5931
|
castShadow = false;
|
|
5649
5932
|
/** false = play the clip once, then emit `animationFinished`. */
|
|
5650
5933
|
animationLoop = true;
|
|
5934
|
+
/**
|
|
5935
|
+
* Two-layer animation: play this clip on the UPPER BODY ONLY (the
|
|
5936
|
+
* `upperBodyRoot` bone subtree) while `animation` keeps driving the rest —
|
|
5937
|
+
* attack while running, wave while walking. One-shot by default
|
|
5938
|
+
* (`animationUpperLoop: false`): finishes, emits `animationFinished` with
|
|
5939
|
+
* this key, and auto-clears back to ''.
|
|
5940
|
+
*/
|
|
5941
|
+
animationUpper = "";
|
|
5942
|
+
animationUpperLoop = false;
|
|
5943
|
+
upperBodyRoot = "Spine";
|
|
5651
5944
|
receiveShadow = false;
|
|
5652
5945
|
/** Hex colour multiplied into every material — RESKIN one shared GLB into many
|
|
5653
5946
|
* variants (e.g. a sickly-green zombie from the base human). '' = untinted. */
|
|
@@ -5666,6 +5959,12 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5666
5959
|
mixer = null;
|
|
5667
5960
|
playing = "";
|
|
5668
5961
|
currentAction = null;
|
|
5962
|
+
playingUpper = "";
|
|
5963
|
+
upperAction = null;
|
|
5964
|
+
/** Which variant the BASE action is playing ('lower' while a layer is on). */
|
|
5965
|
+
baseMode = "full";
|
|
5966
|
+
/** Masked-clip cache: source clip uuid + layer root → {upper, lower}. */
|
|
5967
|
+
maskCache = /* @__PURE__ */ new Map();
|
|
5669
5968
|
/** action → the `animation` key that played it, so `animationFinished` reports
|
|
5670
5969
|
* the clip that ACTUALLY finished (a crossfaded-away one-shot can finish a few
|
|
5671
5970
|
* frames after `this.animation` already moved on). */
|
|
@@ -5759,6 +6058,12 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5759
6058
|
this.mixer.addEventListener("finished", (e) => {
|
|
5760
6059
|
const action = e.action;
|
|
5761
6060
|
const key = (action && this.actionKeys.get(action)) ?? this.animation;
|
|
6061
|
+
if (action && action === this.upperAction) {
|
|
6062
|
+
action.fadeOut(UPPER_FADE);
|
|
6063
|
+
this.upperAction = null;
|
|
6064
|
+
this.playingUpper = "";
|
|
6065
|
+
this.animationUpper = "";
|
|
6066
|
+
}
|
|
5762
6067
|
this.emit("animationFinished", key);
|
|
5763
6068
|
});
|
|
5764
6069
|
this.playing = "";
|
|
@@ -5801,6 +6106,7 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5801
6106
|
this.applyMaterialMods();
|
|
5802
6107
|
this.applyPoses();
|
|
5803
6108
|
this.syncAnimation(assets);
|
|
6109
|
+
this.syncUpperAnimation(assets);
|
|
5804
6110
|
}
|
|
5805
6111
|
/** Apply per-instance material mods — `tint` (multiplied into colour) and the
|
|
5806
6112
|
* `metalness`/`roughness` overrides — by cloning the captured originals
|
|
@@ -5844,33 +6150,68 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5844
6150
|
if (obj) obj.rotation.set(rot[0] * DEG, rot[1] * DEG, rot[2] * DEG);
|
|
5845
6151
|
}
|
|
5846
6152
|
}
|
|
6153
|
+
/** Resolve a clip ref: embedded name → '$asset' / URL (VRM-retargeted when
|
|
6154
|
+
* needed). 'loading' = asset still fetching, try again next frame. */
|
|
6155
|
+
resolveClip(ref, assets) {
|
|
6156
|
+
if (!this.entry) return null;
|
|
6157
|
+
const embedded = this.entry.animations.find((c) => c.name === ref);
|
|
6158
|
+
if (embedded) return embedded;
|
|
6159
|
+
if (!(ref.startsWith("$") || /\.(glb|gltf)$/i.test(ref))) return null;
|
|
6160
|
+
const anim = assets.getAnimation(ref);
|
|
6161
|
+
if (anim.status === "loading") return "loading";
|
|
6162
|
+
if (anim.status !== "ready" || !anim.scene) return null;
|
|
6163
|
+
const source = (anim.clip ? anim.clips.find((c) => c.name === anim.clip) : void 0) ?? anim.clips[0];
|
|
6164
|
+
if (!source) return null;
|
|
6165
|
+
if (this.entry.isVrm && this.entry.vrm) {
|
|
6166
|
+
const cached = this.entry.retargeted.get(ref);
|
|
6167
|
+
if (cached) return cached;
|
|
6168
|
+
const result = retargetClipToVrm(source, anim.scene, this.entry.vrm);
|
|
6169
|
+
if (result.skippedBones.length > 0) console.warn(`incanto: retarget '${ref}' skipped bones: ${result.skippedBones.join(", ")}`);
|
|
6170
|
+
this.entry.retargeted.set(ref, result.clip);
|
|
6171
|
+
return result.clip;
|
|
6172
|
+
}
|
|
6173
|
+
return source;
|
|
6174
|
+
}
|
|
6175
|
+
/** Masked upper/lower variants of a clip for two-layer playback (cached). */
|
|
6176
|
+
layerVariants(clip) {
|
|
6177
|
+
const rootBone = this.findBone(this.upperBodyRoot);
|
|
6178
|
+
if (!rootBone) return null;
|
|
6179
|
+
const key = `${clip.uuid} ${rootBone.name}`;
|
|
6180
|
+
let v = this.maskCache.get(key);
|
|
6181
|
+
if (!v) {
|
|
6182
|
+
const bones = boneSubtree(rootBone);
|
|
6183
|
+
v = {
|
|
6184
|
+
upper: maskClip(clip, bones, "keep"),
|
|
6185
|
+
lower: maskClip(clip, bones, "drop")
|
|
6186
|
+
};
|
|
6187
|
+
this.maskCache.set(key, v);
|
|
6188
|
+
}
|
|
6189
|
+
return v;
|
|
6190
|
+
}
|
|
6191
|
+
upperLayerActive() {
|
|
6192
|
+
return this.animationUpper !== "" || this.upperAction !== null;
|
|
6193
|
+
}
|
|
5847
6194
|
syncAnimation(assets) {
|
|
5848
|
-
if (!this.mixer || !this.entry
|
|
6195
|
+
if (!this.mixer || !this.entry) return;
|
|
6196
|
+
const mode = this.upperLayerActive() ? "lower" : "full";
|
|
6197
|
+
if (this.playing === this.animation && this.baseMode === mode) return;
|
|
5849
6198
|
if (this.animation === "") {
|
|
5850
6199
|
if (this.currentAction) this.currentAction.fadeOut(ANIM_CROSSFADE);
|
|
5851
6200
|
this.currentAction = null;
|
|
5852
6201
|
this.playing = "";
|
|
6202
|
+
this.baseMode = mode;
|
|
5853
6203
|
return;
|
|
5854
6204
|
}
|
|
5855
|
-
|
|
5856
|
-
if (
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
if (source) if (this.entry.isVrm && this.entry.vrm) {
|
|
5862
|
-
const cached = this.entry.retargeted.get(this.animation);
|
|
5863
|
-
if (cached) clip = cached;
|
|
5864
|
-
else {
|
|
5865
|
-
const result = retargetClipToVrm(source, anim.scene, this.entry.vrm);
|
|
5866
|
-
if (result.skippedBones.length > 0) console.warn(`incanto: retarget '${this.animation}' skipped bones: ${result.skippedBones.join(", ")}`);
|
|
5867
|
-
this.entry.retargeted.set(this.animation, result.clip);
|
|
5868
|
-
clip = result.clip;
|
|
5869
|
-
}
|
|
5870
|
-
} else clip = source;
|
|
5871
|
-
}
|
|
6205
|
+
const resolved = this.resolveClip(this.animation, assets);
|
|
6206
|
+
if (resolved === "loading") return;
|
|
6207
|
+
let clip = resolved;
|
|
6208
|
+
if (clip && mode === "lower") {
|
|
6209
|
+
const variants = this.layerVariants(clip);
|
|
6210
|
+
if (variants) clip = variants.lower;
|
|
5872
6211
|
}
|
|
6212
|
+
const samePlaying = this.playing === this.animation;
|
|
5873
6213
|
this.playing = this.animation;
|
|
6214
|
+
this.baseMode = mode;
|
|
5874
6215
|
if (clip) {
|
|
5875
6216
|
const action = this.mixer.clipAction(clip);
|
|
5876
6217
|
action.reset();
|
|
@@ -5883,12 +6224,50 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5883
6224
|
action.play();
|
|
5884
6225
|
this.actionKeys.set(action, this.animation);
|
|
5885
6226
|
if (this.currentAction && this.currentAction !== action) {
|
|
6227
|
+
if (samePlaying && clip.duration > 0) action.time = this.currentAction.time % clip.duration;
|
|
5886
6228
|
const warp = clip.duration > 0 && this.currentAction.getClip().duration > 0;
|
|
5887
6229
|
action.crossFadeFrom(this.currentAction, ANIM_CROSSFADE, warp);
|
|
5888
6230
|
}
|
|
5889
6231
|
this.currentAction = action;
|
|
5890
6232
|
} else console.warn(`incanto: model '${this.model}' has no animation '${this.animation}' — embedded: ${this.entry.animations.map((c) => c.name).join(", ") || "(none)"}; assets: ${assets.animationRefs().join(", ") || "(none)"}`);
|
|
5891
6233
|
}
|
|
6234
|
+
/** The upper-body layer: masked clip on the `upperBodyRoot` subtree. */
|
|
6235
|
+
syncUpperAnimation(assets) {
|
|
6236
|
+
if (!this.mixer || !this.entry || this.playingUpper === this.animationUpper) return;
|
|
6237
|
+
if (this.animationUpper === "") {
|
|
6238
|
+
this.upperAction?.fadeOut(UPPER_FADE);
|
|
6239
|
+
this.upperAction = null;
|
|
6240
|
+
this.playingUpper = "";
|
|
6241
|
+
return;
|
|
6242
|
+
}
|
|
6243
|
+
const resolved = this.resolveClip(this.animationUpper, assets);
|
|
6244
|
+
if (resolved === "loading") return;
|
|
6245
|
+
if (!resolved) {
|
|
6246
|
+
console.warn(`incanto: model '${this.model}' has no animation '${this.animationUpper}' for the upper layer.`);
|
|
6247
|
+
this.playingUpper = this.animationUpper;
|
|
6248
|
+
return;
|
|
6249
|
+
}
|
|
6250
|
+
const variants = this.layerVariants(resolved);
|
|
6251
|
+
if (!variants) {
|
|
6252
|
+
console.warn(`incanto: upperBodyRoot '${this.upperBodyRoot}' not found in '${this.model}' — upper layer skipped.`);
|
|
6253
|
+
this.playingUpper = this.animationUpper;
|
|
6254
|
+
return;
|
|
6255
|
+
}
|
|
6256
|
+
const action = this.mixer.clipAction(variants.upper);
|
|
6257
|
+
action.reset();
|
|
6258
|
+
if (this.animationUpperLoop) action.setLoop(LoopRepeat, Infinity);
|
|
6259
|
+
else {
|
|
6260
|
+
action.setLoop(LoopOnce, 1);
|
|
6261
|
+
action.clampWhenFinished = true;
|
|
6262
|
+
}
|
|
6263
|
+
action.setEffectiveWeight(1);
|
|
6264
|
+
action.fadeIn(UPPER_FADE);
|
|
6265
|
+
action.play();
|
|
6266
|
+
this.actionKeys.set(action, this.animationUpper);
|
|
6267
|
+
if (this.upperAction && this.upperAction !== action) this.upperAction.fadeOut(UPPER_FADE);
|
|
6268
|
+
this.upperAction = action;
|
|
6269
|
+
this.playingUpper = this.animationUpper;
|
|
6270
|
+
}
|
|
5892
6271
|
update(dt) {
|
|
5893
6272
|
this.mixer?.update(dt);
|
|
5894
6273
|
if (this.entry?.isVrm && this.entry.claimedBy === this) this.entry.vrm?.update(dt);
|
|
@@ -5906,6 +6285,10 @@ var ModelInstance3D = class extends Node3D {
|
|
|
5906
6285
|
this.fitGroup = null;
|
|
5907
6286
|
this.mixer = null;
|
|
5908
6287
|
this.currentAction = null;
|
|
6288
|
+
this.upperAction = null;
|
|
6289
|
+
this.playingUpper = "";
|
|
6290
|
+
this.baseMode = "full";
|
|
6291
|
+
this.maskCache.clear();
|
|
5909
6292
|
this.entry = null;
|
|
5910
6293
|
this.mountedRef = "";
|
|
5911
6294
|
this.playing = "";
|
|
@@ -11716,6 +12099,8 @@ function registerNodes3D() {
|
|
|
11716
12099
|
registerNode(Joint3D);
|
|
11717
12100
|
registerNode(Trail3D);
|
|
11718
12101
|
registerNode(BoneAttachment3D);
|
|
12102
|
+
registerNode(BoneLookAt3D);
|
|
12103
|
+
registerNode(InstancedMesh3D);
|
|
11719
12104
|
registerNode(Terrain3D);
|
|
11720
12105
|
registerNode(Water3D);
|
|
11721
12106
|
registerNode(Foliage3D);
|
|
@@ -11731,4 +12116,4 @@ function registerNodes3D() {
|
|
|
11731
12116
|
registerNode(CharacterBody3D);
|
|
11732
12117
|
}
|
|
11733
12118
|
//#endregion
|
|
11734
|
-
export {
|
|
12119
|
+
export { Area3D as A, movementState as B, BoneAttachment3D as C, TERRAIN_THEMES as D, DEFAULT_TERRAIN_TEXTURE_BASE as E, Node3D as F, validateCollider3D as I, QUARTER_PITCH as L, PhysicsBody3D as M, RigidBody3D as N, terrainThemeLayers as O, StaticBody3D as P, cameraRelative as R, BoneLookAt3D as S, Terrain3D as T, rigPose as V, Flowers3D as _, VoxelGrid3D as a, CharacterController3D as b, Particles3D as c, DirectionalLight3D as d, OmniLight3D as f, DENSITY_PRESETS as g, Foliage3D as h, VOXEL_PALETTE as i, CharacterBody3D as j, Joint3D as k, ModelInstance3D as l, MeshInstance3D as m, Water3D as n, Tree3D as o, InstancedMesh3D as p, WATER_MAX_RIPPLES as r, Trail3D as s, registerNodes3D as t, LoftMesh3D as u, resolveFlowerDensity as v, Billboard3D as w, Camera3D as x, FLOWER_VARIETIES as y, keyboardIntensity as z };
|