Draw road mesh, refactor

This commit is contained in:
sergeypdev 2025-01-03 23:18:27 +04:00
parent e404a33c6f
commit 64dbe0c5e4
4 changed files with 249 additions and 236 deletions

View File

@ -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)
}

View File

@ -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)
}

178
game/track.odin Normal file
View File

@ -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)
}
}
}

View File

@ -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,