From 3dc12efadda5a3933b5bb73684deead86bdd3805 Mon Sep 17 00:00:00 2001 From: Emanuel Rabina Date: Sat, 10 Feb 2024 17:59:45 +1300 Subject: [PATCH] FullScreenContainer for scaling all children up to full screen --- .../cli/mediaplayer/ImageScript.groovy | 43 ------ .../cli/mediaplayer/MediaPlayer.groovy | 8 +- .../engine/extensions/JomlExtensions.groovy | 26 ++++ .../engine/graphics/GraphicsSystem.groovy | 1 - .../redhorizon/engine/graphics/Shader.groovy | 2 +- .../redhorizon/engine/scenegraph/Node.groovy | 4 +- .../nodes/FullScreenContainer.groovy | 144 ++++++++++++++++++ .../engine/scenegraph/nodes/Sprite.groovy | 9 +- 8 files changed, 184 insertions(+), 53 deletions(-) delete mode 100644 redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/ImageScript.groovy create mode 100644 redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/FullScreenContainer.groovy diff --git a/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/ImageScript.groovy b/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/ImageScript.groovy deleted file mode 100644 index 870982be..00000000 --- a/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/ImageScript.groovy +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.cli.mediaplayer - -import nz.net.ultraq.redhorizon.engine.scenegraph.Scene -import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Sprite -import nz.net.ultraq.redhorizon.engine.scenegraph.scripting.Script - -/** - * A script to make a sprite node behave as a full-screen image. - * - * @author Emanuel Rabina - */ -class ImageScript extends Script { - - @Delegate - private Sprite applyDelegate() { - return scriptable - } - - @Override - void onSceneAdded(Scene scene) { - - var width = imageFile.width - var height = imageFile.height - transform.scaleXY(scene.window.renderResolution.calculateScaleToFit(width, height)) - transform.translate(-width / 2, -height / 2) - } -} diff --git a/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/MediaPlayer.groovy b/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/MediaPlayer.groovy index 3acce9d3..f3d13220 100644 --- a/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/MediaPlayer.groovy +++ b/redhorizon-cli/source/nz/net/ultraq/redhorizon/cli/mediaplayer/MediaPlayer.groovy @@ -21,6 +21,8 @@ import nz.net.ultraq.redhorizon.engine.audio.AudioConfiguration import nz.net.ultraq.redhorizon.engine.graphics.GraphicsConfiguration import nz.net.ultraq.redhorizon.engine.input.KeyEvent import nz.net.ultraq.redhorizon.engine.scenegraph.Node +import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.FullScreenContainer +import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.FullScreenContainer.FillMode import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Sound import nz.net.ultraq.redhorizon.engine.scenegraph.nodes.Sprite import nz.net.ultraq.redhorizon.filetypes.ImageFile @@ -69,13 +71,13 @@ class MediaPlayer extends Application { logger.info('File details: {}', mediaFile) - var media = switch (mediaFile) { - case ImageFile -> new Sprite(mediaFile).attachScript(new ImageScript()) + var mediaNode = switch (mediaFile) { + case ImageFile -> new FullScreenContainer(fillMode: FillMode.ASPECT_RATIO).addChild(new Sprite(mediaFile)) case SoundFile -> new Sound(mediaFile).attachScript(new SoundPlaybackScript()) default -> throw new UnsupportedOperationException("No media script for the associated file class of ${mediaFile}") } - scene << media + scene << mediaNode // mediaLoader = switch (mediaFile) { // case VideoFile -> new VideoLoader(mediaFile, scene, graphicsSystem, gameClock, inputEventStream) diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/extensions/JomlExtensions.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/extensions/JomlExtensions.groovy index 38464ce0..67c84b4e 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/extensions/JomlExtensions.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/extensions/JomlExtensions.groovy @@ -50,6 +50,32 @@ class JomlExtensions { throw new IllegalArgumentException("Cannot convert Rectanglef to type ${clazz}") } + /** + * Calculate the scale factor for a rectangle to fit into the current one + * while maintaining its aspect ratio. + * + * @param rectangle + * @return + */ + static float calculateScaleToFit(Rectanglef self, Rectanglef other) { + + return Math.min(self.lengthX() / other.lengthX(), self.lengthY() / other.lengthY()) + } + + /** + * Update a rectangle's values so each point is equidistant from an 0,0 point + * as if on a plot. + * + * @param self + * @return + */ + static Rectanglef center(Rectanglef self) { + + var halfLengthX = self.lengthX() / 2 as float + var halfLengthY = self.lengthY() / 2 as float + return self.set(-halfLengthX, -halfLengthY, halfLengthX, halfLengthY) + } + /** * Overload the {@code -} operator to perform vector subtraction. Note that * this creates a new object to store the result and is 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 7aaee0c9..ac9ea02a 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 @@ -148,7 +148,6 @@ class GraphicsSystem extends EngineSystem implements GraphicsRequests { if (deletionRequests) { deletionRequests.drain().each { deletionRequest -> switch (deletionRequest) { - case Material -> renderer.deleteMaterial(deletionRequest) case Mesh -> renderer.deleteMesh(deletionRequest) case Texture -> renderer.deleteTexture(deletionRequest) default -> throw new IllegalArgumentException("Cannot delete resource of type ${deletionRequest}") diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy index 1ed077cc..470a4d43 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/graphics/Shader.groovy @@ -89,7 +89,7 @@ abstract class Shader implements GraphicsResource { def string = "${name} shader program" if (uniforms) { - string += " (${uniforms.length}, uniforms)" + string += " (${uniforms.length}, uniform(s))" } return string } 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 eabf8533..092831ad 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 @@ -88,10 +88,10 @@ class Node implements SceneEvents, Scriptable, Visitable { * @return */ // TODO: Surely this is inefficient having to calculate this each time? 🤔 - private Matrix4f getGlobalTransform() { + protected Matrix4f getGlobalTransform() { return parent != null ? - globalTransform.mul(parent.globalTransform) : + transform.mul(parent.globalTransform, globalTransform) : globalTransform.set(transform) } diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/FullScreenContainer.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/FullScreenContainer.groovy new file mode 100644 index 00000000..f4d9429b --- /dev/null +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/FullScreenContainer.groovy @@ -0,0 +1,144 @@ +/* + * 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.nodes + +import nz.net.ultraq.redhorizon.engine.graphics.Colour +import nz.net.ultraq.redhorizon.engine.graphics.GraphicsElement +import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRenderer +import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.MeshRequest +import nz.net.ultraq.redhorizon.engine.graphics.GraphicsRequests.ShaderRequest +import nz.net.ultraq.redhorizon.engine.graphics.Mesh +import nz.net.ultraq.redhorizon.engine.graphics.MeshType +import nz.net.ultraq.redhorizon.engine.graphics.Shader +import nz.net.ultraq.redhorizon.engine.graphics.VertexBufferLayout +import nz.net.ultraq.redhorizon.engine.graphics.VertexBufferLayoutPart +import nz.net.ultraq.redhorizon.engine.graphics.opengl.PrimitivesShader +import nz.net.ultraq.redhorizon.engine.scenegraph.Node +import nz.net.ultraq.redhorizon.engine.scenegraph.Scene + +import org.joml.Vector2f +import org.joml.primitives.Rectanglef + +/** + * A node used for making its children take up the whole screen. + * + * @author Emanuel Rabina + */ +class FullScreenContainer extends Node implements GraphicsElement { + + static enum FillMode { + STRETCH, + ASPECT_RATIO + } + + FillMode fillMode = FillMode.ASPECT_RATIO + + @Override + void delete(GraphicsRenderer renderer) { + } + + @Override + void init(GraphicsRenderer renderer) { + } + + @Override + void onSceneAdded(Scene scene) { + + bounds + .set(scene.window.renderResolution as Rectanglef) + .center() +// transform.translate(-bounds.lengthX() / 2 as float, -bounds.lengthY() / 2 as float) + + // Update children to take up the full screen + children.each { child -> + switch (fillMode) { + case FillMode.ASPECT_RATIO -> { + child.transform.scaleXY(bounds.calculateScaleToFit(child.bounds)) + } + case FillMode.STRETCH -> { + child.transform.scaleXY( + bounds.lengthX() / child.bounds.lengthX() as float, + bounds.lengthY() / child.bounds.lengthY() as float + ) + } + } + } + + addChild(new Outline()) + super.onSceneAdded(scene) + } + + @Override + void render(GraphicsRenderer renderer) { + } + + // TODO: Make this a primitives node + static class Outline extends Node implements GraphicsElement { + + private Mesh mesh + private Shader shader + + @Override + void delete(GraphicsRenderer renderer) { + } + + @Override + void init(GraphicsRenderer renderer) { + } + + @Override + void onSceneAdded(Scene scene) { + +// bounds.set( +// parent.bounds.minX + 5 as float, +// parent.bounds.minY + 5 as float, +// parent.bounds.maxX - 5 as float, +// parent.bounds.maxY - 5 as float +// ) + bounds.set(0, 0, 10, 10) + mesh = scene + .requestCreateOrGet(new MeshRequest( + MeshType.LINE_LOOP, + new VertexBufferLayout(VertexBufferLayoutPart.COLOUR, VertexBufferLayoutPart.POSITION), + Colour.GREEN, + bounds as Vector2f[] + )) + .get() + shader = scene + .requestCreateOrGet(new ShaderRequest(PrimitivesShader.NAME)) + .get() + + super.onSceneAdded(scene) + } + + @Override + void onSceneRemoved(Scene scene) { + + scene.requestDelete(mesh) + } + + @Override + void render(GraphicsRenderer renderer) { + + if (!mesh || !shader) { + return + } + + renderer.draw(mesh, transform, shader) + } + } +} diff --git a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy index cbe96916..864c9cce 100644 --- a/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy +++ b/redhorizon-engine/source/nz/net/ultraq/redhorizon/engine/scenegraph/nodes/Sprite.groovy @@ -49,7 +49,9 @@ class Sprite extends Node implements GraphicsElement { Sprite(ImageFile imageFile) { - bounds.set(0, 0, imageFile.width, imageFile.height) + bounds + .set(0, 0, imageFile.width, imageFile.height) + .center() this.imageFile = imageFile } @@ -69,7 +71,7 @@ class Sprite extends Node implements GraphicsElement { var format = imageFile.format mesh = scene - .requestCreateOrGet(new SpriteMeshRequest(new Rectanglef(0, 0, width, height))) + .requestCreateOrGet(new SpriteMeshRequest(bounds)) .get() shader = scene .requestCreateOrGet(new ShaderRequest(SpriteShader.NAME)) @@ -97,6 +99,7 @@ class Sprite extends Node implements GraphicsElement { return } - renderer.draw(mesh, transform, shader, material) + var globalTransform = getGlobalTransform() + renderer.draw(mesh, globalTransform, shader, material) } }