notaneimu commited on
Commit
e333b5d
·
1 Parent(s): aef63b7

update viewer

Browse files
Files changed (5) hide show
  1. .DS_Store +0 -0
  2. viewer/index.css +33 -0
  3. viewer/index.html +58 -7
  4. viewer/index.js +173 -17
  5. viewer/index.js.map +0 -0
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
viewer/index.css CHANGED
@@ -242,6 +242,39 @@ button {
242
  #settingsPanel > .settingsRow > div:hover {
243
  color: #E0DCDD;
244
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  #settingsPanel > .divider {
246
  width: 100%;
247
  height: 1px;
 
242
  #settingsPanel > .settingsRow > div:hover {
243
  color: #E0DCDD;
244
  }
245
+ #settingsPanel .sliderContainer {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: 4px;
249
+ }
250
+ #settingsPanel .slider {
251
+ display: flex;
252
+ flex-direction: column;
253
+ align-items: flex-start;
254
+ gap: 2px;
255
+ padding: 4px 8px;
256
+ }
257
+ #settingsPanel .slider .sliderHeader {
258
+ display: flex;
259
+ justify-content: space-between;
260
+ align-items: center;
261
+ width: 100%;
262
+ }
263
+ #settingsPanel .slider .sliderHeader label {
264
+ font-size: 11px;
265
+ color: #AAA;
266
+ text-transform: uppercase;
267
+ }
268
+ #settingsPanel .slider .sliderHeader span {
269
+ font-size: 11px;
270
+ color: #fff;
271
+ font-variant-numeric: tabular-nums;
272
+ }
273
+ #settingsPanel .slider input[type=range] {
274
+ width: 100%;
275
+ cursor: pointer;
276
+ accent-color: #fff;
277
+ }
278
  #settingsPanel > .divider {
279
  width: 100%;
280
  height: 1px;
viewer/index.html CHANGED
@@ -18,7 +18,7 @@
18
  const posterUrl = url.searchParams.get('poster');
19
  const skyboxUrl = url.searchParams.get('skybox');
20
  const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';
21
- const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.ply';
22
 
