From 497919352877bb11aaad2583dcac6d171cbe3ccd Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 7 Aug 2026 13:12:57 -0500 Subject: [PATCH] The viewer uses the engine's projection, not the console's It was drawing the map mirrored. The X flip came from the setup console's picture of Brewer's Bane, which is an illustration and not a screenshot - the game does not draw it that way round. What the game does: L4GaugeImagePrimitive::Draw plots MoveToAbsolute(dest->x, dest->z), so the screen axes are view-space X and Z, and the graphics view's origin is bottom left with Y increasing upward - BackgroundLine draws endpoints.bottomLeft to endpoints.topRight, and the port's zero-degree blit is documented with the origin in the bottom left corner. So +X runs right and +Z runs up. Also added the heading the real display has. NavDisplay centres on the vehicle and turns with it, inverting the viewer's transform and taking yaw only unless rockAndRoll is set; the viewer defaults to heading 0, which is the north-up case, and Q/E/R turn it. Panning and dragging now work in what you see rather than in world axes, so up stays up when the map is turned. TRACKS.html is left following the console pictures on purpose: nine of those cards are the console pictures, so the reconstructed nine have to sit beside them consistently. The two disagree by a mirror and each is right for what it is, which both READMEs now say. --- tools/mapview/README.md | 22 ++++++++++--- tools/mapview/mapview.html | 63 ++++++++++++++++++++++++++++++-------- tools/mapview/mapview.js | 46 ++++++++++++++++++++-------- tools/mapview/mapview.tpl | 17 ++++++++++ 4 files changed, 119 insertions(+), 29 deletions(-) diff --git a/tools/mapview/README.md b/tools/mapview/README.md index e22cd44..4d77edb 100644 --- a/tools/mapview/README.md +++ b/tools/mapview/README.md @@ -62,8 +62,20 @@ convenient nearby image. ## Orientation -Seen from above with +Z up the page, the engine's +X runs to the **left**. The -console's own picture of Brewer's Bane is what settles it: long leg up the right -side to Score Zone 1, the corner, then the run out to Score Zone 2. Drawn the -other way round every plan comes out mirrored, which is the bug this shares a -fix with in [../pages/](../pages/). +**+X runs right, +Z runs up.** That is the engine's projection rather than a +choice. `L4GaugeImagePrimitive::Draw` plots `MoveToAbsolute(dest->x, dest->z)`, +so the screen axes are view-space X and Z; and the graphics view's origin is at +the bottom left with Y increasing upward — `BackgroundLine` draws +`endpoints.bottomLeft` to `endpoints.topRight`, and the port's zero-degree blit +is documented with the origin at the bottom-left corner. + +Worth knowing: **the setup console's pictures of the tracks are mirrored against +this.** They are illustrations, not screenshots. The plans in `TRACKS.html` +deliberately follow the console pictures, because nine of those cards *are* the +console pictures and the reconstructed nine have to sit beside them consistently. +This viewer follows the game instead. The two disagree by a mirror, and both are +right for what they are. + +The nav display also centres on the vehicle and turns with it, inverting the +viewer's transform and taking yaw only unless `rockAndRoll` is set. Heading 0 +here is the north-up case; Q/E turn the map. diff --git a/tools/mapview/mapview.html b/tools/mapview/mapview.html index e5c2133..a738743 100644 --- a/tools/mapview/mapview.html +++ b/tools/mapview/mapview.html @@ -108,6 +108,8 @@ kbd { pan N/S/E/W +-zoom []previous / next track + QEturn the map ±15° + Rheading back to zero Ffit the course Afit everything, strays included GGPS / NavDisplay LOD @@ -131,6 +133,21 @@ kbd {

Colours are the game's own: palette indices resolved through secpal.pcc, the palette the pod's secondary port is configured with. The walls are grey because index 51 is grey.

+ +

+X runs right and +Z runs up, which is the engine's + projection and not a choice: L4GaugeImagePrimitive::Draw plots + (dest->x, dest->z), and the graphics view has its origin at + the bottom left with Y increasing upward. Note the setup console's own + pictures of the tracks are mirrored against this — they are + illustrations, not screenshots. The plans in TRACKS.html follow + the console pictures so the drawn and reconstructed cards agree with each + other; this viewer follows the game.

+ +

Heading. The real nav display centres on your vehicle + and turns with it — it inverts the viewer's transform, taking yaw only + unless rockAndRoll is set. Free panning at heading 0 is the + north-up case; Q and E turn the map to see what it looks + like underway.

