// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #include "ImportModel.h" #if COMPILE_WITH_ASSETS_IMPORTER #include "Engine/Core/Log.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Graphics/Models/ModelData.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Content/Storage/ContentStorageManager.h" #include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Content.h" #include "Engine/Platform/FileSystem.h" bool ImportModelFile::TryGetImportOptions(String path, Options& options) { #if IMPORT_MODEL_CACHE_OPTIONS // Check if target asset file exists if (FileSystem::FileExists(path)) { // Try to load asset file and asset info auto tmpFile = ContentStorageManager::GetStorage(path); AssetInitData data; if (tmpFile && tmpFile->GetEntriesCount() == 1 && ( (tmpFile->GetEntry(0).TypeName == Model::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 4) || (tmpFile->GetEntry(0).TypeName == SkinnedModel::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1) || (tmpFile->GetEntry(0).TypeName == Animation::TypeName && !tmpFile->LoadAssetHeader(0, data) && data.SerializedVersion >= 1) )) { // Check import meta rapidjson_flax::Document metadata; metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length()); if (metadata.HasParseError() == false) { // Success options.Deserialize(metadata, nullptr); return true; } } } #endif return false; } void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData) { // Skip if file is missing if (!FileSystem::FileExists(context.TargetAssetPath)) return; // Try to load asset that gets reimported AssetReference asset = Content::LoadAsync(context.TargetAssetPath); if (asset == nullptr) return; if (asset->WaitForLoaded()) return; // Get model object ModelBase* model = nullptr; if (asset.Get()->GetTypeName() == Model::TypeName) { model = ((Model*)asset.Get()); } else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName) { model = ((SkinnedModel*)asset.Get()); } if (!model) return; // Peek materials for (int32 i = 0; i < modelData.Materials.Count(); i++) { auto& dstSlot = modelData.Materials[i]; if (model->MaterialSlots.Count() > i) { auto& srcSlot = model->MaterialSlots[i]; dstSlot.Name = srcSlot.Name; dstSlot.ShadowsMode = srcSlot.ShadowsMode; dstSlot.AssetID = srcSlot.Material.GetID(); } } } CreateAssetResult ImportModelFile::Import(CreateAssetContext& context) { // Get import options Options options; if (context.CustomArg != nullptr) { // Copy import options from argument options = *static_cast(context.CustomArg); } else { // Restore the previous settings or use default ones if (!TryGetImportOptions(context.TargetAssetPath, options)) { LOG(Warning, "Missing model import options. Using default values."); } } // Import model file ModelData modelData; String errorMsg; if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, StringUtils::GetDirectoryName(context.TargetAssetPath) / StringUtils::GetFileNameWithoutExtension(context.InputPath))) { LOG(Error, "Cannot import model file. {0}", errorMsg); return CreateAssetResult::Error; } // Check if restore materials on model reimport if (options.RestoreMaterialsOnReimport && modelData.Materials.HasItems()) { TryRestoreMaterials(context, modelData); } // Auto calculate LODs transition settings modelData.CalculateLODsScreenSizes(); // Create destination asset type CreateAssetResult result = CreateAssetResult::InvalidTypeID; switch (options.Type) { case ModelTool::ModelType::Model: result = ImportModel(context, modelData); break; case ModelTool::ModelType::SkinnedModel: result = ImportSkinnedModel(context, modelData); break; case ModelTool::ModelType::Animation: result = ImportAnimation(context, modelData); break; } if (result != CreateAssetResult::Ok) return result; #if IMPORT_MODEL_CACHE_OPTIONS // Create json with import context rapidjson_flax::StringBuffer importOptionsMetaBuffer; importOptionsMetaBuffer.Reserve(256); CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer); JsonWriter& importOptionsMeta = importOptionsMetaObj; importOptionsMeta.StartObject(); { context.AddMeta(importOptionsMeta); options.Serialize(importOptionsMeta, nullptr); } importOptionsMeta.EndObject(); context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize()); #endif return CreateAssetResult::Ok; } CreateAssetResult ImportModelFile::Create(CreateAssetContext& context) { ASSERT(context.CustomArg != nullptr); auto& modelData = *(ModelData*)context.CustomArg; // Ensure model has any meshes if ((modelData.LODs.IsEmpty() || modelData.LODs[0].Meshes.IsEmpty())) { LOG(Warning, "Models has no valid meshes"); return CreateAssetResult::Error; } // Auto calculate LODs transition settings modelData.CalculateLODsScreenSizes(); // Import return ImportModel(context, modelData); } CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData) { // Base IMPORT_SETUP(Model, Model::SerializedVersion); // Save model header MemoryWriteStream stream(4096); if (modelData.Pack2ModelHeader(&stream)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); // Pack model LODs data const auto lodCount = modelData.GetLODsCount(); for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { stream.SetPosition(0); // Pack meshes auto& meshes = modelData.LODs[lodIndex].Meshes; for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) { if (meshes[meshIndex]->Pack2Model(&stream)) { LOG(Warning, "Cannot pack mesh."); return CreateAssetResult::Error; } } const int32 chunkIndex = lodIndex + 1; if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); } return CreateAssetResult::Ok; } CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData) { // Base IMPORT_SETUP(SkinnedModel, 4); // Save skinned model header MemoryWriteStream stream(4096); if (modelData.Pack2SkinnedModelHeader(&stream)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); // Pack model LODs data const auto lodCount = modelData.GetLODsCount(); for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++) { stream.SetPosition(0); // Pack meshes auto& meshes = modelData.LODs[lodIndex].Meshes; for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) { if (meshes[meshIndex]->Pack2SkinnedModel(&stream)) { LOG(Warning, "Cannot pack mesh."); return CreateAssetResult::Error; } } const int32 chunkIndex = lodIndex + 1; if (context.AllocateChunk(chunkIndex)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[chunkIndex]->Data.Copy(stream.GetHandle(), stream.GetPosition()); } return CreateAssetResult::Ok; } CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData) { // Base IMPORT_SETUP(Animation, Animation::SerializedVersion); // Save animation data MemoryWriteStream stream(8182); if (modelData.Pack2AnimationHeader(&stream)) return CreateAssetResult::Error; if (context.AllocateChunk(0)) return CreateAssetResult::CannotAllocateChunk; context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition()); return CreateAssetResult::Ok; } #endif