You're breathtaking!

This commit is contained in:
Wojtek Figat
2020-12-07 23:40:54 +01:00
commit 6fb9eee74c
5143 changed files with 1153594 additions and 0 deletions

View File

@@ -0,0 +1,488 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_ASSETS_IMPORTER
#include "AssetsImportingManager.h"
#include "Engine/Core/Utilities.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Threading/MainThreadTask.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Cache/AssetsCache.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Core/Log.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/Platform.h"
#include "ImportTexture.h"
#include "ImportModelFile.h"
#include "ImportAudio.h"
#include "ImportShader.h"
#include "ImportFont.h"
#include "CreateMaterial.h"
#include "CreateMaterialInstance.h"
#include "CreateRawData.h"
#include "CreateCollisionData.h"
#include "CreateAnimationGraph.h"
#include "CreateSkeletonMask.h"
#include "CreateParticleEmitter.h"
#include "CreateParticleSystem.h"
#include "CreateSceneAnimation.h"
#include "CreateMaterialFunction.h"
#include "CreateParticleEmitterFunction.h"
#include "CreateAnimationGraphFunction.h"
#include "CreateVisualScript.h"
// Tags used to detect asset creation mode
const String AssetsImportingManager::CreateTextureTag(TEXT("Texture"));
const String AssetsImportingManager::CreateTextureAsTextureDataTag(TEXT("TextureAsTextureData"));
const String AssetsImportingManager::CreateTextureAsInitDataTag(TEXT("TextureAsInitData"));
const String AssetsImportingManager::CreateMaterialTag(TEXT("Material"));
const String AssetsImportingManager::CreateMaterialInstanceTag(TEXT("MaterialInstance"));
const String AssetsImportingManager::CreateCubeTextureTag(TEXT("CubeTexture"));
const String AssetsImportingManager::CreateModelTag(TEXT("Model"));
const String AssetsImportingManager::CreateRawDataTag(TEXT("RawData"));
const String AssetsImportingManager::CreateCollisionDataTag(TEXT("CollisionData"));
const String AssetsImportingManager::CreateAnimationGraphTag(TEXT("AnimationGraph"));
const String AssetsImportingManager::CreateSkeletonMaskTag(TEXT("SkeletonMask"));
const String AssetsImportingManager::CreateParticleEmitterTag(TEXT("ParticleEmitter"));
const String AssetsImportingManager::CreateParticleSystemTag(TEXT("ParticleSystem"));
const String AssetsImportingManager::CreateSceneAnimationTag(TEXT("SceneAnimation"));
const String AssetsImportingManager::CreateMaterialFunctionTag(TEXT("MaterialFunction"));
const String AssetsImportingManager::CreateParticleEmitterFunctionTag(TEXT("ParticleEmitterFunction"));
const String AssetsImportingManager::CreateAnimationGraphFunctionTag(TEXT("AnimationGraphFunction"));
const String AssetsImportingManager::CreateVisualScriptTag(TEXT("VisualScript"));
class AssetsImportingManagerService : public EngineService
{
public:
AssetsImportingManagerService()
: EngineService(TEXT("AssetsImportingManager"), -400)
{
}
bool Init() override;
void Dispose() override;
};
AssetsImportingManagerService AssetsImportingManagerServiceInstance;
Array<AssetImporter> AssetsImportingManager::Importers;
Array<AssetCreator> AssetsImportingManager::Creators;
CreateAssetContext::CreateAssetContext(const StringView& inputPath, const StringView& outputPath, const Guid& id, void* arg)
{
InputPath = inputPath;
TargetAssetPath = outputPath;
CustomArg = arg;
Data.Header.ID = id;
SkipMetadata = false;
// TODO: we should use ASNI only chars path (Assimp can use only that kind)
OutputPath = Content::CreateTemporaryAssetPath();
}
CreateAssetResult CreateAssetContext::Run(const CreateAssetFunction& callback)
{
ASSERT(callback.IsBinded());
// Call action
auto result = callback(*this);
if (result != CreateAssetResult::Ok)
return result;
// Validate assigned TypeID
if (Data.Header.TypeName.IsEmpty())
{
LOG(Warning, "Assigned asset TypeName is invalid.");
return CreateAssetResult::InvalidTypeID;
}
// Add import metadata to the file (if it's empty)
if (!SkipMetadata && Data.Metadata.IsInvalid())
{
rapidjson_flax::StringBuffer buffer;
CompactJsonWriter writer(buffer);
writer.StartObject();
{
AddMeta(writer);
}
writer.EndObject();
Data.Metadata.Copy((const byte*)buffer.GetString(), (uint32)buffer.GetSize());
}
// Save file
result = Save();
if (result == CreateAssetResult::Ok)
{
_applyChangesResult = CreateAssetResult::Abort;
INVOKE_ON_MAIN_THREAD(CreateAssetContext, CreateAssetContext::ApplyChanges, this);
result = _applyChangesResult;
}
FileSystem::DeleteFile(OutputPath);
return result;
}
bool CreateAssetContext::AllocateChunk(int32 index)
{
// Check index
if (index < 0 || index >= ASSET_FILE_DATA_CHUNKS)
{
LOG(Warning, "Invalid asset chunk index {0}.", index);
return true;
}
// Check if chunk has been already allocated
if (Data.Header.Chunks[index] != nullptr)
{
LOG(Warning, "Asset chunk {0} has been already allocated.", index);
return true;
}
// Create new chunk
const auto chunk = New<FlaxChunk>();
Data.Header.Chunks[index] = chunk;
if (chunk == nullptr)
{
OUT_OF_MEMORY;
}
return false;
}
void CreateAssetContext::AddMeta(JsonWriter& writer) const
{
writer.JKEY("ImportPath");
writer.String(InputPath);
writer.JKEY("ImportUsername");
writer.String(Platform::GetUserName());
}
CreateAssetResult CreateAssetContext::Save()
{
return FlaxStorage::Create(OutputPath, Data) ? CreateAssetResult::CannotSaveFile : CreateAssetResult::Ok;
}
void CreateAssetContext::ApplyChanges()
{
// Get access
auto storage = ContentStorageManager::TryGetStorage(TargetAssetPath);
if (storage && storage->IsLoaded())
{
storage->CloseFileHandles();
}
// Move file
if (FileSystem::MoveFile(TargetAssetPath, OutputPath, true))
{
LOG(Warning, "Cannot move imported file to the destination path.");
_applyChangesResult = CreateAssetResult::CannotSaveFile;
return;
}
// Reload (any asset using it will receive OnStorageReloaded event and handle it)
if (storage)
{
storage->Reload();
}
_applyChangesResult = CreateAssetResult::Ok;
}
const AssetImporter* AssetsImportingManager::GetImporter(const String& extension)
{
for (int32 i = 0; i < Importers.Count(); i++)
{
if (Importers[i].FileExtension.Compare(extension, StringSearchCase::IgnoreCase) == 0)
return &Importers[i];
}
return nullptr;
}
const AssetCreator* AssetsImportingManager::GetCreator(const String& tag)
{
for (int32 i = 0; i < Creators.Count(); i++)
{
if (Creators[i].Tag == tag)
return &Creators[i];
}
return nullptr;
}
bool AssetsImportingManager::Create(const CreateAssetFunction& importFunc, const StringView& outputPath, Guid& assetId, void* arg)
{
return Create(importFunc, StringView::Empty, outputPath, assetId, arg);
}
bool AssetsImportingManager::Create(const String& tag, const StringView& outputPath, Guid& assetId, void* arg)
{
const auto creator = GetCreator(tag);
if (creator == nullptr)
{
LOG(Warning, "Cannot find asset creator object for tag \'{0}\'.", tag);
return true;
}
return Create(creator->Callback, outputPath, assetId, arg);
}
bool AssetsImportingManager::Import(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION)));
LOG(Info, "Importing file '{0}' to '{1}'...", inputPath, outputPath);
// Check if input file exists
if (!FileSystem::FileExists(inputPath))
{
LOG(Error, "Missing file '{0}'", inputPath);
return true;
}
// Get file extension and try to find import function for it
const String extension = FileSystem::GetExtension(inputPath).ToLower();
// Special case for raw assets
const String assetExtension = ASSET_FILES_EXTENSION;
if (assetExtension.Compare(extension, StringSearchCase::IgnoreCase) == 0)
{
// Simply copy file (content layer will resolve duplicated IDs, etc.)
return FileSystem::CopyFile(outputPath, inputPath);
}
// Find valid importer for that file
const auto importer = GetImporter(extension);
if (importer == nullptr)
{
LOG(Error, "Cannot import file \'{0}\'. Unknown file type.", inputPath);
return true;
}
return Create(importer->Callback, inputPath, outputPath, assetId, arg);
}
bool AssetsImportingManager::ImportIfEdited(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
ASSERT(outputPath.EndsWith(StringView(ASSET_FILES_EXTENSION)));
// Check if asset not exists
if (!FileSystem::FileExists(outputPath))
{
return Import(inputPath, outputPath, assetId, arg);
}
// Check if need to reimport file (it could be checked via asset header but this way is faster and works in general)
const DateTime sourceEdited = FileSystem::GetFileLastEditTime(inputPath);
const DateTime assetEdited = FileSystem::GetFileLastEditTime(outputPath);
if (sourceEdited > assetEdited)
{
return Import(inputPath, outputPath, assetId, arg);
}
// No import
if (!assetId.IsValid())
{
AssetInfo assetInfo;
Content::GetAssetInfo(outputPath, assetInfo);
assetId = assetInfo.ID;
}
return false;
}
bool AssetsImportingManager::Create(const Function<CreateAssetResult(CreateAssetContext&)>& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg)
{
const auto startTime = Platform::GetTimeSeconds();
// Pick ID if not specified
if (!assetId.IsValid())
assetId = Guid::New();
// Check if asset at target path is loaded
AssetReference<Asset> asset = Content::GetAsset(outputPath);
if (asset)
{
// Use the same ID
assetId = asset->GetID();
}
else
{
// Check if asset already exists and isn't empty
if (FileSystem::FileExists(outputPath) && FileSystem::GetFileSize(outputPath) > 0)
{
// Load storage container
const auto storage = ContentStorageManager::GetStorage(outputPath);
if (storage)
{
// Try to load old asset header and then use old ID
Array<FlaxStorage::Entry> e;
storage->GetEntries(e);
if (e.Count() == 1)
{
// Override asset id (use the old value)
assetId = e[0].ID;
LOG(Info, "Asset already exists. Using old ID: {0}", assetId);
}
else
{
LOG(Warning, "File {0} is a package.", outputPath);
}
}
else
{
LOG(Warning, "Cannot open storage container at {0}", outputPath);
}
}
else
{
// Ensure that path exists
const String outputDirectory = StringUtils::GetDirectoryName(*outputPath);
if (FileSystem::CreateDirectory(outputDirectory))
{
LOG(Warning, "Cannot create directory '{0}'", outputDirectory);
}
}
}
// Import file
CreateAssetContext context(inputPath, outputPath, assetId, arg);
const auto result = context.Run(callback);
// Clear reference
asset = nullptr;
// Switch result
if (result == CreateAssetResult::Ok)
{
// Register asset
Content::GetRegistry()->RegisterAsset(context.Data.Header, outputPath);
// Done
const auto endTime = Platform::GetTimeSeconds();
LOG(Info, "Asset '{0}' imported in {2}s! {1}", outputPath, context.Data.Header.ToString(), Utilities::RoundTo2DecimalPlaces(endTime - startTime));
}
else if (result == CreateAssetResult::Abort)
{
// Do nothing
return true;
}
else
{
LOG(Error, "Cannot import file '{0}'! Result: {1}", inputPath, ::ToString(result));
return true;
}
return false;
}
bool AssetsImportingManagerService::Init()
{
// Initialize with in-build importers
AssetImporter InBuildImporters[] =
{
// Textures and Cube Textures
{ TEXT("tga"), ImportTexture::Import },
{ TEXT("dds"), ImportTexture::Import },
{ TEXT("png"), ImportTexture::Import },
{ TEXT("bmp"), ImportTexture::Import },
{ TEXT("gif"), ImportTexture::Import },
{ TEXT("tiff"), ImportTexture::Import },
{ TEXT("tif"), ImportTexture::Import },
{ TEXT("jpeg"), ImportTexture::Import },
{ TEXT("jpg"), ImportTexture::Import },
{ TEXT("hdr"), ImportTexture::Import },
{ TEXT("raw"), ImportTexture::Import },
// IES Profiles
{ TEXT("ies"), ImportTexture::ImportIES },
// Shaders
{ TEXT("shader"), ImportShader::Import },
// Audio
{ TEXT("wav"), ImportAudio::ImportWav },
{ TEXT("mp3"), ImportAudio::ImportMp3 },
#if COMPILE_WITH_OGG_VORBIS
{ TEXT("ogg"), ImportAudio::ImportOgg },
#endif
// Fonts
{ TEXT("ttf"), ImportFont::Import },
{ TEXT("otf"), ImportFont::Import },
// Models
{ TEXT("obj"), ImportModelFile::Import },
{ TEXT("fbx"), ImportModelFile::Import },
{ TEXT("x"), ImportModelFile::Import },
{ TEXT("dae"), ImportModelFile::Import },
{ TEXT("gltf"), ImportModelFile::Import },
{ TEXT("glb"), ImportModelFile::Import },
// Models (untested formats - may fail :/)
{ TEXT("blend"), ImportModelFile::Import },
{ TEXT("bvh"), ImportModelFile::Import },
{ TEXT("ase"), ImportModelFile::Import },
{ TEXT("ply"), ImportModelFile::Import },
{ TEXT("dxf"), ImportModelFile::Import },
{ TEXT("ifc"), ImportModelFile::Import },
{ TEXT("nff"), ImportModelFile::Import },
{ TEXT("smd"), ImportModelFile::Import },
{ TEXT("vta"), ImportModelFile::Import },
{ TEXT("mdl"), ImportModelFile::Import },
{ TEXT("md2"), ImportModelFile::Import },
{ TEXT("md3"), ImportModelFile::Import },
{ TEXT("md5mesh"), ImportModelFile::Import },
{ TEXT("q3o"), ImportModelFile::Import },
{ TEXT("q3s"), ImportModelFile::Import },
{ TEXT("ac"), ImportModelFile::Import },
{ TEXT("stl"), ImportModelFile::Import },
{ TEXT("lwo"), ImportModelFile::Import },
{ TEXT("lws"), ImportModelFile::Import },
{ TEXT("lxo"), ImportModelFile::Import },
};
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));
// Initialize with in-build creators
AssetCreator InBuildCreators[] =
{
// Textures
{ AssetsImportingManager::CreateTextureTag, ImportTexture::Import },
{ AssetsImportingManager::CreateTextureAsTextureDataTag, ImportTexture::ImportAsTextureData },
{ AssetsImportingManager::CreateTextureAsInitDataTag, ImportTexture::ImportAsInitData },
{ AssetsImportingManager::CreateCubeTextureTag, ImportTexture::ImportCube },
// Materials
{ AssetsImportingManager::CreateMaterialTag, CreateMaterial::Create },
{ AssetsImportingManager::CreateMaterialInstanceTag, CreateMaterialInstance::Create },
// Models
{ AssetsImportingManager::CreateModelTag, ImportModelFile::Create },
// Other
{ AssetsImportingManager::CreateRawDataTag, CreateRawData::Create },
{ AssetsImportingManager::CreateCollisionDataTag, CreateCollisionData::Create },
{ AssetsImportingManager::CreateAnimationGraphTag, CreateAnimationGraph::Create },
{ AssetsImportingManager::CreateSkeletonMaskTag, CreateSkeletonMask::Create },
{ AssetsImportingManager::CreateParticleEmitterTag, CreateParticleEmitter::Create },
{ AssetsImportingManager::CreateParticleSystemTag, CreateParticleSystem::Create },
{ AssetsImportingManager::CreateSceneAnimationTag, CreateSceneAnimation::Create },
{ AssetsImportingManager::CreateMaterialFunctionTag, CreateMaterialFunction::Create },
{ AssetsImportingManager::CreateParticleEmitterFunctionTag, CreateParticleEmitterFunction::Create },
{ AssetsImportingManager::CreateAnimationGraphFunctionTag, CreateAnimationGraphFunction::Create },
{ AssetsImportingManager::CreateVisualScriptTag, CreateVisualScript::Create },
};
AssetsImportingManager::Creators.Add(InBuildCreators, ARRAY_COUNT(InBuildCreators));
return false;
}
void AssetsImportingManagerService::Dispose()
{
// Cleanup
AssetsImportingManager::Importers.Clear();
AssetsImportingManager::Creators.Clear();
}
#endif

