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)); 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 cast(Model_Handle)(it_index + 1); } } return load_fbx(path); } #import "ufbx";