diff --git a/extension/doc_classes/ModelSingleton.xml b/extension/doc_classes/ModelSingleton.xml
index 74c669af..c340cdce 100644
--- a/extension/doc_classes/ModelSingleton.xml
+++ b/extension/doc_classes/ModelSingleton.xml
@@ -35,5 +35,37 @@
+
+
+
+
+ Gets the animation from an internal cache or file system using the provided file path.
+
+
+
+
+
+
+ Gets the animation from an internal cache or file system using the provided file path.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extension/src/openvic-extension/singletons/ModelSingleton.cpp b/extension/src/openvic-extension/singletons/ModelSingleton.cpp
index 5b2fa8b8..70ff0c28 100644
--- a/extension/src/openvic-extension/singletons/ModelSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/ModelSingleton.cpp
@@ -1,5 +1,7 @@
#include "ModelSingleton.hpp"
+#include
+#include
#include
#include
@@ -9,6 +11,11 @@
#include "openvic-extension/singletons/GameSingleton.hpp"
#include "openvic-extension/utility/ClassBindings.hpp"
#include "openvic-extension/utility/Utilities.hpp"
+#include "godot_cpp/classes/global_constants.hpp"
+#include "godot_cpp/classes/node3d.hpp"
+#include "godot_cpp/classes/resource_loader.hpp"
+#include "godot_cpp/classes/shader_material.hpp"
+#include "godot_cpp/variant/string_name.hpp"
using namespace godot;
using namespace OpenVic;
@@ -19,6 +26,11 @@ 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" });
+ OV_BIND_METHOD(ModelSingleton::setup_flag_shader);
+ OV_BIND_METHOD(ModelSingleton::set_scroll_material_texture, {"texture_name"});
+ OV_BIND_METHOD(ModelSingleton::set_unit_material_texture, {"type", "texture_name"});
}
ModelSingleton* ModelSingleton::get_singleton() {
@@ -481,3 +493,151 @@ 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()) {
+ 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()) {
+ Node3D* unit = (Node3D*)it->second->duplicate();
+ unit->call("unit_init", true);
+ //if(unit->get_script()->) {
+
+ //}
+ return unit;
+ //return (Node3D*)it->second->duplicate();
+ }
+
+ 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 XAC Model from file: ",Utilities::godot_to_std_string(source_file));
+
+ Node3D* node = XacLoader()._load_xac_model(FileAccess::open(path, FileAccess::READ));
+ xac_cache.emplace(source_file,node);
+
+ //if we return the "prototype" in the cache, then it will get scale by 1/256, and then all subsequent units
+ //duplicated from the cache will get effectively scaled by 1/256*1/256, among other problems.
+ Node3D* unit = (Node3D*)node->duplicate();
+ unit->call("unit_init", true);
+ return unit;
+}
+
+Error ModelSingleton::setup_flag_shader() {
+ godot::Error result = godot::OK;
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ ERR_FAIL_NULL_V(game_singleton, {});
+
+ static const godot::StringName Param_flag_dimensions = "flag_dims";
+ static const godot::StringName Param_flag_texture_sheet = "texture_flag_sheet_diffuse";
+ godot::ResourceLoader* loader = godot::ResourceLoader::get_singleton();
+ static const godot::Ref flag_shader = loader->load("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 void pushback_shader_array(godot::Ref shader, godot::String property, godot::Variant value) {
+ godot::Array arr = shader->get_shader_parameter(property);
+ arr.push_back(value);
+ shader->set_shader_parameter(property, arr);
+}
+
+Ref ModelSingleton::get_unit_shader() {
+ if(unit_shader.is_null()) {
+ godot::ResourceLoader* loader = godot::ResourceLoader::get_singleton();
+ unit_shader = loader->load("res://src/Game/Model/unit_colours_mat.tres");
+ }
+ return unit_shader;
+}
+
+Ref ModelSingleton::get_scroll_shader() {
+ if(scroll_shader.is_null()) {
+ godot::ResourceLoader* loader = godot::ResourceLoader::get_singleton();
+ scroll_shader = loader->load("res://src/Game/Model/scrolling_mat.tres");
+ }
+ return scroll_shader;
+}
+
+//TODO: put this back, 64 is likely needed because of names being added twice
+// (due to 2 loaders operating)
+static constexpr uint32_t MAX_UNIT_TEXTURES = 32;
+
+int32_t ModelSingleton::set_unit_material_texture(int32_t type, godot::String texture_name) {//MAP_TYPE::Values
+ shader_array_index_map_t& map = type==2 ? diffuse_texture_index_map : specular_texture_index_map; //OpenVic::MAP_TYPE::DIFFUSE
+ const shader_array_index_map_t::const_iterator it = map.find(texture_name);
+ if(it != map.end()) {
+ //godot::UtilityFunctions::print(
+ // godot::vformat("cached texture: %s type: %d, index %d", texture_name, type, it->second)
+ //);
+ return it->second; //return the index
+ }
+
+ int32_t index = map.size();
+ map.emplace(texture_name,index);
+ if (map.size() >= MAX_UNIT_TEXTURES) {
+ Logger::error("Number of textures exceeded max supported by a shader!");
+ return 0;
+ }
+
+ // Parameters for the default model shader
+ static const godot::StringName Param_texture_diffuse = "texture_diffuse";
+ //red channel is specular, green and blue are nation colours
+ static const godot::StringName Param_texture_nation_colors_mask = "texture_nation_colors_mask";
+ //static const godot::StringName Param_texture_shadow = "texture_shadow";
+ String shader_param = type==2 ? Param_texture_diffuse : Param_texture_nation_colors_mask; //OpenVic::MAP_TYPE::DIFFUSE
+
+ //godot::UtilityFunctions::print(
+ // godot::vformat("texture: %s type: %d, index %d", texture_name, type, index)
+ //);
+ pushback_shader_array(get_unit_shader(), shader_param, XacLoader::get_model_texture(texture_name));
+
+ return index;
+}
+
+int32_t ModelSingleton::set_scroll_material_texture(godot::String texture_name) {
+ const shader_array_index_map_t::const_iterator it = scroll_index_map.find(texture_name);
+ if(it != scroll_index_map.end()) {
+ return it->second; //return the index
+ }
+
+ scroll_index_map.emplace(texture_name,scroll_index_map.size());
+ if (scroll_index_map.size() >= MAX_UNIT_TEXTURES) {
+ Logger::error("Number of textures exceeded max supported by a shader!");
+ return 0;
+ }
+
+ static const godot::StringName Param_Scroll_texture_diffuse = "scroll_texture_diffuse";
+ static const godot::StringName Param_Scroll_factor = "scroll_factor";
+
+ pushback_shader_array(get_scroll_shader(), Param_Scroll_texture_diffuse, XacLoader::get_model_texture(texture_name));
+
+ float scroll_factor = 0.0;
+ static const godot::StringName tracks = "TexAnim";
+ static const godot::StringName smoke = "Smoke";
+ if(texture_name == tracks) { scroll_factor = 2.5; }
+ else if(texture_name == smoke) { scroll_factor = 0.3; }
+
+ pushback_shader_array(get_scroll_shader(), Param_Scroll_factor, scroll_factor);
+
+ return scroll_index_map[texture_name];
+}
\ 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 ebec7504..f4a470f0 100644
--- a/extension/src/openvic-extension/singletons/ModelSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/ModelSingleton.hpp
@@ -1,12 +1,21 @@
#pragma once
+#include
+#include
+#include
#include
#include
#include
#include
+#include "../utility/XSMLoader.hpp"
+#include "../utility/XACLoader.hpp"
+#include "godot_cpp/classes/shader.hpp"
+#include "godot_cpp/classes/shader_material.hpp"
+#include "godot_cpp/classes/node3d.hpp"
namespace OpenVic {
+
struct BuildingInstance;
class ModelSingleton : public godot::Object {
@@ -31,10 +40,23 @@ 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;
+ using shader_array_index_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::Ref unit_shader;
+ godot::Ref scroll_shader;
+ shader_array_index_map_t diffuse_texture_index_map;
+ shader_array_index_map_t specular_texture_index_map;
+ //shader_array_index_map_t shadow_texture_index_map;
+ shader_array_index_map_t scroll_index_map;
+
godot::Dictionary get_animation_dict(GFX::Actor::Animation const& animation);
godot::Dictionary get_model_dict(GFX::Actor const& actor);
@@ -56,5 +78,14 @@ 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);
+ godot::Error setup_flag_shader();
+
+ godot::Ref get_unit_shader();
+ godot::Ref get_scroll_shader();
+ int32_t set_unit_material_texture(int32_t type, godot::String name); //MAP_TYPE::Values
+ int32_t set_scroll_material_texture(godot::String texture_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..bbff49a3
--- /dev/null
+++ b/extension/src/openvic-extension/utility/XACLoader.cpp
@@ -0,0 +1,712 @@
+#include "XACLoader.hpp"
+//#include "Utilities.hpp"
+
+#include "godot_cpp/classes/array_mesh.hpp"
+#include "godot_cpp/classes/mesh_instance3d.hpp"
+#include "godot_cpp/classes/resource_loader.hpp"
+#include "godot_cpp/classes/resource_preloader.hpp"
+#include "godot_cpp/classes/script.hpp"
+#include "godot_cpp/classes/shader.hpp"
+#include "godot_cpp/classes/shader_material.hpp"
+#include "godot_cpp/classes/skeleton3d.hpp"
+
+#include "godot_cpp/variant/array.hpp"
+#include "godot_cpp/variant/dictionary.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_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/variant.hpp"
+
+#include "openvic-simulation/utility/Logger.hpp"
+
+using namespace::OpenVic;
+using namespace::godot;
+
+bool XacLoader::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;
+}
+
+bool XacLoader::read_xac_metadata(godot::Ref const& file, xac_metadata_v2_t& 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, true);
+ return ret;
+}
+
+bool XacLoader::read_node_data(godot::Ref const& file, node_data_t& node_data) {
+ bool ret = read_struct(file, node_data.packed);
+ ret &= read_string(file, node_data.name);
+ return ret;
+}
+
+bool XacLoader::read_node_hierarchy(godot::Ref const& file, node_hierarchy_t& hierarchy) {
+ bool ret = read_struct(file, hierarchy.packed);
+ for(int32_t i=0; i const& file, material_totals_t& totals) {
+ return read_struct(file, totals);
+}
+
+bool XacLoader::read_layer(godot::Ref const& file, material_layer_t& layer) {
+ bool ret = true;
+ ret &= read_struct(file, layer.packed);
+ ret &= read_string(file, layer.texture, false);
+ //Logger::info(Utilities::godot_to_std_string(layer.texture));
+ return ret;
+}
+
+bool XacLoader::read_material_definition(godot::Ref const& file, material_definition_t& def, int32_t version) {
+ bool ret = read_struct(file, def.packed);
+ ret &= read_string(file, def.name, false);
+ if (version > 1) { //in version 1, the layers are defined in separate chunks of type 0x4
+ for(uint32_t i=0; i const& file, vertices_attribute_t& attribute, int32_t vertices_count) {
+ bool ret = read_struct(file, attribute.packed);
+ ret &= read_struct_array(file, attribute.data, vertices_count*attribute.packed.attribute_size);
+ return ret;
+}
+
+bool XacLoader::read_submesh(godot::Ref const& file, submesh_t& 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;
+}
+
+bool XacLoader::read_mesh(godot::Ref const& file, mesh_t& mesh) {
+ bool ret = read_struct(file, mesh.packed);
+ for(uint32_t i=0; i const& file, skinning_t& 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_t const& 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;
+}
+
+bool XacLoader::read_node_chunk(godot::Ref const& file, node_chunk_t& node) {
+ bool ret = read_struct(file, node.packed);
+ ret &= read_string(file, node.name);
+ //Logger::info(Utilities::godot_to_std_string(node.name));
+ return ret;
+}
+
+/*
+====================================
+Skeleton helper functions
+
+====================================
+*/
+
+//TODO: Verify
+static godot::Transform3D make_transform(vec3d_t const& position, quat_v1_t const& quaternion, vec3d_t const& scale) {
+ godot::Transform3D transform = godot::Transform3D();
+
+ godot::Basis basis = godot::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?
+godot::Skeleton3D* XacLoader::build_armature_hierarchy(node_hierarchy_t const& hierarchy) {
+ static const godot::StringName skeleton_name = "skeleton";
+ godot::Skeleton3D* skeleton = memnew(godot::Skeleton3D);
+ skeleton->set_name(skeleton_name);
+
+ uint32_t current_id = 0;
+
+ for(node_data_t const& node : hierarchy.node_data) {
+ skeleton->add_bone(node.name);
+ skeleton->set_bone_parent(current_id, node.packed.parent_node_id);
+
+ godot::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;
+}
+
+godot::Skeleton3D* XacLoader::build_armature_nodes(std::vector const& nodes) {
+ static const godot::StringName skeleton_name = "skeleton";
+ godot::Skeleton3D* skeleton = memnew(godot::Skeleton3D);
+ skeleton->set_name(skeleton_name);
+
+ uint32_t current_id = 0;
+
+ for(node_chunk_t const& node : nodes) {
+ skeleton->add_bone(node.name);
+ skeleton->set_bone_parent(current_id, node.packed.parent_node_id);
+
+ godot::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
+
+====================================
+*/
+
+//TODO: remove if unnecessary
+/*static void set_model_texture_set_texture(godot::String const& layer_name, godot::String* name) {
+ if (name->is_empty()) {
+ *name = layer_name;
+ }
+ else {
+ Logger::error("Multiple diffuse layers in material: ",
+ Utilities::godot_to_std_string(*name),
+ " and ",
+ Utilities::godot_to_std_string(layer_name)
+ );
+ }
+ return;
+}*/
+
+XacLoader::model_texture_set XacLoader::get_model_textures(material_definition_t const& material) {
+ static const godot::StringName Texture_skip_nospec = "nospec";
+ static const godot::StringName Texture_skip_flag = "unionjacksquare";
+ static const godot::StringName Texture_skip_diff = "test256texture";
+
+ model_texture_set texture_set;
+
+ for(material_layer_t const& layer : material.layers) {
+ if (layer.texture == Texture_skip_diff || layer.texture == Texture_skip_flag) { //|| layer.texture == Texture_skip_nospec
+ continue;
+ }
+ //Get the texture names
+ switch(static_cast(layer.packed.map_type)) {
+ case MAP_TYPE::DIFFUSE:
+ texture_set.diffuse_name = layer.texture;
+ break;
+ case MAP_TYPE::SPECULAR:
+ texture_set.specular_name = layer.texture;
+ break;
+ case MAP_TYPE::NORMAL:
+ texture_set.normal_name = layer.texture;
+ break;
+ case MAP_TYPE::SHADOW:
+ /*godot::UtilityFunctions::print(
+ godot::vformat("Shadow layer: %s", layer.texture)
+ );*/
+ break;
+ default:
+ godot::UtilityFunctions::print(
+ godot::vformat("Unknown layer type: %x, texture: %s", layer.packed.map_type, layer.texture)
+ );
+ break;
+ }
+ }
+ if(texture_set.specular_name.is_empty()) { texture_set.specular_name = Texture_skip_nospec; }
+ return texture_set;
+}
+
+//unit colours not working, likely due to improper setup of the unit script
+std::vector XacLoader::build_materials(std::vector const& materials) {
+
+ // Scrolling textures (smoke, tank tracks)
+ static const godot::StringName tracks = "TexAnim";
+ static const godot::StringName smoke = "Smoke";
+
+ //General
+ godot::ResourceLoader* loader = godot::ResourceLoader::get_singleton();
+ std::vector mappings;
+ ModelSingleton* model_singleton = ModelSingleton::get_singleton();
+
+
+ for(material_definition_t const& mat : materials) {
+ model_texture_set texture_names = get_model_textures(mat);
+ material_mapping mapping;
+
+ // *** Determine the correct material to use, and set it up ***
+ // Note all 3 of these materials correspond to different compile targets of avatar.fx
+ // it has techniques for TexAnim (tracks), Smoke, Shadow, Flag, Standard (we combine TexAnim+smoke, shadow+standard),
+ // in the original shader, shadow = just sample the color, smoke and flag share speed properties
+
+ //flag TODO: perhaps flag should be determined by hard-coding so that other models can have a normal texture?
+ //There shouldn't be a specular texture
+ if (!texture_names.normal_name.is_empty() && texture_names.diffuse_name.is_empty()) {
+ static const godot::StringName Param_texture_normal = "texture_normal";
+ static const godot::Ref flag_shader = loader->load("res://src/Game/Model/flag_mat.tres");
+ flag_shader->set_shader_parameter(Param_texture_normal, get_model_texture(texture_names.normal_name));
+ mapping.godot_material = flag_shader;
+ }
+ //Scrolling texture
+ else if (!texture_names.diffuse_name.is_empty() && (mat.name == tracks || mat.name == smoke)) {
+ mapping.scroll_index = model_singleton->set_scroll_material_texture(texture_names.diffuse_name);
+ mapping.godot_material = model_singleton->get_scroll_shader();
+ }
+ //standard material (diffuse optionally with a specular/unit colours)
+ else if(!texture_names.diffuse_name.is_empty()){
+ mapping.diffuse_texture_index = model_singleton->set_unit_material_texture(2, texture_names.diffuse_name); //MAP_TYPE::DIFFUSE
+ mapping.specular_texture_index = model_singleton->set_unit_material_texture(3, texture_names.specular_name); //MAP_TYPE::SPECULAR
+ mapping.godot_material = model_singleton->get_unit_shader();
+ }
+
+ else {
+ godot::UtilityFunctions::push_warning(
+ godot::vformat("Material %s did not have a diffuse texture! Skipping", mat.name)
+ );
+ }
+
+ 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
+
+template
+struct byte_array_wrapper : std::span {
+ byte_array_wrapper() = default;
+ byte_array_wrapper(std::vector const& source) :
+ std::span(reinterpret_cast(source.data()), source.size() / sizeof(T)) {}
+};
+
+godot::MeshInstance3D* XacLoader::build_mesh(mesh_t const& mesh_chunk, skinning_t* skin, std::vector const& materials) {
+ static const uint32_t EXTRA_CULL_MARGIN = 2;
+
+ godot::MeshInstance3D* mesh_inst = memnew(godot::MeshInstance3D);
+ mesh_inst->set_extra_cull_margin(EXTRA_CULL_MARGIN);
+
+ godot::Ref mesh = godot::Ref();
+ mesh.instantiate();
+
+ byte_array_wrapper verts;
+ byte_array_wrapper normals;
+ byte_array_wrapper tangents;
+ uint32_t uvs_read = 0;
+ byte_array_wrapper uv1;
+ byte_array_wrapper uv2;
+ byte_array_wrapper influence_range_indices;
+
+ for(vertices_attribute_t const& attribute : mesh_chunk.vertices_attributes) {
+ switch(attribute.packed.type) {
+ case ATTRIBUTE::POSITION:
+ verts = attribute.data;
+ break;
+ case ATTRIBUTE::NORMAL:
+ normals = attribute.data;
+ break;
+ case ATTRIBUTE::TANGENT:
+ tangents = attribute.data;
+ break;
+ case ATTRIBUTE::UV:
+ if (uvs_read == 0) {
+ uv1 = attribute.data;
+ }
+ else if (uvs_read == 1) {
+ uv2 = 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 = attribute.data;
+ break;
+ //for now, ignore color data
+ case ATTRIBUTE::COL_32:
+ case ATTRIBUTE::COL_128:
+ default:
+ break;
+ }
+ }
+
+ //if (skin != nullptr) Logger::info("skin infdat size: ", skin->influence_data.size(), " ", skin->packed.influences_count);
+ //if (skin != nullptr) Logger::info("skin infran size: ", skin->influence_ranges.size(), " ", mesh_chunk.packed.influence_ranges_count);
+ //else Logger::info("no skinning chunk");
+
+
+ uint32_t vert_total = 0;
+ static const godot::StringName key_diffuse = "tex_index_diffuse";
+ static const godot::StringName key_specular = "tex_index_specular";
+ static const godot::StringName key_scroll = "scroll_tex_index_diffuse";
+
+ //for now we treat a submesh as a godot mesh surface
+ for(submesh_t const& submesh : mesh_chunk.submeshes) {
+ godot::Array arrs; //surface vertex data arrays
+ ERR_FAIL_COND_V(arrs.resize(godot::Mesh::ARRAY_MAX) != godot::OK, {});
+
+ godot::PackedVector3Array verts_submesh = {};
+ godot::PackedVector3Array normals_submesh = {};
+ godot::PackedFloat32Array tangents_submesh = {};
+ godot::PackedVector2Array uv1_submesh = {};
+ godot::PackedVector2Array uv2_submesh = {};
+ godot::PackedInt32Array bone_ids = {};
+ godot::PackedFloat32Array weights = {};
+ //godot uses a fixed 4 bones influencing a vertex, so size the array accordingly
+ //TODO: should this actually be verts.size()?
+ bone_ids.resize(submesh.relative_indices.size()*4);
+ weights.resize(submesh.relative_indices.size()*4);
+
+ //1 vertex is in the surface per relative index
+ for(uint32_t const& rel_ind : submesh.relative_indices) {
+ uint32_t index = rel_ind + vert_total;
+
+ verts_submesh.push_back(vec3d_to_godot(verts[index], true));
+ if (normals.size() > index) {
+ normals_submesh.push_back( vec3d_to_godot(normals[index],true));
+ }
+
+ //TODO: verify if we need to invert the x for each tangent
+ if (tangents.size() > index) {
+ vec4d_t const& tan = tangents[index];
+ tangents_submesh.push_back(-tan.x);
+ tangents_submesh.push_back(tan.y);
+ tangents_submesh.push_back(tan.z);
+ tangents_submesh.push_back(tan.w);
+ }
+
+ 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]));
+ }
+
+ if (skin != nullptr && influence_range_indices.size() > index) {
+ uint32_t const& influence_range_ind = influence_range_indices[index];
+ influence_range_t const& range = skin->influence_ranges[influence_range_ind];
+
+ for(uint32_t i = 0; i < godot::Math::min(range.influences_count,4); i++) {
+ influence_data_t const& inf_data = skin->influence_data[range.first_influence_index + i];
+ bone_ids[rel_ind*4 + i] = inf_data.bone_id;
+ weights[rel_ind*4 + i] = inf_data.weight;
+ }
+ }
+ }
+
+ if (!verts_submesh.is_empty()) {
+ arrs[godot::Mesh::ARRAY_VERTEX] = std::move(verts_submesh);
+ }
+ if (!normals_submesh.is_empty()) {
+ arrs[godot::Mesh::ARRAY_NORMAL] = std::move(normals_submesh);
+ }
+ if (!tangents_submesh.is_empty()) {
+ arrs[godot::Mesh::ARRAY_TANGENT] = std::move(tangents_submesh);
+ }
+ if (!uv1_submesh.is_empty()) {
+ arrs[godot::Mesh::ARRAY_TEX_UV] = std::move(uv1_submesh);
+ }
+ if (!uv2_submesh.is_empty()) {
+ arrs[godot::Mesh::ARRAY_TEX_UV2] = std::move(uv2_submesh);
+ }
+ if (skin != nullptr && influence_range_indices.size() > 0) {
+ arrs[godot::Mesh::ARRAY_BONES] = std::move(bone_ids);
+ arrs[godot::Mesh::ARRAY_WEIGHTS] = std::move(weights);
+ }
+ mesh->add_surface_from_arrays(godot::Mesh::PRIMITIVE_TRIANGLES, arrs);
+
+ //setup materials for the surface
+ material_mapping const& 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.specular_texture_index);
+ }
+ if (material_mapping.scroll_index != -1) {
+ mesh_inst->set_instance_shader_parameter(key_scroll, material_mapping.scroll_index);
+ }
+
+ vert_total += submesh.relative_indices.size();
+ }
+
+ mesh_inst->set_mesh(mesh);
+
+ return mesh_inst;
+}
+
+/*
+====================================
+Main loading function
+
+====================================
+*/
+
+godot::Node3D* XacLoader::_load_xac_model(godot::Ref const& file) {
+ bool res = read_xac_header(file);
+
+ // TODO: gets the unit script if it has the specular/unit colours texture or
+
+ /*is_unit = is_unit or (
+ # Needed for animations
+ idle_key in model_dict or move_key in model_dict or attack_key in model_dict
+ # Currently needs UnitModel's attach_model helper function
+ or attachments_key in model_dict
+ )*/
+
+ godot::ResourceLoader* loader = godot::ResourceLoader::get_singleton();
+
+ //TODO: start with the assumption that all units will use this, then reduce later
+ static const godot::Ref unit_script = loader->load("res://src/Game/Model/UnitModel.gd");
+
+ godot::Node3D* base = memnew(godot::Node3D);
+
+ if (!res) {
+ return base;
+ }
+
+ xac_metadata_v2_t metadata;
+
+ bool hierarchy_read = false;
+ node_hierarchy_t hierarchy;
+ std::vector nodes; //other way of making a bone hierarchy
+
+ material_totals_t material_totals = {};
+ std::vector materials;
+
+ std::vector meshes;
+ std::vector skinnings;
+
+ bool log_all = false;
+
+ //0xA unknown (ex. Spanish_Helmet1.xac), only 2 bytes long...
+ //0x6 unknown (ex. ...)
+ //0x8 junk data (ie. old versions of the file duplicated in append mode)
+ // (ex. Port_Empty.xac)
+ //0xC morphtargets (not used in vic2)
+
+ while (!file->eof_reached()) {
+ chunk_header_t header = {};
+ if (!read_chunk_header(file, header)) {
+ break;
+ }
+
+ if (log_all) {
+ godot::UtilityFunctions::print(
+ godot::vformat("XAC chunk: type = %x, length = %x, version = %d at %x", header.type, header.length, header.version, file->get_position())
+ );
+ }
+
+ 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 == 3) {
+ node_chunk_t node;
+ read_node_chunk(file, node);
+ nodes.push_back(node);
+ }
+ else if (header.type == 0x1 && header.version == 1) {
+ mesh_t mesh;
+ read_mesh(file, mesh);
+ meshes.push_back(mesh);
+ }
+ else if (header.type == 0x2) {
+ skinning_t skin;
+ read_skinning(file, skin, meshes, header.version);
+ skinnings.push_back(skin);
+ }
+ else if (header.type == 0x3) {
+ material_definition_t mat;
+ read_material_definition(file, mat, header.version);
+ materials.push_back(mat);
+ }
+ else if (header.type == 0x4 && header.version == 2) {
+ material_layer_t layer;
+ read_layer(file, layer);
+ if (layer.packed.material_id < materials.size()) {
+ materials[layer.packed.material_id].layers.push_back(layer);
+ }
+ else{
+ Logger::error("No material of id ", layer.packed.material_id, " to attach layer to");
+ }
+ }
+ else if (header.type == 0xD && header.version == 1) {
+ read_material_totals(file, material_totals);
+ }
+ else if (header.type == 0x8 || header.type == 0xA) { //skip junk data and whatever 0xA is...
+ if (header.length + file->get_position() < file->get_length()) {
+ file->get_buffer(header.length);
+ }
+ else {
+ res = false;
+ break;
+ }
+ }
+ else {
+ godot::UtilityFunctions::print(
+ godot::vformat("Unsupported XAC chunk: type = %x, length = %x, version = %d at %x", header.type, header.length, header.version, file->get_position())
+ );
+ log_all = true;
+ // Skip unsupported chunks by using get_buffer, make sure this doesn't break anything, since chunk length can be wrong
+ if (header.length + file->get_position() < file->get_length()) {
+ file->get_buffer(header.length);
+ }
+ else {
+ res = false;
+ break;
+ }
+ }
+ }
+
+ base->set_name(metadata.original_file_name.get_file().get_basename());
+ //TODO: don't set the unit script if we don't have one of:
+ // -specular (unit colours) texture
+ // -skeleton with animation
+ base->set_script(unit_script);
+
+ //Setup skeleton
+ //TODO: if no skinning chunk, skip
+ godot::Skeleton3D* skeleton = nullptr;
+ if(!skinnings.empty()) {
+ 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 const& material_mappings = build_materials(materials);
+
+ //setup mesh
+ for(mesh_t const& mesh : meshes) {
+ if (mesh.packed.is_collision_mesh) { continue; } //we'll use our own collision meshes where needed
+
+ skinning_t* mesh_skin = nullptr;
+ for(uint32_t i=0; i < skinnings.size(); i++) {
+ skinning_t& skin = skinnings[i];
+ if (skin.node_id == mesh.packed.node_id && skin.packed.is_for_collision == mesh.packed.is_collision_mesh) {
+ mesh_skin = &skin;
+ break;
+ }
+ }
+
+ if (skeleton != nullptr && mesh_skin == nullptr) { continue; }
+
+ godot::MeshInstance3D* mesh_inst = build_mesh(mesh, mesh_skin, material_mappings);
+ base->add_child(mesh_inst);
+ mesh_inst->set_owner(base);
+
+ if (skeleton != nullptr) {
+ 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);
+ }
+ }
+ }
+ //base->call("unit_init", true);
+
+ return base;
+}
\ No newline at end of file
diff --git a/extension/src/openvic-extension/utility/XACLoader.hpp b/extension/src/openvic-extension/utility/XACLoader.hpp
new file mode 100644
index 00000000..67eabe2e
--- /dev/null
+++ b/extension/src/openvic-extension/utility/XACLoader.hpp
@@ -0,0 +1,326 @@
+#pragma once
+#include
+#include
+#include
+
+#include "XACUtilities.hpp"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "godot_cpp/classes/mesh_instance3d.hpp"
+#include "godot_cpp/classes/skeleton3d.hpp"
+#include
+#include
+#include
+
+
+namespace OpenVic {
+
+ //needed for material loading functions, which use modelSingleton to track state between calls
+ struct ModelSingleton;
+
+ /*enum struct MAP_TYPE {
+ DIFFUSE = 2,
+ SPECULAR,
+ SHADOW,
+ NORMAL
+ };*/
+ /*struct MAP_TYPE {
+ enum Values : int32_t {
+ DIFFUSE = 2,
+ SPECULAR,
+ SHADOW,
+ NORMAL
+ };
+ };*/
+
+ class XacLoader {
+ static constexpr uint32_t XAC_FORMAT_SPECIFIER = ' CAX'; // Order reversed due to little endian
+ static constexpr uint8_t XAC_VERSION_MAJOR = 1, XAC_VERSION_MINOR = 0;
+
+ //TODO: How do we get this enum to work both here and in modelSingleton?
+ //public:
+ enum class MAP_TYPE {
+ DIFFUSE = 2,
+ SPECULAR,
+ SHADOW,
+ NORMAL
+ };
+ /*struct MAP_TYPE {
+ enum Values : int32_t {
+ DIFFUSE = 2,
+ SPECULAR,
+ SHADOW,
+ NORMAL
+ };
+ };
+ private:*/
+
+ #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_t {
+ 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_t { //v1
+ int32_t node_count;
+ int32_t root_node_count; //nodes with parent_id == -1
+ };
+
+ struct node_data_pack_t { //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_t { //v1
+ int32_t total_materials_count;
+ int32_t standard_materials_count;
+ int32_t fx_materials_count;
+ };
+
+ struct material_definition_pack_t {
+ 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; //in v1, unknown, but used
+ uint8_t layers_count; //in v1, unknown, but used
+ };
+
+ struct material_layer_pack_t { //also chunk 0x4, v2
+ float amount;
+ vec2d_t uv_offset;
+ vec2d_t uv_tiling;
+ float rotation_in_radians;
+ int16_t material_id;
+ uint8_t map_type; // (1-5) enum MAP_TYPE
+ uint8_t unused;
+ };
+
+ struct mesh_pack_t {
+ 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 ATTRIBUTE {
+ enum Values : int32_t {
+ POSITION,
+ NORMAL,
+ TANGENT,
+ UV,
+ COL_32,
+ INFLUENCE_RANGE,
+ COL_128
+ };
+ };
+
+ struct vertices_attribute_pack_t {
+ 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_t {
+ int32_t indices_count;
+ int32_t vertices_count;
+ int32_t material_id;
+ int32_t bones_count;
+ };
+
+ struct skinning_pack_t {
+ int32_t influences_count;
+ uint8_t is_for_collision; //bool
+ uint8_t pad[3];
+ };
+
+ struct influence_data_t {
+ float weight;
+ int16_t bone_id;
+ int16_t pad;
+ };
+
+ struct influence_range_t {
+ 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, v3
+ struct node_chunk_pack_t {
+ quat_v1_t rotation;
+ quat_v1_t scale_rotation;
+ vec3d_t position;
+ vec3d_t scale;
+ vec3d_t unused; //-1, -1, -1
+ int32_t unknown; //-1 (0xFFFF)
+ int32_t parent_node_id;
+ float uncertain[17]; //likely a matrix44 + fimportancefactor
+ };
+
+ /*
+ 0x6, v? unknown
+ 12x int32?
+ 09x float?
+ */
+
+ #pragma pack(pop)
+
+ struct xac_metadata_v2_t {
+ xac_metadata_v2_pack_t packed = {};
+ godot::String source_app;
+ godot::String original_file_name;
+ godot::String export_date;
+ godot::String actor_name;
+ };
+
+ struct node_data_t { //v1
+ node_data_pack_t packed = {};
+ godot::String name;
+ };
+
+ struct node_hierarchy_t { //v1
+ node_hierarchy_pack_t packed = {};
+ std::vector node_data;
+ };
+
+ struct material_layer_t {
+ material_layer_pack_t packed = {};
+ godot::String texture;
+ };
+
+ struct material_definition_t {
+ material_definition_pack_t packed = {};
+ godot::String name;
+ std::vector layers;
+ };
+
+ struct vertices_attribute_t {
+ vertices_attribute_pack_t packed = {};
+ std::vector data;
+ };
+
+ struct submesh_t {
+ submesh_pack_t packed = {};
+ std::vector relative_indices;
+ std::vector bone_ids;
+ };
+
+ struct mesh_t {
+ mesh_pack_t packed = {};
+ std::vector vertices_attributes;
+ std::vector submeshes;
+ };
+
+ struct skinning_t {
+ int32_t node_id = 0;
+ int32_t local_bones_count = -1; //v3 only
+ skinning_pack_t packed = {};
+ std::vector influence_data;
+ std::vector influence_ranges;
+ };
+
+ struct node_chunk_t {
+ node_chunk_pack_t packed = {};
+ godot::String name;
+ };
+
+ //Dataloading functions
+ bool read_xac_header(godot::Ref const& file);
+ bool read_xac_metadata(godot::Ref const& file, xac_metadata_v2_t& metadata);
+ bool read_node_data(godot::Ref const& file, node_data_t& node_data);
+ bool read_node_hierarchy(godot::Ref const& file, node_hierarchy_t& hierarchy);
+ bool read_material_totals(godot::Ref const& file, material_totals_t& totals);
+ bool read_layer(godot::Ref const& file, material_layer_t& layer);
+ bool read_material_definition(godot::Ref const& file, material_definition_t& def, int32_t version);
+ bool read_vertices_attribute(godot::Ref const& file, vertices_attribute_t& attribute, int32_t vertices_count);
+ bool read_submesh(godot::Ref const& file, submesh_t& submesh);
+ bool read_mesh(godot::Ref const& file, mesh_t& mesh);
+ bool read_skinning(godot::Ref const& file, skinning_t& skin, std::vector const& meshes, int32_t version);
+ bool read_node_chunk(godot::Ref const& file, node_chunk_t& node);
+
+ //Xac -> godot conversion functions
+ struct material_mapping {
+ //-1 means unused
+ godot::Ref godot_material;
+ int32_t diffuse_texture_index = -1;
+ int32_t specular_texture_index = -1;
+ //int32_t shadow_texture_index = -1;
+ int32_t scroll_index = -1;
+ };
+
+ struct model_texture_set {
+ godot::String diffuse_name;
+ godot::String specular_name;
+ godot::String normal_name;
+ //godot::String shadow_name;
+ };
+
+ godot::Skeleton3D* build_armature_hierarchy(node_hierarchy_t const& hierarchy);
+ godot::Skeleton3D* build_armature_nodes(std::vector const& nodes);
+
+ model_texture_set get_model_textures(material_definition_t const& material);
+ std::vector build_materials(std::vector const& materials);
+ godot::MeshInstance3D* build_mesh(mesh_t const& mesh_chunk, skinning_t* skin, std::vector const& materials);
+
+ public:
+ //godot::Ref get_texture(godot::String name);
+ static godot::Ref get_model_texture(godot::String name) {
+ /*godot::UtilityFunctions::print(
+ godot::vformat("texture: %s", name)
+ );*/
+ AssetManager* asset_manager = AssetManager::get_singleton();
+ //ERR_FAIL_NULL_V(asset_manager, {});
+ static const godot::StringName Textures_path = "gfx/anims/%s.dds";
+ return asset_manager->get_texture(godot::vformat(Textures_path, name));
+ }
+ godot::Node3D* _load_xac_model(godot::Ref const& file);
+ };
+}
\ 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..488f372c
--- /dev/null
+++ b/extension/src/openvic-extension/utility/XACUtilities.hpp
@@ -0,0 +1,218 @@
+#pragma once
+#include
+#include
+#include
+//#include "openvic-simulation/utility/Logger.hpp"
+//#include "openvic-extension/utility/Utilities.hpp"
+#include
+#include
+
+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)
+
+ static bool read_string(godot::Ref const& file, godot::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) {
+ godot::UtilityFunctions::print(vformat("before %s", str));
+ }
+ if (replace_chars) {
+ str = str.replace(":", "_");
+ str = str.replace("\\", "_");
+ str = str.replace("/", "_");
+ }
+ if (log) {
+ godot::UtilityFunctions::print(vformat("after %s", str));
+ }
+ return true;
+ }
+
+ template
+ static bool read_struct(godot::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(godot::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(godot::Ref const& file, chunk_header_t& header) {
+ //ERR_FAIL_COND_V(!read_struct(file, header), false);
+ bool res = read_struct(file, header);
+
+ //godot::UtilityFunctions::print(
+ // vformat("XAC/XSM chunk: type = %x, length = %x, version = %d, successful? %s", header.type, header.length, header.version, res)
+ //);
+
+ return res;
+ }
+
+ static godot::Vector2 vec2d_to_godot(vec2d_t const& vec2_in) {
+ godot::Vector2 vec2_out = {
+ vec2_in.x,
+ vec2_in.y
+ };
+ return vec2_out;
+ }
+
+ static godot::Vector3 vec3d_to_godot(vec3d_t const& vec3_in, bool is_position = false) {
+ godot::Vector3 vec3_out = {
+ vec3_in.x,
+ vec3_in.y,
+ vec3_in.z
+ };
+ if (is_position) {
+ vec3_out.x *= -1;
+ }
+ return vec3_out;
+ }
+
+ static godot::Vector4 vec4d_to_godot(vec4d_t const& vec4_in) {
+ godot::Vector4 vec4_out = {
+ vec4_in.x,
+ vec4_in.y,
+ vec4_in.z,
+ vec4_in.w
+ };
+ return vec4_out;
+ }
+
+ static godot::Quaternion quat_v1_to_godot(quat_v1_t const& quat_in) {
+ godot::Quaternion quat_out = godot::Quaternion(
+ quat_in.x,
+ -quat_in.y,
+ -quat_in.z,
+ quat_in.w
+ );
+ if (!quat_out.is_normalized()) {
+ quat_out = godot::Quaternion();
+ }
+ return quat_out;
+ }
+
+ static godot::Quaternion quat_v2_to_godot(quat_v2_t const& quat_in) {
+ static const float scale = 32767;
+ godot::Quaternion quat_out = godot::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()) {
+ quat_out = godot::Quaternion();
+ }
+
+ return quat_out;
+ }
+
+ static godot::Color color_32_to_godot(color_32_t const& color_in) {
+ static const float scale = 256;
+ godot::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 godot::Color color_128_to_godot(color_128_t const& color_in) {
+ static const double scale = 2147483647;
+ godot::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 godot::Color vec4d_to_godot_color(vec4d_t const& color_in) {
+ godot::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..6715f529
--- /dev/null
+++ b/extension/src/openvic-extension/utility/XSMLoader.hpp
@@ -0,0 +1,427 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "XACUtilities.hpp"
+
+namespace OpenVic {
+ static constexpr uint32_t XSM_FORMAT_SPECIFIER = ' MSX'; /* Order reversed due to little 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_t {
+ float unused;
+ float max_acceptable_error;
+ };
+
+ struct xsm_metadata_pack_t {
+ int32_t fps;
+ uint8_t exporter_version_major; //these 3 properties are uncertain
+ uint8_t exporter_version_minor;
+ uint16_t pad;
+ };
+
+ struct position_key_t {
+ vec3d_t position;
+ float time;
+ };
+
+ struct rotation_key_v2_t {
+ quat_v2_t rotation;
+ float time;
+ };
+
+ struct rotation_key_v1_t {
+ quat_v1_t rotation;
+ float time;
+ };
+
+ struct scale_key_t {
+ vec3d_t scale;
+ float time;
+ };
+
+ //the quaternions come at the start of the skeletal submotion
+ struct submotion_rot_v2_pack_t {
+ 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_t {
+ 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_t {
+ 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_t {
+ 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)
+
+ struct xsm_metadata_t {
+ bool has_v2_data = false;
+ xsm_metadata_v2_pack_extra_t v2_data = {};
+ xsm_metadata_pack_t packed = {};
+ godot::String source_app;
+ godot::String original_file_name;
+ godot::String export_date;
+ godot::String motion_name;
+ };
+
+ struct rotation_key_t {
+ godot::Quaternion rotation;
+ float time;
+ };
+
+ struct skeletal_submotion_t {
+ godot::Quaternion pose_rotation;
+ godot::Quaternion bind_pose_rotation;
+ godot::Quaternion pose_scale_rotation;
+ godot::Quaternion bind_pose_scale_rotation;
+ skeletal_submotion_pack_t packed = {};
+ godot::String node_name;
+ std::vector position_keys;
+ std::vector rotation_keys;
+ std::vector scale_keys;
+ std::vector scale_rotation_keys;
+ };
+
+ struct bone_animation_t {
+ int32_t submotion_count = 0;
+ std::vector submotions;
+ };
+
+ /*
+ 0xC8, v1 chunk documentation: (Let's call it node_submotion)
+ 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
+ */
+
+ struct node_submotion_t {
+ godot::Quaternion pose_rotation;
+ godot::Quaternion bind_pose_rotation;
+ godot::Quaternion pose_scale_rotation;
+ godot::Quaternion bind_pose_scale_rotation;
+ node_submotion_pack_t packed = {};
+ godot::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(godot::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, godot::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, godot::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(godot::Ref const& file, xsm_metadata_t& 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(godot::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_t const& 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_t const& key : rot_keys_v2) {
+ keys_out->push_back({quat_v2_to_godot(key.rotation), key.time});
+ }
+ }
+
+ return res;
+ }
+
+ static bool read_skeletal_submotion(godot::Ref const& file, skeletal_submotion_t& submotion, int32_t version) {
+ bool res = true;
+ if (version == 1) { //float component quats (v1)
+ submotion_rot_v1_pack_t 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_t 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(godot::Ref const& file, bone_animation_t& bone_animation, int32_t version) {
+ bone_animation.submotion_count = file->get_32();
+ bool ret = true;
+
+ for(uint32_t i=0; i const& file, std::vector* submotions) {
+ bool res = true;
+ node_submotion_t submotion;
+
+ submotion_rot_v1_pack_t 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(godot::Ref anim, T const& submotion) {
+ static const godot::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
+ godot::String skeleton_path = godot::vformat(SKELETON_NODE_PATH, submotion.node_name);
+
+ int32_t pos_id = anim->add_track(godot::Animation::TYPE_POSITION_3D);
+ int32_t rot_id = anim->add_track(godot::Animation::TYPE_ROTATION_3D);
+ int32_t scale_id = anim->add_track(godot::Animation::TYPE_SCALE_3D);
+
+ anim->track_set_path(pos_id, skeleton_path);
+ anim->track_set_path(rot_id, skeleton_path);
+ anim->track_set_path(scale_id, skeleton_path);
+
+ for(position_key_t const& 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_t const& key : submotion.rotation_keys) {
+ anim->rotation_track_insert_key(rot_id, key.time, key.rotation);
+ if (key.time > max_time) {
+ max_time = key.time;
+ }
+ }
+
+ //not needed for vic2 animations, but we can still support it
+ for(scale_key_t const& 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_t const& key : submotion.scale_rotation_keys) {
+ if (key.time > max_time) {
+ max_time = key.time;
+ }
+ }
+
+ //TODO: why is this hack needed to make the animations work?
+ if (anim->track_find_key(pos_id, 0) != -1) {
+ anim->track_remove_key_at_time(pos_id, 0);
+ }
+ if (anim->track_find_key(rot_id, 0) != -1) {
+ anim->track_remove_key_at_time(rot_id, 0);
+ }
+ if (anim->track_find_key(scale_id, 0) != -1) {
+ anim->track_remove_key_at_time(scale_id, 0);
+ }
+ //TODO: if you pay close attention, there's still a small jump along the loop point
+ // though this is visible in vic2 aswell
+
+ //add the "default" positions/rotation/... as keys
+ 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.pose_rotation);
+ anim->scale_track_insert_key(scale_id, 0, 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 godot::Ref _load_xsm_animation(godot::Ref const& file) {
+
+ read_xsm_header(file);
+
+ xsm_metadata_t metadata;
+ bone_animation_t 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 {
+ godot::UtilityFunctions::print(
+ godot::vformat("Unsupported XSM chunk: file: %s, type = %x, length = %x, version = %d at %x", file->get_path(), header.type, header.length, header.version, file->get_position())
+ );
+
+ // Skip unsupported chunks
+ if (header.length + file->get_position() < file->get_length()) {
+ godot::PackedByteArray buf = file->get_buffer(header.length);
+ godot::UtilityFunctions::print(buf);
+ }
+ else {
+ res = false;
+ break;
+ }
+ }
+ }
+
+ float animation_length = 0.0;
+ godot::Ref anim = godot::Ref();
+ anim.instantiate();
+
+ anim->set_step(1.0/metadata.packed.fps);
+ anim->set_loop_mode(godot::Animation::LOOP_LINEAR);
+
+ if (res == false) {
+ return anim; //exit early if reading the chunks in failed
+ }
+
+ for(skeletal_submotion_t const& submotion : bone_anim.submotions) {
+ float submotion_len = add_submotion(anim, submotion);
+ if (submotion_len > animation_length) {
+ animation_length = submotion_len;
+ }
+ }
+
+ for(node_submotion_t const& submotion : node_submotions) {
+ float submotion_len = add_submotion(anim, submotion);
+ if (submotion_len > animation_length) {
+ animation_length = submotion_len;
+ }
+ }
+
+ anim->set_length(animation_length);
+
+ return anim;
+ }
+}
\ No newline at end of file
diff --git a/game/src/Game/GameSession/GameSession.tscn b/game/src/Game/GameSession/GameSession.tscn
index 8d882b99..0b7b68bf 100644
--- a/game/src/Game/GameSession/GameSession.tscn
+++ b/game/src/Game/GameSession/GameSession.tscn
@@ -48,9 +48,15 @@ _game_session_menu = NodePath("UICanvasLayer/UI/GameSessionMenu")
[node name="MapView" parent="." instance=ExtResource("4_xkg5j")]
-[node name="ModelManager" type="Node3D" parent="." node_paths=PackedStringArray("_map_view")]
+[node name="ModelManager" type="Node3D" parent="." node_paths=PackedStringArray("_map_view", "_buildings", "_units")]
script = ExtResource("3_qwk4j")
_map_view = NodePath("../MapView")
+_buildings = NodePath("Buildings")
+_units = NodePath("Units")
+
+[node name="Buildings" type="Node3D" parent="ModelManager"]
+
+[node name="Units" type="Node3D" parent="ModelManager"]
[node name="BillboardManager" type="MultiMeshInstance3D" parent="." node_paths=PackedStringArray("_map_view")]
sorting_offset = 10.0
diff --git a/game/src/Game/GameSession/ModelManager.gd b/game/src/Game/GameSession/ModelManager.gd
index de9de547..91ff0a5c 100644
--- a/game/src/Game/GameSession/ModelManager.gd
+++ b/game/src/Game/GameSession/ModelManager.gd
@@ -2,11 +2,13 @@ class_name ModelManager
extends Node3D
@export var _map_view : MapView
+@export var _buildings : Node3D
+@export var _units : Node3D
const MODEL_SCALE : float = 1.0 / 256.0
func generate_units() -> void:
- XACLoader.setup_flag_shader()
+ ModelSingleton.setup_flag_shader()
for unit : Dictionary in ModelSingleton.get_units():
_generate_unit(unit)
@@ -61,7 +63,7 @@ func _generate_unit(unit_dict : Dictionary) -> void:
model.secondary_colour = unit_dict[secondary_colour_key]
model.tertiary_colour = unit_dict[tertiary_colour_key]
- add_child(model)
+ _units.add_child(model,true)
func generate_buildings() -> void:
for building : Dictionary in ModelSingleton.get_buildings():
@@ -75,12 +77,12 @@ func _generate_building(building_dict : Dictionary) -> void:
var model : Node3D = _generate_model(building_dict[model_key])
if not model:
return
-
model.scale *= MODEL_SCALE
+
model.rotate_y(PI + building_dict.get(rotation_key, 0.0))
model.set_position(_map_view._map_to_world_coords(building_dict[position_key]) + Vector3(0, 0.1 * MODEL_SCALE, 0))
- add_child(model)
+ _buildings.add_child(model,true)
func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : bool = false) -> Node3D:
const file_key : StringName = &"file"
@@ -104,26 +106,39 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b
or attachments_key in model_dict
)
- var model : Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit)
+ var model : Node3D
+ if !is_unit:
+ model = ModelSingleton.get_xac_model(model_dict[file_key])
+ #model = XACLoader.get_xac_model(model_dict[file_key], is_unit)
+ # print(file_key)
+ #ModelSingleton.get_xac_model(model_dict[file_key])
+ else:
+ #model = XACLoader.get_xac_model(model_dict[file_key], is_unit)
+ model = ModelSingleton.get_xac_model(model_dict[file_key])
+ #var model : UnitModel = ModelSingleton.get_xac_model(model_dict[file_key])
+ #var model : Node3D = XACLoader.get_xac_model(model_dict[file_key], is_unit)
+
if not model:
return null
- model.scale *= model_dict[scale_key]
-
+ model.scale *= model_dict[scale_key] #* MODEL_SCALE #TODO: sometimes 0?
+
+ #print("GENERATE MODEL")
if model is UnitModel:
# Animations
var idle_dict : Dictionary = model_dict.get(idle_key, {})
+ var move_dict : Dictionary = model_dict.get(move_key, {})
+ var attack_dict : Dictionary = model_dict.get(attack_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.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.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.scroll_speed_attack = attack_dict[animation_time_key]
# Attachments
@@ -133,7 +148,7 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b
attachment_model.free()
if culture:
- const gun_bone_name : String = "GunNode"
+ const gun_bone_name : StringName = &"GunNode"
if model.has_bone(gun_bone_name):
var gun_dict : Dictionary = ModelSingleton.get_cultural_gun_model(culture)
if gun_dict:
@@ -141,7 +156,7 @@ func _generate_model(model_dict : Dictionary, culture : String = "", is_unit : b
if gun_model and model.attach_model(gun_bone_name, gun_model) != OK:
gun_model.free()
- const helmet_bone_name : String = "HelmetNode"
+ const helmet_bone_name : StringName = &"HelmetNode"
if model.has_bone(helmet_bone_name):
var helmet_dict : Dictionary = ModelSingleton.get_cultural_helmet_model(culture)
if helmet_dict:
diff --git a/game/src/Game/Model/UnitModel.gd b/game/src/Game/Model/UnitModel.gd
index d482c12f..899cf572 100644
--- a/game/src/Game/Model/UnitModel.gd
+++ b/game/src/Game/Model/UnitModel.gd
@@ -107,7 +107,9 @@ const ANIMATION_ATTACK : String = ANIMATION_LIBRARY + "/attack"
for unit : UnitModel in sub_units:
unit.scroll_speed_attack = speed_in
-func unit_init() -> void:
+func unit_init(print:bool = false) -> void:
+ #if print:
+ # print("unit_init called!")
for child : Node in get_children():
if child is MeshInstance3D:
meshes.append(child)
diff --git a/game/src/Game/Model/XACLoader.gd b/game/src/Game/Model/XACLoader.gd
index ad2ea2d6..92a2eafc 100644
--- a/game/src/Game/Model/XACLoader.gd
+++ b/game/src/Game/Model/XACLoader.gd
@@ -354,8 +354,9 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh
if diffuse_name and specular_name:
if normal_name:
push_error("Normal texture present in unit colours material: ", normal_name)
-
- var textures_index_spec : int = added_unit_textures_spec.find(specular_name)
+ var textures_index_diffuse = ModelSingleton.set_unit_material_texture(2,diffuse_name)
+ var textures_index_spec = ModelSingleton.set_unit_material_texture(3,specular_name)
+ """var textures_index_spec : int = added_unit_textures_spec.find(specular_name)
if textures_index_spec < 0:
var unit_colours_mask_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % specular_name)
if unit_colours_mask_texture:
@@ -392,7 +393,8 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh
unit_shader.set_shader_parameter(param_texture_diffuse, diffuse_textures)
else:
push_error("Failed to load diffuse texture: ", diffuse_name)
-
+ """
+
materials.push_back(MaterialDefinition.new(unit_shader, textures_index_diffuse, textures_index_spec))
# Flag (diffuse is unionjacksquare which is ignored)
@@ -414,7 +416,7 @@ static func make_materials(materialDefinitionChunks : Array[MaterialDefinitionCh
push_error("Specular texture present in scrolling material: ", specular_name)
if normal_name:
push_error("Normal texture present in scrolling material: ", normal_name)
-
+ #ModelSingleton. TODO
var scroll_textures_index_diffuse : int = added_scrolling_textures_diffuse.find(diffuse_name)
if scroll_textures_index_diffuse < 0:
var diffuse_texture : ImageTexture = AssetManager.get_texture(TEXTURES_PATH % diffuse_name)
diff --git a/game/src/Game/Model/unit_colours.gdshader b/game/src/Game/Model/unit_colours.gdshader
index 2ffc7710..78f148bd 100644
--- a/game/src/Game/Model/unit_colours.gdshader
+++ b/game/src/Game/Model/unit_colours.gdshader
@@ -1,12 +1,14 @@
shader_type spatial;
-render_mode cull_disabled;
+render_mode cull_disabled, depth_prepass_alpha;
//hold all the textures for the units that need this shader to mix in their
//nation colours (mostly generic infantry units)
-uniform sampler2D texture_diffuse[32] : source_color, filter_linear_mipmap, repeat_enable;
-uniform sampler2D texture_nation_colors_mask[32] : source_color, filter_linear_mipmap, repeat_enable;
+//TODO: decrease back to 32
+uniform sampler2D texture_diffuse[64] : source_color, filter_linear_mipmap, repeat_enable;
+uniform sampler2D texture_nation_colors_mask[64] : source_color, filter_linear_mipmap, repeat_enable;
+//uniform sampler2D texture_shadow[32] : source_color, filter_linear_mipmap, repeat_enable;
instance uniform vec3 colour_primary : source_color;
instance uniform vec3 colour_secondary : source_color;
@@ -14,18 +16,24 @@ instance uniform vec3 colour_tertiary : source_color;
//used to access the right textures since different units (with different textures)
//will use this same shader
-instance uniform uint tex_index_diffuse;
-instance uniform uint tex_index_specular;
+instance uniform int tex_index_diffuse;
+instance uniform int tex_index_specular;
+//instance uniform int tex_index_shadow = -1; //0 = none
void fragment() {
vec2 base_uv = UV;
vec4 diffuse_tex = texture(texture_diffuse[tex_index_diffuse], base_uv);
vec4 nation_colours_tex = texture(texture_nation_colors_mask[tex_index_specular], base_uv);
-
+
//set colours to either be white (1,1,1) or the nation colour based on the mask
vec3 primary_col = mix(vec3(1.0, 1.0, 1.0), colour_primary, nation_colours_tex.g);
vec3 secondary_col = mix(vec3(1.0, 1.0, 1.0), colour_secondary, nation_colours_tex.b);
vec3 tertiary_col = mix(vec3(1.0, 1.0, 1.0), colour_tertiary, nation_colours_tex.r);
-
+
ALBEDO = diffuse_tex.rgb * primary_col * secondary_col * tertiary_col;
+ ALPHA = diffuse_tex.a;
+ /*if(tex_index_shadow != -1){
+ vec4 shadow_tex = texture(texture_shadow[tex_index_shadow], base_uv);
+ ALBEDO = shadow_tex.rgb;
+ }*/
}