murow 0.0.72 → 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.
Files changed (115) hide show
  1. package/README.md +15 -1
  2. package/dist/cjs/core/binary-codec/binary-codec.js +1 -1
  3. package/dist/cjs/core/driver/driver.js +1 -1
  4. package/dist/cjs/core/driver/drivers/immediate.js +1 -1
  5. package/dist/cjs/core/driver/drivers/raf.js +1 -1
  6. package/dist/cjs/core/driver/drivers/timeout.js +1 -1
  7. package/dist/cjs/core/input/index.js +1 -1
  8. package/dist/cjs/core/input/mouse-look/index.js +1 -0
  9. package/dist/cjs/core/input/mouse-look/mouse-look.js +1 -0
  10. package/dist/cjs/core/input/scroll-zoom/index.js +1 -0
  11. package/dist/cjs/core/input/scroll-zoom/scroll-zoom.js +1 -0
  12. package/dist/cjs/core/sparse-batcher/sparse-batcher.js +1 -1
  13. package/dist/cjs/ecs/component.js +1 -1
  14. package/dist/cjs/ecs/system-builder.js +1 -1
  15. package/dist/cjs/ecs/world.js +1 -1
  16. package/dist/cjs/game/loop/loop.js +1 -1
  17. package/dist/cjs/net/adapters/bun-websocket.js +1 -1
  18. package/dist/cjs/renderer/base/renderer-3d.js +1 -1
  19. package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
  20. package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
  21. package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
  22. package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
  23. package/dist/esm/core/binary-codec/binary-codec.js +1 -1
  24. package/dist/esm/core/driver/drivers/immediate.js +1 -1
  25. package/dist/esm/core/driver/drivers/raf.js +1 -1
  26. package/dist/esm/core/driver/drivers/timeout.js +1 -1
  27. package/dist/esm/core/input/index.js +1 -1
  28. package/dist/esm/core/input/mouse-look/index.js +1 -0
  29. package/dist/esm/core/input/mouse-look/mouse-look.js +1 -0
  30. package/dist/esm/core/input/scroll-zoom/index.js +1 -0
  31. package/dist/esm/core/input/scroll-zoom/scroll-zoom.js +1 -0
  32. package/dist/esm/core/sparse-batcher/sparse-batcher.js +1 -1
  33. package/dist/esm/ecs/component.js +1 -1
  34. package/dist/esm/ecs/system-builder.js +1 -1
  35. package/dist/esm/ecs/world.js +1 -1
  36. package/dist/esm/game/loop/loop.js +1 -1
  37. package/dist/esm/net/adapters/bun-websocket.js +1 -1
  38. package/dist/esm/renderer/base/renderer-3d.js +1 -1
  39. package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
  40. package/dist/esm/renderer/prefab-bucket/index.js +1 -1
  41. package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
  42. package/dist/netcode/cjs/index.js +1552 -0
  43. package/dist/netcode/esm/index.js +1530 -0
  44. package/dist/netcode/types/client/game-client.d.ts +125 -0
  45. package/dist/netcode/types/client/index.d.ts +1 -0
  46. package/dist/netcode/types/client/interpolation-buffer.d.ts +37 -0
  47. package/dist/netcode/types/client/interpolation-buffer.test.d.ts +1 -0
  48. package/dist/netcode/types/codec/delta-codec.d.ts +17 -0
  49. package/dist/netcode/types/codec/delta-codec.test.d.ts +1 -0
  50. package/dist/netcode/types/codec/index.d.ts +1 -0
  51. package/dist/netcode/types/components/index.d.ts +1 -0
  52. package/dist/netcode/types/components/sync-spec.d.ts +43 -0
  53. package/dist/netcode/types/components/sync-spec.test.d.ts +1 -0
  54. package/dist/netcode/types/ctx.d.ts +105 -0
  55. package/dist/netcode/types/ctx.test.d.ts +1 -0
  56. package/dist/netcode/types/handlers/define-handlers.d.ts +47 -0
  57. package/dist/netcode/types/handlers/index.d.ts +1 -0
  58. package/dist/netcode/types/index.d.ts +11 -0
  59. package/dist/netcode/types/integration.test.d.ts +1 -0
  60. package/dist/netcode/types/intents/define-intents.d.ts +53 -0
  61. package/dist/netcode/types/intents/define-intents.test.d.ts +1 -0
  62. package/dist/netcode/types/intents/index.d.ts +1 -0
  63. package/dist/netcode/types/network/base.d.ts +120 -0
  64. package/dist/netcode/types/network/index.d.ts +2 -0
  65. package/dist/netcode/types/network/transport.d.ts +1 -0
  66. package/dist/netcode/types/packets/convergence.test.d.ts +1 -0
  67. package/dist/netcode/types/packets/harness.d.ts +103 -0
  68. package/dist/netcode/types/packets/index.d.ts +2 -0
  69. package/dist/netcode/types/packets/intermittent-intents.test.d.ts +1 -0
  70. package/dist/netcode/types/packets/pathological.test.d.ts +1 -0
  71. package/dist/netcode/types/packets/peer-interpolation.test.d.ts +1 -0
  72. package/dist/netcode/types/packets/reordering.test.d.ts +1 -0
  73. package/dist/netcode/types/packets/virtual-network.d.ts +65 -0
  74. package/dist/netcode/types/predictions/define-predictions.d.ts +45 -0
  75. package/dist/netcode/types/predictions/define-predictions.test.d.ts +1 -0
  76. package/dist/netcode/types/predictions/index.d.ts +1 -0
  77. package/dist/netcode/types/reconciliation.test.d.ts +1 -0
  78. package/dist/netcode/types/rpcs/define-rpcs.d.ts +44 -0
  79. package/dist/netcode/types/rpcs/define-rpcs.test.d.ts +1 -0
  80. package/dist/netcode/types/rpcs/index.d.ts +1 -0
  81. package/dist/netcode/types/server/game-server.d.ts +77 -0
  82. package/dist/netcode/types/server/index.d.ts +2 -0
  83. package/dist/netcode/types/server/plugins/aoi-grid.d.ts +34 -0
  84. package/dist/netcode/types/server/plugins/index.d.ts +3 -0
  85. package/dist/netcode/types/server/plugins/lag-compensation.d.ts +34 -0
  86. package/dist/netcode/types/server/plugins/plugin.d.ts +24 -0
  87. package/dist/netcode/types/tick-rate.test.d.ts +1 -0
  88. package/dist/netcode/types/transports/index.d.ts +1 -0
  89. package/dist/netcode/types/transports/memory-transport.d.ts +51 -0
  90. package/dist/netcode/types/types.test.d.ts +1 -0
  91. package/dist/types/core/binary-codec/binary-codec.d.ts +89 -31
  92. package/dist/types/core/driver/driver.d.ts +8 -8
  93. package/dist/types/core/driver/drivers/immediate.d.ts +4 -4
  94. package/dist/types/core/driver/drivers/raf.d.ts +17 -6
  95. package/dist/types/core/driver/drivers/timeout.d.ts +4 -4
  96. package/dist/types/core/input/index.d.ts +2 -0
  97. package/dist/types/core/input/mouse-look/index.d.ts +1 -0
  98. package/dist/types/core/input/mouse-look/mouse-look.d.ts +139 -0
  99. package/dist/types/core/input/scroll-zoom/index.d.ts +1 -0
  100. package/dist/types/core/input/scroll-zoom/scroll-zoom.d.ts +38 -0
  101. package/dist/types/ecs/component.d.ts +67 -11
  102. package/dist/types/ecs/entity-handle.d.ts +5 -5
  103. package/dist/types/ecs/system-builder.d.ts +13 -0
  104. package/dist/types/ecs/world.d.ts +72 -4
  105. package/dist/types/game/loop/loop.d.ts +21 -2
  106. package/dist/types/net/adapters/bun-websocket.d.ts +19 -3
  107. package/dist/types/renderer/base/renderer-3d.d.ts +1 -1
  108. package/dist/types/renderer/prefab-bucket/concrete.d.ts +42 -2
  109. package/dist/types/renderer/prefab-bucket/index.d.ts +12 -2
  110. package/dist/types/renderer/prefab-bucket/specs.d.ts +46 -3
  111. package/dist/types/renderer/types.d.ts +5 -3
  112. package/dist/webgpu/cjs/index.js +591 -40
  113. package/dist/webgpu/esm/index.js +591 -40
  114. package/dist/webgpu/types/3d/renderer.d.ts +111 -5
  115. package/package.json +6 -1
