@heliguy-xyz/splat-viewer 1.0.0-rc.25 → 1.0.0-rc.27

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 (38) hide show
  1. package/README.md +39 -0
  2. package/dist/web-component/splat-viewer.esm.js +684 -562
  3. package/dist/web-component/splat-viewer.esm.min.js +1 -1
  4. package/dist/web-component/splat-viewer.js +684 -562
  5. package/dist/web-component/splat-viewer.min.js +1 -1
  6. package/dist/web-component/supersplat-core/controllers.d.ts +5 -0
  7. package/dist/web-component/supersplat-core/controllers.d.ts.map +1 -1
  8. package/dist/web-component/supersplat-core/splat.d.ts.map +1 -1
  9. package/dist/web-component/types/supersplat-core/controllers.d.ts +5 -0
  10. package/dist/web-component/types/supersplat-core/controllers.d.ts.map +1 -1
  11. package/dist/web-component/types/supersplat-core/splat.d.ts.map +1 -1
  12. package/dist/web-component/types/web-component/OrbitCameraScript.d.ts.map +1 -1
  13. package/dist/web-component/types/web-component/SplatViewerCore.d.ts +1 -0
  14. package/dist/web-component/types/web-component/SplatViewerCore.d.ts.map +1 -1
  15. package/dist/web-component/types/web-component/SplatViewerElement.d.ts +2 -0
  16. package/dist/web-component/types/web-component/SplatViewerElement.d.ts.map +1 -1
  17. package/dist/web-component/types/web-component/SupersplatAdapter.d.ts +9 -0
  18. package/dist/web-component/types/web-component/SupersplatAdapter.d.ts.map +1 -1
  19. package/dist/web-component/types/web-component/types/attributes.d.ts +3 -0
  20. package/dist/web-component/types/web-component/types/attributes.d.ts.map +1 -1
  21. package/dist/web-component/types/web-component/types/core.d.ts +2 -0
  22. package/dist/web-component/types/web-component/types/core.d.ts.map +1 -1
  23. package/dist/web-component/types/web-component/utils/config.d.ts +1 -0
  24. package/dist/web-component/types/web-component/utils/config.d.ts.map +1 -1
  25. package/dist/web-component/web-component/OrbitCameraScript.d.ts.map +1 -1
  26. package/dist/web-component/web-component/SplatViewerCore.d.ts +1 -0
  27. package/dist/web-component/web-component/SplatViewerCore.d.ts.map +1 -1
  28. package/dist/web-component/web-component/SplatViewerElement.d.ts +2 -0
  29. package/dist/web-component/web-component/SplatViewerElement.d.ts.map +1 -1
  30. package/dist/web-component/web-component/SupersplatAdapter.d.ts +9 -0
  31. package/dist/web-component/web-component/SupersplatAdapter.d.ts.map +1 -1
  32. package/dist/web-component/web-component/types/attributes.d.ts +3 -0
  33. package/dist/web-component/web-component/types/attributes.d.ts.map +1 -1
  34. package/dist/web-component/web-component/types/core.d.ts +2 -0
  35. package/dist/web-component/web-component/types/core.d.ts.map +1 -1
  36. package/dist/web-component/web-component/utils/config.d.ts +1 -0
  37. package/dist/web-component/web-component/utils/config.d.ts.map +1 -1
  38. package/package.json +1 -1
@@ -139526,6 +139526,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
139526
139526
  enableStats: false,
139527
139527
  autoFocus: true,
139528
139528
  maxSplats: 2000000,
139529
+ previewMode: false,
139529
139530
  camera: {
139530
139531
  position: { x: 0, y: 0, z: 10 },
139531
139532
  target: { x: 0, y: 0, z: 0 },
@@ -139563,11 +139564,15 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
139563
139564
  }
139564
139565
  /**
139565
139566
  * Convert a string to a boolean value
139567
+ * For HTML boolean attributes, an empty string means the attribute is present (true)
139566
139568
  */
139567
139569
  function parseBoolean(value) {
139568
139570
  if (typeof value === 'boolean')
139569
139571
  return value;
139570
139572
  if (typeof value === 'string') {
139573
+ // Empty string means attribute is present without value (should be true for boolean attributes)
139574
+ if (value === '')
139575
+ return true;
139571
139576
  return value.toLowerCase() === 'true' || value === '1';
139572
139577
  }
139573
139578
  return false;
@@ -139615,6 +139620,10 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
139615
139620
  if (enableStats !== null) {
139616
139621
  config.enableStats = parseBoolean(enableStats);
139617
139622
  }
139623
+ const previewMode = element.getAttribute('preview-mode');
139624
+ if (previewMode !== null) {
139625
+ config.previewMode = parseBoolean(previewMode);
139626
+ }
139618
139627
  // Parse number attributes
139619
139628
  const maxSplats = element.getAttribute('max-splats');
139620
139629
  if (maxSplats !== null) {
@@ -140454,6 +140463,10 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
140454
140463
  this.isPanning = false;
140455
140464
  // Allow host to temporarily disable camera input (e.g., while dragging gizmos)
140456
140465
  this._inputEnabled = true;
140466
+ // Granular control over specific input types
140467
+ this._orbitEnabled = true;
140468
+ this._panEnabled = true;
140469
+ this._zoomEnabled = true;
140457
140470
  // Setup context menu prevention
140458
140471
  this.setupContextMenuPrevention();
140459
140472
  // Use PlayCanvas mouse events for reliable input handling
@@ -140491,6 +140504,19 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
140491
140504
  this.isPanning = false;
140492
140505
  }
140493
140506
  };
