Add PHOTOMODE.md: shelved design for automatic mech photo capture
Records the research behind a proposed capture mode for producing clean head-on
mech renders as source art for the damage paper dolls. Nothing is implemented;
this exists so the investigation does not have to be repeated.
The key finding is that the capture already exists and ships. MechView
(-mechview / -mv, documented in -help) bakes a 25-frame turntable of the
MechLab mech into a 2048x2048 atlas and plays it back on the pod's radar screen.
That bake renders the mech alone, on a solid background, at stepped rotations,
and reads it out of the back buffer - which is exactly the capture wanted. It
just writes a texture rather than files, and is gated behind CTCL.
Two things confirmed from source that remove the main uncertainties:
- MechLab::UpdateDisplay() is only s_MechCamera->DrawScene(true), so the
capture draws no UI at all; and gos_SetupViewport already takes the
background colour as a parameter.
- MechLab exposes GetChassisCount() and SetMech(), so a batch mode can walk
the whole roster without any interaction.
Proposed shape is "MW4.exe -photoall": boots, writes photos\<chassis>.bmp for
all 65 chassis, exits. No hotkeys or new UI. A new MechLab tab was considered
and rejected - tabs need shell-script plus callback work, and the tab's own UI
would be on screen during the capture. Recommended route into MechLab is to
bypass the shell and construct it directly, as MW4Shell.cpp:4693 does, rather
than adding a shellStart mode.
The only genuinely new code identified is a ~40 line BMP writer; the tree has
no screenshot facility at all. Everything else is wiring together routines that
already run in the shipped game.
Open questions needing a real run are recorded: per-chassis camera framing, and
which yaw is actually head-on. Constraints noted: run windowed for 32-bit colour
(fullscreen is bitdepth 16), and refuse to arm when -mechview or CTCL owns the
render hook, since MW4Shell.cpp:1623 asserts a single owner.
All 21 source references in the document were verified against the tree.
Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
This commit is contained in:
co-authored by
Claude Opus 5
GitHub Copilot
parent
7d71d2a771
commit
42a66157ea
+157
@@ -0,0 +1,157 @@
|
||||
# Photo Mode ? proposal and research notes
|
||||
|
||||
**Status: shelved 2026-08-09. Nothing implemented. This is a design record so the research does
|
||||
not have to be repeated.**
|
||||
|
||||
Goal: produce clean head-on renders of every 'Mech, on a solid background with no UI, to use as
|
||||
source art for the MFD / Radar / print damage paper dolls.
|
||||
|
||||
Every source reference below was verified against the tree on 2026-08-09.
|
||||
|
||||
## Why this is cheap: the capture already exists
|
||||
|
||||
The game already renders the mech alone, on a solid background, at stepped rotations, and reads
|
||||
it back out of the frame buffer. It is the **MechView** feature, and it ships enabled behind a
|
||||
switch. Photo mode is that same code writing files instead of a texture.
|
||||
|
||||
`-help` documents it (`MW4Application.cpp` around line 919):
|
||||
|
||||
```text
|
||||
-mechview <1|2> Show the rotating mech view. 1 = on the radar
|
||||
-mv <1|2> screen, 2 = on the main screen. Default: off.
|
||||
```
|
||||
|
||||
What it does today: on entering MechLab it bakes a **25-frame turntable** of the current mech into
|
||||
a 2048x2048 atlas of 360x360 cells (5x5, so 14.4 degrees per step), then flips through the cells
|
||||
to animate a rotating mech on the pod's radar monitor or on the main screen. It is pre-rendered
|
||||
because drawing live 3D onto a *secondary* DirectDraw device every frame is not practical ? the
|
||||
same constraint behind all the MFD work.
|
||||
|
||||
Authored by "jcem", same era as the other CTCL cabinet features.
|
||||
|
||||
## The machinery, with verified locations
|
||||
|
||||
| Piece | Where | Notes |
|
||||
|---|---|---|
|
||||
| Per-frame render hook | `CoreTech/Libraries/GameOS/WinMain.cpp:1215` | `if (g_pfnCTCL_AfterBeginScene) { (*g_pfnCTCL_AfterBeginScene)(); }` ? called right after `BeginScene` |
|
||||
| Hook pointer | `WinMain.cpp:43` | `void (__stdcall *g_pfnCTCL_AfterBeginScene)() = NULL;` ? free unless CTCL installs it |
|
||||
| The bake (our template) | `mw4/Code/MW4/MW4Shell.cpp:14857` | `CTCL_AfterBeginScene()` ? the whole capture loop |
|
||||
| The playback | `MW4Shell.cpp:14936` | `CTCL_UpdateMechView()` ? swaps in after the bake |
|
||||
| Hook install | `MW4Shell.cpp:1705` | `g_pfnCTCL_AfterBeginScene = CTCL_AfterBeginScene;` |
|
||||
| Exclusivity assert | `MW4Shell.cpp:1623` | `gosASSERT(!g_pfnCTCL_AfterBeginScene);` ? only one owner allowed |
|
||||
| Solid-colour clear | `CoreTech/Libraries/GameOS/3DRasterizer.cpp:1044` | `gos_SetupViewport(bool FillZ, float ZBuffer, bool FillBG, DWORD BGColor, ...)` ? background colour is already a parameter; CTCL passes `0x00000000` |
|
||||
| Mech-only draw | `mw4/Code/MW4/MechLab.cpp:1598` | `MechLab::UpdateDisplay()` is just `s_MechCamera->DrawScene(true)` ? **no UI is drawn** |
|
||||
| Rotation | `MechLab.cpp:257` `Execute()` | `MechLab.cpp:285` is the `yaw += 0.02f` spin; `MechLab.cpp:275` gates an absolute-yaw branch on `g_pfnCTCL_AfterBeginScene == CTCL_AfterBeginScene` |
|
||||
| Chassis count | `MechLab.cpp:1185` | `MechLab::GetChassisCount()` |
|
||||
| Chassis swap | `MechLab.cpp:1199` | `MechLab::SetMech(int mech_id, const char *mech_name, const char *path)` |
|
||||
| MechLab construction | `MW4Shell.cpp:4693` | `MechLab::Instance = new MechLab;` |
|
||||
| Camera | `MechLab.cpp:175` | `SetPerspective(1.0f, 1100.0f, Stuff::Pi_Over_3)`, full viewport |
|
||||
| Atlas dimensions | `CoreTech/Libraries/GameOS/render.cpp:1443` | `m_ptMechViewDim` 2048x2048, `m_ptMechViewSize` 360x360 |
|
||||
| Mode global | `CoreTech/Libraries/GameOS/VideoCard.cpp:64` | `g_nMechViewType` ? 0 off, 1 radar screen, 2 main screen |
|
||||
| Switch parsing | `MW4Application.cpp:1575` | `-mechview ` / `-mv ` |
|
||||
|
||||
The capture loop, abridged from `CTCL_AfterBeginScene`:
|
||||
|
||||
```cpp
|
||||
for (int qq = 0; qq < nCount; qq++) {
|
||||
gos_SetupViewport(1, 1.0, 1, 0x00000000, 0.0, 0.0, 1.0, 1.0); // clear to solid colour
|
||||
MechLab::Instance->Execute(); // step to an absolute yaw
|
||||
MechLab::Instance->UpdateDisplay(); // draw ONLY the mech
|
||||
BackBufferSurface->Lock(&rcSource, &desc2, DDLOCK_..., NULL); // read back a centre crop
|
||||
/* memcpy into the atlas cell */
|
||||
BackBufferSurface->Unlock(&rcSource);
|
||||
}
|
||||
```
|
||||
|
||||
Also noted: `MechView_Before()` / `MechView_After()` at `MechLab.cpp:97` and `:102` toggle
|
||||
`ClearBackBufferBeforeDraw`. They are **defined and never called** anywhere in the tree ? dead
|
||||
helpers. They are an alternative route to the same clear if the viewport approach proves awkward.
|
||||
|
||||
## Chosen design
|
||||
|
||||
**A fully automatic batch mode. No hotkeys, no new UI, no interaction.**
|
||||
|
||||
```text
|
||||
MW4.exe -photoall
|
||||
```
|
||||
|
||||
Boots, writes `photos\<chassis>.bmp` for all 65 chassis, exits. Optional later:
|
||||
`-photoall <n>` for an n-step turntable per chassis instead of a single head-on frame.
|
||||
|
||||
### Rejected: a new MechLab tab
|
||||
|
||||
MechLab tabs are defined in `Content/ShellScripts/MechBay/*.script` **plus** code callbacks
|
||||
(`ML_CallbackHandler`, the `MLDataSetup*` IDs). A new tab means script authoring, new callback
|
||||
IDs and layout work ? and the tab's own UI would be on screen during capture, which is the exact
|
||||
thing being avoided. It would also ship to players who have no use for it.
|
||||
|
||||
### Rejected: interactive hotkey as the primary interface
|
||||
|
||||
A `-photo` switch plus F9/F10 in MechLab is feasible ? `gos_GetKeyStatus()` is usable from shell
|
||||
code (see `MW4Shell.cpp:11678`) ? but it needs one manual session per chassis. Worth adding later
|
||||
as a supplement for reshooting a single mech; not the main path.
|
||||
|
||||
### Getting into MechLab without navigating menus
|
||||
|
||||
Two routes:
|
||||
|
||||
1. **Drive the shell.** Add a `PhotoShellStart` to the existing `shellStart` enum; there is
|
||||
precedent (`InstantActionShellStart`, `CampaignShellStart`, `CampaignLoadShellStart`). Fits the
|
||||
existing design but requires understanding the shell state machine.
|
||||
2. **Bypass the shell.** Construct `MechLab` directly the way `MW4Shell.cpp:4693` does, loop,
|
||||
capture, exit. A batch art tool needs no shell screen at all, so this is smaller, more
|
||||
predictable, and cannot get stuck on a dialog. Same shape as the editor's headless `-report`.
|
||||
|
||||
**Recommendation: route 2.**
|
||||
|
||||
## Implementation plan
|
||||
|
||||
| # | File | Work |
|
||||
|---|---|---|
|
||||
| 1 | `MW4Application.cpp` (~1575, beside `-mechview`) | parse `-photoall` (+ optional step count); set globals |
|
||||
| 2 | `MechLab.cpp:275` | widen the absolute-yaw condition so photo mode also sets an exact angle instead of the `+= 0.02f` spin |
|
||||
| 3 | new `Photo_AfterBeginScene()` | modelled on `CTCL_AfterBeginScene`; explicit angles rather than the 5x5 grid maths, configurable crop, writes files |
|
||||
| 4 | install into `g_pfnCTCL_AfterBeginScene` | assignment only; `WinMain.cpp:1215` already calls it |
|
||||
| 5 | **BMP writer** | **the only genuinely new code, ~40 lines.** R5G6B5 or X8R8G8B8 to 24bpp BMP. Nothing in the tree writes images ? verified, there is no screenshot facility anywhere |
|
||||
| 6 | `-help` | document the switch |
|
||||
|
||||
## Open questions ? need a real run to settle
|
||||
|
||||
1. **Framing per chassis.** The camera is fixed (`SetPerspective(1.0f, 1100.0f, Pi_Over_3)`, full
|
||||
viewport) with the mech at the scene root. A Flea and an Atlas will not fill the frame equally.
|
||||
Probably acceptable since each mech gets its own 512 sheet, but if consistent scale is wanted,
|
||||
derive a per-mech camera distance from the mech's bounding box.
|
||||
2. **Which yaw is head-on.** `yaw = 0` gives the model's native orientation; whether that is front,
|
||||
back or side has to be eyeballed once and then hard-coded.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Run windowed.** Fullscreen is `bitdepth=16` (R5G6B5). Windowed sets `bitDepth = DesktopBpp`
|
||||
= 32. Bigger window gives a bigger capture, up to the engine's 4:3 maximum of 1600x1200
|
||||
(`ImageHlp.cpp` asserts the supported mode list). 1200px tall beats the 1024 the original art
|
||||
pipeline started from.
|
||||
- **Must not clash with `-mechview` / CTCL.** `MW4Shell.cpp:1623` asserts the hook is unowned;
|
||||
both cannot install it. Photo mode should refuse to arm when MechView or CTCL is active.
|
||||
- Requires a Windows VC6 rebuild to test; none of this is verifiable on Linux.
|
||||
|
||||
## Possible phase 2: per-component capture
|
||||
|
||||
The mech is an `ElementRenderer` element with sub-elements per body part. If per-part visibility
|
||||
is reachable, photo mode could capture **each zone separately** and hand over the exploded pieces
|
||||
directly ? turning the paper-doll pipeline from "capture, then cut up by hand in an image editor"
|
||||
into "capture, done". Not investigated. This is the difference between a convenience and a genuine
|
||||
step change, so it is worth checking the element hierarchy before finalising the design.
|
||||
|
||||
## Why this was wanted
|
||||
|
||||
From the MFD/Radar mapping work on 2026-08-09 (see `MFD-RADAR-MAPPINGS.md`):
|
||||
|
||||
- **The Urbanmech has no damage art at all.** Its external MFD, radar and cockpit-atlas bitmaps
|
||||
are byte-identical copies of the Uller's; only its mechlab portrait is genuine, and that is a
|
||||
cropped three-quarter view unusable as a paper-doll source. It is the only unintentional
|
||||
placeholder in the roster (Behemoth II sharing Behemoth's art is deliberate). Fixing it needs
|
||||
new source renders ? exactly what photo mode would produce.
|
||||
- **Several chassis have crude MFD sheets.** `thor`, `loki`, `bushwacker`, `solitaire`, `chimera`,
|
||||
`cougar` all sit in the 0.85?0.90 fit band with loose boxes. The Atlas was regenerated from its
|
||||
Radar art (0.795 -> 0.956), but that trick only works where good Radar art exists. Photo mode
|
||||
would remove that dependency.
|
||||
Reference in New Issue
Block a user