Browse Source

Octree is feature complete. Need to plug it into the visibility tests

Panagiotis Christopoulos Charitos 7 years ago
parent
commit
6a6a7e0a8c

+ 1 - 0
src/anki/Scene.h

@@ -19,6 +19,7 @@
 #include <anki/scene/PlayerNode.h>
 #include <anki/scene/OccluderNode.h>
 #include <anki/scene/DecalNode.h>
+#include <anki/scene/Octree.h>
 
 #include <anki/scene/components/MoveComponent.h>
 #include <anki/scene/components/RenderComponent.h>

+ 2 - 0
src/anki/collision/Frustum.cpp

@@ -210,6 +210,8 @@ Mat4 OrthographicFrustum::calculateProjectionMatrix() const
 
 void OrthographicFrustum::recalculate()
 {
+	ANKI_ASSERT(m_left < m_right && m_far < m_near && m_bottom < m_top);
+
 	// Planes
 	m_planesL[FrustumPlaneType::LEFT] = Plane(Vec4(1.0f, 0.0f, 0.0f, 0.0f), m_left);
 	m_planesL[FrustumPlaneType::RIGHT] = Plane(Vec4(-1.0f, 0.0f, 0.0f, 0.0f), -m_right);

+ 1 - 0
src/anki/collision/Frustum.h

@@ -308,6 +308,7 @@ public:
 	/// Set all
 	void setAll(F32 left, F32 right, F32 near, F32 far, F32 top, F32 bottom)
 	{
+		ANKI_ASSERT(left < right && far < near && bottom < top);
 		m_left = left;
 		m_right = right;
 		m_near = near;

+ 1 - 1
src/anki/scene/Common.h

@@ -28,7 +28,7 @@ class DecalComponent;
 
 /// The type of the scene's allocator
 template<typename T>
-using SceneAllocator = ChainAllocator<T>;
+using SceneAllocator = HeapAllocator<T>;
 
 /// The type of the scene's frame allocator
 template<typename T>

+ 107 - 20
src/anki/scene/Octree.cpp

@@ -6,10 +6,15 @@
 #include <anki/scene/Octree.h>
 #include <anki/collision/Tests.h>
 #include <anki/collision/Aabb.h>
+#include <anki/collision/Frustum.h>
 
 namespace anki
 {
 
+Octree::~Octree()
+{
+}
+
 void Octree::init(const Vec3& sceneAabbMin, const Vec3& sceneAabbMax, U32 maxDepth)
 {
 	ANKI_ASSERT(sceneAabbMin < sceneAabbMax);
@@ -20,47 +25,65 @@ void Octree::init(const Vec3& sceneAabbMin, const Vec3& sceneAabbMax, U32 maxDep
 	m_sceneAabbMax = sceneAabbMax;
 
 	m_rootLeaf = newLeaf();
+	m_rootLeaf->m_aabbMin = sceneAabbMin;
+	m_rootLeaf->m_aabbMax = sceneAabbMax;
 }
 
-void Octree::place(const Aabb& volume, OctreeHandle* handle)
+void Octree::place(const Aabb& volume, OctreePlaceable* placeable)
 {
 	ANKI_ASSERT(m_rootLeaf);
-	ANKI_ASSERT(handle);
+	ANKI_ASSERT(placeable);
+	ANKI_ASSERT(testCollisionShapes(
+					volume, Aabb(Vec4(Vec3(m_rootLeaf->m_aabbMin), 0.0f), Vec4(Vec3(m_rootLeaf->m_aabbMax), 0.0f)))
+				&& "volume is outside the scene");
 
-	// Remove the handle from the Octree...
-	// TODO
+	LockGuard<Mutex> lock(m_globalMtx);
+
+	// Remove the placeable from the Octree...
+	removeInternal(*placeable);
 
 	// .. and re-place it
-	placeRecursive(volume, handle, m_rootLeaf, m_sceneAabbMin, m_sceneAabbMax, 0);
+	placeRecursive(volume, placeable, m_rootLeaf, 0);
+}
+
+void Octree::remove(OctreePlaceable& placeable)
+{
+	LockGuard<Mutex> lock(m_globalMtx);
+	removeInternal(placeable);
 }
 
-void Octree::placeRecursive(
-	const Aabb& volume, OctreeHandle* handle, Leaf* parent, const Vec3& aabbMin, const Vec3& aabbMax, U32 depth)
+void Octree::placeRecursive(const Aabb& volume, OctreePlaceable* placeable, Leaf* parent, U32 depth)
 {
-	ANKI_ASSERT(handle);
+	ANKI_ASSERT(placeable);
 	ANKI_ASSERT(parent);
-	ANKI_ASSERT(testCollisionShapes(volume, Aabb(Vec4(aabbMin, 0.0f), Vec4(aabbMax, 0.0f))) && "Should be inside");
+	ANKI_ASSERT(testCollisionShapes(volume, Aabb(Vec4(parent->m_aabbMin, 0.0f), Vec4(parent->m_aabbMax, 0.0f)))
+				&& "Should be inside");
 
 	if(depth == m_maxDepth)
 	{
-		// Need to stop and bin the handle to the leaf
+		// Need to stop and bin the placeable to the leaf
 
 		// Checks
-		for(const LeafNode& node : handle->m_leafs)
+		for(const LeafNode& node : placeable->m_leafs)
 		{
 			ANKI_ASSERT(node.m_leaf != parent && "Already binned. That's wrong");
 		}
 
-		// Connect handle and leaf
-		handle->m_leafs.pushBack(newLeafNode(parent));
-		parent->m_handles.pushBack(newHandleNode(handle));
+		for(const PlaceableNode& node : parent->m_placeables)
+		{
+			ANKI_ASSERT(node.m_placeable != placeable);
+		}
+
+		// Connect placeable and leaf
+		placeable->m_leafs.pushBack(newLeafNode(parent));
+		parent->m_placeables.pushBack(newPlaceableNode(placeable));
 
 		return;
 	}
 
 	const Vec4& vMin = volume.getMin();
 	const Vec4& vMax = volume.getMax();
-	const Vec3 center = (aabbMax + aabbMin) / 2.0f;
+	const Vec3 center = (parent->m_aabbMax + parent->m_aabbMin) / 2.0f;
 
 	LeafMask maskX;
 	if(vMin.x() > center.x())
@@ -125,14 +148,19 @@ void Octree::placeRecursive(
 			if(parent->m_leafs[i] == nullptr)
 			{
 				parent->m_leafs[i] = newLeaf();
-			}
 
-			// Compute AABB
-			Vec3 childAabbMin, childAabbMax;
-			computeChildAabb(crntBit, aabbMin, aabbMax, center, childAabbMin, childAabbMax);
+				// Compute AABB
+				Vec3 childAabbMin, childAabbMax;
+				computeChildAabb(crntBit,
+					parent->m_aabbMin,
+					parent->m_aabbMax,
+					center,
+					parent->m_leafs[i]->m_aabbMin,
+					parent->m_leafs[i]->m_aabbMax);
+			}
 
 			// Move deeper
-			placeRecursive(volume, handle, parent->m_leafs[i], childAabbMin, childAabbMax, depth + 1);
+			placeRecursive(volume, placeable, parent->m_leafs[i], depth + 1);
 		}
 	}
 }
@@ -190,4 +218,63 @@ void Octree::computeChildAabb(LeafMask child,
 	}
 }
 
+void Octree::removeInternal(OctreePlaceable& placeable)
+{
+	// TODO Maybe remove some leafs
+
+	while(!placeable.m_leafs.isEmpty())
+	{
+		// Pop a leaf node
+		LeafNode& leafNode = placeable.m_leafs.getFront();
+		placeable.m_leafs.popFront();
+
+		// Iterate the placeables of the leaf
+		Bool found = false;
+		for(PlaceableNode& placeableNode : leafNode.m_leaf->m_placeables)
+		{
+			if(placeableNode.m_placeable == &placeable)
+			{
+				found = true;
+				leafNode.m_leaf->m_placeables.erase(&placeableNode);
+				releasePlaceableNode(&placeableNode);
+				break;
+			}
+		}
+		ANKI_ASSERT(found);
+
+		// Delete the leaf node
+		releaseLeafNode(&leafNode);
+	}
+}
+
+void Octree::gatherVisibleRecursive(
+	const Frustum& frustum, U32 testId, Leaf* leaf, DynamicArrayAuto<OctreePlaceable*>& out)
+{
+	ANKI_ASSERT(leaf);
+
+	// Add the placeables that belong to that leaf
+	for(PlaceableNode& placeableNode : leaf->m_placeables)
+	{
+		if(!placeableNode.m_placeable->alreadyVisited(testId))
+		{
+			out.emplaceBack(placeableNode.m_placeable);
+		}
+	}
+
+	// Move to children leafs
+	Aabb aabb;
+	for(Leaf* child : leaf->m_leafs)
+	{
+		if(child)
+		{
+			aabb.setMin(Vec4(child->m_aabbMin, 0.0f));
+			aabb.setMax(Vec4(child->m_aabbMax, 0.0f));
+			if(testCollisionShapes(frustum, aabb))
+			{
+				gatherVisibleRecursive(frustum, testId, child, out);
+			}
+		}
+	}
+}
+
 } // end namespace anki

