@viggle/splat-engine-three 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -2
  3. package/dist/chunks/chunk-MM6DJU6S.js +7801 -0
  4. package/dist/chunks/chunk-MM6DJU6S.js.map +7 -0
  5. package/dist/index.js +52 -0
  6. package/dist/index.js.map +7 -0
  7. package/dist/types/anim/animation-conversion.d.ts +25 -0
  8. package/dist/types/anim/bind-pose.d.ts +15 -0
  9. package/dist/types/anim/keyframe-splines.d.ts +59 -0
  10. package/dist/types/anim/rest-relative.d.ts +32 -0
  11. package/dist/types/anim/spline.d.ts +14 -0
  12. package/dist/types/animation.d.ts +216 -0
  13. package/dist/types/armature.d.ts +524 -0
  14. package/dist/types/character.d.ts +540 -0
  15. package/dist/types/engine.d.ts +166 -0
  16. package/dist/types/environment.d.ts +53 -0
  17. package/dist/types/index.d.ts +37 -0
  18. package/dist/types/lighting/color.d.ts +13 -0
  19. package/dist/types/lighting/point-light.d.ts +33 -0
  20. package/dist/types/lighting/sky-light.d.ts +22 -0
  21. package/dist/types/lighting/sun-light.d.ts +34 -0
  22. package/dist/types/loader/animation/animation-glb.d.ts +117 -0
  23. package/dist/types/loader/character/index.d.ts +33 -0
  24. package/dist/types/loader/character/legacy/index.d.ts +34 -0
  25. package/dist/types/loader/character/legacy/interleaved-splat.d.ts +101 -0
  26. package/dist/types/loader/character/legacy/planar-splat.d.ts +139 -0
  27. package/dist/types/loader/character/load.d.ts +33 -0
  28. package/dist/types/loader/character/types.d.ts +129 -0
  29. package/dist/types/loader/character/v1/decode.d.ts +32 -0
  30. package/dist/types/loader/environment/ply.d.ts +16 -0
  31. package/dist/types/loader/shared/bin-cache.d.ts +19 -0
  32. package/dist/types/loader/skeleton/index.d.ts +19 -0
  33. package/dist/types/loader/skeleton/skeleton-data-441.d.ts +5 -0
  34. package/dist/types/loader/skeleton/skeleton-data.d.ts +6 -0
  35. package/dist/types/math.d.ts +82 -0
  36. package/dist/types/skeletal/animation-player.d.ts +185 -0
  37. package/dist/types/skeletal/bone-mask.d.ts +27 -0
  38. package/dist/types/skeletal/bone-stretch.d.ts +65 -0
  39. package/dist/types/skeletal/pose.d.ts +27 -0
  40. package/dist/types/skeletal/skeleton.d.ts +57 -0
  41. package/dist/types/skeletal/splat-skinning.d.ts +69 -0
  42. package/dist/types/splat/backend.d.ts +57 -0
  43. package/dist/types/splat/bone-binding.d.ts +38 -0
  44. package/dist/types/splat/depth-sort.d.ts +58 -0
  45. package/dist/types/splat/packed-splats.d.ts +36 -0
  46. package/dist/types/splat/splat-mesh.d.ts +71 -0
  47. package/dist/types/splat/splat.d.ts +186 -0
  48. package/dist/types/splat/transform-palette.d.ts +42 -0
  49. package/dist/types/splat/webgl/backend.d.ts +17 -0
  50. package/dist/types/splat/webgl/shaders.d.ts +2 -0
  51. package/dist/types/splat/webgl/splat-mesh.d.ts +52 -0
  52. package/dist/types/splat/webgpu/backend.d.ts +25 -0
  53. package/dist/types/splat/webgpu/lighting.d.ts +34 -0
  54. package/dist/types/splat/webgpu/radix-sort.d.ts +46 -0
  55. package/dist/types/splat/webgpu/splat-mesh.d.ts +71 -0
  56. package/dist/types/three/loaders.d.ts +77 -0
  57. package/dist/types/three/splat-character.d.ts +192 -0
  58. package/dist/types/types.d.ts +30 -0
  59. package/dist/types/webgpu.d.ts +5 -0
  60. package/dist/webgpu.js +813 -0
  61. package/dist/webgpu.js.map +7 -0
  62. package/package.json +46 -4
