Sprite sheets, atlases, game UI, time of day, weather presets, headless captures

- BillboardInstance: per-instance uv sub-rect, feet anchor, turn, dithered alpha, flash;
  BillboardBatch::sharedTexture; Texture createEmpty/update/cellUV/cellAnchor/nearest
- SpriteAtlas: fixed-cell runtime atlas with gutters
- hd2d::ui: Octopath-style panels, fonts (Cinzel, EB Garamond), dialogue box with
  typewriter + portraits + choices, menus, nameplates, toasts, ImGui theme
- DayCycle (dawn/day/dusk/night keyframes, sun + moon arcs), Weather overcast/mist/
  lightning, night-aware presets, stars and moon in the sky
- Hidden-window offscreen captures (Window visible=false, Renderer output FBO,
  OffscreenTarget); --screenshot never opens a window
- Camera::worldToScreen; CMake usable via add_subdirectory (HD2D_BUILD_DEMO,
  HD2D_ENGINE_ASSET_DIR)
This commit is contained in:
Gabrieal Jimmy
2026-10-02 15:05:23 -05:00
parent 16c03afd16
commit 83a1f16806
44 changed files with 2835 additions and 226 deletions

View File

@@ -104,7 +104,8 @@ so any documented endpoint parameter can be used.
- **Feet on the lowest opaque row.** Billboards are planted by the lowest
pixel with alpha ≥ 128 (`Texture::opaqueBounds`), so empty space below the
feet is fine but stray pixels below them make the sprite float.
feet is fine but stray pixels below them make the sprite float. Sheet
frames and atlas cells use a per-instance `anchor` instead (below).
- **No baked ground shadows.** The engine casts real shadows and draws a
contact blob under the player. PixelLab sometimes paints a grey ellipse under
objects; regenerate or erase it (alpha 0) before use.
@@ -115,19 +116,55 @@ so any documented endpoint parameter can be used.
- Sprite: `BillboardBatch b; b.loadTexture(path);` then push
`BillboardInstance{pos, sway, size, flip, tint}` and `b.upload()`.
`sway` 0 = rigid, 1 = grass-like wind and player push.
`sway` 0 = rigid, 1 = grass-like wind and player push. The remaining
fields default to "whole texture, no effects": `uv` (sub-rect), `anchor`
(feet row v), `turn` (extra yaw, radians; paper-turn / facing hints, shadow
cards stay sun-facing), `alpha` (0..1 fade, dithered in color and shadow
passes; there is no blending) and `flash` (0..1 toward bright white for hit
or capture flashes).
- Terrain layer: add the file stem to `kLayers` in `demo/DemoScene.cpp` (all
layers must be the same size) and reference its index from a
`TerrainMaterial`.
### Characters and animation (not wired up yet)
### Sprite sheets and atlases
One instance draws one sub-rect of its batch's texture: `uv` = (minU, minV,
maxU, maxV) in GL orientation (v = 0 is the image **bottom**), `size` = world
size of that rect (`BillboardBatch::cellSize(cellPixels, 32)`), and `anchor`
= v of the frame's feet row (`anchor < 0` falls back to the texture's lowest
opaque row, which is wrong for any frame but the bottom row). `flip`
mirrors inside the rect.
- **Grid sheets** (PNG authored top-down, equal cells, no spacing):
`uv = Texture::cellUV(tex.size(), cell, col, rowFromTop)` and
`anchor = Texture::cellAnchor(tex.size(), cell, rowFromTop, feetRowFromCellTop)`,
where `feetRowFromCellTop` is the pixel row (0 = top of the cell) the feet
stand on. Keep the feet on the same row in every frame of an animation or
the sprite bobs. Leave at least one transparent pixel between a frame's
art and its cell edge, otherwise filtering (and minified mips) bleeds the
neighbour frame in.
- **Runtime atlas** (`hd2d::SpriteAtlas`, for sprites created or downloaded
while the game runs): `atlas.init({64, 64}, cols, rows)`, `int slot =
atlas.add(image)` (bottom-centered in its cell, cropped to the cell, -1
when full), `set` / `remove` to replace or free a slot. Every cell has its
own 1 px transparent gutter. Share the page with
`batch.sharedTexture = &atlas.texture()` and use `uv = atlas.uv(slot)`,
`anchor = atlas.anchor(slot)` (the image's lowest opaque row). Images you
compose on the CPU should go through `bleedTransparentColors` (the atlas
and `loadImage` already do).
- `Texture::create/load(..., nearest = true)` makes a `GL_NEAREST`, mip-less
copy for crisp ImGui previews; billboards should keep the default.
- `hd2d_demo --sprite-test` shows a labelled row of atlas / sheet sprites
(flip, turn, alpha, flash, frame cycling) in front of the player.
### Characters and animation
PixelLab can make consistent characters with 4/8 directions and animations
(`POST /create-character-v3`, `/characters/animations`, then
`GET /characters/{id}/zip` or `/spritesheet`). To support them, add a new
`kind` to `tools/pixellab.py` (submit, poll `GET /background-jobs/{id}` every
5–10 s until `completed`, download) and a sprite-sheet animation path to
`BillboardBatch` (UV rect per frame). Keep the 32 px/unit density.
`GET /characters/{id}/zip` or `/spritesheet`). `tools/pixellab.py` does not
download them yet: add a new `kind` (submit, poll `GET /background-jobs/{id}`
every 5–10 s until `completed`, download) and play the frames through the
sprite-sheet path above. Keep the 32 px/unit density.
## 4. High-resolution textures for 3D models
@@ -244,10 +281,19 @@ Always look at the result; don't assume it looks right.
cmake -S . -B build -G Ninja && ninja -C build
./build/hd2d_demo --screenshot out.png --frames 90 --pos X,Z --dist 8 --yaw 30 --set dof=0
./build/hd2d_demo --screenshot rain.png --frames 90 --pos X,Z --rain 1
./build/hd2d_demo --screenshot night.png --frames 90 --pos X,Z --hour 23 # also try --hour 6 / 18
./build/hd2d_demo --screenshot ui.png --frames 420 --ui-demo # game UI (hd2d::ui)
```
- `--pos` puts the player (and camera target) at world X,Z; `--yaw` and
`--dist` orbit the camera; `--set dof=0` removes blur for inspection.
- Check every new model from at least two opposite yaws, in rain (wet look,
wind) and near water (reflection).
wind), at night (`--hour 23`: moonlight + fire light, emissives) and near
water (reflection).
- Check sprites at default zoom next to the player for scale and pixel size.
- UI fonts (`assets/fonts/`, loaded by `hd2d::ui::loadFonts`): OFL only, with
the font's `OFL.txt` saved as `<Family>-OFL.txt`; never modify or re-instance
a font with a Reserved Font Name (Cinzel). Dear ImGui's stb_truetype renders
a variable font's default instance, so choose fonts whose default weight
reads well. Check UI changes with `--ui-demo` at `--frames 120` (mid-reveal)
and `--frames 420`, at `--size 1920x1080` as well as the default size.

BIN
docs/ui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB