@jgengine/shell 0.19.0 → 0.21.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/CHANGELOG.md +346 -0
- package/dist/GameHost.d.ts +12 -1
- package/dist/GameHost.js +14 -1
- package/dist/GamePlayerShell.js +113 -15
- package/dist/Shell3dPresentation.d.ts +2 -2
- package/dist/Shell3dPresentation.js +49 -7
- package/dist/ShellChrome.d.ts +2 -1
- package/dist/ShellChrome.js +22 -6
- package/dist/ShellHudPresentation.d.ts +11 -1
- package/dist/ShellHudPresentation.js +20 -6
- package/dist/audio/audioEngine.d.ts +16 -0
- package/dist/audio/audioEngine.js +178 -89
- package/dist/audio/audioWire.js +2 -2
- package/dist/audio/synthEngine.d.ts +7 -1
- package/dist/audio/synthEngine.js +103 -38
- package/dist/camera/GameCameraRig.js +2 -1
- package/dist/camera/GameFirstPersonCamera.d.ts +2 -2
- package/dist/camera/GameFirstPersonCamera.js +17 -13
- package/dist/camera/cameraRigs.js +8 -2
- package/dist/camera/chaseHeading.d.ts +9 -0
- package/dist/camera/chaseHeading.js +10 -0
- package/dist/camera/customMuzzle.d.ts +14 -0
- package/dist/camera/customMuzzle.js +68 -0
- package/dist/camera/index.d.ts +1 -0
- package/dist/camera/index.js +1 -0
- package/dist/camera/rigResolve.d.ts +3 -0
- package/dist/camera/rigResolve.js +11 -0
- package/dist/combat/ProjectileModels.d.ts +31 -0
- package/dist/combat/ProjectileModels.js +70 -0
- package/dist/commandSink.d.ts +1 -2
- package/dist/commandSink.js +3 -2
- package/dist/defineGame.d.ts +12 -5
- package/dist/defineGame.js +24 -9
- package/dist/devtools/DevtoolsOverlay.d.ts +2 -69
- package/dist/devtools/DevtoolsOverlay.js +14 -168
- package/dist/devtools/DevtoolsRuntime.d.ts +7 -0
- package/dist/devtools/DevtoolsRuntime.js +61 -0
- package/dist/devtools/agentBridge.js +1 -1
- package/dist/devtools/devtoolsReports.d.ts +68 -0
- package/dist/devtools/devtoolsReports.js +100 -0
- package/dist/devtools/latencyInstrumentation.d.ts +3 -0
- package/dist/devtools/latencyInstrumentation.js +11 -0
- package/dist/devtools/playtestProbe.d.ts +4 -0
- package/dist/devtools/playtestProbe.js +22 -0
- package/dist/drivers/FrameDriver.js +25 -21
- package/dist/drivers/HudOnlyDriver.d.ts +6 -1
- package/dist/drivers/HudOnlyDriver.js +32 -14
- package/dist/environment/EnvironmentScene.js +11 -10
- package/dist/inputSink.d.ts +6 -6
- package/dist/inputSink.js +60 -21
- package/dist/lazyShell3dPresentation.d.ts +9 -0
- package/dist/lazyShell3dPresentation.js +24 -0
- package/dist/materialOverride.d.ts +42 -0
- package/dist/materialOverride.js +95 -36
- package/dist/multiplayer.d.ts +33 -3
- package/dist/multiplayer.js +105 -22
- package/dist/pointer/pointerService.d.ts +2 -0
- package/dist/pointer/pointerService.js +12 -2
- package/dist/postfx/PostProcessing.d.ts +10 -0
- package/dist/postfx/PostProcessing.js +9 -41
- package/dist/postfx/gradeShader.d.ts +1 -1
- package/dist/postfx/gradeShader.js +30 -2
- package/dist/postfx/shadowPrepass.d.ts +3 -0
- package/dist/postfx/shadowPrepass.js +18 -0
- package/dist/presentationRecovery.d.ts +75 -0
- package/dist/presentationRecovery.js +204 -0
- package/dist/presentationRecovery3d.d.ts +4 -0
- package/dist/presentationRecovery3d.js +32 -0
- package/dist/presentationResolve.d.ts +8 -1
- package/dist/presentationResolve.js +5 -2
- package/dist/render/CascadedShadows.d.ts +7 -2
- package/dist/render/CascadedShadows.js +98 -20
- package/dist/render/EnvironmentLighting.js +74 -35
- package/dist/render/HairCards.d.ts +7 -0
- package/dist/render/HairCards.js +23 -0
- package/dist/render/SceneModels.js +51 -77
- package/dist/render/authoredSurfaceMaterial.d.ts +43 -0
- package/dist/render/authoredSurfaceMaterial.js +108 -0
- package/dist/render/materialAppearanceSignals.d.ts +23 -0
- package/dist/render/materialAppearanceSignals.js +58 -0
- package/dist/render/materialAsset.d.ts +98 -0
- package/dist/render/materialAsset.js +454 -0
- package/dist/render/measureBounds.js +16 -3
- package/dist/render/measureCollisionMesh.js +8 -1
- package/dist/render/modelAssets.d.ts +8 -0
- package/dist/render/modelAssets.js +43 -0
- package/dist/render/modelBindPose.d.ts +5 -0
- package/dist/render/modelBindPose.js +35 -0
- package/dist/render/modelLoad.d.ts +3 -0
- package/dist/render/modelLoad.js +9 -0
- package/dist/render/modelNodeVisibility.d.ts +6 -0
- package/dist/render/modelNodeVisibility.js +18 -0
- package/dist/render/modelRender.d.ts +18 -1
- package/dist/render/modelRender.js +81 -3
- package/dist/render/projectedOverdraw.d.ts +35 -0
- package/dist/render/projectedOverdraw.js +204 -0
- package/dist/render/resolveModel.d.ts +12 -2
- package/dist/render/resolveModel.js +21 -4
- package/dist/render/rigNode.d.ts +26 -0
- package/dist/render/rigNode.js +32 -0
- package/dist/render/spriteRender.d.ts +4 -0
- package/dist/render/spriteRender.js +5 -0
- package/dist/render/staticShapeInstances.d.ts +34 -0
- package/dist/render/staticShapeInstances.js +78 -0
- package/dist/render/useFootIk.js +76 -11
- package/dist/render/useModelAnimation.d.ts +28 -2
- package/dist/render/useModelAnimation.js +194 -69
- package/dist/render/useModelInstance.d.ts +25 -0
- package/dist/render/useModelInstance.js +43 -0
- package/dist/scatter/scatterModels.d.ts +3 -2
- package/dist/scatter/scatterModels.js +15 -7
- package/dist/scene/AuthoredScene.d.ts +14 -6
- package/dist/scene/AuthoredScene.js +30 -9
- package/dist/scene/GeneratedAssetRenderer.js +11 -42
- package/dist/settings/SettingsMenu.js +11 -18
- package/dist/settings/SettingsRuntime.d.ts +5 -0
- package/dist/settings/SettingsRuntime.js +29 -2
- package/dist/shellControlSuspension.d.ts +18 -0
- package/dist/shellControlSuspension.js +28 -0
- package/dist/shellInputPublication.d.ts +28 -0
- package/dist/shellInputPublication.js +49 -0
- package/dist/shellPointerInput.d.ts +18 -0
- package/dist/shellPointerInput.js +49 -0
- package/dist/structures/BuildingKitBatch.d.ts +3 -1
- package/dist/structures/BuildingKitBatch.js +34 -59
- package/dist/structures/BuildingRenderBudget.d.ts +23 -0
- package/dist/structures/BuildingRenderBudget.js +54 -0
- package/dist/structures/GeneratedBuilding.js +13 -8
- package/dist/structures/buildingKitMaterials.d.ts +20 -0
- package/dist/structures/buildingKitMaterials.js +70 -0
- package/dist/structures/buildingSpatialBatch.d.ts +11 -0
- package/dist/structures/buildingSpatialBatch.js +45 -0
- package/dist/terrain/GrassField.d.ts +2 -1
- package/dist/terrain/GrassField.js +59 -5
- package/dist/terrain/grassGeometry.d.ts +10 -0
- package/dist/terrain/grassGeometry.js +55 -0
- package/dist/terrain/grassMaterial.js +2 -2
- package/dist/terrain/terrainDetailMaterial.d.ts +3 -3
- package/dist/terrain/terrainDetailMaterial.js +7 -7
- package/dist/touch/TouchControlsOverlay.js +4 -4
- package/dist/useShellMultiplayerSync.js +4 -0
- package/dist/vfx/ParticleField.d.ts +23 -20
- package/dist/vfx/ParticleField.js +168 -50
- package/dist/visibility/CullingProvider.js +2 -62
- package/dist/visibility/cullingDriver.d.ts +9 -0
- package/dist/visibility/cullingDriver.js +68 -0
- package/dist/weather/AuthoredWeatherLayer.d.ts +16 -0
- package/dist/weather/AuthoredWeatherLayer.js +31 -0
- package/dist/weather/DustField.d.ts +2 -1
- package/dist/weather/DustField.js +17 -3
- package/dist/weather/FireSpreadLayer.d.ts +4 -2
- package/dist/weather/FireSpreadLayer.js +5 -11
- package/dist/weather/LightningStrike.d.ts +4 -1
- package/dist/weather/LightningStrike.js +13 -17
- package/dist/weather/RainField.d.ts +2 -1
- package/dist/weather/RainField.js +17 -3
- package/dist/weather/RainImpactField.d.ts +16 -0
- package/dist/weather/RainImpactField.js +89 -0
- package/dist/weather/SnowField.d.ts +2 -1
- package/dist/weather/SnowField.js +17 -3
- package/dist/weather/WeatherLayer.d.ts +11 -2
- package/dist/weather/WeatherLayer.js +30 -6
- package/dist/weather/index.d.ts +2 -0
- package/dist/weather/index.js +2 -0
- package/dist/weather/weatherMath.d.ts +2 -0
- package/dist/weather/weatherMath.js +8 -0
- package/dist/weather/weatherShelter.d.ts +18 -0
- package/dist/weather/weatherShelter.js +75 -0
- package/dist/weather/weatherUniforms.d.ts +40 -1
- package/dist/weather/weatherUniforms.js +29 -9
- package/dist/world/AuthoredFlocks.d.ts +22 -0
- package/dist/world/AuthoredFlocks.js +87 -0
- package/dist/world/StaticObjectModels.d.ts +8 -0
- package/dist/world/StaticObjectModels.js +75 -0
- package/dist/world/WorldHud.d.ts +14 -6
- package/dist/world/WorldHud.js +25 -12
- package/dist/world/WorldParticles.d.ts +10 -23
- package/dist/world/WorldParticles.js +209 -61
- package/dist/world/WorldScene.d.ts +3 -1
- package/dist/world/WorldScene.js +41 -26
- package/dist/world/staticObjectBatches.d.ts +35 -0
- package/dist/world/staticObjectBatches.js +163 -0
- package/dist/world/worldBarSamples.d.ts +37 -33
- package/dist/world/worldBarSamples.js +117 -75
- package/package.json +4 -4
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/jgengine/SKILL.md +22 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-ui/SKILL.md +3 -1
- package/skills/jgengine-ui/api.md +204 -67
- package/skills/jgengine-ui/capabilities.md +115 -1
- package/skills/jgengine-ui/reference-primitives.md +93 -3
- package/skills/jgengine-ui/reference.md +2 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +200 -57
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +152 -5
- package/skills/jgengine-world/visibility.md +28 -1
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useFrame, useLoader } from "@react-three/fiber";
|
|
3
|
-
import {
|
|
4
|
-
import { Component, Suspense, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useFrame, useLoader, useThree } from "@react-three/fiber";
|
|
3
|
+
import { Component, Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
5
4
|
import * as THREE from "three";
|
|
5
|
+
import { parseModelHiddenNodes } from "@jgengine/core/editor/modelNodeVisibility";
|
|
6
6
|
import { createSpriteClipPlayer } from "@jgengine/core/render/sprite2d";
|
|
7
7
|
import { reportFallbackSeam } from "@jgengine/core/devtools/fallbackSeams";
|
|
8
8
|
import { useOptionalGameContext } from "@jgengine/react/provider";
|
|
9
|
-
import { sharedGltfLoader } from "./modelLoad.js";
|
|
9
|
+
import { detectKtx2Support, sharedGltfLoader } from "./modelLoad.js";
|
|
10
|
+
import { modelAssetRequests } from "./modelAssets.js";
|
|
10
11
|
import { measureLocalBounds, reportMeasuredBounds } from "./measureBounds.js";
|
|
11
12
|
import { measureLocalCollisionTriangles, reportMeasuredCollisionMesh } from "./measureCollisionMesh.js";
|
|
12
|
-
import {
|
|
13
|
+
import { useModelInstance } from "./useModelInstance.js";
|
|
14
|
+
import { applyMaterialAssignments, useModelMaterialTextures, MaterialTextureLoader, validateMaterialAssignmentTargets } from "./materialAsset.js";
|
|
13
15
|
import { useFootIk } from "./useFootIk.js";
|
|
16
|
+
import { resolveRigNode } from "./rigNode.js";
|
|
14
17
|
import { PartMotionRig } from "./PartMotion.js";
|
|
18
|
+
import { syncSpriteFrame } from "./spriteRender.js";
|
|
15
19
|
import { applyMaterialOverride } from "../materialOverride.js";
|
|
16
|
-
import { applyPaintTextureToMaterials, cacheStandardMaterials, cloneModelScene, createPaintCanvas,
|
|
20
|
+
import { applyPaintTextureToMaterials, cacheStandardMaterials, cloneModelScene, createPaintCanvas, disposeModelScene, ownModelMaterial, disposePaintCanvas, syncPaintCanvas, } from "./modelRender.js";
|
|
17
21
|
export function EntitySprite({ sprite }) {
|
|
22
|
+
const invalidate = useThree((state) => state.invalidate);
|
|
18
23
|
const texture = useLoader(THREE.TextureLoader, sprite.clip?.atlas.image ?? sprite.url);
|
|
19
24
|
const map = useMemo(() => texture.clone(), [texture]);
|
|
20
25
|
const player = useMemo(() => (sprite.clip === undefined ? null : createSpriteClipPlayer(sprite.clip.atlas)), [sprite.clip]);
|
|
@@ -28,7 +33,11 @@ export function EntitySprite({ sprite }) {
|
|
|
28
33
|
useEffect(() => {
|
|
29
34
|
if (player !== null && animation !== undefined)
|
|
30
35
|
player.play(animation);
|
|
31
|
-
|
|
36
|
+
const frame = player?.frame();
|
|
37
|
+
if (frame !== undefined && sprite.clip !== undefined)
|
|
38
|
+
syncSpriteFrame(map, frame, sprite.clip.atlas.size);
|
|
39
|
+
invalidate();
|
|
40
|
+
}, [player, animation, map, sprite.clip, invalidate]);
|
|
32
41
|
useFrame((_state, delta) => {
|
|
33
42
|
if (player === null || sprite.clip === undefined)
|
|
34
43
|
return;
|
|
@@ -36,10 +45,7 @@ export function EntitySprite({ sprite }) {
|
|
|
36
45
|
const frame = player.frame();
|
|
37
46
|
if (frame === undefined)
|
|
38
47
|
return;
|
|
39
|
-
|
|
40
|
-
map.repeat.set(frame.w / atlasWidth, frame.h / atlasHeight);
|
|
41
|
-
map.offset.set(frame.x / atlasWidth, 1 - (frame.y + frame.h) / atlasHeight);
|
|
42
|
-
map.needsUpdate = true;
|
|
48
|
+
syncSpriteFrame(map, frame, sprite.clip.atlas.size);
|
|
43
49
|
});
|
|
44
50
|
useEffect(() => {
|
|
45
51
|
if (player === null) {
|
|
@@ -97,13 +103,14 @@ function BoneAttachment({ rig, model, slot, position, rotation, scale, }) {
|
|
|
97
103
|
const rz = rotation?.[2] ?? 0;
|
|
98
104
|
const s = scale ?? 1;
|
|
99
105
|
useEffect(() => {
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
106
|
+
const resolved = resolveRigNode(rig, slot);
|
|
107
|
+
if (resolved.node === undefined) {
|
|
102
108
|
if (typeof console !== "undefined") {
|
|
103
|
-
console.warn(`[jgengine] entityModels attachment:
|
|
109
|
+
console.warn(`[jgengine] entityModels attachment: ${resolved.diagnostic.message}`);
|
|
104
110
|
}
|
|
105
111
|
return;
|
|
106
112
|
}
|
|
113
|
+
const bone = resolved.node;
|
|
107
114
|
weaponScene.position.set(px, py, pz);
|
|
108
115
|
weaponScene.rotation.set(rx, ry, rz);
|
|
109
116
|
weaponScene.scale.setScalar(s);
|
|
@@ -112,80 +119,47 @@ function BoneAttachment({ rig, model, slot, position, rotation, scale, }) {
|
|
|
112
119
|
bone.remove(weaponScene);
|
|
113
120
|
};
|
|
114
121
|
}, [rig, weaponScene, slot, px, py, pz, rx, ry, rz, s]);
|
|
115
|
-
useEffect(() => () =>
|
|
122
|
+
useEffect(() => () => disposeModelScene(weaponScene), [weaponScene]);
|
|
116
123
|
return null;
|
|
117
124
|
}
|
|
118
125
|
/** Renders a static kit-of-parts child at a fixed local offset under the parent model — no bone/rig lookup, unlike `BoneAttachment`. The nested `EntityModel` still applies its own `dims`/anchor centering, so scale/pivot sanity carries over per part. */
|
|
119
126
|
function ModelPartGroup({ model, position, rotation, scale, }) {
|
|
120
127
|
return (_jsx("group", { position: position ?? [0, 0, 0], rotation: rotation ?? [0, 0, 0], scale: scale ?? 1, children: _jsx(IsolatedModelPart, { model: model, children: _jsx(EntityModel, { model: model }) }) }));
|
|
121
128
|
}
|
|
122
|
-
function ModelMaterialMapsApplier({ scene, maps }) {
|
|
123
|
-
const entries = useMemo(() => {
|
|
124
|
-
const record = {};
|
|
125
|
-
if (maps.color !== undefined)
|
|
126
|
-
record.color = maps.color;
|
|
127
|
-
if (maps.normal !== undefined)
|
|
128
|
-
record.normal = maps.normal;
|
|
129
|
-
if (maps.roughness !== undefined)
|
|
130
|
-
record.roughness = maps.roughness;
|
|
131
|
-
if (maps.ao !== undefined)
|
|
132
|
-
record.ao = maps.ao;
|
|
133
|
-
if (maps.metalness !== undefined)
|
|
134
|
-
record.metalness = maps.metalness;
|
|
135
|
-
if (maps.emissive !== undefined)
|
|
136
|
-
record.emissive = maps.emissive;
|
|
137
|
-
if (maps.height !== undefined)
|
|
138
|
-
record.height = maps.height;
|
|
139
|
-
return record;
|
|
140
|
-
}, [maps.color, maps.normal, maps.roughness, maps.ao, maps.metalness, maps.emissive, maps.height]);
|
|
141
|
-
const textures = useTexture(entries);
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (textures.color !== undefined)
|
|
144
|
-
textures.color.colorSpace = THREE.SRGBColorSpace;
|
|
145
|
-
applyMaterialOverride(scene, {}, { clone: false, textures });
|
|
146
|
-
}, [scene, textures]);
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
129
|
export function EntityModel({ model, instanceId, measure, }) {
|
|
150
|
-
|
|
130
|
+
parseModelHiddenNodes(model.hiddenNodes);
|
|
131
|
+
const renderer = useThree(state => state.gl);
|
|
132
|
+
if (renderer.isWebGLRenderer)
|
|
133
|
+
detectKtx2Support(renderer);
|
|
134
|
+
const assets = modelAssetRequests(model);
|
|
135
|
+
for (const url of assets.models)
|
|
136
|
+
useLoader.preload(sharedGltfLoader, url);
|
|
151
137
|
// Optional, not required: measured bounds and paint strokes are live-world extras, and a model
|
|
152
138
|
// that threw without a running game could not be inspected outside one — which is how a broken
|
|
153
139
|
// composition stayed undiagnosable in `EntityPreview` (#1588).
|
|
154
140
|
const ctx = useOptionalGameContext();
|
|
155
141
|
const material = model.material;
|
|
142
|
+
const materialAssetKey = JSON.stringify((model.materialAssets ?? []).filter(asset => model.materialAssignments?.some(assignment => assignment.materialId === asset.id)));
|
|
143
|
+
const materialAssets = useMemo(() => (model.materialAssets ?? []).filter(asset => model.materialAssignments?.some(assignment => assignment.materialId === asset.id)), [materialAssetKey]);
|
|
144
|
+
const materialAssignments = model.materialAssignments;
|
|
145
|
+
const assignmentSource = useLoader(sharedGltfLoader, materialAssignments?.length ? [model.url] : [])[0]?.scene;
|
|
146
|
+
const assignmentKey = JSON.stringify(materialAssignments);
|
|
147
|
+
useMemo(() => {
|
|
148
|
+
if (assignmentSource !== undefined)
|
|
149
|
+
validateMaterialAssignmentTargets(assignmentSource, materialAssets, materialAssignments, { baseAlphaMode: material?.alphaMode });
|
|
150
|
+
}, [assignmentSource, materialAssets, assignmentKey, material?.alphaMode]);
|
|
151
|
+
for (const urls of assets.textureGroups)
|
|
152
|
+
useLoader.preload(MaterialTextureLoader, urls);
|
|
153
|
+
const materialTextures = useModelMaterialTextures(model);
|
|
156
154
|
const baseY = model.y ?? 0;
|
|
157
155
|
const dims = model.dims;
|
|
158
|
-
const
|
|
159
|
-
const scene = useMemo(() => {
|
|
160
|
-
const cloned = cloneModelScene(gltf.scene, { shadows });
|
|
156
|
+
const configure = useCallback((content) => {
|
|
161
157
|
if (material !== undefined)
|
|
162
|
-
applyMaterialOverride(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
const box = new THREE.Box3().setFromObject(scene);
|
|
169
|
-
const height = box.max.y - box.min.y;
|
|
170
|
-
if (!Number.isFinite(height) || height <= 0)
|
|
171
|
-
return null;
|
|
172
|
-
return {
|
|
173
|
-
normalize: model.targetHeight / height,
|
|
174
|
-
minY: box.min.y,
|
|
175
|
-
centerX: (box.min.x + box.max.x) / 2,
|
|
176
|
-
centerZ: (box.min.z + box.max.z) / 2,
|
|
177
|
-
};
|
|
178
|
-
}, [scene, model.targetHeight]);
|
|
179
|
-
const scale = (model.scale ?? 1) * (measured?.normalize ?? 1);
|
|
180
|
-
const centered = (model.anchor ?? "center") === "center" && dims !== undefined;
|
|
181
|
-
const position = measured !== null
|
|
182
|
-
? [-scale * measured.centerX, baseY - scale * measured.minY, -scale * measured.centerZ]
|
|
183
|
-
: centered
|
|
184
|
-
? [-scale * dims.center.x, baseY - scale * dims.minY, -scale * dims.center.z]
|
|
185
|
-
: [0, baseY, 0];
|
|
186
|
-
useEffect(() => () => {
|
|
187
|
-
disposeClonedMaterials(scene);
|
|
188
|
-
}, [scene]);
|
|
158
|
+
applyMaterialOverride(content, material, { clone: false, textures: materialTextures.global, ownMaterial: replacement => ownModelMaterial(content, replacement) });
|
|
159
|
+
if (materialAssignments !== undefined)
|
|
160
|
+
applyMaterialAssignments(content, materialAssets ?? [], materialAssignments, materialTextures.assets);
|
|
161
|
+
}, [material, materialTextures, materialAssets, materialAssignments]);
|
|
162
|
+
const { scene, scale, position } = useModelInstance(model, { instanceId, configure });
|
|
189
163
|
// A model without index-measured dims can't drive the fitted-collider path, so report the live
|
|
190
164
|
// measurement (the primitive mounts with only this uniform scale + position) instead of letting
|
|
191
165
|
// the kind fall back to a fixed-size box. Index dims keep priority — skip when they exist.
|
|
@@ -193,7 +167,8 @@ export function EntityModel({ model, instanceId, measure, }) {
|
|
|
193
167
|
const measureKey = measure?.key;
|
|
194
168
|
const dimsMaxY = dims?.maxY;
|
|
195
169
|
const [positionX, positionY, positionZ] = position;
|
|
196
|
-
|
|
170
|
+
// Measure before the instance hook's passive animation effects can move imported root nodes.
|
|
171
|
+
useLayoutEffect(() => {
|
|
197
172
|
if (ctx === null || measureTarget === undefined || measureKey === undefined || dimsMaxY !== undefined)
|
|
198
173
|
return;
|
|
199
174
|
const raw = measureLocalBounds(scene);
|
|
@@ -207,7 +182,7 @@ export function EntityModel({ model, instanceId, measure, }) {
|
|
|
207
182
|
}, [ctx, scene, scale, positionX, positionY, positionZ, measureTarget, measureKey, dimsMaxY]);
|
|
208
183
|
// Runs with or without index dims: the loaded geometry upgrades whatever box the kind resolved
|
|
209
184
|
// (fitted or measured) to a hitbox that raycasts the model's own triangles.
|
|
210
|
-
|
|
185
|
+
useLayoutEffect(() => {
|
|
211
186
|
if (ctx === null || measureTarget === undefined || measureKey === undefined)
|
|
212
187
|
return;
|
|
213
188
|
const triangles = measureLocalCollisionTriangles(scene, {
|
|
@@ -217,7 +192,6 @@ export function EntityModel({ model, instanceId, measure, }) {
|
|
|
217
192
|
if (triangles !== null)
|
|
218
193
|
reportMeasuredCollisionMesh(ctx, measureTarget, measureKey, triangles);
|
|
219
194
|
}, [ctx, scene, scale, positionX, positionY, positionZ, measureTarget, measureKey]);
|
|
220
|
-
useModelAnimation(scene, gltf.animations, model.animation, instanceId);
|
|
221
195
|
useFootIk(scene, model.ik, ctx, instanceId, baseY);
|
|
222
196
|
const paintCanvasRef = useRef(null);
|
|
223
197
|
const paintDrawnCountRef = useRef(0);
|
|
@@ -255,7 +229,7 @@ export function EntityModel({ model, instanceId, measure, }) {
|
|
|
255
229
|
}
|
|
256
230
|
paintDrawnCountRef.current = syncPaintCanvas(paintCanvasRef.current, cache.seedColor, strokes, paintDrawnCountRef.current);
|
|
257
231
|
});
|
|
258
|
-
const base = (_jsxs(_Fragment, { children: [_jsx("primitive", { object: scene, position: position, scale: [scale, scale, scale] }),
|
|
232
|
+
const base = (_jsxs(_Fragment, { children: [_jsx("primitive", { object: scene, position: position, scale: [scale, scale, scale] }), (model.attachments ?? []).map((attachment, index) => typeof attachment.model === "string" ? null : (_jsx(IsolatedModelPart, { model: attachment.model, children: _jsx(BoneAttachment, { rig: scene, model: attachment.model, slot: attachment.slot, position: attachment.position, rotation: attachment.rotation, scale: attachment.scale }) }, `${attachment.slot}-${index}`)))] }));
|
|
259
233
|
const parts = model.parts ?? [];
|
|
260
234
|
// Any role-tagged part switches the composition onto the procedural part-motion rig:
|
|
261
235
|
// root bob/flinch/topple plus per-role limb swing, driven from the entity's live state.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { ModelMaterialOverride } from "@jgengine/core/game/playableGame";
|
|
3
|
+
import { type MaterialOverrideTextures } from "../materialOverride.js";
|
|
4
|
+
/** Serializable PBR settings for an authored primitive surface. Map roles use the model-material contract. */
|
|
5
|
+
export interface AuthoredSurfaceConfig extends Omit<ModelMaterialOverride, "anisotropy" | "normalScale"> {
|
|
6
|
+
/** Physical metres per texture tile on unit boxes/cylinders, including instance and parent scale. Omit to retain authored UVs. */
|
|
7
|
+
repeatMetres?: number;
|
|
8
|
+
/** Texture sampler policy on owned clones; defaults to repeat on both axes and anisotropy 1. */
|
|
9
|
+
wrapping?: "repeat" | "clamp" | "mirror";
|
|
10
|
+
anisotropy?: number;
|
|
11
|
+
/** Physical directional highlight strength [0,1]; `anisotropy` remains the legacy texture sampler setting. */
|
|
12
|
+
surfaceAnisotropy?: number;
|
|
13
|
+
normalScale?: readonly [number, number];
|
|
14
|
+
/** Normalized [0,1] height maps displace in local shape units (Three defaults: scale 1, bias 0). */
|
|
15
|
+
displacementScale?: number;
|
|
16
|
+
displacementBias?: number;
|
|
17
|
+
transparent?: boolean;
|
|
18
|
+
opacity?: number;
|
|
19
|
+
depthWrite?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Primitive UV layout whose physical dimensions drive `repeatMetres`. */
|
|
22
|
+
export type SurfaceShape = "box" | "cylinder";
|
|
23
|
+
/** Stable material identity, independent of object/map property insertion order. @internal */
|
|
24
|
+
export declare function authoredSurfaceKey(config: AuthoredSurfaceConfig): string;
|
|
25
|
+
/** Configure owned map clones without changing textures retained in a loader cache. @internal */
|
|
26
|
+
export declare function authoredSurfaceTextures(entries: Record<string, string>, sources: readonly THREE.Texture[], config?: AuthoredSurfaceConfig): MaterialOverrideTextures;
|
|
27
|
+
/** Apply PBR settings and optional metre UVs to an owned standard material. @internal */
|
|
28
|
+
export declare function configureAuthoredSurface(material: THREE.MeshStandardMaterial, config: AuthoredSurfaceConfig, textures: MaterialOverrideTextures, shape: SurfaceShape): void;
|
|
29
|
+
/**
|
|
30
|
+
* Load declared surface-map roles and own the material/map clones (cached textures remain untouched).
|
|
31
|
+
* Colour, emissive, sheen-colour and specular-colour maps are sRGB; numeric maps are linear. Cylinder side metres use the RMS ellipse circumference approximation.
|
|
32
|
+
* Static batches and custom meshes share this lifecycle.
|
|
33
|
+
* @capability authored-surface-material render declared PBR maps with colour-space handling and metre-scale box/cylinder UVs
|
|
34
|
+
*/
|
|
35
|
+
export declare function useAuthoredSurfaceMaterial(config: AuthoredSurfaceConfig, shape?: SurfaceShape): THREE.MeshStandardMaterial;
|
|
36
|
+
/**
|
|
37
|
+
* Mount an owned PBR surface material on a mesh.
|
|
38
|
+
* @capability authored-surface-material mount an owned PBR surface material on a mesh
|
|
39
|
+
*/
|
|
40
|
+
export declare function AuthoredSurfaceMaterial({ surface, shape }: {
|
|
41
|
+
surface: AuthoredSurfaceConfig;
|
|
42
|
+
shape?: SurfaceShape;
|
|
43
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLoader } from "@react-three/fiber";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import * as THREE from "three";
|
|
5
|
+
import { MATERIAL_TEXTURE_SEMANTICS } from "@jgengine/core/material/materialAsset";
|
|
6
|
+
import { applyMaterialOverrideToMaterial, requiresPhysicalMaterial } from "../materialOverride.js";
|
|
7
|
+
import { modelMapEntries } from "./modelAssets.js";
|
|
8
|
+
import { useDisposable } from "./useDisposable.js";
|
|
9
|
+
/** Stable material identity, independent of object/map property insertion order. @internal */
|
|
10
|
+
export function authoredSurfaceKey(config) {
|
|
11
|
+
return JSON.stringify(Object.entries({ ...config, maps: modelMapEntries(config.maps ?? {}) }).sort(([a], [b]) => a.localeCompare(b)));
|
|
12
|
+
}
|
|
13
|
+
/** Configure owned map clones without changing textures retained in a loader cache. @internal */
|
|
14
|
+
export function authoredSurfaceTextures(entries, sources, config = {}) {
|
|
15
|
+
const wrapping = config.wrapping === "clamp" ? THREE.ClampToEdgeWrapping : config.wrapping === "mirror" ? THREE.MirroredRepeatWrapping : THREE.RepeatWrapping;
|
|
16
|
+
return Object.fromEntries(Object.keys(entries).map((role, index) => {
|
|
17
|
+
const texture = sources[index].clone();
|
|
18
|
+
texture.colorSpace = MATERIAL_TEXTURE_SEMANTICS[role]?.colorSpace === "srgb" ? THREE.SRGBColorSpace : THREE.NoColorSpace;
|
|
19
|
+
texture.wrapS = wrapping;
|
|
20
|
+
texture.wrapT = wrapping;
|
|
21
|
+
texture.anisotropy = config.anisotropy ?? 1;
|
|
22
|
+
texture.needsUpdate = true;
|
|
23
|
+
return [role, texture];
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/** Apply PBR settings and optional metre UVs to an owned standard material. @internal */
|
|
27
|
+
export function configureAuthoredSurface(material, config, textures, shape) {
|
|
28
|
+
const tile = config.repeatMetres;
|
|
29
|
+
if (tile !== undefined && (!Number.isFinite(tile) || tile <= 0))
|
|
30
|
+
throw new Error("repeatMetres must be finite and positive");
|
|
31
|
+
if (config.normalScale?.some(value => !Number.isFinite(value)))
|
|
32
|
+
throw new Error("normalScale must be finite");
|
|
33
|
+
if (config.anisotropy !== undefined && (!Number.isFinite(config.anisotropy) || config.anisotropy < 1))
|
|
34
|
+
throw new Error("anisotropy must be finite and >= 1");
|
|
35
|
+
if ([config.displacementScale, config.displacementBias].some(value => value !== undefined && !Number.isFinite(value)))
|
|
36
|
+
throw new Error("displacement settings must be finite");
|
|
37
|
+
const { anisotropy: _samplerAnisotropy, surfaceAnisotropy, ...surface } = config;
|
|
38
|
+
if (requiresPhysicalMaterial({ ...surface, normalScale: surface.normalScale === undefined ? undefined : [...surface.normalScale], anisotropy: surfaceAnisotropy }, textures) && !material.isMeshPhysicalMaterial)
|
|
39
|
+
throw new Error("Physical authored surface settings require MeshPhysicalMaterial");
|
|
40
|
+
applyMaterialOverrideToMaterial(material, { ...surface, normalScale: surface.normalScale === undefined ? undefined : [...surface.normalScale], anisotropy: surfaceAnisotropy }, false, textures);
|
|
41
|
+
if (config.displacementScale !== undefined)
|
|
42
|
+
material.displacementScale = config.displacementScale;
|
|
43
|
+
if (config.displacementBias !== undefined)
|
|
44
|
+
material.displacementBias = config.displacementBias;
|
|
45
|
+
if (config.transparent !== undefined)
|
|
46
|
+
material.transparent = config.transparent;
|
|
47
|
+
if (config.opacity !== undefined)
|
|
48
|
+
material.opacity = config.opacity;
|
|
49
|
+
if (config.depthWrite !== undefined)
|
|
50
|
+
material.depthWrite = config.depthWrite;
|
|
51
|
+
if (config.normalScale !== undefined)
|
|
52
|
+
material.normalScale.set(...config.normalScale);
|
|
53
|
+
if (tile === undefined)
|
|
54
|
+
return;
|
|
55
|
+
const previousCompile = material.onBeforeCompile;
|
|
56
|
+
const previousKey = material.customProgramCacheKey();
|
|
57
|
+
material.onBeforeCompile = (shader, renderer) => {
|
|
58
|
+
previousCompile.call(material, shader, renderer);
|
|
59
|
+
shader.vertexShader = shader.vertexShader.replace("#include <uv_vertex>", `#include <uv_vertex>
|
|
60
|
+
mat4 surfaceTransform = modelMatrix;
|
|
61
|
+
#ifdef USE_INSTANCING
|
|
62
|
+
surfaceTransform = modelMatrix * instanceMatrix;
|
|
63
|
+
#endif
|
|
64
|
+
vec3 surfaceExtent = vec3(length(surfaceTransform[0].xyz), length(surfaceTransform[1].xyz), length(surfaceTransform[2].xyz));
|
|
65
|
+
vec2 surfaceRepeat = ${shape === "box"
|
|
66
|
+
? "abs(normal.x) > 0.5 ? surfaceExtent.zy : abs(normal.y) > 0.5 ? surfaceExtent.xz : surfaceExtent.xy"
|
|
67
|
+
: "abs(normal.y) > 0.5 ? surfaceExtent.xz * 2.0 : vec2(6.28318530718 * sqrt((surfaceExtent.x * surfaceExtent.x + surfaceExtent.z * surfaceExtent.z) * 0.5), surfaceExtent.y)"};
|
|
68
|
+
surfaceRepeat /= ${tile.toExponential(8)};
|
|
69
|
+
${Object.entries({ MAP: "vMapUv", NORMALMAP: "vNormalMapUv", ROUGHNESSMAP: "vRoughnessMapUv", AOMAP: "vAoMapUv", METALNESSMAP: "vMetalnessMapUv", EMISSIVEMAP: "vEmissiveMapUv", DISPLACEMENTMAP: "vDisplacementMapUv", ALPHAMAP: "vAlphaMapUv", SHEENCOLORMAP: "vSheenColorMapUv", SHEENROUGHNESSMAP: "vSheenRoughnessMapUv", ANISOTROPYMAP: "vAnisotropyMapUv", CLEARCOATMAP: "vClearcoatMapUv", CLEARCOATROUGHNESSMAP: "vClearcoatRoughnessMapUv", CLEARCOATNORMALMAP: "vClearcoatNormalMapUv", SPECULARINTENSITYMAP: "vSpecularIntensityMapUv", SPECULARCOLORMAP: "vSpecularColorMapUv", TRANSMISSIONMAP: "vTransmissionMapUv", THICKNESSMAP: "vThicknessMapUv", IRIDESCENCEMAP: "vIridescenceMapUv", IRIDESCENCETHICKNESSMAP: "vIridescenceThicknessMapUv" }).map(([role, name]) => {
|
|
70
|
+
return `#ifdef USE_${role}\n ${name} *= surfaceRepeat;\n#endif`;
|
|
71
|
+
}).join("\n")}`);
|
|
72
|
+
};
|
|
73
|
+
material.customProgramCacheKey = () => `${previousKey}|authored-surface:${shape}:${tile}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Load declared surface-map roles and own the material/map clones (cached textures remain untouched).
|
|
77
|
+
* Colour, emissive, sheen-colour and specular-colour maps are sRGB; numeric maps are linear. Cylinder side metres use the RMS ellipse circumference approximation.
|
|
78
|
+
* Static batches and custom meshes share this lifecycle.
|
|
79
|
+
* @capability authored-surface-material render declared PBR maps with colour-space handling and metre-scale box/cylinder UVs
|
|
80
|
+
*/
|
|
81
|
+
export function useAuthoredSurfaceMaterial(config, shape = "box") {
|
|
82
|
+
const key = authoredSurfaceKey(config);
|
|
83
|
+
const entries = useMemo(() => modelMapEntries(config.maps ?? {}), [key]);
|
|
84
|
+
const loaded = useLoader(THREE.TextureLoader, Object.values(entries));
|
|
85
|
+
const resources = useDisposable(() => {
|
|
86
|
+
const textures = authoredSurfaceTextures(entries, loaded, config);
|
|
87
|
+
const { anisotropy: _samplerAnisotropy, surfaceAnisotropy, ...surface } = config;
|
|
88
|
+
const material = requiresPhysicalMaterial({ ...surface, normalScale: surface.normalScale === undefined ? undefined : [...surface.normalScale], anisotropy: surfaceAnisotropy }, textures) ? new THREE.MeshPhysicalMaterial() : new THREE.MeshStandardMaterial();
|
|
89
|
+
try {
|
|
90
|
+
configureAuthoredSurface(material, config, textures, shape);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
material.dispose();
|
|
94
|
+
Object.values(textures).forEach(texture => texture.dispose());
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return [material, ...Object.values(textures)];
|
|
98
|
+
}, [key, entries, loaded, shape]);
|
|
99
|
+
return resources[0];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Mount an owned PBR surface material on a mesh.
|
|
103
|
+
* @capability authored-surface-material mount an owned PBR surface material on a mesh
|
|
104
|
+
*/
|
|
105
|
+
export function AuthoredSurfaceMaterial({ surface, shape = "box" }) {
|
|
106
|
+
const material = useAuthoredSurfaceMaterial(surface, shape);
|
|
107
|
+
return _jsx("primitive", { object: material, attach: "material" });
|
|
108
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { type MaterialAppearanceBaseline, type MaterialAppearancePolicy, type MaterialAppearanceResponse } from "@jgengine/core/material/appearanceSignals";
|
|
3
|
+
import type { EnvironmentSample } from "@jgengine/core/world/envField";
|
|
4
|
+
/** Immutable resolved baseline for one caller-owned assigned material, including its original card exposure. */
|
|
5
|
+
export interface RenderMaterialAppearanceBaseline extends MaterialAppearanceBaseline {
|
|
6
|
+
cardBacklightExposure: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Capture once after assignment; recapture after an authored change. Never capture an already wet material.
|
|
10
|
+
* @capability material-appearance-signals capture immutable resolved dry values from one owned assigned material
|
|
11
|
+
*/
|
|
12
|
+
export declare function captureMaterialAppearanceBaseline(material: THREE.MeshStandardMaterial): RenderMaterialAppearanceBaseline;
|
|
13
|
+
/**
|
|
14
|
+
* Apply one sample to an already owned material. Imported/cache materials must be cloned before use.
|
|
15
|
+
* Allocates no textures or materials; clearcoat/sheen require an existing physical material and opt-in policy.
|
|
16
|
+
* @capability material-appearance-signals update one owned material from wetness and local card exposure without global lighting edits
|
|
17
|
+
*/
|
|
18
|
+
export declare function applyMaterialAppearanceSignals(material: THREE.MeshStandardMaterial, baseline: MaterialAppearanceBaseline, sample: EnvironmentSample, policy?: MaterialAppearancePolicy): MaterialAppearanceResponse;
|
|
19
|
+
/**
|
|
20
|
+
* Restore the dry baseline when removing an appearance binding; maps and unrelated imported features remain borrowed.
|
|
21
|
+
* @capability material-appearance-signals restore dry material response when removing an environment appearance binding
|
|
22
|
+
*/
|
|
23
|
+
export declare function restoreMaterialAppearanceBaseline(material: THREE.MeshStandardMaterial, baseline: RenderMaterialAppearanceBaseline): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { resolveMaterialAppearance } from "@jgengine/core/material/appearanceSignals";
|
|
3
|
+
import { setHairCardLightExposure } from "./materialAsset.js";
|
|
4
|
+
/**
|
|
5
|
+
* Capture once after assignment; recapture after an authored change. Never capture an already wet material.
|
|
6
|
+
* @capability material-appearance-signals capture immutable resolved dry values from one owned assigned material
|
|
7
|
+
*/
|
|
8
|
+
export function captureMaterialAppearanceBaseline(material) {
|
|
9
|
+
const physical = material;
|
|
10
|
+
return Object.freeze({
|
|
11
|
+
roughness: material.roughness,
|
|
12
|
+
colorLinear: Object.freeze([material.color.r, material.color.g, material.color.b]),
|
|
13
|
+
...(physical.isMeshPhysicalMaterial ? { sheen: physical.sheen, clearcoat: physical.clearcoat, clearcoatRoughness: physical.clearcoatRoughness } : {}),
|
|
14
|
+
cardBacklightExposure: material.userData.jgHairLightExposure ?? 1,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Apply one sample to an already owned material. Imported/cache materials must be cloned before use.
|
|
19
|
+
* Allocates no textures or materials; clearcoat/sheen require an existing physical material and opt-in policy.
|
|
20
|
+
* @capability material-appearance-signals update one owned material from wetness and local card exposure without global lighting edits
|
|
21
|
+
*/
|
|
22
|
+
export function applyMaterialAppearanceSignals(material, baseline, sample, policy = {}) {
|
|
23
|
+
if ((policy.wetness?.sheen !== undefined || policy.wetness?.clearcoat !== undefined) && !material.isMeshPhysicalMaterial)
|
|
24
|
+
throw new TypeError("Wet sheen or clearcoat requires an owned MeshPhysicalMaterial; declare the capability when assigning it.");
|
|
25
|
+
const response = resolveMaterialAppearance(sample, baseline, policy);
|
|
26
|
+
const physical = material;
|
|
27
|
+
if (response.surface.roughness !== undefined)
|
|
28
|
+
material.roughness = response.surface.roughness;
|
|
29
|
+
if (response.colorLinear !== undefined)
|
|
30
|
+
material.color.setRGB(...response.colorLinear, THREE.LinearSRGBColorSpace);
|
|
31
|
+
if (response.surface.sheen !== undefined)
|
|
32
|
+
physical.sheen = response.surface.sheen;
|
|
33
|
+
if (response.surface.clearcoat !== undefined)
|
|
34
|
+
physical.clearcoat = response.surface.clearcoat;
|
|
35
|
+
if (response.surface.clearcoatRoughness !== undefined)
|
|
36
|
+
physical.clearcoatRoughness = response.surface.clearcoatRoughness;
|
|
37
|
+
if (response.backlightExposure !== undefined)
|
|
38
|
+
setHairCardLightExposure(material, response.backlightExposure);
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Restore the dry baseline when removing an appearance binding; maps and unrelated imported features remain borrowed.
|
|
43
|
+
* @capability material-appearance-signals restore dry material response when removing an environment appearance binding
|
|
44
|
+
*/
|
|
45
|
+
export function restoreMaterialAppearanceBaseline(material, baseline) {
|
|
46
|
+
if ((baseline.sheen !== undefined || baseline.clearcoat !== undefined) && !material.isMeshPhysicalMaterial)
|
|
47
|
+
throw new TypeError("The appearance baseline belongs to a physical material.");
|
|
48
|
+
material.roughness = baseline.roughness;
|
|
49
|
+
material.color.setRGB(...baseline.colorLinear, THREE.LinearSRGBColorSpace);
|
|
50
|
+
const physical = material;
|
|
51
|
+
if (baseline.sheen !== undefined)
|
|
52
|
+
physical.sheen = baseline.sheen;
|
|
53
|
+
if (baseline.clearcoat !== undefined)
|
|
54
|
+
physical.clearcoat = baseline.clearcoat;
|
|
55
|
+
if (baseline.clearcoatRoughness !== undefined)
|
|
56
|
+
physical.clearcoatRoughness = baseline.clearcoatRoughness;
|
|
57
|
+
setHairCardLightExposure(material, baseline.cardBacklightExposure);
|
|
58
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ModelConfig } from "@jgengine/core/game/playableGame";
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
import { type MaterialAsset, type MaterialAssignment, type MaterialAssignmentValidationOptions, type MaterialSurfaceParameters, type MaterialTextureMetadata, type MaterialTextureRole } from "@jgengine/core/material/materialAsset";
|
|
4
|
+
import { type MaterialOverrideTextures } from "../materialOverride.js";
|
|
5
|
+
import { type SurfaceShape } from "./authoredSurfaceMaterial.js";
|
|
6
|
+
/** Shares the model transcoder and loader cache while loading native authored images normally. @internal */
|
|
7
|
+
export declare class MaterialTextureLoader extends THREE.Loader<THREE.Texture> {
|
|
8
|
+
load(url: string, onLoad?: (texture: THREE.Texture) => void, onProgress?: (event: ProgressEvent) => void, onError?: (error: unknown) => void): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate and configure an owned texture view while retaining the loader's immutable source.
|
|
12
|
+
* @capability material-texture-metadata configure owned map views with semantic color space, packed channels, UV transforms and sampling
|
|
13
|
+
*/
|
|
14
|
+
export declare function configureMaterialTexture(role: MaterialTextureRole, metadata: MaterialTextureMetadata, source: THREE.Texture): THREE.Texture;
|
|
15
|
+
/**
|
|
16
|
+
* Physical defaults adapt a declared construction; explicit authored parameters remain authoritative.
|
|
17
|
+
* @capability material-assets resolve fabric and hair construction into sparse physical appearance parameters
|
|
18
|
+
*/
|
|
19
|
+
export declare function materialAssetSurface(asset: MaterialAsset, overrides?: MaterialSurfaceParameters): MaterialSurfaceParameters;
|
|
20
|
+
/**
|
|
21
|
+
* Apply one reusable material's parameters, map views and construction to an imported PBR slot.
|
|
22
|
+
* @capability material-assets clone and configure an imported PBR slot while retaining its omitted physical features and maps
|
|
23
|
+
*/
|
|
24
|
+
export declare function applyMaterialAsset(material: THREE.Material, asset: MaterialAsset, textures?: MaterialOverrideTextures, overrides?: MaterialSurfaceParameters): THREE.MeshStandardMaterial;
|
|
25
|
+
/** Read-only target validation policy; source coverage can reflect an earlier all-slot override. */
|
|
26
|
+
export interface MaterialAssignmentTargetOptions extends MaterialAssignmentValidationOptions {
|
|
27
|
+
/** Coverage applied to every imported slot before named assignments; never mutates the source. */
|
|
28
|
+
baseAlphaMode?: MaterialSurfaceParameters["alphaMode"];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Check selectors, PBR slots, UVs and construction prerequisites before allocating material or texture views.
|
|
32
|
+
* Optional role restrictions apply only to referenced assets; imported maps remain untouched.
|
|
33
|
+
* @capability material-slot-selection preflight native model assignments without allocating or changing imported resources
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateMaterialAssignmentTargets(root: THREE.Object3D, assets: readonly MaterialAsset[], assignments: readonly MaterialAssignment[], options?: MaterialAssignmentTargetOptions): void;
|
|
36
|
+
/**
|
|
37
|
+
* Apply intersecting mesh/slot selectors without changing omitted imported slots. Returns caller-owned replacements; model clones register them only after success.
|
|
38
|
+
* @capability material-assets assign reusable physical assets to independent imported model slots
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyMaterialAssignments(root: THREE.Object3D, assets: readonly MaterialAsset[], assignments: readonly MaterialAssignment[], textures?: ReadonlyMap<string, MaterialOverrideTextures>): THREE.Material[];
|
|
41
|
+
/** Reusable primitive surface options; physical metre UVs reuse the instanced surface renderer. */
|
|
42
|
+
export interface MaterialAssetSurfaceOptions {
|
|
43
|
+
shape?: SurfaceShape;
|
|
44
|
+
repeatMetres?: number;
|
|
45
|
+
overrides?: MaterialSurfaceParameters;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load map views, own their cleanup and build the same physical shader used by model assignments.
|
|
49
|
+
* @capability material-assets own a serializable material asset's textures and runtime physical shader for a custom renderer
|
|
50
|
+
*/
|
|
51
|
+
export declare function useMaterialAssetMaterial(asset: MaterialAsset, options?: MaterialAssetSurfaceOptions): THREE.MeshStandardMaterial;
|
|
52
|
+
/**
|
|
53
|
+
* Mount a serializable material asset on an authored primitive, sharing model-slot rendering and owned resource cleanup.
|
|
54
|
+
* @capability material-assets render authored physical surfaces, woven detail and approximate hair-card appearance
|
|
55
|
+
*/
|
|
56
|
+
export declare function MaterialAssetSurface({ asset, ...options }: {
|
|
57
|
+
asset: MaterialAsset;
|
|
58
|
+
} & MaterialAssetSurfaceOptions): import("react").JSX.Element;
|
|
59
|
+
/**
|
|
60
|
+
* Estimate texture allocation from loaded dimensions/mips and report pass/coverage risks. Compressed maps count actual mip bytes; uncompressed bytes bound texture views that may share uploads. This does not measure pixel overdraw.
|
|
61
|
+
* @capability material-resource-metrics inspect loaded texture byte bounds and physical, coverage and transmission participation
|
|
62
|
+
*/
|
|
63
|
+
export declare function materialResourceMetrics(materials: readonly THREE.Material[]): {
|
|
64
|
+
materials: number;
|
|
65
|
+
textures: number;
|
|
66
|
+
textureBytes: number;
|
|
67
|
+
unknownTextureSizes: number;
|
|
68
|
+
blendedMaterials: number;
|
|
69
|
+
maskedMaterials: number;
|
|
70
|
+
transmissionMaterials: number;
|
|
71
|
+
doubleSidedMaterials: number;
|
|
72
|
+
physicalMaterials: number;
|
|
73
|
+
};
|
|
74
|
+
/** Runtime imported-slot inventory for precise authoring selectors; indices address the mesh material array. */
|
|
75
|
+
export interface ModelMaterialSlotInfo {
|
|
76
|
+
mesh: string;
|
|
77
|
+
slot: string;
|
|
78
|
+
slotIndex: number;
|
|
79
|
+
materialType: string;
|
|
80
|
+
uvSets: number[];
|
|
81
|
+
tangents: boolean;
|
|
82
|
+
physical: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Inspect imported names, local slot indices and directional/map prerequisites without mutating the source.
|
|
86
|
+
* @capability material-slots discover exact imported mesh and material selectors with UV and tangent prerequisites
|
|
87
|
+
*/
|
|
88
|
+
export declare function inspectModelMaterialSlots(root: THREE.Object3D): ModelMaterialSlotInfo[];
|
|
89
|
+
/** Loaded, owned map views for one model configuration. Loader sources and imported maps remain borrowed. @internal */
|
|
90
|
+
export declare function useModelMaterialTextures(model: Pick<ModelConfig, "material" | "materialAssets" | "materialAssignments">): {
|
|
91
|
+
global: MaterialOverrideTextures;
|
|
92
|
+
assets: ReadonlyMap<string, MaterialOverrideTextures>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Gate the existing thin-sheet hair-card scatter by sampled local light exposure, without changing scene exposure.
|
|
96
|
+
* @capability material-appearance-signals apply sampled local exposure to the hair-card backlight approximation without global lighting edits
|
|
97
|
+
*/
|
|
98
|
+
export declare function setHairCardLightExposure(material: THREE.Material, exposure: number): void;
|