b201897ae6
Cuts down foliage data size by over 50% and improves load times.
1753 lines
60 KiB
C++
1753 lines
60 KiB
C++
// Copyright (c) Wojciech Figat. All rights reserved.
|
|
|
|
#include "Foliage.h"
|
|
#include "FoliageType.h"
|
|
#include "FoliageCluster.h"
|
|
#include "Engine/Core/Log.h"
|
|
#include "Engine/Core/Random.h"
|
|
#include "Engine/Core/Collections/BitArray.h"
|
|
#include "Engine/Engine/Engine.h"
|
|
#include "Engine/Graphics/Graphics.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Graphics/RenderTools.h"
|
|
#include "Engine/Graphics/GPUDevice.h"
|
|
#include "Engine/Content/Deprecated.h"
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
#include "Engine/Threading/JobSystem.h"
|
|
#endif
|
|
#include "Engine/Level/SceneQuery.h"
|
|
#include "Engine/Profiler/ProfilerCPU.h"
|
|
#include "Engine/Renderer/RenderList.h"
|
|
#include "Engine/Renderer/GlobalSignDistanceFieldPass.h"
|
|
#include "Engine/Renderer/GI/GlobalSurfaceAtlasPass.h"
|
|
#include "Engine/Serialization/Serialization.h"
|
|
#include "Engine/Serialization/MemoryReadStream.h"
|
|
#include "Engine/Serialization/MemoryWriteStream.h"
|
|
#include "Engine/Utilities/Encryption.h"
|
|
#include <ThirdParty/LZ4/lz4.h>
|
|
|
|
#define FOLIAGE_GET_DRAW_MODES(renderContext, type) (type._drawModes & renderContext.View.Pass & renderContext.View.GetShadowsDrawPassMask(type.ShadowsMode))
|
|
#define FOLIAGE_CAN_DRAW(renderContext, type) (type.IsReady() && FOLIAGE_GET_DRAW_MODES(renderContext, type) != DrawPass::None && type.Model->CanBeRendered())
|
|
|
|
Foliage::Foliage(const SpawnParams& params)
|
|
: Actor(params)
|
|
{
|
|
_disableFoliageTypeEvents = false;
|
|
|
|
// When using separate quad-tree for each foliage type we can run async job to draw them in separate, otherwise just draw whole foliage in async as one
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
_drawCategory = SceneRendering::SceneDrawAsync;
|
|
#else
|
|
_drawCategory = SceneRendering::SceneDraw;
|
|
#endif
|
|
}
|
|
|
|
void Foliage::AddToCluster(ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE>& clusters, FoliageCluster* cluster, FoliageInstance& instance)
|
|
{
|
|
ASSERT_LOW_LAYER(instance.Bounds.Radius > ZeroTolerance);
|
|
|
|
// Minor clusters don't use bounds intersection but try to find the first free cluster instead
|
|
if (cluster->IsMinor)
|
|
{
|
|
// Insert into the first non-full child cluster or subdivide 1st child
|
|
#define CHECK_CHILD(idx) \
|
|
if (cluster->Children[idx]->Instances.Count() < FOLIAGE_CLUSTER_CAPACITY) \
|
|
{ \
|
|
cluster->Children[idx]->Instances.Add(&instance); \
|
|
return; \
|
|
}
|
|
CHECK_CHILD(3);
|
|
CHECK_CHILD(2);
|
|
CHECK_CHILD(1);
|
|
cluster = cluster->Children[0];
|
|
#undef CHECK_CHILD
|
|
}
|
|
else
|
|
{
|
|
// Find target cluster
|
|
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
|
|
while (cluster->Children[0])
|
|
{
|
|
#define CHECK_CHILD(idx) \
|
|
if (cluster->Children[idx]->Bounds.Intersects(instance.Bounds)) \
|
|
{ \
|
|
cluster = cluster->Children[idx]; \
|
|
continue; \
|
|
}
|
|
CHECK_CHILD(0);
|
|
CHECK_CHILD(1);
|
|
CHECK_CHILD(2);
|
|
CHECK_CHILD(3);
|
|
#undef CHECK_CHILD
|
|
}
|
|
}
|
|
|
|
// Check if it's not full
|
|
if (cluster->Instances.Count() != FOLIAGE_CLUSTER_CAPACITY)
|
|
{
|
|
// Insert into cluster
|
|
cluster->Instances.Add(&instance);
|
|
}
|
|
else
|
|
{
|
|
// Subdivide cluster
|
|
const int32 count = clusters.Count();
|
|
clusters.Resize(count + 4);
|
|
cluster->Children[0] = &clusters[count + 0];
|
|
cluster->Children[1] = &clusters[count + 1];
|
|
cluster->Children[2] = &clusters[count + 2];
|
|
cluster->Children[3] = &clusters[count + 3];
|
|
|
|
// Setup children
|
|
const Vector3 min = cluster->Bounds.Minimum;
|
|
const Vector3 max = cluster->Bounds.Maximum;
|
|
const Vector3 size = max - min;
|
|
cluster->Children[0]->Init(BoundingBox(min, min + size * Vector3(0.5f, 1.0f, 0.5f)));
|
|
cluster->Children[1]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.5f), max));
|
|
cluster->Children[2]->Init(BoundingBox(min + size * Vector3(0.5f, 0.0f, 0.0f), min + size * Vector3(1.0f, 1.0f, 0.5f)));
|
|
cluster->Children[3]->Init(BoundingBox(min + size * Vector3(0.0f, 0.0f, 0.5f), min + size * Vector3(0.5f, 1.0f, 1.0f)));
|
|
if (cluster->IsMinor || size.MinValue() < 1.0f)
|
|
{
|
|
// Mark children as minor to avoid infinite subdivision
|
|
cluster->IsMinor = true;
|
|
cluster->Children[0]->IsMinor = true;
|
|
cluster->Children[1]->IsMinor = true;
|
|
cluster->Children[2]->IsMinor = true;
|
|
cluster->Children[3]->IsMinor = true;
|
|
}
|
|
|
|
// Move instances to a proper cells
|
|
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
|
{
|
|
AddToCluster(clusters, cluster, *cluster->Instances[i]);
|
|
}
|
|
cluster->Instances.Clear();
|
|
AddToCluster(clusters, cluster, instance);
|
|
}
|
|
}
|
|
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
|
|
|
void Foliage::DrawInstance(DrawContext& context, FoliageInstance& instance, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
|
|
{
|
|
const auto& meshes = model->LODs.Get()[lod].Meshes;
|
|
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
|
{
|
|
auto& drawCall = drawCallsLists[lod][meshIndex];
|
|
if (!drawCall.Material)
|
|
continue;
|
|
|
|
DrawKey key;
|
|
key.Mat = drawCall.Material;
|
|
key.Geo = &meshes.Get()[meshIndex];
|
|
key.Lightmap = instance.Lightmap.TextureIndex;
|
|
auto* e = result.TryGet(key);
|
|
if (!e)
|
|
{
|
|
e = &result.Add(key, BatchedDrawCall(context.RenderContext.List))->Value;
|
|
ASSERT_LOW_LAYER(key.Mat);
|
|
e->DrawCall.Material = key.Mat;
|
|
e->DrawCall.Surface.Lightmap = EnumHasAnyFlags(_staticFlags, StaticFlags::Lightmap) && _scene ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr;
|
|
e->DrawCall.Surface.GeometrySize = key.Geo->GetBox().GetSize();
|
|
}
|
|
|
|
// Add instance to the draw batch
|
|
auto& instanceData = e->Instances.AddOne();
|
|
Matrix world;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
const Float3 translation = transform.Translation - context.ViewOrigin;
|
|
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
|
|
constexpr float worldDeterminantSign = 1.0f;
|
|
instanceData.Store(world, world, instance.Lightmap.UVsArea, drawCall.Surface.GeometrySize, instance.Random, worldDeterminantSign, lodDitherFactor);
|
|
}
|
|
}
|
|
|
|
void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const
|
|
{
|
|
// Skip clusters that around too far from view
|
|
if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
|
return;
|
|
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
|
|
|
|
// Draw visible children
|
|
if (cluster->Children[0])
|
|
{
|
|
BoundingBox box;
|
|
#define DRAW_CLUSTER(idx) \
|
|
box = cluster->Children[idx]->TotalBounds; \
|
|
box.Minimum -= context.ViewOrigin; \
|
|
box.Maximum -= context.ViewOrigin; \
|
|
if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
|
|
DrawCluster(context, cluster->Children[idx], drawCallsLists, result)
|
|
DRAW_CLUSTER(0);
|
|
DRAW_CLUSTER(1);
|
|
DRAW_CLUSTER(2);
|
|
DRAW_CLUSTER(3);
|
|
#undef DRAW_CLUSTER
|
|
}
|
|
//else // Minor clusters can be subdivided and contain instances
|
|
{
|
|
// Draw visible instances
|
|
const auto frame = Engine::FrameCount;
|
|
const auto model = context.FoliageType.Model.Get();
|
|
const auto transitionLOD = context.RenderContext.View.Pass != DrawPass::Depth; // Let the main view pass update LOD transitions
|
|
// TODO: move DrawState to be stored per-view (so shadows can fade objects on their own)
|
|
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
|
{
|
|
auto& instance = *cluster->Instances.Get()[i];
|
|
BoundingSphere sphere = instance.Bounds;
|
|
sphere.Center -= context.ViewOrigin;
|
|
if (Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
|
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
|
|
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, (float)sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
|
|
{
|
|
const auto modelFrame = instance.DrawState.PrevFrame + 1;
|
|
|
|
// Select a proper LOD index (model may be culled)
|
|
int32 lodIndex = RenderTools::ComputeModelLOD(model, sphere.Center, (float)sphere.Radius, context.RenderContext);
|
|
if (lodIndex == -1)
|
|
{
|
|
// Handling model fade-out transition
|
|
if (modelFrame == frame && instance.DrawState.PrevLOD != -1)
|
|
{
|
|
if (transitionLOD)
|
|
{
|
|
// Check if start transition
|
|
if (instance.DrawState.LODTransition == 255)
|
|
{
|
|
instance.DrawState.LODTransition = 0;
|
|
}
|
|
|
|
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
|
|
|
// Check if end transition
|
|
if (instance.DrawState.LODTransition == 255)
|
|
{
|
|
instance.DrawState.PrevLOD = lodIndex;
|
|
}
|
|
else
|
|
{
|
|
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
|
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
|
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
|
}
|
|
}
|
|
else if (instance.DrawState.LODTransition < 255)
|
|
{
|
|
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
|
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
|
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
|
}
|
|
}
|
|
instance.DrawState.PrevFrame = frame;
|
|
continue;
|
|
}
|
|
lodIndex += context.RenderContext.View.ModelLODBias;
|
|
lodIndex = model->ClampLODIndex(lodIndex);
|
|
|
|
if (transitionLOD)
|
|
{
|
|
// Check if it's the new frame and could update the drawing state (note: model instance could be rendered many times per frame to different viewports)
|
|
if (modelFrame == frame)
|
|
{
|
|
// Check if start transition
|
|
if (instance.DrawState.PrevLOD != lodIndex && instance.DrawState.LODTransition == 255)
|
|
{
|
|
instance.DrawState.LODTransition = 0;
|
|
}
|
|
|
|
RenderTools::UpdateModelLODTransition(instance.DrawState.LODTransition);
|
|
|
|
// Check if end transition
|
|
if (instance.DrawState.LODTransition == 255)
|
|
{
|
|
instance.DrawState.PrevLOD = lodIndex;
|
|
}
|
|
}
|
|
// Check if there was a gap between frames in drawing this model instance
|
|
else if (modelFrame < frame || instance.DrawState.PrevLOD == -1)
|
|
{
|
|
// Reset state
|
|
instance.DrawState.PrevLOD = lodIndex;
|
|
instance.DrawState.LODTransition = 0;
|
|
}
|
|
}
|
|
|
|
// Draw
|
|
if (instance.DrawState.PrevLOD == lodIndex)
|
|
{
|
|
DrawInstance(context, instance, model, lodIndex, 0.0f, drawCallsLists, result);
|
|
}
|
|
else if (instance.DrawState.PrevLOD == -1)
|
|
{
|
|
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
|
DrawInstance(context, instance, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result);
|
|
}
|
|
else
|
|
{
|
|
const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD);
|
|
const float normalizedProgress = static_cast<float>(instance.DrawState.LODTransition) * (1.0f / 255.0f);
|
|
DrawInstance(context, instance, model, prevLOD, normalizedProgress, drawCallsLists, result);
|
|
DrawInstance(context, instance, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result);
|
|
}
|
|
|
|
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
|
|
|
|
if (transitionLOD)
|
|
instance.DrawState.PrevFrame = frame;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
void Foliage::DrawCluster(DrawContext& context, FoliageCluster* cluster, Mesh::DrawInfo& draw)
|
|
{
|
|
// Skip clusters that around too far from view
|
|
if (Float3::Distance(context.LodView.Position, cluster->TotalBoundsSphere.Center - context.LodView.Origin) - (float)cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
|
|
return;
|
|
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
|
|
|
|
// Draw visible children
|
|
if (cluster->Children[0])
|
|
{
|
|
BoundingBox box;
|
|
#define DRAW_CLUSTER(idx) \
|
|
box = cluster->Children[idx]->TotalBounds; \
|
|
box.Minimum -= context.ViewOrigin; \
|
|
box.Maximum -= context.ViewOrigin; \
|
|
if (context.RenderContext.View.CullingFrustum.Intersects(box)) \
|
|
DrawCluster(context, cluster->Children[idx], draw)
|
|
DRAW_CLUSTER(0);
|
|
DRAW_CLUSTER(1);
|
|
DRAW_CLUSTER(2);
|
|
DRAW_CLUSTER(3);
|
|
#undef DRAW_CLUSTER
|
|
}
|
|
//else // Minor clusters can be subdivided and contain instances
|
|
{
|
|
// Draw visible instances
|
|
const auto frame = Engine::FrameCount;
|
|
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
|
{
|
|
auto& instance = *cluster->Instances[i];
|
|
auto& type = FoliageTypes[instance.Type];
|
|
BoundingSphere sphere = instance.Bounds;
|
|
sphere.Center -= context.ViewOrigin;
|
|
|
|
// Check if can draw this instance
|
|
if (type._canDraw &&
|
|
Float3::Distance(context.LodView.Position, sphere.Center) - (float)sphere.Radius < instance.CullDistance &&
|
|
context.RenderContext.View.CullingFrustum.Intersects(sphere) &&
|
|
RenderTools::ComputeBoundsScreenRadiusSquared(sphere.Center, sphere.Radius, context.RenderContext.View) * context.ViewScreenSizeSq >= context.MinObjectPixelSizeSq)
|
|
{
|
|
Matrix world;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
const Float3 translation = transform.Translation - context.ViewOrigin;
|
|
Matrix::Transformation(transform.Scale, transform.Orientation, translation, world);
|
|
|
|
// Disable motion blur
|
|
instance.DrawState.PrevWorld = world;
|
|
|
|
// Draw model
|
|
draw.Lightmap = _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex);
|
|
draw.LightmapUVs = &instance.Lightmap.UVsArea;
|
|
draw.Buffer = &type.Entries;
|
|
draw.World = &world;
|
|
draw.DrawState = &instance.DrawState;
|
|
draw.Bounds = sphere;
|
|
draw.PerInstanceRandom = instance.Random;
|
|
draw.DrawModes = type._drawModes;
|
|
draw.SetStencilValue(_layer);
|
|
type.Model->Draw(context.RenderContext, draw);
|
|
|
|
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
|
|
|
|
instance.DrawState.PrevFrame = frame;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
|
|
void Foliage::DrawClusterGlobalSDF(class GlobalSignDistanceFieldPass* globalSDF, const BoundingBox& globalSDFBounds, FoliageCluster* cluster, const FoliageType& type)
|
|
{
|
|
if (cluster->Children[0])
|
|
{
|
|
// Draw children recursive
|
|
#define DRAW_CLUSTER(idx) \
|
|
if (globalSDFBounds.Intersects(cluster->Children[idx]->TotalBounds)) \
|
|
DrawClusterGlobalSDF(globalSDF, globalSDFBounds, cluster->Children[idx], type)
|
|
DRAW_CLUSTER(0);
|
|
DRAW_CLUSTER(1);
|
|
DRAW_CLUSTER(2);
|
|
DRAW_CLUSTER(3);
|
|
#undef DRAW_CLUSTER
|
|
}
|
|
//else // Minor clusters can be subdivided and contain instances
|
|
{
|
|
// Draw visible instances
|
|
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
|
{
|
|
auto& instance = *cluster->Instances[i];
|
|
if (CollisionsHelper::BoxIntersectsSphere(globalSDFBounds, instance.Bounds))
|
|
{
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
BoundingBox bounds;
|
|
BoundingBox::FromSphere(instance.Bounds, bounds);
|
|
globalSDF->RasterizeModelSDF(this, type.Model->SDF, transform, bounds);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Foliage::DrawClusterGlobalSA(GlobalSurfaceAtlasPass* globalSA, const Vector4& cullingPosDistance, FoliageCluster* cluster, const FoliageType& type, const BoundingBox& localBounds)
|
|
{
|
|
if (cluster->Children[0])
|
|
{
|
|
// Draw children recursive
|
|
#define DRAW_CLUSTER(idx) \
|
|
if (CollisionsHelper::DistanceBoxPoint(cluster->Children[idx]->TotalBounds, Vector3(cullingPosDistance)) < cullingPosDistance.W) \
|
|
DrawClusterGlobalSA(globalSA, cullingPosDistance, cluster->Children[idx], type, localBounds)
|
|
DRAW_CLUSTER(0);
|
|
DRAW_CLUSTER(1);
|
|
DRAW_CLUSTER(2);
|
|
DRAW_CLUSTER(3);
|
|
#undef DRAW_CLUSTER
|
|
}
|
|
//else // Minor clusters can be subdivided and contain instances
|
|
{
|
|
// Draw visible instances
|
|
for (int32 i = 0; i < cluster->Instances.Count(); i++)
|
|
{
|
|
auto& instance = *cluster->Instances[i];
|
|
if (CollisionsHelper::DistanceSpherePoint(instance.Bounds, Vector3(cullingPosDistance)) < cullingPosDistance.W)
|
|
{
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
globalSA->RasterizeActor(this, &instance, instance.Bounds, transform, localBounds, MAX_uint32, true, 0.5f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Foliage::DrawFoliageJob(int32 i)
|
|
{
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(Graphics);
|
|
const FoliageType& type = FoliageTypes[i];
|
|
if (type._canDraw)
|
|
{
|
|
DrawCallsList drawCallsLists[MODEL_MAX_LODS];
|
|
for (RenderContext& renderContext : _renderContextBatch->Contexts)
|
|
{
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
|
DrawType(renderContext, type, drawCallsLists);
|
|
#else
|
|
Mesh::DrawInfo draw;
|
|
draw.Flags = GetStaticFlags();
|
|
draw.DrawModes = (DrawPass)(DrawPass::Default & renderContext.View.Pass);
|
|
draw.LODBias = 0;
|
|
draw.ForcedLOD = -1;
|
|
draw.VertexColors = nullptr;
|
|
draw.Deformation = nullptr;
|
|
DrawType(renderContext, type, draw);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING
|
|
void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, DrawCallsList* drawCallsLists)
|
|
#else
|
|
void Foliage::DrawType(RenderContext& renderContext, const FoliageType& type, Mesh::DrawInfo& draw)
|
|
#endif
|
|
{
|
|
if (!type.Root || !FOLIAGE_CAN_DRAW(renderContext, type))
|
|
return;
|
|
const DrawPass typeDrawModes = FOLIAGE_GET_DRAW_MODES(renderContext, type);
|
|
PROFILE_CPU_ASSET(type.Model);
|
|
DrawContext context
|
|
{
|
|
renderContext,
|
|
renderContext.LodProxyView ? *renderContext.LodProxyView : renderContext.View,
|
|
type,
|
|
renderContext.View.Origin,
|
|
Math::Square(Graphics::Shadows::MinObjectPixelSize),
|
|
renderContext.View.ScreenSize.X * renderContext.View.ScreenSize.Y,
|
|
};
|
|
if (context.RenderContext.View.Pass != DrawPass::Depth)
|
|
context.MinObjectPixelSizeSq = 0.0f; // Don't use it in main view
|
|
#if FOLIAGE_USE_DRAW_CALLS_BATCHING
|
|
// Initialize draw calls for foliage type all LODs meshes
|
|
for (int32 lod = 0; lod < type.Model->LODs.Count(); lod++)
|
|
{
|
|
auto& modelLod = type.Model->LODs[lod];
|
|
DrawCallsList& drawCallsList = drawCallsLists[lod];
|
|
const auto& meshes = modelLod.Meshes;
|
|
drawCallsList.Resize(meshes.Count());
|
|
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
|
{
|
|
const auto& mesh = meshes.Get()[meshIndex];
|
|
auto& drawCall = drawCallsList.Get()[meshIndex];
|
|
drawCall.Material = nullptr; // DrawInstance skips draw calls from meshes with unset material
|
|
|
|
// Check entry visibility
|
|
const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()];
|
|
if (!entry.Visible || !mesh.IsInitialized())
|
|
continue;
|
|
const MaterialSlot& slot = type.Model->MaterialSlots[mesh.GetMaterialSlotIndex()];
|
|
|
|
// Select material
|
|
MaterialBase* material;
|
|
if (entry.Material && entry.Material->IsLoaded())
|
|
material = entry.Material;
|
|
else if (slot.Material && slot.Material->IsLoaded())
|
|
material = slot.Material;
|
|
else
|
|
material = GPUDevice::Instance->GetDefaultMaterial();
|
|
if (!material || !material->IsSurface())
|
|
continue;
|
|
|
|
// Select draw modes
|
|
const auto shadowsMode = entry.ShadowsMode & slot.ShadowsMode;
|
|
const auto drawModes = typeDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode) & material->GetDrawModes();
|
|
if (drawModes == DrawPass::None)
|
|
continue;
|
|
|
|
drawCall.Material = material;
|
|
drawCall.Surface.GeometrySize = mesh.GetBox().GetSize();
|
|
}
|
|
}
|
|
|
|
// Draw instances of the foliage type
|
|
BatchedDrawCalls result(&renderContext.List->Memory);
|
|
DrawCluster(context, type.Root, drawCallsLists, result);
|
|
|
|
// Submit draw calls with valid instances added
|
|
for (auto& e : result)
|
|
{
|
|
auto& batch = e.Value;
|
|
if (batch.Instances.IsEmpty())
|
|
continue;
|
|
const auto& mesh = *e.Key.Geo;
|
|
const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()];
|
|
const MaterialSlot& slot = type.Model->MaterialSlots[mesh.GetMaterialSlotIndex()];
|
|
const auto shadowsMode = entry.ShadowsMode & slot.ShadowsMode;
|
|
const auto drawModes = typeDrawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode) & batch.DrawCall.Material->GetDrawModes();
|
|
|
|
// Setup draw call
|
|
mesh.GetDrawCallGeometry(batch.DrawCall);
|
|
batch.DrawCall.InstanceCount = 1;
|
|
auto& firstInstance = batch.Instances[0];
|
|
firstInstance.Load(batch.DrawCall);
|
|
#if USE_EDITOR
|
|
if (renderContext.View.Mode == ViewMode::LightmapUVsDensity)
|
|
batch.DrawCall.Surface.LODDitherFactor = type.ScaleInLightmap; // See LightmapUVsDensityMaterialShader
|
|
#endif
|
|
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::Forward))
|
|
{
|
|
// Transparency requires sorting by depth so convert back the batched draw call into normal draw calls (RenderList impl will handle this)
|
|
DrawCall drawCall = batch.DrawCall;
|
|
for (int32 j = 0; j < batch.Instances.Count(); j++)
|
|
{
|
|
auto& instance = batch.Instances[j];
|
|
instance.Load(drawCall);
|
|
const int32 drawCallIndex = renderContext.List->DrawCalls.Add(drawCall);
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Forward].Indices.Add(drawCallIndex);
|
|
}
|
|
}
|
|
|
|
// Add draw call batch
|
|
const int32 batchIndex = renderContext.List->BatchedDrawCalls.Add(MoveTemp(batch));
|
|
|
|
// Add draw call to proper draw lists
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::Depth))
|
|
{
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Depth].PreBatchedDrawCalls.Add(batchIndex);
|
|
}
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::GBuffer))
|
|
{
|
|
if (entry.ReceiveDecals)
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].PreBatchedDrawCalls.Add(batchIndex);
|
|
else
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].PreBatchedDrawCalls.Add(batchIndex);
|
|
}
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::Distortion))
|
|
{
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Distortion].PreBatchedDrawCalls.Add(batchIndex);
|
|
}
|
|
if (EnumHasAnyFlags(drawModes, DrawPass::MotionVectors) && (_staticFlags & StaticFlags::Transform) == StaticFlags::None)
|
|
{
|
|
renderContext.List->DrawCallsLists[(int32)DrawCallsListType::MotionVectors].PreBatchedDrawCalls.Add(batchIndex);
|
|
}
|
|
}
|
|
#else
|
|
DrawCluster(context, type.Root, draw);
|
|
#endif
|
|
}
|
|
|
|
void Foliage::InitType(const RenderView& view, FoliageType& type)
|
|
{
|
|
const DrawPass drawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
|
|
type._canDraw = type.IsReady() && drawModes != DrawPass::None && type.Model && type.Model->CanBeRendered();
|
|
bool drawModesDirty = false;
|
|
for (int32 j = 0; j < type.Entries.Count(); j++)
|
|
{
|
|
auto& e = type.Entries[j];
|
|
e.ReceiveDecals = type.ReceiveDecals != 0;
|
|
e.ShadowsMode = type.ShadowsMode;
|
|
if (type._drawModesDirty)
|
|
{
|
|
type._drawModesDirty = 0;
|
|
drawModesDirty = true;
|
|
}
|
|
}
|
|
if (drawModesDirty)
|
|
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::DrawModes);
|
|
}
|
|
|
|
int32 Foliage::GetInstancesCount() const
|
|
{
|
|
return Instances.Count();
|
|
}
|
|
|
|
FoliageInstance Foliage::GetInstance(int32 index) const
|
|
{
|
|
return Instances[index];
|
|
}
|
|
|
|
int32 Foliage::GetFoliageTypesCount() const
|
|
{
|
|
return FoliageTypes.Count();
|
|
}
|
|
|
|
FoliageType* Foliage::GetFoliageType(int32 index)
|
|
{
|
|
CHECK_RETURN(index >= 0 && index < FoliageTypes.Count(), nullptr)
|
|
return &FoliageTypes[index];
|
|
}
|
|
|
|
void Foliage::AddFoliageType(Model* model)
|
|
{
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(LevelFoliage);
|
|
|
|
// Ensure to have unique model
|
|
CHECK(model);
|
|
for (int32 i = 0; i < FoliageTypes.Count(); i++)
|
|
{
|
|
if (FoliageTypes[i].Model == model)
|
|
{
|
|
LOG(Error, "The given model is already used by other foliage type.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add
|
|
_disableFoliageTypeEvents = true;
|
|
auto& item = FoliageTypes.AddOne();
|
|
_disableFoliageTypeEvents = false;
|
|
|
|
// Setup
|
|
item.Foliage = this;
|
|
item.Index = FoliageTypes.Count() - 1;
|
|
item.Model = model;
|
|
}
|
|
|
|
void Foliage::RemoveFoliageType(int32 index)
|
|
{
|
|
PROFILE_CPU();
|
|
|
|
// Remove instances using this foliage type
|
|
if (FoliageTypes.Count() != 1)
|
|
{
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type == index)
|
|
{
|
|
Instances.Remove(i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
// Update all instances using foliage types with higher index to point into a valid type
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type > index)
|
|
i->Type--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Instances.Clear();
|
|
}
|
|
|
|
// Remove foliage instance type
|
|
for (int32 i = index + 1; i < FoliageTypes.Count(); i++)
|
|
{
|
|
FoliageTypes[i].Index--;
|
|
}
|
|
auto& item = FoliageTypes[index];
|
|
item.Model = nullptr;
|
|
item.Entries.Release();
|
|
FoliageTypes.RemoveAtKeepOrder(index);
|
|
|
|
RebuildClusters();
|
|
}
|
|
|
|
int32 Foliage::GetFoliageTypeInstancesCount(int32 index) const
|
|
{
|
|
PROFILE_CPU();
|
|
int32 result = 0;
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type == index)
|
|
result++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Foliage::AddInstance(const FoliageInstance& instance)
|
|
{
|
|
PROFILE_MEM(LevelFoliage);
|
|
ASSERT(instance.Type >= 0 && instance.Type < FoliageTypes.Count());
|
|
auto type = &FoliageTypes[instance.Type];
|
|
|
|
// Add instance
|
|
auto data = Instances.Add(instance);
|
|
data->Bounds = BoundingSphere::Empty;
|
|
data->Random = Random::Rand();
|
|
data->CullDistance = type->CullDistance + type->CullDistanceRandomRange * data->Random;
|
|
|
|
// Validate foliage type model
|
|
if (!type->IsReady())
|
|
return;
|
|
|
|
// Update bounds
|
|
Vector3 corners[8];
|
|
auto& meshes = type->Model->LODs[0].Meshes;
|
|
const Transform transform = _transform.LocalToWorld(data->Transform);
|
|
for (int32 j = 0; j < meshes.Count(); j++)
|
|
{
|
|
meshes[j].GetBox().GetCorners(corners);
|
|
|
|
for (int32 k = 0; k < 8; k++)
|
|
{
|
|
Vector3::Transform(corners[k], transform, corners[k]);
|
|
}
|
|
BoundingSphere meshBounds;
|
|
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
|
ASSERT(meshBounds.Radius > ZeroTolerance);
|
|
|
|
BoundingSphere::Merge(data->Bounds, meshBounds, data->Bounds);
|
|
}
|
|
data->Bounds.Radius += ZeroTolerance;
|
|
}
|
|
|
|
void Foliage::RemoveInstance(ChunkedArray<FoliageInstance, FOLIAGE_INSTANCE_CHUNKS_SIZE>::Iterator i)
|
|
{
|
|
Instances.Remove(i);
|
|
}
|
|
|
|
void Foliage::SetInstanceTransform(int32 index, const Transform& value)
|
|
{
|
|
auto& instance = Instances[index];
|
|
auto type = &FoliageTypes[instance.Type];
|
|
|
|
// Change transform
|
|
instance.Transform = value;
|
|
|
|
// Update bounds
|
|
instance.Bounds = BoundingSphere::Empty;
|
|
if (!type->IsReady())
|
|
return;
|
|
Vector3 corners[8];
|
|
auto& meshes = type->Model->LODs[0].Meshes;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
for (int32 j = 0; j < meshes.Count(); j++)
|
|
{
|
|
meshes[j].GetBox().GetCorners(corners);
|
|
|
|
for (int32 k = 0; k < 8; k++)
|
|
{
|
|
Vector3::Transform(corners[k], transform, corners[k]);
|
|
}
|
|
BoundingSphere meshBounds;
|
|
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
|
ASSERT(meshBounds.Radius > ZeroTolerance);
|
|
|
|
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
|
}
|
|
instance.Bounds.Radius += ZeroTolerance;
|
|
}
|
|
|
|
void Foliage::OnFoliageTypeModelLoaded(int32 index)
|
|
{
|
|
if (_disableFoliageTypeEvents)
|
|
return;
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(LevelFoliage);
|
|
auto& type = FoliageTypes[index];
|
|
ASSERT(type.IsReady());
|
|
|
|
// Update bounds for instances using this type
|
|
bool hasAnyInstance = false;
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
BoundingBox totalBoundsType, box;
|
|
#endif
|
|
{
|
|
PROFILE_CPU_NAMED("Update Bounds");
|
|
Vector3 corners[8];
|
|
auto& meshes = type.Model->LODs[0].Meshes;
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
if (instance.Type != index)
|
|
continue;
|
|
instance.Bounds = BoundingSphere::Empty;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
|
|
// Include all meshes
|
|
for (int32 j = 0; j < meshes.Count(); j++)
|
|
{
|
|
// TODO: cache bounds for all model meshes and reuse later
|
|
meshes[j].GetBox().GetCorners(corners);
|
|
|
|
// TODO: use SIMD
|
|
for (int32 k = 0; k < 8; k++)
|
|
{
|
|
Vector3::Transform(corners[k], transform, corners[k]);
|
|
}
|
|
BoundingSphere meshBounds;
|
|
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
|
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
|
}
|
|
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
// TODO: use SIMD
|
|
BoundingBox::FromSphere(instance.Bounds, box);
|
|
if (hasAnyInstance)
|
|
BoundingBox::Merge(totalBoundsType, box, totalBoundsType);
|
|
else
|
|
totalBoundsType = box;
|
|
#endif
|
|
hasAnyInstance = true;
|
|
}
|
|
}
|
|
if (!hasAnyInstance)
|
|
return;
|
|
|
|
// Refresh quad-tree
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
RebuildClusters();
|
|
#else
|
|
{
|
|
PROFILE_CPU_NAMED("Setup");
|
|
|
|
// Setup first and topmost cluster
|
|
type.Clusters.Resize(1);
|
|
type.Root = &type.Clusters[0];
|
|
type.Root->Init(totalBoundsType);
|
|
|
|
// Update bounds of the foliage
|
|
_box = totalBoundsType;
|
|
for (auto& e : FoliageTypes)
|
|
{
|
|
if (e.Index != index && e.Root)
|
|
BoundingBox::Merge(_box, e.Root->Bounds, _box);
|
|
}
|
|
BoundingSphere::FromBox(_box, _sphere);
|
|
if (_sceneRenderingKey != -1)
|
|
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds);
|
|
}
|
|
{
|
|
PROFILE_CPU_NAMED("Create Clusters");
|
|
|
|
// Create clusters for foliage type quad tree
|
|
const float globalDensityScale = GetGlobalDensityScale();
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
const float densityScale = type.UseDensityScaling ? globalDensityScale * type.DensityScalingScale : 1.0f;
|
|
if (instance.Type == index && instance.Random < densityScale)
|
|
{
|
|
AddToCluster(type.Clusters, type.Root, instance);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
PROFILE_CPU_NAMED("Update Cache");
|
|
type.Root->UpdateTotalBoundsAndCullDistance();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Foliage::RebuildClusters()
|
|
{
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(LevelFoliage);
|
|
|
|
// Faster path if foliage is empty or no types is ready
|
|
bool anyTypeReady = false;
|
|
for (auto& type : FoliageTypes)
|
|
anyTypeReady |= type.IsReady();
|
|
if (!anyTypeReady || Instances.IsEmpty())
|
|
{
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
Root = nullptr;
|
|
Clusters.Clear();
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
type.Root = nullptr;
|
|
type.Clusters.Clear();
|
|
}
|
|
#endif
|
|
_box = BoundingBox(_transform.Translation, _transform.Translation);
|
|
_sphere = BoundingSphere(_transform.Translation, 0.0f);
|
|
if (_sceneRenderingKey != -1)
|
|
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey);
|
|
return;
|
|
}
|
|
|
|
// Clear clusters and initialize root
|
|
{
|
|
PROFILE_CPU_NAMED("Init Root");
|
|
|
|
BoundingBox totalBounds, box;
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
{
|
|
// Calculate total bounds of all instances
|
|
auto i = Instances.Begin();
|
|
for (; i.IsNotEnd(); ++i)
|
|
{
|
|
if (!FoliageTypes[i->Type].IsReady())
|
|
continue;
|
|
BoundingBox::FromSphere(i->Bounds, box);
|
|
totalBounds = box;
|
|
break;
|
|
}
|
|
++i;
|
|
// TODO: inline code and use SIMD
|
|
for (; i.IsNotEnd(); ++i)
|
|
{
|
|
if (!FoliageTypes[i->Type].IsReady())
|
|
continue;
|
|
BoundingBox::FromSphere(i->Bounds, box);
|
|
BoundingBox::Merge(totalBounds, box, totalBounds);
|
|
}
|
|
}
|
|
|
|
// Setup first and topmost cluster
|
|
Clusters.Resize(1);
|
|
Root = &Clusters[0];
|
|
Root->Init(totalBounds);
|
|
#else
|
|
bool hasTotalBounds = false;
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
if (!type.IsReady())
|
|
{
|
|
type.Root = nullptr;
|
|
type.Clusters.Clear();
|
|
continue;
|
|
}
|
|
|
|
// Calculate total bounds of all instances of this type
|
|
BoundingBox totalBoundsType;
|
|
auto i = Instances.Begin();
|
|
for (; i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type == type.Index)
|
|
{
|
|
BoundingBox::FromSphere(i->Bounds, box);
|
|
totalBoundsType = box;
|
|
break;
|
|
}
|
|
}
|
|
++i;
|
|
// TODO: inline code and use SIMD
|
|
for (; i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type == type.Index)
|
|
{
|
|
BoundingBox::FromSphere(i->Bounds, box);
|
|
BoundingBox::Merge(totalBoundsType, box, totalBoundsType);
|
|
}
|
|
}
|
|
|
|
// Setup first and topmost cluster
|
|
type.Clusters.Resize(1);
|
|
type.Root = &type.Clusters[0];
|
|
type.Root->Init(totalBoundsType);
|
|
if (hasTotalBounds)
|
|
{
|
|
BoundingBox::Merge(totalBounds, totalBoundsType, totalBounds);
|
|
}
|
|
else
|
|
{
|
|
totalBounds = totalBoundsType;
|
|
hasTotalBounds = true;
|
|
}
|
|
}
|
|
ASSERT(hasTotalBounds);
|
|
#endif
|
|
ASSERT(!totalBounds.Minimum.IsNanOrInfinity() && !totalBounds.Maximum.IsNanOrInfinity());
|
|
_box = totalBounds;
|
|
BoundingSphere::FromBox(_box, _sphere);
|
|
if (_sceneRenderingKey != -1)
|
|
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Bounds);
|
|
}
|
|
|
|
// Insert all instances to the clusters
|
|
{
|
|
PROFILE_CPU_NAMED("Create Clusters");
|
|
const float globalDensityScale = GetGlobalDensityScale();
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto& type = FoliageTypes[instance.Type];
|
|
const float densityScale = type.UseDensityScaling ? globalDensityScale * type.DensityScalingScale : 1.0f;
|
|
|
|
if (type.IsReady() && instance.Random < densityScale)
|
|
{
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
AddToCluster(Clusters, Root, instance);
|
|
#else
|
|
AddToCluster(type.Clusters, type.Root, instance);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
if (Root)
|
|
{
|
|
PROFILE_CPU_NAMED("Update Cache");
|
|
Root->UpdateTotalBoundsAndCullDistance();
|
|
}
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
if (type.Root)
|
|
{
|
|
PROFILE_CPU_NAMED("Update Cache");
|
|
type.Root->UpdateTotalBoundsAndCullDistance();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Foliage::UpdateCullDistance()
|
|
{
|
|
PROFILE_CPU();
|
|
|
|
{
|
|
PROFILE_CPU_NAMED("Instances");
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto& type = FoliageTypes[instance.Type];
|
|
instance.CullDistance = type.CullDistance + type.CullDistanceRandomRange * instance.Random;
|
|
}
|
|
}
|
|
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
if (Root)
|
|
{
|
|
PROFILE_CPU_NAMED("Clusters");
|
|
Root->UpdateCullDistance();
|
|
}
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
if (type.Root)
|
|
{
|
|
PROFILE_CPU_NAMED("Clusters");
|
|
type.Root->UpdateCullDistance();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Foliage::RemoveAllInstances()
|
|
{
|
|
Instances.Clear();
|
|
RebuildClusters();
|
|
}
|
|
|
|
void Foliage::RemoveLightmap()
|
|
{
|
|
for (auto& e : Instances)
|
|
e.RemoveLightmap();
|
|
}
|
|
|
|
static float GlobalDensityScale = 1.0f;
|
|
|
|
float Foliage::GetGlobalDensityScale()
|
|
{
|
|
return GlobalDensityScale;
|
|
}
|
|
|
|
bool UpdateFoliageDensityScaling(Actor* actor)
|
|
{
|
|
if (auto* foliage = dynamic_cast<Foliage*>(actor))
|
|
{
|
|
foliage->RebuildClusters();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Foliage::SetGlobalDensityScale(float value)
|
|
{
|
|
value = Math::Saturate(value);
|
|
if (Math::NearEqual(value, GlobalDensityScale))
|
|
return;
|
|
|
|
PROFILE_CPU();
|
|
|
|
GlobalDensityScale = value;
|
|
|
|
Function<bool(Actor*)> f(UpdateFoliageDensityScaling);
|
|
SceneQuery::TreeExecute(f);
|
|
}
|
|
|
|
bool Foliage::Intersects(const Ray& ray, Real& distance, Vector3& normal, int32& instanceIndex)
|
|
{
|
|
PROFILE_CPU();
|
|
|
|
instanceIndex = -1;
|
|
normal = Vector3::Up;
|
|
distance = MAX_Real;
|
|
|
|
FoliageInstance* instance = nullptr;
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
if (Root)
|
|
Root->Intersects(this, ray, distance, normal, instance);
|
|
#else
|
|
Real tmpDistance;
|
|
Vector3 tmpNormal;
|
|
FoliageInstance* tmpInstance;
|
|
for (const auto& type : FoliageTypes)
|
|
{
|
|
if (type.Root && type.Root->Intersects(this, ray, tmpDistance, tmpNormal, tmpInstance) && tmpDistance < distance)
|
|
{
|
|
distance = tmpDistance;
|
|
normal = tmpNormal;
|
|
instance = tmpInstance;
|
|
}
|
|
}
|
|
#endif
|
|
if (instance != nullptr)
|
|
{
|
|
int32 j = 0;
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
if (&*i == instance)
|
|
{
|
|
instanceIndex = j;
|
|
return true;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Foliage::Draw(RenderContext& renderContext)
|
|
{
|
|
if (Instances.IsEmpty())
|
|
return;
|
|
PROFILE_CPU();
|
|
const RenderView& view = renderContext.View;
|
|
|
|
// Cache data per foliage instance type
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
InitType(renderContext.View, type);
|
|
}
|
|
|
|
if (renderContext.View.Pass == DrawPass::GlobalSDF)
|
|
{
|
|
auto globalSDF = GlobalSignDistanceFieldPass::Instance();
|
|
BoundingBox globalSDFBounds;
|
|
globalSDF->GetCullingData(globalSDFBounds);
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto& type = FoliageTypes[instance.Type];
|
|
if (type._canDraw && CollisionsHelper::BoxIntersectsSphere(globalSDFBounds, instance.Bounds))
|
|
{
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
BoundingBox bounds;
|
|
BoundingBox::FromSphere(instance.Bounds, bounds);
|
|
globalSDF->RasterizeModelSDF(this, type.Model->SDF, transform, bounds);
|
|
}
|
|
}
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
if (type.Root && FOLIAGE_CAN_DRAW(renderContext, type))
|
|
DrawClusterGlobalSDF(globalSDF, globalSDFBounds, type.Root, type);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas)
|
|
{
|
|
// Draw foliage instances into Global Surface Atlas
|
|
auto globalSA = GlobalSurfaceAtlasPass::Instance();
|
|
Vector4 cullingPosDistance;
|
|
globalSA->GetCullingData(cullingPosDistance);
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto& type = FoliageTypes[instance.Type];
|
|
if (type._canDraw && CollisionsHelper::DistanceSpherePoint(instance.Bounds, Vector3(cullingPosDistance)) < cullingPosDistance.W)
|
|
{
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
BoundingBox localBounds = type.Model->LODs.Last().GetBox();
|
|
globalSA->RasterizeActor(this, &instance, instance.Bounds, transform, localBounds, MAX_uint32, true, 0.5f);
|
|
}
|
|
}
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
if (type.Root && FOLIAGE_CAN_DRAW(renderContext, type))
|
|
{
|
|
BoundingBox localBounds = type.Model->LODs.Last().GetBox();
|
|
DrawClusterGlobalSA(globalSA, cullingPosDistance, type.Root, type, localBounds);
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
if (EnumHasAnyFlags(renderContext.View.Pass, DrawPass::GlobalSurfaceAtlas))
|
|
{
|
|
// Draw single foliage instance projection into Global Surface Atlas
|
|
auto& instance = *(FoliageInstance*)GlobalSurfaceAtlasPass::Instance()->GetCurrentActorObject();
|
|
auto& type = FoliageTypes[instance.Type];
|
|
InitType(renderContext.View, type);
|
|
Matrix world;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
renderContext.View.GetWorldMatrix(transform, world);
|
|
Mesh::DrawInfo draw;
|
|
draw.Flags = GetStaticFlags();
|
|
draw.LODBias = 0;
|
|
draw.ForcedLOD = -1;
|
|
draw.SortOrder = 0;
|
|
draw.VertexColors = nullptr;
|
|
draw.Lightmap = _scene ? _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex) : nullptr;
|
|
draw.LightmapUVs = &instance.Lightmap.UVsArea;
|
|
draw.Buffer = &type.Entries;
|
|
draw.World = &world;
|
|
draw.DrawState = &instance.DrawState;
|
|
draw.Deformation = nullptr;
|
|
draw.Bounds = instance.Bounds;
|
|
draw.PerInstanceRandom = instance.Random;
|
|
draw.DrawModes = type._drawModes & view.Pass & view.GetShadowsDrawPassMask(type.ShadowsMode);
|
|
draw.SetStencilValue(_layer);
|
|
type.Model->Draw(renderContext, draw);
|
|
return;
|
|
}
|
|
|
|
// Draw visible clusters
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE || !FOLIAGE_USE_DRAW_CALLS_BATCHING
|
|
Mesh::DrawInfo draw;
|
|
draw.Flags = GetStaticFlags();
|
|
draw.DrawModes = (DrawPass)(DrawPass::Default & view.Pass);
|
|
draw.LODBias = 0;
|
|
draw.ForcedLOD = -1;
|
|
draw.VertexColors = nullptr;
|
|
draw.Deformation = nullptr;
|
|
#else
|
|
DrawCallsList draw[MODEL_MAX_LODS];
|
|
#endif
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
if (Root)
|
|
DrawCluster(renderContext, Root, draw);
|
|
#else
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
DrawType(renderContext, type, draw);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Foliage::Draw(RenderContextBatch& renderContextBatch)
|
|
{
|
|
if (Instances.IsEmpty())
|
|
return;
|
|
|
|
#if !FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
// Run async job for each foliage type
|
|
const RenderView& view = renderContextBatch.GetMainContext().View;
|
|
if (EnumHasAnyFlags(view.Pass, DrawPass::GBuffer) && !(view.Pass & (DrawPass::GlobalSDF | DrawPass::GlobalSurfaceAtlas)) && renderContextBatch.EnableAsync)
|
|
{
|
|
// Cache data per foliage instance type
|
|
for (FoliageType& type : FoliageTypes)
|
|
InitType(view, type);
|
|
|
|
// Run async job for each foliage type
|
|
_renderContextBatch = &renderContextBatch;
|
|
Function<void(int32)> func;
|
|
func.Bind<Foliage, &Foliage::DrawFoliageJob>(this);
|
|
const int64 waitLabel = JobSystem::Dispatch(func, FoliageTypes.Count());
|
|
renderContextBatch.WaitLabels.Add(waitLabel);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Fallback to default rendering
|
|
Actor::Draw(renderContextBatch);
|
|
}
|
|
|
|
bool Foliage::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
|
|
{
|
|
int32 instanceIndex;
|
|
return Intersects(ray, distance, normal, instanceIndex);
|
|
}
|
|
|
|
// Layout for encoded instance data (serialized as Base64 string)
|
|
|
|
static constexpr int32 GetInstanceBase64Size(int32 size)
|
|
{
|
|
// 4 * (n / 3) -> align up to 4
|
|
return (size * 4 / 3 + 3) & ~3;
|
|
}
|
|
|
|
// [Deprecated on 30.11.2019, expires on 30.11.2021]
|
|
struct InstanceEncoded1
|
|
{
|
|
int32 Type;
|
|
float Random;
|
|
Float3 Translation;
|
|
Quaternion Orientation;
|
|
Float3 Scale;
|
|
|
|
static constexpr int32 Size = 48;
|
|
static constexpr int32 Base64Size = GetInstanceBase64Size(Size);
|
|
};
|
|
|
|
// [Deprecated in v1.13]
|
|
struct InstanceEncoded2
|
|
{
|
|
int32 Type;
|
|
float Random;
|
|
Float3 Translation;
|
|
Quaternion Orientation;
|
|
Float3 Scale;
|
|
LightmapEntry Lightmap;
|
|
|
|
static const int32 Size = 68;
|
|
static const int32 Base64Size = GetInstanceBase64Size(Size);
|
|
};
|
|
|
|
// [Deprecated in v1.13]
|
|
typedef InstanceEncoded2 InstanceEncoded;
|
|
static_assert(InstanceEncoded::Size == sizeof(InstanceEncoded), "Please update base64 buffer size to match the encoded instance buffer.");
|
|
static_assert(InstanceEncoded::Base64Size == GetInstanceBase64Size(sizeof(InstanceEncoded)), "Please update base64 buffer size to match the encoded instance buffer.");
|
|
|
|
struct FoliageChunkMeta
|
|
{
|
|
uint16 InstanceCounter;
|
|
};
|
|
|
|
struct FoliageInstanceData
|
|
{
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
uint16 Type : 14; // Max 16,384 foliage types, which is more than enough for any use case
|
|
uint16 OrientationNegativeW : 1;
|
|
uint16 HasLightmap : 1;
|
|
};
|
|
uint16 Packed;
|
|
};
|
|
int16 OrientationX, OrientationY, OrientationZ;
|
|
float Random;
|
|
Float3 Position, Scale;
|
|
};
|
|
|
|
struct FoliageWriter
|
|
{
|
|
static constexpr int32 InstanceSizeApprox = sizeof(FoliageInstanceData);
|
|
static constexpr int32 InstancesPerChunk = 64;
|
|
typedef uint16 CompressedSize;
|
|
int32 InstanceCounter = 0;
|
|
Foliage::SerializeStream& Stream;
|
|
MemoryWriteStream Memory;
|
|
Array<byte> Compressed;
|
|
Array<char> Base64;
|
|
|
|
FoliageWriter(Foliage::SerializeStream& stream)
|
|
: Stream(stream)
|
|
, Memory(Math::RoundUpToPowerOf2(InstancesPerChunk* InstanceSizeApprox))
|
|
{
|
|
Memory.Move<FoliageChunkMeta>();
|
|
}
|
|
|
|
void Write(const FoliageInstance& instance)
|
|
{
|
|
// Fixed-size data
|
|
FoliageInstanceData data;
|
|
data.Type = (uint16)instance.Type;
|
|
data.HasLightmap = instance.HasLightmap();
|
|
data.Random = instance.Random;
|
|
data.Position = (Float3)instance.Transform.Translation;
|
|
data.Scale = instance.Transform.Scale;
|
|
data.OrientationX = (int16)(instance.Transform.Orientation.X * MAX_int16);
|
|
data.OrientationY = (int16)(instance.Transform.Orientation.Y * MAX_int16);
|
|
data.OrientationZ = (int16)(instance.Transform.Orientation.Z * MAX_int16);
|
|
data.OrientationNegativeW = instance.Transform.Orientation.W < 0;
|
|
Memory.Write(data);
|
|
|
|
// Lightmap data (if used)
|
|
if (data.HasLightmap)
|
|
Memory.Write(instance.Lightmap);
|
|
|
|
// Move to the next instance
|
|
InstanceCounter++;
|
|
if (InstanceCounter >= InstancesPerChunk)
|
|
Flush();
|
|
}
|
|
|
|
void Flush()
|
|
{
|
|
if (InstanceCounter == 0)
|
|
return;
|
|
|
|
// Store chunk size metadata in the beginning
|
|
static_assert(InstancesPerChunk * InstanceSizeApprox * 2 < MAX_uint16, "Too much data for potential chunk storage.");
|
|
auto meta = (FoliageChunkMeta*)Memory.GetHandle();
|
|
meta->InstanceCounter = (uint16)InstanceCounter;
|
|
InstanceCounter = 0;
|
|
|
|
// Compress with LZ4
|
|
const int32 srcSize = (int32)Memory.GetPosition();
|
|
const int32 maxSize = LZ4_compressBound(srcSize);
|
|
Compressed.Resize(maxSize + sizeof(CompressedSize)); // Place decompressed size in the beginning
|
|
const int32 dstSize = LZ4_compress_default((const char*)Memory.GetHandle(), (char*)Compressed.Get() + sizeof(CompressedSize), srcSize, maxSize);
|
|
Compressed.Resize(dstSize + sizeof(CompressedSize));
|
|
*(CompressedSize*)Compressed.Get() = (CompressedSize)srcSize;
|
|
|
|
// Convert raw bytes into Base64 string and write to Json stream
|
|
Encryption::Base64Encode(Compressed.Get(), Compressed.Count(), Base64);
|
|
Stream.String(Base64.Get(), Base64.Count());
|
|
|
|
// Reset memory writer
|
|
Memory.Reset();
|
|
Memory.Move<FoliageChunkMeta>();
|
|
}
|
|
};
|
|
|
|
struct FoliageReader
|
|
{
|
|
typedef FoliageWriter::CompressedSize CompressedSize;
|
|
Array<byte> Base64;
|
|
Array<byte> Decompressed;
|
|
|
|
FoliageReader()
|
|
{
|
|
}
|
|
|
|
void Read(Foliage& foliage, int32& instanceIndex, StringAnsiView base64)
|
|
{
|
|
// Convert Base64 string into raw bytes
|
|
Encryption::Base64Decode(base64.Get(), base64.Length(), Base64);
|
|
|
|
// Decompress with LZ4
|
|
auto originalSize = *(CompressedSize*)Base64.Get();
|
|
Decompressed.Resize(originalSize);
|
|
const int32 res = LZ4_decompress_safe((const char*)Base64.Get() + sizeof(CompressedSize), (char*)Decompressed.Get(), Base64.Count() - sizeof(CompressedSize), originalSize);
|
|
ASSERT(res >= 0);
|
|
Decompressed.Resize(res);
|
|
|
|
// Read instances from memory
|
|
MemoryReadStream memory(Decompressed);
|
|
FoliageChunkMeta meta;
|
|
memory.Read(meta);
|
|
FoliageInstanceData data;
|
|
ASSERT(meta.InstanceCounter <= FoliageWriter::InstancesPerChunk);
|
|
for (int32 i = 0; i < meta.InstanceCounter; i++)
|
|
{
|
|
memory.Read(data);
|
|
|
|
auto& instance = foliage.Instances[instanceIndex++];
|
|
instance.Type = data.Type;
|
|
instance.Random = data.Random;
|
|
instance.Transform.Translation = data.Position;
|
|
instance.Transform.Scale = data.Scale;
|
|
Quaternion q;
|
|
q.X = (float)data.OrientationX * (1.0f / (float)MAX_int16);
|
|
q.Y = (float)data.OrientationY * (1.0f / (float)MAX_int16);
|
|
q.Z = (float)data.OrientationZ * (1.0f / (float)MAX_int16);
|
|
q.W = Math::Sqrt(Math::Max(1.0f - q.X * q.X - q.Y * q.Y - q.Z * q.Z, 0.0f));
|
|
if (data.OrientationNegativeW)
|
|
q.W *= -1;
|
|
q.Normalize();
|
|
instance.Transform.Orientation = q;
|
|
if (data.HasLightmap)
|
|
memory.Read(instance.Lightmap);
|
|
}
|
|
}
|
|
};
|
|
|
|
void Foliage::Serialize(SerializeStream& stream, const void* otherObj)
|
|
{
|
|
// Base
|
|
Actor::Serialize(stream, otherObj);
|
|
|
|
SERIALIZE_GET_OTHER_OBJ(Foliage);
|
|
if (FoliageTypes.IsEmpty())
|
|
return;
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(LevelFoliage);
|
|
|
|
stream.JKEY("Foliage");
|
|
stream.StartArray();
|
|
for (auto& type : FoliageTypes)
|
|
{
|
|
stream.StartObject();
|
|
type.Serialize(stream, nullptr);
|
|
stream.EndObject();
|
|
}
|
|
stream.EndArray();
|
|
|
|
stream.JKEY("Instances");
|
|
stream.StartArray();
|
|
|
|
// Put some metadata
|
|
stream.Int(Instances.Count());
|
|
|
|
// Write instances in chunks
|
|
FoliageWriter writer(stream);
|
|
// TODO: run this in parallel for better performance on large foliage data (keep the order of instances in the stream, run job for each 64 instances, let one job keep writing results back to the stream to avoid too much memory usage)
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
writer.Write(*i);
|
|
}
|
|
writer.Flush();
|
|
|
|
stream.EndArray();
|
|
}
|
|
|
|
void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
|
{
|
|
// Base
|
|
Actor::Deserialize(stream, modifier);
|
|
|
|
PROFILE_CPU();
|
|
PROFILE_MEM(LevelFoliage);
|
|
|
|
// Clear
|
|
#if FOLIAGE_USE_SINGLE_QUAD_TREE
|
|
Root = nullptr;
|
|
Clusters.Release();
|
|
#endif
|
|
Instances.Release();
|
|
FoliageTypes.Resize(0, false);
|
|
|
|
// Deserialize foliage types
|
|
int32 foliageTypesCount = 0;
|
|
const auto& foliageTypesMember = stream.FindMember("Foliage");
|
|
if (foliageTypesMember != stream.MemberEnd() && foliageTypesMember->value.IsArray())
|
|
foliageTypesCount = foliageTypesMember->value.Size();
|
|
if (foliageTypesCount)
|
|
{
|
|
PROFILE_CPU_NAMED("Types");
|
|
const DeserializeStream& items = foliageTypesMember->value;;
|
|
FoliageTypes.Resize(foliageTypesCount, false);
|
|
for (int32 i = 0; i < foliageTypesCount; i++)
|
|
{
|
|
auto& type = FoliageTypes[i];
|
|
type.Foliage = this;
|
|
type.Index = i;
|
|
type.Deserialize((DeserializeStream&)items[i], modifier);
|
|
}
|
|
}
|
|
|
|
// Skip if no foliage
|
|
if (FoliageTypes.IsEmpty())
|
|
return;
|
|
|
|
// Deserialize foliage instances
|
|
const auto& foliageInstancesMember = stream.FindMember("Instances");
|
|
if (foliageInstancesMember == stream.MemberEnd() || !foliageInstancesMember->value.IsArray())
|
|
return;
|
|
if (modifier->EngineBuild >= 7001)
|
|
{
|
|
const DeserializeStream& items = foliageInstancesMember->value;
|
|
int32 chunksCount = (int32)items.Size() - 1;
|
|
if (chunksCount <= 0)
|
|
return;
|
|
int32 foliageInstancesCount = items[0].GetInt();
|
|
Instances.Resize(foliageInstancesCount);
|
|
PROFILE_CPU_NAMED("Instances");
|
|
|
|
FoliageReader reader;
|
|
int32 instanceIndex = 0;
|
|
for (int32 i = 1; i <= chunksCount; i++)
|
|
{
|
|
reader.Read(*this, instanceIndex, items[i].GetStringAnsiView());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// [Deprecated in v1.13]
|
|
MARK_CONTENT_DEPRECATED();
|
|
int32 foliageInstancesCount = foliageInstancesMember->value.Size();
|
|
if (foliageInstancesCount)
|
|
{
|
|
const DeserializeStream& items = foliageInstancesMember->value;
|
|
Instances.Resize(foliageInstancesCount);
|
|
|
|
if (modifier->EngineBuild <= 6189)
|
|
{
|
|
// [Deprecated on 30.11.2019, expires on 30.11.2021]
|
|
MARK_CONTENT_DEPRECATED();
|
|
InstanceEncoded1 enc;
|
|
for (int32 i = 0; i < foliageInstancesCount; i++)
|
|
{
|
|
auto& instance = Instances[i];
|
|
auto& item = items[i];
|
|
|
|
const int32 length = item.GetStringLength();
|
|
if (length != InstanceEncoded1::Base64Size)
|
|
{
|
|
LOG(Warning, "Invalid foliage instance data size.");
|
|
continue;
|
|
}
|
|
Encryption::Base64Decode(item.GetString(), length, (byte*)&enc);
|
|
|
|
instance.Type = enc.Type;
|
|
instance.Random = enc.Random;
|
|
instance.Transform.Translation = enc.Translation;
|
|
instance.Transform.Orientation = enc.Orientation;
|
|
instance.Transform.Scale = enc.Scale;
|
|
instance.Lightmap = LightmapEntry();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InstanceEncoded enc;
|
|
for (int32 i = 0; i < foliageInstancesCount; i++)
|
|
{
|
|
auto& instance = Instances[i];
|
|
auto& item = items[i];
|
|
|
|
const int32 length = item.GetStringLength();
|
|
if (length != InstanceEncoded::Base64Size)
|
|
{
|
|
LOG(Warning, "Invalid foliage instance data size.");
|
|
continue;
|
|
}
|
|
Encryption::Base64Decode(item.GetString(), length, (byte*)&enc);
|
|
|
|
instance.Type = enc.Type;
|
|
instance.Random = enc.Random;
|
|
instance.Transform.Translation = enc.Translation;
|
|
instance.Transform.Orientation = enc.Orientation;
|
|
instance.Transform.Scale = enc.Scale;
|
|
instance.Lightmap = enc.Lightmap;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if BUILD_DEBUG
|
|
// Remove invalid instances
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
if (i->Type < 0 || i->Type >= FoliageTypes.Count())
|
|
{
|
|
LOG(Warning, "Removing invalid foliage instance.");
|
|
Instances.Remove(i);
|
|
--i;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Update cull distance
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto& type = FoliageTypes[instance.Type];
|
|
instance.CullDistance = type.CullDistance + type.CullDistanceRandomRange * instance.Random;
|
|
}
|
|
}
|
|
|
|
void Foliage::OnLayerChanged()
|
|
{
|
|
if (_sceneRenderingKey != -1)
|
|
GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, ISceneRenderingListener::Layer);
|
|
}
|
|
|
|
void Foliage::OnEnable()
|
|
{
|
|
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
|
|
|
|
// Base
|
|
Actor::OnEnable();
|
|
}
|
|
|
|
void Foliage::OnDisable()
|
|
{
|
|
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
|
|
|
|
// Base
|
|
Actor::OnDisable();
|
|
}
|
|
|
|
void Foliage::OnTransformChanged()
|
|
{
|
|
// Base
|
|
Actor::OnTransformChanged();
|
|
|
|
PROFILE_CPU();
|
|
|
|
// Update instances matrices and cached world bounds
|
|
Vector3 corners[8];
|
|
Matrix world;
|
|
GetLocalToWorldMatrix(world);
|
|
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
|
|
{
|
|
auto& instance = *i;
|
|
auto type = &FoliageTypes[instance.Type];
|
|
|
|
// Update bounds
|
|
instance.Bounds = BoundingSphere::Empty;
|
|
if (!type->IsReady())
|
|
continue;
|
|
auto& meshes = type->Model->LODs[0].Meshes;
|
|
const Transform transform = _transform.LocalToWorld(instance.Transform);
|
|
for (int32 j = 0; j < meshes.Count(); j++)
|
|
{
|
|
meshes[j].GetBox().GetCorners(corners);
|
|
|
|
for (int32 k = 0; k < 8; k++)
|
|
{
|
|
Vector3::Transform(corners[k], transform, corners[k]);
|
|
}
|
|
BoundingSphere meshBounds;
|
|
BoundingSphere::FromPoints(corners, 8, meshBounds);
|
|
|
|
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
|
|
}
|
|
}
|
|
|
|
RebuildClusters();
|
|
}
|