载入中...
搜索中...
未找到
RoadNetworkEditingCommands.cpp
浏览该文件的文档.
2#include "procgen/editing/RoadNetworkEditTargetInternal.inc"
3
4#include <algorithm>
5#include <cmath>
6#include <utility>
7
8namespace eve::procgen_editing {
9using namespace detail;
10
12 auto add = [&registry](const char* id, const char* name, editing::EditingCommandPlanner planner) {
15 descriptor.ownerModule = "procgen_editing.road";
16 descriptor.displayName = name;
17 descriptor.category = "Road";
18 descriptor.automationAllowed = true;
19 return registry.registerPlannedCommand(std::move(descriptor), std::move(planner));
20 };
24 result.operations.push_back(std::move(operation).takeValue());
25 return editing::applied(std::move(result));
26 };
27 auto target = [](editing::IEditableTarget& value) { return dynamic_cast<RoadNetworkEditTarget*>(&value); };
28
29 auto registered = add("road.node.add.v1", "Add road node", [target, plan](editing::IEditableTarget& value,
31 auto* road = target(value);
32 float x = 0.f, y = 0.f, z = 0.f, radius = 6.f;
33 if (!road || !readNumber(request.payload, "x", x) || !readNumber(request.payload, "y", y) ||
34 !readNumber(request.payload, "z", z) || (field(request.payload, "junctionRadius") &&
35 !readNumber(request.payload, "junctionRadius", radius)))
36 return commandError<editing::CommandPlan>("editor.road.command.node-add",
37 "Road node add requires finite x, y and z");
38 return plan(road->makeAddNode(x, y, z, radius));
39 });
40 if (!registered.ok()) return registered;
41
42 registered = add("road.node.move.v1", "Move road node", [target, plan](editing::IEditableTarget& value,
44 auto* road = target(value);
45 std::uint32_t id = 0;
46 float x = 0.f, y = 0.f, z = 0.f;
47 if (!road || !readId(request.payload, "id", id) || !readNumber(request.payload, "x", x) ||
48 !readNumber(request.payload, "y", y) || !readNumber(request.payload, "z", z))
49 return commandError<editing::CommandPlan>("editor.road.command.node-move",
50 "Road node move requires id and finite x, y and z");
51 return plan(road->makeMoveNode(id, x, y, z));
52 });
53 if (!registered.ok()) return registered;
54
55 registered = add("road.node.radius.set.v1", "Set road junction radius",
57 auto* road = target(value);
58 std::uint32_t id = 0;
59 float radius = 0.f;
60 if (!road || !readId(request.payload, "id", id) ||
61 !readNumber(request.payload, "junctionRadius", radius))
62 return commandError<editing::CommandPlan>("editor.road.command.node-radius",
63 "Road node radius requires id and radius");
64 return plan(road->makeSetNodeJunctionRadius(id, radius));
65 });
66 if (!registered.ok()) return registered;
67
68 registered = add("road.node.junction-control.set.v1", "Set road junction control",
70 auto* road = target(value);
71 std::uint32_t id = 0;
72 int control = 0;
73 if (!road || !readId(request.payload, "id", id) ||
74 !readSmallInt(request.payload, "junctionControl", 0, 3, control))
75 return commandError<editing::CommandPlan>(
76 "editor.road.command.node-control",
77 "Road junction control requires id and junctionControl in [0,3]");
78 return plan(road->makeSetNodeJunctionControl(
79 id, static_cast<procgen::road::RoadJunctionControl>(control)));
80 });
81 if (!registered.ok()) return registered;
82
83 registered = add("road.node.merge.v1", "Merge nearby road nodes",
85 auto* road = target(value);
86 std::uint32_t keepNodeId = 0, removeNodeId = 0;
87 float maxDistance = 1.f;
88 if (!road || !readId(request.payload, "keepNodeId", keepNodeId) ||
89 !readId(request.payload, "removeNodeId", removeNodeId) ||
90 (field(request.payload, "maxDistance") &&
91 !readNumber(request.payload, "maxDistance", maxDistance)))
92 return commandError<editing::CommandPlan>("editor.road.command.node-merge",
93 "Road node merge requires two node ids");
94 return plan(road->makeMergeNodes(keepNodeId, removeNodeId, maxDistance));
95 });
96 if (!registered.ok()) return registered;
97
98 registered = add("road.node.merge-and-connect.v1", "Merge road nodes and connect turns",
100 auto* road = target(value);
101 std::uint32_t keepNodeId = 0, removeNodeId = 0;
102 float maxDistance = 1.f;
103 if (!road || !readId(request.payload, "keepNodeId", keepNodeId) ||
104 !readId(request.payload, "removeNodeId", removeNodeId) ||
105 (field(request.payload, "maxDistance") &&
106 !readNumber(request.payload, "maxDistance", maxDistance)))
107 return commandError<editing::CommandPlan>(
108 "editor.road.command.node-merge-connect",
109 "Road node merge and connect requires two node ids");
110 return plan(road->makeMergeNodesAndConnect(keepNodeId, removeNodeId, maxDistance));
111 });
112 if (!registered.ok()) return registered;
113
114 registered = add("road.node.remove.v1", "Remove road node", [target, plan](editing::IEditableTarget& value,
116 auto* road = target(value);
117 std::uint32_t id = 0;
118 if (!road || !readId(request.payload, "id", id))
119 return commandError<editing::CommandPlan>("editor.road.command.node-remove",
120 "Road node remove requires id");
121 return plan(road->makeRemoveNode(id));
122 });
123 if (!registered.ok()) return registered;
124
125 registered = add("road.node.connect-all-turns.v1", "Connect all road turns",
127 auto* road = target(value);
128 std::uint32_t id = 0;
129 if (!road || !readId(request.payload, "id", id))
130 return commandError<editing::CommandPlan>("editor.road.command.connect-turns",
131 "Connect all turns requires a node id");
132 return plan(road->makeConnectAllTurns(id));
133 });
134 if (!registered.ok()) return registered;
135
136 registered = add("road.edge.add.v1", "Add road edge", [target, plan](editing::IEditableTarget& value,
138 auto* road = target(value);
139 std::uint32_t from = 0, to = 0, ignored = 0;
140 int forward = 2, backward = 0;
141 std::vector<procgen::road::RoadControlPoint> points;
142 EditorValue::Object pointPayload{{"id", std::int64_t{1}}};
143 if (const auto* encoded = field(request.payload, "points")) pointPayload["points"] = *encoded;
144 if (!road || !readId(request.payload, "from", from) || !readId(request.payload, "to", to) ||
145 !decodeEdge(EditorValue(std::move(pointPayload)), ignored, points) ||
146 (field(request.payload, "lanesForward") &&
147 !readSmallInt(request.payload, "lanesForward", 0, 8, forward)) ||
148 (field(request.payload, "lanesBackward") &&
149 !readSmallInt(request.payload, "lanesBackward", 0, 8, backward)))
150 return commandError<editing::CommandPlan>("editor.road.command.edge-add",
151 "Road edge add requires endpoints and at least two points");
152 return plan(road->makeAddEdge(from, to, std::move(points), forward, backward));
153 });
154 if (!registered.ok()) return registered;
155
156 registered = add("road.edge.add-and-connect.v1", "Add road edge and connect endpoints",
158 auto* road = target(value);
159 std::uint32_t from = 0, to = 0, ignored = 0;
160 int forward = 2, backward = 0;
161 std::vector<procgen::road::RoadControlPoint> points;
162 EditorValue::Object pointPayload{{"id", std::int64_t{1}}};
163 if (const auto* encoded = field(request.payload, "points")) pointPayload["points"] = *encoded;
164 if (!road || !readId(request.payload, "from", from) || !readId(request.payload, "to", to) ||
165 !decodeEdge(EditorValue(std::move(pointPayload)), ignored, points) ||
166 (field(request.payload, "lanesForward") &&
167 !readSmallInt(request.payload, "lanesForward", 0, 8, forward)) ||
168 (field(request.payload, "lanesBackward") &&
169 !readSmallInt(request.payload, "lanesBackward", 0, 8, backward)))
170 return commandError<editing::CommandPlan>(
171 "editor.road.command.edge-add-connect",
172 "Road edge add and connect requires endpoints and at least two points");
174 if (const auto* encodedStyle = field(request.payload, "style")) {
175 const auto* styleObject = encodedStyle->getIf<EditorValue::Object>();
176 if (!styleObject)
177 return commandError<editing::CommandPlan>(
178 "editor.road.command.edge-add-connect-style", "Road edge style is invalid");
179 EditorValue::Object stylePayload = *styleObject;
180 stylePayload["id"] = std::int64_t{1};
181 if (!decodeStyle(EditorValue(std::move(stylePayload)), ignored, style))
182 return commandError<editing::CommandPlan>(
183 "editor.road.command.edge-add-connect-style", "Road edge style is invalid");
184 }
185 return plan(road->makeAddEdgeAndConnect(from, to, std::move(points), forward, backward,
186 style));
187 });
188 if (!registered.ok()) return registered;
189
190 registered = add("road.edge.split.v1", "Split road edge",
192 auto* road = target(value);
193 std::uint32_t id = 0;
194 int index = 0;
195 float radius = 6.f;
196 if (!road || !readId(request.payload, "id", id) ||
197 !readSmallInt(request.payload, "controlPointIndex", 1, 4095, index) ||
198 (field(request.payload, "junctionRadius") &&
199 !readNumber(request.payload, "junctionRadius", radius)))
200 return commandError<editing::CommandPlan>("editor.road.command.edge-split",
201 "Road split requires edge id and interior control-point index");
202 return plan(road->makeSplitEdge(id, static_cast<std::size_t>(index), radius));
203 });
204 if (!registered.ok()) return registered;
205
206 registered = add("road.edge.split-at-position.v1", "Split road edge at world position",
208 auto* road = target(value);
209 std::uint32_t id = 0;
211 float maxDistance = 2.f, radius = 6.f;
212 if (!road || !readId(request.payload, "id", id) ||
213 !readNumber(request.payload, "x", position.x) ||
214 !readNumber(request.payload, "y", position.y) ||
215 !readNumber(request.payload, "z", position.z) ||
216 (field(request.payload, "maxDistance") &&
217 !readNumber(request.payload, "maxDistance", maxDistance)) ||
218 (field(request.payload, "junctionRadius") &&
219 !readNumber(request.payload, "junctionRadius", radius)))
220 return commandError<editing::CommandPlan>(
221 "editor.road.command.edge-split-position",
222 "Road position split requires edge id and a finite world position");
223 return plan(road->makeSplitEdgeAtPosition(id, position, maxDistance, radius));
224 });
225 if (!registered.ok()) return registered;
226
227 registered = add("road.node.connect-at-position.v1", "Connect road node at world position",
229 auto* road = target(value);
230 std::uint32_t sourceNodeId = 0, edgeId = 0;
232 float maxDistance = 2.f, radius = 6.f;
233 int forward = 1, backward = 0;
234 if (!road || !readId(request.payload, "sourceNodeId", sourceNodeId) ||
235 !readId(request.payload, "edgeId", edgeId) ||
236 !readNumber(request.payload, "x", position.x) ||
237 !readNumber(request.payload, "y", position.y) ||
238 !readNumber(request.payload, "z", position.z) ||
239 (field(request.payload, "maxDistance") &&
240 !readNumber(request.payload, "maxDistance", maxDistance)) ||
241 (field(request.payload, "junctionRadius") &&
242 !readNumber(request.payload, "junctionRadius", radius)) ||
243 (field(request.payload, "lanesForward") &&
244 !readSmallInt(request.payload, "lanesForward", 0, 8, forward)) ||
245 (field(request.payload, "lanesBackward") &&
246 !readSmallInt(request.payload, "lanesBackward", 0, 8, backward)))
247 return commandError<editing::CommandPlan>(
248 "editor.road.command.node-connect-position",
249 "Road branch connection requires source node, edge and world position");
250 return plan(road->makeConnectNodeAtPosition(sourceNodeId, edgeId, position, maxDistance,
251 radius, forward, backward));
252 });
253 if (!registered.ok()) return registered;
254
255 registered = add("road.edge.connect-intersection.v1", "Connect crossing road edges",
257 auto* road = target(value);
258 std::uint32_t firstEdgeId = 0, secondEdgeId = 0;
259 float maximumHeightDelta = 0.5f, radius = 6.f;
260 if (!road || !readId(request.payload, "firstEdgeId", firstEdgeId) ||
261 !readId(request.payload, "secondEdgeId", secondEdgeId) ||
262 (field(request.payload, "maximumHeightDelta") &&
263 !readNumber(request.payload, "maximumHeightDelta", maximumHeightDelta)) ||
264 (field(request.payload, "junctionRadius") &&
265 !readNumber(request.payload, "junctionRadius", radius)))
266 return commandError<editing::CommandPlan>(
267 "editor.road.command.edge-intersection",
268 "Road intersection requires two edge ids and finite tolerances");
269 return plan(road->makeConnectEdgeIntersection(firstEdgeId, secondEdgeId,
270 maximumHeightDelta, radius));
271 });
272 if (!registered.ok()) return registered;
273
274 registered = add("road.edge.lanes.set.v1", "Set road edge lanes",
276 auto* road = target(value);
277 std::uint32_t id = 0;
278 int forward = 0, backward = 0;
279 if (!road || !readId(request.payload, "id", id) ||
280 !readSmallInt(request.payload, "lanesForward", 0, 8, forward) ||
281 !readSmallInt(request.payload, "lanesBackward", 0, 8, backward))
282 return commandError<editing::CommandPlan>("editor.road.command.edge-lanes",
283 "Road lane edit requires id and lane counts");
284 return plan(road->makeSetEdgeLaneCounts(id, forward, backward));
285 });
286 if (!registered.ok()) return registered;
287
288 registered = add("road.edge.style.set.v1", "Set road edge style",
290 auto* road = target(value);
291 std::uint32_t edgeId = 0;
293 if (!road || !decodeStyle(request.payload, edgeId, style))
294 return commandError<editing::CommandPlan>("editor.road.command.edge-style",
295 "Road edge style requires a complete style payload");
296 return plan(road->makeSetEdgeStyle(edgeId, style));
297 });
298 if (!registered.ok()) return registered;
299
300 registered = add("road.edge.control-points.set.v1", "Set road edge control points",
302 auto* road = target(value);
303 std::uint32_t edgeId = 0;
304 std::vector<procgen::road::RoadControlPoint> points;
305 if (!road || !decodeEdge(request.payload, edgeId, points))
306 return commandError<editing::CommandPlan>("editor.road.command.edge-control-points",
307 "Road edge control points require id and at least two finite points");
308 return plan(road->makeSetEdgeControlPoints(edgeId, std::move(points)));
309 });
310 if (!registered.ok()) return registered;
311
312 registered = add("road.edge.reverse.v1", "Reverse road edge",
314 auto* road = target(value);
315 std::uint32_t edgeId = 0;
316 if (!road || !readId(request.payload, "id", edgeId))
317 return commandError<editing::CommandPlan>("editor.road.command.edge-reverse",
318 "Road edge reverse requires an id");
319 return plan(road->makeReverseEdge(edgeId));
320 });
321 if (!registered.ok()) return registered;
322
323 registered = add("road.edge.reconnect-endpoint.v1", "Reconnect road edge endpoint",
325 auto* road = target(value);
326 std::uint32_t edgeId = 0, nodeId = 0;
327 const auto* endpointValue = field(request.payload, "fromEndpoint");
328 const auto* fromEndpoint = endpointValue ? endpointValue->getIf<bool>() : nullptr;
329 if (!road || !fromEndpoint || !readId(request.payload, "id", edgeId) ||
330 !readId(request.payload, "nodeId", nodeId))
331 return commandError<editing::CommandPlan>(
332 "editor.road.command.edge-reconnect",
333 "Road edge reconnect requires id, fromEndpoint, and nodeId");
334 return plan(road->makeReconnectEdgeEndpoint(edgeId, *fromEndpoint, nodeId));
335 });
336 if (!registered.ok()) return registered;
337
338 registered = add("road.edge.reconnect-endpoint-and-connect.v1", "Reconnect and connect road edge endpoint",
340 auto* road = target(value);
341 std::uint32_t edgeId = 0, nodeId = 0;
342 const auto* endpointValue = field(request.payload, "fromEndpoint");
343 const auto* fromEndpoint = endpointValue ? endpointValue->getIf<bool>() : nullptr;
344 if (!road || !fromEndpoint || !readId(request.payload, "id", edgeId) ||
345 !readId(request.payload, "nodeId", nodeId))
346 return commandError<editing::CommandPlan>(
347 "editor.road.command.edge-reconnect-connect",
348 "Road edge reconnect-and-connect requires id, fromEndpoint, and nodeId");
349 return plan(road->makeReconnectEdgeEndpointAndConnect(edgeId, *fromEndpoint, nodeId));
350 });
351 if (!registered.ok()) return registered;
352
353 registered = add("road.edge.snap-endpoint.v1", "Snap road edge endpoint to nearest node",
355 auto* road = target(value);
356 std::uint32_t edgeId = 0;
357 float maxDistance = 0.f;
358 bool connectTurns = true;
359 const auto* endpointValue = field(request.payload, "fromEndpoint");
360 const auto* fromEndpoint = endpointValue ? endpointValue->getIf<bool>() : nullptr;
361 const auto* connectValue = field(request.payload, "connectTurns");
362 const auto* connect = connectValue ? connectValue->getIf<bool>() : nullptr;
363 if (!road || !fromEndpoint || (connectValue && !connect) ||
364 !readId(request.payload, "id", edgeId) ||
365 !readNumber(request.payload, "maxDistance", maxDistance))
366 return commandError<editing::CommandPlan>(
367 "editor.road.command.edge-snap",
368 "Road edge snap requires id, fromEndpoint, and a finite maxDistance");
369 if (connect) connectTurns = *connect;
370 return plan(road->makeSnapEdgeEndpoint(edgeId, *fromEndpoint, maxDistance, connectTurns));
371 });
372 if (!registered.ok()) return registered;
373
374 registered = add("road.edge.detach-endpoint.v1", "Detach road edge endpoint",
376 auto* road = target(value);
377 std::uint32_t edgeId = 0;
378 const auto* endpointValue = field(request.payload, "fromEndpoint");
379 const auto* fromEndpoint = endpointValue ? endpointValue->getIf<bool>() : nullptr;
380 if (!road || !fromEndpoint || !readId(request.payload, "id", edgeId))
381 return commandError<editing::CommandPlan>(
382 "editor.road.command.edge-detach",
383 "Road edge detach requires id and fromEndpoint");
384 return plan(road->makeDetachEdgeEndpoint(edgeId, *fromEndpoint));
385 });
386 if (!registered.ok()) return registered;
387
388 registered = add("road.edge.remove.v1", "Remove road edge", [target, plan](editing::IEditableTarget& value,
390 auto* road = target(value);
391 std::uint32_t id = 0;
392 if (!road || !readId(request.payload, "id", id))
393 return commandError<editing::CommandPlan>("editor.road.command.edge-remove",
394 "Road edge remove requires id");
395 return plan(road->makeRemoveEdge(id));
396 });
397 if (!registered.ok()) return registered;
398
399 auto laneLinkCommand = [target, plan](bool addLink, editing::IEditableTarget& value,
401 auto* road = target(value);
403 if (!road || !decodeLink(request.payload, link))
404 return commandError<editing::CommandPlan>("editor.road.command.lane-link",
405 "Road lane-link command requires a complete directed link");
406 return plan(addLink ? road->makeAddLaneLink(link) : road->makeRemoveLaneLink(link));
407 };
408 registered = add("road.lane-link.add.v1", "Add road lane link",
410 return laneLinkCommand(true, value, request);
411 });
412 if (!registered.ok()) return registered;
413 registered = add("road.lane-link.remove.v1", "Remove road lane link",
415 return laneLinkCommand(false, value, request);
416 });
417 if (!registered.ok()) return registered;
418 auto laneLinkBlockCommand = [target, plan](bool block, editing::IEditableTarget& value,
420 auto* road = target(value);
422 if (!road || !decodeLink(request.payload, link))
423 return commandError<editing::CommandPlan>("editor.road.command.lane-link-block",
424 "Road lane-link block command requires a complete directed link");
425 return plan(block ? road->makeBlockLaneLink(link) : road->makeUnblockLaneLink(link));
426 };
427 registered = add("road.lane-link.block.v1", "Block road lane link",
428 [laneLinkBlockCommand](editing::IEditableTarget& value,
430 return laneLinkBlockCommand(true, value, request);
431 });
432 if (!registered.ok()) return registered;
433 return add("road.lane-link.unblock.v1", "Unblock road lane link",
434 [laneLinkBlockCommand](editing::IEditableTarget& value, const editing::CommandRequest& request) {
435 return laneLinkBlockCommand(false, value, request);
436 });
437}
438
439} // namespace eve::procgen_editing
LogicalId target
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string descriptor
std::string from
std::string nodeId
const GltfImportRequest & request
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::array< float, 3 > position
std::string name
float radius
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
int detail
std::string sourceNodeId
Definition UISystem.cpp:70
uint32_t index
Move-only operation result carrying either a value or Status.
Definition Result.h:155
IEditableTarget public API.
Lower-layer registration boundary implemented by an editor or runtime editing host.
virtual Result< void > registerPlannedCommand(EditingCommandDescriptor descriptor, EditingCommandPlanner planner)=0
Register or replace one domain-owned planned command.
std::map< std::string, Value > Object
Transaction target that edits stable RoadNetwork node/edge ids.
std::function< Result< CommandPlan >(IEditableTarget &, const CommandRequest &)> EditingCommandPlanner
Side-effect-free planner invoked with the resolved target.
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
StrongId< CommandIdTag > CommandId
Definition EditingIds.h:58
RoadJunctionControl
Authoritative traffic-control policy applied to every approach of one junction node.
Definition RoadTypes.h:43
bool decodeEdge(const EditorValue &value, std::uint32_t &id, std::vector< procgen::road::RoadControlPoint > &points)
bool decodeLink(const EditorValue &value, procgen::road::RoadLaneConnection &link)
bool decodeStyle(const EditorValue &value, std::uint32_t &id, procgen::road::RoadStyle &style)
bool readId(const EditorValue &value, const char *name, std::uint32_t &output)
const EditorValue * field(const EditorValue &value, const char *name)
bool readNumber(const EditorValue &value, const char *name, float &output)
bool readSmallInt(const EditorValue &value, const char *name, int minimum, int maximum, int &output)
editing::Result< void > registerRoadNetworkEditingCommands(editing::IEditingCommandRegistry &registry)
Register stable road topology commands with an existing editing host.
CommandPlan public API.
std::vector< DomainOperation > operations
CommandRequest public API.
Domain-owned metadata for one planned editing command.
One authored control point on a road centerline (world space, Y-up).
Definition RoadTypes.h:12
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