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,21 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
// Forward declarations
class Foliage;
class FoliageCluster;
class FoliageType;
struct FoliageInstance;
// Enable/disable foliage editing and changing at runtime. If your game need to use procedural foliage then enable this option.
#define FOLIAGE_EDITING (USE_EDITOR)
// Size of the instance allocation chunks (number of instances per allocated page)
#define FOLIAGE_INSTANCE_CHUNKS_SIZE (4096*4)
// Size of the cluster allocation chunks (number of clusters per allocated page)
#define FOLIAGE_CLUSTER_CHUNKS_SIZE (2048)
// Size of the cluster container for instances
#define FOLIAGE_CLUSTER_CAPACITY (64)

View File

@@ -0,0 +1,10 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
using Flax.Build;
/// <summary>
/// Foliage module.
/// </summary>
public class Foliage : EngineModule
{
}

View File

@@ -0,0 +1,843 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "Foliage.h"
#include "FoliageType.h"
#include "FoliageCluster.h"
#include "Engine/Core/Random.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Level/SceneQuery.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Utilities/Encryption.h"
Foliage::Foliage(const SpawnParams& params)
: Actor(params)
{
_disableFoliageTypeEvents = false;
Root = nullptr;
}
void Foliage::EnsureRoot()
{
// Skip if root is already here or there is no instances at all
if (Root || Instances.IsEmpty())
return;
ASSERT(Clusters.IsEmpty());
PROFILE_CPU();
// Calculate total bounds of valid instances
BoundingBox totalBounds;
{
bool anyValid = false;
// TODO: inline code and use SIMD
BoundingBox box;
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (!FoliageTypes[i->Type].IsReady())
continue;
BoundingBox::FromSphere(i->Bounds, box);
ASSERT(!i->Bounds.Center.IsNanOrInfinity());
if (anyValid)
{
BoundingBox::Merge(totalBounds, box, totalBounds);
}
else
{
totalBounds = box;
anyValid = true;
}
}
// Skip if nothing is valid
if (!anyValid)
return;
}
ASSERT(!totalBounds.Minimum.IsNanOrInfinity() && !totalBounds.Maximum.IsNanOrInfinity());
// Setup first and topmost cluster
Clusters.Resize(1);
Root = &Clusters[0];
Root->Init(totalBounds);
// Cache bounds
_box = Root->Bounds;
BoundingSphere::FromBox(_box, _sphere);
}
void Foliage::AddToCluster(FoliageCluster* cluster, FoliageInstance& instance)
{
ASSERT(instance.Bounds.Radius > ZeroTolerance);
ASSERT(cluster->Bounds.Intersects(instance.Bounds));
// Find target cluster
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 = cluster->Bounds.GetSize();
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)));
// Move instances to a proper cells
for (int32 i = 0; i < cluster->Instances.Count(); i++)
{
AddToCluster(cluster, *cluster->Instances[i]);
}
cluster->Instances.Clear();
AddToCluster(cluster, instance);
}
}
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)
{
// 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)
{
// 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.Unlink();
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)
{
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;
// Calculate foliage instance geometry transformation matrix
Matrix matrix, world;
GetLocalToWorldMatrix(world);
data->Transform.GetWorld(matrix);
Matrix::Multiply(matrix, world, data->World);
data->DrawState.PrevWorld = data->World;
// Validate foliage type model
if (!type->IsReady())
return;
// Update bounds
Vector3 corners[8];
auto& meshes = type->Model->LODs[0].Meshes;
for (int32 j = 0; j < meshes.Count(); j++)
{
meshes[j].GetCorners(corners);
for (int32 k = 0; k < 8; k++)
{
Vector3::Transform(corners[k], data->World, 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 world matrix
Matrix matrix, world;
GetLocalToWorldMatrix(world);
instance.Transform.GetWorld(matrix);
Matrix::Multiply(matrix, world, instance.World);
// Update bounds
instance.Bounds = BoundingSphere::Empty;
if (!type->IsReady())
return;
Vector3 corners[8];
auto& meshes = type->Model->LODs[0].Meshes;
for (int32 j = 0; j < meshes.Count(); j++)
{
meshes[j].GetCorners(corners);
for (int32 k = 0; k < 8; k++)
{
Vector3::Transform(corners[k], instance.World, 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;
ASSERT(index >= 0 && index < FoliageTypes.Count());
auto type = &FoliageTypes[index];
ASSERT(type->IsReady());
// TODO: maybe deffer OnFoliageTypeModelLoaded handling to game logic update because many foliage types may fire it during the same frame - save some CPU time
PROFILE_CPU();
// Update bounds for instances using this type
bool hasAnyInstance = false;
{
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;
hasAnyInstance = true;
// Include all meshes
for (int32 j = 0; j < meshes.Count(); j++)
{
meshes[j].GetCorners(corners);
for (int32 k = 0; k < 8; k++)
{
Vector3::Transform(corners[k], instance.World, corners[k]);
}
BoundingSphere meshBounds;
BoundingSphere::FromPoints(corners, 8, meshBounds);
ASSERT(meshBounds.Radius > ZeroTolerance);
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
}
}
}
if (!hasAnyInstance)
return;
RebuildClusters();
}
void Foliage::RebuildClusters()
{
PROFILE_CPU();
// Remove previous clusters data
Root = nullptr;
Clusters.Clear();
EnsureRoot();
// 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)
{
AddToCluster(Root, instance);
}
}
}
if (Root)
{
PROFILE_CPU_NAMED("Update Cache");
Root->UpdateTotalBoundsAndCullDistance();
}
}
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 (Root)
{
PROFILE_CPU_NAMED("Clusters");
Root->UpdateCullDistance();
}
}
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, float& distance, Vector3& normal, int32& instanceIndex)
{
PROFILE_CPU();
instanceIndex = -1;
if (Root)
{
FoliageInstance* instance;
if (Root->Intersects(this, ray, distance, normal, instance))
{
int32 j = 0;
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
if (&*i == instance)
{
instanceIndex = j;
break;
}
j++;
}
return true;
}
}
distance = MAX_float;
normal = Vector3::Up;
return false;
}
void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw)
{
// Skip clusters that around too far from view
if (Vector3::Distance(renderContext.View.Position, cluster->TotalBoundsSphere.Center) - cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance)
return;
//DebugDraw::DrawBox(cluster->Bounds, Color::Red);
// Draw visible children
if (cluster->Children[0])
{
#if BUILD_DEBUG
// Don't store instances in non-leaf nodes
ASSERT(cluster->Instances.IsEmpty());
#endif
#define DRAW_CLUSTER(idx) \
if (renderContext.View.CullingFrustum.Intersects(cluster->Children[idx]->TotalBounds)) \
DrawCluster(renderContext, cluster->Children[idx], draw)
DRAW_CLUSTER(0);
DRAW_CLUSTER(1);
DRAW_CLUSTER(2);
DRAW_CLUSTER(3);
#undef DRAW_CLUSTER
}
else
{
// 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];
// Check if can draw this instance
if (type._canDraw &&
Vector3::Distance(renderContext.View.Position, instance.Bounds.Center) - instance.Bounds.Radius < instance.CullDistance &&
renderContext.View.CullingFrustum.Intersects(instance.Bounds))
{
// Disable motion blur
instance.DrawState.PrevWorld = instance.World;
// Draw model
draw.Lightmap = GetScene()->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex);
draw.LightmapUVs = &instance.Lightmap.UVsArea;
draw.Buffer = &type.Entries;
draw.World = &instance.World;
draw.DrawState = &instance.DrawState;
draw.Bounds = instance.Bounds;
draw.PerInstanceRandom = instance.Random;
draw.DrawModes = type._drawModes;
type.Model->Draw(renderContext, draw);
//DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen);
instance.DrawState.PrevFrame = frame;
}
}
}
}
void Foliage::Draw(RenderContext& renderContext)
{
// Skip if no instances spawned
if (Instances.IsEmpty() || !Root)
return;
auto& view = renderContext.View;
PROFILE_CPU();
// Cache data per foliage instance type
for (int32 i = 0; i < FoliageTypes.Count(); i++)
{
auto& type = FoliageTypes[i];
const auto drawModes = static_cast<DrawPass>(type.DrawModes & view.Pass & (int32)view.GetShadowsDrawPassMask(type.ShadowsMode));
type._canDraw = type.IsReady() && drawModes != DrawPass::None;
type._drawModes = drawModes;
if (type._canDraw)
{
for (int32 j = 0; j < type.Entries.Count(); j++)
{
auto& e = type.Entries[j];
e.ReceiveDecals = type.ReceiveDecals != 0;
e.ShadowsMode = type.ShadowsMode;
}
}
}
// Draw visible clusters
Mesh::DrawInfo draw;
draw.Flags = GetStaticFlags();
draw.DrawModes = (DrawPass)(DrawPass::Default & view.Pass);
draw.LODBias = 0;
draw.ForcedLOD = -1;
draw.VertexColors = nullptr;
DrawCluster(renderContext, Root, draw);
}
void Foliage::DrawGeneric(RenderContext& renderContext)
{
Draw(renderContext);
}
bool Foliage::IntersectsItself(const Ray& ray, float& 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;
Transform Transform;
static constexpr int32 Size = 48;
static constexpr int32 Base64Size = GetInstanceBase64Size(Size);
};
struct InstanceEncoded2
{
int32 Type;
float Random;
Transform Transform;
LightmapEntry Lightmap;
static const int32 Size = 68;
static const int32 Base64Size = GetInstanceBase64Size(Size);
};
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.");
void Foliage::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
Actor::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(Foliage);
if (FoliageTypes.IsEmpty())
return;
PROFILE_CPU();
stream.JKEY("Foliage");
stream.StartArray();
for (int32 i = 0; i < FoliageTypes.Count(); i++)
{
stream.StartObject();
FoliageTypes[i].Serialize(stream, nullptr);
stream.EndObject();
}
stream.EndArray();
stream.JKEY("Instances");
stream.StartArray();
InstanceEncoded enc;
char base64[InstanceEncoded::Base64Size];
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
auto& instance = *i;
enc.Type = instance.Type;
enc.Random = instance.Random;
enc.Transform = instance.Transform;
enc.Lightmap = instance.Lightmap;
Encryption::Base64Encode((const byte*)&enc, sizeof(enc), base64);
stream.String(base64, InstanceEncoded::Base64Size);
}
stream.EndArray();
}
void Foliage::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
Actor::Deserialize(stream, modifier);
PROFILE_CPU();
// Clear
Root = nullptr;
Instances.Release();
Clusters.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)
{
const DeserializeStream& items = foliageTypesMember->value;;
FoliageTypes.Resize(foliageTypesCount, false);
for (int32 i = 0; i < foliageTypesCount; i++)
{
FoliageTypes[i].Foliage = this;
FoliageTypes[i].Index = i;
FoliageTypes[i].Deserialize((DeserializeStream&)items[i], modifier);
}
}
// Skip if no foliage
if (FoliageTypes.IsEmpty())
return;
// Deserialize foliage instances
int32 foliageInstancesCount = 0;
const auto& foliageInstancesMember = stream.FindMember("Instances");
if (foliageInstancesMember != stream.MemberEnd() && foliageInstancesMember->value.IsArray())
{
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]
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 = enc.Transform;
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 = enc.Transform;
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::OnEnable()
{
GetScene()->Rendering.AddGeometry(this);
// Base
Actor::OnEnable();
}
void Foliage::OnDisable()
{
GetScene()->Rendering.RemoveGeometry(this);
// Base
Actor::OnDisable();
}
void Foliage::OnTransformChanged()
{
// Base
Actor::OnTransformChanged();
PROFILE_CPU();
// Update instances matrices and cached world bounds
Vector3 corners[8];
Matrix world, matrix;
GetLocalToWorldMatrix(world);
for (auto i = Instances.Begin(); i.IsNotEnd(); ++i)
{
auto& instance = *i;
auto type = &FoliageTypes[instance.Type];
// Update world matrix
instance.Transform.GetWorld(matrix);
Matrix::Multiply(matrix, world, instance.World);
// Update bounds
instance.Bounds = BoundingSphere::Empty;
if (!type->IsReady())
continue;
auto& meshes = type->Model->LODs[0].Meshes;
for (int32 j = 0; j < meshes.Count(); j++)
{
meshes[j].GetCorners(corners);
for (int32 k = 0; k < 8; k++)
{
Vector3::Transform(corners[k], instance.World, corners[k]);
}
BoundingSphere meshBounds;
BoundingSphere::FromPoints(corners, 8, meshBounds);
BoundingSphere::Merge(instance.Bounds, meshBounds, instance.Bounds);
}
}
RebuildClusters();
}

