feat(prototype): adapt the patrol field to the display aspect ratio
The field was a fixed square, leaving wide monitors with dead side margins. Field width now follows the window aspect (capped at 2.2:1, quantized to avoid rebuilds on minor resizes); routes, obstacles and transit spawns stretch with it, and the grid keeps 100 m cells.
This commit is contained in:
+42
-9
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AREA,
|
||||
MAX_ASPECT,
|
||||
areaWidth,
|
||||
fmtBytes,
|
||||
makeWorld,
|
||||
seed,
|
||||
@@ -9,22 +11,48 @@ import {
|
||||
} from "./sim";
|
||||
|
||||
const VIEW = 1000;
|
||||
const PANEL_W = 320;
|
||||
const HEADER_H = 70;
|
||||
|
||||
// Patrol field aspect ratio for the current window: wide monitors get a
|
||||
// wider field instead of empty side margins. Quantized to 0.25 steps so
|
||||
// minor resizes don't rebuild the world.
|
||||
function displayAspect(): number {
|
||||
const w = Math.max(320, window.innerWidth - PANEL_W);
|
||||
const h = Math.max(320, window.innerHeight - HEADER_H);
|
||||
const q = Math.round((w / h) * 4) / 4;
|
||||
return Math.max(1, Math.min(MAX_ASPECT, q));
|
||||
}
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
const [droneCount, setDroneCount] = useState(8);
|
||||
const [speedup, setSpeedup] = useState(1);
|
||||
const [paused, setPaused] = useState(false);
|
||||
const [aspect, setAspect] = useState(displayAspect);
|
||||
const [world, setWorld] = useState<World>(() => {
|
||||
seed(42);
|
||||
return makeWorld(8);
|
||||
return makeWorld(8, displayAspect());
|
||||
});
|
||||
const raf = useRef(0);
|
||||
const last = useRef(performance.now());
|
||||
|
||||
useEffect(() => {
|
||||
seed(42);
|
||||
setWorld(makeWorld(droneCount));
|
||||
}, [droneCount]);
|
||||
setWorld(makeWorld(droneCount, aspect));
|
||||
}, [droneCount, aspect]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer = 0;
|
||||
const onResize = (): void => {
|
||||
window.clearTimeout(timer);
|
||||
timer = window.setTimeout(() => setAspect(displayAspect()), 250);
|
||||
};
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => {
|
||||
window.clearTimeout(timer);
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loop = (now: number): void => {
|
||||
@@ -86,7 +114,7 @@ export default function App(): JSX.Element {
|
||||
|
||||
<div style={styles.main}>
|
||||
<svg
|
||||
viewBox={`0 0 ${VIEW} ${VIEW}`}
|
||||
viewBox={`0 0 ${sx(areaWidth())} ${VIEW}`}
|
||||
style={styles.canvas}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
@@ -96,7 +124,7 @@ export default function App(): JSX.Element {
|
||||
<stop offset="100%" stopColor="#0a0f18" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width={VIEW} height={VIEW} fill="url(#ground)" />
|
||||
<rect width={sx(areaWidth())} height={VIEW} fill="url(#ground)" />
|
||||
{gridLines()}
|
||||
|
||||
{/* Mesh links */}
|
||||
@@ -202,12 +230,17 @@ function sy(y: number): number {
|
||||
}
|
||||
|
||||
function gridLines(): JSX.Element {
|
||||
const width = sx(areaWidth());
|
||||
const step = VIEW / 10; // one cell per 100 m in both directions
|
||||
const lines = [];
|
||||
for (let i = 1; i < 10; i++) {
|
||||
const p = (i / 10) * VIEW;
|
||||
for (let p = step; p < width; p += step) {
|
||||
lines.push(
|
||||
<line key={`v${i}`} x1={p} y1={0} x2={p} y2={VIEW} stroke="#1a2436" strokeWidth={1} />,
|
||||
<line key={`h${i}`} x1={0} y1={p} x2={VIEW} y2={p} stroke="#1a2436" strokeWidth={1} />,
|
||||
<line key={`v${p}`} x1={p} y1={0} x2={p} y2={VIEW} stroke="#1a2436" strokeWidth={1} />,
|
||||
);
|
||||
}
|
||||
for (let p = step; p < VIEW; p += step) {
|
||||
lines.push(
|
||||
<line key={`h${p}`} x1={0} y1={p} x2={width} y2={p} stroke="#1a2436" strokeWidth={1} />,
|
||||
);
|
||||
}
|
||||
return <g>{lines}</g>;
|
||||
|
||||
Reference in New Issue
Block a user