@@ -3656,7 +3656,8 @@ var SSTAT_CB = 5;
3656
3656
  var SSTAT_BONE_OFFSET = 6;
3657
3657
  var prefabHandles = /* @__PURE__ */ new WeakMap();
3658
3658
  function isPrefab3D(value) {
3659
- return value.type === "gltf" || value.type === "grid";
3659
+ const t = value.type;
3660
+ return t === "gltf" || t === "grid" || t === "cube" || t === "composite";
3660
3661
  }
3661
3662
  function resolveTransform(opts) {
3662
3663
  const [px, py, pz] = opts.position ?? [0, 0, 0];
@@ -3690,8 +3691,8 @@ function computeBucketStats(bucket) {
3690
3691
  }
3691
3692
  var WebGPU3DRenderer = class extends Base3DRenderer {
3692
3693
  constructor(canvas, options) {
3693
- const resolvedMaxModels = options.maxModels ?? (options.prefabs ? options.prefabs.size + 16 : 32);
3694
- super(canvas, { ...options, maxModels: resolvedMaxModels });
3694
+ const resolvedMaxInstances = options.maxInstances ?? (options.prefabs ? options.prefabs.size + 16 : 32);
3695
+ super(canvas, { ...options, maxInstances: resolvedMaxInstances });
3695
3696
  this.resizeObserver = null;
3696
3697
  this.resizeCallbacks = [];
3697
3698
  // layer=0, sheetId=modelId
@@ -3717,6 +3718,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3717
3718
  this.skinnedStaticDirty = false;
3718
3719
  this.skinnedAnimStates = [];
3719
3720
  this.nextBoneOffset = 0;
3721
+ /** Reusable bone-offset blocks per skinIndex. Pushed on remove, popped on add. Indexed by skinIndex (low cardinality), so a Map of lists is fine. */
3722
+ this.freedBoneOffsets = /* @__PURE__ */ new Map();
3720
3723
  // Frustum planes (6 planes × 4 floats each), extracted from VP matrix
3721
3724
  this.frustumPlanes = new Float32Array(24);
3722
3725
  this.uniformData = new Float32Array(24);
@@ -3726,22 +3729,26 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3726
3729
  this._prefabs = options.prefabs ?? null;
3727
3730
  const SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP = 3;
3728
3731
  const bucketStats = this._prefabs ? computeBucketStats(this._prefabs) : null;
3729
- const maxInstances = options.maxInstances ?? resolvedMaxModels;
3730
- this.maxSkinnedInstances = options.maxSkinnedInstances ?? (bucketStats ? maxInstances * Math.max(1, Math.min(bucketStats.maxSkinnedParts, SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP)) : 5e3);
3732
+ this.maxSkinnedInstances = options.maxSkinnedInstances ?? (bucketStats ? resolvedMaxInstances * Math.max(1, Math.min(bucketStats.maxSkinnedParts, SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP)) : 5e3);
3731
3733
  this.maxBonesPerSkin = options.maxBonesPerSkin ?? (bucketStats ? Math.max(1, bucketStats.maxJointCount) : 64);
3734
+ const cullDist = options.animationCullDistance ?? 50;
3735
+ this.animationCullDistanceSq = cullDist * cullDist;
3732
3736
  this.maxTotalBones = this.maxSkinnedInstances * this.maxBonesPerSkin * 2;
3733
3737
  this.updatedBoneOffsets = new Uint8Array(this.maxTotalBones);
3734
- this.freeList = new FreeList3(resolvedMaxModels);
3735
- this.batcher = new SparseBatcher2(resolvedMaxModels);
3736
- this.dynamicData = new Float32Array(resolvedMaxModels * DYNAMIC_MESH_FLOATS);
3737
- this.staticData = new Float32Array(resolvedMaxModels * STATIC_MESH_FLOATS);
3738
- this.slotIndexData = new Uint32Array(resolvedMaxModels);
3739
- this.instanceModelIds = new Uint8Array(resolvedMaxModels);
3738
+ this.boneOffsetRefcount = new Uint32Array(this.maxTotalBones);
3739
+ this.boneOffsetSkinIndex = new Uint32Array(this.maxTotalBones);
3740
+ this.freeList = new FreeList3(resolvedMaxInstances);
3741
+ this.batcher = new SparseBatcher2(resolvedMaxInstances);
3742
+ this.dynamicData = new Float32Array(resolvedMaxInstances * DYNAMIC_MESH_FLOATS);
3743
+ this.staticData = new Float32Array(resolvedMaxInstances * STATIC_MESH_FLOATS);
3744
+ this.slotIndexData = new Uint32Array(resolvedMaxInstances);
3745
+ this.instanceModelIds = new Uint8Array(resolvedMaxInstances);
3740
3746
  const msi = this.maxSkinnedInstances;
3741
3747
  this.skinnedFreeList = new FreeList3(msi);
3742
3748
  this.skinnedBatcher = new SparseBatcher2(msi);
3743
3749
  this.skinnedDynamicData = new Float32Array(msi * DYNAMIC_MESH_FLOATS);
3744
3750
  this.skinnedStaticData = new Float32Array(msi * SKINNED_STATIC_MESH_FLOATS);
3751
+ this.skinnedStaticDV = new DataView(this.skinnedStaticData.buffer);
3745
3752
  this.skinnedSlotIndexData = new Uint32Array(msi);
3746
3753
  this.skinnedInstanceModelIds = new Uint8Array(msi);
3747
3754
  this.skinnedInstanceBoneOffsets = new Uint32Array(msi);
@@ -3752,7 +3759,19 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3752
3759
  this.gpuInstDV = new DataView(this.gpuInstData.buffer);
3753
3760
  }
3754
3761
  async init() {
3755
- this.root = await tgpu6.init();
3762
+ const adapter = await navigator.gpu.requestAdapter();
3763
+ if (!adapter)
3764
+ throw new Error("WebGPU3DRenderer: no GPU adapter available");
3765
+ const a = adapter.limits;
3766
+ const requiredLimits = {
3767
+ maxBufferSize: a.maxBufferSize,
3768
+ maxStorageBufferBindingSize: a.maxStorageBufferBindingSize,
3769
+ maxStorageBuffersPerShaderStage: a.maxStorageBuffersPerShaderStage,
3770
+ maxComputeWorkgroupStorageSize: a.maxComputeWorkgroupStorageSize,
3771
+ maxComputeInvocationsPerWorkgroup: a.maxComputeInvocationsPerWorkgroup
3772
+ };
3773
+ const device = await adapter.requestDevice({ requiredLimits });
3774
+ this.root = tgpu6.initFromDevice({ device });
3756
3775
  this.device = this.root.device;
3757
3776
  this.context = this.canvas.getContext("webgpu");
3758
3777
  this.format = navigator.gpu.getPreferredCanvasFormat();
@@ -3769,7 +3788,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3769
3788
  format: "depth24plus",
3770
3789
  usage: GPUTextureUsage.RENDER_ATTACHMENT
3771
3790
  });
3772
- this.meshLayout = createMeshLayout(this.maxModels);
3791
+ this.meshLayout = createMeshLayout(this.maxInstances);
3773
3792
  const depthStencil = {
3774
3793
  format: "depth24plus",
3775
3794
  depthWriteEnabled: true,
@@ -3816,10 +3835,10 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3816
3835
  primitive,
3817
3836
  depthStencil
3818
3837
  });
