diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.cpp b/extension/src/openvic-extension/singletons/ModelSingleton.cpp index f51ae77f..483935a8 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,8 @@ #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" #include "openvic-extension/utility/Utilities.hpp" +#include "openvic-simulation/utility/Logger.hpp" +//#include "godot_cpp/classes/file_access.hpp" using namespace godot; using namespace OpenVic; @@ -19,6 +22,7 @@ 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"}); } ModelSingleton* ModelSingleton::get_singleton() { @@ -481,3 +485,20 @@ 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(); + 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; +} \ 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..848a8195 100644 --- a/extension/src/openvic-extension/singletons/ModelSingleton.hpp +++ b/extension/src/openvic-extension/singletons/ModelSingleton.hpp @@ -1,10 +1,13 @@ #pragma once +#include +#include #include #include #include #include +#include "../utility/XSMLoader.hpp" namespace OpenVic { struct BuildingInstance; @@ -31,9 +34,11 @@ namespace OpenVic { using animation_map_t = deque_ordered_map; using model_map_t = deque_ordered_map; + using xsm_map_t = deque_ordered_map>; animation_map_t animation_cache; model_map_t model_cache; + xsm_map_t xsm_cache; godot::Dictionary get_animation_dict(GFX::Actor::Animation const& animation); godot::Dictionary get_model_dict(GFX::Actor const& actor); @@ -56,5 +61,7 @@ namespace OpenVic { godot::Dictionary get_flag_model(bool floating); godot::TypedArray get_buildings(); + + godot::Ref get_xsm_animation(godot::String anim_name); }; } 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..b2e36cda --- /dev/null +++ b/extension/src/openvic-extension/utility/XACLoader.hpp @@ -0,0 +1,299 @@ +/*#include +#include +#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 "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +//using OpenVic::Utilities::std_view_to_godot_string; + +static constexpr uint32_t XSM_FORMAT_SPECIFIER = ' CAX'; /* Order reversed due to litte endian */ +/*static constexpr uint8_t XSM_VERSION_MAJOR = 1, XSM_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_v1_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; +}; + +struct material_layer_pack_v2 { + 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 material_layer_pack_v1 { + int32_t unknown[3]; //could be a vec3? + 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]; +}; + +struct vertices_attribute_pack { + int32_t type; //0-6 + 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_v3_pack { + int32_t node_id; + int32_t local_bones_count; + int32_t influences_count; + uint8_t is_for_collision; //bool + uint8_t pad[3]; +}; + +struct skinning_v2_pack { + int32_t node_id; + 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 material_layer_v2 { + material_layer_pack_v2 packed; + String texture; +}; + +struct material_definition_v2 { + material_definition_pack packed; + String name; + std::vector layers; +}; + +struct material_layer_v1 { + material_layer_pack_v1 packed; + String texture; +}; + +struct material_definition_v1 { + material_definition_pack packed; + String name; + std::vector layers; +}; + +struct vertices_attribute { + vertices_attribute_pack packed; + PackedByteArray data; +}; + +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_v3 { + skinning_v3_pack packed; + std::vector influence_data; + std::vector influence_ranges; +}; + +struct skinning_v2 { + skinning_v2_pack packed; + std::vector influence_data; + std::vector influence_ranges; +}; + +struct node_chunk { + node_chunk_pack packed; + String name; +}; + + +/* +static bool read_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; +} + +Skeleton3D* ModelLoader::load_xac_model(godot::String const& xac_path) { + ERR_FAIL_COND_V(xac_path.is_empty(), nullptr); + + const Ref file = FileAccess::open(xac_path, FileAccess::ModeFlags::READ); + Error err = FileAccess::get_open_error(); + ERR_FAIL_COND_V_MSG( + err != OK || file.is_null(), nullptr, vformat("Failed to open XAC model file: %s", xac_path) + ); + + ERR_FAIL_COND_V_MSG( + !read_header(file), nullptr, vformat("Failed to read and validate XAC header for model file: %s", xac_path) + ); + + Skeleton3D* skeleton = memnew(Skeleton3D); + ERR_FAIL_NULL_V(skeleton, nullptr); + + while (!file->eof_reached()) { + ERR_FAIL_COND_V(!read_chunk(file), nullptr); + } + + // build skeleton and child nodes... + + return skeleton; +} +*/ \ 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..480723b7 --- /dev/null +++ b/extension/src/openvic-extension/utility/XACUtilities.hpp @@ -0,0 +1,207 @@ +#include +#include +#include + +//#include "openvic-extension/utility/Utilities.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) { + /* + string = uint32 len, char[len] + */ + uint32_t length = file->get_32(); + if(file->get_length() - file->get_position() < length) return false; + + Error error = str.resize(length); + str = file->get_buffer(length).get_string_from_ascii(); + if(replace_chars){ + str.replace(":", "_"); + str.replace("\\", "_"); + str.replace("/", "_"); + } + 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); + file->seek(file->get_position() + sizeof(T)); + return res; + } + + //Warning: works on the assumption of it being a packed struct being loaded into the array + //TODO: verify this works properly + 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.reserve(size*sizeof(T)); + bool res = file->get_buffer(reinterpret_cast(&t), sizeof(t)) == sizeof(t); + file->seek(file->get_position() + 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); + + UtilityFunctions::print( + vformat("XAC/XSM chunk: type = %x, length = %x, version = %d", header.type, header.length, header.version) + ); + + return true; + } + + 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) { + Vector3 vec3_out = { + vec3_in.x, + vec3_in.y, + vec3_in.z + }; + 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 = { + quat_in.x, + quat_in.y, + quat_in.z, + quat_in.w + }; + return quat_out; + } + + static Quaternion quat_v2_to_godot(quat_v2_t const& quat_in) { + Quaternion quat_out = { + static_cast(quat_in.x / 32767.0), + static_cast(quat_in.y / 32767.0), + static_cast(quat_in.z / 32767.0), + static_cast(quat_in.w / 32767.0) + }; + /*quat_out.x = quat_in.x / 32767.0; + quat_out.y = quat_in.y / 32767.0; + quat_out.z = quat_in.z / 32767.0; + quat_out.w = quat_in.w / 32767.0;*/ + return quat_out; + } + + static Color color_32_to_godot(color_32_t color_in){ + Color color_out = { + (float)color_in.r, + (float)color_in.g, + (float)color_in.b, + (float)color_in.a + }; + 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){ + Color color_out = { + static_cast(color_in.r / 2147483647.0), + static_cast(color_in.g / 2147483647.0), + static_cast(color_in.b / 2147483647.0), + static_cast(color_in.a / 2147483647.0) + }; + /*color_out.r = color_in.r / 0xFFFFFFFF; + color_out.g = color_in.g / 0xFFFFFFFF; + color_out.b = color_in.b / 0xFFFFFFFF; + color_out.a = color_in.a / 0xFFFFFFFF;*/ + 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..f1be09fc --- /dev/null +++ b/extension/src/openvic-extension/utility/XSMLoader.hpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include "XACUtilities.hpp" +#include "godot_cpp/classes/animation.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 "openvic-simulation/utility/Logger.hpp" +//#include "openvic-extension/utility/Utilities.hpp" + +using namespace godot; +using namespace OpenVic; + +//using OpenVic::Utilities::std_view_to_godot_string; + +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; +}; + +struct xsm_metadata_v2_pack { + float unused; + float max_acceptable_error; + int32_t fps; + uint8_t exporter_version_major; + 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; +}; + +struct scale_rotation_key_v2 { + quat_v2_t rotation; + float time; +}; + +struct scale_rotation_key_v1 { + quat_v1_t rotation; + float time; +}; + +struct skeletal_submotion_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; + 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 skeletal_submotion_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; + 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; +}; + +#pragma pack(pop) + +struct xsm_metadata { + xsm_metadata_v2_pack packed; + String source_app; + String original_file_name; + String export_date; + String motion_name; +}; + +// structs which can't be read directly +struct skeletal_submotion_v2 { + skeletal_submotion_v2_pack packed; + String node_name; + std::vector position_keys; + std::vector rotation_keys; + std::vector scale_keys; + std::vector scale_rotation_keys; +}; + +struct skeletal_submotion_v1 { + skeletal_submotion_v1_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_v2 { + int32_t submotion_count; + std::vector submotions; +}; + +struct bone_animation_v1 { + int32_t submotion_count; + std::vector submotions; +}; + +static bool read_xsm_header(Ref const& file) { + xsm_header_t header; + ERR_FAIL_COND_V(!read_struct(file, header), false); + + Logger::info("magic is correct? ", header.format_identifier==XSM_FORMAT_SPECIFIER); + Logger::info("XSM file version: ", header.version_major, ".", header.version_minor," bigendien?: ",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) { + ERR_FAIL_COND_V(!read_struct(file, metadata.packed), false); + bool 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_skeletal_submotion_v1(Ref const& file, skeletal_submotion_v1& submotion){ + ERR_FAIL_COND_V(!read_struct(file, submotion.packed), false); + bool res = read_string(file, submotion.node_name); + res &= read_struct_array(file,submotion.position_keys,submotion.packed.position_key_count); + res &= read_struct_array(file,submotion.rotation_keys,submotion.packed.rotation_key_count); + res &= read_struct_array(file,submotion.scale_keys,submotion.packed.scale_key_count); + res &= read_struct_array(file,submotion.scale_rotation_keys,submotion.packed.scale_rotation_key_count); + return res; +} + +static bool read_xsm_bone_animation_v1(Ref const& file, bone_animation_v1& bone_animation){ + bone_animation.submotion_count = file->get_32(); + bool ret = true; + bone_animation.submotions.reserve(bone_animation.submotion_count); + for(int i=0; i const& file, skeletal_submotion_v2& submotion){ + ERR_FAIL_COND_V(!read_struct(file, submotion.packed), false); + bool res = read_string(file, submotion.node_name); + res &= read_struct_array(file,submotion.position_keys,submotion.packed.position_key_count); + res &= read_struct_array(file,submotion.rotation_keys,submotion.packed.rotation_key_count); + res &= read_struct_array(file,submotion.scale_keys,submotion.packed.scale_key_count); + res &= read_struct_array(file,submotion.scale_rotation_keys,submotion.packed.scale_rotation_key_count); + return res; +} + +static bool read_xsm_bone_animation_v2(Ref const& file, bone_animation_v2& bone_animation){ + bone_animation.submotion_count = file->get_32(); + bool ret = true; + bone_animation.submotions.reserve(bone_animation.submotion_count); + for(int i=0; i _load_xsm_animation(Ref const& file){ + read_xsm_header(file); + + xsm_metadata metadata; + std::vector anims_v1; + std::vector anims_v2; + + while(!file->eof_reached()){ + chunk_header_t header; + read_chunk_header(file, header); + if(header.type == 0xC9 && header.version == 2) read_xsm_metadata(file, metadata); + else if(header.type == 0xCA && header.version == 2) { + bone_animation_v2 anim; + read_xsm_bone_animation_v2(file, anim); + anims_v2.push_back(anim); + } + else if(header.type == 0xCA && header.version == 1) { + bone_animation_v1 anim; + read_xsm_bone_animation_v1(file, anim); + anims_v1.push_back(anim); + } + else { + UtilityFunctions::print( + vformat("Unsupported XSM chunk: type = %x, length = %x, version = %d", header.type, header.length, header.version) + ); + // Skip unsupported chunks + file->get_buffer(header.length); + } + } + + float animation_length = 0.0; + Ref anim = Ref(); + anim.instantiate(); + + //NOTE: godot uses ':' to specify properties, so we replaced such characters with '_' when we read them in + //TODO: perhaps experiment was just adding pose keys every time? + + for(bone_animation_v1 bone_anim : anims_v1){ + for(skeletal_submotion_v1 submotion : bone_anim.submotions){ + String skeleton_path = vformat("./skeleton:%s", submotion.node_name); + // POSITION + if(submotion.packed.position_key_count > 0) { + int id = anim->add_track(Animation::TYPE_POSITION_3D); + anim->track_set_path(id, skeleton_path); + for(position_key key : submotion.position_keys){ + anim->position_track_insert_key(id, key.time, vec3d_to_godot(key.position)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_POSITION_3D); + anim->track_set_path(id, skeleton_path); + anim->position_track_insert_key(id, 0, vec3d_to_godot(submotion.packed.pose_position)); + } + + // ROTATION + if(submotion.packed.rotation_key_count > 0) { + int id = anim->add_track(Animation::TYPE_ROTATION_3D); + anim->track_set_path(id, skeleton_path); + for(rotation_key_v1 key : submotion.rotation_keys){ + anim->rotation_track_insert_key(id, key.time, quat_v1_to_godot(key.rotation)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_ROTATION_3D); + anim->track_set_path(id, skeleton_path); + anim->rotation_track_insert_key(id, 0, quat_v1_to_godot(submotion.packed.pose_rotation)); + } + + // SCALE + if(submotion.packed.scale_key_count > 0) { + int id = anim->add_track(Animation::TYPE_SCALE_3D); + anim->track_set_path(id, skeleton_path); + for(scale_key key : submotion.scale_keys){ + anim->scale_track_insert_key(id, key.time, vec3d_to_godot(key.scale)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_SCALE_3D); + anim->track_set_path(id, skeleton_path); + anim->scale_track_insert_key(id, 0, vec3d_to_godot(submotion.packed.pose_scale)); + } + // TODO: SCALEROTATION (unused in paradox) + } + } + //Do it all again for v2 (ie. int16 style quaternions) + + for(bone_animation_v2 bone_anim : anims_v2){ + for(skeletal_submotion_v2 submotion : bone_anim.submotions){ + String skeleton_path = vformat("./skeleton:%s",submotion.node_name); + // POSITION + if(submotion.packed.position_key_count > 0) { + int id = anim->add_track(Animation::TYPE_POSITION_3D); + anim->track_set_path(id, skeleton_path); + for(position_key key : submotion.position_keys){ + anim->position_track_insert_key(id, key.time, vec3d_to_godot(key.position)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_POSITION_3D); + anim->track_set_path(id, skeleton_path); + anim->position_track_insert_key(id, 0, vec3d_to_godot(submotion.packed.pose_position)); + } + + // ROTATION + if(submotion.packed.rotation_key_count > 0) { + int id = anim->add_track(Animation::TYPE_ROTATION_3D); + anim->track_set_path(id, skeleton_path); + for(rotation_key_v2 key : submotion.rotation_keys){ + anim->rotation_track_insert_key(id, key.time, quat_v2_to_godot(key.rotation)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_ROTATION_3D); + anim->track_set_path(id, skeleton_path); + anim->rotation_track_insert_key(id, 0, quat_v2_to_godot(submotion.packed.pose_rotation)); + } + + // SCALE + if(submotion.packed.scale_key_count > 0) { + int id = anim->add_track(Animation::TYPE_SCALE_3D); + anim->track_set_path(id, skeleton_path); + for(scale_key key : submotion.scale_keys){ + anim->scale_track_insert_key(id, key.time, vec3d_to_godot(key.scale)); + if(key.time > animation_length) animation_length = key.time; + } + } + else { + int id = anim->add_track(Animation::TYPE_SCALE_3D); + anim->track_set_path(id, skeleton_path); + anim->scale_track_insert_key(id, 0, vec3d_to_godot(submotion.packed.pose_scale)); + } + // TODO: SCALEROTATION (unused in paradox) + } + } + + anim->set_length(animation_length); + anim->set_loop_mode(Animation::LOOP_LINEAR); + + 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..109e8bb7 100644 --- a/game/src/Game/GameSession/ModelManager.gd +++ b/game/src/Game/GameSession/ModelManager.gd @@ -106,12 +106,15 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b 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]) + print(idle_dict[animation_file_key]) + model.idle_anim = ModelSingleton.get_xsm_animation(idle_dict[animation_file_key]) + #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, {})