View File

@@ -0,0 +1,201 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Config.h"
#include "FoliageInstance.h"
#include "FoliageCluster.h"
#include "FoliageType.h"
#include "Engine/Level/Actor.h"
#include "Engine/Core/Collections/ChunkedArray.h"
/// <summary>
/// Represents a foliage actor that contains a set of instanced meshes.
/// </summary>
/// <seealso cref="Actor" />
API_CLASS() class FLAXENGINE_API Foliage final : public Actor
{
DECLARE_SCENE_OBJECT(Foliage);
private:
bool _disableFoliageTypeEvents;
public:
/// <summary>
/// The root cluster. Contains all the instances and it's the starting point of the quad-tree hierarchy. Null if no foliage added. It's read-only.
/// </summary>
FoliageCluster* Root;
/// <summary>
/// The allocated foliage instances. It's read-only.
/// </summary>
ChunkedArray<FoliageInstance, FOLIAGE_INSTANCE_CHUNKS_SIZE> Instances;
/// <summary>
/// The allocated foliage clusters. It's read-only.
/// </summary>
ChunkedArray<FoliageCluster, FOLIAGE_CLUSTER_CHUNKS_SIZE> Clusters;
/// <summary>
/// The foliage instances types used by the current foliage actor. It's read-only.
/// </summary>
API_FIELD(ReadOnly, Attributes="HideInEditor, NoSerialize")
Array<FoliageType> FoliageTypes;
public:
/// <summary>
/// Gets the total amount of the instanced of foliage.
/// </summary>
/// <returns>The foliage instances count.</returns>
API_PROPERTY() int32 GetInstancesCount() const;
/// <summary>
/// Gets the foliage instance by index.
/// </summary>
/// <param name="index">The zero-based index of the foliage instance.</param>
/// <returns>The foliage instance data.</returns>
API_FUNCTION() FoliageInstance GetInstance(int32 index) const;
/// <summary>
/// Gets the total amount of the types of foliage.
/// </summary>
/// <returns>The foliage types count.</returns>
API_PROPERTY() int32 GetFoliageTypesCount() const;
/// <summary>
/// Gets the foliage type.
/// </summary>
/// <param name="index">The zero-based index of the foliage type.</param>
/// <returns>The foliage type.</returns>
API_FUNCTION() FoliageType* GetFoliageType(int32 index);
/// <summary>
/// Adds the type of the foliage.
/// </summary>
/// <param name="model">The model to assign. It cannot be null nor already used by the other instance type (it must be unique within the given foliage actor).</param>
API_FUNCTION() void AddFoliageType(Model* model);
/// <summary>
/// Removes the foliage instance type and all foliage instances using this type.
/// </summary>
/// <param name="index">The zero-based index of the foliage instance type.</param>
API_FUNCTION() void RemoveFoliageType(int32 index);
/// <summary>
/// Gets the total amount of the instanced that use the given foliage type.
/// </summary>
/// <param name="index">The zero-based index of the foliage type.</param>
/// <returns>The foliage type instances count.</returns>
API_FUNCTION() int32 GetFoliageTypeInstancesCount(int32 index) const;
/// <summary>
/// Adds the new foliage instance. Ensure to always call <see cref="RebuildClusters"/> after editing foliage to sync cached data (call it once after editing one or more instances).
/// </summary>
/// <remarks>Input instance bounds, instance random and world matrix are ignored (recalculated).</remarks>
/// <param name="instance">The instance.</param>
API_FUNCTION() void AddInstance(API_PARAM(Ref) const FoliageInstance& instance);
/// <summary>
/// Removes the foliage instance. Ensure to always call <see cref="RebuildClusters"/> after editing foliage to sync cached data (call it once after editing one or more instances).
/// </summary>
/// <param name="index">The zero-based index of the instance to remove.</param>
API_FUNCTION() void RemoveInstance(int32 index)
{
RemoveInstance(Instances.IteratorAt(index));
}
/// <summary>
/// Removes the foliage instance. Ensure to always call <see cref="RebuildClusters"/> after editing foliage to sync cached data (call it once after editing one or more instances).
/// </summary>
/// <param name="i">The iterator from foliage instances that points to the instance to remove.</param>
void RemoveInstance(ChunkedArray<FoliageInstance, FOLIAGE_INSTANCE_CHUNKS_SIZE>::Iterator i);
/// <summary>
/// Sets the foliage instance transformation. Ensure to always call <see cref="RebuildClusters"/> after editing foliage to sync cached data (call it once after editing one or more instances).
/// </summary>
/// <param name="index">The zero-based index of the foliage instance.</param>
/// <param name="value">The value.</param>
API_FUNCTION() void SetInstanceTransform(int32 index, API_PARAM(Ref) const Transform& value);
/// <summary>
/// Called when foliage type model is loaded.
/// </summary>
/// <param name="index">The zero-based index of the foliage type.</param>
void OnFoliageTypeModelLoaded(int32 index);
/// <summary>
/// Rebuilds the foliage clusters used as internal acceleration structures (quad tree).
/// </summary>
API_FUNCTION() void RebuildClusters();
/// <summary>
/// Updates the cull distance for all foliage instances and for created clusters.
/// </summary>
API_FUNCTION() void UpdateCullDistance();
public:
/// <summary>
/// Gets the global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.
/// </summary>
/// <returns>The value.</returns>
API_PROPERTY() static float GetGlobalDensityScale();
/// <summary>
/// Sets the global density scale for all foliage instances. The default value is 1. Use values from range 0-1. Lower values decrease amount of foliage instances in-game. Use it to tweak game performance for slower devices.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() static void SetGlobalDensityScale(float value);
private:
/// <summary>
/// Ensures that the root node of the foliage was added.
/// </summary>
void EnsureRoot();
/// <summary>
/// Adds the given foliage instance to the cluster.
/// </summary>
/// <param name="cluster">The root cluster.</param>
/// <param name="instance">The instance.</param>
void AddToCluster(FoliageCluster* cluster, FoliageInstance& instance);
/// <summary>
/// Draws the cluster.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="cluster">The cluster.</param>
/// <param name="draw">The draw data.</param>
void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw);
public:
/// <summary>
/// Determines if there is an intersection between the current object or any it's child and a ray.
/// </summary>
/// <param name="ray">The ray to test.</param>
/// <param name="distance">When the method completes, contains the distance of the intersection (if any valid).</param>
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
/// <param name="instanceIndex">When the method completes, contains zero-based index of the foliage instance that is the closest to the ray.</param>
/// <returns>True whether the two objects intersected, otherwise false.</returns>
API_FUNCTION() bool Intersects(API_PARAM(Ref) const Ray& ray, API_PARAM(Out) float& distance, API_PARAM(Out) Vector3& normal, API_PARAM(Out) int32& instanceIndex);
public:
// [Actor]
void Draw(RenderContext& renderContext) override;
void DrawGeneric(RenderContext& renderContext) override;
bool IntersectsItself(const Ray& ray, float& distance, Vector3& normal) override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
protected:
// [Actor]
void OnEnable() override;
void OnDisable() override;
void OnTransformChanged() override;
};

