You're breathtaking!
This commit is contained in:
488
Source/Engine/ContentImporters/AssetsImportingManager.cpp
Normal file
488
Source/Engine/ContentImporters/AssetsImportingManager.cpp
Normal 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
|
||||
233
Source/Engine/ContentImporters/AssetsImportingManager.h
Normal file
233
Source/Engine/ContentImporters/AssetsImportingManager.h
Normal 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
|
||||
35
Source/Engine/ContentImporters/ContentImporters.Build.cs
Normal file
35
Source/Engine/ContentImporters/ContentImporters.Build.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
48
Source/Engine/ContentImporters/CreateAnimationGraph.cpp
Normal file
48
Source/Engine/ContentImporters/CreateAnimationGraph.cpp
Normal 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
|
||||
24
Source/Engine/ContentImporters/CreateAnimationGraph.h
Normal file
24
Source/Engine/ContentImporters/CreateAnimationGraph.h
Normal 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
|
||||
@@ -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
|
||||
63
Source/Engine/ContentImporters/CreateCollisionData.cpp
Normal file
63
Source/Engine/ContentImporters/CreateCollisionData.cpp
Normal 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
|
||||
38
Source/Engine/ContentImporters/CreateCollisionData.h
Normal file
38
Source/Engine/ContentImporters/CreateCollisionData.h
Normal 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
|
||||
87
Source/Engine/ContentImporters/CreateJson.cpp
Normal file
87
Source/Engine/ContentImporters/CreateJson.cpp
Normal 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
|
||||
23
Source/Engine/ContentImporters/CreateJson.h
Normal file
23
Source/Engine/ContentImporters/CreateJson.h
Normal 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
|
||||
226
Source/Engine/ContentImporters/CreateMaterial.cpp
Normal file
226
Source/Engine/ContentImporters/CreateMaterial.cpp
Normal 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
|
||||
57
Source/Engine/ContentImporters/CreateMaterial.h
Normal file
57
Source/Engine/ContentImporters/CreateMaterial.h
Normal 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
|
||||
55
Source/Engine/ContentImporters/CreateMaterialFunction.h
Normal file
55
Source/Engine/ContentImporters/CreateMaterialFunction.h
Normal 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
|
||||
42
Source/Engine/ContentImporters/CreateMaterialInstance.h
Normal file
42
Source/Engine/ContentImporters/CreateMaterialInstance.h
Normal 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
|
||||
47
Source/Engine/ContentImporters/CreateParticleEmitter.h
Normal file
47
Source/Engine/ContentImporters/CreateParticleEmitter.h
Normal 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
|
||||
@@ -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
|
||||
45
Source/Engine/ContentImporters/CreateParticleSystem.h
Normal file
45
Source/Engine/ContentImporters/CreateParticleSystem.h
Normal 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
|
||||
39
Source/Engine/ContentImporters/CreateRawData.h
Normal file
39
Source/Engine/ContentImporters/CreateRawData.h
Normal 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
|
||||
44
Source/Engine/ContentImporters/CreateSceneAnimation.h
Normal file
44
Source/Engine/ContentImporters/CreateSceneAnimation.h
Normal 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
|
||||
43
Source/Engine/ContentImporters/CreateSkeletonMask.h
Normal file
43
Source/Engine/ContentImporters/CreateSkeletonMask.h
Normal 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
|
||||
55
Source/Engine/ContentImporters/CreateVisualScript.h
Normal file
55
Source/Engine/ContentImporters/CreateVisualScript.h
Normal 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
|
||||
302
Source/Engine/ContentImporters/ImportAudio.cpp
Normal file
302
Source/Engine/ContentImporters/ImportAudio.cpp
Normal 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
|
||||
97
Source/Engine/ContentImporters/ImportAudio.h
Normal file
97
Source/Engine/ContentImporters/ImportAudio.h
Normal 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
|
||||
39
Source/Engine/ContentImporters/ImportFont.cpp
Normal file
39
Source/Engine/ContentImporters/ImportFont.cpp
Normal 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
|
||||
24
Source/Engine/ContentImporters/ImportFont.h
Normal file
24
Source/Engine/ContentImporters/ImportFont.h
Normal 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
|
||||
56
Source/Engine/ContentImporters/ImportModel.h
Normal file
56
Source/Engine/ContentImporters/ImportModel.h
Normal 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
|
||||
283
Source/Engine/ContentImporters/ImportModelFile.cpp
Normal file
283
Source/Engine/ContentImporters/ImportModelFile.cpp
Normal 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
|
||||
57
Source/Engine/ContentImporters/ImportModelFile.h
Normal file
57
Source/Engine/ContentImporters/ImportModelFile.h
Normal 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
|
||||
55
Source/Engine/ContentImporters/ImportShader.cpp
Normal file
55
Source/Engine/ContentImporters/ImportShader.cpp
Normal 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
|
||||
24
Source/Engine/ContentImporters/ImportShader.h
Normal file
24
Source/Engine/ContentImporters/ImportShader.h
Normal 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
|
||||
564
Source/Engine/ContentImporters/ImportTexture.cpp
Normal file
564
Source/Engine/ContentImporters/ImportTexture.cpp
Normal 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
|
||||
102
Source/Engine/ContentImporters/ImportTexture.h
Normal file
102
Source/Engine/ContentImporters/ImportTexture.h
Normal 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
|
||||
163
Source/Engine/ContentImporters/Types.h
Normal file
163
Source/Engine/ContentImporters/Types.h
Normal 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
|
||||
Reference in New Issue
Block a user