Fix FP/VR visibility in iframe, speed control, pointer lock
Browse files- Add FP and JOIN buttons to index.html controls bar
- Forward toggle-fp and show-join messages to 3d iframe
- Add pointer-lock, xr-spatial-tracking, fullscreen to iframe allow
- Click overlay for pointer lock (user gesture required)
- Fix speed control: set simSpeed immediately before async API call
- Prevent speed flickering at 10x/50x
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- web/3d.html +17 -2
- web/index.html +7 -11
web/3d.html
CHANGED
|
@@ -192,6 +192,13 @@
|
|
| 192 |
<div id="fp-hint" style="display:none;position:fixed;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:50;text-align:center">
|
| 193 |
WASD — move · Mouse — look · ESC — exit · E — interact
|
| 194 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
<div id="player-login">
|
| 197 |
<h2>JOIN SOCI CITY</h2>
|
|
@@ -2902,7 +2909,7 @@ fpControls.addEventListener('lock', () => {
|
|
| 2902 |
fpControls.addEventListener('unlock', () => {
|
| 2903 |
document.getElementById('fp-crosshair').style.display = 'none';
|
| 2904 |
if (fpMode) {
|
| 2905 |
-
|
| 2906 |
}
|
| 2907 |
});
|
| 2908 |
|
|
@@ -2912,10 +2919,15 @@ function enterFPMode() {
|
|
| 2912 |
fpCamera.aspect = window.innerWidth / window.innerHeight;
|
| 2913 |
fpCamera.updateProjectionMatrix();
|
| 2914 |
controls.enabled = false;
|
| 2915 |
-
fpControls.lock();
|
| 2916 |
fpClock.start();
|
| 2917 |
document.getElementById('btn-fp').classList.add('active');
|
| 2918 |
document.getElementById('btn-fp').title = 'Exit first-person';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2919 |
}
|
| 2920 |
|
| 2921 |
function exitFPMode() {
|
|
@@ -2925,6 +2937,7 @@ function exitFPMode() {
|
|
| 2925 |
fpClock.stop();
|
| 2926 |
document.getElementById('fp-crosshair').style.display = 'none';
|
| 2927 |
document.getElementById('fp-hint').style.display = 'none';
|
|
|
|
| 2928 |
document.getElementById('btn-fp').classList.remove('active');
|
| 2929 |
document.getElementById('btn-fp').title = 'First-person view';
|
| 2930 |
}
|
|
@@ -3131,6 +3144,8 @@ if (isEmbedded) {
|
|
| 3131 |
if (e.data?.type === 'zoom-out') zoomOut();
|
| 3132 |
if (e.data?.type === 'reset-camera') resetCamera();
|
| 3133 |
if (e.data?.type === 'set-speed') setDemoSpeed(e.data.multiplier);
|
|
|
|
|
|
|
| 3134 |
});
|
| 3135 |
}
|
| 3136 |
connectWebSocket();
|
|
|
|
| 192 |
<div id="fp-hint" style="display:none;position:fixed;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:50;text-align:center">
|
| 193 |
WASD — move · Mouse — look · ESC — exit · E — interact
|
| 194 |
</div>
|
| 195 |
+
<div id="fp-click-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.6);z-index:250;cursor:pointer;display:none;align-items:center;justify-content:center">
|
| 196 |
+
<div style="text-align:center;color:#fff">
|
| 197 |
+
<div style="font-size:48px;margin-bottom:16px">👁</div>
|
| 198 |
+
<div style="font-size:20px;font-weight:300;letter-spacing:2px">CLICK TO ENTER FIRST-PERSON</div>
|
| 199 |
+
<div style="font-size:13px;color:#aaa;margin-top:8px">WASD — move · Mouse — look · ESC — exit</div>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
|
| 203 |
<div id="player-login">
|
| 204 |
<h2>JOIN SOCI CITY</h2>
|
|
|
|
| 2909 |
fpControls.addEventListener('unlock', () => {
|
| 2910 |
document.getElementById('fp-crosshair').style.display = 'none';
|
| 2911 |
if (fpMode) {
|
| 2912 |
+
document.getElementById('fp-click-overlay').style.display = 'flex';
|
| 2913 |
}
|
| 2914 |
});
|
| 2915 |
|
|
|
|
| 2919 |
fpCamera.aspect = window.innerWidth / window.innerHeight;
|
| 2920 |
fpCamera.updateProjectionMatrix();
|
| 2921 |
controls.enabled = false;
|
|
|
|
| 2922 |
fpClock.start();
|
| 2923 |
document.getElementById('btn-fp').classList.add('active');
|
| 2924 |
document.getElementById('btn-fp').title = 'Exit first-person';
|
| 2925 |
+
const overlay = document.getElementById('fp-click-overlay');
|
| 2926 |
+
overlay.style.display = 'flex';
|
| 2927 |
+
overlay.onclick = () => {
|
| 2928 |
+
overlay.style.display = 'none';
|
| 2929 |
+
fpControls.lock();
|
| 2930 |
+
};
|
| 2931 |
}
|
| 2932 |
|
| 2933 |
function exitFPMode() {
|
|
|
|
| 2937 |
fpClock.stop();
|
| 2938 |
document.getElementById('fp-crosshair').style.display = 'none';
|
| 2939 |
document.getElementById('fp-hint').style.display = 'none';
|
| 2940 |
+
document.getElementById('fp-click-overlay').style.display = 'none';
|
| 2941 |
document.getElementById('btn-fp').classList.remove('active');
|
| 2942 |
document.getElementById('btn-fp').title = 'First-person view';
|
| 2943 |
}
|
|
|
|
| 3144 |
if (e.data?.type === 'zoom-out') zoomOut();
|
| 3145 |
if (e.data?.type === 'reset-camera') resetCamera();
|
| 3146 |
if (e.data?.type === 'set-speed') setDemoSpeed(e.data.multiplier);
|
| 3147 |
+
if (e.data?.type === 'toggle-fp') window._toggleFP();
|
| 3148 |
+
if (e.data?.type === 'show-join') window._showJoin();
|
| 3149 |
});
|
| 3150 |
}
|
| 3151 |
connectWebSocket();
|
web/index.html
CHANGED
|
@@ -293,6 +293,9 @@
|
|
| 293 |
<button class="ctrl-btn" onclick="send3d('zoom-in')" title="Zoom In (scroll in 3D view)">+</button>
|
| 294 |
<button class="ctrl-btn" onclick="send3d('zoom-out')" title="Zoom Out (scroll in 3D view)">-</button>
|
| 295 |
<button class="ctrl-btn" onclick="send3d('reset-camera')" title="Reset 3D camera">Fit</button>
|
|
|
|
|
|
|
|
|
|
| 296 |
</span>
|
| 297 |
<span id="api-calls">API: 0</span>
|
| 298 |
<span id="cost">$0.00</span>
|
|
@@ -301,7 +304,7 @@
|
|
| 301 |
</div>
|
| 302 |
<div id="main">
|
| 303 |
<div id="viewport-3d">
|
| 304 |
-
<iframe id="city3d" allow="autoplay"></iframe>
|
| 305 |
<script>document.getElementById('city3d').src = location.protocol === 'file:' ? '3d.html' : '/3d';</script>
|
| 306 |
<div id="tooltip" style="z-index:200"></div>
|
| 307 |
<div id="toast-container"></div>
|
|
@@ -3249,19 +3252,12 @@ async function togglePause() {
|
|
| 3249 |
}
|
| 3250 |
|
| 3251 |
async function setSpeed(mult) {
|
| 3252 |
-
try {
|
| 3253 |
-
const res = await fetch(`${API_BASE}/controls/speed?multiplier=${mult}`, { method: 'POST' });
|
| 3254 |
-
if (res.ok) {
|
| 3255 |
-
const data = await res.json();
|
| 3256 |
-
simSpeed = data.speed;
|
| 3257 |
-
updateControlsUI();
|
| 3258 |
-
send3dSpeed(simSpeed);
|
| 3259 |
-
return;
|
| 3260 |
-
}
|
| 3261 |
-
} catch(e) {}
|
| 3262 |
simSpeed = mult;
|
| 3263 |
updateControlsUI();
|
| 3264 |
send3dSpeed(mult);
|
|
|
|
|
|
|
|
|
|
| 3265 |
}
|
| 3266 |
|
| 3267 |
function updateControlsUI() {
|
|
|
|
| 293 |
<button class="ctrl-btn" onclick="send3d('zoom-in')" title="Zoom In (scroll in 3D view)">+</button>
|
| 294 |
<button class="ctrl-btn" onclick="send3d('zoom-out')" title="Zoom Out (scroll in 3D view)">-</button>
|
| 295 |
<button class="ctrl-btn" onclick="send3d('reset-camera')" title="Reset 3D camera">Fit</button>
|
| 296 |
+
<span style="color:#1a3a6e;margin:0 4px">│</span>
|
| 297 |
+
<button class="ctrl-btn" id="btn-fp-main" onclick="send3d('toggle-fp')" title="First-person view">👁 FP</button>
|
| 298 |
+
<button class="ctrl-btn" id="btn-join-main" onclick="send3d('show-join')" title="Join as player">JOIN</button>
|
| 299 |
</span>
|
| 300 |
<span id="api-calls">API: 0</span>
|
| 301 |
<span id="cost">$0.00</span>
|
|
|
|
| 304 |
</div>
|
| 305 |
<div id="main">
|
| 306 |
<div id="viewport-3d">
|
| 307 |
+
<iframe id="city3d" allow="autoplay; pointer-lock; xr-spatial-tracking; fullscreen"></iframe>
|
| 308 |
<script>document.getElementById('city3d').src = location.protocol === 'file:' ? '3d.html' : '/3d';</script>
|
| 309 |
<div id="tooltip" style="z-index:200"></div>
|
| 310 |
<div id="toast-container"></div>
|
|
|
|
| 3252 |
}
|
| 3253 |
|
| 3254 |
async function setSpeed(mult) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3255 |
simSpeed = mult;
|
| 3256 |
updateControlsUI();
|
| 3257 |
send3dSpeed(mult);
|
| 3258 |
+
try {
|
| 3259 |
+
await fetch(`${API_BASE}/controls/speed?multiplier=${mult}`, { method: 'POST' });
|
| 3260 |
+
} catch(e) {}
|
| 3261 |
}
|
| 3262 |
|
| 3263 |
function updateControlsUI() {
|