140507
+ OrbitCamera.prototype.setInputControls = function (options) {
140508
+ if (options.orbit !== undefined)
140509
+ this._orbitEnabled = !!options.orbit;
140510
+ if (options.pan !== undefined)
140511
+ this._panEnabled = !!options.pan;
140512
+ if (options.zoom !== undefined)
140513
+ this._zoomEnabled = !!options.zoom;
140514
+ // Clear any in-progress interactions if being disabled
140515
+ if (this._orbitEnabled === false)
140516
+ this.isOrbiting = false;
140517
+ if (this._panEnabled === false)
140518
+ this.isPanning = false;
140519
+ };
140494
140520
  OrbitCamera.prototype.update = function (dt) {
140495
140521
  // Update navigation cube if enabled
140496
140522
  if (this.navigationCube &&
@@ -140507,12 +140533,13 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
140507
140533
  typeof this.navigationCube.cancelTransition === 'function') {
140508
140534
  this.navigationCube.cancelTransition('manual-interaction');
140509
140535
  }
140510
- if (event.button === MOUSEBUTTON_LEFT) {
140536
+ if (event.button === MOUSEBUTTON_LEFT && this._orbitEnabled !== false) {
140511
140537
  this.isOrbiting = true;
140512
140538
  this.emitInteractionEvent?.('interaction-start', 'rotate');
140513
140539
  }
140514
- else if (event.button === MOUSEBUTTON_RIGHT ||
140515
- event.button === MOUSEBUTTON_MIDDLE) {
140540
+ else if ((event.button === MOUSEBUTTON_RIGHT ||
140541
+ event.button === MOUSEBUTTON_MIDDLE) &&
140542
+ this._panEnabled !== false) {
140516
140543
  this.isPanning = true;
140517
140544
  this.emitInteractionEvent?.('interaction-start', 'pan');
140518
140545
  }
@@ -140560,7 +140587,7 @@ fn fragmentMain(input: FragmentInput) -> FragmentOutput {
140560
140587
  }
140561
140588
  };
140562
140589
  OrbitCamera.prototype.onMouseWheel = function (event) {
140563
- if (this._inputEnabled === false)
140590
+ if (this._inputEnabled === false || this._zoomEnabled === false)
140564
140591
  return;
140565
140592
  // Cancel any ongoing navigation cube transition when manual zoom interaction starts
140566
140593
  if (this.navigationCube &&
@@ -142696,6 +142723,10 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
142696
142723
  this.entity.setEulerAngles(orientation);
142697
142724
  this.entity.addComponent('gsplat', { asset });
142698
142725
  const instance = this.entity.gsplat.instance;
142726
+ // Check if the gsplat component initialized properly
142727
+ if (!instance || !instance.meshInstance) {
142728
+ throw new Error('Failed to initialize gsplat component. The file may not contain valid Gaussian Splatting data.');
142729
+ }
142699
142730
  // use custom render order distance calculation for splats
142700
142731
  instance.meshInstance.calculateSortDistance = (meshInstance, pos, dir) => {
142701
142732
  const bound = this.localBound;
@@ -143168,222 +143199,222 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143168
143199
  }
143169
143200
  }
143170
143201
 
143171
- const vertexShader$6 = /* glsl */ `
143172
- attribute vec2 vertex_position;
143173
- void main(void) {
143174
- gl_Position = vec4(vertex_position, 0.0, 1.0);
143175
- }
143176
- `;
143177
- const fragmentShader$6 = /* glsl */ `
143178
- uniform highp usampler2D transformA; // splat center x, y, z
143179
- uniform highp usampler2D splatTransform; // transform palette index
143180
- uniform sampler2D transformPalette; // palette of transforms
143181
- uniform sampler2D splatState; // per-splat state
143182
- uniform highp ivec3 splat_params; // texture width, texture height, num splats
143183
- uniform highp uint mode; // 0: selected, 1: visible
143184
-
143185
- // calculate min and max for a single column of splats
143186
- void main(void) {
143187
-
143188
- vec3 boundMin = vec3(1e6);
143189
- vec3 boundMax = vec3(-1e6);
143190
-
143191
- for (int id = 0; id < splat_params.y; id++) {
143192
- // calculate splatUV
143193
- ivec2 splatUV = ivec2(gl_FragCoord.x, id);
143194
-
143195
- // skip out-of-range splats
143196
- if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
143197
- continue;
143198
- }
143199
-
143200
- // read splat state
143201
- uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
143202
-
143203
- // skip deleted or locked splats
143204
- if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
143205
- continue;
143206
- }
143207
-
143208
- // read splat center
143209
- vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143210
-
143211
- // apply optional per-splat transform
143212
- uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143213
- if (transformIndex > 0u) {
143214
- // read transform matrix
143215
- int u = int(transformIndex % 512u) * 3;
143216
- int v = int(transformIndex / 512u);
143217
-
143218
- mat3x4 t;
143219
- t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143220
- t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143221
- t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143222
-
143223
- center = vec4(center, 1.0) * t;
143224
- }
143225
-
143226
- boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
143227
- boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
143228
- }
143229
-
143230
- pcFragColor0 = vec4(boundMin, 0.0);
143231
- pcFragColor1 = vec4(boundMax, 0.0);
143232
- }
143233
- `;
143234
-
143235
- const vertexShader$5 = /* glsl */ `
143236
- attribute vec2 vertex_position;
143237
- void main(void) {
143238
- gl_Position = vec4(vertex_position, 0.0, 1.0);
143239
- }
143240
- `;
143241
- const fragmentShader$5 = /* glsl */ `
143242
- uniform highp usampler2D transformA; // splat center x, y, z
143243
- uniform highp usampler2D splatTransform; // transform palette index
143244
- uniform sampler2D transformPalette; // palette of transforms
143245
- uniform uvec2 splat_params; // splat texture width, num splats
143246
-
143247
- uniform mat4 matrix_model;
143248
- uniform mat4 matrix_viewProjection;
143249
-
143250
- uniform uvec2 output_params; // output width, height
143251
-
143252
- // 0: mask, 1: rect, 2: sphere
143253
- uniform int mode;
143254
-
143255
- // mask params
143256
- uniform sampler2D mask; // mask in alpha channel
143257
- uniform vec2 mask_params; // mask width, height
143258
-
143259
- // rect params
143260
- uniform vec4 rect_params; // rect x, y, width, height
143261
-
143262
- // sphere params
143263
- uniform vec4 sphere_params; // sphere x, y, z, radius
143264
-
143265
- // box params
143266
- uniform vec4 box_params; // box x, y, z
143267
- uniform vec4 aabb_params; // len x, y, z
143268
-
143269
- void main(void) {
143270
- // calculate output id
143271
- uvec2 outputUV = uvec2(gl_FragCoord);
143272
- uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
143273
-
143274
- vec4 clr = vec4(0.0);
143275
-
143276
- for (uint i = 0u; i < 4u; i++) {
143277
- uint id = outputId + i;
143278
-
143279
- if (id >= splat_params.y) {
143280
- continue;
143281
- }
143282
-
143283
- // calculate splatUV
143284
- ivec2 splatUV = ivec2(
143285
- int(id % splat_params.x),
143286
- int(id / splat_params.x)
143287
- );
143288
-
143289
- // read splat center
143290
- vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143291
-
143292
- // apply optional per-splat transform
143293
- uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143294
- if (transformIndex > 0u) {
143295
- // read transform matrix
143296
- int u = int(transformIndex % 512u) * 3;
143297
- int v = int(transformIndex / 512u);
143298
-
143299
- mat3x4 t;
143300
- t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143301
- t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143302
- t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143303
-
143304
- center = vec4(center, 1.0) * t;
143305
- }
143306
-
143307
- // transform to clip space and discard if outside
143308
- vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
143309
- vec4 clip = matrix_viewProjection * vec4(world, 1.0);
143310
- vec3 ndc = clip.xyz / clip.w;
143311
-
143312
- // skip offscreen fragments
143313
- if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
143314
- if (mode == 0) {
143315
- // select by mask
143316
- ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
143317
- clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
143318
- } else if (mode == 1) {
143319
- // select by rect
143320
- clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;
143321
- } else if (mode == 2) {
143322
- // select by sphere
143323
- clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
143324
- } else if (mode == 3) {
143325
- // select by box
143326
- vec3 relativePosition = world - box_params.xyz;
143327
- bool isInsideCube = true;
143328
- if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
143329
- isInsideCube = false;
143330
- }
143331
- if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
143332
- isInsideCube = false;
143333
- }
143334
- if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
143335
- isInsideCube = false;
143336
- }
143337
- clr[i] = isInsideCube ? 1.0 : 0.0;
143338
- }
143339
- }
143340
- }
143341
-
143342
- gl_FragColor = clr;
143343
- }
143344
- `;
143345
-
143346
- const vertexShader$4 = /* glsl */ `
143347
- attribute vec2 vertex_position;
143348
- void main(void) {
143349
- gl_Position = vec4(vertex_position, 0.0, 1.0);
143350
- }
143351
- `;
143352
- const fragmentShader$4 = /* glsl */ `
143353
- uniform highp usampler2D transformA; // splat center x, y, z
143354
- uniform highp usampler2D splatTransform; // transform palette index
143355
- uniform sampler2D transformPalette; // palette of transforms
143356
- uniform ivec2 splat_params; // splat texture width, num splats
143357
-
143358
- void main(void) {
143359
- // calculate output id
143360
- ivec2 splatUV = ivec2(gl_FragCoord);
143361
-
143362
- // skip if splat index is out of bounds
143363
- if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
143364
- discard;
143365
- }
143366
-
143367
- // read splat center
143368
- vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143369
-
143370
- // apply optional per-splat transform
143371
- uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143372
- if (transformIndex > 0u) {
143373
- // read transform matrix
143374
- int u = int(transformIndex % 512u) * 3;
143375
- int v = int(transformIndex / 512u);
143376
-
143377
- mat3x4 t;
143378
- t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143379
- t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143380
- t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143381
-
143382
- center = vec4(center, 1.0) * t;
143383
- }
143384
-
143385
- gl_FragColor = vec4(center, 0.0);
143386
- }
143202
+ const vertexShader$6 = /* glsl */ `
143203
+ attribute vec2 vertex_position;
143204
+ void main(void) {
143205
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
143206
+ }
143207
+ `;
143208
+ const fragmentShader$6 = /* glsl */ `
143209
+ uniform highp usampler2D transformA; // splat center x, y, z
143210
+ uniform highp usampler2D splatTransform; // transform palette index
143211
+ uniform sampler2D transformPalette; // palette of transforms
143212
+ uniform sampler2D splatState; // per-splat state
143213
+ uniform highp ivec3 splat_params; // texture width, texture height, num splats
143214
+ uniform highp uint mode; // 0: selected, 1: visible
143215
+
143216
+ // calculate min and max for a single column of splats
143217
+ void main(void) {
143218
+
143219
+ vec3 boundMin = vec3(1e6);
143220
+ vec3 boundMax = vec3(-1e6);
143221
+
143222
+ for (int id = 0; id < splat_params.y; id++) {
143223
+ // calculate splatUV
143224
+ ivec2 splatUV = ivec2(gl_FragCoord.x, id);
143225
+
143226
+ // skip out-of-range splats
143227
+ if ((splatUV.x + splatUV.y * splat_params.x) >= splat_params.z) {
143228
+ continue;
143229
+ }
143230
+
143231
+ // read splat state
143232
+ uint state = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
143233
+
143234
+ // skip deleted or locked splats
143235
+ if (((mode == 0u) && (state != 1u)) || ((mode == 1u) && ((state & 4u) != 0u))) {
143236
+ continue;
143237
+ }
143238
+
143239
+ // read splat center
143240
+ vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143241
+
143242
+ // apply optional per-splat transform
143243
+ uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143244
+ if (transformIndex > 0u) {
143245
+ // read transform matrix
143246
+ int u = int(transformIndex % 512u) * 3;
143247
+ int v = int(transformIndex / 512u);
143248
+
143249
+ mat3x4 t;
143250
+ t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143251
+ t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143252
+ t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143253
+
143254
+ center = vec4(center, 1.0) * t;
143255
+ }
143256
+
143257
+ boundMin = min(boundMin, mix(center, boundMin, isinf(center)));
143258
+ boundMax = max(boundMax, mix(center, boundMax, isinf(center)));
143259
+ }
143260
+
143261
+ pcFragColor0 = vec4(boundMin, 0.0);
143262
+ pcFragColor1 = vec4(boundMax, 0.0);
143263
+ }
143264
+ `;
143265
+
143266
+ const vertexShader$5 = /* glsl */ `
143267
+ attribute vec2 vertex_position;
143268
+ void main(void) {
143269
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
143270
+ }
143271
+ `;
143272
+ const fragmentShader$5 = /* glsl */ `
143273
+ uniform highp usampler2D transformA; // splat center x, y, z
143274
+ uniform highp usampler2D splatTransform; // transform palette index
143275
+ uniform sampler2D transformPalette; // palette of transforms
143276
+ uniform uvec2 splat_params; // splat texture width, num splats
143277
+
143278
+ uniform mat4 matrix_model;
143279
+ uniform mat4 matrix_viewProjection;
143280
+
143281
+ uniform uvec2 output_params; // output width, height
143282
+
143283
+ // 0: mask, 1: rect, 2: sphere
143284
+ uniform int mode;
143285
+
143286
+ // mask params
143287
+ uniform sampler2D mask; // mask in alpha channel
143288
+ uniform vec2 mask_params; // mask width, height
143289
+
143290
+ // rect params
143291
+ uniform vec4 rect_params; // rect x, y, width, height
143292
+
143293
+ // sphere params
143294
+ uniform vec4 sphere_params; // sphere x, y, z, radius
143295
+
143296
+ // box params
143297
+ uniform vec4 box_params; // box x, y, z
143298
+ uniform vec4 aabb_params; // len x, y, z
143299
+
143300
+ void main(void) {
143301
+ // calculate output id
143302
+ uvec2 outputUV = uvec2(gl_FragCoord);
143303
+ uint outputId = (outputUV.x + outputUV.y * output_params.x) * 4u;
143304
+
143305
+ vec4 clr = vec4(0.0);
143306
+
143307
+ for (uint i = 0u; i < 4u; i++) {
143308
+ uint id = outputId + i;
143309
+
143310
+ if (id >= splat_params.y) {
143311
+ continue;
143312
+ }
143313
+
143314
+ // calculate splatUV
143315
+ ivec2 splatUV = ivec2(
143316
+ int(id % splat_params.x),
143317
+ int(id / splat_params.x)
143318
+ );
143319
+
143320
+ // read splat center
143321
+ vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143322
+
143323
+ // apply optional per-splat transform
143324
+ uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143325
+ if (transformIndex > 0u) {
143326
+ // read transform matrix
143327
+ int u = int(transformIndex % 512u) * 3;
143328
+ int v = int(transformIndex / 512u);
143329
+
143330
+ mat3x4 t;
143331
+ t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143332
+ t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143333
+ t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143334
+
143335
+ center = vec4(center, 1.0) * t;
143336
+ }
143337
+
143338
+ // transform to clip space and discard if outside
143339
+ vec3 world = (matrix_model * vec4(center, 1.0)).xyz;
143340
+ vec4 clip = matrix_viewProjection * vec4(world, 1.0);
143341
+ vec3 ndc = clip.xyz / clip.w;
143342
+
143343
+ // skip offscreen fragments
143344
+ if (!any(greaterThan(abs(ndc), vec3(1.0)))) {
143345
+ if (mode == 0) {
143346
+ // select by mask
143347
+ ivec2 maskUV = ivec2((ndc.xy * vec2(0.5, -0.5) + 0.5) * mask_params);
143348
+ clr[i] = texelFetch(mask, maskUV, 0).a < 1.0 ? 0.0 : 1.0;
143349
+ } else if (mode == 1) {
143350
+ // select by rect
143351
+ clr[i] = all(greaterThan(ndc.xy * vec2(1.0, -1.0), rect_params.xy)) && all(lessThan(ndc.xy * vec2(1.0, -1.0), rect_params.zw)) ? 1.0 : 0.0;
143352
+ } else if (mode == 2) {
143353
+ // select by sphere
143354
+ clr[i] = length(world - sphere_params.xyz) < sphere_params.w ? 1.0 : 0.0;
143355
+ } else if (mode == 3) {
143356
+ // select by box
143357
+ vec3 relativePosition = world - box_params.xyz;
143358
+ bool isInsideCube = true;
143359
+ if (relativePosition.x < -aabb_params.x || relativePosition.x > aabb_params.x) {
143360
+ isInsideCube = false;
143361
+ }
143362
+ if (relativePosition.y < -aabb_params.y || relativePosition.y > aabb_params.y) {
143363
+ isInsideCube = false;
143364
+ }
143365
+ if (relativePosition.z < -aabb_params.z || relativePosition.z > aabb_params.z) {
143366
+ isInsideCube = false;
143367
+ }
143368
+ clr[i] = isInsideCube ? 1.0 : 0.0;
143369
+ }
143370
+ }
143371
+ }
143372
+
143373
+ gl_FragColor = clr;
143374
+ }
143375
+ `;
143376
+
143377
+ const vertexShader$4 = /* glsl */ `
143378
+ attribute vec2 vertex_position;
143379
+ void main(void) {
143380
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
143381
+ }
143382
+ `;
143383
+ const fragmentShader$4 = /* glsl */ `
143384
+ uniform highp usampler2D transformA; // splat center x, y, z
143385
+ uniform highp usampler2D splatTransform; // transform palette index
143386
+ uniform sampler2D transformPalette; // palette of transforms
143387
+ uniform ivec2 splat_params; // splat texture width, num splats
143388
+
143389
+ void main(void) {
143390
+ // calculate output id
143391
+ ivec2 splatUV = ivec2(gl_FragCoord);
143392
+
143393
+ // skip if splat index is out of bounds
143394
+ if (splatUV.x + splatUV.y * splat_params.x >= splat_params.y) {
143395
+ discard;
143396
+ }
143397
+
143398
+ // read splat center
143399
+ vec3 center = uintBitsToFloat(texelFetch(transformA, splatUV, 0).xyz);
143400
+
143401
+ // apply optional per-splat transform
143402
+ uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
143403
+ if (transformIndex > 0u) {
143404
+ // read transform matrix
143405
+ int u = int(transformIndex % 512u) * 3;
143406
+ int v = int(transformIndex / 512u);
143407
+
143408
+ mat3x4 t;
143409
+ t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
143410
+ t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
143411
+ t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
143412
+
143413
+ center = vec4(center, 1.0) * t;
143414
+ }
143415
+
143416
+ gl_FragColor = vec4(center, 0.0);
143417
+ }
143387
143418
  `;
143388
143419
 
143389
143420
  const v1 = new Vec3();
@@ -143420,18 +143451,18 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143420
143451
  attributes: {
143421
143452
  vertex_position: SEMANTIC_POSITION
143422
143453
  },
143423
- vertexGLSL: `
143424
- attribute vec2 vertex_position;
143425
- void main(void) {
143426
- gl_Position = vec4(vertex_position, 0.0, 1.0);
143427
- }
143454
+ vertexGLSL: `
143455
+ attribute vec2 vertex_position;
143456
+ void main(void) {
143457
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
143458
+ }
143428
143459
  `,
143429
- fragmentGLSL: `
143430
- uniform sampler2D colorTex;
143431
- void main(void) {
143432
- ivec2 texel = ivec2(gl_FragCoord.xy);
143433
- gl_FragColor = texelFetch(colorTex, texel, 0);
143434
- }
143460
+ fragmentGLSL: `
143461
+ uniform sampler2D colorTex;
143462
+ void main(void) {
143463
+ ivec2 texel = ivec2(gl_FragCoord.xy);
143464
+ gl_FragColor = texelFetch(colorTex, texel, 0);
143465
+ }
143435
143466
  `
143436
143467
  });
143437
143468
  // intersection test
@@ -143804,6 +143835,10 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143804
143835
  };
143805
143836
  // Allow temporarily disabling camera controls (e.g. while dragging gizmos).
143806
143837
  let enabled = true;
143838
+ // Granular control over specific input types
143839
+ let orbitEnabled = true;
143840
+ let panEnabled = true;
143841
+ let zoomEnabled = true;
143807
143842
  const resetState = () => {
143808
143843
  pressedButton = -1;
143809
143844
  touches = [];
@@ -143910,13 +143945,13 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143910
143945
  (event.shiftKey || event.ctrlKey ? 'orbit' :
143911
143946
  (event.altKey || event.metaKey ? 'zoom' : null)) :
143912
143947
  null;
143913
- if (mod === 'orbit' || (mod === null && pressedButton === 0)) {
143948
+ if ((mod === 'orbit' || (mod === null && pressedButton === 0)) && orbitEnabled) {
143914
143949
  orbit(dx, dy);
143915
143950
  }
143916
- else if (mod === 'zoom' || (mod === null && pressedButton === 1)) {
143951
+ else if ((mod === 'zoom' || (mod === null && pressedButton === 1)) && zoomEnabled) {
143917
143952
  zoom(dy * -0.02);
143918
143953
  }
143919
- else if (mod === 'pan' || (mod === null && pressedButton === 2)) {
143954
+ else if ((mod === 'pan' || (mod === null && pressedButton === 2)) && panEnabled) {
143920
143955
  pan(x, y, dx, dy);
143921
143956
  }
143922
143957
  }
@@ -143927,7 +143962,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143927
143962
  const dy = event.offsetY - touch.y;
143928
143963
  touch.x = event.offsetX;
143929
143964
  touch.y = event.offsetY;
143930
- orbit(dx, dy);
143965
+ if (orbitEnabled) {
143966
+ orbit(dx, dy);
143967
+ }
143931
143968
  }
143932
143969
  else if (touches.length === 2) {
143933
143970
  const touch = touches[touches.map(t => t.id).indexOf(event.pointerId)];
@@ -143936,8 +143973,12 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143936
143973
  const mx = (touches[0].x + touches[1].x) * 0.5;
143937
143974
  const my = (touches[0].y + touches[1].y) * 0.5;
143938
143975
  const ml = dist(touches[0].x, touches[0].y, touches[1].x, touches[1].y);
143939
- pan(mx, my, (mx - midx), (my - midy));
143940
- zoom((ml - midlen) * 0.01);
143976
+ if (panEnabled) {
143977
+ pan(mx, my, (mx - midx), (my - midy));
143978
+ }
143979
+ if (zoomEnabled) {
143980
+ zoom((ml - midlen) * 0.01);
143981
+ }
143941
143982
  midx = mx;
143942
143983
  midy = my;
143943
143984
  midlen = ml;
@@ -143955,16 +143996,20 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
143955
143996
  return;
143956
143997
  const { deltaX, deltaY } = event;
143957
143998
  if (isMouseEvent(deltaX, deltaY)) {
143958
- zoom(deltaY * -2e-3);
143999
+ if (zoomEnabled)
144000
+ zoom(deltaY * -2e-3);
143959
144001
  }
143960
144002
  else if (event.ctrlKey || event.metaKey) {
143961
- zoom(deltaY * -0.02);
144003
+ if (zoomEnabled)
144004
+ zoom(deltaY * -0.02);
143962
144005
  }
143963
144006
  else if (event.shiftKey) {
143964
- pan(event.offsetX, event.offsetY, deltaX, deltaY);
144007
+ if (panEnabled)
144008
+ pan(event.offsetX, event.offsetY, deltaX, deltaY);
143965
144009
  }
143966
144010
  else {
143967
- orbit(deltaX, deltaY);
144011
+ if (orbitEnabled)
144012
+ orbit(deltaX, deltaY);
143968
144013
  }
143969
144014
  event.preventDefault();
143970
144015
  };
@@ -144032,6 +144077,14 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
144032
144077
  resetState();
144033
144078
  }
144034
144079
  };
144080
+ this.setInputControls = (options) => {
144081
+ if (options.orbit !== undefined)
144082
+ orbitEnabled = !!options.orbit;
144083
+ if (options.pan !== undefined)
144084
+ panEnabled = !!options.pan;
144085
+ if (options.zoom !== undefined)
144086
+ zoomEnabled = !!options.zoom;
144087
+ };
144035
144088
  }
144036
144089
  }
144037
144090
 
@@ -144670,176 +144723,176 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
144670
144723
  }
144671
144724
  }
144672
144725
 
144673
- const vertexShader$3 = /* glsl*/ `
144674
- uniform vec3 near_origin;
144675
- uniform vec3 near_x;
144676
- uniform vec3 near_y;
144677
-
144678
- uniform vec3 far_origin;
144679
- uniform vec3 far_x;
144680
- uniform vec3 far_y;
144681
-
144682
- attribute vec2 vertex_position;
144683
-
144684
- varying vec3 worldFar;
144685
- varying vec3 worldNear;
144686
-
144687
- void main(void) {
144688
- gl_Position = vec4(vertex_position, 0.0, 1.0);
144689
-
144690
- vec2 p = vertex_position * 0.5 + 0.5;
144691
- worldNear = near_origin + near_x * p.x + near_y * p.y;
144692
- worldFar = far_origin + far_x * p.x + far_y * p.y;
144693
- }
144694
- `;
144695
- const fragmentShader$3 = /* glsl*/ `
144696
- uniform vec3 view_position;
144697
- uniform mat4 matrix_viewProjection;
144698
- uniform sampler2D blueNoiseTex32;
144699
-
144700
- uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
144701
-
144702
- vec4 planes[3] = vec4[3](
144703
- vec4(1.0, 0.0, 0.0, 0.0),
144704
- vec4(0.0, 1.0, 0.0, 0.0),
144705
- vec4(0.0, 0.0, 1.0, 0.0)
144706
- );
144707
-
144708
- vec3 colors[3] = vec3[3](
144709
- vec3(1.0, 0.2, 0.2),
144710
- vec3(0.2, 1.0, 0.2),
144711
- vec3(0.2, 0.2, 1.0)
144712
- );
144713
-
144714
- int axis0[3] = int[3](1, 0, 0);
144715
- int axis1[3] = int[3](2, 2, 1);
144716
-
144717
- varying vec3 worldNear;
144718
- varying vec3 worldFar;
144719
-
144720
- bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
144721
- float d = dot(dir, plane.xyz);
144722
- if (abs(d) < 1e-06) {
144723
- return false;
144724
- }
144725
-
144726
- float n = -(dot(pos, plane.xyz) + plane.w) / d;
144727
- if (n < 0.0) {
144728
- return false;
144729
- }
144730
-
144731
- t = n;
144732
-
144733
- return true;
144734
- }
144735
-
144736
- // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
144737
- float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
144738
- vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
144739
- bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
144740
- vec2 targetWidth = vec2(
144741
- invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
144742
- invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
144743
- );
144744
- vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
144745
- vec2 lineAA = uvDeriv * 1.5;
144746
- vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
144747
- gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
144748
- gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
144749
- vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
144750
-
144751
- grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
144752
- grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
144753
- grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
144754
- grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
144755
-
144756
- return mix(grid2.x, 1.0, grid2.y);
144757
- }
144758
-
144759
- float calcDepth(vec3 p) {
144760
- vec4 v = matrix_viewProjection * vec4(p, 1.0);
144761
- return (v.z / v.w) * 0.5 + 0.5;
144762
- }
144763
-
144764
- bool writeDepth(float alpha) {
144765
- vec2 uv = fract(gl_FragCoord.xy / 32.0);
144766
- float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
144767
- return alpha > noise;
144768
- }
144769
-
144770
- void main(void) {
144771
- vec3 p = worldNear;
144772
- vec3 v = normalize(worldFar - worldNear);
144773
-
144774
- // intersect ray with the world xz plane
144775
- float t;
144776
- if (!intersectPlane(t, p, v, planes[plane])) {
144777
- discard;
144778
- }
144779
-
144780
- // calculate grid intersection
144781
- vec3 worldPos = p + v * t;
144782
- vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
144783
- vec2 ddx = dFdx(pos);
144784
- vec2 ddy = dFdy(pos);
144785
-
144786
- float epsilon = 1.0 / 255.0;
144787
-
144788
- // calculate fade
144789
- float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
144790
- if (fade < epsilon) {
144791
- discard;
144792
- }
144793
-
144794
- vec2 levelPos;
144795
- float levelSize;
144796
- float levelAlpha;
144797
-
144798
- // 10m grid with colored main axes
144799
- levelPos = pos * 0.1;
144800
- levelSize = 2.0 / 1000.0;
144801
- levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
144802
- if (levelAlpha > epsilon) {
144803
- vec3 color;
144804
- vec2 loc = abs(levelPos);
144805
- if (loc.x < levelSize) {
144806
- if (loc.y < levelSize) {
144807
- color = vec3(1.0);
144808
- } else {
144809
- color = colors[axis1[plane]];
144810
- }
144811
- } else if (loc.y < levelSize) {
144812
- color = colors[axis0[plane]];
144813
- } else {
144814
- color = vec3(0.9);
144815
- }
144816
- gl_FragColor = vec4(color, levelAlpha);
144817
- gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144818
- return;
144819
- }
144820
-
144821
- // 1m grid
144822
- levelPos = pos;
144823
- levelSize = 1.0 / 100.0;
144824
- levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
144825
- if (levelAlpha > epsilon) {
144826
- gl_FragColor = vec4(vec3(0.7), levelAlpha);
144827
- gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144828
- return;
144829
- }
144830
-
144831
- // 0.1m grid
144832
- levelPos = pos * 10.0;
144833
- levelSize = 1.0 / 100.0;
144834
- levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
144835
- if (levelAlpha > epsilon) {
144836
- gl_FragColor = vec4(vec3(0.7), levelAlpha);
144837
- gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144838
- return;
144839
- }
144840
-
144841
- discard;
144842
- }
144726
+ const vertexShader$3 = /* glsl*/ `
144727
+ uniform vec3 near_origin;
144728
+ uniform vec3 near_x;
144729
+ uniform vec3 near_y;
144730
+
144731
+ uniform vec3 far_origin;
144732
+ uniform vec3 far_x;
144733
+ uniform vec3 far_y;
144734
+
144735
+ attribute vec2 vertex_position;
144736
+
144737
+ varying vec3 worldFar;
144738
+ varying vec3 worldNear;
144739
+
144740
+ void main(void) {
144741
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
144742
+
144743
+ vec2 p = vertex_position * 0.5 + 0.5;
144744
+ worldNear = near_origin + near_x * p.x + near_y * p.y;
144745
+ worldFar = far_origin + far_x * p.x + far_y * p.y;
144746
+ }
144747
+ `;
144748
+ const fragmentShader$3 = /* glsl*/ `
144749
+ uniform vec3 view_position;
144750
+ uniform mat4 matrix_viewProjection;
144751
+ uniform sampler2D blueNoiseTex32;
144752
+
144753
+ uniform int plane; // 0: x (yz), 1: y (xz), 2: z (xy)
144754
+
144755
+ vec4 planes[3] = vec4[3](
144756
+ vec4(1.0, 0.0, 0.0, 0.0),
144757
+ vec4(0.0, 1.0, 0.0, 0.0),
144758
+ vec4(0.0, 0.0, 1.0, 0.0)
144759
+ );
144760
+
144761
+ vec3 colors[3] = vec3[3](
144762
+ vec3(1.0, 0.2, 0.2),
144763
+ vec3(0.2, 1.0, 0.2),
144764
+ vec3(0.2, 0.2, 1.0)
144765
+ );
144766
+
144767
+ int axis0[3] = int[3](1, 0, 0);
144768
+ int axis1[3] = int[3](2, 2, 1);
144769
+
144770
+ varying vec3 worldNear;
144771
+ varying vec3 worldFar;
144772
+
144773
+ bool intersectPlane(inout float t, vec3 pos, vec3 dir, vec4 plane) {
144774
+ float d = dot(dir, plane.xyz);
144775
+ if (abs(d) < 1e-06) {
144776
+ return false;
144777
+ }
144778
+
144779
+ float n = -(dot(pos, plane.xyz) + plane.w) / d;
144780
+ if (n < 0.0) {
144781
+ return false;
144782
+ }
144783
+
144784
+ t = n;
144785
+
144786
+ return true;
144787
+ }
144788
+
144789
+ // https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8#1e7c
144790
+ float pristineGrid(in vec2 uv, in vec2 ddx, in vec2 ddy, vec2 lineWidth) {
144791
+ vec2 uvDeriv = vec2(length(vec2(ddx.x, ddy.x)), length(vec2(ddx.y, ddy.y)));
144792
+ bvec2 invertLine = bvec2(lineWidth.x > 0.5, lineWidth.y > 0.5);
144793
+ vec2 targetWidth = vec2(
144794
+ invertLine.x ? 1.0 - lineWidth.x : lineWidth.x,
144795
+ invertLine.y ? 1.0 - lineWidth.y : lineWidth.y
144796
+ );
144797
+ vec2 drawWidth = clamp(targetWidth, uvDeriv, vec2(0.5));
144798
+ vec2 lineAA = uvDeriv * 1.5;
144799
+ vec2 gridUV = abs(fract(uv) * 2.0 - 1.0);
144800
+ gridUV.x = invertLine.x ? gridUV.x : 1.0 - gridUV.x;
144801
+ gridUV.y = invertLine.y ? gridUV.y : 1.0 - gridUV.y;
144802
+ vec2 grid2 = smoothstep(drawWidth + lineAA, drawWidth - lineAA, gridUV);
144803
+
144804
+ grid2 *= clamp(targetWidth / drawWidth, 0.0, 1.0);
144805
+ grid2 = mix(grid2, targetWidth, clamp(uvDeriv * 2.0 - 1.0, 0.0, 1.0));
144806
+ grid2.x = invertLine.x ? 1.0 - grid2.x : grid2.x;
144807
+ grid2.y = invertLine.y ? 1.0 - grid2.y : grid2.y;
144808
+
144809
+ return mix(grid2.x, 1.0, grid2.y);
144810
+ }
144811
+
144812
+ float calcDepth(vec3 p) {
144813
+ vec4 v = matrix_viewProjection * vec4(p, 1.0);
144814
+ return (v.z / v.w) * 0.5 + 0.5;
144815
+ }
144816
+
144817
+ bool writeDepth(float alpha) {
144818
+ vec2 uv = fract(gl_FragCoord.xy / 32.0);
144819
+ float noise = texture2DLod(blueNoiseTex32, uv, 0.0).y;
144820
+ return alpha > noise;
144821
+ }
144822
+
144823
+ void main(void) {
144824
+ vec3 p = worldNear;
144825
+ vec3 v = normalize(worldFar - worldNear);
144826
+
144827
+ // intersect ray with the world xz plane
144828
+ float t;
144829
+ if (!intersectPlane(t, p, v, planes[plane])) {
144830
+ discard;
144831
+ }
144832
+
144833
+ // calculate grid intersection
144834
+ vec3 worldPos = p + v * t;
144835
+ vec2 pos = plane == 0 ? worldPos.yz : (plane == 1 ? worldPos.xz : worldPos.xy);
144836
+ vec2 ddx = dFdx(pos);
144837
+ vec2 ddy = dFdy(pos);
144838
+
144839
+ float epsilon = 1.0 / 255.0;
144840
+
144841
+ // calculate fade
144842
+ float fade = 1.0 - smoothstep(400.0, 1000.0, length(worldPos - view_position));
144843
+ if (fade < epsilon) {
144844
+ discard;
144845
+ }
144846
+
144847
+ vec2 levelPos;
144848
+ float levelSize;
144849
+ float levelAlpha;
144850
+
144851
+ // 10m grid with colored main axes
144852
+ levelPos = pos * 0.1;
144853
+ levelSize = 2.0 / 1000.0;
144854
+ levelAlpha = pristineGrid(levelPos, ddx * 0.1, ddy * 0.1, vec2(levelSize)) * fade;
144855
+ if (levelAlpha > epsilon) {
144856
+ vec3 color;
144857
+ vec2 loc = abs(levelPos);
144858
+ if (loc.x < levelSize) {
144859
+ if (loc.y < levelSize) {
144860
+ color = vec3(1.0);
144861
+ } else {
144862
+ color = colors[axis1[plane]];
144863
+ }
144864
+ } else if (loc.y < levelSize) {
144865
+ color = colors[axis0[plane]];
144866
+ } else {
144867
+ color = vec3(0.9);
144868
+ }
144869
+ gl_FragColor = vec4(color, levelAlpha);
144870
+ gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144871
+ return;
144872
+ }
144873
+
144874
+ // 1m grid
144875
+ levelPos = pos;
144876
+ levelSize = 1.0 / 100.0;
144877
+ levelAlpha = pristineGrid(levelPos, ddx, ddy, vec2(levelSize)) * fade;
144878
+ if (levelAlpha > epsilon) {
144879
+ gl_FragColor = vec4(vec3(0.7), levelAlpha);
144880
+ gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144881
+ return;
144882
+ }
144883
+
144884
+ // 0.1m grid
144885
+ levelPos = pos * 10.0;
144886
+ levelSize = 1.0 / 100.0;
144887
+ levelAlpha = pristineGrid(levelPos, ddx * 10.0, ddy * 10.0, vec2(levelSize)) * fade;
144888
+ if (levelAlpha > epsilon) {
144889
+ gl_FragColor = vec4(vec3(0.7), levelAlpha);
144890
+ gl_FragDepth = writeDepth(levelAlpha) ? calcDepth(worldPos) : 1.0;
144891
+ return;
144892
+ }
144893
+
144894
+ discard;
144895
+ }
144843
144896
  `;
144844
144897
 
144845
144898
  const resolve = (scope, values) => {
@@ -144910,36 +144963,36 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
144910
144963
  }
144911
144964
  }
