From 533b6b1c00a2adeba1d5b54057ee02454899a04c Mon Sep 17 00:00:00 2001 From: Marto Date: Sun, 26 Apr 2026 16:53:46 +0200 Subject: [PATCH] Setting up groundwork for vehicle (car) and pathfinding implementation --- src/common/constants.odin | 13 ++++- src/common/structures.odin | 1 + src/draw.odin | 49 +++++++++++++++---- src/infrastructure/road.odin | 6 ++- ...ement.odin => infrastructure_helpers.odin} | 10 +++- src/input.odin | 28 ++++++----- src/simulator.odin | 4 ++ src/vehicles/car.odin | 44 +++++++++++++++++ 8 files changed, 131 insertions(+), 24 deletions(-) rename src/{entity_management.odin => infrastructure_helpers.odin} (87%) create mode 100644 src/vehicles/car.odin diff --git a/src/common/constants.odin b/src/common/constants.odin index 865ffa5..4cd0e0b 100644 --- a/src/common/constants.odin +++ b/src/common/constants.odin @@ -31,4 +31,15 @@ NODE_CURSOR_COLOUR :: rl.BLUE // The colour of the overlay displaying the snap radius NODE_SNAP_COLOUR :: rl.PINK // The default colour of the text displayed -TEXT_COLOUR :: rl.BLACK \ No newline at end of file +TEXT_COLOUR :: rl.BLACK +// Default car colour +CAR_COLOUR :: rl.BLUE + +// Speed limit for majority of roads +DEFAULT_SPEED_LIMIT :: 60 +// Maximum fuel level +FUEL_MAX :: 100 +// Maximum car speed +CAR_MAX_SPEED :: 240 +CAR_HEIGHT :: 20 +CAR_WIDTH :: 2 * CAR_HEIGHT \ No newline at end of file diff --git a/src/common/structures.odin b/src/common/structures.odin index 977c072..a83d9c0 100644 --- a/src/common/structures.odin +++ b/src/common/structures.odin @@ -14,4 +14,5 @@ Intersection_Data :: struct { Entity :: enum { Node, Road, + Car, } \ No newline at end of file diff --git a/src/draw.odin b/src/draw.odin index add8018..e2c02c1 100644 --- a/src/draw.odin +++ b/src/draw.odin @@ -9,7 +9,16 @@ import "common" draw :: proc(self: ^Simulator, pos: rl.Vector2) { rl.ClearBackground(rl.LIGHTGRAY) - // draw roads + draw_roads(self) + draw_nodes(self) + draw_cars(self) + draw_temp_road(self, pos) + + draw_ui(self) +} + +@(private="file") +draw_roads :: proc(self: ^Simulator) { for &road, index in self.roads { start := road.nodes[0] end := road.nodes[1] @@ -21,24 +30,44 @@ draw :: proc(self: ^Simulator, pos: rl.Vector2) { rl.DrawLineEx(self.nodes[start].pos, self.nodes[end].pos, common.ROAD_SIZE, road_colour) } - - // draw nodes +} + +@(private="file") +draw_nodes :: proc(self: ^Simulator) { 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) } - - // draw temp road if exists - if val, ok := self.temp_node.?; ok { - rl.DrawLineEx(self.nodes[val].pos, pos, common.ROAD_SIZE, common.ROAD_COLOUR) +} + +@(private="file") +draw_cars :: proc(self: ^Simulator) { + for &car in self.cars { + pos := self.nodes[car.origin].pos - rl.DrawCircleV(self.nodes[val].pos, common.NODE_RADIUS, common.NODE_BUILD_COLOUR) - rl.DrawCircleV(pos, common.NODE_RADIUS, common.NODE_CURSOR_COLOUR) + rect := rl.Rectangle { + x = pos.x, + y = pos.y, + width = common.CAR_WIDTH, + height = common.CAR_HEIGHT + } + + rl.DrawRectangleRec(rect, common.CAR_COLOUR) } +} + +@(private="file") +draw_temp_road :: proc(self: ^Simulator, pos: rl.Vector2) { + // draw temp road if exists + val, ok := self.temp_node.? + if !ok do return - draw_ui(self) + rl.DrawLineEx(self.nodes[val].pos, pos, common.ROAD_SIZE, common.ROAD_COLOUR) + + rl.DrawCircleV(self.nodes[val].pos, common.NODE_RADIUS, common.NODE_BUILD_COLOUR) + rl.DrawCircleV(pos, common.NODE_RADIUS, common.NODE_CURSOR_COLOUR) } // Drawing UI text, mostly for debugging purposes diff --git a/src/infrastructure/road.odin b/src/infrastructure/road.odin index 0ab758d..f180f8d 100644 --- a/src/infrastructure/road.odin +++ b/src/infrastructure/road.odin @@ -1,14 +1,18 @@ package infrastructure +import "../common" + Road :: struct { // Index to nodes that limit the road nodes: [2]u32, + speed_limit: u8, } // Road Initialisation road_init :: proc(start: u32, end: u32) -> Road { return { - nodes = {start, end} + nodes = {start, end}, + speed_limit = common.DEFAULT_SPEED_LIMIT, } } diff --git a/src/entity_management.odin b/src/infrastructure_helpers.odin similarity index 87% rename from src/entity_management.odin rename to src/infrastructure_helpers.odin index d2143af..e89e645 100644 --- a/src/entity_management.odin +++ b/src/infrastructure_helpers.odin @@ -4,6 +4,7 @@ import rl "vendor:raylib" import "common" import inf "infrastructure" +import v "vehicles" // This function only returns the index to the node or if it doesn't exist bool in the tuple is false @private @@ -62,6 +63,8 @@ delete_entity :: proc(self: ^Simulator, entity_index: u32, type: common.Entity) mlen = u32(len(self.nodes)) case .Road: mlen = u32(len(self.roads)) + case .Car: + mlen = u32(len(self.cars)) } last := mlen - 1 @@ -76,14 +79,19 @@ delete_entity :: proc(self: ^Simulator, entity_index: u32, type: common.Entity) if !swap_made do return {}, false for &road in self.roads do inf.road_update_node_reference(&road, index_change[0], index_change[1]) + for &car in self.cars do v.car_update_node_reference(&car, index_change[0], index_change[1]) return index_change, true - case .Road: unordered_remove(&self.roads, entity_index) if !swap_made do return {}, false for &node in self.nodes do inf.node_update_road_reference(&node, index_change[0], index_change[1]) return index_change, true + case .Car: + unordered_remove(&self.cars, entity_index) + if !swap_made do return {}, false + // So far NOT needed as we don't reference the cars anywhere YET + // In the future this might be the cause for failure!!! } return {}, false diff --git a/src/input.odin b/src/input.odin index ffd37b6..3858eca 100644 --- a/src/input.odin +++ b/src/input.odin @@ -3,6 +3,7 @@ package main import rl "vendor:raylib" import inf "infrastructure" +import v "vehicles" // Public input function that gets called in graphics library loop handle_input :: proc(self: ^Simulator, pos: rl.Vector2) { @@ -11,21 +12,26 @@ handle_input :: proc(self: ^Simulator, pos: rl.Vector2) { } // General keyboard input event handler -@(private="file") +@(private = "file") handle_keyboard_input :: proc(self: ^Simulator) { self.show_details = rl.IsKeyDown(.LEFT_ALT) self.auto_continue = rl.IsKeyDown(.LEFT_CONTROL) self.delete_mode = rl.IsKeyDown(.LEFT_SHIFT) - - if (rl.IsKeyReleased(.C)) { + + if rl.IsKeyReleased(.C) { self.temp_node = nil clear(&self.roads) clear(&self.nodes) } + + if !rl.IsKeyReleased(.N) || len(self.nodes) == 0 do return + + car := v.car_init(u32(len(self.nodes))) + append(&self.cars, car) } // Generally mouse event handler -@(private="file") +@(private = "file") handle_mouse_input :: proc(self: ^Simulator, pos: rl.Vector2) { if (rl.IsMouseButtonReleased(.LEFT)) { left_click_event(self, pos) @@ -35,28 +41,28 @@ handle_mouse_input :: proc(self: ^Simulator, pos: rl.Vector2) { } // Handles left click functionality -@(private="file") +@(private = "file") left_click_event :: proc(self: ^Simulator, pos: rl.Vector2) { if self.delete_mode { if road, ok := self.highlighted_road.?; ok do delete_road(self, road) return } - + create_road(self, pos) } // Handles right click functionality -@(private="file") +@(private = "file") right_click_event :: proc(self: ^Simulator) { if self.temp_node == nil do return index := self.temp_node.? - + temp_node := &self.nodes[index] self.temp_node = nil - if len(temp_node.roads) > 0 do return - + if len(temp_node.roads) > 0 do return + inf.node_deinit(temp_node) // We can safely call the remove here as the only way it will get deleted is if it's the only node aka it was created during our creation process // Consequently this means that it will always be on last place and never swapped with anything unordered_remove(&self.nodes, index) -} \ No newline at end of file +} diff --git a/src/simulator.odin b/src/simulator.odin index 38cd873..7c11d18 100644 --- a/src/simulator.odin +++ b/src/simulator.odin @@ -5,12 +5,15 @@ import rl "vendor:raylib" import "common" import "core:fmt" import inf "infrastructure" +import v "vehicles" Simulator :: struct { // Stores all nodes nodes: [dynamic]inf.Node, // Stores all roads roads: [dynamic]inf.Road, + // Stores all cars + cars: [dynamic]v.Car, // Tracks the temporary node location temp_node: Maybe(u32), // Tracks the selected road @@ -34,6 +37,7 @@ deinit :: proc(self: ^Simulator) { inf.node_deinit(&node) } delete(self.nodes) + delete(self.cars) } // Functionality that runs every tick regardless of input diff --git a/src/vehicles/car.odin b/src/vehicles/car.odin new file mode 100644 index 0000000..172c618 --- /dev/null +++ b/src/vehicles/car.odin @@ -0,0 +1,44 @@ +package vehicles + +import "core:math/rand" + +import "../common" + +Car :: struct { + // Fuel level 0-100% + fuel_level: u8, + // Vehicle's maximum speed + max_speed: u8, + + // Pathfinding + + // Car's origin node + origin: u32, + // Car's destination node + destination: Maybe(u32), +} + +// Constructor +car_init :: proc(nodes_len: u32) -> Car { + return { + fuel_level = common.FUEL_MAX, + max_speed = common.CAR_MAX_SPEED, + origin = rand.uint32_max(nodes_len) + } +} + +// Sets a (valid) route for the car +// +// Does NOT guarantee the route is reachable (TODO?) +car_set_route :: proc(self: ^Car, nodes_len: u32) { + for self.origin == self.destination { + self.destination = rand.uint32_max(nodes_len) + } + +} + +// Updates (origin and destination) node reference +car_update_node_reference :: proc(self: ^Car, old_ref: u32, new_ref: u32) { + if self.origin == old_ref do self.origin = new_ref + if self.destination == old_ref do self.destination = new_ref +} \ No newline at end of file