3819
- this.dynamicBuffer = this.root.createBuffer(d.arrayOf(DynamicMesh, this.maxModels)).$usage("storage");
3820
- this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.maxModels)).$usage("storage");
3838
+ this.dynamicBuffer = this.root.createBuffer(d.arrayOf(DynamicMesh, this.maxInstances)).$usage("storage");
3839
+ this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.maxInstances)).$usage("storage");
3821
3840
  this.uniformBuffer = this.root.createBuffer(MeshUniforms).$usage("uniform");
3822
- this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.maxModels)).$usage("storage");
3841
+ this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.maxInstances)).$usage("storage");
3823
3842
  this.rawDynamicBuffer = this.root.unwrap(this.dynamicBuffer);
3824
3843
  this.rawStaticBuffer = this.root.unwrap(this.staticBuffer);
3825
3844
  this.rawUniformBuffer = this.root.unwrap(this.uniformBuffer);
@@ -3921,6 +3940,9 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3921
3940
  lineWidth: prefab.lineWidth
3922
3941
  });
3923
3942
  prefabHandles.set(prefab, model);
3943
+ } else if (prefab.type === "cube") {
3944
+ const model = this.createCube({ size: prefab.size });
3945
+ prefabHandles.set(prefab, model);
3924
3946
  }
3925
3947
  }
3926
3948
  }