View File

@@ -0,0 +1,135 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "FoliageCluster.h"
#include "FoliageInstance.h"
#include "Foliage.h"
void FoliageCluster::Init(const BoundingBox& bounds)
{
Bounds = bounds;
TotalBounds = bounds;
MaxCullDistance = 0.0f;
Children[0] = nullptr;
Children[1] = nullptr;
Children[2] = nullptr;
Children[3] = nullptr;
Instances.Clear();
}
void FoliageCluster::UpdateTotalBoundsAndCullDistance()
{
if (Children[0])
{
ASSERT(Instances.IsEmpty());
Children[0]->UpdateTotalBoundsAndCullDistance();
Children[1]->UpdateTotalBoundsAndCullDistance();
Children[2]->UpdateTotalBoundsAndCullDistance();
Children[3]->UpdateTotalBoundsAndCullDistance();
TotalBounds = Children[0]->TotalBounds;
BoundingBox::Merge(TotalBounds, Children[1]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[2]->TotalBounds, TotalBounds);
BoundingBox::Merge(TotalBounds, Children[3]->TotalBounds, TotalBounds);
MaxCullDistance = Children[0]->MaxCullDistance;
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
}
else if (Instances.HasItems())
{
BoundingBox box;
BoundingBox::FromSphere(Instances[0]->Bounds, TotalBounds);
MaxCullDistance = Instances[0]->CullDistance;
for (int32 i = 1; i < Instances.Count(); i++)
{
BoundingBox::FromSphere(Instances[i]->Bounds, box);
BoundingBox::Merge(TotalBounds, box, TotalBounds);
MaxCullDistance = Math::Max(MaxCullDistance, Instances[i]->CullDistance);
}
}
else
{
TotalBounds = Bounds;
MaxCullDistance = 0;
}
BoundingSphere::FromBox(TotalBounds, TotalBoundsSphere);
}
void FoliageCluster::UpdateCullDistance()
{
if (Children[0])
{
Children[0]->UpdateCullDistance();
Children[1]->UpdateCullDistance();
Children[2]->UpdateCullDistance();
Children[3]->UpdateCullDistance();
MaxCullDistance = Children[0]->MaxCullDistance;
MaxCullDistance = Math::Max(MaxCullDistance, Children[1]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[2]->MaxCullDistance);
MaxCullDistance = Math::Max(MaxCullDistance, Children[3]->MaxCullDistance);
}
else if (Instances.HasItems())
{
MaxCullDistance = Instances[0]->CullDistance;
for (int32 i = 1; i < Instances.Count(); i++)
{
MaxCullDistance = Math::Max(MaxCullDistance, Instances[i]->CullDistance);
}
}
else
{
MaxCullDistance = 0;
}
}
bool FoliageCluster::Intersects(Foliage* foliage, const Ray& ray, float& distance, Vector3& normal, FoliageInstance*& instance)
{
bool result = false;
float minDistance = MAX_float;
Vector3 minDistanceNormal = Vector3::Up;
FoliageInstance* minInstance = nullptr;
if (Children[0])
{
#define CHECK_CHILD(idx) \
if (Children[idx]->TotalBounds.Intersects(ray) && Children[idx]->Intersects(foliage, ray, distance, normal, instance) && minDistance > distance) \
{ \
minDistanceNormal = normal; \
minDistance = distance; \
minInstance = instance; \
result = true; \
}
CHECK_CHILD(0);
CHECK_CHILD(1);
CHECK_CHILD(2);
CHECK_CHILD(3);
#undef CHECK_CHILD
}
else
{
Mesh* mesh;
for (int32 i = 0; i < Instances.Count(); i++)
{
auto& ii = *Instances[i];
auto& type = foliage->FoliageTypes[ii.Type];
if (type.IsReady() && ii.Bounds.Intersects(ray) && type.Model->Intersects(ray, ii.World, distance, normal, &mesh) && minDistance > distance)
{
minDistanceNormal = normal;
minDistance = distance;
minInstance = &ii;
result = true;
}
}
}
distance = minDistance;
normal = minDistanceNormal;
instance = minInstance;
return result;
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Config.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Math/BoundingSphere.h"
/// <summary>
/// Represents a single foliage cluster that contains a sub clusters organized in quad-tree or if it's a leaf node it contains a set of foliage instances.
/// </summary>
class FLAXENGINE_API FoliageCluster
{
public:
/// <summary>
/// The cluster bounds (in world space). Made of subdivided parent node in quad-tree.
/// </summary>
BoundingBox Bounds;
/// <summary>
/// The cached cluster total bounds (in world space). Made of attached instances bounds including children.
/// </summary>
BoundingBox TotalBounds;
/// <summary>
/// The cached cluster total bounds (in world space). Made of attached instances bounds including children.
/// </summary>
BoundingSphere TotalBoundsSphere;
/// <summary>
/// The maximum cull distance for the instances that are located in this cluster (including child clusters).
/// </summary>
float MaxCullDistance;
/// <summary>
/// The child clusters. If any element is valid then all are created.
/// </summary>
FoliageCluster* Children[4];
/// <summary>
/// The allocated foliage instances within this cluster.
/// </summary>
Array<FoliageInstance*, FixedAllocation<FOLIAGE_CLUSTER_CAPACITY>> Instances;
public:
/// <summary>
/// Initializes this instance.
/// </summary>
/// <param name="bounds">The bounds.</param>
void Init(const BoundingBox& bounds);
/// <summary>
/// Updates the total bounds of the cluster and all child clusters and cull distance (as UpdateCullDistance does).
/// </summary>
void UpdateTotalBoundsAndCullDistance();
/// <summary>
/// Updates the cull distance for all foliage instances added to the cluster and its children.
/// </summary>
void UpdateCullDistance();
/// <summary>
/// Determines if there is an intersection between the current object or any it's child and a ray.
/// </summary>
/// <param name="foliage">The parent foliage actor.</param>
/// <param name="ray">The ray to test.</param>
/// <param name="distance">When the method completes, contains the distance of the intersection (if any valid).</param>
/// <param name="normal">When the method completes, contains the intersection surface normal vector (if any valid).</param>
/// <param name="instance">When the method completes, contains pointer of the foliage instance that is the closest to the ray.</param>
/// <returns>True whether the two objects intersected, otherwise false.</returns>
bool Intersects(Foliage* foliage, const Ray& ray, float& distance, Vector3& normal, FoliageInstance*& instance);
};

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Core/Math/Transform.h"
#include "Engine/Core/Math/BoundingSphere.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Level/Scene/Lightmap.h"
/// <summary>
/// Foliage instanced mesh instance. Packed data with very little of logic. Managed by the foliage chunks and foliage actor itself.
/// </summary>
API_STRUCT(NoPod) struct FoliageInstance
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageInstance);
/// <summary>
/// The local-space transformation of the mesh relative to the foliage actor.
/// </summary>
API_FIELD() Transform Transform;
/// <summary>
/// The cached world transformation matrix of this instance.
/// </summary>
API_FIELD() Matrix World;
/// <summary>
/// The model drawing state.
/// </summary>
GeometryDrawStateData DrawState;
/// <summary>
/// The foliage type index. Foliage types are hold in foliage actor and shared by instances using the same model.
/// </summary>
API_FIELD() int32 Type;
/// <summary>
/// The per-instance random value from range [0;1].
/// </summary>
API_FIELD() float Random;
/// <summary>
/// The cull distance for this instance.
/// </summary>
float CullDistance;
/// <summary>
/// The cached instance bounds (in world space).
/// </summary>
API_FIELD() BoundingSphere Bounds;
/// <summary>
/// The lightmap entry for the foliage instance.
/// </summary>
LightmapEntry Lightmap;
public:
bool operator==(const FoliageInstance& v) const
{
return Type == v.Type && Math::NearEqual(Random, v.Random) && Transform == v.Transform;
}
/// <summary>
/// Determines whether this foliage instance has valid lightmap data.
/// </summary>
/// <returns><c>true</c> if this foliage instance has valid lightmap data; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool HasLightmap() const
{
return Lightmap.TextureIndex != INVALID_INDEX;
}
/// <summary>
/// Removes the lightmap data from the foliage instance.
/// </summary>
FORCE_INLINE void RemoveLightmap()
{
Lightmap.TextureIndex = INVALID_INDEX;
}
};

