载入中...
搜索中...
未找到
DualGrid.cpp
浏览该文件的文档.
1#include "map/DualGrid.h"
3
4#include <algorithm>
5#include <cmath>
6#include <limits>
7
8namespace eve::map {
9namespace {
10
11// SpriteCook / common dual-grid 4×4 sheet: index by 4-bit corner mask.
12// mask 0 → empty; other values map to atlas local ids 0..15.
13constexpr std::array<int, 16> kDefaultFrameByMask = {
14 -1, 15, 8, 9, 0, 11, 14, 7, 13, 4, 1, 10, 3, 2, 5, 6,
15};
16
17int resolveFirstGid(TileLayer* display, int firstDisplayGid) {
18 if (firstDisplayGid > 0) return firstDisplayGid;
19 const int ts = display->getTilesetFirstGid();
20 return ts > 0 ? ts : 1;
21}
22
23float staggerPitchY(const TileLayer::Config& cfg) {
24 const bool hex = cfg.orientation == MapOrientation::Hexagonal && cfg.hexSideLength > 0.f;
25 return hex ? (cfg.tileH + cfg.hexSideLength) * 0.5f : cfg.tileH * 0.5f;
26}
27
28float staggerPitchX(const TileLayer::Config& cfg) {
29 const bool hex = cfg.orientation == MapOrientation::Hexagonal && cfg.hexSideLength > 0.f;
30 return hex ? (cfg.tileW + cfg.hexSideLength) * 0.5f : cfg.tileW * 0.5f;
31}
32
33float smoothstep(float a, float b, float x) {
34 if (a == b) return x < a ? 0.f : 1.f;
35 const float t = std::clamp((x - a) / (b - a), 0.f, 1.f);
36 return t * t * (3.f - 2.f * t);
37}
38
39uint32_t hash2(uint32_t seed, int x, int y) {
40 uint32_t h = seed ^ uint32_t(x) * 0x9e3779b9u ^ uint32_t(y) * 0x85ebca6bu;
41 h ^= h >> 16;
42 h *= 0x7feb352du;
43 h ^= h >> 15;
44 h *= 0x846ca68bu;
45 return h ^ (h >> 16);
46}
47
48float valueNoise(uint32_t seed, float x, float y) {
49 const int x0 = int(std::floor(x)), y0 = int(std::floor(y));
50 const float fx = x - float(x0), fy = y - float(y0);
51 const float sx = fx * fx * (3.f - 2.f * fx), sy = fy * fy * (3.f - 2.f * fy);
52 auto sample = [seed](int px, int py) { return float(hash2(seed, px, py) & 0xffffu) / 32767.5f - 1.f; };
53 const float a = std::lerp(sample(x0, y0), sample(x0 + 1, y0), sx);
54 const float b = std::lerp(sample(x0, y0 + 1), sample(x0 + 1, y0 + 1), sx);
55 return std::lerp(a, b, sy);
56}
57
58void distanceTransform(const std::vector<uint8_t>& binary, int width, int height, uint8_t target,
59 std::vector<float>& out) {
60 constexpr float diagonal = 1.41421356237f;
61 const float infinity = std::numeric_limits<float>::max() / 4.f;
62 out.resize(binary.size());
63 for (size_t i = 0; i < binary.size(); ++i) out[i] = binary[i] == target ? 0.f : infinity;
64 for (int y = 0; y < height; ++y)
65 for (int x = 0; x < width; ++x) {
66 float& d = out[size_t(y * width + x)];
67 if (x > 0) d = std::min(d, out[size_t(y * width + x - 1)] + 1.f);
68 if (y > 0) d = std::min(d, out[size_t((y - 1) * width + x)] + 1.f);
69 if (x > 0 && y > 0) d = std::min(d, out[size_t((y - 1) * width + x - 1)] + diagonal);
70 if (x + 1 < width && y > 0) d = std::min(d, out[size_t((y - 1) * width + x + 1)] + diagonal);
71 }
72 for (int y = height - 1; y >= 0; --y)
73 for (int x = width - 1; x >= 0; --x) {
74 float& d = out[size_t(y * width + x)];
75 if (x + 1 < width) d = std::min(d, out[size_t(y * width + x + 1)] + 1.f);
76 if (y + 1 < height) d = std::min(d, out[size_t((y + 1) * width + x)] + 1.f);
77 if (x + 1 < width && y + 1 < height) d = std::min(d, out[size_t((y + 1) * width + x + 1)] + diagonal);
78 if (x > 0 && y + 1 < height) d = std::min(d, out[size_t((y + 1) * width + x - 1)] + diagonal);
79 }
80}
81
82} // namespace
83
84float DualGridMaskAtlas::coverageAt(int mask, int x, int y) const {
85 if (mask < 0 || mask > 15 || x < 0 || y < 0 || x >= width || y >= height) return 0.f;
86 return coverage[(size_t(mask) * size_t(height) + size_t(y)) * size_t(width) + size_t(x)];
87}
88
89float DualGridMaskAtlas::signedDistanceAt(int mask, int x, int y) const {
90 if (mask < 0 || mask > 15 || x < 0 || y < 0 || x >= width || y >= height) return 0.f;
91 return signedDistance[(size_t(mask) * size_t(height) + size_t(y)) * size_t(width) + size_t(x)];
92}
93
94float DualGridMaskAtlas::bandAt(int mask, int x, int y, float center, float halfWidth, float softness) const {
95 if (halfWidth < 0.f || softness < 0.f) return 0.f;
96 const float d = std::abs(signedDistanceAt(mask, x, y) - center);
97 return 1.f - smoothstep(halfWidth, halfWidth + softness, d);
98}
99
101 if (config.width < 2 || config.height < 2 || config.width > 2048 || config.height > 2048 ||
102 !std::isfinite(config.edgeWidth) || config.edgeWidth <= 0.f || config.edgeWidth > 0.5f ||
103 !std::isfinite(config.noiseScale) || config.noiseScale < 0.f || !std::isfinite(config.noiseStrength) ||
104 config.noiseStrength < 0.f || config.noiseStrength > 0.5f) {
106 eve::DiagnosticCode::InvalidArgument, "invalid dual-grid mask dimensions or shaping parameters",
107 "map.dual_grid_mask.config"));
108 }
109
110 DualGridMaskAtlas atlas;
111 atlas.width = config.width;
112 atlas.height = config.height;
113 const size_t framePixels = size_t(config.width) * size_t(config.height);
114 atlas.coverage.resize(framePixels * 16u);
115 atlas.signedDistance.resize(framePixels * 16u);
116 std::vector<uint8_t> binary(framePixels);
117 std::vector<float> insideDistance, outsideDistance;
118 const float distanceNorm = float(std::max(config.width, config.height));
119
120 for (int mask = 0; mask < 16; ++mask) {
121 const float top = (mask & 1) ? 1.f : 0.f;
122 const float right = (mask & 2) ? 1.f : 0.f;
123 const float left = (mask & 4) ? 1.f : 0.f;
124 const float bottom = (mask & 8) ? 1.f : 0.f;
125 for (int y = 0; y < config.height; ++y)
126 for (int x = 0; x < config.width; ++x) {
127 const float u = (float(x) + 0.5f) / float(config.width);
128 const float v = (float(y) + 0.5f) / float(config.height);
129 // Isometric diamond vertices map the logical TL/TR/BL/BR bits to visual T/R/L/B.
130 const float topWeight = (1.f - v) * (1.f - std::abs(2.f * u - 1.f));
131 const float bottomWeight = v * (1.f - std::abs(2.f * u - 1.f));
132 const float leftWeight = (1.f - u) * (1.f - std::abs(2.f * v - 1.f));
133 const float rightWeight = u * (1.f - std::abs(2.f * v - 1.f));
134 const float sum = std::max(topWeight + rightWeight + leftWeight + bottomWeight, 1e-6f);
135 float field = (top * topWeight + right * rightWeight + left * leftWeight + bottom * bottomWeight) / sum;
136 field += valueNoise(config.seed ^ uint32_t(mask) * 0x9e3779b9u, u * config.noiseScale,
137 v * config.noiseScale) *
138 config.noiseStrength;
139 binary[size_t(y * config.width + x)] = field >= 0.5f ? 1u : 0u;
140 }
141 distanceTransform(binary, config.width, config.height, 0u, outsideDistance);
142 distanceTransform(binary, config.width, config.height, 1u, insideDistance);
143 const size_t base = size_t(mask) * framePixels;
144 for (size_t i = 0; i < framePixels; ++i) {
145 float sd;
146 if (mask == 0)
147 sd = -1.f;
148 else if (mask == 15)
149 sd = 1.f;
150 else
151 sd = binary[i] ? outsideDistance[i] / distanceNorm : -insideDistance[i] / distanceNorm;
152 sd = std::clamp(sd, -1.f, 1.f);
153 atlas.signedDistance[base + i] = sd;
154 atlas.coverage[base + i] = smoothstep(-config.edgeWidth, config.edgeWidth, sd);
155 }
156 }
157 return eve::Result<DualGridMaskAtlas>::success(std::move(atlas));
158}
159
161 const DualGridRgbaImage& terrainB,
162 const DualGridMaskConfig& config) {
163 const size_t expected = size_t(config.width) * size_t(config.height) * 4u;
164 if (terrainA.width != config.width || terrainA.height != config.height || terrainB.width != config.width ||
165 terrainB.height != config.height || terrainA.pixels.size() != expected || terrainB.pixels.size() != expected) {
168 "transition tiles must be same-size RGBA8 images matching mask config", "map.dual_grid_mask.tiles"));
169 }
170 auto generated = generateDualGridMaskAtlas(config);
171 if (!generated) return eve::Result<DualGridRgbaImage>::failure(generated.status());
172
173 DualGridRgbaImage result;
174 result.width = config.width * 4;
175 result.height = config.height * 4;
176 result.pixels.resize(size_t(result.width) * size_t(result.height) * 4u);
177 for (int mask = 0; mask < 16; ++mask) {
178 const int frameX = (mask % 4) * config.width;
179 const int frameY = (mask / 4) * config.height;
180 for (int y = 0; y < config.height; ++y)
181 for (int x = 0; x < config.width; ++x) {
182 const float coverage = generated.value().coverageAt(mask, x, y);
183 const size_t source = size_t(y * config.width + x) * 4u;
184 const size_t target = size_t((frameY + y) * result.width + frameX + x) * 4u;
185 for (size_t channel = 0; channel < 4; ++channel) {
186 const float mixed = std::lerp(float(terrainA.pixels[source + channel]),
187 float(terrainB.pixels[source + channel]), coverage);
188 result.pixels[target + channel] = uint8_t(std::clamp(std::lround(mixed), 0l, 255l));
189 }
190 }
191 }
192 return eve::Result<DualGridRgbaImage>::success(std::move(result));
193}
194
195const std::array<int, 16>& dualGridDefaultFrameTable() { return kDefaultFrameByMask; }
196
198 if (mask < 0 || mask > 15) return -1;
199 return kDefaultFrameByMask[size_t(mask)];
200}
201
202void dualGridHalfOffset(const TileLayer::Config& cfg, float& offX, float& offY) {
203 switch (cfg.orientation) {
205 // tileToWorld(tx-0.5, ty-0.5) with same iso formula ⇒ origin (ox, oy - th/2).
206 offX = 0.f;
207 offY = -cfg.tileH * 0.5f;
208 break;
211 if (cfg.staggerAxis == StaggerAxis::Y) {
212 offX = -cfg.tileW * 0.5f;
213 offY = -staggerPitchY(cfg) * 0.5f;
214 } else {
215 offX = -staggerPitchX(cfg) * 0.5f;
216 offY = -cfg.tileH * 0.5f;
217 }
218 break;
220 default:
221 offX = -cfg.tileW * 0.5f;
222 offY = -cfg.tileH * 0.5f;
223 break;
224 }
225}
226
227bool dualGridLogicFilled(TileLayer& logic, int tx, int ty, int filledGid) {
228 auto cfg = logic.config();
229 if (tx < 0 || ty < 0 || tx >= cfg->mapW || ty >= cfg->mapH) return false;
230 const int gid = int(tileGid(uint32_t(logic.getTile(tx, ty))));
231 if (gid == 0) return false;
232 if (filledGid == 0) return true;
233 return gid == filledGid;
234}
235
236int dualGridMaskAt(TileLayer& logic, int dx, int dy, int filledGid) {
237 const bool tl = dualGridLogicFilled(logic, dx - 1, dy - 1, filledGid);
238 const bool tr = dualGridLogicFilled(logic, dx, dy - 1, filledGid);
239 const bool bl = dualGridLogicFilled(logic, dx - 1, dy, filledGid);
240 const bool br = dualGridLogicFilled(logic, dx, dy, filledGid);
241 return dualGridMaskFromCorners(tl, tr, bl, br);
242}
243
244bool resolveDualGrid(TileLayer* logic, TileLayer* display, const DualGridOptions& opts, std::string* error) {
245 if (!logic || !display) {
246 if (error) *error = "resolveDualGrid: logic and display layers required";
247 return false;
248 }
249 if (logic == display) {
250 if (error) *error = "resolveDualGrid: logic and display must be different layers";
251 return false;
252 }
253
254 const int logicW = logic->getMapWidth();
255 const int logicH = logic->getMapHeight();
256 if (logicW <= 0 || logicH <= 0) {
257 if (error) *error = "resolveDualGrid: logic layer has empty size";
258 return false;
259 }
260
261 const float tileW = logic->getTileWidth();
262 const float tileH = logic->getTileHeight();
263 display->setTileSize(tileW, tileH);
264 display->resize(logicW + 1, logicH + 1);
265
266 auto lc = logic->config();
267 auto dc = display->config();
268 dc->orientation = lc->orientation;
269 dc->staggerAxis = lc->staggerAxis;
270 dc->staggerIndex = lc->staggerIndex;
271 dc->hexSideLength = lc->hexSideLength;
272
273 if (opts.applyHalfOffset) {
274 float offX = 0.f, offY = 0.f;
275 dualGridHalfOffset(*lc, offX, offY);
276 display->setOrigin(logic->getX() + offX, logic->getY() + offY);
277 } else {
278 display->setOrigin(logic->getX(), logic->getY());
279 }
280
281 // Keep draw bookkeeping in sync when display is freshly created.
282 display->setLayer(logic->getLayer());
283 display->setCamera(logic->draw()->camera);
284 display->setCanvas(logic->draw()->canvas);
285
286 const int firstGid = resolveFirstGid(display, opts.firstDisplayGid);
287 const int filledGid = opts.filledGid;
288
289 for (int dy = 0; dy < logicH + 1; ++dy) {
290 for (int dx = 0; dx < logicW + 1; ++dx) {
291 const int mask = dualGridMaskAt(*logic, dx, dy, filledGid);
292 int frame = -1;
293 if (opts.useDefaultFrameTable) {
294 frame = dualGridDefaultFrame(mask);
295 } else if (mask != 0) {
296 frame = mask;
297 }
298 const int gid = (frame < 0) ? 0 : firstGid + frame;
299 display->setTile(dx, dy, gid);
300 }
301 }
302
303 if (opts.hideLogic) logic->setVisible(false);
304 display->setVisible(true);
305
306 if (error) error->clear();
307 return true;
308}
309
310bool resolveDualGrid(TileLayer* logic, TileLayer* display, std::string* error) {
311 return resolveDualGrid(logic, display, DualGridOptions{}, error);
312}
313
314} // namespace eve::map
LogicalId target
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int mask
float py
float u
Definition Grass.cpp:233
float v
HexVec3 left
HexVec3 right
int h
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float tr
std::string error
Definition Package.cpp:60
bool diagonal
float halfWidth
std::uint32_t seed
Definition PointSet.cpp:807
float d
float t
float dy
float dx
int firstGid
int tileW
int tileH
const UnitySourceAsset & source
float frameY
float frameX
float bottom
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
Definition TileLayer.h:32
float getTileWidth()
Tile size in pixels.
Definition TileLayer.cpp:37
void setOrigin(float x, float y)
World origin of the layer (pixels).
Definition TileLayer.cpp:27
void setTile(int tx, int ty, int gid)
Tile GID access; 0 = empty.
Definition TileLayer.cpp:56
void resize(int mapW, int mapH)
Resizes the tile grid (existing GIDs preserved where possible).
Definition TileLayer.cpp:46
void setLayer(int layer)
Draw order layer / visibility / tint.
void setCamera(graphics::Camera2D *camera)
int getTile(int tx, int ty)
Definition TileLayer.cpp:72
void setTileSize(float tileW, float tileH)
Definition TileLayer.cpp:40
void setCanvas(graphics::Canvas *canvas)
Draw target canvas / camera.
void setVisible(bool visible)
int getMapWidth()
Map dimensions in tiles.
Definition TileLayer.cpp:35
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
int dualGridMaskAt(TileLayer &logic, int dx, int dy, int filledGid)
4-bit corner mask for display cell (dx,dy) on a (logicW+1)×(logicH+1) grid. Samples logic (dx-1,...
Definition DualGrid.cpp:236
bool dualGridLogicFilled(TileLayer &logic, int tx, int ty, int filledGid)
Whether logic cell (tx,ty) is filled under options. Out of bounds → false.
Definition DualGrid.cpp:227
int dualGridMaskFromCorners(bool tl, bool tr, bool bl, bool br)
Pack four corner occupancy bits: TL=1, TR=2, BL=4, BR=8.
Definition DualGrid.h:128
eve::Result< DualGridRgbaImage > bakeDualGridTransitionAtlas(const DualGridRgbaImage &terrainA, const DualGridRgbaImage &terrainB, const DualGridMaskConfig &config)
Bake two ordinary same-size RGBA8 tiles into a row-major 4x4 transition atlas.
Definition DualGrid.cpp:160
void dualGridHalfOffset(const TileLayer::Config &cfg, float &offX, float &offY)
Half-step origin delta for the display layer (added to logic origin). Orthogonal: (-tileW/2,...
Definition DualGrid.cpp:202
uint32_t tileGid(uint32_t raw)
Strip Tiled flip / rotate flags; keep low 28 bits.
Definition TileLayer.h:383
const std::array< int, 16 > & dualGridDefaultFrameTable()
Copy of the default frame table (16 entries).
Definition DualGrid.cpp:195
bool resolveDualGrid(TileLayer *logic, TileLayer *display, const DualGridOptions &opts, std::string *error)
Resolve logic → display dual-grid tiles. Resizes display to (logicW+1)×(logicH+1),...
Definition DualGrid.cpp:244
int dualGridDefaultFrame(int mask)
Default 4x4 atlas frame index for mask 0..15. -1 means draw nothing. Matches the SpriteCook / common ...
Definition DualGrid.cpp:197
eve::Result< DualGridMaskAtlas > generateDualGridMaskAtlas(const DualGridMaskConfig &config)
Generate the complete reusable 16-frame dual-grid mask atlas.
Definition DualGrid.cpp:100
double sample(const Heightmap &map, double u, double v)
Sample.
Owning CPU result containing all 16 dual-grid masks and their signed-distance fields.
Definition DualGrid.h:72
float signedDistanceAt(int mask, int x, int y) const
Read normalized signed distance. Invalid coordinates return zero.
Definition DualGrid.cpp:89
std::vector< float > coverage
Definition DualGrid.h:75
std::vector< float > signedDistance
Definition DualGrid.h:76
float bandAt(int mask, int x, int y, float center, float halfWidth, float softness=0.01f) const
Select a band such as wet sand or foam around the generated boundary.
Definition DualGrid.cpp:94
float coverageAt(int mask, int x, int y) const
Read normalized coverage. Invalid coordinates return zero.
Definition DualGrid.cpp:84
Deterministic settings for the reusable 16-frame procedural transition-mask atlas.
Definition DualGrid.h:52
Dual-grid autotile (Oskar Stålberg style).
Definition DualGrid.h:32
bool hideLogic
Hide the logic layer after resolve (logic stays for gameplay queries).
Definition DualGrid.h:43
int filledGid
0 = any non-zero logic GID counts as filled; else only this GID.
Definition DualGrid.h:34
bool applyHalfOffset
Apply projection-correct half-step origin offset on the display layer.
Definition DualGrid.h:41
int firstDisplayGid
GID of atlas local id 0 on the display tileset. 0 = use display->getTilesetFirstGid() (fallback 1).
Definition DualGrid.h:39
bool useDefaultFrameTable
When true, use the default SpriteCook-style 4x4 atlas frame table. When false, GID = firstDisplayGid ...
Definition DualGrid.h:48
Owning RGBA8 image used by the renderer-neutral transition-atlas baker.
Definition DualGrid.h:87
std::vector< uint8_t > pixels
Definition DualGrid.h:90
Config public API.
Definition TileLayer.h:41
MapOrientation orientation
Definition TileLayer.h:50