@jgengine/core 0.18.1 → 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 +504 -7
- package/dist/ai/decisionGraph.d.ts +50 -4
- package/dist/ai/decisionGraph.js +159 -23
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +43 -1
- package/dist/anim/animGraph.js +326 -10
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/locomotionGraph.d.ts +18 -1
- package/dist/anim/locomotionGraph.js +42 -3
- package/dist/audio/audioFalloff.d.ts +6 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +20 -0
- package/dist/combat/abilityKit.js +41 -4
- package/dist/combat/death.d.ts +11 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +119 -18
- package/dist/editor/environment.js +8 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/types.d.ts +15 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +54 -2
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +11 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +134 -35
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/input/actionBindings.d.ts +15 -1
- package/dist/input/actionBindings.js +14 -3
- package/dist/input/actionContexts.d.ts +25 -1
- package/dist/input/actionContexts.js +82 -25
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +36 -2
- package/dist/input/gamepadModel.js +69 -30
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +444 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +63 -1
- package/dist/movement/movementModel.js +136 -26
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +48 -2
- package/dist/movement/playerMovement.js +493 -87
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +11 -1
- package/dist/movement/solidObstacles.js +48 -3
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +10 -10
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/navMesh.d.ts +43 -2
- package/dist/nav/navMesh.js +534 -128
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +21 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +40 -5
- package/dist/physics/kinematicVehicle.js +17 -2
- package/dist/physics/physicsBackend.d.ts +12 -0
- package/dist/physics/physicsBackendConformance.js +34 -0
- package/dist/physics/physicsWorldBackend.d.ts +1 -0
- package/dist/physics/physicsWorldBackend.js +52 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +375 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/physics/worldColliders.d.ts +1 -1
- package/dist/physics/worldColliders.js +35 -3
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/render/lookPreset.d.ts +9 -2
- package/dist/render/lookPreset.js +29 -2
- package/dist/render/postProcessing.d.ts +30 -0
- package/dist/render/postProcessing.js +1 -1
- package/dist/runtime/cameraDirector.d.ts +54 -3
- package/dist/runtime/cameraDirector.js +187 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/commandInput.d.ts +37 -0
- package/dist/runtime/commandInput.js +58 -0
- package/dist/runtime/commandRunner.d.ts +27 -0
- package/dist/runtime/commandRunner.js +31 -3
- package/dist/runtime/context/combat.d.ts +12 -4
- package/dist/runtime/context/combat.js +179 -11
- package/dist/runtime/context/deathLoot.d.ts +11 -8
- package/dist/runtime/context/deathLoot.js +17 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +86 -13
- package/dist/runtime/gameContextTypes.d.ts +33 -3
- package/dist/runtime/gameRuntime.d.ts +3 -1
- package/dist/runtime/gameRuntime.js +4 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +22 -4
- package/dist/runtime/inputSnapshot.js +41 -3
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/transport.d.ts +9 -0
- package/dist/runtime/transport.js +7 -1
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +38 -7
- package/dist/scene/behaviorRuntime.js +293 -30
- package/dist/scene/behaviors.d.ts +35 -2
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +41 -3
- package/dist/scene/vehicleSeat.js +94 -3
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +11 -1
- package/dist/sensor/perception.js +57 -10
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +2 -0
- package/dist/settings/graphicsProfile.js +12 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +10 -3
- package/dist/stats/eventMeter.js +6 -3
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +11 -4
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/buildings.d.ts +34 -2
- package/dist/world/buildings.js +11 -1
- package/dist/world/cityKind.d.ts +7 -0
- package/dist/world/cityKind.js +52 -1
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +32 -5
- package/dist/world/features.js +6 -0
- package/dist/world/footprintGrid.d.ts +62 -0
- package/dist/world/footprintGrid.js +217 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +9 -1
- package/dist/world/terraform.js +53 -18
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +95 -0
- package/dist/world/worldSolids.js +351 -0
- package/dist/world.d.ts +6 -6
- package/dist/world.js +5 -5
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +2 -2
- package/skills/jgengine/SKILL.md +28 -6
- package/skills/jgengine/api.md +139 -99
- package/skills/jgengine/capabilities.md +28 -0
- package/skills/jgengine/recipes/minimal-game.md +10 -2
- package/skills/jgengine-combat/SKILL.md +13 -2
- package/skills/jgengine-combat/api.md +150 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +583 -463
- package/skills/jgengine-gameplay/capabilities.md +92 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +123 -0
- package/skills/jgengine-world/SKILL.md +25 -4
- package/skills/jgengine-world/api.md +1045 -655
- package/skills/jgengine-world/capabilities.md +246 -9
- package/skills/jgengine-world/recipes/character-animation.md +62 -0
- package/skills/jgengine-world/recipes/controls.md +48 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +55 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +87 -0
- package/skills/jgengine-world/reference.md +180 -4
- package/skills/jgengine-world/visibility.md +139 -0
|
@@ -4,20 +4,124 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @capability item-instance-registry a runtime store for procedurally generated item instances
|
|
6
6
|
*/
|
|
7
|
-
export function createItemInstanceRegistry(prefix = "item") {
|
|
8
|
-
let seq = 0;
|
|
9
|
-
|
|
7
|
+
export function createItemInstanceRegistry(prefix = "item", options = {}) {
|
|
8
|
+
let seq = options.sequence ?? 0;
|
|
9
|
+
if (!Number.isSafeInteger(seq) || seq < 0)
|
|
10
|
+
throw new RangeError("Invalid item allocation sequence");
|
|
11
|
+
let revision = 0;
|
|
12
|
+
let memory = new Map();
|
|
13
|
+
const store = options.storage ?? {
|
|
14
|
+
get: (id) => memory.get(id),
|
|
15
|
+
has: (id) => memory.has(id),
|
|
16
|
+
set: (id, def) => void memory.set(id, def),
|
|
17
|
+
delete: (id) => void memory.delete(id),
|
|
18
|
+
count: () => memory.size,
|
|
19
|
+
entries: () => memory.entries(),
|
|
20
|
+
replace: (entries) => { memory = new Map(entries); },
|
|
21
|
+
};
|
|
22
|
+
const validHeader = (next) => next.prefix === prefix && Number.isSafeInteger(next.sequence) && next.sequence >= 0;
|
|
10
23
|
return {
|
|
24
|
+
state: () => ({ prefix, sequence: seq, entries: structuredClone(Array.from(store.entries())) }),
|
|
25
|
+
statePages(pageSize) {
|
|
26
|
+
if (!Number.isSafeInteger(pageSize) || pageSize < 1)
|
|
27
|
+
throw new RangeError("Invalid item page size");
|
|
28
|
+
const sequence = seq;
|
|
29
|
+
const expectedRevision = revision;
|
|
30
|
+
return (function* () {
|
|
31
|
+
const entries = store.entries()[Symbol.iterator]();
|
|
32
|
+
let done = false;
|
|
33
|
+
let first = true;
|
|
34
|
+
try {
|
|
35
|
+
while (!done) {
|
|
36
|
+
if (revision !== expectedRevision)
|
|
37
|
+
throw new Error("Item registry changed during export");
|
|
38
|
+
const batch = [];
|
|
39
|
+
while (batch.length < pageSize) {
|
|
40
|
+
const next = entries.next();
|
|
41
|
+
if (next.done) {
|
|
42
|
+
done = true;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
batch.push(next.value);
|
|
46
|
+
}
|
|
47
|
+
const detached = structuredClone(batch);
|
|
48
|
+
if (revision !== expectedRevision)
|
|
49
|
+
throw new Error("Item registry changed during export");
|
|
50
|
+
if (first || detached.length > 0)
|
|
51
|
+
yield { prefix, sequence, entries: detached };
|
|
52
|
+
first = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
entries.return?.();
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
},
|
|
60
|
+
restore(next) {
|
|
61
|
+
if (!validHeader(next))
|
|
62
|
+
return false;
|
|
63
|
+
const entries = structuredClone(next.entries);
|
|
64
|
+
store.replace(entries);
|
|
65
|
+
seq = next.sequence;
|
|
66
|
+
revision += 1;
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
restorePages(pages) {
|
|
70
|
+
const iterator = pages[Symbol.iterator]();
|
|
71
|
+
let closed = false;
|
|
72
|
+
const close = () => {
|
|
73
|
+
if (closed)
|
|
74
|
+
return;
|
|
75
|
+
closed = true;
|
|
76
|
+
iterator.return?.();
|
|
77
|
+
};
|
|
78
|
+
try {
|
|
79
|
+
const first = iterator.next();
|
|
80
|
+
if (first.done || !validHeader(first.value))
|
|
81
|
+
return false;
|
|
82
|
+
const sequence = first.value.sequence;
|
|
83
|
+
store.replace((function* () {
|
|
84
|
+
let page = first;
|
|
85
|
+
try {
|
|
86
|
+
while (!page.done) {
|
|
87
|
+
if (!validHeader(page.value) || page.value.sequence !== sequence) {
|
|
88
|
+
throw new Error("Inconsistent item registry pages");
|
|
89
|
+
}
|
|
90
|
+
yield* structuredClone(page.value.entries);
|
|
91
|
+
page = iterator.next();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
close();
|
|
96
|
+
}
|
|
97
|
+
})());
|
|
98
|
+
seq = sequence;
|
|
99
|
+
revision += 1;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
close();
|
|
104
|
+
}
|
|
105
|
+
},
|
|
11
106
|
register(baseId, def) {
|
|
12
|
-
|
|
13
|
-
|
|
107
|
+
let id;
|
|
108
|
+
do {
|
|
109
|
+
if (seq === Number.MAX_SAFE_INTEGER)
|
|
110
|
+
throw new RangeError("Item allocation sequence exhausted");
|
|
111
|
+
seq += 1;
|
|
112
|
+
revision += 1;
|
|
113
|
+
id = `${prefix}:${baseId}:${seq}`;
|
|
114
|
+
} while (store.has(id));
|
|
14
115
|
store.set(id, def);
|
|
15
116
|
return id;
|
|
16
117
|
},
|
|
17
118
|
get: (id) => store.get(id),
|
|
18
119
|
has: (id) => store.has(id),
|
|
19
|
-
release
|
|
20
|
-
|
|
120
|
+
release(id) {
|
|
121
|
+
store.delete(id);
|
|
122
|
+
revision += 1;
|
|
123
|
+
},
|
|
124
|
+
count: () => store.count(),
|
|
21
125
|
};
|
|
22
126
|
}
|
|
23
127
|
/**
|
package/dist/item/itemgen.d.ts
CHANGED
|
@@ -148,7 +148,7 @@ export interface GenSchema {
|
|
|
148
148
|
}
|
|
149
149
|
/** Per-call options for {@link generate}. */
|
|
150
150
|
export interface GenerateOptions {
|
|
151
|
-
/**
|
|
151
|
+
/** Fix a step to an option id that still must satisfy its pool and constraint, consuming no rng for it. */
|
|
152
152
|
pin?: Readonly<Record<string, string>>;
|
|
153
153
|
}
|
|
154
154
|
/**
|
package/dist/item/itemgen.js
CHANGED
|
@@ -41,7 +41,7 @@ export function generate(schema, rng, options = {}) {
|
|
|
41
41
|
const pinId = pin?.[step.id];
|
|
42
42
|
if (pinId !== undefined) {
|
|
43
43
|
const option = poolFor(step.pool, view).find((candidate) => candidate.id === pinId);
|
|
44
|
-
if (option === undefined)
|
|
44
|
+
if (option === undefined || (step.accept !== undefined && !step.accept(option, view)))
|
|
45
45
|
return false;
|
|
46
46
|
chosen.push({
|
|
47
47
|
step: step.id,
|
package/dist/item/modularItem.js
CHANGED
|
@@ -48,7 +48,13 @@ export function isComplete(def, installed) {
|
|
|
48
48
|
return missingRequiredSlots(def, installed).length === 0;
|
|
49
49
|
}
|
|
50
50
|
export function createModularItem(def, initial = []) {
|
|
51
|
-
let installed =
|
|
51
|
+
let installed = [];
|
|
52
|
+
for (const { slotId, part } of initial) {
|
|
53
|
+
const result = install(def, installed, slotId, part);
|
|
54
|
+
if (result.status === "rejected")
|
|
55
|
+
throw new Error(`Invalid initial part in slot ${slotId}: ${result.reason}`);
|
|
56
|
+
installed = result.installed;
|
|
57
|
+
}
|
|
52
58
|
return {
|
|
53
59
|
def,
|
|
54
60
|
parts() {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EnvironmentField, EnvironmentSample } from "../world/envField.js";
|
|
2
|
+
import type { MaterialSurfaceParameters } from "./materialAsset.js";
|
|
3
|
+
/** Resolved dry values from one assigned surface, after imported and instance edits. RGB is linear. */
|
|
4
|
+
export interface MaterialAppearanceBaseline {
|
|
5
|
+
roughness: number;
|
|
6
|
+
colorLinear: readonly [number, number, number];
|
|
7
|
+
sheen?: number;
|
|
8
|
+
clearcoat?: number;
|
|
9
|
+
clearcoatRoughness?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Opt-in wet endpoints. Color retention multiplies linear albedo; it never replaces the authored hue. */
|
|
12
|
+
export interface MaterialWetnessPolicy {
|
|
13
|
+
influence?: number;
|
|
14
|
+
roughness?: number;
|
|
15
|
+
colorRetention?: number;
|
|
16
|
+
sheen?: number;
|
|
17
|
+
/** Requires an already physical material; omitting this preserves imported coat without enabling a new one. */
|
|
18
|
+
clearcoat?: {
|
|
19
|
+
strength: number;
|
|
20
|
+
roughness?: number;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Local appearance only. Wind is a separate geometry/simulation input, and no field creates motion. */
|
|
24
|
+
export interface MaterialAppearancePolicy {
|
|
25
|
+
wetness?: MaterialWetnessPolicy;
|
|
26
|
+
/** Sky-exposure attenuation of existing hair-card backlighting; this is not a shadow or scattering model. */
|
|
27
|
+
cardBacklightExposure?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Sparse edits for one sample. Unrequested imported parameters, textures, coverage and transmission stay intact. */
|
|
30
|
+
export interface MaterialAppearanceResponse {
|
|
31
|
+
surface: MaterialSurfaceParameters;
|
|
32
|
+
colorLinear?: [number, number, number];
|
|
33
|
+
backlightExposure?: number;
|
|
34
|
+
wetness: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve one surface from an immutable dry baseline, avoiding cumulative drift. Wetness already includes
|
|
38
|
+
* field shelter; neither exposure nor sheltered is multiplied into it again. No accumulation or drying simulation.
|
|
39
|
+
* @capability material-appearance-signals adapt selected surface response to sampled wetness and local card exposure
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveMaterialAppearance(sample: EnvironmentSample, baseline: MaterialAppearanceBaseline, policy?: MaterialAppearancePolicy): MaterialAppearanceResponse;
|
|
42
|
+
/** Explicit sample position; callers schedule a bounded set of visible or relevant targets. */
|
|
43
|
+
export interface MaterialAppearanceTarget {
|
|
44
|
+
x: number;
|
|
45
|
+
z: number;
|
|
46
|
+
y?: number;
|
|
47
|
+
time: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Exactly one field sample per target; no traversal, subscription, hidden tick, or full-world scan.
|
|
51
|
+
* @capability material-appearance-signals sample one explicitly scheduled appearance target from the shared environment field
|
|
52
|
+
*/
|
|
53
|
+
export declare function sampleMaterialAppearance(field: Pick<EnvironmentField, "sample">, target: MaterialAppearanceTarget, baseline: MaterialAppearanceBaseline, policy?: MaterialAppearancePolicy): MaterialAppearanceResponse;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function unit(value, path) {
|
|
2
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
3
|
+
throw new TypeError(`${path}: expected a finite value from 0 to 1.`);
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
function target(baseline, endpoint, wetness, path) {
|
|
7
|
+
if (baseline === undefined)
|
|
8
|
+
throw new TypeError(`${path}: resolve the original physical material value before adapting it.`);
|
|
9
|
+
const start = unit(baseline, `baseline.${path}`);
|
|
10
|
+
const end = unit(endpoint, `policy.${path}`);
|
|
11
|
+
return wetness === 0 ? start : wetness === 1 ? end : start + (end - start) * wetness;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve one surface from an immutable dry baseline, avoiding cumulative drift. Wetness already includes
|
|
15
|
+
* field shelter; neither exposure nor sheltered is multiplied into it again. No accumulation or drying simulation.
|
|
16
|
+
* @capability material-appearance-signals adapt selected surface response to sampled wetness and local card exposure
|
|
17
|
+
*/
|
|
18
|
+
export function resolveMaterialAppearance(sample, baseline, policy = {}) {
|
|
19
|
+
const wet = policy.wetness;
|
|
20
|
+
const wetness = unit(sample.wetness, "sample.wetness") * unit(wet?.influence ?? 1, "policy.wetness.influence");
|
|
21
|
+
const result = { surface: {}, wetness };
|
|
22
|
+
if (wet?.roughness !== undefined)
|
|
23
|
+
result.surface.roughness = target(baseline.roughness, wet.roughness, wetness, "roughness");
|
|
24
|
+
if (wet?.colorRetention !== undefined) {
|
|
25
|
+
const retention = 1 + (unit(wet.colorRetention, "policy.wetness.colorRetention") - 1) * wetness;
|
|
26
|
+
if (baseline.colorLinear.some(value => !Number.isFinite(value) || value < 0))
|
|
27
|
+
throw new TypeError("baseline.colorLinear: expected finite nonnegative linear RGB.");
|
|
28
|
+
result.colorLinear = baseline.colorLinear.map(value => value * retention);
|
|
29
|
+
}
|
|
30
|
+
if (wet?.sheen !== undefined)
|
|
31
|
+
result.surface.sheen = target(baseline.sheen, wet.sheen, wetness, "sheen");
|
|
32
|
+
if (wet?.clearcoat !== undefined) {
|
|
33
|
+
result.surface.clearcoat = target(baseline.clearcoat, wet.clearcoat.strength, wetness, "clearcoat");
|
|
34
|
+
if (wet.clearcoat.roughness !== undefined)
|
|
35
|
+
result.surface.clearcoatRoughness = target(baseline.clearcoatRoughness, wet.clearcoat.roughness, wetness, "clearcoatRoughness");
|
|
36
|
+
}
|
|
37
|
+
if (policy.cardBacklightExposure)
|
|
38
|
+
result.backlightExposure = unit(sample.lightExposure, "sample.lightExposure");
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Exactly one field sample per target; no traversal, subscription, hidden tick, or full-world scan.
|
|
43
|
+
* @capability material-appearance-signals sample one explicitly scheduled appearance target from the shared environment field
|
|
44
|
+
*/
|
|
45
|
+
export function sampleMaterialAppearance(field, target, baseline, policy = {}) {
|
|
46
|
+
return resolveMaterialAppearance(field.sample(target.x, target.z, target.time, target.y), baseline, policy);
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { MaterialDiagnostic } from "./materialAsset.js";
|
|
2
|
+
/** Tangents follow card width (UV U); strand length follows UV V. Angles are radians. */
|
|
3
|
+
export declare const HAIR_CARD_STRAND_ROTATION: number;
|
|
4
|
+
/** An authored centreline, in model-local metres, with root-to-tip points. */
|
|
5
|
+
export interface HairCardGuide {
|
|
6
|
+
id: string;
|
|
7
|
+
points: readonly (readonly [number, number, number])[];
|
|
8
|
+
width: number;
|
|
9
|
+
tipWidth?: number;
|
|
10
|
+
/** Desired front-facing normal; must not be parallel to the strand direction. */
|
|
11
|
+
facing: readonly [number, number, number];
|
|
12
|
+
/** Atlas rectangle [uMin,vMin,uMax,vMax]; U is width and V is root to tip. */
|
|
13
|
+
uvRect?: readonly [number, number, number, number];
|
|
14
|
+
}
|
|
15
|
+
/** Serializable geometry authoring, independent of materials, grooming physics and collisions. */
|
|
16
|
+
export interface HairCardAuthoring {
|
|
17
|
+
guides: readonly HairCardGuide[];
|
|
18
|
+
subdivisionsPerSegment?: number;
|
|
19
|
+
/** Allocation ceiling for offline/editor generation; default 65,536 vertices. */
|
|
20
|
+
maxVertices?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Renderer-neutral indexed ribbons with an explicit tangent frame and guide ranges. */
|
|
23
|
+
export interface HairCardMesh {
|
|
24
|
+
positions: number[];
|
|
25
|
+
normals: number[];
|
|
26
|
+
tangents: number[];
|
|
27
|
+
uvs: number[];
|
|
28
|
+
indices: number[];
|
|
29
|
+
bounds: {
|
|
30
|
+
min: [number, number, number];
|
|
31
|
+
max: [number, number, number];
|
|
32
|
+
};
|
|
33
|
+
guides: {
|
|
34
|
+
id: string;
|
|
35
|
+
vertexStart: number;
|
|
36
|
+
vertexCount: number;
|
|
37
|
+
indexStart: number;
|
|
38
|
+
indexCount: number;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate finite guides, tangent frames and bounded generation; crossing/collision is not solved.
|
|
43
|
+
* @capability hair-card-geometry validate authored ribbon guides and their vertex allocation ceiling
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateHairCardAuthoring(value: unknown): MaterialDiagnostic[];
|
|
46
|
+
/** Build deterministic model-local ribbon geometry; guides remain separately editable source data. */
|
|
47
|
+
export declare function buildHairCards(input: HairCardAuthoring): HairCardMesh;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/** Tangents follow card width (UV U); strand length follows UV V. Angles are radians. */
|
|
2
|
+
export const HAIR_CARD_STRAND_ROTATION = Math.PI / 2;
|
|
3
|
+
function vec(value) { return Array.isArray(value) && value.length === 3 && value.every((item) => typeof item === "number" && Number.isFinite(item)); }
|
|
4
|
+
function subtract(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }
|
|
5
|
+
function cross(a, b) { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; }
|
|
6
|
+
function length(v) { return Math.hypot(...v); }
|
|
7
|
+
function normalized(v) { const magnitude = length(v); return [v[0] / magnitude, v[1] / magnitude, v[2] / magnitude]; }
|
|
8
|
+
/**
|
|
9
|
+
* Validate finite guides, tangent frames and bounded generation; crossing/collision is not solved.
|
|
10
|
+
* @capability hair-card-geometry validate authored ribbon guides and their vertex allocation ceiling
|
|
11
|
+
*/
|
|
12
|
+
export function validateHairCardAuthoring(value) {
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
const error = (path, message) => diagnostics.push({ severity: "error", code: "invalid-hair-guide", path, message });
|
|
15
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
16
|
+
return [{ severity: "error", code: "invalid-hair-guide", path: "", message: "Expected hair card authoring data." }];
|
|
17
|
+
const input = value;
|
|
18
|
+
for (const key of Object.keys(input))
|
|
19
|
+
if (!["guides", "subdivisionsPerSegment", "maxVertices"].includes(key))
|
|
20
|
+
error(key, "Unsupported hair geometry field.");
|
|
21
|
+
const subdivisions = input.subdivisionsPerSegment ?? 1;
|
|
22
|
+
const maxVertices = input.maxVertices ?? 65_536;
|
|
23
|
+
if (typeof subdivisions !== "number" || !Number.isInteger(subdivisions) || subdivisions < 1 || subdivisions > 256)
|
|
24
|
+
error("subdivisionsPerSegment", "Expected an integer from 1 to 256.");
|
|
25
|
+
if (typeof maxVertices !== "number" || !Number.isInteger(maxVertices) || maxVertices < 4 || maxVertices > 1_000_000)
|
|
26
|
+
error("maxVertices", "Expected a vertex ceiling from 4 to 1,000,000.");
|
|
27
|
+
if (!Array.isArray(input.guides) || input.guides.length === 0 || input.guides.length > 10_000) {
|
|
28
|
+
error("guides", "Expected 1 to 10,000 authored guides.");
|
|
29
|
+
return diagnostics;
|
|
30
|
+
}
|
|
31
|
+
const ids = new Set();
|
|
32
|
+
let vertexCount = 0;
|
|
33
|
+
for (let index = 0; index < input.guides.length; index++) {
|
|
34
|
+
const guide = input.guides[index];
|
|
35
|
+
const path = `guides.${index}`;
|
|
36
|
+
if (typeof guide !== "object" || guide === null || Array.isArray(guide)) {
|
|
37
|
+
error(path, "Expected a guide object.");
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
for (const key of Object.keys(guide))
|
|
41
|
+
if (!["id", "points", "width", "tipWidth", "facing", "uvRect"].includes(key))
|
|
42
|
+
error(`${path}.${key}`, "Unsupported guide field.");
|
|
43
|
+
if (typeof guide.id !== "string" || guide.id.trim().length === 0 || ids.has(guide.id))
|
|
44
|
+
error(`${path}.id`, "Expected a unique stable guide ID.");
|
|
45
|
+
else
|
|
46
|
+
ids.add(guide.id);
|
|
47
|
+
if (typeof guide.width !== "number" || !Number.isFinite(guide.width) || guide.width <= 0)
|
|
48
|
+
error(`${path}.width`, "Expected a positive width in metres.");
|
|
49
|
+
if (guide.tipWidth !== undefined && (typeof guide.tipWidth !== "number" || !Number.isFinite(guide.tipWidth) || guide.tipWidth < 0))
|
|
50
|
+
error(`${path}.tipWidth`, "Expected a nonnegative tip width in metres.");
|
|
51
|
+
const facingValid = vec(guide.facing) && length(guide.facing) > 1e-8;
|
|
52
|
+
if (!facingValid)
|
|
53
|
+
error(`${path}.facing`, "Expected a finite nonzero facing vector.");
|
|
54
|
+
if (!Array.isArray(guide.points) || guide.points.length < 2 || guide.points.length > 10_000) {
|
|
55
|
+
error(`${path}.points`, "Expected 2 to 10,000 finite model-local points.");
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
vertexCount += 2 * ((guide.points.length - 1) * (typeof subdivisions === "number" ? subdivisions : 1) + 1);
|
|
59
|
+
const ceiling = typeof maxVertices === "number" && Number.isFinite(maxVertices) ? Math.min(maxVertices, 1_000_000) : 65_536;
|
|
60
|
+
if (vertexCount > ceiling) {
|
|
61
|
+
error("maxVertices", `Guides require at least ${vertexCount} vertices, above the ${ceiling} ceiling.`);
|
|
62
|
+
return diagnostics;
|
|
63
|
+
}
|
|
64
|
+
if (!guide.points.every(vec)) {
|
|
65
|
+
error(`${path}.points`, "Expected finite model-local points.");
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (let point = 1; point < guide.points.length; point++) {
|
|
69
|
+
const direction = subtract(guide.points[point], guide.points[point - 1]);
|
|
70
|
+
if (length(direction) <= 1e-8)
|
|
71
|
+
error(`${path}.points.${point}`, "Adjacent guide points must be distinct.");
|
|
72
|
+
else if (facingValid && length(cross(normalized(direction), normalized(guide.facing))) <= 1e-6)
|
|
73
|
+
error(`${path}.facing`, "Facing is parallel to a guide segment; choose a different card orientation.");
|
|
74
|
+
if (point < guide.points.length - 1 && length(subtract(guide.points[point + 1], guide.points[point - 1])) <= 1e-8)
|
|
75
|
+
error(`${path}.points.${point}`, "A reversing guide has no stable tangent at its bend.");
|
|
76
|
+
}
|
|
77
|
+
if (guide.uvRect !== undefined && (!Array.isArray(guide.uvRect) || guide.uvRect.length !== 4 || !guide.uvRect.every((item) => typeof item === "number" && Number.isFinite(item) && item >= 0 && item <= 1) || guide.uvRect[0] >= guide.uvRect[2] || guide.uvRect[1] >= guide.uvRect[3]))
|
|
78
|
+
error(`${path}.uvRect`, "Expected an ordered atlas rectangle within 0 to 1.");
|
|
79
|
+
}
|
|
80
|
+
if (typeof maxVertices === "number" && vertexCount > maxVertices)
|
|
81
|
+
error("maxVertices", `Guides require ${vertexCount} vertices, above the ${maxVertices} ceiling.`);
|
|
82
|
+
return diagnostics;
|
|
83
|
+
}
|
|
84
|
+
/** Build deterministic model-local ribbon geometry; guides remain separately editable source data. */
|
|
85
|
+
export function buildHairCards(input) {
|
|
86
|
+
const errors = validateHairCardAuthoring(input);
|
|
87
|
+
if (errors.length > 0)
|
|
88
|
+
throw new TypeError(errors.map((item) => `${item.path}: ${item.message}`).join("; "));
|
|
89
|
+
const mesh = { positions: [], normals: [], tangents: [], uvs: [], indices: [], bounds: { min: [Infinity, Infinity, Infinity], max: [-Infinity, -Infinity, -Infinity] }, guides: [] };
|
|
90
|
+
const subdivisions = input.subdivisionsPerSegment ?? 1;
|
|
91
|
+
for (const guide of input.guides) {
|
|
92
|
+
const vertexStart = mesh.positions.length / 3;
|
|
93
|
+
const indexStart = mesh.indices.length;
|
|
94
|
+
const samples = [[...guide.points[0]]];
|
|
95
|
+
for (let segment = 1; segment < guide.points.length; segment++) {
|
|
96
|
+
const from = guide.points[segment - 1], to = guide.points[segment];
|
|
97
|
+
for (let step = 1; step <= subdivisions; step++) {
|
|
98
|
+
const t = step / subdivisions;
|
|
99
|
+
samples.push([from[0] + (to[0] - from[0]) * t, from[1] + (to[1] - from[1]) * t, from[2] + (to[2] - from[2]) * t]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const distances = [0];
|
|
103
|
+
for (let index = 1; index < samples.length; index++)
|
|
104
|
+
distances.push(distances[index - 1] + length(subtract(samples[index], samples[index - 1])));
|
|
105
|
+
const totalLength = distances[distances.length - 1];
|
|
106
|
+
const atlas = guide.uvRect ?? [0, 0, 1, 1];
|
|
107
|
+
for (let index = 0; index < samples.length; index++) {
|
|
108
|
+
const tangent = normalized(subtract(samples[Math.min(index + 1, samples.length - 1)], samples[Math.max(index - 1, 0)]));
|
|
109
|
+
const widthDirection = cross(tangent, normalized(guide.facing));
|
|
110
|
+
if (length(widthDirection) <= 1e-6 || !widthDirection.every(Number.isFinite))
|
|
111
|
+
throw new TypeError(`Guide ${guide.id} has an unstable tangent frame at sample ${index}; change its bend or facing.`);
|
|
112
|
+
const across = normalized(widthDirection);
|
|
113
|
+
const normal = normalized(cross(across, tangent));
|
|
114
|
+
const t = distances[index] / totalLength;
|
|
115
|
+
const halfWidth = (guide.width + ((guide.tipWidth ?? guide.width * 0.1) - guide.width) * t) / 2;
|
|
116
|
+
for (let side = 0; side < 2; side++) {
|
|
117
|
+
const sign = side === 0 ? -1 : 1;
|
|
118
|
+
const position = [samples[index][0] + sign * across[0] * halfWidth, samples[index][1] + sign * across[1] * halfWidth, samples[index][2] + sign * across[2] * halfWidth];
|
|
119
|
+
mesh.positions.push(...position);
|
|
120
|
+
mesh.normals.push(...normal);
|
|
121
|
+
mesh.tangents.push(...across, 1);
|
|
122
|
+
mesh.uvs.push(side === 0 ? atlas[0] : atlas[2], atlas[1] + (atlas[3] - atlas[1]) * t);
|
|
123
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
124
|
+
mesh.bounds.min[axis] = Math.min(mesh.bounds.min[axis], position[axis]);
|
|
125
|
+
mesh.bounds.max[axis] = Math.max(mesh.bounds.max[axis], position[axis]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (index < samples.length - 1) {
|
|
129
|
+
const left = vertexStart + index * 2;
|
|
130
|
+
mesh.indices.push(left, left + 1, left + 2, left + 1, left + 3, left + 2);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
mesh.guides.push({ id: guide.id, vertexStart, vertexCount: samples.length * 2, indexStart, indexCount: mesh.indices.length - indexStart });
|
|
134
|
+
}
|
|
135
|
+
return mesh;
|
|
136
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/** Physical surface families; these describe appearance, never geometry or simulation. */
|
|
2
|
+
export type MaterialFamily = "standard" | "fabric" | "hair" | "glass" | "metal" | "skin" | "stone" | "plastic";
|
|
3
|
+
/** Renderer features an authored material explicitly requires. */
|
|
4
|
+
export type MaterialCapability = "pbr" | "sheen" | "anisotropy" | "clearcoat" | "specular" | "transmission" | "volume" | "iridescence" | "alpha";
|
|
5
|
+
/** Sparse surface edits. Omitted values preserve the imported material. Angles are radians. */
|
|
6
|
+
export interface MaterialSurfaceParameters {
|
|
7
|
+
color?: string;
|
|
8
|
+
metalness?: number;
|
|
9
|
+
roughness?: number;
|
|
10
|
+
emissive?: string;
|
|
11
|
+
emissiveIntensity?: number;
|
|
12
|
+
normalScale?: [number, number];
|
|
13
|
+
alphaMode?: "opaque" | "mask" | "blend";
|
|
14
|
+
alphaCutoff?: number;
|
|
15
|
+
opacity?: number;
|
|
16
|
+
doubleSided?: boolean;
|
|
17
|
+
sheen?: number;
|
|
18
|
+
sheenColor?: string;
|
|
19
|
+
sheenRoughness?: number;
|
|
20
|
+
anisotropy?: number;
|
|
21
|
+
anisotropyRotation?: number;
|
|
22
|
+
clearcoat?: number;
|
|
23
|
+
clearcoatRoughness?: number;
|
|
24
|
+
ior?: number;
|
|
25
|
+
specularIntensity?: number;
|
|
26
|
+
specularColor?: string;
|
|
27
|
+
transmission?: number;
|
|
28
|
+
thickness?: number;
|
|
29
|
+
attenuationColor?: string;
|
|
30
|
+
/** Positive metres. Omit for no volume attenuation; Infinity is not serializable. */
|
|
31
|
+
attenuationDistance?: number;
|
|
32
|
+
iridescence?: number;
|
|
33
|
+
iridescenceIOR?: number;
|
|
34
|
+
iridescenceThicknessRange?: [number, number];
|
|
35
|
+
}
|
|
36
|
+
/** Supported texture semantics; packed channels follow glTF and Three's PBR shaders. */
|
|
37
|
+
export type MaterialTextureRole = "color" | "normal" | "roughness" | "metalness" | "ao" | "emissive" | "alpha" | "height" | "sheenColor" | "sheenRoughness" | "anisotropy" | "clearcoat" | "clearcoatRoughness" | "clearcoatNormal" | "specularIntensity" | "specularColor" | "transmission" | "thickness" | "iridescence" | "iridescenceThickness";
|
|
38
|
+
/** Texture channels used by the pinned standard/physical shader path. */
|
|
39
|
+
export type MaterialTextureChannel = "r" | "g" | "b" | "a" | "rgb" | "rgba";
|
|
40
|
+
/** Portable material or image authorship. */
|
|
41
|
+
export interface MaterialProvenance {
|
|
42
|
+
author: string;
|
|
43
|
+
license: string;
|
|
44
|
+
sourceUrl?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Serializable sampling and UV metadata; metres are descriptive, UV transforms drive rendering. */
|
|
48
|
+
export interface MaterialTextureMetadata {
|
|
49
|
+
url: string;
|
|
50
|
+
colorSpace: "srgb" | "linear";
|
|
51
|
+
channel?: MaterialTextureChannel;
|
|
52
|
+
uvSet?: 0 | 1 | 2 | 3;
|
|
53
|
+
transform?: {
|
|
54
|
+
offset?: [number, number];
|
|
55
|
+
scale?: [number, number];
|
|
56
|
+
rotation?: number;
|
|
57
|
+
};
|
|
58
|
+
physicalSize?: [number, number];
|
|
59
|
+
normalConvention?: "opengl" | "directx";
|
|
60
|
+
sampler?: {
|
|
61
|
+
wrapS?: "repeat" | "clamp" | "mirror";
|
|
62
|
+
wrapT?: "repeat" | "clamp" | "mirror";
|
|
63
|
+
minFilter?: "nearest" | "linear" | "nearest-mipmap-nearest" | "nearest-mipmap-linear" | "linear-mipmap-nearest" | "linear-mipmap-linear";
|
|
64
|
+
magFilter?: "nearest" | "linear";
|
|
65
|
+
anisotropy?: number;
|
|
66
|
+
};
|
|
67
|
+
compression?: "none" | "ktx2" | "basisu" | "webp";
|
|
68
|
+
provenance?: MaterialProvenance;
|
|
69
|
+
}
|
|
70
|
+
/** Shared, versioned authoring data. IDs survive rename, save and export. */
|
|
71
|
+
export interface MaterialAsset {
|
|
72
|
+
schemaVersion: 1;
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
family: MaterialFamily;
|
|
76
|
+
capabilities: readonly MaterialCapability[];
|
|
77
|
+
provenance?: MaterialProvenance;
|
|
78
|
+
surface: MaterialSurfaceParameters;
|
|
79
|
+
textures?: Partial<Record<MaterialTextureRole, MaterialTextureMetadata>>;
|
|
80
|
+
fabric?: FabricAppearance;
|
|
81
|
+
hair?: HairAppearance;
|
|
82
|
+
}
|
|
83
|
+
/** Procedural cloth surface response; this does not create or bend cloth geometry. */
|
|
84
|
+
export interface FabricAppearance {
|
|
85
|
+
construction: "woven" | "fuzzy";
|
|
86
|
+
weaveDirection?: number;
|
|
87
|
+
threadScale?: number;
|
|
88
|
+
normalStrength?: number;
|
|
89
|
+
variation?: number;
|
|
90
|
+
}
|
|
91
|
+
/** Anisotropic surface approximation for existing hair cards or strand meshes, without grooming. */
|
|
92
|
+
export interface HairAppearance {
|
|
93
|
+
geometry: "cards" | "strands";
|
|
94
|
+
/** Radians from UV U; cards generated by hairCards use V for strand length (PI/2). */
|
|
95
|
+
strandDirection?: number;
|
|
96
|
+
/** Thin-sheet direct-light approximation on cards, independent of physical transmission. */
|
|
97
|
+
backlightStrength?: number;
|
|
98
|
+
backlightColor?: string;
|
|
99
|
+
}
|
|
100
|
+
/** Names constrain mesh and imported material slot independently; all provided tests must match. */
|
|
101
|
+
export interface MaterialSelector {
|
|
102
|
+
mesh?: string;
|
|
103
|
+
slot?: string;
|
|
104
|
+
/** Index in this mesh's material array, independent of the glTF global material index. */
|
|
105
|
+
slotIndex?: number;
|
|
106
|
+
}
|
|
107
|
+
/** A reusable asset reference with an optional instance-only sparse edit. */
|
|
108
|
+
export interface MaterialAssignment {
|
|
109
|
+
materialId: string;
|
|
110
|
+
selector: MaterialSelector;
|
|
111
|
+
overrides?: MaterialSurfaceParameters;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Exact name/index selector matching shared by authoring and rendering.
|
|
115
|
+
* @capability material-slot-selection match named meshes and imported material slots independently
|
|
116
|
+
*/
|
|
117
|
+
export declare function matchesMaterialSelector(selector: MaterialSelector, mesh: string, slot: string, slotIndex: number): boolean;
|
|
118
|
+
/** Shader channel defaults; standalone alpha maps use green, base-color alpha stays in its color map. */
|
|
119
|
+
export declare const MATERIAL_TEXTURE_SEMANTICS: Readonly<Record<MaterialTextureRole, {
|
|
120
|
+
colorSpace: "srgb" | "linear";
|
|
121
|
+
channel: MaterialTextureChannel;
|
|
122
|
+
}>>;
|
|
123
|
+
/** An actionable authoring/import diagnostic. */
|
|
124
|
+
export interface MaterialDiagnostic {
|
|
125
|
+
severity: "error" | "warning";
|
|
126
|
+
code: string;
|
|
127
|
+
path: string;
|
|
128
|
+
message: string;
|
|
129
|
+
}
|
|
130
|
+
/** Feature declarations required by each sparse physical parameter. */
|
|
131
|
+
export declare const MATERIAL_SURFACE_CAPABILITIES: Partial<Record<keyof MaterialSurfaceParameters, MaterialCapability>>;
|
|
132
|
+
/** Feature declarations required by authored texture roles. */
|
|
133
|
+
export declare const MATERIAL_TEXTURE_CAPABILITIES: Readonly<Partial<Record<MaterialTextureRole, MaterialCapability>>>;
|
|
134
|
+
/** Determine feature requirements from authored fields, including intentional zero values. */
|
|
135
|
+
export declare function materialCapabilitiesForSurface(surface: MaterialSurfaceParameters): MaterialCapability[];
|
|
136
|
+
/**
|
|
137
|
+
* Determine required shader features from surface fields, maps and appearance adapters without editing them.
|
|
138
|
+
* @capability material-feature-requirements Declare material shader requirements when authors add maps or appearance adapters.
|
|
139
|
+
*/
|
|
140
|
+
export declare function materialCapabilitiesForAsset(asset: Pick<MaterialAsset, "surface" | "textures" | "fabric" | "hair">): MaterialCapability[];
|
|
141
|
+
/**
|
|
142
|
+
* Validate sparse surface edits without mutating them or guessing unsupported fields.
|
|
143
|
+
* @capability material-surface-validation diagnose unsupported or physically invalid sparse surface edits
|
|
144
|
+
*/
|
|
145
|
+
export declare function validateMaterialSurface(value: unknown, path?: string): MaterialDiagnostic[];
|
|
146
|
+
/** Validate material JSON and renderer prerequisites before saving or assigning it. */
|
|
147
|
+
export declare function validateMaterialAsset(value: unknown): MaterialDiagnostic[];
|
|
148
|
+
/** Parse assignment JSON at editor/RPC boundaries. Invalid data fails with an actionable path. */
|
|
149
|
+
export declare function parseMaterialAssignments(value: unknown): MaterialAssignment[];
|
|
150
|
+
/** Optional renderer limits; ordinary model assignment accepts every supported texture role. */
|
|
151
|
+
export interface MaterialAssignmentValidationOptions {
|
|
152
|
+
disallowedTextureRoles?: readonly MaterialTextureRole[];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Validate references and selectors without accepting accidental whole-model assignment.
|
|
156
|
+
* @capability material-slot-selection validate reusable asset references and explicit per-slot selectors
|
|
157
|
+
*/
|
|
158
|
+
export declare function validateMaterialAssignments(assignments: readonly MaterialAssignment[], assets: readonly MaterialAsset[], options?: MaterialAssignmentValidationOptions): MaterialDiagnostic[];
|
|
159
|
+
/** Editable engine-authored surface starting points, independent of any game's palette or assets. */
|
|
160
|
+
export type MaterialTemplate = "wool" | "cotton" | "silk" | "brushed-metal" | "glass" | "coated-plastic" | "stone" | "hair-cards";
|
|
161
|
+
/** Create fresh, serializable material data with provenance; templates create no textures or geometry. */
|
|
162
|
+
export declare function createMaterialTemplate(template: MaterialTemplate, id: string, name?: MaterialTemplate): MaterialAsset;
|