fix(drone): resolve drone feed fog and flicker issues
- Add preserveDrawingBuffer to enable automated test screenshots - Neutralize scene fog during drone render pass to prevent misted aerial view - Render drone feed every frame instead of alternating with ground view - Update visor styling with scanline effect and improved borders - Add debug getters for WebGL renderer and scene information
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -30,7 +30,9 @@ scene.fog = new THREE.Fog(0x87ceeb, 120, 600);
|
|||||||
const camera = new THREE.PerspectiveCamera(72, 1, 0.1, 2000);
|
const camera = new THREE.PerspectiveCamera(72, 1, 0.1, 2000);
|
||||||
camera.position.set(0, 1.7, 0);
|
camera.position.set(0, 1.7, 0);
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
// preserveDrawingBuffer: lets automated tests/screenshots read real
|
||||||
|
// frame pixels (the PIP visor proof); cost is negligible here.
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||||
renderer.shadowMap.enabled = true;
|
renderer.shadowMap.enabled = true;
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||||
@@ -4347,6 +4349,18 @@ function renderDroneView() {
|
|||||||
drone.cam.aspect = w / h;
|
drone.cam.aspect = w / h;
|
||||||
drone.cam.updateProjectionMatrix();
|
drone.cam.updateProjectionMatrix();
|
||||||
|
|
||||||
|
// Neutralise the city fog for the drone feed so the aerial view is
|
||||||
|
// never misted ("embassado"). Only the fog uniform values change for
|
||||||
|
// this pass — three refreshes them per render, so no material is
|
||||||
|
// recompiled and the ground view keeps its weather haze.
|
||||||
|
const fog = scene.fog;
|
||||||
|
const fogNear = fog ? fog.near : 0;
|
||||||
|
const fogFar = fog ? fog.far : 0;
|
||||||
|
if (fog) {
|
||||||
|
fog.near = 1e7;
|
||||||
|
fog.far = 2e7; // smoothstep → factor 0 at any real distance
|
||||||
|
}
|
||||||
|
|
||||||
renderer.setScissorTest(true);
|
renderer.setScissorTest(true);
|
||||||
renderer.setScissor(x, y, w, h);
|
renderer.setScissor(x, y, w, h);
|
||||||
renderer.setViewport(x, y, w, h);
|
renderer.setViewport(x, y, w, h);
|
||||||
@@ -4354,6 +4368,12 @@ function renderDroneView() {
|
|||||||
renderer.setScissorTest(false);
|
renderer.setScissorTest(false);
|
||||||
renderer.setViewport(0, 0, cr.width, cr.height);
|
renderer.setViewport(0, 0, cr.width, cr.height);
|
||||||
|
|
||||||
|
if (fog) {
|
||||||
|
fog.near = fogNear;
|
||||||
|
fog.far = fogFar;
|
||||||
|
}
|
||||||
|
|
||||||
|
drone.fogCleared = !!fog;
|
||||||
drone.view = { x, y, w, h };
|
drone.view = { x, y, w, h };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7137,10 +7157,10 @@ function animate() {
|
|||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
perfFrame.render = performance.now() - tRender;
|
perfFrame.render = performance.now() - tRender;
|
||||||
|
|
||||||
// Drone command-panel feed: scissored second pass on alternate frames.
|
// Drone command-panel feed: scissored pass every frame so the visor
|
||||||
|
// never alternates between the drone view and the ground view.
|
||||||
if (drone.active && drone.mesh) {
|
if (drone.active && drone.mesh) {
|
||||||
drone.frame += 1;
|
renderDroneView();
|
||||||
if (drone.frame % 2 === 0) renderDroneView();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
animate();
|
animate();
|
||||||
@@ -7626,6 +7646,26 @@ window.__city10 = {
|
|||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
playerCrossingAllowed,
|
playerCrossingAllowed,
|
||||||
|
/** Debug: the WebGL renderer (frame forensics for automated tests). */
|
||||||
|
get gl() {
|
||||||
|
return renderer;
|
||||||
|
},
|
||||||
|
/** Debug: live scene background/fog hex + camera pose. */
|
||||||
|
get sceneInfo() {
|
||||||
|
return {
|
||||||
|
bg: scene.background ? scene.background.getHexString() : null,
|
||||||
|
fog: scene.fog ? scene.fog.color.getHexString() : null,
|
||||||
|
fogNear: scene.fog ? scene.fog.near : null,
|
||||||
|
fogFar: scene.fog ? scene.fog.far : null,
|
||||||
|
cam: {
|
||||||
|
x: +camera.position.x.toFixed(2),
|
||||||
|
y: +camera.position.y.toFixed(2),
|
||||||
|
z: +camera.position.z.toFixed(2),
|
||||||
|
},
|
||||||
|
toneMapping: renderer.toneMapping,
|
||||||
|
exposure: renderer.toneMappingExposure,
|
||||||
|
};
|
||||||
|
},
|
||||||
/** Current bump-to-talk peer (null when the chat is closed). */
|
/** Current bump-to-talk peer (null when the chat is closed). */
|
||||||
get talkPeer() {
|
get talkPeer() {
|
||||||
return currentPeer;
|
return currentPeer;
|
||||||
@@ -7684,6 +7724,8 @@ window.__city10 = {
|
|||||||
trafficLights: trafficSystem ? trafficSystem.heads.length / 2 : 0,
|
trafficLights: trafficSystem ? trafficSystem.heads.length / 2 : 0,
|
||||||
crosswalks: crosswalkCount,
|
crosswalks: crosswalkCount,
|
||||||
sidewalkSegments: sidewalkSegmentCount,
|
sidewalkSegments: sidewalkSegmentCount,
|
||||||
|
hour: +(sim.hour ?? 0).toFixed(1),
|
||||||
|
night: +(sim.nightFactor ?? 0).toFixed(2),
|
||||||
beach: beachSystem ? beachSystem.counts() : null,
|
beach: beachSystem ? beachSystem.counts() : null,
|
||||||
flights: airplaneSystem ? airplaneSystem.flights() : null,
|
flights: airplaneSystem ? airplaneSystem.flights() : null,
|
||||||
port: portSystem ? portSystem.counts() : null,
|
port: portSystem ? portSystem.counts() : null,
|
||||||
@@ -7693,6 +7735,7 @@ window.__city10 = {
|
|||||||
y: +drone.pos.y.toFixed(1),
|
y: +drone.pos.y.toFixed(1),
|
||||||
z: +drone.pos.z.toFixed(1),
|
z: +drone.pos.z.toFixed(1),
|
||||||
yaw: +drone.yaw.toFixed(2),
|
yaw: +drone.yaw.toFixed(2),
|
||||||
|
fogCleared: !!drone.fogCleared,
|
||||||
view: drone.view || { x: 0, y: 0, w: 0, h: 0 },
|
view: drone.view || { x: 0, y: 0, w: 0, h: 0 },
|
||||||
} : null,
|
} : null,
|
||||||
robots: pedestrianSystem
|
robots: pedestrianSystem
|
||||||
|
|||||||
@@ -254,9 +254,13 @@
|
|||||||
style="border:0; background:rgba(148,163,184,.18); color:#e2e8f0; border-radius:.35rem; cursor:pointer; padding:.1rem .45rem;">✕</button>
|
style="border:0; background:rgba(148,163,184,.18); color:#e2e8f0; border-radius:.35rem; cursor:pointer; padding:.1rem .45rem;">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="drone-video"
|
<div id="drone-video"
|
||||||
style="height:200px; background:transparent;
|
style="height:200px;
|
||||||
border-top:1px solid rgba(148,163,184,.35);
|
background:repeating-linear-gradient(0deg,
|
||||||
border-bottom:1px solid rgba(148,163,184,.35);"></div>
|
rgba(0,0,0,.14) 0px, rgba(0,0,0,.14) 2px,
|
||||||
|
rgba(0,0,0,0) 2px, rgba(0,0,0,0) 4px);
|
||||||
|
border:7px solid #050a14; border-radius:.4rem;
|
||||||
|
box-shadow:inset 0 0 16px rgba(0,0,0,.75),
|
||||||
|
0 0 0 2px rgba(148,163,184,.45);"></div>
|
||||||
<div id="drone-hints"
|
<div id="drone-hints"
|
||||||
style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);">
|
style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);">
|
||||||
{{ __('city.drone_hint') }}
|
{{ __('city.drone_hint') }}
|
||||||
|
|||||||