Skip to content

Commit

Permalink
Add GDScript "docstrings"
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-mcclintock committed Feb 7, 2021
1 parent 05edaf7 commit be452ab
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 70 deletions.
87 changes: 55 additions & 32 deletions octree.gd
Original file line number Diff line number Diff line change
@@ -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 = []

Expand All @@ -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:

Expand All @@ -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
79 changes: 41 additions & 38 deletions octree_node.gd
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
Expand All @@ -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:
Expand All @@ -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
#
Expand All @@ -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):
Expand All @@ -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):
Expand Down

0 comments on commit be452ab

Please sign in to comment.