@@ -3985,6 +4007,36 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
3985
4007
  createCompute(name, options) {
3986
4008
  return new ComputeBuilder(name, options, this.root);
3987
4009
  }
4010
+ /**
4011
+ * Set the maximum distance (in world units) at which the renderer keeps
4012
+ * computing skeletal animation. Skinned instances farther than this are
4013
+ * still drawn, but with their last-computed bone matrices instead of
4014
+ * fresh ones, which saves GPU compute work. Their internal animation
4015
+ * clocks keep ticking on CPU, so when they come back into range they
4016
+ * resume in sync.
4017
+ *
4018
+ * Lower values trade visual smoothness on distant characters for FPS.
4019
+ * Pass `Infinity` to disable culling entirely (always animate).
4020
+ *
4021
+ * Safe to call any time; takes effect on the next frame.
4022
+ *
4023
+ * @param distance Max distance to animate at, in world units.
4024
+ */
4025
+ setAnimationCullDistance(distance) {
4026
+ this.animationCullDistanceSq = distance * distance;
4027
+ }
4028
+ /** Current animation cull distance (in world units). See `setAnimationCullDistance`. */
4029
+ get animationCullDistance() {
4030
+ return Math.sqrt(this.animationCullDistanceSq);
4031
+ }
4032
+ /**
4033
+ * Max skinned instances the renderer was sized for at construction.
4034
+ * Independent budget from `maxInstances` since skinned characters use a
4035
+ * separate set of GPU buffers. Read-only.
4036
+ */
4037
+ get maxSkinned() {
4038
+ return this.maxSkinnedInstances;
4039
+ }
3988
4040
  /**
3989
4041
  * Create a flat grid mesh on the XZ plane at Y=0.
3990
4042
  *
@@ -4016,6 +4068,245 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4016
4068
  indices: new Uint16Array(indices)
4017
4069
  });
4018
4070
  }
4071
+ /**
4072
+ * Create a unit-cube mesh centered at the origin. Pass `size` to scale the
4073
+ * edge length, or keep size = 1 and scale at the instance level.
4074
+ *
4075
+ * ```ts
4076
+ * const cube = renderer.createCube();
4077
+ * renderer.addInstance({ model: cube, color: [1, 0.5, 0.2], scale: 2 });
4078
+ * ```
4079
+ */
4080
+ createCube(opts = {}) {
4081
+ const h = (opts.size ?? 1) / 2;
4082
+ const positions = new Float32Array([
4083
+ // +Z
4084
+ -h,
4085
+ -h,
4086
+ h,
4087
+ h,
4088
+ -h,
4089
+ h,
4090
+ h,
4091
+ h,
4092
+ h,
4093
+ -h,
4094
+ -h,
4095
+ h,
4096
+ h,
4097
+ h,
4098
+ h,
4099
+ -h,
4100
+ h,
4101
+ h,
4102
+ // -Z
4103
+ h,
4104
+ -h,
4105
+ -h,
4106
+ -h,
4107
+ -h,
4108
+ -h,
4109
+ -h,
4110
+ h,
4111
+ -h,
4112
+ h,
4113
+ -h,
4114
+ -h,
4115
+ -h,
4116
+ h,
4117
+ -h,
4118
+ h,
4119
+ h,
4120
+ -h,
4121
+ // +Y
4122
+ -h,
4123
+ h,
4124
+ h,
4125
+ h,
4126
+ h,
4127
+ h,
4128
+ h,
4129
+ h,
4130
+ -h,
4131
+ -h,
4132
+ h,
4133
+ h,
4134
+ h,
4135
+ h,
4136
+ -h,
4137
+ -h,
4138
+ h,
4139
+ -h,
4140
+ // -Y
4141
+ -h,
4142
+ -h,
4143
+ -h,
4144
+ h,
4145
+ -h,
4146
+ -h,
4147
+ h,
4148
+ -h,
4149
+ h,
4150
+ -h,
4151
+ -h,
4152
+ -h,
4153
+ h,
4154
+ -h,
4155
+ h,
4156
+ -h,
4157
+ -h,
4158
+ h,
4159
+ // +X
4160
+ h,
4161
+ -h,
4162
+ h,
4163
+ h,
4164
+ -h,
4165
+ -h,
4166
+ h,
4167
+ h,
4168
+ -h,
4169
+ h,
4170
+ -h,
4171
+ h,
4172
+ h,
4173
+ h,
4174
+ -h,
4175
+ h,
4176
+ h,
4177
+ h,
4178
+ // -X
4179
+ -h,
4180
+ -h,
4181
+ -h,
4182
+ -h,
4183
+ -h,
4184
+ h,
4185
+ -h,
4186
+ h,
4187
+ h,
4188
+ -h,
4189
+ -h,
4190
+ -h,
4191
+ -h,
4192
+ h,
4193
+ h,
4194
+ -h,
4195
+ h,
4196
+ -h
4197
+ ]);
4198
+ const normals = new Float32Array([
4199
+ 0,
4200
+ 0,
4201
+ 1,
4202
+ 0,
4203
+ 0,
4204
+ 1,
4205
+ 0,
4206
+ 0,
4207
+ 1,
4208
+ 0,
4209
+ 0,
4210
+ 1,
4211
+ 0,
4212
+ 0,
4213
+ 1,
4214
+ 0,
4215
+ 0,
4216
+ 1,
4217
+ 0,
4218
+ 0,
4219
+ -1,
4220
+ 0,
4221
+ 0,
4222
+ -1,
4223
+ 0,
4224
+ 0,
4225
+ -1,
4226
+ 0,
4227
+ 0,
4228
+ -1,
4229
+ 0,
4230
+ 0,
4231
+ -1,
4232
+ 0,
4233
+ 0,
4234
+ -1,
4235
+ 0,
4236
+ 1,
4237
+ 0,
4238
+ 0,
4239
+ 1,
4240
+ 0,
4241
+ 0,
4242
+ 1,
4243
+ 0,
4244
+ 0,
4245
+ 1,
4246
+ 0,
4247
+ 0,
4248
+ 1,
4249
+ 0,
4250
+ 0,
4251
+ 1,
4252
+ 0,
4253
+ 0,
4254
+ -1,
4255
+ 0,
4256
+ 0,
4257
+ -1,
4258
+ 0,
4259
+ 0,
4260
+ -1,
4261
+ 0,
4262
+ 0,
4263
+ -1,
4264
+ 0,
4265
+ 0,
4266
+ -1,
4267
+ 0,
4268
+ 0,
4269
+ -1,
4270
+ 0,
4271
+ 1,
4272
+ 0,
4273
+ 0,
4274
+ 1,
4275
+ 0,
4276
+ 0,
4277
+ 1,
4278
+ 0,
4279
+ 0,
4280
+ 1,
4281
+ 0,
4282
+ 0,
4283
+ 1,
4284
+ 0,
4285
+ 0,
4286
+ 1,
4287
+ 0,
4288
+ 0,
4289
+ -1,
4290
+ 0,
4291
+ 0,
4292
+ -1,
4293
+ 0,
4294
+ 0,
4295
+ -1,
4296
+ 0,
4297
+ 0,
4298
+ -1,
4299
+ 0,
4300
+ 0,
4301
+ -1,
4302
+ 0,
4303
+ 0,
4304
+ -1,
4305
+ 0,
4306
+ 0
4307
+ ]);
4308
+ return this.loadModel({ positions, normals });
4309
+ }
4019
4310
  /**
4020
4311
  * Register a model. Returns a handle for addInstance().
4021
4312
  *
@@ -4331,18 +4622,22 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4331
4622
  * with another instance (e.g., when spawning all parts of a character).
4332
4623
  */
