Files
FlaxEngine/Source/Engine/CSG/CSGBuilder.cpp

441 lines
14 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "CSGBuilder.h"
#include "CSGMesh.h"
#include "CSGData.h"
#include "Engine/Level/Level.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Level/Actor.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/TimeSpan.h"
#include "Engine/Graphics/Models/ModelData.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Physics/CollisionData.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/ContentImporters/CreateCollisionData.h"
#include "Engine/Content/Assets/RawDataAsset.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#endif
#if COMPILE_WITH_CSG_BUILDER
using namespace CSG;
// Enable/disable locking scene during building CSG brushes nodes
#define CSG_USE_SCENE_LOCKS 0
struct BuildData;
namespace CSGBuilderImpl
{
Array<Scene*> ScenesToRebuild;
void onSceneUnloading(Scene* scene, const Guid& sceneId);
bool buildInner(Scene* scene, BuildData& data);
void build(Scene* scene);
bool generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath);
}
using namespace CSGBuilderImpl;
class CSGBuilderService : public EngineService
{
public:
CSGBuilderService()
: EngineService(TEXT("CSG Builder"), 90)
{
}
bool Init() override;
void Update() override;
};
CSGBuilderService CSGBuilderServiceInstance;
Delegate<Brush*> Builder::OnBrushModified;
void CSGBuilderImpl::onSceneUnloading(Scene* scene, const Guid& sceneId)
{
// Ensure to remove scene (prevent crashes)
ScenesToRebuild.Remove(scene);
}
bool CSGBuilderService::Init()
{
Level::SceneUnloading.Bind(onSceneUnloading);
return false;
}
void CSGBuilderService::Update()
{
// Check if build is pending
if (ScenesToRebuild.HasItems() && Engine::IsReady())
{
auto now = DateTime::NowUTC();
for (int32 i = 0; ScenesToRebuild.HasItems() && i < ScenesToRebuild.Count(); i++)
{
auto scene = ScenesToRebuild[i];
if (now - scene->CSGData.BuildTime >= 0)
{
scene->CSGData.BuildTime.Ticks = 0;
ScenesToRebuild.RemoveAt(i--);
build(scene);
}
}
}
}
bool Builder::IsActive()
{
return ScenesToRebuild.HasItems();
}
void Builder::Build(Scene* scene, float timeoutMs)
{
if (scene == nullptr)
return;
#if USE_EDITOR
// Disable building during play mode
if (Editor::IsPlayMode)
return;
#endif
// Register building
if (!ScenesToRebuild.Contains(scene))
{
ScenesToRebuild.Add(scene);
}
scene->CSGData.BuildTime = DateTime::NowUTC() + TimeSpan::FromMilliseconds(timeoutMs);
}
namespace CSG
{
typedef Dictionary<Actor*, Mesh*> MeshesLookup;
bool walkTree(Actor* actor, MeshesArray& meshes, MeshesLookup& cache)
{
// Check if actor is a brush
auto brush = dynamic_cast<Brush*>(actor);
if (brush)
{
// Check if can build it
if (brush->CanUseCSG())
{
// Skip subtract/common meshes from the beginning (they have no effect)
if (meshes.Count() > 0 || brush->GetBrushMode() == Mode::Additive)
{
// Create new mesh and build for given brush
auto mesh = New<CSG::Mesh>();
mesh->Build(brush);
// Save results
meshes.Add(mesh);
cache.Add(actor, mesh);
}
else
{
// Info
LOG(Info, "Skipping CSG brush '{0}'", actor->ToString());
}
}
}
return true;
}
Mesh* Combine(Actor* actor, MeshesLookup& cache, Mesh* combineParent)
{
ASSERT(actor);
Mesh* result = nullptr;
Mesh* myBrush = nullptr;
cache.TryGet(actor, myBrush);
// Get first child mesh with valid data (has additive brush)
int32 childIndex = 0;
while (childIndex < actor->Children.Count())
{
auto child = Combine(actor->Children[childIndex], cache, combineParent);
childIndex++;
if (child)
{
// If brush was based on additive brush or current actor is a brush we can stop searching
if (child->HasMode(Mode::Additive) || myBrush)
{
// End searching
result = child;
break;
}
if (combineParent)
{
// Combine
combineParent->PerformOperation(child);
}
}
}
// Check if has any child with CSG brush
if (result)
{
// Check if has own brush
if (myBrush)
{
// Combine with first child
myBrush->PerformOperation(result);
// Set this actor brush as a result
result = myBrush;
}
// Merge with the other children
while (childIndex < actor->Children.Count())
{
auto child = Combine(actor->Children[childIndex], cache, result);
if (child)
{
// Combine
result->PerformOperation(child);
}
childIndex++;
}
}
else
{
// Use this actor brush (may be empty)
result = myBrush;
}
return result;
}
Mesh* Combine(Scene* scene, MeshesLookup& cache)
{
#if CSG_USE_SCENE_LOCKS
auto Level = Level::Instance();
Level->Lock();
#endif
Mesh* result = Combine(scene, cache, nullptr);
#if CSG_USE_SCENE_LOCKS
Level->Unlock();
#endif
return result;
}
}
struct BuildData
{
MeshesArray meshes;
MeshesLookup cache;
Guid outputModelAssetId = Guid::Empty;
Guid outputRawDataAssetId = Guid::Empty;
Guid outputCollisionDataAssetId = Guid::Empty;
BuildData(int32 meshesCapacity = 32)
: meshes(meshesCapacity * 32)
, cache(meshesCapacity * 4)
{
}
};
bool CSGBuilderImpl::buildInner(Scene* scene, BuildData& data)
{
// Setup CSG meshes list and build them
{
Function<bool(Actor*, MeshesArray&, MeshesLookup&)> treeWalkFunction(walkTree);
SceneQuery::TreeExecute<Array<CSG::Mesh*>&, MeshesLookup&>(treeWalkFunction, data.meshes, data.cache);
}
if (data.meshes.IsEmpty())
return false;
// Process all meshes (performs actual CSG opterations on geometry in tree structure)
CSG::Mesh* combinedMesh = Combine(scene, data.cache);
if (combinedMesh == nullptr)
return false;
// TODO: split too big meshes (too many verts, to far parts, etc.)
// Triangulate meshes
{
// TODO: setup valid loop for splited meshes
// Convert CSG meshes into raw triangles data
RawData meshData;
Array<MeshVertex> vertexBuffer;
combinedMesh->Triangulate(meshData, vertexBuffer);
meshData.RemoveEmptySlots();
if (meshData.Slots.HasItems())
{
const auto sceneDataFolderPath = scene->GetDataFolderPath();
// Convert CSG mesh data to common storage type
ModelData modelData;
meshData.ToModelData(modelData);
// Convert CSG mesh to the local transformation of the scene
if (!scene->GetTransform().IsIdentity())
{
Matrix t;
scene->GetWorldToLocalMatrix(t);
modelData.TransformBuffer(t);
}
// Import model data to the asset
{
Guid modelDataAssetId = scene->CSGData.Model.GetID();
if (!modelDataAssetId.IsValid())
modelDataAssetId = Guid::New();
const String modelDataAssetPath = sceneDataFolderPath / TEXT("CSG_Mesh") + ASSET_FILES_EXTENSION_WITH_DOT;
if (AssetsImportingManager::Create(AssetsImportingManager::CreateModelTag, modelDataAssetPath, modelDataAssetId, &modelData))
{
LOG(Warning, "Failed to import CSG mesh data");
return true;
}
data.outputModelAssetId = modelDataAssetId;
}
// Generate asset with CSG mesh metadata (for collisions and brush queries)
{
Guid rawDataAssetId = scene->CSGData.Data.GetID();
if (!rawDataAssetId.IsValid())
rawDataAssetId = Guid::New();
const String rawDataAssetPath = sceneDataFolderPath / TEXT("CSG_Data") + ASSET_FILES_EXTENSION_WITH_DOT;
if (generateRawDataAsset(scene, meshData, rawDataAssetId, rawDataAssetPath))
{
LOG(Warning, "Failed to create raw CSG data");
return true;
}
data.outputRawDataAssetId = rawDataAssetId;
}
// Generate CSG mesh collision asset
{
// Convert CSG mesh to scene local space (fix issues when scene has transformation applied)
if (!scene->GetTransform().IsIdentity())
{
Matrix m1, m2;
scene->GetTransform().GetWorld(m1);
Matrix::Invert(m1, m2);
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
{
auto lod = &modelData.LODs[lodIndex];
for (int32 meshIndex = 0; meshIndex < lod->Meshes.Count(); meshIndex++)
{
Array<Float3>& v = lod->Meshes[meshIndex]->Positions;
for (int32 i = 0; i < v.Count(); i++)
Float3::Transform(v[i], m2, v[i]);
}
}
}
#if COMPILE_WITH_PHYSICS_COOKING
CollisionCooking::Argument arg;
arg.Type = CollisionDataType::TriangleMesh;
arg.OverrideModelData = &modelData;
Guid collisionDataAssetId = scene->CSGData.CollisionData.GetID();
if (!collisionDataAssetId.IsValid())
collisionDataAssetId = Guid::New();
const String collisionDataAssetPath = sceneDataFolderPath / TEXT("CSG_Collision") + ASSET_FILES_EXTENSION_WITH_DOT;
if (AssetsImportingManager::Create(AssetsImportingManager::CreateCollisionDataTag, collisionDataAssetPath, collisionDataAssetId, &arg))
{
LOG(Warning, "Failed to cook CSG mesh collision data");
return true;
}
data.outputCollisionDataAssetId = collisionDataAssetId;
#else
data.outputCollisionDataAssetId = Guid::Empty;
#endif
}
}
}
return false;
}
void CSGBuilderImpl::build(Scene* scene)
{
// Start
auto startTime = DateTime::Now();
LOG(Info, "Start building CSG...");
// Build
BuildData data;
bool failed = buildInner(scene, data);
// Link new (or empty) CSG mesh
scene->CSGData.Data = Content::LoadAsync<RawDataAsset>(data.outputRawDataAssetId);
scene->CSGData.Model = Content::LoadAsync<Model>(data.outputModelAssetId);
scene->CSGData.CollisionData = Content::LoadAsync<CollisionData>(data.outputCollisionDataAssetId);
// TODO: also set CSGData.InstanceBuffer - lightmap scales for the entries so csg mesh gets better quality in lightmaps
scene->CSGData.PostCSGBuild();
// End
data.meshes.ClearDelete();
auto endTime = DateTime::Now();
LOG(Info, "CSG build in {0} ms! {1} brush(es)", (endTime - startTime).GetTotalMilliseconds(), data.meshes.Count());
}
bool CSGBuilderImpl::generateRawDataAsset(Scene* scene, RawData& meshData, Guid& assetId, const String& assetPath)
{
// Prepare data
MemoryWriteStream stream(4096);
{
// Header (with version number)
stream.WriteInt32(1);
const int32 brushesCount = meshData.Brushes.Count();
stream.WriteInt32(brushesCount);
// Surfaces data locations
int32 surfacesDataOffset = sizeof(int32) * 2 + (sizeof(Guid) + sizeof(int32)) * brushesCount;
for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
{
auto& surfaces = brush->Value.Surfaces;
stream.Write(brush->Key);
stream.WriteInt32(surfacesDataOffset);
// Calculate offset in data storage to the next brush data
int32 brushDataSize = 0;
for (int32 i = 0; i < surfaces.Count(); i++)
{
brushDataSize += sizeof(int32) + sizeof(RawData::SurfaceTriangle) * surfaces[i].Triangles.Count();
}
surfacesDataOffset += brushDataSize;
}
// Surfaces data
for (auto brush = meshData.Brushes.Begin(); brush.IsNotEnd(); ++brush)
{
auto& surfaces = brush->Value.Surfaces;
for (int32 i = 0; i < surfaces.Count(); i++)
{
auto& triangles = surfaces[i].Triangles;
stream.WriteInt32(triangles.Count());
stream.WriteBytes(triangles.Get(), triangles.Count() * sizeof(RawData::SurfaceTriangle));
}
}
}
// Serialize
BytesContainer bytesContainer;
bytesContainer.Link(ToSpan(stream));
return AssetsImportingManager::Create(AssetsImportingManager::CreateRawDataTag, assetPath, assetId, (void*)&bytesContainer);
}
#endif