12#include <unordered_set>
20int gLastVisibleTileCount = 0;
21int gLastCustomVisualCount = 0;
22int gLastAtlasCount = 0;
23int gLastVisitedChunkCount = 0;
24int gLastVisitedCellCount = 0;
25double gAnimationTimeMs = 0.0;
34ViewCam fromEntity(graphics::Camera2D *ent) {
46Color solidForGid(uint32_t gid,
const Color &
tint) {
47 const uint32_t
h = gid * 2654435761u;
48 const float r = ((
h >> 0) & 255u) / 255.f;
49 const float g = ((
h >> 8) & 255u) / 255.f;
50 const float b = ((
h >> 16) & 255u) / 255.f;
54const TileLayer::Tileset::Atlas *atlasForGid(
const TileLayer::Tileset &ts, uint32_t gid) {
55 const TileLayer::Tileset::Atlas *
best =
nullptr;
56 for (
const auto &atlas : ts.atlases) {
57 if (gid >= uint32_t(atlas.firstGid) && (!
best || atlas.firstGid >
best->firstGid))
best = &atlas;
62bool atlasUV(
const TileLayer::Tileset::Atlas &atlas, uint32_t gid,
float &u0,
float &v0,
float &u1,
float &v1) {
63 if (!atlas.texture || atlas.columns <= 0 || atlas.tileW <= 0 || atlas.tileH <= 0)
return false;
64 if (gid < uint32_t(atlas.firstGid))
return false;
65 const int local = int(gid) - atlas.firstGid;
66 const int col =
local % atlas.columns;
67 const int row =
local / atlas.columns;
68 const float iw = float(atlas.texture->getWidth());
69 const float ih = float(atlas.texture->getHeight());
70 if (iw <= 0.f || ih <= 0.f)
return false;
72 const float px = float(atlas.margin + col * (atlas.tileW + atlas.spacing));
73 const float py = float(atlas.margin + row * (atlas.tileH + atlas.spacing));
76 u1 = (
px + float(atlas.tileW)) / iw;
77 v1 = (
py + float(atlas.tileH)) / ih;
81const TileLayer::Tileset::Visual *visualForGid(
const TileLayer::Tileset &ts, uint32_t gid) {
82 for (
const auto &visual : ts.
visuals)
83 if (visual.gid == int(gid)) return &visual;
87bool visualUV(
const TileLayer::Tileset::Atlas &atlas,
const TileLayer::Tileset::Visual &visual,
float &u0,
float &v0,
88 float &u1,
float &v1) {
89 if (!atlas.texture || visual.width <= 0 || visual.height <= 0)
return false;
90 const float iw = float(atlas.texture->getWidth());
91 const float ih = float(atlas.texture->getHeight());
92 if (iw <= 0.f || ih <= 0.f)
return false;
93 u0 = float(visual.x) / iw;
94 v0 = float(visual.y) / ih;
95 u1 = float(visual.x + visual.width) / iw;
96 v1 = float(visual.y + visual.height) / ih;
100bool subtileUV(
const TileLayer::Tileset::Atlas &atlas,
const TileLayer::Tileset::Visual::Subtile &part,
float &u0,
101 float &v0,
float &u1,
float &v1) {
102 if (!atlas.texture || part.width <= 0 || part.height <= 0)
return false;
103 const float iw = float(atlas.texture->getWidth());
104 const float ih = float(atlas.texture->getHeight());
105 if (iw <= 0.f || ih <= 0.f)
return false;
106 u0 = float(part.x) / iw;
107 v0 = float(part.y) / ih;
108 u1 = float(part.x + part.width) / iw;
109 v1 = float(part.y + part.height) / ih;
113const TileLayer::Tileset::Visual::SubtileFrame *subtileFrame(
const TileLayer::Tileset::Visual &visual) {
114 if (visual.subtileFrames.empty())
return nullptr;
116 for (
const auto &frame : visual.subtileFrames) total +=
std::
max(1, frame.durationMs);
117 int cursor = total > 0 ? int(std::fmod(gAnimationTimeMs,
double(total))) : 0;
118 for (
const auto &frame : visual.subtileFrames) {
119 cursor -= std::max(1, frame.durationMs);
120 if (
cursor < 0)
return &frame;
122 return &visual.subtileFrames.back();
125int64_t fileModtime(
const std::string &
path) {
126 auto *fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>(
"Filesystem");
127 if (!fs) fs = eve::filesystem::Filesystem::create();
129 if (!fs->getInfo(
path,
info))
return -1;
133uint32_t animatedGid(
const TileLayer::Tileset &ts, uint32_t gid) {
135 if (animation.gid !=
int(gid) || animation.frames.empty())
continue;
137 for (
const auto &frame : animation.frames) total +=
std::
max(1, frame.durationMs);
138 if (total <= 0)
return gid;
139 int cursor = int(std::fmod(gAnimationTimeMs,
double(total)));
140 for (
const auto &frame : animation.frames) {
141 cursor -= std::max(1, frame.durationMs);
142 if (
cursor < 0)
return uint32_t(frame.gid);
148void applyTiledTransform(graphics::DrawItem2D &item, uint32_t raw) {
149 const bool horizontal = (raw & 0x80000000u) != 0;
150 const bool vertical = (raw & 0x40000000u) != 0;
151 const bool diagonal = (raw & 0x20000000u) != 0;
153 item.flipX = horizontal;
154 item.flipY = vertical;
157 item.rotation = horizontal ? 90.f : 270.f;
158 item.flipX = horizontal == vertical;
162bool chunkVisible(
const TileLayer::Config &cfg,
const TileLayer::Tileset &ts,
const ViewCam &cam,
163 int viewWidth,
int viewHeight,
int x0,
int y0,
int x1,
int y1,
float scaleX,
165 if (!cam.valid || viewWidth <= 0 || viewHeight <= 0)
return true;
166 float minX = 1e30f, minY = 1e30f, maxX = -1e30f, maxY = -1e30f;
167 const int xs[] = {x0, std::max(x0, x1 - 1)};
168 const int ys[] = {y0, std::max(y0, y1 - 1)};
171 float wx = 0.f,
wy = 0.f;
173 minX = std::min(minX,
wx);
174 minY = std::min(minY,
wy);
175 maxX = std::max(maxX,
wx + cfg.tileW * scaleX);
176 maxY = std::max(maxY,
wy + cfg.tileH * scaleY);
179 float expandX = cfg.tileW * scaleX;
180 float expandY = cfg.tileH * scaleY;
181 for (
const auto &visual : ts.
visuals) {
182 expandX = std::max(expandX,
float(visual.width) * scaleX + std::fabs(visual.pivotX) * scaleX);
183 expandY = std::max(expandY,
float(visual.height) * scaleY + std::fabs(visual.pivotY) * scaleY);
189 const float zoom = cam.zoom > 0.f ? cam.zoom : 0.0001f;
190 const float left = cam.x - float(viewWidth) * 0.5f /
zoom;
191 const float right = cam.x + float(viewWidth) * 0.5f /
zoom;
192 const float top = cam.y - float(viewHeight) * 0.5f /
zoom;
193 const float bottom = cam.y + float(viewHeight) * 0.5f /
zoom;
194 return maxX >=
left && minX <= right && maxY >=
top && minY <=
bottom;
203 gLastVisibleTileCount = 0;
204 gLastCustomVisualCount = 0;
206 gLastVisitedChunkCount = 0;
207 gLastVisitedCellCount = 0;
208 if (ecs::current()->getManager<TileLayer>() ==
nullptr)
return;
210 std::unordered_set<graphics::Texture *> atlases;
214 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
216 if (!
draw->visible || cfg->mapW <= 0 || cfg->mapH <= 0)
continue;
217 if (
tiles->gids.size() < size_t(cfg->mapW * cfg->mapH))
continue;
220 const ViewCam cam = fromEntity(
draw->camera);
221 const float scaleX =
draw->visualScaleX > 0.f ?
draw->visualScaleX : 1.f;
222 const float scaleY =
draw->visualScaleY > 0.f ?
draw->visualScaleY : 1.f;
224 const int chunkColumns = std::max(1,
tiles->chunkColumns);
225 const int chunkRows = std::max(1,
tiles->chunkRows);
226 for (
int cy = 0;
cy < chunkRows; ++
cy) {
227 for (
int cx = 0;
cx < chunkColumns; ++
cx) {
228 const int chunkIndex =
cy * chunkColumns +
cx;
229 if (!
tiles->chunks.empty() &&
230 (chunkIndex >=
int(
tiles->chunks.size()) ||
231 tiles->chunks[
size_t(chunkIndex)].nonEmpty == 0))
237 if (!chunkVisible(*cfg, *ts, cam, viewWidth, viewHeight, x0, y0, x1, y1, scaleX,
240 ++gLastVisitedChunkCount;
241 for (
int ty = y0; ty < y1; ++ty) {
242 for (
int tx = x0; tx < x1; ++tx) {
243 ++gLastVisitedCellCount;
244 const uint32_t raw =
tiles->gids[size_t(ty * cfg->mapW + tx)];
245 const uint32_t gid = animatedGid(*ts,
tileGid(raw));
246 if (gid == 0)
continue;
250 item.
w = cfg->tileW * scaleX;
251 item.
h = cfg->tileH * scaleY;
262 applyTiledTransform(item, raw);
264 float u0, v0, u1, v1;
265 const auto* visual = visualForGid(*ts, gid);
266 const auto *atlas = atlasForGid(*ts, gid);
267 const auto *subtiles = visual ? subtileFrame(*visual) :
nullptr;
268 if (subtiles && atlas) {
269 for (
const auto &part : subtiles->parts) {
271 if (!subtileUV(*atlas, part, u0, v0, u1, v1))
continue;
272 float centerX = (part.offsetX + float(part.width) * 0.5f) * scaleX
273 - cfg->tileW * scaleX * 0.5f;
274 float centerY = (part.offsetY + float(part.height) * 0.5f) * scaleY
275 - cfg->tileH * scaleY * 0.5f;
276 if (raw & 0x20000000u) std::swap(centerX, centerY);
277 if (raw & 0x80000000u) centerX = -centerX;
278 if (raw & 0x40000000u) centerY = -centerY;
279 quarter.
x += cfg->tileW * scaleX * 0.5f + centerX
280 - float(part.width) * scaleX * 0.5f;
281 quarter.
y += cfg->tileH * scaleY * 0.5f + centerY
282 - float(part.height) * scaleY * 0.5f;
283 quarter.
w = float(part.width) * scaleX;
284 quarter.
h = float(part.height) * scaleY;
285 quarter.
texture = atlas->texture;
286 quarter.
hasUV =
true;
293 out.push_back(quarter);
294 ++gLastCustomVisualCount;
296 ++gLastVisibleTileCount;
297 if (atlas->texture) atlases.insert(atlas->texture);
300 if (visual && atlas && visualUV(*atlas, *visual, u0, v0, u1, v1)) {
301 ++gLastCustomVisualCount;
302 item.
x -= visual->pivotX * scaleX;
303 item.
y -= visual->pivotY * scaleY;
304 item.
w = float(visual->width) * scaleX;
305 item.
h = float(visual->height) * scaleY;
306 item.
depthY += visual->sortBias;
314 }
else if (atlas && atlasUV(*atlas, gid, u0, v0, u1, v1)) {
328 ++gLastVisibleTileCount;
335 gLastAtlasCount = int(atlases.size());
340 std::vector<graphics::DrawItem2D> items;
353 if (dt > 0.f && std::isfinite(dt)) gAnimationTimeMs += double(dt) * 1000.0;
357 if (ecs::current()->getManager<TileLayer>() ==
nullptr)
return 0;
360 auto view = ecs::View<TileLayer, TileLayer::Config, TileLayer::Resource>();
361 for (
auto it =
view.begin(); it !=
view.end(); ++it) {
362 auto [cfg, res] = *it;
363 if (!res->autoReload || res->path.empty() || !cfg->entity)
continue;
365 const int64_t mt = fileModtime(res->path);
366 bool changed = mt >= 0 && mt != res->modtime;
367 for (
size_t index = 0; !changed &&
index < res->dependencyPaths.size(); ++
index) {
368 const int64_t dependencyModtime = fileModtime(res->dependencyPaths[
index]);
369 const int64_t known =
index < res->dependencyModtimes.size() ? res->dependencyModtimes[
index] : -1;
370 changed = dependencyModtime >= 0 && dependencyModtime != known;
372 if (!changed)
continue;
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
std::map< Cell, int > best
std::vector< TileLayer::Tileset::Visual > visuals
std::vector< TileLayer::Tileset::Animation > animations
std::vector< WfcTile > tiles
int getWidth() const
Returns the width.
int getHeight() const
Returns the height.
static void drawItems(Graphics &gfx, std::vector< DrawItem2D > &items, bool present)
Sort and draw items. If present=true, calls gfx.present() at the end. Map::render uses present=false ...
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
static int lastVisibleTileCount()
Number of non-empty visible tiles collected by the last pass.
static void collect(std::vector< graphics::DrawItem2D > &out)
Append non-empty visible tiles into a shared 2D draw queue.
static int lastVisitedCellCount()
Last visited cell count.
static int lastAtlasCount()
Number of distinct atlas textures referenced by the last pass.
static void render(graphics::Graphics *gfx)
Collect + draw tiles only (no present). Null gfx is a no-op.
static int lastCustomVisualCount()
Number of collected tiles using irregular per-GID metadata.
static void update(float dt)
Advance TileSet animation clocks once per map update.
static int lastVisitedChunkCount()
Spatial-index work counters for deterministic performance regressions.
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
bool reloadConfigFile(TileLayer *layer, std::string *error)
Reloads config file.
uint32_t tileGid(uint32_t raw)
Strip Tiled flip / rotate flags; keep low 28 bits.
void tileToWorld(const TileLayer::Config &cfg, int tx, int ty, float &wx, float &wy)
Converts a tile index to world pixels using the layer's projection.
float tileToDepthY(const TileLayer::Config &cfg, int tx, int ty)
Painter's-algorithm depth for the tile under the layer's projection.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
glm::vec4 Color
Render-neutral RGBA color shared by graphics-facing modules.
Shared 2D draw queue item (sprites + tiles).
float camX
Screen-space camera already resolved; if cameraEntity set, RenderSystem resolves.
static constexpr int kChunkSize