4333
4624
  addInstance(opts) {
4625
+ const userPrefabId = isPrefab3D(opts.model) ? opts.model.id : null;
4626
+ if (isPrefab3D(opts.model) && opts.model.type === "composite") {
4627
+ return this.addCompositeInstance(opts, opts.model);
4628
+ }
4334
4629
  const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
4335
4630
  if ("parts" in modelOrGltf) {
4336
- return this.addGltfInstance(opts, modelOrGltf);
4631
+ return this.addGltfInstance(opts, modelOrGltf, userPrefabId);
4337
4632
  }
4338
4633
  const modelHandle = modelOrGltf;
4339
4634
  const model = this.models[modelHandle.id];
4340
4635
  if (model?.skinned) {
4341
- return this.addSkinnedInstance(opts, modelHandle, model.skinIndex);
4636
+ return this.addSkinnedInstance(opts, modelHandle, model.skinIndex, void 0, userPrefabId);
4342
4637
  }
4343
4638
  const slot = this.freeList.allocate();
4344
4639
  if (slot === -1)
4345
- throw new Error(`Max instances (${this.maxModels}) reached`);
4640
+ throw new Error(`Max instances (${this.maxInstances}) reached`);
4346
4641
  const dynBase = slot * DYNAMIC_MESH_FLOATS;
4347
4642
  const statBase = slot * STATIC_MESH_FLOATS;
4348
4643
  const t = resolveTransform(opts);
@@ -4369,8 +4664,16 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4369
4664
  this.batcher.add(0, modelHandle.id, slot);
4370
4665
  const dynamicData = this.dynamicData;
4371
4666
  const staticData = this.staticData;
4372
- return {
4667
+ const self = this;
4668
+ let destroyed = false;
4669
+ const posOut = [0, 0, 0];
4670
+ const rotOut = [0, 0, 0];
4671
+ const sclOut = [0, 0, 0];
4672
+ const handle = {
4673
+ slot,
4674
+ modelId: modelHandle.id,
4373
4675
  skinned: false,
4676
+ prefabId: userPrefabId,
4374
4677
  setPosition(nx, ny, nz) {
4375
4678
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4376
4679
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -4385,10 +4688,47 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4385
4688
  staticData[statBase + STAT_SX] = nx;
4386
4689
  staticData[statBase + STAT_SY] = ny;
4387
4690
  staticData[statBase + STAT_SZ] = nz;
4691
+ },
4692
+ teleport(nx, ny, nz) {
4693
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
4694
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
4695
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
4696
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
4697
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
4698
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
4699
+ },
4700
+ get position() {
4701
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
4702
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
4703
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
4704
+ return posOut;
4705
+ },
4706
+ get rotation() {
4707
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
4708
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
4709
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
4710
+ return rotOut;
4711
+ },
4712
+ get scale() {
4713
+ sclOut[0] = staticData[statBase + STAT_SX];
4714
+ sclOut[1] = staticData[statBase + STAT_SY];
4715
+ sclOut[2] = staticData[statBase + STAT_SZ];
4716
+ return sclOut;
4717
+ },
4718
+ destroy() {
4719
+ if (destroyed)
4720
+ return;
4721
+ destroyed = true;
4722
+ self.batcher.remove(0, modelHandle.id, slot);
4723
+ self.freeList.free(slot);
4724
+ dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
4725
+ staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
4726
+ self.staticDirty = true;
4388
4727
  }
4389
4728
  };
4729
+ return handle;
4390
4730
  }
4391
- addGltfInstance(opts, gltf) {
4731
+ addGltfInstance(opts, gltf, prefabId) {
4392
4732
  const childHandles = [];
4393
4733
  let firstSkinnedSlot;
4394
4734
  for (const part of gltf.parts) {
@@ -4396,7 +4736,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4396
4736
  const model = this.models[part.id];
4397
4737
  let handle;
4398
4738
  if (model?.skinned) {
4399
- handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot);
4739
+ handle = this.addSkinnedInstance(partOpts, part, model.skinIndex, firstSkinnedSlot, prefabId);
4400
4740
  if (firstSkinnedSlot === void 0)
4401
4741
  firstSkinnedSlot = handle.slot;
4402
4742
  } else {
@@ -4405,8 +4745,10 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4405
4745
  childHandles.push(handle);
4406
4746
  }
4407
4747
  const skinnedHandle = childHandles.find((h) => h.skinned);
4748
+ const lead = childHandles[0];
4408
4749
  return {
4409
4750
  skinned: gltf.skinned,
4751
+ prefabId,
4410
4752
  setPosition(x, y, z) {
4411
4753
  for (const h of childHandles)
4412
4754
  h.setPosition(x, y, z);
@@ -4419,15 +4761,131 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4419
4761
  for (const h of childHandles)
4420
4762
  h.setScale(x, y, z);
4421
4763
  },
4764
+ teleport(x, y, z) {
4765
+ for (const h of childHandles)
4766
+ h.teleport(x, y, z);
4767
+ },
4768
+ get position() {
4769
+ return lead.position;
4770
+ },
4771
+ get rotation() {
4772
+ return lead.rotation;
4773
+ },
4774
+ get scale() {
4775
+ return lead.scale;
4776
+ },
4422
4777
  play: skinnedHandle?.play ? (name, opts2) => {
4423
4778
  skinnedHandle.play(name, opts2);
4424
4779
  } : void 0,
4425
4780
  stop: skinnedHandle?.stop ? () => {
4426
4781
  skinnedHandle.stop();
4427
- } : void 0
4782
+ } : void 0,
4783
+ destroy() {
4784
+ for (const h of childHandles)
4785
+ h.destroy();
4786
+ }
4787
+ };
4788
+ }
4789
+ /**
4790
+ * Spawn a composite prefab by spawning each of its parts at the composed
4791
+ * (instance + offset) transform. The returned handle broadcasts subsequent
4792
+ * `setPosition` / `setRotation` to every child, keeping each child's
4793
+ * baked offset applied on top of the new value.
4794
+ */
4795
+ addCompositeInstance(opts, composite) {
4796
+ const bucket = this._prefabs;
4797
+ if (!bucket) {
4798
+ throw new Error(
4799
+ `addInstance: composite '${composite.id}' requires the renderer to be constructed with the bucket (\`prefabs\`).`
4800
+ );
4801
+ }
4802
+ const basePos = opts.position ?? [0, 0, 0];
4803
+ const baseRot = opts.rotation ?? [0, 0, 0];
4804
+ const offsets = composite.parts.map((p) => ({
4805
+ px: p.offset?.position?.[0] ?? 0,
4806
+ py: p.offset?.position?.[1] ?? 0,
4807
+ pz: p.offset?.position?.[2] ?? 0,
4808
+ rx: p.offset?.rotation?.[0] ?? 0,
4809
+ ry: p.offset?.rotation?.[1] ?? 0,
4810
+ rz: p.offset?.rotation?.[2] ?? 0
4811
+ }));
4812
+ const childHandles = [];
4813
+ const posOut = [basePos[0], basePos[1], basePos[2]];
4814
+ const rotOut = [baseRot[0], baseRot[1], baseRot[2]];
4815
+ const sclOut = [1, 1, 1];
4816
+ const initialScale = opts.scale;
4817
+ if (typeof initialScale === "number") {
4818
+ sclOut[0] = sclOut[1] = sclOut[2] = initialScale;
4819
+ } else if (initialScale) {
4820
+ sclOut[0] = initialScale[0];
4821
+ sclOut[1] = initialScale[1];
4822
+ sclOut[2] = initialScale[2];
4823
+ }
4824
+ for (let i = 0; i < composite.parts.length; i++) {
4825
+ const part = composite.parts[i];
4826
+ const off = offsets[i];
4827
+ const partPrefab = bucket.get(part.partId);
4828
+ const partOpts = {
4829
+ ...opts,
4830
+ model: partPrefab,
4831
+ position: [basePos[0] + off.px, basePos[1] + off.py, basePos[2] + off.pz],
4832
+ rotation: [baseRot[0] + off.rx, baseRot[1] + off.ry, baseRot[2] + off.rz]
4833
+ };
4834
+ childHandles.push(this.addInstance(partOpts));
4835
+ }
4836
+ return {
4837
+ skinned: childHandles.some((h) => h.skinned),
4838
+ prefabId: composite.id,
4839
+ setPosition(x, y, z) {
4840
+ posOut[0] = x;
4841
+ posOut[1] = y;
4842
+ posOut[2] = z;
4843
+ for (let i = 0; i < childHandles.length; i++) {
4844
+ const o = offsets[i];
4845
+ childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
4846
+ }
4847
+ },
4848
+ setRotation(x, y, z) {
4849
+ rotOut[0] = x;
4850
+ rotOut[1] = y;
4851
+ rotOut[2] = z;
4852
+ for (let i = 0; i < childHandles.length; i++) {
4853
+ const o = offsets[i];
4854
+ childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
4855
+ }
4856
+ },
4857
+ setScale(x, y, z) {
4858
+ sclOut[0] = x;
4859
+ sclOut[1] = y;
4860
+ sclOut[2] = z;
4861
+ for (const h of childHandles)
4862
+ h.setScale(x, y, z);
4863
+ },
4864
+ teleport(x, y, z) {
4865
+ posOut[0] = x;
4866
+ posOut[1] = y;
4867
+ posOut[2] = z;
4868
+ for (let i = 0; i < childHandles.length; i++) {
4869
+ const o = offsets[i];
4870
+ childHandles[i].teleport(x + o.px, y + o.py, z + o.pz);
4871
+ }
4872
+ },
4873
+ get position() {
4874
+ return posOut;
4875
+ },
4876
+ get rotation() {
4877
+ return rotOut;
4878
+ },
4879
+ get scale() {
4880
+ return sclOut;
4881
+ },
4882
+ destroy() {
4883
+ for (const h of childHandles)
4884
+ h.destroy();
4885
+ }
4428
4886
  };
4429
4887
  }
4430
- addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
4888
+ addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot, prefabId = null) {
4431
4889
  const slot = this.skinnedFreeList.allocate();
4432
4890
  if (slot === -1)
4433
4891
  throw new Error(`Max skinned instances (${this.maxSkinnedInstances}) reached`);
@@ -4438,11 +4896,27 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4438
4896
  if (linkedSlot !== void 0) {
4439
4897
  boneOffset = this.skinnedInstanceBoneOffsets[linkedSlot];
4440
4898
  animState = this.skinnedAnimStates[linkedSlot];
4899
+ this.boneOffsetRefcount[boneOffset]++;
4441
4900
  } else {
4442
- boneOffset = this.nextBoneOffset + jointCount;
4443
- this.nextBoneOffset += jointCount * 2;
4444
- skinModel.animation.computeRestPose(this.boneMatrixData, boneOffset * 16);
4445
- this.boneMatrixDirty = true;
4901
+ const pool = this.freedBoneOffsets.get(skinIndex);
4902
+ if (pool && pool.length > 0) {
4903
+ boneOffset = pool.pop();
4904
+ } else {
4905
+ boneOffset = this.nextBoneOffset + jointCount;
4906
+ this.nextBoneOffset += jointCount * 2;
4907
+ }
4908
+ this.boneOffsetRefcount[boneOffset] = 1;
4909
+ this.boneOffsetSkinIndex[boneOffset] = skinIndex;
4910
+ const restOffsetFloats = boneOffset * 16;
4911
+ const restLengthFloats = jointCount * 16;
4912
+ skinModel.animation.computeRestPose(this.boneMatrixData, restOffsetFloats);
4913
+ this.device.queue.writeBuffer(
4914
+ this.rawBoneMatrixBuffer,
4915
+ restOffsetFloats * 4,
4916
+ this.boneMatrixData.buffer,
4917
+ this.boneMatrixData.byteOffset + restOffsetFloats * 4,
4918
+ restLengthFloats * 4
4919
+ );
4446
4920
  animState = skinModel.animation.clipCount > 0 ? skinModel.animation.createState(0, 1, true) : null;
4447
4921
  }
4448
4922
  this.skinnedInstanceBoneOffsets[slot] = boneOffset;
@@ -4468,11 +4942,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4468
4942
  this.skinnedStaticData[statBase + SSTAT_CR] = t.cr;
4469
4943
  this.skinnedStaticData[statBase + SSTAT_CG] = t.cg;
4470
4944
  this.skinnedStaticData[statBase + SSTAT_CB] = t.cb;
4471
- new DataView(this.skinnedStaticData.buffer).setUint32(
4472
- (statBase + SSTAT_BONE_OFFSET) * 4,
4473
- boneOffset,
4474
- true
4475
- );
4945
+ this.skinnedStaticDV.setUint32((statBase + SSTAT_BONE_OFFSET) * 4, boneOffset, true);
4476
4946
  this.skinnedStaticDirty = true;
4477
4947
  this.skinnedInstanceModelIds[slot] = modelHandle.id;
4478
4948
  this.skinnedBatcher.add(0, modelHandle.id, slot);
@@ -4480,10 +4950,18 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4480
4950
  const staticData = this.skinnedStaticData;
4481
4951
  const animStates = this.skinnedAnimStates;
4482
4952
  const animation = skinModel.animation;
4953
+ const self = this;
4954
+ const capturedBoneOffset = boneOffset;
4955
+ const capturedSkinIndex = skinIndex;
4956
+ let destroyed = false;
4957
+ const posOut = [0, 0, 0];
4958
+ const rotOut = [0, 0, 0];
4959
+ const sclOut = [0, 0, 0];
4483
4960
  return {
4484
4961
  slot,
4485
4962
  modelId: modelHandle.id,
4486
4963
  skinned: true,
4964
+ prefabId,
4487
4965
  setPosition(nx, ny, nz) {
4488
4966
  dynamicData[dynBase + DYN_CURR_PX] = nx;
4489
4967
  dynamicData[dynBase + DYN_CURR_PY] = ny;
@@ -4499,6 +4977,32 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4499
4977
  staticData[statBase + SSTAT_SY] = ny;
4500
4978
  staticData[statBase + SSTAT_SZ] = nz;
4501
4979
  },
4980
+ teleport(nx, ny, nz) {
4981
+ dynamicData[dynBase + DYN_PREV_PX] = nx;
4982
+ dynamicData[dynBase + DYN_PREV_PY] = ny;
4983
+ dynamicData[dynBase + DYN_PREV_PZ] = nz;
4984
+ dynamicData[dynBase + DYN_CURR_PX] = nx;
4985
+ dynamicData[dynBase + DYN_CURR_PY] = ny;
4986
+ dynamicData[dynBase + DYN_CURR_PZ] = nz;
4987
+ },
4988
+ get position() {
4989
+ posOut[0] = dynamicData[dynBase + DYN_CURR_PX];
4990
+ posOut[1] = dynamicData[dynBase + DYN_CURR_PY];
4991
+ posOut[2] = dynamicData[dynBase + DYN_CURR_PZ];
4992
+ return posOut;
4993
+ },
4994
+ get rotation() {
4995
+ rotOut[0] = dynamicData[dynBase + DYN_CURR_RX];
4996
+ rotOut[1] = dynamicData[dynBase + DYN_CURR_RY];
4997
+ rotOut[2] = dynamicData[dynBase + DYN_CURR_RZ];
4998
+ return rotOut;
4999
+ },
5000
+ get scale() {
5001
+ sclOut[0] = staticData[statBase + SSTAT_SX];
5002
+ sclOut[1] = staticData[statBase + SSTAT_SY];
5003
+ sclOut[2] = staticData[statBase + SSTAT_SZ];
5004
+ return sclOut;
5005
+ },
4502
5006
  play(name, opts2) {
4503
5007
  const state = animStates[slot];
4504
5008
  if (state)
@@ -4508,6 +5012,25 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4508
5012
  const state = animStates[slot];
4509
5013
  if (state)
4510
5014
  animation.stop(state);
5015
+ },
5016
+ destroy() {
5017
+ if (destroyed)
5018
+ return;
5019
+ destroyed = true;
5020
+ self.skinnedBatcher.remove(0, modelHandle.id, slot);
5021
+ self.skinnedFreeList.free(slot);
5022
+ dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
5023
+ staticData.fill(0, statBase, statBase + SKINNED_STATIC_MESH_FLOATS);
5024
+ animStates[slot] = null;
5025
+ self.skinnedStaticDirty = true;
5026
+ if (--self.boneOffsetRefcount[capturedBoneOffset] === 0) {
5027
+ let pool = self.freedBoneOffsets.get(capturedSkinIndex);
5028
+ if (!pool) {
5029
+ pool = [];
5030
+ self.freedBoneOffsets.set(capturedSkinIndex, pool);
5031
+ }
5032
+ pool.push(capturedBoneOffset);
5033
+ }
4511
5034
  }
4512
5035
  };
4513
5036
  }
