Explorar o código

Introduce new scene components

Panagiotis Christopoulos Charitos %!s(int64=2) %!d(string=hai) anos
pai
achega
9a883c1531
Modificáronse 29 ficheiros con 831 adicións e 43 borrados
  1. 1 1
      AnKi/Scene/Common.h
  2. 0 2
      AnKi/Scene/Components/BodyComponent.cpp
  3. 62 0
      AnKi/Scene/Components/CameraComponent.cpp
  4. 100 0
      AnKi/Scene/Components/CameraComponent.h
  5. 0 2
      AnKi/Scene/Components/DecalComponent.cpp
  6. 0 2
      AnKi/Scene/Components/FogDensityComponent.cpp
  7. 0 2
      AnKi/Scene/Components/FrustumComponent.cpp
  8. 0 2
      AnKi/Scene/Components/GenericGpuComputeJobComponent.cpp
  9. 0 2
      AnKi/Scene/Components/GlobalIlluminationProbeComponent.cpp
  10. 0 2
      AnKi/Scene/Components/GpuParticleEmitterComponent.cpp
  11. 0 2
      AnKi/Scene/Components/JointComponent.cpp
  12. 0 2
      AnKi/Scene/Components/LensFlareComponent.cpp
  13. 0 2
      AnKi/Scene/Components/LightComponent.cpp
  14. 0 2
      AnKi/Scene/Components/ModelComponent.cpp
  15. 0 2
      AnKi/Scene/Components/MoveComponent.cpp
  16. 0 2
      AnKi/Scene/Components/ParticleEmitterComponent.cpp
  17. 0 2
      AnKi/Scene/Components/PlayerControllerComponent.cpp
  18. 0 2
      AnKi/Scene/Components/ReflectionProbeComponent.cpp
  19. 61 0
      AnKi/Scene/Components/SceneComponentClasses.defs.h
  20. 35 0
      AnKi/Scene/Components/SceneComponentStatics.cpp
  21. 0 2
      AnKi/Scene/Components/ScriptComponent.cpp
  22. 0 2
      AnKi/Scene/Components/SkinComponent.cpp
  23. 0 2
      AnKi/Scene/Components/SkyboxComponent.cpp
  24. 0 2
      AnKi/Scene/Components/SpatialComponent.cpp
  25. 0 2
      AnKi/Scene/Components/TriggerComponent.cpp
  26. 0 2
      AnKi/Scene/Components/UiComponent.cpp
  27. 2 0
      AnKi/Scene/Events/AnimationEvent.h
  28. 206 0
      AnKi/Scene/Frustum.cpp
  29. 364 0
      AnKi/Scene/Frustum.h

+ 1 - 1
AnKi/Scene/Common.h

