Files
coven/renderer/model.jai
2025-03-26 23:40:35 +01:00

684 lines
23 KiB
Plaintext

Node_Handle :: #type, distinct u32;
Material_Handle :: #type, distinct u32;
Model_Handle :: #type, isa u32;
Model_Asset :: struct {
path: string;
}
MAX_BONES :: 128;
MAX_WEIGHTS :: 4;
Node :: struct {
name : string;
path : string;
transform : Transform;
meshes : [..] Mesh_Handle;
material_defaults : [..] Model_Material;
materials_old : [..] Material_Old;
num_bones: s32;
parent : Node_Handle;
children : [..] Node_Handle;
// Instance variables
materials : [..] *Material;
has_sampled_animation: bool;
}
Node_Animation :: struct {
framerate : float64;
num_frames : s32;
rotation : [..] Quaternion;
position : [..] Vector3;
scale : [..] Vector3;
constant : bool;
const_rotation : Quaternion;
const_position : Vector3;
const_scale : Vector3;
is_set: bool;
}
Animation :: struct {
name: string;
duration: float64;
num_frames: s32;
framerate: float64;
nodes : [..] Node_Animation;
}
Model_Material :: struct {
base_color : Vector4;
textures : struct {
base_color: Texture_Handle;
normal: Texture_Handle;
}
}
Model :: struct {
name : string;
path : string;
nodes : [..] Node;
animations : [..] Animation;
materials : [..] Model_Material;
}
get_first_mesh_from_model :: (handle: Model_Handle) -> Mesh_Handle, bool {
model := get_model_by_handle(handle);
for model.nodes {
for m: it.meshes {
return m, true;
}
}
return 0, false;
}
get_mesh_by_name :: (model: Model, name: string) -> Mesh_Handle, bool {
for model.nodes {
for m: it.meshes {
mesh := parray_get(*engine.renderer.meshes, m);
if mesh.name == name {
return m, true;
}
}
}
return 0, false;
}
Coloru16 :: struct {
r: u16;
g: u16;
b: u16;
a: u16;
}
parse_fbx_node :: (model: *Model, fbx_node: *ufbx_node) {
node : Node;
node.name = copy_string(to_string(fbx_node.name.data));
if fbx_node.parent != null {
node.parent = cast(Node_Handle) fbx_node.parent.typed_id + 1; // UFBX ids are 0-indexed, but 0 means none in our API
}
t := create_identity_transform();
transform := ufbx_matrix_to_transform(*fbx_node.node_to_parent);
t.position.x = cast(float)transform.translation.x;
t.position.y = cast(float)transform.translation.y;
t.position.z = cast(float)transform.translation.z;
t.scale.x = xx transform.scale.x;
t.scale.y = xx transform.scale.y;
t.scale.z = xx transform.scale.z;
t.orientation.x = cast(float)transform.rotation.x;
t.orientation.y = cast(float)transform.rotation.y;
t.orientation.z = cast(float)transform.rotation.z;
t.orientation.w = cast(float)transform.rotation.w;
update_matrix(*t);
//t.model_matrix = make_matrix(world_position, world_orientation, world_scale);
t.dirty = true;
node.transform = t;
Mesh_Vertex :: struct {
position: Vector3;
normal : Vector3;
uv : Vector2;
};
if fbx_node.mesh != null {
fbx_mesh := fbx_node.mesh;
max_parts : size_t = 0;
max_triangles : size_t = 0;
for pi: 0..cast(s32)fbx_mesh.materials.count-1 {
mesh_mat := fbx_mesh.materials.data[pi];
if mesh_mat.num_triangles == 0 continue;
max_parts += 1;
max_triangles = max(max_triangles, mesh_mat.num_triangles);
}
num_tri_indices := fbx_mesh.max_face_triangles * 3;
tri_indices : [..] u32;
vertices : [..] Mesh_Vertex;
indices : [..] u32;
skin_vertices : [..] Skin_Vertex;
mesh_skin_vertices : [..] Skin_Vertex;
tri_indices.allocator = temp;
vertices.allocator = temp;
indices.allocator = temp;
skin_vertices.allocator = temp;
mesh_skin_vertices.allocator = temp;
indices.allocator = temp;
array_resize(*tri_indices, xx num_tri_indices);
array_resize(*vertices, xx max_triangles * 3);
array_resize(*indices, xx max_triangles * 3);
array_resize(*skin_vertices, xx max_triangles * 3);
array_resize(*mesh_skin_vertices, xx max_triangles * 3);
num_bones : s32 = 0;
skin : *ufbx_skin_deformer = null;
skinned : bool;
bone_indices: [..] s32;
bone_matrices: [..] Matrix4;
bone_indices.allocator = temp;
bone_matrices.allocator = temp;
if fbx_mesh.skin_deformers.count > 0 && fbx_mesh.skin_deformers.data[0].clusters.count > 0 {
skinned = true;
skin = fbx_mesh.skin_deformers.data[0];
for ci: 0..skin.clusters.count-1 {
cluster := skin.clusters.data[ci];
if num_bones < MAX_BONES {
array_add(*bone_indices, cast(s32)cluster.bone_node.typed_id);
array_add(*bone_matrices, ufbx_to_mat4(cluster.geometry_to_bone));
num_bones += 1;
}
}
for vi: 0..fbx_mesh.num_vertices-1 {
num_weights := 0;
total_weight := 0.0;
weights : [4] float;
clusters : [4] float;
vertex_weights := skin.vertices.data[vi];
for wi: 0..vertex_weights.num_weights-1 {
if num_weights >= 4 break;
weight := skin.weights.data[vertex_weights.weight_begin + wi];
if weight.cluster_index < MAX_BONES {
total_weight += cast(float)weight.weight;
clusters[num_weights] = cast(float)weight.cluster_index;
weights[num_weights] = cast(float)weight.weight;
num_weights += 1;
}
}
if total_weight > 0.0 {
skin_vert := *mesh_skin_vertices[vi];
quantized_sum : float = 0;
for i: 0..3 {
quantized_weight := weights[i] / total_weight;
quantized_sum += quantized_weight;
skin_vert.bone_index[i] = clusters[i];
skin_vert.bone_weight[i] = quantized_weight;
}
skin_vert.bone_weight[0] += 1.0 - quantized_sum;
}
}
}
node.num_bones = num_bones;
for pi: 0..fbx_mesh.materials.count-1 {
mesh : Mesh;
mesh.name = copy_string(to_string(fbx_mesh.name.data));
mesh.num_bones = num_bones;
array_resize(*mesh.bone_indices, bone_indices.count);
array_resize(*mesh.bone_matrices, bone_matrices.count);
memcpy(mesh.bone_indices.data, bone_indices.data, size_of(s32) * bone_indices.count);
memcpy(mesh.bone_matrices.data, bone_matrices.data, size_of(Matrix4) * bone_matrices.count);
mesh_mat := fbx_mesh.materials.data[pi];
if mesh_mat.num_triangles == 0 continue;
num_indices : size_t = 0;
for fi: 0..mesh_mat.num_faces-1 {
face := fbx_mesh.faces.data[mesh_mat.face_indices.data[fi]];
num_tris := ufbx_catch_triangulate_face(null, tri_indices.data, num_tri_indices, fbx_mesh, face);
default_uv : ufbx_vec2;
for vi: 0..num_tris * 3 - 1 {
ix := tri_indices.data[num_tris * 3 - 1 -vi];
vert := *vertices.data[num_indices];
pos := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_position, ix);
normal := ufbx_catch_get_vertex_vec3(null, *fbx_mesh.vertex_normal, ix);
uv : ufbx_vec2;
if fbx_mesh.vertex_uv.exists {
uv = ufbx_catch_get_vertex_vec2(null, *fbx_mesh.vertex_uv, ix);
}
vert.position.x = xx pos.x;
vert.position.y = xx pos.y;
vert.position.z = xx pos.z;
vert.normal.x = xx normal.x;
vert.normal.y = xx normal.y;
vert.normal.z = xx normal.z;
vert.normal = normalize(vert.normal);
vert.uv.x = xx uv.x;
vert.uv.y = xx uv.y;
if skin {
skin_vertices[num_indices] = mesh_skin_vertices[fbx_mesh.vertex_indices.data[ix]];
}
num_indices += 1;
}
}
num_streams : u64 = 1;
streams : [2] ufbx_vertex_stream;
streams[0].data = vertices.data;
streams[0].vertex_size = size_of(Mesh_Vertex);
if skin {
num_streams = 2;
streams[1].data = skin_vertices.data;
streams[1].vertex_size = size_of(Skin_Vertex);
}
error : ufbx_error;
num_vertices := ufbx_generate_indices(streams.data, num_streams, indices.data, num_indices, null, *error);
if error.type != .UFBX_ERROR_NONE {
log_error("Failed to generate index buffer\n");
}
array_reserve(*mesh.indices, xx num_indices);
for 0..num_indices-1 {
array_add(*mesh.indices, cast(u32)indices[it]);
}
array_reserve(*mesh.positions, xx num_vertices);
array_reserve(*mesh.normals, xx num_vertices);
array_reserve(*mesh.texcoords, xx num_vertices);
if skin {
array_reserve(*mesh.skin_data, xx num_vertices);
}
for 0..num_vertices-1 {
v := vertices[it];
array_add(*mesh.positions, v.position);
array_add(*mesh.normals, v.normal);
array_add(*mesh.texcoords, v.uv);
if skin {
array_add(*mesh.skin_data, skin_vertices[it]);
}
}
// Generate tangents + bitangents
if mesh.texcoords.count > 0 {
array_resize(*mesh.tangents, mesh.positions.count);
array_resize(*mesh.bitangents, mesh.positions.count);
i : u64 = 0;
while i < num_indices - 1 {
defer i += 3;
i0 := mesh.indices[i+0];
i1 := mesh.indices[i+1];
i2 := mesh.indices[i+2];
pos0 := mesh.positions[i0];
pos1 := mesh.positions[i1];
pos2 := mesh.positions[i2];
uv0 := mesh.texcoords[i0];
uv1 := mesh.texcoords[i1];
uv2 := mesh.texcoords[i2];
edge1 := pos1 - pos0;
edge2 := pos2 - pos0;
delta_uv1 := uv1 - uv0;
delta_uv2 := uv2 - uv0;
f := 1.0 / (delta_uv1.x * delta_uv2.y - delta_uv2.x * delta_uv1.y);
tangent : Vector3 = ---;
bitangent : Vector3 = ---;
tangent.x = f * (delta_uv2.y * edge1.x - delta_uv1.y * edge2.x);
tangent.y = f * (delta_uv2.y * edge1.y - delta_uv1.y * edge2.y);
tangent.z = f * (delta_uv2.y * edge1.z - delta_uv1.y * edge2.z);
bitangent.x = f * (-delta_uv2.x * edge1.x + delta_uv1.x * edge2.x);
bitangent.y = f * (-delta_uv2.x * edge1.y + delta_uv1.x * edge2.y);
bitangent.z = f * (-delta_uv2.x * edge1.z + delta_uv1.x * edge2.z);
mesh.tangents[i0] = tangent;
mesh.tangents[i1] = tangent;
mesh.tangents[i2] = tangent;
mesh.bitangents[i0] = bitangent;
mesh.bitangents[i1] = bitangent;
mesh.bitangents[i2] = bitangent;
}
}
ib_size := size_of(u32)*mesh.indices.count;
mesh.ib = create_index_buffer(engine.renderer, mesh.indices.data, xx ib_size);
//material : Base_Material;
//if mesh_mat.material != null {
// for 0..mesh_mat.material.props.props.count-1 {
// prop := mesh_mat.material.props.props.data[it];
// prop_name := to_string(prop.name.data);
// if prop_name == "DiffuseColor" {
// material.base_color.x = xx prop.value_vec3.x;
// material.base_color.y = xx prop.value_vec3.y;
// material.base_color.z = xx prop.value_vec3.z;
// material.base_color.w = 1.0;
// }
// }
//}
array_add(*node.meshes, parray_add(*engine.renderer.meshes, mesh));struct {
path: string;
}
array_add(*node.material_defaults, model.materials[mesh_mat.material.typed_id]); // @Incomplete
}
}
array_reserve(*node.children, xx fbx_node.children.count);
for 0..cast(s32)fbx_node.children.count-1 {
array_add(*node.children, cast(Node_Handle)fbx_node.children.data[it].typed_id + 1);
}
array_add(*model.nodes, node);
}
ufbx_to_v3 :: (ufbx_v3: ufbx_vec3) -> Vector3 {
v : Vector3 = ---;
v.x = cast(float)ufbx_v3.x;
v.y = cast(float)ufbx_v3.y;
v.z = cast(float)ufbx_v3.z;
return v;
}
ufbx_to_quat :: (ufbx_q: ufbx_quat) -> Quaternion {
q : Quaternion = ---;
q.x = cast(float)ufbx_q.x;
q.y = cast(float)ufbx_q.y;
q.z = cast(float)ufbx_q.z;
q.w = cast(float)ufbx_q.w;
return q;
}
ufbx_to_mat4 :: (m: ufbx_matrix) -> Matrix4 {
result : Matrix4;
result._11 = cast(float)m.m00;
result._12 = cast(float)m.m01;
result._13 = cast(float)m.m02;
result._14 = cast(float)m.m03;
result._21 = cast(float)m.m10;
result._22 = cast(float)m.m11;
result._23 = cast(float)m.m12;
result._24 = cast(float)m.m13;
result._31 = cast(float)m.m20;
result._32 = cast(float)m.m21;
result._33 = cast(float)m.m22;
result._34 = cast(float)m.m23;
result._41 = 0.0;
result._42 = 0.0;
result._43 = 0.0;
result._44 = 1.0;
return result;
}
v3_equal :: (v1: Vector3, v2: Vector3) -> bool {
return abs(v1.x - v2.x) < 0.0001 && abs(v1.y - v2.y) < 0.0001 && abs(v1.z - v2.z) < 0.0001;
}
quat_equal :: (q1: Quaternion, q2: Quaternion) -> bool {
return abs(q1.x - q2.x) < 0.0001 && abs(q1.y - q2.y) < 0.0001 && abs(q1.z - q2.z) < 0.0001 && abs(q1.w - q2.w) < 0.0001;
}
parse_node_anim :: (animation: *Animation, node_anim: *Node_Animation, stack: *ufbx_anim_stack, node: *ufbx_node) {
array_resize(*node_anim.rotation, animation.num_frames);
array_resize(*node_anim.position, animation.num_frames);
array_resize(*node_anim.scale, animation.num_frames);
const_rotation := true;
const_position := true;
const_scale := true;
if animation.num_frames == 0 {
node_anim.const_rotation = .{0,0,0,1};
node_anim.const_position = .{0,0,0};
node_anim.const_scale = .{1,1,1};
} else {
// Sample the node's transform evenly for the whole animation stack duration
for i: 0..animation.num_frames-1 {
time := stack.time_begin + cast(float64)i / animation.framerate;
transform := ufbx_evaluate_transform(*stack.anim, node, time);
node_anim.rotation[i] = ufbx_to_quat(transform.rotation);
node_anim.position[i] = ufbx_to_v3(transform.translation);
node_anim.scale[i] = ufbx_to_v3(transform.scale);
if i > 0 {
// Negated quaternions are equivalent, but interpolating between ones of different
// polarity takes a the longer path, so flip the quaternion if necessary.
if dot(node_anim.rotation[i], node_anim.rotation[i - 1]) < 0.0 {
node_anim.rotation[i].x = -node_anim.rotation[i].x;
node_anim.rotation[i].y = -node_anim.rotation[i].y;
node_anim.rotation[i].z = -node_anim.rotation[i].z;
node_anim.rotation[i].w = -node_anim.rotation[i].w;
}
// Keep track of which channels are constant for the whole animation as an optimization
if !quat_equal(node_anim.rotation[i - 1], node_anim.rotation[i]) const_rotation = false;
if !v3_equal(node_anim.position[i - 1], node_anim.position[i]) const_position = false;
if !v3_equal(node_anim.scale[i - 1], node_anim.scale[i]) const_scale = false;
}
}
if const_rotation { node_anim.const_rotation = node_anim.rotation[0]; array_free(node_anim.rotation); node_anim.rotation.count = 0; }
if const_position { node_anim.const_position = node_anim.position[0]; array_free(node_anim.position); node_anim.position.count = 0; }
if const_scale { node_anim.const_scale = node_anim.scale[0]; array_free(node_anim.scale); node_anim.scale.count = 0; }
}
node_anim.is_set = true;
}
parse_anim_stack :: (animation: *Animation, stack: *ufbx_anim_stack, scene: *ufbx_scene, model: *Model) {
animation.name = copy_string(to_string(stack.name.data));
animation.framerate = 24.0;
animation.duration = cast(float)(stack.time_end - stack.time_begin);
animation.num_frames = cast(s32)(animation.duration * animation.framerate);
animation.framerate = cast(float)(animation.num_frames - 1) / animation.duration;
array_resize(*animation.nodes, xx model.nodes.count);
if scene.nodes.count != xx model.nodes.count {
for 0..scene.nodes.count-1 {
scene_node := scene.nodes.data[it];
for j: 0..model.nodes.count-1 {
model_node := model.nodes[j];
if to_string(scene_node.name.data) == model_node.name {
animation.nodes[j] = .{};
parse_node_anim(animation, *animation.nodes[j], stack, scene_node);
break;
}
}
}
for 0..model.nodes.count-1 {
if !animation.nodes[it].is_set {
animation.nodes[it].const_position = model.nodes[it].transform.position;
animation.nodes[it].const_rotation = model.nodes[it].transform.orientation;
animation.nodes[it].const_scale = model.nodes[it].transform.scale;
animation.nodes[it].is_set = true;
}
}
} else {
for 0..scene.nodes.count-1 {
parse_node_anim(animation, *animation.nodes[it], stack, scene.nodes.data[it]);
}
}
}
load_fbx_animations_into_existing_model :: (path: string, model: *Model) {
// Load the file as normal, but only loop through the animations
opts : ufbx_load_opts = .{};
opts.load_external_files = true;
opts.evaluate_skinning = true;
opts.target_axes.right = .POSITIVE_X;
opts.target_axes.up = .POSITIVE_Y;
opts.target_axes.front = .NEGATIVE_Z;
opts.target_unit_meters = 1.0;
//opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
error : ufbx_error;
scene := ufbx_load_file(to_temp_c_string(path), *opts, *error);
if scene == null {
log_error("FBX '%' could not be loaded\n", path);
}
// The rig and nodes should be the same as the specified model
//if cast(s32)scene.nodes.count != model.nodes.count {
// log_error("Node count for '%' (%) not the same as original model (%)\n", path, scene.nodes.count, model.nodes.count);
// ufbx_free_scene(scene);
// return;
//}
for 0..scene.anim_stacks.count-1 {
animation : Animation;
parse_anim_stack(*animation, scene.anim_stacks.data[it], scene, model);
array_add(*model.animations, animation);
}
ufbx_free_scene(scene);
}
get_model_by_path :: (path: string) -> *Model {
for * engine.renderer.model_lib {
if it.path == path {
return it;
}
}
return null;
}
load_fbx_texture :: (map: ufbx_material_map, format: Format) -> Texture_Handle {
if map.texture != null && map.texture.content.size > 0 {
return load_texture_from_data(engine.renderer, map.texture.content.data, map.texture.content.size, format=format);
}
return 0;
}
load_fbx :: (path: string) -> Model_Handle, bool {
opts : ufbx_load_opts = .{};
opts.load_external_files = true;
opts.generate_missing_normals = true;
opts.normalize_normals = true;
opts.evaluate_skinning = true;
opts.target_axes.right = .POSITIVE_X;
opts.target_axes.up = .POSITIVE_Y;
opts.target_axes.front = .NEGATIVE_Z;
opts.target_unit_meters = 1.0;
//opts.space_conversion = ufbx_space_conversion.UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
error : ufbx_error;
scene := ufbx_load_file(to_temp_c_string(path), *opts, *error);
if scene == null {
log_error("FBX '%' could not be loaded", path);
return 0, false;
}
model : Model;
model.path = copy_string(path);
model.name = copy_string(path);
// Materials
for i: 0..scene.materials.count - 1 {
mat := scene.materials.data[i];
model_material : Model_Material;
model_material.textures.base_color = load_fbx_texture(mat.pbr.base_color, format=.R8G8B8A8_UNORM_SRGB);
model_material.textures.normal = load_fbx_texture(mat.pbr.normal_map, format=.R8G8B8A8_UNORM);
for 0..mat.props.props.count-1 {
prop := mat.props.props.data[it];
prop_name := to_string(prop.name.data,, allocator=temp);
if prop_name == "DiffuseColor" {
model_material.base_color.x = xx prop.value_vec3.x;
model_material.base_color.y = xx prop.value_vec3.y;
model_material.base_color.z = xx prop.value_vec3.z;
model_material.base_color.w = 1.0;
}
}
//mat.pbr.base_factor;
//create_texture :: (using renderer: *Renderer, data: *void, width: u32, height: u32, channels: u32, path: string = "", generate_mips: bool = true) -> Texture_Handle {
//Material &dst = materials[i + 1];
//dst.base_factor.value.x = 1.0f;
//setup_texture(dst.base_factor, mat->pbr.base_factor);
//setup_texture(dst.base_color, mat->pbr.base_color);
//setup_texture(dst.roughness, mat->pbr.roughness);
//setup_texture(dst.metallic, mat->pbr.metalness);
//setup_texture(dst.emission_factor, mat->pbr.emission_factor);
//setup_texture(dst.emission_color, mat->pbr.emission_color);
//dst.base_color.image.srgb = true;
//dst.emission_color.image.srgb = true;
array_add(*model.materials, model_material);
}
array_reserve(*model.nodes, xx scene.nodes.count);
zero_handle : Node_Handle = 0;
for i: 0..scene.nodes.count-1 {
node := scene.nodes.data[i];
parse_fbx_node(*model, node);
}
// Load animations
array_resize(*model.animations, xx scene.anim_stacks.count);
for 0..model.animations.count-1 {
parse_anim_stack(*model.animations[it], scene.anim_stacks.data[it], scene, *model);
}
ufbx_free_scene(scene);
array_add(*engine.renderer.model_lib, model);
handle := cast(Model_Handle)engine.renderer.model_lib.count;
return handle, false;
}
get_or_load_model :: (path: string) -> Model_Handle {
for * engine.renderer.model_lib {
if it.path == path {
return xx (it_index + 1);
}
}
return load_fbx(path);
}
#import "ufbx";