+ 70 - 27
src/anki/scene/Octree.h

@@ -17,7 +17,7 @@ namespace anki
 {
 
 // Forward
-class OctreeHandle;
+class OctreePlaceable;
 
 /// @addtogroup scene
 /// @{
@@ -25,7 +25,7 @@ class OctreeHandle;
 /// Octree for visibility tests.
 class Octree
 {
-	friend class OctreeHandle;
+	friend class OctreePlaceable;
 
 public:
 	Octree(SceneAllocator<U8> alloc)
@@ -38,23 +38,33 @@ public:
 	void init(const Vec3& sceneAabbMin, const Vec3& sceneAabbMax, U32 maxDepth);
 
 	/// Place or re-place an element in the tree.
-	/// @note It's thread-safe.
-	void place(const Aabb& volume, OctreeHandle* handle);
+	/// @note It's thread-safe against place and remove methods.
+	void place(const Aabb& volume, OctreePlaceable* placeable);
 
 	/// Remove an element from the tree.
-	/// @note It's thread-safe.
-	void remove(OctreeHandle& handle);
+	/// @note It's thread-safe against place and remove methods.
+	void remove(OctreePlaceable& placeable);
 
-	/// Gather visible handles.
-	/// @note It's thread-safe.
-	DynamicArray<OctreeHandle*> gatherVisible(GenericMemoryPoolAllocator<U8> alloc, U32 testId);
+	/// Gather visible placeables.
+	/// @note It's thread-safe against other gatherVisible calls.
+	void gatherVisible(const Frustum& frustum, U32 testId, DynamicArrayAuto<OctreePlaceable*>& out)
+	{
+		gatherVisibleRecursive(frustum, testId, m_rootLeaf, out);
+	}
 
 private:
-	/// XXX
-	class HandleNode : public IntrusiveListEnabled<HandleNode>
+	/// List node.
+	class PlaceableNode : public IntrusiveListEnabled<PlaceableNode>
 	{
 	public:
-		OctreeHandle* m_handle = nullptr;
+		OctreePlaceable* m_placeable = nullptr;
+
+#if ANKI_ASSERTS_ENABLED
+		~PlaceableNode()
+		{
+			m_placeable = nullptr;
+		}
+#endif
 	};
 
 	/// Octree leaf.
@@ -62,15 +72,33 @@ private:
 	class Leaf
 	{
 	public:
-		IntrusiveList<HandleNode> m_handles;
+		IntrusiveList<PlaceableNode> m_placeables;
+		Vec3 m_aabbMin;
+		Vec3 m_aabbMax;
 		Array<Leaf*, 8> m_leafs = {};
+
+#if ANKI_ASSERTS_ENABLED
+		~Leaf()
+		{
+			ANKI_ASSERT(m_placeables.isEmpty());
+			m_leafs = {};
+			m_aabbMin = m_aabbMax = Vec3(0.0f);
+		}
+#endif
 	};
 
-	/// Used so that OctreeHandle knows which leafs it belongs to.
+	/// Used so that OctreePlaceable knows which leafs it belongs to.
 	class LeafNode : public IntrusiveListEnabled<LeafNode>
 	{
 	public:
 		Leaf* m_leaf = nullptr;
+
+#if ANKI_ASSERTS_ENABLED
+		~LeafNode()
+		{
+			m_leaf = nullptr;
+		}
+#endif
 	};
 
 	/// P: Stands for positive and N: Negative
@@ -100,10 +128,11 @@ private:
 	U32 m_maxDepth = 0;
 	Vec3 m_sceneAabbMin = Vec3(0.0f);
 	Vec3 m_sceneAabbMax = Vec3(0.0f);
+	Mutex m_globalMtx;
 
 	ObjectAllocatorSameType<Leaf, 256> m_leafAlloc;
 	ObjectAllocatorSameType<LeafNode, 128> m_leafNodeAlloc;
-	ObjectAllocatorSameType<HandleNode, 256> m_handleAlloc;
+	ObjectAllocatorSameType<PlaceableNode, 256> m_placeableNodeAlloc;
 
 	Leaf* m_rootLeaf = nullptr;
 
@@ -117,17 +146,17 @@ private:
 		m_leafAlloc.deleteInstance(m_alloc, leaf);
 	}
 
-	HandleNode* newHandleNode(OctreeHandle* handle)
+	PlaceableNode* newPlaceableNode(OctreePlaceable* placeable)
 	{
-		ANKI_ASSERT(handle);
-		HandleNode* out = m_handleAlloc.newInstance(m_alloc);
-		out->m_handle = handle;
+		ANKI_ASSERT(placeable);
+		PlaceableNode* out = m_placeableNodeAlloc.newInstance(m_alloc);
+		out->m_placeable = placeable;
 		return out;
 	}
 
-	void releaseHandle(HandleNode* handle)
+	void releasePlaceableNode(PlaceableNode* placeable)
 	{
-		m_handleAlloc.deleteInstance(m_alloc, handle);
+		m_placeableNodeAlloc.deleteInstance(m_alloc, placeable);
 	}
 
 	LeafNode* newLeafNode(Leaf* leaf)
@@ -143,19 +172,24 @@ private:
 		m_leafNodeAlloc.deleteInstance(m_alloc, node);
 	}
 