144912
144965
 
144913
- const vertexShader$2 = /* glsl*/ `
144914
- attribute vec2 vertex_position;
144915
- void main(void) {
144916
- gl_Position = vec4(vertex_position, 0.0, 1.0);
144917
- }
144918
- `;
144919
- const fragmentShader$2 = /* glsl*/ `
144920
- uniform sampler2D outlineTexture;
144921
- uniform float alphaCutoff;
144922
- uniform vec4 clr;
144923
-
144924
- void main(void) {
144925
- ivec2 texel = ivec2(gl_FragCoord.xy);
144926
-
144927
- // skip solid pixels
144928
- if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
144929
- discard;
144930
- }
144931
-
144932
- for (int x = -2; x <= 2; x++) {
144933
- for (int y = -2; y <= 2; y++) {
144934
- if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
144935
- gl_FragColor = clr;
144936
- return;
144937
- }
144938
- }
144939
- }
144940
-
144941
- discard;
144942
- }
144966
+ const vertexShader$2 = /* glsl*/ `
144967
+ attribute vec2 vertex_position;
144968
+ void main(void) {
144969
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
144970
+ }
144971
+ `;
144972
+ const fragmentShader$2 = /* glsl*/ `
144973
+ uniform sampler2D outlineTexture;
144974
+ uniform float alphaCutoff;
144975
+ uniform vec4 clr;
144976
+
144977
+ void main(void) {
144978
+ ivec2 texel = ivec2(gl_FragCoord.xy);
144979
+
144980
+ // skip solid pixels
144981
+ if (texelFetch(outlineTexture, texel, 0).a > alphaCutoff) {
144982
+ discard;
144983
+ }
144984
+
144985
+ for (int x = -2; x <= 2; x++) {
144986
+ for (int y = -2; y <= 2; y++) {
144987
+ if ((x != 0) && (y != 0) && (texelFetch(outlineTexture, texel + ivec2(x, y), 0).a > alphaCutoff)) {
144988
+ gl_FragColor = clr;
144989
+ return;
144990
+ }
144991
+ }
144992
+ }
144993
+
144994
+ discard;
144995
+ }
144943
144996
  `;
144944
144997
 
144945
144998
  class Outline extends Element$1 {
@@ -145257,72 +145310,72 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
145257
145310
  }
145258
145311
  }
145259
145312
 
145260
- const vertexShader$1 = /* glsl */ `
145261
- attribute uint vertex_id;
145262
-
145263
- uniform mat4 matrix_model;
145264
- uniform mat4 matrix_viewProjection;
145265
-
145266
- uniform sampler2D splatState;
145267
- uniform highp usampler2D splatPosition;
145268
- uniform highp usampler2D splatTransform; // per-splat index into transform palette
145269
- uniform sampler2D transformPalette; // palette of transform matrices
145270
-
145271
- uniform uvec2 texParams;
145272
-
145273
- uniform float splatSize;
145274
- uniform vec4 selectedClr;
145275
- uniform vec4 unselectedClr;
145276
-
145277
- varying vec4 varying_color;
145278
-
145279
- // calculate the current splat index and uv
145280
- ivec2 calcSplatUV(uint index, uint width) {
145281
- return ivec2(int(index % width), int(index / width));
145282
- }
145283
-
145284
- void main(void) {
145285
- ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
145286
- uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
145287
-
145288
- if ((splatState & 6u) != 0u) {
145289
- // deleted or locked (4 or 2)
145290
- gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
145291
- gl_PointSize = 0.0;
145292
- } else {
145293
- mat4 model = matrix_model;
145294
-
145295
- // handle per-splat transform
145296
- uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
145297
- if (transformIndex > 0u) {
145298
- // read transform matrix
145299
- int u = int(transformIndex % 512u) * 3;
145300
- int v = int(transformIndex / 512u);
145301
-
145302
- mat4 t;
145303
- t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
145304
- t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
145305
- t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
145306
- t[3] = vec4(0.0, 0.0, 0.0, 1.0);
145307
-
145308
- model = matrix_model * transpose(t);
145309
- }
145310
-
145311
- varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
145312
-
145313
- vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
145314
-
145315
- gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
145316
- gl_PointSize = splatSize;
145317
- }
145318
- }
145319
- `;
145320
- const fragmentShader$1 = /* glsl */ `
145321
- varying vec4 varying_color;
145322
-
145323
- void main(void) {
145324
- gl_FragColor = varying_color;
145325
- }
145313
+ const vertexShader$1 = /* glsl */ `
145314
+ attribute uint vertex_id;
145315
+
145316
+ uniform mat4 matrix_model;
145317
+ uniform mat4 matrix_viewProjection;
145318
+
145319
+ uniform sampler2D splatState;
145320
+ uniform highp usampler2D splatPosition;
145321
+ uniform highp usampler2D splatTransform; // per-splat index into transform palette
145322
+ uniform sampler2D transformPalette; // palette of transform matrices
145323
+
145324
+ uniform uvec2 texParams;
145325
+
145326
+ uniform float splatSize;
145327
+ uniform vec4 selectedClr;
145328
+ uniform vec4 unselectedClr;
145329
+
145330
+ varying vec4 varying_color;
145331
+
145332
+ // calculate the current splat index and uv
145333
+ ivec2 calcSplatUV(uint index, uint width) {
145334
+ return ivec2(int(index % width), int(index / width));
145335
+ }
145336
+
145337
+ void main(void) {
145338
+ ivec2 splatUV = calcSplatUV(vertex_id, texParams.x);
145339
+ uint splatState = uint(texelFetch(splatState, splatUV, 0).r * 255.0);
145340
+
145341
+ if ((splatState & 6u) != 0u) {
145342
+ // deleted or locked (4 or 2)
145343
+ gl_Position = vec4(0.0, 0.0, 2.0, 1.0);
145344
+ gl_PointSize = 0.0;
145345
+ } else {
145346
+ mat4 model = matrix_model;
145347
+
145348
+ // handle per-splat transform
145349
+ uint transformIndex = texelFetch(splatTransform, splatUV, 0).r;
145350
+ if (transformIndex > 0u) {
145351
+ // read transform matrix
145352
+ int u = int(transformIndex % 512u) * 3;
145353
+ int v = int(transformIndex / 512u);
145354
+
145355
+ mat4 t;
145356
+ t[0] = texelFetch(transformPalette, ivec2(u, v), 0);
145357
+ t[1] = texelFetch(transformPalette, ivec2(u + 1, v), 0);
145358
+ t[2] = texelFetch(transformPalette, ivec2(u + 2, v), 0);
145359
+ t[3] = vec4(0.0, 0.0, 0.0, 1.0);
145360
+
145361
+ model = matrix_model * transpose(t);
145362
+ }
145363
+
145364
+ varying_color = (splatState == 1u) ? selectedClr : unselectedClr;
145365
+
145366
+ vec3 center = uintBitsToFloat(texelFetch(splatPosition, splatUV, 0).xyz);
145367
+
145368
+ gl_Position = matrix_viewProjection * model * vec4(center, 1.0);
145369
+ gl_PointSize = splatSize;
145370
+ }
145371
+ }
145372
+ `;
145373
+ const fragmentShader$1 = /* glsl */ `
145374
+ varying vec4 varying_color;
145375
+
145376
+ void main(void) {
145377
+ gl_FragColor = varying_color;
145378
+ }
145326
145379
  `;
