murow 0.0.72 → 0.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/driver/drivers/raf.js +1 -1
- package/dist/cjs/core/sparse-batcher/sparse-batcher.js +1 -1
- package/dist/cjs/renderer/base/renderer-3d.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/index.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/cjs/renderer/prefab-bucket/specs.js +1 -1
- package/dist/esm/core/driver/drivers/raf.js +1 -1
- package/dist/esm/core/sparse-batcher/sparse-batcher.js +1 -1
- package/dist/esm/renderer/base/renderer-3d.js +1 -1
- package/dist/esm/renderer/prefab-bucket/concrete.js +1 -1
- package/dist/esm/renderer/prefab-bucket/index.js +1 -1
- package/dist/esm/renderer/prefab-bucket/parsers.js +1 -1
- package/dist/types/core/driver/drivers/raf.d.ts +13 -2
- package/dist/types/renderer/base/renderer-3d.d.ts +1 -1
- package/dist/types/renderer/prefab-bucket/concrete.d.ts +42 -2
- package/dist/types/renderer/prefab-bucket/index.d.ts +12 -2
- package/dist/types/renderer/prefab-bucket/specs.d.ts +46 -3
- package/dist/types/renderer/types.d.ts +5 -3
- package/dist/webgpu/cjs/index.js +471 -35
- package/dist/webgpu/esm/index.js +471 -35
- package/dist/webgpu/types/3d/renderer.d.ts +73 -5
- package/package.json +1 -1
package/dist/webgpu/cjs/index.js
CHANGED
|
@@ -3768,7 +3768,8 @@ var SSTAT_CB = 5;
|
|
|
3768
3768
|
var SSTAT_BONE_OFFSET = 6;
|
|
3769
3769
|
var prefabHandles = /* @__PURE__ */ new WeakMap();
|
|
3770
3770
|
function isPrefab3D(value) {
|
|
3771
|
-
|
|
3771
|
+
const t = value.type;
|
|
3772
|
+
return t === "gltf" || t === "grid" || t === "cube" || t === "composite";
|
|
3772
3773
|
}
|
|
3773
3774
|
function resolveTransform(opts) {
|
|
3774
3775
|
const [px, py, pz] = opts.position ?? [0, 0, 0];
|
|
@@ -3802,8 +3803,8 @@ function computeBucketStats(bucket) {
|
|
|
3802
3803
|
}
|
|
3803
3804
|
var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
3804
3805
|
constructor(canvas, options) {
|
|
3805
|
-
const
|
|
3806
|
-
super(canvas, { ...options,
|
|
3806
|
+
const resolvedMaxInstances = options.maxInstances ?? (options.prefabs ? options.prefabs.size + 16 : 32);
|
|
3807
|
+
super(canvas, { ...options, maxInstances: resolvedMaxInstances });
|
|
3807
3808
|
this.resizeObserver = null;
|
|
3808
3809
|
this.resizeCallbacks = [];
|
|
3809
3810
|
// layer=0, sheetId=modelId
|
|
@@ -3829,6 +3830,8 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3829
3830
|
this.skinnedStaticDirty = false;
|
|
3830
3831
|
this.skinnedAnimStates = [];
|
|
3831
3832
|
this.nextBoneOffset = 0;
|
|
3833
|
+
/** Reusable bone-offset blocks per skinIndex. Pushed on remove, popped on add. Indexed by skinIndex (low cardinality), so a Map of lists is fine. */
|
|
3834
|
+
this.freedBoneOffsets = /* @__PURE__ */ new Map();
|
|
3832
3835
|
// Frustum planes (6 planes × 4 floats each), extracted from VP matrix
|
|
3833
3836
|
this.frustumPlanes = new Float32Array(24);
|
|
3834
3837
|
this.uniformData = new Float32Array(24);
|
|
@@ -3838,22 +3841,26 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3838
3841
|
this._prefabs = options.prefabs ?? null;
|
|
3839
3842
|
const SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP = 3;
|
|
3840
3843
|
const bucketStats = this._prefabs ? computeBucketStats(this._prefabs) : null;
|
|
3841
|
-
|
|
3842
|
-
this.maxSkinnedInstances = options.maxSkinnedInstances ?? (bucketStats ? maxInstances * Math.max(1, Math.min(bucketStats.maxSkinnedParts, SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP)) : 5e3);
|
|
3844
|
+
this.maxSkinnedInstances = options.maxSkinnedInstances ?? (bucketStats ? resolvedMaxInstances * Math.max(1, Math.min(bucketStats.maxSkinnedParts, SKINNED_PARTS_PER_INSTANCE_DEFAULT_CAP)) : 5e3);
|
|
3843
3845
|
this.maxBonesPerSkin = options.maxBonesPerSkin ?? (bucketStats ? Math.max(1, bucketStats.maxJointCount) : 64);
|
|
3846
|
+
const cullDist = options.animationCullDistance ?? 50;
|
|
3847
|
+
this.animationCullDistanceSq = cullDist * cullDist;
|
|
3844
3848
|
this.maxTotalBones = this.maxSkinnedInstances * this.maxBonesPerSkin * 2;
|
|
3845
3849
|
this.updatedBoneOffsets = new Uint8Array(this.maxTotalBones);
|
|
3846
|
-
this.
|
|
3847
|
-
this.
|
|
3848
|
-
this.
|
|
3849
|
-
this.
|
|
3850
|
-
this.
|
|
3851
|
-
this.
|
|
3850
|
+
this.boneOffsetRefcount = new Uint32Array(this.maxTotalBones);
|
|
3851
|
+
this.boneOffsetSkinIndex = new Uint32Array(this.maxTotalBones);
|
|
3852
|
+
this.freeList = new import_free_list3.FreeList(resolvedMaxInstances);
|
|
3853
|
+
this.batcher = new import_sparse_batcher2.SparseBatcher(resolvedMaxInstances);
|
|
3854
|
+
this.dynamicData = new Float32Array(resolvedMaxInstances * DYNAMIC_MESH_FLOATS);
|
|
3855
|
+
this.staticData = new Float32Array(resolvedMaxInstances * STATIC_MESH_FLOATS);
|
|
3856
|
+
this.slotIndexData = new Uint32Array(resolvedMaxInstances);
|
|
3857
|
+
this.instanceModelIds = new Uint8Array(resolvedMaxInstances);
|
|
3852
3858
|
const msi = this.maxSkinnedInstances;
|
|
3853
3859
|
this.skinnedFreeList = new import_free_list3.FreeList(msi);
|
|
3854
3860
|
this.skinnedBatcher = new import_sparse_batcher2.SparseBatcher(msi);
|
|
3855
3861
|
this.skinnedDynamicData = new Float32Array(msi * DYNAMIC_MESH_FLOATS);
|
|
3856
3862
|
this.skinnedStaticData = new Float32Array(msi * SKINNED_STATIC_MESH_FLOATS);
|
|
3863
|
+
this.skinnedStaticDV = new DataView(this.skinnedStaticData.buffer);
|
|
3857
3864
|
this.skinnedSlotIndexData = new Uint32Array(msi);
|
|
3858
3865
|
this.skinnedInstanceModelIds = new Uint8Array(msi);
|
|
3859
3866
|
this.skinnedInstanceBoneOffsets = new Uint32Array(msi);
|
|
@@ -3864,7 +3871,19 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3864
3871
|
this.gpuInstDV = new DataView(this.gpuInstData.buffer);
|
|
3865
3872
|
}
|
|
3866
3873
|
async init() {
|
|
3867
|
-
|
|
3874
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
3875
|
+
if (!adapter)
|
|
3876
|
+
throw new Error("WebGPU3DRenderer: no GPU adapter available");
|
|
3877
|
+
const a = adapter.limits;
|
|
3878
|
+
const requiredLimits = {
|
|
3879
|
+
maxBufferSize: a.maxBufferSize,
|
|
3880
|
+
maxStorageBufferBindingSize: a.maxStorageBufferBindingSize,
|
|
3881
|
+
maxStorageBuffersPerShaderStage: a.maxStorageBuffersPerShaderStage,
|
|
3882
|
+
maxComputeWorkgroupStorageSize: a.maxComputeWorkgroupStorageSize,
|
|
3883
|
+
maxComputeInvocationsPerWorkgroup: a.maxComputeInvocationsPerWorkgroup
|
|
3884
|
+
};
|
|
3885
|
+
const device = await adapter.requestDevice({ requiredLimits });
|
|
3886
|
+
this.root = import_typegpu9.default.initFromDevice({ device });
|
|
3868
3887
|
this.device = this.root.device;
|
|
3869
3888
|
this.context = this.canvas.getContext("webgpu");
|
|
3870
3889
|
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
@@ -3881,7 +3900,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3881
3900
|
format: "depth24plus",
|
|
3882
3901
|
usage: GPUTextureUsage.RENDER_ATTACHMENT
|
|
3883
3902
|
});
|
|
3884
|
-
this.meshLayout = createMeshLayout(this.
|
|
3903
|
+
this.meshLayout = createMeshLayout(this.maxInstances);
|
|
3885
3904
|
const depthStencil = {
|
|
3886
3905
|
format: "depth24plus",
|
|
3887
3906
|
depthWriteEnabled: true,
|
|
@@ -3928,10 +3947,10 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
3928
3947
|
primitive,
|
|
3929
3948
|
depthStencil
|
|
3930
3949
|
});
|
|
3931
|
-
this.dynamicBuffer = this.root.createBuffer(d.arrayOf(DynamicMesh, this.
|
|
3932
|
-
this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.
|
|
3950
|
+
this.dynamicBuffer = this.root.createBuffer(d.arrayOf(DynamicMesh, this.maxInstances)).$usage("storage");
|
|
3951
|
+
this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.maxInstances)).$usage("storage");
|
|
3933
3952
|
this.uniformBuffer = this.root.createBuffer(MeshUniforms).$usage("uniform");
|
|
3934
|
-
this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.
|
|
3953
|
+
this.slotIndexBuffer = this.root.createBuffer(d.arrayOf(d.u32, this.maxInstances)).$usage("storage");
|
|
3935
3954
|
this.rawDynamicBuffer = this.root.unwrap(this.dynamicBuffer);
|
|
3936
3955
|
this.rawStaticBuffer = this.root.unwrap(this.staticBuffer);
|
|
3937
3956
|
this.rawUniformBuffer = this.root.unwrap(this.uniformBuffer);
|
|
@@ -4033,6 +4052,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4033
4052
|
lineWidth: prefab.lineWidth
|
|
4034
4053
|
});
|
|
4035
4054
|
prefabHandles.set(prefab, model);
|
|
4055
|
+
} else if (prefab.type === "cube") {
|
|
4056
|
+
const model = this.createCube({ size: prefab.size });
|
|
4057
|
+
prefabHandles.set(prefab, model);
|
|
4036
4058
|
}
|
|
4037
4059
|
}
|
|
4038
4060
|
}
|
|
@@ -4097,6 +4119,36 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4097
4119
|
createCompute(name, options) {
|
|
4098
4120
|
return new ComputeBuilder(name, options, this.root);
|
|
4099
4121
|
}
|
|
4122
|
+
/**
|
|
4123
|
+
* Set the maximum distance (in world units) at which the renderer keeps
|
|
4124
|
+
* computing skeletal animation. Skinned instances farther than this are
|
|
4125
|
+
* still drawn, but with their last-computed bone matrices instead of
|
|
4126
|
+
* fresh ones, which saves GPU compute work. Their internal animation
|
|
4127
|
+
* clocks keep ticking on CPU, so when they come back into range they
|
|
4128
|
+
* resume in sync.
|
|
4129
|
+
*
|
|
4130
|
+
* Lower values trade visual smoothness on distant characters for FPS.
|
|
4131
|
+
* Pass `Infinity` to disable culling entirely (always animate).
|
|
4132
|
+
*
|
|
4133
|
+
* Safe to call any time; takes effect on the next frame.
|
|
4134
|
+
*
|
|
4135
|
+
* @param distance Max distance to animate at, in world units.
|
|
4136
|
+
*/
|
|
4137
|
+
setAnimationCullDistance(distance) {
|
|
4138
|
+
this.animationCullDistanceSq = distance * distance;
|
|
4139
|
+
}
|
|
4140
|
+
/** Current animation cull distance (in world units). See `setAnimationCullDistance`. */
|
|
4141
|
+
get animationCullDistance() {
|
|
4142
|
+
return Math.sqrt(this.animationCullDistanceSq);
|
|
4143
|
+
}
|
|
4144
|
+
/**
|
|
4145
|
+
* Max skinned instances the renderer was sized for at construction.
|
|
4146
|
+
* Independent budget from `maxInstances` since skinned characters use a
|
|
4147
|
+
* separate set of GPU buffers. Read-only.
|
|
4148
|
+
*/
|
|
4149
|
+
get maxSkinned() {
|
|
4150
|
+
return this.maxSkinnedInstances;
|
|
4151
|
+
}
|
|
4100
4152
|
/**
|
|
4101
4153
|
* Create a flat grid mesh on the XZ plane at Y=0.
|
|
4102
4154
|
*
|
|
@@ -4128,6 +4180,245 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4128
4180
|
indices: new Uint16Array(indices)
|
|
4129
4181
|
});
|
|
4130
4182
|
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Create a unit-cube mesh centered at the origin. Pass `size` to scale the
|
|
4185
|
+
* edge length, or keep size = 1 and scale at the instance level.
|
|
4186
|
+
*
|
|
4187
|
+
* ```ts
|
|
4188
|
+
* const cube = renderer.createCube();
|
|
4189
|
+
* renderer.addInstance({ model: cube, color: [1, 0.5, 0.2], scale: 2 });
|
|
4190
|
+
* ```
|
|
4191
|
+
*/
|
|
4192
|
+
createCube(opts = {}) {
|
|
4193
|
+
const h = (opts.size ?? 1) / 2;
|
|
4194
|
+
const positions = new Float32Array([
|
|
4195
|
+
// +Z
|
|
4196
|
+
-h,
|
|
4197
|
+
-h,
|
|
4198
|
+
h,
|
|
4199
|
+
h,
|
|
4200
|
+
-h,
|
|
4201
|
+
h,
|
|
4202
|
+
h,
|
|
4203
|
+
h,
|
|
4204
|
+
h,
|
|
4205
|
+
-h,
|
|
4206
|
+
-h,
|
|
4207
|
+
h,
|
|
4208
|
+
h,
|
|
4209
|
+
h,
|
|
4210
|
+
h,
|
|
4211
|
+
-h,
|
|
4212
|
+
h,
|
|
4213
|
+
h,
|
|
4214
|
+
// -Z
|
|
4215
|
+
h,
|
|
4216
|
+
-h,
|
|
4217
|
+
-h,
|
|
4218
|
+
-h,
|
|
4219
|
+
-h,
|
|
4220
|
+
-h,
|
|
4221
|
+
-h,
|
|
4222
|
+
h,
|
|
4223
|
+
-h,
|
|
4224
|
+
h,
|
|
4225
|
+
-h,
|
|
4226
|
+
-h,
|
|
4227
|
+
-h,
|
|
4228
|
+
h,
|
|
4229
|
+
-h,
|
|
4230
|
+
h,
|
|
4231
|
+
h,
|
|
4232
|
+
-h,
|
|
4233
|
+
// +Y
|
|
4234
|
+
-h,
|
|
4235
|
+
h,
|
|
4236
|
+
h,
|
|
4237
|
+
h,
|
|
4238
|
+
h,
|
|
4239
|
+
h,
|
|
4240
|
+
h,
|
|
4241
|
+
h,
|
|
4242
|
+
-h,
|
|
4243
|
+
-h,
|
|
4244
|
+
h,
|
|
4245
|
+
h,
|
|
4246
|
+
h,
|
|
4247
|
+
h,
|
|
4248
|
+
-h,
|
|
4249
|
+
-h,
|
|
4250
|
+
h,
|
|
4251
|
+
-h,
|
|
4252
|
+
// -Y
|
|
4253
|
+
-h,
|
|
4254
|
+
-h,
|
|
4255
|
+
-h,
|
|
4256
|
+
h,
|
|
4257
|
+
-h,
|
|
4258
|
+
-h,
|
|
4259
|
+
h,
|
|
4260
|
+
-h,
|
|
4261
|
+
h,
|
|
4262
|
+
-h,
|
|
4263
|
+
-h,
|
|
4264
|
+
-h,
|
|
4265
|
+
h,
|
|
4266
|
+
-h,
|
|
4267
|
+
h,
|
|
4268
|
+
-h,
|
|
4269
|
+
-h,
|
|
4270
|
+
h,
|
|
4271
|
+
// +X
|
|
4272
|
+
h,
|
|
4273
|
+
-h,
|
|
4274
|
+
h,
|
|
4275
|
+
h,
|
|
4276
|
+
-h,
|
|
4277
|
+
-h,
|
|
4278
|
+
h,
|
|
4279
|
+
h,
|
|
4280
|
+
-h,
|
|
4281
|
+
h,
|
|
4282
|
+
-h,
|
|
4283
|
+
h,
|
|
4284
|
+
h,
|
|
4285
|
+
h,
|
|
4286
|
+
-h,
|
|
4287
|
+
h,
|
|
4288
|
+
h,
|
|
4289
|
+
h,
|
|
4290
|
+
// -X
|
|
4291
|
+
-h,
|
|
4292
|
+
-h,
|
|
4293
|
+
-h,
|
|
4294
|
+
-h,
|
|
4295
|
+
-h,
|
|
4296
|
+
h,
|
|
4297
|
+
-h,
|
|
4298
|
+
h,
|
|
4299
|
+
h,
|
|
4300
|
+
-h,
|
|
4301
|
+
-h,
|
|
4302
|
+
-h,
|
|
4303
|
+
-h,
|
|
4304
|
+
h,
|
|
4305
|
+
h,
|
|
4306
|
+
-h,
|
|
4307
|
+
h,
|
|
4308
|
+
-h
|
|
4309
|
+
]);
|
|
4310
|
+
const normals = new Float32Array([
|
|
4311
|
+
0,
|
|
4312
|
+
0,
|
|
4313
|
+
1,
|
|
4314
|
+
0,
|
|
4315
|
+
0,
|
|
4316
|
+
1,
|
|
4317
|
+
0,
|
|
4318
|
+
0,
|
|
4319
|
+
1,
|
|
4320
|
+
0,
|
|
4321
|
+
0,
|
|
4322
|
+
1,
|
|
4323
|
+
0,
|
|
4324
|
+
0,
|
|
4325
|
+
1,
|
|
4326
|
+
0,
|
|
4327
|
+
0,
|
|
4328
|
+
1,
|
|
4329
|
+
0,
|
|
4330
|
+
0,
|
|
4331
|
+
-1,
|
|
4332
|
+
0,
|
|
4333
|
+
0,
|
|
4334
|
+
-1,
|
|
4335
|
+
0,
|
|
4336
|
+
0,
|
|
4337
|
+
-1,
|
|
4338
|
+
0,
|
|
4339
|
+
0,
|
|
4340
|
+
-1,
|
|
4341
|
+
0,
|
|
4342
|
+
0,
|
|
4343
|
+
-1,
|
|
4344
|
+
0,
|
|
4345
|
+
0,
|
|
4346
|
+
-1,
|
|
4347
|
+
0,
|
|
4348
|
+
1,
|
|
4349
|
+
0,
|
|
4350
|
+
0,
|
|
4351
|
+
1,
|
|
4352
|
+
0,
|
|
4353
|
+
0,
|
|
4354
|
+
1,
|
|
4355
|
+
0,
|
|
4356
|
+
0,
|
|
4357
|
+
1,
|
|
4358
|
+
0,
|
|
4359
|
+
0,
|
|
4360
|
+
1,
|
|
4361
|
+
0,
|
|
4362
|
+
0,
|
|
4363
|
+
1,
|
|
4364
|
+
0,
|
|
4365
|
+
0,
|
|
4366
|
+
-1,
|
|
4367
|
+
0,
|
|
4368
|
+
0,
|
|
4369
|
+
-1,
|
|
4370
|
+
0,
|
|
4371
|
+
0,
|
|
4372
|
+
-1,
|
|
4373
|
+
0,
|
|
4374
|
+
0,
|
|
4375
|
+
-1,
|
|
4376
|
+
0,
|
|
4377
|
+
0,
|
|
4378
|
+
-1,
|
|
4379
|
+
0,
|
|
4380
|
+
0,
|
|
4381
|
+
-1,
|
|
4382
|
+
0,
|
|
4383
|
+
1,
|
|
4384
|
+
0,
|
|
4385
|
+
0,
|
|
4386
|
+
1,
|
|
4387
|
+
0,
|
|
4388
|
+
0,
|
|
4389
|
+
1,
|
|
4390
|
+
0,
|
|
4391
|
+
0,
|
|
4392
|
+
1,
|
|
4393
|
+
0,
|
|
4394
|
+
0,
|
|
4395
|
+
1,
|
|
4396
|
+
0,
|
|
4397
|
+
0,
|
|
4398
|
+
1,
|
|
4399
|
+
0,
|
|
4400
|
+
0,
|
|
4401
|
+
-1,
|
|
4402
|
+
0,
|
|
4403
|
+
0,
|
|
4404
|
+
-1,
|
|
4405
|
+
0,
|
|
4406
|
+
0,
|
|
4407
|
+
-1,
|
|
4408
|
+
0,
|
|
4409
|
+
0,
|
|
4410
|
+
-1,
|
|
4411
|
+
0,
|
|
4412
|
+
0,
|
|
4413
|
+
-1,
|
|
4414
|
+
0,
|
|
4415
|
+
0,
|
|
4416
|
+
-1,
|
|
4417
|
+
0,
|
|
4418
|
+
0
|
|
4419
|
+
]);
|
|
4420
|
+
return this.loadModel({ positions, normals });
|
|
4421
|
+
}
|
|
4131
4422
|
/**
|
|
4132
4423
|
* Register a model. Returns a handle for addInstance().
|
|
4133
4424
|
*
|
|
@@ -4443,6 +4734,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4443
4734
|
* with another instance (e.g., when spawning all parts of a character).
|
|
4444
4735
|
*/
|
|
4445
4736
|
addInstance(opts) {
|
|
4737
|
+
if (isPrefab3D(opts.model) && opts.model.type === "composite") {
|
|
4738
|
+
return this.addCompositeInstance(opts, opts.model);
|
|
4739
|
+
}
|
|
4446
4740
|
const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
|
|
4447
4741
|
if ("parts" in modelOrGltf) {
|
|
4448
4742
|
return this.addGltfInstance(opts, modelOrGltf);
|
|
@@ -4454,7 +4748,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4454
4748
|
}
|
|
4455
4749
|
const slot = this.freeList.allocate();
|
|
4456
4750
|
if (slot === -1)
|
|
4457
|
-
throw new Error(`Max instances (${this.
|
|
4751
|
+
throw new Error(`Max instances (${this.maxInstances}) reached`);
|
|
4458
4752
|
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
4459
4753
|
const statBase = slot * STATIC_MESH_FLOATS;
|
|
4460
4754
|
const t = resolveTransform(opts);
|
|
@@ -4481,7 +4775,11 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4481
4775
|
this.batcher.add(0, modelHandle.id, slot);
|
|
4482
4776
|
const dynamicData = this.dynamicData;
|
|
4483
4777
|
const staticData = this.staticData;
|
|
4484
|
-
|
|
4778
|
+
const self = this;
|
|
4779
|
+
let destroyed = false;
|
|
4780
|
+
const handle = {
|
|
4781
|
+
slot,
|
|
4782
|
+
modelId: modelHandle.id,
|
|
4485
4783
|
skinned: false,
|
|
4486
4784
|
setPosition(nx, ny, nz) {
|
|
4487
4785
|
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
@@ -4497,8 +4795,19 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4497
4795
|
staticData[statBase + STAT_SX] = nx;
|
|
4498
4796
|
staticData[statBase + STAT_SY] = ny;
|
|
4499
4797
|
staticData[statBase + STAT_SZ] = nz;
|
|
4798
|
+
},
|
|
4799
|
+
destroy() {
|
|
4800
|
+
if (destroyed)
|
|
4801
|
+
return;
|
|
4802
|
+
destroyed = true;
|
|
4803
|
+
self.batcher.remove(0, modelHandle.id, slot);
|
|
4804
|
+
self.freeList.free(slot);
|
|
4805
|
+
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4806
|
+
staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
|
|
4807
|
+
self.staticDirty = true;
|
|
4500
4808
|
}
|
|
4501
4809
|
};
|
|
4810
|
+
return handle;
|
|
4502
4811
|
}
|
|
4503
4812
|
addGltfInstance(opts, gltf) {
|
|
4504
4813
|
const childHandles = [];
|
|
@@ -4536,7 +4845,71 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4536
4845
|
} : void 0,
|
|
4537
4846
|
stop: skinnedHandle?.stop ? () => {
|
|
4538
4847
|
skinnedHandle.stop();
|
|
4539
|
-
} : void 0
|
|
4848
|
+
} : void 0,
|
|
4849
|
+
destroy() {
|
|
4850
|
+
for (const h of childHandles)
|
|
4851
|
+
h.destroy();
|
|
4852
|
+
}
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
/**
|
|
4856
|
+
* Spawn a composite prefab by spawning each of its parts at the composed
|
|
4857
|
+
* (instance + offset) transform. The returned handle broadcasts subsequent
|
|
4858
|
+
* `setPosition` / `setRotation` to every child, keeping each child's
|
|
4859
|
+
* baked offset applied on top of the new value.
|
|
4860
|
+
*/
|
|
4861
|
+
addCompositeInstance(opts, composite) {
|
|
4862
|
+
const bucket = this._prefabs;
|
|
4863
|
+
if (!bucket) {
|
|
4864
|
+
throw new Error(
|
|
4865
|
+
`addInstance: composite '${composite.id}' requires the renderer to be constructed with the bucket (\`prefabs\`).`
|
|
4866
|
+
);
|
|
4867
|
+
}
|
|
4868
|
+
const basePos = opts.position ?? [0, 0, 0];
|
|
4869
|
+
const baseRot = opts.rotation ?? [0, 0, 0];
|
|
4870
|
+
const offsets = composite.parts.map((p) => ({
|
|
4871
|
+
px: p.offset?.position?.[0] ?? 0,
|
|
4872
|
+
py: p.offset?.position?.[1] ?? 0,
|
|
4873
|
+
pz: p.offset?.position?.[2] ?? 0,
|
|
4874
|
+
rx: p.offset?.rotation?.[0] ?? 0,
|
|
4875
|
+
ry: p.offset?.rotation?.[1] ?? 0,
|
|
4876
|
+
rz: p.offset?.rotation?.[2] ?? 0
|
|
4877
|
+
}));
|
|
4878
|
+
const childHandles = [];
|
|
4879
|
+
for (let i = 0; i < composite.parts.length; i++) {
|
|
4880
|
+
const part = composite.parts[i];
|
|
4881
|
+
const off = offsets[i];
|
|
4882
|
+
const partPrefab = bucket.get(part.partId);
|
|
4883
|
+
const partOpts = {
|
|
4884
|
+
...opts,
|
|
4885
|
+
model: partPrefab,
|
|
4886
|
+
position: [basePos[0] + off.px, basePos[1] + off.py, basePos[2] + off.pz],
|
|
4887
|
+
rotation: [baseRot[0] + off.rx, baseRot[1] + off.ry, baseRot[2] + off.rz]
|
|
4888
|
+
};
|
|
4889
|
+
childHandles.push(this.addInstance(partOpts));
|
|
4890
|
+
}
|
|
4891
|
+
return {
|
|
4892
|
+
skinned: childHandles.some((h) => h.skinned),
|
|
4893
|
+
setPosition(x, y, z) {
|
|
4894
|
+
for (let i = 0; i < childHandles.length; i++) {
|
|
4895
|
+
const o = offsets[i];
|
|
4896
|
+
childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
|
|
4897
|
+
}
|
|
4898
|
+
},
|
|
4899
|
+
setRotation(x, y, z) {
|
|
4900
|
+
for (let i = 0; i < childHandles.length; i++) {
|
|
4901
|
+
const o = offsets[i];
|
|
4902
|
+
childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
|
|
4903
|
+
}
|
|
4904
|
+
},
|
|
4905
|
+
setScale(x, y, z) {
|
|
4906
|
+
for (const h of childHandles)
|
|
4907
|
+
h.setScale(x, y, z);
|
|
4908
|
+
},
|
|
4909
|
+
destroy() {
|
|
4910
|
+
for (const h of childHandles)
|
|
4911
|
+
h.destroy();
|
|
4912
|
+
}
|
|
4540
4913
|
};
|
|
4541
4914
|
}
|
|
4542
4915
|
addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
|
|
@@ -4550,11 +4923,27 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4550
4923
|
if (linkedSlot !== void 0) {
|
|
4551
4924
|
boneOffset = this.skinnedInstanceBoneOffsets[linkedSlot];
|
|
4552
4925
|
animState = this.skinnedAnimStates[linkedSlot];
|
|
4926
|
+
this.boneOffsetRefcount[boneOffset]++;
|
|
4553
4927
|
} else {
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4928
|
+
const pool = this.freedBoneOffsets.get(skinIndex);
|
|
4929
|
+
if (pool && pool.length > 0) {
|
|
4930
|
+
boneOffset = pool.pop();
|
|
4931
|
+
} else {
|
|
4932
|
+
boneOffset = this.nextBoneOffset + jointCount;
|
|
4933
|
+
this.nextBoneOffset += jointCount * 2;
|
|
4934
|
+
}
|
|
4935
|
+
this.boneOffsetRefcount[boneOffset] = 1;
|
|
4936
|
+
this.boneOffsetSkinIndex[boneOffset] = skinIndex;
|
|
4937
|
+
const restOffsetFloats = boneOffset * 16;
|
|
4938
|
+
const restLengthFloats = jointCount * 16;
|
|
4939
|
+
skinModel.animation.computeRestPose(this.boneMatrixData, restOffsetFloats);
|
|
4940
|
+
this.device.queue.writeBuffer(
|
|
4941
|
+
this.rawBoneMatrixBuffer,
|
|
4942
|
+
restOffsetFloats * 4,
|
|
4943
|
+
this.boneMatrixData.buffer,
|
|
4944
|
+
this.boneMatrixData.byteOffset + restOffsetFloats * 4,
|
|
4945
|
+
restLengthFloats * 4
|
|
4946
|
+
);
|
|
4558
4947
|
animState = skinModel.animation.clipCount > 0 ? skinModel.animation.createState(0, 1, true) : null;
|
|
4559
4948
|
}
|
|
4560
4949
|
this.skinnedInstanceBoneOffsets[slot] = boneOffset;
|
|
@@ -4580,11 +4969,7 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4580
4969
|
this.skinnedStaticData[statBase + SSTAT_CR] = t.cr;
|
|
4581
4970
|
this.skinnedStaticData[statBase + SSTAT_CG] = t.cg;
|
|
4582
4971
|
this.skinnedStaticData[statBase + SSTAT_CB] = t.cb;
|
|
4583
|
-
|
|
4584
|
-
(statBase + SSTAT_BONE_OFFSET) * 4,
|
|
4585
|
-
boneOffset,
|
|
4586
|
-
true
|
|
4587
|
-
);
|
|
4972
|
+
this.skinnedStaticDV.setUint32((statBase + SSTAT_BONE_OFFSET) * 4, boneOffset, true);
|
|
4588
4973
|
this.skinnedStaticDirty = true;
|
|
4589
4974
|
this.skinnedInstanceModelIds[slot] = modelHandle.id;
|
|
4590
4975
|
this.skinnedBatcher.add(0, modelHandle.id, slot);
|
|
@@ -4592,6 +4977,10 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4592
4977
|
const staticData = this.skinnedStaticData;
|
|
4593
4978
|
const animStates = this.skinnedAnimStates;
|
|
4594
4979
|
const animation = skinModel.animation;
|
|
4980
|
+
const self = this;
|
|
4981
|
+
const capturedBoneOffset = boneOffset;
|
|
4982
|
+
const capturedSkinIndex = skinIndex;
|
|
4983
|
+
let destroyed = false;
|
|
4595
4984
|
return {
|
|
4596
4985
|
slot,
|
|
4597
4986
|
modelId: modelHandle.id,
|
|
@@ -4620,6 +5009,25 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4620
5009
|
const state = animStates[slot];
|
|
4621
5010
|
if (state)
|
|
4622
5011
|
animation.stop(state);
|
|
5012
|
+
},
|
|
5013
|
+
destroy() {
|
|
5014
|
+
if (destroyed)
|
|
5015
|
+
return;
|
|
5016
|
+
destroyed = true;
|
|
5017
|
+
self.skinnedBatcher.remove(0, modelHandle.id, slot);
|
|
5018
|
+
self.skinnedFreeList.free(slot);
|
|
5019
|
+
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
5020
|
+
staticData.fill(0, statBase, statBase + SKINNED_STATIC_MESH_FLOATS);
|
|
5021
|
+
animStates[slot] = null;
|
|
5022
|
+
self.skinnedStaticDirty = true;
|
|
5023
|
+
if (--self.boneOffsetRefcount[capturedBoneOffset] === 0) {
|
|
5024
|
+
let pool = self.freedBoneOffsets.get(capturedSkinIndex);
|
|
5025
|
+
if (!pool) {
|
|
5026
|
+
pool = [];
|
|
5027
|
+
self.freedBoneOffsets.set(capturedSkinIndex, pool);
|
|
5028
|
+
}
|
|
5029
|
+
pool.push(capturedBoneOffset);
|
|
5030
|
+
}
|
|
4623
5031
|
}
|
|
4624
5032
|
};
|
|
4625
5033
|
}
|
|
@@ -4705,6 +5113,31 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4705
5113
|
this.animJointLookupOffset = pb.jointLookupOffset;
|
|
4706
5114
|
return true;
|
|
4707
5115
|
}
|
|
5116
|
+
/**
|
|
5117
|
+
* Returns true if a skinned instance's bone-matrix compute should be
|
|
5118
|
+
* dispatched this frame. Combines frustum culling (scaled bounding sphere)
|
|
5119
|
+
* and a configurable distance cull from the camera. Callers pass the
|
|
5120
|
+
* camera position + cullDistSq once outside the loop to avoid re-reading.
|
|
5121
|
+
*/
|
|
5122
|
+
shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq) {
|
|
5123
|
+
const skinModel = model && model.skinIndex >= 0 ? this.skinnedModels[model.skinIndex] : null;
|
|
5124
|
+
const baseRadius = skinModel?.boundingRadius ?? 10;
|
|
5125
|
+
const base = slot * DYNAMIC_MESH_FLOATS;
|
|
5126
|
+
const sBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
5127
|
+
const cx = this.skinnedDynamicData[base + DYN_CURR_PX];
|
|
5128
|
+
const cy = this.skinnedDynamicData[base + DYN_CURR_PY];
|
|
5129
|
+
const cz = this.skinnedDynamicData[base + DYN_CURR_PZ];
|
|
5130
|
+
const sx = Math.abs(this.skinnedStaticData[sBase + SSTAT_SX]);
|
|
5131
|
+
const sy = Math.abs(this.skinnedStaticData[sBase + SSTAT_SY]);
|
|
5132
|
+
const sz = Math.abs(this.skinnedStaticData[sBase + SSTAT_SZ]);
|
|
5133
|
+
const maxScale = sx > sy ? sx > sz ? sx : sz : sy > sz ? sy : sz;
|
|
5134
|
+
if (!this.isInFrustum(cx, cy, cz, baseRadius * maxScale))
|
|
5135
|
+
return false;
|
|
5136
|
+
const dxv = cx - camX, dyv = cy - camY, dzv = cz - camZ;
|
|
5137
|
+
if (dxv * dxv + dyv * dyv + dzv * dzv > cullDistSq)
|
|
5138
|
+
return false;
|
|
5139
|
+
return true;
|
|
5140
|
+
}
|
|
4708
5141
|
updateAnimations(deltaTime) {
|
|
4709
5142
|
this.syncLazyAnimationChanges();
|
|
4710
5143
|
if (this.animComputeNeedsRebuild && this.packedAnimData.clips.length > 0) {
|
|
@@ -4748,6 +5181,9 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4748
5181
|
this.updatedBoneOffsets.fill(0);
|
|
4749
5182
|
let count = 0;
|
|
4750
5183
|
const dv = this.gpuInstDV;
|
|
5184
|
+
const camPos = this.camera.position;
|
|
5185
|
+
const camX = camPos[0], camY = camPos[1], camZ = camPos[2];
|
|
5186
|
+
const cullDistSq = this.animationCullDistanceSq;
|
|
4751
5187
|
for (let slot = 0; slot < this.maxSkinnedInstances; slot++) {
|
|
4752
5188
|
const animState = this.skinnedAnimStates[slot];
|
|
4753
5189
|
if (!animState)
|
|
@@ -4786,6 +5222,8 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4786
5222
|
const modelId = this.skinnedInstanceModelIds[slot];
|
|
4787
5223
|
const model = this.models[modelId];
|
|
4788
5224
|
const skinIdx = model?.skinIndex ?? 0;
|
|
5225
|
+
if (!this.shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq))
|
|
5226
|
+
continue;
|
|
4789
5227
|
const off = count * 32;
|
|
4790
5228
|
dv.setInt32(off, animState.clipId, true);
|
|
4791
5229
|
dv.setFloat32(off + 4, animState.time, true);
|
|
@@ -4830,14 +5268,12 @@ var WebGPU3DRenderer = class extends import_renderer4.Base3DRenderer {
|
|
|
4830
5268
|
}
|
|
4831
5269
|
}
|
|
4832
5270
|
}
|
|
5271
|
+
/**
|
|
5272
|
+
* Free an instance's renderer slot. Equivalent to `handle.destroy()` -
|
|
5273
|
+
* kept as a convenience for direct lookup. Safe to call multiple times.
|
|
5274
|
+
*/
|
|
4833
5275
|
removeInstance(handle) {
|
|
4834
|
-
|
|
4835
|
-
this.freeList.free(handle.slot);
|
|
4836
|
-
const dynBase = handle.slot * DYNAMIC_MESH_FLOATS;
|
|
4837
|
-
const statBase = handle.slot * STATIC_MESH_FLOATS;
|
|
4838
|
-
this.dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4839
|
-
this.staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
|
|
4840
|
-
this.staticDirty = true;
|
|
5276
|
+
handle.destroy();
|
|
4841
5277
|
}
|
|
4842
5278
|
storePreviousState() {
|
|
4843
5279
|
this.camera.storePrevious();
|