View File

@@ -0,0 +1,233 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Types.h"
/// <summary>
/// Assets Importing service allows to import or create new assets
/// </summary>
class AssetsImportingManager
{
public:
/// <summary>
/// The asset importers.
/// </summary>
static Array<AssetImporter> Importers;
/// <summary>
/// The asset creators.
/// </summary>
static Array<AssetCreator> Creators;
public:
/// <summary>
/// The create texture tag (using internal import pipeline to crate texture asset from custom image source).
/// </summary>
static const String CreateTextureTag;
/// <summary>
/// The create texture from raw data. Argument must be TextureData*.
/// </summary>
static const String CreateTextureAsTextureDataTag;
/// <summary>
/// The create texture from raw data. Argument must be TextureBase::InitData*.
/// </summary>
static const String CreateTextureAsInitDataTag;
/// <summary>
/// The create material tag.
/// </summary>
static const String CreateMaterialTag;
/// <summary>
/// The create material tag.
/// </summary>
static const String CreateMaterialInstanceTag;
/// <summary>
/// The create cube texture tag. Argument must be TextureData*.
/// </summary>
static const String CreateCubeTextureTag;
/// <summary>
/// The create model tag. Argument must be ModelData*.
/// </summary>
static const String CreateModelTag;
/// <summary>
/// The create raw data asset tag. Argument must be BytesContainer*.
/// </summary>
static const String CreateRawDataTag;
/// <summary>
/// The create collision data asset tag.
/// </summary>
static const String CreateCollisionDataTag;
/// <summary>
/// The create animation graph asset tag.
/// </summary>
static const String CreateAnimationGraphTag;
/// <summary>
/// The create skeleton mask asset tag.
/// </summary>
static const String CreateSkeletonMaskTag;
/// <summary>
/// The create particle emitter asset tag.
/// </summary>
static const String CreateParticleEmitterTag;
/// <summary>
/// The create particle system asset tag.
/// </summary>
static const String CreateParticleSystemTag;
/// <summary>
/// The create scene animation asset tag.
/// </summary>
static const String CreateSceneAnimationTag;
/// <summary>
/// The create material function asset tag.
/// </summary>
static const String CreateMaterialFunctionTag;
/// <summary>
/// The create particle graph function asset tag.
/// </summary>
static const String CreateParticleEmitterFunctionTag;
/// <summary>
/// The create animation graph function asset tag.
/// </summary>
static const String CreateAnimationGraphFunctionTag;
/// <summary>
/// The create visual script asset tag.
/// </summary>
static const String CreateVisualScriptTag;
public:
/// <summary>
/// Gets the asset importer by file extension.
/// </summary>
/// <param name="extension">The file extension.</param>
/// <returns>Importer or null if not found.</returns>
static const AssetImporter* GetImporter(const String& extension);
/// <summary>
/// Gets the asset creator by tag.
/// </summary>
/// <param name="tag">The tag.</param>
/// <returns>Creator or null if not found.</returns>
static const AssetCreator* GetCreator(const String& tag);
public:
/// <summary>
/// Creates new asset.
/// </summary>
/// <param name="importFunc">The import function.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="assetId">The asset identifier. If valid then used as new asset id. Set to the actual asset id after import.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Create(const CreateAssetFunction& importFunc, const StringView& outputPath, Guid& assetId, void* arg = nullptr);
/// <summary>
/// Creates new asset.
/// </summary>
/// <param name="importFunc">The import function.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Create(const CreateAssetFunction& importFunc, const StringView& outputPath, void* arg = nullptr)
{
Guid id = Guid::Empty;
return Create(importFunc, outputPath, id, arg);
}
/// <summary>
/// Creates new asset.
/// </summary>
/// <param name="tag">The asset type tag.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="assetId">The asset identifier.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Create(const String& tag, const StringView& outputPath, Guid& assetId, void* arg = nullptr);
/// <summary>
/// Creates new asset.
/// </summary>
/// <param name="tag">The asset type tag.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Create(const String& tag, const StringView& outputPath, void* arg = nullptr)
{
Guid id = Guid::Empty;
return Create(tag, outputPath, id, arg);
}
/// <summary>
/// Imports file and creates asset. If asset already exists overwrites it's contents.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="assetId">The asset identifier. If valid then used as new asset id. Set to the actual asset id after import.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Import(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg = nullptr);
/// <summary>
/// Imports file and creates asset. If asset already exists overwrites it's contents.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Import(const StringView& inputPath, const StringView& outputPath, void* arg = nullptr)
{
Guid id = Guid::Empty;
return Import(inputPath, outputPath, id, arg);
}
/// <summary>
/// Imports file and creates asset only if source file has been modified. If asset already exists overwrites it's contents.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="assetId">The asset identifier. If valid then used as new asset id. Set to the actual asset id after import.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool ImportIfEdited(const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg = nullptr);
/// <summary>
/// Imports file and creates asset only if source file has been modified. If asset already exists overwrites it's contents.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool ImportIfEdited(const StringView& inputPath, const StringView& outputPath, void* arg = nullptr)
{
Guid id = Guid::Empty;
return ImportIfEdited(inputPath, outputPath, id, arg);
}
private:
static bool Create(const CreateAssetFunction& callback, const StringView& inputPath, const StringView& outputPath, Guid& assetId, void* arg);
};
#endif

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// Content importing module.
/// </summary>
public class ContentImporters : EngineModule
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PublicDependencies.Add("AudioTool");
options.PublicDependencies.Add("ModelTool");
options.PublicDependencies.Add("TextureTool");
// TODO: convert into private deps and fix CreateCollisionData to not include physics?
options.PublicDependencies.Add("Physics");
options.PrivateDependencies.Add("Graphics");
options.PrivateDependencies.Add("Particles");
options.PublicDefinitions.Add("COMPILE_WITH_ASSETS_IMPORTER");
options.PublicDefinitions.Add("COMPILE_WITH_MATERIAL_GRAPH");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
}
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "CreateAnimationGraph.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/AnimationGraph.h"
#include "Engine/Serialization/MemoryWriteStream.h"
CreateAssetResult CreateAnimationGraph::Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(AnimationGraph, 1);
AnimGraph graph(nullptr);
// Create empty surface with animation output node
graph.Nodes.Resize(1);
auto& rootNode = graph.Nodes[0];
rootNode.Type = GRAPH_NODE_MAKE_TYPE(9, 1);
rootNode.ID = 1;
rootNode.Values.Resize(1);
rootNode.Values[0] = (int32)RootMotionMode::NoExtraction;
rootNode.Boxes.Resize(1);
rootNode.Boxes[0] = AnimGraphBox(&rootNode, 0, VariantType::Void);
// Add parameter (hidden) used to pass the skinned model asset (skeleton source)
graph.Parameters.Resize(1);
AnimGraphParameter& baseModelParam = graph.Parameters[0];
baseModelParam.Identifier = ANIM_GRAPH_PARAM_BASE_MODEL_ID;
baseModelParam.Type = VariantType::Asset;
baseModelParam.IsPublic = false;
baseModelParam.Value = Guid::Empty;
// Serialize
MemoryWriteStream stream(256);
if (graph.Save(&stream, true))
return CreateAssetResult::Error;
// Copy to asset chunk
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Creating animation graph utility
/// </summary>
class CreateAnimationGraph
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
};
#endif

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/AnimationGraphFunction.h"
#include "Engine/Serialization/MemoryWriteStream.h"
/// <summary>
/// Creating Anim Graph function asset utility.
/// </summary>
class CreateAnimationGraphFunction
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(AnimationGraphFunction, 1);
// Create single output function graph
MaterialGraph graph;
auto& outputNode = graph.Nodes.AddOne();
outputNode.ID = 1;
outputNode.Type = GRAPH_NODE_MAKE_TYPE(16, 2);
outputNode.Values.Resize(2);
outputNode.Values[0] = TEXT("System.Single");
outputNode.Values[1] = TEXT("Output");
auto& outputBox = outputNode.Boxes.AddOne();
outputBox.Parent = &outputNode;
outputBox.ID = 0;
outputBox.Type = VariantType::Float;
// Save graph to first chunk
MemoryWriteStream stream(512);
if (graph.Save(&stream, true))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "CreateCollisionData.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "AssetsImportingManager.h"
CreateAssetResult CreateCollisionData::Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(CollisionData, 1);
CollisionData::SerializedOptions options;
// Check if has cooking module and input argument has been specified
#if COMPILE_WITH_PHYSICS_COOKING
if (context.CustomArg)
{
// Cook
const auto arg = (CollisionCooking::Argument*)context.CustomArg;
BytesContainer outputData;
if (CollisionCooking::CookCollision(*arg, options, outputData))
{
return CreateAssetResult::Error;
}
// Save data to asset
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Allocate(outputData.Length() + sizeof(options));
const auto resultChunkPtr = context.Data.Header.Chunks[0]->Data.Get();
Platform::MemoryCopy(resultChunkPtr, &options, sizeof(options));
Platform::MemoryCopy(resultChunkPtr + sizeof(options), outputData.Get(), outputData.Length());
}
else
#endif
{
// Use empty options
options.Type = CollisionDataType::None;
options.Model = Guid::Empty;
options.ModelLodIndex = 0;
options.ConvexFlags = ConvexMeshGenerationFlags::None;
options.ConvexVertexLimit = 0;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(&options);
}
return CreateAssetResult::Ok;
}
#if COMPILE_WITH_PHYSICS_COOKING
bool CreateCollisionData::CookMeshCollision(const String& outputPath, CollisionCooking::Argument& arg)
{
// Use in-build assets importing/creating pipeline to generate asset
return AssetsImportingManager::Create(AssetsImportingManager::CreateCollisionDataTag, outputPath, &arg);
}
#endif
#endif

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Physics/CollisionCooking.h"
/// <summary>
/// Creating collision data asset utility
/// </summary>
class CreateCollisionData
{
public:
/// <summary>
/// Creates the CollisionData.
/// </summary>
/// <param name="context">The creating context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
#if COMPILE_WITH_PHYSICS_COOKING
/// <summary>
/// Cooks the mesh collision data and saves it to the asset using <see cref="CollisionData"/> format.
/// </summary>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The input argument data.</param>
/// <returns>True if failed, otherwise false. See log file to track errors better.</returns>
static bool CookMeshCollision(const String& outputPath, CollisionCooking::Argument& arg);
#endif
};
#endif

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "CreateJson.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Platform/File.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Storage/JsonStorageProxy.h"
#include "Engine/Serialization/JsonWriters.h"
#include "FlaxEngine.Gen.h"
bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename)
{
auto str = dataTypename.ToStringAnsi();
return Create(path, data, str.Get());
}
bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename)
{
DataContainer<char> data1;
DataContainer<char> data2;
data1.Link((char*)data.GetString(), (int32)data.GetSize());
data2.Link((char*)dataTypename, StringUtils::Length(dataTypename));
return Create(path, data1, data2);
}
bool CreateJson::Create(const StringView& path, DataContainer<char>& data, DataContainer<char>& dataTypename)
{
Guid id = Guid::New();
LOG(Info, "Creating json resource of type \'{1}\' at \'{0}\'", path, String(dataTypename.Get()));
// Try use the same asset ID
if (FileSystem::FileExists(path))
{
String typeName;
JsonStorageProxy::GetAssetInfo(path, id, typeName);
if (typeName != String(dataTypename.Get(), dataTypename.Length()))
{
LOG(Warning, "Asset will have different type name {0} -> {1}", typeName, String(dataTypename.Get()));
}
}
rapidjson_flax::StringBuffer buffer;
// Serialize to json
PrettyJsonWriter writerObj(buffer);
JsonWriter& writer = writerObj;
writer.StartObject();
{
// Json resource header
writer.JKEY("ID");
writer.Guid(id);
writer.JKEY("TypeName");
writer.String(dataTypename.Get(), dataTypename.Length());
writer.JKEY("EngineBuild");
writer.Int(FLAXENGINE_VERSION_BUILD);
// Json resource data
writer.JKEY("Data");
writer.RawValue(data.Get(), data.Length());
}
writer.EndObject();
// Save json to file
if (File::WriteAllBytes(path, (byte*)buffer.GetString(), (int32)buffer.GetSize()))
{
LOG(Warning, "Failed to save json to file");
return true;
}
// Reload asset at the target location if is loaded
auto asset = Content::GetAsset(path);
if (asset)
{
asset->Reload();
}
return false;
}
#endif

