@downdraft/engine 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/app/src/index.ts +10 -0
- package/app/src/renderer/bootstrap.ts +271 -0
- package/app/src/renderer/downdraft-base.css +93 -0
- package/app/src/renderer/feature-log.ts +200 -0
- package/app/src/renderer/game-module.ts +923 -0
- package/app/src/renderer/hooks.ts +88 -0
- package/app/src/renderer/hot-reload.ts +178 -0
- package/app/src/renderer/import-cache.ts +88 -0
- package/app/src/renderer/index.ts +206 -0
- package/app/src/renderer/mcp-harness.ts +144 -0
- package/app/src/renderer/save-store-factory.ts +239 -0
- package/app/src/renderer/sim-bridge.ts +183 -0
- package/app/src/renderer/standard-automation-tools.ts +771 -0
- package/app/src/shared/feature-log.ts +71 -0
- package/app/src/shared/gpu-info.ts +82 -0
- package/app/src/shared/import-cache-store.ts +142 -0
- package/app/src/shared/index.ts +6 -0
- package/app/src/shared/mcp-discovery.ts +47 -0
- package/app/src/shared/mcp-proxy.ts +82 -0
- package/app/src/shared/types.ts +324 -0
- package/asset-bake/src/bake-audio.ts +119 -0
- package/asset-bake/src/bake-gltf.ts +172 -0
- package/asset-bake/src/bake.ts +78 -0
- package/asset-bake/src/cache.ts +188 -0
- package/asset-bake/src/config.ts +186 -0
- package/asset-bake/src/index.ts +36 -0
- package/core/src/animation/animation-event.ts +41 -0
- package/core/src/animation/bone-mask.ts +122 -0
- package/core/src/animation/clip.ts +252 -0
- package/core/src/animation/index.ts +22 -0
- package/core/src/animation/mixamo.ts +113 -0
- package/core/src/animation/morph-target.ts +69 -0
- package/core/src/animation/player.ts +697 -0
- package/core/src/animation/retarget.ts +158 -0
- package/core/src/animation/skeleton-animator.ts +1399 -0
- package/core/src/animation/skeleton.ts +214 -0
- package/core/src/animation/state-machine.ts +280 -0
- package/core/src/assets/blob-store.ts +66 -0
- package/core/src/assets/cache.ts +186 -0
- package/core/src/assets/cubemap-converter.ts +155 -0
- package/core/src/assets/environment-manager.ts +121 -0
- package/core/src/assets/import-cache.ts +65 -0
- package/core/src/assets/import-settings.ts +72 -0
- package/core/src/assets/importer.ts +108 -0
- package/core/src/assets/index.ts +74 -0
- package/core/src/assets/irradiance-generator.ts +198 -0
- package/core/src/assets/loader-audio.ts +86 -0
- package/core/src/assets/loader-cubemap.ts +211 -0
- package/core/src/assets/loader-dds.ts +236 -0
- package/core/src/assets/loader-hdr.ts +474 -0
- package/core/src/assets/loader-mesh.ts +143 -0
- package/core/src/assets/loader-shader.ts +73 -0
- package/core/src/assets/loader-texture.ts +349 -0
- package/core/src/assets/lod.ts +198 -0
- package/core/src/assets/manager.ts +480 -0
- package/core/src/assets/manifest.ts +81 -0
- package/core/src/assets/material-bridge.ts +186 -0
- package/core/src/assets/model-importer.ts +72 -0
- package/core/src/assets/model-normalizer.ts +248 -0
- package/core/src/assets/model-to-gpu.ts +70 -0
- package/core/src/assets/model-to-mesh.ts +237 -0
- package/core/src/assets/prefilter-generator.ts +250 -0
- package/core/src/audio/engine.ts +177 -0
- package/core/src/audio/interface.ts +130 -0
- package/core/src/audio/listener.ts +32 -0
- package/core/src/audio/mixer.ts +141 -0
- package/core/src/audio/registry.ts +52 -0
- package/core/src/audio/sab.ts +87 -0
- package/core/src/audio/source.ts +89 -0
- package/core/src/change-detection/atomics.ts +1 -0
- package/core/src/change-detection/tracker.ts +47 -0
- package/core/src/debug-draw/lines.ts +64 -0
- package/core/src/debug-draw/points.ts +17 -0
- package/core/src/debug-draw/queue.ts +124 -0
- package/core/src/debug-draw/text.ts +25 -0
- package/core/src/ecs/archetype.ts +323 -0
- package/core/src/ecs/component.ts +188 -0
- package/core/src/ecs/entity.ts +25 -0
- package/core/src/ecs/events.ts +93 -0
- package/core/src/ecs/hierarchy.ts +124 -0
- package/core/src/ecs/hmr-swap-registry.ts +22 -0
- package/core/src/ecs/index.ts +25 -0
- package/core/src/ecs/query.ts +141 -0
- package/core/src/ecs/resource.ts +8 -0
- package/core/src/ecs/schedule.ts +181 -0
- package/core/src/ecs/system-params.ts +99 -0
- package/core/src/ecs/system.ts +42 -0
- package/core/src/ecs/world.ts +348 -0
- package/core/src/index.ts +352 -0
- package/core/src/input/context.ts +73 -0
- package/core/src/input/dom-handler.ts +380 -0
- package/core/src/input/local-player-manager.ts +218 -0
- package/core/src/input/mapping.ts +40 -0
- package/core/src/input/multi-sab-bridge.ts +276 -0
- package/core/src/input/multi-state.ts +54 -0
- package/core/src/input/sab-bridge.ts +71 -0
- package/core/src/input/state.ts +132 -0
- package/core/src/input/ui-router.ts +87 -0
- package/core/src/library/host.ts +317 -0
- package/core/src/library/index.ts +16 -0
- package/core/src/library/library.ts +292 -0
- package/core/src/material/compiler.ts +80 -0
- package/core/src/material/graph-bridge.ts +172 -0
- package/core/src/material/index.ts +9 -0
- package/core/src/material/library.ts +442 -0
- package/core/src/material/material.ts +156 -0
- package/core/src/material/variants.ts +61 -0
- package/core/src/math/mat4.ts +202 -0
- package/core/src/math/perlin-noise-2d.ts +77 -0
- package/core/src/math/perlin-noise-3d.ts +110 -0
- package/core/src/math/quat.ts +33 -0
- package/core/src/math/rng.ts +29 -0
- package/core/src/math/trig.ts +47 -0
- package/core/src/math/value-noise.ts +67 -0
- package/core/src/mesh/builder.ts +177 -0
- package/core/src/mesh/csg.ts +312 -0
- package/core/src/mesh/greased-line.ts +145 -0
- package/core/src/mesh/mesh.ts +17 -0
- package/core/src/mesh/parametric.ts +501 -0
- package/core/src/mesh/skinning.ts +67 -0
- package/core/src/mesh/vertex-layout.ts +91 -0
- package/core/src/module/cross-thread.ts +163 -0
- package/core/src/module/diagnostics.ts +123 -0
- package/core/src/module/host.ts +451 -0
- package/core/src/module/index.ts +22 -0
- package/core/src/module/module.ts +109 -0
- package/core/src/module/registry.ts +94 -0
- package/core/src/module/renderer-module.ts +247 -0
- package/core/src/module/ts-loader.ts +26 -0
- package/core/src/particles/compute-pass.ts +378 -0
- package/core/src/particles/emitter.ts +156 -0
- package/core/src/particles/index.ts +11 -0
- package/core/src/particles/particle-data.ts +47 -0
- package/core/src/particles/render-pass.ts +365 -0
- package/core/src/particles/simulator.ts +264 -0
- package/core/src/particles/system.ts +193 -0
- package/core/src/physics/body.ts +59 -0
- package/core/src/physics/ccd-heuristic.ts +55 -0
- package/core/src/physics/character.ts +282 -0
- package/core/src/physics/collider.ts +55 -0
- package/core/src/physics/collision-system.ts +91 -0
- package/core/src/physics/events.ts +133 -0
- package/core/src/physics/index.ts +34 -0
- package/core/src/physics/interface.ts +473 -0
- package/core/src/physics/interpolation-buffer.ts +192 -0
- package/core/src/physics/lifecycle.ts +87 -0
- package/core/src/physics/load-shedder.ts +164 -0
- package/core/src/physics/physics-accumulator.ts +101 -0
- package/core/src/physics/physics-system.ts +240 -0
- package/core/src/physics/ragdoll-presets.ts +272 -0
- package/core/src/physics/ragdoll-system.ts +220 -0
- package/core/src/physics/ragdoll.ts +182 -0
- package/core/src/physics/raycast.ts +143 -0
- package/core/src/physics/realm-manager.ts +499 -0
- package/core/src/physics/realm-worker-pool.ts +191 -0
- package/core/src/physics/realm-worker.ts +102 -0
- package/core/src/physics/realm.ts +189 -0
- package/core/src/physics/registry.ts +52 -0
- package/core/src/physics/safety.ts +247 -0
- package/core/src/physics/snapshot-manager.ts +141 -0
- package/core/src/physics/worker-protocol.ts +30 -0
- package/core/src/platform/bun-preload.ts +169 -0
- package/core/src/platform/css-stub.ts +4 -0
- package/core/src/platform/glob-polyfill.ts +124 -0
- package/core/src/platform/hidpi.ts +57 -0
- package/core/src/platform/render-surface.ts +77 -0
- package/core/src/platform/rpc.ts +98 -0
- package/core/src/platform/runtime.ts +132 -0
- package/core/src/plugin/context.ts +364 -0
- package/core/src/plugin/diagnostics.ts +61 -0
- package/core/src/plugin/discovery.ts +186 -0
- package/core/src/plugin/extension-loaders.ts +172 -0
- package/core/src/plugin/host.ts +659 -0
- package/core/src/plugin/index.ts +106 -0
- package/core/src/plugin/loader-asset.ts +124 -0
- package/core/src/plugin/loader-quickjs.ts +84 -0
- package/core/src/plugin/loader-wasm.ts +411 -0
- package/core/src/plugin/loader-worker.ts +261 -0
- package/core/src/plugin/manifest.ts +653 -0
- package/core/src/plugin/material-registry.ts +65 -0
- package/core/src/plugin/mcp-tools.ts +165 -0
- package/core/src/plugin/permissions.ts +175 -0
- package/core/src/plugin/quickjs-bridge.ts +294 -0
- package/core/src/plugin/registry.ts +107 -0
- package/core/src/plugin/sandbox-shim.ts +86 -0
- package/core/src/plugin/sandbox-worker.ts +215 -0
- package/core/src/plugin/wasm-abi.ts +152 -0
- package/core/src/plugin/wasm-worker.ts +229 -0
- package/core/src/plugin/workshop.ts +169 -0
- package/core/src/profiling/event-loop.ts +180 -0
- package/core/src/profiling/index.ts +69 -0
- package/core/src/profiling/iops/idb-patch.ts +242 -0
- package/core/src/profiling/iops/opfs-patch.ts +276 -0
- package/core/src/profiling/profiling-sab.ts +839 -0
- package/core/src/profiling/task-latency.ts +134 -0
- package/core/src/profiling/thread-metrics.ts +126 -0
- package/core/src/profiling/trace-event-writer.ts +205 -0
- package/core/src/profiling/warnings.ts +423 -0
- package/core/src/profiling/worker-prelude.ts +376 -0
- package/core/src/render/bind-group.ts +38 -0
- package/core/src/render/bindless/bindless-struct.ts +42 -0
- package/core/src/render/bindless/bindless.wgsl.ts +167 -0
- package/core/src/render/bindless/frame-bindings.ts +176 -0
- package/core/src/render/bindless/index.ts +10 -0
- package/core/src/render/bindless/material-manager.ts +257 -0
- package/core/src/render/bindless/texture-registry.ts +609 -0
- package/core/src/render/buffer.ts +82 -0
- package/core/src/render/camera-2d.ts +217 -0
- package/core/src/render/camera-controller.ts +440 -0
- package/core/src/render/camera.ts +409 -0
- package/core/src/render/canvas-resize-watcher.ts +46 -0
- package/core/src/render/compute-kernel.ts +172 -0
- package/core/src/render/constants.ts +2 -0
- package/core/src/render/device.ts +111 -0
- package/core/src/render/frame-graph.ts +768 -0
- package/core/src/render/frustum.ts +185 -0
- package/core/src/render/fullscreen-vs.ts +25 -0
- package/core/src/render/g-buffer.ts +186 -0
- package/core/src/render/game-renderer.ts +1452 -0
- package/core/src/render/gpu-driven/cull-pass.ts +394 -0
- package/core/src/render/gpu-driven/hzb.ts +215 -0
- package/core/src/render/gpu-driven/index.ts +7 -0
- package/core/src/render/gpu-driven/indirect-draw-pass.ts +324 -0
- package/core/src/render/gpu-driven/instance-buffer.ts +178 -0
- package/core/src/render/gpu-driven/mesh-table.ts +175 -0
- package/core/src/render/gpu-driven/passes/hzb-build-pass.ts +47 -0
- package/core/src/render/gpu-utils.ts +96 -0
- package/core/src/render/hdr.ts +88 -0
- package/core/src/render/hot-reload.ts +295 -0
- package/core/src/render/ibl-bind-group.ts +266 -0
- package/core/src/render/ibl.ts +180 -0
- package/core/src/render/index.ts +163 -0
- package/core/src/render/input-manager.ts +259 -0
- package/core/src/render/lighting/area-light.ts +196 -0
- package/core/src/render/lighting/cluster-grid.ts +288 -0
- package/core/src/render/lighting/cluster-types.ts +193 -0
- package/core/src/render/lighting/ies-parser.ts +162 -0
- package/core/src/render/lighting.ts +454 -0
- package/core/src/render/material-types/depth.wgsl +44 -0
- package/core/src/render/material-types/line.wgsl +50 -0
- package/core/src/render/material-types/matcap.wgsl +51 -0
- package/core/src/render/material-types/normal.wgsl +41 -0
- package/core/src/render/material-types/physical.wgsl +152 -0
- package/core/src/render/material-types/sprite.wgsl +46 -0
- package/core/src/render/material-types/sss.wgsl +79 -0
- package/core/src/render/material-types/toon.wgsl +79 -0
- package/core/src/render/passes/README.md +121 -0
- package/core/src/render/passes/atmosphere.ts +152 -0
- package/core/src/render/passes/cluster-lighting-pass.ts +476 -0
- package/core/src/render/passes/csm.ts +408 -0
- package/core/src/render/passes/cubemap-capture.ts +114 -0
- package/core/src/render/passes/debug-viz.ts +278 -0
- package/core/src/render/passes/debug.ts +538 -0
- package/core/src/render/passes/decal-mesh.ts +111 -0
- package/core/src/render/passes/decal-pass.ts +377 -0
- package/core/src/render/passes/deferred-lighting.ts +434 -0
- package/core/src/render/passes/depth-prepass.ts +163 -0
- package/core/src/render/passes/dome-360.ts +83 -0
- package/core/src/render/passes/fluid-render.ts +137 -0
- package/core/src/render/passes/gi-types.ts +118 -0
- package/core/src/render/passes/graph-compute.ts +300 -0
- package/core/src/render/passes/greased-line-pass.ts +232 -0
- package/core/src/render/passes/morph-target.ts +40 -0
- package/core/src/render/passes/opaque.ts +1028 -0
- package/core/src/render/passes/point-light-shadow.ts +266 -0
- package/core/src/render/passes/procedural-texture.ts +185 -0
- package/core/src/render/passes/reflection-probe.ts +140 -0
- package/core/src/render/passes/rsm-pass.ts +248 -0
- package/core/src/render/passes/sdf-text.ts +155 -0
- package/core/src/render/passes/shadow-map.ts +163 -0
- package/core/src/render/passes/shadow.ts +220 -0
- package/core/src/render/passes/skinning-vs.ts +121 -0
- package/core/src/render/passes/skinning.ts +326 -0
- package/core/src/render/passes/sky-dome.ts +404 -0
- package/core/src/render/passes/skybox.ts +162 -0
- package/core/src/render/passes/spot-light-shadow.ts +273 -0
- package/core/src/render/passes/terrain.ts +326 -0
- package/core/src/render/passes/transparent.ts +353 -0
- package/core/src/render/passes/underwater-fog.ts +185 -0
- package/core/src/render/passes/video-texture.ts +79 -0
- package/core/src/render/passes/volumetric-pass.ts +312 -0
- package/core/src/render/passes/volumetric-types.ts +171 -0
- package/core/src/render/passes/water.ts +753 -0
- package/core/src/render/pbr.ts +296 -0
- package/core/src/render/pipeline.ts +52 -0
- package/core/src/render/render-pass.ts +30 -0
- package/core/src/render/renderer-input-bus.ts +220 -0
- package/core/src/render/renderer-module-host.ts +623 -0
- package/core/src/render/resource-tracker.ts +67 -0
- package/core/src/render/sab-canvas-overlay.ts +122 -0
- package/core/src/render/shader-validator.ts +200 -0
- package/core/src/render/shaders/debug/debug-line.wgsl +27 -0
- package/core/src/render/shaders/debug/debug-point.wgsl +30 -0
- package/core/src/render/shaders/debug/debug-text.wgsl +42 -0
- package/core/src/render/shaders/debug-viz/aabb.wgsl +20 -0
- package/core/src/render/shaders/debug-viz/depth.wgsl +29 -0
- package/core/src/render/shaders/debug-viz/lod.wgsl +38 -0
- package/core/src/render/shaders/debug-viz/normals.wgsl +30 -0
- package/core/src/render/shaders/debug-viz/overdraw.wgsl +21 -0
- package/core/src/render/shaders/debug-viz/tangents.wgsl +29 -0
- package/core/src/render/shaders/debug-viz/wireframe.wgsl +33 -0
- package/core/src/render/shaders/shadow-chunks.ts +135 -0
- package/core/src/render/skybox.ts +146 -0
- package/core/src/render/splitscreen.ts +85 -0
- package/core/src/render/surface.ts +78 -0
- package/core/src/render/tonemap.ts +180 -0
- package/core/src/render/tracked-render-pass.ts +158 -0
- package/core/src/render/viewport.ts +109 -0
- package/core/src/render/wgsl-hmr.ts +70 -0
- package/core/src/sab/core-input-channel.ts +23 -0
- package/core/src/sab/define.ts +239 -0
- package/core/src/sab/errors.ts +76 -0
- package/core/src/sab/game-input.ts +343 -0
- package/core/src/sab/gamepad-devices.ts +259 -0
- package/core/src/sab/grid-buffer.ts +117 -0
- package/core/src/sab/input.ts +55 -0
- package/core/src/sab/layout.ts +219 -0
- package/core/src/sab/multi-input-channel.ts +50 -0
- package/core/src/sab/record-buffer.ts +155 -0
- package/core/src/sab/sab-polyfill.ts +56 -0
- package/core/src/sab/sim-channel.ts +453 -0
- package/core/src/sab/slot-buffer.ts +190 -0
- package/core/src/sab/types.ts +158 -0
- package/core/src/safety/bounds.ts +76 -0
- package/core/src/safety/json.ts +80 -0
- package/core/src/safety/path.ts +167 -0
- package/core/src/save/binary-format.ts +117 -0
- package/core/src/save/game-save-system.ts +161 -0
- package/core/src/save/grid-save-system.ts +158 -0
- package/core/src/save/migrate.ts +44 -0
- package/core/src/save/migration-registry.ts +94 -0
- package/core/src/save/persist-types.ts +208 -0
- package/core/src/save/schema.ts +46 -0
- package/core/src/save/serializer.ts +180 -0
- package/core/src/scene/builder.ts +65 -0
- package/core/src/scene/camera.ts +142 -0
- package/core/src/scene/checkpoint.ts +109 -0
- package/core/src/scene/game-loop.ts +198 -0
- package/core/src/scene/index.ts +24 -0
- package/core/src/scene/layer.ts +6 -0
- package/core/src/scene/prefab.ts +145 -0
- package/core/src/scene/scene-manager.ts +157 -0
- package/core/src/scene/scene.ts +192 -0
- package/core/src/scene/sky.ts +256 -0
- package/core/src/scene/spatial-grid.ts +176 -0
- package/core/src/scene/streaming.ts +304 -0
- package/core/src/scene/vegetation.ts +163 -0
- package/core/src/scene/world.ts +89 -0
- package/core/src/scripting/binding.ts +129 -0
- package/core/src/scripting/hot-reload.ts +144 -0
- package/core/src/scripting/script.ts +99 -0
- package/core/src/sim/hot-reload-pipeline.ts +49 -0
- package/core/src/sim/sim-state-helper.ts +47 -0
- package/core/src/sim/transient-state-registry.ts +77 -0
- package/core/src/sim/types.ts +113 -0
- package/core/src/sim/worker-loop.ts +222 -0
- package/core/src/stores/game-store.ts +158 -0
- package/core/src/stores/hot-reload-store.ts +44 -0
- package/core/src/telemetry/collector.ts +410 -0
- package/core/src/telemetry/gc-controller.ts +453 -0
- package/core/src/telemetry/gc-profiler.ts +126 -0
- package/core/src/telemetry/gc-tracker.ts +69 -0
- package/core/src/telemetry/gpu-profiler.ts +703 -0
- package/core/src/telemetry/gpu-resource-tracker.ts +285 -0
- package/core/src/telemetry/gpu-timer-pool.ts +234 -0
- package/core/src/telemetry/gpu-timer.ts +123 -0
- package/core/src/telemetry/gpu-timestamp-gate.ts +21 -0
- package/core/src/telemetry/reporter.ts +62 -0
- package/core/src/types/engine-types.ts +129 -0
- package/core/src/ui-font-scale.ts +112 -0
- package/core/src/util/broad-phase-grid.ts +82 -0
- package/core/src/util/feature-log.ts +220 -0
- package/core/src/util/index.ts +17 -0
- package/core/src/util/logger.ts +604 -0
- package/core/src/webgpu-destroy-augmentation.d.ts +25 -0
- package/core/src/worker/base-worker-host.ts +183 -0
- package/core/src/worker/buffer-sync.ts +550 -0
- package/core/src/worker/crash-recovery.ts +201 -0
- package/core/src/worker/entity-sim-worker-host.ts +190 -0
- package/core/src/worker/instrumented-worker-host.ts +171 -0
- package/core/src/worker/rpc.ts +253 -0
- package/core/src/worker/sim-worker-base.ts +913 -0
- package/core/src/worker/sim-worker-host.ts +358 -0
- package/core/src/worker/task-pool.ts +256 -0
- package/core/src/worker/task-worker.ts +83 -0
- package/libraries/asset-browser/src/index.ts +11 -0
- package/libraries/asset-browser/src/software-thumbnails.ts +949 -0
- package/libraries/audio-kira/README.md +17 -0
- package/libraries/audio-kira/native/Cargo.toml +14 -0
- package/libraries/audio-kira/native/src/engine.rs +27 -0
- package/libraries/audio-kira/native/src/lib.rs +185 -0
- package/libraries/audio-kira/native/src/mixer.rs +11 -0
- package/libraries/audio-kira/native/src/source.rs +80 -0
- package/libraries/audio-kira/src/backend.ts +469 -0
- package/libraries/audio-kira/src/index.ts +8 -0
- package/libraries/audio-kira/src/library.ts +82 -0
- package/libraries/blitz-ui/native/Cargo.toml +33 -0
- package/libraries/blitz-ui/native/assets/DejaVuSans.woff2 +0 -0
- package/libraries/blitz-ui/native/src/bridge.rs +100 -0
- package/libraries/blitz-ui/native/src/fonts.rs +20 -0
- package/libraries/blitz-ui/native/src/input.rs +80 -0
- package/libraries/blitz-ui/native/src/lib.rs +96 -0
- package/libraries/blitz-ui/native/src/shell.rs +301 -0
- package/libraries/blitz-ui/native-osr/Cargo.toml +24 -0
- package/libraries/blitz-ui/native-osr/src/events.rs +237 -0
- package/libraries/blitz-ui/native-osr/src/lib.rs +846 -0
- package/libraries/blitz-ui/native-osr/src/mutate.rs +385 -0
- package/libraries/blitz-ui/native-osr/src/resources.rs +45 -0
- package/libraries/blitz-ui/native-osr-spike.ts +70 -0
- package/libraries/blitz-ui/src/index.ts +15 -0
- package/libraries/blitz-ui/src/native-module.ts +221 -0
- package/libraries/blitz-ui/src/native-osr-ffi.ts +423 -0
- package/libraries/blitz-ui/src/router.ts +101 -0
- package/libraries/blitz-ui/src/shaders/ui-blit.wgsl.ts +35 -0
- package/libraries/blitz-ui/src/ui-blit-pass.ts +116 -0
- package/libraries/character/README.md +25 -0
- package/libraries/character/src/character-animator.ts +374 -0
- package/libraries/character/src/index.ts +24 -0
- package/libraries/character/src/loader.ts +375 -0
- package/libraries/character/src/locomotion.ts +124 -0
- package/libraries/character/src/preview.ts +287 -0
- package/libraries/devtools/README.md +51 -0
- package/libraries/devtools/native/Cargo.toml +12 -0
- package/libraries/devtools/native/src/lib.rs +1646 -0
- package/libraries/devtools/native/src/panels/console.rs +338 -0
- package/libraries/devtools/native/src/panels/dom_tree.rs +47 -0
- package/libraries/devtools/native/src/panels/generic.rs +317 -0
- package/libraries/devtools/native/src/panels/gpu.rs +107 -0
- package/libraries/devtools/native/src/panels/input.rs +88 -0
- package/libraries/devtools/native/src/panels/mod.rs +154 -0
- package/libraries/devtools/native/src/panels/perf_metrics.rs +91 -0
- package/libraries/devtools/native/src/panels/perf_recorder.rs +158 -0
- package/libraries/devtools/native/src/panels/scene.rs +72 -0
- package/libraries/devtools/native/src/state.rs +394 -0
- package/libraries/devtools/src/cdp-bridge.ts +349 -0
- package/libraries/devtools/src/collectors.ts +346 -0
- package/libraries/devtools/src/egui-ffi.ts +804 -0
- package/libraries/devtools/src/egui-renderer.ts +407 -0
- package/libraries/devtools/src/host.ts +363 -0
- package/libraries/devtools/src/index.ts +40 -0
- package/libraries/devtools/src/mirror.ts +794 -0
- package/libraries/devtools/src/native-providers.ts +744 -0
- package/libraries/devtools/src/verify.ts +167 -0
- package/libraries/devtools/src/web/browser.ts +102 -0
- package/libraries/devtools/src/web/host.ts +212 -0
- package/libraries/devtools/src/web/mirror.ts +430 -0
- package/libraries/devtools/src/web/server.ts +198 -0
- package/libraries/entities/README.md +17 -0
- package/libraries/entities/src/index.ts +4 -0
- package/libraries/entities/src/library.ts +64 -0
- package/libraries/entities/src/model-renderer.ts +1345 -0
- package/libraries/entities/src/shaders/model.wgsl +375 -0
- package/libraries/gamepad/src/feed-input.ts +83 -0
- package/libraries/gamepad/src/filters.ts +49 -0
- package/libraries/gamepad/src/glyphs.ts +93 -0
- package/libraries/gamepad/src/hub.ts +206 -0
- package/libraries/gamepad/src/index.ts +22 -0
- package/libraries/gamepad/src/library.ts +68 -0
- package/libraries/gamepad/src/source-sab.ts +70 -0
- package/libraries/gamepad/src/sysfs-enrich.ts +138 -0
- package/libraries/gaussian-splats/README.md +25 -0
- package/libraries/gaussian-splats/src/gpu-sort.ts +595 -0
- package/libraries/gaussian-splats/src/index.ts +31 -0
- package/libraries/gaussian-splats/src/library.ts +60 -0
- package/libraries/gaussian-splats/src/parser.ts +382 -0
- package/libraries/gaussian-splats/src/renderer.ts +427 -0
- package/libraries/gaussian-splats/src/sh-eval.ts +211 -0
- package/libraries/gaussian-splats/src/sorter.ts +110 -0
- package/libraries/gaussian-splats/src/tile-raster.ts +473 -0
- package/libraries/gpu-kernels/README.md +154 -0
- package/libraries/gpu-kernels/src/index.ts +11 -0
- package/libraries/gpu-kernels/src/kernel.ts +696 -0
- package/libraries/gpu-kernels/src/transpile.ts +958 -0
- package/libraries/html-ui-kit/src/behaviors.ts +250 -0
- package/libraries/html-ui-kit/src/components.ts +309 -0
- package/libraries/html-ui-kit/src/index.ts +30 -0
- package/libraries/html-ui-kit/src/nav/nav-controller.ts +238 -0
- package/libraries/html-ui-kit/src/nav/nav-input.ts +145 -0
- package/libraries/html-ui-kit/src/nav/osk.ts +153 -0
- package/libraries/html-ui-kit/src/nav/router.ts +167 -0
- package/libraries/html-ui-kit/src/nav/vcursor.ts +203 -0
- package/libraries/html-ui-kit/src/ten-foot.ts +182 -0
- package/libraries/html-ui-kit/src/theme.ts +238 -0
- package/libraries/lighting/README.md +20 -0
- package/libraries/lighting/src/index.ts +5 -0
- package/libraries/lighting/src/library.ts +66 -0
- package/libraries/lighting/src/light-system.ts +452 -0
- package/libraries/lighting/src/lighting-system.ts +68 -0
- package/libraries/lighting/src/shaders/light-debug.wgsl +30 -0
- package/libraries/marching-cubes/README.md +25 -0
- package/libraries/marching-cubes/src/chunked-field.ts +177 -0
- package/libraries/marching-cubes/src/deformation.ts +63 -0
- package/libraries/marching-cubes/src/generator.ts +407 -0
- package/libraries/marching-cubes/src/index.ts +32 -0
- package/libraries/marching-cubes/src/library.ts +80 -0
- package/libraries/marching-cubes/src/lod.ts +122 -0
- package/libraries/marching-cubes/src/marching-tetrahedra.ts +252 -0
- package/libraries/marching-cubes/src/sab.ts +45 -0
- package/libraries/marching-cubes/src/streaming-config.ts +48 -0
- package/libraries/marching-cubes/src/streaming-manager.ts +686 -0
- package/libraries/marching-cubes/src/tables.ts +323 -0
- package/libraries/marching-cubes/src/types.ts +18 -0
- package/libraries/models/README.md +25 -0
- package/libraries/models/src/bake-node-transforms.ts +171 -0
- package/libraries/models/src/codecs/basisu-codec.ts +180 -0
- package/libraries/models/src/codecs/draco-codec.ts +372 -0
- package/libraries/models/src/codecs/draco3dgltf.d.ts +40 -0
- package/libraries/models/src/codecs/index.ts +22 -0
- package/libraries/models/src/codecs/meshopt-codec.ts +91 -0
- package/libraries/models/src/codecs/registry.ts +221 -0
- package/libraries/models/src/dae.ts +753 -0
- package/libraries/models/src/default-part-selection.ts +56 -0
- package/libraries/models/src/dxf.ts +200 -0
- package/libraries/models/src/fbx/index.ts +457 -0
- package/libraries/models/src/fbx/interpreter/animation.ts +431 -0
- package/libraries/models/src/fbx/interpreter/blend-shapes.ts +323 -0
- package/libraries/models/src/fbx/interpreter/connections.ts +166 -0
- package/libraries/models/src/fbx/interpreter/diagnostics.ts +65 -0
- package/libraries/models/src/fbx/interpreter/geometry.ts +535 -0
- package/libraries/models/src/fbx/interpreter/global-settings.ts +92 -0
- package/libraries/models/src/fbx/interpreter/materials.ts +265 -0
- package/libraries/models/src/fbx/interpreter/nodes.ts +197 -0
- package/libraries/models/src/fbx/interpreter/skeleton.ts +476 -0
- package/libraries/models/src/fbx/parsers/binary-parser.ts +244 -0
- package/libraries/models/src/fbx/types.ts +119 -0
- package/libraries/models/src/gltf-extensions.ts +196 -0
- package/libraries/models/src/gltf.ts +935 -0
- package/libraries/models/src/index.ts +87 -0
- package/libraries/models/src/library.ts +75 -0
- package/libraries/models/src/loader.ts +265 -0
- package/libraries/models/src/material-adapter.ts +108 -0
- package/libraries/models/src/mini-xml.ts +174 -0
- package/libraries/models/src/normalize.ts +216 -0
- package/libraries/models/src/obj.ts +264 -0
- package/libraries/models/src/off.ts +95 -0
- package/libraries/models/src/ply.ts +348 -0
- package/libraries/models/src/pose-sampler.ts +219 -0
- package/libraries/models/src/sidecar/blender-extras.ts +37 -0
- package/libraries/models/src/sidecar/ddmeta.ts +114 -0
- package/libraries/models/src/sidecar/godot-import.ts +86 -0
- package/libraries/models/src/sidecar/resolver.ts +145 -0
- package/libraries/models/src/sidecar/types.ts +11 -0
- package/libraries/models/src/sidecar/unity-meta.ts +61 -0
- package/libraries/models/src/skeleton-synthesis.ts +132 -0
- package/libraries/models/src/stl.ts +149 -0
- package/libraries/models/src/threeds.ts +363 -0
- package/libraries/models/src/threemf.ts +201 -0
- package/libraries/models/src/types.ts +188 -0
- package/libraries/models/src/vtk.ts +218 -0
- package/libraries/models/test-fixtures/fbx/LICENSE-notice.txt +24 -0
- package/libraries/models/test-fixtures/fbx/README.md +46 -0
- package/libraries/models/test-fixtures/fbx/m01_cube_phong.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m02_geo_ngons.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m03_normals.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m04_material_properties.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m05_external_diffuse.png +0 -0
- package/libraries/models/test-fixtures/fbx/m05_textures.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m06_uv_transform.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m07_multimaterial.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m08_transforms.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m09_skinning.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m10_morph.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m11_node_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m12_skeletal_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m13_morph_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m14_multiclip.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m15_camera_lights.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_axis_yup.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_axis_zup.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_units_254.fbx +0 -0
- package/libraries/navmesh/README.md +23 -0
- package/libraries/navmesh/src/crowd-system.ts +240 -0
- package/libraries/navmesh/src/debug-viz.ts +74 -0
- package/libraries/navmesh/src/index.ts +15 -0
- package/libraries/navmesh/src/library.ts +64 -0
- package/libraries/navmesh/src/navmesh-generator.ts +407 -0
- package/libraries/navmesh/src/navmesh.ts +159 -0
- package/libraries/navmesh/src/pathfinder.ts +252 -0
- package/libraries/navmesh/src/types.ts +63 -0
- package/libraries/networking/README.md +25 -0
- package/libraries/networking/src/authority.ts +121 -0
- package/libraries/networking/src/connection.ts +139 -0
- package/libraries/networking/src/discovery.ts +373 -0
- package/libraries/networking/src/enhanced-replication.ts +355 -0
- package/libraries/networking/src/epic-adapter.ts +129 -0
- package/libraries/networking/src/index.ts +45 -0
- package/libraries/networking/src/library.ts +69 -0
- package/libraries/networking/src/lobby.ts +108 -0
- package/libraries/networking/src/platform-adapter.ts +163 -0
- package/libraries/networking/src/rcs-adapter.ts +127 -0
- package/libraries/networking/src/remote-input.ts +188 -0
- package/libraries/networking/src/replication.ts +327 -0
- package/libraries/networking/src/rpc.ts +188 -0
- package/libraries/networking/src/session.ts +173 -0
- package/libraries/networking/src/transport.ts +201 -0
- package/libraries/networking/src/webrtc.ts +323 -0
- package/libraries/pathfinding-2d/README.md +15 -0
- package/libraries/pathfinding-2d/src/astar-grid.ts +312 -0
- package/libraries/pathfinding-2d/src/index.ts +11 -0
- package/libraries/persistence/README.md +25 -0
- package/libraries/persistence/src/autosave-manager.ts +112 -0
- package/libraries/persistence/src/binary-record-store.ts +244 -0
- package/libraries/persistence/src/browser.ts +51 -0
- package/libraries/persistence/src/file-save-store.ts +553 -0
- package/libraries/persistence/src/hash-utils.ts +59 -0
- package/libraries/persistence/src/index.ts +57 -0
- package/libraries/persistence/src/library.ts +74 -0
- package/libraries/persistence/src/mock-opfs.ts +141 -0
- package/libraries/persistence/src/node-fs-directory-handle.ts +161 -0
- package/libraries/persistence/src/opfs-save-store.ts +745 -0
- package/libraries/persistence/src/save-worker-proxy.ts +127 -0
- package/libraries/persistence/src/save-worker.ts +99 -0
- package/libraries/persistence/src/xxh3-ts.d.ts +10 -0
- package/libraries/physics-native/README.md +21 -0
- package/libraries/physics-native/native/Cargo.toml +11 -0
- package/libraries/physics-native/native/src/body.rs +403 -0
- package/libraries/physics-native/native/src/character.rs +226 -0
- package/libraries/physics-native/native/src/collider.rs +299 -0
- package/libraries/physics-native/native/src/joint.rs +81 -0
- package/libraries/physics-native/native/src/lib.rs +830 -0
- package/libraries/physics-native/native/src/query.rs +261 -0
- package/libraries/physics-native/native/src/realm.rs +33 -0
- package/libraries/physics-native/native/src/step.rs +54 -0
- package/libraries/physics-native/native/src/world.rs +148 -0
- package/libraries/physics-native/src/backend.ts +1046 -0
- package/libraries/physics-native/src/broadphase.ts +101 -0
- package/libraries/physics-native/src/ffi-spike-worker.ts +71 -0
- package/libraries/physics-native/src/index.ts +11 -0
- package/libraries/physics-native/src/library.ts +79 -0
- package/libraries/physics-native/src/narrowphase-shapes.ts +420 -0
- package/libraries/physics-native/src/narrowphase.ts +123 -0
- package/libraries/physics-native/src/solver.ts +141 -0
- package/libraries/physics-native/src/types.ts +76 -0
- package/libraries/physics-rapier/README.md +21 -0
- package/libraries/physics-rapier/bench/ffi-bench.ts +79 -0
- package/libraries/physics-rapier/bench/wasm-baseline.ts +90 -0
- package/libraries/physics-rapier/src/api.ts +452 -0
- package/libraries/physics-rapier/src/auto-backend.ts +59 -0
- package/libraries/physics-rapier/src/backend.ts +1207 -0
- package/libraries/physics-rapier/src/bulk-ops.ts +68 -0
- package/libraries/physics-rapier/src/ffi-backend.ts +21 -0
- package/libraries/physics-rapier/src/ffi-lib.ts +597 -0
- package/libraries/physics-rapier/src/index.ts +12 -0
- package/libraries/physics-rapier/src/library.ts +107 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +1052 -0
- package/libraries/physics-rapier/src/realm-worker-script.ts +237 -0
- package/libraries/postfx/README.md +17 -0
- package/libraries/postfx/src/index.ts +19 -0
- package/libraries/postfx/src/library.ts +337 -0
- package/libraries/postfx/src/post-process-stack.ts +1752 -0
- package/libraries/postfx/src/shaders/post-process/afterimage.wgsl +14 -0
- package/libraries/postfx/src/shaders/post-process/ascii.wgsl +24 -0
- package/libraries/postfx/src/shaders/post-process/blit.wgsl +12 -0
- package/libraries/postfx/src/shaders/post-process/bloom-blur.wgsl +21 -0
- package/libraries/postfx/src/shaders/post-process/bloom-bright.wgsl +15 -0
- package/libraries/postfx/src/shaders/post-process/bloom-composite.wgsl +14 -0
- package/libraries/postfx/src/shaders/post-process/bloom-downsample.wgsl +60 -0
- package/libraries/postfx/src/shaders/post-process/bloom-soft.wgsl +46 -0
- package/libraries/postfx/src/shaders/post-process/bloom-upsample.wgsl +51 -0
- package/libraries/postfx/src/shaders/post-process/channel-mixer.wgsl +36 -0
- package/libraries/postfx/src/shaders/post-process/chromatic-aberration.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/dithering.wgsl +64 -0
- package/libraries/postfx/src/shaders/post-process/dof.wgsl +131 -0
- package/libraries/postfx/src/shaders/post-process/edges.wgsl +45 -0
- package/libraries/postfx/src/shaders/post-process/fullscreen-vs.wgsl +15 -0
- package/libraries/postfx/src/shaders/post-process/fxaa.wgsl +102 -0
- package/libraries/postfx/src/shaders/post-process/gaussian-blur.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/glow-blur.wgsl +33 -0
- package/libraries/postfx/src/shaders/post-process/glow-composite.wgsl +25 -0
- package/libraries/postfx/src/shaders/post-process/grain.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/halftone.wgsl +80 -0
- package/libraries/postfx/src/shaders/post-process/highlight-blur.wgsl +33 -0
- package/libraries/postfx/src/shaders/post-process/highlight-composite.wgsl +32 -0
- package/libraries/postfx/src/shaders/post-process/lens-distortion.wgsl +43 -0
- package/libraries/postfx/src/shaders/post-process/lens-flare.wgsl +68 -0
- package/libraries/postfx/src/shaders/post-process/lut.wgsl +30 -0
- package/libraries/postfx/src/shaders/post-process/motion-blur.wgsl +55 -0
- package/libraries/postfx/src/shaders/post-process/occluder-chunk.wgsl +28 -0
- package/libraries/postfx/src/shaders/post-process/outline.wgsl +51 -0
- package/libraries/postfx/src/shaders/post-process/pixelation.wgsl +47 -0
- package/libraries/postfx/src/shaders/post-process/sharpen.wgsl +32 -0
- package/libraries/postfx/src/shaders/post-process/sobel.wgsl +26 -0
- package/libraries/postfx/src/shaders/post-process/split-tone.wgsl +36 -0
- package/libraries/postfx/src/shaders/post-process/ssao-blur.wgsl +38 -0
- package/libraries/postfx/src/shaders/post-process/ssao-composite.wgsl +25 -0
- package/libraries/postfx/src/shaders/post-process/ssao.wgsl +147 -0
- package/libraries/postfx/src/shaders/post-process/ssr.wgsl +155 -0
- package/libraries/postfx/src/shaders/post-process/taa.wgsl +98 -0
- package/libraries/postfx/src/shaders/post-process/tonemap.wgsl +40 -0
- package/libraries/postfx/src/shaders/post-process/watercolor.wgsl +82 -0
- package/libraries/postfx/src/shaders/post-process/white-balance.wgsl +41 -0
- package/libraries/recast/README.md +21 -0
- package/libraries/recast/src/backend.ts +150 -0
- package/libraries/recast/src/crowd-system.ts +352 -0
- package/libraries/recast/src/index.ts +12 -0
- package/libraries/recast/src/library.ts +91 -0
- package/libraries/recast/src/types.ts +86 -0
- package/libraries/sand/README.md +25 -0
- package/libraries/sand/src/cell.ts +43 -0
- package/libraries/sand/src/fields.ts +29 -0
- package/libraries/sand/src/fluid-grid.ts +261 -0
- package/libraries/sand/src/grid-dims.ts +74 -0
- package/libraries/sand/src/grid-sim-buffer.ts +271 -0
- package/libraries/sand/src/index.ts +81 -0
- package/libraries/sand/src/library.ts +92 -0
- package/libraries/sand/src/materials.ts +492 -0
- package/libraries/sand/src/palette.ts +93 -0
- package/libraries/sand/src/particles.ts +185 -0
- package/libraries/sand/src/render/sand-grid-pass.ts +430 -0
- package/libraries/sand/src/rng.ts +51 -0
- package/libraries/sand/src/rules/builtin-rules.ts +132 -0
- package/libraries/sand/src/rules/rule-compiler.ts +162 -0
- package/libraries/sand/src/rules/rule-engine.ts +231 -0
- package/libraries/sand/src/rules/rule-types.ts +87 -0
- package/libraries/sand/src/sand-step-pool.ts +484 -0
- package/libraries/sand/src/sand-step-worker.ts +97 -0
- package/libraries/sand/src/sand-world.ts +3227 -0
- package/libraries/stickman/README.md +25 -0
- package/libraries/stickman/src/index.ts +25 -0
- package/libraries/stickman/src/library.ts +61 -0
- package/libraries/stickman/src/proportions.ts +45 -0
- package/libraries/stickman/src/shader.ts +117 -0
- package/libraries/stickman/src/skeleton.ts +197 -0
- package/libraries/stickman/src/stickman-pass.ts +255 -0
- package/libraries/stickman/src/thick-line.ts +123 -0
- package/libraries/surface-nets/README.md +25 -0
- package/libraries/surface-nets/src/chunked-field.ts +14 -0
- package/libraries/surface-nets/src/deformation.ts +45 -0
- package/libraries/surface-nets/src/index.ts +36 -0
- package/libraries/surface-nets/src/library.ts +59 -0
- package/libraries/surface-nets/src/lod.ts +7 -0
- package/libraries/surface-nets/src/sab.ts +4 -0
- package/libraries/surface-nets/src/streaming-config.ts +5 -0
- package/libraries/surface-nets/src/streaming-manager.ts +15 -0
- package/libraries/surface-nets/src/surface-nets.ts +334 -0
- package/libraries/surface-nets/src/types.ts +23 -0
- package/libraries/water/README.md +25 -0
- package/libraries/water/src/buoyancy.ts +103 -0
- package/libraries/water/src/index.ts +43 -0
- package/libraries/water/src/library.ts +85 -0
- package/libraries/water/src/shore-damping.ts +93 -0
- package/libraries/water/src/water-buffer.ts +89 -0
- package/libraries/water/src/water-chunks.ts +13 -0
- package/libraries/water/src/water-physics.ts +376 -0
- package/libraries/water/src/water-renderer.ts +809 -0
- package/libraries/water/src/water-sab.ts +161 -0
- package/libraries/water/src/wave-sources.ts +84 -0
- package/libraries/weather/README.md +20 -0
- package/libraries/weather/src/index.ts +9 -0
- package/libraries/weather/src/library.ts +72 -0
- package/libraries/weather/src/types.ts +64 -0
- package/libraries/weather/src/weather-blend.ts +112 -0
- package/libraries/weather/src/weather-system.ts +271 -0
- package/libraries/weatherfx/README.md +24 -0
- package/libraries/weatherfx/src/cloud-provider.ts +31 -0
- package/libraries/weatherfx/src/cloud-system.ts +372 -0
- package/libraries/weatherfx/src/index.ts +9 -0
- package/libraries/weatherfx/src/library.ts +58 -0
- package/libraries/weatherfx/src/particle-system.ts +434 -0
- package/libraries/weatherfx/src/shaders/cloud.wgsl +111 -0
- package/libraries/weatherfx/src/shaders/particle-compute.wgsl +187 -0
- package/libraries/weatherfx/src/shaders/particle-render.wgsl +95 -0
- package/lint-plugin.mjs +41 -0
- package/mcp/src/client.ts +703 -0
- package/mcp/src/engine-context.ts +186 -0
- package/mcp/src/http-transport.ts +471 -0
- package/mcp/src/index.ts +13 -0
- package/mcp/src/prompts/add-entity.ts +42 -0
- package/mcp/src/prompts/create-scene.ts +44 -0
- package/mcp/src/prompts/debug-frame.ts +39 -0
- package/mcp/src/prompts/index.ts +12 -0
- package/mcp/src/resources/asset-list.ts +32 -0
- package/mcp/src/resources/checkpoint-list.ts +33 -0
- package/mcp/src/resources/entity-state.ts +55 -0
- package/mcp/src/resources/gpu-info.ts +103 -0
- package/mcp/src/resources/index.ts +57 -0
- package/mcp/src/resources/performance.ts +37 -0
- package/mcp/src/resources/scene-tree.ts +43 -0
- package/mcp/src/server.ts +375 -0
- package/mcp/src/tools/animation.ts +118 -0
- package/mcp/src/tools/asset.ts +161 -0
- package/mcp/src/tools/audio.ts +96 -0
- package/mcp/src/tools/build.ts +143 -0
- package/mcp/src/tools/camera.ts +167 -0
- package/mcp/src/tools/checkpoint.ts +100 -0
- package/mcp/src/tools/component.ts +199 -0
- package/mcp/src/tools/debug.ts +300 -0
- package/mcp/src/tools/entity.ts +361 -0
- package/mcp/src/tools/inspect.ts +210 -0
- package/mcp/src/tools/lighting.ts +263 -0
- package/mcp/src/tools/material.ts +243 -0
- package/mcp/src/tools/mesh.ts +239 -0
- package/mcp/src/tools/physics.ts +266 -0
- package/mcp/src/tools/scene.ts +153 -0
- package/mcp/src/tools/script.ts +122 -0
- package/mcp/src/types.ts +92 -0
- package/mcp/src/undo-redo.ts +77 -0
- package/modules/camera-controls/README.md +20 -0
- package/modules/camera-controls/src/index.ts +161 -0
- package/modules/controller-ui/src/index.ts +164 -0
- package/modules/devtools/README.md +25 -0
- package/modules/devtools/src/api.ts +461 -0
- package/modules/devtools/src/asset-utils.ts +105 -0
- package/modules/devtools/src/bind-debug-store.ts +48 -0
- package/modules/devtools/src/data-bridge.ts +286 -0
- package/modules/devtools/src/debug-overlay.ts +238 -0
- package/modules/devtools/src/debug-raycast.ts +328 -0
- package/modules/devtools/src/debug-store.ts +92 -0
- package/modules/devtools/src/debug-view-descriptors.ts +59 -0
- package/modules/devtools/src/doctor-panel.ts +182 -0
- package/modules/devtools/src/gizmo-module.ts +136 -0
- package/modules/devtools/src/graph-model.ts +509 -0
- package/modules/devtools/src/grid-renderer.ts +157 -0
- package/modules/devtools/src/height-ruler-renderer.ts +216 -0
- package/modules/devtools/src/index.ts +81 -0
- package/modules/devtools/src/init.ts +261 -0
- package/modules/devtools/src/label-overlay.ts +154 -0
- package/modules/devtools/src/material-stats-panel.ts +188 -0
- package/modules/devtools/src/profiling-bridge.ts +377 -0
- package/modules/devtools/src/profiling-hooks.ts +98 -0
- package/modules/devtools/src/renderer-adapter.ts +76 -0
- package/modules/devtools/src/scene-inspector.ts +637 -0
- package/modules/devtools/src/scene-store.ts +320 -0
- package/modules/devtools/src/scene-sync.ts +116 -0
- package/modules/devtools/src/shaders/transform-gizmo.wgsl +38 -0
- package/modules/devtools/src/sim-stats-panel.ts +272 -0
- package/modules/devtools/src/sim-stats-provider.ts +105 -0
- package/modules/devtools/src/skeleton-renderer.ts +351 -0
- package/modules/devtools/src/transform-gizmo.ts +928 -0
- package/modules/devtools/src/types.ts +290 -0
- package/modules/devtools/src/worker-expose.ts +67 -0
- package/modules/devtools/src/worker-sync.ts +137 -0
- package/modules/editor/src/commands/component.ts +156 -0
- package/modules/editor/src/commands/entity.ts +287 -0
- package/modules/editor/src/commands/hierarchy.ts +83 -0
- package/modules/editor/src/commands/index.ts +61 -0
- package/modules/editor/src/commands/prefab.ts +163 -0
- package/modules/editor/src/commands/registry.ts +331 -0
- package/modules/editor/src/commands/scene-io.ts +211 -0
- package/modules/editor/src/commands/selection.ts +73 -0
- package/modules/editor/src/commands/transform.ts +91 -0
- package/modules/editor/src/ddscene.ts +267 -0
- package/modules/editor/src/document.ts +123 -0
- package/modules/editor/src/editor-context.ts +172 -0
- package/modules/editor/src/index.ts +41 -0
- package/modules/editor/src/mcp.ts +103 -0
- package/modules/editor/src/module.ts +148 -0
- package/modules/editor/src/selection.ts +70 -0
- package/modules/editor/src/shell.ts +456 -0
- package/modules/editor/src/viewport.ts +199 -0
- package/modules/html-ui/src/composite.ts +135 -0
- package/modules/html-ui/src/create-html-ui.ts +188 -0
- package/modules/html-ui/src/doc-backend.ts +343 -0
- package/modules/html-ui/src/host.ts +646 -0
- package/modules/html-ui/src/index.ts +13 -0
- package/modules/html-ui/src/jsx-dev-runtime.ts +12 -0
- package/modules/html-ui/src/jsx-runtime.ts +126 -0
- package/modules/html-ui/src/protocol.ts +88 -0
- package/modules/html-ui/src/ui-worker.ts +16 -0
- package/modules/mcp/README.md +18 -0
- package/modules/mcp/src/index.ts +27 -0
- package/modules/movement-2d/README.md +19 -0
- package/modules/movement-2d/src/grid-character.ts +455 -0
- package/modules/movement-2d/src/index.ts +45 -0
- package/modules/movement-3d/README.md +21 -0
- package/modules/movement-3d/src/character-motor.ts +230 -0
- package/modules/movement-3d/src/index.ts +46 -0
- package/modules/native-osr/src/index.ts +22 -0
- package/modules/native-osr/src/input-router.ts +401 -0
- package/modules/native-osr/src/native-osr-manager.ts +247 -0
- package/modules/native-osr/src/native-texture-source.ts +86 -0
- package/modules/native-osr/src/types.ts +112 -0
- package/modules/native-osr/src/world-space-ui-pass.ts +351 -0
- package/modules/sailing/README.md +18 -0
- package/modules/sailing/src/index.ts +51 -0
- package/modules/terrain/README.md +18 -0
- package/modules/terrain/src/index.ts +78 -0
- package/modules/vitals/README.md +17 -0
- package/modules/vitals/src/index.ts +43 -0
- package/modules/vitals/src/vitals.ts +265 -0
- package/modules/xr/README.md +25 -0
- package/modules/xr/src/camera-rig.ts +146 -0
- package/modules/xr/src/debug.ts +59 -0
- package/modules/xr/src/frame-loop.ts +197 -0
- package/modules/xr/src/index.ts +16 -0
- package/modules/xr/src/input.ts +208 -0
- package/modules/xr/src/layer.ts +140 -0
- package/modules/xr/src/session.ts +156 -0
- package/modules/xr/src/sim-module.ts +23 -0
- package/modules/xr/src/types.ts +32 -0
- package/modules/xr/src/webxr.d.ts +182 -0
- package/modules/xr/src/xr-module.ts +179 -0
- package/package.json +232 -3
- package/shader-graph/src/chunks.ts +349 -0
- package/shader-graph/src/compiler.ts +732 -0
- package/shader-graph/src/compute-compiler.ts +453 -0
- package/shader-graph/src/compute-graph.ts +112 -0
- package/shader-graph/src/compute-profiles.ts +54 -0
- package/shader-graph/src/graph.ts +52 -0
- package/shader-graph/src/index.ts +53 -0
- package/shader-graph/src/profiles.ts +407 -0
- package/shader-graph/src/validator.ts +48 -0
- package/shader-graph/src/wgsl-binding-validator.ts +143 -0
- package/shader-graph/src/wgsl-struct-validator.ts +245 -0
- package/shader-graph/src/wgsl-struct.ts +397 -0
- package/test/src/index.ts +7 -0
- package/test/src/vision-test.ts +369 -0
|
@@ -0,0 +1,1345 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Model Renderer — renders imported 3D models (FBX/GLTF/OBJ) via WebGPU
|
|
3
|
+
//
|
|
4
|
+
// Bindless binding model: per-draw material data lives in a shared material
|
|
5
|
+
// SSBO (@group(3)), indexed by a `materialIndex` written into the per-draw
|
|
6
|
+
// uniform struct (@group(0)). Textures are registered into the
|
|
7
|
+
// BindlessTextureRegistry's texture_2d_array buckets. The bindless bind group
|
|
8
|
+
// (@group(3)) is set once per frame by the caller (via setBindlessBindGroup)
|
|
9
|
+
// — no per-draw bind-group creation or texture bind-group churn.
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
calculateViewProj,
|
|
14
|
+
createLogger, createValidatedShaderModule, DEPTH_FORMAT,
|
|
15
|
+
MSAA_SAMPLE_COUNT,
|
|
16
|
+
type BindlessMaterialManager,
|
|
17
|
+
type BindlessTextureRegistry,
|
|
18
|
+
type CameraState,
|
|
19
|
+
type MaterialParams
|
|
20
|
+
} from "@downdraft/engine";
|
|
21
|
+
import type { MaterialData, MeshData } from "@downdraft/engine/libraries/models";
|
|
22
|
+
import MODEL_WGSL from "./shaders/model.wgsl?raw" with { type: "text" };
|
|
23
|
+
|
|
24
|
+
const log = createLogger();
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
interface ModelGPUResources {
|
|
28
|
+
vertexBuffer: GPUBuffer;
|
|
29
|
+
indexBuffer: GPUBuffer;
|
|
30
|
+
indexCount: number;
|
|
31
|
+
indexFormat: GPUIndexFormat;
|
|
32
|
+
uniformOffset: number;
|
|
33
|
+
materialIndex: number;
|
|
34
|
+
// Skinning (optional): second vertex buffer with joints+weights, and a flag
|
|
35
|
+
// to select the skinned pipeline + skin-matrix bind group.
|
|
36
|
+
skinVertexBuffer?: GPUBuffer;
|
|
37
|
+
skinned: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ModelRendererBindlessDeps {
|
|
41
|
+
registry: BindlessTextureRegistry;
|
|
42
|
+
materialManager: BindlessMaterialManager;
|
|
43
|
+
/** Bind group layout for @group(3) bindless resources. */
|
|
44
|
+
bindGroupLayout: GPUBindGroupLayout;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ModelRenderer {
|
|
48
|
+
private device: GPUDevice;
|
|
49
|
+
private format: GPUTextureFormat;
|
|
50
|
+
private pipeline: GPURenderPipeline | null = null;
|
|
51
|
+
/** Inverted-hull outline pipeline (front-face culled, vertex-extruded). */
|
|
52
|
+
private maskPipeline: GPURenderPipeline | null = null;
|
|
53
|
+
/** Depth-only pipeline for shadow map rendering (no color targets). */
|
|
54
|
+
private depthPipeline: GPURenderPipeline | null = null;
|
|
55
|
+
private bindGroupLayout: GPUBindGroupLayout | null = null;
|
|
56
|
+
private uniformBuffer: GPUBuffer | null = null;
|
|
57
|
+
private bindGroup: GPUBindGroup | null = null;
|
|
58
|
+
private perSlotBindGroups: GPUBindGroup[] = [];
|
|
59
|
+
|
|
60
|
+
// 4096 slots × 256 bytes = 1 MiB uniform buffer. Character FBX models can
|
|
61
|
+
// have hundreds of meshes (e.g. Aisha has 374 geometry nodes → 374 uniform
|
|
62
|
+
// slots). The old limit of 256 was exhausted by a single character model,
|
|
63
|
+
// leaving zero slots for prop models — props then uploaded 0 meshes but
|
|
64
|
+
// still had their nodeId written to the SAB, making them invisible (skipped
|
|
65
|
+
// by builtin rendering, early-returned by model rendering) while their
|
|
66
|
+
// physics bodies still provided collision.
|
|
67
|
+
private static readonly MAX_MODELS = 4096;
|
|
68
|
+
private static readonly UNIFORM_SIZE = 256; // 64 floats, padded to 256
|
|
69
|
+
/** Initial bone buffer capacity. Grows dynamically when larger skeletons are loaded. */
|
|
70
|
+
private static readonly INITIAL_BONE_CAPACITY = 256;
|
|
71
|
+
|
|
72
|
+
private modelResources: Map<string, ModelGPUResources[]> = new Map();
|
|
73
|
+
// Per-model instance uniform base offsets. Key = nodeId, value = array of
|
|
74
|
+
// base offsets (in bytes) for each instance. Instance 0 uses the original
|
|
75
|
+
// offsets from modelResources; instances 1+ use allocated offsets from here.
|
|
76
|
+
private modelInstanceOffsets: Map<string, number[]> = new Map();
|
|
77
|
+
private viewProjCache: Float32Array | null = null;
|
|
78
|
+
private cameraPosCache: [number, number, number] = [0, 0, 0];
|
|
79
|
+
// Light state for the fragment shader (defaults match the old hardcoded values).
|
|
80
|
+
// lightDir is in world space (does not need to be normalized — the shader normalizes).
|
|
81
|
+
private lightDirCache: [number, number, number] = [0.5, 0.8, 0.3];
|
|
82
|
+
private lightAmbientCache = 0.5;
|
|
83
|
+
private lightIntensityCache = 0.5;
|
|
84
|
+
private nextUniformOffset = 0;
|
|
85
|
+
private reusableUniforms = new Float32Array(64);
|
|
86
|
+
private reusableUniformsU32 = new Uint32Array(this.reusableUniforms.buffer);
|
|
87
|
+
private textureLoadVersion = new Map<string, number>();
|
|
88
|
+
|
|
89
|
+
// Skinning: a shared storage buffer of bone matrices (MAX_BONES * mat4),
|
|
90
|
+
// updated per-frame via updateSkinMatrices. Bound at @group(1) for skinned
|
|
91
|
+
// draws only.
|
|
92
|
+
private skinMatrixBuffer: GPUBuffer | null = null;
|
|
93
|
+
private skinBindGroupLayout: GPUBindGroupLayout | null = null;
|
|
94
|
+
private skinBindGroup: GPUBindGroup | null = null;
|
|
95
|
+
private skinnedPipeline: GPURenderPipeline | null = null;
|
|
96
|
+
private skinBindGroupSetThisFrame = false;
|
|
97
|
+
/** Current bone capacity (number of mat4s the skin buffer can hold). Grows on demand. */
|
|
98
|
+
private boneCapacity = ModelRenderer.INITIAL_BONE_CAPACITY;
|
|
99
|
+
|
|
100
|
+
// Bindless deps
|
|
101
|
+
private bindless: ModelRendererBindlessDeps | null = null;
|
|
102
|
+
private bindlessLayout: GPUBindGroupLayout | null = null;
|
|
103
|
+
private bindlessBindGroup: GPUBindGroup | null = null;
|
|
104
|
+
private bindlessBindGroupSetThisFrame = false;
|
|
105
|
+
|
|
106
|
+
// Frame-global lighting bind group (group 2). Provides sun color, hemisphere
|
|
107
|
+
// ambient, and point lights. Defaults to a neutral white-sun / no-point-light
|
|
108
|
+
// bind group so existing consumers see no visual change. Games with colored
|
|
109
|
+
// lighting call setFrameLightingBindGroup() each frame to override.
|
|
110
|
+
private frameLightingLayout: GPUBindGroupLayout | null = null;
|
|
111
|
+
private defaultFrameLightingBuffer: GPUBuffer | null = null;
|
|
112
|
+
private defaultFrameLightingBg: GPUBindGroup | null = null;
|
|
113
|
+
private frameLightingBg: GPUBindGroup | null = null;
|
|
114
|
+
private frameLightingBgSetThisFrame = false;
|
|
115
|
+
/** materialIndex for the default white material (albedo = default white layer). */
|
|
116
|
+
private defaultMaterialIndex = 0;
|
|
117
|
+
/** Composite key `${nodeId}:${matIdx}` → bindless materialIndex, so reupload reuses the same slot. */
|
|
118
|
+
private meshMaterialIndex = new Map<string, number>();
|
|
119
|
+
/** Composite key `${nodeId}:${matIdx}` → texture sourceId (used for async update + cleanup). */
|
|
120
|
+
private meshTextureSourceId = new Map<string, string>();
|
|
121
|
+
private meshNormalTextureSourceId = new Map<string, string>();
|
|
122
|
+
/** Track the current albedo handle per materialKey (for normal texture updates). */
|
|
123
|
+
private meshAlbedoHandle = new Map<string, number>();
|
|
124
|
+
/** When true, newly registered mesh textures get a full mip chain.
|
|
125
|
+
* Toggle takes effect on the next uploadModel/reuploadModel (textures are
|
|
126
|
+
* re-registered into a mipmapped bucket). */
|
|
127
|
+
private mipmapsEnabled = false;
|
|
128
|
+
|
|
129
|
+
constructor(device: GPUDevice, format: GPUTextureFormat) {
|
|
130
|
+
this.device = device;
|
|
131
|
+
this.format = format;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Provide the bindless texture registry + material manager. Required before init. */
|
|
135
|
+
setBindlessDeps(deps: ModelRendererBindlessDeps): void {
|
|
136
|
+
this.bindless = deps;
|
|
137
|
+
this.bindlessLayout = deps.bindGroupLayout;
|
|
138
|
+
// Register a default white material (all texture slots → default white handle).
|
|
139
|
+
this.defaultMaterialIndex = deps.materialManager.registerMaterial({
|
|
140
|
+
baseColor: [1, 1, 1, 1],
|
|
141
|
+
roughness: 1,
|
|
142
|
+
metallic: 0,
|
|
143
|
+
emissiveIntensity: 0,
|
|
144
|
+
albedoTexHandle: deps.registry.defaultWhiteHandle,
|
|
145
|
+
normalTexHandle: deps.registry.defaultNormalHandle,
|
|
146
|
+
metallicRoughnessTexHandle: deps.registry.defaultWhiteHandle,
|
|
147
|
+
aoTexHandle: deps.registry.defaultWhiteHandle,
|
|
148
|
+
emissiveTexHandle: deps.registry.defaultWhiteHandle,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Set the shared bindless bind group (@group(3)) for the frame. */
|
|
153
|
+
setBindlessBindGroup(bg: GPUBindGroup | null): void {
|
|
154
|
+
this.bindlessBindGroup = bg;
|
|
155
|
+
this.bindlessBindGroupSetThisFrame = false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Set the frame-global lighting bind group (group 2). Provides sun color,
|
|
160
|
+
* hemisphere ambient, and point lights via a uniform buffer matching the
|
|
161
|
+
* FrameLighting WGSL struct. Call once per frame before render(). If never
|
|
162
|
+
* called, a default neutral bind group is used (white sun, no point lights).
|
|
163
|
+
*/
|
|
164
|
+
setFrameLightingBindGroup(bg: GPUBindGroup | null): void {
|
|
165
|
+
this.frameLightingBg = bg;
|
|
166
|
+
this.frameLightingBgSetThisFrame = false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Get the frame-lighting bind group layout (for creating compatible bind groups). */
|
|
170
|
+
getFrameLightingLayout(): GPUBindGroupLayout | null {
|
|
171
|
+
return this.frameLightingLayout;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async init(): Promise<void> {
|
|
175
|
+
this.uniformBuffer = this.device.createBuffer({
|
|
176
|
+
size: ModelRenderer.MAX_MODELS * ModelRenderer.UNIFORM_SIZE,
|
|
177
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Explicit bind group layout for group(0) — per-draw uniform with
|
|
181
|
+
// hasDynamicOffset so we can index into the uniform buffer per instance.
|
|
182
|
+
this.bindGroupLayout = this.device.createBindGroupLayout({
|
|
183
|
+
entries: [
|
|
184
|
+
{
|
|
185
|
+
binding: 0,
|
|
186
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
187
|
+
buffer: { type: "uniform", hasDynamicOffset: true },
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
this.bindGroup = this.device.createBindGroup({
|
|
193
|
+
layout: this.bindGroupLayout,
|
|
194
|
+
entries: [
|
|
195
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer, size: ModelRenderer.UNIFORM_SIZE } },
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
// Per-slot bind groups with the uniform offset baked into the resource.
|
|
199
|
+
// The layout declares hasDynamicOffset, so bind calls must pass [0].
|
|
200
|
+
this.perSlotBindGroups = [];
|
|
201
|
+
for (let i = 0; i < ModelRenderer.MAX_MODELS; i++) {
|
|
202
|
+
this.perSlotBindGroups.push(this.device.createBindGroup({
|
|
203
|
+
layout: this.bindGroupLayout,
|
|
204
|
+
entries: [{ binding: 0, resource: { buffer: this.uniformBuffer, offset: i * ModelRenderer.UNIFORM_SIZE, size: ModelRenderer.UNIFORM_SIZE } }],
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Frame-lighting bind group layout (group 2): a single uniform buffer
|
|
209
|
+
// providing sun color, hemisphere ambient, and point lights. A default
|
|
210
|
+
// neutral bind group is created so existing consumers see no visual change.
|
|
211
|
+
this.frameLightingLayout = this.device.createBindGroupLayout({
|
|
212
|
+
label: "model-frame-lighting-layout",
|
|
213
|
+
entries: [
|
|
214
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
// Default frame-lighting UBO: white sun, neutral hemisphere, no point lights.
|
|
218
|
+
// Layout: sunDir(3)+ambientIntensity(1), sunColor(3)+pointLightCount(1),
|
|
219
|
+
// skyAmbient(3)+pad(1), groundAmbient(3)+pad(1), pointLights(16 vec4s)
|
|
220
|
+
const defaultLightingData = new Float32Array(80);
|
|
221
|
+
// sunDir must be non-zero: fs_main does normalize(sunDir), and a zero
|
|
222
|
+
// vector produces NaN that turns the entire fragment output black.
|
|
223
|
+
defaultLightingData[0] = 0.4; defaultLightingData[1] = 0.8; defaultLightingData[2] = 0.3; // sunDir
|
|
224
|
+
defaultLightingData[3] = 1.0; // ambientIntensity
|
|
225
|
+
defaultLightingData[4] = 1.0; defaultLightingData[5] = 1.0; defaultLightingData[6] = 1.0; // sunColor
|
|
226
|
+
defaultLightingData[8] = 1.0; defaultLightingData[9] = 1.0; defaultLightingData[10] = 1.0; // skyAmbient
|
|
227
|
+
defaultLightingData[12] = 1.0; defaultLightingData[13] = 1.0; defaultLightingData[14] = 1.0; // groundAmbient
|
|
228
|
+
this.defaultFrameLightingBuffer = this.device.createBuffer({
|
|
229
|
+
label: "model-default-frame-lighting",
|
|
230
|
+
size: defaultLightingData.byteLength,
|
|
231
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
232
|
+
});
|
|
233
|
+
this.device.queue.writeBuffer(this.defaultFrameLightingBuffer, 0, defaultLightingData as Float32Array<ArrayBuffer>);
|
|
234
|
+
this.defaultFrameLightingBg = this.device.createBindGroup({
|
|
235
|
+
label: "model-default-frame-lighting-bg",
|
|
236
|
+
layout: this.frameLightingLayout,
|
|
237
|
+
entries: [{ binding: 0, resource: { buffer: this.defaultFrameLightingBuffer } }],
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const shaderModule = createValidatedShaderModule(this.device, { code: MODEL_WGSL, label: "ModelRenderer" });
|
|
241
|
+
// Explicit pipeline layout: group(0) = per-draw uniform (dynamic offset),
|
|
242
|
+
// group(2) = frame-lighting UBO, group(3) = bindless materials SSBO +
|
|
243
|
+
// texture arrays. Group 1 is unused by the non-skinned pipeline (empty layout).
|
|
244
|
+
const emptyLayout = this.device.createBindGroupLayout({ entries: [] });
|
|
245
|
+
const pipelineLayout = this.device.createPipelineLayout({
|
|
246
|
+
bindGroupLayouts: [this.bindGroupLayout, emptyLayout, this.frameLightingLayout, this.bindlessLayout],
|
|
247
|
+
});
|
|
248
|
+
this.pipeline = this.device.createRenderPipeline({
|
|
249
|
+
layout: pipelineLayout,
|
|
250
|
+
vertex: {
|
|
251
|
+
module: shaderModule,
|
|
252
|
+
entryPoint: "vs_main",
|
|
253
|
+
buffers: [
|
|
254
|
+
{
|
|
255
|
+
arrayStride: 44, // pos3 + normal3 + uv2 + color3 = 11 floats
|
|
256
|
+
attributes: [
|
|
257
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
258
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" },
|
|
259
|
+
{ shaderLocation: 2, offset: 24, format: "float32x2" },
|
|
260
|
+
{ shaderLocation: 3, offset: 32, format: "float32x3" },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
fragment: {
|
|
266
|
+
module: shaderModule,
|
|
267
|
+
entryPoint: "fs_main",
|
|
268
|
+
targets: [{ format: this.format }],
|
|
269
|
+
},
|
|
270
|
+
primitive: { topology: "triangle-list" },
|
|
271
|
+
multisample: { count: MSAA_SAMPLE_COUNT },
|
|
272
|
+
depthStencil: {
|
|
273
|
+
format: DEPTH_FORMAT,
|
|
274
|
+
depthWriteEnabled: true,
|
|
275
|
+
depthCompare: "less",
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ── Skinning resources ──
|
|
280
|
+
// Shared storage buffer for bone matrices. Starts at INITIAL_BONE_CAPACITY
|
|
281
|
+
// and grows on demand when larger skeletons are loaded.
|
|
282
|
+
this.skinMatrixBuffer = this.device.createBuffer({
|
|
283
|
+
size: this.boneCapacity * 64, // mat4x4 = 64 bytes
|
|
284
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Bind group layout for @group(1): a single read-only storage buffer.
|
|
288
|
+
this.skinBindGroupLayout = this.device.createBindGroupLayout({
|
|
289
|
+
entries: [
|
|
290
|
+
{
|
|
291
|
+
binding: 0,
|
|
292
|
+
visibility: GPUShaderStage.VERTEX,
|
|
293
|
+
buffer: { type: "read-only-storage" },
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
this.skinBindGroup = this.device.createBindGroup({
|
|
299
|
+
layout: this.skinBindGroupLayout,
|
|
300
|
+
entries: [{ binding: 0, resource: { buffer: this.skinMatrixBuffer } }],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Skinned pipeline: same pipeline layout (group 1 is now the skin layout
|
|
304
|
+
// instead of an empty layout, group 2 is the frame-lighting layout), with
|
|
305
|
+
// a second vertex buffer for joints + weights and the vs_skinned entry point.
|
|
306
|
+
const skinnedPipelineLayout = this.device.createPipelineLayout({
|
|
307
|
+
bindGroupLayouts: [this.bindGroupLayout, this.skinBindGroupLayout, this.frameLightingLayout, this.bindlessLayout],
|
|
308
|
+
});
|
|
309
|
+
this.skinnedPipeline = this.device.createRenderPipeline({
|
|
310
|
+
layout: skinnedPipelineLayout,
|
|
311
|
+
vertex: {
|
|
312
|
+
module: shaderModule,
|
|
313
|
+
entryPoint: "vs_skinned",
|
|
314
|
+
buffers: [
|
|
315
|
+
{
|
|
316
|
+
arrayStride: 44, // pos3 + normal3 + uv2 + color3 = 11 floats
|
|
317
|
+
attributes: [
|
|
318
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
319
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" },
|
|
320
|
+
{ shaderLocation: 2, offset: 24, format: "float32x2" },
|
|
321
|
+
{ shaderLocation: 3, offset: 32, format: "float32x3" },
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
// joints (4 x uint8 packed as uint32x4 = 16 bytes) + weights (4 x float32 = 16 bytes) = 32 bytes
|
|
326
|
+
arrayStride: 32,
|
|
327
|
+
attributes: [
|
|
328
|
+
{ shaderLocation: 4, offset: 0, format: "uint32x4" },
|
|
329
|
+
{ shaderLocation: 5, offset: 16, format: "float32x4" },
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
fragment: {
|
|
335
|
+
module: shaderModule,
|
|
336
|
+
entryPoint: "fs_main",
|
|
337
|
+
targets: [{ format: this.format }],
|
|
338
|
+
},
|
|
339
|
+
primitive: { topology: "triangle-list" },
|
|
340
|
+
multisample: { count: MSAA_SAMPLE_COUNT },
|
|
341
|
+
depthStencil: {
|
|
342
|
+
format: DEPTH_FORMAT,
|
|
343
|
+
depthWriteEnabled: true,
|
|
344
|
+
depthCompare: "less",
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ── Mask pipeline for post-process outline ──
|
|
349
|
+
// Renders the model as solid white to a mask texture. Uses the same
|
|
350
|
+
// vertex shader as the normal render (vs_main) with back-face culling
|
|
351
|
+
// and depth testing (read-only) so the mask respects occlusion.
|
|
352
|
+
this.maskPipeline = this.device.createRenderPipeline({
|
|
353
|
+
layout: pipelineLayout,
|
|
354
|
+
vertex: {
|
|
355
|
+
module: shaderModule,
|
|
356
|
+
entryPoint: "vs_main",
|
|
357
|
+
buffers: [
|
|
358
|
+
{
|
|
359
|
+
arrayStride: 44,
|
|
360
|
+
attributes: [
|
|
361
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
362
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" },
|
|
363
|
+
{ shaderLocation: 2, offset: 24, format: "float32x2" },
|
|
364
|
+
{ shaderLocation: 3, offset: 32, format: "float32x3" },
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
fragment: {
|
|
370
|
+
module: shaderModule,
|
|
371
|
+
entryPoint: "fs_mask",
|
|
372
|
+
targets: [{ format: "rgba8unorm" }],
|
|
373
|
+
},
|
|
374
|
+
primitive: { topology: "triangle-list", cullMode: "back" },
|
|
375
|
+
depthStencil: {
|
|
376
|
+
format: DEPTH_FORMAT,
|
|
377
|
+
depthWriteEnabled: false,
|
|
378
|
+
depthCompare: "less-equal",
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ── Depth-only pipeline for shadow map rendering ──
|
|
383
|
+
// Reuses vs_main (same vertex transform) but has no fragment shader /
|
|
384
|
+
// color targets — only writes depth. Uses a minimal pipeline layout with
|
|
385
|
+
// just group(0) (the per-draw uniform with dynamic offset) so the caller
|
|
386
|
+
// doesn't need to bind lighting/bindless groups for the shadow pass.
|
|
387
|
+
const depthPipelineLayout = this.device.createPipelineLayout({
|
|
388
|
+
bindGroupLayouts: [this.bindGroupLayout],
|
|
389
|
+
});
|
|
390
|
+
this.depthPipeline = this.device.createRenderPipeline({
|
|
391
|
+
label: "model-depth-only",
|
|
392
|
+
layout: depthPipelineLayout,
|
|
393
|
+
vertex: {
|
|
394
|
+
module: shaderModule,
|
|
395
|
+
entryPoint: "vs_main",
|
|
396
|
+
buffers: [
|
|
397
|
+
{
|
|
398
|
+
arrayStride: 44,
|
|
399
|
+
attributes: [
|
|
400
|
+
{ shaderLocation: 0, offset: 0, format: "float32x3" },
|
|
401
|
+
{ shaderLocation: 1, offset: 12, format: "float32x3" },
|
|
402
|
+
{ shaderLocation: 2, offset: 24, format: "float32x2" },
|
|
403
|
+
{ shaderLocation: 3, offset: 32, format: "float32x3" },
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
primitive: { topology: "triangle-list", cullMode: "back" },
|
|
409
|
+
depthStencil: {
|
|
410
|
+
format: DEPTH_FORMAT,
|
|
411
|
+
depthWriteEnabled: true,
|
|
412
|
+
depthCompare: "less",
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Upload a model's meshes to GPU buffers and allocate uniform slots.
|
|
419
|
+
* Returns the number of meshes actually uploaded — this may be less than
|
|
420
|
+
* `meshes.length` (or 0) if the uniform buffer is full. Callers should
|
|
421
|
+
* check the return value and fall back appropriately when 0 meshes are
|
|
422
|
+
* uploaded (e.g. render as a builtin cube instead of leaving the entity
|
|
423
|
+
* invisible).
|
|
424
|
+
*/
|
|
425
|
+
uploadModel(nodeId: string, meshes: MeshData[], materials?: MaterialData[], modelBaseUrl?: string): number {
|
|
426
|
+
this.removeModel(nodeId);
|
|
427
|
+
|
|
428
|
+
const resources: ModelGPUResources[] = [];
|
|
429
|
+
let uniformOffset = this.nextUniformOffset;
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < meshes.length && uniformOffset < ModelRenderer.MAX_MODELS; i++) {
|
|
432
|
+
const mesh = meshes[i];
|
|
433
|
+
// Degenerate mesh (partial parse failure, empty geometry): nothing to
|
|
434
|
+
// draw, and a 0-byte index buffer trips wgpu-native's setIndexBuffer
|
|
435
|
+
// validation — a Rust panic that aborts the whole process.
|
|
436
|
+
if (mesh.indexCount === 0 || mesh.vertexCount === 0 || mesh.indices.byteLength === 0) continue;
|
|
437
|
+
const vertexCount = mesh.vertexCount;
|
|
438
|
+
const stride = 11; // pos3 + normal3 + uv2 + color3
|
|
439
|
+
const interleaved = new Float32Array(vertexCount * stride);
|
|
440
|
+
|
|
441
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
442
|
+
interleaved[v * stride] = mesh.vertices[v * 6];
|
|
443
|
+
interleaved[v * stride + 1] = mesh.vertices[v * 6 + 1];
|
|
444
|
+
interleaved[v * stride + 2] = mesh.vertices[v * 6 + 2];
|
|
445
|
+
interleaved[v * stride + 3] = mesh.vertices[v * 6 + 3];
|
|
446
|
+
interleaved[v * stride + 4] = mesh.vertices[v * 6 + 4];
|
|
447
|
+
interleaved[v * stride + 5] = mesh.vertices[v * 6 + 5];
|
|
448
|
+
interleaved[v * stride + 6] = mesh.uvs ? mesh.uvs[v * 2] : 0;
|
|
449
|
+
interleaved[v * stride + 7] = mesh.uvs ? mesh.uvs[v * 2 + 1] : 0;
|
|
450
|
+
interleaved[v * stride + 8] = mesh.colors ? mesh.colors[v * 3] : 1;
|
|
451
|
+
interleaved[v * stride + 9] = mesh.colors ? mesh.colors[v * 3 + 1] : 1;
|
|
452
|
+
interleaved[v * stride + 10] = mesh.colors ? mesh.colors[v * 3 + 2] : 1;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const vertexBuffer = this.device.createBuffer({
|
|
456
|
+
size: interleaved.byteLength,
|
|
457
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
458
|
+
});
|
|
459
|
+
this.device.queue.writeBuffer(vertexBuffer, 0, interleaved as Float32Array<ArrayBuffer>);
|
|
460
|
+
|
|
461
|
+
const indexFormat: GPUIndexFormat =
|
|
462
|
+
mesh.indices instanceof Uint32Array ? "uint32" : "uint16";
|
|
463
|
+
const indexByteLength = mesh.indices.byteLength;
|
|
464
|
+
const paddedIndexSize = Math.ceil(indexByteLength / 4) * 4;
|
|
465
|
+
const indexBuffer = this.device.createBuffer({
|
|
466
|
+
size: paddedIndexSize,
|
|
467
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
468
|
+
});
|
|
469
|
+
if (paddedIndexSize === indexByteLength) {
|
|
470
|
+
this.device.queue.writeBuffer(indexBuffer, 0, mesh.indices as (Uint16Array<ArrayBuffer> | Uint32Array<ArrayBuffer>));
|
|
471
|
+
} else {
|
|
472
|
+
const padded = new Uint8Array(paddedIndexSize);
|
|
473
|
+
padded.set(new Uint8Array(mesh.indices.buffer, mesh.indices.byteOffset, indexByteLength));
|
|
474
|
+
this.device.queue.writeBuffer(indexBuffer, 0, padded);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Determine the bindless material index for this mesh based on its
|
|
478
|
+
// materialIndex field (index into ModelData.materials). Each unique
|
|
479
|
+
// (nodeId, matIdx) gets its own bindless material slot so multi-material
|
|
480
|
+
// models render each sub-mesh with its own texture.
|
|
481
|
+
const matIdx = mesh.materialIndex ?? 0;
|
|
482
|
+
const materialKey = `${nodeId}:${matIdx}`;
|
|
483
|
+
let bindlessMatIndex = this.defaultMaterialIndex;
|
|
484
|
+
if (this.bindless) {
|
|
485
|
+
const existing = this.meshMaterialIndex.get(materialKey);
|
|
486
|
+
if (existing !== undefined) {
|
|
487
|
+
bindlessMatIndex = existing;
|
|
488
|
+
} else {
|
|
489
|
+
const mat = materials?.[matIdx];
|
|
490
|
+
bindlessMatIndex = this.bindless.materialManager.registerMaterial({
|
|
491
|
+
baseColor: mat?.baseColor ?? [1, 1, 1, 1],
|
|
492
|
+
roughness: mat?.roughness ?? 1,
|
|
493
|
+
metallic: mat?.metallic ?? 0,
|
|
494
|
+
emissiveIntensity: 0,
|
|
495
|
+
albedoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
496
|
+
normalTexHandle: this.bindless.registry.defaultNormalHandle,
|
|
497
|
+
metallicRoughnessTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
498
|
+
aoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
499
|
+
emissiveTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
500
|
+
});
|
|
501
|
+
this.meshMaterialIndex.set(materialKey, bindlessMatIndex);
|
|
502
|
+
|
|
503
|
+
// Async load this material's texture (embedded data or external URI)
|
|
504
|
+
if (mat?.textureData && mat.textureData.byteLength > 0) {
|
|
505
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
506
|
+
this.textureLoadVersion.set(materialKey, version);
|
|
507
|
+
this.loadMeshTexture(materialKey, mat.textureData, version);
|
|
508
|
+
} else if (mat?.textureUri && modelBaseUrl) {
|
|
509
|
+
// Resolve relative texture URI against the model's base URL.
|
|
510
|
+
const resolvedUri = modelBaseUrl + mat.textureUri;
|
|
511
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
512
|
+
this.textureLoadVersion.set(materialKey, version);
|
|
513
|
+
this.loadMeshTextureFromUri(materialKey, resolvedUri, version);
|
|
514
|
+
}
|
|
515
|
+
// Async load the normal texture if present.
|
|
516
|
+
if ((mat?.normalTextureData && mat.normalTextureData.byteLength > 0) || mat?.normalTextureUri) {
|
|
517
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
518
|
+
this.loadMeshNormalTexture(materialKey, mat.normalTextureData ?? null, mat?.normalTextureUri, version);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Build the skin vertex buffer (joints + weights) if this mesh is skinned.
|
|
524
|
+
const skinVertexBuffer = this.buildSkinVertexBuffer(mesh);
|
|
525
|
+
|
|
526
|
+
resources.push({
|
|
527
|
+
vertexBuffer,
|
|
528
|
+
indexBuffer,
|
|
529
|
+
indexCount: mesh.indexCount,
|
|
530
|
+
indexFormat,
|
|
531
|
+
uniformOffset: uniformOffset * ModelRenderer.UNIFORM_SIZE,
|
|
532
|
+
materialIndex: bindlessMatIndex,
|
|
533
|
+
skinVertexBuffer,
|
|
534
|
+
skinned: skinVertexBuffer !== undefined,
|
|
535
|
+
});
|
|
536
|
+
uniformOffset++;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
this.nextUniformOffset = uniformOffset;
|
|
540
|
+
this.modelResources.set(nodeId, resources);
|
|
541
|
+
|
|
542
|
+
if (resources.length < meshes.length) {
|
|
543
|
+
log.warn(
|
|
544
|
+
"ModelRenderer",
|
|
545
|
+
`Uniform buffer full: uploaded ${resources.length}/${meshes.length} meshes for "${nodeId}" ` +
|
|
546
|
+
`(${uniformOffset}/${ModelRenderer.MAX_MODELS} slots used). ` +
|
|
547
|
+
`Increase MAX_MODELS to avoid invisible models.`,
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return resources.length;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Allocate an additional uniform slot for an existing model, sharing its
|
|
556
|
+
* vertex/index buffers. Returns the instance index (1-based, since instance
|
|
557
|
+
* 0 uses the original offsets), or -1 if the model doesn't exist or the
|
|
558
|
+
* uniform buffer is full.
|
|
559
|
+
*
|
|
560
|
+
* Use with render()'s instanceIndex parameter to draw multiple instances of
|
|
561
|
+
* the same model at different positions in a single frame. Without this,
|
|
562
|
+
* multiple render() calls with the same nodeId overwrite each other's
|
|
563
|
+
* uniforms (device.queue.writeBuffer is a queue-level op, so all writes
|
|
564
|
+
* execute before the render pass — only the last write is visible).
|
|
565
|
+
*/
|
|
566
|
+
allocateInstance(nodeId: string): number {
|
|
567
|
+
const resources = this.modelResources.get(nodeId);
|
|
568
|
+
if (!resources || resources.length === 0) return -1;
|
|
569
|
+
|
|
570
|
+
const meshCount = resources.length;
|
|
571
|
+
if (this.nextUniformOffset + meshCount > ModelRenderer.MAX_MODELS) return -1;
|
|
572
|
+
|
|
573
|
+
// Allocate `meshCount` contiguous uniform slots for this instance.
|
|
574
|
+
const baseOffset = this.nextUniformOffset * ModelRenderer.UNIFORM_SIZE;
|
|
575
|
+
this.nextUniformOffset += meshCount;
|
|
576
|
+
|
|
577
|
+
const instances = this.modelInstanceOffsets.get(nodeId) ?? [];
|
|
578
|
+
instances.push(baseOffset);
|
|
579
|
+
this.modelInstanceOffsets.set(nodeId, instances);
|
|
580
|
+
return instances.length; // 1-based instance index
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Free all instance uniform slots for a model (except instance 0 which is
|
|
585
|
+
* tied to the model upload). Call when an instance is no longer needed.
|
|
586
|
+
*/
|
|
587
|
+
freeInstances(nodeId: string): void {
|
|
588
|
+
const instances = this.modelInstanceOffsets.get(nodeId);
|
|
589
|
+
if (instances && instances.length > 0) {
|
|
590
|
+
// Reclaim uniform slots (simple: just reset nextUniformOffset if these
|
|
591
|
+
// were the last allocated. A production implementation would use a free
|
|
592
|
+
// list, but instance lifetimes are typically long-lived.)
|
|
593
|
+
this.nextUniformOffset = Math.max(0, this.nextUniformOffset - instances.length * this.modelResources.get(nodeId)!.length);
|
|
594
|
+
instances.length = 0;
|
|
595
|
+
}
|
|
596
|
+
this.modelInstanceOffsets.delete(nodeId);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Get the number of allocated instances for a model (0 = only the original). */
|
|
600
|
+
getInstanceCount(nodeId: string): number {
|
|
601
|
+
return this.modelInstanceOffsets.get(nodeId)?.length ?? 0;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Build the per-mesh skin vertex buffer: joints as uint32x4 (4 bone indices,
|
|
606
|
+
* upcast from uint8/uint16/uint32 source) followed by weights as float32x4.
|
|
607
|
+
* 32 bytes/vertex. Returns undefined when the mesh has no skinning data.
|
|
608
|
+
*/
|
|
609
|
+
private buildSkinVertexBuffer(mesh: MeshData): GPUBuffer | undefined {
|
|
610
|
+
if (!mesh.joints || !mesh.weights || mesh.joints.length < mesh.vertexCount * 4) return undefined;
|
|
611
|
+
const vertexCount = mesh.vertexCount;
|
|
612
|
+
// 8 slots of 4 bytes per vertex: [j0,j1,j2,j3, w0,w1,w2,w3] = 32 bytes.
|
|
613
|
+
const data = new Float32Array(vertexCount * 8);
|
|
614
|
+
const dataU32 = new Uint32Array(data.buffer);
|
|
615
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
616
|
+
const src = v * 4;
|
|
617
|
+
const dst = v * 8;
|
|
618
|
+
dataU32[dst] = mesh.joints[src];
|
|
619
|
+
dataU32[dst + 1] = mesh.joints[src + 1];
|
|
620
|
+
dataU32[dst + 2] = mesh.joints[src + 2];
|
|
621
|
+
dataU32[dst + 3] = mesh.joints[src + 3];
|
|
622
|
+
data[dst + 4] = mesh.weights[src];
|
|
623
|
+
data[dst + 5] = mesh.weights[src + 1];
|
|
624
|
+
data[dst + 6] = mesh.weights[src + 2];
|
|
625
|
+
data[dst + 7] = mesh.weights[src + 3];
|
|
626
|
+
}
|
|
627
|
+
const buffer = this.device.createBuffer({
|
|
628
|
+
size: data.byteLength,
|
|
629
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
630
|
+
});
|
|
631
|
+
this.device.queue.writeBuffer(buffer, 0, data as Float32Array<ArrayBuffer>);
|
|
632
|
+
return buffer;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private async loadMeshTexture(materialKey: string, textureData: ArrayBuffer, version: number): Promise<void> {
|
|
636
|
+
if (!this.bindless) return;
|
|
637
|
+
try {
|
|
638
|
+
const blob = new Blob([textureData]);
|
|
639
|
+
const imageBitmap = await createImageBitmap(blob, { premultiplyAlpha: 'none', colorSpaceConversion: 'none' });
|
|
640
|
+
|
|
641
|
+
if (this.textureLoadVersion.get(materialKey) !== version) {
|
|
642
|
+
imageBitmap.close();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Register the bitmap into the bindless texture registry. The sourceId
|
|
647
|
+
// is the materialKey so reuploadModel can reuse/update it.
|
|
648
|
+
const sourceId = `model:${materialKey}`;
|
|
649
|
+
const existing = this.bindless.registry.getRegistration(sourceId);
|
|
650
|
+
let handle: number;
|
|
651
|
+
if (existing) {
|
|
652
|
+
// Update in place (same dimensions expected).
|
|
653
|
+
this.bindless.registry.updateFromImageBitmap(sourceId, imageBitmap);
|
|
654
|
+
handle = existing.handle;
|
|
655
|
+
} else {
|
|
656
|
+
// Atlas textures must not be mipmapped — adjacent atlas regions bleed
|
|
657
|
+
// into each other during mip downsample, causing distance-based color shifts.
|
|
658
|
+
// Albedo/color textures are sRGB-encoded (e.g. Kenney colormap.png) — use
|
|
659
|
+
// the srgb format so the GPU decodes to linear on sample. Treating them as
|
|
660
|
+
// linear (rgba8unorm) washes out midtones since the pipeline renders in
|
|
661
|
+
// linear HDR and re-encodes to sRGB at the tonemap/output stage.
|
|
662
|
+
const reg = this.bindless.registry.registerFromImageBitmap(sourceId, imageBitmap, "rgba8unorm-srgb", 1, this.mipmapsEnabled);
|
|
663
|
+
handle = reg.handle;
|
|
664
|
+
}
|
|
665
|
+
this.meshTextureSourceId.set(materialKey, sourceId);
|
|
666
|
+
this.meshAlbedoHandle.set(materialKey, handle);
|
|
667
|
+
|
|
668
|
+
// Update the material to point at the real albedo texture.
|
|
669
|
+
const materialIndex = this.meshMaterialIndex.get(materialKey);
|
|
670
|
+
if (materialIndex !== undefined) {
|
|
671
|
+
const matParams: MaterialParams = {
|
|
672
|
+
baseColor: [1, 1, 1, 1],
|
|
673
|
+
roughness: 1,
|
|
674
|
+
metallic: 0,
|
|
675
|
+
emissiveIntensity: 0,
|
|
676
|
+
albedoTexHandle: handle,
|
|
677
|
+
normalTexHandle: this.bindless.registry.defaultNormalHandle,
|
|
678
|
+
metallicRoughnessTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
679
|
+
aoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
680
|
+
emissiveTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
681
|
+
};
|
|
682
|
+
this.bindless.materialManager.updateMaterial(materialIndex, matParams);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
log.info("ModelRenderer", `Texture ready for ${materialKey}: ${imageBitmap.width}x${imageBitmap.height} (bindless)`);
|
|
686
|
+
imageBitmap.close();
|
|
687
|
+
} catch (e) {
|
|
688
|
+
log.error("ModelRenderer", `Failed to load texture for ${materialKey}: ${e}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Async-load a base color texture from an external URI (for GLBs that
|
|
694
|
+
* reference textures by relative path instead of embedding them).
|
|
695
|
+
*/
|
|
696
|
+
private async loadMeshTextureFromUri(materialKey: string, textureUri: string, version: number): Promise<void> {
|
|
697
|
+
if (!this.bindless) return;
|
|
698
|
+
try {
|
|
699
|
+
const resp = await fetch(textureUri);
|
|
700
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
701
|
+
const blob = await resp.blob();
|
|
702
|
+
const imageBitmap = await createImageBitmap(blob, { premultiplyAlpha: 'none', colorSpaceConversion: 'none' });
|
|
703
|
+
|
|
704
|
+
if (this.textureLoadVersion.get(materialKey) !== version) {
|
|
705
|
+
imageBitmap.close();
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const sourceId = `model:${materialKey}`;
|
|
710
|
+
const existing = this.bindless.registry.getRegistration(sourceId);
|
|
711
|
+
let handle: number;
|
|
712
|
+
if (existing) {
|
|
713
|
+
this.bindless.registry.updateFromImageBitmap(sourceId, imageBitmap);
|
|
714
|
+
handle = existing.handle;
|
|
715
|
+
} else {
|
|
716
|
+
// sRGB-encoded albedo (see loadMeshTexture for rationale).
|
|
717
|
+
const reg = this.bindless.registry.registerFromImageBitmap(sourceId, imageBitmap, "rgba8unorm-srgb", 1, this.mipmapsEnabled);
|
|
718
|
+
handle = reg.handle;
|
|
719
|
+
}
|
|
720
|
+
this.meshTextureSourceId.set(materialKey, sourceId);
|
|
721
|
+
this.meshAlbedoHandle.set(materialKey, handle);
|
|
722
|
+
|
|
723
|
+
const materialIndex = this.meshMaterialIndex.get(materialKey);
|
|
724
|
+
if (materialIndex !== undefined) {
|
|
725
|
+
const matParams: MaterialParams = {
|
|
726
|
+
baseColor: [1, 1, 1, 1],
|
|
727
|
+
roughness: 1,
|
|
728
|
+
metallic: 0,
|
|
729
|
+
emissiveIntensity: 0,
|
|
730
|
+
albedoTexHandle: handle,
|
|
731
|
+
normalTexHandle: this.bindless.registry.defaultNormalHandle,
|
|
732
|
+
metallicRoughnessTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
733
|
+
aoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
734
|
+
emissiveTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
735
|
+
};
|
|
736
|
+
this.bindless.materialManager.updateMaterial(materialIndex, matParams);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
imageBitmap.close();
|
|
740
|
+
} catch (e) {
|
|
741
|
+
log.error("ModelRenderer", `Failed to load texture from URI for ${materialKey} (${textureUri}): ${e}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Async-load a normal texture for a material and update the bindless material
|
|
747
|
+
* to point at it. Normal textures are loaded from embedded bufferView data
|
|
748
|
+
* (normalTextureData) or fetched from normalTextureUri.
|
|
749
|
+
*/
|
|
750
|
+
private async loadMeshNormalTexture(materialKey: string, textureData: ArrayBuffer | null, textureUri: string | undefined, version: number): Promise<void> {
|
|
751
|
+
if (!this.bindless) return;
|
|
752
|
+
try {
|
|
753
|
+
let imageBitmap: ImageBitmap;
|
|
754
|
+
if (textureData && textureData.byteLength > 0) {
|
|
755
|
+
const blob = new Blob([textureData]);
|
|
756
|
+
imageBitmap = await createImageBitmap(blob, { premultiplyAlpha: 'none', colorSpaceConversion: 'none' });
|
|
757
|
+
} else if (textureUri) {
|
|
758
|
+
const resp = await fetch(textureUri);
|
|
759
|
+
const blob = await resp.blob();
|
|
760
|
+
imageBitmap = await createImageBitmap(blob, { premultiplyAlpha: 'none', colorSpaceConversion: 'none' });
|
|
761
|
+
} else {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
if (this.textureLoadVersion.get(materialKey) !== version) {
|
|
766
|
+
imageBitmap.close();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const sourceId = `model-normal:${materialKey}`;
|
|
771
|
+
const existing = this.bindless.registry.getRegistration(sourceId);
|
|
772
|
+
let handle: number;
|
|
773
|
+
if (existing) {
|
|
774
|
+
this.bindless.registry.updateFromImageBitmap(sourceId, imageBitmap);
|
|
775
|
+
handle = existing.handle;
|
|
776
|
+
} else {
|
|
777
|
+
const reg = this.bindless.registry.registerFromImageBitmap(sourceId, imageBitmap, "rgba8unorm", 1, this.mipmapsEnabled);
|
|
778
|
+
handle = reg.handle;
|
|
779
|
+
}
|
|
780
|
+
this.meshNormalTextureSourceId.set(materialKey, sourceId);
|
|
781
|
+
|
|
782
|
+
// Update the material's normalTexHandle. Preserve existing albedo handle.
|
|
783
|
+
const materialIndex = this.meshMaterialIndex.get(materialKey);
|
|
784
|
+
if (materialIndex !== undefined) {
|
|
785
|
+
const albedoHandle = this.meshAlbedoHandle.get(materialKey) ?? this.bindless.registry.defaultWhiteHandle;
|
|
786
|
+
const matParams: MaterialParams = {
|
|
787
|
+
baseColor: [1, 1, 1, 1],
|
|
788
|
+
roughness: 1,
|
|
789
|
+
metallic: 0,
|
|
790
|
+
emissiveIntensity: 0,
|
|
791
|
+
albedoTexHandle: albedoHandle,
|
|
792
|
+
normalTexHandle: handle,
|
|
793
|
+
metallicRoughnessTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
794
|
+
aoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
795
|
+
emissiveTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
796
|
+
};
|
|
797
|
+
this.bindless.materialManager.updateMaterial(materialIndex, matParams);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
log.info("ModelRenderer", `Normal texture ready for ${materialKey}: ${imageBitmap.width}x${imageBitmap.height}`);
|
|
801
|
+
imageBitmap.close();
|
|
802
|
+
} catch (e) {
|
|
803
|
+
log.error("ModelRenderer", `Failed to load normal texture for ${materialKey}: ${e}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
removeModel(nodeId: string): void {
|
|
808
|
+
const resources = this.modelResources.get(nodeId);
|
|
809
|
+
if (resources) {
|
|
810
|
+
for (let i = 0; i < resources.length; i++) {
|
|
811
|
+
resources[i].vertexBuffer.destroy();
|
|
812
|
+
resources[i].indexBuffer.destroy();
|
|
813
|
+
resources[i].skinVertexBuffer?.destroy();
|
|
814
|
+
}
|
|
815
|
+
this.nextUniformOffset = Math.max(0, this.nextUniformOffset - resources.length);
|
|
816
|
+
this.modelResources.delete(nodeId);
|
|
817
|
+
}
|
|
818
|
+
// Unregister all per-mesh textures + materials from the bindless managers.
|
|
819
|
+
// Clean up any materialKey that starts with `${nodeId}:`.
|
|
820
|
+
const prefix = `${nodeId}:`;
|
|
821
|
+
for (const [materialKey, matIdx] of this.meshMaterialIndex.entries()) {
|
|
822
|
+
if (materialKey.startsWith(prefix)) {
|
|
823
|
+
const sourceId = this.meshTextureSourceId.get(materialKey);
|
|
824
|
+
if (sourceId && this.bindless) {
|
|
825
|
+
this.bindless.registry.unregister(sourceId);
|
|
826
|
+
this.meshTextureSourceId.delete(materialKey);
|
|
827
|
+
}
|
|
828
|
+
if (this.bindless && matIdx !== this.defaultMaterialIndex) {
|
|
829
|
+
this.bindless.materialManager.unregisterMaterial(matIdx);
|
|
830
|
+
}
|
|
831
|
+
this.meshMaterialIndex.delete(materialKey);
|
|
832
|
+
this.textureLoadVersion.delete(materialKey);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
reuploadModel(nodeId: string, meshes: MeshData[], materials?: MaterialData[], modelBaseUrl?: string): number {
|
|
838
|
+
// Preserve existing per-mesh texture registrations to avoid flickering on
|
|
839
|
+
// part selection changes. Per-mesh material indices are reused.
|
|
840
|
+
const oldResources = this.modelResources.get(nodeId);
|
|
841
|
+
if (oldResources) {
|
|
842
|
+
for (let i = 0; i < oldResources.length; i++) {
|
|
843
|
+
oldResources[i].vertexBuffer.destroy();
|
|
844
|
+
oldResources[i].indexBuffer.destroy();
|
|
845
|
+
oldResources[i].skinVertexBuffer?.destroy();
|
|
846
|
+
}
|
|
847
|
+
this.nextUniformOffset = Math.max(0, this.nextUniformOffset - oldResources.length);
|
|
848
|
+
this.modelResources.delete(nodeId);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const newResources: ModelGPUResources[] = [];
|
|
852
|
+
let uniformOffset = this.nextUniformOffset;
|
|
853
|
+
|
|
854
|
+
for (let i = 0; i < meshes.length && uniformOffset < ModelRenderer.MAX_MODELS; i++) {
|
|
855
|
+
const mesh = meshes[i];
|
|
856
|
+
if (mesh.indexCount === 0 || mesh.vertexCount === 0 || mesh.indices.byteLength === 0) continue;
|
|
857
|
+
const vertexCount = mesh.vertexCount;
|
|
858
|
+
const stride = 11;
|
|
859
|
+
const interleaved = new Float32Array(vertexCount * stride);
|
|
860
|
+
|
|
861
|
+
for (let v = 0; v < vertexCount; v++) {
|
|
862
|
+
interleaved[v * stride] = mesh.vertices[v * 6];
|
|
863
|
+
interleaved[v * stride + 1] = mesh.vertices[v * 6 + 1];
|
|
864
|
+
interleaved[v * stride + 2] = mesh.vertices[v * 6 + 2];
|
|
865
|
+
interleaved[v * stride + 3] = mesh.vertices[v * 6 + 3];
|
|
866
|
+
interleaved[v * stride + 4] = mesh.vertices[v * 6 + 4];
|
|
867
|
+
interleaved[v * stride + 5] = mesh.vertices[v * 6 + 5];
|
|
868
|
+
interleaved[v * stride + 6] = mesh.uvs ? mesh.uvs[v * 2] : 0;
|
|
869
|
+
interleaved[v * stride + 7] = mesh.uvs ? mesh.uvs[v * 2 + 1] : 0;
|
|
870
|
+
interleaved[v * stride + 8] = mesh.colors ? mesh.colors[v * 3] : 1;
|
|
871
|
+
interleaved[v * stride + 9] = mesh.colors ? mesh.colors[v * 3 + 1] : 1;
|
|
872
|
+
interleaved[v * stride + 10] = mesh.colors ? mesh.colors[v * 3 + 2] : 1;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const vertexBuffer = this.device.createBuffer({
|
|
876
|
+
size: interleaved.byteLength,
|
|
877
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
878
|
+
});
|
|
879
|
+
this.device.queue.writeBuffer(vertexBuffer, 0, interleaved as Float32Array<ArrayBuffer>);
|
|
880
|
+
|
|
881
|
+
const indexFormat: GPUIndexFormat =
|
|
882
|
+
mesh.indices instanceof Uint32Array ? "uint32" : "uint16";
|
|
883
|
+
const indexByteLength = mesh.indices.byteLength;
|
|
884
|
+
const paddedIndexSize = Math.ceil(indexByteLength / 4) * 4;
|
|
885
|
+
const indexBuffer = this.device.createBuffer({
|
|
886
|
+
size: paddedIndexSize,
|
|
887
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
888
|
+
});
|
|
889
|
+
if (paddedIndexSize === indexByteLength) {
|
|
890
|
+
this.device.queue.writeBuffer(indexBuffer, 0, mesh.indices as (Uint16Array<ArrayBuffer> | Uint32Array<ArrayBuffer>));
|
|
891
|
+
} else {
|
|
892
|
+
const padded = new Uint8Array(paddedIndexSize);
|
|
893
|
+
padded.set(new Uint8Array(mesh.indices.buffer, mesh.indices.byteOffset, indexByteLength));
|
|
894
|
+
this.device.queue.writeBuffer(indexBuffer, 0, padded);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Per-mesh material: reuse existing bindless material if already registered,
|
|
898
|
+
// otherwise register a new one and start async texture load.
|
|
899
|
+
const matIdx = mesh.materialIndex ?? 0;
|
|
900
|
+
const materialKey = `${nodeId}:${matIdx}`;
|
|
901
|
+
let bindlessMatIndex = this.defaultMaterialIndex;
|
|
902
|
+
if (this.bindless) {
|
|
903
|
+
const existing = this.meshMaterialIndex.get(materialKey);
|
|
904
|
+
if (existing !== undefined) {
|
|
905
|
+
bindlessMatIndex = existing;
|
|
906
|
+
} else {
|
|
907
|
+
const mat = materials?.[matIdx];
|
|
908
|
+
bindlessMatIndex = this.bindless.materialManager.registerMaterial({
|
|
909
|
+
baseColor: mat?.baseColor ?? [1, 1, 1, 1],
|
|
910
|
+
roughness: mat?.roughness ?? 1,
|
|
911
|
+
metallic: mat?.metallic ?? 0,
|
|
912
|
+
emissiveIntensity: 0,
|
|
913
|
+
albedoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
914
|
+
normalTexHandle: this.bindless.registry.defaultNormalHandle,
|
|
915
|
+
metallicRoughnessTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
916
|
+
aoTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
917
|
+
emissiveTexHandle: this.bindless.registry.defaultWhiteHandle,
|
|
918
|
+
});
|
|
919
|
+
this.meshMaterialIndex.set(materialKey, bindlessMatIndex);
|
|
920
|
+
|
|
921
|
+
// Start async texture load if this material has texture data and no
|
|
922
|
+
// existing registration (first time seeing this material).
|
|
923
|
+
if (mat?.textureData && mat.textureData.byteLength > 0 && !this.meshTextureSourceId.has(materialKey)) {
|
|
924
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
925
|
+
this.textureLoadVersion.set(materialKey, version);
|
|
926
|
+
this.loadMeshTexture(materialKey, mat.textureData, version);
|
|
927
|
+
} else if (mat?.textureUri && modelBaseUrl && !this.meshTextureSourceId.has(materialKey)) {
|
|
928
|
+
const resolvedUri = modelBaseUrl + mat.textureUri;
|
|
929
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
930
|
+
this.textureLoadVersion.set(materialKey, version);
|
|
931
|
+
this.loadMeshTextureFromUri(materialKey, resolvedUri, version);
|
|
932
|
+
}
|
|
933
|
+
// Start async normal texture load if present and not yet loaded.
|
|
934
|
+
if (((mat?.normalTextureData && mat.normalTextureData.byteLength > 0) || mat?.normalTextureUri) && !this.meshNormalTextureSourceId.has(materialKey)) {
|
|
935
|
+
const version = (this.textureLoadVersion.get(materialKey) ?? 0) + 1;
|
|
936
|
+
this.loadMeshNormalTexture(materialKey, mat.normalTextureData ?? null, mat?.normalTextureUri, version);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const skinVertexBuffer = this.buildSkinVertexBuffer(mesh);
|
|
942
|
+
|
|
943
|
+
newResources.push({
|
|
944
|
+
vertexBuffer,
|
|
945
|
+
indexBuffer,
|
|
946
|
+
indexCount: mesh.indexCount,
|
|
947
|
+
indexFormat,
|
|
948
|
+
uniformOffset: uniformOffset * ModelRenderer.UNIFORM_SIZE,
|
|
949
|
+
materialIndex: bindlessMatIndex,
|
|
950
|
+
skinVertexBuffer,
|
|
951
|
+
skinned: skinVertexBuffer !== undefined,
|
|
952
|
+
});
|
|
953
|
+
uniformOffset++;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
this.nextUniformOffset = uniformOffset;
|
|
957
|
+
this.modelResources.set(nodeId, newResources);
|
|
958
|
+
return newResources.length;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
beginFrame(camera: CameraState): void {
|
|
962
|
+
this.viewProjCache = calculateViewProj(camera);
|
|
963
|
+
this.cameraPosCache = [camera.position[0], camera.position[1], camera.position[2]];
|
|
964
|
+
this.bindlessBindGroupSetThisFrame = false;
|
|
965
|
+
this.skinBindGroupSetThisFrame = false;
|
|
966
|
+
this.frameLightingBgSetThisFrame = false;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* The effective frame-lighting bind group for group(2): the caller-provided
|
|
971
|
+
* one if set, otherwise the default neutral one created in init(). The model
|
|
972
|
+
* pipeline's group(2) layout is the explicit frameLightingLayout, so a
|
|
973
|
+
* compatible bind group MUST be bound before every draw — otherwise a stale
|
|
974
|
+
* group(2) from a prior pipeline (e.g. an auto-layout procedural pipeline)
|
|
975
|
+
* triggers a WebGPU bind-group-layout incompatibility error.
|
|
976
|
+
*/
|
|
977
|
+
private get effectiveFrameLightingBg(): GPUBindGroup | null {
|
|
978
|
+
return this.frameLightingBg ?? this.defaultFrameLightingBg;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Set the directional light state for the fragment shader.
|
|
983
|
+
* Call once per frame before render(). All values are in world space.
|
|
984
|
+
*
|
|
985
|
+
* @param dir Light direction (the direction the light travels). The shader
|
|
986
|
+
* normalizes this. For a sun at the top of the sky in a Y-down world, use
|
|
987
|
+
* a negative Y (e.g. [0.5, -0.8, 0.3]).
|
|
988
|
+
* @param ambient Ambient light level (0-1). Combined with directional.
|
|
989
|
+
* @param intensity Directional light intensity (0-1). Final lighting = ambient + ndotl * intensity.
|
|
990
|
+
*/
|
|
991
|
+
setLightState(dir: [number, number, number], ambient: number, intensity: number): void {
|
|
992
|
+
this.lightDirCache = dir;
|
|
993
|
+
this.lightAmbientCache = ambient;
|
|
994
|
+
this.lightIntensityCache = intensity;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Enable/disable mip-chain generation for mesh textures. Takes effect on
|
|
999
|
+
* the next uploadModel/reuploadModel — existing registrations are not
|
|
1000
|
+
* re-bucketed. Callers should re-upload (removeModel + uploadModel) to
|
|
1001
|
+
* apply the change to already-loaded models.
|
|
1002
|
+
*/
|
|
1003
|
+
setMipmapsEnabled(enabled: boolean): void {
|
|
1004
|
+
this.mipmapsEnabled = enabled;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
getMipmapsEnabled(): boolean {
|
|
1008
|
+
return this.mipmapsEnabled;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Upload bone skin matrices for the current frame. The `matrices` buffer is
|
|
1013
|
+
* a flat Float32Array of boneCount * 16 floats (column-major mat4s). It is
|
|
1014
|
+
* copied into the shared skin matrix storage buffer. If the buffer is too
|
|
1015
|
+
* small for the current skeleton, it is recreated (grown) automatically.
|
|
1016
|
+
* Call once per frame before render() for skinned models.
|
|
1017
|
+
*/
|
|
1018
|
+
updateSkinMatrices(matrices: Float32Array): void {
|
|
1019
|
+
if (!this.skinMatrixBuffer) return;
|
|
1020
|
+
|
|
1021
|
+
const requiredBones = matrices.length / 16;
|
|
1022
|
+
if (requiredBones > this.boneCapacity) {
|
|
1023
|
+
// Grow the skin matrix buffer to fit the larger skeleton.
|
|
1024
|
+
// Round up to the next power of 2 to reduce reallocations.
|
|
1025
|
+
const newCapacity = Math.max(requiredBones, this.boneCapacity * 2);
|
|
1026
|
+
this.skinMatrixBuffer.destroy();
|
|
1027
|
+
this.skinMatrixBuffer = this.device.createBuffer({
|
|
1028
|
+
size: newCapacity * 64,
|
|
1029
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
1030
|
+
});
|
|
1031
|
+
this.boneCapacity = newCapacity;
|
|
1032
|
+
// Recreate the bind group with the new buffer
|
|
1033
|
+
if (this.skinBindGroupLayout) {
|
|
1034
|
+
this.skinBindGroup = this.device.createBindGroup({
|
|
1035
|
+
layout: this.skinBindGroupLayout,
|
|
1036
|
+
entries: [{ binding: 0, resource: { buffer: this.skinMatrixBuffer } }],
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
this.skinBindGroupSetThisFrame = false; // force re-bind
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
this.device.queue.writeBuffer(
|
|
1043
|
+
this.skinMatrixBuffer,
|
|
1044
|
+
0,
|
|
1045
|
+
matrices as Float32Array<ArrayBuffer>,
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
render(
|
|
1050
|
+
passEncoder: GPURenderPassEncoder,
|
|
1051
|
+
nodeId: string,
|
|
1052
|
+
position: [number, number, number],
|
|
1053
|
+
rotation: [number, number, number, number],
|
|
1054
|
+
scale: [number, number, number],
|
|
1055
|
+
instanceIndex: number = 0,
|
|
1056
|
+
/** Per-draw highlight: 0 = normal shading, 1 = ghost hologram (cyan),
|
|
1057
|
+
* 2 = hover outline (bright rim). Values >= 3 encode per-prop material
|
|
1058
|
+
* overrides: `3 + shaderMode + texMode * 4` where shaderMode is
|
|
1059
|
+
* 0=Standard 1=Toon 2=Hologram 3=Outline and texMode is
|
|
1060
|
+
* 0=Default 1=Wireframe 2=Checker. Defaults to 0 (normal). */
|
|
1061
|
+
highlight: number = 0,
|
|
1062
|
+
): void {
|
|
1063
|
+
if (!this.pipeline || !this.bindGroup || !this.uniformBuffer || !this.viewProjCache) return;
|
|
1064
|
+
|
|
1065
|
+
const resources = this.modelResources.get(nodeId);
|
|
1066
|
+
if (!resources || resources.length === 0) return;
|
|
1067
|
+
|
|
1068
|
+
// For instance 0, use the original uniform offsets stored in resources.
|
|
1069
|
+
// For instance 1+, compute offsets from the allocated instance base.
|
|
1070
|
+
let instanceBaseOffset = 0; // in bytes, added to each mesh's uniformOffset
|
|
1071
|
+
if (instanceIndex > 0) {
|
|
1072
|
+
const instances = this.modelInstanceOffsets.get(nodeId);
|
|
1073
|
+
if (!instances || instanceIndex > instances.length) return;
|
|
1074
|
+
instanceBaseOffset = instances[instanceIndex - 1];
|
|
1075
|
+
// instanceBaseOffset is the absolute byte offset for mesh 0 of this
|
|
1076
|
+
// instance. Mesh r's offset = instanceBaseOffset + r * UNIFORM_SIZE.
|
|
1077
|
+
// But res.uniformOffset is already mesh 0's original offset + r * UNIFORM_SIZE.
|
|
1078
|
+
// So the per-mesh offset for this instance = instanceBaseOffset + r * UNIFORM_SIZE.
|
|
1079
|
+
// We compute it as: instanceBaseOffset - resources[0].uniformOffset + res.uniformOffset
|
|
1080
|
+
// = instanceBaseOffset + (res.uniformOffset - resources[0].uniformOffset)
|
|
1081
|
+
// = instanceBaseOffset + r * UNIFORM_SIZE (since original offsets are contiguous).
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Set the bindless material bind group once per frame (group 3).
|
|
1085
|
+
if (this.bindlessBindGroup && !this.bindlessBindGroupSetThisFrame) {
|
|
1086
|
+
passEncoder.setBindGroup(3, this.bindlessBindGroup);
|
|
1087
|
+
this.bindlessBindGroupSetThisFrame = true;
|
|
1088
|
+
}
|
|
1089
|
+
// Set the frame-lighting bind group once per frame (group 2). Must be
|
|
1090
|
+
// bound explicitly — the model pipeline's group(2) uses an explicit layout
|
|
1091
|
+
// that is incompatible with auto-layout bind groups left over from other
|
|
1092
|
+
// pipelines drawn earlier in the same pass.
|
|
1093
|
+
const frameLightBg = this.effectiveFrameLightingBg;
|
|
1094
|
+
if (frameLightBg && !this.frameLightingBgSetThisFrame) {
|
|
1095
|
+
passEncoder.setBindGroup(2, frameLightBg);
|
|
1096
|
+
this.frameLightingBgSetThisFrame = true;
|
|
1097
|
+
}
|
|
1098
|
+
// Set the skin-matrix bind group once per frame (group 1) when any skinned
|
|
1099
|
+
// mesh is drawn. The skin matrix buffer is updated per-frame by the caller
|
|
1100
|
+
// via updateSkinMatrices().
|
|
1101
|
+
const hasSkinned = resources.some((r) => r.skinned);
|
|
1102
|
+
if (hasSkinned && this.skinBindGroup && !this.skinBindGroupSetThisFrame) {
|
|
1103
|
+
passEncoder.setBindGroup(1, this.skinBindGroup);
|
|
1104
|
+
this.skinBindGroupSetThisFrame = true;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const mesh0Offset = resources[0].uniformOffset;
|
|
1108
|
+
for (let r = 0; r < resources.length; r++) {
|
|
1109
|
+
const res = resources[r];
|
|
1110
|
+
// Compute the uniform offset for this instance + mesh.
|
|
1111
|
+
const uniformOffset = instanceIndex === 0
|
|
1112
|
+
? res.uniformOffset
|
|
1113
|
+
: instanceBaseOffset + (res.uniformOffset - mesh0Offset);
|
|
1114
|
+
const uniforms = this.reusableUniforms;
|
|
1115
|
+
for (let i = 0; i < 16; i++) uniforms[i] = this.viewProjCache[i];
|
|
1116
|
+
uniforms[16] = this.cameraPosCache[0];
|
|
1117
|
+
uniforms[17] = this.cameraPosCache[1];
|
|
1118
|
+
uniforms[18] = this.cameraPosCache[2];
|
|
1119
|
+
uniforms[19] = performance.now() / 1000;
|
|
1120
|
+
uniforms[20] = position[0];
|
|
1121
|
+
uniforms[21] = position[1];
|
|
1122
|
+
uniforms[22] = position[2];
|
|
1123
|
+
// uniforms[23] = padding (vec3<f32> alignment in WGSL uniform layout)
|
|
1124
|
+
uniforms[24] = scale[0];
|
|
1125
|
+
uniforms[25] = scale[1];
|
|
1126
|
+
uniforms[26] = scale[2];
|
|
1127
|
+
// uniforms[27] = padding (vec4<f32> alignment in WGSL uniform layout)
|
|
1128
|
+
uniforms[28] = rotation[0];
|
|
1129
|
+
uniforms[29] = rotation[1];
|
|
1130
|
+
uniforms[30] = rotation[2];
|
|
1131
|
+
uniforms[31] = rotation[3];
|
|
1132
|
+
// materialIndex (u32) at float slot 32 — write via Uint32Array view so
|
|
1133
|
+
// the shader reads the correct u32 bit pattern (not a float reinterpretation).
|
|
1134
|
+
this.reusableUniformsU32[32] = res.materialIndex;
|
|
1135
|
+
// Light state at float slots 33-35 (the former _pad3/_pad4/_pad5 slots):
|
|
1136
|
+
// 33: lightDir.x, 34: lightDir.y, 35: lightDir.z
|
|
1137
|
+
// 36: ambient, 37: intensity
|
|
1138
|
+
uniforms[33] = this.lightDirCache[0];
|
|
1139
|
+
uniforms[34] = this.lightDirCache[1];
|
|
1140
|
+
uniforms[35] = this.lightDirCache[2];
|
|
1141
|
+
uniforms[36] = this.lightAmbientCache;
|
|
1142
|
+
uniforms[37] = this.lightIntensityCache;
|
|
1143
|
+
// highlight flag (float slot 38 — the former _pad6). Written as a float
|
|
1144
|
+
// so the WGSL `highlight: f32` field reads the correct bit pattern.
|
|
1145
|
+
uniforms[38] = highlight;
|
|
1146
|
+
// Outline params (slots 39-42) — unused by fs_main but written so the
|
|
1147
|
+
// same uniform buffer works for the outline pipeline (vs_outline /
|
|
1148
|
+
// fs_outline). Defaults: 0 width, black color.
|
|
1149
|
+
uniforms[39] = 0.0;
|
|
1150
|
+
uniforms[40] = 0.0; uniforms[41] = 0.0; uniforms[42] = 0.0;
|
|
1151
|
+
|
|
1152
|
+
this.device.queue.writeBuffer(
|
|
1153
|
+
this.uniformBuffer,
|
|
1154
|
+
uniformOffset,
|
|
1155
|
+
uniforms as Float32Array<ArrayBuffer>,
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
// Select the skinned pipeline + skin vertex buffer for skinned meshes,
|
|
1159
|
+
// otherwise the standard non-skinned pipeline.
|
|
1160
|
+
const slotIdx = uniformOffset / ModelRenderer.UNIFORM_SIZE;
|
|
1161
|
+
const mBg = this.perSlotBindGroups[slotIdx] ?? this.bindGroup;
|
|
1162
|
+
if (res.skinned && this.skinnedPipeline && res.skinVertexBuffer) {
|
|
1163
|
+
passEncoder.setPipeline(this.skinnedPipeline);
|
|
1164
|
+
if (this.perSlotBindGroups.length > 0) passEncoder.setBindGroup(0, mBg, [0]);
|
|
1165
|
+
else passEncoder.setBindGroup(0, mBg!, [uniformOffset]);
|
|
1166
|
+
passEncoder.setVertexBuffer(0, res.vertexBuffer);
|
|
1167
|
+
passEncoder.setVertexBuffer(1, res.skinVertexBuffer);
|
|
1168
|
+
passEncoder.setIndexBuffer(res.indexBuffer, res.indexFormat);
|
|
1169
|
+
passEncoder.drawIndexed(res.indexCount);
|
|
1170
|
+
} else {
|
|
1171
|
+
passEncoder.setPipeline(this.pipeline);
|
|
1172
|
+
if (this.perSlotBindGroups.length > 0) passEncoder.setBindGroup(0, mBg, [0]);
|
|
1173
|
+
else passEncoder.setBindGroup(0, mBg!, [uniformOffset]);
|
|
1174
|
+
passEncoder.setVertexBuffer(0, res.vertexBuffer);
|
|
1175
|
+
passEncoder.setIndexBuffer(res.indexBuffer, res.indexFormat);
|
|
1176
|
+
passEncoder.drawIndexed(res.indexCount);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Render the model as solid white to a mask texture for the post-process
|
|
1183
|
+
* outline. Uses the same vertex shader as the normal render (vs_main)
|
|
1184
|
+
* with a simple white fragment shader (fs_mask). Depth-tested against
|
|
1185
|
+
* the scene depth (read-only) so the mask respects occlusion.
|
|
1186
|
+
*/
|
|
1187
|
+
renderMask(
|
|
1188
|
+
passEncoder: GPURenderPassEncoder,
|
|
1189
|
+
nodeId: string,
|
|
1190
|
+
position: [number, number, number],
|
|
1191
|
+
rotation: [number, number, number, number],
|
|
1192
|
+
scale: [number, number, number],
|
|
1193
|
+
): void {
|
|
1194
|
+
if (!this.maskPipeline || !this.bindGroup || !this.uniformBuffer || !this.viewProjCache) return;
|
|
1195
|
+
|
|
1196
|
+
const resources = this.modelResources.get(nodeId);
|
|
1197
|
+
if (!resources || resources.length === 0) return;
|
|
1198
|
+
|
|
1199
|
+
if (this.bindlessBindGroup && !this.bindlessBindGroupSetThisFrame) {
|
|
1200
|
+
passEncoder.setBindGroup(3, this.bindlessBindGroup);
|
|
1201
|
+
this.bindlessBindGroupSetThisFrame = true;
|
|
1202
|
+
}
|
|
1203
|
+
// renderMask() runs in a separate render pass from render(), so the
|
|
1204
|
+
// per-frame "set this frame" flags are unreliable (they may already be
|
|
1205
|
+
// true from the scene pass). Bind group 2 (frame lighting) and group 3
|
|
1206
|
+
// (bindless) explicitly here — both are non-empty in the mask pipeline's
|
|
1207
|
+
// layout and must be re-bound for this pass.
|
|
1208
|
+
const maskFrameLightBg = this.effectiveFrameLightingBg;
|
|
1209
|
+
if (maskFrameLightBg) passEncoder.setBindGroup(2, maskFrameLightBg);
|
|
1210
|
+
if (this.bindlessBindGroup) passEncoder.setBindGroup(3, this.bindlessBindGroup);
|
|
1211
|
+
|
|
1212
|
+
for (let r = 0; r < resources.length; r++) {
|
|
1213
|
+
const res = resources[r];
|
|
1214
|
+
const uniformOffset = res.uniformOffset;
|
|
1215
|
+
const uniforms = this.reusableUniforms;
|
|
1216
|
+
for (let i = 0; i < 16; i++) uniforms[i] = this.viewProjCache[i];
|
|
1217
|
+
uniforms[16] = this.cameraPosCache[0];
|
|
1218
|
+
uniforms[17] = this.cameraPosCache[1];
|
|
1219
|
+
uniforms[18] = this.cameraPosCache[2];
|
|
1220
|
+
uniforms[19] = performance.now() / 1000;
|
|
1221
|
+
uniforms[20] = position[0];
|
|
1222
|
+
uniforms[21] = position[1];
|
|
1223
|
+
uniforms[22] = position[2];
|
|
1224
|
+
uniforms[24] = scale[0];
|
|
1225
|
+
uniforms[25] = scale[1];
|
|
1226
|
+
uniforms[26] = scale[2];
|
|
1227
|
+
uniforms[28] = rotation[0];
|
|
1228
|
+
uniforms[29] = rotation[1];
|
|
1229
|
+
uniforms[30] = rotation[2];
|
|
1230
|
+
uniforms[31] = rotation[3];
|
|
1231
|
+
this.reusableUniformsU32[32] = res.materialIndex;
|
|
1232
|
+
// Remaining fields not read by fs_mask.
|
|
1233
|
+
this.device.queue.writeBuffer(
|
|
1234
|
+
this.uniformBuffer,
|
|
1235
|
+
uniformOffset,
|
|
1236
|
+
uniforms as Float32Array<ArrayBuffer>,
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
passEncoder.setPipeline(this.maskPipeline);
|
|
1240
|
+
passEncoder.setBindGroup(0, this.bindGroup, [uniformOffset]);
|
|
1241
|
+
passEncoder.setVertexBuffer(0, res.vertexBuffer);
|
|
1242
|
+
passEncoder.setIndexBuffer(res.indexBuffer, res.indexFormat);
|
|
1243
|
+
passEncoder.drawIndexed(res.indexCount);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* Render the model depth-only into a shadow map from an arbitrary
|
|
1249
|
+
* view-projection (the light's VP). Uses the depthPipeline (no color
|
|
1250
|
+
* targets, depthWriteEnabled). Only group(0) (per-draw uniform) is bound
|
|
1251
|
+
* — the depthPipeline's layout has no lighting/bindless groups.
|
|
1252
|
+
*
|
|
1253
|
+
* @param viewProj The light's view-projection matrix (16 floats).
|
|
1254
|
+
*/
|
|
1255
|
+
renderDepth(
|
|
1256
|
+
passEncoder: GPURenderPassEncoder,
|
|
1257
|
+
viewProj: Float32Array,
|
|
1258
|
+
nodeId: string,
|
|
1259
|
+
position: [number, number, number],
|
|
1260
|
+
rotation: [number, number, number, number],
|
|
1261
|
+
scale: [number, number, number],
|
|
1262
|
+
): void {
|
|
1263
|
+
if (!this.depthPipeline || !this.bindGroup || !this.uniformBuffer) return;
|
|
1264
|
+
|
|
1265
|
+
const resources = this.modelResources.get(nodeId);
|
|
1266
|
+
if (!resources || resources.length === 0) return;
|
|
1267
|
+
|
|
1268
|
+
const uniforms = this.reusableUniforms;
|
|
1269
|
+
for (let r = 0; r < resources.length; r++) {
|
|
1270
|
+
const res = resources[r];
|
|
1271
|
+
const uniformOffset = res.uniformOffset;
|
|
1272
|
+
// viewProj (light VP) replaces the camera viewProj for shadow rendering.
|
|
1273
|
+
for (let i = 0; i < 16; i++) uniforms[i] = viewProj[i];
|
|
1274
|
+
// cameraPos + time not read by the vertex shader, but zero for safety.
|
|
1275
|
+
uniforms[16] = 0; uniforms[17] = 0; uniforms[18] = 0; uniforms[19] = 0;
|
|
1276
|
+
uniforms[20] = position[0];
|
|
1277
|
+
uniforms[21] = position[1];
|
|
1278
|
+
uniforms[22] = position[2];
|
|
1279
|
+
uniforms[24] = scale[0];
|
|
1280
|
+
uniforms[25] = scale[1];
|
|
1281
|
+
uniforms[26] = scale[2];
|
|
1282
|
+
uniforms[28] = rotation[0];
|
|
1283
|
+
uniforms[29] = rotation[1];
|
|
1284
|
+
uniforms[30] = rotation[2];
|
|
1285
|
+
uniforms[31] = rotation[3];
|
|
1286
|
+
this.reusableUniformsU32[32] = res.materialIndex;
|
|
1287
|
+
|
|
1288
|
+
this.device.queue.writeBuffer(
|
|
1289
|
+
this.uniformBuffer,
|
|
1290
|
+
uniformOffset,
|
|
1291
|
+
uniforms as Float32Array<ArrayBuffer>,
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
passEncoder.setPipeline(this.depthPipeline);
|
|
1295
|
+
passEncoder.setBindGroup(0, this.bindGroup, [uniformOffset]);
|
|
1296
|
+
passEncoder.setVertexBuffer(0, res.vertexBuffer);
|
|
1297
|
+
passEncoder.setIndexBuffer(res.indexBuffer, res.indexFormat);
|
|
1298
|
+
passEncoder.drawIndexed(res.indexCount);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
hasModel(nodeId: string): boolean {
|
|
1303
|
+
return this.modelResources.has(nodeId);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Overwrite a mesh's vertex buffer with new interleaved data (stride 11:
|
|
1308
|
+
* pos3 + normal3 + uv2 + color3). Used for CPU-side mesh deformation —
|
|
1309
|
+
* the caller owns the vertex layout and must supply vertexCount*11 floats.
|
|
1310
|
+
*/
|
|
1311
|
+
updateVertexBuffer(nodeId: string, meshIndex: number, interleaved: Float32Array): void {
|
|
1312
|
+
const res = this.modelResources.get(nodeId)?.[meshIndex];
|
|
1313
|
+
if (!res) return;
|
|
1314
|
+
this.device.queue.writeBuffer(res.vertexBuffer, 0, interleaved as Float32Array<ArrayBuffer>);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Overwrite a mesh's index buffer with a compacted triangle list and update
|
|
1319
|
+
* the draw count. Used for CPU-side mesh deformation that deletes triangles
|
|
1320
|
+
* (e.g. punched-through holes). `indices`/`indexCount` describe the live
|
|
1321
|
+
* prefix of the caller's index array.
|
|
1322
|
+
*/
|
|
1323
|
+
updateIndexBuffer(nodeId: string, meshIndex: number, indices: Uint16Array | Uint32Array, indexCount: number): void {
|
|
1324
|
+
const res = this.modelResources.get(nodeId)?.[meshIndex];
|
|
1325
|
+
if (!res) return;
|
|
1326
|
+
const live = indices.subarray(0, indexCount);
|
|
1327
|
+
const byteLen = live.byteLength;
|
|
1328
|
+
if (byteLen % 4 === 0) {
|
|
1329
|
+
this.device.queue.writeBuffer(res.indexBuffer, 0, live as (Uint16Array<ArrayBuffer> | Uint32Array<ArrayBuffer>));
|
|
1330
|
+
} else {
|
|
1331
|
+
const padded = new Uint8Array(Math.ceil(byteLen / 4) * 4);
|
|
1332
|
+
padded.set(new Uint8Array(live.buffer, live.byteOffset, byteLen));
|
|
1333
|
+
this.device.queue.writeBuffer(res.indexBuffer, 0, padded);
|
|
1334
|
+
}
|
|
1335
|
+
res.indexCount = indexCount;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
destroy(): void {
|
|
1339
|
+
const ids = Array.from(this.modelResources.keys());
|
|
1340
|
+
for (let i = 0; i < ids.length; i++) {
|
|
1341
|
+
this.removeModel(ids[i]);
|
|
1342
|
+
}
|
|
1343
|
+
this.skinMatrixBuffer?.destroy();
|
|
1344
|
+
}
|
|
1345
|
+
}
|