diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.cpp b/extension/src/openvic-extension/singletons/ModelSingleton.cpp index f51ae77f..04508a06 100644 --- a/extension/src/openvic-extension/singletons/ModelSingleton.cpp +++ b/extension/src/openvic-extension/singletons/ModelSingleton.cpp @@ -1,5 +1,6 @@ #include "ModelSingleton.hpp" +#include #include #include @@ -9,6 +10,9 @@ #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/Utilities.hpp" +//#include "godot_cpp/classes/node3d.hpp" +//#include "openvic-simulation/utility/Logger.hpp" +//#include "godot_cpp/classes/file_access.hpp" using namespace godot; using namespace OpenVic; @@ -19,6 +23,8 @@ void ModelSingleton::_bind_methods() { OV_BIND_METHOD(ModelSingleton::get_cultural_helmet_model, { "culture" }); OV_BIND_METHOD(ModelSingleton::get_flag_model, { "floating" }); OV_BIND_METHOD(ModelSingleton::get_buildings); + OV_BIND_METHOD(ModelSingleton::get_xsm_animation,{"animation_name"}); + OV_BIND_METHOD(ModelSingleton::get_xac_model,{"model_name"}); } ModelSingleton* ModelSingleton::get_singleton() { @@ -481,3 +487,38 @@ TypedArray ModelSingleton::get_buildings() { return ret; } + +Ref ModelSingleton::get_xsm_animation(String source_file) { + const xsm_map_t::const_iterator it = xsm_cache.find(source_file); + if(it != xsm_cache.end()) { + //Logger::info("Load XSM Animation from cache: ",Utilities::godot_to_std_string(source_file)); + return it->second; + } + + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + String path = game_singleton->lookup_file_path(source_file); + + //Logger::info("Load XSM Animation from file: ",Utilities::godot_to_std_string(source_file)); + + Ref anim = _load_xsm_animation(FileAccess::open(path, FileAccess::READ)); + xsm_cache.emplace(source_file,anim); + return anim; +} + +Node3D* ModelSingleton::get_xac_model(String source_file) { + const xac_map_t::const_iterator it = xac_cache.find(source_file); + if(it != xac_cache.end()) { + //Logger::info("Load XAC Model from cache: ",Utilities::godot_to_std_string(source_file)); + return it->second; //TODO: do we need to add a .instantiate or something to make it a duplicate node? + } + + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + String path = game_singleton->lookup_file_path(source_file); + Node3D* node = _load_xac_model(FileAccess::open(path, FileAccess::READ)); + xac_cache.emplace(source_file,node); + return node; +} \ No newline at end of file diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.hpp b/extension/src/openvic-extension/singletons/ModelSingleton.hpp index f0c45be0..b73074fc 100644 --- a/extension/src/openvic-extension/singletons/ModelSingleton.hpp +++ b/extension/src/openvic-extension/singletons/ModelSingleton.hpp @@ -1,10 +1,16 @@ #pragma once +#include +#include #include #include #include #include +#include "../utility/XSMLoader.hpp" +#include "../utility/XACLoader.hpp" +//#include "godot_cpp/classes/node.hpp" +#include "godot_cpp/classes/node3d.hpp" namespace OpenVic { struct BuildingInstance; @@ -31,9 +37,13 @@ namespace OpenVic { using animation_map_t = deque_ordered_map; using model_map_t = deque_ordered_map; + using xsm_map_t = deque_ordered_map>; + using xac_map_t = deque_ordered_map; animation_map_t animation_cache; model_map_t model_cache; + xsm_map_t xsm_cache; + xac_map_t xac_cache; godot::Dictionary get_animation_dict(GFX::Actor::Animation const& animation); godot::Dictionary get_model_dict(GFX::Actor const& actor); @@ -56,5 +66,8 @@ namespace OpenVic { godot::Dictionary get_flag_model(bool floating); godot::TypedArray get_buildings(); + + godot::Ref get_xsm_animation(godot::String source_file); + godot::Node3D* get_xac_model(godot::String source_file); }; } diff --git a/extension/src/openvic-extension/utility/XACLoader.cpp b/extension/src/openvic-extension/utility/XACLoader.cpp new file mode 100644 index 00000000..e69de29b diff --git a/extension/src/openvic-extension/utility/XACLoader.hpp b/extension/src/openvic-extension/utility/XACLoader.hpp new file mode 100644 index 00000000..46c08cbb --- /dev/null +++ b/extension/src/openvic-extension/utility/XACLoader.hpp @@ -0,0 +1,900 @@ +#pragma once +#include +#include +#include +#include +#include +#include "Utilities.hpp" +#include "XACUtilities.hpp" +//#include "godot_cpp/classes/animation.hpp" +#include "godot_cpp/classes/array_mesh.hpp" +#include "godot_cpp/classes/image.hpp" +#include "godot_cpp/classes/image_texture.hpp" +#include "godot_cpp/classes/material.hpp" +//#include "godot_cpp/classes/node.hpp" +#include +#include "godot_cpp/classes/mesh_instance3d.hpp" +#include "godot_cpp/classes/resource_preloader.hpp" +#include "godot_cpp/classes/shader_material.hpp" +#include "godot_cpp/classes/skeleton3d.hpp" +//#include "godot_cpp/variant/basis.hpp" +//#include "godot_cpp/variant/dictionary.hpp" +#include "godot_cpp/variant/dictionary.hpp" +#include "godot_cpp/variant/packed_byte_array.hpp" +#include "godot_cpp/variant/packed_float32_array.hpp" +#include "godot_cpp/variant/packed_int32_array.hpp" +#include "godot_cpp/variant/packed_int64_array.hpp" +#include "godot_cpp/variant/packed_string_array.hpp" +#include "godot_cpp/variant/packed_vector2_array.hpp" +#include "godot_cpp/variant/packed_vector3_array.hpp" +#include "godot_cpp/variant/packed_vector4_array.hpp" +#include "godot_cpp/variant/transform3d.hpp" +#include "godot_cpp/variant/typed_array.hpp" +#include "godot_cpp/variant/variant.hpp" +#include "godot_cpp/variant/vector3.hpp" +#include "openvic-simulation/utility/Logger.hpp" + +#include +#include +#include +#include + +//#include "openvic-extension/utility/Utilities.hpp" + +//using namespace godot; +//using namespace OpenVic; +//using godot::Node3D; + +namespace OpenVic { + + //using OpenVic::Utilities::std_view_to_godot_string; + + static constexpr uint32_t XAC_FORMAT_SPECIFIER = ' CAX'; // Order reversed due to litte endian + static constexpr uint8_t XAC_VERSION_MAJOR = 1, XAC_VERSION_MINOR = 0; + + #pragma pack(push) + #pragma pack(1) + + struct xac_header_t { + uint32_t format_identifier; + uint8_t version_major; + uint8_t version_minor; + uint8_t big_endian; + uint8_t multiply_order; + }; + + struct xac_metadata_v2_pack { + uint32_t reposition_mask; //1=position, 2=rotation, 4=scale + int32_t repositioning_node; + uint8_t exporter_major_version; + uint8_t exporter_minor_version; + uint16_t pad; + float retarget_root_offset; + }; + + struct node_hierarchy_pack { + int32_t node_count; + int32_t root_node_count; //nodes with parent_id == -1 + }; + + struct node_data_pack { //v1 + quat_v1_t rotation; + quat_v1_t scale_rotation; + vec3d_t position; + vec3d_t scale; + float unused[3]; + int32_t unknown[2]; + int32_t parent_node_id; + int32_t child_nodes_count; + int32_t include_in_bounds_calculation; //bool + matrix44_t transform; + float importance_factor; + }; + + struct material_totals { //v1 + int32_t total_materials_count; + int32_t standard_materials_count; + int32_t fx_materials_count; + }; + + struct material_definition_pack { + vec4d_t ambient_color; + vec4d_t diffuse_color; + vec4d_t specular_color; + vec4d_t emissive_color; + float shine; + float shine_strength; + float opacity; + float ior; //index of refraction + uint8_t double_sided; //bool + uint8_t wireframe; //bool + uint8_t unused; + uint8_t layers_count; + }; + + //comes as the first 3xint32s when version == 1 + struct material_layer_pack_v1_unk { + int32_t unknown[3]; //could be a vec3? + }; + + struct material_layer_pack { + float amount; + vec2d_t uv_offset; + vec2d_t uv_tiling; + float rotation_in_radians; + int16_t material_id; + uint8_t map_type; + uint8_t unused; + }; + + struct mesh_pack { + int32_t node_id; + int32_t influence_ranges_count; + int32_t vertices_count; + int32_t indices_count; + int32_t submeshes_count; + int32_t attribute_layers_count; + uint8_t is_collision_mesh; //bool + uint8_t pad[3]; + }; + + enum ATTRIBUTE { + POSITION, + NORMAL, + TANGENT, + UV, + COL_32, + INFLUENCE_RANGE, + COL_128 + }; + + struct vertices_attribute_pack { + int32_t type; //0-6 (enum ATTRIBUTE) + int32_t attribute_size; + uint8_t keep_originals; //bool + uint8_t is_scale_factor; //bool + uint16_t pad; + }; + + struct submesh_pack { + int32_t indices_count; + int32_t vertices_count; + int32_t material_id; + int32_t bones_count; + }; + + struct skinning_pack { + int32_t influences_count; + uint8_t is_for_collision; //bool + uint8_t pad[3]; + }; + + struct influence_data { + float weight; + int16_t bone_id; + int16_t pad; + }; + + struct influence_range { + int32_t first_influence_index; + int32_t influences_count; + }; + + // 0x4, 0x6, 0xA chunk types appear in vic2, but what they do + // is unknown + // 0x8 is junk data + // 0x0 is the older node/bone chunk + + //0x0 + struct node_chunk_pack { + quat_v1_t rotation; + quat_v1_t scale_rotation; + vec3d_t position; + vec3d_t scale; + vec3d_t unused; + int32_t unknown; //uncertain + int32_t parent_node_id; + matrix44_t possible_matrix; //uncertain + float possible_importance_factor; //uncertain + }; + + #pragma pack(pop) + + struct xac_metadata_v2 { + xac_metadata_v2_pack packed; + String source_app; + String original_file_name; + String export_date; + String actor_name; + }; + + struct node_data { //v1 + node_data_pack packed; + String name; + }; + + struct node_hierarchy { //v1 + node_hierarchy_pack packed; + std::vector node_data; + }; + + struct material_layer { + material_layer_pack_v1_unk unk; //optional + material_layer_pack packed; + String texture; + }; + + struct material_definition { + material_definition_pack packed; + String name; + std::vector layers; + }; + + struct vertices_attribute { + vertices_attribute_pack packed; + std::vector data; // sometimes int32, other times uint32 + }; + + struct submesh { + submesh_pack packed; + std::vector relative_indices; + std::vector bone_ids; + }; + + struct mesh { + mesh_pack packed; + std::vector vertices_attributes; + std::vector submeshes; + }; + + struct skinning { + int32_t node_id; + int32_t local_bones_count; //v3 only + skinning_pack packed; + std::vector influence_data; + std::vector influence_ranges; + }; + + struct node_chunk { + node_chunk_pack packed; + String name; + }; + + static bool read_xac_header(Ref const& file) { + xac_header_t header; + ERR_FAIL_COND_V(!read_struct(file, header), false); + + ERR_FAIL_COND_V_MSG( + header.format_identifier != XAC_FORMAT_SPECIFIER, false, vformat( + "Invalid XAC format identifier: %x (should be %x)", header.format_identifier, XAC_FORMAT_SPECIFIER + ) + ); + + ERR_FAIL_COND_V_MSG( + header.version_major != XAC_VERSION_MAJOR || header.version_minor != XAC_VERSION_MINOR, false, vformat( + "Invalid XAC version: %d.%d (should be %d.%d)", + header.version_major, header.version_minor, XAC_VERSION_MAJOR, XAC_VERSION_MINOR + ) + ); + + ERR_FAIL_COND_V_MSG( + header.big_endian != 0, false, "Invalid XAC endianness: big endian (only little endian is supported)" + ); + + ERR_FAIL_COND_V_MSG( + header.multiply_order != 0, false, "Invalid XAC multiply order: ???" + ); + + return true; + } + + static bool read_xac_metadata(Ref const& file, xac_metadata_v2& metadata) { + bool ret = read_struct(file, metadata.packed); + ret &= read_string(file, metadata.source_app, false); + ret &= read_string(file, metadata.original_file_name, false); + ret &= read_string(file, metadata.export_date, false); + ret &= read_string(file, metadata.actor_name, false); + return ret; + } + + static bool read_node_data(Ref const& file, node_data& node_data) { + bool ret = read_struct(file, node_data); + ret &= read_string(file, node_data.name); + return ret; + } + + static bool read_node_hierarchy(Ref const& file, node_hierarchy& hierarchy) { + bool ret = read_struct(file, hierarchy.packed); + for(int i=0; i const& file, material_totals& totals) { + return read_struct(file, totals); + } + + static bool read_layer(Ref const& file, material_layer& layer, int32_t version) { + bool ret = true; + if(version == 1){ + ret &= read_struct(file, layer.unk); + } + ret &= read_struct(file, layer.packed); + ret &= read_string(file, layer.texture, false); + return ret; + } + + static bool read_material_definition(Ref const& file, material_definition& def, int32_t version) { + bool ret = read_struct(file, def.packed); + ret &= read_string(file, def.name, false); + for(int i=0; i const& file, vertices_attribute& attribute, int32_t vertices_count) { + bool ret = read_struct(file, attribute.packed); + //ret &= read_buffer(file, attribute.data, vertices_count*attribute.packed.attribute_size); + ret &= read_struct_array(file, attribute.data, vertices_count*attribute.packed.attribute_size/4); + return ret; + } + + static bool read_submesh(Ref const& file, submesh& submesh) { + bool ret = read_struct(file, submesh.packed); + ret &= read_struct_array(file, submesh.relative_indices, submesh.packed.indices_count); + ret &= read_struct_array(file, submesh.bone_ids, submesh.packed.bones_count); + return ret; + } + + static bool read_mesh(Ref const& file, mesh& mesh) { + bool ret = read_struct(file, mesh.packed); + for(int i=0; i const& file, skinning& skin, std::vector const* meshes, int32_t version) { + bool ret = read_struct(file, skin.node_id); + if(version == 3) ret &= read_struct(file, skin.local_bones_count); + ret &= read_struct(file, skin.packed); + ret &= read_struct_array(file, skin.influence_data, skin.packed.influences_count); + bool found = false; + for(mesh mesh : *meshes) { + if(mesh.packed.is_collision_mesh == skin.packed.is_for_collision && mesh.packed.node_id == skin.node_id){ + ret &= read_struct_array(file, skin.influence_ranges, mesh.packed.influence_ranges_count); + found = true; + break; + } + } + ret &= found; + return ret; + } + + static bool read_node_chunk(Ref const& file, node_chunk& node) { + bool ret = read_struct(file, node.packed); + ret &= read_string(file, node.name); + return ret; + } + + /* + ==================================== + Skeleton helper functions + + ==================================== + */ + + //TODO: Verify + static Transform3D make_transform(vec3d_t position, quat_v1_t quaternion, vec3d_t scale) { + Transform3D transform = Transform3D(); + + Basis basis = Basis(); + basis.set_quaternion(quat_v1_to_godot(quaternion)); + basis.scale(vec3d_to_godot(scale)); + + transform.set_basis(basis); + transform.translate_local(vec3d_to_godot(position, true)); + + return transform; + } + + //TODO: do we want to use node's bone id instead of current_id? + static Skeleton3D* build_armature_hierarchy(node_hierarchy& hierarchy) { + static const StringName skeleton_name = "skeleton"; + Skeleton3D* skeleton = memnew(Skeleton3D); + skeleton->set_name(skeleton_name); + + uint32_t current_id = 0; + + for(node_data const node : hierarchy.node_data) { + skeleton->add_bone(node.name); + skeleton->set_bone_parent(current_id, node.packed.parent_node_id); + + Transform3D transform = make_transform(node.packed.position, node.packed.rotation, node.packed.scale); + skeleton->set_bone_rest(current_id, transform); + skeleton->set_bone_pose(current_id, transform); + + current_id += 1; + } + + return skeleton; + } + + static Skeleton3D* build_armature_nodes(std::vector const* nodes) { + static const StringName skeleton_name = "skeleton"; + Skeleton3D* skeleton = memnew(Skeleton3D); + skeleton->set_name(skeleton_name); + + uint32_t current_id = 0; + + for(node_chunk const node : *nodes) { + skeleton->add_bone(node.name); + skeleton->set_bone_parent(current_id, node.packed.parent_node_id); + + Transform3D transform = make_transform(node.packed.position, node.packed.rotation, node.packed.scale); + skeleton->set_bone_rest(current_id, transform); + skeleton->set_bone_pose(current_id, transform); + + current_id += 1; + } + + return skeleton; + } + + /* + ==================================== + Material helper functions + + ==================================== + */ + + struct material_mapping { + //-1 means unused + Ref godot_material; + int32_t diffuse_texture_index = -1; + int32_t specular_texture_index = -1; + int32_t scroll_index = -1; + }; + + static Error setup_flag_shader() { + Error result = OK; + GameSingleton const* game_singleton = GameSingleton::get_singleton(); + ERR_FAIL_NULL_V(game_singleton, {}); + + static const StringName Param_flag_dimensions = "flag_dims"; + static const StringName Param_flag_texture_sheet = "texture_flag_sheet_diffuse"; + static const Ref flag_shader = ResourcePreloader().get_resource("res://src/Game/Model/flag_mat.tres"); + + flag_shader->set_shader_parameter(Param_flag_dimensions, game_singleton->get_flag_dims()); + flag_shader->set_shader_parameter(Param_flag_texture_sheet, game_singleton->get_flag_sheet_texture()); + return result; + } + + static std::vector build_materials(std::vector* materials) { + + static const StringName Textures_path = "gfx/anims/%s.dds"; + + // Parameters for the default model shader + static const StringName Param_texture_diffuse = "texture_diffuse"; + //red channel is specular, green and blue are nation colours + static const StringName Param_texture_nation_colors_mask = "texture_nation_colors_mask"; + + // Scrolling textures (smoke, tank tracks) + static const StringName Param_Scroll_texture_diffuse = "scroll_texture_diffuse"; + static const StringName Param_Scroll_factor = "scroll_factor"; + static Dictionary SCROLLING_MATERIAL_FACTORS; + SCROLLING_MATERIAL_FACTORS["TexAnim"] = 2.5; + SCROLLING_MATERIAL_FACTORS["Smoke"] = 0.3; + + static PackedStringArray Scrolling_textures_diffuse; + static PackedStringArray unit_textures_diffuse; + static PackedStringArray unit_textures_specular; + + // Flag textures + + static const StringName Param_texture_normal = "texture_normal"; + + //General + static const uint32_t MAX_UNIT_TEXTURES = 32; + + static const StringName Texture_skip_nospec = "nospec"; + static const StringName Texture_skip_flag = "unionjacksquare"; + static const StringName Texture_skip_diff = "test256texture"; + + static const Ref unit_shader = ResourcePreloader().get_resource("res://src/Game/Model/unit_colours_mat.tres"); + static const Ref scrolling_shader = ResourcePreloader().get_resource("res://src/Game/Model/scrolling_mat.tres"); + static const Ref flag_shader = ResourcePreloader().get_resource("res://src/Game/Model/flag_mat.tres"); + + + std::vector mappings; + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, {}); + + for(material_definition mat : *materials) { + String diffuse_name; + String specular_name; + String normal_name; + + for(material_layer layer : mat.layers) { + if(layer.texture == Texture_skip_diff || layer.texture == Texture_skip_flag || layer.texture == Texture_skip_nospec) { + continue; + } + //Get the texture names + switch(layer.packed.map_type){ + case 2: //diffuse + if(diffuse_name.is_empty()) diffuse_name = layer.texture; + else Logger::error("Multiple diffuse layers in material: ", Utilities::godot_to_std_string(diffuse_name), " and ", Utilities::godot_to_std_string(layer.texture)); + break; + case 3: //specular + if(specular_name.is_empty()) specular_name = layer.texture; + else Logger::error("Multiple specular layers in material: ", Utilities::godot_to_std_string(specular_name), " and ", Utilities::godot_to_std_string(layer.texture)); + break; + case 4: //? + break; + case 5: //normal + if(normal_name.is_empty()) normal_name = layer.texture; + else Logger::error("Multiple normal layers in material: ", Utilities::godot_to_std_string(normal_name), " and ", Utilities::godot_to_std_string(layer.texture)); + break; + default: + Logger::warning("Unknown layer type: ", layer.packed.map_type); + } + } + + Ref diffuse_texture; + Ref specular_texture; + Ref normal_texture; + material_mapping mapping; + + if(!diffuse_name.is_empty()) diffuse_texture = asset_manager->get_texture(vformat(Textures_path, diffuse_name)); + if(!specular_name.is_empty()) specular_texture = asset_manager->get_texture(vformat(Textures_path, specular_name)); + if(!normal_name.is_empty()) normal_texture = asset_manager->get_texture(vformat(Textures_path, normal_name)); + + //flag + if(!normal_texture.is_null() && diffuse_texture.is_null()) { + //There shouldn't be a specular texture + flag_shader->set_shader_parameter(Param_texture_normal, normal_texture); + mapping.godot_material = flag_shader; + } + //Scrolling texture + else if(!diffuse_texture.is_null() && SCROLLING_MATERIAL_FACTORS.has(mat.name)) { + int32_t scroll_textures_index_diffuse = Scrolling_textures_diffuse.find(diffuse_name); + if(scroll_textures_index_diffuse < 0){ + Scrolling_textures_diffuse.push_back(diffuse_name); + //err check + TypedArray scroll_diffuse_textures = scrolling_shader->get_shader_parameter(Param_Scroll_texture_diffuse); + scroll_diffuse_textures.push_back(diffuse_texture); + scrolling_shader->set_shader_parameter(Param_Scroll_texture_diffuse, scroll_diffuse_textures); + + PackedFloat32Array scroll_factors = scrolling_shader->get_shader_parameter(Param_Scroll_factor); + + scroll_factors.push_back(SCROLLING_MATERIAL_FACTORS[mat.name]); + scrolling_shader->set_shader_parameter(Param_Scroll_factor, scroll_factors); + } + mapping.godot_material = scrolling_shader; + mapping.scroll_index = scroll_textures_index_diffuse; + + } + //standard material (diffuse optionally with a specular/unit colours) + else { + int32_t textures_index_diffuse = unit_textures_diffuse.find(diffuse_name); + int32_t textures_index_specular = unit_textures_specular.find(specular_name); + + if(textures_index_diffuse < 0 && !diffuse_name.is_empty()) { + unit_textures_diffuse.push_back(diffuse_name); + if(unit_textures_diffuse.size() >= MAX_UNIT_TEXTURES) { + Logger::error("Number of diffuse textures exceeded max supported by a shader!"); + } + TypedArray diffuse_textures = unit_shader->get_shader_parameter(Param_texture_diffuse); + diffuse_textures.push_back(diffuse_texture); + textures_index_diffuse = diffuse_textures.size() - 1; + unit_shader->set_shader_parameter(Param_texture_diffuse, diffuse_textures); + } + if(textures_index_specular < 0 && !specular_name.is_empty()) { + unit_textures_specular.push_back(specular_name); + if(unit_textures_specular.size() >= MAX_UNIT_TEXTURES) { + Logger::error("Number of diffuse textures exceeded max supported by a shader!"); + } + TypedArray specular_textures = unit_shader->get_shader_parameter(Param_texture_nation_colors_mask); + specular_textures.push_back(specular_texture); + textures_index_specular = specular_textures.size() - 1; + unit_shader->set_shader_parameter(Param_texture_nation_colors_mask, specular_textures); + } + + mapping.godot_material = unit_shader; + mapping.diffuse_texture_index = textures_index_diffuse; + mapping.specular_texture_index = textures_index_specular; + } + + mappings.push_back(mapping); + } + + return mappings; + } + + /* + ==================================== + Mesh helper functions + + ==================================== + */ + // + + //TODO: before, we used surface tool to generate the mesh + // this time around, it might be faster/easier to use the array tools + + //TODO: last time we loaded collision mesh chunks, since we can likely get + // away with a radius around a unit's position, why not skip them + //TODO: last time, we hardcoded lists of other chunk names not to load + // why not try this: + + // if skeleton but mesh chunk has no corresponding skinning chunk: + // skip + // if collision mesh chunk + // skip + + // alternatively, see if a mesh name corresponds to a name in the hierarchy + // if yes, keep the mesh + + //this should get rid of mesh chunks that aren't attached to anything + + static MeshInstance3D* build_mesh(mesh& mesh_chunk, skinning* skin, std::vector& materials) { + static const uint32_t EXTRA_CULL_MARGIN = 2; + + MeshInstance3D* mesh_inst = memnew(MeshInstance3D); + mesh_inst->set_extra_cull_margin(EXTRA_CULL_MARGIN); + + Ref mesh = Ref(); + mesh.instantiate(); + + std::vector* verts; + std::vector* normals; + std::vector* tangents; + int uvs_read = 0; + std::vector* uv1; + std::vector* uv2; + std::vector* influence_range_indices; + + for(vertices_attribute attribute : mesh_chunk.vertices_attributes) { + switch(attribute.packed.type){ + case ATTRIBUTE::POSITION: + verts = (std::vector*)attribute.data.data(); + break; + case ATTRIBUTE::NORMAL: + normals = (std::vector*)attribute.data.data(); + break; + case ATTRIBUTE::TANGENT: + tangents = (std::vector*)attribute.data.data(); + break; + case ATTRIBUTE::UV: + if(uvs_read == 0) uv1 = (std::vector*)&attribute.data; + else if(uvs_read == 1) uv2 = (std::vector*)&attribute.data; + uvs_read += 1; + break; + case ATTRIBUTE::INFLUENCE_RANGE: + if(skin == nullptr){ + Logger::warning("Mesh chunk has influence attribute but no corresponding skinning chunk"); + break; + } + influence_range_indices = (std::vector*)&attribute.data; + break; + default: //for now, ignore color data + break; + } + } + + uint32_t vert_total = 0; + static const StringName key_diffuse = "tex_index_diffuse"; + static const StringName key_specular = "tex_index_specular"; + static const StringName key_scroll = "scroll_tex_index_diffuse"; + + //for now we treat a submesh as a godot mesh surface + for(submesh submesh : mesh_chunk.submeshes) { + Array arrs; //surface vertex data arrays + PackedVector3Array verts_submesh = {}; + PackedVector3Array normals_submesh = {}; + PackedVector4Array tangents_submesh = {}; + PackedVector2Array uv1_submesh = {}; + PackedVector2Array uv2_submesh = {}; + PackedInt32Array bone_ids = {}; + PackedFloat32Array weights = {}; + //godot uses a fixed 4 bones influencing a vertex, so size the array accordingly + bone_ids.resize(submesh.relative_indices.size()*4); + bone_ids.fill(0); + + //1 vertex is in the surface per relative index + for(uint32_t rel_ind : submesh.relative_indices) { + uint32_t index = rel_ind + vert_total; + + if((*verts).size() > index) verts_submesh.push_back(vec3d_to_godot((*verts)[index], true));//verts[index]); + if((*normals).size() > index) normals_submesh.push_back( vec3d_to_godot((*normals)[index])); + + //TODO: verify if we need to invert the x for each tangent + if((*tangents).size() > index) { + tangents_submesh.push_back(vec4d_to_godot((*tangents)[index])); + tangents_submesh[tangents_submesh.size()-1].x *= -1; + } + + if((*uv1).size() > index) uv1_submesh.push_back(vec2d_to_godot((*uv1)[index])); + if((*uv2).size() > index) uv2_submesh.push_back(vec2d_to_godot((*uv2)[index])); + + uint32_t influence_range_ind = (*influence_range_indices)[index]; + uint32_t influences_ind = skin->influence_ranges[influence_range_ind].first_influence_index; + uint32_t influences_count = Math::min(skin->influence_ranges[influence_range_ind].influences_count, 4); + + for(int i = 0; i < influences_count; i++) { + bone_ids[rel_ind + i] = skin->influence_data[influences_ind + i].bone_id; + weights[rel_ind + i] = skin->influence_data[influences_ind + i].weight; + } + + } + + arrs[Mesh::ARRAY_VERTEX] = verts_submesh; + arrs[Mesh::ARRAY_NORMAL] = normals_submesh; + arrs[Mesh::ARRAY_TANGENT] = tangents_submesh; + arrs[Mesh::ARRAY_TEX_UV] = uv1_submesh; + arrs[Mesh::ARRAY_TEX_UV2] = uv2_submesh; //can enable this later if needed + arrs[Mesh::ARRAY_BONES] = bone_ids; + arrs[Mesh::ARRAY_WEIGHTS] = weights; + + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrs); + + //setup materials for the surface + material_mapping material_mapping = materials[submesh.packed.material_id]; + mesh->surface_set_material(mesh->get_surface_count()-1, material_mapping.godot_material); + if(material_mapping.diffuse_texture_index != -1) { + mesh_inst->set_instance_shader_parameter(key_diffuse, material_mapping.diffuse_texture_index); + } + if(material_mapping.specular_texture_index != -1) { + mesh_inst->set_instance_shader_parameter(key_specular, material_mapping.diffuse_texture_index); + } + if(material_mapping.scroll_index != -1) { + mesh_inst->set_instance_shader_parameter(key_scroll, material_mapping.diffuse_texture_index); + } + + vert_total += submesh.relative_indices.size(); + } + + mesh_inst->set_mesh(mesh); + + return mesh_inst; + } + + + + /* + ==================================== + Main loading function + + ==================================== + */ + static Node3D* _load_xac_model(Ref const& file) { + bool res = read_xac_header(file); + + Node3D* base = memnew(Node3D); + + if(!res) return base; + + xac_metadata_v2 metadata; + + bool hierarchy_read = false; + node_hierarchy hierarchy; + std::vector nodes; //other way of making a bone hierarchy + + material_totals material_totals; + std::vector materials; + + std::vector meshes; + std::vector skinnings; + + + //0xA unknown + //0x4 unknown + //0x6 unknown + //0x8 junk data (ex. old versions of the file duplicated in append mode) + + while(!file->eof_reached()){ + chunk_header_t header; + if(!read_chunk_header(file, header)) break; + + if(header.type == 0x7 && header.version == 2) read_xac_metadata(file, metadata); + + else if(header.type == 0xB && header.version == 1) { + hierarchy_read = read_node_hierarchy(file, hierarchy); + } + else if(header.type == 0x0 && header.version == 1) { + node_chunk node; + read_node_chunk(file, node); + nodes.push_back(node); + } + else if(header.type == 0xD && header.version == 1) { + read_material_totals(file, material_totals); + } + else if(header.type == 0x3) { + material_definition mat; + read_material_definition(file, mat, header.version); + materials.push_back(mat); + } + else if(header.type == 0x1 && header.version == 1) { + mesh mesh; + read_mesh(file, mesh); + meshes.push_back(mesh); + } + else if(header.type == 0x2) { + skinning skin; + read_skinning(file, skin, &meshes, header.version); + skinnings.push_back(skin); + } + else { + UtilityFunctions::print( + vformat("Unsupported XAC chunk: type = %x, length = %x, version = %d", header.type, header.length, header.version) + ); + // Skip unsupported chunks by using get_buffer, make sure this doesn't break anything + if(header.length + file->get_position() < file->get_length()) file->get_buffer(header.length); + else { + res = false; + break; + } + } + } + + //Setup skeleton + Skeleton3D* skeleton; + if(hierarchy_read) { + skeleton = build_armature_hierarchy(hierarchy); + base->add_child(skeleton); + } + else if(!nodes.empty()) { + skeleton = build_armature_nodes(&nodes); + base->add_child(skeleton); + } + + //Setup materials + std::vector material_mappings = build_materials(&materials); + + //setup mesh + for(mesh mesh : meshes) { + if(mesh.packed.is_collision_mesh) continue; //we'll use our own collision meshes where needed + + skinning* mesh_skin = nullptr; + for(skinning skin : skinnings){ + if(skin.node_id == mesh.packed.node_id && skin.packed.is_for_collision == mesh.packed.is_collision_mesh){ + mesh_skin = &skin; + break; + } + } + + if(skeleton && mesh_skin == nullptr) continue; + + MeshInstance3D* mesh_inst = build_mesh(mesh, mesh_skin, material_mappings); + base->add_child(mesh_inst); + mesh_inst->set_owner(base); + + if(skeleton) { + mesh_inst->set_skeleton_path(mesh_inst->get_path_to(skeleton)); + int32_t node_id = mesh.packed.node_id; + if(hierarchy_read && node_id < hierarchy.node_data.size()) { + mesh_inst->set_name(hierarchy.node_data[node_id].name); + } + else if(!nodes.empty() && node_id < nodes.size()) { + mesh_inst->set_name(nodes[node_id].name); + } + } + } + + return base; + } +} \ No newline at end of file diff --git a/extension/src/openvic-extension/utility/XACUtilities.hpp b/extension/src/openvic-extension/utility/XACUtilities.hpp new file mode 100644 index 00000000..a9eb15b1 --- /dev/null +++ b/extension/src/openvic-extension/utility/XACUtilities.hpp @@ -0,0 +1,211 @@ +#pragma once +#include +#include +#include +#include "openvic-simulation/utility/Logger.hpp" +//#include "openvic-extension/utility/Utilities.hpp" +#include "godot_cpp/variant/packed_byte_array.hpp" +#include "godot_cpp/variant/quaternion.hpp" + +namespace OpenVic { + #pragma pack(push) + #pragma pack(1) + + struct chunk_header_t { + int32_t type; + int32_t length; + int32_t version; + }; + + struct vec2d_t { //not a real datatype in the files. Just using it for convenience + float x; + float y; + }; + + struct vec3d_t { + float x; + float y; + float z; + }; + + struct vec4d_t { + float x; + float y; + float z; + float w; + }; + + struct quat_v1_t { + float x; + float y; + float z; + float w; + }; + + struct quat_v2_t { // divide by 32767 to get proper quat + int16_t x; + int16_t y; + int16_t z; + int16_t w; + }; + + struct matrix44_t { + vec4d_t col1; + vec4d_t col2; + vec4d_t col3; + vec4d_t col4; + }; + + struct color_32_t { + int8_t r; + int8_t g; + int8_t b; + int8_t a; + }; + + struct color_128_t { + int32_t r; + int32_t g; + int32_t b; + int32_t a; + }; + + #pragma pack(pop) + + using namespace godot; + using namespace OpenVic; + + static bool read_string(Ref const& file, String& str, bool replace_chars = true, bool log = false) { + //string = uint32 len, char[len] + uint32_t length = file->get_32(); + if(file->get_length() - file->get_position() < length) return false; + + str = file->get_buffer(length).get_string_from_ascii(); + if(log) UtilityFunctions::print(vformat("before %s", str)); + + if(replace_chars){ + str = str.replace(":", "_"); + str = str.replace("\\", "_"); + str = str.replace("/", "_"); + } + if(log) UtilityFunctions::print(vformat("after %s", str)); + return true; + } + + template + static bool read_struct(Ref const& file, T& t) { + if(file->get_length() - file->get_position() < sizeof(T)) return false; + bool res = file->get_buffer(reinterpret_cast(&t), sizeof(t)) == sizeof(t); + return res; + } + + //Warning: works on the assumption of it being a packed struct being loaded into the array + template + static bool read_struct_array(Ref const& file, std::vector& t, uint32_t size) { + if(file->get_length() - file->get_position() < size*sizeof(T)) return false; + t.resize(size*sizeof(T)); + bool res = file->get_buffer(reinterpret_cast(t.data()), sizeof(T)*size) == sizeof(t); + return res; + } + + static bool read_chunk_header(Ref const& file, chunk_header_t& header) { + //ERR_FAIL_COND_V(!read_struct(file, header), false); + bool res = read_struct(file, header); + + //UtilityFunctions::print( + // vformat("XAC/XSM chunk: type = %x, length = %x, version = %d, successful? %s", header.type, header.length, header.version, res) + //); + + return res; + } + + static Vector2 vec2d_to_godot(vec2d_t const& vec2_in) { + Vector2 vec2_out = { + vec2_in.x, + vec2_in.y + }; + return vec2_out; + } + + static Vector3 vec3d_to_godot(vec3d_t const& vec3_in, bool is_position = false) { + Vector3 vec3_out = { + vec3_in.x, + vec3_in.y, + vec3_in.z + }; + if(is_position) vec3_out.x *= -1; + return vec3_out; + } + + static Vector4 vec4d_to_godot(vec4d_t const& vec4_in) { + Vector4 vec4_out = { + vec4_in.x, + vec4_in.y, + vec4_in.z, + vec4_in.w + }; + return vec4_out; + } + + static Quaternion quat_v1_to_godot(quat_v1_t const& quat_in) { + Quaternion quat_out = Quaternion( + quat_in.x, + -quat_in.y, + -quat_in.z, + quat_in.w + ); + if(!quat_out.is_normalized()){ + quat_out = Quaternion(); + } + return quat_out; + } + + static Quaternion quat_v2_to_godot(quat_v2_t const& quat_in) { + static const float scale = 32767; + Quaternion quat_out = Quaternion( + static_cast(quat_in.x) / scale, + static_cast(-quat_in.y) / scale, + static_cast(-quat_in.z) / scale, + static_cast(quat_in.w) / scale + ); + if(!quat_out.is_normalized()){ + //Logger::error("quat isn't normalized"); + quat_out = Quaternion(); + } + + return quat_out; + } + + static Color color_32_to_godot(color_32_t color_in){ + static const float scale = 256; + Color color_out = { + (float)color_in.r / scale, + (float)color_in.g / scale, + (float)color_in.b / scale, + (float)color_in.a / scale + }; + return color_out; + } + + //TODO: verify this conversion is correct >> don't think it is + static Color color_128_to_godot(color_128_t color_in){ + static const double scale = 2147483647; + Color color_out = { + static_cast(color_in.r / scale), + static_cast(color_in.g / scale), + static_cast(color_in.b / scale), + static_cast(color_in.a / scale) + }; + return color_out; + } + + static Color vec4d_to_godot_color(vec4d_t color_in){ + Color color_out = { + color_in.x, + color_in.y, + color_in.z, + color_in.w + }; + return color_out; + } +} \ No newline at end of file diff --git a/extension/src/openvic-extension/utility/XSMLoader.cpp b/extension/src/openvic-extension/utility/XSMLoader.cpp new file mode 100644 index 00000000..e69de29b diff --git a/extension/src/openvic-extension/utility/XSMLoader.hpp b/extension/src/openvic-extension/utility/XSMLoader.hpp new file mode 100644 index 00000000..bc5ddbb0 --- /dev/null +++ b/extension/src/openvic-extension/utility/XSMLoader.hpp @@ -0,0 +1,422 @@ +#pragma once +#include +//#include +#include +//#include "Utilities.hpp" +#include "XACUtilities.hpp" +#include "godot_cpp/classes/animation.hpp" +#include "godot_cpp/variant/packed_byte_array.hpp" +#include "godot_cpp/variant/packed_vector3_array.hpp" +#include "godot_cpp/variant/packed_vector4_array.hpp" +//#include "openvic-simulation/utility/Logger.hpp" + +#include +#include +#include "godot_cpp/variant/quaternion.hpp" +#include "openvic-simulation/utility/Logger.hpp" +//#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +static constexpr uint32_t XSM_FORMAT_SPECIFIER = ' MSX'; /* Order reversed due to litte endian */ +static constexpr uint8_t XSM_VERSION_MAJOR = 1, XSM_VERSION_MINOR = 0; + +// Pack structs of data that can be read directly + +#pragma pack(push) +#pragma pack(1) + +struct xsm_header_t { + uint32_t format_identifier; + uint8_t version_major; + uint8_t version_minor; + uint8_t big_endian; + uint8_t pad; +}; + +//v2 of the header adds these 2 properties at the start +struct xsm_metadata_v2_pack_extra { + float unused; + float max_acceptable_error; +}; + +struct xsm_metadata_pack { + int32_t fps; + uint8_t exporter_version_major; //these 3 properties are uncertain + uint8_t exporter_version_minor; + uint16_t pad; +}; + +struct position_key { + vec3d_t position; + float time; +}; + +struct rotation_key_v2 { + quat_v2_t rotation; + float time; +}; + +struct rotation_key_v1 { + quat_v1_t rotation; + float time; +}; + +struct scale_key { + vec3d_t scale; + float time; +}; + +//the quaternions come at the start of the skeletal submotion +struct submotion_rot_v2_pack { + quat_v2_t pose_rotation; + quat_v2_t bind_pose_rotation; + quat_v2_t pose_scale_rotation; + quat_v2_t bind_pose_scale_rotation; +}; + +struct submotion_rot_v1_pack { + quat_v1_t pose_rotation; + quat_v1_t bind_pose_rotation; + quat_v1_t pose_scale_rotation; + quat_v1_t bind_pose_scale_rotation; +}; + +struct skeletal_submotion_pack { + vec3d_t pose_position; + vec3d_t pose_scale; + vec3d_t bind_pose_position; + vec3d_t bind_pose_scale; + int32_t position_key_count; + int32_t rotation_key_count; + int32_t scale_key_count; + int32_t scale_rotation_key_count; + float max_error; +}; + +struct node_submotion_pack { + vec3d_t pose_position; + vec3d_t pose_scale; + vec3d_t bind_pose_position; + vec3d_t bind_pose_scale; + int32_t position_key_count; + int32_t rotation_key_count; + int32_t scale_key_count; + int32_t scale_rotation_key_count; +}; + +#pragma pack(pop) + +/* +0xC8, v1 chunk documentation: + 4*8x 32 bit sections = 32 + 4x quat32 = 16 + 4x vec3 = 12 + 4x keycount 04 + 32 + string node_name + data... + + so this chunk is the same as a skeletal submotion v1 + but without the fMaxError + +*** This chunk appears a lot in the zulu_moving.xsm animation +zulu moving doesn't have any 0xCA chunks, so this is likely a replacement +for the bone animation chunk... +Let's call it node_submotion +*/ + +struct xsm_metadata { + bool has_v2_data = false; + xsm_metadata_v2_pack_extra v2_data; + xsm_metadata_pack packed; + String source_app; + String original_file_name; + String export_date; + String motion_name; +}; + +struct rotation_key { + Quaternion rotation; + float time; +}; + +struct skeletal_submotion { + Quaternion pose_rotation; + Quaternion bind_pose_rotation; + Quaternion pose_scale_rotation; + Quaternion bind_pose_scale_rotation; + skeletal_submotion_pack packed; + String node_name; + std::vector position_keys; + std::vector rotation_keys; + std::vector scale_keys; + std::vector scale_rotation_keys; +}; + +struct bone_animation { + int32_t submotion_count; + std::vector submotions; +}; + +struct node_submotion { + Quaternion pose_rotation; + Quaternion bind_pose_rotation; + Quaternion pose_scale_rotation; + Quaternion bind_pose_scale_rotation; + node_submotion_pack packed; + String node_name; + std::vector position_keys; + std::vector rotation_keys; + std::vector scale_keys; + std::vector scale_rotation_keys; +}; + +static bool read_xsm_header(Ref const& file) { + xsm_header_t header; + ERR_FAIL_COND_V(!read_struct(file, header), false); + + //Logger::info("XSM file version: ", header.version_major, ".", header.version_minor," big endian?: ",header.big_endian); + + ERR_FAIL_COND_V_MSG( + header.format_identifier != XSM_FORMAT_SPECIFIER, false, vformat( + "Invalid XSM format identifier: %x (should be %x)", header.format_identifier, XSM_FORMAT_SPECIFIER + ) + ); + + ERR_FAIL_COND_V_MSG( + header.version_major != XSM_VERSION_MAJOR || header.version_minor != XSM_VERSION_MINOR, false, vformat( + "Invalid XSM version: %d.%d (should be %d.%d)", + header.version_major, header.version_minor, XSM_VERSION_MAJOR, XSM_VERSION_MINOR + ) + ); + + ERR_FAIL_COND_V_MSG( + header.big_endian != 0, false, "Invalid XSM endianness: big endian (only little endian is supported)" + ); + + return true; +} + +static bool read_xsm_metadata(Ref const& file, xsm_metadata& metadata, int32_t version) { + bool res = true; + if(version != 1) { + res &= read_struct(file, metadata.v2_data); + metadata.has_v2_data = true; + } + res &= read_struct(file, metadata.packed); + res &= read_string(file, metadata.source_app); + res &= read_string(file, metadata.original_file_name); + res &= read_string(file, metadata.export_date); + res &= read_string(file, metadata.motion_name); + + return res; +} + +static bool read_rot_keys(Ref const& file, std::vector* keys_out, int32_t count, int32_t version) { + bool res = true; + + if(version == 1) { + std::vector rot_keys_v1; + res &= read_struct_array(file,rot_keys_v1,count); + for(rotation_key_v1 key : rot_keys_v1) { + keys_out->push_back({quat_v1_to_godot(key.rotation), key.time}); + } + } + else { + std::vector rot_keys_v2; + res &= read_struct_array(file,rot_keys_v2,count); + for(rotation_key_v2 key : rot_keys_v2) { + keys_out->push_back({quat_v2_to_godot(key.rotation), key.time}); + } + } + + return res; +} + +static bool read_skeletal_submotion(Ref const& file, skeletal_submotion& submotion, int32_t version){ + bool res = true; + if(version == 1) { //float component quats (v1) + submotion_rot_v1_pack rot_comps; + res &= read_struct(file,rot_comps); + submotion.pose_rotation = quat_v1_to_godot(rot_comps.pose_rotation); + submotion.bind_pose_rotation = quat_v1_to_godot(rot_comps.bind_pose_rotation); + submotion.pose_scale_rotation = quat_v1_to_godot(rot_comps.pose_scale_rotation); + submotion.bind_pose_scale_rotation = quat_v1_to_godot(rot_comps.bind_pose_scale_rotation); + } + else { //int16 component quats (v2) + submotion_rot_v2_pack rot_comps; + res &= read_struct(file,rot_comps); + submotion.pose_rotation = quat_v2_to_godot(rot_comps.pose_rotation); + submotion.bind_pose_rotation = quat_v2_to_godot(rot_comps.bind_pose_rotation); + submotion.pose_scale_rotation = quat_v2_to_godot(rot_comps.pose_scale_rotation); + submotion.bind_pose_scale_rotation = quat_v2_to_godot(rot_comps.bind_pose_scale_rotation); + } + + ERR_FAIL_COND_V(!read_struct(file, submotion.packed), false); + res &= read_string(file, submotion.node_name,true); + res &= read_struct_array(file,submotion.position_keys,submotion.packed.position_key_count); + res &= read_rot_keys(file,&submotion.rotation_keys,submotion.packed.rotation_key_count, version); + res &= read_struct_array(file,submotion.scale_keys,submotion.packed.scale_key_count); + res &= read_rot_keys(file,&submotion.scale_rotation_keys,submotion.packed.scale_rotation_key_count, version); + + return res; +} + +static bool read_xsm_bone_animation(Ref const& file, bone_animation& bone_animation, int32_t version){ + bone_animation.submotion_count = file->get_32(); + bool ret = true; + + for(int i=0; i const& file, std::vector* submotions) { + bool res = true; + node_submotion submotion; + + submotion_rot_v1_pack rot_comps; + res &= read_struct(file,rot_comps); + submotion.pose_rotation = quat_v1_to_godot(rot_comps.pose_rotation); + submotion.bind_pose_rotation = quat_v1_to_godot(rot_comps.bind_pose_rotation); + submotion.pose_scale_rotation = quat_v1_to_godot(rot_comps.pose_scale_rotation); + submotion.bind_pose_scale_rotation = quat_v1_to_godot(rot_comps.bind_pose_scale_rotation); + + res &= read_struct(file, submotion.packed); + res &= read_string(file, submotion.node_name); + res &= read_struct_array(file,submotion.position_keys,submotion.packed.position_key_count); + res &= read_rot_keys(file,&submotion.rotation_keys,submotion.packed.rotation_key_count, 1); + res &= read_struct_array(file,submotion.scale_keys,submotion.packed.scale_key_count); + res &= read_rot_keys(file,&submotion.scale_rotation_keys,submotion.packed.scale_rotation_key_count, 1); + submotions->push_back(submotion); + + return res; +} + +//returns highest time in the submotion +template +float add_submotion(Ref anim, T submotion) { + static const StringName SKELETON_NODE_PATH = "./skeleton:%s"; + float max_time = 0; + + //NOTE: godot uses ':' to specify properties, so we replaced such characters with '_' when we read them in + String skeleton_path = vformat(SKELETON_NODE_PATH, submotion.node_name); + + int pos_id = anim->add_track(Animation::TYPE_POSITION_3D); + int rot_id = anim->add_track(Animation::TYPE_ROTATION_3D); + int scale_id = anim->add_track(Animation::TYPE_SCALE_3D); + + /*anim->track_set_interpolation_type(pos_id, Animation::INTERPOLATION_LINEAR); + anim->track_set_interpolation_type(rot_id, Animation::INTERPOLATION_LINEAR); + anim->track_set_interpolation_type(scale_id, Animation::INTERPOLATION_LINEAR); + */ + + + anim->track_set_path(pos_id, skeleton_path); + anim->track_set_path(rot_id, skeleton_path); + anim->track_set_path(scale_id, skeleton_path); + + anim->position_track_insert_key(pos_id, 0, vec3d_to_godot(submotion.packed.pose_position, true)); + anim->rotation_track_insert_key(rot_id, 0, submotion.bind_pose_rotation); + anim->scale_track_insert_key(scale_id, 0, vec3d_to_godot(submotion.packed.pose_scale)); + + for(position_key key : submotion.position_keys){ + anim->position_track_insert_key(pos_id, key.time, vec3d_to_godot(key.position, true)); + if(key.time > max_time) max_time = key.time; + } + + for(rotation_key key : submotion.rotation_keys){ + anim->rotation_track_insert_key(rot_id, key.time, key.rotation); + if(key.time > max_time) max_time = key.time; + } + + for(scale_key key : submotion.scale_keys){ + anim->scale_track_insert_key(scale_id, key.time, vec3d_to_godot(key.scale)); + if(key.time > max_time) max_time = key.time; + } + + // TODO: SCALEROTATION + for(rotation_key key : submotion.scale_rotation_keys){ + if(key.time > max_time) max_time = key.time; + } + + // ***** experiment, doesn't seem to help + /*anim->position_track_insert_key(pos_id, max_time+0.01, vec3d_to_godot(submotion.packed.pose_position, true)); + anim->rotation_track_insert_key(rot_id, max_time+0.01, submotion.bind_pose_rotation); + anim->scale_track_insert_key(scale_id, max_time+0.01, vec3d_to_godot(submotion.packed.pose_scale)); + */ + + return max_time; +} + +//a variable string or stringname with %s makes godot fail silently if placed outside a function + +static Ref _load_xsm_animation(Ref const& file){ + + read_xsm_header(file); + + xsm_metadata metadata; + bone_animation bone_anim; + std::vector node_submotions; + + bool res = true; + + while(!file->eof_reached()){ + chunk_header_t header; + if(!read_chunk_header(file, header)) break; + + if(header.type == 0xC9) read_xsm_metadata(file, metadata, header.version); //in zulu_moving.xsm, this is v1 + else if(header.type == 0xC8) read_node_submotion(file,&node_submotions); + else if(header.type == 0xCA) read_xsm_bone_animation(file, bone_anim, header.version); + else { + UtilityFunctions::print( + vformat("Unsupported XSM chunk: file: %s, type = %x, length = %x, version = %d", file->get_path(), header.type, header.length, header.version) + ); + + // Skip unsupported chunks + if(header.length + file->get_position() < file->get_length()) { + PackedByteArray buf = file->get_buffer(header.length); + UtilityFunctions::print(buf); + } + else { + res = false; + break; + } + } + } + + float animation_length = 0.0; + Ref anim = Ref(); + anim.instantiate(); + + anim->set_step(1.0/metadata.packed.fps); + anim->set_loop_mode(Animation::LOOP_LINEAR); + + if(res == false) return anim; //exit early if reading the chunks in failed + + + for(skeletal_submotion submotion : bone_anim.submotions){ + float submotion_len = add_submotion(anim, submotion); + if(submotion_len > animation_length) animation_length = submotion_len; + } + + for(node_submotion submotion : node_submotions){ + float submotion_len = add_submotion(anim, submotion); + if(submotion_len > animation_length) animation_length = submotion_len; + } + + //its as if the last animation frame was being messed up + //increase the animation time to hold it for longer temporarily + anim->set_length(animation_length + 2); + //anim->set_length(animation_length); + + + + return anim; +} \ No newline at end of file diff --git a/game/src/Game/GameSession/ModelManager.gd b/game/src/Game/GameSession/ModelManager.gd index 8cec49dc..319a2d2c 100644 --- a/game/src/Game/GameSession/ModelManager.gd +++ b/game/src/Game/GameSession/ModelManager.gd @@ -102,26 +102,34 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b or attachments_key in model_dict ) + #var model : Node3D + #if !is_unit: + # model = ModelSingleton.get_xac_model(model_dict[file_key]) + #else: + # model = XACLoader.get_xac_model(model_dict[file_key], is_unit) var model : Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit) if not model: return null model.scale *= model_dict[scale_key] - + #print("GENERATE MODEL") if model is UnitModel: # Animations var idle_dict : Dictionary = model_dict.get(idle_key, {}) if idle_dict: - model.idle_anim = XSMLoader.get_xsm_animation(idle_dict[animation_file_key]) + model.idle_anim = ModelSingleton.get_xsm_animation(idle_dict[animation_file_key]) + #model.idle_anim = XSMLoader.get_xsm_animation(idle_dict[animation_file_key]) model.scroll_speed_idle = idle_dict[animation_time_key] var move_dict : Dictionary = model_dict.get(move_key, {}) if move_dict: - model.move_anim = XSMLoader.get_xsm_animation(move_dict[animation_file_key]) + model.move_anim = ModelSingleton.get_xsm_animation(move_dict[animation_file_key]) + #model.move_anim = XSMLoader.get_xsm_animation(move_dict[animation_file_key]) model.scroll_speed_move = move_dict[animation_time_key] var attack_dict : Dictionary = model_dict.get(attack_key, {}) if attack_dict: - model.attack_anim = XSMLoader.get_xsm_animation(attack_dict[animation_file_key]) + model.attack_anim = ModelSingleton.get_xsm_animation(attack_dict[animation_file_key]) + #model.attack_anim = XSMLoader.get_xsm_animation(attack_dict[animation_file_key]) model.scroll_speed_attack = attack_dict[animation_time_key] # Attachments