载入中...
搜索中...
未找到
PlacementWorld.cpp
浏览该文件的文档.
2#include "building/Ghost.h"
4#include "data/DataModule.h"
5#include "data/JsonDocument.h"
7#include "map/TileLayer.h"
8
9#include <Poco/JSON/Object.h>
10
11#include <algorithm>
12#include <cmath>
13#include <memory>
14
15namespace eve::building {
16
17PlacementWorld::PlacementWorld(int width, int height, float cellSize) {
18 width_ = width > 0 ? width : 1;
19 height_ = height > 0 ? height : 1;
20 grid_ = std::make_unique<grid::GridConfig>();
21 grid_->cellW = cellSize > 0.f ? cellSize : 1.f;
22 grid_->cellH = grid_->cellW;
23 occupancy_.assign(size_t(width_) * size_t(height_), 0);
24 terrain_.assign(size_t(width_) * size_t(height_), 0);
25 terrainOverrides_.assign(size_t(width_) * size_t(height_), 0);
27}
28
30
31float PlacementWorld::getCellSize() const { return grid_->cellW; }
32float PlacementWorld::getOriginX() const { return grid_->originX; }
33float PlacementWorld::getOriginY() const { return grid_->originY; }
34const grid::GridConfig &PlacementWorld::getGrid() const { return *grid_; }
36
37void PlacementWorld::destroy() { delete this; }
38
40 if (s > 0.f) {
41 grid_->cellW = s;
42 grid_->cellH = s;
43 }
44}
45
46void PlacementWorld::setOrigin(float x, float y) {
47 grid_->originX = x;
48 grid_->originY = y;
49}
50
51void PlacementWorld::setExtra(const std::string &key, const std::string &value) {
52 extra_[key] = value;
53}
54
55std::string PlacementWorld::getExtra(const std::string &key, const std::string &fallback) const {
56 auto it = extra_.find(key);
57 return it == extra_.end() ? fallback : it->second;
58}
59
60// ---- Grid 配置 ----
61
62void PlacementWorld::setGridLayout(const std::string &layout) {
64}
65
67 return grid::GridConfig::layoutName(grid_->layout);
68}
69
70void PlacementWorld::setGridPlane(const std::string &plane) {
71 grid_->plane = grid::GridConfig::planeFromName(plane);
72}
73
75 return grid::GridConfig::planeName(grid_->plane);
76}
77
78void PlacementWorld::setCellGap(float gapX, float gapY) {
79 grid_->cellGapX = gapX;
80 grid_->cellGapY = gapY;
81}
82
83void PlacementWorld::setHexSideLength(float s) { grid_->hexSideLength = s; }
84
85void PlacementWorld::setStagger(const std::string &axis, const std::string &index) {
86 grid_->staggerAxis = (axis == "x" || axis == "X") ? grid::StaggerAxis::X
88 grid_->staggerIndex = (index == "even" || index == "Even") ? grid::StaggerIndex::Even
90}
91
93 if (!layer) return;
94 const auto &c = *layer->config();
95 switch (c.orientation) {
97 grid_->layout = grid::GridLayout::Rectangle;
98 break;
100 grid_->layout = grid::GridLayout::Isometric;
101 break;
103 grid_->layout = grid::GridLayout::Staggered;
104 break;
106 grid_->layout = grid::GridLayout::Hexagon;
107 break;
108 }
109 grid_->cellW = c.tileW;
110 grid_->cellH = c.tileH;
111 grid_->originX = c.originX;
112 grid_->originY = c.originY;
113 grid_->staggerAxis = c.staggerAxis == map::StaggerAxis::Y ? grid::StaggerAxis::Y
115 grid_->staggerIndex = c.staggerIndex == map::StaggerIndex::Odd ? grid::StaggerIndex::Odd
117 grid_->hexSideLength = c.hexSideLength;
118}
119
120// ---- 坐标换算 ----
121
122int PlacementWorld::worldToCellX(float worldX) const {
123 int cx = 0, cy = 0;
124 grid::worldToCell(*grid_, worldX, 0.f, cx, cy, width_, height_);
125 return cx;
126}
127
128int PlacementWorld::worldToCellY(float worldY) const {
129 int cx = 0, cy = 0;
130 grid::worldToCell(*grid_, 0.f, worldY, cx, cy, width_, height_);
131 return cy;
132}
133
134float PlacementWorld::cellToWorldX(int cellX) const {
135 float px = 0.f, py = 0.f;
136 grid::cellToWorld(*grid_, cellX, 0, px, py);
137 return px;
138}
139
140float PlacementWorld::cellToWorldY(int cellY) const {
141 float px = 0.f, py = 0.f;
142 grid::cellToWorld(*grid_, 0, cellY, px, py);
143 return py;
144}
145
146void PlacementWorld::cellToWorldPlane(int cellX, int cellY, float &px, float &py) const {
147 grid::cellToWorld(*grid_, cellX, cellY, px, py);
148}
149
150void PlacementWorld::cellToWorld3D(int cellX, int cellY, float elevation, float &worldX,
151 float &worldY, float &worldZ) const {
152 float px = 0.f, py = 0.f;
153 grid::cellToWorld(*grid_, cellX, cellY, px, py);
154 if (grid_->plane == grid::GridPlane::XZ) {
155 worldX = px;
156 worldY = elevation;
157 worldZ = py;
158 } else {
159 worldX = px;
160 worldY = py;
161 worldZ = elevation;
162 }
163}
164
165float PlacementWorld::cellToWorld3DX(int cellX, int cellY, float elevation) const {
166 float wx = 0.f, wy = 0.f, wz = 0.f;
167 cellToWorld3D(cellX, cellY, elevation, wx, wy, wz);
168 return wx;
169}
170
171float PlacementWorld::cellToWorld3DY(int cellX, int cellY, float elevation) const {
172 float wx = 0.f, wy = 0.f, wz = 0.f;
173 cellToWorld3D(cellX, cellY, elevation, wx, wy, wz);
174 return wy;
175}
176
177float PlacementWorld::cellToWorld3DZ(int cellX, int cellY, float elevation) const {
178 float wx = 0.f, wy = 0.f, wz = 0.f;
179 cellToWorld3D(cellX, cellY, elevation, wx, wy, wz);
180 return wz;
181}
182
183void PlacementWorld::worldToCell3D(float worldX, float worldY, float worldZ, int &cellX,
184 int &cellY) const {
185 const float px = worldX;
186 const float py = (grid_->plane == grid::GridPlane::XZ) ? worldZ : worldY;
187 grid::worldToCell(*grid_, px, py, cellX, cellY, width_, height_);
188}
189
190int PlacementWorld::worldToCell3DX(float worldX, float worldY, float worldZ) const {
191 int cx = 0, cy = 0;
192 worldToCell3D(worldX, worldY, worldZ, cx, cy);
193 return cx;
194}
195
196int PlacementWorld::worldToCell3DY(float worldX, float worldY, float worldZ) const {
197 int cx = 0, cy = 0;
198 worldToCell3D(worldX, worldY, worldZ, cx, cy);
199 return cy;
200}
201
202// ---- 地形 ----
203
205 std::fill(terrain_.begin(), terrain_.end(), semantic);
206 std::fill(terrainOverrides_.begin(), terrainOverrides_.end(), 1);
207}
208
209void PlacementWorld::setTerrain(int cellX, int cellY, int semantic) {
210 if (!inBounds(cellX, cellY)) return;
211 const size_t idx = size_t(cellY) * size_t(width_) + size_t(cellX);
212 terrain_[idx] = semantic;
213 terrainOverrides_[idx] = 1;
214}
215
216int PlacementWorld::getTerrain(int cellX, int cellY) const {
217 if (!inBounds(cellX, cellY)) return 0;
218 const size_t idx = size_t(cellY) * size_t(width_) + size_t(cellX);
219 if (terrainOverrides_[idx]) return terrain_[idx];
220 if (terrainBound_) return terrainFromGid(cellX, cellY);
221 return terrain_[idx];
222}
223
224int PlacementWorld::terrainFromGid(int cellX, int cellY) const {
225 if (!tileLayer_) return 0;
226 const int gid = tileLayer_->getTile(cellX, cellY);
227 auto it = terrainGidMap_.find(gid);
228 return it == terrainGidMap_.end() ? 0 : it->second;
229}
230
231bool PlacementWorld::inBounds(int cellX, int cellY) const {
232 return cellX >= 0 && cellY >= 0 && cellX < width_ && cellY < height_;
233}
234
235// ---- Tilemap 绑定 ----
236
238 tileLayer_ = layer;
239 terrainBound_ = (layer != nullptr);
241 // 绑定后地形由 GID 懒解析;清掉旧手动地形,避免旧值泄漏。
242 std::fill(terrain_.begin(), terrain_.end(), 0);
243 std::fill(terrainOverrides_.begin(), terrainOverrides_.end(), 0);
244}
245
247 tileLayer_ = nullptr;
248 terrainBound_ = false;
249 std::fill(terrain_.begin(), terrain_.end(), 0);
250 std::fill(terrainOverrides_.begin(), terrainOverrides_.end(), 0);
251}
252
253void PlacementWorld::setTerrainGidMapJson(const std::string &json) {
254 auto *dm = eve::data::DataModule::create();
255 std::string err;
256 std::unique_ptr<data::JsonDocument> doc(dm->decodeJson(json, &err));
257 if (!doc || !doc->isObject()) return;
258 auto obj = doc->object();
259 if (!obj) return;
260 std::vector<std::string> names;
261 obj->getNames(names);
262 for (const auto &n : names) {
263 try {
264 const int gid = std::atoi(n.c_str());
265 const int semantic = obj->getValue<int>(n);
266 if (gid >= 0) terrainGidMap_[gid] = semantic;
267 } catch (...) {
268 }
269 }
270}
271
273 if (gid >= 0) terrainGidMap_[gid] = semantic;
274}
275
276void PlacementWorld::clearTerrainGidMap() { terrainGidMap_.clear(); }
277
279 if (std::isfinite(height) && height > 0.f) floorHeight_ = height;
280}
281
282PlacementWorld::ChannelMap &PlacementWorld::edgeChannels(EdgeAxis axis, int level) {
283 if (level == 0)
284 return axis == EdgeAxis::Horizontal ? horizontalEdgeChannels_ : verticalEdgeChannels_;
285 return axis == EdgeAxis::Horizontal ? horizontalEdgesByLevel_[level]
286 : verticalEdgesByLevel_[level];
287}
288
289const PlacementWorld::ChannelMap *PlacementWorld::findEdgeChannels(EdgeAxis axis,
290 int level) const {
291 if (level == 0)
292 return axis == EdgeAxis::Horizontal ? &horizontalEdgeChannels_ : &verticalEdgeChannels_;
293 const auto &levels =
294 axis == EdgeAxis::Horizontal ? horizontalEdgesByLevel_ : verticalEdgesByLevel_;
295 const auto found = levels.find(level);
296 return found == levels.end() ? nullptr : &found->second;
297}
298
299PlacementWorld::ChannelMap &PlacementWorld::cornerChannels(int level) {
300 return level == 0 ? cornerChannels_ : cornersByLevel_[level];
301}
302
303const PlacementWorld::ChannelMap *PlacementWorld::findCornerChannels(int level) const {
304 if (level == 0) return &cornerChannels_;
305 const auto found = cornersByLevel_.find(level);
306 return found == cornersByLevel_.end() ? nullptr : &found->second;
307}
308
309// ---- 占用查询 ----
310
311std::vector<int> &PlacementWorld::channelOccupancy(const std::string &channel) {
312 return channelOccupancy(channel, activeLevel_);
313}
314
315std::vector<int> &PlacementWorld::channelOccupancy(const std::string &channel, int level) {
316 if (level != 0) {
317 auto &occupancy = cellChannelsByLevel_[level][channel];
318 const size_t required = size_t(width_) * size_t(height_);
319 if (occupancy.size() != required) occupancy.assign(required, 0);
320 return occupancy;
321 }
322 if (channel.empty()) return occupancy_;
323 auto &ch = allChannels_[channel];
324 if (ch.size() != occupancy_.size()) ch.assign(occupancy_.size(), 0);
325 return ch;
326}
327
328int PlacementWorld::getOccupant(int cellX, int cellY) const {
329 return getOccupantInChannel(std::string{}, cellX, cellY);
330}
331
332int PlacementWorld::getOccupantInChannel(const std::string &channel, int cellX, int cellY) const {
333 return getOccupantAtLevel(channel, cellX, cellY, activeLevel_);
334}
335
336int PlacementWorld::getOccupantAtLevel(const std::string &channel, int cellX, int cellY,
337 int level) const {
338 if (!inBounds(cellX, cellY)) return 0;
339 const size_t idx = size_t(cellY) * size_t(width_) + size_t(cellX);
340 if (level != 0) {
341 const auto levelIt = cellChannelsByLevel_.find(level);
342 if (levelIt == cellChannelsByLevel_.end()) return 0;
343 const auto channelIt = levelIt->second.find(channel);
344 return channelIt == levelIt->second.end() || idx >= channelIt->second.size()
345 ? 0
346 : channelIt->second[idx];
347 }
348 if (channel.empty()) return occupancy_[idx];
349 auto it = allChannels_.find(channel);
350 if (it == allChannels_.end() || idx >= it->second.size()) return 0;
351 return it->second[idx];
352}
353
354int PlacementWorld::getAnyOccupant(int cellX, int cellY) const {
355 return getAnyOccupantAtLevel(cellX, cellY, activeLevel_);
356}
357
358int PlacementWorld::getAnyOccupantAtLevel(int cellX, int cellY, int level) const {
359 const int occ = getOccupantAtLevel(std::string{}, cellX, cellY, level);
360 if (occ != 0) return occ;
361 if (level != 0) {
362 const auto levelIt = cellChannelsByLevel_.find(level);
363 if (levelIt == cellChannelsByLevel_.end()) return 0;
364 for (const auto &kv : levelIt->second) {
365 const int value = getOccupantAtLevel(kv.first, cellX, cellY, level);
366 if (value != 0) return value;
367 }
368 return 0;
369 }
370 for (const auto &kv : allChannels_) {
371 const int v = getOccupantAtLevel(kv.first, cellX, cellY, 0);
372 if (v != 0) return v;
373 }
374 return 0;
375}
376
377bool PlacementWorld::isCellEmpty(int cellX, int cellY) const {
378 return getOccupant(cellX, cellY) == 0;
379}
380
381bool PlacementWorld::isCellEmptyInChannel(const std::string &channel, int cellX, int cellY) const {
382 return getOccupantInChannel(channel, cellX, cellY) == 0;
383}
384
385int PlacementWorld::getBuildingCount() const { return int(instanceOrder_.size()); }
386
387bool PlacementWorld::hasBuilding(int instanceId) const {
388 return buildings_.count(instanceId) > 0;
389}
390
391std::string PlacementWorld::getBuildingId(int instanceId) const {
392 auto it = buildings_.find(instanceId);
393 return it == buildings_.end() ? std::string{} : it->second.buildingId;
394}
395
396int PlacementWorld::getBuildingCellX(int instanceId) const {
397 auto it = buildings_.find(instanceId);
398 return it == buildings_.end() ? 0 : it->second.originCellX;
399}
400
401int PlacementWorld::getBuildingCellY(int instanceId) const {
402 auto it = buildings_.find(instanceId);
403 return it == buildings_.end() ? 0 : it->second.originCellY;
404}
405
406float PlacementWorld::getBuildingWorldX(int instanceId) const {
407 auto it = buildings_.find(instanceId);
408 return it == buildings_.end() ? 0.f : it->second.worldX;
409}
410
411float PlacementWorld::getBuildingWorldY(int instanceId) const {
412 auto it = buildings_.find(instanceId);
413 if (it == buildings_.end()) return 0.f;
414 const PlacedBuilding &pb = it->second;
415 // 真实世界 Y:XZ 平面 = 高度;XY 平面 = 平面第二轴。
416 return grid_->plane == grid::GridPlane::XZ
417 ? pb.elevation + float(pb.level) * floorHeight_
418 : pb.worldY;
419}
420
421float PlacementWorld::getBuildingWorldZ(int instanceId) const {
422 auto it = buildings_.find(instanceId);
423 if (it == buildings_.end()) return 0.f;
424 const PlacedBuilding &pb = it->second;
425 return grid_->plane == grid::GridPlane::XZ
426 ? pb.worldY
427 : pb.elevation + float(pb.level) * floorHeight_;
428}
429
430float PlacementWorld::getBuildingElevation(int instanceId) const {
431 auto it = buildings_.find(instanceId);
432 return it == buildings_.end() ? 0.f : it->second.elevation;
433}
434
435int PlacementWorld::getBuildingLevel(int instanceId) const {
436 auto it = buildings_.find(instanceId);
437 return it == buildings_.end() ? 0 : it->second.level;
438}
439
441 const auto found = buildings_.find(instanceId);
442 return found == buildings_.end() ? 0 : int(found->second.supportInstanceIds.size());
443}
444
445int PlacementWorld::getBuildingSupportAt(int instanceId, int index) const {
446 const auto found = buildings_.find(instanceId);
447 if (found == buildings_.end() || index < 0 ||
448 index >= int(found->second.supportInstanceIds.size()))
449 return 0;
450 return found->second.supportInstanceIds[size_t(index)];
451}
452
454 int count = 0;
455 for (const auto &[id, placed] : buildings_) {
456 (void)id;
457 if (std::find(placed.supportInstanceIds.begin(), placed.supportInstanceIds.end(),
458 instanceId) != placed.supportInstanceIds.end())
459 ++count;
460 }
461 return count;
462}
463
464int PlacementWorld::getEdgeOccupant(const std::string &channel, int cellX, int cellY,
465 const std::string &direction) const {
466 return getEdgeOccupantAtLevel(channel, cellX, cellY, direction, activeLevel_);
467}
468
469int PlacementWorld::getEdgeOccupantAtLevel(const std::string &channel, int cellX, int cellY,
470 const std::string &direction, int level) const {
471 auto canonical = PlacementSystem::canonicalEdge(cellX, cellY, direction);
472 if (!canonical.ok()) return 0;
473 const EdgeAddress edge = std::move(canonical).takeValue();
474 const bool inBounds = edge.axis == EdgeAxis::Horizontal
475 ? edge.x >= 0 && edge.x < width_ && edge.y >= 0 && edge.y <= height_
476 : edge.x >= 0 && edge.x <= width_ && edge.y >= 0 && edge.y < height_;
477 if (!inBounds) return 0;
478 const ChannelMap *channels = nullptr;
479 if (level == 0) {
480 channels = edge.axis == EdgeAxis::Horizontal ? &horizontalEdgeChannels_
481 : &verticalEdgeChannels_;
482 } else {
483 const auto &levels = edge.axis == EdgeAxis::Horizontal ? horizontalEdgesByLevel_
484 : verticalEdgesByLevel_;
485 const auto levelIt = levels.find(level);
486 if (levelIt == levels.end()) return 0;
487 channels = &levelIt->second;
488 }
489 const auto it = channels->find(channel);
490 if (it == channels->end()) return 0;
491 const size_t index = edge.axis == EdgeAxis::Horizontal
492 ? size_t(edge.y) * size_t(width_) + size_t(edge.x)
493 : size_t(edge.y) * size_t(width_ + 1) + size_t(edge.x);
494 return index < it->second.size() ? it->second[index] : 0;
495}
496
497bool PlacementWorld::isEdgeEmpty(const std::string &channel, int cellX, int cellY,
498 const std::string &direction) const {
499 return getEdgeOccupant(channel, cellX, cellY, direction) == 0;
500}
501
502int PlacementWorld::getCornerOccupant(const std::string &channel, int vertexX,
503 int vertexY) const {
504 return getCornerOccupantAtLevel(channel, vertexX, vertexY, activeLevel_);
505}
506
507int PlacementWorld::getCornerOccupantAtLevel(const std::string &channel, int vertexX,
508 int vertexY, int level) const {
509 if (vertexX < 0 || vertexX > width_ || vertexY < 0 || vertexY > height_) return 0;
510 const ChannelMap *channels = findCornerChannels(level);
511 if (!channels) return 0;
512 const auto found = channels->find(channel);
513 if (found == channels->end()) return 0;
514 const size_t index = size_t(vertexY) * size_t(width_ + 1) + size_t(vertexX);
515 return index < found->second.size() ? found->second[index] : 0;
516}
517
518int PlacementWorld::getAnyCornerOccupantAtLevel(int vertexX, int vertexY, int level) const {
519 const ChannelMap *channels = findCornerChannels(level);
520 if (!channels) return 0;
521 for (const auto &[channel, occupancy] : *channels) {
522 (void)occupancy;
523 const int occupant = getCornerOccupantAtLevel(channel, vertexX, vertexY, level);
524 if (occupant != 0) return occupant;
525 }
526 return 0;
527}
528
529bool PlacementWorld::isCornerEmpty(const std::string &channel, int vertexX, int vertexY) const {
530 return getCornerOccupant(channel, vertexX, vertexY) == 0;
531}
532
533int PlacementWorld::getFreeOccupant(const std::string &channel, float worldX,
534 float worldY) const {
535 return getFreeOccupantAtLevel(channel, worldX, worldY, activeLevel_);
536}
537
538int PlacementWorld::getFreeOccupantAtLevel(const std::string &channel, float worldX,
539 float worldY, int level) const {
540 for (int instanceId : instanceOrder_) {
541 const auto found = buildings_.find(instanceId);
542 if (found == buildings_.end()) continue;
543 const PlacedBuilding &placed = found->second;
544 if (placed.placementKind != "free" || placed.level != level ||
545 placed.channel != channel)
546 continue;
547 if (PlacementSystem::containsFreePoint(placed, worldX, worldY))
548 return instanceId;
549 }
550 return 0;
551}
552
553int PlacementWorld::getEdgeConnectionMask(int instanceId) const {
554 return static_cast<int>(PlacementSystem::edgeConnectionMask(*this, instanceId));
555}
556
557std::string PlacementWorld::getEdgeVariant(int instanceId) const {
558 return PlacementSystem::edgeVariant(*this, instanceId);
559}
560
561std::string PlacementWorld::getBuildingSurfaceId(int instanceId) const {
562 auto it = buildings_.find(instanceId);
563 return it == buildings_.end() ? std::string{} : it->second.surfaceId;
564}
565
566int64_t PlacementWorld::getBuildingSurfaceRevision(int instanceId) const {
567 auto it = buildings_.find(instanceId);
568 return it == buildings_.end() ? 0 : static_cast<int64_t>(it->second.surfaceRevision);
569}
570
571float PlacementWorld::getBuildingSurfaceNormalX(int instanceId) const {
572 auto it = buildings_.find(instanceId);
573 return it == buildings_.end() ? 0.f : it->second.surfaceNormalX;
574}
575
576float PlacementWorld::getBuildingSurfaceNormalY(int instanceId) const {
577 auto it = buildings_.find(instanceId);
578 return it == buildings_.end() ? 1.f : it->second.surfaceNormalY;
579}
580
581float PlacementWorld::getBuildingSurfaceNormalZ(int instanceId) const {
582 auto it = buildings_.find(instanceId);
583 return it == buildings_.end() ? 0.f : it->second.surfaceNormalZ;
584}
585
586std::string PlacementWorld::getBuildingChannel(int instanceId) const {
587 auto it = buildings_.find(instanceId);
588 return it == buildings_.end() ? std::string{} : it->second.channel;
589}
590
591float PlacementWorld::getBuildingRotation(int instanceId) const {
592 auto it = buildings_.find(instanceId);
593 return it == buildings_.end() ? 0.f : it->second.rotationDeg;
594}
595
596std::string PlacementWorld::getBuildingProp(int instanceId, const std::string &key,
597 const std::string &fallback) const {
598 auto it = buildings_.find(instanceId);
599 return it == buildings_.end() ? fallback : it->second.getProp(key, fallback);
600}
601
602void PlacementWorld::setBuildingProp(int instanceId, const std::string &key,
603 const std::string &value) {
604 auto it = buildings_.find(instanceId);
605 if (it == buildings_.end()) return;
606 it->second.setProp(key, value);
607}
608
609bool PlacementWorld::buildingHasTag(int instanceId, const std::string &tag) const {
610 auto it = buildings_.find(instanceId);
611 return it == buildings_.end() ? false : it->second.hasTag(tag);
612}
613
615 if (index < 0 || index >= int(instanceOrder_.size())) return 0;
616 return instanceOrder_[size_t(index)];
617}
618
619// ---- 便捷操作 ----
620
621bool PlacementWorld::canPlace(const std::string &buildingId, int cellX, int cellY,
622 float rotationDeg) {
623 return PlacementSystem::canPlace(this, buildingId, cellX, cellY, rotationDeg, 0, nullptr);
624}
625
626std::string PlacementWorld::canPlaceReason(const std::string &buildingId, int cellX, int cellY,
627 float rotationDeg) {
628 std::string reason;
629 PlacementSystem::canPlace(this, buildingId, cellX, cellY, rotationDeg, 0, &reason);
630 return reason;
631}
632
633int PlacementWorld::placeAt(const std::string &buildingId, int cellX, int cellY,
634 float rotationDeg) {
635 return PlacementSystem::placeAt(this, buildingId, cellX, cellY, rotationDeg);
636}
637
638int PlacementWorld::placeAtWorld(const std::string &buildingId, float worldX, float worldY,
639 float rotationDeg) {
640 return PlacementSystem::placeAtWorld(this, buildingId, worldX, worldY, rotationDeg);
641}
642
643int PlacementWorld::placeAtWorld3D(const std::string &buildingId, float worldX, float worldY,
644 float worldZ, float rotationDeg) {
645 return PlacementSystem::placeAtWorld3D(this, buildingId, worldX, worldY, worldZ, rotationDeg);
646}
647
649 return PlacementSystem::placeGhost(this, ghost);
650}
651
653 const auto found = edgeCurveGroups_.find(id.value);
654 if (!id || found == edgeCurveGroups_.end()) {
656 eve::DiagnosticCode::NotFound, "edge curve group was not found",
657 std::to_string(id.value), {}, "building.edge-curve-group"));
658 }
660}
661
663 const auto placed = buildings_.find(instanceId);
664 if (placed == buildings_.end() || !placed->second.edgeCurveGroupId) {
666 eve::DiagnosticCode::NotFound, "edge instance is not linked to a curve group",
667 std::to_string(instanceId), {}, "building.edge-curve-group"));
668 }
669 return edgeCurveGroup(placed->second.edgeCurveGroupId);
670}
671
672std::vector<EdgeCurveGroupId> PlacementWorld::edgeCurveGroupIds() const {
673 std::vector<EdgeCurveGroupId> ids;
674 ids.reserve(edgeCurveGroups_.size());
675 for (const auto &[id, group] : edgeCurveGroups_) ids.push_back(group.id);
676 std::sort(ids.begin(), ids.end(), [](EdgeCurveGroupId lhs, EdgeCurveGroupId rhs) {
677 return lhs.value < rhs.value;
678 });
679 return ids;
680}
681
682int PlacementWorld::placeEdge(const std::string &buildingId, int cellX, int cellY,
683 const std::string &direction) {
684 return PlacementSystem::placeEdge(this, buildingId, cellX, cellY, direction);
685}
686
687bool PlacementWorld::canPlaceEdge(const std::string &buildingId, int cellX, int cellY,
688 const std::string &direction) {
689 return PlacementSystem::canPlaceEdge(this, buildingId, cellX, cellY, direction, 0, nullptr);
690}
691
692std::string PlacementWorld::canPlaceEdgeReason(const std::string &buildingId, int cellX, int cellY,
693 const std::string &direction) {
694 std::string reason;
695 PlacementSystem::canPlaceEdge(this, buildingId, cellX, cellY, direction, 0, &reason);
696 return reason;
697}
698
699int PlacementWorld::placeCorner(const std::string &buildingId, int vertexX, int vertexY) {
700 return PlacementSystem::placeCorner(this, buildingId, vertexX, vertexY);
701}
702
703bool PlacementWorld::canPlaceCorner(const std::string &buildingId, int vertexX, int vertexY) {
704 return PlacementSystem::canPlaceCorner(this, buildingId, vertexX, vertexY);
705}
706
707std::string PlacementWorld::canPlaceCornerReason(const std::string &buildingId, int vertexX,
708 int vertexY) {
709 std::string reason;
710 PlacementSystem::canPlaceCorner(this, buildingId, vertexX, vertexY, 0, &reason);
711 return reason;
712}
713
714
715int PlacementWorld::placeFree(const std::string &buildingId, float worldX, float worldY,
716 float elevation, float rotationDeg) {
717 return PlacementSystem::placeFree(this, buildingId, worldX, worldY, elevation, rotationDeg);
718}
719
720bool PlacementWorld::canPlaceFree(const std::string &buildingId, float worldX, float worldY) {
721 return PlacementSystem::canPlaceFree(this, buildingId, worldX, worldY);
722}
723
724std::string PlacementWorld::canPlaceFreeReason(const std::string &buildingId, float worldX,
725 float worldY) {
726 std::string reason;
727 PlacementSystem::canPlaceFree(this, buildingId, worldX, worldY, 0, &reason);
728 return reason;
729}
730
731bool PlacementWorld::removeBuilding(int instanceId) {
732 return PlacementSystem::removeBuilding(this, instanceId);
733}
734
735bool PlacementWorld::moveBuilding(int instanceId, int cellX, int cellY, float rotationDeg) {
736 return PlacementSystem::moveBuilding(this, instanceId, cellX, cellY, rotationDeg);
737}
738
740
741std::unique_ptr<PlacementWorld> PlacementWorld::cloneState() const {
742 auto copy = std::make_unique<PlacementWorld>(width_, height_, getCellSize());
743 copy->id_ = id_;
744 *copy->grid_ = *grid_;
745 copy->snapMode_ = snapMode_;
746 copy->validateRule_ = validateRule_;
747 copy->floorHeight_ = floorHeight_;
748 copy->activeLevel_ = activeLevel_;
749 copy->occupancy_ = occupancy_;
750 copy->terrain_ = terrain_;
751 copy->terrainOverrides_ = terrainOverrides_;
752 copy->allChannels_ = allChannels_;
753 copy->horizontalEdgeChannels_ = horizontalEdgeChannels_;
754 copy->verticalEdgeChannels_ = verticalEdgeChannels_;
755 copy->cornerChannels_ = cornerChannels_;
756 copy->cellChannelsByLevel_ = cellChannelsByLevel_;
757 copy->horizontalEdgesByLevel_ = horizontalEdgesByLevel_;
758 copy->verticalEdgesByLevel_ = verticalEdgesByLevel_;
759 copy->cornersByLevel_ = cornersByLevel_;
760 copy->buildings_ = buildings_;
761 copy->edgeCurveGroups_ = edgeCurveGroups_;
762 copy->nextEdgeCurveGroupId_ = nextEdgeCurveGroupId_;
763 copy->publishEvents_ = false;
764 copy->extra_ = extra_;
765 copy->instanceOrder_ = instanceOrder_;
766 copy->tileLayer_ = tileLayer_;
767 copy->terrainBound_ = terrainBound_;
768 copy->terrainGidMap_ = terrainGidMap_;
769 return copy;
770}
771
772std::string PlacementWorld::canRemoveBuildingReason(int instanceId) const {
773 if (!hasBuilding(instanceId)) return "not_found";
774 return getBuildingDependentCount(instanceId) > 0 ? "support_in_use" : std::string{};
775}
776
778 return PlacementSystem::removeBuildingCascade(this, instanceId);
779}
780
781void PlacementWorld::swapState(PlacementWorld& candidate) noexcept {
782 using std::swap;
783 swap(id_, candidate.id_);
784 swap(width_, candidate.width_);
785 swap(height_, candidate.height_);
786 swap(grid_, candidate.grid_);
787 swap(snapMode_, candidate.snapMode_);
788 swap(validateRule_, candidate.validateRule_);
789 swap(floorHeight_, candidate.floorHeight_);
790 swap(activeLevel_, candidate.activeLevel_);
791 swap(occupancy_, candidate.occupancy_);
792 swap(terrain_, candidate.terrain_);
793 swap(terrainOverrides_, candidate.terrainOverrides_);
794 swap(allChannels_, candidate.allChannels_);
795 swap(horizontalEdgeChannels_, candidate.horizontalEdgeChannels_);
796 swap(verticalEdgeChannels_, candidate.verticalEdgeChannels_);
797 swap(cornerChannels_, candidate.cornerChannels_);
798 swap(cellChannelsByLevel_, candidate.cellChannelsByLevel_);
799 swap(horizontalEdgesByLevel_, candidate.horizontalEdgesByLevel_);
800 swap(verticalEdgesByLevel_, candidate.verticalEdgesByLevel_);
801 swap(cornersByLevel_, candidate.cornersByLevel_);
802 swap(buildings_, candidate.buildings_);
803 swap(edgeCurveGroups_, candidate.edgeCurveGroups_);
804 swap(nextEdgeCurveGroupId_, candidate.nextEdgeCurveGroupId_);
805 swap(extra_, candidate.extra_);
806 swap(instanceOrder_, candidate.instanceOrder_);
807 swap(tileLayer_, candidate.tileLayer_);
808 swap(terrainBound_, candidate.terrainBound_);
809 swap(terrainGidMap_, candidate.terrainGidMap_);
810}
811
812} // namespace eve::building
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::map< std::string, std::vector< Key >, std::less<> > channels
const std::string & s
building::EdgeCurveGroup group
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float py
std::string layout
std::uint32_t key
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
Pure cell/world projection helpers for GridConfig.
float v
std::int32_t c
float elevation
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
bool required
int level
const std::string * tag
TileLayer * layer
int idx
RoadLaneDirection direction
const RoadEdge * edge
bool found
std::uint32_t count
bool placed
uint32_t index
uint32_t semantic
Definition WfcSimple.cpp:15
float wz
float wx
float wy
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
EVENGINE_API_WORLD public API.
Definition Ghost.h:16
static int placeAt(PlacementWorld *world, const std::string &buildingId, int cellX, int cellY, float rotationDeg=0.f)
成功返回 instanceId;失败返回 0。
static int placeFree(PlacementWorld *world, const std::string &buildingId, float worldX, float worldY, float elevation=0.f, float rotationDeg=0.f)
Compatibility-only id projection; prefer placeFreeResult in C++.
static int placeCorner(PlacementWorld *world, const std::string &buildingId, int vertexX, int vertexY)
Compatibility-only id projection; prefer placeCornerResult in C++.
static bool containsFreePoint(const PlacedBuilding &placed, float worldX, float worldY)
Test a world-plane point against a committed circular or oriented-box footprint.
static int placeAtWorld3D(PlacementWorld *world, const std::string &buildingId, float worldX, float worldY, float worldZ, float rotationDeg=0.f)
static int removeBuildingCascade(PlacementWorld *world, int instanceId)
Compatibility-only count projection; prefer removeBuildingCascadeResult in C++.
static void clearBuildings(PlacementWorld *world)
static std::string edgeVariant(const PlacementWorld &world, int instanceId)
Stable visual topology class: isolated/end/straight/corner/tee/cross.
static void ensureBuiltins()
确保内置规则已注册(模块首次使用时自动调用)。
static bool canPlaceFree(PlacementWorld *world, const std::string &buildingId, float worldX, float worldY, int excludeInstanceId=0, std::string *reason=nullptr, int level=std::numeric_limits< int >::min(), float rotationDeg=0.f)
Validate an exact free-domain world-plane anchor and circular or oriented-box footprint.
static bool removeBuilding(PlacementWorld *world, int instanceId)
static bool canPlaceCorner(PlacementWorld *world, const std::string &buildingId, int vertexX, int vertexY, int excludeInstanceId=0, std::string *reason=nullptr, int level=std::numeric_limits< int >::min())
Validate a corner-domain placement at one canonical grid vertex.
static bool moveBuilding(PlacementWorld *world, int instanceId, int cellX, int cellY, float rotationDeg=-1.f)
Compatibility projection of moveBuildingResult; prefer the Result API in C++.
static int placeAtWorld(PlacementWorld *world, const std::string &buildingId, float worldX, float worldY, float rotationDeg=0.f)
先吸附再放置;free/自定义 snap 会保留吸附后的世界坐标。
static uint8_t edgeConnectionMask(const PlacementWorld &world, int instanceId)
Compute compatible neighbouring edge topology.
static int placeGhost(PlacementWorld *world, Ghost *ghost)
static int placeEdge(PlacementWorld *world, const std::string &buildingId, int cellX, int cellY, const std::string &direction)
Place an edge-domain definition, returning the new instance id or zero.
static eve::Result< EdgeAddress > canonicalEdge(int cellX, int cellY, const std::string &direction)
Normalize a cell-relative cardinal direction to a unique edge address.
static bool canPlace(PlacementWorld *world, const std::string &buildingId, int cellX, int cellY, float rotationDeg=0.f, int excludeInstanceId=0, std::string *reason=nullptr)
static bool canPlaceEdge(PlacementWorld *world, const std::string &buildingId, int cellX, int cellY, const std::string &direction, int excludeInstanceId=0, std::string *reason=nullptr, int level=std::numeric_limits< int >::min())
Validate an edge-domain placement without mutating the world.
格子型建筑放置世界(脚本可直接操作)。
float getBuildingSurfaceNormalZ(int instanceId) const
Z component of the unit surface normal captured at placement time.
bool isEdgeEmpty(const std::string &channel, int cellX, int cellY, const std::string &direction) const
Return whether an edge is unoccupied in the requested channel.
int getBuildingSupportCount(int instanceId) const
Number of direct structural supports used by an instance.
std::string canPlaceReason(const std::string &buildingId, int cellX, int cellY, float rotationDeg=0.f)
Can place reason.
std::string canPlaceEdgeReason(const std::string &buildingId, int cellX, int cellY, const std::string &direction)
Stable rejection token for edge placement.
PlacementWorld(int width, int height, float cellSize=32.f)
创建 width×height 的格子世界,cellSize 为像素/格。
bool inBounds(int cellX, int cellY) const
In bounds.
int worldToCellX(float worldX) const
世界像素 ↔ 格子坐标。
int placeFree(const std::string &buildingId, float worldX, float worldY, float elevation=0.f, float rotationDeg=0.f)
Compatibility-only script projection of exact free placement.
float getOriginX() const
Returns the origin x.
void destroy()
释放资源并使其失效。
int getTerrain(int cellX, int cellY) const
Returns the terrain.
std::string getBuildingProp(int instanceId, const std::string &key, const std::string &fallback={}) const
Returns the building prop.
void setCellSize(float s)
Sets the cell size.
bool canPlaceEdge(const std::string &buildingId, int cellX, int cellY, const std::string &direction)
Validate edge placement without mutation.
float getBuildingWorldY(int instanceId) const
Returns the building world y.
int placeCorner(const std::string &buildingId, int vertexX, int vertexY)
Compatibility-only script projection of canonical corner placement.
void clearBuildings()
Clears buildings.
int getBuildingCellY(int instanceId) const
Returns the building cell y.
float getBuildingSurfaceNormalX(int instanceId) const
X component of the unit surface normal captured at placement time.
float getOriginY() const
Returns the origin y.
int getEdgeConnectionMask(int instanceId) const
Six-bit local connection mask for an edge object's compatible neighbours.
int getFreeOccupantAtLevel(const std::string &channel, float worldX, float worldY, int level) const
Query a free-domain footprint at an explicit floor.
std::unordered_map< std::string, std::vector< int > > ChannelMap
void setGridFromLayer(map::TileLayer *layer)
Sets the grid from layer.
std::unique_ptr< PlacementWorld > cloneState() const
Clone complete logical state into an independently owned world candidate.
void setHexSideLength(float s)
Sets the hex side length.
float getBuildingWorldX(int instanceId) const
Returns the building world x.
std::string canPlaceFreeReason(const std::string &buildingId, float worldX, float worldY)
Can place free reason.
bool moveBuilding(int instanceId, int cellX, int cellY, float rotationDeg=-1.f)
Moves building.
int getOccupantAtLevel(const std::string &channel, int cellX, int cellY, int level) const
Query one channel at an explicit discrete floor.
~PlacementWorld()
Placement world.
std::string getGridLayoutName() const
Returns the grid layout name.
int getBuildingLevel(int instanceId) const
Return an instance's authoritative discrete floor.
float getBuildingElevation(int instanceId) const
Returns the building elevation.
int getOccupant(int cellX, int cellY) const
占用查询:格子上建筑实例 / 是否为空。
bool isCellEmptyInChannel(const std::string &channel, int cellX, int cellY) const
已放置建筑实例查询。
int removeBuildingCascade(int instanceId)
Compatibility-only script projection of the canonical Result removal API.
bool hasBuilding(int instanceId) const
True when building.
int getBuildingSupportAt(int instanceId, int index) const
Direct structural support id by stable footprint order, or zero.
void setOrigin(float x, float y)
Sets the origin.
const grid::GridConfig & getGrid() const
Returns the grid.
int getBuildingInstanceAt(int index) const
按插入顺序取实例 id。
int getBuildingCellX(int instanceId) const
Returns the building cell x.
float cellToWorld3DX(int cellX, int cellY, float elevation) const
Cell to world 3 dx.
bool buildingHasTag(int instanceId, const std::string &tag) const
Building has tag.
const std::vector< int > & occupancy() const
Occupancy.
int placeAt(const std::string &buildingId, int cellX, int cellY, float rotationDeg=0.f)
Place at.
int placeEdge(const std::string &buildingId, int cellX, int cellY, const std::string &direction)
Place an edge definition on a north/east/south/west edge; returns instance id or 0.
void setGridLayout(const std::string &layout)
Sets the grid layout.
void setFloorHeight(float height)
Set positive height between discrete floors. Invalid values are ignored.
std::vector< EdgeCurveGroupId > edgeCurveGroupIds() const
Return an owning ascending list of all curve-group identities.
void setExtra(const std::string &key, const std::string &value)
世界级额外属性(键值)。
int getAnyCornerOccupantAtLevel(int vertexX, int vertexY, int level) const
Query the first corner occupant across channels at an explicit floor.
void setCellGap(float gapX, float gapY)
Sets the cell gap.
int getBuildingCount() const
Returns the building count.
bool isCellEmpty(int cellX, int cellY) const
True when cell empty.
void setTerrain(int cellX, int cellY, int semantic)
Sets the terrain.
void setTerrainGid(int gid, int semantic)
Sets the terrain gid.
bool removeBuilding(int instanceId)
Removes building.
float getBuildingRotation(int instanceId) const
Returns the building rotation.
void clearTileLayer()
Clears tile layer.
int64_t getBuildingSurfaceRevision(int instanceId) const
Surface provider revision captured when this instance was placed.
int worldToCell3DX(float worldX, float worldY, float worldZ) const
World to cell 3 dx.
int getEdgeOccupant(const std::string &channel, int cellX, int cellY, const std::string &direction) const
Return the occupant of a canonicalized north/east/south/west cell edge.
bool canPlaceFree(const std::string &buildingId, float worldX, float worldY)
Can place free.
float cellToWorldX(int cellX) const
Cell to world x.
int placeAtWorld3D(const std::string &buildingId, float worldX, float worldY, float worldZ, float rotationDeg=0.f)
Place at world 3 d.
int getEdgeOccupantAtLevel(const std::string &channel, int cellX, int cellY, const std::string &direction, int level) const
Query a canonical edge at an explicit discrete floor.
std::string getGridPlaneName() const
Returns the grid plane name.
bool canPlace(const std::string &buildingId, int cellX, int cellY, float rotationDeg=0.f)
便捷操作(转发 PlacementSystem):放置 / 移除 / 移动。
void swapState(PlacementWorld &candidate) noexcept
Atomically exchange complete logical state with a prepared candidate.
float cellToWorldY(int cellY) const
Cell to world y.
std::string getEdgeVariant(int instanceId) const
Stable topology variant name for an edge instance.
int placeGhost(Ghost *ghost)
Place ghost.
int worldToCellY(float worldY) const
World to cell y.
void cellToWorldPlane(int cellX, int cellY, float &px, float &py) const
Cell to world plane.
eve::Result< EdgeCurveGroup > edgeCurveGroupForInstance(int instanceId) const
Return the owning curve-group snapshot linked by an edge instance.
int getCornerOccupantAtLevel(const std::string &channel, int vertexX, int vertexY, int level) const
Query a corner-domain occupant at an explicit floor.
void setBuildingProp(int instanceId, const std::string &key, const std::string &value)
Sets the building prop.
std::string getBuildingId(int instanceId) const
Returns the building id.
void setGridPlane(const std::string &plane)
Sets the grid plane.
eve::Result< EdgeCurveGroup > edgeCurveGroup(EdgeCurveGroupId id) const
Return an owning curve-group snapshot.
void bindTileLayer(map::TileLayer *layer)
Binds tile layer.
std::string getExtra(const std::string &key, const std::string &fallback={}) const
Returns the extra.
int worldToCell3DY(float worldX, float worldY, float worldZ) const
World to cell 3 dy.
void setStagger(const std::string &axis, const std::string &index)
Sets the stagger.
int placeAtWorld(const std::string &buildingId, float worldX, float worldY, float rotationDeg=0.f)
Place at world.
float getCellSize() const
Returns the cell size.
int getCornerOccupant(const std::string &channel, int vertexX, int vertexY) const
Query a corner-domain occupant at the active floor.
int getAnyOccupant(int cellX, int cellY) const
Returns the any occupant.
void worldToCell3D(float worldX, float worldY, float worldZ, int &cellX, int &cellY) const
World to cell 3 d.
std::string canPlaceCornerReason(const std::string &buildingId, int vertexX, int vertexY)
Stable corner placement rejection token.
std::string canRemoveBuildingReason(int instanceId) const
Stable rejection token for ordinary removal (support_in_use when depended on).
float cellToWorld3DY(int cellX, int cellY, float elevation) const
Cell to world 3 dy.
int getAnyOccupantAtLevel(int cellX, int cellY, int level) const
Query the first occupant across channels at an explicit floor.
int getFreeOccupant(const std::string &channel, float worldX, float worldY) const
Return the first free-domain object whose circular footprint contains the point.
std::string getBuildingSurfaceId(int instanceId) const
Surface provider identity captured when this instance was placed.
std::string getBuildingChannel(int instanceId) const
Returns the building channel.
void setTerrainGidMapJson(const std::string &json)
Sets the terrain gid map json.
float getBuildingWorldZ(int instanceId) const
Returns the building world z.
int getOccupantInChannel(const std::string &channel, int cellX, int cellY) const
Returns the occupant in channel.
bool canPlaceCorner(const std::string &buildingId, int vertexX, int vertexY)
Validate corner placement without mutation.
float getBuildingSurfaceNormalY(int instanceId) const
Y component of the unit surface normal captured at placement time.
float cellToWorld3DZ(int cellX, int cellY, float elevation) const
Cell to world 3 dz.
std::vector< int > & channelOccupancy(const std::string &channel)
Channel occupancy.
void fillTerrain(int semantic)
地形语义(占用检查用)。
void clearTerrainGidMap()
Clears terrain gid map.
void cellToWorld3D(int cellX, int cellY, float elevation, float &worldX, float &worldY, float &worldZ) const
Cell to world 3 d.
bool isCornerEmpty(const std::string &channel, int vertexX, int vertexY) const
Return whether a corner-domain address is empty in one channel.
int getBuildingDependentCount(int instanceId) const
Number of instances that directly depend on this instance.
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
Definition TileLayer.h:32
int getTile(int tx, int ty)
Definition TileLayer.cpp:72
建筑放置模块入口:定义 / 放置世界 / 鬼影 / 变更事件的脚本绑定点。 设计文档:docs/dev/建筑放置系统设计.md
Definition Building.cpp:7
EdgeAxis
Canonical orientation of an edge in logical grid coordinates.
void worldToCell(const GridConfig &cfg, float px, float py, int &cx, int &cy, int mapW, int mapH)
Maps planar coordinates to the nearest cell. Staggered/hex layouts use a bounded nearest-neighbor sea...
void cellToWorld(const GridConfig &cfg, int cx, int cy, float &px, float &py)
Maps a cell to planar coordinates (px, py). On XY plane these are world (x, y); on XZ plane they are ...
Canonical address of one grid edge.
Strong world-local identity of one authoritative edge-curve group.
已放置的建筑实例。
int level
Authoritative discrete floor coordinate used by occupancy and topology.
Grid shape, cell metrics, origin, and placement plane. Pure data; use GridProjection helpers for worl...
Definition GridConfig.h:34
static const char * layoutName(GridLayout l)
Stable string name for a layout enum value.
Definition GridConfig.cpp:5
static const char * planeName(GridPlane p)
Stable string name for a plane enum value.
static GridPlane planeFromName(const std::string &name)
Parse a plane name; unknown names fall back to XY.
static GridLayout layoutFromName(const std::string &name)
Parse a layout name; unknown names fall back to Rectangle.