From fb31f9d1e594ef1561b7023fff468004d7cbec86 Mon Sep 17 00:00:00 2001 From: Areloch Date: Sun, 10 May 2020 03:48:08 -0500 Subject: [PATCH] Implements the Asset Importer integrated into the engine, avoiding the need for calldowns into the tool suite to import assets --- Engine/source/T3D/assets/assetImporter.cpp | 1912 +++++++++++++++++ Engine/source/T3D/assets/assetImporter.h | 680 ++++++ .../T3D/assets/assetImporter_ScriptBinding.h | 140 ++ 3 files changed, 2732 insertions(+) create mode 100644 Engine/source/T3D/assets/assetImporter.cpp create mode 100644 Engine/source/T3D/assets/assetImporter.h create mode 100644 Engine/source/T3D/assets/assetImporter_ScriptBinding.h diff --git a/Engine/source/T3D/assets/assetImporter.cpp b/Engine/source/T3D/assets/assetImporter.cpp new file mode 100644 index 000000000..c96eb1681 --- /dev/null +++ b/Engine/source/T3D/assets/assetImporter.cpp @@ -0,0 +1,1912 @@ +#include "assetImporter.h" +#include "assetImporter_ScriptBinding.h" +#include "core/strings/findMatch.h" +#include "ImageAsset.h" +#include "ShapeAsset.h" +#include "SoundAsset.h" +#include "MaterialAsset.h" +#include "ShapeAnimationAsset.h" + +#include "ts/collada/colladaUtils.h" +#include "ts/collada/colladaAppNode.h" +#include "ts/collada/colladaShapeLoader.h" + +#include "ts/assimp/assimpShapeLoader.h" +#include + + +ConsoleDocClass(AssetImportConfig, + "@brief Defines properties for an AssetImprotConfig object.\n" + "@AssetImportConfig is a SimObject derived object intended to act as a container for all the necessary configuration data when running the Asset Importer.\n" + "@It dictates if and how any given asset type will be processed when running an import action. This is because the Asset Importer utilizes a lot of informed logic\n" + "@to try and automate as much of the import process as possible. In theory, you would run the import on a given file, and based on your config the importer will do\n" + "@everything from importing the designated file, as well as finding and importing any associated files such as images or materials, and prepping the objects at time\n" + "@of import to avoid as much manual post-processing as possible.\n\n" + "@ingroup Assets\n" +); + +IMPLEMENT_CONOBJECT(AssetImportConfig); + +AssetImportConfig::AssetImportConfig() : + DuplicatAutoResolution("AutoRename"), + WarningsAsErrors(false), + PreventImportWithErrors(true), + AutomaticallyPromptMissingFiles(false), + ImportMesh(true), + DoUpAxisOverride(false), + UpAxisOverride("Z_AXIS"), + DoScaleOverride(false), + ScaleOverride(false), + IgnoreNodeScale(false), + AdjustCenter(false), + AdjustFloor(false), + CollapseSubmeshes(false), + LODType("TrailingNumber"), + ImportedNodes(""), + IgnoreNodes(""), + ImportMeshes(""), + IgnoreMeshes(""), + convertLeftHanded(false), + calcTangentSpace(false), + removeRedundantMats(false), + genUVCoords(false), + TransformUVs(false), + flipUVCoords(false), + findInstances(false), + limitBoneWeights(false), + JoinIdenticalVerts(false), + reverseWindingOrder(false), + invertNormals(false), + ImportMaterials(true), + CreatePBRConfig(true), + UseDiffuseSuffixOnOriginImage(false), + UseExistingMaterials(false), + IgnoreMaterials(""), + PopulateMaterialMaps(false), + ImportAnimations(true), + SeparateAnimations(false), + SeparateAnimationPrefix(""), + animTiming("FrameCount"), + animFPS(false), + GenerateCollisions(false), + GenCollisionType(""), + CollisionMeshPrefix(""), + GenerateLOSCollisions(false), + GenLOSCollisionType(""), + LOSCollisionMeshPrefix(""), + importImages(true), + ImageType("GUI"), + DiffuseTypeSuffixes("_ALBEDO,_DIFFUSE,_ALB,_DIF,_COLOR,_COL,_A,_C,-ALBEDO,-DIFFUSE,-ALB,-DIF,-COLOR,-COL,-A,-C"), + NormalTypeSuffixes("_NORMAL,_NORM,_N,-NORMAL,-NORM,-N"), + MetalnessTypeSuffixes("_METAL,_MET,_METALNESS,_METALLIC,_M,-METAL, -MET, -METALNESS, -METALLIC, -M"), + RoughnessTypeSuffixes("_ROUGH,_ROUGHNESS,_R,-ROUGH,-ROUGHNESS,-R"), + SmoothnessTypeSuffixes("_SMOOTH,_SMOOTHNESS,_S,-SMOOTH,-SMOOTHNESS,-S"), + AOTypeSuffixes("_AO,_AMBIENT,_AMBIENTOCCLUSION,-AO,-AMBIENT,-AMBIENTOCCLUSION"), + PBRTypeSuffixes("_COMP,_COMPOSITE,_PBR,-COMP,-COMPOSITE,-PBR"), + TextureFilteringMode("Bilinear"), + UseMips(true), + IsHDR(false), + Scaling(false), + Compresse(false), + GenerateMaterialOnImport(true), + importSounds(true), + VolumeAdjust(false), + PitchAdjust(false), + Compressed(false) +{ +} + +AssetImportConfig::~AssetImportConfig() +{ + +} + +/// Engine. +void AssetImportConfig::initPersistFields() +{ + Parent::initPersistFields(); +} + +void AssetImportConfig::loadImportConfig(Settings* configSettings, String configName) +{ + //General + DuplicatAutoResolution = configSettings->value(String(configName + "/General/DuplicatAutoResolution").c_str()); + WarningsAsErrors = dAtob(configSettings->value(String(configName + "/General/WarningsAsErrors").c_str())); + PreventImportWithErrors = dAtob(configSettings->value(String(configName + "/General/PreventImportWithErrors").c_str())); + AutomaticallyPromptMissingFiles = dAtob(configSettings->value(String(configName + "/General/AutomaticallyPromptMissingFiles").c_str())); + + //Meshes + ImportMesh = dAtob(configSettings->value(String(configName + "/Meshes/ImportMesh").c_str())); + DoUpAxisOverride = dAtob(configSettings->value(String(configName + "/Meshes/DoUpAxisOverride").c_str())); + UpAxisOverride = configSettings->value(String(configName + "/Meshes/UpAxisOverride").c_str()); + DoScaleOverride = dAtob(configSettings->value(String(configName + "/Meshes/DoScaleOverride").c_str())); + ScaleOverride = dAtof(configSettings->value(String(configName + "/Meshes/ScaleOverride").c_str())); + IgnoreNodeScale = dAtob(configSettings->value(String(configName + "/Meshes/IgnoreNodeScale").c_str())); + AdjustCenter = dAtob(configSettings->value(String(configName + "/Meshes/AdjustCenter").c_str())); + AdjustFloor = dAtob(configSettings->value(String(configName + "/Meshes/AdjustFloor").c_str())); + CollapseSubmeshes = dAtob(configSettings->value(String(configName + "/Meshes/CollapseSubmeshes").c_str())); + LODType = configSettings->value(String(configName + "/Meshes/LODType").c_str()); + ImportedNodes = configSettings->value(String(configName + "/Meshes/ImportedNodes").c_str()); + IgnoreNodes = configSettings->value(String(configName + "/Meshes/IgnoreNodes").c_str()); + ImportMeshes = configSettings->value(String(configName + "/Meshes/ImportMeshes").c_str()); + IgnoreMeshes = configSettings->value(String(configName + "/Meshes/IgnoreMeshes").c_str()); + + //Assimp/Collada + convertLeftHanded = dAtob(configSettings->value(String(configName + "/Meshes/convertLeftHanded").c_str())); + calcTangentSpace = dAtob(configSettings->value(String(configName + "/Meshes/calcTangentSpace").c_str())); + removeRedundantMats = dAtob(configSettings->value(String(configName + "/Meshes/removeRedundantMats").c_str())); + genUVCoords = dAtob(configSettings->value(String(configName + "/Meshes/genUVCoords").c_str())); + TransformUVs = dAtob(configSettings->value(String(configName + "/Meshes/TransformUVs").c_str())); + flipUVCoords = dAtob(configSettings->value(String(configName + "/Meshes/flipUVCoords").c_str())); + findInstances = dAtob(configSettings->value(String(configName + "/Meshes/findInstances").c_str())); + limitBoneWeights = dAtob(configSettings->value(String(configName + "/Meshes/limitBoneWeights").c_str())); + JoinIdenticalVerts = dAtob(configSettings->value(String(configName + "/Meshes/JoinIdenticalVerts").c_str())); + reverseWindingOrder = dAtob(configSettings->value(String(configName + "/Meshes/reverseWindingOrder").c_str())); + invertNormals = dAtob(configSettings->value(String(configName + "/Meshes/invertNormals").c_str())); + + //Materials + ImportMaterials = dAtob(configSettings->value(String(configName + "/Materials/ImportMaterials").c_str())); + CreatePBRConfig = dAtob(configSettings->value(String(configName + "/Materials/CreatePBRConfig").c_str())); + UseDiffuseSuffixOnOriginImage = dAtob(configSettings->value(String(configName + "/Materials/UseDiffuseSuffixOnOriginImage").c_str())); + UseExistingMaterials = dAtob(configSettings->value(String(configName + "/Materials/UseExistingMaterials").c_str())); + IgnoreMaterials = configSettings->value(String(configName + "/Materials/IgnoreMaterials").c_str()); + PopulateMaterialMaps = dAtob(configSettings->value(String(configName + "/Materials/invertPopulateMaterialMapsNormals").c_str())); + + //Animations + ImportAnimations = dAtob(configSettings->value(String(configName + "/Animations/ImportAnimations").c_str())); + SeparateAnimations = dAtob(configSettings->value(String(configName + "/Animations/SeparateAnimations").c_str())); + SeparateAnimationPrefix = configSettings->value(String(configName + "/Animations/SeparateAnimationPrefix").c_str()); + animTiming = configSettings->value(String(configName + "/Animations/animTiming").c_str()); + animFPS = dAtof(configSettings->value(String(configName + "/Animations/animFPS").c_str())); + + //Collisions + GenerateCollisions = dAtob(configSettings->value(String(configName + "/Collision/GenerateCollisions").c_str())); + GenCollisionType = configSettings->value(String(configName + "/Collision/GenCollisionType").c_str()); + CollisionMeshPrefix = configSettings->value(String(configName + "/Collision/CollisionMeshPrefix").c_str()); + GenerateLOSCollisions = dAtob(configSettings->value(String(configName + "/Collision/GenerateLOSCollisions").c_str())); + GenLOSCollisionType = configSettings->value(String(configName + "/Collision/GenLOSCollisionType").c_str()); + LOSCollisionMeshPrefix = configSettings->value(String(configName + "/Collision/LOSCollisionMeshPrefix").c_str()); + + //Images + importImages = dAtob(configSettings->value(String(configName + "/Images/importImages").c_str())); + ImageType = configSettings->value(String(configName + "/Images/ImageType").c_str()); + DiffuseTypeSuffixes = configSettings->value(String(configName + "/Images/DiffuseTypeSuffixes").c_str()); + NormalTypeSuffixes = configSettings->value(String(configName + "/Images/NormalTypeSuffixes").c_str()); + MetalnessTypeSuffixes = configSettings->value(String(configName + "/Images/MetalnessTypeSuffixes").c_str()); + RoughnessTypeSuffixes = configSettings->value(String(configName + "/Images/RoughnessTypeSuffixes").c_str()); + SmoothnessTypeSuffixes = configSettings->value(String(configName + "/Images/SmoothnessTypeSuffixes").c_str()); + AOTypeSuffixes = configSettings->value(String(configName + "/Images/AOTypeSuffixes").c_str()); + PBRTypeSuffixes = configSettings->value(String(configName + "/Images/PBRTypeSuffixes").c_str()); + TextureFilteringMode = configSettings->value(String(configName + "/Images/TextureFilteringMode").c_str()); + UseMips = dAtob(configSettings->value(String(configName + "/Images/UseMips").c_str())); + IsHDR = dAtob(configSettings->value(String(configName + "/Images/IsHDR").c_str())); + Scaling = dAtof(configSettings->value(String(configName + "/Images/Scaling").c_str())); + Compressed = dAtob(configSettings->value(String(configName + "/Images/Compressed").c_str())); + GenerateMaterialOnImport = dAtob(configSettings->value(String(configName + "/Images/GenerateMaterialOnImport").c_str())); + + //Sounds + VolumeAdjust = dAtof(configSettings->value(String(configName + "/Sounds/VolumeAdjust").c_str())); + PitchAdjust = dAtof(configSettings->value(String(configName + "/Sounds/PitchAdjust").c_str())); + Compressed = dAtob(configSettings->value(String(configName + "/Sounds/Compressed").c_str())); +} + +ConsoleDocClass(AssetImportObject, + "@brief Defines properties for an AssetImportObject object.\n" + "@AssetImportObject is a SimObject derived object intended to act as a stand-in for the to-be imported objects.\n" + "@It contains important info such as dependencies, if it's been processed, any error/status issues and the originating file\n" + "@or if it's been programmatically generated as part of the import process.\n\n" + "@ingroup Assets\n" +); + +IMPLEMENT_CONOBJECT(AssetImportObject); + +AssetImportObject::AssetImportObject() : + dirty(false), + skip(false), + processed(false), + generatedAsset(false), + parentAssetItem(nullptr), + tamlFilePath(""), + imageSuffixType(""), + shapeInfo(nullptr) +{ + +} + +AssetImportObject::~AssetImportObject() +{ + +} + +void AssetImportObject::initPersistFields() +{ + Parent::initPersistFields(); +} + +ConsoleDocClass(AssetImporter, + "@brief Defines properties for an AssetImportObject object.\n" + "@AssetImportObject is a SimObject derived object intended to act as a stand-in for the to-be imported objects.\n" + "@It contains important info such as dependencies, if it's been processed, any error/status issues and the originating file\n" + "@or if it's been programmatically generated as part of the import process.\n\n" + "@ingroup Assets\n" +); + +IMPLEMENT_CONOBJECT(AssetImporter); + +AssetImporter::AssetImporter() : + importIssues(false), + isReimport(false), + assetHeirarchyChanged(false) +{ +} + +AssetImporter::~AssetImporter() +{ + +} + +void AssetImporter::initPersistFields() +{ + Parent::initPersistFields(); +} + +// +// Utility Functions +// + +AssetImportObject* AssetImporter::addImportingFile(Torque::Path filePath) +{ + String assetType = getAssetTypeByFile(filePath); + + if (assetType.isEmpty()) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Unable to import file %s because it is of an unrecognized/unsupported type.", filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + return nullptr; + } + + AssetImportObject* newAssetItem = addImportingAsset(assetType, filePath, nullptr, ""); + + originalImportingAssets.push_back(filePath); + + return newAssetItem; +} + +AssetImportObject* AssetImporter::addImportingAsset(String assetType, Torque::Path filePath, AssetImportObject* parentItem, String assetNameOverride) +{ + String assetName; + + //In some cases(usually generated assets on import, like materials) we'll want to specifically define the asset name instead of peeled from the filePath + if (assetNameOverride.isNotEmpty()) + assetName = assetNameOverride; + else + assetName = filePath.getFileName(); + + AssetImportObject* assetImportObj = new AssetImportObject(); + assetImportObj->registerObject(); + + assetImportObj->assetType = assetType; + assetImportObj->filePath = filePath; + assetImportObj->assetName = assetName; + assetImportObj->cleanAssetName = assetName; + assetImportObj->moduleName = targetModuleId; + assetImportObj->status = ""; + assetImportObj->statusType = ""; + assetImportObj->statusInfo = ""; + + assetImportObj->dirty = false; + assetImportObj->skip = false; + assetImportObj->processed = false; + assetImportObj->generatedAsset = false; + + if (parentItem != nullptr) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Added Child Importing Asset to %s", parentItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + parentItem->childAssetItems.push_back(assetImportObj); + assetImportObj->parentAssetItem = parentItem; + } + else + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Added Importing Asset"); + activityLog.push_back(importLogBuffer); + } + + dSprintf(importLogBuffer, sizeof(importLogBuffer), " Asset Info: Name: %s | Type: %s", assetImportObj->assetName.c_str(), assetImportObj->assetType.c_str()); + activityLog.push_back(importLogBuffer); + + if (!filePath.isEmpty()) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), " File: %s", filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + } + + return assetImportObj; +} + +void AssetImporter::deleteImportingAsset(AssetImportObject* assetItem) +{ + assetItem->skip = true; + + //log it + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Deleting Importing Asset %s and all it's child items", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); +} + +AssetImportObject* AssetImporter::findImportingAssetByName(String assetName, AssetImportObject* assetItem) +{ + if (assetItem == nullptr) + { + for (U32 i = 0; i < importingAssets.size(); i++) + { + if (importingAssets[i]->cleanAssetName == assetName) + { + return importingAssets[i]; + } + + //If it wasn't a match, try recusing on the children(if any) + AssetImportObject* retItem = findImportingAssetByName(assetName, importingAssets[i]); + if (retItem != nullptr) + return retItem; + } + } + else + { + //this is the child recursing section + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + if (assetItem->childAssetItems[i]->cleanAssetName == assetName) + { + return assetItem->childAssetItems[i]; + } + + //If it wasn't a match, try recusing on the children(if any) + AssetImportObject* retItem = findImportingAssetByName(assetName, assetItem->childAssetItems[i]); + if (retItem != nullptr) + return retItem; + } + } + + return nullptr; +} + +ModuleDefinition* AssetImporter::getModuleFromPath(Torque::Path filePath) +{ + U32 folderCount = StringUnit::getUnitCount(filePath.getPath().c_str(), "/"); + + for (U32 i = 0; i < folderCount; i++) + { + String folderName = StringUnit::getUnit(filePath.getPath().c_str(), i, "/"); + + ModuleDefinition* moduleDef = ModuleDatabase.findModule(folderName.c_str(), 1); + if (moduleDef != nullptr) + return moduleDef; + } + + return nullptr; +} + +String AssetImporter::parseImageSuffixes(String assetName, String* suffixType) +{ + U32 suffixTypeIdx = 0; + while (suffixTypeIdx < 6) + { + String suffixList; + switch (suffixTypeIdx) + { + case 0: + suffixList = activeImportConfig.DiffuseTypeSuffixes; + suffixType->insert(0, "Albedo", 10); + break; + case 1: + suffixList = activeImportConfig.NormalTypeSuffixes; + suffixType->insert(0, "Normal", 10); + break; + case 2: + suffixList = activeImportConfig.RoughnessTypeSuffixes; + suffixType->insert(0, "Roughness", 10); + break; + case 3: + suffixList = activeImportConfig.AOTypeSuffixes; + suffixType->insert(0, "AO", 10); + break; + case 4: + suffixList = activeImportConfig.MetalnessTypeSuffixes; + suffixType->insert(0, "Metalness", 10); + break; + case 5: + suffixList = activeImportConfig.PBRTypeSuffixes; + suffixType->insert(0, "PBRConfig", 10); + break; + default: + suffixList = ""; + } + + suffixTypeIdx++; + + U32 suffixCount = StringUnit::getUnitCount(suffixList, ",;"); + for (U32 i = 0; i < suffixCount; i++) + { + String suffix = StringUnit::getUnit(suffixList, i, ",;"); + String searchSuffix = String("*") + suffix; + if (FindMatch::isMatch(searchSuffix.c_str(), assetName.c_str(), false)) + { + //We have a match, so indicate as such + return suffix; + } + } + } + + return ""; +} + +String AssetImporter::getAssetTypeByFile(Torque::Path filePath) +{ + String fileExt = filePath.getExtension(); + + if (fileExt == String("png") || fileExt == String("jpg") || fileExt == String("jpeg") || fileExt == String("dds")) + return "ImageAsset"; + else if (fileExt == String("dae") || fileExt == String("fbx") || fileExt == String("blend") || fileExt == String("obj") || fileExt == String("dts") || fileExt == String("gltf") || fileExt == String("gltb")) + return "ShapeAsset"; + else if (fileExt == String("dsq")) + return "ShapeAnimationAsset"; + else if (fileExt == String("ogg") || fileExt == String("wav") || fileExt == String("mp3")) + return "SoundAsset"; + else if (fileExt == String("zip")) + return "Zip"; + else if (fileExt.isEmpty()) + return "Folder"; + + return ""; +} + +void AssetImporter::resetImportSession() +{ + importingAssets.clear(); + activityLog.clear(); + + for (U32 i = 0; i < originalImportingAssets.size(); i++) + { + addImportingFile(originalImportingAssets[i]); + } +} + +void AssetImporter::resetImportAsset(AssetImportObject* assetItem) +{ +} + +S32 AssetImporter::getActivityLogLineCount() +{ + return activityLog.size(); +} + +String AssetImporter::getActivityLogLine(U32 line) +{ + if (line >= activityLog.size()) + return ""; + + return activityLog[line]; +} + +void AssetImporter::dumpActivityLog() +{ + for (U32 i = 0; i < activityLog.size(); i++) + { + Con::printf(activityLog[i].c_str()); + } +} + +S32 AssetImporter::getAssetItemCount() +{ + return importingAssets.size(); +} + +AssetImportObject* AssetImporter::getAssetItem(U32 index) +{ + if (index >= importingAssets.size()) + return nullptr; + + return importingAssets[index]; +} + +S32 AssetImporter::getAssetItemChildCount(AssetImportObject* assetItem) +{ + return assetItem->childAssetItems.size(); +} + +AssetImportObject* AssetImporter::getAssetItemChild(AssetImportObject* assetItem, U32 index) +{ + if (index >= assetItem->childAssetItems.size()) + return nullptr; + + return assetItem->childAssetItems[index]; +} +// +// Processing +// +// Helper struct for counting nodes, meshes and polygons down through the scene +// hierarchy +struct SceneStats +{ + S32 numNodes; + S32 numMeshes; + S32 numPolygons; + S32 numMaterials; + S32 numLights; + S32 numClips; + + SceneStats() : numNodes(0), numMeshes(0), numPolygons(0), numMaterials(0), numLights(0), numClips(0) { } +}; + +// Recurse through the adding nodes and geometry to the GuiTreeView control +static void processNode(GuiTreeViewCtrl* tree, domNode* node, S32 parentID, SceneStats& stats) +{ + stats.numNodes++; + S32 nodeID = tree->insertItem(parentID, _GetNameOrId(node), "node", "", 0, 0); + + // Update mesh and poly counts + for (S32 i = 0; i < node->getContents().getCount(); i++) + { + domGeometry* geom = 0; + const char* elemName = ""; + + daeElement* child = node->getContents()[i]; + switch (child->getElementType()) + { + case COLLADA_TYPE::INSTANCE_GEOMETRY: + { + domInstance_geometry* instgeom = daeSafeCast(child); + if (instgeom) + { + geom = daeSafeCast(instgeom->getUrl().getElement()); + elemName = _GetNameOrId(geom); + } + break; + } + + case COLLADA_TYPE::INSTANCE_CONTROLLER: + { + domInstance_controller* instctrl = daeSafeCast(child); + if (instctrl) + { + domController* ctrl = daeSafeCast(instctrl->getUrl().getElement()); + elemName = _GetNameOrId(ctrl); + if (ctrl && ctrl->getSkin()) + geom = daeSafeCast(ctrl->getSkin()->getSource().getElement()); + else if (ctrl && ctrl->getMorph()) + geom = daeSafeCast(ctrl->getMorph()->getSource().getElement()); + } + break; + } + + case COLLADA_TYPE::INSTANCE_LIGHT: + stats.numLights++; + tree->insertItem(nodeID, _GetNameOrId(node), "light", "", 0, 0); + break; + } + + if (geom && geom->getMesh()) + { + const char* name = _GetNameOrId(node); + if (dStrEqual(name, "null") || dStrEndsWith(name, "PIVOT")) + name = _GetNameOrId(daeSafeCast(node->getParent())); + + stats.numMeshes++; + tree->insertItem(nodeID, name, "mesh", "", 0, 0); + + for (S32 j = 0; j < geom->getMesh()->getTriangles_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getTriangles_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getTristrips_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getTristrips_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getTrifans_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getTrifans_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getPolygons_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getPolygons_array()[j]->getCount(); + for (S32 j = 0; j < geom->getMesh()->getPolylist_array().getCount(); j++) + stats.numPolygons += geom->getMesh()->getPolylist_array()[j]->getCount(); + } + } + + // Recurse into child nodes + for (S32 i = 0; i < node->getNode_array().getCount(); i++) + processNode(tree, node->getNode_array()[i], nodeID, stats); + + for (S32 i = 0; i < node->getInstance_node_array().getCount(); i++) + { + domInstance_node* instnode = node->getInstance_node_array()[i]; + domNode* dNode = daeSafeCast(instnode->getUrl().getElement()); + if (dNode) + processNode(tree, dNode, nodeID, stats); + } +} + +static bool enumColladaForImport(const char* shapePath, GuiTreeViewCtrl* tree, bool loadCachedDts) +{ + // Check if a cached DTS is available => no need to import the collada file + // if we can load the DTS instead + Torque::Path path(shapePath); + if (loadCachedDts && ColladaShapeLoader::canLoadCachedDTS(path)) + return false; + + // Check if this is a Sketchup file (.kmz) and if so, mount the zip filesystem + // and get the path to the DAE file. + String mountPoint; + Torque::Path daePath; + bool isSketchup = ColladaShapeLoader::checkAndMountSketchup(path, mountPoint, daePath); + + // Load the Collada file into memory + domCOLLADA* root = ColladaShapeLoader::getDomCOLLADA(daePath); + if (!root) + { + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + return false; + } + + if (isSketchup) + { + // Unmount the zip if we mounted it + Torque::FS::Unmount(mountPoint); + } + + // Initialize tree + tree->removeItem(0); + S32 nodesID = tree->insertItem(0, "Shape", "", "", 0, 0); + S32 matsID = tree->insertItem(0, "Materials", "", "", 0, 0); + S32 animsID = tree->insertItem(0, "Animations", "", "", 0, 0); + + SceneStats stats; + + // Query DOM for shape summary details + for (S32 i = 0; i < root->getLibrary_visual_scenes_array().getCount(); i++) + { + const domLibrary_visual_scenes* libScenes = root->getLibrary_visual_scenes_array()[i]; + for (S32 j = 0; j < libScenes->getVisual_scene_array().getCount(); j++) + { + const domVisual_scene* visualScene = libScenes->getVisual_scene_array()[j]; + for (S32 k = 0; k < visualScene->getNode_array().getCount(); k++) + processNode(tree, visualScene->getNode_array()[k], nodesID, stats); + } + } + + // Get material count + for (S32 i = 0; i < root->getLibrary_materials_array().getCount(); i++) + { + const domLibrary_materials* libraryMats = root->getLibrary_materials_array()[i]; + stats.numMaterials += libraryMats->getMaterial_array().getCount(); + for (S32 j = 0; j < libraryMats->getMaterial_array().getCount(); j++) + { + domMaterial* mat = libraryMats->getMaterial_array()[j]; + tree->insertItem(matsID, _GetNameOrId(mat), "", "", 0, 0); + } + } + + // Get images count + for (S32 i = 0; i < root->getLibrary_images_array().getCount(); i++) + { + const domLibrary_images* libraryImages = root->getLibrary_images_array()[i]; + + for (S32 j = 0; j < libraryImages->getImage_array().getCount(); j++) + { + domImage* img = libraryImages->getImage_array()[j]; + + S32 materialID = tree->findItemByName(_GetNameOrId(img)); + + if (materialID == 0) + continue; + + tree->setItemValue(materialID, img->getInit_from()->getValue().str().c_str()); + } + } + + // Get animation count + for (S32 i = 0; i < root->getLibrary_animation_clips_array().getCount(); i++) + { + const domLibrary_animation_clips* libraryClips = root->getLibrary_animation_clips_array()[i]; + stats.numClips += libraryClips->getAnimation_clip_array().getCount(); + for (S32 j = 0; j < libraryClips->getAnimation_clip_array().getCount(); j++) + { + domAnimation_clip* clip = libraryClips->getAnimation_clip_array()[j]; + tree->insertItem(animsID, _GetNameOrId(clip), "animation", "", 0, 0); + } + } + if (stats.numClips == 0) + { + // No clips => check if there are any animations (these will be added to a default clip) + for (S32 i = 0; i < root->getLibrary_animations_array().getCount(); i++) + { + const domLibrary_animations* libraryAnims = root->getLibrary_animations_array()[i]; + if (libraryAnims->getAnimation_array().getCount()) + { + stats.numClips = 1; + tree->insertItem(animsID, "ambient", "animation", "", 0, 0); + break; + } + } + } + + // Extract the global scale and up_axis from the top level element, + F32 unit = 1.0f; + domUpAxisType upAxis = UPAXISTYPE_Z_UP; + if (root->getAsset()) { + if (root->getAsset()->getUnit()) + unit = root->getAsset()->getUnit()->getMeter(); + if (root->getAsset()->getUp_axis()) + upAxis = root->getAsset()->getUp_axis()->getValue(); + } + + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Load complete"); + + // Store shape information in the tree control + tree->setDataField(StringTable->insert("_nodeCount"), 0, avar("%d", stats.numNodes)); + tree->setDataField(StringTable->insert("_meshCount"), 0, avar("%d", stats.numMeshes)); + tree->setDataField(StringTable->insert("_polygonCount"), 0, avar("%d", stats.numPolygons)); + tree->setDataField(StringTable->insert("_materialCount"), 0, avar("%d", stats.numMaterials)); + tree->setDataField(StringTable->insert("_lightCount"), 0, avar("%d", stats.numLights)); + tree->setDataField(StringTable->insert("_animCount"), 0, avar("%d", stats.numClips)); + tree->setDataField(StringTable->insert("_unit"), 0, avar("%g", unit)); + + if (upAxis == UPAXISTYPE_X_UP) + tree->setDataField(StringTable->insert("_upAxis"), 0, "X_AXIS"); + else if (upAxis == UPAXISTYPE_Y_UP) + tree->setDataField(StringTable->insert("_upAxis"), 0, "Y_AXIS"); + else + tree->setDataField(StringTable->insert("_upAxis"), 0, "Z_AXIS"); + + char shapesStr[16]; + dSprintf(shapesStr, 16, "%i", stats.numMeshes); + char materialsStr[16]; + dSprintf(materialsStr, 16, "%i", stats.numMaterials); + char animationsStr[16]; + dSprintf(animationsStr, 16, "%i", stats.numClips); + + tree->setItemValue(nodesID, StringTable->insert(shapesStr)); + tree->setItemValue(matsID, StringTable->insert(materialsStr)); + tree->setItemValue(animsID, StringTable->insert(animationsStr)); + + return true; +} + +void AssetImporter::processImportAssets(AssetImportObject* assetItem) +{ + if (assetItem == nullptr) + { + assetHeirarchyChanged = false; + + for (U32 i = 0; i < importingAssets.size(); i++) + { + AssetImportObject* item = importingAssets[i]; + if (item->skip) + continue; + + if (!item->processed) + { + //Sanitize before modifying our asset name(suffix additions, etc) + if (item->assetName != item->cleanAssetName) + item->assetName = item->cleanAssetName; + + //handle special pre-processing here for any types that need it + + //process the asset items + if (item->assetType == String("ImageAsset")) + processImageAsset(item); + else if (item->assetType == String("ShapeAsset")) + processShapeAsset(item); + else if (item->assetType == String("SoundAsset")) + SoundAsset::prepareAssetForImport(this, item); + else if (item->assetType == String("MaterialAsset")) + processMaterialAsset(item); + else if (item->assetType == String("ShapeAnimationAsset")) + ShapeAnimationAsset::prepareAssetForImport(this, item); + + item->processed = true; + } + + //try recusing on the children(if any) + processImportAssets(item); + } + } + else + { + //this is the child recursing section + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip) + continue; + + if (!childItem->processed) + { + //Sanitize before modifying our asset name(suffix additions, etc) + if (childItem->assetName != childItem->cleanAssetName) + childItem->assetName = childItem->cleanAssetName; + + //handle special pre-processing here for any types that need it + + //process the asset items + if (childItem->assetType == String("ImageAsset")) + processImageAsset(childItem); + else if (childItem->assetType == String("ShapeAsset")) + processShapeAsset(childItem); + else if (childItem->assetType == String("SoundAsset")) + SoundAsset::prepareAssetForImport(this, childItem); + else if (childItem->assetType == String("MaterialAsset")) + processMaterialAsset(childItem); + else if (childItem->assetType == String("ShapeAnimationAsset")) + ShapeAnimationAsset::prepareAssetForImport(this, childItem); + + childItem->processed = true; + } + + //try recusing on the children(if any) + processImportAssets(childItem); + } + } + + //If our hierarchy changed, it's because we did so during processing + //so we'll loop back through again until everything has been processed + if (assetHeirarchyChanged) + processImportAssets(); +} + +void AssetImporter::processImageAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Preparing Image for Import: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + if ((activeImportConfig.GenerateMaterialOnImport && assetItem->parentAssetItem == nullptr) || assetItem->parentAssetItem != nullptr) + { + //find our suffix match, if any + String noSuffixName = assetItem->assetName; + String suffixType; + String suffix = parseImageSuffixes(assetItem->assetName, &suffixType); + if (suffix.isNotEmpty()) + { + assetItem->imageSuffixType = suffixType; + S32 suffixPos =assetItem->assetName.find(suffix, 0, String::NoCase|String::Left); + noSuffixName = assetItem->assetName.substr(0, suffixPos); + } + + //We try to automatically populate materials under the naming convention: materialName: Rock, image maps: Rock_Albedo, Rock_Normal, etc + + AssetImportObject* materialAsset = findImportingAssetByName(noSuffixName); + if (materialAsset != nullptr && materialAsset->assetType != String("MaterialAsset")) + { + //We may have a situation where an asset matches the no-suffix name, but it's not a material asset. Ignore this + //asset item for now + materialAsset = nullptr; + } + + //If we didn't find a matching material asset in our current items, we'll make one now + if (materialAsset == nullptr) + { + if (!assetItem->filePath.isEmpty()) + { + materialAsset = addImportingAsset("MaterialAsset", "", nullptr, noSuffixName); + //Add the material into the primary list of importing assets + importingAssets.push_back(materialAsset); + } + } + + //Not that, one way or another, we have the generated material asset, lets move on to associating our image with it + if (materialAsset != nullptr && materialAsset != assetItem->parentAssetItem) + { + if (assetItem->parentAssetItem != nullptr) + { + //If the image had an existing parent, it gets removed from that parent's child item list + assetItem->parentAssetItem->childAssetItems.remove(assetItem); + } + else + { + //If it didn't have one, we're going to pull it from the importingAssets list + importingAssets.remove(assetItem); + } + + //Now we can add it to the correct material asset + materialAsset->childAssetItems.push_back(assetItem); + assetItem->parentAssetItem = materialAsset; + + assetHeirarchyChanged = true; + } + + //Now to do some cleverness. If we're generating a material, we can parse like assets being imported(similar filenames) but different suffixes + //If we find these, we'll just populate into the original's material + + //if we need to append the diffuse suffix and indeed didn't find a suffix on the name, do that here + if (suffixType.isEmpty()) + { + if (activeImportConfig.UseDiffuseSuffixOnOriginImage) + { + String diffuseToken = StringUnit::getUnit(activeImportConfig.DiffuseTypeSuffixes, 0, ",;"); + assetItem->assetName = assetItem->assetName + diffuseToken; + } + else + { + //We need to ensure that our image asset doesn't match the same name as the material asset, so if we're not trying to force the diffuse suffix + //we'll give it a generic one + if (materialAsset->assetName.compare(assetItem->assetName) == 0) + { + assetItem->assetName = assetItem->assetName + "_image"; + } + } + + suffixType = "Albedo"; + } + + if (suffixType.isNotEmpty()) + { + assetItem->imageSuffixType = suffixType; + + //otherwise, if we have some sort of suffix, we'll want to figure out if we've already got an existing material, and should append to it + if (activeImportConfig.PopulateMaterialMaps) + { + + } + } + } + + assetItem->processed = true; +} + +void AssetImporter::processMaterialAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Preparing Material for Import: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + String filePath = assetItem->filePath.getFullPath(); + String fileName = assetItem->filePath.getFileName(); + String fileExt = assetItem->filePath.getExtension(); + const char* assetName = assetItem->assetName.c_str(); + + assetItem->generatedAsset = true; + + if (activeImportConfig.IgnoreMaterials.isNotEmpty()) + { + U32 ignoredMatNameCount = StringUnit::getUnitCount(activeImportConfig.IgnoreMaterials, ".;"); + for (U32 i = 0; i < ignoredMatNameCount; i++) + { + String ignoredName = StringUnit::getUnit(activeImportConfig.IgnoreMaterials, i, ".;"); + if (FindMatch::isMatch(ignoredName.c_str(), assetName, false)) + { + assetItem->skip = true; + + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Material %s has been ignored due to it's name being listed in the IgnoreMaterials list in the Import Config.", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + return; + } + } + } + + if (activeImportConfig.PopulateMaterialMaps) + { + //If we're trying to populate the rest of our material maps, we need to go looking + } + + assetItem->processed = true; +} + +void AssetImporter::processShapeAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Preparing Shape for Import: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + String filePath = assetItem->filePath.getFullPath(); + String fileName = assetItem->filePath.getFileName(); + String fileExt = assetItem->filePath.getExtension(); + const char* assetName = assetItem->assetName.c_str(); + + if (assetItem->shapeInfo == nullptr) + { + GuiTreeViewCtrl* shapeInfo = new GuiTreeViewCtrl(); + shapeInfo->registerObject(); + + if (fileExt.compare("dae") == 0) + { + enumColladaForImport(filePath, shapeInfo, false); + } + else + { + // Check if a cached DTS is available => no need to import the source file + // if we can load the DTS instead + + AssimpShapeLoader loader; + loader.fillGuiTreeView(filePath.c_str(), shapeInfo); + } + + assetItem->shapeInfo = shapeInfo; + } + + S32 meshCount = dAtoi(assetItem->shapeInfo->getDataField(StringTable->insert("_meshCount"), nullptr)); + S32 shapeItem = assetItem->shapeInfo->findItemByName("Meshes"); + + S32 animCount = dAtoi(assetItem->shapeInfo->getDataField(StringTable->insert("_animCount"), nullptr)); + S32 animItem = assetItem->shapeInfo->findItemByName("Animations"); + + S32 materialCount = dAtoi(assetItem->shapeInfo->getDataField(StringTable->insert("_materialCount"), nullptr)); + S32 matItem = assetItem->shapeInfo->findItemByName("Materials"); + + dSprintf(importLogBuffer, sizeof(importLogBuffer), " Shape Info: Mesh Count: %i | Material Count: %i | Anim Count: %i", meshCount, animCount, materialCount); + activityLog.push_back(importLogBuffer); + + if (activeImportConfig.ImportMesh && meshCount > 0) + { + + } + + if (activeImportConfig.ImportAnimations && animCount > 0) + { + //If we have animations but no meshes, then this is a pure animation file so we can swap the asset type here + if (meshCount == 0) + { + //assetItem->assetType = "ShapeAnimation"; + } + } + + if (activeImportConfig.ImportMaterials && materialCount > 0) + { + S32 materialId = assetItem->shapeInfo->getChildItem(matItem); + processShapeMaterialInfo(assetItem, materialId); + + materialId = assetItem->shapeInfo->getNextSiblingItem(materialId); + while (materialId != 0) + { + processShapeMaterialInfo(assetItem, materialId); + materialId = assetItem->shapeInfo->getNextSiblingItem(materialId); + } + } + + assetItem->processed = true; +} + +void AssetImporter::processShapeMaterialInfo(AssetImportObject* assetItem, S32 materialItemId) +{ + String matName = assetItem->shapeInfo->getItemText(materialItemId); + + Torque::Path filePath = assetItem->shapeInfo->getItemValue(materialItemId); + if (filePath.getFullFileName().isNotEmpty()) + { + if (!Platform::isFile(filePath.getFullFileName())) + { + //could be a stale path reference, such as if it was downloaded elsewhere. Trim to just the filename and see + //if we can find it there + String shapePathBase = assetItem->filePath.getPath(); + + String matFilePath = filePath.getFileName() + "." + filePath.getExtension(); + //trim (not found) if needbe + /* + %suffixPos = strpos(strlwr(%filename), " (not found)", 0); + %filename = getSubStr(%filename, 0, %suffixPos); + */ + + String testFileName = shapePathBase + "/" + matFilePath; + if (Platform::isFile(testFileName)) + { + filePath = testFileName; + } + } + + AssetImportObject* matAssetItem = addImportingAsset("MaterialAsset", "", assetItem, matName); + addImportingAsset("ImageAsset", filePath, matAssetItem, ""); + } + else + { + /* + //check to see if it's actually just a flat color + if(getWordCount(%filePath) == 4 && getWord(%filePath, 0) $= "Color:") + { + AssetBrowser.addImportingAsset("MaterialAsset", %matName, %assetItem); + } + else + { + //we need to try and find our material, since the shapeInfo wasn't able to find it automatically + %filePath = findImageFile(filePath(%assetItem.filePath), %matName); + if(%filePath !$= "" && isFile(%filePath)) + AssetBrowser.addImportingAsset("MaterialAsset", %filePath, %assetItem); + else + AssetBrowser.addImportingAsset("MaterialAsset", filePath(%assetItem.filePath) @ "/" @ %matName, %assetItem); + } + */ + } +} + +// +// Validation +// + +bool AssetImporter::validateAssets() +{ + importIssues = false; + + resetAssetValidationStatus(); + + for (U32 i = 0; i < importingAssets.size(); i++) + { + validateAsset(importingAssets[i]); + resolveAssetItemIssues(importingAssets[i]); + } + + return importIssues; +} + +void AssetImporter::validateAsset(AssetImportObject* assetItem) +{ + if (assetItem->skip) + return; + + bool hasCollision = checkAssetForCollision(assetItem); + + if (hasCollision) + { + importIssues = true; + return; + } + + if (!isReimport) + { + AssetQuery aQuery; + U32 numAssetsFound = AssetDatabase.findAllAssets(&aQuery); + + bool hasCollision = false; + for (U32 i = 0; i < numAssetsFound; i++) + { + StringTableEntry assetId = aQuery.mAssetList[i]; + + ModuleDefinition* moduleDef = AssetDatabase.getAssetModuleDefinition(assetId); + + if (moduleDef->getModuleId() != StringTable->insert(targetModuleId.c_str())) + continue; + + StringTableEntry assetName = AssetDatabase.getAssetName(assetId); + + if (assetName == StringTable->insert(assetItem->assetName.c_str())) + { + hasCollision = true; + assetItem->status = "Error"; + assetItem->statusType = "DuplicateAsset"; + assetItem->statusInfo = "Duplicate asset names found within the target module!\nAsset \"" + assetItem->assetName + "\" of type \"" + assetItem->assetType + "\" has a matching name.\nPlease rename it and try again!"; + + //log it + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Asset %s has an identically named asset in the target module.", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + break; + } + } + } + + if (!assetItem->filePath.isEmpty() && !assetItem->generatedAsset && !Platform::isFile(assetItem->filePath.getFullPath().c_str())) + { + assetItem->status = "Error"; + assetItem->statusType = "MissingFile"; + assetItem->statusInfo = "Unable to find file to be imported with provided path: " + assetItem->filePath + "\n Please select a valid file."; + + //log it + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Asset %s's file at %s was not found.", assetItem->assetName.c_str(), assetItem->filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + } + + if (assetItem->status == String("Warning")) + { + if (activeImportConfig.WarningsAsErrors) + { + assetItem->status = "Error"; + + //log it + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Import configuration has treated an import warning as an error.", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + } + } + + if (assetItem->status == String("Error")) + importIssues = true; + + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + validateAsset(assetItem->childAssetItems[i]); + resolveAssetItemIssues(assetItem->childAssetItems[i]); + } + + return; +} + +void AssetImporter::resetAssetValidationStatus(AssetImportObject* assetItem) +{ + if (assetItem == nullptr) + { + for (U32 i = 0; i < importingAssets.size(); i++) + { + if (importingAssets[i]->skip) + continue; + + importingAssets[i]->status = ""; + importingAssets[i]->statusType = ""; + importingAssets[i]->statusInfo = ""; + + //If it wasn't a match, try recusing on the children(if any) + resetAssetValidationStatus(importingAssets[i]); + } + } + else + { + //this is the child recursing section + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + if (assetItem->childAssetItems[i]->skip) + continue; + + assetItem->childAssetItems[i]->status = ""; + assetItem->childAssetItems[i]->statusType = ""; + assetItem->childAssetItems[i]->statusInfo = ""; + + //If it wasn't a match, try recusing on the children(if any) + resetAssetValidationStatus(assetItem->childAssetItems[i]); + } + } +} + +bool AssetImporter::checkAssetForCollision(AssetImportObject* assetItemToCheck, AssetImportObject* assetItem) +{ + bool results = false; + + if (assetItem == nullptr) + { + for (U32 i = 0; i < importingAssets.size(); i++) + { + if (importingAssets[i]->skip) + continue; + + if ((assetItemToCheck->assetName.compare(importingAssets[i]->assetName) == 0) && (assetItemToCheck->getId() != importingAssets[i]->getId())) + { + //we do have a collision, note the collsion and bail out + assetItemToCheck->status = "Warning"; + assetItemToCheck->statusType = "DuplicateImportAsset"; + assetItemToCheck->statusInfo = "Duplicate asset names found with importing assets!\nAsset \"" + importingAssets[i]->assetName + "\" of the type \"" + importingAssets[i]->assetType + "\" and \"" + + assetItemToCheck->assetName + "\" of the type \"" + assetItemToCheck->assetType + "\" have matching names.\nPlease rename one of them."; + + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Warning! Asset %s, type %s has a naming collision with another importing asset: %s, type %s", + assetItemToCheck->assetName.c_str(), assetItemToCheck->assetType.c_str(), + importingAssets[i]->assetName.c_str(), importingAssets[i]->assetType.c_str()); + activityLog.push_back(importLogBuffer); + + return true; + } + + //If it wasn't a match, try recusing on the children(if any) + results = checkAssetForCollision(assetItemToCheck, importingAssets[i]); + if (results) + return results; + } + } + else + { + //this is the child recursing section + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + if (assetItem->childAssetItems[i]->skip) + continue; + + if ((assetItemToCheck->assetName.compare(assetItem->childAssetItems[i]->assetName) == 0) && (assetItemToCheck->getId() != assetItem->childAssetItems[i]->getId())) + { + //we do have a collision, note the collsion and bail out + assetItemToCheck->status = "Warning"; + assetItemToCheck->statusType = "DuplicateImportAsset"; + assetItemToCheck->statusInfo = "Duplicate asset names found with importing assets!\nAsset \"" + assetItem->assetName + "\" of the type \"" + assetItem->assetType + "\" and \"" + + assetItemToCheck->assetName + "\" of the type \"" + assetItemToCheck->assetType + "\" have matching names.\nPlease rename one of them."; + + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Warning! Asset %s, type %s has a naming collision with another importing asset: %s, type %s", + assetItemToCheck->assetName.c_str(), assetItemToCheck->assetType.c_str(), + importingAssets[i]->assetName.c_str(), importingAssets[i]->assetType.c_str()); + activityLog.push_back(importLogBuffer); + + return true; + } + + //If it wasn't a match, try recusing on the children(if any) + results = checkAssetForCollision(assetItemToCheck, assetItem->childAssetItems[i]); + if (results) + return results; + } + } + + return results; +} + +void AssetImporter::resolveAssetItemIssues(AssetImportObject* assetItem) +{ + if (assetItem->statusType == String("DuplicateImportAsset") || assetItem->statusType == String("DuplicateAsset")) + { + String humanReadableReason = assetItem->statusType == String("DuplicateImportAsset") ? "Importing asset was duplicate of another importing asset" : "Importing asset was duplicate of an existing asset"; + + //get the config value for duplicateAutoResolution + if (activeImportConfig.DuplicatAutoResolution == String("AutoPrune")) + { + //delete the item + deleteImportingAsset(assetItem); + + //log it's deletion + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Asset %s was autoprined due to %s as part of the Import Configuration", assetItem->assetName.c_str(), humanReadableReason.c_str()); + activityLog.push_back(importLogBuffer); + + importIssues = false; + } + else if (activeImportConfig.DuplicatAutoResolution == String("AutoRename")) + { + //Set trailing number + String renamedAssetName = assetItem->assetName; + renamedAssetName = Sim::getUniqueName(renamedAssetName.c_str()); + + //Log it's renaming + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Asset %s was renamed due to %s as part of the Import Configuration", assetItem->assetName.c_str(), humanReadableReason.c_str()); + activityLog.push_back(importLogBuffer); + + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Asset %s was renamed to %s", assetItem->assetName.c_str(), renamedAssetName.c_str()); + activityLog.push_back(importLogBuffer); + + assetItem->assetName = renamedAssetName; + + //Whatever status we had prior is no longer relevent, so reset the status + resetAssetValidationStatus(assetItem); + importIssues = false; + } + } + else if (assetItem->statusType == String("MissingFile")) + { + //Trigger callback to look? + } +} + +// +// Importing +// + +//new AssetImporter(AsImport); +//AsImport.autoImportFile("data/pbr/images/greasy-pan-2-albedo.png"); +//AsImport.autoImportFile("data/pbr/shapeTest/Blockout_Primitive_Cube.fbx"); + + +//AsImport.setTargetPath("data/pbr/importTest/"); +//AsImport.addImportingFile("D:/Gamedev/art/Blockout/Blockout_Primitive_Cube.fbx"); +//AsImport.processImportingAssets(); +//AsImport.validateImportingAssets(); +//AsImport.importAssets(); +StringTableEntry AssetImporter::autoImportFile(Torque::Path filePath) +{ + String assetType = getAssetTypeByFile(filePath); + + if (assetType == String("Folder") || assetType == String("Zip")) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Unable to import file %s because it is a folder or zip.", filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + return StringTable->EmptyString(); + } + + if (assetType.isEmpty()) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Unable to import file %s because it is of an unrecognized/unsupported type.", filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + return StringTable->EmptyString(); + } + + //Find out if the filepath has an associated module to it. If we're importing in-place, it needs to be within a module's directory + ModuleDefinition* targetModuleDef = getModuleFromPath(filePath); + + if (targetModuleDef == nullptr) + { + //log it + return StringTable->EmptyString(); + } + else + { + targetModuleId = targetModuleDef->getModuleId(); + } + + //set our path + targetPath = filePath.getPath(); + + //use a default import config + activeImportConfig = AssetImportConfig(); + + AssetImportObject* assetItem = addImportingAsset(assetType, filePath, nullptr, ""); + + importingAssets.push_back(assetItem); + + processImportAssets(); + + bool hasIssues = validateAssets(); + + if (hasIssues) + { + //log it + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Import process has failed due to issues discovered during validation!"); + activityLog.push_back(importLogBuffer); + } + else + { + importAssets(); + } + +#if TORQUE_DEBUG + for (U32 i = 0; i < activityLog.size(); i++) + { + Con::printf(activityLog[i].c_str()); + } +#endif +} + +void AssetImporter::importAssets(AssetImportObject* assetItem) +{ + ModuleDefinition* moduleDef = ModuleDatabase.findModule(targetModuleId.c_str(), 1); + + if (moduleDef == nullptr) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "AssetImporter::importAssets - Unable to find moduleId %s", targetModuleId.c_str()); + activityLog.push_back(importLogBuffer); + return; + } + + if (assetItem == nullptr) + { + for (U32 i = 0; i < importingAssets.size(); i++) + { + if (importingAssets[i]->skip) + continue; + + Torque::Path assetPath; + if (importingAssets[i]->assetType == String("ImageAsset")) + assetPath = importImageAsset(importingAssets[i]); + else if (importingAssets[i]->assetType == String("ShapeAsset")) + assetPath = importShapeAsset(importingAssets[i]); + else if (importingAssets[i]->assetType == String("SoundAsset")) + assetPath = SoundAsset::importAsset(importingAssets[i]); + else if (importingAssets[i]->assetType == String("MaterialAsset")) + assetPath = importMaterialAsset(importingAssets[i]); + else if (importingAssets[i]->assetType == String("ShapeAnimationAsset")) + assetPath = ShapeAnimationAsset::importAsset(importingAssets[i]); + + //If we got a valid filepath back from the import action, then we know we're good to go and we can go ahead and register the asset! + if (!assetPath.isEmpty() && !isReimport) + { + bool registerSuccess = AssetDatabase.addDeclaredAsset(moduleDef, assetPath.getFullPath().c_str()); + + if (!registerSuccess) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "AssetImporter::importAssets - Failed to successfully register new asset at path %s to moduleId %s", assetPath.getFullPath().c_str(), targetModuleId.c_str()); + activityLog.push_back(importLogBuffer); + } + } + + //recurse if needed + importAssets(importingAssets[i]); + } + } + else + { + //this is the child recursing section + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip) + continue; + + Torque::Path assetPath; + if (childItem->assetType == String("ImageAsset")) + assetPath = importImageAsset(childItem); + else if (childItem->assetType == String("ShapeAsset")) + assetPath = importShapeAsset(childItem); + else if (childItem->assetType == String("SoundAsset")) + assetPath = SoundAsset::importAsset(childItem); + else if (childItem->assetType == String("MaterialAsset")) + assetPath = importMaterialAsset(childItem); + else if (childItem->assetType == String("ShapeAnimationAsset")) + assetPath = ShapeAnimationAsset::importAsset(childItem); + + //If we got a valid filepath back from the import action, then we know we're good to go and we can go ahead and register the asset! + if (!assetPath.isEmpty() && !isReimport) + { + bool registerSuccess = AssetDatabase.addDeclaredAsset(moduleDef, assetPath.getFullPath().c_str()); + + if (!registerSuccess) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "AssetImporter::importAssets - Failed to successfully register new asset at path %s to moduleId %s", assetPath.getFullPath().c_str(), targetModuleId.c_str()); + activityLog.push_back(importLogBuffer); + } + } + + //recurse if needed + importAssets(childItem); + } + } +} + +// +// Type-specific import logic +// + +Torque::Path AssetImporter::importImageAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Beginning importing of Image Asset: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + ImageAsset* newAsset = new ImageAsset(); + newAsset->registerObject(); + + StringTableEntry assetName = StringTable->insert(assetItem->assetName.c_str()); + + String imageFileName = assetItem->filePath.getFileName() + "." + assetItem->filePath.getExtension(); + String assetPath = targetPath + "/" + imageFileName; + String tamlPath = targetPath + "/" + assetName + ".asset.taml"; + String originalPath = assetItem->filePath.getFullPath().c_str(); + + char qualifiedFromFile[2048]; + char qualifiedToFile[2048]; + + Platform::makeFullPathName(originalPath.c_str(), qualifiedFromFile, sizeof(qualifiedFromFile)); + Platform::makeFullPathName(assetPath.c_str(), qualifiedToFile, sizeof(qualifiedToFile)); + + newAsset->setAssetName(assetName); + newAsset->setImageFileName(imageFileName.c_str()); + newAsset->setDataField(StringTable->insert("originalFilePath"), nullptr, qualifiedFromFile); + + ImageAsset::ImageTypes imageType = ImageAsset::getImageTypeFromName(assetItem->imageSuffixType.c_str()); + newAsset->setImageType(imageType); + + Taml tamlWriter; + bool importSuccessful = tamlWriter.write(newAsset, tamlPath.c_str()); + + if (!importSuccessful) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to write asset taml file %s", tamlPath.c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + + if (!isReimport) + { + bool isInPlace = !dStrcmp(qualifiedFromFile, qualifiedToFile); + + if (!isInPlace && !dPathCopy(qualifiedFromFile, qualifiedToFile, !isReimport)) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to copy file %s", assetItem->filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + } + + return tamlPath; +} + +Torque::Path AssetImporter::importMaterialAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Beginning importing of Material Asset: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + MaterialAsset* newAsset = new MaterialAsset(); + newAsset->registerObject(); + + StringTableEntry assetName = StringTable->insert(assetItem->assetName.c_str()); + + String tamlPath = targetPath + "/" + assetName + ".asset.taml"; + String scriptName = assetItem->assetName + ".cs"; + String scriptPath = targetPath + "/" + scriptName; + String originalPath = assetItem->filePath.getFullPath().c_str(); + + char qualifiedFromFile[2048]; + + Platform::makeFullPathName(originalPath.c_str(), qualifiedFromFile, sizeof(qualifiedFromFile)); + + newAsset->setAssetName(assetName); + newAsset->setScriptFile(scriptName.c_str()); + newAsset->setDataField(StringTable->insert("originalFilePath"), nullptr, qualifiedFromFile); + + //iterate through and write out the material maps dependencies + S32 dependencySlotId = 0; + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip || !childItem->processed || childItem->assetType.compare("ImageAsset") != 0) + continue; + + char dependencyFieldName[64]; + dSprintf(dependencyFieldName, 64, "imageMap%i", dependencySlotId); + + char dependencyFieldDef[512]; + dSprintf(dependencyFieldDef, 512, "@Asset=%s:%s", targetModuleId.c_str(), childItem->assetName.c_str()); + + newAsset->setDataField(StringTable->insert(dependencyFieldName), nullptr, dependencyFieldDef); + + dependencySlotId++; + } + + Taml tamlWriter; + bool importSuccessful = tamlWriter.write(newAsset, tamlPath.c_str()); + + if (!importSuccessful) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to write asset taml file %s", tamlPath.c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + + //build the PBRConfig file if we're flagged to and have valid image maps + if (activeImportConfig.CreatePBRConfig) + { + AssetImportObject* pbrConfigMap = nullptr; + AssetImportObject* roughnessMap = nullptr; + AssetImportObject* metalnessMap = nullptr; + AssetImportObject* aoMap = nullptr; + + //We need to find any/all respective image maps for the given channels + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip || childItem->assetType.compare("ImageAsset") != 0) + continue; + + if (childItem->imageSuffixType.compare("PBRConfig") == 0) + pbrConfigMap = childItem; + else if(childItem->imageSuffixType.compare("Roughness") == 0) + roughnessMap = childItem; + else if (childItem->imageSuffixType.compare("Metalness") == 0) + metalnessMap = childItem; + else if (childItem->imageSuffixType.compare("AO") == 0) + aoMap = childItem; + } + + if (pbrConfigMap != nullptr && pbrConfigMap->generatedAsset) + { + if (roughnessMap != nullptr || metalnessMap != nullptr || aoMap != nullptr) + { + U32 channelKey[4] = { 0,1,2,3 }; + + GFX->getTextureManager()->saveCompositeTexture(aoMap->filePath.getFullPath(), roughnessMap->filePath.getFullPath(), metalnessMap->filePath.getFullPath(), "", + channelKey, pbrConfigMap->filePath.getFullPath(), &GFXTexturePersistentProfile); + } + } + } + + FileObject* file = new FileObject(); + file->registerObject(); + + //Now write the script file containing our material out + if (file->openForWrite(scriptPath.c_str())) + { + file->writeLine((U8*)"//--- OBJECT WRITE BEGIN ---"); + + char lineBuffer[1024]; + dSprintf(lineBuffer, 1024, "singleton Material(%s) {", assetName); + file->writeLine((U8*)lineBuffer); + + dSprintf(lineBuffer, 1024, " mapTo=\"%s\";", assetName); + file->writeLine((U8*)lineBuffer); + + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip || !childItem->processed || childItem->assetType.compare("ImageAsset") != 0) + continue; + + String mapFieldName = ""; + + if (childItem->imageSuffixType.compare("Albedo") == 0) + { + mapFieldName = "DiffuseMap"; + } + + String path = childItem->filePath.getFullFileName(); + dSprintf(lineBuffer, 1024, " %s[0] = \"%s\";", mapFieldName.c_str(), path.c_str()); + file->writeLine((U8*)lineBuffer); + + dSprintf(lineBuffer, 1024, " %sAsset[0] = \"%s:%s\";", mapFieldName.c_str(), targetModuleId.c_str(), childItem->assetName.c_str()); + file->writeLine((U8*)lineBuffer); + } + + file->writeLine((U8*)"};"); + file->writeLine((U8*)"//--- OBJECT WRITE END ---"); + + file->close(); + } + else + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to write asset script file %s", scriptPath.c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + + return tamlPath; +} + +Torque::Path AssetImporter::importShapeAsset(AssetImportObject* assetItem) +{ + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Beginning importing of Shape Asset: %s", assetItem->assetName.c_str()); + activityLog.push_back(importLogBuffer); + + ShapeAsset* newAsset = new ShapeAsset(); + newAsset->registerObject(); + + StringTableEntry assetName = StringTable->insert(assetItem->assetName.c_str()); + + String shapeFileName = assetItem->filePath.getFileName() + "." + assetItem->filePath.getExtension(); + String assetPath = targetPath + "/" + shapeFileName; + String constructorPath = targetPath + "/" + assetItem->filePath.getFileName() + ".cs"; + String tamlPath = targetPath + "/" + assetName + ".asset.taml"; + String originalPath = assetItem->filePath.getFullPath().c_str(); + + char qualifiedFromFile[2048]; + char qualifiedToFile[2048]; + + Platform::makeFullPathName(originalPath.c_str(), qualifiedFromFile, sizeof(qualifiedFromFile)); + Platform::makeFullPathName(assetPath.c_str(), qualifiedToFile, sizeof(qualifiedToFile)); + + newAsset->setAssetName(assetName); + newAsset->setShapeFile(shapeFileName.c_str()); + newAsset->setDataField(StringTable->insert("originalFilePath"), nullptr, qualifiedFromFile); + + //iterate through and write out the material maps dependencies + S32 dependencySlotId = 0; + for (U32 i = 0; i < assetItem->childAssetItems.size(); i++) + { + AssetImportObject* childItem = assetItem->childAssetItems[i]; + + if (childItem->skip || !childItem->processed) + continue; + + if (childItem->assetType.compare("MaterialAsset") == 0) + { + char dependencyFieldName[64]; + dSprintf(dependencyFieldName, 64, "materialSlot%i", dependencySlotId); + + char dependencyFieldDef[512]; + dSprintf(dependencyFieldDef, 512, "@Asset=%s:%s", targetModuleId.c_str(), childItem->assetName.c_str()); + + newAsset->setDataField(StringTable->insert(dependencyFieldName), nullptr, dependencyFieldDef); + + dependencySlotId++; + } + else if (childItem->assetType.compare("ShapeAnimationAsset") == 0) + { + char dependencyFieldName[64]; + dSprintf(dependencyFieldName, 64, "animationSequence%i", dependencySlotId); + + char dependencyFieldDef[512]; + dSprintf(dependencyFieldDef, 512, "@Asset=%s:%s", targetModuleId.c_str(), childItem->assetName.c_str()); + + newAsset->setDataField(StringTable->insert(dependencyFieldName), nullptr, dependencyFieldDef); + + dependencySlotId++; + } + } + + Taml tamlWriter; + bool importSuccessful = tamlWriter.write(newAsset, tamlPath.c_str()); + + if (!importSuccessful) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to write asset taml file %s", tamlPath.c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + + if (!isReimport) + { + bool isInPlace = !dStrcmp(qualifiedFromFile, qualifiedToFile); + + if (!isInPlace && !dPathCopy(qualifiedFromFile, qualifiedToFile, !isReimport)) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Unable to copy file %s", assetItem->filePath.getFullPath().c_str()); + activityLog.push_back(importLogBuffer); + return ""; + } + } + + //find/create shape constructor + TSShapeConstructor* constructor = TSShapeConstructor::findShapeConstructor(Torque::Path(qualifiedToFile).getFullPath()); + if (constructor == nullptr) + { + constructor = new TSShapeConstructor(qualifiedToFile); + + String constructorName = assetItem->filePath.getFileName() + "_" + assetItem->filePath.getExtension().substr(0, 3); + constructorName.replace("-", "_"); + constructorName.replace(".", "_"); + constructorName = Sim::getUniqueName(constructorName.c_str()); + constructor->registerObject(constructorName.c_str()); + } + + //now we write the import config logic into the constructor itself to ensure we load like we wanted it to + String neverImportMats; + + if (activeImportConfig.IgnoreMaterials.isNotEmpty()) + { + U32 ignoredMatNamesCount = StringUnit::getUnitCount(activeImportConfig.IgnoreMaterials, ",;"); + for (U32 i = 0; i < ignoredMatNamesCount; i++) + { + if (i == 0) + neverImportMats = StringUnit::getUnit(activeImportConfig.IgnoreMaterials, i, ",;"); + else + neverImportMats += String("\t") + StringUnit::getUnit(activeImportConfig.IgnoreMaterials, i, ",;"); + } + } + + if (activeImportConfig.DoUpAxisOverride) + { + S32 upAxis; + if (activeImportConfig.UpAxisOverride.compare("X_AXIS") == 0) + { + upAxis = domUpAxisType::UPAXISTYPE_X_UP; + } + else if(activeImportConfig.UpAxisOverride.compare("Y_AXIS") == 0) + { + upAxis = domUpAxisType::UPAXISTYPE_Y_UP; + } + else if(activeImportConfig.UpAxisOverride.compare("Z_AXIS") == 0) + { + upAxis = domUpAxisType::UPAXISTYPE_Z_UP; + } + constructor->mOptions.upAxis = (domUpAxisType)upAxis; + } + + if (activeImportConfig.DoScaleOverride) + constructor->mOptions.unit = activeImportConfig.ScaleOverride; + else + constructor->mOptions.unit = -1; + + enum eAnimTimingType + { + FrameCount = 0, + Seconds = 1, + Milliseconds = 1000 + }; + + S32 lodType; + if (activeImportConfig.LODType.compare("TrailingNumber") == 0) + lodType = ColladaUtils::ImportOptions::eLodType::TrailingNumber; + else if (activeImportConfig.LODType.compare("SingleSize") == 0) + lodType = ColladaUtils::ImportOptions::eLodType::SingleSize; + else if (activeImportConfig.LODType.compare("DetectDTS") == 0) + lodType = ColladaUtils::ImportOptions::eLodType::DetectDTS; + constructor->mOptions.lodType = (ColladaUtils::ImportOptions::eLodType)lodType; + + constructor->mOptions.singleDetailSize = activeImportConfig.convertLeftHanded; + constructor->mOptions.alwaysImport = activeImportConfig.ImportedNodes; + constructor->mOptions.neverImport = activeImportConfig.IgnoreNodes; + constructor->mOptions.alwaysImportMesh = activeImportConfig.ImportMeshes; + constructor->mOptions.neverImportMesh = activeImportConfig.IgnoreMeshes; + constructor->mOptions.ignoreNodeScale = activeImportConfig.IgnoreNodeScale; + constructor->mOptions.adjustCenter = activeImportConfig.AdjustCenter; + constructor->mOptions.adjustFloor = activeImportConfig.AdjustFloor; + + constructor->mOptions.convertLeftHanded = activeImportConfig.convertLeftHanded; + constructor->mOptions.calcTangentSpace = activeImportConfig.calcTangentSpace; + constructor->mOptions.genUVCoords = activeImportConfig.genUVCoords; + constructor->mOptions.flipUVCoords = activeImportConfig.flipUVCoords; + constructor->mOptions.findInstances = activeImportConfig.findInstances; + constructor->mOptions.limitBoneWeights = activeImportConfig.limitBoneWeights; + constructor->mOptions.joinIdenticalVerts = activeImportConfig.JoinIdenticalVerts; + constructor->mOptions.reverseWindingOrder = activeImportConfig.reverseWindingOrder; + constructor->mOptions.invertNormals = activeImportConfig.invertNormals; + constructor->mOptions.removeRedundantMats = activeImportConfig.removeRedundantMats; + + S32 animTimingType; + if (activeImportConfig.animTiming.compare("FrameCount") == 0) + animTimingType = ColladaUtils::ImportOptions::eAnimTimingType::FrameCount; + else if (activeImportConfig.animTiming.compare("Seconds") == 0) + animTimingType = ColladaUtils::ImportOptions::eAnimTimingType::Seconds; + else if (activeImportConfig.animTiming.compare("Milliseconds") == 0) + animTimingType = ColladaUtils::ImportOptions::eAnimTimingType::Milliseconds; + constructor->mOptions.animTiming = (ColladaUtils::ImportOptions::eAnimTimingType)animTimingType; + + constructor->mOptions.animFPS = activeImportConfig.animFPS; + + constructor->mOptions.neverImportMat = neverImportMats; + + if (!constructor->save(constructorPath.c_str())) + { + dSprintf(importLogBuffer, sizeof(importLogBuffer), "Error! Failed to save shape constructor file to %s", constructorPath.c_str()); + activityLog.push_back(importLogBuffer); + } + + return tamlPath; +} diff --git a/Engine/source/T3D/assets/assetImporter.h b/Engine/source/T3D/assets/assetImporter.h new file mode 100644 index 000000000..7b4710a2c --- /dev/null +++ b/Engine/source/T3D/assets/assetImporter.h @@ -0,0 +1,680 @@ +#pragma once + +#include "assets/assetPtr.h" +#include "assets/assetManager.h" +#include "module/moduleManager.h" +#include "util/settings.h" +#include "gui\controls\guiTreeViewCtrl.h" + +/// +/// AssetImportConfig is a SimObject derived object intended to act as a container for all the necessary configuration data when running the Asset Importer. +/// It dictates if and how any given asset type will be processed when running an import action. This is because the Asset Importer utilizes a lot of informed logic +/// to try and automate as much of the import process as possible. In theory, you would run the import on a given file, and based on your config the importer will do +/// everything from importing the designated file, as well as finding and importing any associated files such as images or materials, and prepping the objects at time +/// of import to avoid as much manual post-processing as possible. +/// +class AssetImportConfig : public SimObject +{ + //General Settings +public: + /// + /// Duplicate Asset Auto-Resolution Action. Options are None, AutoPrune, AutoRename + /// + String DuplicatAutoResolution; + + /// + /// Indicates if warnings should be treated as errors. + /// + bool WarningsAsErrors; + + /// + /// Indicates if importing should be prevented from completing if any errors are detected at all + /// + bool PreventImportWithErrors; + + /// + /// Should the importer automatically prompt to find missing files if they are not detected automatically by the importer + /// + bool AutomaticallyPromptMissingFiles; + // + + // + //Mesh Settings + /// + /// Indicates if this config supports importing meshes + /// + bool ImportMesh; + + /// + /// Indicates if the up axis in the model file should be overridden + /// + bool DoUpAxisOverride; + + /// + /// If overriding, what axis should be used as up. Options are X_AXIS, Y_AXIS, Z_AXIS + /// + String UpAxisOverride; + + /// + /// Indicates if the scale in the model file should be overridden + /// + bool DoScaleOverride; + + /// + /// If overriding, what scale should be used + /// + F32 ScaleOverride; + + /// + /// Indicates if scale of nodes should be ignored + /// + bool IgnoreNodeScale; + + /// + /// Indicates if the center of the model file should be automatically recentered + /// + bool AdjustCenter; + + /// + /// Indicates if the floor height of the model file should be automatically zero'd + /// + bool AdjustFloor; + + /// + /// Indicates if submeshes should be collapsed down into a single main mesh + /// + bool CollapseSubmeshes; + + /// + /// Indicates what LOD mode the model file should utilize to process out LODs. Options are TrailingNumber, DetectDTS, SingleSize + /// + String LODType; + + //ImportAssetConfigSettingsList.addNewConfigSetting("TrailingNumber", "Trailing Number", "float", "", "2", "", "Mesh"); + /// + /// A list of what nodes should be guaranteed to be imported if found in the model file. Separated by either , or ; + /// + String ImportedNodes; + + /// + /// A list of what nodes should be guaranteed to not be imported if found in the model file. Separated by either , or ; + /// + String IgnoreNodes; + + /// + /// A list of what mesh objects should be guaranteed to be imported if found in the model file. Separated by either , or ; + /// + String ImportMeshes; + + /// + /// A list of what mesh objects should be guaranteed to not be imported if found in the model file. Separated by either , or ; + /// + String IgnoreMeshes; + + //Assimp/Collada params + /// + /// Flag to indicate the shape loader should convert to a left-handed coordinate system + /// + bool convertLeftHanded; + + /// + /// Should the shape loader calculate tangent space values + /// + bool calcTangentSpace; + + /// + /// Should the shape loader automatically prune redundant/duplicate materials + /// + bool removeRedundantMats; + + /// + /// Should the shape loader auto-generate UV Coordinates for the mesh. + /// + bool genUVCoords; + + /// + /// Should the UV coordinates be transformed. + /// + bool TransformUVs; + + /// + /// Should the UV coordinates be flipped + /// + bool flipUVCoords; + + /// + /// Should the shape loader automatically look for instanced submeshes in the model file + /// + bool findInstances; + + /// + /// Should the shape loader limit the bone weights + /// + bool limitBoneWeights; + + /// + /// Should the shape loader automatically merge identical/duplicate verts + /// + bool JoinIdenticalVerts; + + /// + /// Should the shape loader reverse the winding order of the mesh's face indicies + /// + bool reverseWindingOrder; + + /// + /// Should the normals on the model be inverted + /// + bool invertNormals; + // + + // + //Materials + /// + /// Does this config allow for importing of materials + /// + bool ImportMaterials; + + /// + /// When importing a material, should it automatically attempt to merge Roughness, AO and Metalness maps into a single, composited PBR Configuration map + /// + bool CreatePBRConfig; + + /// + /// When generating a material off of an importing image, should the importer force appending a diffusemap suffix onto the end to avoid potential naming confusion. + /// e.g. MyCoolStuff.png is imported, generating MyCoolStuff material asset and MyCoolStuff_Diffuse image asset + /// + bool UseDiffuseSuffixOnOriginImage; + + /// + /// Should the importer try and use existing material assets in the game directory if at all possible. (Not currently utilized) + /// + bool UseExistingMaterials; + + /// + /// A list of material names that should not be imported. Separated by either , or ; + /// + String IgnoreMaterials; + + /// + /// When processing a material asset, should the importer attempt to populate the various material maps on it by looking up common naming conventions for potentially relevent image files + /// e.g. If MyCoolStuff_Diffuse.png is imported, generating MyCoolStuff material, it would also find MyCoolStuff_Normal and MyCoolStuff_PBR images and map them to the normal and PBRConfig maps respectively automatically + /// + bool PopulateMaterialMaps; + + // + //Animations + /// + /// Does this config allow for importing Shape Animations + /// + bool ImportAnimations; + + /// + /// When importing a shape file, should the animations within be separated out into unique files + /// + bool SeparateAnimations; + + /// + /// If separating animations out from a source file, what prefix should be added to the names for grouping association + /// + String SeparateAnimationPrefix; + + /// + /// Defines the animation timing for the given animation sequence. Options are FrameTime, Seconds, Milliseconds + /// + String animTiming; + + /// + /// The FPS of the animation sequence + /// + F32 animFPS; + + // + //Collision + /// + /// Does this configuration generate collision geometry when importing. (Not currently enabled) + /// + bool GenerateCollisions; + + /// + /// What sort of collision geometry is generated. (Not currently enabled) + /// + String GenCollisionType; + + /// + /// What prefix is added to the collision geometry generated. (Not currently enabled) + /// + String CollisionMeshPrefix; + + /// + /// Does this configuration generate Line of Sight collision geometry. (Not currently enabled) + /// + bool GenerateLOSCollisions; + + /// + /// What sort of Line of Sight collision geometry is generated. (Not currently enabled) + /// + String GenLOSCollisionType; + + /// + /// What prefix is added to the Line of Sight collision geometry generated. (Not currently enabled) + /// + String LOSCollisionMeshPrefix; + + // + //Images + /// + /// Does this configuration support importing images. + /// + bool importImages; + + /// + /// What is the default ImageType images are imported as. Options are: N/A, Diffuse, Normal, Metalness, Roughness, AO, PBRConfig, GUI, Cubemap + /// + String ImageType; + + /// + /// What type of suffixes are scanned to detect if an importing image is a diffuse map. + /// e.g. _Albedo or _Color + /// + String DiffuseTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a normal map. + /// e.g. _Normal or _Norm + /// + String NormalTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a metalness map. + /// e.g. _Metalness or _Metal + /// + String MetalnessTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a roughness map. + /// e.g. _roughness or _rough + /// + String RoughnessTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a smoothness map. + /// e.g. _smoothness or _smooth + /// + String SmoothnessTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a ambient occlusion map. + /// e.g. _ambient or _ao + /// + String AOTypeSuffixes; + + /// + /// What type of suffixes are scanned to detect if an importing image is a PBRConfig map. + /// e.g. _Composite or _PBR + /// + String PBRTypeSuffixes; + + /// + /// Indicates what filter mode images imported with this configuration utilizes. Options are Linear, Bilinear, Trilinear + /// + String TextureFilteringMode; + + /// + /// Indicates if images imported with this configuration utilize mipmaps + /// + bool UseMips; + + /// + /// Indicates if images imported with this configuration are in an HDR format + /// + bool IsHDR; + + /// + /// Indicates what amount of scaling images imported with this configuration use + /// + F32 Scaling; + + /// + /// Indicates if images imported with this configuration are compressed + /// + bool Compressed; + + /// + /// Indicates if images imported with this configuration generate a parent material for it as well + /// + bool GenerateMaterialOnImport; + + // + //Sounds + /// + /// Indicates if sounds are imported with this configuration + /// + bool importSounds; + + /// + /// Indicates what amount the volume is adjusted on sounds imported with this configuration + /// + F32 VolumeAdjust; + + /// + /// Indicates what amount the pitch is adjusted on sounds imported with this configuration + /// + F32 PitchAdjust; + + /// + /// Indicates if sounds imported with this configuration are compressed + /// + bool Compressed; + +public: + AssetImportConfig(); + virtual ~AssetImportConfig(); + + /// Engine. + static void initPersistFields(); + + /// + /// Loads a configuration from a Settings object + /// @param configSettings, The Settings object to load from + /// @param configName, The name of the configuration setting to load from the setting object + /// + void loadImportConfig(Settings* configSettings, String configName); + + /// Declare Console Object. + DECLARE_CONOBJECT(AssetImportConfig); +}; + +/// +/// AssetImportConfig is a SimObject derived object that represents and holds information for an importing asset. They are generated and processed by the AssetImporter +/// +class AssetImportObject : public SimObject +{ + typedef SimObject Parent; + +public: + /// + /// What type is the importing asset + /// + String assetType; + + /// + /// What is the source file path of the importing asset + /// + Torque::Path filePath; + + /// + /// What is the asset's name + /// + String assetName; + + /// + /// What is the original, unmodified by processing, asset name + /// + String cleanAssetName; + + /// + /// What is the name of the module this asset will be importing into + /// + String moduleName; + + /// + /// What is the current status of this asset item in it's import process + /// + String status; + + /// + /// If there is a warning or error status, what type is the condition for this asset item + /// + String statusType; + + /// + /// What is the articulated information of the status of the asset. Contains the error or warning log data. + /// + String statusInfo; + + /// + /// Is the asset item currently flagged as dirty + /// + bool dirty; + + /// + /// Is this asset item marked to be skipped. If it is, it's usually due to being marked as deleted + /// + bool skip; + + /// + /// Has the asset item been processed + /// + bool processed; + + /// + /// Is this specific asset item generated as part of the import process of another item + /// + bool generatedAsset; + + /// + /// What, if any, importing asset item is this item's parent + /// + AssetImportObject* parentAssetItem; + + /// + /// What, if any, importing asset item are children of this item + /// + Vector< AssetImportObject*> childAssetItems; + + /// + /// What is the ultimate asset taml file path for this import item + /// + String tamlFilePath; + + // + /// + /// Specific to ImageAsset type + /// What is the image asset's suffix type. Options are: Albedo, Normal, Roughness, AO, Metalness, PBRConfig + /// + String imageSuffixType; + + // + /// + /// Specific to ShapeAsset type + /// Processed information about the shape file. Contains numbers and lists of meshes, materials and animations + /// + GuiTreeViewCtrl* shapeInfo; + +public: + AssetImportObject(); + virtual ~AssetImportObject(); + + /// Engine. + static void initPersistFields(); + + /// Declare Console Object. + DECLARE_CONOBJECT(AssetImportObject); +}; + +/// +/// AssetImporter is a SimObject derived object that processed and imports files and turns them into assets if they are of valid types. +/// Utilizes an AssetImportConfig to inform the importing process's behavior. +/// +class AssetImporter : public SimObject +{ + typedef SimObject Parent; + + /// + /// The import configuration that is currently being utilized + /// + AssetImportConfig activeImportConfig; + + /// + /// A log of all the actions that have been performed by the importer + /// + Vector activityLog; + + /// + /// A list of AssetImportObjects that are to be imported + /// + Vector importingAssets; + + /// + /// A list of AssetImportObjects that are to be imported. These are unmodified by anything in the importing session, and are only used for resetting purposes; + /// + Vector originalImportingAssets; + + /// + /// The Id of the module the assets are to be imported into + /// + String targetModuleId; + + /// + /// The path any imported assets are placed in as their destination + /// + String targetPath; + + /// + /// Are there any issues with any of the current import asset items + /// + bool importIssues; + + /// + /// Is this import action a reimport of an existing asset + /// + bool isReimport; + + /// + /// Has the heirarchy of asset import items changed due to processing + /// + bool assetHeirarchyChanged; + + /// + /// A string used for writing into the importLog + /// + char importLogBuffer[1024]; + +public: + AssetImporter(); + virtual ~AssetImporter(); + + /// Engine. + static void initPersistFields(); + + /// Declare Console Object. + DECLARE_CONOBJECT(AssetImporter); + + /// + /// Sets the target path for the assets being imported to be deposited into + /// @param pTargetPath, The filePath of the destination point assets are imported into + /// + void setTargetPath(Torque::Path pTargetPath) { targetPath = pTargetPath; } + + /// + /// Processes a file into an AssetImportObject and adds it to the session for importing + /// @param filePath, The filePath of the file to be imported in as an asset + /// @return AssetImportObject that was created + /// + AssetImportObject* addImportingFile(Torque::Path filePath); + + /// + /// Adds an importing asset to the current session + /// @param assetType, Type of the asset being imported + /// @param filePath, path of the file to be imported + /// @param parentItem, AssetImportObject that the new item is a child of. null if no parent + /// @param assetNameOverride, If not blank, will be the new item's assetName instead of being created off of the filePath + /// @return AssetImportObject that was created + /// + AssetImportObject* addImportingAsset(String assetType, Torque::Path filePath, AssetImportObject* parentItem, String assetNameOverride); + + /// + /// Deletes the asset item from the import session. Affects the item's children as well + /// @param assetItem, asset item to be marked as deleted + /// + void deleteImportingAsset(AssetImportObject* assetItem); + + /// + /// Finds an asset item in the session if it exists, by name + /// @param assetName, Asset name to find + /// @param assetItem, if null, will loop over and recurse the main import asset items, if a specific AssetImportObject is passed in, it will recurse it's children + /// @return AssetImportObject that was found + /// + AssetImportObject* findImportingAssetByName(String assetName, AssetImportObject* assetItem = nullptr); + + /// + /// Finds the module associated with a given file path + /// @param filePath, File path to parse the the module from + /// @return ModuleDefinition that was found + /// + ModuleDefinition* getModuleFromPath(Torque::Path filePath); + + /// + /// Parses an asset's name to try and find if any of the import config's suffix lists match to it + /// @param assetName, Asset name to parse any image suffix out of + /// @param suffixType, output, The suffix type that was matched to the asset name + /// @return suffix that matched to the asset name + /// + String parseImageSuffixes(String assetName, String* suffixType); + String getAssetTypeByFile(Torque::Path filePath); + void resetImportSession(); + void resetImportAsset(AssetImportObject* assetItem); + S32 getActivityLogLineCount(); + String getActivityLogLine(U32 line); + void dumpActivityLog(); + + S32 getAssetItemCount(); + AssetImportObject* getAssetItem(U32 index); + S32 getAssetItemChildCount(AssetImportObject* assetItem); + AssetImportObject* getAssetItemChild(AssetImportObject* assetItem, U32 index); + + /// + /// Finds an asset item in the session if it exists + /// @param assetName, Asset name to find + /// @param assetItem, if null, will loop over and recurse the main import asset items, if a specific AssetImportObject is passed in, it will recurse it's children + /// @return AssetImportObject that was found + /// + void processImportAssets(AssetImportObject* assetItem = nullptr); + void processImageAsset(AssetImportObject* assetItem); + void processMaterialAsset(AssetImportObject* assetItem); + void processShapeAsset(AssetImportObject* assetItem); + void processShapeMaterialInfo(AssetImportObject* assetItem, S32 materialItemId); + + bool validateAssets(); + void validateAsset(AssetImportObject* assetItem); + + /// + /// Finds an asset item in the session if it exists + /// @param assetName, Asset name to find + /// @param assetItem, if null, will loop over and recurse the main import asset items, if a specific AssetImportObject is passed in, it will recurse it's children + /// @return AssetImportObject that was found + /// + void resetAssetValidationStatus(AssetImportObject* assetItem = nullptr); + + /// + /// Finds an asset item in the session if it exists + /// @param assetName, Asset name to find + /// @param assetItem, if null, will loop over and recurse the main import asset items, if a specific AssetImportObject is passed in, it will recurse it's children + /// @return AssetImportObject that was found + /// + bool checkAssetForCollision(AssetImportObject* assetItemToCheckFor, AssetImportObject* assetItem = nullptr); + + void resolveAssetItemIssues(AssetImportObject* assetItem); + + /// + /// Runs the import process on a single file in-place. Intended primarily for autoimporting a loose file that's in the game directory. + /// @param filePath, The filePath of the file to be imported in as an asset + /// @return AssetId of the asset that was imported. If import failed, it will be empty. + /// + StringTableEntry autoImportFile(Torque::Path filePath); + + /// + /// Finds an asset item in the session if it exists + /// @param assetName, Asset name to find + /// @param assetItem, if null, will loop over and recurse the main import asset items, if a specific AssetImportObject is passed in, it will recurse it's children + /// @return AssetImportObject that was found + /// + void importAssets(AssetImportObject* assetItem = nullptr); + Torque::Path importImageAsset(AssetImportObject* assetItem); + Torque::Path importMaterialAsset(AssetImportObject* assetItem); + Torque::Path importShapeAsset(AssetImportObject* assetItem); + + // + AssetImportConfig* getImportConfig() { return &activeImportConfig; } +}; diff --git a/Engine/source/T3D/assets/assetImporter_ScriptBinding.h b/Engine/source/T3D/assets/assetImporter_ScriptBinding.h new file mode 100644 index 000000000..6c3d9fedb --- /dev/null +++ b/Engine/source/T3D/assets/assetImporter_ScriptBinding.h @@ -0,0 +1,140 @@ +#pragma once + +#include "console/engineAPI.h" +#include "assetImporter.h" + +//Console Functions + +DefineEngineMethod(AssetImportConfig, loadImportConfig, void, (Settings* configSettings, String configName), (nullAsType(), ""), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->loadImportConfig(configSettings, configName); +} + +DefineEngineMethod(AssetImporter, setTargetPath, void, (String path), (""), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->setTargetPath(path); +} + +DefineEngineMethod(AssetImporter, resetImportSession, void, (), , + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->resetImportSession(); +} + +DefineEngineMethod(AssetImporter, dumpActivityLog, void, (), , + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->dumpActivityLog(); +} + +DefineEngineMethod(AssetImporter, getActivityLogLineCount, S32, (),, + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->getActivityLogLineCount(); +} + +DefineEngineMethod(AssetImporter, getActivityLogLine, String, (S32 i), (0), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->getActivityLogLine(0); +} + +DefineEngineMethod(AssetImporter, autoImportFile, String, (String path), (""), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->autoImportFile(path); +} + +DefineEngineMethod(AssetImporter, addImportingFile, AssetImportObject*, (String path), (""), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->addImportingFile(path); +} + +DefineEngineMethod(AssetImporter, processImportingAssets, void, (), , + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->processImportAssets(); +} + +DefineEngineMethod(AssetImporter, validateImportingAssets, bool, (), , + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->validateAssets(); +} + +DefineEngineMethod(AssetImporter, resolveAssetItemIssues, void, (AssetImportObject* assetItem), (nullAsType< AssetImportObject*>()), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + object->resolveAssetItemIssues(assetItem); +} + +DefineEngineMethod(AssetImporter, importAssets, void, (),, + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->importAssets(); +} + +DefineEngineMethod(AssetImporter, getAssetItemCount, S32, (),, + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->getAssetItemCount(); +} + +DefineEngineMethod(AssetImporter, getAssetItem, AssetImportObject*, (S32 index), (0), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + return object->getAssetItem(index); +} + +DefineEngineMethod(AssetImporter, getAssetItemChildCount, S32, (AssetImportObject* assetItem), (nullAsType< AssetImportObject*>()), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + if (assetItem == nullptr) + return 0; + + return object->getAssetItemChildCount(assetItem); +} + +DefineEngineMethod(AssetImporter, getAssetItemChild, AssetImportObject*, (AssetImportObject* assetItem, S32 index), (nullAsType< AssetImportObject*>(), 0), + "Creates a new script asset using the targetFilePath.\n" + "@return The bool result of calling exec") +{ + if (assetItem == nullptr) + return nullptr; + + return object->getAssetItemChild(assetItem, index); +} + + +/*DefineEngineFunction(enumColladaForImport, bool, (const char* shapePath, const char* ctrl, bool loadCachedDts), ("", "", true), + "(string shapePath, GuiTreeViewCtrl ctrl) Collect scene information from " + "a COLLADA file and store it in a GuiTreeView control. This function is " + "used by the COLLADA import gui to show a preview of the scene contents " + "prior to import, and is probably not much use for anything else.\n" + "@param shapePath COLLADA filename\n" + "@param ctrl GuiTreeView control to add elements to\n" + "@param loadCachedDts dictates if it should try and load the cached dts file if it exists" + "@return true if successful, false otherwise\n" + "@ingroup Editors\n" + "@internal") +{ + return enumColladaForImport(shapePath, ctrl, loadCachedDts); +}*/