From 288cfd6b504056bd0dc25ce9d6af4c8241e4655e Mon Sep 17 00:00:00 2001 From: Marto Date: Wed, 8 Apr 2026 21:41:08 +0200 Subject: [PATCH] Intersection sorting implemented --- src/infrastructure/node_manager.zig | 7 +++- src/infrastructure/road_manager.zig | 3 +- src/simulator.zig | 51 +++++++++++++++++++---------- src/structures.zig | 8 +++++ src/utils.zig | 16 +++++++++ 5 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/utils.zig diff --git a/src/infrastructure/node_manager.zig b/src/infrastructure/node_manager.zig index 0e08b16..dacc3d2 100644 --- a/src/infrastructure/node_manager.zig +++ b/src/infrastructure/node_manager.zig @@ -82,7 +82,7 @@ pub const NodeManager = struct { /// generates finds the last element in the list and returns id + 1 or 0 if there are no elements in the list fn getNewID(self: *const NodeManager) usize { - return if (self.getLastRef()) |ref| ref.*.id + 1 else 0; + return if (self.nodes.getLastOrNull()) |last| last.id + 1 else 0; } /// Iterates through all nodes and runs the deinit procedure on each of them @@ -98,6 +98,11 @@ pub const NodeManager = struct { // This also means we don't have to deinit it, since it has no elements if (node_to_remove.roads.items.len > 0) return; + // TODO + // When a node gets removed the empty space left by now non-existant node will get swapped with another node + // In practice this however means that pointers to the node that gets swapped will be invalid + // causing fatal errors + for (self.nodes.items, 0..) |*node, i| { if (node.id != node_to_remove.id) continue; diff --git a/src/infrastructure/road_manager.zig b/src/infrastructure/road_manager.zig index 376c37f..b1afada 100644 --- a/src/infrastructure/road_manager.zig +++ b/src/infrastructure/road_manager.zig @@ -57,12 +57,11 @@ pub const RoadManager = struct { /// Generates finds the last element in the list and returns id + 1 or 0 if there are no elements in the list fn getNewID(self: *const RoadManager) usize { - return if (self.getLastRef()) |ref| ref.*.id + 1 else 0; + return if (self.roads.getLastOrNull()) |last| last.id + 1 else 0; } /// Removes the road which reference is passed in; returns error if the road is invalid pub fn remove(self: *RoadManager, road_to_remove: *Road) !void { - // todo fix leak try road_to_remove.unreferenceNodes(); for (self.roads.items, 0..) |*road, i| { diff --git a/src/simulator.zig b/src/simulator.zig index c738bd5..04d6cb3 100644 --- a/src/simulator.zig +++ b/src/simulator.zig @@ -3,6 +3,7 @@ const rl = @import("raylib"); const c = @import("constants.zig"); const st = @import("structures.zig"); +const utils = @import("utils.zig"); const NodeManager = @import("infrastructure/node_manager.zig").NodeManager; const RoadManager = @import("infrastructure/road_manager.zig").RoadManager; @@ -19,8 +20,6 @@ pub const Simulator = struct { display_details: bool, /// Tracks whether to automatically start build a new road after the creation of previous one is finished auto_continue: bool, - /// Displays intersection data for easier implementation and debugging - debug_intersection_data: std.ArrayList(st.IntersectionData), mode: st.Mode, pub fn init(allocator: std.mem.Allocator) !Simulator { @@ -30,7 +29,6 @@ pub const Simulator = struct { .road_man = try .init(allocator), .display_details = false, .auto_continue = false, - .debug_intersection_data = .empty, .mode = .VISUAL, }; } @@ -38,7 +36,6 @@ pub const Simulator = struct { pub fn deinit(self: *Simulator) void { self.node_man.deinit(self.allocator); self.road_man.deinit(self.allocator); - self.debug_intersection_data.deinit(self.allocator); } /// Input handling function that is exposed to the public @@ -53,7 +50,6 @@ pub const Simulator = struct { self.auto_continue = rl.isKeyDown(.left_control); if (rl.isKeyReleased(.c) and self.mode == .DELETE) { - self.debug_intersection_data.clearRetainingCapacity(); self.node_man.temp_node = null; self.road_man.roads.clearRetainingCapacity(); self.node_man.clear(self.allocator); @@ -104,9 +100,16 @@ pub const Simulator = struct { }; // get intersections made - self.getIntersectingRoads(temp, node) catch |err| { + const data = self.getIntersectingRoads(self.allocator, temp, node) catch |err| { std.debug.panic("Failed to save intersection data: {}\n", .{err}); }; + defer self.allocator.free(data); + + std.debug.print("Displaying intersection points in order from start (temp):\n", .{}); + for (0..data.len) |i| { + std.debug.print("{d}: ({d}, {d})\n", .{i+1, data[i].point.x, data[i].point.y}); + } + std.debug.print("\n", .{}); self.node_man.temp_node = if (self.auto_continue) node else null; } @@ -127,11 +130,6 @@ pub const Simulator = struct { self.node_man.draw(self.display_details); self.drawUI(); - // Display intersection 'nodes' - for (self.debug_intersection_data.items) |intersection| { - rl.drawCircleV(intersection.point, c.NODE_RADIUS / 2, .red); - } - rl.clearBackground(.light_gray); } @@ -167,25 +165,44 @@ pub const Simulator = struct { } /// Gets list of pointers of all roads that 'collide' with the road bounded by the nodes we pass into it - fn getIntersectingRoads(self: *Simulator, start: *const Node, end: *const Node) !void { + fn getIntersectingRoads(self: *Simulator, allocator: std.mem.Allocator, start: *const Node, end: *const Node) ![]st.IntersectionData { + var intersections: std.ArrayList(st.IntersectionData) = .empty; var collision_point: rl.Vector2 = undefined; outer: for (self.road_man.roads.items) |*road| { - if (!rl.checkCollisionLines(start.pos, end.pos, road.nodes[0].pos, road.nodes[1].pos, &collision_point)) continue; - const intersection = st.IntersectionData { + // If there is no collision check the next road + if (!rl.checkCollisionLines( + start.pos, + end.pos, + road.nodes[0].pos, + road.nodes[1].pos, + &collision_point)) continue; + + // Save the collision info + const data = st.IntersectionData { .road = road, .point = collision_point, }; - const node: Node = .init(0, intersection.point); + const node: Node = .init(0, data.point); // here we need to check if the points captured already are already within the reach - for (self.debug_intersection_data.items) |collision| { + for (intersections.items) |collision| { if (node.withinSnappingRadius(collision.point)) continue :outer; } - if (self.node_man.getNodeWithinRadius(intersection.point) == null) try self.debug_intersection_data.append(self.allocator, intersection); + // This checks whether our collision point is actually to close to the existing node(s) + // to form another intersection there + if (self.node_man.getNodeWithinRadius(data.point) != null) continue; + + try intersections.append(self.allocator, data); } + + // we must sort the items by the distance from the first (usually temp) node + const sorted_intersections = try intersections.toOwnedSlice(allocator); + std.sort.block(st.IntersectionData, sorted_intersections, start, utils.compareIntersections); + + return sorted_intersections; } pub fn update(self: *Simulator) void { diff --git a/src/structures.zig b/src/structures.zig index b499aaa..34bf09b 100644 --- a/src/structures.zig +++ b/src/structures.zig @@ -2,13 +2,21 @@ const Vector2 = @import("raylib").Vector2; const Road = @import("infrastructure/road.zig").Road; +/// Represents the data, particularly used in the instance when we draw over already existing roads pub const IntersectionData = struct { + /// Tracks the road we intersected road: *Road, + /// Exact point where the road above was intersected point: Vector2, }; +/// Tracks different modes of interaction with the simulation pub const Mode = enum { + /// Represents visual, read-only mode VISUAL, + /// Represents build mode BUILD, + /// Represents deleting or modifying existing entities + /// May also be called edit mode in the future DELETE }; \ No newline at end of file diff --git a/src/utils.zig b/src/utils.zig new file mode 100644 index 0000000..51efaee --- /dev/null +++ b/src/utils.zig @@ -0,0 +1,16 @@ +const st = @import("structures.zig"); +const Node = @import("infrastructure/node.zig").Node; + +pub fn compareIntersections(ctx: *const Node, inter_a: st.IntersectionData, inter_b: st.IntersectionData) bool { + const distance_a = getRelativeInterDistance(ctx, inter_a); + const distance_b = getRelativeInterDistance(ctx, inter_b); + + return distance_a < distance_b; +} + +fn getRelativeInterDistance(ctx: *const Node, intersection: st.IntersectionData) f32 { + const x_diff = intersection.point.x - ctx.pos.x; + const y_diff = intersection.point.y - ctx.pos.y; + + return x_diff * x_diff + y_diff * y_diff; +} \ No newline at end of file