From 47d0596910918f91cc82df733d27007af1de6be8 Mon Sep 17 00:00:00 2001 From: Emanuel Rabina Date: Tue, 5 Mar 2024 22:39:16 +1300 Subject: [PATCH] Add a scene overview --- .../redhorizon/engine/Application.groovy | 12 +- .../engine/audio/openal/OpenALRenderer.groovy | 6 +- .../engine/graphics/GraphicsRequests.groovy | 6 + .../engine/graphics/GraphicsSystem.groovy | 9 +- .../{GameMenu.groovy => MainMenu.groovy} | 12 +- .../redhorizon/engine/graphics/Switch.groovy | 28 ++- ...nderPass.groovy => ControlsOverlay.groovy} | 9 +- ...yRenderPass.groovy => DebugOverlay.groovy} | 14 +- .../engine/graphics/imgui/ImGuiLayer.groovy | 176 +++++++++--------- ...yRenderPass.groovy => ImGuiElement.groovy} | 31 +-- .../graphics/pipeline/RenderPass.groovy | 26 +-- .../graphics/pipeline/RenderPipeline.groovy | 26 ++- .../redhorizon/engine/scenegraph/Node.groovy | 18 +- .../redhorizon/engine/scenegraph/Scene.groovy | 8 +- .../engine/scenegraph/nodes/Sound.groovy | 8 +- .../redhorizon/explorer/EntryList.groovy | 7 +- .../redhorizon/explorer/Explorer.groovy | 14 +- .../redhorizon/explorer/NodeList.groovy | 85 +++++++++ .../redhorizon/explorer/objects/Map.groovy | 6 +- .../explorer/scripts/PlaybackScript.groovy | 30 +-- 20 files changed, 315 insertions(+), 216 deletions(-) rename redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/{GameMenu.groovy => MainMenu.groovy} (81%) rename redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/{ControlsOverlayRenderPass.groovy => ControlsOverlay.groovy} (86%) rename redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/{DebugOverlayRenderPass.groovy => DebugOverlay.groovy} (92%) rename redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/{OverlayRenderPass.groovy => ImGuiElement.groovy} (51%) create mode 100644 redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/NodeList.groovy diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/Application.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/Application.groovy index 07e8eb7b..bb3d41f4 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/Application.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/Application.groovy @@ -22,9 +22,9 @@ import nz.net.ultraq.redhorizon.engine.graphics.GraphicsConfiguration import nz.net.ultraq.redhorizon.engine.graphics.GraphicsSystem import nz.net.ultraq.redhorizon.engine.graphics.WindowCreatedEvent import nz.net.ultraq.redhorizon.engine.graphics.WindowMaximizedEvent -import nz.net.ultraq.redhorizon.engine.graphics.imgui.DebugOverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.imgui.DebugOverlay import nz.net.ultraq.redhorizon.engine.graphics.imgui.GuiEvent -import nz.net.ultraq.redhorizon.engine.graphics.pipeline.OverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.engine.input.InputEventStream import nz.net.ultraq.redhorizon.engine.input.KeyEvent import nz.net.ultraq.redhorizon.engine.scenegraph.Scene @@ -89,7 +89,7 @@ class Application implements EventTarget { * input into the application. */ Application addGraphicsSystem(GraphicsConfiguration config = new GraphicsConfiguration(), - OverlayRenderPass... overlayRenderPasses) { + ImGuiElement... overlayRenderPasses) { var graphicsSystem = new GraphicsSystem(windowTitle, inputEventStream, config) graphicsSystem.on(WindowCreatedEvent) { event -> @@ -97,14 +97,14 @@ class Application implements EventTarget { } graphicsSystem.on(SystemReadyEvent) { event -> var audioSystem = engine.systems.find { it instanceof AudioSystem } as AudioSystem - graphicsSystem.renderPipeline.addOverlayPass( - new DebugOverlayRenderPass(config.debug) + graphicsSystem.renderPipeline.addImGuiElement( + new DebugOverlay(config.debug) .addAudioRenderer(audioSystem.renderer) .addGraphicsRenderer(graphicsSystem.renderer) .toggleWith(inputEventStream, GLFW_KEY_D) ) overlayRenderPasses.each { overlayRenderPass -> - graphicsSystem.renderPipeline.addOverlayPass(overlayRenderPass) + graphicsSystem.renderPipeline.addImGuiElement(overlayRenderPass) } } graphicsSystem.relay(WindowMaximizedEvent, this) diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/audio/openal/OpenALRenderer.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/audio/openal/OpenALRenderer.groovy index bff903db..8a1bb4b0 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/audio/openal/OpenALRenderer.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/audio/openal/OpenALRenderer.groovy @@ -28,6 +28,8 @@ import nz.net.ultraq.redhorizon.engine.audio.SourceDeletedEvent import org.joml.Vector3f import org.lwjgl.openal.AL +import org.slf4j.Logger +import org.slf4j.LoggerFactory import static org.lwjgl.openal.AL10.* import static org.lwjgl.system.MemoryStack.stackPush @@ -41,6 +43,8 @@ import java.nio.ByteBuffer */ class OpenALRenderer implements AudioRenderer { + private static Logger logger = LoggerFactory.getLogger(OpenALRenderer) + private final AudioConfiguration config /** @@ -67,7 +71,7 @@ class OpenALRenderer implements AudioRenderer { var errorCode = AL.getFields().find { field -> return Modifier.isStatic(field.modifiers) && field.name.startsWith("AL_") && field.getInt(null) == error } - throw new Exception("OpenAL error: ${errorCode}") + logger.error("OpenAL error: ${errorCode}") } return result } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy index bf854182..71822c54 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsRequests.groovy @@ -16,6 +16,7 @@ package nz.net.ultraq.redhorizon.engine.graphics +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.filetypes.ColourFormat import org.joml.Vector2f @@ -59,6 +60,11 @@ interface GraphicsRequests { static record SpriteSheetRequest(int width, int height, ColourFormat format, ByteBuffer[] data) implements Request { } + /** + * Add an ImGui element to the render pipeline. + */ + void addImGuiElement(ImGuiElement overlayRenderPass) + /** * Request the creation or retrieval of the given resource type from the * graphics system, which will eventually be resolved in the returned diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy index 45d3b18a..73a70d8a 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GraphicsSystem.groovy @@ -24,6 +24,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLCamera import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLContext import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLRenderer import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLWindow +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.engine.graphics.pipeline.RenderPipeline import nz.net.ultraq.redhorizon.engine.input.InputEventStream import nz.net.ultraq.redhorizon.engine.input.KeyEvent @@ -74,6 +75,12 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests { this.config = config } + @Override + void addImGuiElement(ImGuiElement overlayRenderPass) { + + renderPipeline.addImGuiElement(overlayRenderPass) + } + /** * Implementation of double-click being used to toggle between windowed and * full screen modes. This isn't natively supported in GLFW given platform @@ -98,7 +105,7 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests { scene.graphicsRequestHandler = this scene.window = window scene.camera = camera - scene.gameMenu = imGuiLayer.gameMenu + scene.gameMenu = imGuiLayer.mainMenu renderPipeline.scene = scene } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GameMenu.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/MainMenu.groovy similarity index 81% rename from redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GameMenu.groovy rename to redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/MainMenu.groovy index a06b9ebf..17a9f53d 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/GameMenu.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/MainMenu.groovy @@ -22,17 +22,9 @@ package nz.net.ultraq.redhorizon.engine.graphics * * @author Emanuel Rabina */ -abstract class GameMenu { +interface MainMenu { - final List additionalOptionsItems = [] - - /** - * Add a menu item that'll appear in the Options menu. - */ - void addOptionsItem(MenuItem menuItem) { - - additionalOptionsItems << menuItem - } + final List optionsMenu = [] /** * An additional menu item and the behaviour behind it. diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Switch.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Switch.groovy index 187bfb61..74cc1b8d 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Switch.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Switch.groovy @@ -16,13 +16,21 @@ package nz.net.ultraq.redhorizon.engine.graphics +import nz.net.ultraq.redhorizon.engine.input.InputEventStream +import nz.net.ultraq.redhorizon.engine.input.KeyEvent + +import static org.lwjgl.glfw.GLFW.GLFW_PRESS + +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.SimpleType + /** * The trait for something being in an enabled/disabled state, with operations * to flip between the 2. * * @author Emanuel Rabina */ -trait Switch { +trait Switch { boolean enabled @@ -33,4 +41,22 @@ trait Switch { enabled = !enabled } + + /** + * Toggle the state of this render pass with the given key. + */ + T toggleWith(InputEventStream inputEventStream, int key, + @ClosureParams(value = SimpleType, options = 'nz.net.ultraq.redhorizon.engine.graphics.pipeline.RenderPass') Closure closure = null) { + + inputEventStream.on(KeyEvent) { event -> + if (event.action == GLFW_PRESS && event.key == key) { + toggle() + if (closure) { + closure(this) + } + } + } + + return (T)this + } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlayRenderPass.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlay.groovy similarity index 86% rename from redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlayRenderPass.groovy rename to redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlay.groovy index b59d0486..c95d366d 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlayRenderPass.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ControlsOverlay.groovy @@ -17,8 +17,7 @@ package nz.net.ultraq.redhorizon.engine.graphics.imgui import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer -import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer -import nz.net.ultraq.redhorizon.engine.graphics.pipeline.OverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.engine.input.ControlAddedEvent import nz.net.ultraq.redhorizon.engine.input.ControlRemovedEvent import nz.net.ultraq.redhorizon.engine.input.InputEventStream @@ -34,13 +33,13 @@ import java.util.concurrent.CopyOnWriteArrayList * * @author Emanuel Rabina */ -class ControlsOverlayRenderPass implements OverlayRenderPass { +class ControlsOverlay implements ImGuiElement { private List controls = new CopyOnWriteArrayList<>() private int controlsWindowSizeX = 350 private int controlsWindowSizeY = 200 - ControlsOverlayRenderPass(InputEventStream inputEventStream) { + ControlsOverlay(InputEventStream inputEventStream) { enabled = true @@ -53,7 +52,7 @@ class ControlsOverlayRenderPass implements OverlayRenderPass { } @Override - void render(GraphicsRenderer renderer, Framebuffer sceneFramebufferResult) { + void render(int dockspaceId, Framebuffer sceneFramebufferResult) { if (!controls) { return diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlayRenderPass.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlay.groovy similarity index 92% rename from redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlayRenderPass.groovy rename to redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlay.groovy index 8376e26e..8a09ac97 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlayRenderPass.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/DebugOverlay.groovy @@ -32,7 +32,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.MeshCreatedEvent import nz.net.ultraq.redhorizon.engine.graphics.MeshDeletedEvent import nz.net.ultraq.redhorizon.engine.graphics.TextureCreatedEvent import nz.net.ultraq.redhorizon.engine.graphics.TextureDeletedEvent -import nz.net.ultraq.redhorizon.engine.graphics.pipeline.OverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import imgui.ImGui import imgui.type.ImBoolean @@ -47,7 +47,7 @@ import java.util.concurrent.atomic.AtomicInteger * * @author Emanuel Rabina */ -class DebugOverlayRenderPass implements OverlayRenderPass { +class DebugOverlay implements ImGuiElement { private static final int MAX_DEBUG_LINES = 10 @@ -68,7 +68,7 @@ class DebugOverlayRenderPass implements OverlayRenderPass { * adding the renderers via the {@code add*} methods to get stats on their * use. */ - DebugOverlayRenderPass(boolean enabled) { + DebugOverlay(boolean enabled) { ImGuiLoggingAppender.instance.on(ImGuiLogEvent) { event -> if (event.persistentKey) { @@ -87,7 +87,7 @@ class DebugOverlayRenderPass implements OverlayRenderPass { /** * Add the audio renderer to get stats on audio sources, buffers, etc. */ - DebugOverlayRenderPass addAudioRenderer(AudioRenderer audioRenderer) { + DebugOverlay addAudioRenderer(AudioRenderer audioRenderer) { audioRenderer.on(AudioRendererEvent) { event -> switch (event) { @@ -103,7 +103,7 @@ class DebugOverlayRenderPass implements OverlayRenderPass { /** * Add the graphics renderer to get status on draws, textures, etc. */ - DebugOverlayRenderPass addGraphicsRenderer(GraphicsRenderer graphicsRenderer) { + DebugOverlay addGraphicsRenderer(GraphicsRenderer graphicsRenderer) { graphicsRenderer.on(GraphicsRendererEvent) { event -> switch (event) { @@ -120,9 +120,9 @@ class DebugOverlayRenderPass implements OverlayRenderPass { } @Override - void render(GraphicsRenderer renderer, Framebuffer sceneFramebufferResult) { + void render(int dockspaceId, Framebuffer sceneFramebufferResult) { - def viewport = ImGui.getMainViewport() + var viewport = ImGui.getMainViewport() ImGui.setNextWindowBgAlpha(0.4f) ImGui.setNextWindowPos(viewport.sizeX - debugWindowSizeX - 10 as float, viewport.workPosY + 10 as float) diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ImGuiLayer.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ImGuiLayer.groovy index 29628d77..537b59ab 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ImGuiLayer.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/imgui/ImGuiLayer.groovy @@ -19,10 +19,10 @@ package nz.net.ultraq.redhorizon.engine.graphics.imgui import nz.net.ultraq.redhorizon.engine.geometry.Dimension import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer import nz.net.ultraq.redhorizon.engine.graphics.FramebufferSizeEvent -import nz.net.ultraq.redhorizon.engine.graphics.GameMenu import nz.net.ultraq.redhorizon.engine.graphics.GraphicsConfiguration import nz.net.ultraq.redhorizon.engine.graphics.Window import nz.net.ultraq.redhorizon.engine.graphics.opengl.OpenGLTexture +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.engine.input.InputSource import nz.net.ultraq.redhorizon.engine.input.KeyEvent import nz.net.ultraq.redhorizon.events.EventTarget @@ -35,6 +35,7 @@ import imgui.glfw.ImGuiImplGlfw import imgui.type.ImBoolean import org.slf4j.Logger import org.slf4j.LoggerFactory +import static imgui.flag.ImGuiCond.FirstUseEver import static imgui.flag.ImGuiConfigFlags.DockingEnable import static imgui.flag.ImGuiDockNodeFlags.NoResize import static imgui.flag.ImGuiDockNodeFlags.PassthruCentralNode @@ -43,6 +44,7 @@ import static imgui.flag.ImGuiWindowFlags.* import static org.lwjgl.glfw.GLFW.GLFW_KEY_O import static org.lwjgl.glfw.GLFW.GLFW_PRESS +import groovy.transform.TupleConstructor import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -62,30 +64,26 @@ class ImGuiLayer implements AutoCloseable, InputSource { static { - // Extract and use the locally built natives for macOS running M1 processors + // Extract and use the locally built natives for macOS running M processors if (System.isMacOs() && System.isArm64()) { ImGuiLayer.classLoader.getResourceAsStream('io/imgui/java/native-bin/libimgui-javaarm64.dylib').withStream { inputStream -> - def tmpDir = File.createTempDir('imgui-java-natives-macos-arm64') + var tmpDir = File.createTempDir('imgui-java-natives-macos-arm64') tmpDir.deleteOnExit() - def libFile = new File(tmpDir, 'libimgui-javaarm64.dylib') + var libFile = new File(tmpDir, 'libimgui-javaarm64.dylib') Files.copy(inputStream, libFile.toPath(), StandardCopyOption.REPLACE_EXISTING) System.load(libFile.absolutePath) } } } - final ImGuiGameMenu gameMenu + final MainMenu mainMenu private final GraphicsConfiguration config private final ImGuiImplGl3 imGuiGl3 private final ImGuiImplGlfw imGuiGlfw + private final GameWindow gameWindow private boolean drawChrome - private Dimension lastWindowSize - private int dockspaceId - private boolean docked - - // Options private boolean debugOverlay private boolean shaderScanlines private boolean shaderSharpUpscaling @@ -102,16 +100,15 @@ class ImGuiLayer implements AutoCloseable, InputSource { shaderScanlines = config.scanlines shaderSharpUpscaling = true - // TODO: Split the ImGui setup from the debug overlay imGuiGlfw = new ImGuiImplGlfw() imGuiGl3 = new ImGuiImplGl3() ImGui.createContext() - def io = ImGui.getIO() + var io = ImGui.getIO() io.setConfigFlags(DockingEnable) getResourceAsStream('nz/net/ultraq/redhorizon/engine/graphics/imgui/Roboto-Medium.ttf').withCloseable { stream -> - def fontConfig = new ImFontConfig() + var fontConfig = new ImFontConfig() io.fonts.addFontFromMemoryTTF(stream.bytes, 16 * window.monitorScale as float, fontConfig) fontConfig.destroy() } @@ -119,7 +116,8 @@ class ImGuiLayer implements AutoCloseable, InputSource { imGuiGlfw.init(window.handle, true) imGuiGl3.init('#version 410 core') - gameMenu = new ImGuiGameMenu() + mainMenu = new MainMenu() + gameWindow = new GameWindow(config.targetAspectRatio) window.on(KeyEvent) { event -> if (event.action == GLFW_PRESS) { @@ -138,66 +136,9 @@ class ImGuiLayer implements AutoCloseable, InputSource { ImGui.destroyContext() } - /** - * Draw the scene from the given framebuffer into an ImGui window that will - * take up the whole screen by default. - * - * @param sceneFramebufferResult - */ - private void drawScene(Framebuffer sceneFramebufferResult) { - - ImGui.pushStyleVar(WindowPadding, 0, 0) - ImGui.begin("Scene", new ImBoolean(true), NoCollapse | NoScrollbar) - ImGui.popStyleVar() - - if (!docked && drawChrome) { - imgui.internal.ImGui.dockBuilderDockWindow('Scene', dockspaceId) - imgui.internal.ImGui.dockBuilderFinish(dockspaceId) - docked = true - } - - def framebufferSize = sceneFramebufferResult.texture.size - def windowSize = new Dimension(ImGui.contentRegionMaxX as int, ImGui.contentRegionMaxY as int) - def imageSizeX = windowSize.width - def imageSizeY = windowSize.height - def uvX = 0f - def uvY = 0f - def cursorX = 0 - def cursorY = ImGui.getCursorPosY() - - // Window is wider - if (windowSize.aspectRatio > framebufferSize.aspectRatio) { - uvX = 1 / (framebufferSize.width - (framebufferSize.width - windowSize.width)) as float - imageSizeX = imageSizeY * framebufferSize.aspectRatio as float - cursorX = (windowSize.width - imageSizeX) * 0.5f as float - } - // Window is taller - else if (windowSize.aspectRatio < framebufferSize.aspectRatio) { - uvY = 1 / (framebufferSize.height - (framebufferSize.height - windowSize.height)) as float - imageSizeY = imageSizeX / framebufferSize.aspectRatio as float - cursorY = cursorY + (windowSize.height - imageSizeY) * 0.5f as float - } - - ImGui.setCursorPos(cursorX, cursorY) - ImGui.image(((OpenGLTexture)sceneFramebufferResult.texture).textureId, imageSizeX, imageSizeY, - uvX, 1 - uvY as float, 1 - uvX as float, uvY) - - ImGui.end() - - if (windowSize != lastWindowSize) { - logger.debug('Scene window changed to {}', windowSize) - def targetResolution = windowSize.calculateFit(config.targetAspectRatio) - logger.debug('Target resolution changed to {}', targetResolution) - trigger(new FramebufferSizeEvent(windowSize, windowSize, targetResolution)) - lastWindowSize = windowSize - } - } - /** * Automatically mark the beginning and end of a frame as before and after the * execution of the given closure. - * - * @param closure */ void frame(Closure closure) { @@ -213,21 +154,24 @@ class ImGuiLayer implements AutoCloseable, InputSource { /** * Draw all of the ImGui elements to the screen. */ - void render(Framebuffer sceneFramebufferResult) { + int render(Framebuffer sceneFramebufferResult) { if (drawChrome) { - gameMenu.render() - setUpDockspace() - drawScene(sceneFramebufferResult) + var dockspaceId = setUpDockspace() + mainMenu.render(dockspaceId, sceneFramebufferResult) + gameWindow.render(dockspaceId, sceneFramebufferResult) + return dockspaceId } + + return -1 } /** * Build the docking window into which the app will be rendered. */ - private void setUpDockspace() { + private static int setUpDockspace() { - def viewport = ImGui.getMainViewport() + var viewport = ImGui.getMainViewport() ImGui.setNextWindowPos(0, 0) ImGui.setNextWindowSize(viewport.sizeX, viewport.sizeY) ImGui.pushStyleVar(WindowBorderSize, 0) @@ -238,21 +182,23 @@ class ImGuiLayer implements AutoCloseable, InputSource { NoTitleBar | NoCollapse | NoResize | NoMove | NoBringToFrontOnFocus | NoNavFocus | MenuBar | NoDocking | NoBackground) ImGui.popStyleVar(3) - dockspaceId = ImGui.getID('MyDockspace') + var dockspaceId = ImGui.getID('MyDockspace') ImGui.dockSpace(dockspaceId, viewport.workSizeX, viewport.workSizeY, PassthruCentralNode) ImGui.end() + + return dockspaceId } /** * An ImGUI implementation of the main menu bar. + * + * @author Emanuel Rabina */ - private class ImGuiGameMenu extends GameMenu implements EventTarget { + private class MainMenu implements nz.net.ultraq.redhorizon.engine.graphics.MainMenu, ImGuiElement, EventTarget { - /** - * Draw the menu. - */ - void render() { + @Override + void render(int dockspaceId, Framebuffer sceneFramebufferResult) { if (ImGui.beginMainMenuBar()) { @@ -277,7 +223,7 @@ class ImGuiLayer implements AutoCloseable, InputSource { trigger(new ChangeEvent(OPTIONS_SHADER_SHARP_UPSCALING, shaderSharpUpscaling)) } - additionalOptionsItems*.render() + optionsMenu*.render() ImGui.endMenu() } @@ -286,4 +232,66 @@ class ImGuiLayer implements AutoCloseable, InputSource { } } } + + /** + * When window chrome is enabled, the scene will be rendered to an ImGui image + * texture held by this class. + * + * @author Emanuel Rabina + */ + @TupleConstructor + private class GameWindow implements ImGuiElement, EventTarget { + + final float targetAspectRatio + + private Dimension lastWindowSize + + @Override + void render(int dockspaceId, Framebuffer sceneFramebufferResult) { + + ImGui.setNextWindowSize(800, 600, FirstUseEver) + ImGui.pushStyleVar(WindowPadding, 0, 0) + ImGui.begin('Game', new ImBoolean(true), NoCollapse | NoScrollbar) + ImGui.popStyleVar() + + imgui.internal.ImGui.dockBuilderDockWindow('Game', dockspaceId) + imgui.internal.ImGui.dockBuilderFinish(dockspaceId) + + var framebufferSize = sceneFramebufferResult.texture.size + var windowSize = new Dimension(ImGui.contentRegionMaxX as int, ImGui.contentRegionMaxY as int) + var imageSizeX = windowSize.width + var imageSizeY = windowSize.height + var uvX = 0f + var uvY = 0f + var cursorX = 0 + var cursorY = ImGui.getCursorPosY() + + // Window is wider + if (windowSize.aspectRatio > framebufferSize.aspectRatio) { + uvX = 1 / (framebufferSize.width - (framebufferSize.width - windowSize.width)) as float + imageSizeX = imageSizeY * framebufferSize.aspectRatio as float + cursorX = (windowSize.width - imageSizeX) * 0.5f as float + } + // Window is taller + else if (windowSize.aspectRatio < framebufferSize.aspectRatio) { + uvY = 1 / (framebufferSize.height - (framebufferSize.height - windowSize.height)) as float + imageSizeY = imageSizeX / framebufferSize.aspectRatio as float + cursorY = cursorY + (windowSize.height - imageSizeY) * 0.5f as float + } + + ImGui.setCursorPos(cursorX, cursorY) + ImGui.image(((OpenGLTexture)sceneFramebufferResult.texture).textureId, imageSizeX, imageSizeY, + uvX, 1 - uvY as float, 1 - uvX as float, uvY) + + ImGui.end() + + if (windowSize != lastWindowSize) { + logger.debug('Scene window changed to {}', windowSize) + var targetResolution = windowSize.calculateFit(targetAspectRatio) + logger.debug('Target resolution changed to {}', targetResolution) + trigger(new FramebufferSizeEvent(windowSize, windowSize, targetResolution)) + lastWindowSize = windowSize + } + } + } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/OverlayRenderPass.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/ImGuiElement.groovy similarity index 51% rename from redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/OverlayRenderPass.groovy rename to redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/ImGuiElement.groovy index a8e43f07..7a3dc167 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/OverlayRenderPass.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/ImGuiElement.groovy @@ -17,38 +17,17 @@ package nz.net.ultraq.redhorizon.engine.graphics.pipeline import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer -import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer -import nz.net.ultraq.redhorizon.engine.input.InputEventStream +import nz.net.ultraq.redhorizon.engine.graphics.Switch /** - * A rendering pass for drawing overlay content to the game viewport. + * A object that contains ImGui content to render. * * @author Emanuel Rabina */ -interface OverlayRenderPass extends RenderPass { +interface ImGuiElement extends Switch> { /** - * Returns {@code null} for overlay render passes. - * - * @return {@code null} + * Draw the ImGui content. */ - @Override - default Framebuffer getFramebuffer() { - - return null - } - - /** - * Render the overlay. - * - * @param renderer - * @param sceneFramebufferResult - */ - void render(GraphicsRenderer renderer, Framebuffer sceneFramebufferResult) - - @Override - default OverlayRenderPass toggleWith(InputEventStream inputEventStream, int key) { - - return super.toggleWith(inputEventStream, key) as OverlayRenderPass - } + void render(int dockspaceId, Framebuffer sceneFramebufferResult) } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPass.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPass.groovy index 3b2a1943..368688a7 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPass.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPass.groovy @@ -19,13 +19,6 @@ package nz.net.ultraq.redhorizon.engine.graphics.pipeline import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer import nz.net.ultraq.redhorizon.engine.graphics.Switch -import nz.net.ultraq.redhorizon.engine.input.InputEventStream -import nz.net.ultraq.redhorizon.engine.input.KeyEvent - -import static org.lwjgl.glfw.GLFW.GLFW_PRESS - -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.SimpleType /** * A single rendering pass with a specific framebuffer as the rendering target. @@ -34,7 +27,7 @@ import groovy.transform.stc.SimpleType * The expected type of input data from a prior rendering pass. * @author Emanuel Rabina */ -interface RenderPass extends Switch { +interface RenderPass extends Switch> { /** * Perform any cleanup for this render pass. @@ -59,21 +52,4 @@ interface RenderPass extends Switch { * @param previous */ void render(GraphicsRenderer renderer, T previous) - - /** - * Toggle the state of this render pass with the given key. - */ - default RenderPass toggleWith(InputEventStream inputEventStream, int key, - @ClosureParams(value = SimpleType, options = 'nz.net.ultraq.redhorizon.engine.graphics.pipeline.RenderPass') Closure closure = null) { - - inputEventStream.on(KeyEvent) { event -> - if (event.action == GLFW_PRESS && event.key == key) { - toggle() - if (closure) { - closure(this) - } - } - } - return this - } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy index cbdfa4fc..ac763135 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/pipeline/RenderPipeline.groovy @@ -26,7 +26,7 @@ import nz.net.ultraq.redhorizon.engine.graphics.Mesh import nz.net.ultraq.redhorizon.engine.graphics.Shader import nz.net.ultraq.redhorizon.engine.graphics.Window import nz.net.ultraq.redhorizon.engine.graphics.imgui.ChangeEvent -import nz.net.ultraq.redhorizon.engine.graphics.imgui.ControlsOverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.imgui.ControlsOverlay 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 @@ -58,7 +58,7 @@ class RenderPipeline implements AutoCloseable { private final Mesh fullScreenQuad private final List renderPasses = [] - private final List overlayPasses = [] + private final List imGuiElements = [] /** * Constructor, configure the rendering pipeline. @@ -71,7 +71,7 @@ class RenderPipeline implements AutoCloseable { fullScreenQuad = renderer.createSpriteMesh(new Rectanglef(-1, -1, 1, 1)) - overlayPasses << new ControlsOverlayRenderPass(inputEventStream).toggleWith(inputEventStream, GLFW_KEY_C) + imGuiElements << new ControlsOverlay(inputEventStream).toggleWith(inputEventStream, GLFW_KEY_C) // Allow for changes to the pipeline from the GUI imGuiLayer.on(ChangeEvent) { event -> @@ -88,14 +88,12 @@ class RenderPipeline implements AutoCloseable { } /** - * Register an overlay rendering pass with the rendering pipeline. Overlays - * are drawn after the scene and use the target resolution of the window. - * - * @param overlayPass + * Register an ImGui element with the rendering pipeline. These are drawn + * after the scene and use the target resolution of the window. */ - void addOverlayPass(OverlayRenderPass overlayPass) { + void addImGuiElement(ImGuiElement imGuiElement) { - overlayPasses << overlayPass + imGuiElements << imGuiElement } @Override @@ -163,11 +161,11 @@ class RenderPipeline implements AutoCloseable { } as Framebuffer renderer.setRenderTarget(null) - // Draw overlays - imGuiLayer.render(sceneResult) - overlayPasses.each { overlayPass -> - if (overlayPass.enabled) { - overlayPass.render(renderer, sceneResult) + // Draw ImGui objects + var dockspaceId = imGuiLayer.render(sceneResult) + imGuiElements.each { imGuiElement -> + if (imGuiElement.enabled) { + imGuiElement.render(dockspaceId, sceneResult) } } } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Node.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Node.groovy index 6243371b..67664fb3 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Node.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Node.groovy @@ -34,8 +34,8 @@ class Node implements SceneEvents, Scriptable, Visitable { final Matrix4f transform = new Matrix4f() final Rectanglef bounds = new Rectanglef() - protected Node parent - protected CopyOnWriteArrayList children = new CopyOnWriteArrayList<>() + Node parent + CopyOnWriteArrayList children = new CopyOnWriteArrayList<>() private final Rectanglef globalBounds = new Rectanglef() private final Matrix4f globalTransform = new Matrix4f() @@ -106,9 +106,17 @@ class Node implements SceneEvents, Scriptable, Visitable { } /** - * An alias for {@link #addChild(Node)} - * - * @param child + * Returns this node's name. Used for the scene overview and debugging, + * defaults to the class name of the node. + */ + String getName() { + + return this.class.simpleName + } + + /** + * Overload of the {@code <<} operator as an alias for + * {@link #addChild(Node)}. */ void leftShift(Node child) { diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy index fd9595b9..f0c1dd27 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/Scene.groovy @@ -19,8 +19,8 @@ package nz.net.ultraq.redhorizon.engine.scenegraph import nz.net.ultraq.redhorizon.engine.audio.AudioRequests import nz.net.ultraq.redhorizon.engine.audio.Listener import nz.net.ultraq.redhorizon.engine.graphics.Camera -import nz.net.ultraq.redhorizon.engine.graphics.GameMenu import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests +import nz.net.ultraq.redhorizon.engine.graphics.MainMenu import nz.net.ultraq.redhorizon.engine.graphics.Window import nz.net.ultraq.redhorizon.engine.input.InputEventStream import nz.net.ultraq.redhorizon.engine.time.TimeSystem @@ -36,7 +36,7 @@ import java.util.concurrent.CopyOnWriteArrayList */ class Scene implements EventTarget, Visitable { - private final List nodes = new CopyOnWriteArrayList<>() + final List nodes = new CopyOnWriteArrayList<>() @Delegate AudioRequests audioRequestsHandler @@ -49,7 +49,7 @@ class Scene implements EventTarget, Visitable { Window window Camera camera Listener listener - GameMenu gameMenu + MainMenu gameMenu /** * Allow visitors into the scene for traversal. @@ -110,7 +110,7 @@ class Scene implements EventTarget, Visitable { */ T findNode(Closure predicate) { - return nodes.find(predicate) + return (T)nodes.find(predicate) } /** diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sound.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sound.groovy index cc49673d..f53bb02a 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sound.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sound.groovy @@ -37,6 +37,7 @@ import nz.net.ultraq.redhorizon.filetypes.StreamingSampleEvent import groovy.transform.TupleConstructor import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.BlockingQueue +import java.util.concurrent.CancellationException import java.util.concurrent.Executors import java.util.concurrent.LinkedBlockingQueue @@ -219,7 +220,12 @@ class Sound extends Node implements AudioElement, Playable, Temporal { @Override void onSceneRemoved(Scene scene) { - streamingDecoder?.cancel(true) + streamingDecoder.cancel(true) + try { + streamingDecoder.get() + } + catch (CancellationException ignored) { + } scene.requestDelete(*streamedBuffers.drain()) } diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/EntryList.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/EntryList.groovy index 0cbd7d91..e2cad31b 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/EntryList.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/EntryList.groovy @@ -17,8 +17,7 @@ package nz.net.ultraq.redhorizon.explorer import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer -import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer -import nz.net.ultraq.redhorizon.engine.graphics.pipeline.OverlayRenderPass +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement import nz.net.ultraq.redhorizon.events.EventTarget import imgui.ImGui @@ -31,7 +30,7 @@ import static imgui.flag.ImGuiStyleVar.WindowPadding * * @author Emanuel Rabina */ -class EntryList implements EventTarget, OverlayRenderPass { +class EntryList implements EventTarget, ImGuiElement { final List entries @@ -44,7 +43,7 @@ class EntryList implements EventTarget, OverlayRenderPass { } @Override - void render(GraphicsRenderer renderer, Framebuffer sceneResult) { + void render(int dockspaceId, Framebuffer sceneResult) { ImGui.setNextWindowSize(300, 500, FirstUseEver) ImGui.pushStyleVar(WindowPadding, 0, 0) diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/Explorer.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/Explorer.groovy index a64c3d36..46b010c8 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/Explorer.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/Explorer.groovy @@ -25,8 +25,8 @@ import nz.net.ultraq.redhorizon.classic.units.UnitData import nz.net.ultraq.redhorizon.engine.Application import nz.net.ultraq.redhorizon.engine.geometry.Dimension import nz.net.ultraq.redhorizon.engine.graphics.Colour -import nz.net.ultraq.redhorizon.engine.graphics.GameMenu.MenuItem import nz.net.ultraq.redhorizon.engine.graphics.GraphicsConfiguration +import nz.net.ultraq.redhorizon.engine.graphics.MainMenu.MenuItem import nz.net.ultraq.redhorizon.engine.graphics.WindowMaximizedEvent import nz.net.ultraq.redhorizon.engine.resources.ResourceManager import nz.net.ultraq.redhorizon.engine.scenegraph.Scene @@ -77,7 +77,7 @@ class Explorer { private InputStream selectedFileInputStream private Palette palette private boolean touchpadInput - private MenuItem touchpadInputMenuItem + private NodeList nodeList /** * Constructor, sets up an application with the default configurations. @@ -114,7 +114,6 @@ class Explorer { // Also toggle the explorer GUI with the same key for toggling the ImGui chrome entryList.toggleWith(scene.inputEventStream, GLFW_KEY_O) - buildList(new File(System.getProperty("user.dir"))) // Handle events from the explorer GUI @@ -143,15 +142,16 @@ class Explorer { } } - // Add a menu item for touchpad input - scene.gameMenu.additionalOptionsItems << new MenuItem() { + nodeList = new NodeList(scene) + scene.addImGuiElement(nodeList) + // Add a menu item for touchpad input + scene.gameMenu.optionsMenu << new MenuItem() { @Override void render() { - if (ImGui.menuItem('Touchpad input', null, touchpadInput)) { touchpadInput = !touchpadInput - var mapNode = scene.findNode { node -> node instanceof Map } as Map + Map mapNode = scene.findNode { node -> node instanceof Map } if (mapNode) { ((MapViewerScript)mapNode.script).touchpadInput = touchpadInput } diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/NodeList.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/NodeList.groovy new file mode 100644 index 00000000..0437f30b --- /dev/null +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/NodeList.groovy @@ -0,0 +1,85 @@ +/* + * 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.explorer + +import nz.net.ultraq.redhorizon.engine.graphics.Framebuffer +import nz.net.ultraq.redhorizon.engine.graphics.pipeline.ImGuiElement +import nz.net.ultraq.redhorizon.engine.scenegraph.Node +import nz.net.ultraq.redhorizon.engine.scenegraph.Scene + +import imgui.ImGui +import imgui.type.ImBoolean +import static imgui.flag.ImGuiCond.FirstUseEver +import static imgui.flag.ImGuiStyleVar.WindowPadding +import static imgui.flag.ImGuiTreeNodeFlags.* + +/** + * An ImGUI panel showing what nodes are currently present in the scene. + * + * @author Emanuel Rabina + */ +class NodeList implements ImGuiElement { + + final Scene scene + + NodeList(Scene scene) { + + this.scene = scene + enabled = true + } + + @Override + void render(int dockspaceId, Framebuffer sceneFramebufferResult) { + + ImGui.setNextWindowSize(300, 500, FirstUseEver) + ImGui.pushStyleVar(WindowPadding, 0, 0) + ImGui.begin('Scene', new ImBoolean(true)) + ImGui.popStyleVar() + + // File list + if (ImGui.beginListBox('##NodeList', -Float.MIN_VALUE, -Float.MIN_VALUE)) { + scene.nodes.each { node -> + renderNodeAndChildren(node) + } + ImGui.endListBox() + } + + ImGui.end() + } + + /** + * Create an entry in the UI for each node and its children. + */ + private void renderNodeAndChildren(Node node) { + + var flags = DefaultOpen | SpanFullWidth + if (!node.children) { + flags |= Leaf + } + if (ImGui.treeNodeEx(node.name ?: '(no name)', flags)) { + node.children.each { child -> + renderNodeAndChildren(child) + } + if (node.script) { + if (ImGui.treeNodeEx(node.script.class.simpleName, SpanFullWidth | Leaf)) { + ImGui.treePop() + } + } + ImGui.treePop() + } + } +} diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy index 676aa988..dd4effe8 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/objects/Map.groovy @@ -42,7 +42,7 @@ class Map extends Node { private static final int TILE_HEIGHT = 24 final MapFile mapFile - final String name + final String name = "Map - ${mapFile.name}" final Theater theater final Rectanglef boundary final Vector2f initialPosition @@ -56,8 +56,6 @@ class Map extends Node { this.mapFile = mapFile - name = mapFile.name - var mapSection = mapFile.mapSection theater = Theater.valueOf(mapSection.theater()) palette = getResourceAsStream("ra-${theater.label.toLowerCase()}.pal").withBufferedStream { inputStream -> @@ -89,7 +87,7 @@ class Map extends Node { return """ Red Alert map - - Name: ${name} + - Name: ${mapFile.name} - Theater: ${theater} - Bounds: x=${boundary.minX},y=${boundary.minY},w=${boundary.lengthX()},h=${boundary.lengthY()} """.stripIndent() diff --git a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/PlaybackScript.groovy b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/PlaybackScript.groovy index bca1d6e4..c31a0460 100644 --- a/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/PlaybackScript.groovy +++ b/redhorizon-explorer/source/nz/net/ultraq/redhorizon/explorer/scripts/PlaybackScript.groovy @@ -44,7 +44,7 @@ class PlaybackScript extends Script { final boolean runOnce - private RemoveControlFunction removePlaybackControl + private List removeControlFunctions = [] @Delegate private Playable applyDelegate() { @@ -54,7 +54,7 @@ class PlaybackScript extends Script { @Override void onSceneAdded(Scene scene) { - removePlaybackControl = scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_SPACE, 'Play/Pause', { -> + removeControlFunctions << scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_SPACE, 'Play/Pause', { -> if (runOnce) { logger.debug('Pausing/Resuming playback') scene.gameClock.togglePause() @@ -68,14 +68,19 @@ class PlaybackScript extends Script { })) if (scriptable instanceof Sound) { - scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_LEFT, 'Move audio source left', { -> - scriptable.transform.translate(-0.25, 0) - logger.debug("Sound at: ${scriptable.transform.getTranslation(new Vector3f()).x()}") - })) - scene.inputEventStream.addControl(new KeyControl(GLFW_KEY_RIGHT, 'Move audio source right', { -> - scriptable.transform.translate(0.25, 0) - logger.debug("Sound at: ${scriptable.transform.getTranslation(new Vector3f()).x()}") - })) + removeControlFunctions << scene.inputEventStream.addControl( + new KeyControl(GLFW_KEY_LEFT, 'Move audio source left', { + -> + scriptable.transform.translate(-0.25, 0) + logger.debug("Sound at: ${scriptable.transform.getTranslation(new Vector3f()).x()}") + }) + ) + removeControlFunctions << scene.inputEventStream.addControl( + new KeyControl(GLFW_KEY_RIGHT, 'Move audio source right', { -> + scriptable.transform.translate(0.25, 0) + logger.debug("Sound at: ${scriptable.transform.getTranslation(new Vector3f()).x()}") + }) + ) } on(PlaybackReadyEvent) { event -> @@ -91,6 +96,9 @@ class PlaybackScript extends Script { @Override void onSceneRemoved(Scene scene) { - removePlaybackControl.remove() + removeControlFunctions*.remove() + if (scene.gameClock.paused) { + scene.gameClock.resume() + } } }