@@ -4593,6 +5116,31 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4593
5116
  this.animJointLookupOffset = pb.jointLookupOffset;
4594
5117
  return true;
4595
5118
  }
5119
+ /**
5120
+ * Returns true if a skinned instance's bone-matrix compute should be
5121
+ * dispatched this frame. Combines frustum culling (scaled bounding sphere)
5122
+ * and a configurable distance cull from the camera. Callers pass the
5123
+ * camera position + cullDistSq once outside the loop to avoid re-reading.
5124
+ */
5125
+ shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq) {
5126
+ const skinModel = model && model.skinIndex >= 0 ? this.skinnedModels[model.skinIndex] : null;
5127
+ const baseRadius = skinModel?.boundingRadius ?? 10;
5128
+ const base = slot * DYNAMIC_MESH_FLOATS;
5129
+ const sBase = slot * SKINNED_STATIC_MESH_FLOATS;
5130
+ const cx = this.skinnedDynamicData[base + DYN_CURR_PX];
5131
+ const cy = this.skinnedDynamicData[base + DYN_CURR_PY];
5132
+ const cz = this.skinnedDynamicData[base + DYN_CURR_PZ];
5133
+ const sx = Math.abs(this.skinnedStaticData[sBase + SSTAT_SX]);
5134
+ const sy = Math.abs(this.skinnedStaticData[sBase + SSTAT_SY]);
5135
+ const sz = Math.abs(this.skinnedStaticData[sBase + SSTAT_SZ]);
5136
+ const maxScale = sx > sy ? sx > sz ? sx : sz : sy > sz ? sy : sz;
5137
+ if (!this.isInFrustum(cx, cy, cz, baseRadius * maxScale))
5138
+ return false;
5139
+ const dxv = cx - camX, dyv = cy - camY, dzv = cz - camZ;
5140
+ if (dxv * dxv + dyv * dyv + dzv * dzv > cullDistSq)
5141
+ return false;
5142
+ return true;
5143
+ }
4596
5144
  updateAnimations(deltaTime) {
4597
5145
  this.syncLazyAnimationChanges();
4598
5146
  if (this.animComputeNeedsRebuild && this.packedAnimData.clips.length > 0) {
@@ -4636,6 +5184,9 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4636
5184
  this.updatedBoneOffsets.fill(0);
4637
5185
  let count = 0;
4638
5186
  const dv = this.gpuInstDV;
5187
+ const camPos = this.camera.position;
5188
+ const camX = camPos[0], camY = camPos[1], camZ = camPos[2];
5189
+ const cullDistSq = this.animationCullDistanceSq;
4639
5190
  for (let slot = 0; slot < this.maxSkinnedInstances; slot++) {
4640
5191
  const animState = this.skinnedAnimStates[slot];
4641
5192
  if (!animState)
@@ -4674,6 +5225,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4674
5225
  const modelId = this.skinnedInstanceModelIds[slot];
4675
5226
  const model = this.models[modelId];
4676
5227
  const skinIdx = model?.skinIndex ?? 0;
5228
+ if (!this.shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq))
5229
+ continue;
4677
5230
  const off = count * 32;
4678
5231
  dv.setInt32(off, animState.clipId, true);
4679
5232
  dv.setFloat32(off + 4, animState.time, true);
@@ -4718,14 +5271,12 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
4718
5271
  }
4719
5272
  }
4720
5273
  }
5274
+ /**
5275
+ * Free an instance's renderer slot. Equivalent to `handle.destroy()` -
5276
+ * kept as a convenience for direct lookup. Safe to call multiple times.
5277
+ */
4721
5278
  removeInstance(handle) {
4722
- this.batcher.remove(0, handle.modelId, handle.slot);
4723
- this.freeList.free(handle.slot);
4724
- const dynBase = handle.slot * DYNAMIC_MESH_FLOATS;
4725
- const statBase = handle.slot * STATIC_MESH_FLOATS;
4726
- this.dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
4727
- this.staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
4728
- this.staticDirty = true;
5279
+ handle.destroy();
4729
5280
  }
4730
5281
  storePreviousState() {
4731
5282
  this.camera.storePrevious();