From 28f0d737b1c7e1b893ed7e3e336c6b94741e4ea4 Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 29 May 2026 00:36:55 +0200 Subject: [PATCH] asdf --- idk/main.odin | 211 +++++++++ idk/run.sh | 4 + idk/shader.glsl.frag | 9 + idk/shader.glsl.vert | 16 + idk/shader.spv.frag | Bin 0 -> 448 bytes idk/shader.spv.vert | Bin 0 -> 1324 bytes main copy.odin | 768 ++++++++++++++++++++++++++++++++ shaders/cubes.frag.glsl | 15 +- shaders/cubes.frag.spv | Bin 712 -> 1244 bytes shaders/cubes.vert.glsl | 5 + shaders/cubes.vert.spv | Bin 1228 -> 1404 bytes src/format/common.odin | 114 +++++ src/format/gnd_format.odin | 385 ++++++++++++++++ src/format/rsm_format.odin | 282 ++++++++++++ src/gndview/gndview.odin | 875 +++++++++++++++++++++++++++++++++++++ src/main.odin | 537 +++-------------------- src/rsmview/rsmview.odin | 646 +++++++++++++++++++++++++++ src/shared/shared.odin | 98 +++++ 18 files changed, 3472 insertions(+), 493 deletions(-) create mode 100644 idk/main.odin create mode 100755 idk/run.sh create mode 100644 idk/shader.glsl.frag create mode 100644 idk/shader.glsl.vert create mode 100644 idk/shader.spv.frag create mode 100644 idk/shader.spv.vert create mode 100644 main copy.odin create mode 100644 src/format/common.odin create mode 100644 src/format/gnd_format.odin create mode 100644 src/format/rsm_format.odin create mode 100644 src/gndview/gndview.odin create mode 100644 src/rsmview/rsmview.odin create mode 100644 src/shared/shared.odin diff --git a/idk/main.odin b/idk/main.odin new file mode 100644 index 0000000..f8b6c82 --- /dev/null +++ b/idk/main.odin @@ -0,0 +1,211 @@ +package main + +import sdl "vendor:sdl3" +import "core:math/linalg" +import "core:mem" + +frag_shader_code := #load("shader.spv.frag") +vert_shader_code := #load("shader.spv.vert") + +main :: proc() { + ok := sdl.Init({.VIDEO}); assert(ok) + + sdl.SetLogPriorities(.VERBOSE) + + window := sdl.CreateWindow("Pain and Suffering and Pleasure", 800, 500, nil) + + device := sdl.CreateGPUDevice({.SPIRV}, true, nil) + + ok = sdl.ClaimWindowForGPUDevice(device, window); assert(ok) + + vert_shader := sdl.CreateGPUShader(device, { + code_size = len(vert_shader_code), + code = raw_data(vert_shader_code), + entrypoint = "main", + format = {.SPIRV}, + stage = .VERTEX, + num_uniform_buffers = 1, + }) + + frag_shader := sdl.CreateGPUShader(device, { + code_size = len(frag_shader_code), + code = raw_data(frag_shader_code), + entrypoint = "main", + format = {.SPIRV}, + stage = .FRAGMENT, + num_uniform_buffers = 0, + }) + + Vec3 :: [3]f32 + + Vertex_Data :: struct { + pos: Vec3, + color: sdl.FColor, + } + + size : f32= 1 + + vertices := []Vertex_Data { + { pos = {-size, size, 1}, color = {1,0,0,1} }, + {pos = {size, size, 1}, color = {0,1,1,1}}, + {pos={-size,-size, 1}, color = {1,0,1,1}}, + {pos={size,-size, 1}, color = {1,0,1,1}}, + } + + vertices_byte_size := len(vertices) * size_of(vertices[0]) + + indicies := []u16 { + 0,1,2, + 2,1,3 + } + + indices_byte_size := len(indicies) * size_of(indicies[0]) + vertex_buf := sdl.CreateGPUBuffer(device, { + usage = {.VERTEX}, + size = u32(vertices_byte_size), + }) + + index_buf := sdl.CreateGPUBuffer(device, { + usage = {.INDEX}, + size = u32(vertices_byte_size), + }) + + transfer_buf := sdl.CreateGPUTransferBuffer(device, { + usage = .UPLOAD, + size = u32(vertices_byte_size + indices_byte_size), + }) + + transfer_mem := transmute([^]byte)sdl.MapGPUTransferBuffer(device, transfer_buf, false) + mem.copy(transfer_mem, raw_data(vertices), vertices_byte_size) + mem.copy(transfer_mem[vertices_byte_size:], raw_data(indicies), indices_byte_size) + sdl.UnmapGPUTransferBuffer(device, transfer_buf) + + copy_buffer := sdl.AcquireGPUCommandBuffer(device) + copy_pass := sdl.BeginGPUCopyPass(copy_buffer) + + sdl.UploadToGPUBuffer(copy_pass, { + transfer_buffer = transfer_buf, + }, { + buffer = vertex_buf, + size = u32(vertices_byte_size), + }, false) + + sdl.UploadToGPUBuffer(copy_pass, { + transfer_buffer = transfer_buf, offset = u32(vertices_byte_size) + }, { + buffer = index_buf, + size = u32(indices_byte_size) + }, false) + + sdl.EndGPUCopyPass(copy_pass) + + ok = sdl.SubmitGPUCommandBuffer(copy_buffer); assert(ok) + + sdl.ReleaseGPUTransferBuffer(device, transfer_buf) + + + vertex_attrs := []sdl.GPUVertexAttribute { + { + location = 0, + format = .FLOAT3, + offset = u32(offset_of(Vertex_Data, pos)), + }, + { + location = 1, + format = .FLOAT4, + offset = u32(offset_of(Vertex_Data, color)), + } + } + + pipeline := sdl.CreateGPUGraphicsPipeline(device, { + vertex_shader = vert_shader, + fragment_shader = frag_shader, + primitive_type = .TRIANGLELIST, + vertex_input_state = { + num_vertex_buffers = 1, + vertex_buffer_descriptions = &(sdl.GPUVertexBufferDescription{ + slot = 0, + pitch = size_of(Vertex_Data), + }), + num_vertex_attributes = u32(len(vertex_attrs)), + vertex_attributes = raw_data(vertex_attrs), + }, + target_info = { + num_color_targets = 1, + color_target_descriptions = &(sdl.GPUColorTargetDescription{ + format = sdl.GetGPUSwapchainTextureFormat(device, window) + }) + } + }) + + defer sdl.ReleaseGPUShader(device, frag_shader) + defer sdl.ReleaseGPUShader(device, vert_shader) + + win_size: [2]i32 + ok = sdl.GetWindowSize(window, &win_size.x, &win_size.y); assert(ok) + + rotation_speed := linalg.to_radians(f32(90)) + + rotation := f32(0.62) + + proj := linalg.matrix4_perspective_f32(linalg.to_radians(f32(70)), f32(win_size.x) / f32(win_size.y), 0.0001, 1000) + + + UBO :: struct { + mvp: matrix[4,4]f32 + } + + last_ticks := sdl.GetTicks() + + + for { + new_ticks := sdl.GetTicks() + delta_time := f32(new_ticks - last_ticks) / 1000 + last_ticks = new_ticks + event: sdl.Event + for sdl.PollEvent(&event) { + #partial switch(event.type) { + case .QUIT: + return + case .KEY_DOWN: + #partial switch(event.key.scancode) { + case .ESCAPE: + return + } + } + } + + buffer := sdl.AcquireGPUCommandBuffer(device) + + texture: ^sdl.GPUTexture + + ok = sdl.WaitAndAcquireGPUSwapchainTexture(buffer, window, &texture, nil, nil); assert(ok) + + rotation += rotation_speed * delta_time + model_mat := linalg.matrix4_translate_f32({0,0,-5}) * linalg.matrix4_rotate_f32(rotation, {0,1,0}) + + ubo := UBO {mvp = proj * model_mat} + + if texture != nil { + color_target := sdl.GPUColorTargetInfo{ + texture = texture, + load_op = .CLEAR, + clear_color = {0,0.2,0.4,1}, + store_op = .STORE, + } + render_pass := sdl.BeginGPURenderPass(buffer, &color_target, 1, nil) + + sdl.BindGPUGraphicsPipeline(render_pass, pipeline) + sdl.BindGPUVertexBuffers(render_pass, 0, &(sdl.GPUBufferBinding{ + buffer = vertex_buf, + + }),1) + sdl.BindGPUIndexBuffer(render_pass, {buffer = index_buf}, ._16BIT) + sdl.PushGPUVertexUniformData(buffer, 0, &ubo, size_of(ubo)) + sdl.DrawGPUIndexedPrimitives(render_pass, 6, 1, 0, 0 ,0) + sdl.EndGPURenderPass(render_pass) + } + + ok = sdl.SubmitGPUCommandBuffer(buffer); assert(ok) + } +} \ No newline at end of file diff --git a/idk/run.sh b/idk/run.sh new file mode 100755 index 0000000..74d360c --- /dev/null +++ b/idk/run.sh @@ -0,0 +1,4 @@ +glslc ./idk/shader.glsl.vert -o idk/shader.spv.vert +glslc ./idk/shader.glsl.frag -o idk/shader.spv.frag + +odin run idk/ \ No newline at end of file diff --git a/idk/shader.glsl.frag b/idk/shader.glsl.frag new file mode 100644 index 0000000..1612d8a --- /dev/null +++ b/idk/shader.glsl.frag @@ -0,0 +1,9 @@ +#version 460 + +layout(location=0) out vec4 frag_color; + +layout(location=0) in vec4 color; + +void main() { + frag_color = color; +} \ No newline at end of file diff --git a/idk/shader.glsl.vert b/idk/shader.glsl.vert new file mode 100644 index 0000000..c9c163f --- /dev/null +++ b/idk/shader.glsl.vert @@ -0,0 +1,16 @@ +#version 460 + +layout(set=1, binding=0) uniform UBO { + mat4 mvp; +}; + +layout(location=0) in vec3 position; +layout(location=1) in vec4 color; + +layout(location=0) out vec4 out_color; + +void main() { + gl_Position = mvp * vec4(position, 1); + out_color = color; + +} \ No newline at end of file diff --git a/idk/shader.spv.frag b/idk/shader.spv.frag new file mode 100644 index 0000000000000000000000000000000000000000..fabe49f749338059dc338b0360510a1aa7a910c3 GIT binary patch literal 448 zcmYjNO>4qH6dX)(9|L~pHA0gN$hCYn@g|<1s&xMN^dQAG0xy*225p4Qiv89VDs&lr z#*jMkG~g~^SKy0>LiAu_Yu0NMuWWKW+EewJXEt|5W>Z&YN##B=={hx?E0X*@tqUS5 z>b?;o@4YLxZ#FsPhZ1B$Kg$Ke5Np0$hL*5q{ljBUsX6cb#PItG_bqZW#*}*9M_Ar7 z{yWTi5%G%)mWMdid(6D{e_dFf;+{F7{M1WJXTHfq%&%nC^NJz;l2!E;{^98t#xF2W BE93wG literal 0 HcmV?d00001 diff --git a/idk/shader.spv.vert b/idk/shader.spv.vert new file mode 100644 index 0000000000000000000000000000000000000000..5ce92d832b13dd0c0bfd879250e8bbb000c34c87 GIT binary patch literal 1324 zcmYk4-EPxB5QVo%+?JNMw52~l2~Gp$0;%c+AS48cKbeb2DXPk?RXG+~$+fM-si0SV zg}xUL#0?VXTib~@n#|6bGqY!Q-EQ@lj9DCznlZVY5`| zlH)Uj!);oe*=wClO_@Wpa-S2Mf3cI|B8}o%jvF91O_Ev8)11uCV!tVW{%NS1j@yG@ zUga*gO`zrJ$6e*MunObuvS|@US(1$%)^T@P$AS7YFB-q82YXiSYpi@nUU<%<1|Boi zJoaGRDZSBN_MU6^guy4r(uZU2$(k6tS%Eq+W6wqN_)sxg$TN4hk*Bs9^X(AB-W3a;X4ujHvb-;2y{KZoR{9+%UXh0n*42*r>mKAVU%b2W_5Hl9 zV%`~ybJiWN?8`aC8#30uV(y5(=<2G9`var*mOQh7(VLii10#RG+QVnYJ$W>9Hu-(| z1sOQ?#M~ Mat4 { + return Mat4{1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1} +} + +mat4_mul :: proc(a, b: Mat4) -> Mat4 { + r := Mat4{} + for c in 0 ..< 4 { + for row in 0 ..< 4 { + r[c*4+row] = + a[0*4+row] * b[c*4+0] + + a[1*4+row] * b[c*4+1] + + a[2*4+row] * b[c*4+2] + + a[3*4+row] * b[c*4+3] + } + } + return r +} + +vec3_sub :: proc(a, b: Vec3) -> Vec3 { + return Vec3{a[0] - b[0], a[1] - b[1], a[2] - b[2]} +} + +vec3_add :: proc(a, b: Vec3) -> Vec3 { + return Vec3{a[0] + b[0], a[1] + b[1], a[2] + b[2]} +} + +vec3_scale :: proc(v: Vec3, s: f32) -> Vec3 { + return Vec3{v[0] * s, v[1] * s, v[2] * s} +} + +vec3_dot :: proc(a, b: Vec3) -> f32 { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +} + +vec3_cross :: proc(a, b: Vec3) -> Vec3 { + return Vec3{ + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0], + } +} + +vec3_normalize :: proc(v: Vec3) -> Vec3 { + len2 := vec3_dot(v, v) + if len2 <= 0.000001 { + return Vec3{0, 0, 0} + } + inv := 1.0 / math.sqrt(len2) + return vec3_scale(v, inv) +} + +rotor_identity :: proc() -> Rotor { + return Rotor{1, 0, 0, 0} +} + +rotor_normalize :: proc(r: Rotor) -> Rotor { + len2 := r.s*r.s + r.x*r.x + r.y*r.y + r.z*r.z + if len2 <= 0.000001 { + return rotor_identity() + } + inv := 1.0 / math.sqrt(len2) + return Rotor{r.s * inv, r.x * inv, r.y * inv, r.z * inv} +} + +rotor_mul :: proc(a, b: Rotor) -> Rotor { + return Rotor{ + a.s*b.s - a.x*b.x - a.y*b.y - a.z*b.z, + a.s*b.x + a.x*b.s + a.y*b.z - a.z*b.y, + a.s*b.y - a.x*b.z + a.y*b.s + a.z*b.x, + a.s*b.z + a.x*b.y - a.y*b.x + a.z*b.s, + } +} + +rotor_from_axis_angle :: proc(axis: Vec3, angle: f32) -> Rotor { + a := vec3_normalize(axis) + h := angle * 0.5 + c := math.cos(h) + s := math.sin(h) + return Rotor{c, a[0] * s, a[1] * s, a[2] * s} +} + +rotate_vec3 :: proc(r: Rotor, v: Vec3) -> Vec3 { + u := Vec3{r.x, r.y, r.z} + t := vec3_scale(vec3_cross(u, v), 2.0) + return vec3_add(v, vec3_add(vec3_scale(t, r.s), vec3_cross(u, t))) +} + +mat4_translate :: proc(p: Vec3) -> Mat4 { + m := mat4_identity() + m[12] = p[0] + m[13] = p[1] + m[14] = p[2] + return m +} + +mat4_scale_uniform :: proc(s: f32) -> Mat4 { + m := mat4_identity() + m[0] = s + m[5] = s + m[10] = s + return m +} + +mat4_perspective :: proc(fov_deg, aspect, z_near, z_far: f32) -> Mat4 { + f := 1.0 / math.tan((fov_deg * 0.5) * (math.PI / 180.0)) + m := Mat4{} + m[0] = f / aspect + m[5] = f + m[10] = (z_far + z_near) / (z_near - z_far) + m[11] = -1 + m[14] = (2.0 * z_far * z_near) / (z_near - z_far) + return m +} + +mat4_look_at :: proc(eye, target, up: Vec3) -> Mat4 { + fwd := vec3_normalize(vec3_sub(target, eye)) + right := vec3_normalize(vec3_cross(fwd, up)) + real_up := vec3_cross(right, fwd) + + m := mat4_identity() + m[0] = right[0] + m[1] = real_up[0] + m[2] = -fwd[0] + m[4] = right[1] + m[5] = real_up[1] + m[6] = -fwd[1] + m[8] = right[2] + m[9] = real_up[2] + m[10] = -fwd[2] + m[12] = -vec3_dot(right, eye) + m[13] = -vec3_dot(real_up, eye) + m[14] = vec3_dot(fwd, eye) + return m +} + +camera_forward :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 0, -1})) +} + +camera_right :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{1, 0, 0})) +} + +camera_up :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 1, 0})) +} + +camera_position :: proc(c: Camera) -> Vec3 { + return vec3_sub(c.center, vec3_scale(camera_forward(c), c.distance)) +} + +read_file_or_fail :: proc(path: string) -> []u8 { + file_bytes, err := os.read_entire_file(path, context.allocator) + if err != nil { + fmt.println("failed to read file:", path, "error:", err) + return nil + } + return file_bytes +} + +create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage) -> ^sdl.GPUShader { + bytes := read_file_or_fail(path) + if len(bytes) == 0 { + return nil + } + + ci := sdl.GPUShaderCreateInfo{ + code_size = uint(len(bytes)), + code = raw_data(bytes), + entrypoint = cstring("main"), + format = sdl.GPUShaderFormat{.SPIRV}, + stage = stage, + num_samplers = 0, + num_storage_textures = 0, + num_storage_buffers = 0, + num_uniform_buffers = 1, + props = 0, + } + + shader := sdl.CreateGPUShader(device, ci) + delete(bytes) + if shader == nil { + fmt.println("CreateGPUShader failed:", path, "error:", sdl.GetError()) + } + return shader +} + +loadModel :: proc(device: ^sdl.GPUDevice, model: ^RSM_Model, vertices: ^[dynamic]Vertex, indices: ^[dynamic]u16) -> (vbuf: ^sdl.GPUBuffer, ibuf: ^sdl.GPUBuffer) { + clear(vertices) + clear(indices) + for node in model.nodes { + fmt.println(node.offset_matrix) + fmt.println(node.translation1) + fmt.println(node.translation2) + fmt.println(node.scale) + fmt.println(node.rotation_angle) + fmt.println(node.rotation_axis) + fmt.println(node.rotation_keyframes[:]) + + for face in node.faces { + for i in 0..<3 { + v_idx := face.vertex_position_indices[i] + t_idx := face.texture_coordinate_indices[i] + pos := node.vertex_positions[v_idx] + uv := node.texture_coordinates[t_idx].coordinates + + append(vertices, Vertex{ + pos = pos + // uv = uv, + }) + + append(indices, u16(len(vertices)-1)) + } + } + } + + vbuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.VERTEX}, + size = u32(len(vertices) * size_of(vertices[0])), + props = 0, + } + vbuf = sdl.CreateGPUBuffer(device, vbuf_info) + if vbuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + ibuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.INDEX}, + size = u32(len(indices) * size_of(indices[0])), + props = 0, + } + ibuf = sdl.CreateGPUBuffer(device, ibuf_info) + if ibuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + + { + setup_cmd := sdl.AcquireGPUCommandBuffer(device) + if setup_cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + return + } + if !copy_data_to_gpu(device, setup_cmd, vertices[:], vbuf, indices[:], ibuf) { + _ = sdl.CancelGPUCommandBuffer(setup_cmd) + return + } + if !sdl.SubmitGPUCommandBuffer(setup_cmd) { + fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) + return + } + } + + return vbuf, ibuf +} + +copy_data_to_gpu :: proc(device: ^sdl.GPUDevice, cmd: ^sdl.GPUCommandBuffer, vertices: []Vertex, vert_buf: ^sdl.GPUBuffer, indices: []u16, ind_buf: ^sdl.GPUBuffer) -> bool { + vert_byte_count := len(vertices) * size_of(vertices[0]) + ind_byte_count := len(indices) * size_of(indices[0]) + tb_info := sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(vert_byte_count + ind_byte_count), + props = 0, + } + tb := sdl.CreateGPUTransferBuffer(device, tb_info) + if tb == nil { + fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError()) + return false + } + defer sdl.ReleaseGPUTransferBuffer(device, tb) + + mapped := transmute([^]byte)sdl.MapGPUTransferBuffer(device, tb, false) + if mapped == nil { + fmt.println("MapGPUTransferBuffer failed:", sdl.GetError()) + return false + } + mem.copy_non_overlapping(mapped, raw_data(vertices), vert_byte_count) + mem.copy_non_overlapping(mapped[vert_byte_count:], raw_data(indices), ind_byte_count) + sdl.UnmapGPUTransferBuffer(device, tb) + + cp := sdl.BeginGPUCopyPass(cmd) + if cp == nil { + fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) + return false + } + + vert_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0} + vert_dst := sdl.GPUBufferRegion{buffer = vert_buf, offset = 0, size = u32(vert_byte_count)} + sdl.UploadToGPUBuffer(cp, vert_src, vert_dst, false) + + ind_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = u32(vert_byte_count)} + ind_dst := sdl.GPUBufferRegion{buffer = ind_buf, offset = 0, size = u32(ind_byte_count)} + sdl.UploadToGPUBuffer(cp, ind_src, ind_dst, false) + sdl.EndGPUCopyPass(cp) + return true +} + +GPU_Vertex :: struct { + position: [3]f32, + uv: [2]f32, +} + +main :: proc() { + if !sdl.Init(sdl.INIT_VIDEO) { + fmt.println("SDL init failed:", sdl.GetError()) + return + } + defer sdl.Quit() + + window := sdl.CreateWindow("Odin + SDL3 GPU Cubes", 1280, 720, sdl.WINDOW_RESIZABLE) + if window == nil { + fmt.println("window create failed:", sdl.GetError()) + return + } + defer sdl.DestroyWindow(window) + + device := sdl.CreateGPUDevice(sdl.GPUShaderFormat{.SPIRV}, false, cstring("vulkan")) + if device == nil { + fmt.println("CreateGPUDevice failed:", sdl.GetError()) + return + } + defer sdl.DestroyGPUDevice(device) + + if !sdl.ClaimWindowForGPUDevice(device, window) { + fmt.println("ClaimWindowForGPUDevice failed:", sdl.GetError()) + return + } + defer sdl.ReleaseWindowFromGPUDevice(device, window) + + driver_name := sdl.GetGPUDeviceDriver(device) + fmt.println("SDL GPU driver:", string(driver_name)) + if string(driver_name) != "vulkan" { + fmt.println("expected Vulkan driver, got:", string(driver_name)) + return + } + + vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX) + if vertex_shader == nil { + return + } + defer sdl.ReleaseGPUShader(device, vertex_shader) + + fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT) + if fragment_shader == nil { + return + } + defer sdl.ReleaseGPUShader(device, fragment_shader) + + color_format := sdl.GetGPUSwapchainTextureFormat(device, window) + if color_format == .INVALID { + fmt.println("invalid swapchain format:", sdl.GetError()) + return + } + + vb_descs := [1]sdl.GPUVertexBufferDescription{ + {slot = 0, pitch = size_of(Vertex), input_rate = .VERTEX, instance_step_rate = 0}, + } + vattrs := [1]sdl.GPUVertexAttribute{ + {location = 0, buffer_slot = 0, format = .FLOAT3, offset = 0}, + } + + blend := sdl.GPUColorTargetBlendState{ + src_color_blendfactor = .ONE, + dst_color_blendfactor = .ZERO, + color_blend_op = .ADD, + src_alpha_blendfactor = .ONE, + dst_alpha_blendfactor = .ZERO, + alpha_blend_op = .ADD, + color_write_mask = sdl.GPUColorComponentFlags{.R, .G, .B, .A}, + enable_blend = false, + enable_color_write_mask = true, + } + color_targets := [1]sdl.GPUColorTargetDescription{ + {format = color_format, blend_state = blend}, + } + + pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{ + vertex_shader = vertex_shader, + fragment_shader = fragment_shader, + vertex_input_state = sdl.GPUVertexInputState{ + vertex_buffer_descriptions = &vb_descs[0], + num_vertex_buffers = 1, + vertex_attributes = &vattrs[0], + num_vertex_attributes = 1, + }, + primitive_type = .TRIANGLELIST, + rasterizer_state = sdl.GPURasterizerState{ + fill_mode = .FILL, + cull_mode = .BACK, + front_face = .COUNTER_CLOCKWISE, + depth_bias_constant_factor = 0, + depth_bias_clamp = 0, + depth_bias_slope_factor = 0, + enable_depth_bias = false, + enable_depth_clip = true, + }, + multisample_state = sdl.GPUMultisampleState{ + sample_count = ._1, + sample_mask = 0, + enable_mask = false, + enable_alpha_to_coverage = false, + }, + depth_stencil_state = sdl.GPUDepthStencilState{ + compare_op = .LESS, + back_stencil_state = sdl.GPUStencilOpState{fail_op = .KEEP, pass_op = .KEEP, depth_fail_op = .KEEP, compare_op = .ALWAYS}, + front_stencil_state = sdl.GPUStencilOpState{fail_op = .KEEP, pass_op = .KEEP, depth_fail_op = .KEEP, compare_op = .ALWAYS}, + compare_mask = 0, + write_mask = 0, + enable_depth_test = false, + enable_depth_write = false, + enable_stencil_test = false, + }, + target_info = sdl.GPUGraphicsPipelineTargetInfo{ + color_target_descriptions = &color_targets[0], + num_color_targets = 1, + depth_stencil_format = .INVALID, + has_depth_stencil_target = false, + }, + props = 0, + } + + pipeline := sdl.CreateGPUGraphicsPipeline(device, pipeline_ci) + if pipeline == nil { + fmt.println("CreateGPUGraphicsPipeline failed:", sdl.GetError()) + return + } + defer sdl.ReleaseGPUGraphicsPipeline(device, pipeline) + + + pipeline_create_info := sdl.GPUGraphicsPipelineCreateInfo{ + primitive_type = .TRIANGLELIST, + rasterizer_state = { + fill_mode = .LINE, + cull_mode = .NONE, + } + } + + models: [dynamic]RSM_Model + + walk("/home/pavel/neoragnarok_backup/kro_client/data", &models) + + vertices: [dynamic]Vertex + indices: [dynamic]u16 + + vbuf, ibuf := loadModel(device, &models[modelIndex], &vertices, &indices) + defer sdl.ReleaseGPUBuffer(device, ibuf) + defer sdl.ReleaseGPUBuffer(device, vbuf) + + init_yaw := rotor_from_axis_angle(Vec3{0, 1, 0}, 0.9) + init_right := rotate_vec3(init_yaw, Vec3{1, 0, 0}) + init_pitch := rotor_from_axis_angle(init_right, -0.45) + + camera := Camera{ + center = Vec3{0, 0.5, 0}, + distance = 8, + orientation = rotor_normalize(rotor_mul(init_pitch, init_yaw)), + fov_deg = 60, + } + + cubes := []Cube_Instance{ + {pos = Vec3{0, 0, 0}, scale = 1.0, color = Vec3{0.95, 0.45, 0.20}}, + {pos = Vec3{2, 0.5, -1}, scale = 0.8, color = Vec3{0.20, 0.70, 0.95}}, + {pos = Vec3{-2, -0.2, 1.5}, scale = 1.2, color = Vec3{0.85, 0.85, 0.30}}, + {pos = Vec3{1.0, 1.4, 2.0}, scale = 0.6, color = Vec3{0.40, 0.95, 0.60}}, + } + + running := true + last := time.tick_now() + left_down := false + middle_down := false + debug := Debug_State{} + + for running { + now := time.tick_now() + dt := f32(time.duration_seconds(time.tick_diff(last, now))) + last = now + + event: sdl.Event + for sdl.PollEvent(&event) { + #partial switch event.type { + case .QUIT: + running = false + case .MOUSE_BUTTON_DOWN: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = true + case sdl.BUTTON_MIDDLE: + middle_down = true + } + case .MOUSE_BUTTON_UP: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = false + case sdl.BUTTON_MIDDLE: + middle_down = false + } + case .MOUSE_WHEEL: + camera.distance = math.clamp(camera.distance - event.wheel.y * 0.7, 1.5, 80.0) + case .KEY_DOWN: + if event.key.key == sdl.K_ESCAPE { + running = false + } else if event.key.scancode == sdl.Scancode.F1 && !event.key.repeat { + debug.enabled = !debug.enabled + fmt.println("debug:", debug.enabled) + } else if event.key.key == sdl.K_N { + modelIndex += 1 + sdl.ReleaseGPUBuffer(device, ibuf) + sdl.ReleaseGPUBuffer(device, vbuf) + vbuf, ibuf = loadModel(device, &models[modelIndex], &vertices, &indices) + } + } + } + + x_rel: f32 = 0 + y_rel: f32 = 0 + _ = sdl.GetRelativeMouseState(&x_rel, &y_rel) + + if left_down { + yaw_r := rotor_from_axis_angle(Vec3{0, 1, 0}, -x_rel * 0.006) + q1 := rotor_normalize(rotor_mul(yaw_r, camera.orientation)) + + right_axis := vec3_normalize(rotate_vec3(q1, Vec3{1, 0, 0})) + pitch_r := rotor_from_axis_angle(right_axis, -y_rel * 0.006) + q2 := rotor_normalize(rotor_mul(pitch_r, q1)) + + fwd2 := camera_forward(Camera{center = camera.center, distance = camera.distance, orientation = q2, fov_deg = camera.fov_deg}) + if math.abs(vec3_dot(fwd2, Vec3{0, 1, 0})) < 0.98 { + camera.orientation = q2 + } else { + camera.orientation = q1 + } + } + + if middle_down { + right := camera_right(camera) + up := camera_up(camera) + pan_speed := 0.008 * camera.distance + camera.center = vec3_add(camera.center, vec3_scale(right, -x_rel * pan_speed)) + camera.center = vec3_add(camera.center, vec3_scale(up, y_rel * pan_speed)) + } + + keys := sdl.GetKeyboardState(nil) + forward := camera_forward(camera) + right := camera_right(camera) + up := camera_up(camera) + move_speed := camera.distance * dt * 1.4 + + if keys[sdl.Scancode.W] { + camera.center = vec3_add(camera.center, vec3_scale(forward, move_speed)) + } + if keys[sdl.Scancode.S] { + camera.center = vec3_add(camera.center, vec3_scale(forward, -move_speed)) + } + if keys[sdl.Scancode.A] { + camera.center = vec3_add(camera.center, vec3_scale(right, -move_speed)) + } + if keys[sdl.Scancode.D] { + camera.center = vec3_add(camera.center, vec3_scale(right, move_speed)) + } + + if debug.enabled { + debug.accum += dt + if debug.accum >= 0.2 { + debug.accum = 0 + eye := camera_position(camera) + fmt.println("cam eye:", eye, + "center:", camera.center, + "fwd:", forward, + "right:", right, + "up:", up) + } + } + + w: c.int = 0 + h: c.int = 0 + sdl.GetWindowSize(window, &w, &h) + if w <= 0 || h <= 0 { + continue + } + + cmd := sdl.AcquireGPUCommandBuffer(device) + if cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + break + } + + swap_tex: ^sdl.GPUTexture + swap_w: sdl.Uint32 = 0 + swap_h: sdl.Uint32 = 0 + if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd, window, &swap_tex, &swap_w, &swap_h) { + fmt.println("WaitAndAcquireGPUSwapchainTexture failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + if swap_tex == nil { + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + continue + } + + clear := sdl.FColor{0.08, 0.09, 0.12, 1.0} + cti := sdl.GPUColorTargetInfo{ + texture = swap_tex, + mip_level = 0, + layer_or_depth_plane = 0, + clear_color = clear, + load_op = .CLEAR, + store_op = .STORE, + resolve_texture = nil, + resolve_mip_level = 0, + resolve_layer = 0, + cycle = false, + cycle_resolve_texture = false, + } + + rp := sdl.BeginGPURenderPass(cmd, &cti, 1, nil) + if rp == nil { + fmt.println("BeginGPURenderPass failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + vp := sdl.GPUViewport{x = 0, y = 0, w = f32(swap_w), h = f32(swap_h), min_depth = 0, max_depth = 1} + sdl.SetGPUViewport(rp, vp) + sdl.BindGPUGraphicsPipeline(rp, pipeline) + vb_binding := sdl.GPUBufferBinding{buffer = vbuf, offset = 0} + sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1) + ib_binding := sdl.GPUBufferBinding{buffer = ibuf, offset = 0} + sdl.BindGPUIndexBuffer(rp, ib_binding, ._16BIT) + + aspect := f32(swap_w) / f32(swap_h) + proj := linalg.matrix4_perspective(camera.fov_deg, aspect, 0.1, 300.0) + view := linalg.matrix4_look_at(camera_position(camera), camera.center, camera_up(camera)) + vp_mat := proj * view + + for node in models[modelIndex].nodes { + rotation := linalg.quaternion_angle_axis(node.rotation_angle, node.rotation_axis) + if (len(node.rotation_keyframes) > 0) { + q := node.rotation_keyframes[0].quaternion + rotation = quaternion(imag=q[0], jmag=q[1], kmag=q[2], real=q[3]) + } + + rotation = linalg.quaternion_normalize(rotation) + + r := linalg.matrix3_from_quaternion(rotation) + + s := matrix[3,3]f32{ + node.scale[0], 0, 0 , + 0, node.scale[1], 0 , + 0, 0, node.scale[2], + } + + a3 := r * node.offset_matrix * s + + t := node.translation1 + node.translation2 + + // 4x4 affine matrix + model := matrix[4,4]f32{ + a3[0][0], a3[0][1], a3[0][2], t[0], + a3[1][0], a3[1][1], a3[1][2], t[1], + a3[2][0], a3[2][1], a3[2][2], t[2], + 0, 0, 0, 1 , + } + + + // append(vertices, Vertex{ + // pos = linalg.matrix3_from_quaternion(rotation) * node.offset_matrix * (node.scale * pos) + node.translation1 + node.translation2 + // // uv = uv, + // }) + + // model := mat4_mul(node., mat4_scale_uniform(1)) + + pc := Push_Constants{} + pc.mvp = vp_mat * model + pc.color = [4]f32{1, 0, 0, 1.0} + sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) + sdl.PushGPUFragmentUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) + sdl.DrawGPUIndexedPrimitives(rp, u32(len(indices)), 1, 0, 0, 0) + } + + sdl.EndGPURenderPass(rp) + + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + } + + _ = sdl.WaitForGPUIdle(device) +} + +walk :: proc(dir: string, models: ^[dynamic]RSM_Model) { + f, _ := os.open(dir) + entries, ok := os.read_dir(f, 0, context.allocator) + for entry in entries { + path, _ := filepath.join({dir, entry.name}) + if entry.type == .Directory { + walk(path, models) + } + if strings.contains(entry.name, ".rsm") { + data, _ := os.read_entire_file(path, context.allocator) + parsed, err := parse_rsm(data) + if err != nil { + fmt.printfln("%v", err) + } else { + append_elem(models, parsed) + } + } + } +} diff --git a/shaders/cubes.frag.glsl b/shaders/cubes.frag.glsl index 1b2abfd..3800ac2 100644 --- a/shaders/cubes.frag.glsl +++ b/shaders/cubes.frag.glsl @@ -2,11 +2,16 @@ layout(location = 0) out vec4 out_color; -layout(set = 3, binding = 0) uniform FragmentUniforms { - mat4 u_mvp; - vec4 u_color; -}; +layout(location = 0) in vec2 uv; + +layout(set = 2, binding = 0) uniform sampler2D tex_sampler; void main() { - out_color = u_color; + vec4 c = texture(tex_sampler, uv); + float tol = 0.02; + if (distance(c.rgb, vec3(1.0, 0.0, 1.0)) < tol || distance(c.rgb, vec3(0.0, 0.0, 0.0)) < tol) { + discard; + } + + out_color = c; } diff --git a/shaders/cubes.frag.spv b/shaders/cubes.frag.spv index 883b7eefe616af933587447024101f1d4c2dd57c..5d3bfc4f53d477ed9e0ce8a996b3051d2f7e5ccd 100644 GIT binary patch literal 1244 zcmY+E$!-%t5QaN(oCIelAt8jtPS_JUAOS*RJ0RqXB_B9&97XmVMw-}`mk4gW3l|=N zGcUoDAQlCQ?=x;`=;%*X{rz`!RrScF*@J};X2Mcf2@k`+wGn1v0^Iz>Hkxmn_2H;f zfAsi)iq%k>2-U2Gg)oN|rzct0jSX-N*3rpx0bYr$A$;Y~T&RRbGikJ1jpm!A-R~#E z(T6-u@~oRCootY{N7-4bZn2=ty6t@2DGJIciuabe;{tcIL7Bf5>?nPo3{Ot`c{+e8 zO==sTxnByG-0$TRt@ZZCqom!-djtFinLS?vt{Cs*+lR4zUI#8?bHsQPmAV@DI4%8c zym@RD?4Wlp#`aL<{~}gS?9xdE>pmmh|zM`1mTuS0`}@M<~@_18U^8 zk-t6Voj)Oa!+_Y`*DUYxt}m)tXgO4HxbX;lkaZ)f_-PT3Y?R%v$zPH z!#lF5+XQOlUvh)_Xz?)_RvwJ2Sr*xz|9gb1|2DH-P!S zP=~Xw0`>Br(a6<675t!<1FY*ed-D50&Uxs^8gJqkYrI7)CtNe2SmT{3XN_uoqgdlz z{9=vwh}FygB1^nq`<a0pHrE>TyA+BG6+skTXu1e=A+AG7qXhCOXTh#3gZJ$J(xGb z9Se7~kCYYVMS;5ork?rH;(w#R;fxo-8@|QxZ%_nMs+Qfq{{Mn}L@>b0V*)JSPJPumEvxVrCwQ%?remK&%SH>Kh}28Kqc( z@*rM*X-Rx(83O|=12cmfP3%Q*Qd^L-UtsEHa-Q9GbMkZutF KFHoNa5CZ^=(icqt delta 71 zcmeyvb%v9dnMs+Qfq{{Mn}L@>c_Oc=IFJVfEI^!_n3)G+^8&Ht#>8O8$rl)(Y`(%2 R!8o~w bool { + return int(v.major) > major || (int(v.major) == major && int(v.minor) >= minor) +} + +version_below :: #force_inline proc(v: Version, major, minor: int) -> bool { + return !version_ge(v, major, minor) +} + + +normalize_path :: proc(p: string) -> string { + // Byte-preserving normalization for legacy CP949/EUC-KR path bytes. + // Only ASCII transforms are applied. + b := transmute([]u8)p + out := make([]u8, len(b), context.allocator) + for i in 0..= 'A' && ch <= 'Z' { + out[i] = ch + ('a' - 'A') + } else { + out[i] = ch + } + } + return string(out) +} + +latin1_bytes_to_utf8_string :: proc(src: []u8, allocator := context.allocator) -> string { + // 0x00..0x7F => 1 byte UTF-8, 0x80..0xFF => 2 bytes UTF-8 + out := make([dynamic]u8, 0, len(src) * 2, allocator) + for b in src { + if b < 0x80 { + append(&out, b) + } else { + append(&out, 0xC0 | (b >> 6)) + append(&out, 0x80 | (b & 0x3F)) + } + } + return string(out[:]) +} + +read_u8 :: proc(r: ^RSM_Reader) -> (u8, bool) { + if r.at+1 > len(r.data) { return 0, false } + v := r.data[r.at] + r.at += 1 + return v, true +} + +read_u16 :: proc(r: ^RSM_Reader) -> (u16, bool) { + if r.at+2 > len(r.data) { return 0, false } + v := u16(r.data[r.at]) | (u16(r.data[r.at+1]) << 8) + r.at += 2 + return v, true +} + +read_u32 :: proc(r: ^RSM_Reader) -> (u32, bool) { + if r.at+4 > len(r.data) { return 0, false } + v := u32(r.data[r.at]) | (u32(r.data[r.at+1]) << 8) | (u32(r.data[r.at+2]) << 16) | (u32(r.data[r.at+3]) << 24) + r.at += 4 + return v, true +} + +read_i32 :: proc(r: ^RSM_Reader) -> (i32, bool) { + v, ok := read_u32(r) + return i32(v), ok +} + +read_f32 :: proc(r: ^RSM_Reader) -> (f32, bool) { + bits, ok := read_u32(r) + if !ok { return 0, false } + return transmute(f32)bits, true +} + +read_fixed_string_clean :: proc(r: ^RSM_Reader, n: int) -> (string, bool) { + s, ok := read_fixed_string(r, n) + if !ok { return "", false } + return strings.trim_space(s), true +} + +read_fixed_string :: proc(r: ^RSM_Reader, n: int) -> (string, bool) { + if r.at+n > len(r.data) { return "", false } + buf := r.data[r.at:r.at+n] + r.at += n + end := 0 + for end < len(buf) && buf[end] != 0 { + end += 1 + } + return latin1_bytes_to_utf8_string(buf[:end]), true +} + +read_model_string :: proc(r: ^RSM_Reader, version: Version, fixed_len: int) -> (string, bool) { + if version_ge(version, 2, 2) { + n, ok := read_u32(r) + if !ok || int(n) < 0 || r.at+int(n) > len(r.data) { return "", false } + s := latin1_bytes_to_utf8_string(r.data[r.at:r.at+int(n)]) + r.at += int(n) + return s, true + } + return read_fixed_string(r, fixed_len) +} + +read_vec3 :: proc(r: ^RSM_Reader) -> ([3]f32, bool) { + x, ok1 := read_f32(r); y, ok2 := read_f32(r); z, ok3 := read_f32(r) + return [3]f32{x,y,z}, ok1 && ok2 && ok3 +} \ No newline at end of file diff --git a/src/format/gnd_format.odin b/src/format/gnd_format.odin new file mode 100644 index 0000000..0434563 --- /dev/null +++ b/src/format/gnd_format.odin @@ -0,0 +1,385 @@ +package format + +import "core:strings" + +RSW_Parse_Error :: enum { + None, + UnexpectedEOF, + InvalidSignature, +} + +RSW_WaterSettings :: struct { + water_level: f32, + water_type: i32, + wave_height: f32, + wave_speed: f32, + wave_pitch: f32, + texture_cycling_interval: u32, +} + +RSW_LightSettings :: struct { + light_longitude: i32, + light_latitude: i32, + diffuse_color: [3]f32, + ambient_color: [3]f32, + shadow_map_alpha: f32, +} + +RSW_Transform :: struct { + position: [3]f32, + rotation_deg: [3]f32, + scale: [3]f32, +} + +RSW_Object :: struct { + name: string, + model_name: string, + node_name: string, + transform: RSW_Transform, +} + +RSW_LightSource :: struct { + name: string, + position: [3]f32, + color: [3]f32, + range: f32, +} + +RSW_SoundSource :: struct { + name: string, + sound_file: string, + position: [3]f32, + volume: f32, + width: u32, + height: u32, + range: f32, + cycle: f32, +} + +RSW_EffectSource :: struct { + name: string, + position: [3]f32, + effect_type: u32, + emit_speed: f32, + param0: f32, + param1: f32, + param2: f32, + param3: f32, +} + +RSW_Data :: struct { + version: Version, + build_version: u32, + ground_file: string, + gat_file: string, + water: RSW_WaterSettings, + light: RSW_LightSettings, + objects: [dynamic]RSW_Object, + light_sources: [dynamic]RSW_LightSource, + sound_sources: [dynamic]RSW_SoundSource, + effect_sources: [dynamic]RSW_EffectSource, +} + +read_vec3f :: proc(r: ^RSM_Reader) -> ([3]f32, bool) { + x, ok1 := read_f32(r) + y, ok2 := read_f32(r) + z, ok3 := read_f32(r) + return [3]f32{x,y,z}, ok1 && ok2 && ok3 +} + +parse_rsw :: proc(data: []u8) -> (RSW_Data, RSW_Parse_Error) { + out := RSW_Data{ + objects = make([dynamic]RSW_Object), + light_sources = make([dynamic]RSW_LightSource), + sound_sources = make([dynamic]RSW_SoundSource), + effect_sources = make([dynamic]RSW_EffectSource), + } + + r := RSM_Reader{data = data} + sig, ok := read_fixed_string(&r, 4) + resources_amount: u32 + rtype: i32 + if !ok || sig != "GRSW" { + return out, .InvalidSignature + } + + maj, ok1 := read_u8(&r) + min, ok2 := read_u8(&r) + if !(ok1 && ok2) { return out, .UnexpectedEOF } + out.version = Version{maj, min} + + if version_ge(out.version, 2, 5) { + out.build_version, ok = read_u32(&r) + if !ok { return out, .UnexpectedEOF } + } + if version_ge(out.version, 2, 2) { + _, ok = read_u8(&r) + if !ok { return out, .UnexpectedEOF } + } + + _, ok = read_fixed_string_clean(&r, 40) + if !ok { return out, .UnexpectedEOF } + out.ground_file, ok = read_fixed_string_clean(&r, 40) + if !ok { return out, .UnexpectedEOF } + out.gat_file, ok = read_fixed_string_clean(&r, 40) + if !ok { return out, .UnexpectedEOF } + if version_ge(out.version, 1, 4) { + _, ok = read_fixed_string_clean(&r, 40) + if !ok { return out, .UnexpectedEOF } + } + + if !version_ge(out.version, 2, 6) { + if version_ge(out.version, 1, 3) { + out.water.water_level, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + } + if version_ge(out.version, 1, 8) { + out.water.water_type, ok = read_i32(&r); if !ok { return out, .UnexpectedEOF } + out.water.wave_height, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + out.water.wave_speed, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + out.water.wave_pitch, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + } + if version_ge(out.version, 1, 9) { + out.water.texture_cycling_interval, ok = read_u32(&r); if !ok { return out, .UnexpectedEOF } + } + } + + if version_ge(out.version, 1, 5) { + out.light.light_longitude, ok = read_i32(&r); if !ok { return out, .UnexpectedEOF } + out.light.light_latitude, ok = read_i32(&r); if !ok { return out, .UnexpectedEOF } + out.light.diffuse_color, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + out.light.ambient_color, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + } + if version_ge(out.version, 1, 7) { + out.light.shadow_map_alpha, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + } + + if version_ge(out.version, 1, 6) { + for _ in 0..<4 { + _, ok = read_i32(&r) + if !ok { return out, .UnexpectedEOF } + } + } + + resources_amount, ok = read_u32(&r) + if !ok { return out, .UnexpectedEOF } + + for i := 0; i < int(resources_amount); i += 1 { + rtype, ok = read_i32(&r) + if !ok { return out, .UnexpectedEOF } + + switch rtype { + case 1: + obj := RSW_Object{} + if version_ge(out.version, 1, 3) { + obj.name, ok = read_fixed_string_clean(&r, 40); if !ok { return out, .UnexpectedEOF } + _, ok = read_i32(&r); if !ok { return out, .UnexpectedEOF } + _, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + _, ok = read_i32(&r); if !ok { return out, .UnexpectedEOF } + } + if version_ge(out.version, 2, 6) && out.build_version >= 186 { + _, ok = read_u8(&r) + if !ok { return out, .UnexpectedEOF } + } + obj.model_name, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + obj.node_name, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + obj.model_name = normalize_path(obj.model_name) + obj.transform.position, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + obj.transform.rotation_deg, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + obj.transform.scale, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + obj.transform.position[1] = -obj.transform.position[1] + append(&out.objects, obj) + case 2: + l := RSW_LightSource{} + l.name, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + l.position, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + l.position[1] = -l.position[1] + l.color, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + l.range, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + append(&out.light_sources, l) + case 3: + s := RSW_SoundSource{} + s.name, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + s.sound_file, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + s.position, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + s.position[1] = -s.position[1] + s.volume, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + s.width, ok = read_u32(&r); if !ok { return out, .UnexpectedEOF } + s.height, ok = read_u32(&r); if !ok { return out, .UnexpectedEOF } + s.range, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + if version_ge(out.version, 2, 0) { + s.cycle, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + } else { + s.cycle = 4.0 + } + append(&out.sound_sources, s) + case 4: + e := RSW_EffectSource{} + e.name, ok = read_fixed_string_clean(&r, 80); if !ok { return out, .UnexpectedEOF } + e.position, ok = read_vec3f(&r); if !ok { return out, .UnexpectedEOF } + e.position[1] = -e.position[1] + e.effect_type, ok = read_u32(&r); if !ok { return out, .UnexpectedEOF } + e.emit_speed, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + e.param0, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + e.param1, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + e.param2, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + e.param3, ok = read_f32(&r); if !ok { return out, .UnexpectedEOF } + append(&out.effect_sources, e) + case: + return out, .UnexpectedEOF + } + } + + return out, .None +} + +GND_Info :: struct { version: Version, width, height: i32, zoom: f32 } +GAT_Info :: struct { version: Version, width, height: i32 } + +GND_Surface :: struct { + u: [4]f32, + v: [4]f32, + texture_index: i16, + light_map_index: i16, + color_bgra: [4]u8, +} + +GND_Tile :: struct { + h_sw, h_se, h_nw, h_ne: f32, + top_surface_index: i32, + north_surface_index: i32, + east_surface_index: i32, +} + +GND_Data :: struct { + info: GND_Info, + textures: [dynamic]string, + surfaces: [dynamic]GND_Surface, + tiles: [dynamic]GND_Tile, +} + +parse_gnd_info :: proc(data: []u8) -> (GND_Info, RSW_Parse_Error) { + r := RSM_Reader{data = data} + sig, ok := read_fixed_string(&r, 4) + if !ok || sig != "GRGN" { return GND_Info{}, .InvalidSignature } + maj, ok1 := read_u8(&r) + min, ok2 := read_u8(&r) + if !(ok1 && ok2) { return GND_Info{}, .UnexpectedEOF } + w, ok3 := read_i32(&r) + h, ok4 := read_i32(&r) + z, ok5 := read_f32(&r) + if !(ok3 && ok4 && ok5) { return GND_Info{}, .UnexpectedEOF } + return GND_Info{version = Version{maj, min}, width = w, height = h, zoom = z}, .None +} + +parse_gnd :: proc(data: []u8) -> (GND_Data, RSW_Parse_Error) { + out := GND_Data{ + textures = make([dynamic]string), + surfaces = make([dynamic]GND_Surface), + tiles = make([dynamic]GND_Tile), + } + + info, err := parse_gnd_info(data) + if err != .None { + return out, err + } + out.info = info + + r := RSM_Reader{data = data, at = 4 + 2 + 4 + 4 + 4} + texture_count, ok_tc := read_i32(&r) + if !ok_tc { return out, .UnexpectedEOF } + texture_name_length, ok_tnl := read_i32(&r) + if !ok_tnl { return out, .UnexpectedEOF } + + for _ in 0.. len(r.data) { return out, .UnexpectedEOF } + r.at += skip + } else { + skip := int(light_map_count * 16) + if r.at+skip > len(r.data) { return out, .UnexpectedEOF } + r.at += skip + } + + surface_count, ok_sc := read_i32(&r) + if !ok_sc { return out, .UnexpectedEOF } + for _ in 0.. (GAT_Info, RSW_Parse_Error) { + r := RSM_Reader{data = data} + sig, ok := read_fixed_string(&r, 4) + if !ok || sig != "GRAT" { return GAT_Info{}, .InvalidSignature } + maj, ok1 := read_u8(&r) + min, ok2 := read_u8(&r) + if !(ok1 && ok2) { return GAT_Info{}, .UnexpectedEOF } + w, ok3 := read_i32(&r) + h, ok4 := read_i32(&r) + if !(ok3 && ok4) { return GAT_Info{}, .UnexpectedEOF } + return GAT_Info{version = Version{maj, min}, width = w, height = h}, .None +} diff --git a/src/format/rsm_format.odin b/src/format/rsm_format.odin new file mode 100644 index 0000000..411c0f9 --- /dev/null +++ b/src/format/rsm_format.odin @@ -0,0 +1,282 @@ +package format + +import "core:strings" +import "core:fmt" + +RSM_ScaleKeyframe :: struct { frame: i32, scale: [3]f32, _reserved: f32 } +RSM_RotationKeyframe :: struct { frame: i32, quaternion: [4]f32 } +RSM_TranslationKeyframe :: struct { frame: i32, translation: [3]f32, _reserved: f32 } +RSM_TextureFrame :: struct { frame: i32, operation_value: f32 } +RSM_TextureKeyframe :: struct { operation_type: u32, texture_frames: [dynamic]RSM_TextureFrame } +RSM_TexturesKeyframe :: struct { texture_index: u32, texture_keyframes: [dynamic]RSM_TextureKeyframe } + +RSM_Face :: struct { + length: u32, + vertex_position_indices: [3]u16, + texture_coordinate_indices:[3]u16, + texture_index: u16, + padding: u16, + two_sided: i32, + smooth_group: i32, + smooth_group_extra: [dynamic]i32, +} + +RSM_TextureCoordinate :: struct { color: u32, coordinates: [2]f32 } + +RSM_Node :: struct { + node_name: string, + parent_node_name: string, + texture_indices: [dynamic]u32, + texture_names: [dynamic]string, + offset_matrix: matrix[3,3] f32, + translation1: [3]f32, + translation2: [3]f32, + rotation_angle: f32, + rotation_axis: [3]f32, + scale: [3]f32, + vertex_positions: [dynamic][3]f32, + texture_coordinates: [dynamic]RSM_TextureCoordinate, + faces: [dynamic]RSM_Face, + scale_keyframes: [dynamic]RSM_ScaleKeyframe, + rotation_keyframes: [dynamic]RSM_RotationKeyframe, + translation_keyframes: [dynamic]RSM_TranslationKeyframe, + textures_keyframes: [dynamic]RSM_TexturesKeyframe, +} + +RSM_Model :: struct { + version: Version, + animation_length:u32, + shade_type: u32, + alpha: u8, + frames_per_second:f32, + texture_names: [dynamic]string, + root_node_names: [dynamic]string, + nodes: [dynamic]RSM_Node, +} + +RSM_Parse_Error :: enum { + None, + UnexpectedEOF, + InvalidSignature, + InvalidUtf8, +} + +RSM_Reader :: struct { + data: []u8, + at: int, +} + +parse_rsm :: proc(data: []u8) -> (RSM_Model, RSM_Parse_Error) { + model := RSM_Model{} + r := RSM_Reader{data = data} + ok: bool + sig: string + alpha: u8 + fps: f32 + texture_count, root_count, node_count: u32 + count, vp_count, tc_count, face_count, s_count, r_count, t_count, tk_count, key_count, frame_count: u32 + name, root: string + idx: u32 + v: [3]f32 + sg: i32 + + sig, ok = read_fixed_string(&r, 4) + if !ok || sig != "GRSM" { + fmt.printfln("signature is %v") + return model, .InvalidSignature + } + major, ok1 := read_u8(&r) + minor, ok2 := read_u8(&r) + if !(ok1 && ok2) { return model, .UnexpectedEOF } + model.version = Version{major = major, minor = minor} + + animation_length, ok3 := read_u32(&r) + shade_type, ok4 := read_u32(&r) + if !(ok3 && ok4) { return model, .UnexpectedEOF } + model.animation_length = animation_length + model.shade_type = shade_type + + if version_ge(model.version, 1, 4) { + alpha, ok = read_u8(&r) + if !ok { return model, .UnexpectedEOF } + model.alpha = alpha + } + if version_below(model.version, 2, 2) { + if r.at+16 > len(r.data) { return model, .UnexpectedEOF } + r.at += 16 + } + if version_ge(model.version, 2, 2) { + fps, ok = read_f32(&r) + if !ok { return model, .UnexpectedEOF } + model.frames_per_second = fps + } + + if version_below(model.version, 2, 3) { + texture_count, ok = read_u32(&r) + if !ok { return model, .UnexpectedEOF } + for _ in 0.. 0 { + for _ in 0.. int { + for i := 0; i < len(textures); i+=1 { + if textures[i].path == path { + return i + } + } + + index := len(textures) + + c_path := strings.clone_to_cstring(path) + defer delete(c_path) + + img_size: [2]i32 + pixels := stbi.load(c_path, &img_size.x, &img_size.y, nil, 4); + assert(pixels != nil) + + append(textures, TextureHolder{ + pixels = pixels, + w = img_size.x, + h = img_size.y, + path = path, + }) + + return index +} + +uploadTextures :: proc(device: ^sdl.GPUDevice, textures: ^[dynamic]TextureHolder) { + setup_cmd := sdl.AcquireGPUCommandBuffer(device) + if setup_cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + return + } + fmt.println("copying textures to transfer buffer") + for &texture in textures { + texture.texture = sdl.CreateGPUTexture(device, sdl.GPUTextureCreateInfo{ + format = .R8G8B8A8_UNORM, + usage = {.SAMPLER}, + width = u32(texture.w), + height = u32(texture.h), + layer_count_or_depth = 1, + num_levels = 1, + }); assert(texture.texture != nil) + + tb_tex := sdl.CreateGPUTransferBuffer(device, sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(texture.w * texture.h * 4), + props = 0, + }); assert(tb_tex != nil) + + tex_mapped := sdl.MapGPUTransferBuffer(device, tb_tex, false); assert(tex_mapped != nil) + mem.copy_non_overlapping(tex_mapped, texture.pixels, int(texture.w * texture.h * 4)) + sdl.UnmapGPUTransferBuffer(device, tb_tex) + texture.transferBuffer = tb_tex + } + + cp := sdl.BeginGPUCopyPass(setup_cmd) + if cp == nil { + fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) + return + } + for &texture in textures { + fmt.println("transferring textures") + fmt.println("tex ptr:", texture, "w/h:", texture.w, texture.h) + sdl.UploadToGPUTexture(cp, + { + transfer_buffer = texture.transferBuffer, + offset = 0, + pixels_per_row = u32(texture.w), + rows_per_layer = u32(texture.h), + }, + {texture = texture.texture, w = u32(texture.w), h = u32(texture.h), d = 1}, + false, + ) + sdl.ReleaseGPUTransferBuffer(device, texture.transferBuffer) + fmt.println("transferred textures") + } + + sdl.EndGPUCopyPass(cp) + if !sdl.SubmitGPUCommandBuffer(setup_cmd) { + fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) + return + } + +} + + +loadGndModel :: proc(device: ^sdl.GPUDevice, model: ^format.GND_Data, vertices: ^[dynamic]shared.Vertex, indices: ^[dynamic]u32, nodes: ^[dynamic]GndNode, textures: ^[dynamic]TextureHolder) -> (vbuf: ^sdl.GPUBuffer, ibuf: ^sdl.GPUBuffer) { + clear(vertices) + clear(indices) + clear(nodes) + + fmt.println("version ", model.info.version) + + textureMap : [dynamic]int + defer delete(textureMap) + + fmt.printfln("loading model texture %v", model.textures) + for textureName in model.textures { + textureName, _ := strings.replace_all(textureName, "\\", "/") + + full_path, err := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/texture", textureName}); assert(err == nil) + full_path, err = filepath.clean(full_path); assert(err == nil) + + append(&textureMap, loadTexture(full_path, textures)) + } + + width := model.info.width + height := model.info.height + + for node_idx := 0; node_idx < len(model.tiles); node_idx+= 1 { + x := i32(node_idx) % width + y := i32(node_idx) / width + node := &model.tiles[node_idx] + // fmt.println("h_ne", node.h_ne) + // fmt.println("h_nw", node.h_nw) + // fmt.println("h_se", node.h_se) + // fmt.println("h_sw", node.h_sw) + // fmt.println("east_surface_index", node.east_surface_index) + // fmt.println("north_surface_index", node.north_surface_index) + // fmt.println("top_surface_index", node.top_surface_index) + + + + surface := model.surfaces[0] + + if node.top_surface_index != -1 { + surface = model.surfaces[node.top_surface_index] + } + + fmt.println(surface.u, surface.v) + + n := GndNode{ + start = len(indices), + node = node, + texture = textureMap[0], + } + + if surface.texture_index != -1 { + n.texture = textureMap[surface.texture_index] + } + + // fmt.println("top_surface_index", surface.u) + + + offset := len(vertices) + + pos := shared.Vec2{f32(x) - f32(model.info.width)/2.0, f32(y) - f32(model.info.height)/2.0} + + // node.h_ne = 0 + // node.h_nw = 0 + // node.h_se = 0 + // node.h_sw = 0 + + append(vertices, shared.Vertex{ + pos = {pos.x * model.info.zoom,-node.h_nw, -pos.y * model.info.zoom}, + uv = {surface.u[2], surface.v[2]}, + }) + append(vertices, shared.Vertex{ + pos = {(pos.x+1)* model.info.zoom,-node.h_ne,-pos.y* model.info.zoom}, + uv = {surface.u[3], surface.v[3]}, + }) + append(vertices, shared.Vertex{ + pos = {pos.x* model.info.zoom,-node.h_sw,-(pos.y+1)* model.info.zoom}, + uv = {surface.u[1], surface.v[1]}, + }) + append(vertices, shared.Vertex{ + pos = {(pos.x+1)* model.info.zoom,-node.h_se,-(pos.y+1)* model.info.zoom}, + uv = {surface.u[0], surface.v[0]}, + }) + + append(indices, u32(offset)) + append(indices, u32(offset+1)) + append(indices, u32(offset+2)) + + append(indices, u32(offset+1)) + append(indices, u32(offset+3)) + append(indices, u32(offset+2)) + + n.end = len(indices) + + append(nodes, n) + + } + + vbuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.VERTEX}, + size = u32(len(vertices) * size_of(vertices[0])), + props = 0, + } + vbuf = sdl.CreateGPUBuffer(device, vbuf_info) + if vbuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + ibuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.INDEX}, + size = u32(len(indices) * size_of(indices[0])), + props = 0, + } + ibuf = sdl.CreateGPUBuffer(device, ibuf_info) + if ibuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + + { + setup_cmd := sdl.AcquireGPUCommandBuffer(device) + if setup_cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + return + } + + vert_byte_count := len(vertices) * size_of(vertices[0]) + ind_byte_count := len(indices) * size_of(indices[0]) + tb_info := sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(vert_byte_count + ind_byte_count), + props = 0, + } + tb := sdl.CreateGPUTransferBuffer(device, tb_info) + if tb == nil { + fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError()) + return + } + defer sdl.ReleaseGPUTransferBuffer(device, tb) + + mapped := transmute([^]byte)sdl.MapGPUTransferBuffer(device, tb, false) + if mapped == nil { + fmt.println("MapGPUTransferBuffer failed:", sdl.GetError()) + return + } + mem.copy_non_overlapping(mapped, raw_data(vertices[:]), vert_byte_count) + mem.copy_non_overlapping(mapped[vert_byte_count:], raw_data(indices[:]), ind_byte_count) + sdl.UnmapGPUTransferBuffer(device, tb) + + cp := sdl.BeginGPUCopyPass(setup_cmd) + if cp == nil { + fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) + return + } + + vert_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0} + vert_dst := sdl.GPUBufferRegion{buffer = vbuf, offset = 0, size = u32(vert_byte_count)} + sdl.UploadToGPUBuffer(cp, vert_src, vert_dst, false) + + ind_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = u32(vert_byte_count)} + ind_dst := sdl.GPUBufferRegion{buffer = ibuf, offset = 0, size = u32(ind_byte_count)} + sdl.UploadToGPUBuffer(cp, ind_src, ind_dst, false) + + sdl.EndGPUCopyPass(cp) + if !sdl.SubmitGPUCommandBuffer(setup_cmd) { + fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) + return + } + } + + return vbuf, ibuf +} + +walk :: proc(dir: string, models: ^[dynamic]format.GND_Data) { + f, _ := os.open(dir) + entries, ok := os.read_dir(f, 0, context.allocator) + for entry in entries { + path, _ := filepath.join({dir, entry.name}) + if entry.type == .Directory { + walk(path, models) + } + if strings.contains(entry.name, ".gnd") { + data, _ := os.read_entire_file(path, context.allocator) + parsed, err := format.parse_gnd(data) + if err != nil { + fmt.printfln("%v", err) + } else { + append_elem(models, parsed) + } + } + } +} + +loadRsw :: proc() -> format.RSW_Data { + prontera := "/home/pavel/neoragnarok_backup/kro_client/data/aldebaran.rsw" + data, _ := os.read_entire_file(prontera, context.allocator) + parsed, err := format.parse_rsw(data) + if err != nil { + fmt.printfln("%v", err) + panic("aaa") + } + return parsed +} + +loadRsmModel :: proc(device: ^sdl.GPUDevice, model: ^format.RSM_Model, modelIndex: int, nodes: ^[dynamic]RsmNode, textures: ^[dynamic]TextureHolder, vbufs: ^[dynamic]^sdl.GPUBuffer, ibufs: ^[dynamic]^sdl.GPUBuffer, transform: matrix[4,4]f32) { + vertices: [dynamic]shared.Vertex + indices: [dynamic]u16 + + vbufIndex := len(vbufs) + ibufIndex := len(ibufs) + + + fmt.printfln("version %v", model.version) + + textureMap : [dynamic]int + defer delete(textureMap) + + fmt.printfln("loading model texture %v", model.texture_names[:]) + for textureName in model.texture_names { + textureName, _ := strings.replace_all(textureName, "\\", "/") + + full_path, err := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/texture", textureName}); assert(err == nil) + full_path, err = filepath.clean(full_path); assert(err == nil) + + append(&textureMap, loadTexture(full_path, textures)) + } + + for node_idx := 0; node_idx < len(model.nodes); node_idx+= 1 { + textureOffset := (0) + node := &model.nodes[node_idx] + fmt.println("offset_matrix", node.offset_matrix) + fmt.println("node.translation1", node.translation1) + fmt.println("node.translation2", node.translation2) + fmt.println("node.scale", node.scale) + fmt.println("node.rotation_angle", node.rotation_angle) + fmt.println("node.rotation_axis", node.rotation_axis) + // fmt.println("node.scale_keyframes[:]", node.scale_keyframes[:]) + // fmt.println("node.texture_names[:]", node.texture_names[:]) + + if model.version.major >= 2 && model.version.minor > 2 { + textureOffset = (len(textures)) + } + + for textureName in node.texture_names { + fmt.printfln("loading texture %s", textureName) + full_path, err := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/texture", textureName}); assert(err == nil) + full_path, err = filepath.clean(full_path); assert(err == nil) + full_path, _ = strings.replace_all(full_path, "\\", "/") + + append(&textureMap, loadTexture(full_path, textures)) + } + + for face in node.faces { + n := RsmNode{ + start = len(indices), + node = node, + texture = textureMap[int(face.texture_index) + textureOffset], + modelIndex = modelIndex, + vbufIndex = vbufIndex, + ibufIndex = ibufIndex, + transform = transform, + } + + + for i in 0..<3 { + v_idx := face.vertex_position_indices[i] + t_idx := face.texture_coordinate_indices[i] + pos := node.vertex_positions[v_idx] + uv := node.texture_coordinates[t_idx].coordinates + + append(&vertices, shared.Vertex{ + pos = pos, + uv = uv, + }) + + append(&indices, u16(len(vertices)-1)) + } + n.end = len(indices) + + append(nodes, n) + } + + } + + vbuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.VERTEX}, + size = u32(len(vertices) * size_of(vertices[0])), + props = 0, + } + vbuf := sdl.CreateGPUBuffer(device, vbuf_info); assert(vbuf != nil) + append(vbufs, vbuf) + + ibuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.INDEX}, + size = u32(len(indices) * size_of(indices[0])), + props = 0, + } + ibuf := sdl.CreateGPUBuffer(device, ibuf_info); assert(ibuf != nil) + append(ibufs, ibuf) + + + { + setup_cmd := sdl.AcquireGPUCommandBuffer(device) + if setup_cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + return + } + + vert_byte_count := len(vertices) * size_of(vertices[0]) + ind_byte_count := len(indices) * size_of(indices[0]) + tb_info := sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(vert_byte_count + ind_byte_count), + props = 0, + } + tb := sdl.CreateGPUTransferBuffer(device, tb_info) + if tb == nil { + fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError()) + panic("fail") + } + defer sdl.ReleaseGPUTransferBuffer(device, tb) + + mapped := transmute([^]byte)sdl.MapGPUTransferBuffer(device, tb, false) + if mapped == nil { + fmt.println("MapGPUTransferBuffer failed:", sdl.GetError()) + panic("fail") + } + mem.copy_non_overlapping(mapped, raw_data(vertices[:]), vert_byte_count) + mem.copy_non_overlapping(mapped[vert_byte_count:], raw_data(indices[:]), ind_byte_count) + sdl.UnmapGPUTransferBuffer(device, tb) + + cp := sdl.BeginGPUCopyPass(setup_cmd) + if cp == nil { + fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) + panic("fail") + } + + vert_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0} + vert_dst := sdl.GPUBufferRegion{buffer = vbuf, offset = 0, size = u32(vert_byte_count)} + sdl.UploadToGPUBuffer(cp, vert_src, vert_dst, false) + + ind_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = u32(vert_byte_count)} + ind_dst := sdl.GPUBufferRegion{buffer = ibuf, offset = 0, size = u32(ind_byte_count)} + sdl.UploadToGPUBuffer(cp, ind_src, ind_dst, false) + + sdl.EndGPUCopyPass(cp) + if !sdl.SubmitGPUCommandBuffer(setup_cmd) { + fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) + return + } + } +} + +runGame :: proc(window: ^sdl.Window, device: ^sdl.GPUDevice, pipeline: ^sdl.GPUGraphicsPipeline) { + sampler := sdl.CreateGPUSampler(device, {}) + modelIndex := 0 + models: [dynamic]format.RSM_Model + + rsw := loadRsw() + + rsmVbufs: [dynamic]^sdl.GPUBuffer + rsmIbufs: [dynamic]^sdl.GPUBuffer + textures: [dynamic]TextureHolder + defer for texture in textures do sdl.ReleaseGPUTexture(device, texture.texture) + rsmNodes: [dynamic]RsmNode + + for object in rsw.objects { + modelPath, err1 := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/model", object.model_name}); assert(err1 ==nil) + fmt.println(modelPath) + data, err := os.read_entire_file(modelPath, context.allocator) + if err != nil { + fmt.println(err) + panic("failed to read rsm") + } + fmt.println(object.model_name) + + parsed, err2 := format.parse_rsm(data) + if err2 != nil { + fmt.println(err2) + panic("failed to parse rsm") + } + modelIndex := len(models) + append_elem(&models, parsed) + + T := linalg.matrix4_translate(object.transform.position) + S := linalg.matrix4_scale(object.transform.scale) + + Rx := linalg.matrix4_rotate_f32(object.transform.rotation_deg[0], {1,0,0}) + Ry := linalg.matrix4_rotate_f32(object.transform.rotation_deg[1], {0,1,0}) + Rz := linalg.matrix4_rotate_f32(object.transform.rotation_deg[2], {0,0,1}) + + R := Rz * Ry * Rx + + M := T * R * S + + loadRsmModel(device, &parsed, modelIndex, &rsmNodes, &textures, &rsmVbufs, &rsmIbufs, M) + } + + gnd, err1 := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data", rsw.ground_file}); assert(err1 ==nil) + data, _ := os.read_entire_file(gnd, context.allocator) + + gndModel, err := format.parse_gnd(data); assert(err == nil) + + gndVertices: [dynamic]shared.Vertex + gndIndices: [dynamic]u32 + gndNodes: [dynamic]GndNode + + gndVbuf, gndIbuf := loadGndModel(device, &gndModel, &gndVertices, &gndIndices, &gndNodes, &textures) + defer sdl.ReleaseGPUBuffer(device, gndIbuf) + defer sdl.ReleaseGPUBuffer(device, gndVbuf) + + uploadTextures(device, &textures) + + init_yaw := shared.rotor_from_axis_angle(shared.Vec3{0, 1, 0}, 0.9) + init_right := shared.rotate_vec3(init_yaw, shared.Vec3{1, 0, 0}) + init_pitch := shared.rotor_from_axis_angle(init_right, -0.45) + + camera := shared.Camera{ + center = shared.Vec3{0, 0.5, 0}, + distance = 8, + orientation = shared.rotor_normalize(shared.rotor_mul(init_pitch, init_yaw)), + fov_deg = 60, + } + + + start := time.tick_now() + last := start + left_down := false + middle_down := false + running := true + + depthTex : ^sdl.GPUTexture + dti : sdl.GPUDepthStencilTargetInfo + for running { + now := time.tick_now() + elapsed_ms := time.duration_milliseconds(time.tick_diff(start, now)) + dt_ms := time.duration_milliseconds(time.tick_diff(last, now)) + dt := f32(time.duration_seconds(time.tick_diff(last, now))) + last = now + + event: sdl.Event + for sdl.PollEvent(&event) { + #partial switch event.type { + case .QUIT: + running = false + case .MOUSE_BUTTON_DOWN: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = true + case sdl.BUTTON_MIDDLE: + middle_down = true + } + case .MOUSE_BUTTON_UP: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = false + case sdl.BUTTON_MIDDLE: + middle_down = false + } + case .MOUSE_WHEEL: + camera.distance = math.clamp(camera.distance - event.wheel.y * 0.7, 1.5, 80.0) + // case .KEY_DOWN: + // if event.key.key == sdl.K_ESCAPE { + // running = false + // } else if event.key.key == sdl.K_N { + // modelIndex += 1 + // sdl.ReleaseGPUBuffer(device, gndIbuf) + // sdl.ReleaseGPUBuffer(device, gndVbuf) + // for texture in gndTextures do sdl.ReleaseGPUTexture(device, texture.texture) + // gndVbuf, gndIbuf, gndTextures = loadGndModel(device, &gndModel, &gndVertices, &gndIndices, &gndNodes) + // } + } + } + + x_rel: f32 = 0 + y_rel: f32 = 0 + _ = sdl.GetRelativeMouseState(&x_rel, &y_rel) + + if left_down { + yaw_r := shared.rotor_from_axis_angle(shared.Vec3{0, 1, 0}, -x_rel * 0.006) + q1 := shared.rotor_normalize(shared.rotor_mul(yaw_r, camera.orientation)) + + right_axis := shared.vec3_normalize(shared.rotate_vec3(q1, shared.Vec3{1, 0, 0})) + pitch_r := shared.rotor_from_axis_angle(right_axis, -y_rel * 0.006) + q2 := shared.rotor_normalize(shared.rotor_mul(pitch_r, q1)) + + fwd2 := shared.camera_forward(shared.Camera{center = camera.center, distance = camera.distance, orientation = q2, fov_deg = camera.fov_deg}) + if math.abs(shared.vec3_dot(fwd2, shared.Vec3{0, 1, 0})) < 0.98 { + camera.orientation = q2 + } else { + camera.orientation = q1 + } + } + + if middle_down { + right := shared.camera_right(camera) + up := shared.camera_up(camera) + pan_speed := 0.008 * camera.distance + camera.center = camera.center + right * -x_rel * pan_speed + camera.center = camera.center + up * y_rel * pan_speed + } + + keys := sdl.GetKeyboardState(nil) + forward := shared.camera_forward(camera) + right := shared.camera_right(camera) + up := shared.camera_up(camera) + move_speed := camera.distance * dt * 1.4 + + if keys[sdl.Scancode.W] { + camera.center = camera.center + forward * move_speed + } + if keys[sdl.Scancode.S] { + camera.center = camera.center + forward * -move_speed + } + if keys[sdl.Scancode.A] { + camera.center = camera.center + right * -move_speed + } + if keys[sdl.Scancode.D] { + camera.center = camera.center + right * move_speed + } + + w: c.int = 0 + h: c.int = 0 + sdl.GetWindowSize(window, &w, &h) + if w <= 0 || h <= 0 { + continue + } + + cmd := sdl.AcquireGPUCommandBuffer(device) + if cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + break + } + + swap_tex: ^sdl.GPUTexture + swap_w: sdl.Uint32 = 0 + swap_h: sdl.Uint32 = 0 + if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd, window, &swap_tex, &swap_w, &swap_h) { + fmt.println("WaitAndAcquireGPUSwapchainTexture failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + if swap_tex == nil { + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + continue + } + + if depthTex == nil { + + depthTex = sdl.CreateGPUTexture(device, { + format = .D32_FLOAT, + usage = {.DEPTH_STENCIL_TARGET}, + width = swap_w, + height = swap_h, + layer_count_or_depth = 1, + num_levels = 1, + }) + + dti = sdl.GPUDepthStencilTargetInfo{ + texture = depthTex, + clear_depth = 1.0, + load_op = .CLEAR, + store_op = .STORE, + } + } + + + clear := sdl.FColor{0.08, 0.09, 0.12, 1.0} + cti := sdl.GPUColorTargetInfo{ + texture = swap_tex, + mip_level = 0, + layer_or_depth_plane = 0, + clear_color = clear, + load_op = .CLEAR, + store_op = .STORE, + resolve_texture = nil, + resolve_mip_level = 0, + resolve_layer = 0, + cycle = false, + cycle_resolve_texture = false, + } + + rp := sdl.BeginGPURenderPass(cmd, &cti, 1, &dti) + if rp == nil { + fmt.println("BeginGPURenderPass failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + vp := sdl.GPUViewport{x = 0, y = 0, w = f32(swap_w), h = f32(swap_h), min_depth = 0, max_depth = 1} + sdl.SetGPUViewport(rp, vp) + sdl.BindGPUGraphicsPipeline(rp, pipeline) + vb_binding := sdl.GPUBufferBinding{buffer = gndVbuf, offset = 0} + sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1) + ib_binding := sdl.GPUBufferBinding{buffer = gndIbuf, offset = 0} + sdl.BindGPUIndexBuffer(rp, ib_binding, ._32BIT) + + + + aspect := f32(swap_w) / f32(swap_h) + proj := linalg.matrix4_perspective(linalg.to_radians(camera.fov_deg), aspect, 0.5, 2000.0) + view := linalg.matrix4_look_at(shared.camera_position(camera), camera.center, shared.camera_up(camera)) + vp_mat := proj * view + + for mesh in gndNodes { + node := mesh.node + + pc := Push_Constants{} + + pc.mvp = vp_mat * linalg.MATRIX4F32_IDENTITY + pc.color = [4]f32{1, 0, 0, 1.0} + + sdl.BindGPUFragmentSamplers(rp, 0, &(sdl.GPUTextureSamplerBinding{ + texture = textures[mesh.texture].texture, + sampler = sampler, + }), 1) + + sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) + sdl.DrawGPUIndexedPrimitives(rp, u32(mesh.end-mesh.start), 1, u32(mesh.start), 0, 0) + } + + + renderModel(&models, elapsed_ms, rsmNodes, &textures, sampler, cmd, vp_mat, rp, &rsmVbufs, &rsmIbufs) + + sdl.EndGPURenderPass(rp) + + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + } +} + +renderModel :: proc(models: ^[dynamic]format.RSM_Model, elapsed_ms: f64, nodes: [dynamic]RsmNode, textures: ^[dynamic]TextureHolder, sampler: ^sdl.GPUSampler, cmd: ^sdl.GPUCommandBuffer, vp_mat: matrix[4,4]f32, rp: ^sdl.GPURenderPass, vbufs: ^[dynamic]^sdl.GPUBuffer, ibufs: ^[dynamic]^sdl.GPUBuffer) { + for mesh in nodes { + vb_binding := sdl.GPUBufferBinding{buffer = vbufs[mesh.vbufIndex], offset = 0} + sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1) + ib_binding := sdl.GPUBufferBinding{buffer = ibufs[mesh.ibufIndex], offset = 0} + sdl.BindGPUIndexBuffer(rp, ib_binding, ._16BIT) + + model := &models[mesh.modelIndex] + animation_length := model.animation_length + fps := model.frames_per_second + frame_delay : f32 + total_frames : u32 + + if fps == 0 { + total_seconds := f32(animation_length) / 1000 + total_frames = u32(total_seconds * 30) + frame_delay = 1000 / f32(30) + } else { + total_frames = animation_length + frame_delay = 1000 / fps + } + + current_frame := u32(f32(elapsed_ms) / frame_delay) % total_frames + + node := mesh.node + rotation := linalg.quaternion_angle_axis(node.rotation_angle, node.rotation_axis) + + if len(node.rotation_keyframes) > 0 { + keyframe_idx := 0 + for i := 0; i < len(node.rotation_keyframes); i+= 1 { + keyframe := node.rotation_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + + q := node.rotation_keyframes[keyframe_idx].quaternion + rotation = quaternion(imag=q[0], jmag=q[1], kmag=q[2], real=q[3]) + } + + rotation = linalg.quaternion_normalize(rotation) + + scale := node.scale + if len(node.scale_keyframes) > 0 { + keyframe_idx := 0 + for i := 0; i < len(node.scale_keyframes); i+= 1 { + keyframe := node.scale_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + scale = node.scale_keyframes[keyframe_idx].scale + } + + translation := [3]f32{0,0,0} + if(len(node.translation_keyframes) > 0) { + keyframe_idx := 0 + for i := 0; i < len(node.translation_keyframes); i+= 1 { + keyframe := node.translation_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + translation = node.translation_keyframes[keyframe_idx].translation + } + + r := linalg.matrix3_from_quaternion(rotation) + + s := matrix[3,3]f32{ + scale[0], 0, 0 , + 0, scale[1], 0 , + 0, 0, scale[2], + } + + a3 := r * node.offset_matrix * s + + t := node.translation1 + node.translation2 + translation + + // 4x4 affine matrix + model_mat := matrix[4,4]f32{ + a3[0][0], a3[0][1], a3[0][2], t[0], + a3[1][0], a3[1][1], a3[1][2], t[1], + a3[2][0], a3[2][1], a3[2][2], t[2], + 0, 0, 0, 1 , + } + + pc := Push_Constants{} + + pc.mvp = vp_mat * mesh.transform * model_mat + pc.color = [4]f32{1, 0, 0, 1.0} + sdl.BindGPUFragmentSamplers(rp, 0, &(sdl.GPUTextureSamplerBinding{ + texture = textures[mesh.texture].texture, + sampler = sampler, + }), 1) + + sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) + sdl.DrawGPUIndexedPrimitives(rp, u32(mesh.end-mesh.start), 1, u32(mesh.start), 0, 0) + } +} \ No newline at end of file diff --git a/src/main.odin b/src/main.odin index e03e621..15293cb 100644 --- a/src/main.odin +++ b/src/main.odin @@ -1,201 +1,12 @@ package main -import "core:c" import "core:fmt" -import "core:math" -import "core:mem" import "core:os" -import "core:time" - import sdl "vendor:sdl3" +import "rsmview" +import "gndview" +import "shared" -Vec3 :: [3]f32 -Mat4 :: [16]f32 // column-major - -Camera :: struct { - center: Vec3, - distance: f32, - orientation: Rotor, - fov_deg: f32, -} - -Debug_State :: struct { - enabled: bool, - accum: f32, -} - -Rotor :: struct { - s, x, y, z: f32, -} - -Cube_Instance :: struct { - pos: Vec3, - scale: f32, - color: Vec3, -} - -Vertex :: struct { - pos: Vec3, -} - -Push_Constants :: struct { - mvp: Mat4, - color: [4]f32, -} - -mat4_identity :: proc() -> Mat4 { - return Mat4{1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1} -} - -mat4_mul :: proc(a, b: Mat4) -> Mat4 { - r := Mat4{} - for c in 0 ..< 4 { - for row in 0 ..< 4 { - r[c*4+row] = - a[0*4+row] * b[c*4+0] + - a[1*4+row] * b[c*4+1] + - a[2*4+row] * b[c*4+2] + - a[3*4+row] * b[c*4+3] - } - } - return r -} - -vec3_sub :: proc(a, b: Vec3) -> Vec3 { - return Vec3{a[0] - b[0], a[1] - b[1], a[2] - b[2]} -} - -vec3_add :: proc(a, b: Vec3) -> Vec3 { - return Vec3{a[0] + b[0], a[1] + b[1], a[2] + b[2]} -} - -vec3_scale :: proc(v: Vec3, s: f32) -> Vec3 { - return Vec3{v[0] * s, v[1] * s, v[2] * s} -} - -vec3_dot :: proc(a, b: Vec3) -> f32 { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] -} - -vec3_cross :: proc(a, b: Vec3) -> Vec3 { - return Vec3{ - a[1] * b[2] - a[2] * b[1], - a[2] * b[0] - a[0] * b[2], - a[0] * b[1] - a[1] * b[0], - } -} - -vec3_normalize :: proc(v: Vec3) -> Vec3 { - len2 := vec3_dot(v, v) - if len2 <= 0.000001 { - return Vec3{0, 0, 0} - } - inv := 1.0 / math.sqrt(len2) - return vec3_scale(v, inv) -} - -rotor_identity :: proc() -> Rotor { - return Rotor{1, 0, 0, 0} -} - -rotor_normalize :: proc(r: Rotor) -> Rotor { - len2 := r.s*r.s + r.x*r.x + r.y*r.y + r.z*r.z - if len2 <= 0.000001 { - return rotor_identity() - } - inv := 1.0 / math.sqrt(len2) - return Rotor{r.s * inv, r.x * inv, r.y * inv, r.z * inv} -} - -rotor_mul :: proc(a, b: Rotor) -> Rotor { - return Rotor{ - a.s*b.s - a.x*b.x - a.y*b.y - a.z*b.z, - a.s*b.x + a.x*b.s + a.y*b.z - a.z*b.y, - a.s*b.y - a.x*b.z + a.y*b.s + a.z*b.x, - a.s*b.z + a.x*b.y - a.y*b.x + a.z*b.s, - } -} - -rotor_from_axis_angle :: proc(axis: Vec3, angle: f32) -> Rotor { - a := vec3_normalize(axis) - h := angle * 0.5 - c := math.cos(h) - s := math.sin(h) - return Rotor{c, a[0] * s, a[1] * s, a[2] * s} -} - -rotate_vec3 :: proc(r: Rotor, v: Vec3) -> Vec3 { - u := Vec3{r.x, r.y, r.z} - t := vec3_scale(vec3_cross(u, v), 2.0) - return vec3_add(v, vec3_add(vec3_scale(t, r.s), vec3_cross(u, t))) -} - -mat4_translate :: proc(p: Vec3) -> Mat4 { - m := mat4_identity() - m[12] = p[0] - m[13] = p[1] - m[14] = p[2] - return m -} - -mat4_scale_uniform :: proc(s: f32) -> Mat4 { - m := mat4_identity() - m[0] = s - m[5] = s - m[10] = s - return m -} - -mat4_perspective :: proc(fov_deg, aspect, z_near, z_far: f32) -> Mat4 { - f := 1.0 / math.tan((fov_deg * 0.5) * (math.PI / 180.0)) - m := Mat4{} - m[0] = f / aspect - m[5] = f - m[10] = (z_far + z_near) / (z_near - z_far) - m[11] = -1 - m[14] = (2.0 * z_far * z_near) / (z_near - z_far) - return m -} - -mat4_look_at :: proc(eye, target, up: Vec3) -> Mat4 { - fwd := vec3_normalize(vec3_sub(target, eye)) - right := vec3_normalize(vec3_cross(fwd, up)) - real_up := vec3_cross(right, fwd) - - m := mat4_identity() - m[0] = right[0] - m[1] = real_up[0] - m[2] = -fwd[0] - m[4] = right[1] - m[5] = real_up[1] - m[6] = -fwd[1] - m[8] = right[2] - m[9] = real_up[2] - m[10] = -fwd[2] - m[12] = -vec3_dot(right, eye) - m[13] = -vec3_dot(real_up, eye) - m[14] = vec3_dot(fwd, eye) - return m -} - -camera_forward :: proc(c: Camera) -> Vec3 { - return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 0, -1})) -} - -camera_right :: proc(c: Camera) -> Vec3 { - return vec3_normalize(rotate_vec3(c.orientation, Vec3{1, 0, 0})) -} - -camera_up :: proc(c: Camera) -> Vec3 { - return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 1, 0})) -} - -camera_position :: proc(c: Camera) -> Vec3 { - return vec3_sub(c.center, vec3_scale(camera_forward(c), c.distance)) -} read_file_or_fail :: proc(path: string) -> []u8 { file_bytes, err := os.read_entire_file(path, context.allocator) @@ -206,7 +17,7 @@ read_file_or_fail :: proc(path: string) -> []u8 { return file_bytes } -create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage) -> ^sdl.GPUShader { +create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage, num_samplers: u32) -> ^sdl.GPUShader { bytes := read_file_or_fail(path) if len(bytes) == 0 { return nil @@ -218,7 +29,7 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh entrypoint = cstring("main"), format = sdl.GPUShaderFormat{.SPIRV}, stage = stage, - num_samplers = 0, + num_samplers = num_samplers, num_storage_textures = 0, num_storage_buffers = 0, num_uniform_buffers = 1, @@ -233,41 +44,23 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh return shader } -copy_vertices_to_gpu :: proc(device: ^sdl.GPUDevice, cmd: ^sdl.GPUCommandBuffer, vertices: []Vertex, dst: ^sdl.GPUBuffer) -> bool { - byte_count := len(vertices) * size_of(Vertex) - tb_info := sdl.GPUTransferBufferCreateInfo{ - usage = .UPLOAD, - size = u32(byte_count), - props = 0, - } - tb := sdl.CreateGPUTransferBuffer(device, tb_info) - if tb == nil { - fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError()) - return false - } - defer sdl.ReleaseGPUTransferBuffer(device, tb) - - mapped := sdl.MapGPUTransferBuffer(device, tb, false) - if mapped == nil { - fmt.println("MapGPUTransferBuffer failed:", sdl.GetError()) - return false - } - mem.copy_non_overlapping(mapped, raw_data(vertices), byte_count) - sdl.UnmapGPUTransferBuffer(device, tb) - - cp := sdl.BeginGPUCopyPass(cmd) - if cp == nil { - fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) - return false - } - - src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0} - dst_region := sdl.GPUBufferRegion{buffer = dst, offset = 0, size = u32(byte_count)} - sdl.UploadToGPUBuffer(cp, src, dst_region, false) - sdl.EndGPUCopyPass(cp) +copy_data_to_gpu :: proc( + device: ^sdl.GPUDevice, + cmd: ^sdl.GPUCommandBuffer, + vertices: []shared.Vertex, + vert_buf: ^sdl.GPUBuffer, + indices: []u16, + ind_buf: ^sdl.GPUBuffer +) -> bool { + return true } +GPU_Vertex :: struct { + position: [3]f32, + uv: [2]f32, +} + main :: proc() { if !sdl.Init(sdl.INIT_VIDEO) { fmt.println("SDL init failed:", sdl.GetError()) @@ -302,13 +95,13 @@ main :: proc() { return } - vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX) + vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX, 0) if vertex_shader == nil { return } defer sdl.ReleaseGPUShader(device, vertex_shader) - fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT) + fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT, 1) if fragment_shader == nil { return } @@ -320,11 +113,12 @@ main :: proc() { return } - vb_descs := [1]sdl.GPUVertexBufferDescription{ - {slot = 0, pitch = size_of(Vertex), input_rate = .VERTEX, instance_step_rate = 0}, + vb_descs := []sdl.GPUVertexBufferDescription{ + {slot = 0, pitch = size_of(shared.Vertex), input_rate = .VERTEX, instance_step_rate = 0}, } - vattrs := [1]sdl.GPUVertexAttribute{ - {location = 0, buffer_slot = 0, format = .FLOAT3, offset = 0}, + vattrs := []sdl.GPUVertexAttribute{ + {location = 0, buffer_slot = 0, format = .FLOAT3, offset = u32(offset_of(shared.Vertex, pos))}, + {location = 1, buffer_slot = 0, format = .FLOAT2, offset = u32(offset_of(shared.Vertex, uv))}, } blend := sdl.GPUColorTargetBlendState{ @@ -342,15 +136,15 @@ main :: proc() { {format = color_format, blend_state = blend}, } - pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{ - vertex_shader = vertex_shader, - fragment_shader = fragment_shader, - vertex_input_state = sdl.GPUVertexInputState{ - vertex_buffer_descriptions = &vb_descs[0], - num_vertex_buffers = 1, - vertex_attributes = &vattrs[0], - num_vertex_attributes = 1, - }, + pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{ + vertex_shader = vertex_shader, + fragment_shader = fragment_shader, + vertex_input_state = sdl.GPUVertexInputState{ + vertex_buffer_descriptions = &vb_descs[0], + num_vertex_buffers = 1, + vertex_attributes = &vattrs[0], + num_vertex_attributes = 2, + }, primitive_type = .TRIANGLELIST, rasterizer_state = sdl.GPURasterizerState{ fill_mode = .FILL, @@ -374,15 +168,15 @@ main :: proc() { front_stencil_state = sdl.GPUStencilOpState{fail_op = .KEEP, pass_op = .KEEP, depth_fail_op = .KEEP, compare_op = .ALWAYS}, compare_mask = 0, write_mask = 0, - enable_depth_test = false, - enable_depth_write = false, - enable_stencil_test = false, + enable_depth_test = true, + enable_depth_write = true, + enable_stencil_test = true, }, target_info = sdl.GPUGraphicsPipelineTargetInfo{ color_target_descriptions = &color_targets[0], num_color_targets = 1, - depth_stencil_format = .INVALID, - has_depth_stencil_target = false, + depth_stencil_format = .D32_FLOAT, + has_depth_stencil_target = true, }, props = 0, } @@ -394,249 +188,16 @@ main :: proc() { } defer sdl.ReleaseGPUGraphicsPipeline(device, pipeline) - vertices := [36]Vertex{ - {pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}}, {pos = Vec3{0.5, -0.5, -0.5}}, - {pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}}, - {pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, - {pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}}, - {pos = Vec3{-0.5, 0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{-0.5, -0.5, -0.5}}, - {pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{-0.5, -0.5, 0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}}, - {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, -0.5}}, - {pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, - {pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, - {pos = Vec3{-0.5, -0.5, -0.5}}, {pos = Vec3{0.5, -0.5, 0.5}}, {pos = Vec3{-0.5, -0.5, 0.5}}, - {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, {pos = Vec3{0.5, 0.5, -0.5}}, - {pos = Vec3{-0.5, 0.5, -0.5}}, {pos = Vec3{-0.5, 0.5, 0.5}}, {pos = Vec3{0.5, 0.5, 0.5}}, - } - - vbuf_info := sdl.GPUBufferCreateInfo{ - usage = sdl.GPUBufferUsageFlags{.VERTEX}, - size = u32(len(vertices) * size_of(Vertex)), - props = 0, - } - vbuf := sdl.CreateGPUBuffer(device, vbuf_info) - if vbuf == nil { - fmt.println("CreateGPUBuffer failed:", sdl.GetError()) - return - } - defer sdl.ReleaseGPUBuffer(device, vbuf) - - { - setup_cmd := sdl.AcquireGPUCommandBuffer(device) - if setup_cmd == nil { - fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) - return - } - if !copy_vertices_to_gpu(device, setup_cmd, vertices[:], vbuf) { - _ = sdl.CancelGPUCommandBuffer(setup_cmd) - return - } - if !sdl.SubmitGPUCommandBuffer(setup_cmd) { - fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) - return - } - } - - init_yaw := rotor_from_axis_angle(Vec3{0, 1, 0}, 0.9) - init_right := rotate_vec3(init_yaw, Vec3{1, 0, 0}) - init_pitch := rotor_from_axis_angle(init_right, -0.45) - - camera := Camera{ - center = Vec3{0, 0.5, 0}, - distance = 8, - orientation = rotor_normalize(rotor_mul(init_pitch, init_yaw)), - fov_deg = 60, - } - - cubes := []Cube_Instance{ - {pos = Vec3{0, 0, 0}, scale = 1.0, color = Vec3{0.95, 0.45, 0.20}}, - {pos = Vec3{2, 0.5, -1}, scale = 0.8, color = Vec3{0.20, 0.70, 0.95}}, - {pos = Vec3{-2, -0.2, 1.5}, scale = 1.2, color = Vec3{0.85, 0.85, 0.30}}, - {pos = Vec3{1.0, 1.4, 2.0}, scale = 0.6, color = Vec3{0.40, 0.95, 0.60}}, - } - - running := true - last := time.tick_now() - left_down := false - middle_down := false - debug := Debug_State{} - - for running { - now := time.tick_now() - dt := f32(time.duration_seconds(time.tick_diff(last, now))) - last = now - - event: sdl.Event - for sdl.PollEvent(&event) { - #partial switch event.type { - case .QUIT: - running = false - case .MOUSE_BUTTON_DOWN: - switch event.button.button { - case sdl.BUTTON_LEFT: - left_down = true - case sdl.BUTTON_MIDDLE: - middle_down = true - } - case .MOUSE_BUTTON_UP: - switch event.button.button { - case sdl.BUTTON_LEFT: - left_down = false - case sdl.BUTTON_MIDDLE: - middle_down = false - } - case .MOUSE_WHEEL: - camera.distance = math.clamp(camera.distance - event.wheel.y * 0.7, 1.5, 80.0) - case .KEY_DOWN: - if event.key.key == sdl.K_ESCAPE { - running = false - } else if event.key.scancode == sdl.Scancode.F1 && !event.key.repeat { - debug.enabled = !debug.enabled - fmt.println("debug:", debug.enabled) - } - } - } - - x_rel: f32 = 0 - y_rel: f32 = 0 - _ = sdl.GetRelativeMouseState(&x_rel, &y_rel) - - if left_down { - yaw_r := rotor_from_axis_angle(Vec3{0, 1, 0}, -x_rel * 0.006) - q1 := rotor_normalize(rotor_mul(yaw_r, camera.orientation)) - - right_axis := vec3_normalize(rotate_vec3(q1, Vec3{1, 0, 0})) - pitch_r := rotor_from_axis_angle(right_axis, -y_rel * 0.006) - q2 := rotor_normalize(rotor_mul(pitch_r, q1)) - - fwd2 := camera_forward(Camera{center = camera.center, distance = camera.distance, orientation = q2, fov_deg = camera.fov_deg}) - if math.abs(vec3_dot(fwd2, Vec3{0, 1, 0})) < 0.98 { - camera.orientation = q2 - } else { - camera.orientation = q1 - } - } - - if middle_down { - right := camera_right(camera) - up := camera_up(camera) - pan_speed := 0.008 * camera.distance - camera.center = vec3_add(camera.center, vec3_scale(right, -x_rel * pan_speed)) - camera.center = vec3_add(camera.center, vec3_scale(up, y_rel * pan_speed)) - } - - keys := sdl.GetKeyboardState(nil) - forward := camera_forward(camera) - right := camera_right(camera) - up := camera_up(camera) - move_speed := camera.distance * dt * 1.4 - - if keys[sdl.Scancode.W] { - camera.center = vec3_add(camera.center, vec3_scale(forward, move_speed)) - } - if keys[sdl.Scancode.S] { - camera.center = vec3_add(camera.center, vec3_scale(forward, -move_speed)) - } - if keys[sdl.Scancode.A] { - camera.center = vec3_add(camera.center, vec3_scale(right, -move_speed)) - } - if keys[sdl.Scancode.D] { - camera.center = vec3_add(camera.center, vec3_scale(right, move_speed)) - } - - if debug.enabled { - debug.accum += dt - if debug.accum >= 0.2 { - debug.accum = 0 - eye := camera_position(camera) - fmt.println("cam eye:", eye, - "center:", camera.center, - "fwd:", forward, - "right:", right, - "up:", up) - } - } - - w: c.int = 0 - h: c.int = 0 - sdl.GetWindowSize(window, &w, &h) - if w <= 0 || h <= 0 { - continue - } - - cmd := sdl.AcquireGPUCommandBuffer(device) - if cmd == nil { - fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) - break - } - - swap_tex: ^sdl.GPUTexture - swap_w: sdl.Uint32 = 0 - swap_h: sdl.Uint32 = 0 - if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd, window, &swap_tex, &swap_w, &swap_h) { - fmt.println("WaitAndAcquireGPUSwapchainTexture failed:", sdl.GetError()) - _ = sdl.CancelGPUCommandBuffer(cmd) - break - } - - if swap_tex == nil { - if !sdl.SubmitGPUCommandBuffer(cmd) { - fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) - break - } - continue - } - - clear := sdl.FColor{0.08, 0.09, 0.12, 1.0} - cti := sdl.GPUColorTargetInfo{ - texture = swap_tex, - mip_level = 0, - layer_or_depth_plane = 0, - clear_color = clear, - load_op = .CLEAR, - store_op = .STORE, - resolve_texture = nil, - resolve_mip_level = 0, - resolve_layer = 0, - cycle = false, - cycle_resolve_texture = false, - } - - rp := sdl.BeginGPURenderPass(cmd, &cti, 1, nil) - if rp == nil { - fmt.println("BeginGPURenderPass failed:", sdl.GetError()) - _ = sdl.CancelGPUCommandBuffer(cmd) - break - } - - vp := sdl.GPUViewport{x = 0, y = 0, w = f32(swap_w), h = f32(swap_h), min_depth = 0, max_depth = 1} - sdl.SetGPUViewport(rp, vp) - sdl.BindGPUGraphicsPipeline(rp, pipeline) - vb_binding := sdl.GPUBufferBinding{buffer = vbuf, offset = 0} - sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1) - - aspect := f32(swap_w) / f32(swap_h) - proj := mat4_perspective(camera.fov_deg, aspect, 0.1, 300.0) - view := mat4_look_at(camera_position(camera), camera.center, camera_up(camera)) - vp_mat := mat4_mul(proj, view) - - for cube in cubes { - model := mat4_mul(mat4_translate(cube.pos), mat4_scale_uniform(cube.scale)) - pc := Push_Constants{} - pc.mvp = mat4_mul(vp_mat, model) - pc.color = [4]f32{cube.color[0], cube.color[1], cube.color[2], 1.0} - sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) - sdl.PushGPUFragmentUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) - sdl.DrawGPUPrimitives(rp, 36, 1, 0, 0) - } - - sdl.EndGPURenderPass(rp) - - if !sdl.SubmitGPUCommandBuffer(cmd) { - fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) - break + + pipeline_create_info := sdl.GPUGraphicsPipelineCreateInfo{ + primitive_type = .TRIANGLELIST, + rasterizer_state = { + fill_mode = .LINE, + cull_mode = .FRONT, } } + + gndview.runGame(window, device, pipeline) _ = sdl.WaitForGPUIdle(device) -} +} \ No newline at end of file diff --git a/src/rsmview/rsmview.odin b/src/rsmview/rsmview.odin new file mode 100644 index 0000000..53c9a46 --- /dev/null +++ b/src/rsmview/rsmview.odin @@ -0,0 +1,646 @@ +package rsmview + +import sdl "vendor:sdl3" +import "core:os" +import "core:strings" +import "core:path/filepath" +import "core:fmt" +import stbi "vendor:stb/image" +import "core:time" +import "core:mem" +import "core:math" +import "../format" +import "../shared" +import "core:math/linalg" +import "core:c" + +TextureHolder :: struct { + texture: ^sdl.GPUTexture, + pixels: [^]byte, + transferBuffer: ^sdl.GPUTransferBuffer, + w: i32, + h: i32, +} + +Node :: struct { + start: int, + end: int, + texture: u16, + node: ^format.RSM_Node, + textures: []sdl.GPUTexture, +} + +Push_Constants :: struct { + mvp: matrix[4,4]f32, + color: [4]f32, +} + +last_path_component :: proc(s: string) -> string { + last_slash := strings.last_index_byte(s, '/') + last_backslash := strings.last_index_byte(s, '\\') + last := max(last_slash, last_backslash) + if last < 0 do return s + return s[last+1:] +} + +find_texture_by_basename :: proc(root, wanted_name: string) -> (string, bool) { + wanted_lower := strings.to_lower(last_path_component(wanted_name), context.temp_allocator) + return find_texture_by_basename_walk(root, wanted_lower) +} + +find_texture_by_basename_walk :: proc(dir, wanted_lower: string) -> (string, bool) { + f, err := os.open(dir) + if err != nil do return "", false + defer os.close(f) + + entries, _ := os.read_dir(f, 0, context.temp_allocator) + + for entry in entries { + path, join_err := filepath.join({dir, entry.name}) + if join_err != nil do continue + + if entry.type == .Directory { + if resolved, found := find_texture_by_basename_walk(path, wanted_lower); found { + return resolved, true + } + continue + } + + name_lower := strings.to_lower(entry.name, context.temp_allocator) + if name_lower == wanted_lower { + return path, true + } + } + + return "", false +} + + +loadModel :: proc(device: ^sdl.GPUDevice, model: ^format.RSM_Model, vertices: ^[dynamic]shared.Vertex, indices: ^[dynamic]u16, nodes: ^[dynamic]Node) -> (vbuf: ^sdl.GPUBuffer, ibuf: ^sdl.GPUBuffer, textures: [dynamic]TextureHolder) { + clear(vertices) + clear(indices) + clear(nodes) + + fmt.println("version %v", model.version) + + version := 0 + if model.version.major > 2 && model.version.minor > 2 { + version = 1 + } + + fmt.printfln("loading model texture %v", model.texture_names[:]) + for textureName in model.texture_names { + textureName, _ := strings.replace_all(textureName, "\\", "/") + + full_path, err := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/texture/", textureName}); assert(err == nil) + full_path, err = filepath.clean(full_path); assert(err == nil) + + c_path := strings.clone_to_cstring(full_path) + defer delete(c_path) + + img_size: [2]i32 + pixels := stbi.load(c_path, &img_size.x, &img_size.y, nil, 4); + if pixels == nil { + if fallback_path, found := find_texture_by_basename("/home/pavel/neoragnarok_backup/kro_client/data/texture", textureName); found { + fmt.printfln("fallback texture match %q -> %q", textureName, fallback_path) + c_path := strings.clone_to_cstring(fallback_path) + defer delete(c_path) + pixels = stbi.load(c_path, &img_size.x, &img_size.y, nil, 4) + } + } + if pixels == nil { + fmt.printfln("missing model texture: %q", textureName) + continue + } + + texture := sdl.CreateGPUTexture(device, sdl.GPUTextureCreateInfo{ + format = .R8G8B8A8_UNORM, + usage = {.SAMPLER}, + width = u32(img_size.x), + height = u32(img_size.y), + layer_count_or_depth = 1, + num_levels = 1, + }); assert(texture != nil) + + texture_byte_size := img_size.x * img_size.y * 4 + + append(&textures, TextureHolder{ + texture = texture, + pixels = pixels, + w = img_size.x, + h = img_size.y, + }) + } + + for node_idx := 0; node_idx < len(model.nodes); node_idx+= 1 { + textureOffset := u16(0) + node := &model.nodes[node_idx] + fmt.println("offset_matrix", node.offset_matrix) + fmt.println("node.translation1", node.translation1) + fmt.println("node.translation2", node.translation2) + fmt.println("node.scale", node.scale) + fmt.println("node.rotation_angle", node.rotation_angle) + fmt.println("node.rotation_axis", node.rotation_axis) + // fmt.println("node.scale_keyframes[:]", node.scale_keyframes[:]) + // fmt.println("node.texture_names[:]", node.texture_names[:]) + + if model.version.major >= 2 && model.version.minor > 2 { + textureOffset = u16(len(textures)) + } + + fmt.printfln("texture offset %v", textureOffset) + + fmt.printfln("loading node texture %v", node.texture_names[:]) + for textureName in node.texture_names { + fmt.printfln("loading texture %s", textureName) + full_path, err := filepath.join({"/home/pavel/neoragnarok_backup/kro_client/data/texture/", textureName}); assert(err == nil) + full_path, err = filepath.clean(full_path); assert(err == nil) + full_path, _ = strings.replace_all(full_path, "\\", "/") + + c_path := strings.clone_to_cstring(full_path) + defer delete(c_path) + + img_size: [2]i32 + pixels := stbi.load(c_path, &img_size.x, &img_size.y, nil, 4) + + fmt.printfln("texture has size %v", img_size) + + texture := sdl.CreateGPUTexture(device, sdl.GPUTextureCreateInfo{ + format = .R8G8B8A8_UNORM, + usage = {.SAMPLER}, + width = u32(img_size.x), + height = u32(img_size.y), + layer_count_or_depth = 1, + num_levels = 1, + }); assert(texture != nil) + + texture_byte_size := img_size.x * img_size.y * 4 + + append(&textures, TextureHolder{ + texture = texture, + pixels = pixels, + w = img_size.x, + h = img_size.y, + }) + } + + + + for face in node.faces { + n := Node{ + start = len(indices), + node = node, + texture = face.texture_index + textureOffset, + } + + + for i in 0..<3 { + v_idx := face.vertex_position_indices[i] + t_idx := face.texture_coordinate_indices[i] + pos := node.vertex_positions[v_idx] + uv := node.texture_coordinates[t_idx].coordinates + + append(vertices, shared.Vertex{ + pos = pos, + uv = uv, + }) + + append(indices, u16(len(vertices)-1)) + } + n.end = len(indices) + + append(nodes, n) + } + + } + + vbuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.VERTEX}, + size = u32(len(vertices) * size_of(vertices[0])), + props = 0, + } + vbuf = sdl.CreateGPUBuffer(device, vbuf_info) + if vbuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + ibuf_info := sdl.GPUBufferCreateInfo{ + usage = sdl.GPUBufferUsageFlags{.INDEX}, + size = u32(len(indices) * size_of(indices[0])), + props = 0, + } + ibuf = sdl.CreateGPUBuffer(device, ibuf_info) + if ibuf == nil { + fmt.println("CreateGPUBuffer failed:", sdl.GetError()) + return + } + + + { + setup_cmd := sdl.AcquireGPUCommandBuffer(device) + if setup_cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + return + } + + + fmt.println("copying textures to transfer buffer") + for &texture in textures { + tb_tex := sdl.CreateGPUTransferBuffer(device, sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(texture.w * texture.h * 4), + props = 0, + }); assert(tb_tex != nil) + + tex_mapped := sdl.MapGPUTransferBuffer(device, tb_tex, false); assert(tex_mapped != nil) + mem.copy_non_overlapping(tex_mapped, texture.pixels, int(texture.w * texture.h * 4)) + sdl.UnmapGPUTransferBuffer(device, tb_tex) + + texture.transferBuffer = tb_tex + } + fmt.println("copying textures to transfer buffer") + + vert_byte_count := len(vertices) * size_of(vertices[0]) + ind_byte_count := len(indices) * size_of(indices[0]) + tb_info := sdl.GPUTransferBufferCreateInfo{ + usage = .UPLOAD, + size = u32(vert_byte_count + ind_byte_count), + props = 0, + } + tb := sdl.CreateGPUTransferBuffer(device, tb_info) + if tb == nil { + fmt.println("CreateGPUTransferBuffer failed:", sdl.GetError()) + return + } + defer sdl.ReleaseGPUTransferBuffer(device, tb) + + mapped := transmute([^]byte)sdl.MapGPUTransferBuffer(device, tb, false) + if mapped == nil { + fmt.println("MapGPUTransferBuffer failed:", sdl.GetError()) + return + } + mem.copy_non_overlapping(mapped, raw_data(vertices[:]), vert_byte_count) + mem.copy_non_overlapping(mapped[vert_byte_count:], raw_data(indices[:]), ind_byte_count) + sdl.UnmapGPUTransferBuffer(device, tb) + + cp := sdl.BeginGPUCopyPass(setup_cmd) + if cp == nil { + fmt.println("BeginGPUCopyPass failed:", sdl.GetError()) + return + } + + vert_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = 0} + vert_dst := sdl.GPUBufferRegion{buffer = vbuf, offset = 0, size = u32(vert_byte_count)} + sdl.UploadToGPUBuffer(cp, vert_src, vert_dst, false) + + ind_src := sdl.GPUTransferBufferLocation{transfer_buffer = tb, offset = u32(vert_byte_count)} + ind_dst := sdl.GPUBufferRegion{buffer = ibuf, offset = 0, size = u32(ind_byte_count)} + sdl.UploadToGPUBuffer(cp, ind_src, ind_dst, false) + + fmt.println("transferring textures") + for texture in textures { + fmt.println("tex ptr:", texture, "w/h:", texture.w, texture.h) + sdl.UploadToGPUTexture(cp, + { + transfer_buffer = texture.transferBuffer, + offset = 0, + pixels_per_row = u32(texture.w), + rows_per_layer = u32(texture.h), + }, + {texture = texture.texture, w = u32(texture.w), h = u32(texture.h), d = 1}, + false, + ) + } + fmt.println("transferred textures") + + sdl.EndGPUCopyPass(cp) + if !sdl.SubmitGPUCommandBuffer(setup_cmd) { + fmt.println("SubmitGPUCommandBuffer (setup) failed:", sdl.GetError()) + return + } + } + + return vbuf, ibuf, textures +} + +walk :: proc(dir: string, models: ^[dynamic]format.RSM_Model) { + f, _ := os.open(dir) + entries, ok := os.read_dir(f, 0, context.allocator) + for entry in entries { + path, _ := filepath.join({dir, entry.name}) + if entry.type == .Directory { + walk(path, models) + } + if strings.contains(entry.name, ".rsm") { + data, _ := os.read_entire_file(path, context.allocator) + parsed, err := format.parse_rsm(data) + if err != nil { + fmt.printfln("%v", err) + } else { + for node in parsed.nodes { + if parsed.version.major == 2 && parsed.version.minor == 3{ + append_elem(models, parsed) + break + } + } + + } + } + } +} + + +runGame :: proc(window: ^sdl.Window, device: ^sdl.GPUDevice, pipeline: ^sdl.GPUGraphicsPipeline) {\ + sampler := sdl.CreateGPUSampler(device, {}) + modelIndex := 0 + models: [dynamic]format.RSM_Model + + walk("/home/pavel/neoragnarok_backup/kro_client/data", &models) + + vertices: [dynamic]shared.Vertex + indices: [dynamic]u16 + nodes: [dynamic]Node + + vbuf, ibuf, textures := loadModel(device, &models[modelIndex], &vertices, &indices, &nodes) + defer sdl.ReleaseGPUBuffer(device, ibuf) + defer sdl.ReleaseGPUBuffer(device, vbuf) + defer for texture in textures do sdl.ReleaseGPUTexture(device, texture.texture) + + init_yaw := shared.rotor_from_axis_angle(shared.Vec3{0, 1, 0}, 0.9) + init_right := shared.rotate_vec3(init_yaw, shared.Vec3{1, 0, 0}) + init_pitch := shared.rotor_from_axis_angle(init_right, -0.45) + + camera := shared.Camera{ + center = shared.Vec3{0, 0.5, 0}, + distance = 8, + orientation = shared.rotor_normalize(shared.rotor_mul(init_pitch, init_yaw)), + fov_deg = 60, + } + + + start := time.tick_now() + last := start + left_down := false + middle_down := false + running := true + + for running { + now := time.tick_now() + elapsed_ms := time.duration_milliseconds(time.tick_diff(start, now)) + dt_ms := time.duration_milliseconds(time.tick_diff(last, now)) + dt := f32(time.duration_seconds(time.tick_diff(last, now))) + last = now + + event: sdl.Event + for sdl.PollEvent(&event) { + #partial switch event.type { + case .QUIT: + running = false + case .MOUSE_BUTTON_DOWN: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = true + case sdl.BUTTON_MIDDLE: + middle_down = true + } + case .MOUSE_BUTTON_UP: + switch event.button.button { + case sdl.BUTTON_LEFT: + left_down = false + case sdl.BUTTON_MIDDLE: + middle_down = false + } + case .MOUSE_WHEEL: + camera.distance = math.clamp(camera.distance - event.wheel.y * 0.7, 1.5, 80.0) + case .KEY_DOWN: + if event.key.key == sdl.K_ESCAPE { + running = false + } else if event.key.key == sdl.K_N { + modelIndex += 1 + sdl.ReleaseGPUBuffer(device, ibuf) + sdl.ReleaseGPUBuffer(device, vbuf) + for texture in textures do sdl.ReleaseGPUTexture(device, texture.texture) + vbuf, ibuf, textures = loadModel(device, &models[modelIndex], &vertices, &indices, &nodes) + } + } + } + + x_rel: f32 = 0 + y_rel: f32 = 0 + _ = sdl.GetRelativeMouseState(&x_rel, &y_rel) + + if left_down { + yaw_r := shared.rotor_from_axis_angle(shared.Vec3{0, 1, 0}, -x_rel * 0.006) + q1 := shared.rotor_normalize(shared.rotor_mul(yaw_r, camera.orientation)) + + right_axis := shared.vec3_normalize(shared.rotate_vec3(q1, shared.Vec3{1, 0, 0})) + pitch_r := shared.rotor_from_axis_angle(right_axis, -y_rel * 0.006) + q2 := shared.rotor_normalize(shared.rotor_mul(pitch_r, q1)) + + fwd2 := shared.camera_forward(shared.Camera{center = camera.center, distance = camera.distance, orientation = q2, fov_deg = camera.fov_deg}) + if math.abs(shared.vec3_dot(fwd2, shared.Vec3{0, 1, 0})) < 0.98 { + camera.orientation = q2 + } else { + camera.orientation = q1 + } + } + + if middle_down { + right := shared.camera_right(camera) + up := shared.camera_up(camera) + pan_speed := 0.008 * camera.distance + camera.center = camera.center + right * -x_rel * pan_speed + camera.center = camera.center + up * y_rel * pan_speed + } + + keys := sdl.GetKeyboardState(nil) + forward := shared.camera_forward(camera) + right := shared.camera_right(camera) + up := shared.camera_up(camera) + move_speed := camera.distance * dt * 1.4 + + if keys[sdl.Scancode.W] { + camera.center = camera.center + forward * move_speed + } + if keys[sdl.Scancode.S] { + camera.center = camera.center + forward * -move_speed + } + if keys[sdl.Scancode.A] { + camera.center = camera.center + right * -move_speed + } + if keys[sdl.Scancode.D] { + camera.center = camera.center + right * move_speed + } + + w: c.int = 0 + h: c.int = 0 + sdl.GetWindowSize(window, &w, &h) + if w <= 0 || h <= 0 { + continue + } + + cmd := sdl.AcquireGPUCommandBuffer(device) + if cmd == nil { + fmt.println("AcquireGPUCommandBuffer failed:", sdl.GetError()) + break + } + + swap_tex: ^sdl.GPUTexture + swap_w: sdl.Uint32 = 0 + swap_h: sdl.Uint32 = 0 + if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd, window, &swap_tex, &swap_w, &swap_h) { + fmt.println("WaitAndAcquireGPUSwapchainTexture failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + if swap_tex == nil { + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + continue + } + + clear := sdl.FColor{0.08, 0.09, 0.12, 1.0} + cti := sdl.GPUColorTargetInfo{ + texture = swap_tex, + mip_level = 0, + layer_or_depth_plane = 0, + clear_color = clear, + load_op = .CLEAR, + store_op = .STORE, + resolve_texture = nil, + resolve_mip_level = 0, + resolve_layer = 0, + cycle = false, + cycle_resolve_texture = false, + } + + rp := sdl.BeginGPURenderPass(cmd, &cti, 1, nil) + if rp == nil { + fmt.println("BeginGPURenderPass failed:", sdl.GetError()) + _ = sdl.CancelGPUCommandBuffer(cmd) + break + } + + vp := sdl.GPUViewport{x = 0, y = 0, w = f32(swap_w), h = f32(swap_h), min_depth = 0, max_depth = 1} + sdl.SetGPUViewport(rp, vp) + sdl.BindGPUGraphicsPipeline(rp, pipeline) + vb_binding := sdl.GPUBufferBinding{buffer = vbuf, offset = 0} + sdl.BindGPUVertexBuffers(rp, 0, &vb_binding, 1) + ib_binding := sdl.GPUBufferBinding{buffer = ibuf, offset = 0} + sdl.BindGPUIndexBuffer(rp, ib_binding, ._16BIT) + + + + aspect := f32(swap_w) / f32(swap_h) + proj := linalg.matrix4_perspective(linalg.to_radians(camera.fov_deg), aspect, 0.1, 300.0) + view := linalg.matrix4_look_at(shared.camera_position(camera), camera.center, shared.camera_up(camera)) + vp_mat := proj * view + + renderModel(&models[modelIndex], elapsed_ms, nodes, &textures, sampler, cmd, vp_mat, rp) + + sdl.EndGPURenderPass(rp) + + if !sdl.SubmitGPUCommandBuffer(cmd) { + fmt.println("SubmitGPUCommandBuffer failed:", sdl.GetError()) + break + } + } +} + +renderModel :: proc(model: ^format.RSM_Model, elapsed_ms: f64, nodes: [dynamic]Node, textures: ^[dynamic]TextureHolder, sampler: ^sdl.GPUSampler, cmd: ^sdl.GPUCommandBuffer, vp_mat: matrix[4,4]f32, rp: ^sdl.GPURenderPass) { + animation_length := model.animation_length + fps := model.frames_per_second + frame_delay : f32 + total_frames : u32 + + if fps == 0 { + total_seconds := f32(animation_length) / 1000 + total_frames = u32(total_seconds * 30) + frame_delay = 1000 / f32(30) + } else { + total_frames = animation_length + frame_delay = 1000 / fps + } + + current_frame := u32(f32(elapsed_ms) / frame_delay) % total_frames + + for mesh in nodes { + node := mesh.node + rotation := linalg.quaternion_angle_axis(node.rotation_angle, node.rotation_axis) + + if len(node.rotation_keyframes) > 0 { + keyframe_idx := 0 + for i := 0; i < len(node.rotation_keyframes); i+= 1 { + keyframe := node.rotation_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + + q := node.rotation_keyframes[keyframe_idx].quaternion + rotation = quaternion(imag=q[0], jmag=q[1], kmag=q[2], real=q[3]) + } + + rotation = linalg.quaternion_normalize(rotation) + + scale := node.scale + if len(node.scale_keyframes) > 0 { + keyframe_idx := 0 + for i := 0; i < len(node.scale_keyframes); i+= 1 { + keyframe := node.scale_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + scale = node.scale_keyframes[keyframe_idx].scale + } + + translation := [3]f32{0,0,0} + if(len(node.translation_keyframes) > 0) { + keyframe_idx := 0 + for i := 0; i < len(node.translation_keyframes); i+= 1 { + keyframe := node.translation_keyframes[i] + if u32(keyframe.frame) <= current_frame { + keyframe_idx = i + } else do break + } + translation = node.translation_keyframes[keyframe_idx].translation + } + + r := linalg.matrix3_from_quaternion(rotation) + + s := matrix[3,3]f32{ + scale[0], 0, 0 , + 0, scale[1], 0 , + 0, 0, scale[2], + } + + a3 := r * node.offset_matrix * s + + t := node.translation1 + node.translation2 + translation + + // 4x4 affine matrix + model := matrix[4,4]f32{ + a3[0][0], a3[0][1], a3[0][2], t[0], + a3[1][0], a3[1][1], a3[1][2], t[1], + a3[2][0], a3[2][1], a3[2][2], t[2], + 0, 0, 0, 1 , + } + + pc := Push_Constants{} + + pc.mvp = vp_mat * model + pc.color = [4]f32{1, 0, 0, 1.0} + sdl.BindGPUFragmentSamplers(rp, 0, &(sdl.GPUTextureSamplerBinding{ + texture = textures[mesh.texture].texture, + sampler = sampler, + }), 1) + + sdl.PushGPUVertexUniformData(cmd, 0, &pc, u32(size_of(Push_Constants))) + sdl.DrawGPUIndexedPrimitives(rp, u32(mesh.end-mesh.start), 1, u32(mesh.start), 0, 0) + } +} \ No newline at end of file diff --git a/src/shared/shared.odin b/src/shared/shared.odin new file mode 100644 index 0000000..c19f6e7 --- /dev/null +++ b/src/shared/shared.odin @@ -0,0 +1,98 @@ +package shared + +import "core:math" + +Vec3 :: [3]f32 +Vec2 :: [2]f32 + +Camera :: struct { + center: Vec3, + distance: f32, + orientation: Rotor, + fov_deg: f32, +} + + +Rotor :: struct { + s, x, y, z: f32, +} + + +Vertex :: struct { + pos: Vec3, + uv: Vec2, +} + +vec3_dot :: proc(a, b: Vec3) -> f32 { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +} + +vec3_cross :: proc(a, b: Vec3) -> Vec3 { + return Vec3{ + a[1] * b[2] - a[2] * b[1], + a[2] * b[0] - a[0] * b[2], + a[0] * b[1] - a[1] * b[0], + } +} + +vec3_normalize :: proc(v: Vec3) -> Vec3 { + len2 := vec3_dot(v, v) + if len2 <= 0.000001 { + return Vec3{0, 0, 0} + } + inv := 1.0 / math.sqrt(len2) + return v * inv +} + +rotor_identity :: proc() -> Rotor { + return Rotor{1, 0, 0, 0} +} + +rotor_normalize :: proc(r: Rotor) -> Rotor { + len2 := r.s*r.s + r.x*r.x + r.y*r.y + r.z*r.z + if len2 <= 0.000001 { + return rotor_identity() + } + inv := 1.0 / math.sqrt(len2) + return Rotor{r.s * inv, r.x * inv, r.y * inv, r.z * inv} +} + +rotor_mul :: proc(a, b: Rotor) -> Rotor { + return Rotor{ + a.s*b.s - a.x*b.x - a.y*b.y - a.z*b.z, + a.s*b.x + a.x*b.s + a.y*b.z - a.z*b.y, + a.s*b.y - a.x*b.z + a.y*b.s + a.z*b.x, + a.s*b.z + a.x*b.y - a.y*b.x + a.z*b.s, + } +} + +rotor_from_axis_angle :: proc(axis: Vec3, angle: f32) -> Rotor { + a := vec3_normalize(axis) + h := angle * 0.5 + c := math.cos(h) + s := math.sin(h) + return Rotor{c, a[0] * s, a[1] * s, a[2] * s} +} + +rotate_vec3 :: proc(r: Rotor, v: Vec3) -> Vec3 { + u := Vec3{r.x, r.y, r.z} + t := vec3_cross(u, v) * 2.0 + return v + t * r.s + vec3_cross(u, t) +} + + +camera_forward :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 0, -1})) +} + +camera_right :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{1, 0, 0})) +} + +camera_up :: proc(c: Camera) -> Vec3 { + return vec3_normalize(rotate_vec3(c.orientation, Vec3{0, 1, 0})) +} + +camera_position :: proc(c: Camera) -> Vec3 { + return (c.center - camera_forward(c) * c.distance) +} \ No newline at end of file