23
  const sseConfig = {
24
  poster: posterUrl && createImage(posterUrl),
@@ -27,6 +27,7 @@
27
  contents: fetch(contentUrl),
28
  noui: url.searchParams.has('noui'),
29
  noanim: url.searchParams.has('noanim'),
 
30
  ministats: url.searchParams.has('ministats'),
31
  colorize: url.searchParams.has('colorize'),
32
  unified: url.searchParams.has('unified'),
@@ -91,13 +92,13 @@
91
 
92
  <div class="spacer"></div>
93
 
94
- <button id="orbitCamera" class="controlButton toggle left">
95
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
96
  <g class='stroke'><use href="#orbitIcon"/></g>
97
  <g class='fill'><use href="#orbitIcon"/></g>
98
  </svg>
99
  </button>
100
- <button id="flyCamera" class="controlButton toggle right">
101
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
102
  <g class='stroke'><use href="#flyIcon"/></g>
103
  <g class='fill'><use href="#flyIcon"/></g>
@@ -147,16 +148,54 @@
147
 
148
  <!-- Settings Panel -->
149
  <div id="settingsPanel" class="hidden">
150
- <div class="settingsRow">
151
  <div id="hqCheck" class="checkMark">✓</div>
152
  <div id="hqOption">High Quality Render</div>
153
  </div>
154
- <div class="settingsRow">
155
  <div id="lqCheck" class="checkMark">✓</div>
156
  <div id="lqOption">Low Quality Render</div>
157
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  <div class="divider"></div>
159
  <div class="settingsRow">
 
 
 
 
160
  <button id="frame">Frame</button>
161
  <button id="reset">Reset</button>
162
  </div>
@@ -196,8 +235,20 @@
196
  <span class="control-key">Left Mouse</span>
197
  </div>
198
  <div class="control-item">
199
- <span class="control-action">Fly</span>
200
- <span class="control-key">W,S,A,D</span>
 
 
 
 
 
 
 
 
 
 
 
 
201
  </div>
202
  <div class="control-spacer"></div>
203
  <div class="control-item">
 
18
  const posterUrl = url.searchParams.get('poster');
19
  const skyboxUrl = url.searchParams.get('skybox');
20
  const settingsUrl = url.searchParams.has('settings') ? url.searchParams.get('settings') : './settings.json';
21
+ const contentUrl = url.searchParams.has('content') ? url.searchParams.get('content') : './scene.compressed.ply';
22
 
23
  const sseConfig = {
24
  poster: posterUrl && createImage(posterUrl),
 
27
  contents: fetch(contentUrl),
28
  noui: url.searchParams.has('noui'),
29
  noanim: url.searchParams.has('noanim'),
30
+ minimal: url.searchParams.has('minimal'),
31
  ministats: url.searchParams.has('ministats'),
32
  colorize: url.searchParams.has('colorize'),
33
  unified: url.searchParams.has('unified'),
 
92
 
93
  <div class="spacer"></div>
94
 
95
+ <button id="orbitCamera" class="controlButton toggle left hidden">
96
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
97
  <g class='stroke'><use href="#orbitIcon"/></g>
98
  <g class='fill'><use href="#orbitIcon"/></g>
99
  </svg>
100
  </button>
101
+ <button id="flyCamera" class="controlButton toggle right hidden">
102
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
103
  <g class='stroke'><use href="#flyIcon"/></g>
104
  <g class='fill'><use href="#flyIcon"/></g>
 
148
 
149
  <!-- Settings Panel -->
150
  <div id="settingsPanel" class="hidden">
151
+ <div class="settingsRow hidden">
152
  <div id="hqCheck" class="checkMark">✓</div>
153
  <div id="hqOption">High Quality Render</div>
154
  </div>
155
+ <div class="settingsRow hidden">
156
  <div id="lqCheck" class="checkMark">✓</div>
157
  <div id="lqOption">Low Quality Render</div>
158
  </div>
159
+ <div class="divider hidden"></div>
160
+ <div class="settingsRow sliderContainer">
161
+ <div class="slider">
162
+ <div class="sliderHeader">
163
+ <label>Hue</label>
164
+ <span id="bgHueValue">0°</span>
165
+ </div>
166
+ <input id="bgHue" type="range" min="0" max="360" step="1">
167
+ </div>
168
+ <div class="slider">
169
+ <div class="sliderHeader">
170
+ <label>Saturation</label>
171
+ <span id="bgSaturationValue">0%</span>
172
+ </div>
173
+ <input id="bgSaturation" type="range" min="0" max="100" step="1">
174
+ </div>
175
+ <div class="slider">
176
+ <div class="sliderHeader">
177
+ <label>Lightness</label>
178
+ <span id="bgLightnessValue">0%</span>
179
+ </div>
180
+ <input id="bgLightness" type="range" min="0" max="100" step="1">
181
+ </div>
182
+ </div>
183
+ <div class="divider"></div>
184
+ <div class="settingsRow">
185
+ <div class="slider">
186
+ <div class="sliderHeader">
187
+ <label>FOV</label>
188
+ <span id="fovValue">0°</span>
189
+ </div>
190
+ <input id="fov" type="range" min="1" max="180" step="1">
191
+ </div>
192
+ </div>
193
  <div class="divider"></div>
194
  <div class="settingsRow">
195
+ <button id="screenshot">Capture Image</button>
196
+ </div>
197
+ <div class="divider hidden"></div>
198
+ <div class="settingsRow hidden">
199
  <button id="frame">Frame</button>
200
  <button id="reset">Reset</button>
201
  </div>
 
235
  <span class="control-key">Left Mouse</span>
236
  </div>
237
  <div class="control-item">
238
+ <span class="control-action">Fly Forward / Backward</span>
239
+ <span class="control-key">W / S or Up / Down Arrows</span>
240
+ </div>
241
+ <div class="control-item">
242
+ <span class="control-action">Fly Left / Right</span>
243
+ <span class="control-key">A / D or Left / Right Arrows</span>
244
+ </div>
245
+ <div class="control-item">
246
+ <span class="control-action">Fly Up / Down</span>
247
+ <span class="control-key">Q / E</span>
248
+ </div>
249
+ <div class="control-item">
250
+ <span class="control-action">Fast / Slow</span>
251
+ <span class="control-key">Shift / Ctrl</span>
252
  </div>
253
  <div class="control-spacer"></div>
254
  <div class="control-item">
viewer/index.js CHANGED
@@ -101579,7 +101579,9 @@ const initUI = (global) => {
101579
  'settings', 'settingsPanel',
101580
  'orbitCamera', 'flyCamera',
101581
  'hqCheck', 'hqOption', 'lqCheck', 'lqOption',
101582
- 'reset', 'frame',
 
 
101583
  'loadingText', 'loadingBar',
101584
  'joystickBase', 'joystick',
101585
  'tooltip'
@@ -101739,7 +101741,7 @@ const initUI = (global) => {
101739
  state.animationPaused = true;
101740
  });
101741
  const updatePlayPause = () => {
101742
- if (state.cameraMode !== 'anim' || state.animationPaused) {
101743
  dom.play.classList.remove('hidden');
101744
  dom.pause.classList.add('hidden');
101745
  }
@@ -101747,12 +101749,16 @@ const initUI = (global) => {
101747
  dom.play.classList.add('hidden');
101748
  dom.pause.classList.remove('hidden');
101749
  }
101750
- if (state.cameraMode === 'anim') {
101751
  dom.timelineContainer.classList.remove('hidden');
101752
  }
101753
  else {
101754
  dom.timelineContainer.classList.add('hidden');
101755
  }
 
 
 
 
101756
  };
101757
  // Update UI on animation changes
101758
  events.on('cameraMode:changed', updatePlayPause);
@@ -101797,10 +101803,19 @@ const initUI = (global) => {
101797
  });
101798
  });
