You're breathtaking!

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

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_ASSETS_EXPORTER
#include "Types.h"
class AssetExporters
{
public:
static ExportAssetResult ExportTexture(ExportAssetContext& context);
static ExportAssetResult ExportCubeTexture(ExportAssetContext& context);
static ExportAssetResult ExportAudioClip(ExportAssetContext& context);
static ExportAssetResult ExportModel(ExportAssetContext& context);
static ExportAssetResult ExportSkinnedModel(ExportAssetContext& context);
};
#endif

View File

@@ -0,0 +1,136 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_ASSETS_EXPORTER
#include "AssetsExportingManager.h"
#include "AssetExporters.h"
#include "Engine/Core/Log.h"
#include "Engine/Platform/FileSystem.h"
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Content/Content.h"
#include "Engine/Render2D/SpriteAtlas.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Engine/EngineService.h"
Dictionary<String, ExportAssetFunction> AssetsExportingManager::Exporters;
class AssetsExportingManagerService : public EngineService
{
public:
AssetsExportingManagerService()
: EngineService(TEXT("AssetsExportingManager"), -300)
{
}
bool Init() override;
void Dispose() override;
};
AssetsExportingManagerService AssetsExportingManagerServiceInstance;
ExportAssetContext::ExportAssetContext(const String& inputPath, const String& outputFolder, void* arg)
{
InputPath = inputPath;
OutputFilename = StringUtils::GetFileNameWithoutExtension(inputPath);
OutputFolder = outputFolder;
CustomArg = arg;
}
ExportAssetResult ExportAssetContext::Run(const ExportAssetFunction& callback)
{
ASSERT(callback.IsBinded());
// Check if input file exists
if (!FileSystem::FileExists(InputPath))
return ExportAssetResult::MissingInputFile;
// Load asset (it will perform any required auto-conversions to have valid data)
auto asset = Content::LoadAsync<::Asset>(InputPath);
if (asset == nullptr || asset->WaitForLoaded())
return ExportAssetResult::CannotLoadAsset;
Asset = asset;
// Call action
return callback(*this);
}
const ExportAssetFunction* AssetsExportingManager::GetExporter(const String& typeName)
{
return Exporters.TryGet(typeName);
}
bool AssetsExportingManager::CanExport(const String& inputPath)
{
AssetInfo info;
if (!Content::GetAssetInfo(inputPath, info))
return false;
return Exporters.ContainsKey(info.TypeName);
}
bool AssetsExportingManager::Export(const String& inputPath, const String& outputFolder, void* arg)
{
AssetInfo info;
if (!Content::GetAssetInfo(inputPath, info))
{
LOG(Warning, "Cannot find asset at location {0}", inputPath);
return true;
}
ExportAssetFunction callback;
if (!Exporters.TryGet(info.TypeName, callback))
{
LOG(Warning, "Cannot find exporter for the asset at location {0} (typename: {1})", inputPath, info.TypeName);
return true;
}
return Export(callback, inputPath, outputFolder, arg);
}
bool AssetsExportingManager::Export(const ExportAssetFunction& callback, const String& inputPath, const String& outputFolder, void* arg)
{
LOG(Info, "Exporting asset '{0}' to '{1}'...", inputPath, outputFolder);
const auto startTime = DateTime::Now();
ExportAssetContext context(inputPath, outputFolder, arg);
const auto result = context.Run(callback);
if (result != ExportAssetResult::Ok)
{
LOG(Error, "Asset exporting failed! Result: {0}", ::ToString(result));
return true;
}
const auto endTime = DateTime::Now();
const auto exportTime = endTime - startTime;
LOG(Info, "Asset exported in {0}ms", Math::RoundToInt((float)exportTime.GetTotalMilliseconds()));
return false;
}
bool AssetsExportingManagerService::Init()
{
// Initialize with in-build exporters
AssetsExportingManager::Exporters.Add(Texture::TypeName, AssetExporters::ExportTexture);
AssetsExportingManager::Exporters.Add(SpriteAtlas::TypeName, AssetExporters::ExportTexture);
AssetsExportingManager::Exporters.Add(CubeTexture::TypeName, AssetExporters::ExportCubeTexture);
AssetsExportingManager::Exporters.Add(AudioClip::TypeName, AssetExporters::ExportAudioClip);
AssetsExportingManager::Exporters.Add(Model::TypeName, AssetExporters::ExportModel);
AssetsExportingManager::Exporters.Add(SkinnedModel::TypeName, AssetExporters::ExportSkinnedModel);
return false;
}
void AssetsExportingManagerService::Dispose()
{
// Cleanup
AssetsExportingManager::Exporters.Clear();
AssetsExportingManager::Exporters.SetCapacity(0);
}
#endif

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_ASSETS_EXPORTER
#include "Types.h"
#include "Engine/Core/Collections/Dictionary.h"
/// <summary>
/// Assets Importing service allows to import or create new assets
/// </summary>
class AssetsExportingManager
{
public:
/// <summary>
/// The asset exporting callbacks. Identified by the asset typename.
/// </summary>
static Dictionary<String, ExportAssetFunction> Exporters;
public:
/// <summary>
/// Gets the asset export for thee given asset typename.
/// </summary>
/// <param name="typeName">The asset typename.</param>
/// <returns>Exporter or null if not found.</returns>
static const ExportAssetFunction* GetExporter(const String& typeName);
/// <summary>
/// Checks if the asset at the given location can be exports.
/// </summary>
/// <param name="inputPath">The input asset path.</param>
/// <returns>True if can export it, otherwise false.</returns>
static bool CanExport(const String& inputPath);
public:
/// <summary>
/// Exports the asset.
/// </summary>
/// <param name="inputPath">The input asset path.</param>
/// <param name="outputFolder">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Export(const String& inputPath, const String& outputFolder, void* arg = nullptr);
/// <summary>
/// Exports the asset.
/// </summary>
/// <param name="callback">The custom callback.</param>
/// <param name="inputPath">The input asset path.</param>
/// <param name="outputFolder">The output path.</param>
/// <param name="arg">The custom argument.</param>
/// <returns>True if fails, otherwise false.</returns>
static bool Export(const ExportAssetFunction& callback, const String& inputPath, const String& outputFolder, void* arg = nullptr);
};
#endif

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using System.Collections.Generic;
using Flax.Build;
using Flax.Build.NativeCpp;
/// <summary>
/// Content exporting module.
/// </summary>
public class ContentExporters : EngineModule
{
/// <inheritdoc />
public override void Setup(BuildOptions options)
{
base.Setup(options);
options.PrivateDependencies.Add("AudioTool");
options.PrivateDependencies.Add("TextureTool");
options.PublicDefinitions.Add("COMPILE_WITH_ASSETS_EXPORTER");
}
/// <inheritdoc />
public override void GetFilesToDeploy(List<string> files)
{
}
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "AssetExporters.h"
#if COMPILE_WITH_ASSETS_EXPORTER
#include "Engine/Core/Log.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Platform/File.h"
#include "Engine/Tools/AudioTool/OggVorbisEncoder.h"
ExportAssetResult AssetExporters::ExportAudioClip(ExportAssetContext& context)
{
#if COMPILE_WITH_OGG_VORBIS
// Prepare
auto asset = (AudioClip*)context.Asset.Get();
auto lock = asset->Storage->LockSafe();
auto path = GET_OUTPUT_PATH(context, "ogg");
// Get audio data
Array<byte> rawData;
AudioDataInfo rawDataInfo;
if (asset->ExtractDataRaw(rawData, rawDataInfo))
return ExportAssetResult::CannotLoadData;
// Encode PCM data
BytesContainer encodedData;
OggVorbisEncoder encoder;
if (encoder.Convert(rawData.Get(), rawDataInfo, encodedData, 1.0f))
{
LOG(Warning, "Failed to compress audio data");
return ExportAssetResult::Error;
}
// Save to file
if (File::WriteAllBytes(path, encodedData.Get(), encodedData.Length()))
{
LOG(Warning, "Failed to save data to file");
return ExportAssetResult::Error;
}
return ExportAssetResult::Ok;
#else
LOG(Warning, "OggVorbis support is disabled.");
return ExportAssetResult::Error;
#endif
}
#endif

View File

@@ -0,0 +1,231 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "AssetExporters.h"
#if COMPILE_WITH_ASSETS_EXPORTER
#include "Engine/Core/Log.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Content/Assets/SkinnedModel.h"
#include "Engine/Serialization/FileWriteStream.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Core/DeleteMe.h"
ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
{
// Prepare
auto asset = (Model*)context.Asset.Get();
auto lock = asset->Storage->LockSafe();
auto path = GET_OUTPUT_PATH(context, "obj");
const int32 lodIndex = 0;
// Fetch chunk with data
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
if (asset->LoadChunk(chunkIndex))
return ExportAssetResult::CannotLoadData;
const auto chunk = asset->GetChunk(chunkIndex);
if (!chunk)
return ExportAssetResult::CannotLoadData;
MemoryReadStream stream(chunk->Get(), chunk->Size());
FileWriteStream* output = FileWriteStream::Open(path);
if (output == nullptr)
return ExportAssetResult::Error;
DeleteMe<FileWriteStream> outputDeleteMe(output);
const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi();
output->WriteTextFormatted("# Exported model {0}\n", name.Get());
// Extract all meshes
const auto& lod = asset->LODs[lodIndex];
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& mesh = lod.Meshes[meshIndex];
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
if (vertices == 0 || triangles == 0)
return ExportAssetResult::Error;
auto vb0 = stream.Read<VB0ElementType>(vertices);
auto vb1 = stream.Read<VB1ElementType>(vertices);
bool hasColors = stream.ReadBool();
VB2ElementType18* vb2 = nullptr;
if (hasColors)
{
vb2 = stream.Read<VB2ElementType18>(vertices);
}
auto ib = stream.Read<byte>(indicesCount * ibStride);
output->WriteTextFormatted("# Mesh {0}\n", meshIndex);
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb0[i].Position;
output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z);
}
output->WriteChar('\n');
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb1[i].TexCoord;
output->WriteTextFormatted("vt {0} {1}\n", ConvertHalfToFloat(v.X), ConvertHalfToFloat(v.Y));
}
output->WriteChar('\n');
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb1[i].Normal.ToVector3() * 2.0f - 1.0f;
output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z);
}
output->WriteChar('\n');
if (use16BitIndexBuffer)
{
auto t = (uint16*)ib;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = vertexStart + *t++;
auto i1 = vertexStart + *t++;
auto i2 = vertexStart + *t++;
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
}
}
else
{
auto t = (uint32*)ib;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = vertexStart + *t++;
auto i1 = vertexStart + *t++;
auto i2 = vertexStart + *t++;
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
}
}
output->WriteChar('\n');
vertexStart += vertices;
}
if (output->HasError())
return ExportAssetResult::Error;
return ExportAssetResult::Ok;
}
ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context)
{
// Prepare
auto asset = (SkinnedModel*)context.Asset.Get();
auto lock = asset->Storage->LockSafe();
auto path = GET_OUTPUT_PATH(context, "obj");
const int32 lodIndex = 0;
// Fetch chunk with data
const auto chunkIndex = 1;
if (asset->LoadChunk(chunkIndex))
return ExportAssetResult::CannotLoadData;
const auto chunk = asset->GetChunk(chunkIndex);
if (!chunk)
return ExportAssetResult::CannotLoadData;
MemoryReadStream stream(chunk->Get(), chunk->Size());
FileWriteStream* output = FileWriteStream::Open(path);
if (output == nullptr)
return ExportAssetResult::Error;
DeleteMe<FileWriteStream> outputDeleteMe(output);
const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi();
output->WriteTextFormatted("# Exported model {0}\n", name.Get());
// Extract all meshes
const auto& lod = asset->LODs[lodIndex];
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
auto& mesh = lod.Meshes[meshIndex];
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
if (vertices == 0 || triangles == 0)
return ExportAssetResult::Error;
auto vb0 = stream.Read<VB0SkinnedElementType>(vertices);
auto ib = stream.Read<byte>(indicesCount * ibStride);
output->WriteTextFormatted("# Mesh {0}\n", meshIndex);
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb0[i].Position;
output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z);
}
output->WriteChar('\n');
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb0[i].TexCoord;
output->WriteTextFormatted("vt {0} {1}\n", ConvertHalfToFloat(v.X), ConvertHalfToFloat(v.Y));
}
output->WriteChar('\n');
for (uint32 i = 0; i < vertices; i++)
{
auto v = vb0[i].Normal.ToVector3() * 2.0f - 1.0f;
output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z);
}
output->WriteChar('\n');
if (use16BitIndexBuffer)
{
auto t = (uint16*)ib;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = vertexStart + *t++;
auto i1 = vertexStart + *t++;
auto i2 = vertexStart + *t++;
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
}
}
else
{
auto t = (uint32*)ib;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = vertexStart + *t++;
auto i1 = vertexStart + *t++;
auto i2 = vertexStart + *t++;
output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2);
}
}
output->WriteChar('\n');
vertexStart += vertices;
}
if (output->HasError())
return ExportAssetResult::Error;
return ExportAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,119 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_ASSETS_EXPORTER
#include "AssetExporters.h"
#include "Engine/Core/Log.h"
#include "Engine/Content/Assets/CubeTexture.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Tools/TextureTool/TextureTool.h"
ExportAssetResult AssetExporters::ExportTexture(ExportAssetContext& context)
{
// Prepare
auto asset = (TextureBase*)context.Asset.Get();
auto lock = asset->Storage->LockSafe();
auto path = GET_OUTPUT_PATH(context, "png");
// Load the top mip data
const int32 chunkIndex = 0;
if (asset->LoadChunk(chunkIndex))
return ExportAssetResult::CannotLoadData;
BytesContainer data;
asset->GetMipData(0, data);
if (data.IsInvalid())
return ExportAssetResult::Error;
// Peek image description
const auto format = asset->Format();
const int32 width = asset->Width();
const int32 height = asset->Height();
uint32 rowPitch, slicePitch;
RenderTools::ComputePitch(format, width, height, rowPitch, slicePitch);
// Setup texture data
TextureData textureData;
textureData.Width = width;
textureData.Height = height;
textureData.Depth = 1;
textureData.Format = format;
textureData.Items.Resize(1);
auto& item = textureData.Items[0];
item.Mips.Resize(1);
auto& mip = item.Mips[0];
mip.RowPitch = rowPitch;
mip.DepthPitch = slicePitch;
mip.Lines = height;
mip.Data.Link(data.Get(), slicePitch);
// Export to file
if (TextureTool::ExportTexture(path, textureData))
{
return ExportAssetResult::Error;
}
return ExportAssetResult::Ok;
}
ExportAssetResult AssetExporters::ExportCubeTexture(ExportAssetContext& context)
{
// Prepare
auto asset = (CubeTexture*)context.Asset.Get();
auto lock = asset->Storage->LockSafe();
auto path = GET_OUTPUT_PATH(context, "dds");
// Load the asset data
if (asset->LoadChunks(ALL_ASSET_CHUNKS))
return ExportAssetResult::CannotLoadData;
// Peek image description
const auto format = asset->Format();
const int32 width = asset->Width();
const int32 height = asset->Height();
const int32 mipLevels = asset->StreamingTexture()->TotalMipLevels();
const int32 arraySize = 6;
// Setup texture data
TextureData textureData;
textureData.Width = width;
textureData.Height = height;
textureData.Depth = 1;
textureData.Format = format;
textureData.Items.Resize(arraySize);
for (int32 arrayIndex = 0; arrayIndex < 6; arrayIndex++)
{
auto& item = textureData.Items[arrayIndex];
item.Mips.Resize(mipLevels);
for (int32 mipIndex = 0; mipIndex < mipLevels; mipIndex++)
{
auto& mip = item.Mips[mipIndex];
const int32 mipWidth = Math::Max(1, width >> mipIndex);
const int32 mipHeight = Math::Max(1, height >> mipIndex);
uint32 rowPitch, slicePitch;
RenderTools::ComputePitch(format, mipWidth, mipHeight, rowPitch, slicePitch);
mip.RowPitch = rowPitch;
mip.DepthPitch = slicePitch;
mip.Lines = mipHeight;
BytesContainer wholeMipData;
asset->GetMipData(mipIndex, wholeMipData);
if (wholeMipData.IsInvalid())
return ExportAssetResult::Error;
mip.Data.Link(wholeMipData.Get() + slicePitch * arrayIndex, slicePitch);
}
}
// Export to file
if (TextureTool::ExportTexture(path, textureData))
{
return ExportAssetResult::Error;
}
return ExportAssetResult::Ok;
}
#endif

