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);
|
||||
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.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
@@ -4347,6 +4349,18 @@ function renderDroneView() {
|
||||
drone.cam.aspect = w / h;
|
||||
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.setScissor(x, y, w, h);
|
||||
renderer.setViewport(x, y, w, h);
|
||||
@@ -4354,6 +4368,12 @@ function renderDroneView() {
|
||||
renderer.setScissorTest(false);
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -7137,10 +7157,10 @@ function animate() {
|
||||
renderer.render(scene, camera);
|
||||
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) {
|
||||
drone.frame += 1;
|
||||
if (drone.frame % 2 === 0) renderDroneView();
|
||||
renderDroneView();
|
||||
}
|
||||
}
|
||||
animate();
|
||||
@@ -7626,6 +7646,26 @@ window.__city10 = {
|
||||
return out;
|
||||
},
|
||||
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). */
|
||||
get talkPeer() {
|
||||
return currentPeer;
|
||||
@@ -7684,6 +7724,8 @@ window.__city10 = {
|
||||
trafficLights: trafficSystem ? trafficSystem.heads.length / 2 : 0,
|
||||
crosswalks: crosswalkCount,
|
||||
sidewalkSegments: sidewalkSegmentCount,
|
||||
hour: +(sim.hour ?? 0).toFixed(1),
|
||||
night: +(sim.nightFactor ?? 0).toFixed(2),
|
||||
beach: beachSystem ? beachSystem.counts() : null,
|
||||
flights: airplaneSystem ? airplaneSystem.flights() : null,
|
||||
port: portSystem ? portSystem.counts() : null,
|
||||
@@ -7693,6 +7735,7 @@ window.__city10 = {
|
||||
y: +drone.pos.y.toFixed(1),
|
||||
z: +drone.pos.z.toFixed(1),
|
||||
yaw: +drone.yaw.toFixed(2),
|
||||
fogCleared: !!drone.fogCleared,
|
||||
view: drone.view || { x: 0, y: 0, w: 0, h: 0 },
|
||||
} : null,
|
||||
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>
|
||||
</div>
|
||||
<div id="drone-video"
|
||||
style="height:200px; background:transparent;
|
||||
border-top:1px solid rgba(148,163,184,.35);
|
||||
border-bottom:1px solid rgba(148,163,184,.35);"></div>
|
||||
style="height:200px;
|
||||
background:repeating-linear-gradient(0deg,
|
||||
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"
|
||||
style="padding:.4rem .6rem; font-size:.7rem; color:#cbd5e1; background:rgba(2,8,20,.88);">
|
||||
{{ __('city.drone_hint') }}
|
||||
|
||||