Files
EVEngine/src/modules/map/DualGrid.h
T
西风逍遥游andCursor Agent 1dbea1ebe7 feat(map): Tilemap dual-grid autotile resolve (#15)
* feat(map): add dual-grid autotile resolve for TileLayer

Implement Oskar Stålberg-style dual-grid: logic layer paints fill/empty,
display layer gets a half-tile offset 15-tile resolve. Document that Tiled
has no native dual-grid (Terrain/Wang Corner Set is same-grid only).

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* fix(map): use ComponentRef for DualGrid config access

TileLayer::config() returns ecs::ComponentRef, not a raw pointer.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* test(map): fix dual-grid filledGid expectations and clean layers

Correct corner sampling for a single logic cell (BR vs TL) and clear
layers after resolve tests to avoid ECS draw pollution.

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

* test(map): fix ComponentRef usage in dual-grid orientation tests

Co-authored-by: 西风逍遥游 <sunxfancy@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-10 14:03:37 +08:00

94 lines
3.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "map/TileLayer.h"
#include <array>
#include <cstdint>
#include <string>
namespace eve::map {
/**
* Dual-grid autotile (Oskar Stålberg style).
*
* Logic cells are painted on the world grid; display tiles sit on a second grid
* offset by half a step in the active projection, so each display cell samples
* exactly four logic cells (corners). That yields a 4-bit mask → 15 tiles (+ empty).
*
* Works with Orthogonal / Isometric / Staggered / Hexagonal: the corner mask is
* computed in tile-index space; the display layer inherits orientation params and
* receives a projection-correct half-step origin offset.
*
* Tiled does not provide this offset dual-grid workflow natively. Recommended
* authoring path: paint a binary logic tile layer in Tiled (or at runtime), then
* call resolveDualGrid() to fill a display TileLayer. Tiled's Terrain / Wang
* Corner Set is a related but different same-grid matching system.
*/
struct DualGridOptions {
/** 0 = any non-zero logic GID counts as filled; else only this GID. */
int filledGid = 0;
/**
* GID of atlas local id 0 on the display tileset.
* 0 = use display->getTilesetFirstGid() (fallback 1).
*/
int firstDisplayGid = 0;
/** Apply projection-correct half-step origin offset on the display layer. */
bool applyHalfOffset = true;
/** Hide the logic layer after resolve (logic stays for gameplay queries). */
bool hideLogic = true;
/**
* When true, use the default SpriteCook-style 4x4 atlas frame table.
* When false, GID = firstDisplayGid + mask (mask 0 → empty).
*/
bool useDefaultFrameTable = true;
};
/** Pack four corner occupancy bits: TL=1, TR=2, BL=4, BR=8. */
inline int dualGridMaskFromCorners(bool tl, bool tr, bool bl, bool br) {
return (tl ? 1 : 0) | (tr ? 2 : 0) | (bl ? 4 : 0) | (br ? 8 : 0);
}
/**
* Default 4x4 atlas frame index for mask 0..15.
* -1 means draw nothing. Matches the SpriteCook / common dual-grid sheet layout.
*/
int dualGridDefaultFrame(int mask);
/** Copy of the default frame table (16 entries). */
const std::array<int, 16> &dualGridDefaultFrameTable();
/**
* Half-step origin delta for the display layer (added to logic origin).
* Orthogonal: (-tileW/2, -tileH/2)
* Isometric: (0, -tileH/2) — equivalent to logic coords (tx-0.5, ty-0.5)
* Staggered/Hex Y: (-tileW/2, -pitchY/2)
* Staggered/Hex X: (-pitchX/2, -tileH/2)
*/
void dualGridHalfOffset(const TileLayer::Config &cfg, float &offX, float &offY);
/** Whether logic cell (tx,ty) is filled under options. Out of bounds → false. */
bool dualGridLogicFilled(TileLayer &logic, int tx, int ty, int filledGid);
/**
* 4-bit corner mask for display cell (dx,dy) on a (logicW+1)×(logicH+1) grid.
* Samples logic (dx-1,dy-1), (dx,dy-1), (dx-1,dy), (dx,dy) in index space
* (orientation-independent topology).
*/
int dualGridMaskAt(TileLayer &logic, int dx, int dy, int filledGid = 0);
/**
* Resolve logic → display dual-grid tiles.
* Resizes display to (logicW+1)×(logicH+1), copies tile size / orientation /
* stagger / hex from logic, optionally applies projection-correct half-step
* origin offset, writes GIDs.
* Returns false and sets error on invalid args.
*/
bool resolveDualGrid(TileLayer *logic, TileLayer *display, const DualGridOptions &opts,
std::string *error = nullptr);
/** Convenience: any non-zero filled, default frame table, half offset, hide logic. */
bool resolveDualGrid(TileLayer *logic, TileLayer *display, std::string *error = nullptr);
} // namespace eve::map