From 64dbe0c5e45524ae8be9546a7c2d6189794acc67 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Fri, 3 Jan 2025 23:18:27 +0400 Subject: [PATCH] Draw road mesh, refactor --- game/game.odin | 254 ++++++++++----------------------------- game/raylib_helpers.odin | 50 +------- game/track.odin | 178 +++++++++++++++++++++++++++ ols.json | 3 +- 4 files changed, 249 insertions(+), 236 deletions(-) create mode 100644 game/track.odin diff --git a/game/game.odin b/game/game.odin index 8b96a3c..2547cd0 100644 --- a/game/game.odin +++ b/game/game.odin @@ -25,12 +25,16 @@ PIXEL_WINDOW_HEIGHT :: 360 Track :: struct { points: [dynamic]rl.Vector3, } + +World :: struct { + track: Track, +} + Game_Memory :: struct { assetman: assets.Asset_Manager, player_pos: rl.Vector3, camera_yaw_pitch: rl.Vector2, camera_speed: f32, - track: Track, es: Editor_State, editor: bool, } @@ -53,15 +57,20 @@ Move_Axis :: enum { } Editor_State :: struct { - mouse_captured: bool, - selected_track_point: int, - track_edit_state: Track_Edit_State, - move_axis: Move_Axis, - initial_point_pos: rl.Vector3, + world: World, + mouse_captured: bool, + point_selection: map[int]bool, + track_edit_state: Track_Edit_State, + move_axis: Move_Axis, + initial_point_pos: rl.Vector3, } g_mem: ^Game_Memory +world :: proc() -> ^World { + return &g_mem.es.world +} + game_camera :: proc() -> rl.Camera2D { w := f32(rl.GetScreenWidth()) h := f32(rl.GetScreenHeight()) @@ -135,11 +144,20 @@ update_free_look_camera :: proc() { g_mem.player_pos += (input.x * right + input.y * forward) * g_mem.camera_speed } +select_track_point :: proc(index: int) { + clear(&g_mem.es.point_selection) + g_mem.es.point_selection[index] = true +} + +is_point_selected :: proc() -> bool { + return len(g_mem.es.point_selection) > 0 +} + add_track_spline_point :: proc() { forward := camera_rotation_matrix()[2] - append(&g_mem.track.points, g_mem.player_pos + forward) - g_mem.es.selected_track_point = len(g_mem.track.points) - 1 + append(&world().track.points, g_mem.player_pos + forward) + select_track_point(len(&world().track.points) - 1) } get_movement_axes :: proc( @@ -198,17 +216,27 @@ update_editor :: proc() { add_track_spline_point() } - if rl.IsKeyPressed(.G) && es.selected_track_point >= 0 { - es.track_edit_state = .Move - es.move_axis = .None - es.initial_point_pos = g_mem.track.points[es.selected_track_point] + if is_point_selected() { + if rl.IsKeyPressed(.X) { + for k, v in es.point_selection { + assert(v) + ordered_remove(&world().track.points, k) + } + + clear(&es.point_selection) + } + if rl.IsKeyPressed(.G) { + es.track_edit_state = .Move + es.move_axis = .None + // es.initial_point_pos = g_mem.track.points[es.selected_track_point] + } } } case .Move: { if rl.IsKeyPressed(.ESCAPE) { es.track_edit_state = .Select - g_mem.track.points[es.selected_track_point] = es.initial_point_pos + // g_mem.track.points[es.selected_track_point] = es.initial_point_pos break } @@ -265,7 +293,9 @@ update_editor :: proc() { (rl.Vector4{movement_screen.x, movement_screen.y, 0, 1} * rl.MatrixInvert(view_proj)).xyz } - g_mem.track.points[es.selected_track_point] += movement_world + for k in es.point_selection { + world().track.points[k] += movement_world + } } } } @@ -336,8 +366,7 @@ draw :: proc() { rl.WHITE, ) - points := &g_mem.track.points - points_len := len(points) + points := &world().track.points // road: rl.Mesh // defer rl.UnloadMesh(road) @@ -350,15 +379,6 @@ draw :: proc() { // road_uvs.allocator = context.temp_allocator // road_indices.allocator = context.temp_allocator - // spline_segment_count := max(len(g_mem.track.points) - 4, 0) - - - SPLINE_SUBDIVS_U :: 4 - SPLINE_SUBDIVS_V :: 16 - ROAD_WIDTH :: 2.0 - CATMULL_ROM_ALPHA :: 1.0 - CATMULL_ROM_TENSION :: 0.0 - { // Debug draw spline road { @@ -367,168 +387,13 @@ draw :: proc() { rlgl.Color3f(1, 0, 0) - - Frame :: struct { - tangent, normal: rl.Vector3, - rotation: linalg.Quaternionf32, - } - - ctrl_frames := make_soa(#soa[]Frame, points_len, context.temp_allocator) - - // Normals for control points - for i in 0 ..< points_len - 1 { - pos := points[i] - tangent := linalg.normalize0(points[i + 1] - pos) - bitangent := linalg.normalize0(linalg.cross(tangent, rl.Vector3{0, 1, 0})) - normal := linalg.normalize0(linalg.cross(bitangent, tangent)) - rotation_matrix: linalg.Matrix3f32 - rotation_matrix[0], rotation_matrix[1], rotation_matrix[2] = - bitangent, normal, -tangent - ctrl_frames[i].tangent = tangent - ctrl_frames[i].normal = normal - ctrl_frames[i].rotation = linalg.quaternion_from_matrix3(rotation_matrix) - } - ctrl_frames[points_len - 1] = ctrl_frames[points_len - 2] - - interpolated_points := make( - []rl.Vector3, - (points_len - 1) * SPLINE_SUBDIVS_V + 1, + interpolated_points := calculate_spline_interpolated_points( + points[:], context.temp_allocator, ) - if points_len >= 2 { - extended_start := points[0] + (points[0] - points[1]) - extended_end := - points[points_len - 1] + points[points_len - 1] - points[points_len - 2] - extended_end2 := extended_end + points[points_len - 1] - points[points_len - 2] - - for i in 0 ..< points_len - 1 { - prev := i > 0 ? points[i - 1] : extended_start - current := points[i] - next := i < points_len - 1 ? points[i + 1] : extended_end - next2 := i < points_len - 2 ? points[i + 2] : extended_end2 - - a, b, c, d := catmull_rom_coefs( - prev, - current, - next, - next2, - CATMULL_ROM_ALPHA, - CATMULL_ROM_TENSION, - ) - - v_dt := 1.0 / f32(SPLINE_SUBDIVS_V) - for v_index in 0 ..< SPLINE_SUBDIVS_V { - v_t := f32(v_index) * v_dt - - interpolated_pos := catmull_rom(a, b, c, d, v_t) - interpolated_points[i * SPLINE_SUBDIVS_V + v_index] = interpolated_pos - } - } - - interpolated_points[len(interpolated_points) - 1] = points[points_len - 1] - - frames := make_soa( - #soa[]Frame, - len(interpolated_points), - context.temp_allocator, - ) - - { - for i in 0 ..< len(frames) - 1 { - ctrl_index := i / SPLINE_SUBDIVS_V - v_t := f32(i % SPLINE_SUBDIVS_V) / (SPLINE_SUBDIVS_V) - - pos := interpolated_points[i] - cur_frame := ctrl_frames[ctrl_index] - next_frame := ctrl_frames[ctrl_index + 1] - - frames[i].rotation = linalg.quaternion_slerp( - cur_frame.rotation, - next_frame.rotation, - v_t, - ) - normal := linalg.matrix3_from_quaternion(frames[i].rotation)[1] - tangent := linalg.normalize0(interpolated_points[i + 1] - pos) - - frames[i].tangent = tangent - frames[i].normal = normal - } - - frames[len(frames) - 1] = frames[len(frames) - 2] - } - - // Parallel frame algorithm (doesn't work because it rotates the road too much, but looks pretty) - if false { - // Calculate reference normal - { - tangent := linalg.normalize0( - interpolated_points[1] - interpolated_points[0], - ) - frames[0].tangent = tangent - bitangent := linalg.normalize0( - linalg.cross(tangent, rl.Vector3{0, 1, 0}), - ) - frames[0].normal = linalg.normalize0(linalg.cross(bitangent, tangent)) - } - - for i in 1 ..< len(frames) { - cur, next := interpolated_points[i], interpolated_points[i + 1] - - tangent := linalg.normalize0(next - cur) - bitangent := linalg.cross(frames[i - 1].tangent, tangent) - bitangent_len := linalg.length(bitangent) - normal: rl.Vector3 - if bitangent_len < math.F32_EPSILON { - normal = frames[i - 1].normal - } else { - bitangent /= bitangent_len - angle := math.acos(linalg.dot(frames[i - 1].tangent, tangent)) - normal = - linalg.matrix3_rotate(angle, bitangent) * frames[i - 1].normal - } - - frames[i].tangent = tangent - frames[i].normal = normal - } - } - - { - rlgl.Begin(rlgl.LINES) - defer rlgl.End() - - for i in 0 ..< len(interpolated_points) - 1 { - cur, next := interpolated_points[i], interpolated_points[i + 1] - - // rotation := linalg.matrix3_look_at(cur, next, rl.Vector3{0, 1, 0}) - tangent := frames[i].tangent - normal := frames[i].normal - bitangent := linalg.cross(tangent, normal) - - rlgl.Color4ub(255, 255, 255, 255) - rlgl.Vertex3f(cur.x, cur.y, cur.z) - rlgl.Vertex3f(next.x, next.y, next.z) - - rlgl.Color4ub(255, 0, 0, 255) - rlgl.Vertex3f(cur.x, cur.y, cur.z) - rlgl.Vertex3f(cur.x + tangent.x, cur.y + tangent.y, cur.z + tangent.z) - - rlgl.Color4ub(0, 255, 0, 255) - rlgl.Vertex3f(cur.x, cur.y, cur.z) - rlgl.Vertex3f( - cur.x + bitangent.x, - cur.y + bitangent.y, - cur.z + bitangent.z, - ) - - rlgl.Color4ub(0, 0, 255, 255) - rlgl.Vertex3f(cur.x, cur.y, cur.z) - rlgl.Vertex3f(cur.x + normal.x, cur.y + normal.y, cur.z + normal.z) - - } - } - } - + debug_draw_spline(interpolated_points) + debug_draw_spline_mesh(interpolated_points) } if g_mem.editor { @@ -572,14 +437,24 @@ draw :: proc() { if g_mem.editor { es := &g_mem.es - points := &g_mem.track.points + points := &world().track.points points_len := len(points) + selected_point := false for i in 0 ..< points_len { - if spline_handle(g_mem.track.points[i], camera, es.selected_track_point == i) { - g_mem.es.selected_track_point = i + if spline_handle(world().track.points[i], camera, es.point_selection[i]) { + if !rl.IsKeyDown(.LEFT_CONTROL) { + clear(&g_mem.es.point_selection) + } + + g_mem.es.point_selection[i] = true + selected_point = true } } + + if rl.IsMouseButtonPressed(.LEFT) && !selected_point { + clear(&g_mem.es.point_selection) + } } // axis lines @@ -654,15 +529,14 @@ game_init :: proc() { g_mem^ = Game_Memory{} - g_mem.es.selected_track_point = -1 - game_hot_reloaded(g_mem) } @(export) game_shutdown :: proc() { assets.shutdown(&g_mem.assetman) - delete(g_mem.track.points) + delete(world().track.points) + delete(g_mem.es.point_selection) free(g_mem) } diff --git a/game/raylib_helpers.odin b/game/raylib_helpers.odin index 3ccf421..0e28725 100644 --- a/game/raylib_helpers.odin +++ b/game/raylib_helpers.odin @@ -1,52 +1,12 @@ package game -import "base:runtime" -import "core:c/libc" -import "core:log" -import "core:mem" import rl "vendor:raylib" +import "vendor:raylib/rlgl" -logger: log.Logger = { - lowest_level = .Debug, +rlgl_color3v :: proc(v: rl.Vector3) { + rlgl.Color3f(v.r, v.g, v.b) } -rl_log_buf: []byte -rl_log :: proc "c" (logLevel: rl.TraceLogLevel, text: cstring, args: ^libc.va_list) { - context = runtime.default_context() - context.logger = logger - level: log.Level - switch logLevel { - case .TRACE, .DEBUG: - level = .Debug - case .ALL, .NONE, .INFO: - level = .Info - case .WARNING: - level = .Warning - case .ERROR: - level = .Error - case .FATAL: - level = .Fatal - } - - if level < logger.lowest_level { - return - } - - if rl_log_buf == nil { - rl_log_buf = make([]byte, 1024) - } - - defer mem.zero_slice(rl_log_buf) - - n: int - for { - va := args - n = int(libc.vsnprintf(raw_data(rl_log_buf), len(rl_log_buf), text, va)) - if n < len(rl_log_buf) do break - log.infof("Resizing raylib log buffer from %m to %m", len(rl_log_buf), len(rl_log_buf) * 2) - rl_log_buf, _ = mem.resize_bytes(rl_log_buf, len(rl_log_buf) * 2) - } - - formatted := string(rl_log_buf[:n]) - log.log(level, formatted) +rlgl_vertex3v :: proc(v: rl.Vector3) { + rlgl.Vertex3f(v.x, v.y, v.z) } diff --git a/game/track.odin b/game/track.odin new file mode 100644 index 0000000..121112b --- /dev/null +++ b/game/track.odin @@ -0,0 +1,178 @@ +package game + +import lg "core:math/linalg" +import rl "vendor:raylib" +import "vendor:raylib/rlgl" + +SPLINE_SUBDIVS_U :: 1 +SPLINE_SUBDIVS_V :: 16 +ROAD_WIDTH :: 8.0 +CATMULL_ROM_ALPHA :: 1.0 +CATMULL_ROM_TENSION :: 0.0 + +calculate_spline_ctrl_rotations :: proc( + points: []rl.Vector3, + allocator := context.allocator, +) -> []lg.Quaternionf32 { + points_len := len(points) + ctrl_rotations := make([]lg.Quaternionf32, points_len, allocator) + + // Normals for control points + for i in 0 ..< points_len - 1 { + pos := points[i] + tangent := lg.normalize0(points[i + 1] - pos) + bitangent := lg.normalize0(lg.cross(tangent, rl.Vector3{0, 1, 0})) + normal := lg.normalize0(lg.cross(bitangent, tangent)) + rotation_matrix: lg.Matrix3f32 + rotation_matrix[0], rotation_matrix[1], rotation_matrix[2] = bitangent, normal, -tangent + ctrl_rotations[i] = lg.quaternion_from_matrix3(rotation_matrix) + + if points_len >= 2 { + ctrl_rotations[points_len - 1] = ctrl_rotations[points_len - 2] + } + } + return ctrl_rotations +} + +Interpolated_Point :: struct { + pos: rl.Vector3, + normal: rl.Vector3, +} + +calculate_spline_interpolated_points :: proc( + points: []rl.Vector3, + allocator := context.allocator, +) -> []Interpolated_Point { + points_len := len(points) + + ctrl_rotations := calculate_spline_ctrl_rotations(points, context.temp_allocator) + + + if points_len >= 2 { + interpolated_points := make( + []Interpolated_Point, + (points_len - 1) * SPLINE_SUBDIVS_V + 1, + allocator, + ) + + extended_start := points[0] + (points[0] - points[1]) + extended_end := points[points_len - 1] + points[points_len - 1] - points[points_len - 2] + extended_end2 := extended_end + points[points_len - 1] - points[points_len - 2] + + for i in 0 ..< points_len - 1 { + prev := i > 0 ? points[i - 1] : extended_start + current := points[i] + next := i < points_len - 1 ? points[i + 1] : extended_end + next2 := i < points_len - 2 ? points[i + 2] : extended_end2 + + cur_frame := ctrl_rotations[i] + next_frame := ctrl_rotations[i + 1] + + a, b, c, d := catmull_rom_coefs( + prev, + current, + next, + next2, + CATMULL_ROM_ALPHA, + CATMULL_ROM_TENSION, + ) + + v_dt := 1.0 / f32(SPLINE_SUBDIVS_V) + for v_index in 0 ..< SPLINE_SUBDIVS_V { + v_t := f32(v_index) * v_dt + out_point := &interpolated_points[i * SPLINE_SUBDIVS_V + v_index] + + rotation := lg.quaternion_slerp(cur_frame, next_frame, v_t) + normal := lg.matrix3_from_quaternion(rotation)[1] + out_point.pos = catmull_rom(a, b, c, d, v_t) + out_point.normal = normal + } + } + + interpolated_points[len(interpolated_points) - 1] = + interpolated_points[len(interpolated_points) - 2] + + return interpolated_points + } + + return nil +} + +debug_draw_spline :: proc(interpolated_points: []Interpolated_Point) { + rlgl.Begin(rlgl.LINES) + defer rlgl.End() + for i in 0 ..< len(interpolated_points) - 1 { + cur, next := interpolated_points[i], interpolated_points[i + 1] + + tangent := lg.normalize0(next.pos - cur.pos) + normal := interpolated_points[i].normal + bitangent := lg.cross(tangent, normal) + + rlgl.Color4ub(255, 255, 255, 255) + rlgl_vertex3v(cur.pos) + rlgl_vertex3v(next.pos) + + rlgl.Color4ub(255, 0, 0, 255) + rlgl_vertex3v(cur.pos) + rlgl_vertex3v(cur.pos + tangent) + + rlgl.Color4ub(0, 255, 0, 255) + rlgl_vertex3v(cur.pos) + rlgl_vertex3v(cur.pos + bitangent * ROAD_WIDTH) + + rlgl.Color4ub(0, 0, 255, 255) + rlgl_vertex3v(cur.pos) + rlgl_vertex3v(cur.pos + normal) + + } +} +debug_draw_spline_mesh :: proc(interpolated_points: []Interpolated_Point) { + rlgl.Begin(rlgl.TRIANGLES) + defer rlgl.End() + + for i in 0 ..< len(interpolated_points) - 1 { + cur, next := interpolated_points[i], interpolated_points[i + 1] + + tangent := lg.normalize0(next.pos - cur.pos) + normal := interpolated_points[i].normal + bitangent := lg.normalize0(lg.cross(tangent, normal)) + + next_tangent: rl.Vector3 + if i < len(interpolated_points) - 2 { + next2 := interpolated_points[i + 2] + next_tangent = next2.pos - next.pos + } else { + next_tangent = tangent + } + + next_normal := interpolated_points[i + 1].normal + next_bitangent := lg.normalize0(lg.cross(next_tangent, next_normal)) + + u_dt := 1.0 / f32(SPLINE_SUBDIVS_U) + for u in 0 ..< SPLINE_SUBDIVS_U { + u_t := u_dt * f32(u) + u_t2 := u_t + u_dt + + // [-1, 1] + u_t = u_t * 2 - 1 + u_t2 = u_t2 * 2 - 1 + u_t *= ROAD_WIDTH + u_t2 *= ROAD_WIDTH + + p1 := cur.pos + bitangent * u_t + p2 := cur.pos + bitangent * u_t2 + p3 := next.pos + next_bitangent * u_t + p4 := next.pos + next_bitangent * u_t2 + + rlgl_color3v(normal * 0.5 + 0.5) + rlgl_vertex3v(p1) + rlgl_vertex3v(p2) + rlgl_vertex3v(p3) + + rlgl_color3v(next_normal * 0.5 + 0.5) + rlgl_vertex3v(p2) + rlgl_vertex3v(p4) + rlgl_vertex3v(p3) + } + } +} diff --git a/ols.json b/ols.json index d49db1f..44b6fe9 100644 --- a/ols.json +++ b/ols.json @@ -1,7 +1,8 @@ { "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", "collections": [ - { "name": "common", "path": "./common" } + { "name": "common", "path": "./common" }, + { "name": "game", "path": "./game" }, ], "enable_semantic_tokens": false, "enable_document_symbols": true,