Skip to content

Commit

Permalink
Conditionally dive into child nodes w/ accept/visit
Browse files Browse the repository at this point in the history
  • Loading branch information
ultraq committed Jun 22, 2024
1 parent 0fc52f6 commit 93e3997
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class AudioSystem extends EngineSystem implements AudioRequests {
if (element instanceof AudioElement) {
element.render(renderer)
}
return true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ import static org.lwjgl.opengl.GL30C.glBindFramebuffer
import static org.lwjgl.opengl.GL31C.*
import static org.lwjgl.opengl.KHRDebug.*

import groovy.transform.NamedVariant
import java.lang.reflect.Modifier
import java.nio.Buffer
import java.nio.ByteBuffer
Expand Down Expand Up @@ -288,7 +287,6 @@ class OpenGLRenderer implements GraphicsRenderer {
}

@Override
@NamedVariant
void draw(Mesh mesh, Matrix4f transform, Shader shader, Material material = null) {

averageNanos('draw', 1f, logger) { ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class RenderPipeline implements AutoCloseable {
void render(GraphicsRenderer renderer, Void unused) {

if (scene) {
Camera camera = scene.findNode { node -> node instanceof Camera }
var camera = (Camera)scene.findNode { node -> node instanceof Camera }
if (camera) {
camera.update()

Expand All @@ -203,19 +203,33 @@ class RenderPipeline implements AutoCloseable {
visibleElements.clear()
frustumIntersection.set(camera.viewProjection)
scene.accept { Node element ->
if (element instanceof GraphicsElement && element.isVisible(frustumIntersection)) {
element.update()
element.script?.update()
visibleElements << element
if (element.isVisible(frustumIntersection)) {
if (element instanceof GraphicsElement) {
element.update()
element.script?.update()
visibleElements << element
}
return true
}
return false
}
}

// TODO: Sort objects from top to bottom to allow lower/newer
// renderables to appear 'over' older/higher ones.

visibleElements.each { element ->
element.render(renderer)
// averageNanos('Sorting overhead', 1f, logger) { ->
// visibleElements.sort { e1, e2 ->
// var b1 = e1.globalBounds
// var b2 = e2.globalBounds
// var result =
// b1.maxY > b2.maxY ? -1 : b1.maxY < b2.maxY ? 1 :
// b1.minX < b2.minX ? -1 : b1.minX > b2.minX ? 1 :
// 0
// return result
// }
// }
average('Rendering', 1f, logger) { ->
visibleElements.each { element ->
element.render(renderer)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {
@Override
void accept(SceneVisitor visitor) {

visitor.visit(this)
children*.accept(visitor)
if (visitor.visit(this)) {
children*.accept(visitor)
}
}

/**
Expand Down Expand Up @@ -87,7 +88,7 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {
bounds.set(childBounds)
}

return this
return (T)this
}

/**
Expand Down Expand Up @@ -124,7 +125,6 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {

/**
* Locate the first node in the scene that satisfies the given predicate.
* Shorthand for {@code scene.root.findChild(predicate)}.
*
* @param predicate
* @return
Expand Down Expand Up @@ -243,7 +243,8 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {
*/
boolean isVisible(FrustumIntersection frustumIntersection) {

return frustumIntersection.testPlaneXY(getGlobalBounds())
var plane = getGlobalBounds()
return frustumIntersection.testPlaneXY(plane.minX, plane.minY, plane.maxX, plane.maxY)
}

/**
Expand All @@ -265,7 +266,7 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {
node.parent = null
// TODO: Recalculate bounds
}
return this
return (T)this
}

/**
Expand All @@ -278,7 +279,7 @@ class Node<T extends Node> implements SceneEvents, Scriptable<T>, Visitable {
removeChild(node)
}
}
return this
return (T)this
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,18 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory

import groovy.transform.TupleConstructor
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType

/**
* Entry point for the Red Horizon scene graph, holds all of the objects that
* make up the 'world'.
*
* @author Emanuel Rabina
*/
class Scene implements EventTarget, Visitable {
class Scene implements EventTarget {

private static final Logger logger = LoggerFactory.getLogger(Scene)

@Delegate(includes = ['accept', 'addChild', 'clear', 'findNode', 'leftShift', 'removeChild'], interfaces = false)
final Node root = new RootNode(this)

// TODO: This stuff is really all 'scene/application context' objects, so
Expand All @@ -60,20 +59,11 @@ class Scene implements EventTarget, Visitable {
MainMenu gameMenu
GameWindow gameWindow

/**
* Allow visitors into the scene for traversal.
*/
@Override
void accept(SceneVisitor visitor) {

root.accept(visitor)
}

/**
* Add a top-level node to this scene. Shorthand for
* {@code scene.root.addNode(node)}.
*/
Scene addNode(Node node) {
Scene addChild(Node node) {

time('Adding node', logger) { ->
root.addChild(node)
Expand All @@ -92,27 +82,6 @@ class Scene implements EventTarget, Visitable {
}
}

/**
* Locate the first node in the scene that satisfies the given predicate.
* Shorthand for {@code scene.root.findChild(predicate)}.
*
* @param predicate
* @return
* The matching node, or {@code null} if no match is found.
*/
Node findNode(@ClosureParams(value = SimpleType, options = "Node") Closure<Boolean> predicate) {

return root.findNode(predicate)
}

/**
* Overloads the {@code <<} operator to add elements to this scene.
*/
Scene leftShift(Node element) {

return addNode(element)
}

/**
* Select objects whose bounding volumes intersect the given ray.
*
Expand Down Expand Up @@ -147,23 +116,6 @@ class Scene implements EventTarget, Visitable {
// }
// }

/**
* Removes a top-level node from the scene. Shorthand for
* {@code scene.root.removeChild(node)}.
*
* @param node
* The node to remove. If {@code null}, then this method does nothing.
* @return
* This scene so it can be chained.
*/
Scene removeNode(Node node) {

time('Removing node', logger) { ->
root.removeChild(node)
}
return this
}

/**
* A special instance of {@link Node} that is always present in the scene.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ interface SceneVisitor {
* Allow visiting any scene element.
*
* @param element
* @return
* {@code true} if the visit can continue to this node's children, {@code
* false} otherwise.
*/
void visit(Node element)
boolean visit(Node element)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
/*
* Copyright 2021, 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 All @@ -18,15 +18,17 @@ package nz.net.ultraq.redhorizon.engine.scenegraph

/**
* Elements in a scene which are visitable.
*
* <p>
* The behaviour of a visitable element is slightly different from standard
* iteration in that the visitor can specify if it wishes to visit each of a
* node's children or not, based on the return value from the visit.
*
* @author Emanuel Rabina
*/
interface Visitable {

/**
* Accept any scene visitor.
*
* @param visitor
*/
void accept(SceneVisitor visitor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class FullScreenContainer extends Node<FullScreenContainer> {
children.each { child ->
child.accept { Node node ->
node.bounds.center()
return true
}
switch (fillMode) {
case FillMode.ASPECT_RATIO -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class TimeSystem extends EngineSystem {
if (element instanceof Temporal) {
element.tick(currentTimeMillis)
}
return true
}

lastSystemTimeMillis = currentSystemTimeMillis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class Explorer {
if (ImGui.menuItem('Touchpad input', null, touchpadInput)) {
touchpadInput = !touchpadInput
userPreferences.set(ExplorerPreferences.TOUCHPAD_INPUT, touchpadInput)
Map mapNode = scene.findNode { node -> node instanceof Map }
var mapNode = (Map)scene.findNode { node -> node instanceof Map }
if (mapNode) {
((MapViewerScript)mapNode.script).touchpadInput = touchpadInput
}
Expand All @@ -194,12 +194,12 @@ class Explorer {

// Start all the global/helper nodes
camera = new Camera(renderResolution)
scene.addNode(camera)
scene << camera

globalPalette = new GlobalPalette(loadPalette())
scene.addNode(globalPalette)
scene << globalPalette

scene.addNode(new GridLines(-1536, 1536, 24)) // The max area a Red Alert map can be
scene << new GridLines(-1536, 1536, 24) // The max area a Red Alert map can be
}

@SuppressWarnings('unused')
Expand Down Expand Up @@ -297,7 +297,7 @@ class Explorer {
private void clearPreview() {

selectedFileInputStream?.close()
scene.removeNode(previewNode)
scene.removeChild(previewNode)
previewNode = null
camera.reset()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class SpriteShowcaseScript extends Script<PalettedSprite> {
if (node instanceof FactionColours) {
node.faction = selectedFaction
}
return true
}
}))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Player extends Node<Player> implements GraphicsElement, Rotatable, Tempora

accept { Node node ->
node.bounds.center()
return true
}

attachScript(new PlayerScript())
Expand Down

0 comments on commit 93e3997

Please sign in to comment.