Merge pull request #43921 from RevoluPowered/fbx-update-plugin
[FBX] general purpose fixes 🎄
This commit is contained in:
commit
b5e8b48bb7
20 changed files with 513 additions and 252 deletions
|
@ -33,8 +33,8 @@
|
|||
#include "fbx_node.h"
|
||||
#include "import_state.h"
|
||||
|
||||
Ref<FBXNode> FBXBone::get_link(const ImportState &state) const {
|
||||
print_verbose("bone name: " + bone_name);
|
||||
Ref<FBXNode> FBXSkinDeformer::get_link(const ImportState &state) const {
|
||||
print_verbose("bone name: " + bone->bone_name);
|
||||
|
||||
// safe for when deformers must be polyfilled when skin has different count of binds to bones in the scene ;)
|
||||
if (!cluster) {
|
||||
|
@ -54,53 +54,3 @@ Ref<FBXNode> FBXBone::get_link(const ImportState &state) const {
|
|||
// the node in space this is for, like if it's FOR a target.
|
||||
return link_node;
|
||||
}
|
||||
|
||||
/* right now we just get single skin working and we can patch in the multiple tomorrow - per skin not per bone. */
|
||||
// this will work for multiple meshes :) awesomeness.
|
||||
// okay so these formula's are complex and need proper understanding of
|
||||
// shear, pivots, geometric pivots, pre rotation and post rotation
|
||||
// additionally DO NOT EDIT THIS if your blender file isn't working.
|
||||
// Contact RevoluPowered Gordon MacPherson if you are contemplating making edits to this.
|
||||
Transform FBXBone::get_vertex_skin_xform(const ImportState &state, Transform mesh_global_position, bool &r_valid_pose) {
|
||||
r_valid_pose = false;
|
||||
print_verbose("get_vertex_skin_xform: " + bone_name);
|
||||
|
||||
// safe to do, this means we have 'remove unused deformer' checked.
|
||||
if (!cluster) {
|
||||
print_verbose("bone [" + itos(bone_id) + "] " + bone_name + ": has no skin offset poly-filling the skin to make rasterizer happy with unused deformers not being skinned");
|
||||
r_valid_pose = true;
|
||||
return Transform();
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(cluster == nullptr, Transform(), "[serious] unable to resolve the fbx cluster for this bone " + bone_name);
|
||||
// these methods will ONLY work for Maya.
|
||||
if (cluster->TransformAssociateModelValid()) {
|
||||
//print_error("additive skinning in use");
|
||||
Transform associate_global_init_position = cluster->TransformAssociateModel();
|
||||
Transform associate_global_current_position = Transform();
|
||||
Transform reference_global_init_position = cluster->GetTransform();
|
||||
Transform cluster_global_init_position = cluster->TransformLink();
|
||||
Ref<FBXNode> link_node = get_link(state);
|
||||
ERR_FAIL_COND_V_MSG(link_node.is_null(), Transform(), "invalid link corrupt file detected");
|
||||
r_valid_pose = true;
|
||||
Transform cluster_global_current_position = link_node.is_valid() && link_node->pivot_transform.is_valid() ? link_node->pivot_transform->GlobalTransform : Transform();
|
||||
|
||||
vertex_transform_matrix = reference_global_init_position.affine_inverse() * associate_global_init_position * associate_global_current_position.affine_inverse() *
|
||||
cluster_global_current_position * cluster_global_init_position.affine_inverse() * reference_global_init_position;
|
||||
} else {
|
||||
//print_error("non additive skinning is in use");
|
||||
Transform reference_global_position = cluster->GetTransform();
|
||||
Transform reference_global_current_position = mesh_global_position;
|
||||
//Transform geometric_pivot = Transform(); // we do not use this - 3ds max only
|
||||
Transform global_init_position = cluster->TransformLink();
|
||||
if (global_init_position.basis.determinant() == 0) {
|
||||
global_init_position = Transform(Basis(), global_init_position.origin);
|
||||
}
|
||||
Transform cluster_relative_init_position = global_init_position.affine_inverse() * reference_global_position;
|
||||
Transform cluster_relative_position_inverse = reference_global_current_position.affine_inverse() * global_init_position;
|
||||
vertex_transform_matrix = cluster_relative_position_inverse * cluster_relative_init_position;
|
||||
r_valid_pose = true;
|
||||
}
|
||||
|
||||
return vertex_transform_matrix;
|
||||
}
|
||||
|
|
|
@ -49,9 +49,6 @@ struct FBXBone : public Reference {
|
|||
return !valid_parent;
|
||||
}
|
||||
|
||||
uint64_t target_node_id; // the node target id for the skeleton element
|
||||
bool valid_target = false; // only applies to bones with a mesh / in the skin.
|
||||
|
||||
// Godot specific data
|
||||
int godot_bone_id = -2; // godot internal bone id assigned after import
|
||||
|
||||
|
@ -60,36 +57,34 @@ struct FBXBone : public Reference {
|
|||
bool valid_armature_id = false;
|
||||
uint64_t armature_id = 0;
|
||||
|
||||
// Vertex Weight information
|
||||
Transform transform_link; // todo remove
|
||||
Transform transform_matrix; // todo remove
|
||||
|
||||
/* get associate model - the model can be invalid sometimes */
|
||||
Ref<FBXBone> get_associate_model() const {
|
||||
return parent_bone;
|
||||
}
|
||||
|
||||
/* link node is the parent bone */
|
||||
Ref<FBXNode> get_link(const ImportState &state) const;
|
||||
Transform get_vertex_skin_xform(const ImportState &state, Transform mesh_global_position, bool &valid);
|
||||
Transform vertex_transform_matrix;
|
||||
Transform local_cluster_matrix; // set_bone_pose
|
||||
|
||||
mutable const FBXDocParser::Deformer *skin = nullptr;
|
||||
mutable const FBXDocParser::Cluster *cluster = nullptr;
|
||||
mutable const FBXDocParser::Geometry *geometry = nullptr;
|
||||
mutable const FBXDocParser::ModelLimbNode *limb_node = nullptr;
|
||||
|
||||
void set_pivot_xform(Ref<PivotTransform> p_pivot_xform) {
|
||||
pivot_xform = p_pivot_xform;
|
||||
void set_node(Ref<FBXNode> p_node) {
|
||||
node = p_node;
|
||||
}
|
||||
|
||||
// pose node / if assigned
|
||||
Transform pose_node = Transform();
|
||||
bool assigned_pose_node = false;
|
||||
Ref<FBXBone> parent_bone = Ref<FBXBone>();
|
||||
Ref<PivotTransform> pivot_xform = Ref<PivotTransform>();
|
||||
Ref<FBXSkeleton> fbx_skeleton = Ref<FBXSkeleton>();
|
||||
// Stores the pivot xform for this bone
|
||||
|
||||
Ref<FBXNode> node = nullptr;
|
||||
Ref<FBXBone> parent_bone = nullptr;
|
||||
Ref<FBXSkeleton> fbx_skeleton = nullptr;
|
||||
};
|
||||
|
||||
struct FBXSkinDeformer {
|
||||
FBXSkinDeformer(Ref<FBXBone> p_bone, const FBXDocParser::Cluster *p_cluster) :
|
||||
cluster(p_cluster), bone(p_bone) {}
|
||||
~FBXSkinDeformer() {}
|
||||
const FBXDocParser::Cluster *cluster;
|
||||
Ref<FBXBone> bone;
|
||||
|
||||
/* get associate model - the model can be invalid sometimes */
|
||||
Ref<FBXBone> get_associate_model() const {
|
||||
return bone->parent_bone;
|
||||
}
|
||||
|
||||
Ref<FBXNode> get_link(const ImportState &state) const;
|
||||
};
|
||||
|
||||
#endif // FBX_BONE_H
|
||||
|
|
|
@ -29,9 +29,9 @@
|
|||
/*************************************************************************/
|
||||
|
||||
#include "fbx_material.h"
|
||||
|
||||
#include "scene/resources/material.h"
|
||||
#include "scene/resources/texture.h"
|
||||
#include "tools/validation_tools.h"
|
||||
|
||||
String FBXMaterial::get_material_name() const {
|
||||
return material_name;
|
||||
|
@ -286,10 +286,16 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
}
|
||||
|
||||
if (desc == PROPERTY_DESC_IGNORE) {
|
||||
WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored.");
|
||||
//WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored.");
|
||||
continue;
|
||||
} else {
|
||||
print_verbose("FBX Material parameter: " + String(name.c_str()));
|
||||
|
||||
// Check for Diffuse material system / lambert materials / legacy basically
|
||||
if (name == "Diffuse" && !warning_non_pbr_material) {
|
||||
ValidationTracker::get_singleton()->add_validation_error(state.path, "Invalid material settings change to Ai Standard Surface shader, mat name: " + material_name.c_escape());
|
||||
warning_non_pbr_material = true;
|
||||
}
|
||||
}
|
||||
|
||||
// DISABLE when adding support for all weird and wonderful material formats
|
||||
|
@ -313,20 +319,41 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
const FBXDocParser::TypedProperty<Vector3> *vector_value = dynamic_cast<const FBXDocParser::TypedProperty<Vector3> *>(prop);
|
||||
|
||||
if (!real_value && !vector_value) {
|
||||
WARN_PRINT("unsupported datatype in property: " + String(name.c_str()));
|
||||
//WARN_PRINT("unsupported datatype in property: " + String(name.c_str()));
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Zero / default value properties
|
||||
// TODO: implement fields correctly tomorrow so we check 'has x mapping' before 'read x mapping' etc.
|
||||
|
||||
// if(real_value)
|
||||
// {
|
||||
// if(real_value->Value() == 0 && !vector_value)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (vector_value && !real_value) {
|
||||
if (vector_value->Value() == Vector3(0, 0, 0) && !real_value) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
switch (desc) {
|
||||
case PROPERTY_DESC_ALBEDO_COLOR: {
|
||||
if (vector_value) {
|
||||
const Vector3 &color = vector_value->Value();
|
||||
// Make sure to not lost any eventual opacity.
|
||||
Color c = spatial_material->get_albedo();
|
||||
c[0] = color[0];
|
||||
c[1] = color[1];
|
||||
c[2] = color[2];
|
||||
spatial_material->set_albedo(c);
|
||||
if (color != Vector3(0, 0, 0)) {
|
||||
Color c = Color();
|
||||
c[0] = color[0];
|
||||
c[1] = color[1];
|
||||
c[2] = color[2];
|
||||
spatial_material->set_albedo(c);
|
||||
}
|
||||
|
||||
} else if (real_value) {
|
||||
print_error("albedo is unsupported format?");
|
||||
}
|
||||
|
@ -336,7 +363,7 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
const real_t opacity = real_value->Value();
|
||||
if (opacity < (1.0 - CMP_EPSILON)) {
|
||||
Color c = spatial_material->get_albedo();
|
||||
c[3] = opacity;
|
||||
c.a = opacity;
|
||||
spatial_material->set_albedo(c);
|
||||
material_info.features.push_back(SpatialMaterial::Feature::FEATURE_TRANSPARENT);
|
||||
spatial_material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
|
||||
|
@ -355,16 +382,7 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
print_error("unsupported specular vector value: " + vector_value->Value());
|
||||
}
|
||||
} break;
|
||||
case PROPERTY_DESC_SPECULAR_ROUGHNESS: {
|
||||
if (real_value) {
|
||||
print_verbose("specular roughness value:" + rtos(real_value->Value()));
|
||||
spatial_material->set_roughness(MIN(1.0f, real_value->Value()));
|
||||
}
|
||||
|
||||
if (vector_value) {
|
||||
print_error("unsupported specular roughness color: " + vector_value->Value());
|
||||
}
|
||||
} break;
|
||||
case PROPERTY_DESC_SPECULAR_COLOR: {
|
||||
if (vector_value) {
|
||||
print_error("unsupported specular color: " + vector_value->Value());
|
||||
|
@ -401,7 +419,8 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
}
|
||||
} break;
|
||||
case PROPERTY_DESC_COAT_ROUGHNESS: {
|
||||
if (real_value) {
|
||||
// meaning is that approx equal to zero is disabled not actually zero. ;)
|
||||
if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) {
|
||||
print_verbose("clearcoat real value: " + rtos(real_value->Value()));
|
||||
spatial_material->set_clearcoat_gloss(1.0 - real_value->Value());
|
||||
|
||||
|
@ -411,11 +430,11 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
}
|
||||
} break;
|
||||
case PROPERTY_DESC_EMISSIVE: {
|
||||
if (real_value) {
|
||||
if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) {
|
||||
print_verbose("Emissive real value: " + rtos(real_value->Value()));
|
||||
spatial_material->set_emission_energy(real_value->Value());
|
||||
material_info.features.push_back(SpatialMaterial::Feature::FEATURE_EMISSION);
|
||||
} else {
|
||||
} else if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) {
|
||||
const Vector3 &color = vector_value->Value();
|
||||
Color c;
|
||||
c[0] = color[0];
|
||||
|
@ -426,7 +445,7 @@ Ref<SpatialMaterial> FBXMaterial::import_material(ImportState &state) {
|
|||
}
|
||||
} break;
|
||||
case PROPERTY_DESC_EMISSIVE_COLOR: {
|
||||
if (vector_value) {
|
||||
if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) {
|
||||
const Vector3 &color = vector_value->Value();
|
||||
Color c;
|
||||
c[0] = color[0];
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
struct FBXMaterial : public Reference {
|
||||
String material_name = String();
|
||||
bool warning_non_pbr_material = false;
|
||||
FBXDocParser::Material *material = nullptr;
|
||||
|
||||
/* Godot materials
|
||||
|
@ -149,6 +150,8 @@ struct FBXMaterial : public Reference {
|
|||
{ "Maya|SpecularTexture|file", SpatialMaterial::TextureParam::TEXTURE_METALLIC },
|
||||
|
||||
/* Roughness */
|
||||
// Arnold Roughness Map
|
||||
{ "Maya|specularRoughness", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS },
|
||||
|
||||
{ "3dsMax|Parameters|roughness_map", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS },
|
||||
{ "Maya|TEX_roughness_map", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS },
|
||||
|
@ -169,7 +172,6 @@ struct FBXMaterial : public Reference {
|
|||
|
||||
//{ "Maya|diffuseRoughness", SpatialMaterial::TextureParam::UNSUPPORTED },
|
||||
//{ "Maya|diffuseRoughness|file", SpatialMaterial::TextureParam::UNSUPPORTED },
|
||||
//{ "Maya|specularRoughness", SpatialMaterial::TextureParam::UNSUPPORTED },
|
||||
//{ "ShininessExponent", SpatialMaterial::TextureParam::UNSUPPORTED },
|
||||
//{ "ReflectionFactor", SpatialMaterial::TextureParam::UNSUPPORTED },
|
||||
//{ "TransparentColor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA },
|
||||
|
@ -185,7 +187,6 @@ struct FBXMaterial : public Reference {
|
|||
PROPERTY_DESC_ROUGHNESS,
|
||||
PROPERTY_DESC_SPECULAR,
|
||||
PROPERTY_DESC_SPECULAR_COLOR,
|
||||
PROPERTY_DESC_SPECULAR_ROUGHNESS,
|
||||
PROPERTY_DESC_SHINYNESS,
|
||||
PROPERTY_DESC_COAT,
|
||||
PROPERTY_DESC_COAT_ROUGHNESS,
|
||||
|
@ -203,8 +204,8 @@ struct FBXMaterial : public Reference {
|
|||
{ "Maya|specular", PROPERTY_DESC_SPECULAR },
|
||||
{ "Maya|specularColor", PROPERTY_DESC_SPECULAR_COLOR },
|
||||
|
||||
/* Specular roughness */
|
||||
{ "Maya|specularRoughness", PROPERTY_DESC_SPECULAR_ROUGHNESS },
|
||||
/* Specular roughness - arnold roughness map */
|
||||
{ "Maya|specularRoughness", PROPERTY_DESC_ROUGHNESS },
|
||||
|
||||
/* Transparent */
|
||||
{ "Opacity", PROPERTY_DESC_TRANSPARENT },
|
||||
|
@ -221,10 +222,10 @@ struct FBXMaterial : public Reference {
|
|||
{ "Maya|roughness", PROPERTY_DESC_ROUGHNESS },
|
||||
|
||||
/* Coat */
|
||||
{ "Maya|coat", PROPERTY_DESC_COAT },
|
||||
//{ "Maya|coat", PROPERTY_DESC_COAT },
|
||||
|
||||
/* Coat roughness */
|
||||
{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS },
|
||||
//{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS },
|
||||
|
||||
/* Emissive */
|
||||
{ "Maya|emission", PROPERTY_DESC_EMISSIVE },
|
||||
|
|
|
@ -115,8 +115,9 @@ struct SurfaceData {
|
|||
Array morphs;
|
||||
};
|
||||
|
||||
MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *mesh_geometry, const FBXDocParser::Model *model, bool use_compression) {
|
||||
MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression) {
|
||||
|
||||
mesh_geometry = p_mesh_geometry;
|
||||
// todo: make this just use a uint64_t FBX ID this is a copy of our original materials unfortunately.
|
||||
const std::vector<const FBXDocParser::Material *> &material_lookup = model->GetMaterials();
|
||||
|
||||
|
@ -195,7 +196,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo
|
|||
// TODO please add skinning.
|
||||
//mesh_id = mesh_geometry->ID();
|
||||
|
||||
sanitize_vertex_weights();
|
||||
sanitize_vertex_weights(state);
|
||||
|
||||
// Re organize polygon vertices to to correctly take into account strange
|
||||
// UVs.
|
||||
|
@ -214,7 +215,6 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo
|
|||
// Make sure that from this moment on the mesh_geometry is no used anymore.
|
||||
// This is a safety step, because the mesh_geometry data are no more valid
|
||||
// at this point.
|
||||
mesh_geometry = nullptr;
|
||||
|
||||
const int vertex_count = vertices.size();
|
||||
|
||||
|
@ -305,7 +305,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo
|
|||
|
||||
// This must be done before add_vertex because the surface tool is
|
||||
// expecting this before the st->add_vertex() call
|
||||
add_vertex(
|
||||
add_vertex(state,
|
||||
surface->surface_tool,
|
||||
state.scale,
|
||||
vertex,
|
||||
|
@ -350,6 +350,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo
|
|||
for (unsigned int vi = 0; vi < surface->vertices_map.size(); vi += 1) {
|
||||
const Vertex vertex = surface->vertices_map[vi];
|
||||
add_vertex(
|
||||
state,
|
||||
morph_st,
|
||||
state.scale,
|
||||
vertex,
|
||||
|
@ -405,8 +406,26 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo
|
|||
return godot_mesh;
|
||||
}
|
||||
|
||||
void FBXMeshData::sanitize_vertex_weights() {
|
||||
const int max_bones = VS::ARRAY_WEIGHTS_SIZE;
|
||||
void FBXMeshData::sanitize_vertex_weights(const ImportState &state) {
|
||||
const int max_vertex_influence_count = VS::ARRAY_WEIGHTS_SIZE;
|
||||
Map<int, int> skeleton_to_skin_bind_id;
|
||||
// TODO: error's need added
|
||||
const FBXDocParser::Skin *fbx_skin = mesh_geometry->DeformerSkin();
|
||||
|
||||
if (fbx_skin == nullptr || fbx_skin->Clusters().size() == 0) {
|
||||
return; // do nothing
|
||||
}
|
||||
|
||||
//
|
||||
// Precalculate the skin cluster mapping
|
||||
//
|
||||
|
||||
int bind_id = 0;
|
||||
for (const FBXDocParser::Cluster *cluster : fbx_skin->Clusters()) {
|
||||
Ref<FBXBone> bone = state.fbx_bone_map[cluster->TargetNode()->ID()];
|
||||
skeleton_to_skin_bind_id.insert(bone->godot_bone_id, bind_id);
|
||||
bind_id++;
|
||||
}
|
||||
|
||||
for (const Vertex *v = vertex_weights.next(nullptr); v != nullptr; v = vertex_weights.next(v)) {
|
||||
VertexWeightMapping *vm = vertex_weights.getptr(*v);
|
||||
|
@ -414,7 +433,6 @@ void FBXMeshData::sanitize_vertex_weights() {
|
|||
ERR_CONTINUE(vm->bones_ref.size() != vm->weights.size()); // No message, already checked.
|
||||
|
||||
const int initial_size = vm->weights.size();
|
||||
|
||||
{
|
||||
// Init bone id
|
||||
int *bones_ptr = vm->bones.ptrw();
|
||||
|
@ -423,7 +441,7 @@ void FBXMeshData::sanitize_vertex_weights() {
|
|||
for (int i = 0; i < vm->weights.size(); i += 1) {
|
||||
// At this point this is not possible because the skeleton is already initialized.
|
||||
CRASH_COND(bones_ref_ptr[i]->godot_bone_id == -2);
|
||||
bones_ptr[i] = bones_ref_ptr[i]->godot_bone_id;
|
||||
bones_ptr[i] = skeleton_to_skin_bind_id[bones_ref_ptr[i]->godot_bone_id];
|
||||
}
|
||||
|
||||
// From this point on the data is no more valid.
|
||||
|
@ -446,18 +464,18 @@ void FBXMeshData::sanitize_vertex_weights() {
|
|||
|
||||
{
|
||||
// Resize
|
||||
vm->weights.resize(max_bones);
|
||||
vm->bones.resize(max_bones);
|
||||
vm->weights.resize(max_vertex_influence_count);
|
||||
vm->bones.resize(max_vertex_influence_count);
|
||||
real_t *weights_ptr = vm->weights.ptrw();
|
||||
int *bones_ptr = vm->bones.ptrw();
|
||||
for (int i = initial_size; i < max_bones; i += 1) {
|
||||
for (int i = initial_size; i < max_vertex_influence_count; i += 1) {
|
||||
weights_ptr[i] = 0.0;
|
||||
bones_ptr[i] = 0;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
real_t sum = 0.0;
|
||||
for (int i = 0; i < max_bones; i += 1) {
|
||||
for (int i = 0; i < max_vertex_influence_count; i += 1) {
|
||||
sum += weights_ptr[i];
|
||||
}
|
||||
if (sum > 0.0) {
|
||||
|
@ -499,9 +517,22 @@ void FBXMeshData::reorganize_vertices(
|
|||
// Take the normal and see if we need to duplicate this polygon.
|
||||
if (r_normals_raw.has(index)) {
|
||||
const HashMap<PolygonId, Vector3> *nrml_arr = r_normals_raw.getptr(index);
|
||||
|
||||
if (nrml_arr->has(polygon_index)) {
|
||||
this_vert_poly_normal = nrml_arr->get(polygon_index);
|
||||
} else if (nrml_arr->has(-1)) {
|
||||
this_vert_poly_normal = nrml_arr->get(-1);
|
||||
} else {
|
||||
print_error("invalid normal detected: " + itos(index) + " polygon index: " + itos(polygon_index));
|
||||
for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) {
|
||||
print_verbose("debug contents key: " + itos(*pid));
|
||||
|
||||
if (nrml_arr->has(*pid)) {
|
||||
print_verbose("contents valid: " + nrml_arr->get(*pid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, check if we need to duplicate it.
|
||||
for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) {
|
||||
if (*pid == polygon_index) {
|
||||
|
@ -683,6 +714,7 @@ void FBXMeshData::reorganize_vertices(
|
|||
}
|
||||
|
||||
void FBXMeshData::add_vertex(
|
||||
const ImportState &state,
|
||||
Ref<SurfaceTool> p_surface_tool,
|
||||
real_t p_scale,
|
||||
Vertex p_vertex,
|
||||
|
@ -719,7 +751,22 @@ void FBXMeshData::add_vertex(
|
|||
// TODO what about binormals?
|
||||
// TODO there is other?
|
||||
|
||||
gen_weight_info(p_surface_tool, p_vertex);
|
||||
if (vertex_weights.has(p_vertex)) {
|
||||
// Let's extract the weight info.
|
||||
const VertexWeightMapping *vm = vertex_weights.getptr(p_vertex);
|
||||
const Vector<int> &bones = vm->bones;
|
||||
|
||||
// the bug is that the bone idx is wrong because it is not ref'ing the skin.
|
||||
|
||||
if (bones.size() > VS::ARRAY_WEIGHTS_SIZE) {
|
||||
print_error("[weight overflow detected]");
|
||||
}
|
||||
|
||||
p_surface_tool->add_weights(vm->weights);
|
||||
// 0 1 2 3 4 5 6 7 < local skeleton / skin for mesh
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 < actual skeleton with all joints
|
||||
p_surface_tool->add_bones(bones);
|
||||
}
|
||||
|
||||
// The surface tool want the vertex position as last thing.
|
||||
p_surface_tool->add_vertex((p_vertices_position[p_vertex] + p_morph_value) * p_scale);
|
||||
|
@ -895,14 +942,7 @@ void FBXMeshData::gen_weight_info(Ref<SurfaceTool> st, Vertex vertex_id) const {
|
|||
const VertexWeightMapping *vm = vertex_weights.getptr(vertex_id);
|
||||
st->add_weights(vm->weights);
|
||||
st->add_bones(vm->bones);
|
||||
print_verbose("[doc] Triangle added weights to mesh for bones");
|
||||
} else {
|
||||
// This vertex doesn't have any bone info, while the model is using the
|
||||
// bones.
|
||||
// So nothing more to do.
|
||||
}
|
||||
|
||||
print_verbose("[doc] Triangle added weights to mesh for bones");
|
||||
}
|
||||
|
||||
int FBXMeshData::get_vertex_from_polygon_vertex(const std::vector<int> &p_polygon_indices, int p_index) const {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "import_state.h"
|
||||
#include "tools/import_utils.h"
|
||||
|
||||
struct FBXNode;
|
||||
struct FBXMeshData;
|
||||
struct FBXBone;
|
||||
struct ImportState;
|
||||
|
@ -68,6 +69,10 @@ struct FBXMeshData : Reference {
|
|||
Vector<Vector3> normals;
|
||||
};
|
||||
|
||||
// FIXME: remove this is a hack for testing only
|
||||
mutable const FBXDocParser::MeshGeometry *mesh_geometry = nullptr;
|
||||
|
||||
Ref<FBXNode> mesh_node = nullptr;
|
||||
/// vertex id, Weight Info
|
||||
/// later: perf we can use array here
|
||||
HashMap<int, VertexWeightMapping> vertex_weights;
|
||||
|
@ -75,7 +80,7 @@ struct FBXMeshData : Reference {
|
|||
// translate fbx mesh data from document context to FBX Mesh Geometry Context
|
||||
bool valid_weight_indexes = false;
|
||||
|
||||
MeshInstance *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *mesh_geometry, const FBXDocParser::Model *model, bool use_compression);
|
||||
MeshInstance *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression);
|
||||
|
||||
void gen_weight_info(Ref<SurfaceTool> st, int vertex_id) const;
|
||||
|
||||
|
@ -87,7 +92,7 @@ struct FBXMeshData : Reference {
|
|||
MeshInstance *godot_mesh_instance = nullptr;
|
||||
|
||||
private:
|
||||
void sanitize_vertex_weights();
|
||||
void sanitize_vertex_weights(const ImportState &state);
|
||||
|
||||
/// Make sure to reorganize the vertices so that the correct UV is taken.
|
||||
/// This step is needed because differently from the normal, that can be
|
||||
|
@ -107,6 +112,7 @@ private:
|
|||
HashMap<int, HashMap<int, Vector2> > &r_uv_2_raw);
|
||||
|
||||
void add_vertex(
|
||||
const ImportState &state,
|
||||
Ref<SurfaceTool> p_surface_tool,
|
||||
real_t p_scale,
|
||||
int p_vertex,
|
||||
|
@ -135,17 +141,17 @@ private:
|
|||
/// Returns -1 if `p_index` is invalid.
|
||||
int get_vertex_from_polygon_vertex(const std::vector<int> &p_face_indices, int p_index) const;
|
||||
|
||||
/// Retuns true if this polygon_vertex_index is the end of a new polygon.
|
||||
/// Returns true if this polygon_vertex_index is the end of a new polygon.
|
||||
bool is_end_of_polygon(const std::vector<int> &p_face_indices, int p_index) const;
|
||||
|
||||
/// Retuns true if this polygon_vertex_index is the begin of a new polygon.
|
||||
/// Returns true if this polygon_vertex_index is the begin of a new polygon.
|
||||
bool is_start_of_polygon(const std::vector<int> &p_face_indices, int p_index) const;
|
||||
|
||||
/// Returns the number of polygons.
|
||||
int count_polygons(const std::vector<int> &p_face_indices) const;
|
||||
|
||||
/// Used to extract data from the `MappingData` alligned with vertex.
|
||||
/// Useful to extract normal/uvs/colors/tangets/etc...
|
||||
/// Used to extract data from the `MappingData` aligned with vertex.
|
||||
/// Useful to extract normal/uvs/colors/tangents/etc...
|
||||
/// If the function fails somehow, it returns an hollow vector and print an error.
|
||||
template <class R, class T>
|
||||
HashMap<int, R> extract_per_vertex_data(
|
||||
|
@ -157,7 +163,7 @@ private:
|
|||
R p_fall_back) const;
|
||||
|
||||
/// Used to extract data from the `MappingData` organized per polygon.
|
||||
/// Useful to extract the materila
|
||||
/// Useful to extract the material
|
||||
/// If the function fails somehow, it returns an hollow vector and print an error.
|
||||
template <class T>
|
||||
HashMap<int, T> extract_per_polygon(
|
||||
|
|
|
@ -40,7 +40,6 @@ void FBXSkeleton::init_skeleton(const ImportState &state) {
|
|||
if (skeleton == nullptr && skeleton_bone_count > 0) {
|
||||
skeleton = memnew(Skeleton);
|
||||
|
||||
Ref<FBXNode> skeleton_parent_node;
|
||||
if (fbx_node.is_valid()) {
|
||||
// cache skeleton attachment for later during node creation
|
||||
// can't be done until after node hierarchy is built
|
||||
|
@ -100,11 +99,11 @@ void FBXSkeleton::init_skeleton(const ImportState &state) {
|
|||
ERR_FAIL_COND_MSG(skeleton->get_bone_count() != bone_count, "Not all bones got added, is the file corrupted?");
|
||||
|
||||
for (Map<int, Ref<FBXBone> >::Element *bone_element = bone_map.front(); bone_element; bone_element = bone_element->next()) {
|
||||
Ref<FBXBone> bone = bone_element->value();
|
||||
const Ref<FBXBone> bone = bone_element->value();
|
||||
int bone_index = bone_element->key();
|
||||
print_verbose("working on bone: " + itos(bone_index) + " bone name:" + bone->bone_name);
|
||||
|
||||
skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->pivot_xform->LocalTransform, state.scale));
|
||||
skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->node->pivot_transform->LocalTransform, state.scale));
|
||||
|
||||
// lookup parent ID
|
||||
if (bone->valid_parent && state.fbx_bone_map.has(bone->parent_bone_id)) {
|
||||
|
|
|
@ -42,7 +42,7 @@ struct FBXNode;
|
|||
struct ImportState;
|
||||
struct FBXBone;
|
||||
|
||||
struct FBXSkeleton : Reference, ModelAbstraction {
|
||||
struct FBXSkeleton : Reference {
|
||||
Ref<FBXNode> fbx_node = Ref<FBXNode>();
|
||||
Vector<Ref<FBXBone> > skeleton_bones = Vector<Ref<FBXBone> >();
|
||||
Skeleton *skeleton = nullptr;
|
||||
|
|
|
@ -32,7 +32,9 @@
|
|||
#define IMPORT_STATE_H
|
||||
|
||||
#include "fbx_mesh_data.h"
|
||||
#include "modules/fbx/tools/import_utils.h"
|
||||
#include "tools/import_utils.h"
|
||||
#include "tools/validation_tools.h"
|
||||
|
||||
#include "pivot_transform.h"
|
||||
|
||||
#include "core/bind/core_bind.h"
|
||||
|
|
|
@ -80,14 +80,37 @@ void PivotTransform::ReadTransformChain() {
|
|||
const Vector3 &GeometricScaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricScaling", ok));
|
||||
if (ok) {
|
||||
geometric_scaling = GeometricScaling;
|
||||
} else {
|
||||
geometric_scaling = Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
const Vector3 &GeometricRotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricRotation", ok));
|
||||
if (ok) {
|
||||
geometric_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(GeometricRotation));
|
||||
} else {
|
||||
geometric_rotation = Quat();
|
||||
}
|
||||
|
||||
const Vector3 &GeometricTranslation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricTranslation", ok));
|
||||
if (ok) {
|
||||
geometric_translation = ImportUtils::FixAxisConversions(GeometricTranslation);
|
||||
} else {
|
||||
geometric_translation = Vector3(0, 0, 0);
|
||||
}
|
||||
|
||||
if (geometric_rotation != Quat()) {
|
||||
print_error("geometric rotation is unsupported!");
|
||||
//CRASH_COND(true);
|
||||
}
|
||||
|
||||
if (!geometric_scaling.is_equal_approx(Vector3(1, 1, 1))) {
|
||||
print_error("geometric scaling is unsupported!");
|
||||
//CRASH_COND(true);
|
||||
}
|
||||
|
||||
if (!geometric_translation.is_equal_approx(Vector3(0, 0, 0))) {
|
||||
print_error("geometric translation is unsupported.");
|
||||
//CRASH_COND(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +137,20 @@ Transform PivotTransform::ComputeLocalTransform(Vector3 p_translation, Quat p_ro
|
|||
return T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse();
|
||||
}
|
||||
|
||||
Transform PivotTransform::ComputeGlobalTransform(Transform t) const {
|
||||
Vector3 pos = t.origin;
|
||||
Vector3 scale = t.basis.get_scale();
|
||||
Quat rot = t.basis.get_rotation_quat();
|
||||
return ComputeGlobalTransform(pos, rot, scale);
|
||||
}
|
||||
|
||||
Transform PivotTransform::ComputeLocalTransform(Transform t) const {
|
||||
Vector3 pos = t.origin;
|
||||
Vector3 scale = t.basis.get_scale();
|
||||
Quat rot = t.basis.get_rotation_quat();
|
||||
return ComputeLocalTransform(pos, rot, scale);
|
||||
}
|
||||
|
||||
Transform PivotTransform::ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const {
|
||||
Transform T, Roff, Rp, Soff, Sp, S;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#ifndef PIVOT_TRANSFORM_H
|
||||
#define PIVOT_TRANSFORM_H
|
||||
|
||||
#include "core/math/transform.h"
|
||||
#include "core/reference.h"
|
||||
|
||||
#include "model_abstraction.h"
|
||||
|
@ -84,6 +85,9 @@ struct PivotTransform : Reference, ModelAbstraction {
|
|||
print_verbose("raw pre_rotation " + raw_pre_rotation * (180 / Math_PI));
|
||||
print_verbose("raw post_rotation " + raw_post_rotation * (180 / Math_PI));
|
||||
}
|
||||
|
||||
Transform ComputeGlobalTransform(Transform t) const;
|
||||
Transform ComputeLocalTransform(Transform t) const;
|
||||
Transform ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const;
|
||||
Transform ComputeLocalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const;
|
||||
|
||||
|
|
|
@ -151,17 +151,51 @@ Node *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_flag
|
|||
|
||||
// safety for version handling
|
||||
if (doc.IsSafeToImport()) {
|
||||
Spatial *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8);
|
||||
|
||||
// todo: move to document shutdown (will need to be validated after moving; this code has been validated already)
|
||||
for (FBXDocParser::TokenPtr token : tokens) {
|
||||
if (token) {
|
||||
delete token;
|
||||
token = nullptr;
|
||||
bool is_blender_fbx = false;
|
||||
//const FBXDocParser::PropertyPtr app_vendor = p_document->GlobalSettingsPtr()->Props()
|
||||
// p_document->Creator()
|
||||
const FBXDocParser::PropertyTable *import_props = doc.GetMetadataProperties();
|
||||
const FBXDocParser::PropertyPtr app_name = import_props->Get("Original|ApplicationName");
|
||||
const FBXDocParser::PropertyPtr app_vendor = import_props->Get("Original|ApplicationVendor");
|
||||
const FBXDocParser::PropertyPtr app_version = import_props->Get("Original|ApplicationVersion");
|
||||
//
|
||||
if (app_name) {
|
||||
const FBXDocParser::TypedProperty<std::string> *app_name_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_name);
|
||||
if (app_name_string) {
|
||||
print_verbose("FBX App Name: " + String(app_name_string->Value().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
return spatial;
|
||||
if (app_vendor) {
|
||||
const FBXDocParser::TypedProperty<std::string> *app_vendor_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_vendor);
|
||||
if (app_vendor_string) {
|
||||
print_verbose("FBX App Vendor: " + String(app_vendor_string->Value().c_str()));
|
||||
is_blender_fbx = app_vendor_string->Value().find("Blender") != std::string::npos;
|
||||
}
|
||||
}
|
||||
|
||||
if (app_version) {
|
||||
const FBXDocParser::TypedProperty<std::string> *app_version_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_version);
|
||||
if (app_version_string) {
|
||||
print_verbose("FBX App Version: " + String(app_version_string->Value().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_blender_fbx) {
|
||||
Spatial *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8);
|
||||
// todo: move to document shutdown (will need to be validated after moving; this code has been validated already)
|
||||
for (FBXDocParser::TokenPtr token : tokens) {
|
||||
if (token) {
|
||||
delete token;
|
||||
token = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return spatial;
|
||||
} else {
|
||||
print_error("We can't import blender FBX files, they're not implemented correctly at export time and would require several hacks in the FBX importer which could break Maya imports.");
|
||||
}
|
||||
|
||||
} else {
|
||||
print_error("Cannot import file: " + p_path + " version of file is unsupported, please re-export in your modelling package file version is: " + itos(doc.FBXVersion()));
|
||||
}
|
||||
|
@ -356,6 +390,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
// Size relative to cm.
|
||||
const real_t fbx_unit_scale = p_document->GlobalSettingsPtr()->UnitScaleFactor();
|
||||
|
||||
print_verbose("FBX unit scale import value: " + rtos(fbx_unit_scale));
|
||||
// Set FBX file scale is relative to CM must be converted to M
|
||||
state.scale = fbx_unit_scale / 100.0;
|
||||
print_verbose("FBX unit scale is: " + rtos(state.scale));
|
||||
|
@ -366,6 +401,11 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
state.enable_animation_import = true;
|
||||
Ref<FBXNode> root_node;
|
||||
root_node.instance();
|
||||
|
||||
// make sure fake noFBXDocParser::PropertyPtr ptrde always has a transform too ;)
|
||||
Ref<PivotTransform> pivot_transform;
|
||||
pivot_transform.instance();
|
||||
root_node->pivot_transform = pivot_transform;
|
||||
root_node->node_name = "root node";
|
||||
root_node->current_node_id = 0;
|
||||
root_node->godot_node = state.root;
|
||||
|
@ -376,9 +416,16 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
// cache basic node information from FBX document
|
||||
// grabs all FBX bones
|
||||
BuildDocumentBones(Ref<FBXBone>(), state, p_document, 0L);
|
||||
BuildDocumentNodes(nullptr, state, p_document, 0L, nullptr);
|
||||
BuildDocumentNodes(Ref<PivotTransform>(), state, p_document, 0L, nullptr);
|
||||
|
||||
// Build document skinning information
|
||||
|
||||
// Algorithm is this:
|
||||
// Get Deformer: object with "Skin" class.
|
||||
// Deformer:: has link to Geometry:: (correct mesh for skin)
|
||||
// Deformer:: has Source which is the SubDeformer:: (e.g. the Cluster)
|
||||
// Notes at the end it configures the vertex weight mapping.
|
||||
|
||||
for (uint64_t skin_id : p_document->GetSkinIDs()) {
|
||||
// Validate the parser
|
||||
FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id);
|
||||
|
@ -389,7 +436,6 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
ERR_CONTINUE_MSG(skin == nullptr, "invalid skin added to skin list [parser bug]");
|
||||
|
||||
const std::vector<const FBXDocParser::Connection *> source_to_destination = p_document->GetConnectionsBySourceSequenced(skin_id);
|
||||
const std::vector<const FBXDocParser::Connection *> destination_to_source = p_document->GetConnectionsByDestinationSequenced(skin_id);
|
||||
FBXDocParser::MeshGeometry *mesh = nullptr;
|
||||
uint64_t mesh_id = 0;
|
||||
|
||||
|
@ -409,21 +455,13 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
|
||||
// Validate the mesh exists and was retrieved
|
||||
ERR_CONTINUE_MSG(mesh_id == 0, "mesh id is invalid");
|
||||
const std::vector<const FBXDocParser::Cluster *> clusters = skin->Clusters();
|
||||
|
||||
// NOTE: this will ONLY work on skinned bones (it is by design.)
|
||||
// A cluster is a skinned bone so SKINS won't contain unskinned bones so we need to pre-add all bones and parent them in a step beforehand.
|
||||
for (const FBXDocParser::Connection *con : destination_to_source) {
|
||||
FBXDocParser::Object *ob = con->SourceObject();
|
||||
|
||||
//
|
||||
// Read the FBX Document bone information
|
||||
//
|
||||
|
||||
// Get bone weight data
|
||||
const FBXDocParser::Cluster *deformer = dynamic_cast<const FBXDocParser::Cluster *>(ob);
|
||||
ERR_CONTINUE_MSG(deformer == nullptr, "invalid bone cluster");
|
||||
|
||||
const uint64_t deformer_id = deformer->ID();
|
||||
for (const FBXDocParser::Cluster *cluster : clusters) {
|
||||
ERR_CONTINUE_MSG(cluster == nullptr, "invalid bone cluster");
|
||||
const uint64_t deformer_id = cluster->ID();
|
||||
std::vector<const FBXDocParser::Connection *> connections = p_document->GetConnectionsByDestinationSequenced(deformer_id);
|
||||
|
||||
// Weight data always has a node in the scene lets grab the limb's node in the scene :) (reverse set to true since it's the opposite way around)
|
||||
|
@ -444,8 +482,8 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
//
|
||||
|
||||
// Cache Weight Information into bone for later usage if you want the raw data.
|
||||
const std::vector<unsigned int> &indexes = deformer->GetIndices();
|
||||
const std::vector<float> &weights = deformer->GetWeights();
|
||||
const std::vector<unsigned int> &indexes = cluster->GetIndices();
|
||||
const std::vector<float> &weights = cluster->GetWeights();
|
||||
Ref<FBXMeshData> mesh_vertex_data;
|
||||
|
||||
// this data will pre-exist if vertex weight information is found
|
||||
|
@ -468,7 +506,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
|
||||
VertexWeightMapping &vm = mesh_vertex_data->vertex_weights[vertex_index];
|
||||
vm.weights.push_back(influence_weight);
|
||||
vm.bones.push_back(0);
|
||||
vm.bones.push_back(0); // bone id is pushed on here during sanitization phase
|
||||
vm.bones_ref.push_back(bone_element);
|
||||
}
|
||||
|
||||
|
@ -531,31 +569,6 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
// we opted to merge the entire scene onto one skeleton for now
|
||||
// if we need to change this we have an archive of the old code.
|
||||
|
||||
const std::vector<uint64_t> &bind_pose_ids = p_document->GetBindPoseIDs();
|
||||
|
||||
for (uint64_t skin_id : bind_pose_ids) {
|
||||
|
||||
FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id);
|
||||
const FBXDocParser::FbxPose *active_skin = lazy_skin->Get<FBXDocParser::FbxPose>();
|
||||
|
||||
if (active_skin) {
|
||||
const std::vector<FBXDocParser::FbxPoseNode *> &bind_poses = active_skin->GetBindPoses();
|
||||
|
||||
for (FBXDocParser::FbxPoseNode *pose_node : bind_poses) {
|
||||
Transform t = pose_node->GetBindPose();
|
||||
uint64_t fbx_node_id = pose_node->GetNodeID();
|
||||
if (state.fbx_bone_map.has(fbx_node_id)) {
|
||||
Ref<FBXBone> bone = state.fbx_bone_map[fbx_node_id];
|
||||
if (bone.is_valid()) {
|
||||
print_verbose("assigned skin pose from the file for bone " + bone->bone_name + ", transform: " + t);
|
||||
bone->pose_node = t;
|
||||
bone->assigned_pose_node = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bind pose normally only has 1 per mesh but can have more than one
|
||||
// this is the point of skins
|
||||
// in FBX first bind pose is the master for the first skin
|
||||
|
@ -586,15 +599,22 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
// now populate bone on the armature node list
|
||||
fbx_skeleton_inst->skeleton_bones.push_back(bone);
|
||||
|
||||
CRASH_COND_MSG(!state.fbx_target_map.has(armature_id), "invalid armature [serious]");
|
||||
|
||||
Ref<FBXNode> node = state.fbx_target_map[armature_id];
|
||||
|
||||
CRASH_COND_MSG(node.is_null(), "invalid node [serious]");
|
||||
CRASH_COND_MSG(node->pivot_transform.is_null(), "invalid pivot transform [serious]");
|
||||
fbx_skeleton_inst->fbx_node = node;
|
||||
|
||||
ERR_CONTINUE_MSG(fbx_skeleton_inst->fbx_node.is_null(), "invalid skeleton node [serious]");
|
||||
|
||||
// we need to have a valid armature id and the model configured for the bone to be assigned fully.
|
||||
// happens once per skeleton
|
||||
if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->has_model()) {
|
||||
Ref<FBXNode> node = state.fbx_target_map[armature_id];
|
||||
|
||||
fbx_skeleton_inst->set_model(node->get_model());
|
||||
fbx_skeleton_inst->fbx_node = node;
|
||||
print_verbose("allocated fbx skeleton primary / armature node for the level: " + node->node_name);
|
||||
} else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->has_model()) {
|
||||
if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) {
|
||||
print_verbose("allocated fbx skeleton primary / armature node for the level: " + fbx_skeleton_inst->fbx_node->node_name);
|
||||
} else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) {
|
||||
print_error("bones are not mapped to an armature node for armature id: " + itos(armature_id) + " bone: " + bone->bone_name);
|
||||
// this means bone will be removed and not used, which is safe actually and no skeleton will be created.
|
||||
}
|
||||
|
@ -602,7 +622,14 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
|
||||
// setup skeleton instances if required :)
|
||||
for (Map<uint64_t, Ref<FBXSkeleton> >::Element *skeleton_node = state.skeleton_map.front(); skeleton_node; skeleton_node = skeleton_node->next()) {
|
||||
skeleton_node->value()->init_skeleton(state);
|
||||
Ref<FBXSkeleton> &skeleton = skeleton_node->value();
|
||||
skeleton->init_skeleton(state);
|
||||
|
||||
ERR_CONTINUE_MSG(skeleton->fbx_node.is_null(), "invalid fbx target map, missing skeleton");
|
||||
}
|
||||
|
||||
// This list is not populated
|
||||
for (Map<uint64_t, Ref<FBXNode> >::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) {
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -638,6 +665,8 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
state.renderer_mesh_data.insert(mesh_id, mesh_data_precached);
|
||||
}
|
||||
|
||||
mesh_data_precached->mesh_node = fbx_node;
|
||||
|
||||
// mesh node, mesh id
|
||||
mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, (p_flags & IMPORT_USE_COMPRESSION) != 0);
|
||||
if (!state.MeshNodes.has(mesh_id)) {
|
||||
|
@ -681,33 +710,69 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
}
|
||||
}
|
||||
|
||||
for (Map<uint64_t, Ref<FBXNode> >::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) {
|
||||
const uint64_t mesh_id = skin_mesh->key();
|
||||
Ref<FBXNode> fbx_node = skin_mesh->value();
|
||||
for (Map<uint64_t, Ref<FBXMeshData> >::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) {
|
||||
const uint64_t mesh_id = mesh_data->key();
|
||||
Ref<FBXMeshData> mesh = mesh_data->value();
|
||||
|
||||
ERR_CONTINUE_MSG(state.MeshSkins.has(skin_mesh->key()), "invalid skin already exists for this mesh?");
|
||||
print_verbose("[doc] caching skin for " + itos(mesh_id) + ", mesh node name: " + fbx_node->node_name);
|
||||
Ref<Skin> skin;
|
||||
skin.instance();
|
||||
const FBXDocParser::MeshGeometry *mesh_geometry = p_document->GetObject(mesh_id)->Get<FBXDocParser::MeshGeometry>();
|
||||
|
||||
for (Map<uint64_t, Ref<FBXBone> >::Element *elem = state.fbx_bone_map.front(); elem; elem = elem->next()) {
|
||||
Ref<FBXBone> bone = elem->value();
|
||||
Transform ignore_t;
|
||||
Ref<FBXSkeleton> skeleton = bone->fbx_skeleton;
|
||||
// grab the skin bind
|
||||
bool valid_bind = false;
|
||||
Transform bind = bone->get_vertex_skin_xform(state, fbx_node->pivot_transform->GlobalTransform, valid_bind);
|
||||
ERR_CONTINUE_MSG(mesh->mesh_node.is_null(), "invalid mesh allocation");
|
||||
|
||||
ERR_CONTINUE_MSG(!valid_bind, "invalid bind");
|
||||
const FBXDocParser::Skin *mesh_skin = mesh_geometry->DeformerSkin();
|
||||
|
||||
if (bind.basis.determinant() == 0) {
|
||||
bind = Transform(Basis(), bind.origin);
|
||||
}
|
||||
|
||||
skin->add_named_bind(bone->bone_name, get_unscaled_transform(bind, state.scale));
|
||||
if (!mesh_skin) {
|
||||
continue; // safe to continue
|
||||
}
|
||||
|
||||
state.MeshSkins.insert(mesh_id, skin);
|
||||
//
|
||||
// Skin bone configuration
|
||||
//
|
||||
|
||||
//
|
||||
// Get Mesh Node Xform only
|
||||
//
|
||||
// ERR_CONTINUE_MSG(!state.fbx_target_map.has(mesh_id), "invalid xform for the skin pose: " + itos(mesh_id));
|
||||
// Ref<FBXNode> mesh_node_xform_data = state.fbx_target_map[mesh_id];
|
||||
|
||||
if (!mesh_skin) {
|
||||
continue; // not a deformer.
|
||||
}
|
||||
|
||||
if (mesh_skin->Clusters().size() == 0) {
|
||||
continue; // possibly buggy mesh
|
||||
}
|
||||
|
||||
// Lookup skin or create it if it's not found.
|
||||
Ref<Skin> skin;
|
||||
if (!state.MeshSkins.has(mesh_id)) {
|
||||
print_verbose("Created new skin");
|
||||
skin.instance();
|
||||
state.MeshSkins.insert(mesh_id, skin);
|
||||
} else {
|
||||
print_verbose("Grabbed skin");
|
||||
skin = state.MeshSkins[mesh_id];
|
||||
}
|
||||
|
||||
for (const FBXDocParser::Cluster *cluster : mesh_skin->Clusters()) {
|
||||
// node or bone this cluster targets (in theory will only be a bone target)
|
||||
uint64_t skin_target_id = cluster->TargetNode()->ID();
|
||||
|
||||
print_verbose("adding cluster [" + itos(cluster->ID()) + "] " + String(cluster->Name().c_str()) + " for target: [" + itos(skin_target_id) + "] " + String(cluster->TargetNode()->Name().c_str()));
|
||||
ERR_CONTINUE_MSG(!state.fbx_bone_map.has(skin_target_id), "no bone found by that ID? locator");
|
||||
|
||||
const Ref<FBXBone> bone = state.fbx_bone_map[skin_target_id];
|
||||
const Ref<FBXSkeleton> skeleton = bone->fbx_skeleton;
|
||||
const Ref<FBXNode> skeleton_node = skeleton->fbx_node;
|
||||
|
||||
skin->add_named_bind(
|
||||
bone->bone_name,
|
||||
get_unscaled_transform(
|
||||
skeleton_node->pivot_transform->GlobalTransform.affine_inverse() * cluster->TransformLink().affine_inverse(), state.scale));
|
||||
}
|
||||
|
||||
print_verbose("cluster name / id: " + String(mesh_skin->Name().c_str()) + " [" + itos(mesh_skin->ID()) + "]");
|
||||
print_verbose("skeleton has " + itos(state.fbx_bone_map.size()) + " binds");
|
||||
print_verbose("fbx skin has " + itos(mesh_skin->Clusters().size()) + " binds");
|
||||
}
|
||||
|
||||
// mesh data iteration for populating skeleton mapping
|
||||
|
@ -1211,7 +1276,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene(
|
|||
for (Map<uint64_t, Ref<FBXBone> >::Element *element = state.fbx_bone_map.front(); element; element = element->next()) {
|
||||
Ref<FBXBone> bone = element->value();
|
||||
bone->parent_bone.unref();
|
||||
bone->pivot_xform.unref();
|
||||
bone->node.unref();
|
||||
bone->fbx_skeleton.unref();
|
||||
}
|
||||
|
||||
|
@ -1303,27 +1368,10 @@ void EditorSceneImporterFBX::BuildDocumentBones(Ref<FBXBone> p_parent_bone,
|
|||
}
|
||||
|
||||
uint64_t limb_id = limb_node->ID();
|
||||
const FBXDocParser::Cluster *deformer = ProcessDOMConnection<FBXDocParser::Cluster>(p_doc, limb_id);
|
||||
|
||||
bone_element->bone_id = limb_id;
|
||||
bone_element->bone_name = ImportUtils::FBXNodeToName(model->Name());
|
||||
bone_element->parent_bone = p_parent_bone;
|
||||
|
||||
if (deformer != nullptr) {
|
||||
|
||||
print_verbose("[doc] Mesh Cluster: " + String(deformer->Name().c_str()) + ", " + deformer->TransformLink());
|
||||
print_verbose("fbx node: debug name: " + String(model->Name().c_str()) + "bone name: " + String(deformer->Name().c_str()));
|
||||
|
||||
// assign FBX animation bind pose compensation data;
|
||||
bone_element->transform_link = deformer->TransformLink();
|
||||
bone_element->transform_matrix = deformer->GetTransform();
|
||||
bone_element->cluster = deformer;
|
||||
|
||||
// skin configures target node ID.
|
||||
bone_element->target_node_id = deformer->TargetNode()->ID();
|
||||
bone_element->valid_target = true;
|
||||
bone_element->bone_id = limb_id;
|
||||
}
|
||||
|
||||
// insert limb by ID into list.
|
||||
state.fbx_bone_map.insert(limb_node->ID(), bone_element);
|
||||
}
|
||||
|
@ -1389,7 +1437,7 @@ void EditorSceneImporterFBX::BuildDocumentNodes(
|
|||
if (state.fbx_bone_map.has(current_node_id)) {
|
||||
Ref<FBXBone> bone = state.fbx_bone_map[current_node_id];
|
||||
if (bone.is_valid()) {
|
||||
bone->set_pivot_xform(fbx_transform);
|
||||
bone->set_node(new_node);
|
||||
print_verbose("allocated bone data: " + bone->bone_name);
|
||||
}
|
||||
}
|
||||
|
@ -1403,11 +1451,14 @@ void EditorSceneImporterFBX::BuildDocumentNodes(
|
|||
new_node->set_parent(state.fbx_root_node);
|
||||
}
|
||||
|
||||
CRASH_COND_MSG(new_node->pivot_transform.is_null(), "invalid fbx target map pivot transform [serious]");
|
||||
|
||||
// populate lookup tables with references
|
||||
// [fbx_node_id, fbx_node]
|
||||
|
||||
state.fbx_node_list.push_back(new_node);
|
||||
if (!state.fbx_target_map.has(new_node->current_node_id)) {
|
||||
|
||||
state.fbx_target_map[new_node->current_node_id] = new_node;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,10 @@
|
|||
#include "tools/import_utils.h"
|
||||
|
||||
#include "core/bind/core_bind.h"
|
||||
#include "core/dictionary.h"
|
||||
#include "core/io/resource_importer.h"
|
||||
#include "core/local_vector.h"
|
||||
#include "core/ustring.h"
|
||||
#include "core/vector.h"
|
||||
#include "editor/import/resource_importer_scene.h"
|
||||
#include "editor/project_settings_editor.h"
|
||||
|
|
|
@ -184,7 +184,6 @@ ObjectPtr LazyObject::LoadObject() {
|
|||
if (!strcmp(classtag.c_str(), "Cluster")) {
|
||||
object.reset(new Cluster(id, element, doc, name));
|
||||
} else if (!strcmp(classtag.c_str(), "Skin")) {
|
||||
|
||||
object.reset(new Skin(id, element, doc, name));
|
||||
} else if (!strcmp(classtag.c_str(), "BlendShape")) {
|
||||
object.reset(new BlendShape(id, element, doc, name));
|
||||
|
@ -288,12 +287,15 @@ Document::~Document() {
|
|||
delete v.second;
|
||||
}
|
||||
|
||||
if (metadata_properties != nullptr) {
|
||||
delete metadata_properties;
|
||||
}
|
||||
// clear globals import pointer
|
||||
globals.reset();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
static const unsigned int LowerSupportedVersion = 7100;
|
||||
static const unsigned int LowerSupportedVersion = 7300;
|
||||
static const unsigned int UpperSupportedVersion = 7700;
|
||||
|
||||
bool Document::ReadHeader() {
|
||||
|
@ -310,11 +312,11 @@ bool Document::ReadHeader() {
|
|||
// While we may have some success with newer files, we don't support
|
||||
// the older 6.n fbx format
|
||||
if (fbxVersion < LowerSupportedVersion) {
|
||||
DOMWarning("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013, you can re-export using Maya, 3DS, blender or Autodesk FBX tool");
|
||||
DOMWarning("unsupported, old format version, FBX 2015-2020, you must re-export in a more modern version of your original modelling application");
|
||||
return false;
|
||||
}
|
||||
if (fbxVersion > UpperSupportedVersion) {
|
||||
DOMWarning("unsupported, newer format version, supported are only FBX 2011, up to FBX 2020"
|
||||
DOMWarning("unsupported, newer format version, supported are only FBX 2015, up to FBX 2020"
|
||||
" trying to read it nevertheless");
|
||||
}
|
||||
|
||||
|
@ -323,6 +325,20 @@ bool Document::ReadHeader() {
|
|||
creator = ParseTokenAsString(GetRequiredToken(ecreator, 0));
|
||||
}
|
||||
|
||||
//
|
||||
// Scene Info
|
||||
//
|
||||
|
||||
const ElementPtr scene_info = shead->GetElement("SceneInfo");
|
||||
|
||||
if (scene_info) {
|
||||
PropertyTable *fileExportProps = const_cast<PropertyTable *>(GetPropertyTable(*this, "", scene_info, scene_info->Compound(), true));
|
||||
|
||||
if (fileExportProps) {
|
||||
metadata_properties = fileExportProps;
|
||||
}
|
||||
}
|
||||
|
||||
const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp");
|
||||
if (etimestamp && etimestamp->Compound()) {
|
||||
const ScopePtr stimestamp = etimestamp->Compound();
|
||||
|
|
|
@ -1196,6 +1196,10 @@ public:
|
|||
return globals.get();
|
||||
}
|
||||
|
||||
const PropertyTable *GetMetadataProperties() const {
|
||||
return metadata_properties;
|
||||
}
|
||||
|
||||
const PropertyTemplateMap &Templates() const {
|
||||
return templates;
|
||||
}
|
||||
|
@ -1289,6 +1293,7 @@ private:
|
|||
std::vector<uint64_t> materials;
|
||||
std::vector<uint64_t> skins;
|
||||
mutable std::vector<const AnimationStack *> animationStacksResolved;
|
||||
PropertyTable *metadata_properties = nullptr;
|
||||
std::shared_ptr<FileGlobalSettings> globals = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -106,7 +106,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
const Skin *skin;
|
||||
const Skin *skin = nullptr;
|
||||
std::vector<const BlendShape *> blendShapes;
|
||||
};
|
||||
|
||||
|
|
|
@ -168,10 +168,10 @@ char EncodeBase64(char byte) {
|
|||
}
|
||||
|
||||
/** Encodes a block of 4 bytes to base64 encoding
|
||||
*
|
||||
* @param bytes Bytes to encode.
|
||||
* @param out_string String to write encoded values to.
|
||||
* @param string_pos Position in out_string.*/
|
||||
* @param string_pos Position in out_string.
|
||||
*/
|
||||
void EncodeByteBlock(const char *bytes, std::string &out_string, size_t string_pos) {
|
||||
char b0 = (bytes[0] & 0xFC) >> 2;
|
||||
char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4);
|
||||
|
|
|
@ -83,14 +83,6 @@ namespace FBXDocParser {
|
|||
|
||||
namespace Util {
|
||||
|
||||
/** helper for std::for_each to delete all heap-allocated items in a container */
|
||||
template <typename T>
|
||||
struct delete_fun {
|
||||
void operator()(const volatile T *del) {
|
||||
delete del;
|
||||
}
|
||||
};
|
||||
|
||||
/** Get a string representation for a #TokenType. */
|
||||
const char *TokenTypeString(TokenType t);
|
||||
|
||||
|
|
48
modules/fbx/tools/validation_tools.cpp
Normal file
48
modules/fbx/tools/validation_tools.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*************************************************************************/
|
||||
/* validation_tools.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "validation_tools.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/print_string.h"
|
||||
#include "core/ustring.h"
|
||||
|
||||
ValidationTracker::Entries *ValidationTracker::entries_singleton = memnew(ValidationTracker::Entries);
|
||||
|
||||
// for printing our CSV to dump validation problems of files
|
||||
// later we can make some agnostic tooling for this but this is fine for the time being.
|
||||
void ValidationTracker::Entries::add_validation_error(String asset_path, String message) {
|
||||
print_error(message);
|
||||
// note: implementation is static
|
||||
validation_entries[asset_path].push_back(message);
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
93
modules/fbx/tools/validation_tools.h
Normal file
93
modules/fbx/tools/validation_tools.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*************************************************************************/
|
||||
/* validation_tools.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef FBX_VALIDATION_TOOLS_H
|
||||
#define FBX_VALIDATION_TOOLS_H
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/local_vector.h"
|
||||
#include "core/map.h"
|
||||
#include "core/ustring.h"
|
||||
#include <core/io/json.h>
|
||||
#include <core/os/file_access.h>
|
||||
#include <scene/3d/path.h>
|
||||
|
||||
class ValidationTracker {
|
||||
protected:
|
||||
struct Entries {
|
||||
Map<String, LocalVector<String> > validation_entries = Map<String, LocalVector<String> >();
|
||||
|
||||
// for printing our CSV to dump validation problems of files
|
||||
// later we can make some agnostic tooling for this but this is fine for the time being.
|
||||
void add_validation_error(String asset_path, String message);
|
||||
void print_to_csv() {
|
||||
print_verbose("Exporting assset validation log please wait");
|
||||
String massive_log_file;
|
||||
|
||||
String csv_header = "file_path, error message, extra data\n";
|
||||
massive_log_file += csv_header;
|
||||
|
||||
for (Map<String, LocalVector<String> >::Element *element = validation_entries.front(); element; element = element->next()) {
|
||||
for (unsigned int x = 0; x < element->value().size(); x++) {
|
||||
const String &line_entry = element->key() + ", " + element->value()[x].c_escape() + "\n";
|
||||
massive_log_file += line_entry;
|
||||
}
|
||||
}
|
||||
|
||||
String path = "asset_validation_errors.csv";
|
||||
Error err;
|
||||
FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err);
|
||||
if (!file || err) {
|
||||
if (file)
|
||||
memdelete(file);
|
||||
print_error("ValidationTracker Error - failed to create file - path: %s\n" + path);
|
||||
return;
|
||||
}
|
||||
|
||||
file->store_string(massive_log_file);
|
||||
if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
|
||||
print_error("ValidationTracker Error - failed to write to file - path: %s\n" + path);
|
||||
}
|
||||
file->close();
|
||||
memdelete(file);
|
||||
}
|
||||
};
|
||||
// asset path, error messages
|
||||
static Entries *entries_singleton;
|
||||
|
||||
public:
|
||||
static Entries *get_singleton() {
|
||||
return entries_singleton;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
#endif // FBX_VALIDATION_TOOLS_H
|
Loading…
Reference in a new issue