From b9b7f610ac8f1aa7b5c91cb31ed151115c8eaefc Mon Sep 17 00:00:00 2001 From: Marto Date: Fri, 24 Apr 2026 18:22:55 +0200 Subject: [PATCH] Implementation of road intersections and road splitting (halfway) --- common/constants.odin | 1 + common/structures.odin | 8 ++++ infrastructure/node.odin | 13 ++++-- infrastructure/road.odin | 4 +- simulator.odin | 98 ++++++++++++++++++++++++++++++++++------ 5 files changed, 103 insertions(+), 21 deletions(-) create mode 100644 common/structures.odin diff --git a/common/constants.odin b/common/constants.odin index b3f439c..090a39a 100644 --- a/common/constants.odin +++ b/common/constants.odin @@ -15,5 +15,6 @@ ROAD_COLOUR :: rl.BLACK NODE_DONE_COLOUR :: rl.BROWN NODE_BUILD_COLOUR :: rl.GOLD NODE_CURSOR_COLOUR :: rl.BLUE +NODE_SNAP_COLOUR :: rl.PINK BACKGROUND_COLOUR :: rl.LIGHTGRAY \ No newline at end of file diff --git a/common/structures.odin b/common/structures.odin new file mode 100644 index 0000000..4d21c59 --- /dev/null +++ b/common/structures.odin @@ -0,0 +1,8 @@ +package common + +import rl "vendor:raylib" + +Intersection_Data :: struct { + road: u32, + point: rl.Vector2, +} \ No newline at end of file diff --git a/infrastructure/node.odin b/infrastructure/node.odin index 9ea3a50..7e00921 100644 --- a/infrastructure/node.odin +++ b/infrastructure/node.odin @@ -1,19 +1,26 @@ package infrastructure +import "../common" import rl "vendor:raylib" Node :: struct { - id: u32, enabled: bool, pos: rl.Vector2, roads: [dynamic]u32, } -node_init :: proc(new_id: u32, new_pos: rl.Vector2) -> Node { +node_init :: proc(new_pos: rl.Vector2) -> Node { return { - id = new_id, enabled = true, pos = new_pos, roads = nil, } +} + +node_within_snapping_radius :: proc(self: ^Node, pos: rl.Vector2) -> bool { + return rl.CheckCollisionPointCircle( + pos, + self.pos, + common.NODE_SNAP_RADIUS * common.NODE_RADIUS, + ) } \ No newline at end of file diff --git a/infrastructure/road.odin b/infrastructure/road.odin index 842e118..2c24fc3 100644 --- a/infrastructure/road.odin +++ b/infrastructure/road.odin @@ -1,13 +1,11 @@ package infrastructure Road :: struct { - id: u32, nodes: [2]u32, } -road_init :: proc(new_id: u32, start: u32, end: u32) -> Road { +road_init :: proc(start: u32, end: u32) -> Road { return { - id = new_id, nodes = {start, end} } } \ No newline at end of file diff --git a/simulator.odin b/simulator.odin index 307c2a4..036adf7 100644 --- a/simulator.odin +++ b/simulator.odin @@ -1,6 +1,5 @@ package main -import "base:intrinsics" import rl "vendor:raylib" import "common" @@ -10,6 +9,9 @@ Simulator :: struct { nodes: [dynamic]inf.Node, roads: [dynamic]inf.Road, temp_node_index: Maybe(u32), + // tracking variables + show_details: bool, + auto_continue: bool, } init :: proc() -> Simulator { @@ -17,6 +19,8 @@ init :: proc() -> Simulator { nodes = nil, roads = nil, temp_node_index = nil, + show_details = false, + auto_continue = false, } } @@ -33,6 +37,9 @@ handle_input :: proc(self: ^Simulator, pos: rl.Vector2) { @(private="file") handle_keyboard_input :: proc(self: ^Simulator) { + self.show_details = rl.IsKeyDown(.LEFT_ALT) + self.auto_continue = rl.IsKeyDown(.LEFT_CONTROL) + if (rl.IsKeyReleased(.C)) { self.temp_node_index = nil clear(&self.roads) @@ -47,13 +54,15 @@ handle_mouse_input :: proc(self: ^Simulator, pos: rl.Vector2) { @(private="file") left_click_event :: proc(self: ^Simulator, pos: rl.Vector2) { - cur_node_index := get_selected_node(self, pos) + cur_node_index := get_or_create_node(self, pos) if val, ok := self.temp_node_index.?; ok { - road := inf.road_init(get_new_id(self, self.roads[:]), val, cur_node_index) + // TODO remove + // replace by splitting functionality + road := inf.road_init(val, cur_node_index) append(&self.roads, road) - self.temp_node_index = nil + self.temp_node_index = self.auto_continue ? cur_node_index : nil return } @@ -63,6 +72,7 @@ left_click_event :: proc(self: ^Simulator, pos: rl.Vector2) { draw :: proc(self: ^Simulator, pos: rl.Vector2) { rl.ClearBackground(common.BACKGROUND_COLOUR) + // draw roads for &road in self.roads { start := road.nodes[0] end := road.nodes[1] @@ -70,7 +80,11 @@ draw :: proc(self: ^Simulator, pos: rl.Vector2) { rl.DrawLineEx(self.nodes[start].pos, self.nodes[end].pos, common.ROAD_SIZE, common.ROAD_COLOUR) } + // draw nodes for &node in self.nodes { + // draws the snapping radius if key is held down + if self.show_details do rl.DrawCircleV(node.pos, common.NODE_SNAP_RADIUS * common.NODE_RADIUS, common.NODE_SNAP_COLOUR) + // draws the node rl.DrawCircleV(node.pos, common.NODE_RADIUS, common.NODE_DONE_COLOUR) } @@ -80,26 +94,80 @@ draw :: proc(self: ^Simulator, pos: rl.Vector2) { } } +// This function only returns the index to the node or if it doesn't exist bool in the tuple is false +@(private="file") +get_selected_node :: proc(self: ^Simulator, pos: rl.Vector2) -> (u32, bool) { + for &node, index in self.nodes { + if inf.node_within_snapping_radius(&node, pos) do return u32(index), true + } + + return 0, false +} + // Given position, the function will attempt the return the pointer to the node in near vicinity, // or if unsuccesful manually creating the node based on the position in the list and then returning the pointer to it @(private="file") -get_selected_node :: proc(self: ^Simulator, pos: rl.Vector2) -> u32 { - for node, index in self.nodes { - if (!rl.CheckCollisionPointCircle(pos, node.pos, common.NODE_SNAP_RADIUS * common.NODE_RADIUS)) do continue - - return u32(index) - } +get_or_create_node :: proc(self: ^Simulator, pos: rl.Vector2) -> u32 { + if node, ok := get_selected_node(self, pos); ok do return node - node := inf.node_init(get_new_id(self, self.nodes[:]), pos) + node := inf.node_init(pos) append(&self.nodes, node) return u32(len(self.nodes) - 1) } +// Returns data about roads that intersect the given 2 nodes (points) @(private="file") -get_new_id :: proc(self: ^Simulator, man: []$T) -> u32 -where intrinsics.type_field_type(T, "id") == u32 { - mlen := len(man) +get_intersecting_roads :: proc(self: ^Simulator, start: u32, end: u32) -> []common.Intersection_Data { + intersections: [dynamic]common.Intersection_Data + collision_point: rl.Vector2 - return mlen == 0 ? 0 : man[mlen - 1].id + 1 + outer: for road, index in self.roads { + road_start_node := road.nodes[0] + road_end_node := road.nodes[1] + + if !rl.CheckCollisionLines(self.nodes[start].pos, self.nodes[end].pos, self.nodes[road_start_node].pos, self.nodes[road_end_node].pos, &collision_point) do continue + + // Save the collision info + data := common.Intersection_Data { + road = u32(index), + point = collision_point + } + + node := inf.node_init(data.point) + // Here we check if the intersection points that were recorded before, are already within snapping radius of our current intersected point + for collision in intersections { + if (inf.node_within_snapping_radius(&node, collision.point)) do continue outer + } + + // Here we check if our new intersected point node is too close to already established nodes + if _, ok := get_selected_node(self, data.point); ok do continue + + append(&intersections, data) + } + + return intersections[:] +} + +split_roads_by_points :: proc(self: ^Simulator, intersections: []common.Intersection_Data, start: u32, end: u32) { + if len(intersections) == 0 { + road := inf.road_init(start, end) + append(&self.roads, road) + + return + } + + first_intersection_node := get_or_create_node(self, intersections[0].point) + road := inf.road_init(start, first_intersection_node) + append(&self.roads, road) + + for intersection in intersections { + // TODO + } + + last_intersection_road := intersections[len(intersections) - 1] + last_intersection_node := get_or_create_node(self, last_intersection_road.point) + + road = inf.road_init(last_intersection_node, end) + append(&self.roads, road) } \ No newline at end of file