Compare commits
No commits in common. "28f0d737b1c7e1b893ed7e3e336c6b94741e4ea4" and "cd40b79306c0b9f057a1c215fd0b9f4e9c5bbf61" have entirely different histories.
28f0d737b1
...
cd40b79306
19 changed files with 493 additions and 3473 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,4 +3,3 @@ bin/
|
||||||
*.dll
|
*.dll
|
||||||
*.dylib
|
*.dylib
|
||||||
*.so
|
*.so
|
||||||
*.spv
|
|
||||||
|
|
|
||||||
211
idk/main.odin
211
idk/main.odin
|
|
@ -1,211 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
glslc ./idk/shader.glsl.vert -o idk/shader.spv.vert
|
|
||||||
glslc ./idk/shader.glsl.frag -o idk/shader.spv.frag
|
|
||||||
|
|
||||||
odin run idk/
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#version 460
|
|
||||||
|
|
||||||
layout(location=0) out vec4 frag_color;
|
|
||||||
|
|
||||||
layout(location=0) in vec4 color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
frag_color = color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#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;
|
|
||||||
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
768
main copy.odin
768
main copy.odin
|
|
@ -1,768 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "core:c"
|
|
||||||
import "core:fmt"
|
|
||||||
import "core:math"
|
|
||||||
import "core:mem"
|
|
||||||
import "core:os"
|
|
||||||
import "core:time"
|
|
||||||
import "core:path/filepath"
|
|
||||||
import "core:strings"
|
|
||||||
import "core:math/linalg"
|
|
||||||
|
|
||||||
import sdl "vendor:sdl3"
|
|
||||||
|
|
||||||
modelIndex := 0
|
|
||||||
|
|
||||||
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: matrix[4,4]f32,
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,16 +2,11 @@
|
||||||
|
|
||||||
layout(location = 0) out vec4 out_color;
|
layout(location = 0) out vec4 out_color;
|
||||||
|
|
||||||
layout(location = 0) in vec2 uv;
|
layout(set = 3, binding = 0) uniform FragmentUniforms {
|
||||||
|
mat4 u_mvp;
|
||||||
layout(set = 2, binding = 0) uniform sampler2D tex_sampler;
|
vec4 u_color;
|
||||||
|
};
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 c = texture(tex_sampler, uv);
|
out_color = u_color;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -7,11 +7,6 @@ layout(set = 1, binding = 0) uniform VertexUniforms {
|
||||||
vec4 u_color;
|
vec4 u_color;
|
||||||
};
|
};
|
||||||
|
|
||||||
layout(location=1) in vec2 uv;
|
|
||||||
|
|
||||||
layout(location=0) out vec2 out_uv;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
gl_Position = u_mvp * vec4(a_pos, 1.0);
|
||||||
out_uv = uv;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,114 +0,0 @@
|
||||||
package format
|
|
||||||
|
|
||||||
import "core:strings"
|
|
||||||
|
|
||||||
Version :: struct {
|
|
||||||
major: u8,
|
|
||||||
minor: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
version_ge :: #force_inline proc(v: Version, major, minor: int) -> 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..<len(b) {
|
|
||||||
ch := b[i]
|
|
||||||
if ch == '\\' {
|
|
||||||
out[i] = '/'
|
|
||||||
} else if ch >= '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
|
|
||||||
}
|
|
||||||
|
|
@ -1,385 +0,0 @@
|
||||||
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..<texture_count {
|
|
||||||
t, ok_t := read_fixed_string_clean(&r, int(texture_name_length))
|
|
||||||
if !ok_t { return out, .UnexpectedEOF }
|
|
||||||
append(&out.textures, normalize_path(t))
|
|
||||||
}
|
|
||||||
|
|
||||||
light_map_count, ok_lmc := read_i32(&r); if !ok_lmc { return out, .UnexpectedEOF }
|
|
||||||
light_map_width, ok_lmw := read_i32(&r); if !ok_lmw { return out, .UnexpectedEOF }
|
|
||||||
light_map_height, ok_lmh := read_i32(&r); if !ok_lmh { return out, .UnexpectedEOF }
|
|
||||||
_, ok_lmcpg := read_i32(&r) // light_map_cells_per_grid
|
|
||||||
if !ok_lmcpg { return out, .UnexpectedEOF }
|
|
||||||
|
|
||||||
if version_ge(info.version, 1, 7) {
|
|
||||||
skip := int(light_map_count * light_map_width * light_map_height * 4)
|
|
||||||
if r.at+skip > 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..<surface_count {
|
|
||||||
s := GND_Surface{}
|
|
||||||
for i in 0..<4 {
|
|
||||||
u, ok_sc_u := read_f32(&r)
|
|
||||||
if !ok_sc_u { return out, .UnexpectedEOF }
|
|
||||||
s.u[i] = u
|
|
||||||
}
|
|
||||||
for i in 0..<4 {
|
|
||||||
v, ok_sc_v := read_f32(&r)
|
|
||||||
if !ok_sc_v { return out, .UnexpectedEOF }
|
|
||||||
s.v[i] = v
|
|
||||||
}
|
|
||||||
ti, ok_ti := read_u16(&r); if !ok_ti { return out, .UnexpectedEOF }
|
|
||||||
li, ok_li := read_u16(&r); if !ok_li { return out, .UnexpectedEOF }
|
|
||||||
s.texture_index = i16(ti)
|
|
||||||
s.light_map_index = i16(li)
|
|
||||||
for i in 0..<4 {
|
|
||||||
c, ok_c := read_u8(&r)
|
|
||||||
if !ok_c { return out, .UnexpectedEOF }
|
|
||||||
s.color_bgra[i] = c
|
|
||||||
}
|
|
||||||
append(&out.surfaces, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
tile_count := int(info.width * info.height)
|
|
||||||
for _ in 0..<tile_count {
|
|
||||||
t := GND_Tile{}
|
|
||||||
h_sw, ok_hsw := read_f32(&r); if !ok_hsw { return out, .UnexpectedEOF }
|
|
||||||
h_se, ok_hse := read_f32(&r); if !ok_hse { return out, .UnexpectedEOF }
|
|
||||||
h_nw, ok_hnw := read_f32(&r); if !ok_hnw { return out, .UnexpectedEOF }
|
|
||||||
h_ne, ok_hne := read_f32(&r); if !ok_hne { return out, .UnexpectedEOF }
|
|
||||||
t.h_sw = h_sw
|
|
||||||
t.h_se = h_se
|
|
||||||
t.h_nw = h_nw
|
|
||||||
t.h_ne = h_ne
|
|
||||||
|
|
||||||
if version_ge(info.version, 1, 7) {
|
|
||||||
top_surface_index, ok_tsi := read_i32(&r); if !ok_tsi { return out, .UnexpectedEOF }
|
|
||||||
north_surface_index, ok_nsi := read_i32(&r); if !ok_nsi { return out, .UnexpectedEOF }
|
|
||||||
east_surface_index, ok_esi := read_i32(&r); if !ok_esi { return out, .UnexpectedEOF }
|
|
||||||
t.top_surface_index = top_surface_index
|
|
||||||
t.north_surface_index = north_surface_index
|
|
||||||
t.east_surface_index = east_surface_index
|
|
||||||
} else {
|
|
||||||
top16, ok_top16 := read_u16(&r); if !ok_top16 { return out, .UnexpectedEOF }
|
|
||||||
north16, ok_north16 := read_u16(&r); if !ok_north16 { return out, .UnexpectedEOF }
|
|
||||||
east16, ok_east16 := read_u16(&r); if !ok_east16 { return out, .UnexpectedEOF }
|
|
||||||
t.top_surface_index = i32(i16(top16))
|
|
||||||
t.north_surface_index = i32(i16(north16))
|
|
||||||
t.east_surface_index = i32(i16(east16))
|
|
||||||
}
|
|
||||||
append(&out.tiles, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, .None
|
|
||||||
}
|
|
||||||
|
|
||||||
parse_gat_info :: proc(data: []u8) -> (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
|
|
||||||
}
|
|
||||||
|
|
@ -1,282 +0,0 @@
|
||||||
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..<int(texture_count) {
|
|
||||||
name, ok = read_model_string(&r, model.version, 40)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&model.texture_names, normalize_path(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_below(model.version, 2, 2) {
|
|
||||||
root, ok = read_model_string(&r, model.version, 40)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&model.root_node_names, normalize_path(root))
|
|
||||||
} else {
|
|
||||||
root_count, ok = read_u32(&r)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(root_count) {
|
|
||||||
root, ok = read_model_string(&r, model.version, 40)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&model.root_node_names, normalize_path(root))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node_count, ok = read_u32(&r)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(node_count) {
|
|
||||||
node := RSM_Node{}
|
|
||||||
node.node_name, ok = read_model_string(&r, model.version, 40); if !ok { return model, .UnexpectedEOF }
|
|
||||||
node.parent_node_name, ok = read_model_string(&r, model.version, 40); if !ok { return model, .UnexpectedEOF }
|
|
||||||
|
|
||||||
if version_below(model.version, 2, 3) {
|
|
||||||
count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(count) {
|
|
||||||
idx, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&node.texture_indices, idx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(count) {
|
|
||||||
name, ok = read_model_string(&r, model.version, 40); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&node.texture_names, strings.to_lower(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for x in 0..<3 {
|
|
||||||
for y in 0..<3 {
|
|
||||||
node.offset_matrix[x][y], ok = read_f32(&r)
|
|
||||||
if !ok { return model, .UnexpectedEOF }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_below(model.version, 2, 2) {
|
|
||||||
node.translation1, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
}
|
|
||||||
node.translation2, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
if version_below(model.version, 2, 2) {
|
|
||||||
node.rotation_angle, ok = read_f32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
node.rotation_axis, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
node.scale, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
|
|
||||||
if node.rotation_angle == 0 {
|
|
||||||
node.rotation_axis = {1,0,0}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
node.scale = {1,1,1}
|
|
||||||
node.rotation_angle = 0
|
|
||||||
node.rotation_axis = {1,0,0}
|
|
||||||
}
|
|
||||||
|
|
||||||
vp_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(vp_count) {
|
|
||||||
v, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&node.vertex_positions, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
tc_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(tc_count) {
|
|
||||||
tc := RSM_TextureCoordinate{}
|
|
||||||
if version_ge(model.version, 1, 2) {
|
|
||||||
tc.color, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
}
|
|
||||||
u, ok1 := read_f32(&r); v, ok2 := read_f32(&r)
|
|
||||||
if !(ok1 && ok2) { return model, .UnexpectedEOF }
|
|
||||||
tc.coordinates = [2]f32{u, v}
|
|
||||||
append(&node.texture_coordinates, tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
face_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(face_count) {
|
|
||||||
f := RSM_Face{}
|
|
||||||
if version_ge(model.version, 2, 2) {
|
|
||||||
f.length, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
}
|
|
||||||
for i in 0..<3 { f.vertex_position_indices[i], ok = read_u16(&r); if !ok { return model, .UnexpectedEOF } }
|
|
||||||
for i in 0..<3 { f.texture_coordinate_indices[i], ok = read_u16(&r); if !ok { return model, .UnexpectedEOF } }
|
|
||||||
f.texture_index, ok = read_u16(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
f.padding, ok = read_u16(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
f.two_sided, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
f.smooth_group, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
if version_ge(model.version, 2, 2) {
|
|
||||||
extra_count := (int(f.length) - 24)
|
|
||||||
if extra_count > 0 {
|
|
||||||
for _ in 0..<extra_count/4 {
|
|
||||||
sg, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&f.smooth_group_extra, sg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
append(&node.faces, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_ge(model.version, 1, 6) {
|
|
||||||
s_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(s_count) {
|
|
||||||
sk := RSM_ScaleKeyframe{}
|
|
||||||
sk.frame, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
sk.scale, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
sk._reserved, ok = read_f32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&node.scale_keyframes, sk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(r_count) {
|
|
||||||
rk := RSM_RotationKeyframe{}
|
|
||||||
rk.frame, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for i in 0..<4 { rk.quaternion[i], ok = read_f32(&r); if !ok { return model, .UnexpectedEOF } }
|
|
||||||
append(&node.rotation_keyframes, rk)
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_ge(model.version, 2, 2) {
|
|
||||||
t_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(t_count) {
|
|
||||||
tk := RSM_TranslationKeyframe{}
|
|
||||||
tk.frame, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
tk.translation, ok = read_vec3(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
tk._reserved, ok = read_f32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&node.translation_keyframes, tk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_ge(model.version, 2, 3) {
|
|
||||||
tk_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(tk_count) {
|
|
||||||
tks := RSM_TexturesKeyframe{}
|
|
||||||
tks.texture_index, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
key_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(key_count) {
|
|
||||||
tk := RSM_TextureKeyframe{}
|
|
||||||
tk.operation_type, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
frame_count, ok = read_u32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
for _ in 0..<int(frame_count) {
|
|
||||||
tf := RSM_TextureFrame{}
|
|
||||||
tf.frame, ok = read_i32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
tf.operation_value, ok = read_f32(&r); if !ok { return model, .UnexpectedEOF }
|
|
||||||
append(&tk.texture_frames, tf)
|
|
||||||
}
|
|
||||||
append(&tks.texture_keyframes, tk)
|
|
||||||
}
|
|
||||||
append(&node.textures_keyframes, tks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
append(&model.nodes, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
return model, .None
|
|
||||||
}
|
|
||||||
|
|
@ -1,875 +0,0 @@
|
||||||
package gndview
|
|
||||||
|
|
||||||
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,
|
|
||||||
path: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RsmNode :: struct {
|
|
||||||
start: int,
|
|
||||||
end: int,
|
|
||||||
texture: int,
|
|
||||||
node: ^format.RSM_Node,
|
|
||||||
textures: []sdl.GPUTexture,
|
|
||||||
modelIndex: int,
|
|
||||||
vbufIndex: int,
|
|
||||||
ibufIndex: int,
|
|
||||||
transform: matrix[4,4]f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
GndNode :: struct {
|
|
||||||
start: int,
|
|
||||||
end: int,
|
|
||||||
texture: int,
|
|
||||||
node: ^format.GND_Tile,
|
|
||||||
textures: []sdl.GPUTexture,
|
|
||||||
}
|
|
||||||
|
|
||||||
Push_Constants :: struct {
|
|
||||||
mvp: matrix[4,4]f32,
|
|
||||||
color: [4]f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
loadTexture :: proc(path: string, textures: ^[dynamic]TextureHolder) -> 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
531
src/main.odin
531
src/main.odin
|
|
@ -1,12 +1,201 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "core:c"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:math"
|
||||||
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import sdl "vendor:sdl3"
|
import "core:time"
|
||||||
import "rsmview"
|
|
||||||
import "gndview"
|
|
||||||
import "shared"
|
|
||||||
|
|
||||||
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
|
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 {
|
read_file_or_fail :: proc(path: string) -> []u8 {
|
||||||
file_bytes, err := os.read_entire_file(path, context.allocator)
|
file_bytes, err := os.read_entire_file(path, context.allocator)
|
||||||
|
|
@ -17,7 +206,7 @@ read_file_or_fail :: proc(path: string) -> []u8 {
|
||||||
return file_bytes
|
return file_bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage, num_samplers: u32) -> ^sdl.GPUShader {
|
create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUShaderStage) -> ^sdl.GPUShader {
|
||||||
bytes := read_file_or_fail(path)
|
bytes := read_file_or_fail(path)
|
||||||
if len(bytes) == 0 {
|
if len(bytes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -29,7 +218,7 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh
|
||||||
entrypoint = cstring("main"),
|
entrypoint = cstring("main"),
|
||||||
format = sdl.GPUShaderFormat{.SPIRV},
|
format = sdl.GPUShaderFormat{.SPIRV},
|
||||||
stage = stage,
|
stage = stage,
|
||||||
num_samplers = num_samplers,
|
num_samplers = 0,
|
||||||
num_storage_textures = 0,
|
num_storage_textures = 0,
|
||||||
num_storage_buffers = 0,
|
num_storage_buffers = 0,
|
||||||
num_uniform_buffers = 1,
|
num_uniform_buffers = 1,
|
||||||
|
|
@ -44,23 +233,41 @@ create_gpu_shader :: proc(device: ^sdl.GPUDevice, path: string, stage: sdl.GPUSh
|
||||||
return shader
|
return shader
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_data_to_gpu :: proc(
|
copy_vertices_to_gpu :: proc(device: ^sdl.GPUDevice, cmd: ^sdl.GPUCommandBuffer, vertices: []Vertex, dst: ^sdl.GPUBuffer) -> bool {
|
||||||
device: ^sdl.GPUDevice,
|
byte_count := len(vertices) * size_of(Vertex)
|
||||||
cmd: ^sdl.GPUCommandBuffer,
|
tb_info := sdl.GPUTransferBufferCreateInfo{
|
||||||
vertices: []shared.Vertex,
|
usage = .UPLOAD,
|
||||||
vert_buf: ^sdl.GPUBuffer,
|
size = u32(byte_count),
|
||||||
indices: []u16,
|
props = 0,
|
||||||
ind_buf: ^sdl.GPUBuffer
|
}
|
||||||
) -> bool {
|
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)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
GPU_Vertex :: struct {
|
|
||||||
position: [3]f32,
|
|
||||||
uv: [2]f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
if !sdl.Init(sdl.INIT_VIDEO) {
|
if !sdl.Init(sdl.INIT_VIDEO) {
|
||||||
fmt.println("SDL init failed:", sdl.GetError())
|
fmt.println("SDL init failed:", sdl.GetError())
|
||||||
|
|
@ -95,13 +302,13 @@ main :: proc() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX, 0)
|
vertex_shader := create_gpu_shader(device, "shaders/cubes.vert.spv", .VERTEX)
|
||||||
if vertex_shader == nil {
|
if vertex_shader == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer sdl.ReleaseGPUShader(device, vertex_shader)
|
defer sdl.ReleaseGPUShader(device, vertex_shader)
|
||||||
|
|
||||||
fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT, 1)
|
fragment_shader := create_gpu_shader(device, "shaders/cubes.frag.spv", .FRAGMENT)
|
||||||
if fragment_shader == nil {
|
if fragment_shader == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -113,12 +320,11 @@ main :: proc() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vb_descs := []sdl.GPUVertexBufferDescription{
|
vb_descs := [1]sdl.GPUVertexBufferDescription{
|
||||||
{slot = 0, pitch = size_of(shared.Vertex), input_rate = .VERTEX, instance_step_rate = 0},
|
{slot = 0, pitch = size_of(Vertex), input_rate = .VERTEX, instance_step_rate = 0},
|
||||||
}
|
}
|
||||||
vattrs := []sdl.GPUVertexAttribute{
|
vattrs := [1]sdl.GPUVertexAttribute{
|
||||||
{location = 0, buffer_slot = 0, format = .FLOAT3, offset = u32(offset_of(shared.Vertex, pos))},
|
{location = 0, buffer_slot = 0, format = .FLOAT3, offset = 0},
|
||||||
{location = 1, buffer_slot = 0, format = .FLOAT2, offset = u32(offset_of(shared.Vertex, uv))},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blend := sdl.GPUColorTargetBlendState{
|
blend := sdl.GPUColorTargetBlendState{
|
||||||
|
|
@ -136,15 +342,15 @@ main :: proc() {
|
||||||
{format = color_format, blend_state = blend},
|
{format = color_format, blend_state = blend},
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{
|
pipeline_ci := sdl.GPUGraphicsPipelineCreateInfo{
|
||||||
vertex_shader = vertex_shader,
|
vertex_shader = vertex_shader,
|
||||||
fragment_shader = fragment_shader,
|
fragment_shader = fragment_shader,
|
||||||
vertex_input_state = sdl.GPUVertexInputState{
|
vertex_input_state = sdl.GPUVertexInputState{
|
||||||
vertex_buffer_descriptions = &vb_descs[0],
|
vertex_buffer_descriptions = &vb_descs[0],
|
||||||
num_vertex_buffers = 1,
|
num_vertex_buffers = 1,
|
||||||
vertex_attributes = &vattrs[0],
|
vertex_attributes = &vattrs[0],
|
||||||
num_vertex_attributes = 2,
|
num_vertex_attributes = 1,
|
||||||
},
|
},
|
||||||
primitive_type = .TRIANGLELIST,
|
primitive_type = .TRIANGLELIST,
|
||||||
rasterizer_state = sdl.GPURasterizerState{
|
rasterizer_state = sdl.GPURasterizerState{
|
||||||
fill_mode = .FILL,
|
fill_mode = .FILL,
|
||||||
|
|
@ -168,15 +374,15 @@ main :: proc() {
|
||||||
front_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,
|
compare_mask = 0,
|
||||||
write_mask = 0,
|
write_mask = 0,
|
||||||
enable_depth_test = true,
|
enable_depth_test = false,
|
||||||
enable_depth_write = true,
|
enable_depth_write = false,
|
||||||
enable_stencil_test = true,
|
enable_stencil_test = false,
|
||||||
},
|
},
|
||||||
target_info = sdl.GPUGraphicsPipelineTargetInfo{
|
target_info = sdl.GPUGraphicsPipelineTargetInfo{
|
||||||
color_target_descriptions = &color_targets[0],
|
color_target_descriptions = &color_targets[0],
|
||||||
num_color_targets = 1,
|
num_color_targets = 1,
|
||||||
depth_stencil_format = .D32_FLOAT,
|
depth_stencil_format = .INVALID,
|
||||||
has_depth_stencil_target = true,
|
has_depth_stencil_target = false,
|
||||||
},
|
},
|
||||||
props = 0,
|
props = 0,
|
||||||
}
|
}
|
||||||
|
|
@ -188,16 +394,249 @@ main :: proc() {
|
||||||
}
|
}
|
||||||
defer sdl.ReleaseGPUGraphicsPipeline(device, pipeline)
|
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}},
|
||||||
|
}
|
||||||
|
|
||||||
pipeline_create_info := sdl.GPUGraphicsPipelineCreateInfo{
|
vbuf_info := sdl.GPUBufferCreateInfo{
|
||||||
primitive_type = .TRIANGLELIST,
|
usage = sdl.GPUBufferUsageFlags{.VERTEX},
|
||||||
rasterizer_state = {
|
size = u32(len(vertices) * size_of(Vertex)),
|
||||||
fill_mode = .LINE,
|
props = 0,
|
||||||
cull_mode = .FRONT,
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gndview.runGame(window, device, pipeline)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = sdl.WaitForGPUIdle(device)
|
_ = sdl.WaitForGPUIdle(device)
|
||||||
}
|
}
|
||||||
|
|
@ -1,646 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue