Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion core/include/roadmaker/edit/command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,21 @@ struct DirtySet {
/// pass in phase 2 (#69); phase 4 (#71) reuses it for instanced props.
std::vector<RoadId> objects;

/// Roads or junctions were added or removed (drives tree-model resets).
/// Roads or junctions were added or removed. Drives the editor's wholesale
/// mesh re-upload (a partial per-road upload cannot add or drop an item) and
/// prunes selections that named a now-erased id.
bool topology = false;

/// This command already brought `junctions` up to date itself; the editor
/// must not regenerate them again. Set by the commands that build or tear
/// down junction structure (create/delete junction, split_road, delete_road)
/// — a second regeneration would double-work or fight them.
///
/// Default false is the safe direction: a command that forgets the flag gets
/// a redundant regeneration (slow, correct) rather than a stale junction
/// (fast, wrong). Kept separate from `topology` deliberately — a lane
/// appearing is topology AND needs regeneration, which one flag cannot say.
bool junctions_are_current = false;
};

/// One undoable kernel mutation (docs/m2/01_editing_framework.md §1.1).
Expand Down
61 changes: 51 additions & 10 deletions core/include/roadmaker/edit/operations.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,17 +282,43 @@ struct TAttachOptions {
double s,
const TAttachOptions& options = {});

/// Whether a regeneration may change the junction's turn set.
enum class TurnSetPolicy {
/// Turns may be added and dropped: new connecting roads are created, ones
/// whose turn disappeared are erased, and the connection table is rewritten.
/// The turns that survive keep their connecting-road IDs.
AllowChange,
/// Only geometry and widths may change; a different turn set is an error.
/// For the per-frame preview path ONLY — see regenerate_junction.
InPlaceOnly,
};

/// Re-runs the generator from a junction's recorded arm list and replaces its
/// connecting-road geometry and lane widths in place — connecting-road IDs
/// and the connection table survive, so held references and the undo stack
/// stay valid (02 §6 "Dependency tracking"). The editor triggers this after
/// any edit to an incoming road (via junctions_touching). M2 restriction: the
/// connection COUNT must be unchanged (a lane added/removed on an incoming
/// road changes the turn set — recreate the junction); an empty arm list
/// (foreign junction) is an error. A no-op regeneration writes byte-identical
/// output.
[[nodiscard]] RM_API std::unique_ptr<Command> regenerate_junction(
const RoadNetwork& network, JunctionId junction, const JunctionGenOptions& options = {});
/// connecting-road geometry and lane widths in place (02 §6 "Dependency
/// tracking"). The editor triggers this after any edit to an incoming road
/// (via junctions_touching). An empty arm list (foreign junction) is an error.
/// A no-op regeneration writes byte-identical output.
///
/// A turn that survives keeps its connecting-road ID — matching is by the
/// (incoming road+contact+lane, outgoing road+contact+lane) key, not by order
/// — so held references and the undo stack stay valid across a regeneration.
///
/// Under AllowChange (the default) a lane added to, removed from, or retyped
/// on an incoming road regenerates the junction: turns that appeared get fresh
/// connecting roads, turns that vanished have theirs erased.
///
/// `policy` exists for ONE caller. A preview session reverts and DESTROYS its
/// command on every frame (Document::update_preview), and revert frees created
/// ids with erase_exact, which reserves the slot rather than recycling it — so
/// a discarded command's created slots can never be reused. A per-frame
/// regeneration that creates connecting roads therefore leaks slots for the
/// rest of the session, which is why move_waypoint_following_junctions asks
/// for InPlaceOnly and takes the stale junction (as it does today) instead.
[[nodiscard]] RM_API std::unique_ptr<Command>
regenerate_junction(const RoadNetwork& network,
JunctionId junction,
const JunctionGenOptions& options = {},
TurnSetPolicy policy = TurnSetPolicy::AllowChange);

/// Deletes the junction AND its connecting roads (the §7 closure); incoming
/// roads survive with their predecessor/successor links into the junction
Expand All @@ -315,6 +341,21 @@ add_lane(const RoadNetwork& network, LaneSectionId section, int side, LaneType t
/// referencing the lane are cleared (and restored exactly on undo).
[[nodiscard]] RM_API std::unique_ptr<Command> remove_lane(const RoadNetwork& network, LaneId lane);

/// Inserts a lane at `at_odr_id`, renumbering every lane already at or outside
/// that position one step further out — where add_lane only ever appends the
/// outermost. `at_odr_id` must name a lane that exists (numbering stays
/// contiguous) and share its sign with the side it lands on; the center lane
/// (0) cannot be displaced.
///
/// The inserted lane does NOT link to the neighbouring sections: a lane that
/// appears mid-road is a new lane, not a continuation
/// (asam.net:xodr:1.4.0:road.lane.link.new_lane_appear). The lanes it pushes
/// outward keep their own links, and everything that named them by id —
/// adjacent-section predecessor/successor and junction lane_links — is
/// remapped to the new numbering (restored exactly on undo).
[[nodiscard]] RM_API std::unique_ptr<Command>
insert_lane(const RoadNetwork& network, LaneSectionId section, int at_odr_id, LaneType type);

[[nodiscard]] RM_API std::unique_ptr<Command>
set_lane_type(const RoadNetwork& network, LaneId lane, LaneType type);

Expand Down
Loading
Loading