View File

@@ -0,0 +1,87 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#if COMPILE_WITH_ASSETS_EXPORTER
#include "Engine/Content/Config.h"
#include "Engine/Content/AssetReference.h"
#include "Engine/Core/Enums.h"
#include "Engine/Core/NonCopyable.h"
#include "Engine/Core/Types/String.h"
class JsonWriter;
class ExportAssetContext;
/// <summary>
/// Export asset callback result
/// </summary>
DECLARE_ENUM_6(ExportAssetResult, Ok, Abort, Error, CannotLoadAsset, MissingInputFile, CannotLoadData);
/// <summary>
/// Create/Import new asset callback function
/// </summary>
typedef Function<ExportAssetResult(ExportAssetContext&)> ExportAssetFunction;
/// <summary>
/// Exporting asset context structure
/// </summary>
class ExportAssetContext : public NonCopyable
{
public:
/// <summary>
/// The asset reference (prepared by the context to be used by callback).
/// </summary>
AssetReference<Asset> Asset;
/// <summary>
/// Path of the input file
/// </summary>
String InputPath;
/// <summary>
/// Recommended output filename
/// </summary>
String OutputFilename;
/// <summary>
/// Output file directory
/// </summary>
String OutputFolder;
/// <summary>
/// Custom argument for the importing function
/// </summary>
void* CustomArg;
public:
/// <summary>
/// Initializes a new instance of the <see cref="ExportAssetContext"/> class.
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="arg">The custom argument.</param>
ExportAssetContext(const String& inputPath, const String& outputPath, void* arg);
/// <summary>
/// Finalizes an instance of the <see cref="ExportAssetContext"/> class.
/// </summary>
~ExportAssetContext()
{
}
public:
/// <summary>
/// Runs the specified callback.
/// </summary>
/// <param name="callback">The export asset callback.</param>
/// <returns>Operation result.</returns>
ExportAssetResult Run(const ExportAssetFunction& callback);
};
#define GET_OUTPUT_PATH(context, extension) (context.OutputFolder / context.OutputFilename + TEXT("." extension))
#endif