17PlacementSystem::SurfacePatch surfacePatchFromGhost(
const PlacementWorld &
world,
19 PlacementSystem::SurfacePatch patch;
21 patch.anchor.worldX = ghost.getWorldX();
22 patch.anchor.worldY = ghost.getElevation();
23 patch.anchor.worldZ = ghost.getWorldY();
25 patch.anchor.worldX = ghost.getWorldX();
26 patch.anchor.worldY = ghost.getWorldY();
27 patch.anchor.worldZ = ghost.getElevation();
29 patch.anchor.surfaceId = ghost.getSurfaceId();
30 patch.anchor.surfaceRevision =
static_cast<uint64_t
>(ghost.getSurfaceRevision());
31 patch.anchor.normalX = ghost.getSurfaceNormalX();
32 patch.anchor.normalY = ghost.getSurfaceNormalY();
33 patch.anchor.normalZ = ghost.getSurfaceNormalZ();
34 patch.anchor.tangentX = ghost.getSurfaceTangentX();
35 patch.anchor.tangentY = ghost.getSurfaceTangentY();
36 patch.anchor.tangentZ = ghost.getSurfaceTangentZ();
37 patch.samples.resize(
static_cast<size_t>(std::max(ghost.getSurfaceSampleCount(), 0)));
38 patch.maxSlopeDegrees = ghost.getSurfaceMaxSlopeDegrees();
39 patch.heightDelta = ghost.getSurfaceHeightDelta();
43std::optional<PlacementSystem::PatternKind> patternKindFromName(
const std::string &
name) {
46 if (
name ==
"edge_cubic_bezier")
49 if (
name ==
"rectangle_outline")
84 if (!
world || buildingId.empty())
return false;
89 lastPlacedIds_.clear();
90 edgePathVertices_.clear();
91 edgeCurveControlPoints_.clear();
96 patternActive_ =
false;
103 edgePathVertices_.clear();
104 edgeCurveControlPoints_.clear();
105 patternRequest_ = {};
106 patternPreview_ = {};
107 patternActive_ =
false;
115 if (mode ==
"remove") {
127 if (ghost_) ghost_->
rotateBy(deltaDeg);
135 if (!active_ || !ghost_)
return false;
137 if (!world_)
return false;
145 if (!active_ || !ghost_)
return false;
147 if (!world_)
return false;
155 if (!active_ || !ghost_)
return false;
157 if (!world_)
return false;
178 ghost_->
setCorner(world_, vertexX, vertexY);
196 return ghost_ ? ghost_->
getReason() : std::string{};
199void PlacementSession::refreshValidate() {
200 if (world_ && ghost_) ghost_->
validate(world_);
204 if (!active_ || !world_ || !ghost_)
return 0;
205 if (mode_ ==
"remove") {
209 ghost_->
getEdgeAxis() ==
"horizontal" ?
"north" :
"west";
211 const std::string channel = definition ? definition->channel : std::string{};
215 const std::string channel = definition ? definition->channel : std::string{};
219 const std::string channel = definition ? definition->channel : std::string{};
224 if (occ <= 0)
return 0;
231 int endVertexX,
int endVertexY) {
232 lastPlacedIds_.clear();
233 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
237 request.points = {{
static_cast<float>(startVertexX),
static_cast<float>(startVertexY)},
238 {
static_cast<float>(endVertexX),
static_cast<float>(endVertexY)}};
241 lastPlacedIds_ = std::move(result).takeValue().instanceIds;
246 edgePathVertices_.clear();
247 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
251 edgePathVertices_.push_back({vertexX, vertexY});
260 edgePathVertices_.push_back({vertexX, vertexY});
265 lastPlacedIds_.clear();
266 if (!active_ || !world_ || !ghost_ || mode_ !=
"place" || edgePathVertices_.size() < 2)
272 {
static_cast<float>(vertex.x),
static_cast<float>(vertex.y)});
275 lastPlacedIds_ = std::move(result).takeValue().instanceIds;
276 edgePathVertices_.clear();
281 edgeCurveControlPoints_.clear();
282 if (!active_ || !world_ || !ghost_ || mode_ !=
"place" || !std::isfinite(
x) ||
284 return EdgePathUpdateStatus::Rejected;
285 const BuildingDefinition *definition = BuildingRegistry::find(ghost_->getBuildingId());
286 if (!definition || definition->placementKind !=
"edge")
return EdgePathUpdateStatus::Rejected;
287 edgeCurveControlPoints_.push_back({
x,
y});
288 return EdgePathUpdateStatus::Updated;
292 if (edgeCurveControlPoints_.empty() || edgeCurveControlPoints_.size() >= 4 ||
293 !std::isfinite(
x) || !std::isfinite(
y))
294 return EdgePathUpdateStatus::Rejected;
295 edgeCurveControlPoints_.push_back({
x,
y});
296 return EdgePathUpdateStatus::Updated;
300 lastPlacedIds_.clear();
301 if (!active_ || !world_ || !ghost_ || mode_ !=
"place" ||
302 edgeCurveControlPoints_.size() != 4)
303 return EdgeLineExecuteStatus::Rejected;
305 request.
kind = PlacementSystem::PatternKind::EdgeCubicBezier;
306 request.points = edgeCurveControlPoints_;
307 request.subdivisions = subdivisions;
308 auto result = PlacementSystem::placePattern(world_, ghost_->getBuildingId(),
request);
309 if (!result.ok())
return EdgeLineExecuteStatus::Rejected;
310 lastPlacedIds_ = std::move(result).takeValue().instanceIds;
311 edgeCurveControlPoints_.clear();
312 return EdgeLineExecuteStatus::Placed;
316 int subdivisions,
const std::string &surfaceName) {
317 lastPlacedIds_.clear();
318 if (!active_ || !world_ || !ghost_ || mode_ !=
"place" ||
319 edgeCurveControlPoints_.size() != 4 || surfaceName.empty())
320 return EdgeLineExecuteStatus::Rejected;
322 request.
kind = PlacementSystem::PatternKind::EdgeCubicBezier;
323 request.points = edgeCurveControlPoints_;
324 request.subdivisions = subdivisions;
325 request.surfaceName = surfaceName;
326 auto result = PlacementSystem::placePattern(world_, ghost_->getBuildingId(),
request);
327 if (!result.ok())
return EdgeLineExecuteStatus::Rejected;
328 lastPlacedIds_ = std::move(result).takeValue().instanceIds;
329 edgeCurveControlPoints_.clear();
330 return EdgeLineExecuteStatus::Placed;
333int PlacementSession::getLastPlacedId(
int index)
const {
334 if (index < 0 || index >=
static_cast<int>(lastPlacedIds_.size()))
return 0;
335 return lastPlacedIds_[
static_cast<size_t>(
index)];
340 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
341 return PlacementEditExecuteStatus::Rejected;
343 ghost_->getPlacementKind() ==
"edge"
344 ? PlacementSystem::moveEdgeResult(
345 world_, instanceId, ghost_->getCellX(), ghost_->getCellY(),
346 ghost_->getEdgeAxis() ==
"horizontal" ?
"north" :
"west")
347 : ghost_->getPlacementKind() ==
"corner"
348 ? PlacementSystem::moveCornerResult(world_, instanceId, ghost_->getCellX(),
350 : ghost_->getPlacementKind() ==
"free"
351 ? (!ghost_->getSurfaceId().empty()
352 ? PlacementSystem::moveFreeSurfaceResult(
353 world_, instanceId, surfacePatchFromGhost(*world_, *ghost_),
354 ghost_->getRotationDeg())
355 : PlacementSystem::moveFreeResult(
356 world_, instanceId, ghost_->getWorldX(), ghost_->getWorldY(),
357 ghost_->getElevation(), ghost_->getRotationDeg()))
358 : PlacementSystem::moveBuildingResult(world_, instanceId, ghost_->getCellX(),
359 ghost_->getCellY(), ghost_->getRotationDeg());
360 if (!result.
ok())
return PlacementEditExecuteStatus::Rejected;
361 lastEditedId_ = std::move(result).takeValue().after.instanceId;
362 return PlacementEditExecuteStatus::Committed;
367 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
368 return PlacementEditExecuteStatus::Rejected;
370 PlacementSystem::replaceBuildingResult(world_, instanceId, ghost_->getBuildingId());
371 if (!result.ok())
return PlacementEditExecuteStatus::Rejected;
372 lastEditedId_ = std::move(result).takeValue().after.instanceId;
373 return PlacementEditExecuteStatus::Committed;
379 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
return AreaExecuteStatus::Rejected;
381 request.
kind = PlacementSystem::PatternKind::RectangleFill;
382 request.points = {{
static_cast<float>(minCellX),
static_cast<float>(minCellY)},
383 {
static_cast<float>(maxCellX),
static_cast<float>(maxCellY)}};
384 request.rotationDeg = ghost_->getRotationDeg();
385 auto result = PlacementSystem::previewPattern(world_, ghost_->getBuildingId(),
request);
386 if (!result.ok())
return AreaExecuteStatus::Rejected;
387 areaPreview_ = std::move(result).takeValue().area;
388 return areaPreview_.rejectedCount == 0 ? AreaExecuteStatus::Accepted
389 : AreaExecuteStatus::Rejected;
394 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
return AreaExecuteStatus::Rejected;
396 request.
kind = PlacementSystem::PatternKind::CircleBrush;
397 request.points = {{
static_cast<float>(centerCellX),
static_cast<float>(centerCellY)}};
399 request.rotationDeg = ghost_->getRotationDeg();
400 auto result = PlacementSystem::previewPattern(world_, ghost_->getBuildingId(),
request);
401 if (!result.ok())
return AreaExecuteStatus::Rejected;
402 areaPreview_ = std::move(result).takeValue().area;
403 return areaPreview_.rejectedCount == 0 ? AreaExecuteStatus::Accepted
404 : AreaExecuteStatus::Rejected;
409 lastPlacedIds_.clear();
410 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
return AreaExecuteStatus::Rejected;
412 request.
kind = PlacementSystem::PatternKind::RectangleFill;
413 request.points = {{
static_cast<float>(minCellX),
static_cast<float>(minCellY)},
414 {
static_cast<float>(maxCellX),
static_cast<float>(maxCellY)}};
415 request.rotationDeg = ghost_->getRotationDeg();
416 auto result = PlacementSystem::placePattern(world_, ghost_->getBuildingId(),
request);
417 if (!result.ok())
return AreaExecuteStatus::Rejected;
418 auto placement = std::move(result).takeValue();
419 areaPreview_ = std::move(placement.preview.area);
420 lastPlacedIds_ = std::move(placement.instanceIds);
421 return AreaExecuteStatus::Accepted;
425 lastPlacedIds_.clear();
426 if (!active_ || !world_ || !ghost_ || mode_ !=
"place")
return AreaExecuteStatus::Rejected;
428 request.
kind = PlacementSystem::PatternKind::CircleBrush;
429 request.points = {{
static_cast<float>(centerCellX),
static_cast<float>(centerCellY)}};
431 request.rotationDeg = ghost_->getRotationDeg();
432 auto result = PlacementSystem::placePattern(world_, ghost_->getBuildingId(),
request);
433 if (!result.ok())
return AreaExecuteStatus::Rejected;
434 auto placement = std::move(result).takeValue();
435 areaPreview_ = std::move(placement.preview.area);
436 lastPlacedIds_ = std::move(placement.instanceIds);
437 return AreaExecuteStatus::Accepted;
440int PlacementSession::getAreaPreviewCellX(
int index)
const {
441 return index >= 0 &&
index < getAreaPreviewCount() ? areaPreview_.cells[size_t(
index)].cellX : 0;
443int PlacementSession::getAreaPreviewCellY(
int index)
const {
444 return index >= 0 &&
index < getAreaPreviewCount() ? areaPreview_.cells[size_t(
index)].cellY : 0;
446bool PlacementSession::getAreaPreviewAccepted(
int index)
const {
447 return index >= 0 &&
index < getAreaPreviewCount() && areaPreview_.cells[size_t(
index)].accepted;
449std::string PlacementSession::getAreaPreviewReason(
int index)
const {
450 return index >= 0 &&
index < getAreaPreviewCount() ? areaPreview_.cells[size_t(
index)].reason
455 const auto parsed = patternKindFromName(
kind);
456 if (!active_ || !world_ || !ghost_ || mode_ !=
"place" || !parsed)
457 return PatternUpdateStatus::Rejected;
458 const BuildingDefinition *definition = BuildingRegistry::find(ghost_->getBuildingId());
459 if (!definition || (edgePattern(*parsed) != (definition->placementKind ==
"edge")))
460 return PatternUpdateStatus::Rejected;
461 patternRequest_ = {};
462 patternRequest_.kind = *parsed;
463 patternRequest_.rotationDeg = ghost_->getRotationDeg();
464 patternPreview_ = {};
465 patternPreview_.request = patternRequest_;
467 patternActive_ =
true;
468 return PatternUpdateStatus::Updated;
472 if (!patternActive_ || !std::isfinite(
x) || !std::isfinite(
y))
473 return PatternUpdateStatus::Rejected;
474 size_t maximum = std::numeric_limits<size_t>::max();
475 if (patternRequest_.kind == PlacementSystem::PatternKind::EdgeLine ||
476 patternRequest_.kind == PlacementSystem::PatternKind::RectangleFill ||
477 patternRequest_.kind == PlacementSystem::PatternKind::RectangleOutline)
479 else if (patternRequest_.kind == PlacementSystem::PatternKind::EdgeCubicBezier)
481 else if (patternRequest_.kind == PlacementSystem::PatternKind::CircleBrush)
483 if (patternRequest_.points.size() >=
maximum)
return PatternUpdateStatus::Rejected;
484 patternRequest_.points.push_back({
x,
y});
485 patternPreview_ = {};
486 patternPreview_.request = patternRequest_;
487 return PatternUpdateStatus::Updated;
491 if (!patternActive_ ||
radius < 0)
return PatternUpdateStatus::Rejected;
492 patternRequest_.radius =
radius;
493 return PatternUpdateStatus::Updated;
497 if (!patternActive_ || subdivisions <= 0)
return PatternUpdateStatus::Rejected;
498 patternRequest_.subdivisions = subdivisions;
499 return PatternUpdateStatus::Updated;
503 if (!patternActive_ ||
504 patternRequest_.kind != PlacementSystem::PatternKind::EdgeCubicBezier)
505 return PatternUpdateStatus::Rejected;
506 patternRequest_.surfaceName = surfaceName;
507 return PatternUpdateStatus::Updated;
511 patternPreview_ = {};
513 if (!patternActive_ || !world_ || !ghost_ || mode_ !=
"place")
514 return PatternUpdateStatus::Rejected;
515 patternRequest_.rotationDeg = ghost_->getRotationDeg();
516 auto preview = PlacementSystem::previewPattern(world_, ghost_->getBuildingId(),
518 if (!preview.ok())
return PatternUpdateStatus::Rejected;
519 patternPreview_ = std::move(preview).takeValue();
520 areaPreview_ = patternPreview_.area;
521 if (!edgePattern(patternRequest_.kind) && patternPreview_.area.rejectedCount != 0)
522 return PatternUpdateStatus::Rejected;
523 return PatternUpdateStatus::Updated;
527 lastPlacedIds_.clear();
528 if (!patternActive_ || !world_ || !ghost_ || mode_ !=
"place")
529 return PatternExecuteStatus::Rejected;
530 patternRequest_.rotationDeg = ghost_->getRotationDeg();
531 auto placed = PlacementSystem::placePattern(world_, ghost_->getBuildingId(),
533 if (!
placed.ok())
return PatternExecuteStatus::Rejected;
534 auto receipt = std::move(
placed).takeValue();
535 patternPreview_ = std::move(receipt.preview);
536 areaPreview_ = patternPreview_.area;
537 lastPlacedIds_ = std::move(receipt.instanceIds);
538 return PatternExecuteStatus::Placed;
541std::string PlacementSession::getPatternKind()
const {
542 return patternActive_ ? patternKindName(patternRequest_.kind) : std::string{};
545int PlacementSession::getPatternPreviewX(
int index)
const {
546 if (index < 0 || index >= getPatternPreviewCount())
return 0;
547 return edgePattern(patternPreview_.request.kind)
548 ? patternPreview_.edge.edges[
static_cast<size_t>(
index)].
x
549 : patternPreview_.area.cells[
static_cast<size_t>(
index)].cellX;
552int PlacementSession::getPatternPreviewY(
int index)
const {
553 if (index < 0 || index >= getPatternPreviewCount())
return 0;
554 return edgePattern(patternPreview_.request.kind)
555 ? patternPreview_.edge.edges[
static_cast<size_t>(
index)].
y
556 : patternPreview_.area.cells[
static_cast<size_t>(
index)].cellY;
559std::string PlacementSession::getPatternPreviewAxis(
int index)
const {
560 if (index < 0 || index >= getPatternPreviewCount() ||
561 !edgePattern(patternPreview_.request.kind))
563 return patternPreview_.edge.edges[
static_cast<size_t>(
index)].axis ==
569bool PlacementSession::getPatternPreviewAccepted(
int index)
const {
570 if (index < 0 || index >= getPatternPreviewCount())
return false;
571 return edgePattern(patternPreview_.request.kind) ||
572 patternPreview_.area.cells[
static_cast<size_t>(
index)].accepted;
575std::string PlacementSession::getPatternPreviewReason(
int index)
const {
576 if (index < 0 || index >= getPatternPreviewCount() ||
577 edgePattern(patternPreview_.request.kind))
579 return patternPreview_.area.cells[
static_cast<size_t>(
index)].reason;
const GltfImportRequest & request
Module-neutral grid topology and sizing (no building/map dependency).
graphics::Canvas * previous
RoadLaneDirection direction
Move-only operation result carrying either a value or Status.
bool ok() const noexcept
Whether this result represents a non-failure outcome.
static const BuildingDefinition * find(const std::string &id)
Finds .
EVENGINE_API_WORLD public API.
int getCellY() const
Returns the cell y.
float getWorldY() const
Returns the world y.
void setFree(PlacementWorld *world, float worldX, float worldY, float elevation=0.f)
Set an exact unsnapped world-plane anchor for a free-domain definition.
void setFromWorld(PlacementWorld *world, float worldX, float worldY)
按世界吸附模式,从世界坐标刷新格子与世界位姿。
void setRotationDeg(float deg)
Sets the rotation deg.
int getCellX() const
Returns the cell x.
bool validate(PlacementWorld *world)
对当前姿态做校验,写入 valid_/reason_。
void setFromWorld3D(PlacementWorld *world, float worldX, float worldY, float worldZ)
Sets the from world 3 d.
std::string getReason() const
Returns the reason.
void setCorner(PlacementWorld *world, int vertexX, int vertexY)
Snap this ghost to one canonical grid vertex.
bool isValid() const
True when valid.
std::string getPlacementKind() const
Current placement domain (cell, edge, corner, or free).
float getWorldX() const
Returns the world x.
float getRotationDeg() const
Returns the rotation deg.
std::string getEdgeAxis() const
Canonical edge axis (horizontal/vertical), empty for cell ghosts.
void setEdge(PlacementWorld *world, int cellX, int cellY, const std::string &direction)
Snap this ghost to a canonicalized cell edge and its exact midpoint pose.
std::string getBuildingId() const
Returns the building id.
void rotateBy(float deltaDeg)
Rotate by.
void setFromSurface(PlacementWorld *world, const std::string &surface, float x, float y)
Sets the from surface.
void setBuildingId(const std::string &id)
Sets the building id.
float getRotationDeg() const
Returns the rotation deg.
void stopPlacement()
Stops placement.
void setRotationDeg(float deg)
Sets the rotation deg.
void setMode(const std::string &mode)
Sets the mode.
PlacementSession()
Placement session.
bool startPlacement(PlacementWorld *world, const std::string &buildingId)
Starts placement.
FreeUpdateStatus updateFree(PlacementWorld *world, float worldX, float worldY, float elevation=0.f)
Refresh a free-object ghost at an exact world-plane position.
bool isValid() const
True when valid.
std::string getReason() const
Returns the reason.
EdgeUpdateStatus updateEdge(PlacementWorld *world, int cellX, int cellY, const std::string &direction)
Refresh an edge-object ghost from a cell-relative cardinal edge.
bool updateFromWorld3D(PlacementWorld *world, float worldX, float worldY, float worldZ)
Updates from world 3 d.
EdgeLineExecuteStatus executeEdgeLine(int startVertexX, int startVertexY, int endVertexX, int endVertexY)
Atomically place an axis-aligned edge line between logical vertices.
std::string getBuildingId() const
Returns the building id.
CornerUpdateStatus updateCorner(PlacementWorld *world, int vertexX, int vertexY)
Refresh a corner-object ghost at a canonical grid vertex.
EdgePathUpdateStatus appendEdgePathVertex(int vertexX, int vertexY)
Append one orthogonally connected logical vertex to the current edge path.
bool updateFromWorld(PlacementWorld *world, float worldX, float worldY)
Updates from world.
EdgeLineExecuteStatus executeEdgePath()
Atomically commit the complete edge path.
void rotateBy(float deltaDeg)
Rotate by.
EdgePathUpdateStatus beginEdgePath(int vertexX, int vertexY)
Start a multi-segment edge path at one logical grid vertex.
bool updateFromSurface(PlacementWorld *world, const std::string &surface, float x, float y)
Updates from surface.
static eve::Result< PatternPlacement > placePattern(PlacementWorld *world, const std::string &buildingId, const PatternRequest &request)
Validate and atomically commit any built-in placement pattern.
PatternKind
Built-in geometry families accepted by the unified placement-pattern protocol.
int getOccupant(int cellX, int cellY) const
占用查询:格子上建筑实例 / 是否为空。
bool removeBuilding(int instanceId)
Removes building.
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.
int placeGhost(Ghost *ghost)
Place ghost.
int getCornerOccupant(const std::string &channel, int vertexX, int vertexY) const
Query a corner-domain occupant at the active floor.
int getFreeOccupant(const std::string &channel, float worldX, float worldY) const
Return the first free-domain object whose circular footprint contains the point.
建筑放置模块入口:定义 / 放置世界 / 鬼影 / 变更事件的脚本绑定点。 设计文档:docs/dev/建筑放置系统设计.md
EdgeLineExecuteStatus
Outcome of committing one continuous edge line.
AreaExecuteStatus
Outcome of refreshing or committing an area tool request.
EdgePathUpdateStatus
Outcome of adding a logical vertex to an edge path preview.
FreeUpdateStatus
Outcome of refreshing an exact free-object placement preview.
PatternUpdateStatus
Outcome of configuring or previewing a unified placement pattern.
CornerUpdateStatus
Outcome of refreshing a corner placement preview.
PlacementEditExecuteStatus
Outcome of committing one move or replace edit.
PatternExecuteStatus
Outcome of atomically committing a unified placement pattern.
EdgeUpdateStatus
Outcome of refreshing an edge placement preview.
Canonical address of one logical grid vertex used by corner objects.
Owning, renderer-neutral request for one atomic placement pattern.