载入中...
搜索中...
未找到
RoadNetworkEditOperations.cpp
浏览该文件的文档.
2#include "procgen/editing/RoadNetworkEditTargetInternal.inc"
3
4#include <algorithm>
5#include <cmath>
6#include <limits>
7#include <utility>
8
9namespace eve::procgen_editing {
10using namespace detail;
11
13 float z) const {
14 auto node = network_->nodeResult(nodeId);
15 if (!node.ok())
16 return reject<editing::DomainOperation>("editor.road.node", "Road node does not exist", EditorStatus::NotFound);
17 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
18 return reject<editing::DomainOperation>("editor.road.position", "Road node position must be finite");
21 operation.inverseType = kMoveNode;
22 operation.target = targetId();
23 operation.payload = encodeNode(nodeId, x, y, z);
24 operation.inverse = encodeNode(nodeId, node.value().x, node.value().y, node.value().z);
25 operation.hasInverse = true;
26 operation.mergeKey = "road-node:" + std::to_string(nodeId);
27 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(nodeId));
28 operation.affectedProperties.emplace_back("position");
29 return editing::applied(std::move(operation));
30}
31
33 std::uint32_t nodeId, float junctionRadius) const {
34 auto node = network_->nodeResult(nodeId);
35 if (!node.ok()) return roadFailure<editing::DomainOperation>(node.status());
36 auto candidate = *network_;
37 auto changed = candidate.setNodeJunctionRadius(nodeId, junctionRadius);
38 if (!changed.ok()) return roadFailure<editing::DomainOperation>(changed.status());
41 operation.inverseType = kSetNodeRadius;
42 operation.target = targetId();
43 operation.payload = encodeNodeRadius(nodeId, junctionRadius);
44 operation.inverse = encodeNodeRadius(nodeId, node.value().junctionRadius);
45 operation.hasInverse = true;
46 operation.mergeKey = "road-node:" + std::to_string(nodeId) + ":junction-radius";
47 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(nodeId));
48 operation.affectedProperties.emplace_back("junctionRadius");
49 return editing::applied(std::move(operation));
50}
51
53 std::uint32_t nodeId, procgen::road::RoadJunctionControl control) const {
54 auto node = network_->nodeResult(nodeId);
55 if (!node.ok()) return roadFailure<editing::DomainOperation>(node.status());
56 auto candidate = *network_;
57 auto changed = candidate.setNodeJunctionControl(nodeId, control);
58 if (!changed.ok()) return roadFailure<editing::DomainOperation>(changed.status());
61 operation.inverseType = kSetNodeControl;
62 operation.target = targetId();
63 operation.payload = encodeNodeControl(nodeId, control);
64 operation.inverse = encodeNodeControl(nodeId, node.value().junctionControl);
65 operation.hasInverse = true;
66 operation.mergeKey = "road-node:" + std::to_string(nodeId) + ":junction-control";
67 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(nodeId));
68 operation.affectedProperties.emplace_back("junctionControl");
69 return editing::applied(std::move(operation));
70}
71
73 std::uint32_t removeNodeId,
74 float maxDistance) const {
75 return makeMergeNodesImpl(keepNodeId, removeNodeId, maxDistance, false);
76}
77
79 std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance) const {
80 return makeMergeNodesImpl(keepNodeId, removeNodeId, maxDistance, true);
81}
82
83EditorResult<editing::DomainOperation> RoadNetworkEditTarget::makeMergeNodesImpl(
84 std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance, bool connectTurns) const {
85 auto removedNode = network_->nodeResult(removeNodeId);
86 if (!removedNode.ok()) return roadFailure<editing::DomainOperation>(removedNode.status());
87 std::vector<procgen::road::RoadEdge> affectedEdges;
88 for (const auto& edge : network_->edges())
89 if (edge.from == removeNodeId || edge.to == removeNodeId) affectedEdges.push_back(edge);
90 std::vector<procgen::road::RoadLaneConnection> affectedLinks;
91 for (const auto& link : network_->laneLinks()) {
92 const bool referencesAffectedEdge = std::any_of(affectedEdges.begin(), affectedEdges.end(), [&](const auto& edge) {
93 return link.inEdge == edge.id || link.outEdge == edge.id;
94 });
95 if (referencesAffectedEdge) affectedLinks.push_back(link);
96 }
97 std::vector<procgen::road::RoadLaneConnection> affectedBlockedLinks;
98 for (const auto& link : network_->blockedLaneLinks()) {
99 const bool referencesAffectedEdge = std::any_of(affectedEdges.begin(), affectedEdges.end(), [&](const auto& edge) {
100 return link.inEdge == edge.id || link.outEdge == edge.id;
101 });
102 if (referencesAffectedEdge) affectedBlockedLinks.push_back(link);
103 }
104 auto candidate = *network_;
105 auto merged = candidate.mergeNodes(keepNodeId, removeNodeId, maxDistance);
106 if (!merged.ok()) return roadFailure<editing::DomainOperation>(merged.status());
107 if (connectTurns) {
108 auto connected = candidate.connectAllTurns(keepNodeId);
109 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
110 }
111
112 editing::DomainOperation operation;
113 operation.type = connectTurns ? kMergeNodesAndConnect : kMergeNodes;
114 operation.inverseType = kUnmergeNodes;
115 operation.target = targetId();
116 operation.payload = encodeMergeNodes(keepNodeId, removeNodeId, maxDistance);
117 operation.inverse = encodeUnmergeNodes(removedNode.value(), affectedEdges, affectedLinks, affectedBlockedLinks);
118 operation.hasInverse = true;
119 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(keepNodeId));
120 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(removeNodeId));
121 for (const auto& edge : affectedEdges)
122 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edge.id));
123 operation.affectedProperties.emplace_back("topology");
124 return editing::applied(std::move(operation));
125}
126
128 std::uint32_t edgeId, std::vector<procgen::road::RoadControlPoint> points) const {
129 auto edge = network_->edgeResult(edgeId);
130 if (!edge.ok())
131 return reject<editing::DomainOperation>("editor.road.edge", "Road edge does not exist", EditorStatus::NotFound);
132 auto candidate = *network_;
133 auto valid = candidate.setEdgeControlPoints(edgeId, points);
134 if (!valid.ok()) return roadFailure<editing::DomainOperation>(valid.status());
137 operation.inverseType = kSetEdgePoints;
138 operation.target = targetId();
139 operation.payload = encodeEdge(edgeId, points);
140 operation.inverse = encodeEdge(edgeId, edge.value().controlPoints);
141 operation.hasInverse = true;
142 operation.mergeKey = "road-edge:" + std::to_string(edgeId) + ":control-points";
143 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
144 operation.affectedProperties.emplace_back("controlPoints");
145 return editing::applied(std::move(operation));
146}
147
149 procgen::road::RoadStyle style) const {
150 auto edge = network_->edgeResult(edgeId);
151 if (!edge.ok())
152 return reject<editing::DomainOperation>("editor.road.edge", "Road edge does not exist", EditorStatus::NotFound);
153 auto candidate = *network_;
154 auto valid = candidate.setEdgeStyle(edgeId, style);
155 if (!valid.ok()) return roadFailure<editing::DomainOperation>(valid.status());
158 operation.inverseType = kSetEdgeStyle;
159 operation.target = targetId();
160 operation.payload = encodeStyle(edgeId, style);
161 operation.inverse = encodeStyle(edgeId, edge.value().style);
162 operation.hasInverse = true;
163 operation.mergeKey = "road-edge:" + std::to_string(edgeId) + ":style";
164 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
165 operation.affectedProperties.emplace_back("style");
166 return editing::applied(std::move(operation));
167}
168
170 std::uint32_t edgeId, int lanesForward, int lanesBackward) const {
171 auto edge = network_->edgeResult(edgeId);
172 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
173 std::vector<procgen::road::RoadLaneConnection> removedLinks;
174 for (const auto& link : network_->laneLinks()) {
176 return direction == procgen::road::RoadLaneDirection::Forward ? lanesForward : lanesBackward;
177 };
178 if ((link.inEdge == edgeId && link.inLane >= count(link.inDirection)) ||
179 (link.outEdge == edgeId && link.outLane >= count(link.outDirection)))
180 removedLinks.push_back(link);
181 }
182 std::vector<procgen::road::RoadLaneConnection> removedBlockedLinks;
183 for (const auto& link : network_->blockedLaneLinks()) {
185 return direction == procgen::road::RoadLaneDirection::Forward ? lanesForward : lanesBackward;
186 };
187 if ((link.inEdge == edgeId && link.inLane >= count(link.inDirection)) ||
188 (link.outEdge == edgeId && link.outLane >= count(link.outDirection)))
189 removedBlockedLinks.push_back(link);
190 }
191 auto candidate = *network_;
192 auto changed = candidate.setEdgeLaneCounts(edgeId, lanesForward, lanesBackward);
193 if (!changed.ok()) return roadFailure<editing::DomainOperation>(changed.status());
194
197 operation.inverseType = kSetEdgeLanes;
198 operation.target = targetId();
199 operation.payload = encodeLaneCounts(edgeId, lanesForward, lanesBackward, {}, {});
200 operation.inverse = encodeLaneCounts(edgeId, edge.value().lanesForward, edge.value().lanesBackward, removedLinks,
201 removedBlockedLinks);
202 operation.hasInverse = true;
203 operation.mergeKey = "road-edge:" + std::to_string(edgeId) + ":lanes";
204 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
205 operation.affectedProperties.emplace_back("lanesForward");
206 operation.affectedProperties.emplace_back("lanesBackward");
207 operation.affectedProperties.emplace_back("laneLinks");
208 return editing::applied(std::move(operation));
209}
210
212 auto candidate = *network_;
213 auto reversed = candidate.reverseEdge(edgeId);
214 if (!reversed.ok()) return roadFailure<editing::DomainOperation>(reversed.status());
217 operation.inverseType = kReverseEdge;
218 operation.target = targetId();
219 operation.payload = EditorValue::Object{{"id", std::int64_t{edgeId}}};
220 operation.inverse = operation.payload;
221 operation.hasInverse = true;
222 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
223 operation.affectedProperties.emplace_back("direction");
224 return editing::applied(std::move(operation));
225}
226
228 std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId) const {
229 auto edge = network_->edgeResult(edgeId);
230 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
231 const std::uint32_t oldNodeId = fromEndpoint ? edge.value().from : edge.value().to;
232 if (oldNodeId == nodeId)
233 return reject<editing::DomainOperation>("editor.road.reconnect-noop",
234 "Road endpoint is already connected to that node",
235 EditorStatus::NoOp);
236 auto candidate = *network_;
237 auto changed = candidate.reconnectEdgeEndpoint(edgeId, fromEndpoint, nodeId);
238 if (!changed.ok()) return roadFailure<editing::DomainOperation>(changed.status());
239
240 std::vector<procgen::road::RoadLaneConnection> removedLinks;
241 std::vector<procgen::road::RoadLaneConnection> removedBlockedLinks;
242 for (const auto& link : network_->laneLinks())
243 if ((link.inEdge == edgeId || link.outEdge == edgeId) &&
244 std::none_of(candidate.laneLinks().begin(), candidate.laneLinks().end(),
245 [&](const auto& remaining) { return sameLink(link, remaining); }))
246 removedLinks.push_back(link);
247 for (const auto& link : network_->blockedLaneLinks())
248 if ((link.inEdge == edgeId || link.outEdge == edgeId) &&
249 std::none_of(candidate.blockedLaneLinks().begin(), candidate.blockedLaneLinks().end(),
250 [&](const auto& remaining) { return sameLink(link, remaining); }))
251 removedBlockedLinks.push_back(link);
252
255 operation.inverseType = kReconnectEdgeEndpoint;
256 operation.target = targetId();
257 operation.payload = encodeReconnectEndpoint(edgeId, fromEndpoint, nodeId, {}, {});
258 operation.inverse = encodeReconnectEndpoint(edgeId, fromEndpoint, oldNodeId, removedLinks,
259 removedBlockedLinks);
260 operation.hasInverse = true;
261 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
262 operation.affectedProperties.emplace_back(fromEndpoint ? "from" : "to");
263 operation.affectedProperties.emplace_back("controlPoints");
264 operation.affectedProperties.emplace_back("laneLinks");
265 operation.affectedProperties.emplace_back("blockedLaneLinks");
266 return editing::applied(std::move(operation));
267}
268
270 std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId) const {
271 auto planned = makeReconnectEdgeEndpoint(edgeId, fromEndpoint, nodeId);
272 if (!planned.ok()) return planned;
273 auto candidate = *network_;
274 auto changed = candidate.reconnectEdgeEndpoint(edgeId, fromEndpoint, nodeId);
275 if (!changed.ok()) return roadFailure<editing::DomainOperation>(changed.status());
276 auto connected = candidate.connectAllTurns(nodeId);
277 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
278
279 std::vector<procgen::road::RoadLaneConnection> added;
280 for (const auto& link : candidate.laneLinks()) {
281 if (link.inEdge != edgeId && link.outEdge != edgeId) continue;
282 if (std::none_of(network_->laneLinks().begin(), network_->laneLinks().end(),
283 [&](const auto& original) { return sameLink(link, original); }))
284 added.push_back(link);
285 }
286 planned.value().payload = encodeReconnectEndpoint(edgeId, fromEndpoint, nodeId, added, {});
287 return planned;
288}
289
291 std::uint32_t edgeId, bool fromEndpoint, float maxDistance, bool connectTurns) const {
292 if (!std::isfinite(maxDistance) || maxDistance < 0.f)
293 return reject<editing::DomainOperation>("editor.road.snap-distance",
294 "Road endpoint snap distance must be finite and non-negative");
295 auto edge = network_->edgeResult(edgeId);
296 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
297 const std::uint32_t oldNodeId = fromEndpoint ? edge.value().from : edge.value().to;
298 const std::uint32_t otherNodeId = fromEndpoint ? edge.value().to : edge.value().from;
299 auto endpoint = network_->nodeResult(oldNodeId);
300 if (!endpoint.ok()) return roadFailure<editing::DomainOperation>(endpoint.status());
301
302 const float maximumSquared = maxDistance * maxDistance;
303 float bestSquared = std::numeric_limits<float>::max();
304 std::uint32_t bestNodeId = 0;
305 for (const auto& node : network_->nodes()) {
306 if (node.id == oldNodeId || node.id == otherNodeId) continue;
307 const float dx = node.x - endpoint.value().x;
308 const float dy = node.y - endpoint.value().y;
309 const float dz = node.z - endpoint.value().z;
310 const float distanceSquared = dx * dx + dy * dy + dz * dz;
311 if (distanceSquared > maximumSquared) continue;
312 if (distanceSquared < bestSquared || (distanceSquared == bestSquared && node.id < bestNodeId)) {
313 bestSquared = distanceSquared;
314 bestNodeId = node.id;
315 }
316 }
317 if (bestNodeId == 0)
318 return reject<editing::DomainOperation>("editor.road.snap-not-found",
319 "No distinct road node is inside the endpoint snap distance",
320 EditorStatus::NotFound);
321 return connectTurns ? makeReconnectEdgeEndpointAndConnect(edgeId, fromEndpoint, bestNodeId)
322 : makeReconnectEdgeEndpoint(edgeId, fromEndpoint, bestNodeId);
323}
324
326 std::uint32_t edgeId, bool fromEndpoint) const {
327 auto edge = network_->edgeResult(edgeId);
328 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
329 const std::uint32_t oldNodeId = fromEndpoint ? edge.value().from : edge.value().to;
330 auto candidate = *network_;
331 auto detached = candidate.detachEdgeEndpoint(edgeId, fromEndpoint);
332 if (!detached.ok()) return roadFailure<editing::DomainOperation>(detached.status());
333
334 std::vector<procgen::road::RoadLaneConnection> removedLinks;
335 std::vector<procgen::road::RoadLaneConnection> removedBlockedLinks;
336 for (const auto& link : network_->laneLinks())
337 if ((link.inEdge == edgeId || link.outEdge == edgeId) &&
338 std::none_of(candidate.laneLinks().begin(), candidate.laneLinks().end(),
339 [&](const auto& remaining) { return sameLink(link, remaining); }))
340 removedLinks.push_back(link);
341 for (const auto& link : network_->blockedLaneLinks())
342 if ((link.inEdge == edgeId || link.outEdge == edgeId) &&
343 std::none_of(candidate.blockedLaneLinks().begin(), candidate.blockedLaneLinks().end(),
344 [&](const auto& remaining) { return sameLink(link, remaining); }))
345 removedBlockedLinks.push_back(link);
346
349 operation.inverseType = kReattachEdgeEndpoint;
350 operation.target = targetId();
351 operation.payload = encodeDetachEndpoint(edgeId, fromEndpoint, detached.value());
352 operation.inverse = encodeReattachEndpoint(edgeId, fromEndpoint, oldNodeId, detached.value(), removedLinks,
353 removedBlockedLinks);
354 operation.hasInverse = true;
355 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
356 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(detached.value()));
357 operation.affectedProperties.emplace_back(fromEndpoint ? "from" : "to");
358 operation.affectedProperties.emplace_back("laneLinks");
359 operation.affectedProperties.emplace_back("blockedLaneLinks");
360 return editing::applied(std::move(operation));
361}
362
364 std::size_t controlPointIndex,
365 float junctionRadius) const {
366 auto original = network_->edgeResult(edgeId);
367 if (!original.ok()) return roadFailure<editing::DomainOperation>(original.status());
368 std::vector<procgen::road::RoadLaneConnection> originalLinks;
369 for (const auto& link : network_->laneLinks())
370 if (link.inEdge == edgeId || link.outEdge == edgeId) originalLinks.push_back(link);
371 std::vector<procgen::road::RoadLaneConnection> originalBlockedLinks;
372 for (const auto& link : network_->blockedLaneLinks())
373 if (link.inEdge == edgeId || link.outEdge == edgeId) originalBlockedLinks.push_back(link);
374 auto candidate = *network_;
375 auto split = candidate.splitEdge(edgeId, controlPointIndex, junctionRadius);
376 if (!split.ok()) return roadFailure<editing::DomainOperation>(split.status());
377
380 operation.inverseType = kUnsplitEdge;
381 operation.target = targetId();
382 operation.payload = encodeSplit(edgeId, controlPointIndex, junctionRadius, split.value());
383 operation.inverse = encodeUnsplit(original.value(), originalLinks, originalBlockedLinks, split.value().nodeId,
384 split.value().secondEdgeId);
385 operation.hasInverse = true;
386 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
387 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(split.value().secondEdgeId));
388 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(split.value().nodeId));
389 operation.affectedProperties.emplace_back("topology");
390 return editing::applied(std::move(operation));
391}
392
394 std::uint32_t edgeId, procgen::road::RoadControlPoint position, float maxDistance, float junctionRadius) const {
395 auto original = network_->edgeResult(edgeId);
396 if (!original.ok()) return roadFailure<editing::DomainOperation>(original.status());
397 std::vector<procgen::road::RoadLaneConnection> originalLinks;
398 for (const auto& link : network_->laneLinks())
399 if (link.inEdge == edgeId || link.outEdge == edgeId) originalLinks.push_back(link);
400 std::vector<procgen::road::RoadLaneConnection> originalBlockedLinks;
401 for (const auto& link : network_->blockedLaneLinks())
402 if (link.inEdge == edgeId || link.outEdge == edgeId) originalBlockedLinks.push_back(link);
403 auto candidate = *network_;
404 auto split = candidate.splitEdgeAtPosition(edgeId, position, maxDistance, junctionRadius);
405 if (!split.ok()) return roadFailure<editing::DomainOperation>(split.status());
406
409 operation.inverseType = kUnsplitEdge;
410 operation.target = targetId();
411 operation.payload = encodePositionSplit(edgeId, position, maxDistance, junctionRadius, split.value());
412 operation.inverse = encodeUnsplit(original.value(), originalLinks, originalBlockedLinks, split.value().nodeId,
413 split.value().secondEdgeId);
414 operation.hasInverse = true;
415 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
416 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(split.value().secondEdgeId));
417 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(split.value().nodeId));
418 operation.affectedProperties.emplace_back("topology");
419 return editing::applied(std::move(operation));
420}
421
423 std::uint32_t sourceNodeId, std::uint32_t edgeId, procgen::road::RoadControlPoint position,
424 float maxDistance, float junctionRadius, int lanesForward, int lanesBackward,
425 procgen::road::RoadStyle style) const {
426 auto source = network_->nodeResult(sourceNodeId);
427 if (!source.ok()) return roadFailure<editing::DomainOperation>(source.status());
428 auto original = network_->edgeResult(edgeId);
429 if (!original.ok()) return roadFailure<editing::DomainOperation>(original.status());
430 if (original.value().from == sourceNodeId || original.value().to == sourceNodeId)
431 return reject<editing::DomainOperation>("editor.road.connect-existing-endpoint",
432 "Branch source is already an endpoint of the selected road");
433 std::vector<procgen::road::RoadLaneConnection> originalLinks;
434 for (const auto& link : network_->laneLinks())
435 if (link.inEdge == edgeId || link.outEdge == edgeId) originalLinks.push_back(link);
436 std::vector<procgen::road::RoadLaneConnection> originalBlockedLinks;
437 for (const auto& link : network_->blockedLaneLinks())
438 if (link.inEdge == edgeId || link.outEdge == edgeId) originalBlockedLinks.push_back(link);
439
440 auto candidate = *network_;
441 auto split = candidate.splitEdgeAtPosition(edgeId, position, maxDistance, junctionRadius);
442 if (!split.ok()) return roadFailure<editing::DomainOperation>(split.status());
443 auto inserted = candidate.nodeResult(split.value().nodeId);
444 if (!inserted.ok()) return roadFailure<editing::DomainOperation>(inserted.status());
445 std::vector<procgen::road::RoadControlPoint> branchPoints{
446 {source.value().x, source.value().y, source.value().z},
447 {inserted.value().x, inserted.value().y, inserted.value().z}};
448 auto branchId = candidate.addEdge(sourceNodeId, split.value().nodeId, std::move(branchPoints), lanesForward,
449 lanesBackward, style);
450 if (!branchId.ok()) return roadFailure<editing::DomainOperation>(branchId.status());
451 auto connected = candidate.connectAllTurns(split.value().nodeId);
452 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
453 auto branch = candidate.edgeResult(branchId.value());
454 if (!branch.ok()) return roadFailure<editing::DomainOperation>(branch.status());
455
459 operation.target = targetId();
460 operation.payload = encodeConnectAtPosition(edgeId, position, maxDistance, junctionRadius, split.value(),
461 branch.value());
462 operation.inverse = encodeDisconnectAtPosition(original.value(), originalLinks, originalBlockedLinks,
463 split.value().nodeId, split.value().secondEdgeId,
464 branch.value().id);
465 operation.hasInverse = true;
466 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(sourceNodeId));
467 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(split.value().nodeId));
468 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
469 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(split.value().secondEdgeId));
470 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(branch.value().id));
471 operation.affectedProperties.emplace_back("topology");
472 return editing::applied(std::move(operation));
473}
474
476 std::uint32_t firstEdgeId, std::uint32_t secondEdgeId, float maximumHeightDelta,
477 float junctionRadius) const {
478 if (firstEdgeId == secondEdgeId)
479 return reject<editing::DomainOperation>("editor.road.intersection-same-edge",
480 "Road intersection requires two distinct edges");
481 if (!std::isfinite(maximumHeightDelta) || maximumHeightDelta < 0.f)
482 return reject<editing::DomainOperation>("editor.road.intersection-height",
483 "Road intersection height tolerance is invalid");
484 auto first = network_->edgeResult(firstEdgeId);
485 auto second = network_->edgeResult(secondEdgeId);
486 if (!first.ok()) return roadFailure<editing::DomainOperation>(first.status());
487 if (!second.ok()) return roadFailure<editing::DomainOperation>(second.status());
488 auto intersection = findUniqueInteriorIntersection(first.value(), second.value(), maximumHeightDelta);
489 if (!intersection.ok()) return roadFailure<editing::DomainOperation>(intersection.status());
490 if (intersection.value().count == 0)
491 return reject<editing::DomainOperation>("editor.road.intersection-missing",
492 "Road edges have no eligible same-level interior intersection");
493 if (intersection.value().count > 1)
494 return reject<editing::DomainOperation>("editor.road.intersection-ambiguous",
495 "Road edges intersect more than once; select a specific split instead");
496
497 std::vector<procgen::road::RoadLaneConnection> originalLinks;
498 for (const auto& link : network_->laneLinks())
499 if (link.inEdge == firstEdgeId || link.outEdge == firstEdgeId || link.inEdge == secondEdgeId ||
500 link.outEdge == secondEdgeId)
501 originalLinks.push_back(link);
502 std::vector<procgen::road::RoadLaneConnection> originalBlockedLinks;
503 for (const auto& link : network_->blockedLaneLinks())
504 if (link.inEdge == firstEdgeId || link.outEdge == firstEdgeId || link.inEdge == secondEdgeId ||
505 link.outEdge == secondEdgeId)
506 originalBlockedLinks.push_back(link);
507 auto candidate = *network_;
508 auto firstSplit = candidate.splitEdgeAtSplineParameter(firstEdgeId, intersection.value().firstParameter,
509 junctionRadius);
510 if (!firstSplit.ok()) return roadFailure<editing::DomainOperation>(firstSplit.status());
511 auto secondSplit = candidate.splitEdgeAtSplineParameter(secondEdgeId, intersection.value().secondParameter,
512 junctionRadius);
513 if (!secondSplit.ok()) return roadFailure<editing::DomainOperation>(secondSplit.status());
514 const auto mergedPoint = intersection.value().mergedPoint;
515 auto moved = candidate.setNodePosition(firstSplit.value().nodeId, mergedPoint.x, mergedPoint.y, mergedPoint.z);
516 if (!moved.ok()) return roadFailure<editing::DomainOperation>(moved.status());
517 moved = candidate.setNodePosition(secondSplit.value().nodeId, mergedPoint.x, mergedPoint.y, mergedPoint.z);
518 if (!moved.ok()) return roadFailure<editing::DomainOperation>(moved.status());
519 auto merged = candidate.mergeNodes(firstSplit.value().nodeId, secondSplit.value().nodeId, 1e-3f);
520 if (!merged.ok()) return roadFailure<editing::DomainOperation>(merged.status());
521 auto connected = candidate.connectAllTurns(firstSplit.value().nodeId);
522 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
523
527 operation.target = targetId();
528 operation.payload = encodeIntersectionConnect(firstEdgeId, secondEdgeId, intersection.value().firstParameter,
529 intersection.value().secondParameter, mergedPoint,
530 maximumHeightDelta, junctionRadius, firstSplit.value(),
531 secondSplit.value());
532 operation.inverse = encodeIntersectionDisconnect(first.value(), second.value(), originalLinks,
533 originalBlockedLinks,
534 firstSplit.value().nodeId, firstSplit.value().secondEdgeId,
535 secondSplit.value().secondEdgeId);
536 operation.hasInverse = true;
537 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(firstEdgeId));
538 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(secondEdgeId));
539 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(firstSplit.value().nodeId));
540 operation.affectedProperties.emplace_back("topology");
541 return editing::applied(std::move(operation));
542}
543
545 float junctionRadius) const {
546 auto candidate = *network_;
547 auto added = candidate.addNode(x, y, z, junctionRadius);
548 if (!added.ok()) return roadFailure<editing::DomainOperation>(added.status());
549 auto node = candidate.nodeResult(added.value());
550 if (!node.ok()) return roadFailure<editing::DomainOperation>(node.status());
553 operation.inverseType = kRemoveNode;
554 operation.target = targetId();
555 operation.payload = encodeNodeRecord(node.value());
556 operation.inverse = EditorValue::Object{{"id", std::int64_t{node.value().id}}};
557 operation.hasInverse = true;
558 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(node.value().id));
559 return editing::applied(std::move(operation));
560}
561
563 auto node = network_->nodeResult(nodeId);
564 if (!node.ok()) return roadFailure<editing::DomainOperation>(node.status());
565 auto candidate = *network_;
566 auto removed = candidate.removeNode(nodeId);
567 if (!removed.ok()) return roadFailure<editing::DomainOperation>(removed.status());
570 operation.inverseType = kRestoreNode;
571 operation.target = targetId();
572 operation.payload = EditorValue::Object{{"id", std::int64_t{nodeId}}};
573 operation.inverse = encodeNodeRecord(node.value());
574 operation.hasInverse = true;
575 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(nodeId));
576 return editing::applied(std::move(operation));
577}
578
580 std::uint32_t from, std::uint32_t to, std::vector<procgen::road::RoadControlPoint> points, int lanesForward,
581 int lanesBackward, procgen::road::RoadStyle style) const {
582 auto candidate = *network_;
583 auto added = candidate.addEdge(from, to, std::move(points), lanesForward, lanesBackward, style);
584 if (!added.ok()) return roadFailure<editing::DomainOperation>(added.status());
585 auto edge = candidate.edgeResult(added.value());
586 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
589 operation.inverseType = kRemoveEdge;
590 operation.target = targetId();
591 operation.payload = encodeEdgeRecord(edge.value(), {});
592 operation.inverse = EditorValue::Object{{"id", std::int64_t{edge.value().id}}};
593 operation.hasInverse = true;
594 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edge.value().id));
595 return editing::applied(std::move(operation));
596}
597
599 std::uint32_t from, std::uint32_t to, std::vector<procgen::road::RoadControlPoint> points, int lanesForward,
600 int lanesBackward, procgen::road::RoadStyle style) const {
601 auto planned = makeAddEdge(from, to, std::move(points), lanesForward, lanesBackward, style);
602 if (!planned.ok()) return planned;
603
604 auto operation = std::move(planned).takeValue();
606 std::vector<procgen::road::RoadLaneConnection> ignored;
607 if (!decodeEdgeRecord(operation.payload, edge, ignored))
608 return reject<editing::DomainOperation>("editor.road.edge-connect-plan",
609 "Generated road edge payload is invalid", EditorStatus::Failed);
610
611 auto candidate = *network_;
612 auto restored = candidate.restoreEdge(edge);
613 if (!restored.ok()) return roadFailure<editing::DomainOperation>(restored.status());
614 for (const auto nodeId : {from, to}) {
615 auto connected = candidate.connectAllTurns(nodeId);
616 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
617 }
618
619 std::vector<procgen::road::RoadLaneConnection> edgeLinks;
620 for (const auto& link : candidate.laneLinks())
621 if (link.inEdge == edge.id || link.outEdge == edge.id) edgeLinks.push_back(link);
622 operation.payload = encodeEdgeRecord(edge, edgeLinks);
623 operation.affectedProperties.emplace_back("laneLinks");
624 return editing::applied(std::move(operation));
625}
626
628 auto edge = network_->edgeResult(edgeId);
629 if (!edge.ok()) return roadFailure<editing::DomainOperation>(edge.status());
630 std::vector<procgen::road::RoadLaneConnection> links;
631 for (const auto& link : network_->laneLinks())
632 if (link.inEdge == edgeId || link.outEdge == edgeId) links.push_back(link);
633 std::vector<procgen::road::RoadLaneConnection> blockedLinks;
634 for (const auto& link : network_->blockedLaneLinks())
635 if (link.inEdge == edgeId || link.outEdge == edgeId) blockedLinks.push_back(link);
638 operation.inverseType = kRestoreEdge;
639 operation.target = targetId();
640 operation.payload = EditorValue::Object{{"id", std::int64_t{edgeId}}};
641 operation.inverse = encodeEdgeRecord(edge.value(), links, blockedLinks);
642 operation.hasInverse = true;
643 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(edgeId));
644 for (const auto& link : links) {
645 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.inEdge));
646 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.outEdge));
647 }
648 return editing::applied(std::move(operation));
649}
650
653 auto candidate = *network_;
654 auto added = candidate.addLaneLink(link);
655 if (!added.ok()) return roadFailure<editing::DomainOperation>(added.status());
658 operation.inverseType = kRemoveLaneLink;
659 operation.target = targetId();
660 operation.payload = encodeLink(link);
661 operation.inverse = encodeLink(link);
662 operation.hasInverse = true;
663 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.inEdge));
664 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.outEdge));
665 operation.affectedProperties.emplace_back("laneLinks");
666 return editing::applied(std::move(operation));
667}
668
670 const procgen::road::RoadLaneConnection& link) const {
671 auto candidate = *network_;
672 auto removed = candidate.removeLaneLink(link);
673 if (!removed.ok()) return roadFailure<editing::DomainOperation>(removed.status());
676 operation.inverseType = kRestoreLaneLink;
677 operation.target = targetId();
678 operation.payload = encodeLink(link);
679 operation.inverse = encodeLink(link);
680 operation.hasInverse = true;
681 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.inEdge));
682 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.outEdge));
683 operation.affectedProperties.emplace_back("laneLinks");
684 return editing::applied(std::move(operation));
685}
686
689 auto candidate = *network_;
690 auto blocked = candidate.blockLaneLink(link);
691 if (!blocked.ok()) return roadFailure<editing::DomainOperation>(blocked.status());
694 operation.inverseType = kUnblockLaneLink;
695 operation.target = targetId();
696 operation.payload = encodeBlockedLink(link, false);
697 operation.inverse = encodeBlockedLink(link, blocked.value());
698 operation.hasInverse = true;
699 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.inEdge));
700 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.outEdge));
701 operation.affectedProperties.emplace_back("blockedLaneLinks");
702 return editing::applied(std::move(operation));
703}
704
706 const procgen::road::RoadLaneConnection& link) const {
707 auto candidate = *network_;
708 auto unblocked = candidate.unblockLaneLink(link);
709 if (!unblocked.ok()) return roadFailure<editing::DomainOperation>(unblocked.status());
712 operation.inverseType = kBlockLaneLink;
713 operation.target = targetId();
714 operation.payload = encodeBlockedLink(link, false);
715 operation.inverse = encodeBlockedLink(link, false);
716 operation.hasInverse = true;
717 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.inEdge));
718 operation.affectedObjects.emplace_back(targetId(), "edge/" + std::to_string(link.outEdge));
719 operation.affectedProperties.emplace_back("blockedLaneLinks");
720 return editing::applied(std::move(operation));
721}
722
724 auto candidate = *network_;
725 auto connected = candidate.connectAllTurns(nodeId);
726 if (!connected.ok()) return roadFailure<editing::DomainOperation>(connected.status());
727 if (connected.value() == 0)
728 return reject<editing::DomainOperation>("editor.road.turns-noop", "Road node has no missing turn links",
729 EditorStatus::NoOp);
730
731 std::vector<procgen::road::RoadLaneConnection> added;
732 added.reserve(static_cast<std::size_t>(connected.value()));
733 for (const auto& link : candidate.laneLinks()) {
734 const bool existed = std::any_of(network_->laneLinks().begin(), network_->laneLinks().end(),
735 [&](const auto& current) { return sameLink(current, link); });
736 if (!existed) added.push_back(link);
737 }
740 operation.inverseType = kRemoveLaneLinks;
741 operation.target = targetId();
742 operation.payload = encodeLinks(added);
743 operation.inverse = encodeLinks(added);
744 operation.hasInverse = true;
745 operation.affectedObjects.emplace_back(targetId(), "node/" + std::to_string(nodeId));
746 operation.affectedProperties.emplace_back("laneLinks");
747 return editing::applied(std::move(operation));
748}
749
750} // namespace eve::procgen_editing
ActionParameterOperation operation
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string from
bool split
Definition CaveMesh.cpp:123
std::string nodeId
std::int32_t second
std::int32_t first
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::array< float, 3 > position
bool valid
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
int detail
const RoadNode * node
RoadLaneDirection direction
const RoadEdge * edge
int removed
double current
float dz
float dy
float dx
std::uint32_t count
std::string sourceNodeId
Definition UISystem.cpp:70
const UnitySourceAsset & source
std::vector< int > edges
Move-only operation result carrying either a value or Status.
Definition Result.h:155
std::map< std::string, Value > Object
Result< int > reconnectEdgeEndpoint(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId)
Reconnect one edge endpoint to an existing node and prune connections left at the old junction.
Result< void > removeNode(std::uint32_t nodeId)
Remove an isolated node.
Result< void > unblockLaneLink(const RoadLaneConnection &link)
Remove one exact persistent lane-link prohibition.
Result< void > setEdgeStyle(std::uint32_t edgeId, RoadStyle style)
Replace edge style atomically.
Result< void > addLaneLink(RoadLaneConnection link)
Register one legal turn inside a shared junction node.
Result< RoadEdgeSplitResult > splitEdgeAtSplineParameter(std::uint32_t edgeId, float parameter, float junctionRadius=6.f, std::uint32_t nodeId=0, std::uint32_t secondEdgeId=0)
Split at a normalized parameter on the same Catmull-Rom centerline used by road baking.
Result< std::uint32_t > detachEdgeEndpoint(std::uint32_t edgeId, bool fromEndpoint)
Detach one edge endpoint onto a new coincident stable node.
Result< void > removeLaneLink(const RoadLaneConnection &link)
Remove one exact lane-to-lane connection.
Result< RoadEdge > edgeResult(std::uint32_t id) const
Edge result.
Result< void > reverseEdge(std::uint32_t edgeId)
Atomically reverse one edge while preserving its physical lanes, links, and side-object intervals.
Result< RoadEdgeSplitResult > splitEdgeAtPosition(std::uint32_t edgeId, RoadControlPoint position, float maxDistance, float junctionRadius=6.f)
Project a world position onto the baked Catmull-Rom centerline and split at the nearest point.
Result< void > setNodeJunctionRadius(std::uint32_t nodeId, float junctionRadius)
Replace one node's positive junction trim radius.
Result< void > setNodeJunctionControl(std::uint32_t nodeId, RoadJunctionControl control)
Replace one node's authoritative traffic-control policy.
Result< std::uint32_t > addEdge(std::uint32_t from, std::uint32_t to, std::vector< RoadControlPoint > controlPoints, int lanesForward=2, int lanesBackward=0, RoadStyle style={})
Insert a non-degenerate directed edge between two distinct live nodes.
Result< bool > blockLaneLink(RoadLaneConnection link)
Persistently forbid one exact legal lane connection and remove it if currently active.
Result< RoadEdgeSplitResult > splitEdge(std::uint32_t edgeId, std::size_t controlPointIndex, float junctionRadius=6.f)
Split an edge at one authored interior control point as a single topology mutation.
Result< int > connectAllTurns(std::uint32_t nodeId)
Auto-connect all forward and backward lane ports at one node.
const std::vector< RoadLaneConnection > & laneLinks() const noexcept
Lane links.
Result< std::uint32_t > addNode(float x, float y, float z, float junctionRadius=6.f)
Insert a junction node after validating finite coordinates.
Result< void > setEdgeControlPoints(std::uint32_t edgeId, std::vector< RoadControlPoint > controlPoints)
Replace one edge spline while preserving its node-owned endpoint positions.
Result< RoadNode > nodeResult(std::uint32_t id) const
Node result.
const std::vector< RoadLaneConnection > & blockedLaneLinks() const noexcept
Borrow deterministic persistent turn prohibitions until the next network mutation.
Result< void > restoreEdge(RoadEdge edge)
Restore an edge with an explicit stable id for undo/import paths.
const std::vector< RoadNode > & nodes() const noexcept
Nodes.
Result< int > setEdgeLaneCounts(std::uint32_t edgeId, int lanesForward, int lanesBackward)
Atomically replace directional lane counts and remove links that become out of range.
Result< int > mergeNodes(std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance)
Atomically merge one nearby endpoint node into another stable node.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeRemoveLaneLink(const procgen::road::RoadLaneConnection &link) const
Create an undoable removal of one exact manual lane connection.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeBlockLaneLink(procgen::road::RoadLaneConnection link) const
Plan a persistent prohibited turn, atomically removing an active exact link when present.
static constexpr const char * kConnectNodeAtPosition
static constexpr const char * kSplitEdgeAtPosition
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSetEdgeLaneCounts(std::uint32_t edgeId, int lanesForward, int lanesBackward) const
Create an undoable lane-count replacement, including links invalidated by a shrink.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeReconnectEdgeEndpoint(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId) const
Create an undoable operation reconnecting one road endpoint to an existing node.
static constexpr const char * kDetachEdgeEndpoint
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeAddNode(float x, float y, float z, float junctionRadius=6.f) const
Create an undoable node insertion with a reserved stable id.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSplitEdge(std::uint32_t edgeId, std::size_t controlPointIndex, float junctionRadius=6.f) const
Create an undoable split at an authored interior control point.
static constexpr const char * kDisconnectEdgeIntersection
static constexpr const char * kConnectEdgeIntersection
static constexpr const char * kReconnectEdgeEndpoint
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeReverseEdge(std::uint32_t edgeId) const
Create a self-inverse operation that reverses an edge and all direction-relative metadata.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeConnectAllTurns(std::uint32_t nodeId) const
Plan all missing legal turn links at one node as one atomic undoable operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSetEdgeStyle(std::uint32_t edgeId, procgen::road::RoadStyle style) const
Create an undoable stable-id edge style replacement operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeReconnectEdgeEndpointAndConnect(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId) const
Atomically reconnect one endpoint and add missing turns involving that edge at the new junction.
static constexpr const char * kDisconnectNodeAtPosition
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSnapEdgeEndpoint(std::uint32_t edgeId, bool fromEndpoint, float maxDistance, bool connectTurns=true) const
Snap one endpoint to the nearest distinct node inside an explicit 3D radius.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSplitEdgeAtPosition(std::uint32_t edgeId, procgen::road::RoadControlPoint position, float maxDistance, float junctionRadius=6.f) const
Create an undoable nearest-centerline split from a world position.
static constexpr const char * kReattachEdgeEndpoint
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSetNodeJunctionControl(std::uint32_t nodeId, procgen::road::RoadJunctionControl control) const
Create an undoable junction traffic-control replacement operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeAddEdge(std::uint32_t from, std::uint32_t to, std::vector< procgen::road::RoadControlPoint > points, int lanesForward=2, int lanesBackward=0, procgen::road::RoadStyle style={}) const
Create an undoable edge insertion with a reserved stable id.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSetEdgeControlPoints(std::uint32_t edgeId, std::vector< procgen::road::RoadControlPoint > points) const
Create an undoable stable-id spline replacement operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeConnectNodeAtPosition(std::uint32_t sourceNodeId, std::uint32_t edgeId, procgen::road::RoadControlPoint position, float maxDistance, float junctionRadius=6.f, int lanesForward=1, int lanesBackward=0, procgen::road::RoadStyle style={}) const
Atomically split a road at a world position and add a branch from an existing node.
static constexpr const char * kMergeNodesAndConnect
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeRemoveEdge(std::uint32_t edgeId) const
Create an undoable edge removal including its affected lane links.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeRemoveNode(std::uint32_t nodeId) const
Create an undoable removal for an isolated stable-id node.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeAddEdgeAndConnect(std::uint32_t from, std::uint32_t to, std::vector< procgen::road::RoadControlPoint > points, int lanesForward=2, int lanesBackward=0, procgen::road::RoadStyle style={}) const
Create an edge and every missing endpoint turn involving that edge as one undoable operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeConnectEdgeIntersection(std::uint32_t firstEdgeId, std::uint32_t secondEdgeId, float maximumHeightDelta=0.5f, float junctionRadius=6.f) const
Atomically convert one unique same-level crossing into a connected junction.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeMergeNodesAndConnect(std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance) const
Atomically merge nearby endpoint nodes and generate every missing legal turn at the result.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeAddLaneLink(procgen::road::RoadLaneConnection link) const
Create an undoable manual lane connection.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeUnblockLaneLink(const procgen::road::RoadLaneConnection &link) const
Plan removal of one persistent prohibited turn without automatically activating it.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeDetachEdgeEndpoint(std::uint32_t edgeId, bool fromEndpoint) const
Create an undoable operation detaching one endpoint onto a new coincident stable node.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeSetNodeJunctionRadius(std::uint32_t nodeId, float junctionRadius) const
Create an undoable junction-radius replacement operation.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeMoveNode(std::uint32_t nodeId, float x, float y, float z) const
Create an undoable stable-id node move operation.
editing::TargetId targetId() const override
Target id.
EVENGINE_API_ORCHESTRATION EditorResult< editing::DomainOperation > makeMergeNodes(std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance) const
Create an undoable endpoint-node merge within an explicit 3D snap distance.
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
RoadJunctionControl
Authoritative traffic-control policy applied to every approach of one junction node.
Definition RoadTypes.h:43
RoadLaneDirection
Travel direction of a lane relative to the authored edge centerline.
Definition RoadTypes.h:79
@ Forward
Travels from RoadEdge::from to RoadEdge::to.
EditorValue encodeReconnectEndpoint(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId, const std::vector< procgen::road::RoadLaneConnection > &restoreLinks, const std::vector< procgen::road::RoadLaneConnection > &restoreBlockedLinks)
EditorValue encodeNode(std::uint32_t id, float x, float y, float z)
EditorValue encodeIntersectionConnect(std::uint32_t firstEdgeId, std::uint32_t secondEdgeId, float firstParameter, float secondParameter, procgen::road::RoadControlPoint mergedPoint, float maximumHeightDelta, float junctionRadius, const procgen::road::RoadEdgeSplitResult &firstSplit, const procgen::road::RoadEdgeSplitResult &secondSplit)
EditorValue encodeSplit(std::uint32_t edgeId, std::size_t controlPointIndex, float junctionRadius, const procgen::road::RoadEdgeSplitResult &result)
EditorValue encodeDisconnectAtPosition(const procgen::road::RoadEdge &original, const std::vector< procgen::road::RoadLaneConnection > &originalLinks, const std::vector< procgen::road::RoadLaneConnection > &originalBlockedLinks, std::uint32_t nodeId, std::uint32_t secondEdgeId, std::uint32_t branchEdgeId)
EditorValue encodeUnmergeNodes(const procgen::road::RoadNode &removedNode, const std::vector< procgen::road::RoadEdge > &affectedEdges, const std::vector< procgen::road::RoadLaneConnection > &affectedLinks, const std::vector< procgen::road::RoadLaneConnection > &affectedBlockedLinks)
EditorValue encodeConnectAtPosition(std::uint32_t edgeId, procgen::road::RoadControlPoint position, float maxDistance, float junctionRadius, const procgen::road::RoadEdgeSplitResult &split, const procgen::road::RoadEdge &branch)
EditorValue encodeLink(const procgen::road::RoadLaneConnection &link)
EditorValue encodeIntersectionDisconnect(const procgen::road::RoadEdge &first, const procgen::road::RoadEdge &second, const std::vector< procgen::road::RoadLaneConnection > &links, const std::vector< procgen::road::RoadLaneConnection > &blockedLinks, std::uint32_t sharedNodeId, std::uint32_t firstSecondEdgeId, std::uint32_t secondSecondEdgeId)
EditorValue encodeMergeNodes(std::uint32_t keepNodeId, std::uint32_t removeNodeId, float maxDistance)
EditorValue encodeEdge(std::uint32_t id, const std::vector< procgen::road::RoadControlPoint > &points)
EditorValue encodeNodeRadius(std::uint32_t id, float junctionRadius)
EditorValue encodeEdgeRecord(const procgen::road::RoadEdge &edge, const std::vector< procgen::road::RoadLaneConnection > &links, const std::vector< procgen::road::RoadLaneConnection > &blockedLinks)
EditorValue encodeNodeControl(std::uint32_t id, procgen::road::RoadJunctionControl control)
EditorValue encodeUnsplit(const procgen::road::RoadEdge &edge, const std::vector< procgen::road::RoadLaneConnection > &links, const std::vector< procgen::road::RoadLaneConnection > &blockedLinks, std::uint32_t nodeId, std::uint32_t secondEdgeId)
EditorValue encodeDetachEndpoint(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t nodeId)
EditorValue encodeLinks(const std::vector< procgen::road::RoadLaneConnection > &links)
bool decodeEdgeRecord(const EditorValue &value, procgen::road::RoadEdge &edge, std::vector< procgen::road::RoadLaneConnection > &links, std::vector< procgen::road::RoadLaneConnection > *blockedLinks)
EditorValue encodeReattachEndpoint(std::uint32_t edgeId, bool fromEndpoint, std::uint32_t oldNodeId, std::uint32_t detachedNodeId, const std::vector< procgen::road::RoadLaneConnection > &restoreLinks, const std::vector< procgen::road::RoadLaneConnection > &restoreBlockedLinks)
Result< RoadIntersection > findUniqueInteriorIntersection(const procgen::road::RoadEdge &first, const procgen::road::RoadEdge &second, float maximumHeightDelta)
EditorValue encodeLaneCounts(std::uint32_t edgeId, int lanesForward, int lanesBackward, const std::vector< procgen::road::RoadLaneConnection > &restoreLinks, const std::vector< procgen::road::RoadLaneConnection > &restoreBlockedLinks)
EditorValue encodeBlockedLink(const procgen::road::RoadLaneConnection &link, bool restoreActive)
EditorValue encodePositionSplit(std::uint32_t edgeId, procgen::road::RoadControlPoint position, float maxDistance, float junctionRadius, const procgen::road::RoadEdgeSplitResult &result)
EditorValue encodeStyle(std::uint32_t id, const procgen::road::RoadStyle &style)
EditorValue encodeNodeRecord(const procgen::road::RoadNode &node)
DomainOperation public API.
One authored control point on a road centerline (world space, Y-up).
Definition RoadTypes.h:12
Directed centerline edge with optional reverse lanes.
Definition RoadTypes.h:61
One legal lane-to-lane connection inside a junction.
Definition RoadTypes.h:90
Cross-section and structural style for one road edge.
Definition RoadTypes.h:19