Skip to content

Commit

Permalink
Use a partition hint system to divvy up nodes into the quadtree
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jun 29, 2024
1 parent 9999a3d commit 5a9c68a
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.VertexBufferLayout
import nz.net.ultraq.redhorizon.engine.resources.ResourceManager
import nz.net.ultraq.redhorizon.engine.scenegraph.GraphicsElement
import nz.net.ultraq.redhorizon.engine.scenegraph.Node
import nz.net.ultraq.redhorizon.engine.scenegraph.PartitionHint
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Primitive
import nz.net.ultraq.redhorizon.filetypes.ColourFormat
Expand Down Expand Up @@ -123,7 +124,9 @@ class Map extends Node<Map> {
addChild(new Units())
addChild(new Infantry())

addChild(new MapLines())
addChild(new MapLines().tap {
transform.translate(0, 0, 0.4)
})
}

@Override
Expand Down Expand Up @@ -248,6 +251,7 @@ class Map extends Node<Map> {
private class MapBackground extends Node<MapBackground> {

String name = "MapBackground - ${theater.label}"
final PartitionHint partitionHint = PartitionHint.LargeArea

MapBackground() {

Expand Down Expand Up @@ -286,6 +290,8 @@ class Map extends Node<Map> {
*/
private class MapPack extends Node<MapPack> implements GraphicsElement {

final PartitionHint partitionHint = PartitionHint.LargeArea

private final List<MapTile> mapTiles = []
private Mesh fullMesh
private Shader shader
Expand Down Expand Up @@ -409,6 +415,7 @@ class Map extends Node<Map> {
*/
private class OverlayPack extends Node<OverlayPack> {

final PartitionHint partitionHint = PartitionHint.DoNotParticipate
private int numTiles = 0

OverlayPack() {
Expand Down Expand Up @@ -479,6 +486,8 @@ class Map extends Node<Map> {
overlay.name = "${tile.name} - Variant ${imageVariant}"
overlay.frame = imageVariant
overlay.position = new Vector2f(tilePos).asWorldCoords(1)
overlay.partitionHint = PartitionHint.SmallArea

addChild(overlay)

numTiles++
Expand All @@ -497,6 +506,8 @@ class Map extends Node<Map> {
*/
private class Terrain extends Node<Terrain> {

final PartitionHint partitionHint = PartitionHint.DoNotParticipate

Terrain() {

var terrainData = mapFile.terrainData
Expand All @@ -507,6 +518,7 @@ class Map extends Node<Map> {
var terrain = new PalettedSprite(terrainFile)
terrain.name = "Terrain ${terrainType}"
terrain.position = cellPosXY
terrain.partitionHint = PartitionHint.SmallArea
addChild(terrain)
}
}
Expand All @@ -517,6 +529,8 @@ class Map extends Node<Map> {
*/
private abstract class FactionObjects<T extends FactionObjects, L extends ObjectLine> extends Node<T> {

final PartitionHint partitionHint = PartitionHint.DoNotParticipate

Unit createObject(L objectLine,
@ClosureParams(value = FromString, options = 'nz.net.ultraq.redhorizon.classic.units.Unit, nz.net.ultraq.redhorizon.classic.units.UnitData')
Closure configure) {
Expand Down Expand Up @@ -651,8 +665,9 @@ class Map extends Node<Map> {
return scene.requestCreateOrGet(new SpriteSheetRequest(bibWidth, bibHeight, ColourFormat.FORMAT_INDEXED, bibImageData))
})
bibSprite.name = "Bib"
bibSprite.setPosition(0, -TILE_HEIGHT)
structure.addChild(bibSprite, 0)
bibSprite.setPosition(0, -TILE_HEIGHT, -0.1)
bibSprite.partitionHint = PartitionHint.SmallArea
structure.addChild(bibSprite)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import nz.net.ultraq.redhorizon.classic.nodes.Rotatable
import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.SpriteSheetRequest
import nz.net.ultraq.redhorizon.engine.graphics.SpriteSheet
import nz.net.ultraq.redhorizon.engine.scenegraph.Node
import nz.net.ultraq.redhorizon.engine.scenegraph.PartitionHint
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene
import nz.net.ultraq.redhorizon.engine.scenegraph.Temporal
import nz.net.ultraq.redhorizon.filetypes.ImagesFile
Expand All @@ -45,6 +46,8 @@ class Unit extends Node<Unit> implements FactionColours, Rotatable, Temporal {
*/
static final String DEFAULT_STATE = "default"

final PartitionHint partitionHint = PartitionHint.SmallArea

// TODO: Should this type of file be renamed to better reflect its purpose?
final ImagesFile imagesFile
final UnitData unitData
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2023, Emanuel Rabina (http://www.ultraq.net.nz/)
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -94,6 +94,15 @@ class OpenGLTexture extends Texture {
}
}

/**
* A texture is also considered falsey if it has since been deleted.
*/
boolean asBoolean() {

return glIsTexture(textureId)
}


@Override
void bind(int textureUnit = -1) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ class OpenGLUniformBuffer extends UniformBuffer {
glBindBufferBase(GL_UNIFORM_BUFFER, blockIndex, bufferId)
}

/**
* A buffer is also considered falsey if it has been deleted.
*/
boolean asBoolean() {

return glIsBuffer(bufferId)
}

@Override
void close() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,8 @@ import nz.net.ultraq.redhorizon.engine.graphics.imgui.ChangeEvent
import nz.net.ultraq.redhorizon.engine.graphics.imgui.ImGuiLayer
import nz.net.ultraq.redhorizon.engine.input.InputEventStream
import nz.net.ultraq.redhorizon.engine.scenegraph.GraphicsElement
import nz.net.ultraq.redhorizon.engine.scenegraph.Node
import nz.net.ultraq.redhorizon.engine.scenegraph.Scene

import org.joml.FrustumIntersection
import org.joml.Matrix4f
import org.joml.primitives.Rectanglef
import org.slf4j.Logger
Expand Down Expand Up @@ -170,12 +168,8 @@ class RenderPipeline implements AutoCloseable {
private class SceneRenderPass implements RenderPass<Void> {

final Framebuffer framebuffer
private final boolean traverseScene = true
private final FrustumIntersection frustumIntersection = new FrustumIntersection()

Scene scene

// For object culling
private final List<GraphicsElement> visibleElements = []

SceneRenderPass(Framebuffer framebuffer) {
Expand All @@ -201,28 +195,11 @@ class RenderPipeline implements AutoCloseable {
// Cull the list of renderable items to those just visible in the scene
average('objectCulling', 1f, logger) { ->
visibleElements.clear()

if (traverseScene) {
frustumIntersection.set(camera.viewProjection, false)
scene.traverse { Node element ->
if (element.isVisible(frustumIntersection)) {
if (element instanceof GraphicsElement) {
element.update()
element.script?.update()
visibleElements << element
}
return true
}
return false
}
}
else {
scene.quadTree.query(camera.viewBox).each { Node element ->
if (element instanceof GraphicsElement) {
element.update()
element.script?.update()
visibleElements << element
}
scene.query(camera.viewBox.scale(1.1f)).each { element ->
if (element instanceof GraphicsElement) {
element.update()
element.script?.update()
visibleElements << element
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,14 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T> {
private final Vector3f globalPosition = new Vector3f()
private final Vector3f globalScale = new Vector3f(1, 1, 1)
private final Rectanglef globalBounds = new Rectanglef()
private PartitionHint partitionHint = null

/**
* Adds a child node to this node. If an index is specified, then this will
* shift any existing nodes at the given position to the right to make room.
* Adds a child node to this node.
*/
T addChild(Node child, int index = -1) {
T addChild(Node child) {

// Put child node in place
if (index != -1) {
children.add(index, child)
}
else {
children.add(child)
}
children.add(child)
child.parent = this

// Allow it to process
Expand Down Expand Up @@ -193,6 +187,19 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T> {
return name ?: this.class.simpleName
}

/**
* A hint to the scenegraph to add this node to an appropriate data structure
* for performance purposes.
* <p>
* The default behaviour is to inherit the partition hint of its parent,
* defaulting to {@link PartitionHint#None} if there are no hints in the node's ancestor
* tree.
*/
PartitionHint getPartitionHint() {

return partitionHint ?: parent?.partitionHint ?: PartitionHint.None
}

/**
* Get the local position of this node. Note that the returned vector is a
* live value of this node's position, so be sure to wrap in your own object
Expand Down Expand Up @@ -301,6 +308,14 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T> {
}
}

/**
* Set the partition hint for this node.
*/
void setPartitionHint(PartitionHint partitionHint) {

this.partitionHint = partitionHint
}

/**
* Set the local position of this node.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024, Emanuel Rabina (http://www.ultraq.net.nz/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package nz.net.ultraq.redhorizon.engine.scenegraph

/**
* A hint to the scene to classify a node with the given partition type. This
* can then be used to add the node to specific data structures that excel in
* certain tasks and provide a performance boost.
*
* @author Emanuel Rabina
*/
enum PartitionHint {

/**
* Apply no special treatment to partitioning this object.
*/
None,

/**
* Dot not include this object in any partitioning algorithms.
*/
DoNotParticipate,

/**
* An object which takes up a significant amount of 2D space.
*/
LargeArea,

/**
* An object which takes up a relatively small amount of 2D space. Nodes with
* this partition hint can end up in a quadtree data structure which is good
* for object culling.
*/
SmallArea
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory

import groovy.transform.TupleConstructor
import java.util.concurrent.CopyOnWriteArrayList

/**
* Entry point for the Red Horizon scene graph, holds all of the objects that
Expand Down Expand Up @@ -63,22 +64,31 @@ class Scene implements EventTarget {
@Delegate(includes = ['addChild', 'clear', 'findNode', 'leftShift', 'removeChild', 'traverse'], interfaces = false)
final Node root = new RootNode(this)

// Partition static objects in the quadTree to make culling queries faster
final QuadTree quadTree = new QuadTree(new Rectanglef(-1536, -1536, 1536, 1536))
// Partition objects in the following data structures to make queries faster
private final QuadTree quadTree = new QuadTree(new Rectanglef(-1536, -1536, 1536, 1536))
private final CopyOnWriteArrayList<Node> nodeList = []

Scene() {

on(NodeAddedEvent) { event ->
var node = event.node

if (node instanceof Camera) {
camera = node
return
}
else if (event.node instanceof GraphicsElement) {
quadTree.add(event.node)

switch (node.partitionHint) {
case PartitionHint.SmallArea -> quadTree.add(node)
case PartitionHint.LargeArea, PartitionHint.None -> nodeList.add(node)
}
}

on(NodeRemovedEvent) { event ->
quadTree.remove(event.node)
var node = event.node
if (!quadTree.remove(node)) {
nodeList.remove(node)
}
}
}

Expand All @@ -105,7 +115,20 @@ class Scene implements EventTarget {
}
}

// List<Node> findAll()
/**
* Return all nodes that are within the bounding box of {@code range}.
*/
List<Node> query(Rectanglef range, List<Node> results = []) {

nodeList.each { node ->
var nodeGlobalBounds = node.globalBounds
if (range.containsRectangle(nodeGlobalBounds) || range.intersectsRectangle(nodeGlobalBounds)) {
results << node
}
}
quadTree.query(range, results)
return results
}

/**
* Select objects whose bounding volumes intersect the given ray.
Expand Down
Loading

0 comments on commit 5a9c68a

Please sign in to comment.