@@ -36,7 +36,7 @@ class PhysicsWorld;
 #define ANKI_SCENE_ASSERT(expression) \
 	ANKI_LIKELY(std::invoke([&]() -> Bool { \
 		const Bool ok = (expression); \
-		if(ANKI_UNLIKELY(!ok)) \
+		if(!ok) [[unlikely]] \
 		{ \
 			ANKI_SCENE_LOGE("Expression failed: " #expression); \
 		} \

+ 0 - 2
AnKi/Scene/Components/BodyComponent.cpp

@@ -12,8 +12,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(BodyComponent, 10.0f)
-
 BodyComponent::BodyComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 62 - 0
AnKi/Scene/Components/CameraComponent.cpp

@@ -0,0 +1,62 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Scene/Components/CameraComponent.h>
+#include <AnKi/Scene/Components/MoveComponent.h>
+#include <AnKi/Scene/SceneNode.h>
+
+namespace anki {
+
+CameraComponent::CameraComponent(SceneNode* node)
+	: SceneComponent(node, getStaticClassId())
+{
+	m_frustum.init(FrustumType::kPerspective, &node->getMemoryPool());
+}
+
+CameraComponent::~CameraComponent()
+{
+}
+
+Error CameraComponent::update(SceneComponentUpdateInfo& info, Bool& updated)
+{
+	if(m_moveComponent) [[likely]]
+	{
+		updated = m_frustum.update(m_moveComponent->wasDirtyThisFrame(), m_moveComponent->getWorldTransform());
+	}
+	else
+	{
+		updated = m_frustum.update(true, Transform::getIdentity());
+	}
+
+	return Error::kNone;
+}
+
+void CameraComponent::onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added)
+{
+	if(other->getClassId() != MoveComponent::getStaticClassId())
+	{
+		return;
+	}
+
+	MoveComponent* movec = static_cast<MoveComponent*>(other);
+	if(added)
+	{
+		m_moveComponent = movec;
+	}
+	else if(m_moveComponent == movec)
+	{
+		m_moveComponent = nullptr;
+	}
+}
+
+void CameraComponent::fillCoverage(void* userData, F32* depthValues, U32 width, U32 height)
+{
+	ANKI_ASSERT(userData && depthValues && width > 0 && height > 0);
+
+	CameraComponent& self = *static_cast<CameraComponent*>(userData);
+	self.m_frustum.setCoverageBuffer(depthValues, width, height);
+}
+
+} // end namespace anki

+ 100 - 0
AnKi/Scene/Components/CameraComponent.h

@@ -0,0 +1,100 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Scene/Components/SceneComponent.h>
+#include <AnKi/Scene/Frustum.h>
+
+namespace anki {
+
+/// @addtogroup scene
+/// @{
+
+/// Perspective camera component.
+class CameraComponent : public SceneComponent
+{
+	ANKI_SCENE_COMPONENT(CameraComponent)
+
+public:
+	CameraComponent(SceneNode* node);
+
+	~CameraComponent();
+
+	void setNear(F32 near)
+	{
+		if(ANKI_SCENE_ASSERT(near > 0.0f))
+		{
+			m_frustum.setNear(near);
+		}
+	}
+
+	F32 getNear() const
+	{
+		return m_frustum.getNear();
+	}
+
+	void setFar(F32 far)
+	{
+		if(ANKI_SCENE_ASSERT(far > 0.0f))
+		{
+			m_frustum.setFar(far);
+		}
+	}
+
+	F32 getFar() const
+	{
+		return m_frustum.getFar();
+	}
+
+	void setFovX(F32 fovx)
+	{
+		if(ANKI_SCENE_ASSERT(fovx > 0.0f && fovx < kPi))
+		{
+			m_frustum.setFovX(fovx);
+		}
+	}
+
+	F32 getFovX() const
+	{
+		return m_frustum.getFovX();
+	}
+
+	void setFovY(F32 fovy)
+	{
+		if(ANKI_SCENE_ASSERT(fovy > 0.0f && fovy < kPi))
+		{
+			m_frustum.setFovY(fovy);
+		}
+	}
+
+	F32 getFovY() const
+	{
+		return m_frustum.getFovY();
+	}
+
+	const Frustum& getFrustum() const
+	{
+		return m_frustum;
+	}
+
+	Frustum& getFrustum()
+	{
+		return m_frustum;
+	}
+
+	static void fillCoverage(void* userData, F32* depthValues, U32 width, U32 height);
+
+private:
+	Frustum m_frustum;
+	MoveComponent* m_moveComponent = nullptr;
+
+	Error update(SceneComponentUpdateInfo& info, Bool& updated);
+
+	void onOtherComponentRemovedOrAdded(SceneComponent* other, Bool added);
+};
+/// @}
+
+} // end namespace anki

+ 0 - 2
AnKi/Scene/Components/DecalComponent.cpp

@@ -10,8 +10,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(DecalComponent, 100.0f)
-
 DecalComponent::DecalComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/FogDensityComponent.cpp

@@ -7,6 +7,4 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(FogDensityComponent, 100.0f)
-
 } // end namespace anki

+ 0 - 2
AnKi/Scene/Components/FrustumComponent.cpp

@@ -9,8 +9,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(FrustumComponent, -1.0f)
-
 FrustumComponent::FrustumComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/GenericGpuComputeJobComponent.cpp

@@ -7,6 +7,4 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(GenericGpuComputeJobComponent, -1.0f)
-
 } // end namespace anki

+ 0 - 2
AnKi/Scene/Components/GlobalIlluminationProbeComponent.cpp

@@ -11,8 +11,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(GlobalIlluminationProbeComponent, 100.0f)
-
 GlobalIlluminationProbeComponent::GlobalIlluminationProbeComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/GpuParticleEmitterComponent.cpp

@@ -11,8 +11,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(GpuParticleEmitterComponent, 40.0f)
-
 GpuParticleEmitterComponent::GpuParticleEmitterComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/JointComponent.cpp

@@ -10,8 +10,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(JointComponent, 20.0f)
-
 class JointComponent::JointNode : public IntrusiveListEnabled<JointNode>
 {
 public:

+ 0 - 2
AnKi/Scene/Components/LensFlareComponent.cpp

@@ -10,8 +10,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(LensFlareComponent, 100.0f)
-
 LensFlareComponent::LensFlareComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/LightComponent.cpp

@@ -15,8 +15,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(LightComponent, 100.0f)
-
 LightComponent::LightComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/ModelComponent.cpp

@@ -13,8 +13,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(ModelComponent, 40.0f)
-
 ModelComponent::ModelComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/MoveComponent.cpp

@@ -9,8 +9,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(MoveComponent, 30.0f)
-
 MoveComponent::MoveComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_ignoreLocalTransform(false)

+ 0 - 2
AnKi/Scene/Components/ParticleEmitterComponent.cpp

@@ -17,8 +17,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(ParticleEmitterComponent, 40.0f)
-
 static Vec3 getRandom(const Vec3& min, const Vec3& max)
 {
 	Vec3 out;

+ 0 - 2
AnKi/Scene/Components/PlayerControllerComponent.cpp

@@ -10,8 +10,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(PlayerControllerComponent, 10.0f)
-
 PlayerControllerComponent::PlayerControllerComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 {

+ 0 - 2
AnKi/Scene/Components/ReflectionProbeComponent.cpp

@@ -11,8 +11,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(ReflectionProbeComponent, 100.0f)
-
 ReflectionProbeComponent::ReflectionProbeComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 61 - 0
AnKi/Scene/Components/SceneComponentClasses.defs.h

@@ -0,0 +1,61 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#if !defined(ANKI_SCENE_COMPONENT_SEPERATOR)
+#	define ANKI_SCENE_COMPONENT_SEPERATOR
+#endif
+
+ANKI_DEFINE_SCENE_COMPONENT(Frustum, -1.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(GenericGpuComputeJob, -1.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Spatial, -1.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Script, 0.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Body, 10.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(PlayerController, 10.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Joint, 10.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Move, 30.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Skin, 30.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Trigger, 40.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+
+ANKI_DEFINE_SCENE_COMPONENT(Model, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(ParticleEmitter, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Decal, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Camera, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(FogDensity, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(GlobalIlluminationProbe, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(ReflectionProbe, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Skybox, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Ui, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(GpuParticleEmitter, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(LensFlare, 100.0f)
+ANKI_SCENE_COMPONENT_SEPERATOR
+ANKI_DEFINE_SCENE_COMPONENT(Light, 100.0f)
+
+#undef ANKI_DEFINE_SCENE_COMPONENT
+#undef ANKI_SCENE_COMPONENT_SEPERATOR

+ 35 - 0
AnKi/Scene/Components/SceneComponentStatics.cpp

@@ -0,0 +1,35 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Scene/Components/BodyComponent.h>
+#include <AnKi/Scene/Components/CameraComponent.h>
+#include <AnKi/Scene/Components/DecalComponent.h>
+#include <AnKi/Scene/Components/FogDensityComponent.h>
+#include <AnKi/Scene/Components/FrustumComponent.h>
+#include <AnKi/Scene/Components/GenericGpuComputeJobComponent.h>
+#include <AnKi/Scene/Components/GlobalIlluminationProbeComponent.h>
+#include <AnKi/Scene/Components/GpuParticleEmitterComponent.h>
+#include <AnKi/Scene/Components/JointComponent.h>
+#include <AnKi/Scene/Components/LensFlareComponent.h>
+#include <AnKi/Scene/Components/LightComponent.h>
+#include <AnKi/Scene/Components/ModelComponent.h>
+#include <AnKi/Scene/Components/MoveComponent.h>
+#include <AnKi/Scene/Components/ParticleEmitterComponent.h>
+#include <AnKi/Scene/Components/PlayerControllerComponent.h>
+#include <AnKi/Scene/Components/ReflectionProbeComponent.h>
+#include <AnKi/Scene/Components/ScriptComponent.h>
+#include <AnKi/Scene/Components/SkinComponent.h>
+#include <AnKi/Scene/Components/SkyboxComponent.h>
+#include <AnKi/Scene/Components/SpatialComponent.h>
+#include <AnKi/Scene/Components/TriggerComponent.h>
+#include <AnKi/Scene/Components/UiComponent.h>
+
+namespace anki {
+
+#define ANKI_DEFINE_SCENE_COMPONENT(className, updateOrder) \
+	ANKI_SCENE_COMPONENT_STATICS(className##Component, updateOrder)
+#include <AnKi/Scene/Components/SceneComponentClasses.defs.h>
+
+} // end namespace anki

+ 0 - 2
AnKi/Scene/Components/ScriptComponent.cpp

@@ -12,8 +12,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(ScriptComponent, 0.0f)
-
 ScriptComponent::ScriptComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/SkinComponent.cpp

@@ -13,8 +13,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(SkinComponent, 45.0f)
-
 SkinComponent::SkinComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/SkyboxComponent.cpp

@@ -12,8 +12,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(SkyboxComponent, 100.0f)
-
 SkyboxComponent::SkyboxComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/SpatialComponent.cpp

@@ -9,8 +9,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(SpatialComponent, 50.0f)
-
 SpatialComponent::SpatialComponent(SceneNode* node)
 	: SceneComponent(node, getStaticClassId())
 	, m_node(node)

+ 0 - 2
AnKi/Scene/Components/TriggerComponent.cpp

@@ -11,8 +11,6 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(TriggerComponent, 40.0f)
-
 /// The callbacks execute before the TriggerComponent::update
 class TriggerComponent::MyPhysicsTriggerProcessContactCallback final : public PhysicsTriggerProcessContactCallback
 {

+ 0 - 2
AnKi/Scene/Components/UiComponent.cpp

@@ -7,6 +7,4 @@
 
 namespace anki {
 
-ANKI_SCENE_COMPONENT_STATICS(UiComponent, 100.0f)
-
 } // end namespace anki

+ 2 - 0
AnKi/Scene/Events/AnimationEvent.h

@@ -3,6 +3,8 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
+#pragma once
+
 #include <AnKi/Scene/Events/Event.h>
 #include <AnKi/Resource/AnimationResource.h>
 

+ 206 - 0
AnKi/Scene/Frustum.cpp

@@ -0,0 +1,206 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Scene/Frustum.h>
+#include <AnKi/Collision/Functions.h>
+
+namespace anki {
+
+Frustum::Frustum()
+{
+	// Set some default values
+	init(FrustumType::kPerspective, nullptr);
+	for(U i = 0; i < m_maxLodDistances.getSize(); ++i)
+	{
+		const F32 dist = (m_common.m_far - m_common.m_near) / F32(kMaxLodCount + 1);
+		m_maxLodDistances[i] = m_common.m_near + dist * F32(i + 1);
+	}
+
+	update(true, Transform::getIdentity());
+}
+
+Frustum::~Frustum()
+{
+	m_depthMap.destroy(*m_pool);
+}
+
+void Frustum::init(FrustumType type, HeapMemoryPool* pool)
+{
+	ANKI_ASSERT(type < FrustumType::kCount);
+	m_pool = pool;
+	m_frustumType = type;
+	setNear(kDefaultNear);
+	setFar(kDefaultFar);
+	if(m_frustumType == FrustumType::kPerspective)
+	{
+		setFovX(kDefaultFovAngle);
+		setFovY(kDefaultFovAngle);
+	}
+	else
+	{
+		setLeft(-5.0f);
+		setRight(5.0f);
+		setBottom(-1.0f);
+		setTop(1.0f);
+	}
+}
+
+Bool Frustum::update(Bool worldTransformUpdated, const Transform& worldTransform)
+{
+	Bool updated = false;
+
+	for(U32 i = kPrevMatrixHistory - 1; i != 0; --i)
+	{
+		m_prevViewProjMats[i] = m_prevViewProjMats[i - 1];
+		m_prevViewMats[i] = m_prevViewMats[i - 1];
+		m_prevProjMats[i] = m_prevProjMats[i - 1];
+	}
+
+	m_prevViewProjMats[0] = m_viewProjMat;
+	m_prevViewMats[0] = m_viewMat;
+	m_prevProjMats[0] = m_projMat;
+
+	// Update the shape
+	if(m_shapeMarkedForUpdate)
+	{
+		updated = true;
+
+		if(m_frustumType == FrustumType::kPerspective)
+		{
+			m_projMat = Mat4::calculatePerspectiveProjectionMatrix(m_perspective.m_fovX, m_perspective.m_fovY,
+																   m_perspective.m_near, m_perspective.m_far);
+
+			computeEdgesOfFrustum(m_perspective.m_far, m_perspective.m_fovX, m_perspective.m_fovY,
+								  &m_perspective.m_edgesL[0]);
+
+			// Planes
+			F32 c, s; // cos & sine
+
+			sinCos(kPi + m_perspective.m_fovX / 2.0f, s, c);
+			// right
+			m_viewPlanesL[FrustumPlaneType::kRight] = Plane(Vec4(c, 0.0f, s, 0.0f), 0.0f);
+			// left
+			m_viewPlanesL[FrustumPlaneType::kLeft] = Plane(Vec4(-c, 0.0f, s, 0.0f), 0.0f);
+
+			sinCos((kPi + m_perspective.m_fovY) * 0.5f, s, c);
+			// bottom
+			m_viewPlanesL[FrustumPlaneType::kBottom] = Plane(Vec4(0.0f, s, c, 0.0f), 0.0f);
+			// top
+			m_viewPlanesL[FrustumPlaneType::kTop] = Plane(Vec4(0.0f, -s, c, 0.0f), 0.0f);
+
+			// near
+			m_viewPlanesL[FrustumPlaneType::kNear] = Plane(Vec4(0.0f, 0.0f, -1.0, 0.0f), m_perspective.m_near);
+			// far
+			m_viewPlanesL[FrustumPlaneType::kFar] = Plane(Vec4(0.0f, 0.0f, 1.0, 0.0f), -m_perspective.m_far);
+		}
+		else
+		{
+			m_projMat = Mat4::calculateOrthographicProjectionMatrix(m_ortho.m_right, m_ortho.m_left, m_ortho.m_top,
+																	m_ortho.m_bottom, m_ortho.m_near, m_ortho.m_far);
+
+			// OBB
+			const Vec4 c((m_ortho.m_right + m_ortho.m_left) * 0.5f, (m_ortho.m_top + m_ortho.m_bottom) * 0.5f,
+						 -(m_ortho.m_far + m_ortho.m_near) * 0.5f, 0.0f);
+			const Vec4 e = Vec4(m_ortho.m_right, m_ortho.m_top, -m_ortho.m_far, 0.0f) - c;
+
+			m_ortho.m_obbL = Obb(c, Mat3x4::getIdentity(), e);
+
+			// Planes
+			m_viewPlanesL[FrustumPlaneType::kLeft] = Plane(Vec4(1.0f, 0.0f, 0.0f, 0.0f), m_ortho.m_left);
+			m_viewPlanesL[FrustumPlaneType::kRight] = Plane(Vec4(-1.0f, 0.0f, 0.0f, 0.0f), -m_ortho.m_right);
+			m_viewPlanesL[FrustumPlaneType::kNear] = Plane(Vec4(0.0f, 0.0f, -1.0f, 0.0f), m_ortho.m_near);
+			m_viewPlanesL[FrustumPlaneType::kFar] = Plane(Vec4(0.0f, 0.0f, 1.0f, 0.0f), -m_ortho.m_far);
+			m_viewPlanesL[FrustumPlaneType::kTop] = Plane(Vec4(0.0f, -1.0f, 0.0f, 0.0f), -m_ortho.m_top);
+			m_viewPlanesL[FrustumPlaneType::kBottom] = Plane(Vec4(0.0f, 1.0f, 0.0f, 0.0f), m_ortho.m_bottom);
+		}
+	}
+
+	// Update transform related things
+	if(worldTransformUpdated)
+	{
+		updated = true;
+		m_viewMat = Mat3x4(worldTransform.getInverse());
+	}
+
+	// Fixup the misc data
+	if(m_miscMarkedForUpdate)
+	{
+		updated = true;
+		const F32 frustumFraction = (m_common.m_far - m_common.m_near) / 100.0f;
+
+		for(U32 i = 0; i < m_shadowCascadeCount; ++i)
+		{
+			if(m_shadowCascadeDistances[i] <= m_common.m_near || m_shadowCascadeDistances[i] > m_common.m_far)
+			{
+				m_shadowCascadeDistances[i] =
+					clamp(m_shadowCascadeDistances[i], m_common.m_near + kEpsilonf, m_common.m_far);
+			}
+
+			if(i != 0 && m_shadowCascadeDistances[i - 1] > m_shadowCascadeDistances[i])
+			{
+				m_shadowCascadeDistances[i] = m_shadowCascadeDistances[i - 1] + frustumFraction;
+			}
+		}
+
+		for(U32 i = 0; i < m_maxLodDistances.getSize(); ++i)
+		{
+			if(m_maxLodDistances[i] <= m_common.m_near || m_maxLodDistances[i] > m_common.m_far)
+			{
+				m_maxLodDistances[i] = clamp(m_maxLodDistances[i], m_common.m_near + kEpsilonf, m_common.m_far);
+			}
+
+			if(i != 0 && m_maxLodDistances[i - 1] > m_maxLodDistances[i])
+			{
+				m_maxLodDistances[i] = m_maxLodDistances[i - 1] + frustumFraction;
+			}
+		}
+	}
+
+	// Updates that are affected by transform & shape updates
+	if(updated)
+	{
+		m_viewProjMat = m_projMat * Mat4(m_viewMat, Vec4(0.0f, 0.0f, 0.0f, 1.0f));
+		m_shapeMarkedForUpdate = false;
+		m_miscMarkedForUpdate = false;
+
+		if(m_frustumType == FrustumType::kPerspective)
+		{
+			m_perspective.m_edgesW[0] = worldTransform.getOrigin();
+			m_perspective.m_edgesW[1] = worldTransform.transform(m_perspective.m_edgesL[0]);
+			m_perspective.m_edgesW[2] = worldTransform.transform(m_perspective.m_edgesL[1]);
+			m_perspective.m_edgesW[3] = worldTransform.transform(m_perspective.m_edgesL[2]);
+			m_perspective.m_edgesW[4] = worldTransform.transform(m_perspective.m_edgesL[3]);
+
+			m_perspective.m_hull = ConvexHullShape(&m_perspective.m_edgesW[0], m_perspective.m_edgesW.getSize());
+		}
+		else
+		{
+			m_ortho.m_obbW = m_ortho.m_obbL.getTransformed(worldTransform);
+		}
+
+		for(FrustumPlaneType planeId : EnumIterable<FrustumPlaneType>())
+		{
+			m_viewPlanesW[planeId] = m_viewPlanesL[planeId].getTransformed(worldTransform);
+		}
+	}
+
+	return updated;
+}
+
+void Frustum::setCoverageBuffer(F32* depths, U32 width, U32 height)
+{
+	const U32 elemCount = width * height;
+	if(m_depthMap.getSize() != elemCount) [[unlikely]]
+	{
+		m_depthMap.resize(*m_pool, elemCount);
+	}
+
+	if(depths && elemCount > 0) [[likely]]
+	{
+		memcpy(m_depthMap.getBegin(), depths, elemCount * sizeof(F32));
+	}
+}
+
+} // end namespace anki

+ 364 - 0
AnKi/Scene/Frustum.h

@@ -0,0 +1,364 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Scene/Common.h>
+#include <AnKi/Collision/Obb.h>
+#include <AnKi/Collision/ConvexHullShape.h>
+#include <AnKi/Collision/Plane.h>
+
+namespace anki {
+
+/// @addtogroup scene
+/// @{
+
+/// TODO
+class Frustum
+{
+public:
+	Frustum();
+
+	~Frustum();
+
+	void init(FrustumType type, HeapMemoryPool* pool);
+
+	void setPerspective(F32 near, F32 far, F32 fovX, F32 fovY)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
+		setNear(near);
+		setFar(far);
+		setFovX(fovX);
+		setFovY(fovY);
+	}
+
+	void setOrthographic(F32 near, F32 far, F32 right, F32 left, F32 top, F32 bottom)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		setNear(near);
+		setFar(far);
+		setLeft(left);
+		setRight(right);
+		setTop(top);
+		setBottom(bottom);
+	}
+
+	FrustumType getFrustumType() const
+	{
+		ANKI_ASSERT(m_frustumType != FrustumType::kCount);
+		return m_frustumType;
+	}
+
+	void setNear(F32 near)
+	{
+		ANKI_ASSERT(near > kEpsilonf);
+		m_common.m_near = near;
+		m_shapeMarkedForUpdate = true;
+	}
+
+	F32 getNear() const
+	{
+		return m_common.m_near;
+	}
+
+	void setFar(F32 far)
+	{
+		ANKI_ASSERT(far > kEpsilonf);
+		m_common.m_far = far;
+		m_shapeMarkedForUpdate = true;
+	}
+
+	F32 getFar() const
+	{
+		return m_common.m_far;
+	}
+
+	void setFovX(F32 fovx)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective && fovx > 0.0f && fovx < kPi);
+		m_shapeMarkedForUpdate = true;
+		m_perspective.m_fovX = fovx;
+	}
+
+	F32 getFovX() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
+		return m_perspective.m_fovX;
+	}
+
+	void setFovY(F32 fovy)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective && fovy > 0.0f && fovy < kPi);
+		m_shapeMarkedForUpdate = true;
+		m_perspective.m_fovY = fovy;
+	}
+
+	F32 getFovY() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
+		return m_perspective.m_fovY;
+	}
+
+	F32 getLeft() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		return m_ortho.m_left;
+	}
+
+	void setLeft(F32 value)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		m_shapeMarkedForUpdate = true;
+		m_ortho.m_left = value;
+	}
+
+	F32 getRight() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		return m_ortho.m_right;
+	}
+
+	void setRight(F32 value)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		m_shapeMarkedForUpdate = true;
+		m_ortho.m_right = value;
+	}
+
+	F32 getTop() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		return m_ortho.m_top;
+	}
+
+	void setTop(F32 value)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		m_shapeMarkedForUpdate = true;
+		m_ortho.m_top = value;
+	}
+
+	F32 getBottom() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		return m_ortho.m_bottom;
+	}
+
+	void setBottom(F32 value)
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		m_shapeMarkedForUpdate = true;
+		m_ortho.m_bottom = value;
+	}
+
+	const Mat4& getProjectionMatrix() const
+	{
+		return m_projMat;
+	}
+
+	const Mat3x4& getViewMatrix() const
+	{
+		return m_viewMat;
+	}
+
+	const Mat4& getViewProjectionMatrix() const
+	{
+		return m_viewProjMat;
+	}
+
+	/// @param nMinusOneFrame The number of the previous frame. If 0 means the previous frame, 1 means the
+	///                       previous-previous frame.
+	const Mat4& getPreviousViewProjectionMatrix(U32 nMinusOneFrame = 0) const
+	{
+		return m_prevViewProjMats[nMinusOneFrame];
+	}
+
+	/// @see getPreviousViewProjectionMatrix.
+	const Mat3x4& getPreviousViewMatrix(U32 nMinusOneFrame = 0) const
+	{
+		return m_prevViewMats[nMinusOneFrame];
+	}
+
+	/// @see getPreviousViewProjectionMatrix.
+	const Mat4& getPreviousProjectionMatrix(U32 nMinusOneFrame = 0) const
+	{
+		return m_prevProjMats[nMinusOneFrame];
+	}
+
+	/// Check if a shape is inside the frustum.
+	template<typename T>
+	Bool insideFrustum(const T& t) const
+	{
+		for(const Plane& plane : m_viewPlanesW)
+		{
+			if(testPlane(plane, t) < 0.0f)
+			{
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	Bool hasCoverageBuffer() const
+	{
+		return m_depthMap.getSize() > 0;
+	}
+
+	void getCoverageBufferInfo(ConstWeakArray<F32>& depthBuff, U32& width, U32& height) const
+	{
+		if(m_depthMap.getSize() > 0)
+		{
+			depthBuff = ConstWeakArray<F32>(&m_depthMap[0], m_depthMap.getSize());
+			width = m_depthMapWidth;
+			height = m_depthMapHeight;
+		}
+		else
+		{
+			depthBuff = ConstWeakArray<F32>();
+			width = height = 0;
+		}
+	}
+
+	void setCoverageBuffer(F32* depths, U32 width, U32 height);
+
+	void setShadowCascadeDistance(U32 cascade, F32 distance)
+	{
+		m_shadowCascadeDistances[cascade] = distance;
+		m_miscMarkedForUpdate = true;
+	}
+
+	F32 getShadowCascadeDistance(U32 cascade) const
+	{
+		return m_shadowCascadeDistances[cascade];
+	}
+
+	[[nodiscard]] U32 getShadowCascadeCount() const
+	{
+		return m_shadowCascadeCount;
+	}
+
+	void setShadowCascadeCount(U32 count)
+	{
+		ANKI_ASSERT(count <= kMaxShadowCascades);
+		m_shadowCascadeCount = U8(count);
+		m_miscMarkedForUpdate = true;
+	}
+
+	const ConvexHullShape& getPerspectiveBoundingShapeWorldSpace() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kPerspective);
+		return m_perspective.m_hull;
+	}
+
+	const Obb& getOrthographicBoundingShapeWorldSpace() const
+	{
+		ANKI_ASSERT(m_frustumType == FrustumType::kOrthographic);
+		return m_ortho.m_obbW;
+	}
+
+	const Array<Plane, U32(FrustumPlaneType::kCount)>& getViewPlanes() const
+	{
+		return m_viewPlanesW;
+	}
+
+	/// Set the lod distance. It doesn't interact with the far plane.
+	void setLodDistance(U32 lod, F32 maxDistance)
+	{
+		m_maxLodDistances[lod] = maxDistance;
+		m_miscMarkedForUpdate = true;
+	}
+
+	void setLodDistances(const Array<F32, kMaxLodCount>& distances)
+	{
+		for(U i = 0; i < m_maxLodDistances.getSize(); ++i)
+		{
+			ANKI_ASSERT(distances[i] > m_common.m_near && distances[i] <= m_common.m_far);
+			m_maxLodDistances[i] = distances[i];
+		}
+	}
+
+	/// See setLodDistance.
+	F32 getLodDistance(U32 lod) const
+	{
+		ANKI_ASSERT(m_maxLodDistances[lod] > 0.0f);
+		return m_maxLodDistances[lod];
+	}
+
+	Bool update(Bool worldTransformUpdated, const Transform& worldTransform);
+
+private:
+	class Common
+	{
+	public:
+		F32 m_near;
+		F32 m_far;
+	};
+
+	class Perspective : public Common
+	{
+	public:
+		F32 m_fovX;
+		F32 m_fovY;
+		Array<Vec4, 5> m_edgesW;
+		Array<Vec4, 4> m_edgesL; ///< Don't need the eye point.
+		ConvexHullShape m_hull;
+	};
+
+	class Ortho : public Common
+	{
+	public:
+		F32 m_left;
+		F32 m_right;
+		F32 m_top;
+		F32 m_bottom;
+		Obb m_obbL;
+		Obb m_obbW; ///< Including shape
+	};
+
+	static constexpr F32 kDefaultNear = 0.1f;
+	static constexpr F32 kDefaultFar = 100.0f;
+	static constexpr F32 kDefaultFovAngle = toRad(45.0f);
+
+	HeapMemoryPool* m_pool = nullptr;
+
+	union
+	{
+		Perspective m_perspective;
+		Ortho m_ortho;
+		Common m_common;
+	};
+
+	// View planes
+	Array<Plane, U32(FrustumPlaneType::kCount)> m_viewPlanesL;
+	Array<Plane, U32(FrustumPlaneType::kCount)> m_viewPlanesW;
+
+	Mat4 m_projMat = Mat4::getIdentity(); ///< Projection matrix
+	Mat3x4 m_viewMat = Mat3x4::getIdentity(); ///< View matrix
+	Mat4 m_viewProjMat = Mat4::getIdentity(); ///< View projection matrix
+
+	static constexpr U32 kPrevMatrixHistory = 2;
+	Array<Mat3x4, kPrevMatrixHistory> m_prevViewMats = {Mat3x4::getIdentity(), Mat3x4::getIdentity()};
+	Array<Mat4, kPrevMatrixHistory> m_prevProjMats = {Mat4::getIdentity(), Mat4::getIdentity()};
+	Array<Mat4, kPrevMatrixHistory> m_prevViewProjMats = {Mat4::getIdentity(), Mat4::getIdentity()};
+
+	Array<F32, kMaxShadowCascades> m_shadowCascadeDistances = {};
+	Array<F32, kMaxLodCount - 1> m_maxLodDistances = {};
+
+	// Coverage buffer stuff
+	DynamicArray<F32> m_depthMap;
+	U32 m_depthMapWidth = 0;
+	U32 m_depthMapHeight = 0;
+
+	FrustumType m_frustumType = FrustumType::kCount;
+
+	U8 m_shadowCascadeCount = 0;
+
+	Bool m_shapeMarkedForUpdate : 1 = true;
+	Bool m_miscMarkedForUpdate : 1 = true;
+};
+/// @}
+
+} // end namespace anki