145327
145380
 
145328
145381
  class SplatOverlay extends Element$1 {
@@ -145411,19 +145464,19 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
145411
145464
  }
145412
145465
  }
145413
145466
 
145414
- const vertexShader = /* glsl*/ `
145415
- attribute vec2 vertex_position;
145416
- void main(void) {
145417
- gl_Position = vec4(vertex_position, 0.0, 1.0);
145418
- }
145467
+ const vertexShader = /* glsl*/ `
145468
+ attribute vec2 vertex_position;
145469
+ void main(void) {
145470
+ gl_Position = vec4(vertex_position, 0.0, 1.0);
145471
+ }
145419
145472
  `;
145420
- const fragmentShader = /* glsl*/ `
145421
- uniform sampler2D blitTexture;
145422
- void main(void) {
145423
- ivec2 texel = ivec2(gl_FragCoord.xy);
145424
-
145425
- gl_FragColor = texelFetch(blitTexture, texel, 0);
145426
- }
145473
+ const fragmentShader = /* glsl*/ `
145474
+ uniform sampler2D blitTexture;
145475
+ void main(void) {
145476
+ ivec2 texel = ivec2(gl_FragCoord.xy);
145477
+
145478
+ gl_FragColor = texelFetch(blitTexture, texel, 0);
145479
+ }
145427
145480
  `;
