From be452ab3f1e9ff6cf131b5edac100237cbcc2107 Mon Sep 17 00:00:00 2001 From: daniel-mcclintock <13973653+daniel-mcclintock@users.noreply.github.com> Date: Sun, 7 Feb 2021 21:54:40 +1100 Subject: [PATCH] Add GDScript "docstrings" - see: https://github.com/godotengine/godot/pull/41095 --- octree.gd | 87 +++++++++++++++++++++++++++++++------------------- octree_node.gd | 79 +++++++++++++++++++++++---------------------- 2 files changed, 96 insertions(+), 70 deletions(-) diff --git a/octree.gd b/octree.gd index 6741358..a99ba45 100644 --- a/octree.gd +++ b/octree.gd @@ -1,24 +1,28 @@ class_name Octree extends Reference -# Top level Octree Node wrapper -# It provides some convenience when working with heavy Nodes. -# - Node structure is split 2 level deep (8*8) for easier threaded searches and insertions. -# - Matching Node Mutexes are created to ease in threaded searching -# - Search wrapper functions are provided +## OctreeNode wrapper for spatially storing data. +## +## @desc: +## This class is effectively a wrapper around OctreeNode, it provides an abstraction over +## OctreeNode that allows for easier searches and insertions from multiple Threads. +## The main mechanism by which this is provided is by instantiating the top-level OctreeNode +## pre-subdivided and providing Mutexes for the `top-level` OctreeNodes during insertions. -# The actual top-level OctreeNode -var _octree : OctreeNode +## The effective top-level OctreeNodes, +var _octant_nodes: Array -# The effectively 8*8 top-level OctreeNodes -var _octant_nodes : Array = [] -var _octant_mutexes : Array = [] +## The top-level OctreeNode Mutexes used during insertions. +var _octant_mutexes: Array -func _init(center: Vector3 = Vector3.ZERO, size: float = 1.0, max_items: int = 1000): - _create_storage(size, center, max_items) +## The actual top-level OctreeNode. +var _octree: OctreeNode -func _create_storage(size: float, center: Vector3, max_items: int) -> void: - # Create Node storage that is split 2 level deep (8*8) - # Create Mutexes for threaded searching and insertions +## OctreeSearcher instance configured for relatively "efficient" searching from a ThreadQueue. +var _searcher: OctreeSearcher + + +## Create a new Octree instance +func _init(center := Vector3.ZERO, size := 1.0, max_items := 1000): _octant_mutexes = [] _octant_nodes = [] @@ -43,25 +47,22 @@ func _create_storage(size: float, center: Vector3, max_items: int) -> void: ] ) -func _get_flat_storage_array() -> Array: - # Just to make it easier to use the searcher - var nodes := [] - for _octant_node_array in _octant_nodes: - nodes += _octant_node_array - return nodes +## Instantiates and returns an OctreeSearcher for this Octree. +func instantiate_searcher(thread_queue: ThreadQueue) -> OctreeSearcher: + _searcher = OctreeSearcher.new( + _get_flat_storage_array(), + _get_flat_mutex_array(), + thread_queue + ) -func _get_flat_mutex_array() -> Array: - var mutexes := [] - for _octant_mutex_array in _octant_mutexes: - mutexes += _octant_mutex_array + return _searcher - return mutexes +## Convenience method that returns the total number of items stored within the OctreeNodes func data_count() -> int: - # Convenience method to help query stored data size if not _octant_nodes.is_empty(): - var count = 0 + var count := 0 for octant_array in _octant_nodes: for node in octant_array: @@ -71,14 +72,36 @@ func data_count() -> int: return 0 -func add(position : Vector3, data) -> bool: - # Wrapper method around the underlying OctreeNode's Add method - # It *should* be safe to use this from Threads + +## Inserts data into the Octree at the given position.[br] +## [br]This is effectively a wrapper method around the underlying OctreeNodes's insert method. +## It is a bit messy, but it provides some convenience within this class by abstracting away +## management of Mutexes and allows naive insertions from Threads.[br] +## [br]It *should* be safe to use this from Threads +func insert(position: Vector3, data) -> bool: var octant_index_1 = OctreeNode._get_octant_index(position, _octree._center) var octant_index_2 = OctreeNode._get_octant_index( position, _octree._octant_nodes[octant_index_1]._center ) - return _octant_nodes[octant_index_1][octant_index_2].add( + return _octant_nodes[octant_index_1][octant_index_2].insert( position, data, _octant_mutexes[octant_index_1][octant_index_2] ) + + +## Returns a flat Array containing each of the effective top-level OctreeNodes +func _get_flat_storage_array() -> Array: + var nodes := [] + for _octant_node_array in _octant_nodes: + nodes += _octant_node_array + + return nodes + + +## Returns a flat Array containing Mutexes that accompany the OctreeNodes +func _get_flat_mutex_array() -> Array: + var mutexes := [] + for _octant_mutex_array in _octant_mutexes: + mutexes += _octant_mutex_array + + return mutexes diff --git a/octree_node.gd b/octree_node.gd index a543a47..84773ba 100644 --- a/octree_node.gd +++ b/octree_node.gd @@ -1,26 +1,40 @@ class_name OctreeNode extends Reference -# Shitty Octree for spatially storing data +## Shitty Octree implementation for spatially storing data. +## The center position of the OctreeNode volume. var _center : Vector3 + +## The OctreeNode volume size. var _size : float + +## The OctreeNode volume size, halved. var _half_size : float + +## An [AABB] instance for this OctreeNode, used for intersection math. +## This *might* be null if used externally. var _aabb +## A [Dictionary] used for storing the data within this OctreeNode, +## is empty when this OctreeNode is subdivided. var _data: Dictionary = {} -var _positions: Array = [] + +## An [Array] for storing this OctreeNode octant children, +## is empty until this OctreeNode is subdividied. var _octant_nodes : Array = [] -var _item_count : int = 0 +## The maximum number of items this OctreeNode can store before being subdividied. var max_items : int + func _init(center: Vector3 = Vector3.ZERO, size: float = 1.0, _max_items: int = 1000): max_items = _max_items - _center = center _size = size _half_size = _size * 0.5 + +## Helper method for recursively counting the number of items stored within this OctreeNode. func data_count() -> int: if not _octant_nodes.is_empty(): var count = 0 @@ -29,26 +43,24 @@ func data_count() -> int: return count else: - return _positions.size() + return _data.size() + +## Generate, store and return AABB for native intersection math func _get_aabb() -> AABB: - # Generate, store and return AABB for native intersection math if _aabb: return _aabb _aabb = AABB(_center - Vector3.ONE * _half_size, Vector3.ONE * _size) return _aabb -func check_bounds_point(vec: Vector3) -> bool: - return _get_aabb().has_point(vec) -func add(position : Vector3, data, mtx : Mutex) -> bool: - # Add data to this Node for a given Vector3 position - # - # Args: - # position: The position to store the given data for - # data: A object/variant to store in the Octree - # mtx: A Mutex to lock the Octree for threaded insertions +## Add data to this Node for a given Vector3 position +func insert(position : Vector3, data, mtx : Mutex) -> bool: + if not _get_aabb().has_point(position): + # Early exit if this position is not valid for this OctreeNode + return false + if not _octant_nodes.is_empty(): # We have children so offload to them mtx.lock() @@ -59,41 +71,37 @@ func add(position : Vector3, data, mtx : Mutex) -> bool: # Position is outside of Octree Node area return false - return node.add(position, data, mtx) + return node.insert(position, data, mtx) else: # Add this request payload to appropriate node # Store in _data mtx.lock() - if _positions.has(position): + if _data.has(position): # we already have this position mtx.unlock() return false - _positions.append(position) _data[position] = data - _item_count += 1 - if _item_count == max_items: + if _data.size() >= max_items: # Too much data, spawn octant nodes and shuffle data to them _create_octant_nodes() # Transfor data from this node into its octant nodes for key in _data.keys(): var node = _octant_nodes[_get_octant_index(position, _center)] - node._positions.append(position) node._data[position] = data - node._item_count += 1 _data.clear() - _positions.clear() - _item_count = 0 mtx.unlock() return true + +## Compute the index of this OctreeNode's _octant_nodes Array that would store the given position. +## the computed index aligns with the _octant_nodes Array order. static func _get_octant_index(position: Vector3, center: Vector3) -> int: - # Given a Vector3 position, determine which of this Node's octants would store that position var oct = 0 if position.x >= center.x: @@ -107,8 +115,9 @@ static func _get_octant_index(position: Vector3, center: Vector3) -> int: return oct + +## Create child Nodes for this Octree Node, produces 8 octant child Nodes. func _create_octant_nodes() -> void: - # Create child Nodes for this Octree Node, produces 8 octant child Nodes. var n_size = _half_size var n_hsize = n_size * 0.5 # @@ -133,15 +142,11 @@ func _create_octant_nodes() -> void: OctreeNode.new(n_center, n_size, max_items) ) + +## Given a ray (origin, direction), find all intersecting OctreeNodes. +## The grow argument will increase the size of the underlying AABB's for more generous ray +## intersections, this is useful for volume "ray" hits. func _ray_nodes(origin: Vector3, direction: Vector3, grow: float = 0.0) -> Array: - # Given a ray (origin, direction) find all intersecting octree nodes. - # - # Args: - # origin: The start position of the ray - # direction: The direction of the ray - # grow: Value to increase the size of each Node's AABB when picking ray intersections, - # this is useful when a given ray would exclude a Node that would otherwise include - # positions for a given search radius(see: searcher.gd) if _octant_nodes.is_empty(): # This node has no children and therefore stores data, return self if _get_aabb().grow(grow).intersects_ray(origin, direction): @@ -157,11 +162,9 @@ func _ray_nodes(origin: Vector3, direction: Vector3, grow: float = 0.0) -> Array return [] + +## Given a AABB, find all intersecting OctreeNodes. func _aabb_nodes(aabb: AABB) -> Array: - # Given an AABB, find all intersecting Octree nodes. - # - # Args: - # aabb: The AABB to find all intersecting Nodes for. if _octant_nodes.is_empty(): # This node has no children and therefore stores data, return self if _get_aabb().intersects(aabb):