-	void placeRecursive(
-		const Aabb& volume, OctreeHandle* handle, Leaf* parent, const Vec3& aabbMin, const Vec3& aabbMax, U32 depth);
+	void placeRecursive(const Aabb& volume, OctreePlaceable* placeable, Leaf* parent, U32 depth);
 
-	void computeChildAabb(LeafMask child,
+	static void computeChildAabb(LeafMask child,
 		const Vec3& parentAabbMin,
 		const Vec3& parentAabbMax,
 		const Vec3& parentAabbCenter,
 		Vec3& childAabbMin,
 		Vec3& childAabbMax);
+
+	/// Remove a placeable from the tree.
+	void removeInternal(OctreePlaceable& placeable);
+
+	static void gatherVisibleRecursive(
+		const Frustum& frustum, U32 testId, Leaf* leaf, DynamicArrayAuto<OctreePlaceable*>& out);
 };
 
-/// XXX
-class OctreeHandle
+/// An entity that can be placed in octrees.
+class OctreePlaceable
 {
 	friend class Octree;
 
@@ -167,7 +201,16 @@ public:
 
 private:
 	Atomic<U64> m_visitedMask = {0u};
-	IntrusiveList<Octree::LeafNode> m_leafs; ///< A list of leafs this handle belongs.
+	IntrusiveList<Octree::LeafNode> m_leafs; ///< A list of leafs this placeable belongs.
+
+	/// Check if already visited.
+	/// @note It's thread-safe.
+	Bool alreadyVisited(U32 testId)
+	{
+		const U64 testMask = U64(1u) << U64(testId);
+		const U64 prev = m_visitedMask.fetchOr(testMask);
+		return !!(testMask & prev);
+	}
 };
 /// @}
 