package/dist/webgpu.js ADDED
@@ -0,0 +1,813 @@
1
+ import {
2
+ Animation,
3
+ Armature,
4
+ Character,
5
+ CharacterBundleError,
6
+ EngineDisposedError,
7
+ Environment,
8
+ PointLight,
9
+ SkyLight,
10
+ Splat,
11
+ SplatCharacter,
12
+ SplatClipLoader,
13
+ SplatEngine,
14
+ SplatMesh,
15
+ SplatPLYLoader,
16
+ SunLight,
17
+ VSplatLoader,
18
+ WebGLSplatBackend,
19
+ decodeCharacterBundle,
20
+ loadCharacterBundle,
21
+ loadCharacterBundleFromBytes,
22
+ makeRegionMask,
23
+ packSplats,
24
+ parseGaussianPly,
25
+ registerWebGPUBackend
26
+ } from "./chunks/chunk-MM6DJU6S.js";
27
+
28
+ // src/splat/webgpu/lighting.ts
29
+ import * as TSL from "three/tsl";
30
+ import { Vector4 } from "three/webgpu";
31
+ var { Fn, If, Loop, float, int, length, uniform, uniformArray } = TSL;
32
+ var MAX_POINT_LIGHTS = 16;
33
+ var WebGPULighting = class {
34
+ _engine;
35
+ _sunColor;
36
+ _sunIntensity;
37
+ _ambient;
38
+ _pointCount;
39
+ _pointPosRange;
40
+ _pointColor;
41
+ _pointPosRangeNode;
42
+ _pointColorNode;
43
+ _version = -1;
44
+ constructor(engine) {
45
+ this._engine = engine;
46
+ this._sunColor = uniform(engine.lightingUniforms.uSunColor.value);
47
+ this._ambient = uniform(engine.lightingUniforms.uAmbientColor.value);
48
+ this._sunIntensity = uniform(0);
49
+ this._pointCount = uniform(0, "int");
50
+ this._pointPosRange = Array.from({ length: MAX_POINT_LIGHTS }, () => new Vector4());
51
+ this._pointColor = Array.from({ length: MAX_POINT_LIGHTS }, () => new Vector4());
52
+ this._pointPosRangeNode = uniformArray(this._pointPosRange, "vec4");
53
+ this._pointColorNode = uniformArray(this._pointColor, "vec4");
54
+ }
55
+ /**
56
+ * Refresh the uniforms from the engine's lighting registry.
57
+ * @returns A number that changes whenever the lighting does.
58
+ */
59
+ sync() {
60
+ const engine = this._engine;
61
+ engine.syncLighting();
62
+ if (engine.lightingVersion === this._version) return this._version;
63
+ this._version = engine.lightingVersion;
64
+ this._sunIntensity.value = engine.lightingUniforms.uSunIntensity.value;
65
+ let count = 0;
66
+ for (const p of engine.lighting.points) {
67
+ if (!p.enabled) continue;
68
+ if (count === MAX_POINT_LIGHTS) break;
69
+ this._pointPosRange[count].set(p.position.x, p.position.y, p.position.z, p.range);
70
+ this._pointColor[count].set(p.color.r, p.color.g, p.color.b, p.intensity);
71
+ count++;
72
+ }
73
+ this._pointCount.value = count;
74
+ return this._version;
75
+ }
76
+ /**
77
+ * The multiplier applied to a splat's baked colour: ambient, plus sun, plus
78
+ * range-attenuated point lights. With no lights it is exactly 1.
79
+ */
80
+ litFactor(worldCenter) {
81
+ return Fn(() => {
82
+ const lit = this._ambient.add(this._sunColor.mul(this._sunIntensity)).toVar();
83
+ Loop({ start: int(0), end: this._pointCount, type: "int", condition: "<" }, ({ i }) => {
84
+ const posRange = this._pointPosRangeNode.element(i);
85
+ const colorInt = this._pointColorNode.element(i);
86
+ const dist = length(posRange.xyz.sub(worldCenter)).toVar();
87
+ If(dist.lessThan(posRange.w), () => {
88
+ const t = dist.div(posRange.w);
89
+ const atten = float(1).sub(t.mul(t)).toVar();
90
+ lit.addAssign(colorInt.xyz.mul(colorInt.w).mul(atten.mul(atten)));
91
+ });
92
+ });
93
+ return lit;
94
+ })();
95
+ }
96
+ };
97
+
98
+ // src/splat/webgpu/splat-mesh.ts
99
+ import * as TSL3 from "three/tsl";
100
+ import {
101
+ BufferAttribute,
102
+ DoubleSide,
103
+ InstancedBufferGeometry,
104
+ Matrix3,
105
+ Matrix4,
106
+ NodeMaterial,
107
+ Quaternion,
108
+ SRGBColorSpace,
109
+ Sphere,
110
+ StorageBufferAttribute as StorageBufferAttribute2,
111
+ Vector2,
112
+ Vector3,
113
+ Vector4 as Vector42
114
+ } from "three/webgpu";
115
+
116
+ // src/splat/webgpu/radix-sort.ts
117
+ import * as TSL2 from "three/tsl";
118
+ import { StorageBufferAttribute } from "three/webgpu";
119
+ var { Fn: Fn2, If: If2, Loop: Loop2, atomicAdd, atomicStore, instanceIndex, storage, uint } = TSL2;
120
+ var SORT_KEY_BITS = 16;
121
+ var RADIX = 256;
122
+ var BLOCK = 256;
123
+ var GpuRadixSort = class {
124
+ count;
125
+ /** Write each element's key here (`uint`, low 16 bits significant) before {@link GpuRadixSort.sortNodes} run. */
126
+ keysWrite;
127
+ /** Element indices in ascending key order, valid after {@link GpuRadixSort.sortNodes} have run. */
128
+ orderRead;
129
+ /** Run before the pass that writes keys: clears both digit histograms. */
130
+ resetNode;
131
+ /** Run, in order, after the pass that writes keys. */
132
+ sortNodes;
133
+ _firstHistogram;
134
+ constructor(count) {
135
+ this.count = count;
136
+ const blocks = Math.max(1, Math.ceil(count / BLOCK));
137
+ const histogramSize = blocks * RADIX;
138
+ const keysAttribute = new StorageBufferAttribute(new Uint32Array(count), 1);
139
+ const histogramAttributes = [
140
+ new StorageBufferAttribute(new Uint32Array(histogramSize), 1),
141
+ new StorageBufferAttribute(new Uint32Array(histogramSize), 1)
142
+ ];
143
+ const digitAttribute = new StorageBufferAttribute(new Uint32Array(RADIX), 1);
144
+ const halfSortedAttribute = new StorageBufferAttribute(new Uint32Array(count), 1);
145
+ const orderAttribute = new StorageBufferAttribute(new Uint32Array(count), 1);
146
+ this.keysWrite = storage(keysAttribute, "uint", count);
147
+ const keys = storage(keysAttribute, "uint", count).toReadOnly();
148
+ this.orderRead = storage(orderAttribute, "uint", count).toReadOnly();
149
+ this._firstHistogram = storage(histogramAttributes[0], "uint", histogramSize).toAtomic();
150
+ const secondHistogramAtomic = storage(histogramAttributes[1], "uint", histogramSize).toAtomic();
151
+ const digits = storage(digitAttribute, "uint", RADIX);
152
+ const halfSortedWrite = storage(halfSortedAttribute, "uint", count);
153
+ const halfSorted = storage(halfSortedAttribute, "uint", count).toReadOnly();
154
+ const orderWrite = storage(orderAttribute, "uint", count);
155
+ this.resetNode = Fn2(() => {
156
+ atomicStore(this._firstHistogram.element(instanceIndex), uint(0));
157
+ atomicStore(secondHistogramAtomic.element(instanceIndex), uint(0));
158
+ })().compute(histogramSize, [256]).setName("SplatSortReset");
159
+ const prefixBlocks = (pass) => {
160
+ const histogram = storage(histogramAttributes[pass], "uint", histogramSize);
161
+ return Fn2(() => {
162
+ const sum = uint(0).toVar();
163
+ Loop2({ start: 0, end: blocks, type: "uint", name: "b", condition: "<" }, ({ b }) => {
164
+ const slot = b.mul(RADIX).add(instanceIndex).toVar();
165
+ const n = histogram.element(slot).toVar();
166
+ histogram.element(slot).assign(sum);
167
+ sum.addAssign(n);
168
+ });
169
+ digits.element(instanceIndex).assign(sum);
170
+ })().compute(RADIX, [64]).setName(`SplatSortPrefixBlocks${pass}`);
171
+ };
172
+ const prefixDigits = (pass) => Fn2(() => {
173
+ const sum = uint(0).toVar();
174
+ Loop2({ start: 0, end: RADIX, type: "uint", name: "d", condition: "<" }, ({ d }) => {
175
+ const n = digits.element(d).toVar();
176
+ digits.element(d).assign(sum);
177
+ sum.addAssign(n);
178
+ });
179
+ })().compute(1).setName(`SplatSortPrefixDigits${pass}`);
180
+ const scatter = (pass, input, output) => {
181
+ const histogram = storage(histogramAttributes[pass], "uint", histogramSize);
182
+ const digitBases = storage(digitAttribute, "uint", RADIX).toReadOnly();
183
+ return Fn2(() => {
184
+ const first = instanceIndex.mul(BLOCK).toVar();
185
+ Loop2({ start: 0, end: BLOCK, type: "uint", name: "j", condition: "<" }, ({ j }) => {
186
+ const position = first.add(j).toVar();
187
+ If2(position.lessThan(uint(count)), () => {
188
+ const element = (input ? input.element(position) : position).toVar();
189
+ const key = keys.element(element).toVar();
190
+ const digit = (pass === 0 ? key.bitAnd(uint(RADIX - 1)) : key.shiftRight(uint(8)).bitAnd(uint(RADIX - 1))).toVar();
191
+ const slot = instanceIndex.mul(RADIX).add(digit).toVar();
192
+ const within = histogram.element(slot).toVar();
193
+ histogram.element(slot).assign(within.add(1));
194
+ output.element(digitBases.element(digit).add(within)).assign(element);
195
+ });
196
+ });
197
+ })().compute(blocks, [64]).setName(`SplatSortScatter${pass}`);
198
+ };
199
+ const secondHistogramNode = Fn2(() => {
200
+ const element = halfSorted.element(instanceIndex).toVar();
201
+ const digit = keys.element(element).shiftRight(uint(8)).bitAnd(uint(RADIX - 1)).toVar();
202
+ const block = instanceIndex.div(uint(BLOCK));
203
+ atomicAdd(secondHistogramAtomic.element(block.mul(RADIX).add(digit)), uint(1));
204
+ })().compute(count, [256]).setName("SplatSortHistogram1");
205
+ this.sortNodes = [
206
+ prefixBlocks(0),
207
+ prefixDigits(0),
208
+ scatter(0, null, halfSortedWrite),
209
+ secondHistogramNode,
210
+ prefixBlocks(1),
211
+ prefixDigits(1),
212
+ scatter(1, halfSorted, orderWrite)
213
+ ];
214
+ }
215
+ /**
216
+ * Count `key`'s low digit for the element at `index`. Call from inside the
217
+ * compute pass that writes the keys.
218
+ */
219
+ countFirstDigit(index, key) {
220
+ const block = index.div(uint(BLOCK));
221
+ const digit = key.bitAnd(uint(RADIX - 1));
222
+ atomicAdd(this._firstHistogram.element(block.mul(RADIX).add(digit)), uint(1));
223
+ }
224
+ };
225
+
226
+ // src/splat/webgpu/splat-mesh.ts
227
+ var {
228
+ Discard,
229
+ Fn: Fn3,
230
+ If: If3,
231
+ Loop: Loop3,
232
+ clamp,
233
+ dot,
234
+ exp,
235
+ float: float2,
236
+ instanceIndex: instanceIndex2,
237
+ length: length2,
238
+ log,
239
+ max,
240
+ min,
241
+ mix,
242
+ normalize,
243
+ positionGeometry,
244
+ select,
245
+ sqrt,
246
+ storage: storage2,
247
+ uint: uint2,
248
+ uintBitsToFloat,
249
+ uniform: uniform2,
250
+ uniformArray: uniformArray2,
251
+ unpackHalf2x16,
252
+ uvec4,
253
+ varyingProperty,
254
+ vec2,
255
+ vec3,
256
+ vec4
257
+ } = TSL3;
258
+ var SORT_BINS = 1 << SORT_KEY_BITS;
259
+ var WORKGROUP_SIZE = 256;
260
+ var MAX_VARIANT_GROUPS = 64;
261
+ var EXP4 = Math.exp(-4);
262
+ var INV_EXP4 = 1 / (1 - EXP4);
263
+ var _position = new Vector3();
264
+ var _quaternion = new Quaternion();
265
+ var _scale = new Vector3();
266
+ var _viewport = new Vector42();
267
+ var _sphere = new Sphere();
268
+ var _viewCenter = new Vector3();
269
+ var _direction = new Vector3();
270
+ var INPUT_SLOTS = 80;
271
+ var WebGPUSplatImpl = class {
272
+ geometry;
273
+ material;
274
+ /** Skinning for the sort happens in compute; the CPU never touches a splat. */
275
+ needsCpuCenters = false;
276
+ _host;
277
+ _splat;
278
+ _lighting;
279
+ _paletteArray;
280
+ _paletteAttribute;
281
+ _paletteVersion = -1;
282
+ _boneAttribute = null;
283
+ _groupAttribute = null;
284
+ _variantMask = new Array(MAX_VARIANT_GROUPS).fill(1);
285
+ /** [sort reset, project] - everything that depends on the camera or the pose. */
286
+ _projectNodes = [];
287
+ /** The sort proper - needed only when the depth ORDER may have changed. */
288
+ _sortNodes = [];
289
+ // What the last compute run saw. Compared field by field each frame so an
290
+ // unchanged splat costs a draw call and nothing else.
291
+ _lastInputs = new Float64Array(INPUT_SLOTS).fill(NaN);
292
+ _inputs = new Float64Array(INPUT_SLOTS);
293
+ _lastSortDirection = new Vector3(NaN, NaN, NaN);
294
+ _buildId = 0;
295
+ _sortedBuildId = -1;
296
+ _allNodes = null;
297
+ // Per-drawable uniforms, refreshed in onBeforeRender.
298
+ _u = {
299
+ model: uniform2(new Matrix4()),
300
+ modelLinear: uniform2(new Matrix3()),
301
+ modelRotation: uniform2(new Vector42(0, 0, 0, 1)),
302
+ modelScale: uniform2(new Vector3(1, 1, 1)),
303
+ view: uniform2(new Matrix4()),
304
+ viewRow0: uniform2(new Vector3()),
305
+ viewRow1: uniform2(new Vector3()),
306
+ viewRow2: uniform2(new Vector3()),
307
+ projection: uniform2(new Matrix4()),
308
+ viewport: uniform2(new Vector2(1, 1)),
309
+ minPixelSize: uniform2(0),
310
+ // x: far depth, y: 1 / (far - near), along the view axis.
311
+ sortRange: uniform2(new Vector2(1, 1)),
312
+ tint: uniform2(new Vector42(1, 1, 1, 0)),
313
+ grade: uniform2(new Vector2(1, 1)),
314
+ outputLinear: uniform2(0),
315
+ variantMask: uniformArray2(this._variantMask, "float")
316
+ };
317
+ constructor(host, splat, lighting) {
318
+ const geometry = new InstancedBufferGeometry();
319
+ geometry.setAttribute(
320
+ "position",
321
+ new BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3)
322
+ );
323
+ geometry.setIndex([0, 1, 2, 0, 2, 3]);
324
+ geometry.instanceCount = splat.numSplats;
325
+ geometry.boundingSphere = new Sphere();
326
+ const material = new NodeMaterial();
327
+ material.name = "SplatMaterial";
328
+ material.transparent = true;
329
+ material.depthTest = true;
330
+ material.depthWrite = false;
331
+ material.side = DoubleSide;
332
+ material.forceSinglePass = true;
333
+ material.premultipliedAlpha = true;
334
+ material.fog = false;
335
+ this.geometry = geometry;
336
+ this.material = material;
337
+ this._host = host;
338
+ this._splat = splat;
339
+ this._lighting = lighting;
340
+ this._build();
341
+ this.syncBounds();
342
+ }
343
+ /** The sort runs in the same frame as the draw; the order is never stale. */
344
+ get sortPending() {
345
+ return false;
346
+ }
347
+ /** Force the next frame to re-project and re-sort even if nothing appears to have changed. */
348
+ requestSort() {
349
+ this._lastInputs[0] = NaN;
350
+ this._lastSortDirection.set(NaN, NaN, NaN);
351
+ }
352
+ syncDefines() {
353
+ this._build();
354
+ }
355
+ syncBounds() {
356
+ this._splat.localBounds.getBoundingSphere(this.geometry.boundingSphere);
357
+ }
358
+ syncBoneData() {
359
+ const attribute = this._boneAttribute;
360
+ if (!attribute) return;
361
+ packBoneInfluences(this._splat, attribute.array);
362
+ attribute.needsUpdate = true;
363
+ this.requestSort();
364
+ }
365
+ syncVariantGroups() {
366
+ const attribute = this._groupAttribute;
367
+ const ids = this._splat.groupIds;
368
+ if (!attribute || !ids) return;
369
+ attribute.array.set(ids);
370
+ attribute.needsUpdate = true;
371
+ this.requestSort();
372
+ }
373
+ /**
374
+ * (Re)create the storage buffers, compute nodes and material nodes. Runs at
375
+ * construction, and again when a feature toggle changes the shader or the
376
+ * palette outgrows its buffer.
377
+ */
378
+ _build() {
379
+ const splat = this._splat;
380
+ const count = splat.numSplats;
381
+ const packed = splat.packed;
382
+ const u = this._u;
383
+ const lighting = this._lighting;
384
+ const skinned = !!splat.splatWeights;
385
+ const splatData = new Uint32Array(count * 8);
386
+ const srcA = packed.transformA;
387
+ const srcB = new Uint32Array(packed.transformB.buffer, packed.transformB.byteOffset, count * 2);
388
+ const srcC = new Uint32Array(packed.color.buffer, packed.color.byteOffset, count * 2);
389
+ for (let i = 0; i < count; i++) {
390
+ const o = i * 8;
391
+ splatData[o] = srcA[i * 4];
392
+ splatData[o + 1] = srcA[i * 4 + 1];
393
+ splatData[o + 2] = srcA[i * 4 + 2];
394
+ splatData[o + 3] = srcA[i * 4 + 3];
395
+ splatData[o + 4] = srcB[i * 2];
396
+ splatData[o + 5] = srcB[i * 2 + 1];
397
+ splatData[o + 6] = srcC[i * 2];
398
+ splatData[o + 7] = srcC[i * 2 + 1];
399
+ }
400
+ const splats = storage2(new StorageBufferAttribute2(splatData, 4), "uvec4", count * 2).toReadOnly();
401
+ this._paletteArray = splat.transformPalette.data;
402
+ this._paletteAttribute = new StorageBufferAttribute2(this._paletteArray, 4);
403
+ this._paletteVersion = -1;
404
+ const palette = storage2(this._paletteAttribute, "vec4", this._paletteArray.length / 4).toReadOnly();
405
+ let bones = null;
406
+ this._boneAttribute = null;
407
+ if (skinned) {
408
+ this._boneAttribute = new StorageBufferAttribute2(packBoneInfluences(splat, new Uint32Array(count * 4)), 4);
409
+ bones = storage2(this._boneAttribute, "uvec4", count).toReadOnly();
410
+ }
411
+ let groupIds = null;
412
+ this._groupAttribute = null;
413
+ if (splat.variantMaskEnabled && splat.groupIds) {
414
+ if (splat.variantMask && splat.variantMask.length > MAX_VARIANT_GROUPS) {
415
+ throw new Error(
416
+ `WebGPUSplatBackend: ${splat.variantMask.length} variant groups requested, but this backend supports at most ${MAX_VARIANT_GROUPS}`
417
+ );
418
+ }
419
+ this._groupAttribute = new StorageBufferAttribute2(Uint32Array.from(splat.groupIds), 1);
420
+ groupIds = storage2(this._groupAttribute, "uint", count).toReadOnly();
421
+ }
422
+ const workAttribute = new StorageBufferAttribute2(new Float32Array(count * 12), 4);
423
+ const workWrite = storage2(workAttribute, "vec4", count * 3);
424
+ const sort = new GpuRadixSort(count);
425
+ const readPaletteRows = (slot) => {
426
+ const base = slot.mul(3).toVar();
427
+ const r0 = palette.element(base).toVar();
428
+ const r1 = palette.element(base.add(1)).toVar();
429
+ const r2 = palette.element(base.add(2)).toVar();
430
+ const empty = max(
431
+ length2(vec3(r0.x, r1.x, r2.x)),
432
+ max(length2(vec3(r0.y, r1.y, r2.y)), length2(vec3(r0.z, r1.z, r2.z)))
433
+ ).lessThanEqual(1e-4);
434
+ return [
435
+ select(empty, vec4(1, 0, 0, 0), r0),
436
+ select(empty, vec4(0, 1, 0, 0), r1),
437
+ select(empty, vec4(0, 0, 1, 0), r2)
438
+ ];
439
+ };
440
+ const quatMul = (a, b) => vec4(
441
+ a.w.mul(b.x).add(a.x.mul(b.w)).add(a.y.mul(b.z)).sub(a.z.mul(b.y)),
442
+ a.w.mul(b.y).sub(a.x.mul(b.z)).add(a.y.mul(b.w)).add(a.z.mul(b.x)),
443
+ a.w.mul(b.z).add(a.x.mul(b.y)).sub(a.y.mul(b.x)).add(a.z.mul(b.w)),
444
+ a.w.mul(b.w).sub(a.x.mul(b.x)).sub(a.y.mul(b.y)).sub(a.z.mul(b.z))
445
+ );
446
+ const sRGBToLinear = (c) => mix(
447
+ c.mul(0.9478672986).add(0.0521327014).pow(vec3(2.4)),
448
+ c.mul(0.0773993808),
449
+ c.lessThanEqual(vec3(0.04045))
450
+ );
451
+ const projectNode = Fn3(() => {
452
+ const tA = splats.element(instanceIndex2.mul(2)).toVar();
453
+ const tB = splats.element(instanceIndex2.mul(2).add(1)).toVar();
454
+ const modelCenter = uintBitsToFloat(tA.xyz).toVar();
455
+ const rotXY = unpackHalf2x16(tA.w).toVar();
456
+ const scaleXY = unpackHalf2x16(tB.x).toVar();
457
+ const scaleZRotZ = unpackHalf2x16(tB.y).toVar();
458
+ const packedRot = vec3(rotXY.x, rotXY.y, scaleZRotZ.y).toVar();
459
+ const srcRotation = vec4(packedRot, sqrt(max(float2(0), float2(1).sub(dot(packedRot, packedRot))))).toVar();
460
+ const srcScale = vec3(scaleXY.x, scaleXY.y, scaleZRotZ.x).toVar();
461
+ const row0 = vec4(1, 0, 0, 0).toVar();
462
+ const row1 = vec4(0, 1, 0, 0).toVar();
463
+ const row2 = vec4(0, 0, 1, 0).toVar();
464
+ if (skinned) {
465
+ const influences = bones.element(instanceIndex2).toVar();
466
+ const idx = influences.bitAnd(uvec4(65535)).toVar();
467
+ const weights = vec4(influences.shiftRight(uvec4(16))).div(65535).toVar();
468
+ const total = weights.x.add(weights.y).add(weights.z).add(weights.w).toVar();
469
+ If3(total.greaterThan(1e-3), () => {
470
+ const b0 = vec4(0).toVar();
471
+ const b1 = vec4(0).toVar();
472
+ const b2 = vec4(0).toVar();
473
+ for (const k of ["x", "y", "z", "w"]) {
474
+ If3(weights[k].greaterThan(1e-3), () => {
475
+ const [p0, p1, p2] = readPaletteRows(idx[k]);
476
+ b0.addAssign(p0.mul(weights[k]));
477
+ b1.addAssign(p1.mul(weights[k]));
478
+ b2.addAssign(p2.mul(weights[k]));
479
+ });
480
+ }
481
+ const inv = float2(1).div(total);
482
+ b0.mulAssign(inv);
483
+ b1.mulAssign(inv);
484
+ b2.mulAssign(inv);
485
+ const reasonable = max(
486
+ length2(vec3(b0.x, b1.x, b2.x)),
487
+ max(length2(vec3(b0.y, b1.y, b2.y)), length2(vec3(b0.z, b1.z, b2.z)))
488
+ ).greaterThan(1e-4);
489
+ If3(reasonable, () => {
490
+ row0.assign(b0);
491
+ row1.assign(b1);
492
+ row2.assign(b2);
493
+ });
494
+ });
495
+ }
496
+ const center4 = vec4(modelCenter, 1).toVar();
497
+ const modelDeformed = vec3(dot(row0, center4), dot(row1, center4), dot(row2, center4)).toVar();
498
+ const worldCenter = u.model.mul(vec4(modelDeformed, 1)).xyz.toVar();
499
+ const worldRotation = quatMul(u.modelRotation, srcRotation).toVar();
500
+ If3(worldRotation.w.lessThan(0), () => {
501
+ worldRotation.assign(worldRotation.negate());
502
+ });
503
+ const worldScale = u.modelScale.mul(srcScale).toVar();
504
+ if (skinned) {
505
+ const c0 = u.modelLinear.mul(vec3(row0.x, row1.x, row2.x)).toVar();
506
+ const c1 = u.modelLinear.mul(vec3(row0.y, row1.y, row2.y)).toVar();
507
+ const c2 = u.modelLinear.mul(vec3(row0.z, row1.z, row2.z)).toVar();
508
+ const s = vec3(length2(c0), length2(c1), length2(c2)).toVar();
509
+ If3(s.x.greaterThan(1e-5).and(s.y.greaterThan(1e-5)).and(s.z.greaterThan(1e-5)), () => {
510
+ const x = c0.div(s.x).toVar();
511
+ const y = c1.div(s.y).toVar();
512
+ const z = c2.div(s.z).toVar();
513
+ const q = vec4(0, 0, 0, 1).toVar();
514
+ const trace = x.x.add(y.y).add(z.z).toVar();
515
+ If3(trace.greaterThan(0), () => {
516
+ const k = sqrt(trace.add(1)).mul(2).toVar();
517
+ q.assign(vec4(y.z.sub(z.y).div(k), z.x.sub(x.z).div(k), x.y.sub(y.x).div(k), k.mul(0.25)));
518
+ }).ElseIf(x.x.greaterThan(y.y).and(x.x.greaterThan(z.z)), () => {
519
+ const k = sqrt(float2(1).add(x.x).sub(y.y).sub(z.z)).mul(2).toVar();
520
+ q.assign(vec4(k.mul(0.25), y.x.add(x.y).div(k), z.x.add(x.z).div(k), y.z.sub(z.y).div(k)));
521
+ }).ElseIf(y.y.greaterThan(z.z), () => {
522
+ const k = sqrt(float2(1).add(y.y).sub(x.x).sub(z.z)).mul(2).toVar();
523
+ q.assign(vec4(y.x.add(x.y).div(k), k.mul(0.25), z.y.add(y.z).div(k), z.x.sub(x.z).div(k)));
524
+ }).Else(() => {
525
+ const k = sqrt(float2(1).add(z.z).sub(x.x).sub(y.y)).mul(2).toVar();
526
+ q.assign(vec4(z.x.add(x.z).div(k), z.y.add(y.z).div(k), k.mul(0.25), x.y.sub(y.x).div(k)));
527
+ });
528
+ const composed = normalize(quatMul(q, srcRotation)).toVar();
529
+ If3(composed.w.lessThan(0), () => {
530
+ composed.assign(composed.negate());
531
+ });
532
+ worldRotation.assign(composed);
533
+ });
534
+ if (splat.nonRigidCovariance) {
535
+ worldScale.mulAssign(s);
536
+ }
537
+ }
538
+ const ortho = u.projection.element(3).w.equal(1);
539
+ const centerView = u.view.mul(vec4(worldCenter, 1)).toVar();
540
+ const centerProj = u.projection.mul(centerView).toVar();
541
+ centerProj.z.assign(clamp(centerProj.z, float2(0), centerProj.w.abs()));
542
+ const qx = worldRotation.x, qy = worldRotation.y, qz = worldRotation.z, qw = worldRotation.w;
543
+ const m0 = vec3(
544
+ float2(1).sub(qy.mul(qy).add(qz.mul(qz)).mul(2)),
545
+ qx.mul(qy).add(qw.mul(qz)).mul(2),
546
+ qx.mul(qz).sub(qw.mul(qy)).mul(2)
547
+ ).mul(worldScale.x).toVar();
548
+ const m1 = vec3(
549
+ qx.mul(qy).sub(qw.mul(qz)).mul(2),
550
+ float2(1).sub(qx.mul(qx).add(qz.mul(qz)).mul(2)),
551
+ qy.mul(qz).add(qw.mul(qx)).mul(2)
552
+ ).mul(worldScale.y).toVar();
553
+ const m2 = vec3(
554
+ qx.mul(qz).add(qw.mul(qy)).mul(2),
555
+ qy.mul(qz).sub(qw.mul(qx)).mul(2),
556
+ float2(1).sub(qx.mul(qx).add(qy.mul(qy)).mul(2))
557
+ ).mul(worldScale.z).toVar();
558
+ const focal = u.viewport.x.mul(u.projection.element(0).x).toVar();
559
+ const vp = select(ortho, vec3(0, 0, 1), centerView.xyz).toVar();
560
+ const J1 = focal.div(vp.z).toVar();
561
+ const J2 = vp.xy.mul(J1.negate().div(vp.z)).toVar();
562
+ const t0 = u.viewRow0.mul(J1).add(u.viewRow2.mul(J2.x)).toVar();
563
+ const t1 = u.viewRow1.mul(J1).add(u.viewRow2.mul(J2.y)).toVar();
564
+ const a = vec3(dot(t0, m0), dot(t0, m1), dot(t0, m2)).toVar();
565
+ const b = vec3(dot(t1, m0), dot(t1, m1), dot(t1, m2)).toVar();
566
+ const cov00 = dot(a, a).toVar();
567
+ const cov01 = dot(a, b).toVar();
568
+ const cov11 = dot(b, b).toVar();
569
+ const diagonal1 = cov00.add(0.3).toVar();
570
+ const diagonal2 = cov11.add(0.3).toVar();
571
+ const mid = diagonal1.add(diagonal2).mul(0.5).toVar();
572
+ const radius = length2(vec2(diagonal1.sub(diagonal2).mul(0.5), cov01)).toVar();
573
+ const lambda1 = mid.add(radius).toVar();
574
+ const lambda2 = max(mid.sub(radius), 0.1).toVar();
575
+ const vmin = min(float2(1024), min(u.viewport.x, u.viewport.y)).toVar();
576
+ const l1 = min(sqrt(lambda1.mul(2)), vmin).mul(2).toVar();
577
+ const l2 = min(sqrt(lambda2.mul(2)), vmin).mul(2).toVar();
578
+ const lmax = max(l1, l2).toVar();
579
+ const c = vec2(centerProj.w).div(u.viewport).toVar();
580
+ const diagonalVector = normalize(vec2(cov01, lambda1.sub(diagonal1))).toVar();
581
+ const v1 = diagonalVector.mul(l1).mul(c).toVar();
582
+ const v2 = vec2(diagonalVector.y, diagonalVector.x.negate()).mul(l2).mul(c).toVar();
583
+ const colorRG = unpackHalf2x16(tB.z).toVar();
584
+ const colorBA = unpackHalf2x16(tB.w).toVar();
585
+ const rgb = vec3(colorRG.x, colorRG.y, colorBA.x).toVar();
586
+ const alpha = colorBA.y.toVar();
587
+ if (splat.antiAlias) {
588
+ const detOrig = cov00.mul(cov11).sub(cov01.mul(cov01));
589
+ const detBlur = diagonal1.mul(diagonal2).sub(cov01.mul(cov01));
590
+ alpha.mulAssign(sqrt(max(detOrig.div(detBlur), 0)));
591
+ }
592
+ rgb.mulAssign(lighting.litFactor(worldCenter));
593
+ rgb.assign(mix(rgb, u.tint.xyz, clamp(u.tint.w, 0, 1)));
594
+ If3(u.grade.x.notEqual(1).or(u.grade.y.notEqual(1)), () => {
595
+ const graded = rgb.sub(0.5).mul(u.grade.x).add(0.5).toVar();
596
+ const luma = dot(graded, vec3(0.2126, 0.7152, 0.0722));
597
+ rgb.assign(clamp(mix(vec3(luma), graded, u.grade.y), 0, 1));
598
+ });
599
+ rgb.assign(max(rgb, vec3(0)));
600
+ If3(u.outputLinear.greaterThan(0.5), () => {
601
+ rgb.assign(sRGBToLinear(rgb));
602
+ });
603
+ const behind = ortho.not().and(centerView.z.greaterThan(0));
604
+ const tooSmall = lmax.lessThan(u.minPixelSize);
605
+ const offscreen = centerProj.x.abs().sub(lmax.mul(c.x)).greaterThan(centerProj.w).or(centerProj.y.abs().sub(lmax.mul(c.y)).greaterThan(centerProj.w));
606
+ const transparent = alpha.mul(255).lessThanEqual(1);
607
+ const culled = behind.or(tooSmall).or(offscreen).or(transparent).toVar();
608
+ if (groupIds) {
609
+ const hidden = u.variantMask.element(groupIds.element(instanceIndex2)).lessThan(0.5);
610
+ culled.assign(culled.or(hidden));
611
+ }
612
+ const slot = instanceIndex2.mul(3).toVar();
613
+ workWrite.element(slot).assign(select(culled, vec4(0, 0, 2, 1), centerProj));
614
+ workWrite.element(slot.add(1)).assign(select(culled, vec4(0), vec4(v1, v2)));
615
+ workWrite.element(slot.add(2)).assign(vec4(rgb, select(culled, float2(0), alpha)));
616
+ const depth = centerView.z.negate();
617
+ const normalized = clamp(u.sortRange.x.sub(depth).mul(u.sortRange.y), 0, 1);
618
+ const key = uint2(normalized.mul(SORT_BINS - 1)).toVar();
619
+ sort.keysWrite.element(instanceIndex2).assign(key);
620
+ sort.countFirstDigit(instanceIndex2, key);
621
+ })().compute(count, [WORKGROUP_SIZE]).setName("SplatProject");
622
+ this._projectNodes = [sort.resetNode, projectNode];
623
+ this._sortNodes = sort.sortNodes;
624
+ this._allNodes = null;
625
+ this._buildId++;
626
+ const workRead = storage2(workAttribute, "vec4", count * 3).toReadOnly();
627
+ const orderRead = sort.orderRead;
628
+ const gaussianUV = varyingProperty("vec2", "vGaussianUV");
629
+ const gaussianColor = varyingProperty("vec4", "vGaussianColor");
630
+ const material = this.material;
631
+ material.vertexNode = Fn3(() => {
632
+ const slot = orderRead.element(instanceIndex2).mul(3).toVar();
633
+ const centerProj = workRead.element(slot).toVar();
634
+ const axes = workRead.element(slot.add(1)).toVar();
635
+ const shade = workRead.element(slot.add(2)).toVar();
636
+ const clip = min(float2(1), sqrt(log(max(shade.w.mul(255), 1))).mul(0.5)).toVar();
637
+ const corner = positionGeometry.xy.toVar();
638
+ const offset = axes.xy.mul(corner.x).add(axes.zw.mul(corner.y)).mul(clip);
639
+ gaussianUV.assign(corner.mul(clip));
640
+ gaussianColor.assign(shade);
641
+ return centerProj.add(vec4(offset, 0, 0));
642
+ })();
643
+ material.colorNode = Fn3(() => {
644
+ const A = dot(gaussianUV, gaussianUV).toVar();
645
+ If3(A.greaterThan(1), () => {
646
+ Discard();
647
+ });
648
+ const alpha = exp(A.mul(-4)).sub(EXP4).mul(INV_EXP4).mul(gaussianColor.w).toVar();
649
+ If3(alpha.lessThan(1 / 255), () => {
650
+ Discard();
651
+ });
652
+ return vec4(gaussianColor.xyz, alpha);
653
+ })();
654
+ material.needsUpdate = true;
655
+ }
656
+ beforeRender(renderer, camera) {
657
+ const splat = this._splat;
658
+ const u = this._u;
659
+ const palette = splat.transformPalette;
660
+ if (palette.data !== this._paletteArray) {
661
+ this._build();
662
+ }
663
+ if (palette.version !== this._paletteVersion) {
664
+ this._paletteVersion = palette.version;
665
+ this._paletteAttribute.needsUpdate = true;
666
+ }
667
+ const target = renderer.getRenderTarget();
668
+ if (target) {
669
+ u.viewport.value.set(target.viewport.z, target.viewport.w);
670
+ } else {
671
+ renderer.getViewport(_viewport);
672
+ const pixelRatio = renderer.getPixelRatio();
673
+ u.viewport.value.set(Math.floor(_viewport.z * pixelRatio), Math.floor(_viewport.w * pixelRatio));
674
+ }
675
+ const linear = target ? target.texture.colorSpace !== SRGBColorSpace : renderer.needsFrameBufferTarget === true;
676
+ u.outputLinear.value = linear ? 1 : 0;
677
+ const world = this._host.matrixWorld;
678
+ u.model.value.copy(world);
679
+ u.modelLinear.value.setFromMatrix4(world);
680
+ world.decompose(_position, _quaternion, _scale);
681
+ u.modelRotation.value.set(_quaternion.x, _quaternion.y, _quaternion.z, _quaternion.w);
682
+ u.modelScale.value.copy(_scale);
683
+ const view = camera.matrixWorldInverse;
684
+ const ve = view.elements;
685
+ u.view.value.copy(view);
686
+ u.viewRow0.value.set(ve[0], ve[4], ve[8]);
687
+ u.viewRow1.value.set(ve[1], ve[5], ve[9]);
688
+ u.viewRow2.value.set(ve[2], ve[6], ve[10]);
689
+ u.projection.value.copy(camera.projectionMatrix);
690
+ u.minPixelSize.value = splat.minPixelSize;
691
+ u.tint.value.copy(splat.tint);
692
+ u.grade.value.copy(splat.grade);
693
+ const mask = splat.variantMask;
694
+ if (mask) for (let i = 0; i < mask.length; i++) this._variantMask[i] = mask[i];
695
+ _sphere.copy(this.geometry.boundingSphere).applyMatrix4(world);
696
+ _viewCenter.copy(_sphere.center).applyMatrix4(view);
697
+ const centerDepth = -_viewCenter.z;
698
+ const far = centerDepth + _sphere.radius;
699
+ const near = centerDepth - _sphere.radius;
700
+ u.sortRange.value.set(far, 1 / Math.max(far - near, 1e-6));
701
+ const lightingVersion = this._lighting.sync();
702
+ const inputs = this._inputs;
703
+ let n = 0;
704
+ inputs.set(world.elements, n);
705
+ n += 16;
706
+ inputs.set(ve, n);
707
+ n += 16;
708
+ inputs.set(camera.projectionMatrix.elements, n);
709
+ n += 16;
710
+ inputs[n++] = u.viewport.value.x;
711
+ inputs[n++] = u.viewport.value.y;
712
+ inputs[n++] = u.outputLinear.value;
713
+ inputs[n++] = splat.minPixelSize;
714
+ inputs[n++] = splat.tint.x;
715
+ inputs[n++] = splat.tint.y;
716
+ inputs[n++] = splat.tint.z;
717
+ inputs[n++] = splat.tint.w;
718
+ inputs[n++] = splat.grade.x;
719
+ inputs[n++] = splat.grade.y;
720
+ inputs[n++] = far;
721
+ inputs[n++] = near;
722
+ inputs[n++] = lightingVersion;
723
+ inputs[n++] = palette.version;
724
+ inputs[n++] = this._buildId;
725
+ inputs[n++] = splat.variantMaskVersion;
726
+ const last = this._lastInputs;
727
+ let changed = false;
728
+ for (let i = 0; i < n; i++) {
729
+ if (inputs[i] !== last[i]) {
730
+ changed = true;
731
+ break;
732
+ }
733
+ }
734
+ if (!changed) return;
735
+ last.set(inputs);
736
+ const me = world.elements;
737
+ const fx = -ve[2], fy = -ve[6], fz = -ve[10];
738
+ _direction.set(
739
+ me[0] * fx + me[1] * fy + me[2] * fz,
740
+ me[4] * fx + me[5] * fy + me[6] * fz,
741
+ me[8] * fx + me[9] * fy + me[10] * fz
742
+ );
743
+ const orderMayChange = !!splat.splatWeights || !_direction.equals(this._lastSortDirection) || this._sortedBuildId !== this._buildId;
744
+ if (orderMayChange) {
745
+ this._lastSortDirection.copy(_direction);
746
+ this._sortedBuildId = this._buildId;
747
+ renderer.compute(this._allNodes ??= [...this._projectNodes, ...this._sortNodes]);
748
+ } else {
749
+ renderer.compute(this._projectNodes);
750
+ }
751
+ }
752
+ dispose() {
753
+ this.geometry.dispose();
754
+ this.material.dispose();
755
+ for (const node of [...this._projectNodes, ...this._sortNodes]) node.dispose?.();
756
+ }
757
+ };
758
+ function packBoneInfluences(splat, out) {
759
+ const slots = splat.boneIndicesData;
760
+ const weights = splat.boneWeightsData;
761
+ const n = splat.numSplats * 4;
762
+ for (let i = 0; i < n; i++) {
763
+ const w = Math.max(0, Math.min(65535, Math.round(weights[i] * 65535)));
764
+ out[i] = (slots[i] & 65535 | w << 16) >>> 0;
765
+ }
766
+ return out;
767
+ }
768
+
769
+ // src/splat/webgpu/backend.ts
770
+ var WebGPUSplatBackend = class {
771
+ _lighting = /* @__PURE__ */ new WeakMap();
772
+ createImpl(host, splat, engine) {
773
+ let lighting = this._lighting.get(engine);
774
+ if (!lighting) {
775
+ lighting = new WebGPULighting(engine);
776
+ this._lighting.set(engine, lighting);
777
+ }
778
+ return new WebGPUSplatImpl(host, splat, lighting);
779
+ }
780
+ };
781
+
782
+ // src/webgpu.ts
783
+ registerWebGPUBackend(new WebGPUSplatBackend());
784
+ export {
785
+ Animation,
786
+ Armature,
787
+ Character,
788
+ CharacterBundleError,
789
+ EngineDisposedError,
790
+ Environment,
791
+ MAX_POINT_LIGHTS,
792
+ MAX_VARIANT_GROUPS,
793
+ PointLight,
794
+ SkyLight,
795
+ Splat,
796
+ SplatCharacter,
797
+ Animation as SplatClip,
798
+ SplatClipLoader,
799
+ SplatEngine,
800
+ SplatMesh,
801
+ SplatPLYLoader,
802
+ SunLight,
803
+ VSplatLoader,
804
+ WebGLSplatBackend,
805
+ WebGPUSplatBackend,
806
+ decodeCharacterBundle,
807
+ loadCharacterBundle,
808
+ loadCharacterBundleFromBytes,
809
+ makeRegionMask,
810
+ packSplats,
811
+ parseGaussianPly
812
+ };
813
+ //# sourceMappingURL=webgpu.js.map