View File

@@ -0,0 +1,211 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#include "FoliageType.h"
#include "Engine/Core/Collections/ArrayExtensions.h"
#include "Engine/Core/Random.h"
#include "Engine/Serialization/Serialization.h"
#include "Foliage.h"
FoliageType::FoliageType()
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
, Foliage(nullptr)
, Index(-1)
{
_isReady = 0;
_canDraw = 0;
ReceiveDecals = true;
UseDensityScaling = false;
PlacementAlignToNormal = true;
PlacementRandomYaw = true;
Model.Changed.Bind<FoliageType, &FoliageType::OnModelChanged>(this);
Model.Loaded.Bind<FoliageType, &FoliageType::OnModelLoaded>(this);
}
FoliageType& FoliageType::operator=(const FoliageType& other)
{
Foliage = other.Foliage;
Index = other.Index;
Model = other.Model;
Entries = other.Entries;
CullDistance = other.CullDistance;
CullDistanceRandomRange = other.CullDistanceRandomRange;
ScaleInLightmap = other.ScaleInLightmap;
DrawModes = other.DrawModes;
ShadowsMode = other.ShadowsMode;
PaintDensity = other.PaintDensity;
PaintRadius = other.PaintRadius;
PaintGroundSlopeAngleMin = other.PaintGroundSlopeAngleMin;
PaintGroundSlopeAngleMax = other.PaintGroundSlopeAngleMax;
PaintScaling = other.PaintScaling;
PaintScaleMin = other.PaintScaleMin;
PaintScaleMax = other.PaintScaleMax;
PlacementOffsetY = other.PlacementOffsetY;
PlacementRandomPitchAngle = other.PlacementRandomPitchAngle;
PlacementRandomRollAngle = other.PlacementRandomRollAngle;
DensityScalingScale = other.DensityScalingScale;
ReceiveDecals = other.ReceiveDecals;
UseDensityScaling = other.UseDensityScaling;
PlacementAlignToNormal = other.PlacementAlignToNormal;
PlacementRandomYaw = other.PlacementRandomYaw;
return *this;
}
Array<MaterialBase*> FoliageType::GetMaterials() const
{
Array<MaterialBase*> result;
result.EnsureCapacity(Entries.Count());
for (const auto& e : Entries)
result.Add(e.Material);
return result;
}
void FoliageType::SetMaterials(const Array<MaterialBase*>& value)
{
CHECK(value.Count() == Entries.Count());
for (int32 i = 0; i < value.Count(); i++)
Entries[i].Material = value[i];
}
Vector3 FoliageType::GetRandomScale() const
{
Vector3 result;
float tmp;
switch (PaintScaling)
{
case FoliageScalingModes::Uniform:
result.X = Math::Lerp(PaintScaleMin.X, PaintScaleMax.X, Random::Rand());
result.Y = result.X;
result.Z = result.X;
break;
case FoliageScalingModes::Free:
result.X = Math::Lerp(PaintScaleMin.X, PaintScaleMax.X, Random::Rand());
result.Y = Math::Lerp(PaintScaleMin.Y, PaintScaleMax.Y, Random::Rand());
result.Z = Math::Lerp(PaintScaleMin.Z, PaintScaleMax.Z, Random::Rand());
break;
case FoliageScalingModes::LockXY:
tmp = Random::Rand();
result.X = Math::Lerp(PaintScaleMin.X, PaintScaleMax.X, tmp);
result.Y = Math::Lerp(PaintScaleMin.Y, PaintScaleMax.Y, tmp);
result.Z = Math::Lerp(PaintScaleMin.Z, PaintScaleMax.Z, Random::Rand());
break;
case FoliageScalingModes::LockXZ:
tmp = Random::Rand();
result.X = Math::Lerp(PaintScaleMin.X, PaintScaleMax.X, tmp);
result.Y = Math::Lerp(PaintScaleMin.Y, PaintScaleMax.Y, Random::Rand());
result.Z = Math::Lerp(PaintScaleMin.Z, PaintScaleMax.Z, tmp);
break;
case FoliageScalingModes::LockYZ:
tmp = Random::Rand();
result.X = Math::Lerp(PaintScaleMin.X, PaintScaleMax.X, Random::Rand());
result.Y = Math::Lerp(PaintScaleMin.Y, PaintScaleMax.Y, tmp);
result.Z = Math::Lerp(PaintScaleMin.Z, PaintScaleMax.Z, tmp);
break;
}
return result;
}
void FoliageType::OnModelChanged()
{
// Cleanup
_isReady = 0;
Entries.Release();
}
void FoliageType::OnModelLoaded()
{
// Now it's ready
_isReady = 1;
// Prepare buffer (model may be modified so we should synchronize buffer with the actual asset)
Entries.SetupIfInvalid(Model);
// Inform foliage that instances may need to be updated (data caching, etc.)
Foliage->OnFoliageTypeModelLoaded(Index);
}
void FoliageType::Serialize(SerializeStream& stream, const void* otherObj)
{
SERIALIZE_GET_OTHER_OBJ(FoliageType);
SERIALIZE(Model);
const std::function<bool(const ModelInstanceEntry&)> IsValidMaterial = [](const ModelInstanceEntry& e) -> bool
{
return e.Material;
};
if (ArrayExtensions::Any(Entries, IsValidMaterial))
{
stream.JKEY("Materials");
stream.StartArray();
for (auto& e : Entries)
stream.Guid(e.Material.GetID());
stream.EndArray();
}
SERIALIZE(CullDistance);
SERIALIZE(CullDistanceRandomRange);
SERIALIZE(ScaleInLightmap);
SERIALIZE(DrawModes);
SERIALIZE(ShadowsMode);
SERIALIZE_BIT(ReceiveDecals);
SERIALIZE_BIT(UseDensityScaling);
SERIALIZE(DensityScalingScale);
SERIALIZE(PaintDensity);
SERIALIZE(PaintRadius);
SERIALIZE(PaintGroundSlopeAngleMin);
SERIALIZE(PaintGroundSlopeAngleMax);
SERIALIZE(PaintScaling);
SERIALIZE(PaintScaleMin);
SERIALIZE(PaintScaleMax);
SERIALIZE(PlacementOffsetY);
SERIALIZE(PlacementRandomPitchAngle);
SERIALIZE(PlacementRandomRollAngle);
SERIALIZE_BIT(PlacementAlignToNormal);
SERIALIZE_BIT(PlacementRandomYaw);
}
void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
DESERIALIZE(Model);
const auto member = stream.FindMember("Materials");
if (member != stream.MemberEnd() && member->value.IsArray())
{
auto& materials = member->value;
const auto size = materials.Size();
Entries.Resize(size);
for (rapidjson::SizeType i = 0; i < size; i++)
{
Serialization::Deserialize(materials[i], Entries[i].Material, modifier);
}
}
DESERIALIZE(CullDistance);
DESERIALIZE(CullDistanceRandomRange);
DESERIALIZE(ScaleInLightmap);
DESERIALIZE(DrawModes);
DESERIALIZE(ShadowsMode);
DESERIALIZE_BIT(ReceiveDecals);
DESERIALIZE_BIT(UseDensityScaling);
DESERIALIZE(DensityScalingScale);
DESERIALIZE(PaintDensity);
DESERIALIZE(PaintRadius);
DESERIALIZE(PaintGroundSlopeAngleMin);
DESERIALIZE(PaintGroundSlopeAngleMax);
DESERIALIZE(PaintScaling);
DESERIALIZE(PaintScaleMin);
DESERIALIZE(PaintScaleMax);
DESERIALIZE(PlacementOffsetY);
DESERIALIZE(PlacementRandomPitchAngle);
DESERIALIZE(PlacementRandomRollAngle);
DESERIALIZE_BIT(PlacementAlignToNormal);
DESERIALIZE_BIT(PlacementRandomYaw);
}