101799
  // Camera mode UI
101800
- events.on('cameraMode:changed', () => {
101801
  dom.orbitCamera.classList[state.cameraMode === 'orbit' ? 'add' : 'remove']('active');
101802
  dom.flyCamera.classList[state.cameraMode === 'fly' ? 'add' : 'remove']('active');
101803
- });
 
 
 
 
 
 
 
 
 
101804
  dom.settings.addEventListener('click', () => {
101805
  dom.settingsPanel.classList.toggle('hidden');
101806
  });
@@ -101816,6 +101831,37 @@ const initUI = (global) => {
101816
  dom.frame.addEventListener('click', (event) => {
101817
  events.fire('inputEvent', 'frame', event);
101818
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101819
  // update UI based on touch joystick updates
101820
  events.on('touchJoystickUpdate', (base, stick) => {
101821
  if (base === null) {
@@ -102813,12 +102859,12 @@ class CameraManager {
102813
  // set the global animation flag
102814
  state.hasAnimation = !!controllers.anim;
102815
  state.animationDuration = controllers.anim ? controllers.anim.animState.cursor.duration : 0;
102816
- // initialize camera mode and initial camera position
102817
- state.cameraMode = state.hasAnimation ? 'anim' : (isObjectExperience ? 'orbit' : 'fly');
102818
  this.camera.copy(resetCamera);
102819
  const target = new Camera(this.camera); // the active controller updates this
102820
  const from = new Camera(this.camera); // stores the previous camera state during transition
102821
- let fromMode = isObjectExperience ? 'orbit' : 'fly';
102822
  // enter the initial controller
102823
  getController(state.cameraMode).onEnter(this.camera);
102824
  // transition time between cameras
@@ -102843,6 +102889,10 @@ class CameraManager {
102843
  if (state.cameraMode === 'anim') {
102844
  state.animationTime = controllers.anim.animState.cursor.value;
102845
  }
 
 
 
 
102846
  };
102847
  // handle input events
102848
  events.on('inputEvent', (eventName, event) => {
@@ -102856,7 +102906,7 @@ class CameraManager {
102856
  controllers.orbit.goto(resetCamera);
102857
  break;
102858
  case 'playPause':
102859
- if (state.hasAnimation) {
102860
  if (state.cameraMode === 'anim') {
102861
  state.animationPaused = !state.animationPaused;
102862
  }
@@ -102874,6 +102924,11 @@ class CameraManager {
102874
  break;
102875
  }
102876
  });
 
 
 
 
 
102877
  // handle camera mode switching
102878
  events.on('cameraMode:changed', (value, prev) => {
102879
  // store previous camera mode and pose
@@ -103234,7 +103289,10 @@ class InputController {
103234
  }
103235
  this._state.shift += key[keyCode.SHIFT];
103236
  this._state.ctrl += key[keyCode.CTRL];
103237
- if (state.cameraMode !== 'fly' && this._state.axis.length() > 0) {
 
 
 
103238
  state.cameraMode = 'fly';
103239
  }
103240
  const orbit = +(state.cameraMode === 'orbit');
@@ -103242,13 +103300,17 @@ class InputController {
103242
  const double = +(this._state.touches > 1);
103243
  const pan = this._state.mouse[2] || +(button[2] === -1) || double;
103244
  const orbitFactor = fly ? camera.fov / 120 : 1;
 
 
103245
  const { deltas } = this.frame;
103246
  // desktop move
103247
  const v = tmpV1.set(0, 0, 0);
103248
  const keyMove = this._state.axis.clone().normalize();
103249
- v.add(keyMove.mulScalar(fly * this.moveSpeed * (this._state.shift ? 4 : this._state.ctrl ? 0.25 : 1) * dt));
103250
- const panMove = screenToWorld(camera, mouse[0], mouse[1], distance);
103251
- v.add(panMove.mulScalar(pan));
 
 
103252
  const wheelMove = new Vec3(0, 0, -wheel[0]);
103253
  v.add(wheelMove.mulScalar(this.wheelSpeed * dt));
103254
  // FIXME: need to flip z axis for orbit camera
@@ -103260,8 +103322,10 @@ class InputController {
103260
  deltas.rotate.append([v.x, v.y, v.z]);
103261
  // mobile move
103262
  v.set(0, 0, 0);
103263
- const orbitMove = screenToWorld(camera, touch[0], touch[1], distance);
103264
- v.add(orbitMove.mulScalar(orbit * pan));
 
 
103265
  flyMove.set(leftInput[0], 0, -leftInput[1]);
103266
  v.add(flyMove.mulScalar(fly * this.moveSpeed * dt));
103267
  pinchMove.set(0, 0, pinch[0]);
@@ -103421,6 +103485,62 @@ class Viewer {
103421
  };
103422
  events.on('hqMode:changed', updateHqMode);
103423
  updateHqMode();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103424
  // construct debug ministats
103425
  if (config.ministats) {
103426
  const options = MiniStats.getDefaultOptions();
@@ -104288,7 +104408,7 @@ const main = (app, camera, settingsJson, config) => {
104288
  hqMode: true,
104289
  progress: 0,
104290
  inputMode: 'desktop',
104291
- cameraMode: 'orbit',
104292
  hasAnimation: false,
104293
  animationDuration: 0,
104294
  animationTime: 0,
@@ -104296,7 +104416,11 @@ const main = (app, camera, settingsJson, config) => {
104296
  hasAR: false,
104297
  hasVR: false,
104298
  isFullscreen: false,
104299
- controlsHidden: false
 
 
 
 
104300
  });
104301
  const global = {
104302
  app,
@@ -104306,6 +104430,38 @@ const main = (app, camera, settingsJson, config) => {
104306
  events,
104307
  camera
104308
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104309
  // Initialize the load-time poster
104310
  if (config.poster) {
104311
  initPoster(events);
 
101579
  'settings', 'settingsPanel',
101580
  'orbitCamera', 'flyCamera',
101581
  'hqCheck', 'hqOption', 'lqCheck', 'lqOption',
101582
+ 'reset', 'frame', 'screenshot',
101583
+ 'bgHue', 'bgSaturation', 'bgLightness', 'fov',
101584
+ 'bgHueValue', 'bgSaturationValue', 'bgLightnessValue', 'fovValue',
101585
  'loadingText', 'loadingBar',
101586
  'joystickBase', 'joystick',
101587
  'tooltip'
 
101741
  state.animationPaused = true;
101742
  });
101743
  const updatePlayPause = () => {
101744
+ if (config.minimal || state.cameraMode !== 'anim' || state.animationPaused) {
101745
  dom.play.classList.remove('hidden');
101746
  dom.pause.classList.add('hidden');
101747
  }
 
101749
  dom.play.classList.add('hidden');
101750
  dom.pause.classList.remove('hidden');
101751
  }
101752
+ if (!config.minimal && state.cameraMode === 'anim') {
101753
  dom.timelineContainer.classList.remove('hidden');
101754
  }
101755
  else {
101756
  dom.timelineContainer.classList.add('hidden');
101757
  }
101758
+ if (config.minimal) {
101759
+ dom.play.classList.add('hidden');
101760
+ dom.pause.classList.add('hidden');
101761
+ }
101762
  };
101763
  // Update UI on animation changes
101764
  events.on('cameraMode:changed', updatePlayPause);
 
101803
  });
101804
  });
101805
  // Camera mode UI
101806
+ const updateCameraMode = () => {
101807
  dom.orbitCamera.classList[state.cameraMode === 'orbit' ? 'add' : 'remove']('active');
101808
  dom.flyCamera.classList[state.cameraMode === 'fly' ? 'add' : 'remove']('active');
101809
+ };
101810
+ events.on('cameraMode:changed', updateCameraMode);
101811
+ updateCameraMode(); // Initialize on load
101812
+ // Hide mode buttons if nomode config is set
101813
+ if (config.minimal) {
101814
+ dom.orbitCamera.classList.add('hidden');
101815
+ dom.flyCamera.classList.add('hidden');
101816
+ dom.play.classList.add('hidden');
101817
+ dom.timelineContainer.classList.add('hidden');
101818
+ }
101819
  dom.settings.addEventListener('click', () => {
101820
  dom.settingsPanel.classList.toggle('hidden');
101821
  });
 
101831
  dom.frame.addEventListener('click', (event) => {
101832
  events.fire('inputEvent', 'frame', event);
101833
  });
101834
+ dom.screenshot.addEventListener('click', () => {
101835
+ events.fire('captureImage');
101836
+ });
101837
+ // Background HSL
101838
+ const updateSliders = () => {
101839
+ dom.bgHue.value = state.bgHue.toString();
101840
+ dom.bgSaturation.value = state.bgSaturation.toString();
101841
+ dom.bgLightness.value = state.bgLightness.toString();
101842
+ dom.fov.value = state.fov.toString();
101843
+ dom.bgHueValue.textContent = `${state.bgHue}°`;
101844
+ dom.bgSaturationValue.textContent = `${state.bgSaturation}%`;
101845
+ dom.bgLightnessValue.textContent = `${state.bgLightness}%`;
101846
+ dom.fovValue.textContent = `${Math.round(state.fov)}°`;
101847
+ };
101848
+ dom.bgHue.addEventListener('input', (event) => {
101849
+ state.bgHue = parseInt(event.target.value, 10);
101850
+ });
101851
+ dom.bgSaturation.addEventListener('input', (event) => {
101852
+ state.bgSaturation = parseInt(event.target.value, 10);
101853
+ });
101854
+ dom.bgLightness.addEventListener('input', (event) => {
101855
+ state.bgLightness = parseInt(event.target.value, 10);
101856
+ });
101857
+ dom.fov.addEventListener('input', (event) => {
101858
+ state.fov = parseInt(event.target.value, 10);
101859
+ });
101860
+ events.on('bgHue:changed', updateSliders);
101861
+ events.on('bgSaturation:changed', updateSliders);
101862
+ events.on('bgLightness:changed', updateSliders);
101863
+ events.on('fov:changed', updateSliders);
101864
+ updateSliders();
101865
  // update UI based on touch joystick updates
101866
  events.on('touchJoystickUpdate', (base, stick) => {
101867
  if (base === null) {
 
102859
  // set the global animation flag
102860
  state.hasAnimation = !!controllers.anim;
102861
  state.animationDuration = controllers.anim ? controllers.anim.animState.cursor.duration : 0;
102862
+ // initialize camera mode and initial camera position - always start in fly mode
102863
+ state.cameraMode = 'fly';
102864
  this.camera.copy(resetCamera);
102865
  const target = new Camera(this.camera); // the active controller updates this
102866
  const from = new Camera(this.camera); // stores the previous camera state during transition
102867
+ let fromMode = 'fly';
102868
  // enter the initial controller
102869
  getController(state.cameraMode).onEnter(this.camera);
102870
  // transition time between cameras
 
102889
  if (state.cameraMode === 'anim') {
102890
  state.animationTime = controllers.anim.animState.cursor.value;
102891
  }
102892
+ // sync fov state
102893
+ if (state.fov !== this.camera.fov) {
102894
+ state.fov = Math.round(this.camera.fov);
102895
+ }
102896
  };
102897
  // handle input events
102898
  events.on('inputEvent', (eventName, event) => {
 
102906
  controllers.orbit.goto(resetCamera);
102907
  break;
102908
  case 'playPause':
102909
+ if (state.hasAnimation && !global.config.minimal && !global.config.noanim) {
102910
  if (state.cameraMode === 'anim') {
102911
  state.animationPaused = !state.animationPaused;
102912
  }
 
102924
  break;
102925
  }
102926
  });
102927
+ // handle external fov changes
102928
+ events.on('fov:changed', (value) => {
102929
+ this.camera.fov = value;
102930
+ target.fov = value;
102931
+ });
102932
  // handle camera mode switching
102933
  events.on('cameraMode:changed', (value, prev) => {
102934
  // store previous camera mode and pose
 
103289
  }
103290
  this._state.shift += key[keyCode.SHIFT];
103291
  this._state.ctrl += key[keyCode.CTRL];
103292
+ // Switch to fly mode when using keyboard axis or zooming with wheel
103293
+ const hasWheelZoom = wheel[0] !== 0;
103294
+ const hasPinchZoom = pinch[0] !== 0;
103295
+ if (state.cameraMode !== 'fly' && (this._state.axis.length() > 0 || hasWheelZoom || hasPinchZoom)) {
103296
  state.cameraMode = 'fly';
103297
  }
103298
  const orbit = +(state.cameraMode === 'orbit');
 
103300
  const double = +(this._state.touches > 1);
103301
  const pan = this._state.mouse[2] || +(button[2] === -1) || double;
103302
  const orbitFactor = fly ? camera.fov / 120 : 1;
103303
+ // Speed multiplier for consistent movement (keyboard uses moveSpeed)
103304
+ const speedMultiplier = this._state.shift ? 4 : this._state.ctrl ? 0.25 : 1;
103305
  const { deltas } = this.frame;
103306
  // desktop move
103307
  const v = tmpV1.set(0, 0, 0);
103308
  const keyMove = this._state.axis.clone().normalize();
103309
+ v.add(keyMove.mulScalar(fly * this.moveSpeed * speedMultiplier * dt));
103310
+ // Pan movement - use minimum distance of 1 to ensure panning works in fly mode
103311
+ const panDistance = Math.max(distance, 1);
103312
+ const panMove = screenToWorld(camera, mouse[0], mouse[1], panDistance);
103313
+ v.add(panMove.mulScalar(pan * this.moveSpeed * 0.15));
103314
  const wheelMove = new Vec3(0, 0, -wheel[0]);
103315
  v.add(wheelMove.mulScalar(this.wheelSpeed * dt));
103316
  // FIXME: need to flip z axis for orbit camera
 
103322
  deltas.rotate.append([v.x, v.y, v.z]);
103323
  // mobile move
103324
  v.set(0, 0, 0);
103325
+ // Touch pan movement - use minimum distance for fly mode compatibility
103326
+ const touchPanDistance = Math.max(distance, 1);
103327
+ const orbitMove = screenToWorld(camera, touch[0], touch[1], touchPanDistance);
103328
+ v.add(orbitMove.mulScalar(orbit * pan * this.moveSpeed * 0.15));
103329
  flyMove.set(leftInput[0], 0, -leftInput[1]);
103330
  v.add(flyMove.mulScalar(fly * this.moveSpeed * dt));
103331
  pinchMove.set(0, 0, pinch[0]);
 
103485
  };
103486
  events.on('hqMode:changed', updateHqMode);
103487
  updateHqMode();
103488
+ // handle background HSL changes
103489
+ const hslToRgb = (h, s, l) => {
103490
+ h /= 360;
103491
+ s /= 100;
103492
+ l /= 100;
103493
+ let r, g, b;
103494
+ if (s === 0) {
103495
+ r = g = b = l; // achromatic
103496
+ }
103497
+ else {
103498
+ const hue2rgb = (p, q, t) => {
103499
+ if (t < 0)
103500
+ t += 1;
103501
+ if (t > 1)
103502
+ t -= 1;
103503
+ if (t < 1 / 6)
103504
+ return p + (q - p) * 6 * t;
103505
+ if (t < 1 / 2)
103506
+ return q;
103507
+ if (t < 2 / 3)
103508
+ return p + (q - p) * (2 / 3 - t) * 6;
103509
+ return p;
103510
+ };
103511
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
103512
+ const p = 2 * l - q;
103513
+ r = hue2rgb(p, q, h + 1 / 3);
103514
+ g = hue2rgb(p, q, h);
103515
+ b = hue2rgb(p, q, h - 1 / 3);
103516
+ }
103517
+ return new Color(r, g, b, 1);
103518
+ };
103519
+ const updateBackgroundColor = () => {
103520
+ const color = hslToRgb(state.bgHue, state.bgSaturation, state.bgLightness);
103521
+ camera.camera.clearColor = color;
103522
+ app.renderNextFrame = true;
103523
+ };
103524
+ events.on('bgHue:changed', updateBackgroundColor);
103525
+ events.on('bgSaturation:changed', updateBackgroundColor);
103526
+ events.on('bgLightness:changed', updateBackgroundColor);
103527
+ updateBackgroundColor();
103528
+ // handle screenshot capture
103529
+ events.on('captureImage', () => {
103530
+ app.renderNextFrame = true;
103531
+ app.once('frameend', () => {
103532
+ const canvas = app.graphicsDevice.canvas;
103533
+ const url = canvas.toDataURL('image/png');
103534
+ const link = document.createElement('a');
103535
+ // derive filename from contentUrl
103536
+ const contentUrl = global.config.contentUrl || 'screenshot.png';
103537
+ const filename = contentUrl.split('/').pop().split('?')[0].split('#')[0];
103538
+ const basename = filename.includes('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
103539
+ link.download = `${basename || 'screenshot'}.png`;
103540
+ link.href = url;
103541
+ link.click();
103542
+ });
103543
+ });
103544
  // construct debug ministats
103545
  if (config.ministats) {
103546
  const options = MiniStats.getDefaultOptions();
 
104408
  hqMode: true,
104409
  progress: 0,
104410
  inputMode: 'desktop',
104411
+ cameraMode: 'fly',
104412
  hasAnimation: false,
104413
  animationDuration: 0,
104414
  animationTime: 0,
 
104416
  hasAR: false,
104417
  hasVR: false,
104418
  isFullscreen: false,
104419
+ controlsHidden: false,
104420
+ bgHue: 0,
104421
+ bgSaturation: 0,
104422
+ bgLightness: 0,
104423
+ fov: 0
104424
  });
104425
  const global = {
104426
  app,
 
104430
  events,
104431
  camera
104432
  };
104433
+ // initialize HSL from background color
104434
+ const color = global.settings.background.color;
104435
+ const r = color[0] / 255;
104436
+ const g = color[1] / 255;
104437
+ const b = color[2] / 255;
104438
+ const max = Math.max(r, g, b);
104439
+ const min = Math.min(r, g, b);
104440
+ let h, s;
104441
+ const l = (max + min) / 2;
104442
+ if (max === min) {
104443
+ h = s = 0; // achromatic
104444
+ }
104445
+ else {
104446
+ const d = max - min;
104447
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
104448
+ switch (max) {
104449
+ case r:
104450
+ h = (g - b) / d + (g < b ? 6 : 0);
104451
+ break;
104452
+ case g:
104453
+ h = (b - r) / d + 2;
104454
+ break;
104455
+ case b:
104456
+ h = (r - g) / d + 4;
104457
+ break;
104458
+ }
104459
+ h /= 6;
104460
+ }
104461
+ state.bgHue = Math.round(h * 360);
104462
+ state.bgSaturation = Math.round(s * 100);
104463
+ state.bgLightness = Math.round(l * 100);
104464
+ state.fov = global.settings.cameras[0].initial.fov;
104465
  // Initialize the load-time poster
104466
  if (config.poster) {
104467
  initPoster(events);
viewer/index.js.map CHANGED
The diff for this file is too large to render. See raw diff