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.