Files
FlaxEngine/Source/Engine/ShadowsOfMordor/Builder.Entries.cpp
2024-02-26 19:00:48 +01:00

267 lines
9.5 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Builder.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Level/Actors/BoxBrush.h"
#include "Engine/Level/Actors/StaticModel.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/Level.h"
#include "Engine/Terrain/Terrain.h"
#include "Engine/Terrain/TerrainPatch.h"
#include "Engine/Foliage/Foliage.h"
#include "Engine/Threading/Threading.h"
bool canUseMaterialWithLightmap(MaterialBase* material, ShadowsOfMordor::Builder::SceneBuildCache* scene)
{
// Check objects with missing materials can be used
if (material == nullptr)
return scene->GetSettings().UseGeometryWithNoMaterials;
return material->CanUseLightmap();
}
bool cacheStaticGeometryTree(Actor* actor, ShadowsOfMordor::Builder::SceneBuildCache* scene)
{
ShadowsOfMordor::Builder::GeometryEntry entry;
const bool useLightmap = actor->GetIsActive() && actor->HasStaticFlag(StaticFlags::Lightmap);
auto& results = scene->Entries;
// Switch actor type
if (auto staticModel = dynamic_cast<StaticModel*>(actor))
{
// Check if model has been linked and is loaded
auto model = staticModel->Model.Get();
if (model && !model->WaitForLoaded())
{
entry.Type = ShadowsOfMordor::Builder::GeometryType::StaticModel;
entry.UVsBox = Rectangle(Float2::Zero, Float2::One);
entry.AsStaticModel.Actor = staticModel;
entry.Scale = Math::Clamp(staticModel->GetScaleInLightmap(), 0.0f, LIGHTMAP_SCALE_MAX);
// Use the first LOD
const int32 lodIndex = 0;
auto& lod = model->LODs[lodIndex];
bool anyValid = false;
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
const auto& mesh = lod.Meshes[meshIndex];
auto& bufferEntry = staticModel->Entries[mesh.GetMaterialSlotIndex()];
if (bufferEntry.Visible && canUseMaterialWithLightmap(staticModel->GetMaterial(meshIndex), scene))
{
if (mesh.HasLightmapUVs())
{
anyValid = true;
}
else
{
LOG(Warning, "Model \'{0}\' mesh index {1} (lod: {2}) has missing lightmap UVs (at actor: {3})", model->GetPath(), meshIndex, lodIndex, staticModel->GetNamePath());
}
}
}
if (useLightmap && anyValid && entry.Scale > ZeroTolerance)
{
Matrix worldMatrix;
staticModel->GetLocalToWorldMatrix(worldMatrix);
entry.Box = model->GetBox(worldMatrix);
results.Add(entry);
}
else
{
staticModel->RemoveLightmap();
}
}
}
else if (auto terrain = dynamic_cast<Terrain*>(actor))
{
entry.AsTerrain.Actor = terrain;
entry.Type = ShadowsOfMordor::Builder::GeometryType::Terrain;
entry.UVsBox = Rectangle(Float2::Zero, Float2::One);
entry.Scale = Math::Clamp(terrain->GetScaleInLightmap(), 0.0f, LIGHTMAP_SCALE_MAX);
for (int32 patchIndex = 0; patchIndex < terrain->GetPatchesCount(); patchIndex++)
{
auto patch = terrain->GetPatch(patchIndex);
entry.AsTerrain.PatchIndex = patchIndex;
for (int32 chunkIndex = 0; chunkIndex < Terrain::ChunksCount; chunkIndex++)
{
auto chunk = patch->Chunks[chunkIndex];
entry.AsTerrain.ChunkIndex = chunkIndex;
auto material = chunk.OverrideMaterial ? chunk.OverrideMaterial.Get() : terrain->Material.Get();
const bool canUseLightmap = useLightmap
&& canUseMaterialWithLightmap(material, scene)
&& entry.Scale > ZeroTolerance;
if (canUseLightmap)
{
entry.Box = chunk.GetBounds();
results.Add(entry);
}
else
{
chunk.RemoveLightmap();
}
}
}
}
else if (auto foliage = dynamic_cast<Foliage*>(actor))
{
entry.AsFoliage.Actor = foliage;
entry.Type = ShadowsOfMordor::Builder::GeometryType::Foliage;
entry.UVsBox = Rectangle(Float2::Zero, Float2::One);
for (auto i = foliage->Instances.Begin(); i.IsNotEnd(); ++i)
{
auto& instance = *i;
auto& type = foliage->FoliageTypes[instance.Type];
entry.AsFoliage.InstanceIndex = i.Index();
entry.AsFoliage.TypeIndex = instance.Type;
entry.Scale = Math::Clamp(type.ScaleInLightmap, 0.0f, LIGHTMAP_SCALE_MAX);
const bool canUseLightmap = useLightmap
&& canUseMaterialWithLightmap(type.Entries[0].Material, scene)
&& entry.Scale > ZeroTolerance;
auto model = type.Model.Get();
if (canUseLightmap && model && !model->WaitForLoaded())
{
BoundingBox::FromSphere(instance.Bounds, entry.Box);
const int32 lodIndex = 0;
auto& lod = model->LODs[lodIndex];
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
{
entry.AsFoliage.MeshIndex = meshIndex;
results.Add(entry);
}
}
else
{
instance.RemoveLightmap();
}
}
}
return actor->GetIsActive();
}
void ShadowsOfMordor::Builder::cacheEntries()
{
auto scene = _scenes[_workerActiveSceneIndex];
reportProgress(BuildProgressStep::CacheEntries, 0);
scene->EntriesLocker.Lock();
// Gather static scene geometry entries
Function<bool(Actor*, SceneBuildCache*)> cacheGeometry = &cacheStaticGeometryTree;
scene->Scene->TreeExecute(cacheGeometry, scene);
scene->EntriesLocker.Unlock();
reportProgress(BuildProgressStep::CacheEntries, 1.0f);
}
void ShadowsOfMordor::Builder::updateEntries()
{
auto scene = _scenes[_workerActiveSceneIndex];
reportProgress(BuildProgressStep::UpdateEntries, 0.0f);
scene->EntriesLocker.Lock();
// Update entries (and cache entries list per lightmap as linear array instead of tree structure)
ScopeLock lock(Level::ScenesLock); // TODO: maybe don;t lock scene?
const int32 entriesCount = scene->Entries.Count();
for (int32 i = 0; i < entriesCount; i++)
{
auto& e = scene->Entries[i];
if (e.ChartIndex == INVALID_INDEX)
{
// Removed previously baked lightmap info
LightmapEntry emptyEntry;
switch (e.Type)
{
case GeometryType::StaticModel:
{
auto staticModel = e.AsStaticModel.Actor;
if (staticModel)
staticModel->Lightmap = emptyEntry;
}
break;
case GeometryType::Terrain:
{
auto terrain = e.AsTerrain.Actor;
if (terrain)
terrain->GetPatch(e.AsTerrain.PatchIndex)->Chunks[e.AsTerrain.ChunkIndex].Lightmap = emptyEntry;
}
break;
case GeometryType::Foliage:
{
auto foliage = e.AsFoliage.Actor;
if (foliage)
foliage->Instances[e.AsFoliage.InstanceIndex].Lightmap = emptyEntry;
}
break;
}
continue;
}
auto& chart = scene->Charts[e.ChartIndex];
// Update result uvs by taking into account lightmap uvs box
chart.Result.UVsArea.Size /= e.UVsBox.Size;
chart.Result.UVsArea.Location += e.UVsBox.Location * chart.Result.UVsArea.Size;
switch (e.Type)
{
case GeometryType::StaticModel:
{
auto staticModel = e.AsStaticModel.Actor;
if (staticModel)
{
// Update data
staticModel->Lightmap = chart.Result;
}
else
{
// Discard chart due to data leaks
chart.Result.TextureIndex = INVALID_INDEX;
}
}
break;
case GeometryType::Terrain:
{
auto terrain = e.AsTerrain.Actor;
if (terrain)
{
// Update data
terrain->GetPatch(e.AsTerrain.PatchIndex)->Chunks[e.AsTerrain.ChunkIndex].Lightmap = chart.Result;
}
else
{
// Discard chart due to data leaks
chart.Result.TextureIndex = INVALID_INDEX;
}
}
break;
case GeometryType::Foliage:
{
auto foliage = e.AsFoliage.Actor;
if (foliage)
{
// Update data
foliage->Instances[e.AsFoliage.InstanceIndex].Lightmap = chart.Result;
}
else
{
// Discard chart due to data leaks
chart.Result.TextureIndex = INVALID_INDEX;
}
}
break;
}
// Cache entry link
if (chart.Result.TextureIndex != INVALID_INDEX)
scene->Lightmaps[chart.Result.TextureIndex].Entries.Add(i);
reportProgress(BuildProgressStep::UpdateEntries, static_cast<float>(i) / entriesCount);
}
scene->EntriesLocker.Unlock();
reportProgress(BuildProgressStep::UpdateEntries, 1.0f);
}