View File

@@ -0,0 +1,23 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Serialization/Json.h"
/// <summary>
/// Json resources factory.
/// </summary>
class CreateJson
{
public:
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename);
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename);
static bool Create(const StringView& path, DataContainer<char>& data, DataContainer<char>& dataTypename);
};
#endif

View File

@@ -0,0 +1,226 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "CreateMaterial.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#define COMPILE_WITH_MATERIAL_GRAPH 1
#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h"
#include "Engine/Content/Assets/Material.h"
#include "Engine/Tools/MaterialGenerator/Types.h"
#include "Engine/Tools/MaterialGenerator/MaterialLayer.h"
#include "Engine/Tools/MaterialGenerator/MaterialGenerator.h"
#include "Engine/Serialization/MemoryWriteStream.h"
namespace
{
ShaderGraphNode<>* AddFloatValue(MaterialLayer* layer, const float& value, const float& defaultValue)
{
if (Math::NearEqual(value, defaultValue))
return nullptr;
auto& node = layer->Graph.Nodes.AddOne();
node.ID = layer->Graph.Nodes.Count();
node.Type = GRAPH_NODE_MAKE_TYPE(2, 3);
node.Boxes.Resize(1);
node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Float); // Value
node.Values.Resize(1);
node.Values[0] = value;
return &node;
}
ShaderGraphNode<>* AddColorNode(MaterialLayer* layer, const Color& value, const Color& defaultValue)
{
if (value == defaultValue)
return nullptr;
auto& node = layer->Graph.Nodes.AddOne();
node.ID = layer->Graph.Nodes.Count();
node.Type = GRAPH_NODE_MAKE_TYPE(2, 7);
node.Boxes.Resize(5);
node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector4); // Color
node.Boxes[1] = MaterialGraphBox(&node, 1, VariantType::Float); // R
node.Boxes[2] = MaterialGraphBox(&node, 2, VariantType::Float); // G
node.Boxes[3] = MaterialGraphBox(&node, 3, VariantType::Float); // B
node.Boxes[4] = MaterialGraphBox(&node, 4, VariantType::Float); // A
node.Values.Resize(1);
node.Values[0] = value;
return &node;
}
ShaderGraphNode<>* AddMultiplyNode(MaterialLayer* layer)
{
auto& node = layer->Graph.Nodes.AddOne();
node.ID = layer->Graph.Nodes.Count();
node.Type = GRAPH_NODE_MAKE_TYPE(3, 3);
node.Boxes.Resize(3);
node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector4); // A
node.Boxes[1] = MaterialGraphBox(&node, 1, VariantType::Vector4); // B
node.Boxes[2] = MaterialGraphBox(&node, 2, VariantType::Vector4); // Result
node.Values.Resize(2);
node.Values[0] = 1.0f;
node.Values[1] = 1.0f;
return &node;
}
ShaderGraphNode<>* AddTextureNode(MaterialLayer* layer, const Guid& textureId)
{
if (!textureId.IsValid())
return nullptr;
auto& node = layer->Graph.Nodes.AddOne();
node.ID = layer->Graph.Nodes.Count();
node.Type = GRAPH_NODE_MAKE_TYPE(5, 1);
node.Boxes.Resize(7);
node.Boxes[0] = MaterialGraphBox(&node, 0, VariantType::Vector2); // UVs
node.Boxes[6] = MaterialGraphBox(&node, 6, VariantType::Object); // Texture Reference
node.Boxes[1] = MaterialGraphBox(&node, 1, VariantType::Vector4); // Color
node.Boxes[2] = MaterialGraphBox(&node, 2, VariantType::Float); // R
node.Boxes[3] = MaterialGraphBox(&node, 3, VariantType::Float); // G
node.Boxes[4] = MaterialGraphBox(&node, 4, VariantType::Float); // B
node.Boxes[5] = MaterialGraphBox(&node, 5, VariantType::Float); // A
node.Values.Resize(1);
node.Values[0] = textureId;
return &node;
}
struct Meta11 // TypeID: 11, for nodes
{
Vector2 Position;
bool Selected;
};
}
CreateMaterial::Options::Options()
{
Info.Domain = MaterialDomain::Surface;
Info.BlendMode = MaterialBlendMode::Opaque;
Info.ShadingModel = MaterialShadingModel::Lit;
Info.UsageFlags = MaterialUsageFlags::None;
Info.FeaturesFlags = MaterialFeaturesFlags::None;
Info.DecalBlendingMode = MaterialDecalBlendingMode::Translucent;
Info.PostFxLocation = MaterialPostFxLocation::AfterPostProcessingPass;
Info.CullMode = CullMode::Normal;
Info.MaskThreshold = 0.3f;
Info.OpacityThreshold = 0.12f;
Info.TessellationMode = TessellationMethod::None;
Info.MaxTessellationFactor = 15;
}
CreateAssetResult CreateMaterial::Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(Material, 19);
context.SkipMetadata = true;
ShaderStorage::Header19 shaderHeader;
Platform::MemoryClear(&shaderHeader, sizeof(shaderHeader));
if (context.CustomArg)
{
// Use custom material properties
const Options& options = *(Options*)context.CustomArg;
shaderHeader.Material.Info = options.Info;
// Generate Visject Surface with custom material properties setup
auto layer = MaterialLayer::CreateDefault(context.Data.Header.ID);
if (context.AllocateChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE))
return CreateAssetResult::CannotAllocateChunk;
layer->Graph.Nodes.EnsureCapacity(32);
layer->Root = (MaterialGraphNode*)&layer->Graph.Nodes[0];
for (auto& box : layer->Root->Boxes)
box.Parent = layer->Root;
Meta11 meta;
meta.Selected = false;
#define SET_POS(node, pos) meta.Position = pos; node->Meta.AddEntry(11, (byte*)&meta, sizeof(meta));
#define CONNECT(boxA, boxB) boxA.Connections.Add(&boxB); boxB.Connections.Add(&boxA)
auto diffuseTexture = AddTextureNode(layer, options.Diffuse.Texture);
auto diffuseColor = AddColorNode(layer, options.Diffuse.Color, Color::White);
if (diffuseTexture && diffuseColor)
{
auto diffuseMultiply = AddMultiplyNode(layer);
CONNECT(diffuseMultiply->Boxes[0], diffuseTexture->Boxes[1]);
CONNECT(diffuseMultiply->Boxes[1], diffuseColor->Boxes[0]);
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Color)], diffuseMultiply->Boxes[2]);
SET_POS(diffuseColor, Vector2(-467.7404, 91.41332));
SET_POS(diffuseTexture, Vector2(-538.096, -103.9724));
SET_POS(diffuseMultiply, Vector2(-293.5272f, -2.926111f));
}
else if (diffuseTexture)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Color)], diffuseTexture->Boxes[1]);
SET_POS(diffuseTexture, Vector2(-293.5272f, -2.926111f));
}
else if (diffuseColor)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Color)], diffuseColor->Boxes[0]);
SET_POS(diffuseColor, Vector2(-293.5272f, -2.926111f));
}
if (diffuseTexture && options.Diffuse.HasAlphaMask)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Mask)], diffuseTexture->Boxes[5]);
}
auto emissiveTexture = AddTextureNode(layer, options.Emissive.Texture);
auto emissiveColor = AddColorNode(layer, options.Emissive.Color, Color::Transparent);
if (emissiveTexture && emissiveColor)
{
auto emissiveMultiply = AddMultiplyNode(layer);
CONNECT(emissiveMultiply->Boxes[0], emissiveTexture->Boxes[1]);
CONNECT(emissiveMultiply->Boxes[1], emissiveColor->Boxes[0]);
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Emissive)], emissiveMultiply->Boxes[2]);
SET_POS(emissiveTexture, Vector2(-667.7404, 91.41332));
SET_POS(emissiveTexture, Vector2(-738.096, -103.9724));
SET_POS(emissiveMultiply, Vector2(-493.5272f, -2.926111f));
}
else if (emissiveTexture)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Emissive)], emissiveTexture->Boxes[1]);
SET_POS(emissiveTexture, Vector2(-493.5272f, -2.926111f));
}
else if (emissiveColor)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Emissive)], emissiveColor->Boxes[0]);
SET_POS(emissiveColor, Vector2(-493.5272f, -2.926111f));
}
auto normalMap = AddTextureNode(layer, options.Normals.Texture);
if (normalMap)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Normal)], normalMap->Boxes[1]);
SET_POS(normalMap, Vector2(-893.5272f, -200.926111f));
}
auto opacityTexture = AddTextureNode(layer, options.Opacity.Texture);
auto opacityValue = AddFloatValue(layer, options.Opacity.Value, 1.0f);
if (opacityTexture && opacityValue)
{
auto opacityMultiply = AddMultiplyNode(layer);
CONNECT(opacityMultiply->Boxes[0], opacityTexture->Boxes[1]);
CONNECT(opacityMultiply->Boxes[1], opacityValue->Boxes[0]);
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Opacity)], opacityMultiply->Boxes[2]);
SET_POS(opacityTexture, Vector2(-867.7404, 91.41332));
SET_POS(opacityTexture, Vector2(-938.096, -103.9724));
SET_POS(opacityMultiply, Vector2(-693.5272f, -2.926111f));
}
else if (opacityTexture)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Opacity)], opacityTexture->Boxes[1]);
SET_POS(opacityTexture, Vector2(-693.5272f, -2.926111f));
}
else if (opacityValue)
{
CONNECT(layer->Root->Boxes[static_cast<int32>(MaterialGraphBoxes::Opacity)], opacityValue->Boxes[0]);
SET_POS(opacityValue, Vector2(-693.5272f, -2.926111f));
}
#undef CONNECT
MemoryWriteStream stream(512);
layer->Graph.Save(&stream, true);
context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(stream.GetHandle(), stream.GetPosition());
Delete(layer);
}
else
{
// Use default material properties and don't create Visject Surface because during material loading it will be generated
const Options options;
shaderHeader.Material.Info = options.Info;
}
context.Data.CustomData.Copy(&shaderHeader);
return CreateAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/Material.h"
/// <summary>
/// Creating materials utility
/// </summary>
class CreateMaterial
{
public:
struct Options
{
MaterialInfo Info;
struct
{
Color Color = Color::White;
Guid Texture = Guid::Empty;
bool HasAlphaMask = false;
} Diffuse;
struct
{
Color Color = Color::Transparent;
Guid Texture = Guid::Empty;
} Emissive;
struct
{
float Value = 1.0f;
Guid Texture = Guid::Empty;
} Opacity;
struct
{
Guid Texture = Guid::Empty;
} Normals;
Options();
};
/// <summary>
/// Creates the material asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
};
#endif

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/MaterialFunction.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Tools/MaterialGenerator/Types.h"
/// <summary>
/// Creating material function asset utility.
/// </summary>
class CreateMaterialFunction
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(MaterialFunction, 1);
// Create single output function graph
MaterialGraph graph;
auto& outputNode = graph.Nodes.AddOne();
outputNode.ID = 1;
outputNode.Type = GRAPH_NODE_MAKE_TYPE(16, 2);
outputNode.Values.Resize(2);
outputNode.Values[0] = TEXT("System.Single");
outputNode.Values[1] = TEXT("Output");
auto& outputBox = outputNode.Boxes.AddOne();
outputBox.Parent = &outputNode;
outputBox.ID = 0;
outputBox.Type = VariantType::Float;
// Save graph to first chunk
MemoryWriteStream stream(512);
if (graph.Save(&stream, true))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Graphics/Materials/MaterialParams.h"
#include "Engine/Content/Assets/MaterialInstance.h"
#include "Engine/Serialization/MemoryWriteStream.h"
/// <summary>
/// Creating material instances utility.
/// </summary>
class CreateMaterialInstance
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(MaterialInstance, 4);
// Chunk 0 - Header
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
MemoryWriteStream stream(256);
stream.Write(&Guid::Empty);
MaterialParams::Save(&stream, nullptr);
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Particles/ParticleEmitter.h"
/// <summary>
/// Creating particle emitter asset utility.
/// </summary>
class CreateParticleEmitter
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(ParticleEmitter, 19);
context.SkipMetadata = true;
// Set Custom Data with Header
ShaderStorage::Header19 shaderHeader;
Platform::MemoryClear(&shaderHeader, sizeof(shaderHeader));
context.Data.CustomData.Copy(&shaderHeader);
// Create default layer
if (context.AllocateChunk(SHADER_FILE_CHUNK_VISJECT_SURFACE))
return CreateAssetResult::CannotAllocateChunk;
ParticleEmitterGraphCPU graph;
graph.CreateDefault();
MemoryWriteStream stream(512);
graph.Save(&stream, false);
context.Data.Header.Chunks[SHADER_FILE_CHUNK_VISJECT_SURFACE]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Particles/ParticleEmitterFunction.h"
#include "Engine/Serialization/MemoryWriteStream.h"
/// <summary>
/// Creating particle graph function asset utility.
/// </summary>
class CreateParticleEmitterFunction
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(ParticleEmitterFunction, 1);
// Create single output function graph
ParticleEmitterGraphCPU graph;
auto& outputNode = graph.Nodes.AddOne();
outputNode.ID = 1;
outputNode.Type = GRAPH_NODE_MAKE_TYPE(16, 2);
outputNode.Values.Resize(2);
outputNode.Values[0] = TEXT("System.Single");
outputNode.Values[1] = TEXT("Output");
auto& outputBox = outputNode.Boxes.AddOne();
outputBox.Parent = &outputNode;
outputBox.ID = 0;
outputBox.Type = VariantType::Float;
// Save graph to first chunk
MemoryWriteStream stream(512);
if (graph.Save(&stream, true))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Particles/ParticleSystem.h"
/// <summary>
/// Creating particle system asset utility.
/// </summary>
class CreateParticleSystem
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(ParticleSystem, 1);
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
MemoryWriteStream stream(64);
{
// Create empty system
stream.WriteInt32(2);
stream.WriteFloat(60.0f); // FramesPerSecond
stream.WriteInt32(5 * 60); // DurationFrames
stream.WriteInt32(0); // Emitters Count
stream.WriteInt32(0); // Tracks Count
}
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#include "Engine/Content/Assets/RawDataAsset.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Creating raw data asset utility
/// </summary>
class CreateRawData
{
public:
/// <summary>
/// Creates the raw data asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg);
const auto data = static_cast<BytesContainer*>(context.CustomArg);
// Base
IMPORT_SETUP(RawDataAsset, 1);
// Chunk 0
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(*data);
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Animations/SceneAnimations/SceneAnimation.h"
/// <summary>
/// Creating scene animation asset utility.
/// </summary>
class CreateSceneAnimation
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(SceneAnimation, 1);
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
MemoryWriteStream stream(64);
{
// Create empty timeline
stream.WriteInt32(2);
stream.WriteFloat(60.0f); // FramesPerSecond
stream.WriteInt32(5 * 60); // DurationFrames
stream.WriteInt32(0); // Tracks Count
}
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/SkeletonMask.h"
/// <summary>
/// Creating skeleton mask asset utility.
/// </summary>
class CreateSkeletonMask
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(SkeletonMask, 1);
// Chunk 0
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
struct Empty
{
Guid SkeletonId = Guid::Empty;
int32 Size = 0;
};
Empty emptyData;
context.Data.Header.Chunks[0]->Data.Copy(&emptyData);
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#include "Engine/Content/Assets/VisualScript.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Creating visual script asset utility
/// </summary>
class CreateVisualScript
{
public:
/// <summary>
/// Creates the asset.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg);
const auto baseTypename = static_cast<String*>(context.CustomArg);
// Base
IMPORT_SETUP(VisualScript, 1);
// Chunk 0 - Visject Surface
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
{
const VisualScriptGraph graph;
MemoryWriteStream stream(64);
graph.Save(&stream, true);
context.Data.Header.Chunks[0]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Chunk 1 - Visual Script Metadata
if (context.AllocateChunk(1))
return CreateAssetResult::CannotAllocateChunk;
{
MemoryWriteStream stream(256);
stream.WriteInt32(1);
stream.WriteString(*baseTypename, 31);
stream.WriteInt32((int32)VisualScript::Flags::None);
context.Data.Header.Chunks[1]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
return CreateAssetResult::Ok;
}
};
#endif

View File

@@ -0,0 +1,302 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ImportAudio.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/DeleteMe.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Tools/AudioTool/AudioTool.h"
#include "Engine/Tools/AudioTool/MP3Decoder.h"
#include "Engine/Tools/AudioTool/WaveDecoder.h"
#include "Engine/Tools/AudioTool/OggVorbisDecoder.h"
#include "Engine/Tools/AudioTool/OggVorbisEncoder.h"
#include "Engine/Serialization/JsonWriters.h"
ImportAudio::Options::Options()
{
Format = AudioFormat::Vorbis;
DisableStreaming = false;
Is3D = false;
Quality = 0.4f;
BitDepth = 16;
}
String ImportAudio::Options::ToString() const
{
return String::Format(TEXT("Format:{}, DisableStreaming:{}, Is3D:{}, Quality:{}, BitDepth:{}"),
::ToString(Format),
DisableStreaming,
Is3D,
Quality,
BitDepth
);
}
void ImportAudio::Options::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(ImportAudio::Options);
SERIALIZE(Format);
SERIALIZE(DisableStreaming);
SERIALIZE(Is3D);
SERIALIZE(Quality);
SERIALIZE(BitDepth);
}
void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(Format);
DESERIALIZE(DisableStreaming);
DESERIALIZE(Is3D);
DESERIALIZE(Quality);
DESERIALIZE(BitDepth);
}
bool ImportAudio::TryGetImportOptions(String path, Options& options)
{
#if IMPORT_AUDIO_CACHE_OPTIONS
// Check if target asset file exists
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& tmpFile->GetEntry(0).TypeName == AudioClip::TypeName
&& !tmpFile->LoadAssetHeader(0, data)
&& data.SerializedVersion >= 1)
{
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse(data.Metadata.Get<const char>(), data.Metadata.Length());
if (!metadata.HasParseError())
{
// Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}
CreateAssetResult ImportAudio::Import(CreateAssetContext& context, AudioDecoder& decoder)
{
// Get import options
Options options;
if (context.CustomArg != nullptr)
{
// Copy import options from argument
options = *static_cast<Options*>(context.CustomArg);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing audio import options. Using default values.");
}
}
LOG_STR(Info, options.ToString());
// Open the file
auto stream = FileReadStream::Open(context.InputPath);
if (stream == nullptr)
return CreateAssetResult::InvalidPath;
DeleteMe<FileReadStream> deleteStream(stream);
// Load the audio data
AudioDataInfo info;
Array<byte> audioData;
if (decoder.Convert(stream, info, audioData))
return CreateAssetResult::Error;
const float length = info.NumSamples / static_cast<float>(Math::Max(1U, info.SampleRate * info.NumChannels));
LOG(Info, "Audio: {0}kHz, channels: {1}, Bit depth: {2}, Length: {3}s", info.SampleRate / 1000.0f, info.NumChannels, info.BitDepth, length);
// Load the whole audio data
uint32 bytesPerSample = info.BitDepth / 8;
uint32 bufferSize = info.NumSamples * bytesPerSample;
DataContainer<byte> sampleBuffer;
sampleBuffer.Link(audioData.Get());
// Convert to Mono if used as 3D source
if (options.Is3D && info.NumChannels > 1)
{
const uint32 numSamplesPerChannel = info.NumSamples / info.NumChannels;
const uint32 monoBufferSize = numSamplesPerChannel * bytesPerSample;
sampleBuffer.Allocate(monoBufferSize);
AudioTool::ConvertToMono(audioData.Get(), sampleBuffer.Get(), info.BitDepth, numSamplesPerChannel, info.NumChannels);
info.NumSamples = numSamplesPerChannel;
info.NumChannels = 1;
bufferSize = monoBufferSize;
}
// Convert bit depth if need to
if (options.BitDepth != static_cast<int32>(info.BitDepth))
{
const uint32 outBufferSize = info.NumSamples * (options.BitDepth / 8);
sampleBuffer.Allocate(outBufferSize);
AudioTool::ConvertBitDepth(audioData.Get(), info.BitDepth, sampleBuffer.Get(), options.BitDepth, info.NumSamples);
info.BitDepth = options.BitDepth;
bytesPerSample = info.BitDepth / 8;
bufferSize = outBufferSize;
}
// Base
IMPORT_SETUP(AudioClip, AudioClip::SerializedVersion);
uint32 samplesPerChunk[ASSET_FILE_DATA_CHUNKS];
Platform::MemoryClear(samplesPerChunk, sizeof(samplesPerChunk));
// Helper macro to write audio data to chunk (handles per chunk compression)
#if COMPILE_WITH_OGG_VORBIS
#define HANDLE_VORBIS(chunkIndex, dataPtr, dataSize) \
infoEx.NumSamples = samplesPerChunk[chunkIndex]; \
OggVorbisEncoder encoder; \
if (encoder.Convert(dataPtr, infoEx, context.Data.Header.Chunks[chunkIndex]->Data, options.Quality)) \
{ \
LOG(Warning, "Failed to compress audio data"); \
return CreateAssetResult::Error; \
}
#else
#define HANDLE_VORBIS(chunkIndex, dataPtr, dataSize) \
LOG(Warning, "Vorbis format is not supported."); \
return CreateAssetResult::Error;
#endif
#define HANDLE_RAW(chunkIndex, dataPtr, dataSize) \
context.Data.Header.Chunks[chunkIndex]->Data.Copy(dataPtr, dataSize);
#define WRITE_DATA(chunkIndex, dataPtr, dataSize) \
samplesPerChunk[chunkIndex] = (dataSize) / (options.BitDepth / 8); \
switch (options.Format) \
{ \
case AudioFormat::Raw: \
{ \
HANDLE_RAW(chunkIndex, dataPtr, dataSize); \
} \
break; \
case AudioFormat::Vorbis: \
{ \
HANDLE_VORBIS(chunkIndex, dataPtr, dataSize); \
} \
break; \
default: \
{ \
LOG(Warning, "Unknown audio format."); \
return CreateAssetResult::Error; \
} \
break; \
}
AudioDataInfo infoEx = info;
// If audio has streaming disabled then use a single chunk
if (options.DisableStreaming)
{
// Copy data
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
WRITE_DATA(0, sampleBuffer.Get(), bufferSize);
}
else
{
// Split audio data into a several chunks (uniform data spread)
const int32 MinChunkSize = 1 * 1024 * 1024; // 1 MB
const int32 chunkSize = Math::Max<int32>(MinChunkSize, Math::AlignUp<uint32>(bufferSize / ASSET_FILE_DATA_CHUNKS, 256));
const int32 chunksCount = Math::CeilToInt((float)bufferSize / chunkSize);
ASSERT(chunksCount > 0 && chunksCount <= ASSET_FILE_DATA_CHUNKS);
byte* ptr = sampleBuffer.Get();
int32 size = bufferSize;
for (int32 chunkIndex = 0; chunkIndex < chunksCount; chunkIndex++)
{
if (context.AllocateChunk(chunkIndex))
return CreateAssetResult::CannotAllocateChunk;
const int32 t = Math::Min(size, chunkSize);
WRITE_DATA(chunkIndex, ptr, t);
ptr += t;
size -= t;
}
ASSERT(size == 0);
}
// Save audio header
{
AudioClip::Header header;
header.Format = options.Format;
header.Info = info;
header.Is3D = options.Is3D;
header.Streamable = !options.DisableStreaming;
header.OriginalSize = stream->GetLength();
Platform::MemoryCopy(header.SamplesPerChunk, samplesPerChunk, sizeof(samplesPerChunk));
static_assert(AudioClip::SerializedVersion == 2, "Update this code to match the audio clip header format.");
header.ImportedSize = 0;
for (int32 i = 0; i < ASSET_FILE_DATA_CHUNKS; i++)
{
if (context.Data.Header.Chunks[i])
header.ImportedSize += context.Data.Header.Chunks[i]->Size();
}
context.Data.CustomData.Copy(&header);
}
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMetaObj(importOptionsMetaBuffer);
JsonWriter& importOptionsMeta = importOptionsMetaObj;
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
return CreateAssetResult::Ok;
}
CreateAssetResult ImportAudio::ImportWav(CreateAssetContext& context)
{
WaveDecoder decoder;
return Import(context, decoder);
}
CreateAssetResult ImportAudio::ImportMp3(CreateAssetContext& context)
{
MP3Decoder decoder;
return Import(context, decoder);
}
#if COMPILE_WITH_OGG_VORBIS
CreateAssetResult ImportAudio::ImportOgg(CreateAssetContext& context)
{
OggVorbisDecoder decoder;
return Import(context, decoder);
}
#endif
#endif

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#include "Engine/Tools/AudioTool/AudioDecoder.h"
#include "Engine/Serialization/ISerializable.h"
#include "Engine/Audio/Config.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Enable/disable caching audio import options
/// </summary>
#define IMPORT_AUDIO_CACHE_OPTIONS 1
/// <summary>
/// Importing audio utility
/// </summary>
class ImportAudio
{
public:
/// <summary>
/// Importing audio options
/// </summary>
struct Options : public ISerializable
{
AudioFormat Format;
bool DisableStreaming;
bool Is3D;
int32 BitDepth;
float Quality;
/// <summary>
/// Initializes a new instance of the <see cref="Options"/> struct.
/// </summary>
Options();
/// <summary>
/// Gets string that contains information about options
/// </summary>
/// <returns>String</returns>
String ToString() const;
public:
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};
public:
/// <summary>
/// Tries the get audio import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
/// <summary>
/// Imports the audio data (with given audio decoder).
/// </summary>
/// <param name="context">The importing context.</param>
/// <param name="decoder">The audio decoder.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context, AudioDecoder& decoder);
/// <summary>
/// Imports the .wav audio file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportWav(CreateAssetContext& context);
/// <summary>
/// Imports the .mp3 audio file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportMp3(CreateAssetContext& context);
#if COMPILE_WITH_OGG_VORBIS
/// <summary>
/// Imports the .ogg audio file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportOgg(CreateAssetContext& context);
#endif
};
#endif

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ImportFont.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/DeleteMe.h"
#include "Engine/Core/Log.h"
#include "Engine/Serialization/FileReadStream.h"
#include "Engine/Render2D/FontAsset.h"
CreateAssetResult ImportFont::Import(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(FontAsset, 3);
// Setup header
FontOptions options;
options.Hinting = FontHinting::Default;
options.Flags = FontFlags::AntiAliasing;
context.Data.CustomData.Copy(&options);
// Open the file
auto stream = FileReadStream::Open(context.InputPath);
if (stream == nullptr)
return CreateAssetResult::InvalidPath;
DeleteMe<FileReadStream> deleteStream(stream);
// Copy font file data
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
const auto size = stream->GetLength();
context.Data.Header.Chunks[0]->Data.Allocate(size);
stream->ReadBytes(context.Data.Header.Chunks[0]->Get(), size);
return CreateAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Importing fonts utility
/// </summary>
class ImportFont
{
public:
/// <summary>
/// Imports the font file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
};
#endif

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
{
public:
typedef ModelTool::Options Options;
public:
/// <summary>
/// Tries the get model import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
/// <summary>
/// Imports the model file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
/// <summary>
/// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
};
#endif

View File

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

View File

@@ -0,0 +1,57 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Content/Assets/Model.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
/// <summary>
/// Enable/disable caching model import options
/// </summary>
#define IMPORT_MODEL_CACHE_OPTIONS 1
/// <summary>
/// Importing models utility
/// </summary>
class ImportModelFile
{
public:
typedef ModelTool::Options Options;
public:
/// <summary>
/// Tries the get model import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(String path, Options& options);
/// <summary>
/// Imports the model file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
/// <summary>
/// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context);
private:
static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
};
#endif

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ImportShader.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Platform/File.h"
#include "Engine/Graphics/Shaders/Cache/ShaderStorage.h"
#include "Engine/Graphics/Shaders/Cache/ShaderCacheManager.h"
#include "Engine/Utilities/Encryption.h"
#include "Engine/Content/Assets/Shader.h"
CreateAssetResult ImportShader::Import(CreateAssetContext& context)
{
// Base
IMPORT_SETUP(Shader, 19);
const int32 SourceCodeChunk = 15;
context.SkipMetadata = true;
// Read text (handles any Unicode convert into ANSI)
StringAnsi sourceCodeText;
if (File::ReadAllText(context.InputPath, sourceCodeText))
return CreateAssetResult::InvalidPath;
// Load source code
if (context.AllocateChunk(SourceCodeChunk))
return CreateAssetResult::CannotAllocateChunk;
const auto sourceCodeSize = sourceCodeText.Length();
if (sourceCodeSize < 10)
{
LOG(Warning, "Empty shader source file.");
return CreateAssetResult::Error;
}
context.Data.Header.Chunks[SourceCodeChunk]->Data.Allocate(sourceCodeSize + 1);
const auto sourceCode = context.Data.Header.Chunks[SourceCodeChunk]->Get();
Platform::MemoryCopy(sourceCode, sourceCodeText.Get(), sourceCodeSize);
// Encrypt source code
Encryption::EncryptBytes(sourceCode, sourceCodeSize);
// Set Custom Data with Header
ShaderStorage::Header19 shaderHeader;
Platform::MemoryClear(&shaderHeader, sizeof(shaderHeader));
context.Data.CustomData.Copy(&shaderHeader);
#if COMPILE_WITH_SHADER_CACHE_MANAGER
// Invalidate shader cache
ShaderCacheManager::RemoveCache(context.Data.Header.ID);
#endif
return CreateAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,24 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
/// <summary>
/// Importing shaders utility
/// </summary>
class ImportShader
{
public:
/// <summary>
/// Imports the shader file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
};
#endif

View File

@@ -0,0 +1,564 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "ImportTexture.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Graphics/Textures/TextureUtils.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Content/Storage/ContentStorageManager.h"
#include "Engine/Content/Utilities/IESLoader.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Content/Assets/IESProfile.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Platform/File.h"
bool IsSpriteAtlasOrTexture(const String& typeName)
{
return typeName == Texture::TypeName || typeName == SpriteAtlas::TypeName;
}
bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options)
{
#if IMPORT_TEXTURE_CACHE_OPTIONS
// Check if target asset texture exists
if (FileSystem::FileExists(path))
{
// Try to load asset file and asset info (also check for Sprite Atlas or Texture assets)
auto tmpFile = ContentStorageManager::GetStorage(path);
AssetInitData data;
if (tmpFile
&& tmpFile->GetEntriesCount() == 1
&& IsSpriteAtlasOrTexture(tmpFile->GetEntry(0).TypeName)
&& !tmpFile->LoadAssetHeader(0, data)
&& data.SerializedVersion >= 4)
{
// For sprite atlas try to get sprites from the last chunk
if (tmpFile->GetEntry(0).TypeName == SpriteAtlas::TypeName)
{
auto chunk15 = data.Header.Chunks[15];
if (chunk15 != nullptr && !tmpFile->LoadAssetChunk(chunk15) && chunk15->Data.IsValid())
{
MemoryReadStream stream(chunk15->Data.Get(), chunk15->Data.Length());
// Load tiles data
int32 tilesVersion, tilesCount;
stream.ReadInt32(&tilesVersion);
if (tilesVersion == 1)
{
stream.ReadInt32(&tilesCount);
for (int32 i = 0; i < tilesCount; i++)
{
// Load sprite
Sprite t;
stream.Read(&t.Area);;
stream.ReadString(&t.Name, 49);
options.Sprites.Add(t);
}
}
}
}
// Check import meta
rapidjson_flax::Document metadata;
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
if (metadata.HasParseError() == false)
{
// Success
options.Deserialize(metadata, nullptr);
return true;
}
}
}
#endif
return false;
}
void ImportTexture::InitOptions(CreateAssetContext& context, Options& options)
{
// Gather import options
if (context.CustomArg != nullptr)
{
// Copy options
options = *static_cast<Options*>(context.CustomArg);
ASSERT_LOW_LAYER(options.Sprites.Count() >= 0);
}
else
{
// Restore the previous settings or use default ones
if (!TryGetImportOptions(context.TargetAssetPath, options))
{
LOG(Warning, "Missing texture import options. Using default values.");
}
}
// Tweak options
if (options.IsAtlas)
{
// Disable streaming for atlases
// TODO: maybe we could use streaming for atlases?
options.NeverStream = true;
// Add default tile if has no sprites
if (options.Sprites.IsEmpty())
options.Sprites.Add({ Rectangle(Vector2::Zero, Vector2::One), TEXT("Default") });
}
options.MaxSize = Math::Min(options.MaxSize, GPU_MAX_TEXTURE_SIZE);
}
CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const TextureData& textureData, Options& options)
{
// Check data
bool isCubeMap = false;
if (textureData.GetArraySize() != 1)
{
if (options.IsAtlas)
{
LOG(Warning, "Cannot import sprite atlas texture that has more than one array slice.");
return CreateAssetResult::Error;
}
if (textureData.GetArraySize() != 6)
{
LOG(Warning, "Cannot import texture that has {0} array slices. Use single plane images (single 2D) or cube maps (6 slices).", textureData.GetArraySize());
return CreateAssetResult::Error;
}
else
{
isCubeMap = true;
if (textureData.Width != textureData.Height)
{
LOG(Warning, "Invalid cube texture size.");
return CreateAssetResult::Error;
}
}
}
// Base
if (isCubeMap)
{
IMPORT_SETUP(CubeTexture, 4);
}
else if (options.IsAtlas)
{
IMPORT_SETUP(SpriteAtlas, 4);
}
else
{
IMPORT_SETUP(Texture, 4);
}
// Fill texture header
TextureHeader textureHeader;
textureHeader.NeverStream = options.NeverStream;
textureHeader.Width = textureData.Width;
textureHeader.Height = textureData.Height;
textureHeader.Format = textureData.Format;
textureHeader.Type = options.Type;
textureHeader.MipLevels = textureData.GetMipLevels();
textureHeader.IsSRGB = PixelFormatExtensions::IsSRGB(textureHeader.Format);
textureHeader.IsCubeMap = isCubeMap;
ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS);
// Save header
context.Data.CustomData.Copy(&textureHeader);
// Save atlas sprites data
if (options.IsAtlas)
{
MemoryWriteStream stream(256);
stream.WriteInt32(1); // Version
stream.WriteInt32(options.Sprites.Count()); // Amount of tiles
for (int32 i = 0; i < options.Sprites.Count(); i++)
{
auto& sprite = options.Sprites[i];
stream.Write(&sprite.Area);
stream.WriteString(sprite.Name, 49);
}
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Save mip maps
if (!isCubeMap)
{
for (int32 mipIndex = 0; mipIndex < textureHeader.MipLevels; mipIndex++)
{
auto mipData = textureData.GetData(0, mipIndex);
if (context.AllocateChunk(mipIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[mipIndex]->Data.Copy(mipData->Data.Get(), static_cast<uint32>(mipData->DepthPitch));
}
}
else
{
// Allocate memory for a temporary buffer
const uint32 imageSize = textureData.GetData(0, 0)->DepthPitch * 6;
MemoryWriteStream imageData(imageSize);
// Copy cube sides for every mip into separate chunks
for (int32 mipLevelIndex = 0; mipLevelIndex < textureHeader.MipLevels; mipLevelIndex++)
{
// Write array slices to the stream
imageData.SetPosition(0);
for (int32 cubeFaceIndex = 0; cubeFaceIndex < 6; cubeFaceIndex++)
{
// Get image
const auto image = textureData.GetData(cubeFaceIndex, mipLevelIndex);
if (image == nullptr)
{
LOG(Warning, "Cannot create cube texture '{0}'. Missing image slice.", context.InputPath);
return CreateAssetResult::Error;
}
ASSERT(image->DepthPitch < MAX_int32);
// Copy data
imageData.WriteBytes(image->Data.Get(), image->Data.Length());
}
// Copy mip
if (context.AllocateChunk(mipLevelIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[mipLevelIndex]->Data.Copy(imageData.GetHandle(), imageData.GetPosition());
}
}
#if IMPORT_TEXTURE_CACHE_OPTIONS
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMeta(importOptionsMetaBuffer);
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
#endif
return CreateAssetResult::Ok;
}
CreateAssetResult ImportTexture::Create(CreateAssetContext& context, const TextureBase::InitData& textureData, Options& options)
{
// Check data
bool isCubeMap = false;
if (textureData.ArraySize != 1)
{
if (options.IsAtlas)
{
LOG(Warning, "Cannot import sprite atlas texture that has more than one array slice.");
return CreateAssetResult::Error;
}
if (textureData.ArraySize != 6)
{
LOG(Warning, "Cannot import texture that has {0} array slices. Use single plane images (single 2D) or cube maps (6 slices).", textureData.ArraySize);
return CreateAssetResult::Error;
}
else
{
isCubeMap = true;
if (textureData.Width != textureData.Height)
{
LOG(Warning, "Invalid cube texture size.");
return CreateAssetResult::Error;
}
}
}
// Base
if (isCubeMap)
{
IMPORT_SETUP(CubeTexture, 4);
}
else if (options.IsAtlas)
{
IMPORT_SETUP(SpriteAtlas, 4);
}
else
{
IMPORT_SETUP(Texture, 4);
}
// Fill texture header
TextureHeader textureHeader;
textureHeader.NeverStream = options.NeverStream;
textureHeader.Width = textureData.Width;
textureHeader.Height = textureData.Height;
textureHeader.Format = textureData.Format;
textureHeader.Type = options.Type;
textureHeader.MipLevels = textureData.Mips.Count();
textureHeader.IsSRGB = PixelFormatExtensions::IsSRGB(textureHeader.Format);
textureHeader.IsCubeMap = isCubeMap;
ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS);
// Save header
context.Data.CustomData.Copy(&textureHeader);
// Save atlas sprites data
if (options.IsAtlas)
{
MemoryWriteStream stream(256);
stream.WriteInt32(1); // Version
stream.WriteInt32(options.Sprites.Count()); // Amount of tiles
for (int32 i = 0; i < options.Sprites.Count(); i++)
{
auto& sprite = options.Sprites[i];
stream.Write(&sprite.Area);
stream.WriteString(sprite.Name, 49);
}
if (context.AllocateChunk(15))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[15]->Data.Copy(stream.GetHandle(), stream.GetPosition());
}
// Save mip maps
if (!isCubeMap)
{
for (int32 mipIndex = 0; mipIndex < textureHeader.MipLevels; mipIndex++)
{
auto& mipData = textureData.Mips[mipIndex];
if (context.AllocateChunk(mipIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[mipIndex]->Data.Copy(mipData.Data.Get(), static_cast<uint32>(mipData.SlicePitch));
}
}
else
{
// Allocate memory for a temporary buffer
const uint32 imageSize = textureData.Mips[0].SlicePitch * 6;
MemoryWriteStream imageData(imageSize);
// Copy cube sides for every mip into separate chunks
for (int32 mipLevelIndex = 0; mipLevelIndex < textureHeader.MipLevels; mipLevelIndex++)
{
// Write array slices to the stream
imageData.SetPosition(0);
for (int32 cubeFaceIndex = 0; cubeFaceIndex < 6; cubeFaceIndex++)
{
// Get image
auto& image = textureData.Mips[mipLevelIndex];
const auto data = image.Data.Get() + image.SlicePitch * cubeFaceIndex;
// Copy data
imageData.WriteBytes(data, static_cast<int32>(image.SlicePitch));
}
// Copy mip
if (context.AllocateChunk(mipLevelIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[mipLevelIndex]->Data.Copy(imageData.GetHandle(), imageData.GetPosition());
}
}
#if IMPORT_TEXTURE_CACHE_OPTIONS
// Create json with import context
rapidjson_flax::StringBuffer importOptionsMetaBuffer;
importOptionsMetaBuffer.Reserve(256);
CompactJsonWriter importOptionsMeta(importOptionsMetaBuffer);
importOptionsMeta.StartObject();
{
context.AddMeta(importOptionsMeta);
options.Serialize(importOptionsMeta, nullptr);
}
importOptionsMeta.EndObject();
context.Data.Metadata.Copy((const byte*)importOptionsMetaBuffer.GetString(), (uint32)importOptionsMetaBuffer.GetSize());
#endif
return CreateAssetResult::Ok;
}
CreateAssetResult ImportTexture::Import(CreateAssetContext& context)
{
Options options;
InitOptions(context, options);
// Import
TextureData textureData;
String errorMsg;
if (TextureTool::ImportTexture(context.InputPath, textureData, options, errorMsg))
{
LOG(Error, "Cannot import texture. {0}", errorMsg);
return CreateAssetResult::Error;
}
return Create(context, textureData, options);
}
CreateAssetResult ImportTexture::ImportAsTextureData(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
return Create(context, static_cast<TextureData*>(context.CustomArg));
}
CreateAssetResult ImportTexture::ImportAsInitData(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
return Create(context, static_cast<TextureBase::InitData*>(context.CustomArg));
}
CreateAssetResult ImportTexture::Create(CreateAssetContext& context, TextureData* textureData)
{
Options options;
return Create(context, *textureData, options);
}
CreateAssetResult ImportTexture::Create(CreateAssetContext& context, TextureBase::InitData* initData)
{
Options options;
return Create(context, *initData, options);
}
CreateAssetResult ImportTexture::ImportCube(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
return CreateCube(context, static_cast<TextureData*>(context.CustomArg));
}
CreateAssetResult ImportTexture::CreateCube(CreateAssetContext& context, TextureData* textureData)
{
// Validate
if (textureData == nullptr)
{
LOG(Warning, "Missing argument.");
return CreateAssetResult::Error;
}
if (textureData->GetArraySize() != 6)
{
LOG(Warning, "Invalid cube texture array size.");
return CreateAssetResult::Error;
}
if (textureData->Width != textureData->Height)
{
LOG(Warning, "Invalid cube texture size.");
return CreateAssetResult::Error;
}
// Base
IMPORT_SETUP(CubeTexture, 4);
// Cache data
int32 size = textureData->Width;
PixelFormat format = textureData->Format;
bool isSRGB = PixelFormatExtensions::IsSRGB(format);
int32 mipLevels = textureData->GetMipLevels();
// Fill texture header
TextureHeader textureHeader;
textureHeader.IsSRGB = isSRGB;
textureHeader.Width = size;
textureHeader.Height = size;
textureHeader.IsCubeMap = true;
textureHeader.NeverStream = true; // TODO: could we support streaming for cube textures?
textureHeader.Type = TextureFormatType::Unknown;
textureHeader.Format = format;
textureHeader.MipLevels = mipLevels;
ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS);
// Log info
LOG(Info, "Importing cube texture '{0}': size: {1}, format: {2}, mip levels: {3}, sRGB: {4}", context.TargetAssetPath, size, static_cast<int32>(format), static_cast<int32>(textureHeader.MipLevels), textureHeader.IsSRGB);
// Save header
context.Data.CustomData.Copy(&textureHeader);
// Allocate memory for a temporary buffer
const uint32 imageSize = textureData->GetData(0, 0)->DepthPitch * 6;
MemoryWriteStream imageData(imageSize);
// Copy cube sides for every mip into separate chunks
for (int32 mipLevelIndex = 0; mipLevelIndex < mipLevels; mipLevelIndex++)
{
// Write array slices to the stream
imageData.SetPosition(0);
for (int32 cubeFaceIndex = 0; cubeFaceIndex < 6; cubeFaceIndex++)
{
// Get image
auto image = textureData->GetData(cubeFaceIndex, mipLevelIndex);
if (image == nullptr)
{
LOG(Warning, "Cannot create cube texture '{0}'. Missing image slice.", context.InputPath);
return CreateAssetResult::Error;
}
ASSERT(image->DepthPitch < MAX_int32);
// Copy data
imageData.WriteBytes(image->Data.Get(), image->Data.Length());
}
// Copy mip
if (context.AllocateChunk(mipLevelIndex))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[mipLevelIndex]->Data.Copy(imageData.GetHandle(), imageData.GetPosition());
}
return CreateAssetResult::Ok;
}
CreateAssetResult ImportTexture::ImportIES(class CreateAssetContext& context)
{
// Base
IMPORT_SETUP(IESProfile, 4);
// Load file
Array<byte> fileData;
if (File::ReadAllBytes(context.InputPath, fileData))
{
return CreateAssetResult::InvalidPath;
}
fileData.Add('\0');
// Load IES profile data
IESLoader loader;
if (loader.Load(fileData))
{
return CreateAssetResult::Error;
}
// Extract texture data
Array<byte> rawData;
const float multiplier = loader.ExtractInR16F(rawData);
// Fill texture header
TextureHeader textureHeader;
textureHeader.Width = loader.GetWidth();
textureHeader.Height = loader.GetHeight();
textureHeader.MipLevels = 1;
textureHeader.NeverStream = false;
textureHeader.Type = TextureFormatType::Unknown;
textureHeader.Format = PixelFormat::R16_Float;
textureHeader.IsSRGB = false;
textureHeader.IsCubeMap = false;
auto data = (IESProfile::CustomDataLayout*)textureHeader.CustomData;
data->Brightness = loader.GetBrightness();
data->TextureMultiplier = multiplier;
ASSERT(textureHeader.MipLevels <= GPU_MAX_TEXTURE_MIP_LEVELS);
context.Data.CustomData.Copy(&textureHeader);
// Set mip
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
context.Data.Header.Chunks[0]->Data.Copy(rawData);
return CreateAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Tools/TextureTool/TextureTool.h"
#include "Engine/Graphics/Textures/TextureBase.h"
/// <summary>
/// Enable/disable caching texture import options
/// </summary>
#define IMPORT_TEXTURE_CACHE_OPTIONS 1
/// <summary>
/// Importing textures utility
/// </summary>
class ImportTexture
{
public:
typedef TextureTool::Options Options;
public:
/// <summary>
/// Tries the get texture import options from the target location asset.
/// </summary>
/// <param name="path">The asset path.</param>
/// <param name="options">The options.</param>
/// <returns>True if success, otherwise false.</returns>
static bool TryGetImportOptions(const StringView& path, Options& options);
/// <summary>
/// Imports texture, cube texture or sprite atlas.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult Import(CreateAssetContext& context);
/// <summary>
/// Creates the Texture. Argument must be TextureData*.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportAsTextureData(CreateAssetContext& context);
/// <summary>
/// Creates the Texture. Argument must be TextureBase::InitData*.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportAsInitData(CreateAssetContext& context);
/// <summary>
/// Creates the Texture asset from the given data.
/// </summary>
/// <param name="context">The importing context.</param>
/// <param name="textureData">The texture data.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context, TextureData* textureData);
/// <summary>
/// Creates the Texture asset from the given data.
/// </summary>
/// <param name="context">The importing context.</param>
/// <param name="initData">The texture data.</param>
/// <returns>Result.</returns>
static CreateAssetResult Create(CreateAssetContext& context, TextureBase::InitData* initData);
/// <summary>
/// Imports the Cube Texture.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportCube(CreateAssetContext& context);
/// <summary>
/// Creates the Cube Texture asset from the given data.
/// </summary>
/// <param name="context">The importing context.</param>
/// <param name="textureData">The cube texture data.</param>
/// <returns>Result.</returns>
static CreateAssetResult CreateCube(CreateAssetContext& context, TextureData* textureData);
/// <summary>
/// Imports the IES Profile file.
/// </summary>
/// <param name="context">The importing context.</param>
/// <returns>Result.</returns>
static CreateAssetResult ImportIES(CreateAssetContext& context);
private:
static void InitOptions(CreateAssetContext& context, Options& options);
static CreateAssetResult Create(CreateAssetContext& context, const TextureData& textureData, Options& options);
static CreateAssetResult Create(CreateAssetContext& context, const TextureBase::InitData& textureData, Options& options);
};
#endif

View File

@@ -0,0 +1,163 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Content/Config.h"
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Enums.h"
#include "Engine/Core/NonCopyable.h"
#include "Engine/Core/Delegate.h"
#include "Engine/Core/Types/String.h"
#include "Engine/Content/Storage/FlaxFile.h"
class JsonWriter;
class CreateAssetContext;
/// <summary>
/// Create/Import new asset callback result
/// </summary>
DECLARE_ENUM_7(CreateAssetResult, Ok, Abort, Error, CannotSaveFile, InvalidPath, CannotAllocateChunk, InvalidTypeID);
/// <summary>
/// Create/Import new asset callback function
/// </summary>
typedef Function<CreateAssetResult(CreateAssetContext&)> CreateAssetFunction;
/// <summary>
/// Importing/creating asset context structure
/// </summary>
class CreateAssetContext : public NonCopyable
{
private:
CreateAssetResult _applyChangesResult;
public:
/// <summary>
/// Path of the input file (may be empty if creating new asset)
/// </summary>
String InputPath;
/// <summary>
/// Output file path
/// </summary>
String OutputPath;
/// <summary>
/// Target asset path (may be different than OutputPath)
/// </summary>
String TargetAssetPath;
/// <summary>
/// Asset file data container
/// </summary>
AssetInitData Data;
/// <summary>
/// True if skip the default asset import metadata added by the importer. May generate unwanted version control diffs.
/// </summary>
bool SkipMetadata;
/// <summary>
/// Custom argument for the importing function
/// </summary>
void* CustomArg;
// TODO: add Progress(float progress) to notify operation progress
// TODO: add cancellation feature - so progress can be aborted on demand
public:
/// <summary>
/// Initializes a new instance of the <see cref="CreateAssetContext"/> class.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="id">The identifier.</param>
/// <param name="arg">The custom argument.</param>
CreateAssetContext(const StringView& inputPath, const StringView& outputPath, const Guid& id, void* arg);
/// <summary>
/// Finalizes an instance of the <see cref="CreateAssetContext"/> class.
/// </summary>
~CreateAssetContext()
{
}
public:
/// <summary>
/// Runs the specified callback.
/// </summary>
/// <param name="callback">The import/create asset callback.</param>
/// <returns>Operation result.</returns>
CreateAssetResult Run(const CreateAssetFunction& callback);
public:
/// <summary>
/// Allocates the chunk in the output data so upgrader can write to it.
/// </summary>
/// <param name="index">The index of the chunk.</param>
/// <returns>True if cannot allocate it.</returns>
bool AllocateChunk(int32 index);
/// <summary>
/// Adds the meta to the writer.
/// </summary>
/// <param name="writer">The json metadata writer.</param>
void AddMeta(JsonWriter& writer) const;
/// <summary>
/// Save asset file data to the hard drive
/// </summary>
/// <returns>Saving result</returns>
CreateAssetResult Save();
private:
void ApplyChanges();
};
/// <summary>
/// Asset importer entry
/// </summary>
struct AssetImporter
{
public:
/// <summary>
/// Extension of the file to import with that importer
/// </summary>
String FileExtension;
/// <summary>
/// Call asset importing process
/// </summary>
CreateAssetFunction Callback;
};
/// <summary>
/// Asset creator entry
/// </summary>
struct AssetCreator
{
public:
/// <summary>
/// Asset creators are identifiable by tag
/// </summary>
String Tag;
/// <summary>
/// Call asset creating process
/// </summary>
CreateAssetFunction Callback;
};
#define IMPORT_SETUP(type, serializedVersion) context.Data.Header.TypeName = type::TypeName; context.Data.SerializedVersion = serializedVersion;
#endif