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,13 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-
|
|
3
|
-
import { a as Engine } from "./particle-sim-
|
|
2
|
+
import { _ as registerBehavior, n as loadScene, o as computeViewport, s as resolveViewport } from "./loader-B4OEXDZ8.js";
|
|
3
|
+
import { a as Engine } from "./particle-sim-CFkILGwh.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import {
|
|
5
|
+
import { s as AudioPlayer } from "./register-Dl_ixIJe.js";
|
|
6
6
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
8
|
-
import {
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-CDFgSG6z.js";
|
|
8
|
+
import { g as Node2D, n as UILayer, p as PhysicsBody2D, s as Camera2D, t as registerNodes2D } from "./register-BJCfuuZ2.js";
|
|
9
9
|
import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
|
|
10
|
-
import { n as enablePhysics2D } from "./physics-2d-
|
|
10
|
+
import { n as enablePhysics2D } from "./physics-2d-Cgli1aju.js";
|
|
11
11
|
import { Box3, BufferAttribute, BufferGeometry, Color, LineBasicMaterial, LineSegments, LinearFilter, NearestFilter, OrthographicCamera, Raycaster, SRGBColorSpace, Scene, TextureLoader, Vector2, Vector3, WebGLRenderer } from "three";
|
|
12
12
|
//#region src/2d/assets.ts
|
|
13
13
|
/**
|
|
@@ -102,26 +102,41 @@ function worldFromScreen(view, sx, sy) {
|
|
|
102
102
|
}
|
|
103
103
|
//#endregion
|
|
104
104
|
//#region src/2d/sync.ts
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
function createSync2DScratch() {
|
|
106
|
+
return {
|
|
107
|
+
visited: /* @__PURE__ */ new Set(),
|
|
108
|
+
cameras: []
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function syncTree2D(root, world, ui, assets, uiSize, scratch, opts) {
|
|
112
|
+
const s = scratch ?? createSync2DScratch();
|
|
113
|
+
s.visited.clear();
|
|
114
|
+
s.cameras.length = 0;
|
|
115
|
+
walk(root, world, ui, false, s.visited, s.cameras, assets, uiSize, opts?.ignoreStatic === true);
|
|
116
|
+
prune(world, s.visited);
|
|
117
|
+
prune(ui, s.visited);
|
|
118
|
+
let current = null;
|
|
119
|
+
for (let i = 0; i < s.cameras.length; i++) {
|
|
120
|
+
const c = s.cameras[i];
|
|
121
|
+
if (c.current) {
|
|
122
|
+
current = c;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!current) current = s.cameras[0] ?? null;
|
|
127
|
+
return { activeCamera: current };
|
|
121
128
|
}
|
|
122
|
-
function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
|
|
129
|
+
function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize, ignoreStatic) {
|
|
123
130
|
let nextParent = parentObj;
|
|
124
131
|
let nextInUi = inUi;
|
|
132
|
+
if (node instanceof Node2D && !(node instanceof UILayer)) {
|
|
133
|
+
const frozen = node._ensureObject2D();
|
|
134
|
+
if (node.static && !ignoreStatic && frozen.userData.incantoStaticSynced === true) {
|
|
135
|
+
frozen.userData.incantoStatic = true;
|
|
136
|
+
visited.add(frozen);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
125
140
|
if (node instanceof UILayer) {
|
|
126
141
|
const group = node._ensureGroup();
|
|
127
142
|
if (group.parent !== ui) ui.add(group);
|
|
@@ -141,11 +156,25 @@ function walk(node, parentObj, ui, inUi, visited, cameras, assets, uiSize) {
|
|
|
141
156
|
if (node instanceof Camera2D && !inUi) cameras.push(node);
|
|
142
157
|
nextParent = obj;
|
|
143
158
|
}
|
|
144
|
-
for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, assets, uiSize);
|
|
159
|
+
for (const child of node.children) walk(child, nextParent, ui, nextInUi, visited, cameras, assets, uiSize, ignoreStatic);
|
|
160
|
+
if (node instanceof Node2D && !(node instanceof UILayer)) {
|
|
161
|
+
const obj = node._ensureObject2D();
|
|
162
|
+
if (node.static && !ignoreStatic) {
|
|
163
|
+
obj.userData.incantoStaticSynced = true;
|
|
164
|
+
obj.userData.incantoStatic = true;
|
|
165
|
+
} else if (obj.userData.incantoStaticSynced === true) {
|
|
166
|
+
obj.userData.incantoStaticSynced = false;
|
|
167
|
+
obj.userData.incantoStatic = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
145
170
|
}
|
|
146
171
|
function prune(obj, visited) {
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
const ch = obj.children;
|
|
173
|
+
for (let i = ch.length - 1; i >= 0; i--) {
|
|
174
|
+
const child = ch[i];
|
|
175
|
+
if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
|
|
176
|
+
else if (child.userData.incantoStatic !== true) prune(child, visited);
|
|
177
|
+
}
|
|
149
178
|
}
|
|
150
179
|
//#endregion
|
|
151
180
|
//#region src/2d/renderer.ts
|
|
@@ -163,6 +192,9 @@ var Renderer2D = class {
|
|
|
163
192
|
viewOverride = null;
|
|
164
193
|
webgl;
|
|
165
194
|
worldScene = new Scene();
|
|
195
|
+
syncScratch = createSync2DScratch();
|
|
196
|
+
/** Editors set this: keep syncing `static: true` subtrees every frame. */
|
|
197
|
+
ignoreStatic = false;
|
|
166
198
|
uiScene = new Scene();
|
|
167
199
|
worldCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
|
|
168
200
|
uiCam = new OrthographicCamera(-1, 1, 1, -1, -1e3, 1e3);
|
|
@@ -231,7 +263,7 @@ var Renderer2D = class {
|
|
|
231
263
|
const { activeCamera } = syncTree2D(scene.root, this.worldScene, this.uiScene, this.assets, {
|
|
232
264
|
width: uiW,
|
|
233
265
|
height: uiH
|
|
234
|
-
});
|
|
266
|
+
}, this.syncScratch);
|
|
235
267
|
let center = vp ? {
|
|
236
268
|
x: vp.design[0] / 2,
|
|
237
269
|
y: vp.design[1] / 2
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import { _ as registerBehavior, n as loadScene } from "./loader-
|
|
3
|
-
import { a as Engine } from "./particle-sim-
|
|
2
|
+
import { _ as registerBehavior, n as loadScene } from "./loader-B4OEXDZ8.js";
|
|
3
|
+
import { a as Engine } from "./particle-sim-CFkILGwh.js";
|
|
4
4
|
import { t as IncantoError } from "./errors-BpWbnbb_.js";
|
|
5
|
-
import {
|
|
5
|
+
import { s as AudioPlayer } from "./register-Dl_ixIJe.js";
|
|
6
6
|
import { i as resolveRendering, n as attachTouchControls } from "./touch-031PxtCR.js";
|
|
7
|
-
import { n as registerGameplayBehaviors } from "./gameplay-
|
|
7
|
+
import { n as registerGameplayBehaviors } from "./gameplay-CDFgSG6z.js";
|
|
8
8
|
import { t as debugSources } from "./debug-draw-CZmOYjL2.js";
|
|
9
|
-
import {
|
|
10
|
-
import { n as enablePhysics3D } from "./physics-3d-
|
|
11
|
-
import { ACESFilmicToneMapping, AmbientLight, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
9
|
+
import { O as PhysicsBody3D, c as ModelInstance3D, d as DirectionalLight3D, j as Node3D, t as registerNodes3D, y as Camera3D } from "./register-Dd7Juujf.js";
|
|
10
|
+
import { n as enablePhysics3D } from "./physics-3d-CBAQ12LY.js";
|
|
11
|
+
import { ACESFilmicToneMapping, AmbientLight, BufferAttribute, BufferGeometry, Color, DepthTexture, EquirectangularReflectionMapping, FloatType, Fog, HalfFloatType, LineBasicMaterial, LineSegments, Matrix4, Mesh, PCFShadowMap, PMREMGenerator, PerspectiveCamera, PlaneGeometry, Quaternion, Raycaster, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from "three";
|
|
12
12
|
import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm";
|
|
13
13
|
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
14
14
|
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
|
|
@@ -127,6 +127,122 @@ var AssetStore3D = class {
|
|
|
127
127
|
}
|
|
128
128
|
};
|
|
129
129
|
//#endregion
|
|
130
|
+
//#region src/3d/bloom/bloom-composite.ts
|
|
131
|
+
/**
|
|
132
|
+
* HDR bloom post pass:
|
|
133
|
+
*
|
|
134
|
+
* scene → offscreen HalfFloat target (raw linear HDR — three applies neither
|
|
135
|
+
* tone mapping nor the sRGB transform when rendering into a target)
|
|
136
|
+
* bright-pass at HALF resolution (keep only luminance above `threshold`;
|
|
137
|
+
* with HDR buffers, emissives and sun glints sit above 1.0)
|
|
138
|
+
* separable 9-tap Gaussian blur (H then V) at half res
|
|
139
|
+
* composite: scene + glow × strength, THEN the standard postprocessing tail
|
|
140
|
+
* (ACES tone mapping + linear→sRGB) — skipping that tail is exactly the
|
|
141
|
+
* dark/oversaturated bug the volumetric-clouds pipeline shipped with.
|
|
142
|
+
*/
|
|
143
|
+
const VERT = `
|
|
144
|
+
varying vec2 vUv;
|
|
145
|
+
void main() {
|
|
146
|
+
vUv = uv;
|
|
147
|
+
gl_Position = vec4(position.xy, 0.0, 1.0);
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
const BRIGHT_FRAG = `
|
|
151
|
+
precision highp float;
|
|
152
|
+
varying vec2 vUv;
|
|
153
|
+
uniform sampler2D tScene;
|
|
154
|
+
uniform float uThreshold;
|
|
155
|
+
void main() {
|
|
156
|
+
vec3 c = texture2D(tScene, vUv).rgb;
|
|
157
|
+
// HDR luminance knee: the scene buffer is raw linear HDR (tone mapping is
|
|
158
|
+
// applied later, in the composite), so emissives/sun glints sit ABOVE 1.0 —
|
|
159
|
+
// keep only the energy above the threshold, in proportion
|
|
160
|
+
float l = dot(c, vec3(0.2126, 0.7152, 0.0722));
|
|
161
|
+
float k = max(l - uThreshold, 0.0);
|
|
162
|
+
gl_FragColor = vec4(c * (k / max(l, 1e-4)), 1.0);
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
const BLUR_FRAG = `
|
|
166
|
+
precision highp float;
|
|
167
|
+
varying vec2 vUv;
|
|
168
|
+
uniform sampler2D tTex;
|
|
169
|
+
uniform vec2 uDir;
|
|
170
|
+
void main() {
|
|
171
|
+
// 9-tap Gaussian, sigma ~2 texels (same recipe as the cloud blur)
|
|
172
|
+
vec4 sum = texture2D(tTex, vUv) * 0.227027;
|
|
173
|
+
sum += texture2D(tTex, vUv + uDir * 1.3846) * 0.3162162;
|
|
174
|
+
sum += texture2D(tTex, vUv - uDir * 1.3846) * 0.3162162;
|
|
175
|
+
sum += texture2D(tTex, vUv + uDir * 3.2308) * 0.0702703;
|
|
176
|
+
sum += texture2D(tTex, vUv - uDir * 3.2308) * 0.0702703;
|
|
177
|
+
gl_FragColor = sum;
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
const COMPOSITE_FRAG = `
|
|
181
|
+
precision highp float;
|
|
182
|
+
varying vec2 vUv;
|
|
183
|
+
uniform sampler2D tScene;
|
|
184
|
+
uniform sampler2D tBloom;
|
|
185
|
+
uniform float uStrength;
|
|
186
|
+
void main() {
|
|
187
|
+
vec3 base = texture2D(tScene, vUv).rgb;
|
|
188
|
+
vec3 glow = texture2D(tBloom, vUv).rgb;
|
|
189
|
+
gl_FragColor = vec4(base + glow * uStrength, 1.0);
|
|
190
|
+
// three skips BOTH tone mapping and the output colorspace transform when the
|
|
191
|
+
// scene renders into an offscreen target — the buffer holds raw linear HDR.
|
|
192
|
+
// This is the final to-screen pass, so apply the standard postprocessing
|
|
193
|
+
// tail here (ACES then linear→sRGB); without it the frame comes out dark
|
|
194
|
+
// and oversaturated (the volumetric-clouds pipeline bug, root-caused).
|
|
195
|
+
#include <tonemapping_fragment>
|
|
196
|
+
#include <colorspace_fragment>
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
function quad(fragment, uniforms) {
|
|
200
|
+
const material = new ShaderMaterial({
|
|
201
|
+
vertexShader: VERT,
|
|
202
|
+
fragmentShader: fragment,
|
|
203
|
+
depthTest: false,
|
|
204
|
+
depthWrite: false,
|
|
205
|
+
uniforms
|
|
206
|
+
});
|
|
207
|
+
const mesh = new Mesh(new PlaneGeometry(2, 2), material);
|
|
208
|
+
mesh.frustumCulled = false;
|
|
209
|
+
return mesh;
|
|
210
|
+
}
|
|
211
|
+
/** Half-res bright-pass quad — keeps only pixels brighter than the threshold. */
|
|
212
|
+
function createBloomBrightQuad() {
|
|
213
|
+
const mesh = quad(BRIGHT_FRAG, {
|
|
214
|
+
tScene: { value: null },
|
|
215
|
+
uThreshold: { value: 1 }
|
|
216
|
+
});
|
|
217
|
+
return {
|
|
218
|
+
mesh,
|
|
219
|
+
uniforms: mesh.material.uniforms
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Separable-blur quad (run twice: uDir = (1/w, 0) then (0, 1/h)). */
|
|
223
|
+
function createBloomBlurQuad() {
|
|
224
|
+
const mesh = quad(BLUR_FRAG, {
|
|
225
|
+
tTex: { value: null },
|
|
226
|
+
uDir: { value: new Vector2() }
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
mesh,
|
|
230
|
+
uniforms: mesh.material.uniforms
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/** Full-res composite quad — adds the blurred glow over the scene. */
|
|
234
|
+
function createBloomCompositeQuad() {
|
|
235
|
+
const mesh = quad(COMPOSITE_FRAG, {
|
|
236
|
+
tScene: { value: null },
|
|
237
|
+
tBloom: { value: null },
|
|
238
|
+
uStrength: { value: .85 }
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
mesh,
|
|
242
|
+
uniforms: mesh.material.uniforms
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
130
246
|
//#region src/3d/clouds/clouds-composite.ts
|
|
131
247
|
/**
|
|
132
248
|
* Volumetric clouds — a raymarched cloud DECK the camera flies through, drawn as
|
|
@@ -446,7 +562,12 @@ const CLOUD_KEYS = [
|
|
|
446
562
|
"speed",
|
|
447
563
|
"scale"
|
|
448
564
|
];
|
|
449
|
-
const
|
|
565
|
+
const BLOOM_KEYS = ["threshold", "strength"];
|
|
566
|
+
const SHADOW_KEYS = [
|
|
567
|
+
"mapSize",
|
|
568
|
+
"radius",
|
|
569
|
+
"static"
|
|
570
|
+
];
|
|
450
571
|
const SHADOW_MAP_SIZES = [1024, 2048];
|
|
451
572
|
const DEFAULT_TURBIDITY = 2;
|
|
452
573
|
const DEFAULT_RAYLEIGH = 1;
|
|
@@ -462,6 +583,7 @@ function parseEnvironment3D(env) {
|
|
|
462
583
|
sky: parseSky(env?.sky),
|
|
463
584
|
fog: parseFog(env?.fog, env?.sky !== void 0),
|
|
464
585
|
clouds: parseClouds(env?.clouds),
|
|
586
|
+
bloom: parseBloom(env?.bloom),
|
|
465
587
|
shadows: parseShadows(env?.shadows)
|
|
466
588
|
};
|
|
467
589
|
}
|
|
@@ -616,12 +738,30 @@ function parseClouds(value) {
|
|
|
616
738
|
scale
|
|
617
739
|
};
|
|
618
740
|
}
|
|
741
|
+
function parseBloom(value) {
|
|
742
|
+
if (value === void 0) return null;
|
|
743
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IncantoError("BAD_FORMAT", `environment.bloom must be an object ({ threshold?, strength? }), got ${JSON.stringify(value)}.`, { prop: "bloom" });
|
|
744
|
+
const b = value;
|
|
745
|
+
for (const key of Object.keys(b)) if (!BLOOM_KEYS.includes(key)) throw new IncantoError("BAD_FORMAT", `environment.bloom has unknown key '${key}'. Valid keys: [${BLOOM_KEYS.join(", ")}].`, {
|
|
746
|
+
prop: "bloom",
|
|
747
|
+
validOptions: BLOOM_KEYS
|
|
748
|
+
});
|
|
749
|
+
const threshold = numberOr(b.threshold, 1, "bloom.threshold");
|
|
750
|
+
if (threshold < 0 || threshold > 8) throw new IncantoError("BAD_FORMAT", `environment.bloom.threshold must be in [0, 8] (linear-HDR luminance; 1 = white), got ${threshold}.`, { prop: "bloom" });
|
|
751
|
+
const strength = numberOr(b.strength, .8, "bloom.strength");
|
|
752
|
+
if (strength < 0) throw new IncantoError("BAD_FORMAT", `environment.bloom.strength must be >= 0, got ${strength}.`, { prop: "bloom" });
|
|
753
|
+
return {
|
|
754
|
+
threshold,
|
|
755
|
+
strength
|
|
756
|
+
};
|
|
757
|
+
}
|
|
619
758
|
function parseShadows(value) {
|
|
620
759
|
if (value === void 0) return null;
|
|
621
760
|
if (value === false) return false;
|
|
622
761
|
if (value === true) return {
|
|
623
762
|
mapSize: 2048,
|
|
624
|
-
radius: 1
|
|
763
|
+
radius: 1,
|
|
764
|
+
static: false
|
|
625
765
|
};
|
|
626
766
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new IncantoError("BAD_FORMAT", `environment.shadows must be true, false or an object ({ mapSize?, radius? }), got ${JSON.stringify(value)}.`, { prop: "shadows" });
|
|
627
767
|
const shadows = value;
|
|
@@ -636,9 +776,12 @@ function parseShadows(value) {
|
|
|
636
776
|
});
|
|
637
777
|
const radius = numberOr(shadows.radius, 1, "shadows.radius");
|
|
638
778
|
if (radius < 0) throw new IncantoError("BAD_FORMAT", `environment.shadows.radius must be >= 0, got ${radius}.`, { prop: "shadows" });
|
|
779
|
+
const staticShadows = shadows.static === void 0 ? false : shadows.static;
|
|
780
|
+
if (typeof staticShadows !== "boolean") throw new IncantoError("BAD_FORMAT", `environment.shadows.static must be a boolean, got ${JSON.stringify(shadows.static)}.`, { prop: "shadows" });
|
|
639
781
|
return {
|
|
640
782
|
mapSize,
|
|
641
|
-
radius
|
|
783
|
+
radius,
|
|
784
|
+
static: staticShadows
|
|
642
785
|
};
|
|
643
786
|
}
|
|
644
787
|
function numberOr(value, fallback, at) {
|
|
@@ -730,6 +873,10 @@ var Environment3D = class {
|
|
|
730
873
|
get clouds() {
|
|
731
874
|
return this.config.clouds;
|
|
732
875
|
}
|
|
876
|
+
/** Parsed bloom config, or null when no bloom pass is declared. */
|
|
877
|
+
get bloom() {
|
|
878
|
+
return this.config.bloom;
|
|
879
|
+
}
|
|
733
880
|
/** The live scene fog (color/near/far) the cloud composite fades into, or null. */
|
|
734
881
|
get sceneFog() {
|
|
735
882
|
return this.scene.fog instanceof Fog ? this.scene.fog : null;
|
|
@@ -748,6 +895,12 @@ var Environment3D = class {
|
|
|
748
895
|
if (gl) {
|
|
749
896
|
gl.toneMappingExposure = cfg.exposure;
|
|
750
897
|
gl.shadowMap.enabled = cfg.shadows !== false;
|
|
898
|
+
if (cfg.shadows !== false && cfg.shadows !== null && cfg.shadows.static) {
|
|
899
|
+
if (gl.shadowMap.autoUpdate) {
|
|
900
|
+
gl.shadowMap.autoUpdate = false;
|
|
901
|
+
gl.shadowMap.needsUpdate = true;
|
|
902
|
+
}
|
|
903
|
+
} else if (!gl.shadowMap.autoUpdate) gl.shadowMap.autoUpdate = true;
|
|
751
904
|
}
|
|
752
905
|
this.applyHdri(env);
|
|
753
906
|
this.applySky(cfg.sky, gl);
|
|
@@ -942,7 +1095,8 @@ function createSyncScratch() {
|
|
|
942
1095
|
assets: void 0,
|
|
943
1096
|
sunDirection: null,
|
|
944
1097
|
sunLight: null,
|
|
945
|
-
alpha: 1
|
|
1098
|
+
alpha: 1,
|
|
1099
|
+
ignoreStatic: false
|
|
946
1100
|
}
|
|
947
1101
|
};
|
|
948
1102
|
}
|
|
@@ -966,6 +1120,7 @@ function syncTree(root, threeScene, assets, opts, scratch) {
|
|
|
966
1120
|
const state = s.state;
|
|
967
1121
|
state.assets = assets;
|
|
968
1122
|
state.sunDirection = opts?.sunDirection ?? null;
|
|
1123
|
+
state.ignoreStatic = opts?.ignoreStatic === true;
|
|
969
1124
|
state.sunLight = null;
|
|
970
1125
|
state.alpha = opts?.alpha ?? 1;
|
|
971
1126
|
walk(root, threeScene, state);
|
|
@@ -1037,6 +1192,11 @@ function walk(node, parentObj, state) {
|
|
|
1037
1192
|
let nextParent = parentObj;
|
|
1038
1193
|
if (node instanceof Node3D) {
|
|
1039
1194
|
const obj = node._ensureObject3D();
|
|
1195
|
+
if (node.static && !state.ignoreStatic && obj.userData.incantoStaticSynced === true) {
|
|
1196
|
+
obj.userData.incantoStatic = true;
|
|
1197
|
+
state.visited.add(obj);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1040
1200
|
if (obj.parent !== parentObj) parentObj.add(obj);
|
|
1041
1201
|
node._syncObject3D(state.alpha);
|
|
1042
1202
|
if (state.assets && node instanceof ModelInstance3D) node._syncModel(state.assets);
|
|
@@ -1056,13 +1216,37 @@ function walk(node, parentObj, state) {
|
|
|
1056
1216
|
parent: nextParent
|
|
1057
1217
|
});
|
|
1058
1218
|
for (const child of node.children) walk(child, nextParent, state);
|
|
1219
|
+
if (node instanceof Node3D) {
|
|
1220
|
+
const obj = node._ensureObject3D();
|
|
1221
|
+
if (node.static && !state.ignoreStatic) {
|
|
1222
|
+
obj.userData.incantoStaticSynced = true;
|
|
1223
|
+
obj.userData.incantoStatic = true;
|
|
1224
|
+
warnStaticHazards(node);
|
|
1225
|
+
} else if (obj.userData.incantoStaticSynced === true) {
|
|
1226
|
+
obj.userData.incantoStaticSynced = false;
|
|
1227
|
+
obj.userData.incantoStatic = false;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
const warnedStatic = /* @__PURE__ */ new WeakSet();
|
|
1232
|
+
/** One-time warning when animated machinery is frozen under a static root. */
|
|
1233
|
+
function warnStaticHazards(root) {
|
|
1234
|
+
if (warnedStatic.has(root)) return;
|
|
1235
|
+
warnedStatic.add(root);
|
|
1236
|
+
const hazards = [];
|
|
1237
|
+
const scan = (n) => {
|
|
1238
|
+
if (typeof n._onRender3D === "function" || n instanceof Camera3D) hazards.push(`${n.name} (${n.constructor.typeName ?? "?"})`);
|
|
1239
|
+
for (const c of n.children) scan(c);
|
|
1240
|
+
};
|
|
1241
|
+
scan(root);
|
|
1242
|
+
if (hazards.length > 0) console.warn(`[incanto] static subtree '${root.name}' freezes animated/per-frame nodes: ${hazards.join(", ")} — they will stop updating. Unmark static or move them out.`);
|
|
1059
1243
|
}
|
|
1060
1244
|
function prune(obj, visited) {
|
|
1061
1245
|
const ch = obj.children;
|
|
1062
1246
|
for (let i = ch.length - 1; i >= 0; i--) {
|
|
1063
1247
|
const child = ch[i];
|
|
1064
1248
|
if (child.userData.incantoNode && !visited.has(child)) obj.remove(child);
|
|
1065
|
-
else prune(child, visited);
|
|
1249
|
+
else if (child.userData.incantoStatic !== true) prune(child, visited);
|
|
1066
1250
|
}
|
|
1067
1251
|
}
|
|
1068
1252
|
//#endregion
|
|
@@ -1209,6 +1393,8 @@ var Renderer3D = class {
|
|
|
1209
1393
|
compiledScene = null;
|
|
1210
1394
|
/** Reused per-frame walk scratch (instance-scoped — never a module global). */
|
|
1211
1395
|
syncScratch = createSyncScratch();
|
|
1396
|
+
/** Editors set this: keep syncing `static: true` subtrees every frame. */
|
|
1397
|
+
ignoreStatic = false;
|
|
1212
1398
|
/** Reused render-hook context — gl/scene are stable, camera reassigned/frame. */
|
|
1213
1399
|
renderCtx = null;
|
|
1214
1400
|
/** Underwater caustics pass (lazy — only created the first time submerged). */
|
|
@@ -1228,6 +1414,15 @@ var Renderer3D = class {
|
|
|
1228
1414
|
cloudsBlurUniforms = null;
|
|
1229
1415
|
cloudsCompositeScene = null;
|
|
1230
1416
|
cloudsCompositeUniforms = null;
|
|
1417
|
+
bloomTarget = null;
|
|
1418
|
+
bloomBrightTarget = null;
|
|
1419
|
+
bloomBlurTarget = null;
|
|
1420
|
+
bloomBrightScene = null;
|
|
1421
|
+
bloomBrightUniforms = null;
|
|
1422
|
+
bloomBlurScene = null;
|
|
1423
|
+
bloomBlurUniforms = null;
|
|
1424
|
+
bloomCompositeScene = null;
|
|
1425
|
+
bloomCompositeUniforms = null;
|
|
1231
1426
|
constructor(opts) {
|
|
1232
1427
|
this.canvas = opts.canvas;
|
|
1233
1428
|
this.engine = opts.engine;
|
|
@@ -1277,7 +1472,8 @@ var Renderer3D = class {
|
|
|
1277
1472
|
this.environment.apply(scene.environment, this.webgl);
|
|
1278
1473
|
const { activeCamera: sceneCamera, renderHooks, sunLight } = syncTree(scene.root, this.threeScene, this.assets, {
|
|
1279
1474
|
sunDirection: this.environment.sunDirection,
|
|
1280
|
-
alpha: this.engine.interpolationAlpha
|
|
1475
|
+
alpha: this.engine.interpolationAlpha,
|
|
1476
|
+
ignoreStatic: this.ignoreStatic
|
|
1281
1477
|
}, this.syncScratch);
|
|
1282
1478
|
let activeCamera = sceneCamera;
|
|
1283
1479
|
if (this.viewOverride) {
|
|
@@ -1334,11 +1530,80 @@ var Renderer3D = class {
|
|
|
1334
1530
|
}
|
|
1335
1531
|
this.environment.applyUnderwater(underwater);
|
|
1336
1532
|
const clouds = this.environment.clouds;
|
|
1533
|
+
const bloom = this.environment.bloom;
|
|
1337
1534
|
if (underwater?.caustics.enabled) this.renderWithCaustics(activeCamera, underwater);
|
|
1338
1535
|
else if (clouds && !underwater) this.renderWithClouds(activeCamera, clouds, threeLight);
|
|
1536
|
+
else if (bloom && !underwater) this.renderWithBloom(activeCamera, bloom);
|
|
1339
1537
|
else this.webgl.render(this.threeScene, activeCamera);
|
|
1340
1538
|
}
|
|
1341
1539
|
/**
|
|
1540
|
+
* Bloom post pass (only when `environment.bloom` is declared). LDR and safe:
|
|
1541
|
+
* 1. scene → full-res offscreen (tone-mapped exactly like direct output)
|
|
1542
|
+
* 2. bright-pass at HALF res (keep pixels above threshold)
|
|
1543
|
+
* 3. separable Gaussian blur (H, V) at half res
|
|
1544
|
+
* 4. composite scene + glow×strength → screen (pure ADD — never re-touches
|
|
1545
|
+
* the scene's tone; the clouds pipeline taught us that lesson)
|
|
1546
|
+
* Caustics/clouds paths take precedence (composite pipelines don't stack yet).
|
|
1547
|
+
*/
|
|
1548
|
+
renderWithBloom(camera, bloom) {
|
|
1549
|
+
const buf = this.webgl.getDrawingBufferSize(sizeScratch);
|
|
1550
|
+
const w = Math.max(1, buf.x);
|
|
1551
|
+
const h = Math.max(1, buf.y);
|
|
1552
|
+
const hw = Math.max(1, Math.ceil(w / 2));
|
|
1553
|
+
const hh = Math.max(1, Math.ceil(h / 2));
|
|
1554
|
+
if (this.bloomTarget && (this.bloomTarget.width !== w || this.bloomTarget.height !== h)) {
|
|
1555
|
+
this.bloomTarget.dispose();
|
|
1556
|
+
this.bloomTarget = null;
|
|
1557
|
+
this.bloomBrightTarget?.dispose();
|
|
1558
|
+
this.bloomBrightTarget = null;
|
|
1559
|
+
this.bloomBlurTarget?.dispose();
|
|
1560
|
+
this.bloomBlurTarget = null;
|
|
1561
|
+
}
|
|
1562
|
+
if (!this.bloomTarget) {
|
|
1563
|
+
this.bloomTarget = new WebGLRenderTarget(w, h, { type: HalfFloatType });
|
|
1564
|
+
this.bloomBrightTarget = new WebGLRenderTarget(hw, hh, { type: HalfFloatType });
|
|
1565
|
+
this.bloomBlurTarget = new WebGLRenderTarget(hw, hh, { type: HalfFloatType });
|
|
1566
|
+
}
|
|
1567
|
+
if (!this.bloomBrightScene) {
|
|
1568
|
+
const bright = createBloomBrightQuad();
|
|
1569
|
+
this.bloomBrightScene = new Scene();
|
|
1570
|
+
this.bloomBrightScene.add(bright.mesh);
|
|
1571
|
+
this.bloomBrightUniforms = bright.uniforms;
|
|
1572
|
+
const blur = createBloomBlurQuad();
|
|
1573
|
+
this.bloomBlurScene = new Scene();
|
|
1574
|
+
this.bloomBlurScene.add(blur.mesh);
|
|
1575
|
+
this.bloomBlurUniforms = blur.uniforms;
|
|
1576
|
+
const composite = createBloomCompositeQuad();
|
|
1577
|
+
this.bloomCompositeScene = new Scene();
|
|
1578
|
+
this.bloomCompositeScene.add(composite.mesh);
|
|
1579
|
+
this.bloomCompositeUniforms = composite.uniforms;
|
|
1580
|
+
}
|
|
1581
|
+
const brightTarget = this.bloomBrightTarget;
|
|
1582
|
+
const blurTarget = this.bloomBlurTarget;
|
|
1583
|
+
this.webgl.setRenderTarget(this.bloomTarget);
|
|
1584
|
+
this.webgl.render(this.threeScene, camera);
|
|
1585
|
+
const bu = this.bloomBrightUniforms;
|
|
1586
|
+
bu.tScene.value = this.bloomTarget.texture;
|
|
1587
|
+
bu.uThreshold.value = bloom.threshold;
|
|
1588
|
+
this.webgl.setRenderTarget(brightTarget);
|
|
1589
|
+
this.webgl.render(this.bloomBrightScene, camera);
|
|
1590
|
+
const blu = this.bloomBlurUniforms;
|
|
1591
|
+
blu.tTex.value = brightTarget.texture;
|
|
1592
|
+
blu.uDir.value.set(1 / hw, 0);
|
|
1593
|
+
this.webgl.setRenderTarget(blurTarget);
|
|
1594
|
+
this.webgl.render(this.bloomBlurScene, camera);
|
|
1595
|
+
blu.tTex.value = blurTarget.texture;
|
|
1596
|
+
blu.uDir.value.set(0, 1 / hh);
|
|
1597
|
+
this.webgl.setRenderTarget(brightTarget);
|
|
1598
|
+
this.webgl.render(this.bloomBlurScene, camera);
|
|
1599
|
+
const cu = this.bloomCompositeUniforms;
|
|
1600
|
+
cu.tScene.value = this.bloomTarget.texture;
|
|
1601
|
+
cu.tBloom.value = brightTarget.texture;
|
|
1602
|
+
cu.uStrength.value = bloom.strength;
|
|
1603
|
+
this.webgl.setRenderTarget(null);
|
|
1604
|
+
this.webgl.render(this.bloomCompositeScene, camera);
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1342
1607
|
* Volumetric cloud pass (only when `environment.clouds` is declared — scenes
|
|
1343
1608
|
* without it render exactly as before, zero cost). Three steps:
|
|
1344
1609
|
* 1. scene → full-res offscreen target (color + depth)
|
|
@@ -1546,6 +1811,13 @@ var Renderer3D = class {
|
|
|
1546
1811
|
forward: new Vector3(0, 0, -1).applyQuaternion(q)
|
|
1547
1812
|
};
|
|
1548
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Re-render the shadow pass once (static-shadow scenes,
|
|
1816
|
+
* `environment.shadows.static: true`) after a one-off world edit.
|
|
1817
|
+
*/
|
|
1818
|
+
refreshShadows() {
|
|
1819
|
+
this.webgl.shadowMap.needsUpdate = true;
|
|
1820
|
+
}
|
|
1549
1821
|
dispose() {
|
|
1550
1822
|
this.disconnect();
|
|
1551
1823
|
this.threeScene.traverse((obj) => {
|
|
@@ -1564,10 +1836,16 @@ var Renderer3D = class {
|
|
|
1564
1836
|
this.cloudsTarget?.dispose();
|
|
1565
1837
|
this.cloudsHalfTarget?.dispose();
|
|
1566
1838
|
this.cloudsBlurTarget?.dispose();
|
|
1839
|
+
this.bloomTarget?.dispose();
|
|
1840
|
+
this.bloomBrightTarget?.dispose();
|
|
1841
|
+
this.bloomBlurTarget?.dispose();
|
|
1567
1842
|
for (const s of [
|
|
1568
1843
|
this.cloudsScene,
|
|
1569
1844
|
this.cloudsBlurScene,
|
|
1570
|
-
this.cloudsCompositeScene
|
|
1845
|
+
this.cloudsCompositeScene,
|
|
1846
|
+
this.bloomBrightScene,
|
|
1847
|
+
this.bloomBlurScene,
|
|
1848
|
+
this.bloomCompositeScene
|
|
1571
1849
|
]) {
|
|
1572
1850
|
const m = s?.children[0];
|
|
1573
1851
|
m?.geometry?.dispose();
|
package/dist/debug.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as RendererStats, k as LogLevel, v as Engine } from "./behavior-
|
|
1
|
+
import { C as RendererStats, k as LogLevel, v as Engine } from "./behavior-CWhW3oa6.js";
|
|
2
2
|
|
|
3
3
|
//#region src/debug/panel.d.ts
|
|
4
4
|
/** Minimal document surface the overlay needs (injectable for tests). */
|