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/esm/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
3694
|
-
super(canvas, { ...options,
|
|
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
|
-
|
|
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.
|
|
3735
|
-
this.
|
|
3736
|
-
this.
|
|
3737
|
-
this.
|
|
3738
|
-
this.
|
|
3739
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
3820
|
-
this.staticBuffer = this.root.createBuffer(d.arrayOf(StaticMesh, this.
|
|
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.
|
|
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,6 +4622,9 @@ 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
|
+
if (isPrefab3D(opts.model) && opts.model.type === "composite") {
|
|
4626
|
+
return this.addCompositeInstance(opts, opts.model);
|
|
4627
|
+
}
|
|
4334
4628
|
const modelOrGltf = isPrefab3D(opts.model) ? resolvePrefabHandle(opts.model) : opts.model;
|
|
4335
4629
|
if ("parts" in modelOrGltf) {
|
|
4336
4630
|
return this.addGltfInstance(opts, modelOrGltf);
|
|
@@ -4342,7 +4636,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4342
4636
|
}
|
|
4343
4637
|
const slot = this.freeList.allocate();
|
|
4344
4638
|
if (slot === -1)
|
|
4345
|
-
throw new Error(`Max instances (${this.
|
|
4639
|
+
throw new Error(`Max instances (${this.maxInstances}) reached`);
|
|
4346
4640
|
const dynBase = slot * DYNAMIC_MESH_FLOATS;
|
|
4347
4641
|
const statBase = slot * STATIC_MESH_FLOATS;
|
|
4348
4642
|
const t = resolveTransform(opts);
|
|
@@ -4369,7 +4663,11 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4369
4663
|
this.batcher.add(0, modelHandle.id, slot);
|
|
4370
4664
|
const dynamicData = this.dynamicData;
|
|
4371
4665
|
const staticData = this.staticData;
|
|
4372
|
-
|
|
4666
|
+
const self = this;
|
|
4667
|
+
let destroyed = false;
|
|
4668
|
+
const handle = {
|
|
4669
|
+
slot,
|
|
4670
|
+
modelId: modelHandle.id,
|
|
4373
4671
|
skinned: false,
|
|
4374
4672
|
setPosition(nx, ny, nz) {
|
|
4375
4673
|
dynamicData[dynBase + DYN_CURR_PX] = nx;
|
|
@@ -4385,8 +4683,19 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4385
4683
|
staticData[statBase + STAT_SX] = nx;
|
|
4386
4684
|
staticData[statBase + STAT_SY] = ny;
|
|
4387
4685
|
staticData[statBase + STAT_SZ] = nz;
|
|
4686
|
+
},
|
|
4687
|
+
destroy() {
|
|
4688
|
+
if (destroyed)
|
|
4689
|
+
return;
|
|
4690
|
+
destroyed = true;
|
|
4691
|
+
self.batcher.remove(0, modelHandle.id, slot);
|
|
4692
|
+
self.freeList.free(slot);
|
|
4693
|
+
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4694
|
+
staticData.fill(0, statBase, statBase + STATIC_MESH_FLOATS);
|
|
4695
|
+
self.staticDirty = true;
|
|
4388
4696
|
}
|
|
4389
4697
|
};
|
|
4698
|
+
return handle;
|
|
4390
4699
|
}
|
|
4391
4700
|
addGltfInstance(opts, gltf) {
|
|
4392
4701
|
const childHandles = [];
|
|
@@ -4424,7 +4733,71 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4424
4733
|
} : void 0,
|
|
4425
4734
|
stop: skinnedHandle?.stop ? () => {
|
|
4426
4735
|
skinnedHandle.stop();
|
|
4427
|
-
} : void 0
|
|
4736
|
+
} : void 0,
|
|
4737
|
+
destroy() {
|
|
4738
|
+
for (const h of childHandles)
|
|
4739
|
+
h.destroy();
|
|
4740
|
+
}
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
/**
|
|
4744
|
+
* Spawn a composite prefab by spawning each of its parts at the composed
|
|
4745
|
+
* (instance + offset) transform. The returned handle broadcasts subsequent
|
|
4746
|
+
* `setPosition` / `setRotation` to every child, keeping each child's
|
|
4747
|
+
* baked offset applied on top of the new value.
|
|
4748
|
+
*/
|
|
4749
|
+
addCompositeInstance(opts, composite) {
|
|
4750
|
+
const bucket = this._prefabs;
|
|
4751
|
+
if (!bucket) {
|
|
4752
|
+
throw new Error(
|
|
4753
|
+
`addInstance: composite '${composite.id}' requires the renderer to be constructed with the bucket (\`prefabs\`).`
|
|
4754
|
+
);
|
|
4755
|
+
}
|
|
4756
|
+
const basePos = opts.position ?? [0, 0, 0];
|
|
4757
|
+
const baseRot = opts.rotation ?? [0, 0, 0];
|
|
4758
|
+
const offsets = composite.parts.map((p) => ({
|
|
4759
|
+
px: p.offset?.position?.[0] ?? 0,
|
|
4760
|
+
py: p.offset?.position?.[1] ?? 0,
|
|
4761
|
+
pz: p.offset?.position?.[2] ?? 0,
|
|
4762
|
+
rx: p.offset?.rotation?.[0] ?? 0,
|
|
4763
|
+
ry: p.offset?.rotation?.[1] ?? 0,
|
|
4764
|
+
rz: p.offset?.rotation?.[2] ?? 0
|
|
4765
|
+
}));
|
|
4766
|
+
const childHandles = [];
|
|
4767
|
+
for (let i = 0; i < composite.parts.length; i++) {
|
|
4768
|
+
const part = composite.parts[i];
|
|
4769
|
+
const off = offsets[i];
|
|
4770
|
+
const partPrefab = bucket.get(part.partId);
|
|
4771
|
+
const partOpts = {
|
|
4772
|
+
...opts,
|
|
4773
|
+
model: partPrefab,
|
|
4774
|
+
position: [basePos[0] + off.px, basePos[1] + off.py, basePos[2] + off.pz],
|
|
4775
|
+
rotation: [baseRot[0] + off.rx, baseRot[1] + off.ry, baseRot[2] + off.rz]
|
|
4776
|
+
};
|
|
4777
|
+
childHandles.push(this.addInstance(partOpts));
|
|
4778
|
+
}
|
|
4779
|
+
return {
|
|
4780
|
+
skinned: childHandles.some((h) => h.skinned),
|
|
4781
|
+
setPosition(x, y, z) {
|
|
4782
|
+
for (let i = 0; i < childHandles.length; i++) {
|
|
4783
|
+
const o = offsets[i];
|
|
4784
|
+
childHandles[i].setPosition(x + o.px, y + o.py, z + o.pz);
|
|
4785
|
+
}
|
|
4786
|
+
},
|
|
4787
|
+
setRotation(x, y, z) {
|
|
4788
|
+
for (let i = 0; i < childHandles.length; i++) {
|
|
4789
|
+
const o = offsets[i];
|
|
4790
|
+
childHandles[i].setRotation(x + o.rx, y + o.ry, z + o.rz);
|
|
4791
|
+
}
|
|
4792
|
+
},
|
|
4793
|
+
setScale(x, y, z) {
|
|
4794
|
+
for (const h of childHandles)
|
|
4795
|
+
h.setScale(x, y, z);
|
|
4796
|
+
},
|
|
4797
|
+
destroy() {
|
|
4798
|
+
for (const h of childHandles)
|
|
4799
|
+
h.destroy();
|
|
4800
|
+
}
|
|
4428
4801
|
};
|
|
4429
4802
|
}
|
|
4430
4803
|
addSkinnedInstance(opts, modelHandle, skinIndex, linkedSlot) {
|
|
@@ -4438,11 +4811,27 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4438
4811
|
if (linkedSlot !== void 0) {
|
|
4439
4812
|
boneOffset = this.skinnedInstanceBoneOffsets[linkedSlot];
|
|
4440
4813
|
animState = this.skinnedAnimStates[linkedSlot];
|
|
4814
|
+
this.boneOffsetRefcount[boneOffset]++;
|
|
4441
4815
|
} else {
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4816
|
+
const pool = this.freedBoneOffsets.get(skinIndex);
|
|
4817
|
+
if (pool && pool.length > 0) {
|
|
4818
|
+
boneOffset = pool.pop();
|
|
4819
|
+
} else {
|
|
4820
|
+
boneOffset = this.nextBoneOffset + jointCount;
|
|
4821
|
+
this.nextBoneOffset += jointCount * 2;
|
|
4822
|
+
}
|
|
4823
|
+
this.boneOffsetRefcount[boneOffset] = 1;
|
|
4824
|
+
this.boneOffsetSkinIndex[boneOffset] = skinIndex;
|
|
4825
|
+
const restOffsetFloats = boneOffset * 16;
|
|
4826
|
+
const restLengthFloats = jointCount * 16;
|
|
4827
|
+
skinModel.animation.computeRestPose(this.boneMatrixData, restOffsetFloats);
|
|
4828
|
+
this.device.queue.writeBuffer(
|
|
4829
|
+
this.rawBoneMatrixBuffer,
|
|
4830
|
+
restOffsetFloats * 4,
|
|
4831
|
+
this.boneMatrixData.buffer,
|
|
4832
|
+
this.boneMatrixData.byteOffset + restOffsetFloats * 4,
|
|
4833
|
+
restLengthFloats * 4
|
|
4834
|
+
);
|
|
4446
4835
|
animState = skinModel.animation.clipCount > 0 ? skinModel.animation.createState(0, 1, true) : null;
|
|
4447
4836
|
}
|
|
4448
4837
|
this.skinnedInstanceBoneOffsets[slot] = boneOffset;
|
|
@@ -4468,11 +4857,7 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4468
4857
|
this.skinnedStaticData[statBase + SSTAT_CR] = t.cr;
|
|
4469
4858
|
this.skinnedStaticData[statBase + SSTAT_CG] = t.cg;
|
|
4470
4859
|
this.skinnedStaticData[statBase + SSTAT_CB] = t.cb;
|
|
4471
|
-
|
|
4472
|
-
(statBase + SSTAT_BONE_OFFSET) * 4,
|
|
4473
|
-
boneOffset,
|
|
4474
|
-
true
|
|
4475
|
-
);
|
|
4860
|
+
this.skinnedStaticDV.setUint32((statBase + SSTAT_BONE_OFFSET) * 4, boneOffset, true);
|
|
4476
4861
|
this.skinnedStaticDirty = true;
|
|
4477
4862
|
this.skinnedInstanceModelIds[slot] = modelHandle.id;
|
|
4478
4863
|
this.skinnedBatcher.add(0, modelHandle.id, slot);
|
|
@@ -4480,6 +4865,10 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4480
4865
|
const staticData = this.skinnedStaticData;
|
|
4481
4866
|
const animStates = this.skinnedAnimStates;
|
|
4482
4867
|
const animation = skinModel.animation;
|
|
4868
|
+
const self = this;
|
|
4869
|
+
const capturedBoneOffset = boneOffset;
|
|
4870
|
+
const capturedSkinIndex = skinIndex;
|
|
4871
|
+
let destroyed = false;
|
|
4483
4872
|
return {
|
|
4484
4873
|
slot,
|
|
4485
4874
|
modelId: modelHandle.id,
|
|
@@ -4508,6 +4897,25 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4508
4897
|
const state = animStates[slot];
|
|
4509
4898
|
if (state)
|
|
4510
4899
|
animation.stop(state);
|
|
4900
|
+
},
|
|
4901
|
+
destroy() {
|
|
4902
|
+
if (destroyed)
|
|
4903
|
+
return;
|
|
4904
|
+
destroyed = true;
|
|
4905
|
+
self.skinnedBatcher.remove(0, modelHandle.id, slot);
|
|
4906
|
+
self.skinnedFreeList.free(slot);
|
|
4907
|
+
dynamicData.fill(0, dynBase, dynBase + DYNAMIC_MESH_FLOATS);
|
|
4908
|
+
staticData.fill(0, statBase, statBase + SKINNED_STATIC_MESH_FLOATS);
|
|
4909
|
+
animStates[slot] = null;
|
|
4910
|
+
self.skinnedStaticDirty = true;
|
|
4911
|
+
if (--self.boneOffsetRefcount[capturedBoneOffset] === 0) {
|
|
4912
|
+
let pool = self.freedBoneOffsets.get(capturedSkinIndex);
|
|
4913
|
+
if (!pool) {
|
|
4914
|
+
pool = [];
|
|
4915
|
+
self.freedBoneOffsets.set(capturedSkinIndex, pool);
|
|
4916
|
+
}
|
|
4917
|
+
pool.push(capturedBoneOffset);
|
|
4918
|
+
}
|
|
4511
4919
|
}
|
|
4512
4920
|
};
|
|
4513
4921
|
}
|
|
@@ -4593,6 +5001,31 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4593
5001
|
this.animJointLookupOffset = pb.jointLookupOffset;
|
|
4594
5002
|
return true;
|
|
4595
5003
|
}
|
|
5004
|
+
/**
|
|
5005
|
+
* Returns true if a skinned instance's bone-matrix compute should be
|
|
5006
|
+
* dispatched this frame. Combines frustum culling (scaled bounding sphere)
|
|
5007
|
+
* and a configurable distance cull from the camera. Callers pass the
|
|
5008
|
+
* camera position + cullDistSq once outside the loop to avoid re-reading.
|
|
5009
|
+
*/
|
|
5010
|
+
shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq) {
|
|
5011
|
+
const skinModel = model && model.skinIndex >= 0 ? this.skinnedModels[model.skinIndex] : null;
|
|
5012
|
+
const baseRadius = skinModel?.boundingRadius ?? 10;
|
|
5013
|
+
const base = slot * DYNAMIC_MESH_FLOATS;
|
|
5014
|
+
const sBase = slot * SKINNED_STATIC_MESH_FLOATS;
|
|
5015
|
+
const cx = this.skinnedDynamicData[base + DYN_CURR_PX];
|
|
5016
|
+
const cy = this.skinnedDynamicData[base + DYN_CURR_PY];
|
|
5017
|
+
const cz = this.skinnedDynamicData[base + DYN_CURR_PZ];
|
|
5018
|
+
const sx = Math.abs(this.skinnedStaticData[sBase + SSTAT_SX]);
|
|
5019
|
+
const sy = Math.abs(this.skinnedStaticData[sBase + SSTAT_SY]);
|
|
5020
|
+
const sz = Math.abs(this.skinnedStaticData[sBase + SSTAT_SZ]);
|
|
5021
|
+
const maxScale = sx > sy ? sx > sz ? sx : sz : sy > sz ? sy : sz;
|
|
5022
|
+
if (!this.isInFrustum(cx, cy, cz, baseRadius * maxScale))
|
|
5023
|
+
return false;
|
|
5024
|
+
const dxv = cx - camX, dyv = cy - camY, dzv = cz - camZ;
|
|
5025
|
+
if (dxv * dxv + dyv * dyv + dzv * dzv > cullDistSq)
|
|
5026
|
+
return false;
|
|
5027
|
+
return true;
|
|
5028
|
+
}
|
|
4596
5029
|
updateAnimations(deltaTime) {
|
|
4597
5030
|
this.syncLazyAnimationChanges();
|
|
4598
5031
|
if (this.animComputeNeedsRebuild && this.packedAnimData.clips.length > 0) {
|
|
@@ -4636,6 +5069,9 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4636
5069
|
this.updatedBoneOffsets.fill(0);
|
|
4637
5070
|
let count = 0;
|
|
4638
5071
|
const dv = this.gpuInstDV;
|
|
5072
|
+
const camPos = this.camera.position;
|
|
5073
|
+
const camX = camPos[0], camY = camPos[1], camZ = camPos[2];
|
|
5074
|
+
const cullDistSq = this.animationCullDistanceSq;
|
|
4639
5075
|
for (let slot = 0; slot < this.maxSkinnedInstances; slot++) {
|
|
4640
5076
|
const animState = this.skinnedAnimStates[slot];
|
|
4641
5077
|
if (!animState)
|
|
@@ -4674,6 +5110,8 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4674
5110
|
const modelId = this.skinnedInstanceModelIds[slot];
|
|
4675
5111
|
const model = this.models[modelId];
|
|
4676
5112
|
const skinIdx = model?.skinIndex ?? 0;
|
|
5113
|
+
if (!this.shouldDispatchSkinning(slot, model, camX, camY, camZ, cullDistSq))
|
|
5114
|
+
continue;
|
|
4677
5115
|
const off = count * 32;
|
|
4678
5116
|
dv.setInt32(off, animState.clipId, true);
|
|
4679
5117
|
dv.setFloat32(off + 4, animState.time, true);
|
|
@@ -4718,14 +5156,12 @@ var WebGPU3DRenderer = class extends Base3DRenderer {
|
|
|
4718
5156
|
}
|
|
4719
5157
|
}
|
|
4720
5158
|
}
|
|
5159
|
+
/**
|
|
5160
|
+
* Free an instance's renderer slot. Equivalent to `handle.destroy()` -
|
|
5161
|
+
* kept as a convenience for direct lookup. Safe to call multiple times.
|
|
5162
|
+
*/
|
|
4721
5163
|
removeInstance(handle) {
|
|
4722
|
-
|
|
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;
|
|
5164
|
+
handle.destroy();
|
|
4729
5165
|
}
|
|
4730
5166
|
storePreviousState() {
|
|
4731
5167
|
this.camera.storePrevious();
|