View File

@@ -0,0 +1,231 @@
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
#pragma once
#include "Config.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Serialization/ISerializable.h"
/// <summary>
/// The foliage instances scaling modes.
/// </summary>
API_ENUM() enum class FoliageScalingModes
{
/// <summary>
/// The uniform scaling. All axes are scaled the same.
/// </summary>
Uniform,
/// <summary>
/// The free scaling. Each axis can have custom scale.
/// </summary>
Free,
/// <summary>
/// The lock XZ plane axis. Axes X and Z are constrained to-gather and axis Y is free.
/// </summary>
LockXZ,
/// <summary>
/// The lock XY plane axis. Axes X and Y are constrained to-gather and axis Z is free.
/// </summary>
LockXY,
/// <summary>
/// The lock YZ plane axis. Axes Y and Z are constrained to-gather and axis X is free.
/// </summary>
LockYZ,
};
/// <summary>
/// Foliage mesh instances type descriptor. Defines the shared properties of the spawned mesh instances.
/// </summary>
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public PersistentScriptingObject, public ISerializable
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType);
friend Foliage;
private:
int8 _isReady : 1;
int8 _canDraw : 1; // Cached and used internally by foliage actor
DrawPass _drawModes; // Cached mask during rendering
public:
/// <summary>
/// Initializes a new instance of the <see cref="FoliageType"/> class.
/// </summary>
FoliageType();
FoliageType(const FoliageType& other)
: FoliageType()
{
*this = other;
}
FoliageType& operator=(const FoliageType& other);
public:
/// <summary>
/// The parent foliage actor.
/// </summary>
API_FIELD(ReadOnly) Foliage* Foliage;
/// <summary>
/// The foliage type index.
/// </summary>
API_FIELD(ReadOnly) int32 Index;
/// <summary>
/// The model to draw by the instances.
/// </summary>
API_FIELD() AssetReference<Model> Model;
/// <summary>
/// The shared model instance entries.
/// </summary>
ModelInstanceEntries Entries;
public:
/// <summary>
/// Gets the foliage instance type materials buffer (overrides).
/// </summary>
API_PROPERTY() Array<MaterialBase*> GetMaterials() const;
/// <summary>
/// Sets the foliage instance type materials buffer (overrides).
/// </summary>
API_PROPERTY() void SetMaterials(const Array<MaterialBase*>& value);
public:
/// <summary>
/// The per-instance cull distance.
/// </summary>
API_FIELD() float CullDistance = 10000.0f;
/// <summary>
/// The per-instance cull distance randomization range (randomized per instance and added to master CullDistance value).
/// </summary>
API_FIELD() float CullDistanceRandomRange = 1000.0f;
/// <summary>
/// The scale in lightmap (for instances of this foliage type). Can be used to adjust static lighting quality for the foliage instances.
/// </summary>
API_FIELD() float ScaleInLightmap = 1.0f;
/// <summary>
/// The draw passes to use for rendering this foliage type.
/// </summary>
API_FIELD() DrawPass DrawModes = DrawPass::Default;
/// <summary>
/// The shadows casting mode.
/// </summary>
API_FIELD() ShadowsCastingMode ShadowsMode = ShadowsCastingMode::All;
/// <summary>
/// The foliage instances density defined in instances count per 1000x1000 units area.
/// </summary>
API_FIELD() float PaintDensity = 1.0f;
/// <summary>
/// The minimum radius between foliage instances.
/// </summary>
API_FIELD() float PaintRadius = 0.0f;
/// <summary>
/// The minimum ground slope angle to paint foliage on it (in degrees).
/// </summary>
API_FIELD() float PaintGroundSlopeAngleMin = 0.0f;
/// <summary>
/// The maximum ground slope angle to paint foliage on it (in degrees).
/// </summary>
API_FIELD() float PaintGroundSlopeAngleMax = 45.0f;
/// <summary>
/// The scaling mode.
/// </summary>
API_FIELD() FoliageScalingModes PaintScaling = FoliageScalingModes::Uniform;
/// <summary>
/// The scale minimum values per axis.
/// </summary>
API_FIELD() Vector3 PaintScaleMin = Vector3::One;
/// <summary>
/// The scale maximum values per axis.
/// </summary>
API_FIELD() Vector3 PaintScaleMax = Vector3::One;
/// <summary>
/// The per-instance random offset range on axis Y.
/// </summary>
API_FIELD() Vector2 PlacementOffsetY = Vector2::Zero;
/// <summary>
/// The random pitch angle range (uniform in both ways around normal vector).
/// </summary>
API_FIELD() float PlacementRandomPitchAngle = 0.0f;
/// <summary>
/// The random roll angle range (uniform in both ways around normal vector).
/// </summary>
API_FIELD() float PlacementRandomRollAngle = 0.0f;
/// <summary>
/// The density scaling scale applied to the global scale for the foliage instances of this type. Can be used to boost or reduce density scaling effect on this foliage type. Default is 1.
/// </summary>
API_FIELD() float DensityScalingScale = 1.0f;
/// <summary>
/// Determines whenever this meshes can receive decals.
/// </summary>
API_FIELD() int8 ReceiveDecals : 1;
/// <summary>
/// Flag used to determinate whenever use global foliage density scaling for instances of this foliage type.
/// </summary>
API_FIELD() int8 UseDensityScaling : 1;
/// <summary>
/// If checked, instances will be aligned to normal of the placed surface.
/// </summary>
API_FIELD() int8 PlacementAlignToNormal : 1;
/// <summary>
/// If checked, instances will use randomized yaw when placed. Random yaw uses will rotation range over the Y axis.
/// </summary>
API_FIELD() int8 PlacementRandomYaw : 1;
public:
/// <summary>
/// Determines whether this instance is ready (model is loaded).
/// </summary>
/// <returns><c>true</c> if foliage type model is loaded and instance type is ready; otherwise, <c>false</c>.</returns>
FORCE_INLINE bool IsReady() const
{
return _isReady != 0;
}
/// <summary>
/// Gets the random scale for the foliage instance of this type.
/// </summary>
/// <returns>The scale vector.</returns>
Vector3 GetRandomScale() const;
private:
void OnModelChanged();
void OnModelLoaded();
public:
// [ISerializable]
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
};