@@ -145,14 +162,27 @@ const S = { cx: 0, cz: 0, // view centre, world units mpp: 10, // meters per pixel - the engine's own scale term gps: false, // false = NavDisplay (LOD follows zoom), true = GPS + yaw: 0, // map heading, radians. NavDisplay takes the + // vehicle's; 0 is the map with +Z straight up. stats: {}, }; -// Seen from above with +Z up the page the engine's +X runs to the LEFT. -// The console's own picture of Brewer's Bane is what settles the -// handedness; drawn the other way every plan comes out mirrored. +// The engine's own projection, and it is not the obvious one. +// L4GaugeImagePrimitive::Draw plots MoveToAbsolute(dest->x, dest->z) - so +// the screen axes are view-space X and Z - and the graphics view puts its +// origin at the bottom left with Y increasing UP (BackgroundLine draws +// bottomLeft -> topRight, and the port's zero-degree blit is documented +// with the origin at the bottom left corner). So on the map +X runs RIGHT +// and +Z runs UP. Canvas y grows downward, hence the single negation. +// +// NavDisplay also inverts the viewer's transform before scaling, taking +// yaw only unless rockAndRoll is set - the map turns with the vehicle and +// is centred on it. Free panning with yaw 0 is the north-up case. function toScreen(x, z, w, h) { - return [w / 2 - (x - S.cx) / S.mpp, h / 2 - (z - S.cz) / S.mpp]; + const dx = x - S.cx, dz = z - S.cz; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + return [w / 2 + (dx * c - dz * s) / S.mpp, + h / 2 - (dx * s + dz * c) / S.mpp]; } // Quaternion (x,y,z,w) applied to a point - MUNGA's own convention. @@ -276,6 +306,7 @@ function hud() { ['pixelsPerMeter', px.toFixed(4)], ['LOD value', s.lodValue.toFixed(3)], ['centre X / Z', S.cx.toFixed(0) + ' / ' + S.cz.toFixed(0)], + ['heading', (((S.yaw * 180 / Math.PI) % 360 + 360) % 360).toFixed(0) + '°'], ['objects drawn', s.drawn], ['dropped by LOD', s.culled], ['primitives', s.prims], @@ -292,10 +323,13 @@ function hud() { } // ------------------------------------------------------------ controls -function pan(dx, dz) { +// Pan in what you see, not in world axes: with the map turned, "up" has +// to stay up. Screen delta rotated back out through the heading. +function pan(sx, sy) { const step = 0.1 * Math.min(cv.clientWidth, cv.clientHeight) * S.mpp; - S.cx += dx * step; - S.cz += dz * step; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + S.cx += (sx * c + sy * s) * step; + S.cz += (-sx * s + sy * c) * step; draw(); } @@ -313,11 +347,14 @@ function pick(n) { addEventListener('keydown', e => { if (e.target.tagName === 'SELECT') return; const k = e.key; - // Right on screen is -X, up is +Z: north/south/east/west as drawn. + // Screen directions: right is +X and up is +Z when the heading is zero. if (k === 'ArrowUp') pan(0, 1); else if (k === 'ArrowDown') pan(0, -1); - else if (k === 'ArrowLeft') pan(1, 0); - else if (k === 'ArrowRight') pan(-1, 0); + else if (k === 'ArrowLeft') pan(-1, 0); + else if (k === 'ArrowRight') pan(1, 0); + else if (k === 'q' || k === 'Q') { S.yaw -= Math.PI / 12; draw(); } + else if (k === 'e' || k === 'E') { S.yaw += Math.PI / 12; draw(); } + else if (k === 'r' || k === 'R') { S.yaw = 0; draw(); } else if (k === '+' || k === '=') zoom(1 / 1.25); else if (k === '-' || k === '_') zoom(1.25); else if (k === '[') pick(S.track - 1); @@ -336,8 +373,10 @@ cv.addEventListener('pointerdown', e => { }); cv.addEventListener('pointermove', e => { if (!drag) return; - S.cx -= (e.clientX - drag.x) * S.mpp; - S.cz += (e.clientY - drag.y) * S.mpp; + const dx = (e.clientX - drag.x) * S.mpp, dy = (e.clientY - drag.y) * S.mpp; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + S.cx -= dx * c - dy * s; + S.cz -= -dx * s - dy * c; drag = {x: e.clientX, y: e.clientY}; draw(); }); diff --git a/tools/mapview/mapview.js b/tools/mapview/mapview.js index 925a2e5..1542d70 100644 --- a/tools/mapview/mapview.js +++ b/tools/mapview/mapview.js @@ -9,14 +9,27 @@ const S = { cx: 0, cz: 0, // view centre, world units mpp: 10, // meters per pixel - the engine's own scale term gps: false, // false = NavDisplay (LOD follows zoom), true = GPS + yaw: 0, // map heading, radians. NavDisplay takes the + // vehicle's; 0 is the map with +Z straight up. stats: {}, }; -// Seen from above with +Z up the page the engine's +X runs to the LEFT. -// The console's own picture of Brewer's Bane is what settles the -// handedness; drawn the other way every plan comes out mirrored. +// The engine's own projection, and it is not the obvious one. +// L4GaugeImagePrimitive::Draw plots MoveToAbsolute(dest->x, dest->z) - so +// the screen axes are view-space X and Z - and the graphics view puts its +// origin at the bottom left with Y increasing UP (BackgroundLine draws +// bottomLeft -> topRight, and the port's zero-degree blit is documented +// with the origin at the bottom left corner). So on the map +X runs RIGHT +// and +Z runs UP. Canvas y grows downward, hence the single negation. +// +// NavDisplay also inverts the viewer's transform before scaling, taking +// yaw only unless rockAndRoll is set - the map turns with the vehicle and +// is centred on it. Free panning with yaw 0 is the north-up case. function toScreen(x, z, w, h) { - return [w / 2 - (x - S.cx) / S.mpp, h / 2 - (z - S.cz) / S.mpp]; + const dx = x - S.cx, dz = z - S.cz; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + return [w / 2 + (dx * c - dz * s) / S.mpp, + h / 2 - (dx * s + dz * c) / S.mpp]; } // Quaternion (x,y,z,w) applied to a point - MUNGA's own convention. @@ -140,6 +153,7 @@ function hud() { ['pixelsPerMeter', px.toFixed(4)], ['LOD value', s.lodValue.toFixed(3)], ['centre X / Z', S.cx.toFixed(0) + ' / ' + S.cz.toFixed(0)], + ['heading', (((S.yaw * 180 / Math.PI) % 360 + 360) % 360).toFixed(0) + '°'], ['objects drawn', s.drawn], ['dropped by LOD', s.culled], ['primitives', s.prims], @@ -156,10 +170,13 @@ function hud() { } // ------------------------------------------------------------ controls -function pan(dx, dz) { +// Pan in what you see, not in world axes: with the map turned, "up" has +// to stay up. Screen delta rotated back out through the heading. +function pan(sx, sy) { const step = 0.1 * Math.min(cv.clientWidth, cv.clientHeight) * S.mpp; - S.cx += dx * step; - S.cz += dz * step; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + S.cx += (sx * c + sy * s) * step; + S.cz += (-sx * s + sy * c) * step; draw(); } @@ -177,11 +194,14 @@ function pick(n) { addEventListener('keydown', e => { if (e.target.tagName === 'SELECT') return; const k = e.key; - // Right on screen is -X, up is +Z: north/south/east/west as drawn. + // Screen directions: right is +X and up is +Z when the heading is zero. if (k === 'ArrowUp') pan(0, 1); else if (k === 'ArrowDown') pan(0, -1); - else if (k === 'ArrowLeft') pan(1, 0); - else if (k === 'ArrowRight') pan(-1, 0); + else if (k === 'ArrowLeft') pan(-1, 0); + else if (k === 'ArrowRight') pan(1, 0); + else if (k === 'q' || k === 'Q') { S.yaw -= Math.PI / 12; draw(); } + else if (k === 'e' || k === 'E') { S.yaw += Math.PI / 12; draw(); } + else if (k === 'r' || k === 'R') { S.yaw = 0; draw(); } else if (k === '+' || k === '=') zoom(1 / 1.25); else if (k === '-' || k === '_') zoom(1.25); else if (k === '[') pick(S.track - 1); @@ -200,8 +220,10 @@ cv.addEventListener('pointerdown', e => { }); cv.addEventListener('pointermove', e => { if (!drag) return; - S.cx -= (e.clientX - drag.x) * S.mpp; - S.cz += (e.clientY - drag.y) * S.mpp; + const dx = (e.clientX - drag.x) * S.mpp, dy = (e.clientY - drag.y) * S.mpp; + const c = Math.cos(S.yaw), s = Math.sin(S.yaw); + S.cx -= dx * c - dy * s; + S.cz -= -dx * s - dy * c; drag = {x: e.clientX, y: e.clientY}; draw(); }); diff --git a/tools/mapview/mapview.tpl b/tools/mapview/mapview.tpl index 2148fbf..51d7765 100644 --- a/tools/mapview/mapview.tpl +++ b/tools/mapview/mapview.tpl @@ -28,6 +28,8 @@ pan N/S/E/W +-zoom []previous / next track + QEturn the map ±15° + Rheading back to zero Ffit the course Afit everything, strays included GGPS / NavDisplay LOD @@ -51,6 +53,21 @@

Colours are the game's own: palette indices resolved through secpal.pcc, the palette the pod's secondary port is configured with. The walls are grey because index 51 is grey.

+ +

+X runs right and +Z runs up, which is the engine's + projection and not a choice: L4GaugeImagePrimitive::Draw plots + (dest->x, dest->z), and the graphics view has its origin at + the bottom left with Y increasing upward. Note the setup console's own + pictures of the tracks are mirrored against this — they are + illustrations, not screenshots. The plans in TRACKS.html follow + the console pictures so the drawn and reconstructed cards agree with each + other; this viewer follows the game.

+ +

Heading. The real nav display centres on your vehicle + and turns with it — it inverts the viewer's transform, taking yaw only + unless rockAndRoll is set. Free panning at heading 0 is the + north-up case; Q and E turn the map to see what it looks + like underway.