Files
WickedEngine/Editor/ModelImporter_FBX.cpp
T

1142 lines
39 KiB
C++

#include "stdafx.h"
#include "wiScene.h"
#include "ModelImporter.h"
// ufbx Documentation: https://ufbx.github.io/
#define UFBX_REAL_TYPE float
#include "ufbx.h"
#include "ufbx.c"
using namespace wi::graphics;
using namespace wi::scene;
using namespace wi::ecs;
void FlipZAxis(Scene& scene, Entity rootEntity, wi::unordered_map<size_t, TransformComponent>& transforms_original);
void Import_Mixamo_Bone(Scene& scene, Entity rootEntity, Entity boneEntity);
// File callbacks are implemented for platforms that don't support default c++ filesystem correctly (like UWP)
struct FileDataStream
{
wi::vector<uint8_t> data;
size_t offset = 0;
};
size_t read_cb(void* user, void* data, size_t size)
{
FileDataStream* datastream = (FileDataStream*)user;
uint8_t* src = datastream->data.data() + datastream->offset;
size = std::min(size, datastream->data.size() - datastream->offset);
std::memcpy(data, src, size);
datastream->offset += size;
return size;
}
bool skip_cb(void* user, size_t size)
{
FileDataStream* datastream = (FileDataStream*)user;
size = std::min(size, datastream->data.size() - datastream->offset);
datastream->offset += size;
return true;
}
void close_cb(void* user)
{
FileDataStream* datastream = (FileDataStream*)user;
delete datastream;
}
bool open_file_cb(void* user, ufbx_stream* stream, const char* path, size_t path_len, const ufbx_open_file_info* info)
{
wi::vector<uint8_t> filedata;
if (wi::helper::FileRead(path, filedata))
{
FileDataStream* datastream = new FileDataStream;
datastream->data = std::move(filedata);
stream->user = datastream;
stream->read_fn = read_cb;
stream->skip_fn = skip_cb;
stream->close_fn = close_cb;
return true;
}
return false;
}
void ImportModel_FBX(const std::string& filename, wi::scene::Scene& scene)
{
ufbx_load_opts opts = {};
//opts.target_axes = ufbx_axes_left_handed_y_up;
opts.target_unit_meters = 1.0f;
//opts.target_camera_axes = ufbx_axes_left_handed_y_up;
//opts.target_light_axes = ufbx_axes_left_handed_y_up;
//opts.space_conversion = UFBX_SPACE_CONVERSION_TRANSFORM_ROOT;
//opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
opts.space_conversion = UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY;
opts.generate_missing_normals = true;
opts.open_file_cb.fn = open_file_cb;
ufbx_error error = {};
ufbx_scene* fbxscene = ufbx_load_file(filename.c_str(), &opts, &error);
if (fbxscene == nullptr)
{
std::string str = "FBX import error: ";
str += error.description.data;
wi::backlog::post(str, wi::backlog::LogLevel::Error);
wi::helper::messageBox(str, "Error!");
return;
}
wi::unordered_map<const ufbx_material*, Entity> material_lookup;
wi::unordered_map<const ufbx_skin_deformer*, Entity> skin_lookup;
wi::unordered_map<const ufbx_mesh*, Entity> mesh_lookup;
wi::unordered_map<const ufbx_node*, Entity> node_lookup;
Entity rootEntity = CreateEntity();
{
scene.names.Create(rootEntity) = wi::helper::GetFileNameFromPath(filename);
const ufbx_node* node = fbxscene->root_node;
node_lookup[node] = rootEntity;
TransformComponent& transform = scene.transforms.Create(rootEntity);
transform.scale_local.x = node->local_transform.scale.x;
transform.scale_local.y = node->local_transform.scale.y;
transform.scale_local.z = node->local_transform.scale.z;
transform.rotation_local.x = node->local_transform.rotation.x;
transform.rotation_local.y = node->local_transform.rotation.y;
transform.rotation_local.z = node->local_transform.rotation.z;
transform.rotation_local.w = node->local_transform.rotation.w;
transform.translation_local.x = node->local_transform.translation.x;
transform.translation_local.y = node->local_transform.translation.y;
transform.translation_local.z = node->local_transform.translation.z;
}
wi::vector<wi::Resource> embedded_resources;
auto preload_embedded_texture = [&](ufbx_texture* texture) {
if (texture->content.data == nullptr)
return;
std::string filename = texture->filename.data;
if (filename.empty())
{
// Force some image resource name:
do {
filename.clear();
filename += "fbximport_" + std::to_string(wi::random::GetRandom(std::numeric_limits<uint32_t>::max())) + ".png";
} while (wi::resourcemanager::Contains(filename)); // this is to avoid overwriting an existing imported image
}
auto resource = wi::resourcemanager::Load(
filename,
wi::resourcemanager::Flags::IMPORT_RETAIN_FILEDATA | wi::resourcemanager::Flags::IMPORT_DELAY,
(const uint8_t*)texture->content.data,
texture->content.size
);
if (!resource.IsValid())
return;
embedded_resources.push_back(resource); //retain embedded resource for whole import session
};
for (const ufbx_material* material : fbxscene->materials)
{
Entity entity = CreateEntity();
scene.Component_Attach(entity, rootEntity);
material_lookup[material] = entity;
if (material->name.length > 0)
{
scene.names.Create(entity) = material->name.data;
}
MaterialComponent& materialcomponent = scene.materials.Create(entity);
materialcomponent.SetDoubleSided(material->features.double_sided.enabled);
materialcomponent.baseColor = XMFLOAT4(material->pbr.base_color.value_vec4.v);
if (material->pbr.base_color.texture != nullptr)
{
materialcomponent.textures[MaterialComponent::BASECOLORMAP].name = material->pbr.base_color.texture->filename.data;
preload_embedded_texture(material->pbr.base_color.texture);
}
if (material->pbr.normal_map.texture != nullptr)
{
materialcomponent.textures[MaterialComponent::NORMALMAP].name = material->pbr.normal_map.texture->filename.data;
preload_embedded_texture(material->pbr.normal_map.texture);
}
if (material->fbx.specular_factor.texture != nullptr) // not from pbr.
{
materialcomponent.textures[MaterialComponent::SPECULARMAP].name = material->fbx.specular_factor.texture->filename.data;
preload_embedded_texture(material->fbx.specular_factor.texture);
}
if (material->pbr.displacement_map.texture != nullptr)
{
materialcomponent.textures[MaterialComponent::DISPLACEMENTMAP].name = material->pbr.displacement_map.texture->filename.data;
preload_embedded_texture(material->pbr.displacement_map.texture);
if (material->pbr.displacement_map.has_value)
{
materialcomponent.displacementMapping = material->pbr.displacement_map.value_real;
}
}
if (material->pbr.ambient_occlusion.texture != nullptr)
{
materialcomponent.textures[MaterialComponent::OCCLUSIONMAP].name = material->pbr.ambient_occlusion.texture->filename.data;
preload_embedded_texture(material->pbr.ambient_occlusion.texture);
materialcomponent.SetOcclusionEnabled_Secondary(true);
}
if (material->pbr.emission_color.texture != nullptr)
{
materialcomponent.textures[MaterialComponent::EMISSIVEMAP].name = material->pbr.emission_color.texture->filename.data;
preload_embedded_texture(material->pbr.emission_color.texture);
}
if (
material->shader_type == UFBX_SHADER_3DS_MAX_PBR_METAL_ROUGH ||
material->shader_type == UFBX_SHADER_GLTF_MATERIAL
)
{
materialcomponent.roughness = material->pbr.roughness.value_real;
materialcomponent.metalness = material->pbr.metalness.value_real;
materialcomponent.reflectance = material->pbr.specular_ior.value_real;
}
if (
material->shader_type == UFBX_SHADER_3DS_MAX_PBR_SPEC_GLOSS
)
{
materialcomponent.SetUseSpecularGlossinessWorkflow(true);
}
if (material->pbr.emission_color.has_value)
{
materialcomponent.emissiveColor = XMFLOAT4(material->pbr.emission_color.value_vec4.v);
}
if (material->pbr.emission_factor.has_value)
{
materialcomponent.emissiveColor.w = material->pbr.emission_factor.value_real;
}
materialcomponent.CreateRenderData();
}
if (fbxscene->materials.count == 0 && fbxscene->meshes.count > 0)
{
scene.Entity_CreateMaterial("fbximport_defaultmaterial");
}
for (const ufbx_skin_deformer* skin : fbxscene->skin_deformers)
{
Entity entity = CreateEntity();
scene.Component_Attach(entity, rootEntity);
skin_lookup[skin] = entity;
if (skin->name.length > 0)
{
scene.names.Create(entity) = skin->name.data;
}
scene.armatures.Create(entity);
scene.transforms.Create(entity);
}
for (const ufbx_mesh* mesh : fbxscene->meshes)
{
Entity entity = CreateEntity();
scene.Component_Attach(entity, rootEntity);
mesh_lookup[mesh] = entity;
if (mesh->name.length > 0)
{
scene.names.Create(entity) = mesh->name.data;
}
MeshComponent& meshcomponent = scene.meshes.Create(entity);
uint32_t vertexOffset = 0;
wi::vector<uint32_t> tri_indices(mesh->max_face_triangles * 3);
const ufbx_skin_deformer* skin = nullptr;
if (mesh->skin_deformers.count > 0)
{
skin = mesh->skin_deformers[0];
meshcomponent.armatureID = skin_lookup[skin];
}
for (const ufbx_blend_deformer* deformer : mesh->blend_deformers)
{
meshcomponent.morph_targets.resize(deformer->channels.count);
for (size_t i = 0; i < deformer->channels.count; ++i)
{
ufbx_blend_channel* channel = deformer->channels[i];
if (channel == nullptr)
continue;
meshcomponent.morph_targets[i].weight = channel->weight;
}
}
for (const ufbx_mesh_part& part : mesh->material_parts)
{
wi::vector<XMFLOAT3> positions;
wi::vector<XMFLOAT3> normals;
wi::vector<XMFLOAT2> uvset0;
wi::vector<XMFLOAT2> uvset1;
wi::vector<wi::Color> colors;
wi::vector<XMUINT4> boneindices;
wi::vector<XMFLOAT4> boneweights;
wi::vector<XMUINT4> boneindices2;
wi::vector<XMFLOAT4> boneweights2;
wi::vector<MeshComponent::MorphTarget> morphs(meshcomponent.morph_targets.size());
for (uint32_t face_index : part.face_indices)
{
ufbx_face face = mesh->faces[face_index];
uint32_t num_tris = ufbx_triangulate_face(tri_indices.data(), tri_indices.size(), mesh, face);
for (size_t i = 0; i < num_tris * 3; i++)
{
const uint32_t index = tri_indices[i];
const uint32_t vertex = mesh->vertex_indices[index];
if (mesh->vertex_position.exists)
{
positions.push_back(XMFLOAT3(mesh->vertex_position[index].v));
}
if (mesh->vertex_normal.exists)
{
normals.push_back(XMFLOAT3(mesh->vertex_normal[index].v));
}
if (mesh->vertex_uv.exists)
{
uvset0.push_back(XMFLOAT2(mesh->vertex_uv[index].v));
uvset0.back().y = 1 - uvset0.back().y;
}
if (mesh->uv_sets.count > 1)
{
uvset1.push_back(XMFLOAT2(mesh->uv_sets[1].vertex_uv[index].v));
uvset1.back().y = 1 - uvset1.back().y;
}
if (mesh->vertex_color.exists)
{
colors.push_back(wi::Color::fromFloat4(XMFLOAT4(mesh->vertex_color[index].v)));
}
if (skin)
{
boneindices.emplace_back();
boneweights.emplace_back();
ufbx_skin_vertex skin_vertex = skin->vertices[vertex];
uint32_t num_weights = skin_vertex.num_weights;
num_weights = std::min(num_weights, 8u);
if (num_weights > 4)
{
boneindices2.resize(boneindices.size());
boneweights2.resize(boneweights.size());
}
for (uint32_t i = 0; i < num_weights; ++i)
{
ufbx_skin_weight skin_weight = skin->weights[skin_vertex.weight_begin + i];
if (i < 4)
{
(&boneindices.back().x)[i] = skin_weight.cluster_index;
(&boneweights.back().x)[i] = skin_weight.weight;
}
else
{
(&boneindices2.back().x)[i - 4] = skin_weight.cluster_index;
(&boneweights2.back().x)[i - 4] = skin_weight.weight;
}
}
// Note: normalization of bone weights will be done in MeshComponent::CreateRenderData()
}
for (const ufbx_blend_deformer* deformer : mesh->blend_deformers)
{
for (size_t i = 0; i < deformer->channels.count; ++i)
{
ufbx_blend_channel* channel = deformer->channels[i];
if (channel == nullptr)
continue;
ufbx_blend_shape* shape = channel->target_shape;
if (shape == nullptr)
continue;
ufbx_vec3 vertex_offset = ufbx_get_blend_shape_vertex_offset(shape, vertex);
morphs[i].vertex_positions.push_back(XMFLOAT3(vertex_offset.v));
}
}
}
}
assert(positions.size() == part.num_triangles * 3);
wi::vector<uint32_t> indices;
indices.resize(part.num_triangles * 3);
wi::vector<ufbx_vertex_stream> streams;
if (!positions.empty())
{
streams.push_back({ positions.data(), positions.size(), sizeof(positions[0]) });
}
if (!normals.empty())
{
streams.push_back({ normals.data(), normals.size(), sizeof(normals[0]) });
}
if (!uvset0.empty())
{
streams.push_back({ uvset0.data(), uvset0.size(), sizeof(uvset0[0]) });
}
if (!uvset1.empty())
{
streams.push_back({ uvset1.data(), uvset1.size(), sizeof(uvset1[0]) });
}
if (!colors.empty())
{
streams.push_back({ colors.data(), colors.size(), sizeof(colors[0]) });
}
if (!boneindices.empty())
{
streams.push_back({ boneindices.data(), boneindices.size(), sizeof(boneindices[0]) });
}
if (!boneweights.empty())
{
streams.push_back({ boneweights.data(), boneweights.size(), sizeof(boneweights[0]) });
}
assert(boneindices.size() == boneweights.size());
if (!boneindices2.empty())
{
boneindices2.resize(boneindices.size()); // ensure same size as first bone stream, we only resized so far on demand
streams.push_back({ boneindices2.data(), boneindices2.size(), sizeof(boneindices2[0]) });
}
if (!boneweights2.empty())
{
boneweights2.resize(boneweights.size()); // ensure same size as first bone stream, we only resized so far on demand
streams.push_back({ boneweights2.data(), boneweights2.size(), sizeof(boneweights2[0]) });
}
assert(boneindices2.size() == boneweights2.size());
for (MeshComponent::MorphTarget& morph : morphs)
{
streams.push_back({ morph.vertex_positions.data(), morph.vertex_positions.size(), sizeof(morph.vertex_positions[0]) });
}
const size_t num_vertices = ufbx_generate_indices(streams.data(), streams.size(), indices.data(), indices.size(), nullptr, nullptr);
MeshComponent::MeshSubset& subset = meshcomponent.subsets.emplace_back();
subset.indexOffset = (uint32_t)meshcomponent.indices.size();
subset.indexCount = uint32_t(indices.size());
if (material_lookup.empty())
{
if (scene.materials.GetCount() > 0)
{
subset.materialID = scene.materials.GetEntity(0);
}
}
else
{
subset.materialID = material_lookup[mesh->materials[part.index]];
}
for (uint32_t index : indices)
{
meshcomponent.indices.push_back(vertexOffset + index);
}
vertexOffset += (uint32_t)num_vertices;
if (!positions.empty())
{
positions.resize(num_vertices);
meshcomponent.vertex_positions.insert(meshcomponent.vertex_positions.end(), positions.begin(), positions.end());
}
if (!normals.empty())
{
normals.resize(num_vertices);
meshcomponent.vertex_normals.insert(meshcomponent.vertex_normals.end(), normals.begin(), normals.end());
}
if (!uvset0.empty())
{
uvset0.resize(num_vertices);
meshcomponent.vertex_uvset_0.insert(meshcomponent.vertex_uvset_0.end(), uvset0.begin(), uvset0.end());
}
if (!uvset1.empty())
{
uvset1.resize(num_vertices);
meshcomponent.vertex_uvset_1.insert(meshcomponent.vertex_uvset_1.end(), uvset1.begin(), uvset1.end());
}
if (!colors.empty())
{
colors.resize(num_vertices);
meshcomponent.vertex_colors.insert(meshcomponent.vertex_colors.end(), colors.begin(), colors.end());
}
if (!boneindices.empty())
{
boneindices.resize(num_vertices);
meshcomponent.vertex_boneindices.insert(meshcomponent.vertex_boneindices.end(), boneindices.begin(), boneindices.end());
}
if (!boneweights.empty())
{
boneweights.resize(num_vertices);
meshcomponent.vertex_boneweights.insert(meshcomponent.vertex_boneweights.end(), boneweights.begin(), boneweights.end());
}
if (!boneindices2.empty())
{
boneindices2.resize(num_vertices);
meshcomponent.vertex_boneindices2.insert(meshcomponent.vertex_boneindices2.end(), boneindices2.begin(), boneindices2.end());
}
if (!boneweights2.empty())
{
boneweights2.resize(num_vertices);
meshcomponent.vertex_boneweights2.insert(meshcomponent.vertex_boneweights2.end(), boneweights2.begin(), boneweights2.end());
}
for (size_t i = 0; i < morphs.size(); ++i)
{
morphs[i].vertex_positions.resize(num_vertices);
meshcomponent.morph_targets[i].vertex_positions.insert(meshcomponent.morph_targets[i].vertex_positions.end(), morphs[i].vertex_positions.begin(), morphs[i].vertex_positions.end());
}
if (!meshcomponent.vertex_normals.empty())
{
meshcomponent.vertex_normals.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_uvset_0.empty())
{
meshcomponent.vertex_uvset_0.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_uvset_1.empty())
{
meshcomponent.vertex_uvset_1.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_colors.empty())
{
meshcomponent.vertex_colors.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_boneindices.empty())
{
meshcomponent.vertex_boneindices.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_boneweights.empty())
{
meshcomponent.vertex_boneweights.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_boneindices2.empty())
{
meshcomponent.vertex_boneindices2.resize(meshcomponent.vertex_positions.size());
}
if (!meshcomponent.vertex_boneweights2.empty())
{
meshcomponent.vertex_boneweights2.resize(meshcomponent.vertex_positions.size());
}
}
meshcomponent.CreateRenderData();
}
for (const ufbx_node* node : fbxscene->nodes)
{
if (node->is_root)
continue;
Entity entity = CreateEntity();
node_lookup[node] = entity;
if (node->name.length > 0)
{
scene.names.Create(entity) = node->name.data;
}
TransformComponent& transform = scene.transforms.Create(entity);
transform.scale_local.x = node->local_transform.scale.x;
transform.scale_local.y = node->local_transform.scale.y;
transform.scale_local.z = node->local_transform.scale.z;
transform.rotation_local.x = node->local_transform.rotation.x;
transform.rotation_local.y = node->local_transform.rotation.y;
transform.rotation_local.z = node->local_transform.rotation.z;
transform.rotation_local.w = node->local_transform.rotation.w;
transform.translation_local.x = node->local_transform.translation.x;
transform.translation_local.y = node->local_transform.translation.y;
transform.translation_local.z = node->local_transform.translation.z;
if (node->mesh)
{
ObjectComponent& object = scene.objects.Create(entity);
object.meshID = mesh_lookup[node->mesh];
}
if (node->light)
{
LightComponent& light = scene.lights.Create(entity);
light.SetCastShadow(node->light->cast_shadows);
light.color = XMFLOAT3(node->light->color.v);
light.direction = XMFLOAT3(node->light->local_direction.v);
light.innerConeAngle = node->light->inner_angle;
light.outerConeAngle = node->light->outer_angle;
switch (node->light->type)
{
default:
case UFBX_LIGHT_POINT:
light.SetType(LightComponent::LightType::POINT);
break;
case UFBX_LIGHT_SPOT:
light.SetType(LightComponent::LightType::SPOT);
break;
case UFBX_LIGHT_DIRECTIONAL:
light.SetType(LightComponent::LightType::DIRECTIONAL);
break;
}
}
if (node->camera)
{
CameraComponent& camera = scene.cameras.Create(entity);
camera.zNearP = node->camera->near_plane * 0.01f;
camera.zFarP = node->camera->far_plane * 0.01f;
camera.fov = wi::math::DegreesToRadians(node->camera->field_of_view_deg.y);
}
}
// After all nodes were iterated, determine hierarchy:
for (const ufbx_node* node : fbxscene->nodes)
{
if (node->is_root)
continue;
Entity entity = node_lookup[node];
if (node->parent)
{
Entity parent = node_lookup[node->parent];
scene.Component_Attach(entity, parent, true);
}
else
{
scene.Component_Attach(entity, rootEntity, true);
}
}
// After nodes are complete, fill bones:
for (const ufbx_skin_deformer* skin : fbxscene->skin_deformers)
{
Entity entity = skin_lookup[skin];
ArmatureComponent* armature = scene.armatures.GetComponent(entity);
if (armature == nullptr)
continue;
for (const ufbx_skin_cluster* cluster : skin->clusters)
{
Entity boneEntity = node_lookup[cluster->bone_node];
armature->boneCollection.push_back(boneEntity);
XMFLOAT4X4 inverseBindMatrix = wi::math::IDENTITY_MATRIX;
inverseBindMatrix._11 = cluster->geometry_to_bone.m00;
inverseBindMatrix._12 = cluster->geometry_to_bone.m10;
inverseBindMatrix._13 = cluster->geometry_to_bone.m20;
inverseBindMatrix._21 = cluster->geometry_to_bone.m01;
inverseBindMatrix._22 = cluster->geometry_to_bone.m11;
inverseBindMatrix._23 = cluster->geometry_to_bone.m21;
inverseBindMatrix._31 = cluster->geometry_to_bone.m02;
inverseBindMatrix._32 = cluster->geometry_to_bone.m12;
inverseBindMatrix._33 = cluster->geometry_to_bone.m22;
inverseBindMatrix._41 = cluster->geometry_to_bone.m03;
inverseBindMatrix._42 = cluster->geometry_to_bone.m13;
inverseBindMatrix._43 = cluster->geometry_to_bone.m23;
armature->inverseBindMatrices.push_back(inverseBindMatrix);
Import_Mixamo_Bone(scene, rootEntity, boneEntity);
}
}
if (scene.armatures.GetCount() == 0 && fbxscene->bones.count > 0)
{
// Hack: if scene has no skins but it has bones, then a global armature will be created that includes them
// Unfortunately it seems that there is no way to properly find an armature in ufbx without that doesn't skin a mesh
// But I want to have the ability to import armatures without mesh for example to use in an animation library
ArmatureComponent& armatureWithoutMesh = scene.armatures.Create(rootEntity);
for (const ufbx_bone* bone : fbxscene->bones)
{
const ufbx_node* node = fbxscene->nodes[bone->element_id];
Entity boneEntity = node_lookup[node];
armatureWithoutMesh.boneCollection.push_back(boneEntity);
XMFLOAT4X4 inverseBindMatrix = wi::math::IDENTITY_MATRIX;
inverseBindMatrix._11 = node->geometry_to_world.m00;
inverseBindMatrix._12 = node->geometry_to_world.m10;
inverseBindMatrix._13 = node->geometry_to_world.m20;
inverseBindMatrix._21 = node->geometry_to_world.m01;
inverseBindMatrix._22 = node->geometry_to_world.m11;
inverseBindMatrix._23 = node->geometry_to_world.m21;
inverseBindMatrix._31 = node->geometry_to_world.m02;
inverseBindMatrix._32 = node->geometry_to_world.m12;
inverseBindMatrix._33 = node->geometry_to_world.m22;
inverseBindMatrix._41 = node->geometry_to_world.m03;
inverseBindMatrix._42 = node->geometry_to_world.m13;
inverseBindMatrix._43 = node->geometry_to_world.m23;
armatureWithoutMesh.inverseBindMatrices.push_back(inverseBindMatrix);
Import_Mixamo_Bone(scene, rootEntity, boneEntity);
}
}
for (const ufbx_anim_stack* stack : fbxscene->anim_stacks)
{
ufbx_baked_anim* bake = ufbx_bake_anim(fbxscene, stack->anim, nullptr, nullptr);
assert(bake != nullptr);
if (bake == nullptr)
continue;
Entity entity = CreateEntity();
scene.Component_Attach(entity, rootEntity);
AnimationComponent& animcomponent = scene.animations.Create(entity);
animcomponent.start = (float)bake->playback_time_begin;
animcomponent.end = (float)bake->playback_time_end;
for (const ufbx_baked_node& bake_node : bake->nodes)
{
ufbx_node* scene_node = fbxscene->nodes[bake_node.typed_id];
Entity target = node_lookup[scene_node];
// Translation:
{
Entity animDataEntity = CreateEntity();
AnimationDataComponent& animationdata = scene.animation_datas.Create(animDataEntity);
scene.Component_Attach(animDataEntity, entity);
for (const ufbx_baked_vec3& keyframe : bake_node.translation_keys)
{
animationdata.keyframe_times.push_back((float)keyframe.time);
animationdata.keyframe_data.push_back(keyframe.value.x);
animationdata.keyframe_data.push_back(keyframe.value.y);
animationdata.keyframe_data.push_back(keyframe.value.z);
}
AnimationComponent::AnimationChannel& channel = animcomponent.channels.emplace_back();
channel.target = target;
channel.path = AnimationComponent::AnimationChannel::Path::TRANSLATION;
channel.samplerIndex = (int)animcomponent.samplers.size();
AnimationComponent::AnimationSampler& sampler = animcomponent.samplers.emplace_back();
sampler.mode = AnimationComponent::AnimationSampler::Mode::LINEAR;
sampler.data = animDataEntity;
}
// Rotation:
{
Entity animDataEntity = CreateEntity();
AnimationDataComponent& animationdata = scene.animation_datas.Create(animDataEntity);
scene.Component_Attach(animDataEntity, entity);
for (const ufbx_baked_quat& keyframe : bake_node.rotation_keys)
{
animationdata.keyframe_times.push_back((float)keyframe.time);
animationdata.keyframe_data.push_back(keyframe.value.x);
animationdata.keyframe_data.push_back(keyframe.value.y);
animationdata.keyframe_data.push_back(keyframe.value.z);
animationdata.keyframe_data.push_back(keyframe.value.w);
}
AnimationComponent::AnimationChannel& channel = animcomponent.channels.emplace_back();
channel.target = target;
channel.path = AnimationComponent::AnimationChannel::Path::ROTATION;
channel.samplerIndex = (int)animcomponent.samplers.size();
AnimationComponent::AnimationSampler& sampler = animcomponent.samplers.emplace_back();
sampler.mode = AnimationComponent::AnimationSampler::Mode::LINEAR;
sampler.data = animDataEntity;
}
// Scale:
{
Entity animDataEntity = CreateEntity();
AnimationDataComponent& animationdata = scene.animation_datas.Create(animDataEntity);
scene.Component_Attach(animDataEntity, entity);
for (const ufbx_baked_vec3& keyframe : bake_node.scale_keys)
{
animationdata.keyframe_times.push_back((float)keyframe.time);
animationdata.keyframe_data.push_back(keyframe.value.x);
animationdata.keyframe_data.push_back(keyframe.value.y);
animationdata.keyframe_data.push_back(keyframe.value.z);
}
AnimationComponent::AnimationChannel& channel = animcomponent.channels.emplace_back();
channel.target = target;
channel.path = AnimationComponent::AnimationChannel::Path::SCALE;
channel.samplerIndex = (int)animcomponent.samplers.size();
AnimationComponent::AnimationSampler& sampler = animcomponent.samplers.emplace_back();
sampler.mode = AnimationComponent::AnimationSampler::Mode::LINEAR;
sampler.data = animDataEntity;
}
}
ufbx_free_baked_anim(bake);
}
ufbx_free_scene(fbxscene);
// Note: we use custom FlipZAxis function for transforming to left-handed space
// because it works better than ufbx library's space conversion helpers, especially for ragdolls that will be generated
wi::unordered_map<size_t, TransformComponent> transforms_original; // original transform states, can be used to restore after flip (like GLTF export)
FlipZAxis(scene, rootEntity, transforms_original);
scene.Update(0);
}
void FlipZAxis(Scene& scene, Entity rootEntity, wi::unordered_map<size_t, TransformComponent>& transforms_original)
{
// Flip mesh data first
for (size_t i = 0; i < scene.meshes.GetCount(); ++i)
{
auto& mesh = scene.meshes[i];
for (auto& v_pos : mesh.vertex_positions)
{
v_pos.z *= -1.f;
}
for (auto& v_norm : mesh.vertex_normals)
{
v_norm.z *= -1.f;
}
for (auto& v_tan : mesh.vertex_tangents)
{
v_tan.z *= -1.f;
}
for (auto& v_morph : mesh.morph_targets)
{
for (auto& v_morph_norm : v_morph.vertex_normals)
{
v_morph_norm.z *= -1.f;
}
for (auto& v_morph_pos : v_morph.vertex_positions)
{
v_morph_pos.z *= -1.f;
}
}
mesh.CreateRenderData();
}
// Flip scene's transformComponents
bool state_restore = (transforms_original.size() > 0);
if (!state_restore)
{
wi::unordered_map<wi::ecs::Entity, wi::ecs::Entity> hierarchy_list;
wi::unordered_map<size_t, wi::scene::TransformComponent> correction_queue;
for (size_t i = 0; i < scene.transforms.GetCount(); ++i)
{
auto transformEntity = scene.transforms.GetEntity(i);
if (transformEntity == rootEntity)
continue;
correction_queue[i] = scene.transforms[i];
auto hierarchy = scene.hierarchy.GetComponent(transformEntity);
if (transformEntity != rootEntity && hierarchy != nullptr)
{
hierarchy_list[transformEntity] = hierarchy->parentID;
scene.Component_Detach(transformEntity);
}
}
transforms_original.insert(correction_queue.begin(), correction_queue.end());
for (auto& correction_pair : correction_queue)
{
auto& transform = scene.transforms[correction_pair.first];
auto& transform_original = correction_pair.second;
XMVECTOR V_S, V_R, V_T;
XMMatrixDecompose(&V_S, &V_R, &V_T, XMLoadFloat4x4(&transform_original.world));
XMFLOAT3 pos, scale;
XMFLOAT4 rot;
XMStoreFloat3(&scale, V_S);
XMStoreFloat3(&pos, V_T);
XMStoreFloat4(&rot, V_R);
pos.z *= -1.f;
rot.x *= -1.f;
rot.y *= -1.f;
auto build_m =
XMMatrixScalingFromVector(XMLoadFloat3(&scale)) *
XMMatrixRotationQuaternion(XMLoadFloat4(&rot)) *
XMMatrixTranslationFromVector(XMLoadFloat3(&pos));
XMFLOAT4X4 build_m4;
XMStoreFloat4x4(&build_m4, build_m);
transform.world = build_m4;
transform.ApplyTransform();
}
for (auto& hierarchy_pair : hierarchy_list)
{
scene.Component_Attach(hierarchy_pair.first, hierarchy_pair.second);
}
}
else
{
for (size_t i = 0; i < scene.transforms.GetCount(); ++i)
{
auto transform_original_find = transforms_original.find(i);
if (transform_original_find != transforms_original.end())
{
auto& transform = scene.transforms[i];
transform = transform_original_find->second;
}
}
transforms_original.clear();
}
// Flip armature's bind pose
for (size_t i = 0; i < scene.armatures.GetCount(); ++i)
{
auto& armature = scene.armatures[i];
for (int i = 0; i < armature.inverseBindMatrices.size(); ++i)
{
auto& bind = armature.inverseBindMatrices[i];
XMVECTOR V_S, V_R, V_T;
XMMatrixDecompose(&V_S, &V_R, &V_T, XMLoadFloat4x4(&bind));
XMFLOAT3 pos, scale;
XMFLOAT4 rot;
XMStoreFloat3(&scale, V_S);
XMStoreFloat3(&pos, V_T);
XMStoreFloat4(&rot, V_R);
pos.z *= -1.f;
rot.x *= -1.f;
rot.y *= -1.f;
auto build_m =
XMMatrixScalingFromVector(XMLoadFloat3(&scale)) *
XMMatrixRotationQuaternion(XMLoadFloat4(&rot)) *
XMMatrixTranslationFromVector(XMLoadFloat3(&pos));
XMFLOAT4X4 build_m4;
XMStoreFloat4x4(&build_m4, build_m);
bind = build_m4;
}
}
// Flip animation data for translation and rotation
for (size_t i = 0; i < scene.animations.GetCount(); ++i)
{
auto& animation = scene.animations[i];
for (auto& channel : animation.channels)
{
auto data = scene.animation_datas.GetComponent(animation.samplers[channel.samplerIndex].data);
if (channel.path == wi::scene::AnimationComponent::AnimationChannel::Path::TRANSLATION)
{
for (size_t k = 0; k < data->keyframe_data.size() / 3; ++k)
{
data->keyframe_data[k * 3 + 2] *= -1.f;
}
}
if (channel.path == wi::scene::AnimationComponent::AnimationChannel::Path::ROTATION)
{
for (size_t k = 0; k < data->keyframe_data.size() / 4; ++k)
{
data->keyframe_data[k * 4] *= -1.f;
data->keyframe_data[k * 4 + 1] *= -1.f;
}
}
}
}
}
void Import_Mixamo_Bone(Scene& scene, Entity rootEntity, Entity boneEntity)
{
auto get_humanoid = [&]() -> HumanoidComponent& {
if (scene.humanoids.GetCount() == 0)
{
scene.humanoids.Create(rootEntity);
}
// Note: it seems there are multiple armatures for multiple body parts in some FBX from Mixamo,
// but there should be only one humanoid, so we don't create humanoids for each armature
HumanoidComponent& component = scene.humanoids[0];
return component;
};
const NameComponent* namecomponent = scene.names.GetComponent(boneEntity);
if (namecomponent == nullptr)
return;
const std::string& name = namecomponent->name;
if (!name.compare("mixamorig:Hips"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Hips)] = boneEntity;
}
else if (!name.compare("mixamorig:Spine"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Spine)] = boneEntity;
}
else if (!name.compare("mixamorig:Spine1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Chest)] = boneEntity;
}
else if (!name.compare("mixamorig:Spine2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::UpperChest)] = boneEntity;
}
else if (!name.compare("mixamorig:Neck"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Neck)] = boneEntity;
}
else if (!name.compare("mixamorig:Head"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Head)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftShoulder"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftShoulder)] = boneEntity;
}
else if (!name.compare("mixamorig:RightShoulder"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightShoulder)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftArm"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperArm)] = boneEntity;
}
else if (!name.compare("mixamorig:RightArm"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightUpperArm)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftForeArm"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerArm)] = boneEntity;
}
else if (!name.compare("mixamorig:RightForeArm"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLowerArm)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHand"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftHand)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHand"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightHand)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandThumb1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbMetacarpal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandThumb1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbMetacarpal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandThumb2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandThumb2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandThumb3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandThumb3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandIndex1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandIndex1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandIndex2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandIndex2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandIndex3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandIndex3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandMiddle1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandMiddle1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandMiddle2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandMiddle2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandMiddle3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandMiddle3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandRing1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandRing1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandRing2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandRing2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandRing3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandRing3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandPinky1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandPinky1"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleProximal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandPinky2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandPinky2"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleIntermediate)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftHandPinky3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:RightHandPinky3"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleDistal)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftUpLeg"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperLeg)] = boneEntity;
}
else if (!name.compare("mixamorig:RightUpLeg"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightUpperLeg)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftLeg"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerLeg)] = boneEntity;
}
else if (!name.compare("mixamorig:RightLeg"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLowerLeg)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftFoot"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)] = boneEntity;
}
else if (!name.compare("mixamorig:RightFoot"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)] = boneEntity;
}
else if (!name.compare("mixamorig:LeftToeBase"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftToes)] = boneEntity;
}
else if (!name.compare("mixamorig:RightToeBase"))
{
get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightToes)] = boneEntity;
}
}