#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& transforms_original); void Import_Mixamo_Bone(Scene& scene, Entity rootEntity, Entity boneEntity); void Import_Makehuman_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 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 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 material_lookup; wi::unordered_map skin_lookup; wi::unordered_map mesh_lookup; wi::unordered_map 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 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: filename = "fbximport_" + std::to_string(wi::helper::HashByteData((const uint8_t*)texture->content.data, texture->content.size)) + ".png"; } 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 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 positions; wi::vector normals; wi::vector uvset0; wi::vector uvset1; wi::vector colors; wi::vector boneindices; wi::vector boneweights; wi::vector boneindices2; wi::vector boneweights2; wi::vector 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 indices; indices.resize(part.num_triangles * 3); wi::vector 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); Import_Makehuman_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; XMStoreFloat4x4(&inverseBindMatrix, XMMatrixInverse(nullptr, XMLoadFloat4x4(&inverseBindMatrix))); armatureWithoutMesh.inverseBindMatrices.push_back(inverseBindMatrix); Import_Mixamo_Bone(scene, rootEntity, boneEntity); Import_Makehuman_Bone(scene, rootEntity, boneEntity); } } // Invalid humanoids will be removed, this is to not have humanoid if naming convention matched by mistake when trying to import humanoid bone: for (size_t i = 0; i < scene.humanoids.GetCount();) { if (!scene.humanoids[i].IsValid()) { scene.humanoids.Remove(scene.humanoids.GetEntity(i)); } else { i++; } } 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; if (stack->name.length > 0) { NameComponent& name = scene.names.Create(entity); name = stack->name.data; if (name == "mixamo.com") { // Mixamo animation name is converted to file name without extension for easy animation importing and differentiation: name = wi::helper::RemoveExtension(wi::helper::GetFileNameFromPath(filename)); } } 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 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& 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 hierarchy_list; wi::unordered_map 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; } } void Import_Makehuman_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("pelvis")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Hips)] = boneEntity; } else if (!name.compare("spine_01")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Spine)] = boneEntity; } else if (!name.compare("spine_02")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Chest)] = boneEntity; } else if (!name.compare("spine_03")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::UpperChest)] = boneEntity; } else if (!name.compare("neck_01")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Neck)] = boneEntity; } else if (!name.compare("head")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::Head)] = boneEntity; } else if (!name.compare("clavicle_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftShoulder)] = boneEntity; } else if (!name.compare("clavicle_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightShoulder)] = boneEntity; } else if (!name.compare("upperarm_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperArm)] = boneEntity; } else if (!name.compare("upperarm_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightUpperArm)] = boneEntity; } else if (!name.compare("lowerarm_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerArm)] = boneEntity; } else if (!name.compare("lowerarm_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLowerArm)] = boneEntity; } else if (!name.compare("hand_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftHand)] = boneEntity; } else if (!name.compare("hand_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightHand)] = boneEntity; } else if (!name.compare("thumb_01_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbMetacarpal)] = boneEntity; } else if (!name.compare("thumb_01_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbMetacarpal)] = boneEntity; } else if (!name.compare("thumb_02_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbProximal)] = boneEntity; } else if (!name.compare("thumb_02_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbProximal)] = boneEntity; } else if (!name.compare("thumb_03_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftThumbDistal)] = boneEntity; } else if (!name.compare("thumb_03_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightThumbDistal)] = boneEntity; } else if (!name.compare("index_01_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexProximal)] = boneEntity; } else if (!name.compare("index_01_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexProximal)] = boneEntity; } else if (!name.compare("index_02_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexIntermediate)] = boneEntity; } else if (!name.compare("index_02_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexIntermediate)] = boneEntity; } else if (!name.compare("index_03_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftIndexDistal)] = boneEntity; } else if (!name.compare("index_03_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightIndexDistal)] = boneEntity; } else if (!name.compare("middle_01_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleProximal)] = boneEntity; } else if (!name.compare("middle_01_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleProximal)] = boneEntity; } else if (!name.compare("middle_02_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleIntermediate)] = boneEntity; } else if (!name.compare("middle_02_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleIntermediate)] = boneEntity; } else if (!name.compare("middle_03_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftMiddleDistal)] = boneEntity; } else if (!name.compare("middle_03_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightMiddleDistal)] = boneEntity; } else if (!name.compare("ring_01_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingProximal)] = boneEntity; } else if (!name.compare("ring_01_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingProximal)] = boneEntity; } else if (!name.compare("ring_02_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingIntermediate)] = boneEntity; } else if (!name.compare("ring_02_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingIntermediate)] = boneEntity; } else if (!name.compare("ring_03_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftRingDistal)] = boneEntity; } else if (!name.compare("ring_03_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightRingDistal)] = boneEntity; } else if (!name.compare("pinky_01_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleProximal)] = boneEntity; } else if (!name.compare("pinky_01_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleProximal)] = boneEntity; } else if (!name.compare("pinky_02_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleIntermediate)] = boneEntity; } else if (!name.compare("pinky_02_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleIntermediate)] = boneEntity; } else if (!name.compare("pinky_03_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLittleDistal)] = boneEntity; } else if (!name.compare("pinky_03_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLittleDistal)] = boneEntity; } else if (!name.compare("thigh_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftUpperLeg)] = boneEntity; } else if (!name.compare("thigh_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightUpperLeg)] = boneEntity; } else if (!name.compare("calf_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftLowerLeg)] = boneEntity; } else if (!name.compare("calf_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightLowerLeg)] = boneEntity; } else if (!name.compare("foot_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftFoot)] = boneEntity; } else if (!name.compare("foot_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightFoot)] = boneEntity; } else if (!name.compare("ball_l")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::LeftToes)] = boneEntity; } else if (!name.compare("ball_r")) { get_humanoid().bones[size_t(HumanoidComponent::HumanoidBone::RightToes)] = boneEntity; } }