27float toFloat(
const std::string &
s,
float fallback) {
28 if (
s.empty())
return fallback;
36graphics::Graphics *gfxOrNull() {
return getModInst(graphics, Graphics); }
41 if (!world)
return false;
47 if (!world)
return false;
48 auto it = states_.find(world);
49 if (it == states_.end())
return false;
50 destroyAll(it->second);
56 return world !=
nullptr && states_.count(world) > 0;
66 if (!gfx)
return nullptr;
71void BuildingFx::createVisual(WorldState &st,
const building::BuildingDefinition &def,
72 const building::PlacedBuilding &pb,
73 building::PlacementWorld *world, Visual &
v,
float alpha) {
75 const float cellW = world->getGrid().cellW;
76 const float cellH = world->getGrid().cellH;
77 int effW = def.footprintW;
78 int effH = def.footprintH;
82 auto *r = graphics::Renderable3D::create();
83 float wx = 0.f, wy = 0.f, wz = 0.f;
84 world->cellToWorld3D(pb.originCellX, pb.originCellY, pb.elevation, wx, wy, wz);
85 auto tr = r->transform();
86 tr->x = wx + toFloat(def.getVisual3d(
"offsetX"), 0.f);
87 tr->y = wy + toFloat(def.getVisual3d(
"offsetY"), 0.f);
88 tr->z = wz + toFloat(def.getVisual3d(
"offsetZ"), 0.f);
89 const float height = toFloat(def.getVisual3d(
"height"), 1.f);
90 tr->sx = float(effW) * cellW;
92 tr->sz = float(effH) * cellH;
93 const float rad = pb.rotationDeg * 3.14159265f / 180.f;
99 auto mr = r->meshRenderer();
100 mr->mesh = cubeMesh(gfxOrNull());
101 mr->r = toFloat(def.getVisual3d(
"colorR"), 0.62f);
102 mr->g = toFloat(def.getVisual3d(
"colorG"), 0.62f);
103 mr->b = toFloat(def.getVisual3d(
"colorB"), 0.62f);
106 mr->castShadow =
true;
107 mr->receiveShadow =
true;
110 auto *r = graphics::Renderable2D::create();
111 auto tr = r->transform();
112 tr->x = pb.worldX + toFloat(def.getVisual2d(
"offsetX"), 0.f);
113 tr->y = pb.worldY + toFloat(def.getVisual2d(
"offsetY"), 0.f);
114 tr->rot = pb.rotationDeg;
115 auto sp = r->sprite();
116 sp->width = float(effW) * cellW;
117 sp->height = float(effH) * cellH;
118 sp->r = toFloat(def.getVisual2d(
"colorR"), 0.72f);
119 sp->g = toFloat(def.getVisual2d(
"colorG"), 0.72f);
120 sp->b = toFloat(def.getVisual2d(
"colorB"), 0.72f);
122 sp->layer = int(toFloat(def.getVisual2d(
"layer"), 0.f));
124 const std::string texPath = def.getVisual2d(
"texture");
125 if (!texPath.empty()) {
126 if (
auto *gfx = gfxOrNull()) {
136void BuildingFx::updateVisual(
const building::BuildingDefinition &def,
137 const building::PlacedBuilding &pb,
138 building::PlacementWorld *world, Visual &
v) {
139 const float cellW = world->getGrid().cellW;
140 const float cellH = world->getGrid().cellH;
141 int effW = def.footprintW;
142 int effH = def.footprintH;
146 float wx = 0.f, wy = 0.f, wz = 0.f;
147 world->cellToWorld3D(pb.originCellX, pb.originCellY, pb.elevation, wx, wy, wz);
148 auto tr =
v.r3d->transform();
149 tr->x = wx + toFloat(def.getVisual3d(
"offsetX"), 0.f);
150 tr->y = wy + toFloat(def.getVisual3d(
"offsetY"), 0.f);
151 tr->z = wz + toFloat(def.getVisual3d(
"offsetZ"), 0.f);
152 const float height = toFloat(def.getVisual3d(
"height"), 1.f);
153 tr->sx = float(effW) * cellW;
155 tr->sz = float(effH) * cellH;
156 const float rad = pb.rotationDeg * 3.14159265f / 180.f;
163 auto tr =
v.r2d->transform();
164 tr->x = pb.worldX + toFloat(def.getVisual2d(
"offsetX"), 0.f);
165 tr->y = pb.worldY + toFloat(def.getVisual2d(
"offsetY"), 0.f);
166 tr->rot = pb.rotationDeg;
167 auto sp =
v.r2d->sprite();
168 sp->width = float(effW) * cellW;
169 sp->height = float(effH) * cellH;
173void BuildingFx::destroyVisual(Visual &
v) {
175 ecs::DestroyEntity(
v.r2d);
179 ecs::DestroyEntity(
v.r3d);
184void BuildingFx::setVisible(Visual &
v,
bool visible) {
185 if (
v.r2d)
v.r2d->sprite()->visible = visible;
186 if (
v.r3d)
v.r3d->meshRenderer()->visible = visible;
189void BuildingFx::destroyAll(WorldState &st) {
190 for (
auto &kv : st.visuals) destroyVisual(kv.second);
192 destroyVisual(st.ghost);
193 destroyVisual(st.cursor);
194 st.ghostBuildingId.clear();
195 for (graphics::Renderable3D *
line : st.gridLines) {
198 st.gridLines.clear();
199 st.gridLineCount = -1;
204 auto it = states_.find(world);
205 if (it == states_.end())
return;
206 WorldState &st = it->second;
208 std::unordered_map<int, Visual> next;
210 for (
int i = 0; i <
n; ++i) {
212 auto bit = world->
buildings().find(inst);
213 if (bit == world->
buildings().end())
continue;
214 const auto &pb = bit->second;
218 auto existing = st.visuals.find(inst);
219 if (existing != st.visuals.end()) {
220 next[inst] = existing->second;
221 updateVisual(*def, pb, world, next[inst]);
224 createVisual(st, *def, pb, world,
v, 1.f);
228 for (
auto &kv : st.visuals) {
229 if (next.count(kv.first) == 0) destroyVisual(kv.second);
231 st.visuals = std::move(next);
235 auto it = states_.find(world);
236 return it == states_.end() ? 0 : int(it->second.visuals.size());
240 if (!world || !ghost) {
244 auto it = states_.find(world);
245 if (it == states_.end())
return;
246 WorldState &st = it->second;
252 destroyVisual(st.ghost);
253 destroyVisual(st.cursor);
256 if (!st.ghost.r2d && !st.ghost.r3d) {
266 createVisual(st, *def, pb, world, st.ghost, 0.5f);
277 updateVisual(*def, pb, world, st.ghost);
281 const float cr =
valid ? 0.25f : 0.90f;
282 const float cg =
valid ? 0.85f : 0.25f;
283 const float cb =
valid ? 0.35f : 0.22f;
285 auto sp = st.ghost.r2d->sprite();
291 auto mr = st.ghost.r3d->meshRenderer();
296 setVisible(st.ghost,
true);
299 if (!st.cursor.r2d && !st.cursor.r3d) {
306 auto *r = graphics::Renderable3D::create();
307 auto tr = r->transform();
308 float wx = 0.f, wy = 0.f, wz = 0.f;
314 tr->sx = float(effW) * cellW;
316 tr->sz = float(effH) * cellH;
317 auto mr = r->meshRenderer();
318 mr->mesh = cubeMesh(gfxOrNull());
325 auto *r = graphics::Renderable2D::create();
326 auto tr = r->transform();
329 auto sp = r->sprite();
330 sp->width = float(effW) * cellW;
331 sp->height = float(effH) * cellH;
345 auto tr = st.cursor.r3d->transform();
346 float wx = 0.f, wy = 0.f, wz = 0.f;
352 tr->sx = float(effW) * cellW;
353 tr->sz = float(effH) * cellH;
354 auto mr = st.cursor.r3d->meshRenderer();
358 }
else if (st.cursor.r2d) {
359 auto tr = st.cursor.r2d->transform();
362 auto sp = st.cursor.r2d->sprite();
363 sp->width = float(effW) * cellW;
364 sp->height = float(effH) * cellH;
370 setVisible(st.cursor,
true);
375 auto it = states_.find(world);
376 if (it == states_.end())
return;
377 WorldState &st = it->second;
378 setVisible(st.ghost,
false);
379 setVisible(st.cursor,
false);
384 states_[world].gridVisible = visible;
388 auto it = states_.find(world);
389 return it != states_.end() && it->second.gridVisible;
393 if (!world || !gfx)
return;
394 auto it = states_.find(world);
395 if (it == states_.end() || !it->second.gridVisible)
return;
400 float px = 0.f, py = 0.f;
403 Color{0.75f, 0.78f, 0.85f, 0.06f});
414 const int want = (
w + 1) + (
h + 1);
415 if (
int(st.gridLines.size()) == want && st.gridLineCount == want)
return;
419 st.gridLines.clear();
422 const float xExtent = float(
w) * cellW;
423 const float zExtent = float(
h) * cellH;
424 for (
int x = 0;
x <=
w; ++
x) {
425 auto *r = graphics::Renderable3D::create();
426 auto tr = r->transform();
427 float px = 0.f, py = 0.f;
431 tr->z = xz ? zExtent * 0.5f : 0.f;
435 auto mr = r->meshRenderer();
441 st.gridLines.push_back(r);
443 for (
int y = 0;
y <=
h; ++
y) {
444 auto *r = graphics::Renderable3D::create();
445 auto tr = r->transform();
446 float px = 0.f, py = 0.f;
448 tr->x = xz ? xExtent * 0.5f : 0.f;
454 auto mr = r->meshRenderer();
460 st.gridLines.push_back(r);
462 st.gridLineCount = want;
467 if (!world || !gfx)
return;
468 auto it = states_.find(world);
469 if (it == states_.end())
return;
470 WorldState &st = it->second;
471 rebuildGridLines(st, world, gfx,
height);
473 if (
line)
line->meshRenderer()->visible = st.gridVisible;
477void BuildingFx::expose(ssq::Table &table) {
478 auto cls = table.addClass(
name, BuildingFx::create,
false);
482void BuildingFx::expose(ssq::Class &
cls) {