diff --git a/modules/qbo/qbo_document.cpp b/modules/qbo/qbo_document.cpp index e22ccb22d02..3ff7e19c08c 100644 --- a/modules/qbo/qbo_document.cpp +++ b/modules/qbo/qbo_document.cpp @@ -39,315 +39,371 @@ #include "scene/resources/surface_tool.h" Error QBODocument::_parse_qbo_data(Ref f, Ref p_state, uint32_t p_flags, String p_base_path, String p_path) { - ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open QBO file."); - - Vector> nodes; - HashMap bone_name_map; - Vector parent_stack; - Vector transform_stack; - Vector> node_children; - Ref current_animation; - bool in_hierarchy = false; - bool in_motion = false; - double frame_time = 0.03333333; - int frame_count = 0; - Vector> frame_data; - - Vector vertices; - Vector normals; - Vector uvs; - Vector colors; - HashMap joint_indices_map; - HashMap joint_weights_map; - Ref surf_tool = memnew(SurfaceTool); - Ref gltf_mesh; - gltf_mesh.instantiate(); - String current_material; - - surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES); - - while (!f->eof_reached()) { - String l = f->get_line().strip_edges(); - if (l.is_empty()) { - continue; - } - - if (l.begins_with("HIERARCHY")) { - in_hierarchy = true; - in_motion = false; - parent_stack.clear(); - transform_stack.clear(); - bone_name_map.clear(); - nodes.resize(0); - node_children.resize(0); - } else if (l.begins_with("MOTION")) { - in_hierarchy = false; - in_motion = true; - current_animation.instantiate(); - current_animation->set_name("animation"); - } else if (in_hierarchy) { - if (l.begins_with("ROOT") || l.begins_with("JOINT")) { - Ref node; - node.instantiate(); - - String bone_name = l.substr(l.find(" ") + 1).strip_edges(); - if (bone_name_map.has(bone_name)) { - int count = 2; - String new_name; - do { - new_name = bone_name + "_" + itos(count++); - } while (bone_name_map.has(new_name)); - bone_name = new_name; - } - node->set_original_name(bone_name); - - GLTFNodeIndex parent = -1; - if (!parent_stack.is_empty()) { - parent = parent_stack[parent_stack.size() - 1]; - node_children.write[parent].push_back(nodes.size()); - } - - bone_name_map[bone_name] = nodes.size(); - nodes.push_back(node); - node_children.push_back(Vector()); - parent_stack.push_back(nodes.size() - 1); - transform_stack.push_back(Transform3D()); - } else if (l.begins_with("End Site")) { - if (!parent_stack.is_empty()) { - parent_stack.remove_at(parent_stack.size() - 1); - transform_stack.remove_at(transform_stack.size() - 1); - } - } else if (l.begins_with("OFFSET")) { - Vector parts = l.split(" "); - if (parts.size() >= 4 && !nodes.is_empty()) { - Vector3 offset( - parts[1].to_float(), - parts[2].to_float(), - parts[3].to_float()); - Ref node = nodes[nodes.size() - 1]; - node->set_xform(Transform3D(Basis(), offset)); - } - } else if (l.begins_with("ORIENT")) { - Vector parts = l.split(" "); - if (parts.size() >= 5 && !nodes.is_empty()) { - Quaternion rot( - parts[1].to_float(), - parts[2].to_float(), - parts[3].to_float(), - parts[4].to_float()); - Ref node = nodes[nodes.size() - 1]; - Transform3D t = node->get_xform(); - t.basis = Basis(rot); - node->set_xform(t); - } - } - } else if (in_motion) { - if (l.begins_with("Frames:")) { - frame_count = l.get_slice(" ", 1).to_int(); - } else if (l.begins_with("Frame Time:")) { - frame_time = l.get_slice(" ", 2).to_float(); - } else { - frame_data.push_back(l.split(" ")); - } - } else { - if (l.begins_with("v ")) { - Vector parts = l.split(" "); - Vector3 vertex( - parts[1].to_float(), - parts[2].to_float(), - parts[3].to_float()); - vertices.push_back(vertex); - - if (parts.size() >= 7) { - Color color( - parts[4].to_float(), - parts[5].to_float(), - parts[6].to_float()); - colors.resize(vertices.size()); - colors.write[vertices.size() - 1] = color; - } - } else if (l.begins_with("vt ")) { - Vector parts = l.split(" "); - Vector2 uv( - parts[1].to_float(), - 1.0 - parts[2].to_float()); - uvs.push_back(uv); - } else if (l.begins_with("vn ")) { - Vector parts = l.split(" "); - Vector3 normal( - parts[1].to_float(), - parts[2].to_float(), - parts[3].to_float()); - normals.push_back(normal); - } else if (l.begins_with("vw ")) { - Vector parts = l.split(" "); - int vert_index = parts[1].to_int() - 1; - Vector4i joints; - Vector4 weights; - int count = 0; - - for (int i = 2; i < parts.size(); i += 2) { - if (count >= 4) { - break; - } - - String bone_name = parts[i]; - float weight = parts[i + 1].to_float(); - - if (bone_name_map.has(bone_name)) { - joints[count] = bone_name_map[bone_name]; - weights[count] = weight; - count++; - } - } - - float total = weights.x + weights.y + weights.z + weights.w; - if (total > 0) { - weights /= total; - } - - joint_indices_map[vert_index] = joints; - joint_weights_map[vert_index] = weights; - } else if (l.begins_with("f ")) { - Vector face_verts = l.substr(2).split(" "); - - for (const String &vert : face_verts) { - Vector components = vert.split("/"); - int pos_idx = components[0].to_int() - 1; - - Vector3 normal; - Vector2 uv; - Color color; - Vector4i joints; - Vector4 weights; - - if (pos_idx >= 0) { - if (components.size() > 1 && !components[1].is_empty()) { - uv = uvs[components[1].to_int() - 1]; - } - if (components.size() > 2 && !components[2].is_empty()) { - normal = normals[components[2].to_int() - 1]; - } - } - - if (pos_idx >= 0 && pos_idx < colors.size()) { - color = colors[pos_idx]; - } - - if (joint_indices_map.has(pos_idx)) { - joints = joint_indices_map[pos_idx]; - } - if (joint_weights_map.has(pos_idx)) { - weights = joint_weights_map[pos_idx]; - } - - PackedInt32Array bone_array; - PackedFloat32Array weight_array; - - bone_array.resize(4); - weight_array.resize(4); - - for (int i = 0; i < 4; i++) { - bone_array.set(i, i < 4 ? joints[i] : 0); - weight_array.set(i, i < 4 ? weights[i] : 0.0f); - } - - surf_tool->set_normal(normal); - surf_tool->set_uv(uv); - surf_tool->set_color(color); - surf_tool->set_bones(bone_array); - surf_tool->set_weights(weight_array); - - surf_tool->add_vertex(vertices[pos_idx]); - } - - for (int i = 2; i < face_verts.size(); i++) { - surf_tool->add_index(0); - surf_tool->add_index(i - 1); - surf_tool->add_index(i); - } - } else if (l.begins_with("usemtl ")) { - current_material = l.substr(7).strip_edges(); - } else if (l.begins_with("mtllib ")) { - String mtl_path = l.substr(7).strip_edges(); - // Material loading implementation would go here - } - } - } - - for (GLTFNodeIndex i = 0; i < nodes.size(); i++) { - nodes.write[i]->set_children(node_children[i]); - p_state->nodes.push_back(nodes[i]); - } - - if (current_animation.is_valid()) { - for (int frame_idx = 0; frame_idx < frame_count; frame_idx++) { - if (frame_idx >= frame_data.size()) { - break; - } - const Vector &frame = frame_data[frame_idx]; - int data_idx = 0; - - for (GLTFNodeIndex node_idx = 0; node_idx < nodes.size(); node_idx++) { - if (data_idx + 6 > frame.size()) { - break; - } - - float pos_x = frame[data_idx++].to_float(); - float pos_y = frame[data_idx++].to_float(); - float pos_z = frame[data_idx++].to_float(); - Vector3 position(pos_x, pos_y, pos_z); - - float rot_x = frame[data_idx++].to_float(); - float rot_y = frame[data_idx++].to_float(); - float rot_z = frame[data_idx++].to_float(); - float rot_w = frame[data_idx++].to_float(); - Quaternion rotation(rot_x, rot_y, rot_z, rot_w); - - double time = frame_idx * frame_time; - GLTFAnimation::NodeTrack &track = current_animation->get_node_tracks()[node_idx]; - track.position_track.times.push_back(time); - track.position_track.values.push_back(position); - track.rotation_track.times.push_back(time); - track.rotation_track.values.push_back(rotation); - } - } - p_state->animations.push_back(current_animation); - } - - if (surf_tool->get_vertex_array().size() > 0) { - if (normals.is_empty()) { - surf_tool->generate_normals(); - } - if (uvs.size() == vertices.size()) { - surf_tool->generate_tangents(); - } - - Ref importer_mesh; - importer_mesh.instantiate(); - - Array surface_arrays = surf_tool->commit_to_arrays(); - - importer_mesh->add_surface( - Mesh::PRIMITIVE_TRIANGLES, - surface_arrays); - - gltf_mesh->set_mesh(importer_mesh); - gltf_mesh->set_original_name(p_path.get_file().get_basename()); - - p_state->meshes.push_back(gltf_mesh); - } - - for (GLTFNodeIndex i = 0; i < nodes.size(); i++) { - if (nodes[i]->get_parent() == -1) { - p_state->root_nodes.push_back(i); - } - } - - SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, - p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector()); - - return OK; + ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, "Cannot open QBO file."); + + Vector> nodes; + HashMap bone_name_map; + Vector parent_stack; + Vector transform_stack; + Vector> node_children; + Ref current_animation; + bool in_hierarchy = false; + bool in_motion = false; + double frame_time = 0.03333333; + int frame_count = 0; + Vector> frame_data; + + Vector vertices; + Vector normals; + Vector uvs; + Vector colors; + HashMap joint_indices_map; + HashMap joint_weights_map; + Ref surf_tool = memnew(SurfaceTool); + Ref gltf_mesh; + gltf_mesh.instantiate(); + String current_material; + + surf_tool->begin(Mesh::PRIMITIVE_TRIANGLES); + + while (!f->eof_reached()) { + String l = f->get_line().strip_edges(); + if (l.is_empty()) { + continue; + } + + if (l.begins_with("HIERARCHY")) { + in_hierarchy = true; + in_motion = false; + parent_stack.clear(); + transform_stack.clear(); + bone_name_map.clear(); + nodes.resize(0); + node_children.resize(0); + } else if (l.begins_with("MOTION")) { + in_hierarchy = false; + in_motion = true; + current_animation.instantiate(); + current_animation->set_name("animation"); + } else if (in_hierarchy) { + if (l.begins_with("ROOT") || l.begins_with("JOINT")) { + Ref node; + node.instantiate(); + + String bone_name = l.substr(l.find(" ") + 1).strip_edges(); + if (bone_name_map.has(bone_name)) { + int count = 2; + String new_name; + do { + new_name = bone_name + "_" + itos(count++); + } while (bone_name_map.has(new_name)); + bone_name = new_name; + } + node->set_original_name(bone_name); + + GLTFNodeIndex parent = -1; + if (!parent_stack.is_empty()) { + parent = parent_stack[parent_stack.size() - 1]; + node_children.write[parent].push_back(nodes.size()); + } + node->set_parent(parent); + + bone_name_map[bone_name] = nodes.size(); + nodes.push_back(node); + node_children.push_back(Vector()); + parent_stack.push_back(nodes.size() - 1); + transform_stack.push_back(Transform3D()); + } else if (l.begins_with("End Site")) { + if (!parent_stack.is_empty()) { + parent_stack.remove_at(parent_stack.size() - 1); + transform_stack.remove_at(transform_stack.size() - 1); + } + } else if (l.begins_with("OFFSET")) { + Vector parts = l.split(" "); + if (parts.size() >= 4 && !nodes.is_empty()) { + Vector3 offset( + parts[1].to_float(), + parts[2].to_float(), + parts[3].to_float()); + Ref node = nodes[nodes.size() - 1]; + node->set_xform(Transform3D(Basis(), offset)); + } + } else if (l.begins_with("ORIENT")) { + Vector parts = l.split(" "); + if (parts.size() >= 5 && !nodes.is_empty()) { + Quaternion rot( + parts[1].to_float(), + parts[2].to_float(), + parts[3].to_float(), + parts[4].to_float()); + Ref node = nodes[nodes.size() - 1]; + Transform3D t = node->get_xform(); + t.basis = Basis(rot); + node->set_xform(t); + } + } + } else if (in_motion) { + if (l.begins_with("Frames:")) { + frame_count = l.get_slice(" ", 1).to_int(); + } else if (l.begins_with("Frame Time:")) { + frame_time = l.get_slice(" ", 2).to_float(); + } else { + frame_data.push_back(l.split(" ")); + } + } else { + if (l.begins_with("v ")) { + Vector parts = l.split(" "); + Vector3 vertex( + parts[1].to_float(), + parts[2].to_float(), + parts[3].to_float()); + vertices.push_back(vertex); + + if (parts.size() >= 7) { + Color color( + parts[4].to_float(), + parts[5].to_float(), + parts[6].to_float()); + colors.resize(vertices.size()); + colors.write[vertices.size() - 1] = color; + } + } else if (l.begins_with("vt ")) { + Vector parts = l.split(" "); + Vector2 uv( + parts[1].to_float(), + 1.0 - parts[2].to_float()); + uvs.push_back(uv); + } else if (l.begins_with("vn ")) { + Vector parts = l.split(" "); + Vector3 normal( + parts[1].to_float(), + parts[2].to_float(), + parts[3].to_float()); + normals.push_back(normal); + } else if (l.begins_with("vw ")) { + Vector parts = l.split(" "); + int vert_index = parts[1].to_int() - 1; + Vector4i joints; + Vector4 weights; + int count = 0; + + for (int i = 2; i < parts.size(); i += 2) { + if (count >= 4) { + break; + } + + String bone_name = parts[i]; + float weight = parts[i + 1].to_float(); + + if (bone_name_map.has(bone_name)) { + joints[count] = bone_name_map[bone_name]; + weights[count] = weight; + count++; + } + } + + float total = weights.x + weights.y + weights.z + weights.w; + if (total > 0) { + weights /= total; + } + + joint_indices_map[vert_index] = joints; + joint_weights_map[vert_index] = weights; + } else if (l.begins_with("f ")) { + Vector face_verts = l.substr(2).split(" "); + + for (const String &vert : face_verts) { + Vector components = vert.split("/"); + int pos_idx = components[0].to_int() - 1; + + Vector3 normal; + Vector2 uv; + Color color; + Vector4i joints; + Vector4 weights; + + if (pos_idx >= 0) { + if (components.size() > 1 && !components[1].is_empty()) { + uv = uvs[components[1].to_int() - 1]; + } + if (components.size() > 2 && !components[2].is_empty()) { + normal = normals[components[2].to_int() - 1]; + } + } + + if (pos_idx >= 0 && pos_idx < colors.size()) { + color = colors[pos_idx]; + } + + if (joint_indices_map.has(pos_idx)) { + joints = joint_indices_map[pos_idx]; + } + if (joint_weights_map.has(pos_idx)) { + weights = joint_weights_map[pos_idx]; + } + + PackedInt32Array bone_array; + PackedFloat32Array weight_array; + + bone_array.resize(4); + weight_array.resize(4); + + for (int i = 0; i < 4; i++) { + bone_array.set(i, i < 4 ? joints[i] : 0); + weight_array.set(i, i < 4 ? weights[i] : 0.0f); + } + + surf_tool->set_normal(normal); + surf_tool->set_uv(uv); + surf_tool->set_color(color); + surf_tool->set_bones(bone_array); + surf_tool->set_weights(weight_array); + + surf_tool->add_vertex(vertices[pos_idx]); + } + + for (int i = 2; i < face_verts.size(); i++) { + surf_tool->add_index(0); + surf_tool->add_index(i - 1); + surf_tool->add_index(i); + } + } else if (l.begins_with("usemtl ")) { + current_material = l.substr(7).strip_edges(); + } else if (l.begins_with("mtllib ")) { + String mtl_path = l.substr(7).strip_edges(); + // Material loading implementation would go here + } + } + } + + for (GLTFNodeIndex i = 0; i < nodes.size(); i++) { + nodes.write[i]->set_children(node_children[i]); + p_state->nodes.push_back(nodes[i]); + } + + if (current_animation.is_valid()) { + for (int frame_idx = 0; frame_idx < frame_count; frame_idx++) { + if (frame_idx >= frame_data.size()) { + break; + } + const Vector &frame = frame_data[frame_idx]; + int data_idx = 0; + + for (GLTFNodeIndex node_idx = 0; node_idx < nodes.size(); node_idx++) { + if (data_idx + 6 > frame.size()) { + break; + } + + float pos_x = frame[data_idx++].to_float(); + float pos_y = frame[data_idx++].to_float(); + float pos_z = frame[data_idx++].to_float(); + Vector3 position(pos_x, pos_y, pos_z); + + float rot_x = frame[data_idx++].to_float(); + float rot_y = frame[data_idx++].to_float(); + float rot_z = frame[data_idx++].to_float(); + float rot_w = frame[data_idx++].to_float(); + Quaternion rotation(rot_x, rot_y, rot_z, rot_w); + + double time = frame_idx * frame_time; + GLTFAnimation::NodeTrack &track = current_animation->get_node_tracks()[node_idx]; + track.position_track.times.push_back(time); + track.position_track.values.push_back(position); + track.rotation_track.times.push_back(time); + track.rotation_track.values.push_back(rotation); + } + } + p_state->animations.push_back(current_animation); + } + + if (surf_tool->get_vertex_array().size() > 0) { + if (normals.is_empty()) { + surf_tool->generate_normals(); + } + if (uvs.size() == vertices.size()) { + surf_tool->generate_tangents(); + } + + Ref importer_mesh; + importer_mesh.instantiate(); + + Array surface_arrays = surf_tool->commit_to_arrays(); + + importer_mesh->add_surface( + Mesh::PRIMITIVE_TRIANGLES, + surface_arrays); + + gltf_mesh->set_mesh(importer_mesh); + gltf_mesh->set_original_name(p_path.get_file().get_basename()); + + p_state->meshes.push_back(gltf_mesh); + + Vector used_joints; + for (const KeyValue &E : joint_indices_map) { + Vector4i joints = E.value; + for (int i = 0; i < 4; i++) { + int joint_idx = joints[i]; + if (joint_idx >= 0 && joint_idx < p_state->nodes.size() && !used_joints.has(joint_idx)) { + used_joints.push_back(joint_idx); + } + } + } + + if (!used_joints.is_empty()) { + Vector global_transforms; + global_transforms.resize(p_state->nodes.size()); + + for (GLTFNodeIndex i = 0; i < p_state->nodes.size(); i++) { + Ref node = p_state->nodes[i]; + GLTFNodeIndex parent = node->get_parent(); + if (parent == -1) { + global_transforms.write[i] = node->get_xform(); + } else { + global_transforms.write[i] = global_transforms[parent] * node->get_xform(); + } + } + + TypedArray inverse_binds; + for (int joint_idx : used_joints) { + if (joint_idx >= 0 && joint_idx < global_transforms.size()) { + inverse_binds.push_back(global_transforms[joint_idx].affine_inverse()); + } + } + + Ref skin; + skin.instantiate(); + skin->set_joints(used_joints); + skin->set_inverse_binds(inverse_binds); + p_state->skins.push_back(skin); + + Ref mesh_node; + mesh_node.instantiate(); + mesh_node->set_mesh(p_state->meshes.size() - 1); + mesh_node->set_skin(p_state->skins.size() - 1); + GLTFNodeIndex mesh_node_idx = p_state->nodes.size(); + p_state->nodes.push_back(mesh_node); + + if (!p_state->root_nodes.is_empty()) { + GLTFNodeIndex root_idx = p_state->root_nodes[0]; + mesh_node->set_parent(root_idx); + p_state->nodes.write[root_idx]->get_children().push_back(mesh_node_idx); + } else { + p_state->root_nodes.push_back(mesh_node_idx); + } + } else { + Ref mesh_node; + mesh_node.instantiate(); + mesh_node->set_mesh(p_state->meshes.size() - 1); + GLTFNodeIndex mesh_node_idx = p_state->nodes.size(); + p_state->nodes.push_back(mesh_node); + p_state->root_nodes.push_back(mesh_node_idx); + } + } + + SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, + p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector()); + + return OK; } Error QBODocument::append_from_file(String p_path, Ref p_state, uint32_t p_flags, String p_base_path) {