+ 1 - 1
src/anki/scene/SceneGraph.cpp

@@ -83,7 +83,7 @@ Error SceneGraph::init(AllocAlignedCallback allocCb,
 	m_input = input;
 	m_scriptManager = scriptManager;
 
-	m_alloc = SceneAllocator<U8>(allocCb, allocCbData, 1024 * 10, 1.0, 0);
+	m_alloc = SceneAllocator<U8>(allocCb, allocCbData);
 	m_frameAlloc = SceneFrameAllocator<U8>(allocCb, allocCbData, 1 * 1024 * 1024);
 
 	m_earlyZDist = config.getNumber("scene.earlyZDistance");

+ 7 - 0
src/anki/util/DynamicArray.h

@@ -11,6 +11,10 @@
 namespace anki
 {
 
+// Forward
+template<typename T>
+class DynamicArrayAuto;
+
 /// @addtogroup util_containers
 /// @{
 
@@ -64,6 +68,9 @@ public:
 		return *this;
 	}
 
+	/// Move DynamicArrayAuto to this.
+	DynamicArray& operator=(DynamicArrayAuto<T>&& b);
+
 	// Non-copyable
 	DynamicArray& operator=(const DynamicArray& b) = delete;
 

+ 13 - 0
src/anki/util/DynamicArray.inl.h

@@ -8,6 +8,19 @@
 namespace anki
 {
 
+template<typename T>
+DynamicArray<T>& DynamicArray<T>::operator=(DynamicArrayAuto<T>&& b)
+{
+	ANKI_ASSERT(m_data == nullptr && m_size == 0 && "Cannot move before destroying");
+	T* data;
+	PtrSize size, storageSize;
+	b.moveAndReset(data, size, storageSize);
+	m_data = data;
+	m_size = size;
+	m_capacity = storageSize;
+	return *this;
+}
+
 template<typename T>
 template<typename TAllocator>
 void DynamicArray<T>::resizeStorage(TAllocator& alloc, PtrSize newSize)

+ 7 - 7
src/anki/util/ObjectAllocator.inl.h

@@ -38,24 +38,24 @@ T* ObjectAllocator<T_OBJECT_SIZE, T_OBJECT_ALIGNMENT, T_OBJECTS_PER_CHUNK, TInde
 		// Need to create a new chunk
 
 		// Create the chunk
-		Chunk* newChunk = alloc.template newInstance<Chunk>();
-		newChunk->m_unusedCount = OBJECTS_PER_CHUNK;
+		chunk = alloc.template newInstance<Chunk>();
+		chunk->m_unusedCount = OBJECTS_PER_CHUNK;
 
 		for(U i = 0; i < OBJECTS_PER_CHUNK; ++i)
 		{
-			newChunk->m_unusedStack[i] = OBJECTS_PER_CHUNK - (i + 1);
+			chunk->m_unusedStack[i] = OBJECTS_PER_CHUNK - (i + 1);
 		}
 
 		if(m_chunksTail)
 		{
 			ANKI_ASSERT(m_chunksHead);
-			newChunk->m_prev = m_chunksTail;
-			m_chunksTail->m_next = newChunk;
-			m_chunksTail = newChunk;
+			chunk->m_prev = m_chunksTail;
+			m_chunksTail->m_next = chunk;
+			m_chunksTail = chunk;
 		}
 		else
 		{
-			m_chunksTail = m_chunksHead = newChunk;
+			m_chunksTail = m_chunksHead = chunk;
 		}
 
 		// Allocate one object

+ 80 - 0
tests/scene/Octree.cpp

@@ -0,0 +1,80 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/scene/Octree.h>
+#include <anki/collision/Frustum.h>
+
+namespace anki
+{
+
+ANKI_TEST(Scene, Octree)
+{
+	HeapAllocator<U8> alloc(allocAligned, nullptr);
+
+	// Fuzzy
+	{
+		Octree octree(alloc);
+		octree.init(Vec3(-100.0f), Vec3(100.0f), 4);
+
+		OrthographicFrustum frustum(-200.0f, 200.0f, 200.0f, -200.0f, 200.0f, -200.0f);
+		frustum.resetTransform(Transform::getIdentity());
+
+		const U ITERATION_COUNT = 1000;
+		Array<OctreePlaceable, ITERATION_COUNT> placeables;
+		std::vector<U32> placed;
+		for(U i = 0; i < ITERATION_COUNT; ++i)
+		{
+			F32 min = randRange(-100.0f, 100.0f - 1.0f);
+			F32 max = randRange(min + 1.0f, 100.0f);
+			Aabb volume(Vec4(Vec3(min), 0.0f), Vec4(Vec3(max), 0.0f));
+
+			I mode = rand() % 3;
+			if(mode == 0)
+			{
+				// Place
+				octree.place(volume, &placeables[i]);
+				placed.push_back(i);
+			}
+			else if(mode == 1 && placed.size() > 0)
+			{
+				// Remove
+				octree.remove(placeables[placed.back()]);
+				placed.pop_back();
+			}
+			else if(placed.size() > 0)
+			{
+				// Gather
+				DynamicArrayAuto<OctreePlaceable*> arr(alloc);
+				octree.gatherVisible(frustum, 0, arr);
+
+				ANKI_TEST_EXPECT_EQ(arr.getSize(), placed.size());
+				for(U32 idx : placed)
+				{
+					Bool found = false;
+					for(OctreePlaceable* placeable : arr)
+					{
+						if(&placeables[idx] == placeable)
+						{
+							found = true;
+							break;
+						}
+					}
+
+					ANKI_TEST_EXPECT_EQ(found, true);
+				}
+			}
+		}
+
+		// Remove all
+		while(placed.empty())
+		{
+			octree.remove(placeables[placed.back()]);
+			placed.pop_back();
+		}
+	}
+}
+
+} // end namespace anki