145428
145481
 
145429
145482
  class Underlay extends Element$1 {
@@ -147336,6 +147389,26 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147336
147389
  console.warn('SupersplatAdapter: Failed to toggle camera controls', e);
147337
147390
  }
147338
147391
  }
147392
+ /**
147393
+ * Configure granular control over specific camera input types.
147394
+ * Allows enabling/disabling orbit, pan, and zoom independently.
147395
+ */
147396
+ setCameraInputControls(options) {
147397
+ const controller = this.scene?.camera?.controller;
147398
+ if (!controller)
147399
+ return;
147400
+ try {
147401
+ if (typeof controller.setInputControls === 'function') {
147402
+ controller.setInputControls(options);
147403
+ }
147404
+ else {
147405
+ console.warn('SupersplatAdapter: Camera controller does not support setInputControls');
147406
+ }
147407
+ }
147408
+ catch (e) {
147409
+ console.warn('SupersplatAdapter: Failed to set camera input controls', e);
147410
+ }
147411
+ }
147339
147412
  /**
147340
147413
  * Enable/disable supersplat-core camera auto-update (orbit tweens).
147341
147414
  * When enabled, the camera entity transform may be driven by an external controller (fly mode).
@@ -147700,6 +147773,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147700
147773
  };
147701
147774
  this.enableStats = false;
147702
147775
  this.autoFocus = true;
147776
+ this.previewMode = false;
147703
147777
  this.isLoading = false;
147704
147778
  this.hasModel = false;
147705
147779
  this.error = null;
@@ -147752,6 +147826,11 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147752
147826
  this.canvas = options.canvas || null;
147753
147827
  this.enableStats = options.enableStats || false;
147754
147828
  this.autoFocus = options.autoFocus !== false;
147829
+ this.previewMode = options.previewMode || false;
147830
+ // In preview mode, always enable auto-focus
147831
+ if (this.previewMode) {
147832
+ this.autoFocus = true;
147833
+ }
147755
147834
  this._navigationCubeConfig = options.navigationCube || null;
147756
147835
  if (options.onStatsUpdate !== undefined) {
147757
147836
  this._onStatsUpdate = options.onStatsUpdate;
@@ -147782,11 +147861,20 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147782
147861
  const emitEvent = (type, detail) => {
147783
147862
  this.emit({ type, detail });
147784
147863
  };
147785
- this._supersplat = new SupersplatAdapter(this.canvas, this._navigationCubeConfig, emitEvent);
147864
+ this._supersplat = new SupersplatAdapter(this.canvas,
147865
+ // Disable navigation cube in preview mode
147866
+ this.previewMode ? null : this._navigationCubeConfig, emitEvent);
147786
147867
  this._supersplatReady = this._supersplat.init();
147787
147868
  this._supersplatReady
147788
147869
  ?.then(() => {
147789
- this._setupFlyCameraForSupersplat();
147870
+ // In preview mode, enable zoom only (disable orbit and pan)
147871
+ if (this.previewMode && this._supersplat) {
147872
+ this._supersplat.setCameraInputControls?.({ orbit: false, pan: false, zoom: true });
147873
+ }
147874
+ // Only set up fly camera if not in preview mode
147875
+ if (!this.previewMode) {
147876
+ this._setupFlyCameraForSupersplat();
147877
+ }
147790
147878
  })
147791
147879
  .catch(error => {
147792
147880
  console.error('SplatViewerCore.init: Failed to set up fly camera for supersplat path', error);
@@ -147825,7 +147913,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147825
147913
  // SuperSplat's PCApp omits ScriptComponentSystem, so `addComponent('script')`
147826
147914
  // will not produce `camera.script.create()`. We keep the attempt (in case
147827
147915
  // the underlying app changes), but also support a controller-based fallback.
147828
- if (cameraAny && !cameraAny.script && typeof cameraAny.addComponent === 'function') {
147916
+ if (cameraAny &&
147917
+ !cameraAny.script &&
147918
+ typeof cameraAny.addComponent === 'function') {
147829
147919
  try {
147830
147920
  cameraAny.addComponent('script');
147831
147921
  }
@@ -147851,7 +147941,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
147851
147941
  // Prefer script-based fly when available; fallback to controller otherwise.
147852
147942
  const canCreateScript = typeof cameraAny?.script?.create === 'function';
147853
147943
  if (canCreateScript) {
147854
- const created = cameraAny.script.create('flyCamera', { attributes: flyAttributes });
147944
+ const created = cameraAny.script.create('flyCamera', {
147945
+ attributes: flyAttributes,
147946
+ });
147855
147947
  this._fly = created;
147856
147948
  if (this._fly) {
147857
147949
  ;
@@ -149715,6 +149807,11 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
149715
149807
  // Camera Mode / Fly Camera API
149716
149808
  // ==========================================
149717
149809
  setCameraMode(mode) {
149810
+ // Prevent camera mode changes in preview mode
149811
+ if (this.previewMode) {
149812
+ console.warn('SplatViewerCore.setCameraMode: Camera controls are disabled in preview mode');
149813
+ return;
149814
+ }
149718
149815
  // supersplat-core path: manage mode switching explicitly (camera entity is updated by supersplat-core each frame)
149719
149816
  if (this._supersplat) {
149720
149817
  const prev = this._cameraMode;
@@ -149744,7 +149841,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
149744
149841
  try {
149745
149842
  const pos = this.entities.camera?.getPosition?.();
149746
149843
  if (pos) {
149747
- const posVec = pos.clone ? pos.clone() : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
149844
+ const posVec = pos.clone
149845
+ ? pos.clone()
149846
+ : new Vec3(pos.x || 0, pos.y || 0, pos.z || 0);
149748
149847
  this.entities.camera?.setPosition?.(posVec);
149749
149848
  }
149750
149849
  const euler = this.entities.camera?.getEulerAngles?.();
@@ -149967,8 +150066,8 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
149967
150066
  panSensitivity: 1.0,
149968
150067
  zoomSensitivity: 0.1,
149969
150068
  };
149970
- // Add navigation cube configuration if available
149971
- if (this._navigationCubeConfig) {
150069
+ // Add navigation cube configuration if available (but not in preview mode)
150070
+ if (this._navigationCubeConfig && !this.previewMode) {
149972
150071
  orbitAttributes.enableNavigationCube =
149973
150072
  this._navigationCubeConfig.enabled || false;
149974
150073
  this.entities.camera._navigationCubeConfig =
@@ -149983,48 +150082,58 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
149983
150082
  detail: { type: interactionType },
149984
150083
  });
149985
150084
  };
150085
+ // In preview mode, enable zoom only (disable orbit and pan)
150086
+ if (this.previewMode && this._orbit) {
150087
+ const orbitAny = this._orbit;
150088
+ if (typeof orbitAny.setInputControls === 'function') {
150089
+ // Enable zoom, disable orbit and pan
150090
+ orbitAny.setInputControls({ orbit: false, pan: false, zoom: true });
150091
+ }
150092
+ }
149986
150093
  this.entities.camera.setPosition(0, 0, 10);
149987
150094
  this.entities.camera.lookAt(Vec3.ZERO);
149988
150095
  // ==============================
149989
- // Setup fly camera (disabled by default)
150096
+ // Setup fly camera (disabled by default, skipped in preview mode)
149990
150097
  // ==============================
149991
- try {
149992
- registerFlyCameraScript();
149993
- // Ensure script component exists (created above)
149994
- const flyAttributes = {
149995
- moveSpeed: DEFAULT_FLY_CAMERA_CONFIG.moveSpeed,
149996
- fastSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.fastSpeedMultiplier,
149997
- slowSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.slowSpeedMultiplier,
149998
- lookSensitivity: DEFAULT_FLY_CAMERA_CONFIG.lookSensitivity,
149999
- invertY: DEFAULT_FLY_CAMERA_CONFIG.invertY,
150000
- keyBindings: DEFAULT_FLY_CAMERA_CONFIG.keyBindings,
150001
- smoothing: DEFAULT_FLY_CAMERA_CONFIG.smoothing,
150002
- friction: DEFAULT_FLY_CAMERA_CONFIG.friction,
150003
- enableCollision: DEFAULT_FLY_CAMERA_CONFIG.enableCollision,
150004
- minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
150005
- maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
150006
- };
150007
- this._fly = this.entities.camera.script.create('flyCamera', {
150008
- attributes: flyAttributes,
150009
- });
150010
- // Wire event emission to core
150011
- if (this._fly) {
150012
- ;
150013
- this._fly.emitFlyEvent = (type, detail) => {
150014
- this.emit({ type: type, detail });
150098
+ if (!this.previewMode) {
150099
+ try {
150100
+ registerFlyCameraScript();
150101
+ // Ensure script component exists (created above)
150102
+ const flyAttributes = {
150103
+ moveSpeed: DEFAULT_FLY_CAMERA_CONFIG.moveSpeed,
150104
+ fastSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.fastSpeedMultiplier,
150105
+ slowSpeedMultiplier: DEFAULT_FLY_CAMERA_CONFIG.slowSpeedMultiplier,
150106
+ lookSensitivity: DEFAULT_FLY_CAMERA_CONFIG.lookSensitivity,
150107
+ invertY: DEFAULT_FLY_CAMERA_CONFIG.invertY,
150108
+ keyBindings: DEFAULT_FLY_CAMERA_CONFIG.keyBindings,
150109
+ smoothing: DEFAULT_FLY_CAMERA_CONFIG.smoothing,
150110
+ friction: DEFAULT_FLY_CAMERA_CONFIG.friction,
150111
+ enableCollision: DEFAULT_FLY_CAMERA_CONFIG.enableCollision,
150112
+ minHeight: DEFAULT_FLY_CAMERA_CONFIG.minHeight,
150113
+ maxHeight: DEFAULT_FLY_CAMERA_CONFIG.maxHeight,
150015
150114
  };
150115
+ this._fly = this.entities.camera.script.create('flyCamera', {
150116
+ attributes: flyAttributes,
150117
+ });
150118
+ // Wire event emission to core
150119
+ if (this._fly) {
150120
+ ;
150121
+ this._fly.emitFlyEvent = (type, detail) => {
150122
+ this.emit({ type: type, detail });
150123
+ };
150124
+ }
150125
+ // Deactivate fly by default; orbit is the initial mode
150126
+ if (this._fly?.deactivate) {
150127
+ this._fly.deactivate();
150128
+ }
150129
+ // Initialize camera mode manager
150130
+ this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
150131
+ this.emit({ type: eventType, detail });
150132
+ }, 'orbit');
150016
150133
  }
150017
- // Deactivate fly by default; orbit is the initial mode
150018
- if (this._fly?.deactivate) {
150019
- this._fly.deactivate();
150134
+ catch (e) {
150135
+ console.warn('Failed to set up fly camera', e);
150020
150136
  }
150021
- // Initialize camera mode manager
150022
- this._cameraModeManager = new CameraModeManager(this.app, this.entities.camera, this._orbit, this._fly, (eventType, detail) => {
150023
- this.emit({ type: eventType, detail });
150024
- }, 'orbit');
150025
- }
150026
- catch (e) {
150027
- console.warn('Failed to set up fly camera', e);
150028
150137
  }
150029
150138
  }
150030
150139
  _setupStats() {
@@ -150269,6 +150378,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
150269
150378
  'enable-stats',
150270
150379
  'auto-focus',
150271
150380
  'max-splats',
150381
+ 'preview-mode',
150272
150382
  'camera-position',
150273
150383
  'camera-target',
150274
150384
  'orbit-sensitivity',
@@ -150299,6 +150409,9 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
150299
150409
  get autoFocus() {
150300
150410
  return this.hasAttribute('auto-focus');
150301
150411
  }
150412
+ get previewMode() {
150413
+ return this.hasAttribute('preview-mode');
150414
+ }
150302
150415
  get maxSplats() {
150303
150416
  return this.getAttribute('max-splats');
150304
150417
  }
@@ -150376,6 +150489,14 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
150376
150489
  this.removeAttribute('auto-focus');
150377
150490
  }
150378
150491
  }
150492
+ set previewMode(value) {
150493
+ if (value) {
150494
+ this.setAttribute('preview-mode', '');
150495
+ }
150496
+ else {
150497
+ this.removeAttribute('preview-mode');
150498
+ }
150499
+ }
150379
150500
  set maxSplats(value) {
150380
150501
  if (value === null) {
150381
150502
  this.removeAttribute('max-splats');
@@ -150557,6 +150678,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
150557
150678
  return value.length > 0;
150558
150679
  case 'enable-stats':
150559
150680
  case 'auto-focus':
150681
+ case 'preview-mode':
150560
150682
  case 'enable-navigation-cube':
150561
150683
  // Boolean attributes - any value is valid
150562
150684
  return true;
@@ -151503,7 +151625,7 @@ bool initCenter(SplatSource source, vec3 modelCenter, out SplatCenter center) {
151503
151625
  if (!this._core) {
151504
151626
  throw new Error('SplatViewerElement: Core not initialized. Call connectedCallback first.');
151505
151627
  }
151506
- return this._core.selectSplatsInSphere(center, radius, modelId, addToSelection);
151628
+ return this._core.selectSplatsInSphere(new Vec3(center.x, center.y, center.z), radius, modelId, addToSelection);
151507
151629
  }
151508
151630
  clearSplatSelection() {
151509
151631
  if (!this._core) {