Spaces:
Sleeping
Sleeping
update viewer
Browse files- .DS_Store +0 -0
- viewer/index.css +33 -0
- viewer/index.html +58 -7
- viewer/index.js +173 -17
- 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 =
|
| 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 =
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 *
|
| 103250 |
-
|
| 103251 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 103264 |
-
|
|
|
|
|
|
|
| 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: '
|
| 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
|
|
|