From b0c4a5cbb2895ecba6599d9d44edef089ca627c6 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 11:29:11 -0700 Subject: [PATCH 01/24] Camera can now opld external transform. --- src/main/java/net/whg/we/rendering/Camera.java | 17 ++++++++++++++++- src/test/java/unit/CameraTest.java | 10 ++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/whg/we/rendering/Camera.java b/src/main/java/net/whg/we/rendering/Camera.java index 5243528..f6292a6 100644 --- a/src/main/java/net/whg/we/rendering/Camera.java +++ b/src/main/java/net/whg/we/rendering/Camera.java @@ -10,8 +10,8 @@ */ public class Camera { - private final Transform3D transform = new Transform3D(); private final Matrix4f projectionMatrix = new Matrix4f(); + private final Transform3D transform; private float fov = (float) Math.toRadians(90f); private float nearClip = 0.1f; private float farClip = 1000f; @@ -21,6 +21,21 @@ public class Camera */ public Camera() { + this(new Transform3D()); + } + + /** + * Creates a new camera object with the default projection matrix. The transform + * for this camera is maintained externally, such as being attached to a game + * object, and will return the given transform when {@link #getTransform()} is + * called. + * + * @param transform + * - The transform this camera should use. + */ + public Camera(Transform3D transform) + { + this.transform = transform; rebuildProjectionMatrix(); } diff --git a/src/test/java/unit/CameraTest.java b/src/test/java/unit/CameraTest.java index a01a020..8a7ff3e 100644 --- a/src/test/java/unit/CameraTest.java +++ b/src/test/java/unit/CameraTest.java @@ -6,6 +6,7 @@ import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.Test; +import net.whg.we.main.Transform3D; import net.whg.we.rendering.Camera; public class CameraTest @@ -50,4 +51,13 @@ public void getProjectionMatrix() assertTrue(mat.equals(camera.getProjectionMatrix(), 0.0001f)); } + + @Test + public void externalTransform() + { + Transform3D transform = new Transform3D(); + Camera camera = new Camera(transform); + + assertTrue(transform == camera.getTransform()); + } } From 9010b5076f45e6b4aff60e6c0377ccc9cee161a1 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 12:42:52 -0700 Subject: [PATCH 02/24] Better Generic handling with getBehavior() --- src/main/java/net/whg/we/main/GameObject.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/whg/we/main/GameObject.java b/src/main/java/net/whg/we/main/GameObject.java index 672eab1..44546f2 100644 --- a/src/main/java/net/whg/we/main/GameObject.java +++ b/src/main/java/net/whg/we/main/GameObject.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; import net.whg.we.util.IDisposable; /** @@ -129,11 +128,12 @@ public void removeBehavior(AbstractBehavior behavior) * @return The behavior with the given superclass, or null if no matching * behavior is found. */ - public AbstractBehavior getBehavior(Class behaviorType) + @SuppressWarnings("unchecked") + public T getBehavior(Class behaviorType) { for (AbstractBehavior behavior : behaviors) if (behaviorType.isAssignableFrom(behavior.getClass())) - return behavior; + return (T) behavior; return null; } @@ -156,11 +156,16 @@ public List getBehaviors() * - The type of behaviors to get. * @return A list of behaviors with the given superclass. */ - public List getBehaviors(Class behaviorType) + @SuppressWarnings("unchecked") + public List getBehaviors(Class behaviorType) { - return behaviors.stream() - .filter(behavior -> behaviorType.isAssignableFrom(behavior.getClass())) - .collect(Collectors.toList()); + List list = new ArrayList<>(); + + for (AbstractBehavior behavior : behaviors) + if (behaviorType.isAssignableFrom(behavior.getClass())) + list.add((T) behavior); + + return list; } /** From ae83f39952443228189bd137bd4a038f9a1526c5 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 13:08:00 -0700 Subject: [PATCH 03/24] Deploy now contains JavaDoc and sources. --- .github/workflows/maven.yml | 2 +- pom.xml | 45 ++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 0b4c2e4..63d0e13 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -78,4 +78,4 @@ jobs: run: | mkdir -p ~/.m2 echo "github${USERNAME}${GITHUB_TOKEN}" > ~/.m2/settings.xml - mvn -B deploy + mvn -B clean deploy diff --git a/pom.xml b/pom.xml index 534b406..3b9d6f3 100644 --- a/pom.xml +++ b/pom.xml @@ -298,16 +298,39 @@ sonar-maven-plugin 3.7.0.1746 - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.0.0 - - - + + maven-source-plugin + + + attach-sources + deploy + jar-no-fork + + + + + + maven-javadoc-plugin + + + attach-javadocs + deploy + jar + + + + + + maven-deploy-plugin + + + deploy + deploy + deploy + + + + + From f489ec72a0eafa09f14c446b87ee28b728079555 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 16:12:17 -0700 Subject: [PATCH 04/24] Moved pipeline priority values to constants. --- .../whg/we/main/CullGameObjectsAction.java | 2 +- .../java/net/whg/we/main/IPipelineAction.java | 13 +- .../whg/we/main/PhysicsPipelineAction.java | 2 +- .../net/whg/we/main/PipelineConstants.java | 189 ++++++++++++++++++ .../net/whg/we/main/RenderPipelineAction.java | 2 +- .../java/net/whg/we/main/TimerAction.java | 2 +- .../net/whg/we/main/UpdatePipelineAction.java | 2 +- .../java/unit/CullGameObjectsActionTest.java | 3 +- .../java/unit/PhysicsPipelineActionTest.java | 3 +- .../java/unit/RenderPipelineActionTest.java | 3 +- src/test/java/unit/TimerActionTest.java | 3 +- .../java/unit/UpdatePipelineActionTest.java | 3 +- 12 files changed, 205 insertions(+), 22 deletions(-) create mode 100644 src/main/java/net/whg/we/main/PipelineConstants.java diff --git a/src/main/java/net/whg/we/main/CullGameObjectsAction.java b/src/main/java/net/whg/we/main/CullGameObjectsAction.java index 670b412..91b8963 100644 --- a/src/main/java/net/whg/we/main/CullGameObjectsAction.java +++ b/src/main/java/net/whg/we/main/CullGameObjectsAction.java @@ -30,6 +30,6 @@ public void disableGameObject(GameObject gameObject) @Override public int getPriority() { - return 40000; + return PipelineConstants.DISPOSE_GAMEOBJECTS; } } diff --git a/src/main/java/net/whg/we/main/IPipelineAction.java b/src/main/java/net/whg/we/main/IPipelineAction.java index 92f9353..bd7a924 100644 --- a/src/main/java/net/whg/we/main/IPipelineAction.java +++ b/src/main/java/net/whg/we/main/IPipelineAction.java @@ -11,18 +11,7 @@ *

* Pipeline actions should also override the default priority level for loop * actions to ensure pipeline actions occur in the desired order. Default - * priorities are: - *

    - *
  • Calculate Time Stamps at -1000000
  • - *
  • Physics Updates at -10000
  • - *
  • Updates at 0
  • - *
  • Animation Updates at 10000
  • - *
  • Late Updates at 20000
  • - *
  • Rendering Solids at 30000
  • - *
  • Rendering Transparents at 32500
  • - *
  • Dispose GameObjects at 40000
  • - *
  • End Frame at 1000000
  • - *
+ * priorities are defined in the {@link PipelineConstants}. */ public interface IPipelineAction extends ILoopAction { diff --git a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java b/src/main/java/net/whg/we/main/PhysicsPipelineAction.java index f4a49fc..8b64278 100644 --- a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java +++ b/src/main/java/net/whg/we/main/PhysicsPipelineAction.java @@ -31,6 +31,6 @@ public void disableBehavior(AbstractBehavior behavior) @Override public int getPriority() { - return -10000; + return PipelineConstants.PHYSICS_UPDATES; } } diff --git a/src/main/java/net/whg/we/main/PipelineConstants.java b/src/main/java/net/whg/we/main/PipelineConstants.java new file mode 100644 index 0000000..255e5bd --- /dev/null +++ b/src/main/java/net/whg/we/main/PipelineConstants.java @@ -0,0 +1,189 @@ +package net.whg.we.main; + +/** + * This class contains a set of constants which are used to define the major + * steps within the game loop pipeline. This can be used for determining proper + * offsets for when events should occur. + *

+ * When handling priority values for gameloop actions, direct values, such as + * 23 should not be used. Instead, it is safer and future-proof to + * use values such as PipelineConstants.FRAME_UPDATES + 23. This + * has no effect on preformance and will not break if default values are + * adjusted in the future. + */ +public final class PipelineConstants +{ + /** + * The first call within a frame. Used to preform actions like calculating delta + * time and required phyiscs updates. + *

+ * Value is equal to {@value}. + */ + public static final int CALCULATE_TIMESTAMPS = -1000000; + + /** + * Called early within the frame to update all physics based or other + * time-sensitive updates. This includes things such as player input or AI + * updates. Physics are called on a reliable timer, and are executed multiple + * times per frame, if needed, to catch up, or sometimes ignored completely in + * some frames.s + *

+ * Value is equal to {@value}. + */ + public static final int PHYSICS_UPDATES = -10000; + + /** + * Called to prepare the scene for rendering. One update is called each frame to + * prepare the frame, and only updates events that change each frame. This + * includes actions such as camera transformation, particle effects, or + * interpolating physics updates. This update is also commonly used to indicate + * whether an object will be rendered or not via frustrum culling and distance + * from camera. + *

+ * Value is equal to {@value}. + */ + public static final int FRAME_UPDATES = 0; + + /** + * Called after the update method in order to handle animation updates. This + * usually includes updating skeletal animations and processing particle + * effects. + *

+ * Value is equal to {@value}. + */ + public static final int ANIMATION_UPDATES = 10000; + + /** + * Called after animation updates to post-process skeletal animations. This is + * mainly targeted towards actions such as tweaking foot IK or handling head + * look targets which are applied after the animation pose is determined. + *

+ * Value is equal to {@value}. + */ + public static final int IK_UPDATES = 12500; + + /** + * Called after all updates are preformed on the scene to handle any last minute + * tweaks which would occur before the scene is officially rendered. This is + * most often used for post-update listeners to handle updates which are + * effected by the previous states but effect no others. A common example of + * this is a camera which follows a target, which would be updated in this step + * to ensure the camera follows a smooth path and is called after all other + * entities are in their final render location. + *

+ * Value is equal to {@value}. + */ + public static final int LATE_UPDATES = 20000; + + /** + * This is called at the start of the rendering part of the pipeline to screen + * the screen to render to. This is primarily used to clear the color and depth + * buffer fields. If a skybox is being used, it is rendered during this step. If + * post processing is being used, the buffer frame is prepared during this step. + *

+ * Value is equal to {@value}. + */ + public static final int CLEAR_SCREEN = 29000; + + /** + * After clearing the screen, next the solid objects within the scene are + * rendered. Objects are rendered directly to the output in order from closest + * objects to furthest objects. In a forward rendering path, lighting is applied + * to objects as they are rendered. In a deffered rendering path, objects are + * rendered to the material buffer textures. + *

+ * Value is equal to {@value}. + */ + public static final int RENDER_SOLIDS = 30000; + + /** + * After rendering solids to the screen, solid decals are rendered next. The + * properties of rendering decals is nearly identical to rendering solids. + *

+ * Value is equal to {@value}. + */ + public static final int RENDER_DECALS = 32500; + + /** + * In a deffered rendering pipeline, the lighting updates are applied during + * this phase to draw all lights to the material buffer textures. This step is + * ignored in a forward lighting pipeline. + *

+ * Value is equal to {@value}. + */ + public static final int DEFFERED_RENDERING = 35000; + + /** + * Due to the nature of rendering transparent objects, all transparent objects + * must be rendered together in a single pass. This includes all transparent + * materials and particles. All transparent objects are rendered with a forward + * render pass, regardless of render pipeline, in back-to-front order. + *

+ * Value is equal to {@value}. + */ + public static final int RENDER_TRANSPARENTS = 40000; + + /** + * After the scene is rendered, post processing effects are applied. This + * includes actions such as SSAO or depth blur. If post processing is not + * enabled, nothing happens during this step. + *

+ * Value is equal to {@value}. + */ + public static final int POST_PROCESSING = 50000; + + /** + * To prepare the frame to render UI, the depth buffer is cleared here. + *

+ * Value is equal to {@value}. + */ + public static final int CLEAR_DEPTH = 59000; + + /** + * The UI rendering is all handled in a single pass, in back-to-front order. + *

+ * Value is equal to {@value}. + */ + public static final int RENDER_UI = 60000; + + /** + * At the effective end of the frame, game objects which were marked for removal + * during this frame are removed and disposed. This step is also used for + * general cleanup throughout the scene including disposing unused resources and + * resizing buffers or pools. + *

+ * Value is equal to {@value}. + */ + public static final int DISPOSE_GAMEOBJECTS = 70000; + + /** + * The end frame step is used to trigger some events to mark the frame as + * complete and prepare local buffers for the next frame. A common use-case of + * this is the Input class, which swaps key binding buffers, marking currently + * pressed keys as occuring on the previous frame and preparing to recieve new + * input updates. This step marks the end of frame for all game logic. + *

+ * Value is equal to {@value}. + */ + public static final int ENDFRAME = 80000; + + /** + * Called after all game-logic for a frame is completed, the window is polled + * for new user input events as well as triggering the GPU to swap render + * buffers, pushing the rendered image to the screen. + *

+ * Value is equal to {@value}. + */ + public static final int POLL_WINDOW_EVENTS = 90000; + + /** + * If a framerate cap is enabled, this step serves to sleep the thread as needed + * to enforce that the framerate cap is not exceeded. + *

+ * Value is equal to {@value}. + */ + public static final int FRAMERATE_LIMITER = 100000; + + private PipelineConstants() + {} +} diff --git a/src/main/java/net/whg/we/main/RenderPipelineAction.java b/src/main/java/net/whg/we/main/RenderPipelineAction.java index 386ffe6..23f1275 100644 --- a/src/main/java/net/whg/we/main/RenderPipelineAction.java +++ b/src/main/java/net/whg/we/main/RenderPipelineAction.java @@ -74,6 +74,6 @@ public List renderBehaviors() @Override public int getPriority() { - return 30000; + return PipelineConstants.RENDER_SOLIDS; } } diff --git a/src/main/java/net/whg/we/main/TimerAction.java b/src/main/java/net/whg/we/main/TimerAction.java index c451d8a..02c9283 100644 --- a/src/main/java/net/whg/we/main/TimerAction.java +++ b/src/main/java/net/whg/we/main/TimerAction.java @@ -29,6 +29,6 @@ public void run() @Override public int getPriority() { - return -1000000; + return PipelineConstants.CALCULATE_TIMESTAMPS; } } diff --git a/src/main/java/net/whg/we/main/UpdatePipelineAction.java b/src/main/java/net/whg/we/main/UpdatePipelineAction.java index e182bdc..c292f14 100644 --- a/src/main/java/net/whg/we/main/UpdatePipelineAction.java +++ b/src/main/java/net/whg/we/main/UpdatePipelineAction.java @@ -31,6 +31,6 @@ public void disableBehavior(AbstractBehavior behavior) @Override public int getPriority() { - return 0; + return PipelineConstants.FRAME_UPDATES; } } diff --git a/src/test/java/unit/CullGameObjectsActionTest.java b/src/test/java/unit/CullGameObjectsActionTest.java index bbfc68a..baa86a5 100644 --- a/src/test/java/unit/CullGameObjectsActionTest.java +++ b/src/test/java/unit/CullGameObjectsActionTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import net.whg.we.main.CullGameObjectsAction; import net.whg.we.main.GameObject; +import net.whg.we.main.PipelineConstants; import net.whg.we.main.Scene; public class CullGameObjectsActionTest @@ -34,6 +35,6 @@ public void runAction() @Test public void defaultPriority() { - assertEquals(40000, new CullGameObjectsAction().getPriority()); + assertEquals(PipelineConstants.DISPOSE_GAMEOBJECTS, new CullGameObjectsAction().getPriority()); } } diff --git a/src/test/java/unit/PhysicsPipelineActionTest.java b/src/test/java/unit/PhysicsPipelineActionTest.java index a732d96..5234c31 100644 --- a/src/test/java/unit/PhysicsPipelineActionTest.java +++ b/src/test/java/unit/PhysicsPipelineActionTest.java @@ -6,13 +6,14 @@ import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IFixedUpdateable; import net.whg.we.main.PhysicsPipelineAction; +import net.whg.we.main.PipelineConstants; public class PhysicsPipelineActionTest { @Test public void ensurePipelinePriority() { - assertEquals(-10000, new PhysicsPipelineAction().getPriority()); + assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipelineAction().getPriority()); } @Test diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index ff85645..bdb7591 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.verify; import org.junit.Test; import net.whg.we.main.GameObject; +import net.whg.we.main.PipelineConstants; import net.whg.we.main.RenderBehavior; import net.whg.we.main.RenderPipelineAction; import net.whg.we.rendering.Camera; @@ -74,6 +75,6 @@ public void renderElements_noCamera() @Test public void ensureCorrectPriority() { - assertEquals(30000, new RenderPipelineAction().getPriority()); + assertEquals(PipelineConstants.RENDER_SOLIDS, new RenderPipelineAction().getPriority()); } } diff --git a/src/test/java/unit/TimerActionTest.java b/src/test/java/unit/TimerActionTest.java index 44ec334..5f5cceb 100644 --- a/src/test/java/unit/TimerActionTest.java +++ b/src/test/java/unit/TimerActionTest.java @@ -4,6 +4,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import org.junit.Test; +import net.whg.we.main.PipelineConstants; import net.whg.we.main.Timer; import net.whg.we.main.TimerAction; @@ -23,6 +24,6 @@ public void beginFrameOnRun() @Test public void priorityIs_Negative1000000() { - assertEquals(-1000000, new TimerAction(mock(Timer.class)).getPriority()); + assertEquals(PipelineConstants.CALCULATE_TIMESTAMPS, new TimerAction(mock(Timer.class)).getPriority()); } } diff --git a/src/test/java/unit/UpdatePipelineActionTest.java b/src/test/java/unit/UpdatePipelineActionTest.java index ccd8aa0..a0d0308 100644 --- a/src/test/java/unit/UpdatePipelineActionTest.java +++ b/src/test/java/unit/UpdatePipelineActionTest.java @@ -5,6 +5,7 @@ import org.junit.Test; import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IUpdateable; +import net.whg.we.main.PipelineConstants; import net.whg.we.main.UpdatePipelineAction; public class UpdatePipelineActionTest @@ -12,7 +13,7 @@ public class UpdatePipelineActionTest @Test public void ensurePipelinePriority() { - assertEquals(0, new UpdatePipelineAction().getPriority()); + assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipelineAction().getPriority()); } @Test From c89451f2e75b63a92c6db568b648583c6e5c048d Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 16:15:44 -0700 Subject: [PATCH 05/24] Added version tags to maven plugins. --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3b9d6f3..d873735 100644 --- a/pom.xml +++ b/pom.xml @@ -300,7 +300,9 @@ + org.apache.maven.plugins maven-source-plugin + 3.2.0 attach-sources @@ -311,7 +313,9 @@ + org.apache.maven.plugins maven-javadoc-plugin + 3.1.1 attach-javadocs @@ -321,8 +325,10 @@ - + + org.apache.maven.plugins maven-deploy-plugin + 3.0.0-M1 deploy From 21b43e29f57866713b2fb9e5a4b17efa7a6b616e Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 16:18:19 -0700 Subject: [PATCH 06/24] Moved render actions to correct package. --- .../java/net/whg/we/{main => rendering}/RenderBehavior.java | 6 ++---- .../whg/we/{main => rendering}/RenderPipelineAction.java | 6 ++++-- src/test/java/manual/Scene1Example.java | 2 +- src/test/java/unit/RenderBehaviorTest.java | 4 ++-- src/test/java/unit/RenderPipelineActionTest.java | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) rename src/main/java/net/whg/we/{main => rendering}/RenderBehavior.java (95%) rename src/main/java/net/whg/we/{main => rendering}/RenderPipelineAction.java (92%) diff --git a/src/main/java/net/whg/we/main/RenderBehavior.java b/src/main/java/net/whg/we/rendering/RenderBehavior.java similarity index 95% rename from src/main/java/net/whg/we/main/RenderBehavior.java rename to src/main/java/net/whg/we/rendering/RenderBehavior.java index e6c738e..162f32c 100644 --- a/src/main/java/net/whg/we/main/RenderBehavior.java +++ b/src/main/java/net/whg/we/rendering/RenderBehavior.java @@ -1,9 +1,7 @@ -package net.whg.we.main; +package net.whg.we.rendering; import org.joml.Matrix4f; -import net.whg.we.rendering.Camera; -import net.whg.we.rendering.IMesh; -import net.whg.we.rendering.Material; +import net.whg.we.main.AbstractBehavior; /** * This behavior is used as a method for rendering a mesh to the scene. When diff --git a/src/main/java/net/whg/we/main/RenderPipelineAction.java b/src/main/java/net/whg/we/rendering/RenderPipelineAction.java similarity index 92% rename from src/main/java/net/whg/we/main/RenderPipelineAction.java rename to src/main/java/net/whg/we/rendering/RenderPipelineAction.java index 23f1275..06e86f6 100644 --- a/src/main/java/net/whg/we/main/RenderPipelineAction.java +++ b/src/main/java/net/whg/we/rendering/RenderPipelineAction.java @@ -1,9 +1,11 @@ -package net.whg.we.main; +package net.whg.we.rendering; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import net.whg.we.rendering.Camera; +import net.whg.we.main.AbstractBehavior; +import net.whg.we.main.IPipelineAction; +import net.whg.we.main.PipelineConstants; /** * The renderer pipeline action is used to render elements within a scene. By diff --git a/src/test/java/manual/Scene1Example.java b/src/test/java/manual/Scene1Example.java index 456046f..722e564 100644 --- a/src/test/java/manual/Scene1Example.java +++ b/src/test/java/manual/Scene1Example.java @@ -13,9 +13,9 @@ import net.whg.we.main.GameLoop; import net.whg.we.main.GameObject; import net.whg.we.main.Input; -import net.whg.we.main.RenderBehavior; import net.whg.we.main.Scene; import net.whg.we.main.UserControlsUpdater; +import net.whg.we.rendering.RenderBehavior; import net.whg.we.rendering.Camera; import net.whg.we.rendering.Color; import net.whg.we.rendering.CullingMode; diff --git a/src/test/java/unit/RenderBehaviorTest.java b/src/test/java/unit/RenderBehaviorTest.java index 96524a7..dc7190e 100644 --- a/src/test/java/unit/RenderBehaviorTest.java +++ b/src/test/java/unit/RenderBehaviorTest.java @@ -9,8 +9,8 @@ import org.junit.Test; import net.whg.we.main.GameObject; import net.whg.we.rendering.Material; -import net.whg.we.main.RenderBehavior; -import net.whg.we.main.RenderPipelineAction; +import net.whg.we.rendering.RenderBehavior; +import net.whg.we.rendering.RenderPipelineAction; import net.whg.we.main.Scene; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index bdb7591..23d4142 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -8,8 +8,8 @@ import org.junit.Test; import net.whg.we.main.GameObject; import net.whg.we.main.PipelineConstants; -import net.whg.we.main.RenderBehavior; -import net.whg.we.main.RenderPipelineAction; +import net.whg.we.rendering.RenderBehavior; +import net.whg.we.rendering.RenderPipelineAction; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; import net.whg.we.rendering.Material; From 567c84ef787a869923b2f4f5af9993032eb06dab Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 16:18:36 -0700 Subject: [PATCH 07/24] Removed manual test. --- src/test/java/manual/Scene1Example.java | 151 ------------------------ 1 file changed, 151 deletions(-) delete mode 100644 src/test/java/manual/Scene1Example.java diff --git a/src/test/java/manual/Scene1Example.java b/src/test/java/manual/Scene1Example.java deleted file mode 100644 index 722e564..0000000 --- a/src/test/java/manual/Scene1Example.java +++ /dev/null @@ -1,151 +0,0 @@ -package manual; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.List; -import org.joml.Quaternionf; -import net.whg.we.external.AssimpAPI; -import net.whg.we.external.GlfwApi; -import net.whg.we.external.OpenGLApi; -import net.whg.we.main.GameLoop; -import net.whg.we.main.GameObject; -import net.whg.we.main.Input; -import net.whg.we.main.Scene; -import net.whg.we.main.UserControlsUpdater; -import net.whg.we.rendering.RenderBehavior; -import net.whg.we.rendering.Camera; -import net.whg.we.rendering.Color; -import net.whg.we.rendering.CullingMode; -import net.whg.we.rendering.IMesh; -import net.whg.we.rendering.IRenderingEngine; -import net.whg.we.rendering.IScreenClearHandler; -import net.whg.we.rendering.IShader; -import net.whg.we.rendering.ITexture; -import net.whg.we.rendering.Material; -import net.whg.we.rendering.RawShaderCode; -import net.whg.we.rendering.TextureData; -import net.whg.we.rendering.VertexData; -import net.whg.we.rendering.TextureData.SampleMode; -import net.whg.we.rendering.opengl.IOpenGL; -import net.whg.we.rendering.opengl.OpenGLRenderingEngine; -import net.whg.we.resource.ModelLoader; -import net.whg.we.resource.Resource; -import net.whg.we.resource.TextureLoader; -import net.whg.we.resource.assimp.IAssimp; -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowAdapter; -import net.whg.we.window.WindowSettings; -import net.whg.we.window.glfw.GlfwWindow; -import net.whg.we.window.glfw.IGlfw; - -public class Scene1Example -{ - public static void main(String[] args) throws IOException - { - IGlfw glfw = new GlfwApi(); - IOpenGL opengl = new OpenGLApi(true); - IAssimp assimp = new AssimpAPI(); - - IRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); - WindowSettings windowSettings = new WindowSettings(); - IWindow window = new GlfwWindow(glfw, renderingEngine, windowSettings); - - UserControlsUpdater.bind(window); - - IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); - screenClear.setClearColor(new Color(0.15f, 0.15f, 0.15f)); - - ModelLoader modelLoader = new ModelLoader(assimp); - - List resources = modelLoader.loadScene(new File("src/test/res/cube.obj")); - VertexData cubeData = (VertexData) resources.get(0) - .getData(); - String vertShader = - new String(Files.readAllBytes(Paths.get("src/test/res/normal_shader.vert")), StandardCharsets.UTF_8); - String fragShader = - new String(Files.readAllBytes(Paths.get("src/test/res/normal_shader.frag")), StandardCharsets.UTF_8); - - Camera camera = new Camera(); - camera.getTransform() - .setPosition(0, 0, 5); - - IMesh mesh = renderingEngine.createMesh(); - mesh.update(cubeData); - - IShader shader = renderingEngine.createShader(); - shader.compile(new RawShaderCode(vertShader, fragShader)); - - TextureData textureData = TextureLoader.loadTexture(new File("src/test/res/grass.png")); - textureData.setSampleMode(SampleMode.NEAREST); - textureData.setMipmap(true); - ITexture texture = renderingEngine.createTexture(); - texture.update(textureData); - - renderingEngine.setCullingMode(CullingMode.NONE); - - Material material = new Material(shader); - material.setTextures(new ITexture[] {texture}, new String[] {"diffuse"}); - - GameObject cube = new GameObject(); - RenderBehavior renderer = new RenderBehavior(); - renderer.setMesh(mesh); - renderer.setMaterial(material); - cube.addBehavior(renderer); - - Scene scene = new Scene(); - scene.addGameObject(cube); - - cube.getTransform() - .setRotation(new Quaternionf(0.5f, 0.5f, 0f, 1f)); - - GameLoop gameLoop = new GameLoop(); - - gameLoop.addAction(() -> - { - if (Input.isMouseButtonDown(0)) - { - final float s = 0.01f; - float dx = Input.getMouseDeltaX() * s; - float dy = Input.getMouseDeltaY() * s; - - cube.getTransform() - .getRotation() - .rotateX(dy) - .rotateY(dx); - } - - if (Input.getScrollWheelDelta() != 0) - { - cube.getTransform() - .setSize(cube.getTransform() - .getSize().x - * (float) Math.pow(1.1f, -Input.getScrollWheelDelta())); - } - }); - gameLoop.addAction(() -> screenClear.clearScreen()); - // gameLoop.addAction(() -> scene.getRenderer() - // .render(camera)); - gameLoop.addAction(() -> Input.endFrame()); - gameLoop.addAction(() -> window.pollEvents()); - - window.addWindowListener(new IWindowAdapter() - { - @Override - public void onWindowRequestClose(IWindow window) - { - gameLoop.stop(); - } - }); - - gameLoop.loop(); - - mesh.dispose(); - shader.dispose(); - texture.dispose(); - - window.dispose(); - } -} From 75773d2f9eb3fbabe749ce916f0e77651885d5f3 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 16:20:10 -0700 Subject: [PATCH 08/24] Removed unused test resources. --- src/test/res/cube.mtl | 12 --------- src/test/res/cube.obj | 46 --------------------------------- src/test/res/normal_shader.frag | 13 ---------- src/test/res/normal_shader.vert | 18 ------------- 4 files changed, 89 deletions(-) delete mode 100644 src/test/res/cube.mtl delete mode 100644 src/test/res/cube.obj delete mode 100644 src/test/res/normal_shader.frag delete mode 100644 src/test/res/normal_shader.vert diff --git a/src/test/res/cube.mtl b/src/test/res/cube.mtl deleted file mode 100644 index e8374a5..0000000 --- a/src/test/res/cube.mtl +++ /dev/null @@ -1,12 +0,0 @@ -# Blender MTL File: 'None' -# Material Count: 1 - -newmtl Material -Ns 323.999994 -Ka 1.000000 1.000000 1.000000 -Kd 0.800000 0.800000 0.800000 -Ks 0.500000 0.500000 0.500000 -Ke 0.000000 0.000000 0.000000 -Ni 1.450000 -d 1.000000 -illum 2 diff --git a/src/test/res/cube.obj b/src/test/res/cube.obj deleted file mode 100644 index 6f999ff..0000000 --- a/src/test/res/cube.obj +++ /dev/null @@ -1,46 +0,0 @@ -# Blender v2.81 (sub 16) OBJ File: '' -# www.blender.org -mtllib cube.mtl -o Cube -v 1.000000 1.000000 -1.000000 -v 1.000000 -1.000000 -1.000000 -v 1.000000 1.000000 1.000000 -v 1.000000 -1.000000 1.000000 -v -1.000000 1.000000 -1.000000 -v -1.000000 -1.000000 -1.000000 -v -1.000000 1.000000 1.000000 -v -1.000000 -1.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 0.000000 -vt 1.000000 0.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vt 1.000000 1.000000 -vt 0.000000 1.000000 -vn 0.0000 1.0000 0.0000 -vn 0.0000 0.0000 1.0000 -vn -1.0000 0.0000 0.0000 -vn 0.0000 -1.0000 0.0000 -vn 1.0000 0.0000 0.0000 -vn 0.0000 0.0000 -1.0000 -usemtl Material -s off -f 1/1/1 5/2/1 7/3/1 3/4/1 -f 4/5/2 3/6/2 7/3/2 8/7/2 -f 8/8/3 7/9/3 5/10/3 6/11/3 -f 6/12/4 2/13/4 4/14/4 8/7/4 -f 2/15/5 1/16/5 3/17/5 4/18/5 -f 6/12/6 5/2/6 1/19/6 2/20/6 diff --git a/src/test/res/normal_shader.frag b/src/test/res/normal_shader.frag deleted file mode 100644 index cd1c056..0000000 --- a/src/test/res/normal_shader.frag +++ /dev/null @@ -1,13 +0,0 @@ -#version 330 core - -uniform sampler2D diffuse; - -in vec3 pass_normal; -in vec2 pass_uv; - -out vec4 color; - -void main() -{ - color = vec4(texture(diffuse, pass_uv).rgb, 1.0); -} \ No newline at end of file diff --git a/src/test/res/normal_shader.vert b/src/test/res/normal_shader.vert deleted file mode 100644 index 7ef262c..0000000 --- a/src/test/res/normal_shader.vert +++ /dev/null @@ -1,18 +0,0 @@ -#version 330 - -uniform mat4 mvp; - -layout(location = 0) in vec3 pos; -layout(location = 1) in vec3 normal; -layout(location = 4) in vec2 uv; - -out vec3 pass_normal; -out vec2 pass_uv; - -void main() -{ - gl_Position = mvp * vec4(pos, 1.0); - - pass_normal = normal; - pass_uv = uv; -} \ No newline at end of file From a14b7e196cdca3784846b8da4291db5d5fb5716f Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 22:47:57 -0700 Subject: [PATCH 09/24] Physics updates are now handled correctly. --- .../whg/we/main/PhysicsPipelineAction.java | 25 ++++++++++- .../java/unit/PhysicsPipelineActionTest.java | 44 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java b/src/main/java/net/whg/we/main/PhysicsPipelineAction.java index 8b64278..cb380e5 100644 --- a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java +++ b/src/main/java/net/whg/we/main/PhysicsPipelineAction.java @@ -3,15 +3,36 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +/** + * The physics pipeline action is an action in charge of triggering physics + * updates each frame based on the physics framerate. + */ public class PhysicsPipelineAction implements IPipelineAction { private final List objects = new CopyOnWriteArrayList<>(); + private final Timer timer; + + /** + * Creates a new Physics pipeline action. + * + * @param timer + * - The timer to being this action to. + */ + public PhysicsPipelineAction(Timer timer) + { + this.timer = timer; + } @Override public void run() { - for (IFixedUpdateable obj : objects) - obj.fixedUpdate(); + while (timer.getPhysicsFrame() < timer.getIdealPhysicsFrame()) + { + timer.incrementPhysicsFrame(); + + for (IFixedUpdateable obj : objects) + obj.fixedUpdate(); + } } @Override diff --git a/src/test/java/unit/PhysicsPipelineActionTest.java b/src/test/java/unit/PhysicsPipelineActionTest.java index 5234c31..7273da6 100644 --- a/src/test/java/unit/PhysicsPipelineActionTest.java +++ b/src/test/java/unit/PhysicsPipelineActionTest.java @@ -2,24 +2,31 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.junit.Test; import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IFixedUpdateable; import net.whg.we.main.PhysicsPipelineAction; import net.whg.we.main.PipelineConstants; +import net.whg.we.main.Timer; public class PhysicsPipelineActionTest { @Test public void ensurePipelinePriority() { - assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipelineAction().getPriority()); + assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipelineAction(mock(Timer.class)).getPriority()); } @Test public void updateBehaviors() { - PhysicsPipelineAction action = new PhysicsPipelineAction(); + Timer timer = mock(Timer.class); + when(timer.getIdealPhysicsFrame()).thenReturn(1L); + when(timer.getPhysicsFrame()).thenReturn(0L) + .thenReturn(1L); + + PhysicsPipelineAction action = new PhysicsPipelineAction(timer); action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur UpdatableAction behavior = new UpdatableAction(); @@ -34,6 +41,39 @@ public void updateBehaviors() assertEquals(1, behavior.calls); } + @Test + public void update_twice() + { + Timer timer = mock(Timer.class); + when(timer.getIdealPhysicsFrame()).thenReturn(1L) + .thenReturn(2L); + when(timer.getPhysicsFrame()).thenReturn(0L) + .thenReturn(1L) + .thenReturn(2L); + + PhysicsPipelineAction action = new PhysicsPipelineAction(timer); + UpdatableAction behavior = new UpdatableAction(); + action.enableBehavior(behavior); + + action.run(); + assertEquals(2, behavior.calls); + } + + @Test + public void update_never() + { + Timer timer = mock(Timer.class); + when(timer.getIdealPhysicsFrame()).thenReturn(2L); + when(timer.getPhysicsFrame()).thenReturn(2L); + + PhysicsPipelineAction action = new PhysicsPipelineAction(timer); + UpdatableAction behavior = new UpdatableAction(); + action.enableBehavior(behavior); + + action.run(); + assertEquals(0, behavior.calls); + } + class UpdatableAction extends AbstractBehavior implements IFixedUpdateable { int calls = 0; From 6c31653449726f2b3697d2237d6680dc4d9b7f6f Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 23:07:56 -0700 Subject: [PATCH 10/24] The screen object is now self-contained. --- src/main/java/net/whg/we/main/Screen.java | 61 +++++++----- .../net/whg/we/main/UserControlsUpdater.java | 7 +- .../java/net/whg/we/rendering/Camera.java | 15 ++- src/test/java/unit/CameraTest.java | 13 ++- src/test/java/unit/MaterialTest.java | 3 +- src/test/java/unit/RenderBehaviorTest.java | 9 +- .../java/unit/RenderPipelineActionTest.java | 3 +- src/test/java/unit/ScreenTest.java | 93 ++++++++++++------- 8 files changed, 132 insertions(+), 72 deletions(-) diff --git a/src/main/java/net/whg/we/main/Screen.java b/src/main/java/net/whg/we/main/Screen.java index f2d37a9..cbe41b7 100644 --- a/src/main/java/net/whg/we/main/Screen.java +++ b/src/main/java/net/whg/we/main/Screen.java @@ -1,28 +1,50 @@ package net.whg.we.main; +import net.whg.we.window.IWindow; +import net.whg.we.window.IWindowAdapter; +import net.whg.we.window.WindowSettings; + /** - * The screen object is a public static interface which can be used to determine - * various information about the state of the game screen, as well as modidy the - * screen in certain ways. + * The screen object is an object which can be used to determine various + * information about the state of the game screen, as well as modidy the screen + * in certain ways. Each screen is bound to a single window. */ -public final class Screen +public class Screen { - private static int width; - private static int height; + /** + * A private listener class which recieved events from the window to store + * within the screen object. + */ + private class ScreenListener extends IWindowAdapter + { + @Override + public void onWindowResized(IWindow window, int width, int height) + { + Screen.this.width = width; + Screen.this.height = height; + } + + @Override + public void onWindowUpdated(IWindow window) + { + WindowSettings settings = window.getProperties(); + Screen.this.width = settings.getWidth(); + Screen.this.height = settings.getHeight(); + } + } + + private int width; + private int height; /** - * Assigns the current window size. This should be called every time the window - * is resized. + * Creates a new screen object. * - * @param width - * - The width of the window. - * @param height - * - The height of the window. + * @param window + * - The window this screen is bound to. */ - static void updateWindowSize(int width, int height) + public Screen(IWindow window) { - Screen.width = width; - Screen.height = height; + window.addWindowListener(new ScreenListener()); } /** @@ -30,7 +52,7 @@ static void updateWindowSize(int width, int height) * * @return The width. */ - public static int getWidth() + public int getWidth() { return width; } @@ -40,7 +62,7 @@ public static int getWidth() * * @return The height. */ - public static int getHeight() + public int getHeight() { return height; } @@ -50,11 +72,8 @@ public static int getHeight() * * @return The aspect ratio. */ - public static float getAspect() + public float getAspect() { return (float) width / height; } - - private Screen() - {} } diff --git a/src/main/java/net/whg/we/main/UserControlsUpdater.java b/src/main/java/net/whg/we/main/UserControlsUpdater.java index 216803d..a29ef46 100644 --- a/src/main/java/net/whg/we/main/UserControlsUpdater.java +++ b/src/main/java/net/whg/we/main/UserControlsUpdater.java @@ -58,12 +58,7 @@ private void setWindow(IWindow window) this.window = window; if (this.window != null) - { this.window.addWindowListener(this); - - WindowSettings settings = this.window.getProperties(); - Screen.updateWindowSize(settings.getWidth(), settings.getHeight()); - } } /** @@ -86,7 +81,7 @@ public void onWindowUpdated(IWindow window) @Override public void onWindowResized(IWindow window, int width, int height) { - Screen.updateWindowSize(width, height); + // Do nothing } @Override diff --git a/src/main/java/net/whg/we/rendering/Camera.java b/src/main/java/net/whg/we/rendering/Camera.java index f6292a6..2845d15 100644 --- a/src/main/java/net/whg/we/rendering/Camera.java +++ b/src/main/java/net/whg/we/rendering/Camera.java @@ -12,16 +12,20 @@ public class Camera { private final Matrix4f projectionMatrix = new Matrix4f(); private final Transform3D transform; + private final Screen screen; private float fov = (float) Math.toRadians(90f); private float nearClip = 0.1f; private float farClip = 1000f; /** * Creates a new camera object with the default projection matrix. + * + * @param screen + * - The screen this camera pulls information from. */ - public Camera() + public Camera(Screen screen) { - this(new Transform3D()); + this(new Transform3D(), screen); } /** @@ -32,10 +36,13 @@ public Camera() * * @param transform * - The transform this camera should use. + * @param screen + * - The screen this camera pulls information from. */ - public Camera(Transform3D transform) + public Camera(Transform3D transform, Screen screen) { this.transform = transform; + this.screen = screen; rebuildProjectionMatrix(); } @@ -44,7 +51,7 @@ public Camera(Transform3D transform) */ private void rebuildProjectionMatrix() { - float aspect = Screen.getAspect(); + float aspect = screen.getAspect(); projectionMatrix.identity(); projectionMatrix.perspective(fov, aspect, nearClip, farClip); diff --git a/src/test/java/unit/CameraTest.java b/src/test/java/unit/CameraTest.java index 8a7ff3e..d367070 100644 --- a/src/test/java/unit/CameraTest.java +++ b/src/test/java/unit/CameraTest.java @@ -2,10 +2,13 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.joml.Matrix4f; import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.Test; +import net.whg.we.main.Screen; import net.whg.we.main.Transform3D; import net.whg.we.rendering.Camera; @@ -14,7 +17,7 @@ public class CameraTest @Test public void defaultProperties() { - Camera camera = new Camera(); + Camera camera = new Camera(mock(Screen.class)); assertEquals(0.1f, camera.getNearClip(), 0f); assertEquals(1000f, camera.getFarClip(), 0f); @@ -31,7 +34,7 @@ public void defaultProperties() @Test public void setProperties() { - Camera camera = new Camera(); + Camera camera = new Camera(mock(Screen.class)); camera.setClippingDistance(15f, 30f); assertEquals(15f, camera.getNearClip(), 0f); @@ -44,7 +47,9 @@ public void setProperties() @Test public void getProjectionMatrix() { - Camera camera = new Camera(); + Screen screen = mock(Screen.class); + when(screen.getAspect()).thenReturn(4f / 3f); + Camera camera = new Camera(screen); Matrix4f mat = new Matrix4f(); mat.perspective((float) Math.PI / 2f, 4f / 3f, 0.1f, 1000f); @@ -56,7 +61,7 @@ public void getProjectionMatrix() public void externalTransform() { Transform3D transform = new Transform3D(); - Camera camera = new Camera(transform); + Camera camera = new Camera(transform, mock(Screen.class)); assertTrue(transform == camera.getTransform()); } diff --git a/src/test/java/unit/MaterialTest.java b/src/test/java/unit/MaterialTest.java index 43b1f4f..fbcd4e2 100644 --- a/src/test/java/unit/MaterialTest.java +++ b/src/test/java/unit/MaterialTest.java @@ -10,6 +10,7 @@ import org.joml.Matrix4f; import org.junit.Test; import net.whg.we.rendering.Material; +import net.whg.we.main.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IShader; import net.whg.we.rendering.ITexture; @@ -57,7 +58,7 @@ public void cameraMatrix() IShader shader = mock(IShader.class); Material material = new Material(shader); - Camera camera = new Camera(); + Camera camera = new Camera(mock(Screen.class)); Matrix4f matrix = new Matrix4f(); material.setCameraMatrix(camera, matrix); diff --git a/src/test/java/unit/RenderBehaviorTest.java b/src/test/java/unit/RenderBehaviorTest.java index dc7190e..33488f9 100644 --- a/src/test/java/unit/RenderBehaviorTest.java +++ b/src/test/java/unit/RenderBehaviorTest.java @@ -12,6 +12,7 @@ import net.whg.we.rendering.RenderBehavior; import net.whg.we.rendering.RenderPipelineAction; import net.whg.we.main.Scene; +import net.whg.we.main.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; import net.whg.we.rendering.IShader; @@ -76,7 +77,7 @@ public void render_normalConditions() scene.addGameObject(go); RenderPipelineAction renderPipeline = new RenderPipelineAction(); - renderPipeline.setCamera(new Camera()); + renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -100,7 +101,7 @@ public void render_behaviorAddedLater() go.addBehavior(behavior); RenderPipelineAction renderPipeline = new RenderPipelineAction(); - renderPipeline.setCamera(new Camera()); + renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -122,7 +123,7 @@ public void render_noMesh_dontRender() go.addBehavior(behavior); RenderPipelineAction renderPipeline = new RenderPipelineAction(); - renderPipeline.setCamera(new Camera()); + renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -142,7 +143,7 @@ public void render_noMaterial_dontRender() go.addBehavior(behavior); RenderPipelineAction renderPipeline = new RenderPipelineAction(); - renderPipeline.setCamera(new Camera()); + renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index 23d4142..0c41441 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import net.whg.we.main.GameObject; import net.whg.we.main.PipelineConstants; +import net.whg.we.main.Screen; import net.whg.we.rendering.RenderBehavior; import net.whg.we.rendering.RenderPipelineAction; import net.whg.we.rendering.Camera; @@ -43,7 +44,7 @@ public void renderElements() go.addBehavior(behavior); RenderPipelineAction action = new RenderPipelineAction(); - action.setCamera(new Camera()); + action.setCamera(new Camera(mock(Screen.class))); action.enableBehavior(behavior); action.run(); diff --git a/src/test/java/unit/ScreenTest.java b/src/test/java/unit/ScreenTest.java index ce8d2f5..ff08b6c 100644 --- a/src/test/java/unit/ScreenTest.java +++ b/src/test/java/unit/ScreenTest.java @@ -1,61 +1,92 @@ package unit; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.junit.Test; import net.whg.we.main.Screen; -import net.whg.we.main.UserControlsUpdater; +import net.whg.we.rendering.IRenderingEngine; import net.whg.we.window.IWindow; import net.whg.we.window.IWindowListener; import net.whg.we.window.WindowSettings; public class ScreenTest { - private IWindowListener listener(IWindow window) + private class FakeWindow implements IWindow { - IWindowListener[] l = new IWindowListener[1]; + IWindowListener listener; - doAnswer(a -> - { l[0] = a.getArgument(0); return null; }).when(window) - .addWindowListener(any()); + @Override + public void dispose() + {} - UserControlsUpdater.bind(window); - return l[0]; + @Override + public boolean isDisposed() + { + return false; + } + + @Override + public void setProperties(WindowSettings settings) + {} + + @Override + public WindowSettings getProperties() + { + WindowSettings settings = new WindowSettings(); + settings.setWidth(1600); + settings.setHeight(900); + return settings; + } + + @Override + public IRenderingEngine getRenderingEngine() + { + return null; + } + + @Override + public void addWindowListener(IWindowListener listener) + { + this.listener = listener; + } + + @Override + public void removeWindowListener(IWindowListener listener) + {} + + @Override + public void pollEvents() + {} + + @Override + public long getWindowId() + { + return 1; + } } @Test public void resizeScreen() { - IWindow window = mock(IWindow.class); - when(window.getProperties()).thenReturn(new WindowSettings()); + FakeWindow window = new FakeWindow(); + Screen screen = new Screen(window); - IWindowListener listener = listener(window); + window.listener.onWindowResized(window, 320, 240); - listener.onWindowResized(window, 320, 240); - - assertEquals(320, Screen.getWidth()); - assertEquals(240, Screen.getHeight()); - assertEquals(4f / 3f, Screen.getAspect(), 0.0001f); + assertEquals(320, screen.getWidth()); + assertEquals(240, screen.getHeight()); + assertEquals(4f / 3f, screen.getAspect(), 0.0001f); } @Test public void resizeScreen_updateTrigger() { - IWindow window = mock(IWindow.class); - WindowSettings settings = new WindowSettings(); - settings.setWidth(1600); - settings.setHeight(900); - when(window.getProperties()).thenReturn(settings); - - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Screen screen = new Screen(window); - listener.onWindowUpdated(window); + window.listener.onWindowUpdated(window); - assertEquals(1600, Screen.getWidth()); - assertEquals(900, Screen.getHeight()); - assertEquals(16f / 9f, Screen.getAspect(), 0.0001f); + assertEquals(1600, screen.getWidth()); + assertEquals(900, screen.getHeight()); + assertEquals(16f / 9f, screen.getAspect(), 0.0001f); } } From 869f83279c46044fb38e3db3c2781aaf6139c657 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Sun, 26 Jan 2020 23:21:54 -0700 Subject: [PATCH 11/24] Input object is now self contained. --- src/main/java/net/whg/we/main/Input.java | 166 +++++++--------- src/main/java/net/whg/we/main/Screen.java | 4 +- .../net/whg/we/main/UserControlsUpdater.java | 134 ------------- src/test/java/unit/FakeWindow.java | 58 ++++++ src/test/java/unit/InputTest.java | 186 +++++++----------- src/test/java/unit/ScreenTest.java | 61 +----- .../java/unit/UserControlsUpdaterTest.java | 56 ------ 7 files changed, 209 insertions(+), 456 deletions(-) delete mode 100644 src/main/java/net/whg/we/main/UserControlsUpdater.java create mode 100644 src/test/java/unit/FakeWindow.java delete mode 100644 src/test/java/unit/UserControlsUpdaterTest.java diff --git a/src/main/java/net/whg/we/main/Input.java b/src/main/java/net/whg/we/main/Input.java index 8757e56..4d67978 100644 --- a/src/main/java/net/whg/we/main/Input.java +++ b/src/main/java/net/whg/we/main/Input.java @@ -1,13 +1,14 @@ package net.whg.we.main; -import java.util.Arrays; +import net.whg.we.window.IWindow; +import net.whg.we.window.IWindowAdapter; /** * This input class is a reference for the current key and mouse states for the * focused window. This can be used to check for mouse inputs and key presses as * they occur. */ -public final class Input +public class Input { /** * The largest key code input provided by GLFW. Used for making sure enough @@ -21,85 +22,69 @@ public final class Input */ private static final int MAX_INPUT_MOUSE_BUTTON = 7; - private static boolean[] keyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; - private static boolean[] lastKeyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; - private static boolean[] mouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; - private static boolean[] lastMouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; - private static float mouseX; - private static float mouseY; - private static float lastMouseX; - private static float lastMouseY; - private static float scrollWheelDelta; - - /** - * Clear all data stored in this class, resetting it to default settings. Any - * keys or buttons currently held are assumed unpressed. Mouse position is - * assumed to be at 0, 0. - */ - public static void clear() - { - Arrays.fill(keyStates, false); - Arrays.fill(lastKeyStates, false); - Arrays.fill(mouseButtons, false); - Arrays.fill(lastMouseButtons, false); - - mouseX = 0f; - mouseY = 0f; - lastMouseX = 0f; - lastMouseY = 0f; - scrollWheelDelta = 0f; - } - - /** - * Assigns a new state to a given key. - * - * @param key - * - The key in question. - * @param pressed - * - True if the key was pressed. False if the key was released. - */ - static void setKeyState(final int key, final boolean pressed) - { - keyStates[key] = pressed; - } - /** - * Assigns a new set of coords to the mouse position. + * The input listener is a helper class which can be used to bind to a window to + * listen for events it triggers. + */ + private class InputListener extends IWindowAdapter + { + @Override + public void onMouseMove(IWindow window, float newX, float newY) + { + Input.this.mouseX = newX; + Input.this.mouseY = newY; + } + + @Override + public void onKeyPressed(IWindow window, int keyCode) + { + Input.this.keyStates[keyCode] = true; + } + + @Override + public void onKeyReleased(IWindow window, int keyCode) + { + Input.this.keyStates[keyCode] = false; + } + + @Override + public void onMouseWheel(IWindow window, float scrollX, float scrollY) + { + Input.this.scrollWheelDelta = scrollY; + } + + @Override + public void onMousePressed(IWindow window, int mouseButton) + { + Input.this.mouseButtons[mouseButton] = true; + } + + @Override + public void onMouseReleased(IWindow window, int mouseButton) + { + Input.this.mouseButtons[mouseButton] = false; + } + } + + private final boolean[] keyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; + private final boolean[] lastKeyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; + private final boolean[] mouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; + private final boolean[] lastMouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; + private float mouseX; + private float mouseY; + private float lastMouseX; + private float lastMouseY; + private float scrollWheelDelta; + + /** + * Creates a new input object, and binds a listener to the given window. * - * @param mouseX - * - The current mouse x. - * @param mouseY - * - The current mouse y. + * @param window + * - The window to bind to. */ - static void setMousePos(final float mouseX, final float mouseY) + public Input(IWindow window) { - Input.mouseX = mouseX; - Input.mouseY = mouseY; - } - - /** - * Assigns a new state to the given mouse button. - * - * @param button - * - The mouse button in question. - * @param pressed - * - True if the mouse button was pressed. False if the mouse button was - * released. - */ - static void setMouseButtonState(final int button, final boolean pressed) - { - mouseButtons[button] = pressed; - } - - /** - * Assigns the amount the mouse wheel was scrolled this frame. - * - * @param delta - * - The scroll delta. - */ - static void setScrollWheelDelta(final float delta) - { - scrollWheelDelta = delta; + window.addWindowListener(new InputListener()); } /** @@ -107,7 +92,7 @@ static void setScrollWheelDelta(final float delta) * states are copied from the current frame buffer to the previous frame buffer, * to allow for delta functions to work as intended. */ - public static void endFrame() + public void endFrame() { System.arraycopy(keyStates, 0, lastKeyStates, 0, keyStates.length); System.arraycopy(mouseButtons, 0, lastMouseButtons, 0, mouseButtons.length); @@ -124,7 +109,7 @@ public static void endFrame() * - The key to check for. * @return True if the key is being pressed, false otherwise. */ - public static boolean isKeyDown(final int key) + public boolean isKeyDown(final int key) { return keyStates[key]; } @@ -137,7 +122,7 @@ public static boolean isKeyDown(final int key) * @return True if the key is being pressed and was not pressed on the previous * frame. False otherwise. */ - public static boolean isKeyJustDown(final int key) + public boolean isKeyJustDown(final int key) { return keyStates[key] && !lastKeyStates[key]; } @@ -150,7 +135,7 @@ public static boolean isKeyJustDown(final int key) * @return True if the key was being pressed last frame and is no longer being * pressed. False otherwise. */ - public static boolean isKeyJustUp(final int key) + public boolean isKeyJustUp(final int key) { return !keyStates[key] && lastKeyStates[key]; } @@ -160,7 +145,7 @@ public static boolean isKeyJustUp(final int key) * * @return The mouse x pos. */ - public static float getMouseX() + public float getMouseX() { return mouseX; } @@ -170,7 +155,7 @@ public static float getMouseX() * * @return The mouse y pos. */ - public static float getMouseY() + public float getMouseY() { return mouseY; } @@ -180,7 +165,7 @@ public static float getMouseY() * * @return The mouse delta x. */ - public static float getMouseDeltaX() + public float getMouseDeltaX() { return mouseX - lastMouseX; } @@ -190,7 +175,7 @@ public static float getMouseDeltaX() * * @return The mouse delta y. */ - public static float getMouseDeltaY() + public float getMouseDeltaY() { return mouseY - lastMouseY; } @@ -202,7 +187,7 @@ public static float getMouseDeltaY() * - The mouse button to check for. * @return True if the mouse button is being pressed, false otherwise. */ - public static boolean isMouseButtonDown(final int button) + public boolean isMouseButtonDown(final int button) { return mouseButtons[button]; } @@ -215,7 +200,7 @@ public static boolean isMouseButtonDown(final int button) * @return True if the mouse button is being pressed and was not pressed on the * previous frame. False otherwise. */ - public static boolean isMouseButtonJustDown(final int button) + public boolean isMouseButtonJustDown(final int button) { return mouseButtons[button] && !lastMouseButtons[button]; } @@ -228,7 +213,7 @@ public static boolean isMouseButtonJustDown(final int button) * @return True if the mouse button was being pressed last frame and is no * longer being pressed. False otherwise. */ - public static boolean isMouseButtonJustUp(final int button) + public boolean isMouseButtonJustUp(final int button) { return !mouseButtons[button] && lastMouseButtons[button]; } @@ -238,11 +223,8 @@ public static boolean isMouseButtonJustUp(final int button) * * @return The scroll wheel delta. */ - public static float getScrollWheelDelta() + public float getScrollWheelDelta() { return scrollWheelDelta; } - - private Input() - {} } diff --git a/src/main/java/net/whg/we/main/Screen.java b/src/main/java/net/whg/we/main/Screen.java index cbe41b7..e63a6db 100644 --- a/src/main/java/net/whg/we/main/Screen.java +++ b/src/main/java/net/whg/we/main/Screen.java @@ -37,10 +37,10 @@ public void onWindowUpdated(IWindow window) private int height; /** - * Creates a new screen object. + * Creates a new screen object and binds to the given window. * * @param window - * - The window this screen is bound to. + * - The window to bind to. */ public Screen(IWindow window) { diff --git a/src/main/java/net/whg/we/main/UserControlsUpdater.java b/src/main/java/net/whg/we/main/UserControlsUpdater.java deleted file mode 100644 index a29ef46..0000000 --- a/src/main/java/net/whg/we/main/UserControlsUpdater.java +++ /dev/null @@ -1,134 +0,0 @@ -package net.whg.we.main; - -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowListener; -import net.whg.we.window.WindowSettings; - -/** - * This class acts as a utility class for updating the user control classes in - * the engine, such as Screen and Input. This binds to a window to continously - * update the settings. - */ -public final class UserControlsUpdater implements IWindowListener -{ - private static UserControlsUpdater updater = new UserControlsUpdater(); - - /** - * Binds this updater to the current window. Replaces the binding with previous - * window, if previously binded. This updater is automaically unbound when the - * window is destroyed. - * - * @param window - * - The window to bind with, or null to disable current bindings. - */ - public static void bind(IWindow window) - { - updater.setWindow(window); - } - - /** - * Gets the window which the updater is currently bound to. - * - * @return The window, or null if no window is bound. - */ - public static IWindow getBoundWindow() - { - return updater.getWindow(); - } - - private IWindow window; - - private UserControlsUpdater() - {} - - /** - * Assigns the target window to bind to. - * - * @param window - * - The new target window. - */ - private void setWindow(IWindow window) - { - if (this.window == window) - return; - - if (this.window != null) - this.window.removeWindowListener(this); - - this.window = window; - - if (this.window != null) - this.window.addWindowListener(this); - } - - /** - * Gets the window which this updater is currently bound to. - * - * @return The window, or null if no window is bound. - */ - private IWindow getWindow() - { - return window; - } - - @Override - public void onWindowUpdated(IWindow window) - { - WindowSettings settings = window.getProperties(); - onWindowResized(window, settings.getWidth(), settings.getHeight()); - } - - @Override - public void onWindowResized(IWindow window, int width, int height) - { - // Do nothing - } - - @Override - public void onWindowDestroyed(IWindow window) - { - setWindow(null); - } - - @Override - public void onWindowRequestClose(IWindow window) - { - // Nothing to do. - } - - @Override - public void onMouseMove(IWindow window, float newX, float newY) - { - Input.setMousePos(newX, newY); - } - - @Override - public void onKeyPressed(IWindow window, int keyCode) - { - Input.setKeyState(keyCode, true); - } - - @Override - public void onKeyReleased(IWindow window, int keyCode) - { - Input.setKeyState(keyCode, false); - } - - @Override - public void onMousePressed(IWindow window, int mouseButton) - { - Input.setMouseButtonState(mouseButton, true); - } - - @Override - public void onMouseReleased(IWindow window, int mouseButton) - { - Input.setMouseButtonState(mouseButton, false); - } - - @Override - public void onMouseWheel(IWindow window, float scrollX, float scrollY) - { - Input.setScrollWheelDelta(scrollY); - } -} diff --git a/src/test/java/unit/FakeWindow.java b/src/test/java/unit/FakeWindow.java new file mode 100644 index 0000000..4337a90 --- /dev/null +++ b/src/test/java/unit/FakeWindow.java @@ -0,0 +1,58 @@ +package unit; + +import net.whg.we.rendering.IRenderingEngine; +import net.whg.we.window.IWindow; +import net.whg.we.window.IWindowListener; +import net.whg.we.window.WindowSettings; + +public class FakeWindow implements IWindow +{ + public IWindowListener listener; + public WindowSettings settings; + + @Override + public void dispose() + {} + + @Override + public boolean isDisposed() + { + return false; + } + + @Override + public void setProperties(WindowSettings settings) + {} + + @Override + public WindowSettings getProperties() + { + return settings; + } + + @Override + public IRenderingEngine getRenderingEngine() + { + return null; + } + + @Override + public void addWindowListener(IWindowListener listener) + { + this.listener = listener; + } + + @Override + public void removeWindowListener(IWindowListener listener) + {} + + @Override + public void pollEvents() + {} + + @Override + public long getWindowId() + { + return 1; + } +} diff --git a/src/test/java/unit/InputTest.java b/src/test/java/unit/InputTest.java index 2f712cf..8d54399 100644 --- a/src/test/java/unit/InputTest.java +++ b/src/test/java/unit/InputTest.java @@ -3,197 +3,155 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.junit.Test; import net.whg.we.main.Input; -import net.whg.we.main.UserControlsUpdater; -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowListener; -import net.whg.we.window.WindowSettings; public class InputTest { - private IWindow window() - { - IWindow window = mock(IWindow.class); - when(window.getProperties()).thenReturn(new WindowSettings()); - - return window; - } - - private IWindowListener listener(IWindow window) - { - IWindowListener[] l = new IWindowListener[1]; - - doAnswer(a -> - { l[0] = a.getArgument(0); return null; }).when(window) - .addWindowListener(any()); - - UserControlsUpdater.bind(window); - return l[0]; - } - @Test public void keyPressed() { - Input.clear(); - - IWindow window = window(); - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - listener.onKeyPressed(window, 35); + window.listener.onKeyPressed(window, 35); - assertTrue(Input.isKeyDown(35)); - assertTrue(Input.isKeyJustDown(35)); - assertFalse(Input.isKeyJustUp(35)); + assertTrue(input.isKeyDown(35)); + assertTrue(input.isKeyJustDown(35)); + assertFalse(input.isKeyJustUp(35)); } @Test public void keyReleased() { - Input.clear(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - IWindow window = window(); - IWindowListener listener = listener(window); + window.listener.onKeyPressed(window, 35); + input.endFrame(); + window.listener.onKeyReleased(window, 35); - listener.onKeyPressed(window, 35); - Input.endFrame(); - listener.onKeyReleased(window, 35); - - assertFalse(Input.isKeyDown(35)); - assertFalse(Input.isKeyJustDown(35)); - assertTrue(Input.isKeyJustUp(35)); + assertFalse(input.isKeyDown(35)); + assertFalse(input.isKeyJustDown(35)); + assertTrue(input.isKeyJustUp(35)); } @Test public void keyHeld() { - Input.clear(); - - IWindow window = window(); - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - listener.onKeyPressed(window, 35); - Input.endFrame(); + window.listener.onKeyPressed(window, 35); + input.endFrame(); - assertTrue(Input.isKeyDown(35)); - assertFalse(Input.isKeyJustDown(35)); - assertFalse(Input.isKeyJustUp(35)); + assertTrue(input.isKeyDown(35)); + assertFalse(input.isKeyJustDown(35)); + assertFalse(input.isKeyJustUp(35)); } @Test public void keyIdle() { - Input.clear(); - Input.endFrame(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + + input.endFrame(); - assertFalse(Input.isKeyDown(35)); - assertFalse(Input.isKeyJustDown(35)); - assertFalse(Input.isKeyJustUp(35)); + assertFalse(input.isKeyDown(35)); + assertFalse(input.isKeyJustDown(35)); + assertFalse(input.isKeyJustUp(35)); } @Test public void mousePressed() { - Input.clear(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - IWindow window = window(); - IWindowListener listener = listener(window); + window.listener.onMousePressed(window, 2); - listener.onMousePressed(window, 2); - - assertTrue(Input.isMouseButtonDown(2)); - assertTrue(Input.isMouseButtonJustDown(2)); - assertFalse(Input.isMouseButtonJustUp(2)); + assertTrue(input.isMouseButtonDown(2)); + assertTrue(input.isMouseButtonJustDown(2)); + assertFalse(input.isMouseButtonJustUp(2)); } @Test public void mouseReleased() { - Input.clear(); - - IWindow window = window(); - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - listener.onMousePressed(window, 2); - Input.endFrame(); - listener.onMouseReleased(window, 2); + window.listener.onMousePressed(window, 2); + input.endFrame(); + window.listener.onMouseReleased(window, 2); - assertFalse(Input.isMouseButtonDown(2)); - assertFalse(Input.isMouseButtonJustDown(2)); - assertTrue(Input.isMouseButtonJustUp(2)); + assertFalse(input.isMouseButtonDown(2)); + assertFalse(input.isMouseButtonJustDown(2)); + assertTrue(input.isMouseButtonJustUp(2)); } @Test public void mouseHeld() { - Input.clear(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - IWindow window = window(); - IWindowListener listener = listener(window); + window.listener.onMousePressed(window, 2); + input.endFrame(); - listener.onMousePressed(window, 2); - Input.endFrame(); - - assertTrue(Input.isMouseButtonDown(2)); - assertFalse(Input.isMouseButtonJustDown(2)); - assertFalse(Input.isMouseButtonJustUp(2)); + assertTrue(input.isMouseButtonDown(2)); + assertFalse(input.isMouseButtonJustDown(2)); + assertFalse(input.isMouseButtonJustUp(2)); } @Test public void mouseIdle() { - Input.clear(); - Input.endFrame(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + + input.endFrame(); - assertFalse(Input.isMouseButtonDown(1)); - assertFalse(Input.isMouseButtonJustDown(1)); - assertFalse(Input.isMouseButtonJustUp(1)); + assertFalse(input.isMouseButtonDown(1)); + assertFalse(input.isMouseButtonJustDown(1)); + assertFalse(input.isMouseButtonJustUp(1)); } @Test public void mouseMove() { - Input.clear(); - - IWindow window = window(); - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - listener.onMouseMove(window, 120f, 108f); + window.listener.onMouseMove(window, 120f, 108f); - assertEquals(120f, Input.getMouseX(), 0f); - assertEquals(108f, Input.getMouseY(), 0f); + assertEquals(120f, input.getMouseX(), 0f); + assertEquals(108f, input.getMouseY(), 0f); } @Test public void moveMouse_delta() { - Input.clear(); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - IWindow window = window(); - IWindowListener listener = listener(window); + window.listener.onMouseMove(window, 120f, 108f); + input.endFrame(); + window.listener.onMouseMove(window, 125f, 118f); - listener.onMouseMove(window, 120f, 108f); - Input.endFrame(); - listener.onMouseMove(window, 125f, 118f); - - assertEquals(5f, Input.getMouseDeltaX(), 0.0001f); - assertEquals(10f, Input.getMouseDeltaY(), 0.0001f); + assertEquals(5f, input.getMouseDeltaX(), 0.0001f); + assertEquals(10f, input.getMouseDeltaY(), 0.0001f); } @Test public void scrollWheel() { - Input.clear(); - - IWindow window = window(); - IWindowListener listener = listener(window); + FakeWindow window = new FakeWindow(); + Input input = new Input(window); - listener.onMouseWheel(window, 0f, 3.5f); + window.listener.onMouseWheel(window, 0f, 3.5f); - assertEquals(3.5f, Input.getScrollWheelDelta(), 0f); + assertEquals(3.5f, input.getScrollWheelDelta(), 0f); } } diff --git a/src/test/java/unit/ScreenTest.java b/src/test/java/unit/ScreenTest.java index ff08b6c..6b1a363 100644 --- a/src/test/java/unit/ScreenTest.java +++ b/src/test/java/unit/ScreenTest.java @@ -3,67 +3,10 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import net.whg.we.main.Screen; -import net.whg.we.rendering.IRenderingEngine; -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowListener; import net.whg.we.window.WindowSettings; public class ScreenTest { - private class FakeWindow implements IWindow - { - IWindowListener listener; - - @Override - public void dispose() - {} - - @Override - public boolean isDisposed() - { - return false; - } - - @Override - public void setProperties(WindowSettings settings) - {} - - @Override - public WindowSettings getProperties() - { - WindowSettings settings = new WindowSettings(); - settings.setWidth(1600); - settings.setHeight(900); - return settings; - } - - @Override - public IRenderingEngine getRenderingEngine() - { - return null; - } - - @Override - public void addWindowListener(IWindowListener listener) - { - this.listener = listener; - } - - @Override - public void removeWindowListener(IWindowListener listener) - {} - - @Override - public void pollEvents() - {} - - @Override - public long getWindowId() - { - return 1; - } - } - @Test public void resizeScreen() { @@ -81,8 +24,10 @@ public void resizeScreen() public void resizeScreen_updateTrigger() { FakeWindow window = new FakeWindow(); - Screen screen = new Screen(window); + window.settings = new WindowSettings(); + window.settings.setSize(1600, 900); + Screen screen = new Screen(window); window.listener.onWindowUpdated(window); assertEquals(1600, screen.getWidth()); diff --git a/src/test/java/unit/UserControlsUpdaterTest.java b/src/test/java/unit/UserControlsUpdaterTest.java deleted file mode 100644 index 09b194c..0000000 --- a/src/test/java/unit/UserControlsUpdaterTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package unit; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import org.junit.Test; -import net.whg.we.main.UserControlsUpdater; -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowListener; -import net.whg.we.window.WindowSettings; - -public class UserControlsUpdaterTest -{ - private IWindowListener listener(IWindow window) - { - IWindowListener[] l = new IWindowListener[1]; - - doAnswer(a -> - { l[0] = a.getArgument(0); return null; }).when(window) - .addWindowListener(any()); - - UserControlsUpdater.bind(window); - return l[0]; - } - - @Test - public void unbindWindow() - { - IWindow window = mock(IWindow.class); - when(window.getProperties()).thenReturn(new WindowSettings()); - IWindowListener listener = listener(window); - - listener.onWindowRequestClose(window); - listener.onWindowDestroyed(window); - - assertNull(UserControlsUpdater.getBoundWindow()); - } - - @Test - public void bindWindow_twice() - { - IWindow window = mock(IWindow.class); - when(window.getProperties()).thenReturn(new WindowSettings()); - - UserControlsUpdater.bind(window); - UserControlsUpdater.bind(window); - - assertEquals(window, UserControlsUpdater.getBoundWindow()); - verify(window, never()).removeWindowListener(any()); - } -} From 07f95290f0d99def247d75957587693ede536656 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Mon, 27 Jan 2020 09:23:56 -0700 Subject: [PATCH 12/24] Moved screen and input to correct package. --- src/main/java/net/whg/we/rendering/Camera.java | 2 +- src/main/java/net/whg/we/{main => window}/Input.java | 5 +---- src/main/java/net/whg/we/{main => window}/Screen.java | 6 +----- src/test/java/unit/CameraTest.java | 2 +- src/test/java/unit/InputTest.java | 2 +- src/test/java/unit/MaterialTest.java | 2 +- src/test/java/unit/RenderBehaviorTest.java | 2 +- src/test/java/unit/RenderPipelineActionTest.java | 2 +- src/test/java/unit/ScreenTest.java | 2 +- 9 files changed, 9 insertions(+), 16 deletions(-) rename src/main/java/net/whg/we/{main => window}/Input.java (98%) rename src/main/java/net/whg/we/{main => window}/Screen.java (92%) diff --git a/src/main/java/net/whg/we/rendering/Camera.java b/src/main/java/net/whg/we/rendering/Camera.java index 2845d15..504775f 100644 --- a/src/main/java/net/whg/we/rendering/Camera.java +++ b/src/main/java/net/whg/we/rendering/Camera.java @@ -1,8 +1,8 @@ package net.whg.we.rendering; import org.joml.Matrix4f; -import net.whg.we.main.Screen; import net.whg.we.main.Transform3D; +import net.whg.we.window.Screen; /** * The camera is the object in charge of determing the projection and view diff --git a/src/main/java/net/whg/we/main/Input.java b/src/main/java/net/whg/we/window/Input.java similarity index 98% rename from src/main/java/net/whg/we/main/Input.java rename to src/main/java/net/whg/we/window/Input.java index 4d67978..2423a3a 100644 --- a/src/main/java/net/whg/we/main/Input.java +++ b/src/main/java/net/whg/we/window/Input.java @@ -1,7 +1,4 @@ -package net.whg.we.main; - -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowAdapter; +package net.whg.we.window; /** * This input class is a reference for the current key and mouse states for the diff --git a/src/main/java/net/whg/we/main/Screen.java b/src/main/java/net/whg/we/window/Screen.java similarity index 92% rename from src/main/java/net/whg/we/main/Screen.java rename to src/main/java/net/whg/we/window/Screen.java index e63a6db..1ba1a83 100644 --- a/src/main/java/net/whg/we/main/Screen.java +++ b/src/main/java/net/whg/we/window/Screen.java @@ -1,8 +1,4 @@ -package net.whg.we.main; - -import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowAdapter; -import net.whg.we.window.WindowSettings; +package net.whg.we.window; /** * The screen object is an object which can be used to determine various diff --git a/src/test/java/unit/CameraTest.java b/src/test/java/unit/CameraTest.java index d367070..2edd0ea 100644 --- a/src/test/java/unit/CameraTest.java +++ b/src/test/java/unit/CameraTest.java @@ -8,9 +8,9 @@ import org.joml.Quaternionf; import org.joml.Vector3f; import org.junit.Test; -import net.whg.we.main.Screen; import net.whg.we.main.Transform3D; import net.whg.we.rendering.Camera; +import net.whg.we.window.Screen; public class CameraTest { diff --git a/src/test/java/unit/InputTest.java b/src/test/java/unit/InputTest.java index 8d54399..40f90cc 100644 --- a/src/test/java/unit/InputTest.java +++ b/src/test/java/unit/InputTest.java @@ -4,7 +4,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import org.junit.Test; -import net.whg.we.main.Input; +import net.whg.we.window.Input; public class InputTest { diff --git a/src/test/java/unit/MaterialTest.java b/src/test/java/unit/MaterialTest.java index fbcd4e2..d975f49 100644 --- a/src/test/java/unit/MaterialTest.java +++ b/src/test/java/unit/MaterialTest.java @@ -10,7 +10,7 @@ import org.joml.Matrix4f; import org.junit.Test; import net.whg.we.rendering.Material; -import net.whg.we.main.Screen; +import net.whg.we.window.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IShader; import net.whg.we.rendering.ITexture; diff --git a/src/test/java/unit/RenderBehaviorTest.java b/src/test/java/unit/RenderBehaviorTest.java index 33488f9..274c84f 100644 --- a/src/test/java/unit/RenderBehaviorTest.java +++ b/src/test/java/unit/RenderBehaviorTest.java @@ -11,8 +11,8 @@ import net.whg.we.rendering.Material; import net.whg.we.rendering.RenderBehavior; import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.window.Screen; import net.whg.we.main.Scene; -import net.whg.we.main.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; import net.whg.we.rendering.IShader; diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index 0c41441..515e642 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -8,9 +8,9 @@ import org.junit.Test; import net.whg.we.main.GameObject; import net.whg.we.main.PipelineConstants; -import net.whg.we.main.Screen; import net.whg.we.rendering.RenderBehavior; import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.window.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; import net.whg.we.rendering.Material; diff --git a/src/test/java/unit/ScreenTest.java b/src/test/java/unit/ScreenTest.java index 6b1a363..7665b73 100644 --- a/src/test/java/unit/ScreenTest.java +++ b/src/test/java/unit/ScreenTest.java @@ -2,7 +2,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; -import net.whg.we.main.Screen; +import net.whg.we.window.Screen; import net.whg.we.window.WindowSettings; public class ScreenTest From 83ebf49981d874c8cb78ebbac28a1c366c91cf19 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Mon, 27 Jan 2020 09:30:25 -0700 Subject: [PATCH 13/24] Screens can now be disposed. --- src/main/java/net/whg/we/window/Screen.java | 38 +++++++++++++++++- src/test/java/unit/FakeWindow.java | 4 +- src/test/java/unit/ScreenTest.java | 43 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/whg/we/window/Screen.java b/src/main/java/net/whg/we/window/Screen.java index 1ba1a83..5f05283 100644 --- a/src/main/java/net/whg/we/window/Screen.java +++ b/src/main/java/net/whg/we/window/Screen.java @@ -1,12 +1,16 @@ package net.whg.we.window; +import net.whg.we.util.IDisposable; + /** * The screen object is an object which can be used to determine various * information about the state of the game screen, as well as modidy the screen * in certain ways. Each screen is bound to a single window. */ -public class Screen +public class Screen implements IDisposable { + private static final String SCREEN_DISPOSED = "Screen already disposed!"; + /** * A private listener class which recieved events from the window to store * within the screen object. @@ -29,8 +33,11 @@ public void onWindowUpdated(IWindow window) } } + private final IWindowListener listener = new ScreenListener(); + private IWindow window; private int width; private int height; + private boolean disposed; /** * Creates a new screen object and binds to the given window. @@ -40,7 +47,8 @@ public void onWindowUpdated(IWindow window) */ public Screen(IWindow window) { - window.addWindowListener(new ScreenListener()); + this.window = window; + window.addWindowListener(listener); } /** @@ -50,6 +58,9 @@ public Screen(IWindow window) */ public int getWidth() { + if (isDisposed()) + throw new IllegalStateException(SCREEN_DISPOSED); + return width; } @@ -60,6 +71,9 @@ public int getWidth() */ public int getHeight() { + if (isDisposed()) + throw new IllegalStateException(SCREEN_DISPOSED); + return height; } @@ -70,6 +84,26 @@ public int getHeight() */ public float getAspect() { + if (isDisposed()) + throw new IllegalStateException(SCREEN_DISPOSED); + return (float) width / height; } + + @Override + public void dispose() + { + if (isDisposed()) + return; + + disposed = true; + window.removeWindowListener(listener); + window = null; + } + + @Override + public boolean isDisposed() + { + return disposed; + } } diff --git a/src/test/java/unit/FakeWindow.java b/src/test/java/unit/FakeWindow.java index 4337a90..e2fcb50 100644 --- a/src/test/java/unit/FakeWindow.java +++ b/src/test/java/unit/FakeWindow.java @@ -44,7 +44,9 @@ public void addWindowListener(IWindowListener listener) @Override public void removeWindowListener(IWindowListener listener) - {} + { + this.listener = null; + } @Override public void pollEvents() diff --git a/src/test/java/unit/ScreenTest.java b/src/test/java/unit/ScreenTest.java index 7665b73..020e4cd 100644 --- a/src/test/java/unit/ScreenTest.java +++ b/src/test/java/unit/ScreenTest.java @@ -1,6 +1,7 @@ package unit; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import org.junit.Test; import net.whg.we.window.Screen; import net.whg.we.window.WindowSettings; @@ -34,4 +35,46 @@ public void resizeScreen_updateTrigger() assertEquals(900, screen.getHeight()); assertEquals(16f / 9f, screen.getAspect(), 0.0001f); } + + @Test(expected = IllegalStateException.class) + public void getWidth_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Screen screen = new Screen(window); + screen.dispose(); + + screen.getWidth(); + } + + @Test(expected = IllegalStateException.class) + public void getHeight_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Screen screen = new Screen(window); + screen.dispose(); + + screen.getHeight(); + } + + @Test(expected = IllegalStateException.class) + public void getAspect_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Screen screen = new Screen(window); + screen.dispose(); + + screen.getAspect(); + } + + @Test + public void dipose() + { + FakeWindow window = new FakeWindow(); + + Screen screen = new Screen(window); + screen.dispose(); + screen.dispose(); // To make sure nothing happens when you dispose twice + + assertNull(window.listener); + } } From 31429a360ededfeb714e1e85ffb29796c7b69772 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Mon, 27 Jan 2020 09:38:35 -0700 Subject: [PATCH 14/24] Input is now disposable. --- src/main/java/net/whg/we/window/Input.java | 90 +++++++++++++- src/test/java/unit/InputTest.java | 132 +++++++++++++++++++++ 2 files changed, 220 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/whg/we/window/Input.java b/src/main/java/net/whg/we/window/Input.java index 2423a3a..7b8aaf5 100644 --- a/src/main/java/net/whg/we/window/Input.java +++ b/src/main/java/net/whg/we/window/Input.java @@ -1,12 +1,16 @@ package net.whg.we.window; +import net.whg.we.util.IDisposable; + /** * This input class is a reference for the current key and mouse states for the * focused window. This can be used to check for mouse inputs and key presses as * they occur. */ -public class Input +public class Input implements IDisposable { + private static final String INPUT_DISPOSED = "Input already disposed!"; + /** * The largest key code input provided by GLFW. Used for making sure enough * memory is allocated to store all key states. @@ -63,15 +67,18 @@ public void onMouseReleased(IWindow window, int mouseButton) } } + private final InputListener listener = new InputListener(); private final boolean[] keyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; private final boolean[] lastKeyStates = new boolean[MAX_INPUT_KEY_CODE + 1]; private final boolean[] mouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; private final boolean[] lastMouseButtons = new boolean[MAX_INPUT_MOUSE_BUTTON + 1]; + private IWindow window; private float mouseX; private float mouseY; private float lastMouseX; private float lastMouseY; private float scrollWheelDelta; + private boolean disposed; /** * Creates a new input object, and binds a listener to the given window. @@ -81,16 +88,23 @@ public void onMouseReleased(IWindow window, int mouseButton) */ public Input(IWindow window) { - window.addWindowListener(new InputListener()); + this.window = window; + window.addWindowListener(listener); } /** * This should be called at the end of the frame. When called, key and mouse * states are copied from the current frame buffer to the previous frame buffer, * to allow for delta functions to work as intended. + * + * @throws IllegalStateException + * If input is already disposed. */ public void endFrame() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + System.arraycopy(keyStates, 0, lastKeyStates, 0, keyStates.length); System.arraycopy(mouseButtons, 0, lastMouseButtons, 0, mouseButtons.length); @@ -105,9 +119,14 @@ public void endFrame() * @param key * - The key to check for. * @return True if the key is being pressed, false otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isKeyDown(final int key) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return keyStates[key]; } @@ -118,9 +137,14 @@ public boolean isKeyDown(final int key) * - The key to check for. * @return True if the key is being pressed and was not pressed on the previous * frame. False otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isKeyJustDown(final int key) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return keyStates[key] && !lastKeyStates[key]; } @@ -131,9 +155,14 @@ public boolean isKeyJustDown(final int key) * - The key to check for. * @return True if the key was being pressed last frame and is no longer being * pressed. False otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isKeyJustUp(final int key) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return !keyStates[key] && lastKeyStates[key]; } @@ -141,9 +170,14 @@ public boolean isKeyJustUp(final int key) * Gets the current x position of the mouse. * * @return The mouse x pos. + * @throws IllegalStateException + * If input is already disposed. */ public float getMouseX() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseX; } @@ -151,9 +185,14 @@ public float getMouseX() * Gets the current y position of the mouse. * * @return The mouse y pos. + * @throws IllegalStateException + * If input is already disposed. */ public float getMouseY() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseY; } @@ -161,9 +200,14 @@ public float getMouseY() * Gets the x delta position of the mouse. * * @return The mouse delta x. + * @throws IllegalStateException + * If input is already disposed. */ public float getMouseDeltaX() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseX - lastMouseX; } @@ -171,9 +215,14 @@ public float getMouseDeltaX() * Gets the y delta position of the mouse. * * @return The mouse delta y. + * @throws IllegalStateException + * If input is already disposed. */ public float getMouseDeltaY() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseY - lastMouseY; } @@ -183,9 +232,14 @@ public float getMouseDeltaY() * @param button * - The mouse button to check for. * @return True if the mouse button is being pressed, false otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isMouseButtonDown(final int button) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseButtons[button]; } @@ -196,9 +250,14 @@ public boolean isMouseButtonDown(final int button) * - The button to check for. * @return True if the mouse button is being pressed and was not pressed on the * previous frame. False otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isMouseButtonJustDown(final int button) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return mouseButtons[button] && !lastMouseButtons[button]; } @@ -209,9 +268,14 @@ public boolean isMouseButtonJustDown(final int button) * - The button to check for. * @return True if the mouse button was being pressed last frame and is no * longer being pressed. False otherwise. + * @throws IllegalStateException + * If input is already disposed. */ public boolean isMouseButtonJustUp(final int button) { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return !mouseButtons[button] && lastMouseButtons[button]; } @@ -219,9 +283,31 @@ public boolean isMouseButtonJustUp(final int button) * Gets the amount the scroll wheel was rotated this frame. * * @return The scroll wheel delta. + * @throws IllegalStateException + * If input is already disposed. */ public float getScrollWheelDelta() { + if (isDisposed()) + throw new IllegalStateException(INPUT_DISPOSED); + return scrollWheelDelta; } + + @Override + public void dispose() + { + if (isDisposed()) + return; + + disposed = true; + window.removeWindowListener(listener); + window = null; + } + + @Override + public boolean isDisposed() + { + return disposed; + } } diff --git a/src/test/java/unit/InputTest.java b/src/test/java/unit/InputTest.java index 40f90cc..a88be8e 100644 --- a/src/test/java/unit/InputTest.java +++ b/src/test/java/unit/InputTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.Test; import net.whg.we.window.Input; @@ -154,4 +155,135 @@ public void scrollWheel() assertEquals(3.5f, input.getScrollWheelDelta(), 0f); } + + @Test(expected = IllegalStateException.class) + public void endFrame_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.endFrame(); + } + + @Test(expected = IllegalStateException.class) + public void isKeyDown_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isKeyDown(0); + } + + @Test(expected = IllegalStateException.class) + public void isKeyJustDown_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isKeyJustDown(0); + } + + @Test(expected = IllegalStateException.class) + public void isKeyJustUp_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isKeyJustUp(0); + } + + @Test(expected = IllegalStateException.class) + public void getMouseX_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.getMouseX(); + } + + @Test(expected = IllegalStateException.class) + public void getMouseY_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.getMouseY(); + } + + @Test(expected = IllegalStateException.class) + public void getMouseDeltaX_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.getMouseDeltaX(); + } + + @Test(expected = IllegalStateException.class) + public void getMouseDeltaY_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.getMouseDeltaY(); + } + + @Test(expected = IllegalStateException.class) + public void isMouseButtonDown_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isMouseButtonDown(0); + } + + @Test(expected = IllegalStateException.class) + public void isMouseButtonJustDown_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isMouseButtonJustDown(0); + } + + @Test(expected = IllegalStateException.class) + public void isMouseButtonJustUp_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.isMouseButtonJustUp(0); + } + + @Test(expected = IllegalStateException.class) + public void getScrollWheelDelta_alreadyDisposed() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + + input.getScrollWheelDelta(); + } + + @Test + public void dispose() + { + FakeWindow window = new FakeWindow(); + Input input = new Input(window); + input.dispose(); + input.dispose(); + + assertNull(window.listener); + } } From 6e04b89863f68af4376edb247bee0980b88b88d6 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Tue, 28 Jan 2020 11:28:24 -0700 Subject: [PATCH 15/24] Added poll events action. --- .../net/whg/we/main/PollEventsAction.java | 36 +++++++++++++++++++ src/test/java/unit/PollEventsActionTest.java | 29 +++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/main/java/net/whg/we/main/PollEventsAction.java create mode 100644 src/test/java/unit/PollEventsActionTest.java diff --git a/src/main/java/net/whg/we/main/PollEventsAction.java b/src/main/java/net/whg/we/main/PollEventsAction.java new file mode 100644 index 0000000..14bca63 --- /dev/null +++ b/src/main/java/net/whg/we/main/PollEventsAction.java @@ -0,0 +1,36 @@ +package net.whg.we.main; + +import net.whg.we.window.IWindow; + +/** + * The poll events action is used to trigger a window to poll events and swap + * the render buffer, which needs to be done at the end of every frame in order + * to render the game to the screen. + */ +public class PollEventsAction implements ILoopAction +{ + private final IWindow window; + + /** + * Creates a new poll-events action. + * + * @param window + * - The window to poll the events for each frame. + */ + public PollEventsAction(IWindow window) + { + this.window = window; + } + + @Override + public void run() + { + window.pollEvents(); + } + + @Override + public int getPriority() + { + return PipelineConstants.POLL_WINDOW_EVENTS; + } +} diff --git a/src/test/java/unit/PollEventsActionTest.java b/src/test/java/unit/PollEventsActionTest.java new file mode 100644 index 0000000..165e963 --- /dev/null +++ b/src/test/java/unit/PollEventsActionTest.java @@ -0,0 +1,29 @@ +package unit; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import org.junit.Test; +import net.whg.we.main.PipelineConstants; +import net.whg.we.main.PollEventsAction; +import net.whg.we.window.IWindow; + +public class PollEventsActionTest +{ + @Test + public void defaultPriority() + { + assertEquals(PipelineConstants.POLL_WINDOW_EVENTS, new PollEventsAction(mock(IWindow.class)).getPriority()); + } + + @Test + public void pollEvents() + { + IWindow window = mock(IWindow.class); + PollEventsAction action = new PollEventsAction(window); + + action.run(); + + verify(window).pollEvents(); + } +} From 7e676f4dd57d1d7cbe798b9d59896c190f5c6784 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Tue, 28 Jan 2020 11:32:20 -0700 Subject: [PATCH 16/24] Added input end frame action. --- .../whg/we/window/InputEndFrameAction.java | 31 +++++++++++++++++++ .../java/unit/InputEndFrameActionTest.java | 29 +++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/java/net/whg/we/window/InputEndFrameAction.java create mode 100644 src/test/java/unit/InputEndFrameActionTest.java diff --git a/src/main/java/net/whg/we/window/InputEndFrameAction.java b/src/main/java/net/whg/we/window/InputEndFrameAction.java new file mode 100644 index 0000000..abfb5de --- /dev/null +++ b/src/main/java/net/whg/we/window/InputEndFrameAction.java @@ -0,0 +1,31 @@ +package net.whg.we.window; + +import net.whg.we.main.ILoopAction; +import net.whg.we.main.PipelineConstants; +import net.whg.we.window.Input; + +/** + * This action triggers the end-frame method for input objects near the end of + * the loop to prepare the object for recieving input updates. + */ +public class InputEndFrameAction implements ILoopAction +{ + private final Input input; + + public InputEndFrameAction(Input input) + { + this.input = input; + } + + @Override + public void run() + { + input.endFrame(); + } + + @Override + public int getPriority() + { + return PipelineConstants.ENDFRAME; + } +} diff --git a/src/test/java/unit/InputEndFrameActionTest.java b/src/test/java/unit/InputEndFrameActionTest.java new file mode 100644 index 0000000..88e0953 --- /dev/null +++ b/src/test/java/unit/InputEndFrameActionTest.java @@ -0,0 +1,29 @@ +package unit; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import org.junit.Test; +import net.whg.we.main.PipelineConstants; +import net.whg.we.window.Input; +import net.whg.we.window.InputEndFrameAction; + +public class InputEndFrameActionTest +{ + @Test + public void defaultPriority() + { + assertEquals(PipelineConstants.ENDFRAME, new InputEndFrameAction(mock(Input.class)).getPriority()); + } + + @Test + public void endFrame() + { + Input input = mock(Input.class); + InputEndFrameAction action = new InputEndFrameAction(input); + + action.run(); + + verify(input).endFrame(); + } +} From 0a6fb8b61da8d89f51dc54ea36fe19be8fec6ff3 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Tue, 28 Jan 2020 13:37:02 -0700 Subject: [PATCH 17/24] Added screen clear pipeline. --- .../whg/we/rendering/ScreenClearPipeline.java | 36 +++++++++++++++++++ .../java/unit/ScreenClearPipelineTest.java | 30 ++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/net/whg/we/rendering/ScreenClearPipeline.java create mode 100644 src/test/java/unit/ScreenClearPipelineTest.java diff --git a/src/main/java/net/whg/we/rendering/ScreenClearPipeline.java b/src/main/java/net/whg/we/rendering/ScreenClearPipeline.java new file mode 100644 index 0000000..fd60f98 --- /dev/null +++ b/src/main/java/net/whg/we/rendering/ScreenClearPipeline.java @@ -0,0 +1,36 @@ +package net.whg.we.rendering; + +import net.whg.we.main.IPipelineAction; +import net.whg.we.main.PipelineConstants; + +/** + * The screen clear pipeline simply clears the screen at the begining of the + * render phase of a frame. + */ +public class ScreenClearPipeline implements IPipelineAction +{ + private final IScreenClearHandler screenClear; + + /** + * Creates a new screen clear pipeline object. + * + * @param screenClear + * - The screen clear handler to trigger each frame. + */ + public ScreenClearPipeline(IScreenClearHandler screenClear) + { + this.screenClear = screenClear; + } + + @Override + public void run() + { + screenClear.clearScreen(); + } + + @Override + public int getPriority() + { + return PipelineConstants.CLEAR_SCREEN; + } +} diff --git a/src/test/java/unit/ScreenClearPipelineTest.java b/src/test/java/unit/ScreenClearPipelineTest.java new file mode 100644 index 0000000..203ff85 --- /dev/null +++ b/src/test/java/unit/ScreenClearPipelineTest.java @@ -0,0 +1,30 @@ +package unit; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import org.junit.Test; +import net.whg.we.main.PipelineConstants; +import net.whg.we.rendering.IScreenClearHandler; +import net.whg.we.rendering.ScreenClearPipeline; + +public class ScreenClearPipelineTest +{ + @Test + public void defaultPriority() + { + assertEquals(PipelineConstants.CLEAR_SCREEN, + new ScreenClearPipeline(mock(IScreenClearHandler.class)).getPriority()); + } + + @Test + public void clearScreen() + { + IScreenClearHandler screenClear = mock(IScreenClearHandler.class); + ScreenClearPipeline pipeline = new ScreenClearPipeline(screenClear); + + pipeline.run(); + + verify(screenClear).clearScreen(); + } +} From 93a6fbf4e26687eaa70c79f1369cbb11618a0fd9 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Tue, 28 Jan 2020 13:37:29 -0700 Subject: [PATCH 18/24] Screens are now initialized on creation. --- src/main/java/net/whg/we/window/Screen.java | 2 ++ src/test/java/unit/FakeWindow.java | 2 +- src/test/java/unit/ScreenTest.java | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/whg/we/window/Screen.java b/src/main/java/net/whg/we/window/Screen.java index 5f05283..1de2697 100644 --- a/src/main/java/net/whg/we/window/Screen.java +++ b/src/main/java/net/whg/we/window/Screen.java @@ -49,6 +49,8 @@ public Screen(IWindow window) { this.window = window; window.addWindowListener(listener); + + listener.onWindowUpdated(window); } /** diff --git a/src/test/java/unit/FakeWindow.java b/src/test/java/unit/FakeWindow.java index e2fcb50..25107b9 100644 --- a/src/test/java/unit/FakeWindow.java +++ b/src/test/java/unit/FakeWindow.java @@ -8,7 +8,7 @@ public class FakeWindow implements IWindow { public IWindowListener listener; - public WindowSettings settings; + public WindowSettings settings = new WindowSettings(); @Override public void dispose() diff --git a/src/test/java/unit/ScreenTest.java b/src/test/java/unit/ScreenTest.java index 020e4cd..a98dd8f 100644 --- a/src/test/java/unit/ScreenTest.java +++ b/src/test/java/unit/ScreenTest.java @@ -4,7 +4,6 @@ import static org.junit.Assert.assertNull; import org.junit.Test; import net.whg.we.window.Screen; -import net.whg.we.window.WindowSettings; public class ScreenTest { @@ -25,7 +24,6 @@ public void resizeScreen() public void resizeScreen_updateTrigger() { FakeWindow window = new FakeWindow(); - window.settings = new WindowSettings(); window.settings.setSize(1600, 900); Screen screen = new Screen(window); @@ -77,4 +75,16 @@ public void dipose() assertNull(window.listener); } + + @Test + public void initializeOnCreation() + { + FakeWindow window = new FakeWindow(); + window.settings.setSize(400, 300); + + Screen screen = new Screen(window); + + assertEquals(400, screen.getWidth()); + assertEquals(300, screen.getHeight()); + } } From 4e4c61949789baa965e44ff619eecc607359f858 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Tue, 28 Jan 2020 13:38:34 -0700 Subject: [PATCH 19/24] Created spinning cube demo. --- src/test/java/demo/SpinningCubeDemo.java | 178 +++++++++++++++++++++++ src/test/res/cube.obj | 46 ++++++ src/test/res/diffuse.frag | 17 +++ src/test/res/diffuse.vert | 18 +++ 4 files changed, 259 insertions(+) create mode 100644 src/test/java/demo/SpinningCubeDemo.java create mode 100644 src/test/res/cube.obj create mode 100644 src/test/res/diffuse.frag create mode 100644 src/test/res/diffuse.vert diff --git a/src/test/java/demo/SpinningCubeDemo.java b/src/test/java/demo/SpinningCubeDemo.java new file mode 100644 index 0000000..4cce7d4 --- /dev/null +++ b/src/test/java/demo/SpinningCubeDemo.java @@ -0,0 +1,178 @@ +package demo; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.joml.Quaternionf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import net.whg.we.external.AssimpAPI; +import net.whg.we.external.GlfwApi; +import net.whg.we.external.OpenGLApi; +import net.whg.we.external.TimeSupplierApi; +import net.whg.we.main.AbstractBehavior; +import net.whg.we.main.GameLoop; +import net.whg.we.main.GameObject; +import net.whg.we.main.ITimeSupplier; +import net.whg.we.main.IUpdateable; +import net.whg.we.main.PollEventsAction; +import net.whg.we.main.Scene; +import net.whg.we.main.SceneGameLoop; +import net.whg.we.main.Timer; +import net.whg.we.main.TimerAction; +import net.whg.we.main.UpdatePipelineAction; +import net.whg.we.rendering.Camera; +import net.whg.we.rendering.Color; +import net.whg.we.rendering.IMesh; +import net.whg.we.rendering.IRenderingEngine; +import net.whg.we.rendering.IScreenClearHandler; +import net.whg.we.rendering.IShader; +import net.whg.we.rendering.ITexture; +import net.whg.we.rendering.Material; +import net.whg.we.rendering.RawShaderCode; +import net.whg.we.rendering.RenderBehavior; +import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.rendering.ScreenClearPipeline; +import net.whg.we.rendering.TextureData; +import net.whg.we.rendering.VertexData; +import net.whg.we.rendering.TextureData.SampleMode; +import net.whg.we.rendering.opengl.IOpenGL; +import net.whg.we.rendering.opengl.OpenGLRenderingEngine; +import net.whg.we.resource.ModelLoader; +import net.whg.we.resource.TextureLoader; +import net.whg.we.resource.assimp.IAssimp; +import net.whg.we.window.IWindow; +import net.whg.we.window.IWindowAdapter; +import net.whg.we.window.Screen; +import net.whg.we.window.WindowSettings; +import net.whg.we.window.glfw.GlfwWindow; +import net.whg.we.window.glfw.IGlfw; + +public class SpinningCubeDemo +{ + private static final Logger logger = LoggerFactory.getLogger(SpinningCubeDemo.class); + + public static void main(String[] args) throws IOException + { + logger.info("Running Spinning Cube demo."); + + ITimeSupplier timeSupplier = new TimeSupplierApi(); + Timer timer = new Timer(timeSupplier); + + IOpenGL opengl = new OpenGLApi(); + IRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); + + WindowSettings windowSettings = new WindowSettings(); + windowSettings.setTitle("Spinning Cubes Demo"); + + IGlfw glfw = new GlfwApi(); + IWindow window = new GlfwWindow(glfw, renderingEngine, windowSettings); + + IAssimp assimp = new AssimpAPI(); + + Screen screen = new Screen(window); + Camera camera = new Camera(screen); + camera.getTransform() + .setPosition(0f, 0f, 3f); + + RenderPipelineAction renderPipeline = new RenderPipelineAction(); + renderPipeline.setCamera(camera); + + IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); + screenClear.setClearColor(new Color(0.2f, 0.2f, 0.5f)); + + Scene scene = new Scene(); + scene.addPipelineAction(new UpdatePipelineAction()); + scene.addPipelineAction(new ScreenClearPipeline(screenClear)); + scene.addPipelineAction(renderPipeline); + scene.addGameObject(buildCube(renderingEngine, assimp, timer)); + + SceneGameLoop gameLoop = new SceneGameLoop(); + gameLoop.addScene(scene); + gameLoop.addAction(new TimerAction(timer)); + gameLoop.addAction(new PollEventsAction(window)); + addCloseListener(window, gameLoop); + + timer.startTimer(); + gameLoop.loop(); + window.dispose(); + } + + private static void addCloseListener(IWindow window, GameLoop gameLoop) + { + window.addWindowListener(new IWindowAdapter() + { + @Override + public void onWindowRequestClose(IWindow window) + { + gameLoop.stop(); + } + }); + } + + private static String loadText(File file) throws IOException + { + return new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())), StandardCharsets.UTF_8); + } + + private static GameObject buildCube(IRenderingEngine renderingEngine, IAssimp assimp, Timer timer) + throws IOException + { + File cubeFile = new File("src/test/res/cube.obj"); + File diffuseVertFile = new File("src/test/res/diffuse.vert"); + File diffuseFragFile = new File("src/test/res/diffuse.frag"); + File grassFile = new File("src/test/res/grass.png"); + + IMesh mesh = renderingEngine.createMesh(); + mesh.update((VertexData) new ModelLoader(assimp).loadScene(cubeFile) + .get(0) + .getData()); + + IShader shader = renderingEngine.createShader(); + shader.compile(new RawShaderCode(loadText(diffuseVertFile), loadText(diffuseFragFile))); + + ITexture texture = renderingEngine.createTexture(); + TextureData textureData = TextureLoader.loadTexture(grassFile); + textureData.setSampleMode(SampleMode.NEAREST); + texture.update(textureData); + + Material material = new Material(shader); + material.setTextures(new ITexture[] {texture}, new String[] {"diffuse"}); + + RenderBehavior renderBehavior = new RenderBehavior(); + renderBehavior.setMesh(mesh); + renderBehavior.setMaterial(material); + + GameObject gameObject = new GameObject(); + gameObject.setName("Cube"); + gameObject.addBehavior(renderBehavior); + gameObject.addBehavior(new SpinCube(timer)); + + return gameObject; + } + + private static class SpinCube extends AbstractBehavior implements IUpdateable + { + private final Timer timer; + + SpinCube(Timer timer) + { + this.timer = timer; + } + + @Override + public void update() + { + Quaternionf rot = getGameObject().getTransform() + .getRotation(); + + float t = (float) timer.getElapsedTime(); + rot.identity(); + rot.rotateLocalX(t * 2.2369f); + rot.rotateLocalY(t * 1.4562f); + rot.rotateLocalZ(t * 0.2123f); + } + } +} diff --git a/src/test/res/cube.obj b/src/test/res/cube.obj new file mode 100644 index 0000000..6f999ff --- /dev/null +++ b/src/test/res/cube.obj @@ -0,0 +1,46 @@ +# Blender v2.81 (sub 16) OBJ File: '' +# www.blender.org +mtllib cube.mtl +o Cube +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vn 0.0000 1.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +usemtl Material +s off +f 1/1/1 5/2/1 7/3/1 3/4/1 +f 4/5/2 3/6/2 7/3/2 8/7/2 +f 8/8/3 7/9/3 5/10/3 6/11/3 +f 6/12/4 2/13/4 4/14/4 8/7/4 +f 2/15/5 1/16/5 3/17/5 4/18/5 +f 6/12/6 5/2/6 1/19/6 2/20/6 diff --git a/src/test/res/diffuse.frag b/src/test/res/diffuse.frag new file mode 100644 index 0000000..104dc2a --- /dev/null +++ b/src/test/res/diffuse.frag @@ -0,0 +1,17 @@ +#version 330 core + +const vec3 lightDir = vec3(0.12039, 0.963, 0.24077); + +uniform sampler2D diffuse; + +in vec3 pass_normal; +in vec2 pass_uv; + +out vec4 color; + +void main() +{ + vec3 col = texture(diffuse, pass_uv).rgb; + col *= dot(pass_normal, lightDir) * 0.15 + 0.85; + color = vec4(col, 1.0); +} \ No newline at end of file diff --git a/src/test/res/diffuse.vert b/src/test/res/diffuse.vert new file mode 100644 index 0000000..7ef262c --- /dev/null +++ b/src/test/res/diffuse.vert @@ -0,0 +1,18 @@ +#version 330 + +uniform mat4 mvp; + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec3 normal; +layout(location = 4) in vec2 uv; + +out vec3 pass_normal; +out vec2 pass_uv; + +void main() +{ + gl_Position = mvp * vec4(pos, 1.0); + + pass_normal = normal; + pass_uv = uv; +} \ No newline at end of file From 74831247e583946547c0758679ce949c25e0305e Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Wed, 29 Jan 2020 11:28:20 -0700 Subject: [PATCH 20/24] Added mode uniform naming scheme. --- ...jectsAction.java => CullGameObjectsPipeline.java} | 2 +- ...ysicsPipelineAction.java => PhysicsPipeline.java} | 4 ++-- ...PollEventsAction.java => PollEventsPipeline.java} | 4 ++-- ...UpdatePipelineAction.java => UpdatePipeline.java} | 2 +- ...RenderPipelineAction.java => RenderPipeline.java} | 2 +- src/test/java/demo/SpinningCubeDemo.java | 12 ++++++------ src/test/java/unit/CullGameObjectsActionTest.java | 6 +++--- src/test/java/unit/PhysicsPipelineActionTest.java | 10 +++++----- src/test/java/unit/PollEventsActionTest.java | 6 +++--- src/test/java/unit/RenderBehaviorTest.java | 10 +++++----- src/test/java/unit/RenderPipelineActionTest.java | 10 +++++----- src/test/java/unit/UpdatePipelineActionTest.java | 6 +++--- 12 files changed, 37 insertions(+), 37 deletions(-) rename src/main/java/net/whg/we/main/{CullGameObjectsAction.java => CullGameObjectsPipeline.java} (91%) rename src/main/java/net/whg/we/main/{PhysicsPipelineAction.java => PhysicsPipeline.java} (92%) rename src/main/java/net/whg/we/main/{PollEventsAction.java => PollEventsPipeline.java} (87%) rename src/main/java/net/whg/we/main/{UpdatePipelineAction.java => UpdatePipeline.java} (92%) rename src/main/java/net/whg/we/rendering/{RenderPipelineAction.java => RenderPipeline.java} (96%) diff --git a/src/main/java/net/whg/we/main/CullGameObjectsAction.java b/src/main/java/net/whg/we/main/CullGameObjectsPipeline.java similarity index 91% rename from src/main/java/net/whg/we/main/CullGameObjectsAction.java rename to src/main/java/net/whg/we/main/CullGameObjectsPipeline.java index 91b8963..a423234 100644 --- a/src/main/java/net/whg/we/main/CullGameObjectsAction.java +++ b/src/main/java/net/whg/we/main/CullGameObjectsPipeline.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -public class CullGameObjectsAction implements IPipelineAction +public class CullGameObjectsPipeline implements IPipelineAction { private final List gameObjects = new CopyOnWriteArrayList<>(); diff --git a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java b/src/main/java/net/whg/we/main/PhysicsPipeline.java similarity index 92% rename from src/main/java/net/whg/we/main/PhysicsPipelineAction.java rename to src/main/java/net/whg/we/main/PhysicsPipeline.java index cb380e5..126ab3c 100644 --- a/src/main/java/net/whg/we/main/PhysicsPipelineAction.java +++ b/src/main/java/net/whg/we/main/PhysicsPipeline.java @@ -7,7 +7,7 @@ * The physics pipeline action is an action in charge of triggering physics * updates each frame based on the physics framerate. */ -public class PhysicsPipelineAction implements IPipelineAction +public class PhysicsPipeline implements IPipelineAction { private final List objects = new CopyOnWriteArrayList<>(); private final Timer timer; @@ -18,7 +18,7 @@ public class PhysicsPipelineAction implements IPipelineAction * @param timer * - The timer to being this action to. */ - public PhysicsPipelineAction(Timer timer) + public PhysicsPipeline(Timer timer) { this.timer = timer; } diff --git a/src/main/java/net/whg/we/main/PollEventsAction.java b/src/main/java/net/whg/we/main/PollEventsPipeline.java similarity index 87% rename from src/main/java/net/whg/we/main/PollEventsAction.java rename to src/main/java/net/whg/we/main/PollEventsPipeline.java index 14bca63..d6c8455 100644 --- a/src/main/java/net/whg/we/main/PollEventsAction.java +++ b/src/main/java/net/whg/we/main/PollEventsPipeline.java @@ -7,7 +7,7 @@ * the render buffer, which needs to be done at the end of every frame in order * to render the game to the screen. */ -public class PollEventsAction implements ILoopAction +public class PollEventsPipeline implements ILoopAction { private final IWindow window; @@ -17,7 +17,7 @@ public class PollEventsAction implements ILoopAction * @param window * - The window to poll the events for each frame. */ - public PollEventsAction(IWindow window) + public PollEventsPipeline(IWindow window) { this.window = window; } diff --git a/src/main/java/net/whg/we/main/UpdatePipelineAction.java b/src/main/java/net/whg/we/main/UpdatePipeline.java similarity index 92% rename from src/main/java/net/whg/we/main/UpdatePipelineAction.java rename to src/main/java/net/whg/we/main/UpdatePipeline.java index c292f14..c9b6287 100644 --- a/src/main/java/net/whg/we/main/UpdatePipelineAction.java +++ b/src/main/java/net/whg/we/main/UpdatePipeline.java @@ -3,7 +3,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -public class UpdatePipelineAction implements IPipelineAction +public class UpdatePipeline implements IPipelineAction { private final List objects = new CopyOnWriteArrayList<>(); diff --git a/src/main/java/net/whg/we/rendering/RenderPipelineAction.java b/src/main/java/net/whg/we/rendering/RenderPipeline.java similarity index 96% rename from src/main/java/net/whg/we/rendering/RenderPipelineAction.java rename to src/main/java/net/whg/we/rendering/RenderPipeline.java index 06e86f6..28900a6 100644 --- a/src/main/java/net/whg/we/rendering/RenderPipelineAction.java +++ b/src/main/java/net/whg/we/rendering/RenderPipeline.java @@ -11,7 +11,7 @@ * The renderer pipeline action is used to render elements within a scene. By * default, all behaviors which extend {@link RenderBehavior} are used. */ -public class RenderPipelineAction implements IPipelineAction +public class RenderPipeline implements IPipelineAction { private final List renderedObjects = new ArrayList<>(); private final List renderedObjectsReadOnly = Collections.unmodifiableList(renderedObjects); diff --git a/src/test/java/demo/SpinningCubeDemo.java b/src/test/java/demo/SpinningCubeDemo.java index 4cce7d4..0e9bd3a 100644 --- a/src/test/java/demo/SpinningCubeDemo.java +++ b/src/test/java/demo/SpinningCubeDemo.java @@ -17,12 +17,12 @@ import net.whg.we.main.GameObject; import net.whg.we.main.ITimeSupplier; import net.whg.we.main.IUpdateable; -import net.whg.we.main.PollEventsAction; +import net.whg.we.main.PollEventsPipeline; import net.whg.we.main.Scene; import net.whg.we.main.SceneGameLoop; import net.whg.we.main.Timer; import net.whg.we.main.TimerAction; -import net.whg.we.main.UpdatePipelineAction; +import net.whg.we.main.UpdatePipeline; import net.whg.we.rendering.Camera; import net.whg.we.rendering.Color; import net.whg.we.rendering.IMesh; @@ -33,7 +33,7 @@ import net.whg.we.rendering.Material; import net.whg.we.rendering.RawShaderCode; import net.whg.we.rendering.RenderBehavior; -import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.rendering.RenderPipeline; import net.whg.we.rendering.ScreenClearPipeline; import net.whg.we.rendering.TextureData; import net.whg.we.rendering.VertexData; @@ -77,14 +77,14 @@ public static void main(String[] args) throws IOException camera.getTransform() .setPosition(0f, 0f, 3f); - RenderPipelineAction renderPipeline = new RenderPipelineAction(); + RenderPipeline renderPipeline = new RenderPipeline(); renderPipeline.setCamera(camera); IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); screenClear.setClearColor(new Color(0.2f, 0.2f, 0.5f)); Scene scene = new Scene(); - scene.addPipelineAction(new UpdatePipelineAction()); + scene.addPipelineAction(new UpdatePipeline()); scene.addPipelineAction(new ScreenClearPipeline(screenClear)); scene.addPipelineAction(renderPipeline); scene.addGameObject(buildCube(renderingEngine, assimp, timer)); @@ -92,7 +92,7 @@ public static void main(String[] args) throws IOException SceneGameLoop gameLoop = new SceneGameLoop(); gameLoop.addScene(scene); gameLoop.addAction(new TimerAction(timer)); - gameLoop.addAction(new PollEventsAction(window)); + gameLoop.addAction(new PollEventsPipeline(window)); addCloseListener(window, gameLoop); timer.startTimer(); diff --git a/src/test/java/unit/CullGameObjectsActionTest.java b/src/test/java/unit/CullGameObjectsActionTest.java index baa86a5..1a8b759 100644 --- a/src/test/java/unit/CullGameObjectsActionTest.java +++ b/src/test/java/unit/CullGameObjectsActionTest.java @@ -3,7 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import org.junit.Test; -import net.whg.we.main.CullGameObjectsAction; +import net.whg.we.main.CullGameObjectsPipeline; import net.whg.we.main.GameObject; import net.whg.we.main.PipelineConstants; import net.whg.we.main.Scene; @@ -23,7 +23,7 @@ public void runAction() scene.addGameObject(go3); go2.markForRemoval(); - CullGameObjectsAction action = new CullGameObjectsAction(); + CullGameObjectsPipeline action = new CullGameObjectsPipeline(); scene.addPipelineAction(action); action.run(); @@ -35,6 +35,6 @@ public void runAction() @Test public void defaultPriority() { - assertEquals(PipelineConstants.DISPOSE_GAMEOBJECTS, new CullGameObjectsAction().getPriority()); + assertEquals(PipelineConstants.DISPOSE_GAMEOBJECTS, new CullGameObjectsPipeline().getPriority()); } } diff --git a/src/test/java/unit/PhysicsPipelineActionTest.java b/src/test/java/unit/PhysicsPipelineActionTest.java index 7273da6..91d8475 100644 --- a/src/test/java/unit/PhysicsPipelineActionTest.java +++ b/src/test/java/unit/PhysicsPipelineActionTest.java @@ -6,7 +6,7 @@ import org.junit.Test; import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IFixedUpdateable; -import net.whg.we.main.PhysicsPipelineAction; +import net.whg.we.main.PhysicsPipeline; import net.whg.we.main.PipelineConstants; import net.whg.we.main.Timer; @@ -15,7 +15,7 @@ public class PhysicsPipelineActionTest @Test public void ensurePipelinePriority() { - assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipelineAction(mock(Timer.class)).getPriority()); + assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipeline(mock(Timer.class)).getPriority()); } @Test @@ -26,7 +26,7 @@ public void updateBehaviors() when(timer.getPhysicsFrame()).thenReturn(0L) .thenReturn(1L); - PhysicsPipelineAction action = new PhysicsPipelineAction(timer); + PhysicsPipeline action = new PhysicsPipeline(timer); action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur UpdatableAction behavior = new UpdatableAction(); @@ -51,7 +51,7 @@ public void update_twice() .thenReturn(1L) .thenReturn(2L); - PhysicsPipelineAction action = new PhysicsPipelineAction(timer); + PhysicsPipeline action = new PhysicsPipeline(timer); UpdatableAction behavior = new UpdatableAction(); action.enableBehavior(behavior); @@ -66,7 +66,7 @@ public void update_never() when(timer.getIdealPhysicsFrame()).thenReturn(2L); when(timer.getPhysicsFrame()).thenReturn(2L); - PhysicsPipelineAction action = new PhysicsPipelineAction(timer); + PhysicsPipeline action = new PhysicsPipeline(timer); UpdatableAction behavior = new UpdatableAction(); action.enableBehavior(behavior); diff --git a/src/test/java/unit/PollEventsActionTest.java b/src/test/java/unit/PollEventsActionTest.java index 165e963..5053875 100644 --- a/src/test/java/unit/PollEventsActionTest.java +++ b/src/test/java/unit/PollEventsActionTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.verify; import org.junit.Test; import net.whg.we.main.PipelineConstants; -import net.whg.we.main.PollEventsAction; +import net.whg.we.main.PollEventsPipeline; import net.whg.we.window.IWindow; public class PollEventsActionTest @@ -13,14 +13,14 @@ public class PollEventsActionTest @Test public void defaultPriority() { - assertEquals(PipelineConstants.POLL_WINDOW_EVENTS, new PollEventsAction(mock(IWindow.class)).getPriority()); + assertEquals(PipelineConstants.POLL_WINDOW_EVENTS, new PollEventsPipeline(mock(IWindow.class)).getPriority()); } @Test public void pollEvents() { IWindow window = mock(IWindow.class); - PollEventsAction action = new PollEventsAction(window); + PollEventsPipeline action = new PollEventsPipeline(window); action.run(); diff --git a/src/test/java/unit/RenderBehaviorTest.java b/src/test/java/unit/RenderBehaviorTest.java index 274c84f..cb89bdd 100644 --- a/src/test/java/unit/RenderBehaviorTest.java +++ b/src/test/java/unit/RenderBehaviorTest.java @@ -10,7 +10,7 @@ import net.whg.we.main.GameObject; import net.whg.we.rendering.Material; import net.whg.we.rendering.RenderBehavior; -import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.rendering.RenderPipeline; import net.whg.we.window.Screen; import net.whg.we.main.Scene; import net.whg.we.rendering.Camera; @@ -76,7 +76,7 @@ public void render_normalConditions() go.addBehavior(behavior); scene.addGameObject(go); - RenderPipelineAction renderPipeline = new RenderPipelineAction(); + RenderPipeline renderPipeline = new RenderPipeline(); renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -100,7 +100,7 @@ public void render_behaviorAddedLater() scene.addGameObject(go); go.addBehavior(behavior); - RenderPipelineAction renderPipeline = new RenderPipelineAction(); + RenderPipeline renderPipeline = new RenderPipeline(); renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -122,7 +122,7 @@ public void render_noMesh_dontRender() scene.addGameObject(go); go.addBehavior(behavior); - RenderPipelineAction renderPipeline = new RenderPipelineAction(); + RenderPipeline renderPipeline = new RenderPipeline(); renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); @@ -142,7 +142,7 @@ public void render_noMaterial_dontRender() scene.addGameObject(go); go.addBehavior(behavior); - RenderPipelineAction renderPipeline = new RenderPipelineAction(); + RenderPipeline renderPipeline = new RenderPipeline(); renderPipeline.setCamera(new Camera(mock(Screen.class))); renderPipeline.enableBehavior(behavior); renderPipeline.run(); diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index 515e642..c38e0ca 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -9,7 +9,7 @@ import net.whg.we.main.GameObject; import net.whg.we.main.PipelineConstants; import net.whg.we.rendering.RenderBehavior; -import net.whg.we.rendering.RenderPipelineAction; +import net.whg.we.rendering.RenderPipeline; import net.whg.we.window.Screen; import net.whg.we.rendering.Camera; import net.whg.we.rendering.IMesh; @@ -21,7 +21,7 @@ public class RenderPipelineActionTest public void addBehavior() { RenderBehavior behavior = new RenderBehavior(); - RenderPipelineAction action = new RenderPipelineAction(); + RenderPipeline action = new RenderPipeline(); action.enableBehavior(behavior); assertEquals(1, action.renderBehaviors() @@ -43,7 +43,7 @@ public void renderElements() behavior.setMaterial(material); go.addBehavior(behavior); - RenderPipelineAction action = new RenderPipelineAction(); + RenderPipeline action = new RenderPipeline(); action.setCamera(new Camera(mock(Screen.class))); action.enableBehavior(behavior); @@ -65,7 +65,7 @@ public void renderElements_noCamera() behavior.setMaterial(material); go.addBehavior(behavior); - RenderPipelineAction action = new RenderPipelineAction(); + RenderPipeline action = new RenderPipeline(); action.enableBehavior(behavior); action.run(); @@ -76,6 +76,6 @@ public void renderElements_noCamera() @Test public void ensureCorrectPriority() { - assertEquals(PipelineConstants.RENDER_SOLIDS, new RenderPipelineAction().getPriority()); + assertEquals(PipelineConstants.RENDER_SOLIDS, new RenderPipeline().getPriority()); } } diff --git a/src/test/java/unit/UpdatePipelineActionTest.java b/src/test/java/unit/UpdatePipelineActionTest.java index a0d0308..587168d 100644 --- a/src/test/java/unit/UpdatePipelineActionTest.java +++ b/src/test/java/unit/UpdatePipelineActionTest.java @@ -6,20 +6,20 @@ import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IUpdateable; import net.whg.we.main.PipelineConstants; -import net.whg.we.main.UpdatePipelineAction; +import net.whg.we.main.UpdatePipeline; public class UpdatePipelineActionTest { @Test public void ensurePipelinePriority() { - assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipelineAction().getPriority()); + assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipeline().getPriority()); } @Test public void updateBehaviors() { - UpdatePipelineAction action = new UpdatePipelineAction(); + UpdatePipeline action = new UpdatePipeline(); action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur UpdatableAction behavior = new UpdatableAction(); From abedf375b35d8d0802301e6d76124890098cb171 Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Wed, 29 Jan 2020 13:19:36 -0700 Subject: [PATCH 21/24] The update pipeline now holds a timer reference. --- src/main/java/net/whg/we/main/IUpdateable.java | 6 +++++- .../java/net/whg/we/main/UpdatePipeline.java | 18 +++++++++++++++++- .../java/unit/UpdatePipelineActionTest.java | 12 +++++++++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/whg/we/main/IUpdateable.java b/src/main/java/net/whg/we/main/IUpdateable.java index caa830d..121516f 100644 --- a/src/main/java/net/whg/we/main/IUpdateable.java +++ b/src/main/java/net/whg/we/main/IUpdateable.java @@ -9,6 +9,10 @@ public interface IUpdateable { /** * Called once each frame to update this object. + * + * @param timer + * - The timer associated with the update pipeline. Used to retrieve delta + * times. */ - void update(); + void update(Timer timer); } diff --git a/src/main/java/net/whg/we/main/UpdatePipeline.java b/src/main/java/net/whg/we/main/UpdatePipeline.java index c9b6287..523c84b 100644 --- a/src/main/java/net/whg/we/main/UpdatePipeline.java +++ b/src/main/java/net/whg/we/main/UpdatePipeline.java @@ -3,15 +3,31 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +/** + * The update pipeline is triggered each frame to prepare a scene to be + * rendered, or run logic which needs to be executed every frame. + */ public class UpdatePipeline implements IPipelineAction { private final List objects = new CopyOnWriteArrayList<>(); + private final Timer timer; + + /** + * Creates a new update pipeline object. + * + * @param timer + * - The timer associated with this update pipeline. + */ + public UpdatePipeline(Timer timer) + { + this.timer = timer; + } @Override public void run() { for (IUpdateable obj : objects) - obj.update(); + obj.update(timer); } @Override diff --git a/src/test/java/unit/UpdatePipelineActionTest.java b/src/test/java/unit/UpdatePipelineActionTest.java index 587168d..e6c84ff 100644 --- a/src/test/java/unit/UpdatePipelineActionTest.java +++ b/src/test/java/unit/UpdatePipelineActionTest.java @@ -1,11 +1,13 @@ package unit; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import org.junit.Test; import net.whg.we.main.AbstractBehavior; import net.whg.we.main.IUpdateable; import net.whg.we.main.PipelineConstants; +import net.whg.we.main.Timer; import net.whg.we.main.UpdatePipeline; public class UpdatePipelineActionTest @@ -13,13 +15,14 @@ public class UpdatePipelineActionTest @Test public void ensurePipelinePriority() { - assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipeline().getPriority()); + assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipeline(mock(Timer.class)).getPriority()); } @Test public void updateBehaviors() { - UpdatePipeline action = new UpdatePipeline(); + Timer timer = mock(Timer.class); + UpdatePipeline action = new UpdatePipeline(timer); action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur UpdatableAction behavior = new UpdatableAction(); @@ -28,6 +31,7 @@ public void updateBehaviors() action.enableBehavior(behavior); action.run(); assertEquals(1, behavior.calls); + assertTrue(timer == behavior.timer); action.disableBehavior(behavior); action.run(); @@ -36,11 +40,13 @@ public void updateBehaviors() class UpdatableAction extends AbstractBehavior implements IUpdateable { + Timer timer; int calls = 0; @Override - public void update() + public void update(Timer timer) { + this.timer = timer; calls++; } } From e159a938f98e29a5b6adcd2563b214bd43f7961d Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Wed, 29 Jan 2020 13:21:28 -0700 Subject: [PATCH 22/24] Render pipeline can now be init with a camera. --- .../net/whg/we/rendering/RenderPipeline.java | 19 +++++++++++++++++++ .../java/unit/RenderPipelineActionTest.java | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/java/net/whg/we/rendering/RenderPipeline.java b/src/main/java/net/whg/we/rendering/RenderPipeline.java index 28900a6..c059a66 100644 --- a/src/main/java/net/whg/we/rendering/RenderPipeline.java +++ b/src/main/java/net/whg/we/rendering/RenderPipeline.java @@ -17,6 +17,25 @@ public class RenderPipeline implements IPipelineAction private final List renderedObjectsReadOnly = Collections.unmodifiableList(renderedObjects); private Camera camera; + /** + * Creates a new render pipeline action. + */ + public RenderPipeline() + { + this(null); + } + + /** + * Creates a new render pipeline action and initializes it with a camera. + * + * @param camera + * - The camera to attach to this render pipeline action. + */ + public RenderPipeline(Camera camera) + { + this.camera = camera; + } + @Override public void run() { diff --git a/src/test/java/unit/RenderPipelineActionTest.java b/src/test/java/unit/RenderPipelineActionTest.java index c38e0ca..049b925 100644 --- a/src/test/java/unit/RenderPipelineActionTest.java +++ b/src/test/java/unit/RenderPipelineActionTest.java @@ -78,4 +78,13 @@ public void ensureCorrectPriority() { assertEquals(PipelineConstants.RENDER_SOLIDS, new RenderPipeline().getPriority()); } + + @Test + public void initializeWithCamera() + { + Camera camera = mock(Camera.class); + RenderPipeline pipeline = new RenderPipeline(camera); + + assertTrue(camera == pipeline.getCamera()); + } } From 8e47ad3184fc4aab25011a4d4575fe0791039bcc Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Wed, 29 Jan 2020 13:28:51 -0700 Subject: [PATCH 23/24] Created window close handler. --- .../net/whg/we/window/WindowCloseHandler.java | 79 +++++++++++++++++++ .../java/unit/WindowCloseHandlerTest.java | 58 ++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 src/main/java/net/whg/we/window/WindowCloseHandler.java create mode 100644 src/test/java/unit/WindowCloseHandlerTest.java diff --git a/src/main/java/net/whg/we/window/WindowCloseHandler.java b/src/main/java/net/whg/we/window/WindowCloseHandler.java new file mode 100644 index 0000000..07ebbe1 --- /dev/null +++ b/src/main/java/net/whg/we/window/WindowCloseHandler.java @@ -0,0 +1,79 @@ +package net.whg.we.window; + +import net.whg.we.main.GameLoop; +import net.whg.we.util.IDisposable; + +/** + * This class is a simple utility which listens for when a window close request + * is triggered and stops the connected game loop, allowing the window to close. + *

+ * The window binding can be removed by disposing this object. This object will + * also automatially be disposed after stopping the game loop. + */ +public class WindowCloseHandler implements IDisposable +{ + /** + * Creates a new window close handler. This will automatically bind the listener + * to the window. + * + * @param window + * - The window to bind to. + * @param gameLoop + * - The game loop to stop when the window requests to close. + */ + public static WindowCloseHandler bindToWindow(IWindow window, GameLoop gameLoop) + { + return new WindowCloseHandler(window, gameLoop); + } + + /** + * A simple listener which listens for a window close request. + */ + private class WindowCloseListener extends IWindowAdapter + { + @Override + public void onWindowRequestClose(IWindow window) + { + gameLoop.stop(); + dispose(); + } + } + + private final WindowCloseListener listener = new WindowCloseListener(); + private final IWindow window; + private final GameLoop gameLoop; + private boolean disposed; + + /** + * Creates a new window close handler. This constructor handles the window + * binding automatically. + * + * @param window + * - The window to bind to. + * @param gameLoop + * - The game loop to stop when the window requests to close. + */ + private WindowCloseHandler(IWindow window, GameLoop gameLoop) + { + this.window = window; + this.gameLoop = gameLoop; + + window.addWindowListener(listener); + } + + @Override + public void dispose() + { + if (isDisposed()) + return; + + disposed = true; + window.removeWindowListener(listener); + } + + @Override + public boolean isDisposed() + { + return disposed; + } +} diff --git a/src/test/java/unit/WindowCloseHandlerTest.java b/src/test/java/unit/WindowCloseHandlerTest.java new file mode 100644 index 0000000..c0c5787 --- /dev/null +++ b/src/test/java/unit/WindowCloseHandlerTest.java @@ -0,0 +1,58 @@ +package unit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import org.junit.Test; +import net.whg.we.main.GameLoop; +import net.whg.we.window.IWindow; +import net.whg.we.window.IWindowListener; +import net.whg.we.window.WindowCloseHandler; + +public class WindowCloseHandlerTest +{ + @Test + public void bindToWindow() + { + IWindow window = mock(IWindow.class); + GameLoop loop = mock(GameLoop.class); + + WindowCloseHandler.bindToWindow(window, loop); + + verify(window).addWindowListener(any()); + } + + @Test + public void dispose() + { + IWindow window = mock(IWindow.class); + GameLoop loop = mock(GameLoop.class); + + WindowCloseHandler handler = WindowCloseHandler.bindToWindow(window, loop); + + handler.dispose(); + handler.dispose(); + + verify(window, times(1)).removeWindowListener(any()); + } + + @Test + public void onClose() + { + IWindow window = mock(IWindow.class); + GameLoop loop = mock(GameLoop.class); + + IWindowListener[] l = new IWindowListener[1]; + doAnswer(a -> + { l[0] = a.getArgument(0); return null; }).when(window) + .addWindowListener(any()); + + WindowCloseHandler.bindToWindow(window, loop); + + l[0].onWindowRequestClose(window); + + verify(loop).stop(); + } +} From bdedc73d33db1e9b3619865f49654ac98a6015ca Mon Sep 17 00:00:00 2001 From: TheDudeFromCI Date: Wed, 29 Jan 2020 13:29:07 -0700 Subject: [PATCH 24/24] Updated spinning cube demo documentation. --- src/test/java/demo/SpinningCubeDemo.java | 222 +++++++++++++++++------ 1 file changed, 163 insertions(+), 59 deletions(-) diff --git a/src/test/java/demo/SpinningCubeDemo.java b/src/test/java/demo/SpinningCubeDemo.java index 0e9bd3a..6684903 100644 --- a/src/test/java/demo/SpinningCubeDemo.java +++ b/src/test/java/demo/SpinningCubeDemo.java @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.List; import org.joml.Quaternionf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,9 +14,7 @@ import net.whg.we.external.OpenGLApi; import net.whg.we.external.TimeSupplierApi; import net.whg.we.main.AbstractBehavior; -import net.whg.we.main.GameLoop; import net.whg.we.main.GameObject; -import net.whg.we.main.ITimeSupplier; import net.whg.we.main.IUpdateable; import net.whg.we.main.PollEventsPipeline; import net.whg.we.main.Scene; @@ -38,137 +37,242 @@ import net.whg.we.rendering.TextureData; import net.whg.we.rendering.VertexData; import net.whg.we.rendering.TextureData.SampleMode; -import net.whg.we.rendering.opengl.IOpenGL; import net.whg.we.rendering.opengl.OpenGLRenderingEngine; import net.whg.we.resource.ModelLoader; +import net.whg.we.resource.Resource; import net.whg.we.resource.TextureLoader; -import net.whg.we.resource.assimp.IAssimp; import net.whg.we.window.IWindow; -import net.whg.we.window.IWindowAdapter; import net.whg.we.window.Screen; +import net.whg.we.window.WindowCloseHandler; import net.whg.we.window.WindowSettings; import net.whg.we.window.glfw.GlfwWindow; -import net.whg.we.window.glfw.IGlfw; +/** + * The spinning cube demo shows all of the core elements required to create a + * simple scene set up. This includes loading a window, handling timing, working + * with a pipeline, creating behaviors, updating a scene, loading resources, and + * rendering a scene. + *

+ * In this demo, you can see a small textured cube which was loaded from file, + * placed into a scene and is set rotating endlessly. The purpose of this demo + * is to show how a game would normally be set up to allow for elements to be + * added or configured. + * + * @author TheDudeFromCI + */ public class SpinningCubeDemo { private static final Logger logger = LoggerFactory.getLogger(SpinningCubeDemo.class); + /** + * Launches the spinning cube demo. + * + * @param args + * - Does nothing + * @throws IOException + * If the required resource files could not be found. + */ public static void main(String[] args) throws IOException { logger.info("Running Spinning Cube demo."); - ITimeSupplier timeSupplier = new TimeSupplierApi(); - Timer timer = new Timer(timeSupplier); - - IOpenGL opengl = new OpenGLApi(); - IRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); - + // The window settings object can be used to configure how the window should be + // displayed. This includes things like title, size, fullscreen, vSync, etc. WindowSettings windowSettings = new WindowSettings(); windowSettings.setTitle("Spinning Cubes Demo"); - IGlfw glfw = new GlfwApi(); - IWindow window = new GlfwWindow(glfw, renderingEngine, windowSettings); - - IAssimp assimp = new AssimpAPI(); + // The rendering engine is used to actually render the game. Here, we're loading + // the OpenGL 3.3 rendering engine. + IRenderingEngine renderingEngine = new OpenGLRenderingEngine(new OpenGLApi()); - Screen screen = new Screen(window); - Camera camera = new Camera(screen); - camera.getTransform() - .setPosition(0f, 0f, 3f); + // Creates and displays the window. This window uses the GLFW window handling + // framework. The window will initialize the rendering engine for us here. + IWindow window = new GlfwWindow(new GlfwApi(), renderingEngine, windowSettings); - RenderPipeline renderPipeline = new RenderPipeline(); - renderPipeline.setCamera(camera); + // Now we can start building our scene. A scene is simply a collection of game + // objects which are active at a given time. This could mean they are being + // rendered to the screen, or are simply running logic in the background. A + // scene is needed to maintain what's active and what's not. Scenes operate on a + // pipeline workflow, so we'll need to create the pipeline now. + Scene scene = new Scene(); + // As we can to clear the screen each frame, to draw a new image, we'll add a + // screen clear pipeline action to the scene. This will tell the scene to clear + // the screen each frame at the correct time. We'll also confiugre the clear + // color to be a dark blue color. IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); screenClear.setClearColor(new Color(0.2f, 0.2f, 0.5f)); - - Scene scene = new Scene(); - scene.addPipelineAction(new UpdatePipeline()); scene.addPipelineAction(new ScreenClearPipeline(screenClear)); - scene.addPipelineAction(renderPipeline); - scene.addGameObject(buildCube(renderingEngine, assimp, timer)); + // Since we want to render our scene, we need to add a render pipeline action to + // it. The render pipeline can be initialized with a camera, so we'll go ahead + // and do that here. We'll also offset the position of the camera to see the + // cube better. + Camera camera = new Camera(new Screen(window)); + scene.addPipelineAction(new RenderPipeline(camera)); + camera.getTransform() + .setPosition(0f, 0f, 3f); + + // A game loop is the more important element. This loop is in charge of handling + // running the events each frame which are required to run the game. A + // SceneGameLoop is an extention of the game loop which will automatically + // handle scenes and pipelines as well. SceneGameLoop gameLoop = new SceneGameLoop(); gameLoop.addScene(scene); + + // A lot of updates within a game tend to require a timer to work properly. In + // our case, the time is needed each frame in order to update the rotation of + // the cube. So we'll add that here. Notice this is added to the game loop + // itself, not the scene. + Timer timer = new Timer(new TimeSupplierApi()); gameLoop.addAction(new TimerAction(timer)); + + // In order to spin the cube, it will need to be updated every frame. To trigger + // these updates, we'll need to add an update pipeline action to the scene. We + // can use the timer object we created eariler to initialize the pipeline with. + scene.addPipelineAction(new UpdatePipeline(timer)); + + // When working with a window, it's extremely important to poll the window + // events at the end of each frame in order to actually display anything on + // screen or recieve any window events. We can add this helper here to do that + // for us. Once again, this is added to the game loop and not the scene. gameLoop.addAction(new PollEventsPipeline(window)); - addCloseListener(window, gameLoop); + // When the user clicks the close button on the window, the window sends out a + // window close request. This utility will trigger the game loop to stop once + // the window close request is made. + WindowCloseHandler.bindToWindow(window, gameLoop); + + // Finally, let's create the cube game object, and add it to the scene. + scene.addGameObject(buildCube(renderingEngine)); + + // Now that the scene is set up, we're ready to start the game loop. Since we're + // working with a timer, we want to start the timer right before starting the + // game loop to make sure all the timing is correct. timer.startTimer(); gameLoop.loop(); - window.dispose(); - } - private static void addCloseListener(IWindow window, GameLoop gameLoop) - { - window.addWindowListener(new IWindowAdapter() - { - @Override - public void onWindowRequestClose(IWindow window) - { - gameLoop.stop(); - } - }); + // At the game loop is stopped, we can dispose the window and let the program + // exit peacefully. + window.dispose(); } + /** + * This function loads all the text from a file and returns it as a string. + * + * @param file + * - The file to load. + * @return The text within the file. + * @throws IOException + * If there was an error while loading the file. + */ private static String loadText(File file) throws IOException { return new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())), StandardCharsets.UTF_8); } - private static GameObject buildCube(IRenderingEngine renderingEngine, IAssimp assimp, Timer timer) - throws IOException + /** + * This function loads all resources required by the spinning cube, and returns + * it as a game object to be added to the scene. + * + * @param renderingEngine + * - The rendering engine, to allocate resources with. + * @return The spinning cube game object. + * @throws IOException + * If the resources could not be loaded. + */ + private static GameObject buildCube(IRenderingEngine renderingEngine) throws IOException { + // Just laying out the file paths to where each resource is located. File cubeFile = new File("src/test/res/cube.obj"); File diffuseVertFile = new File("src/test/res/diffuse.vert"); File diffuseFragFile = new File("src/test/res/diffuse.frag"); File grassFile = new File("src/test/res/grass.png"); + // First, let's load the mesh. This one is a bit messy looking, so let's break + // it down. The model loader is an object which can be used to load 3D model + // files. We'll use this here to load the cube file. + ModelLoader modelLoader = new ModelLoader(new AssimpAPI()); + + // When the model loader loads a file, a list of resources are returned. These + // are all the resources the model loader was able to pull from the file. + // (I.e. mesh, skeleton, materials, etc) In our case, the file only contains + // mesh data, so we can extract the first resource from the list and pull the + // data as vertex data. + List resources = modelLoader.loadScene(cubeFile); + VertexData vertexData = (VertexData) resources.get(0) + .getData(); + + // Now that we have the vertex data, we can compile it into a mesh. IMesh mesh = renderingEngine.createMesh(); - mesh.update((VertexData) new ModelLoader(assimp).loadScene(cubeFile) - .get(0) - .getData()); + mesh.update(vertexData); + // To load the shader, we just need the vertex shader code and the fragment + // shader code. We can load the text from those files, and place them into a raw + // shader code object to pass to the shader. + RawShaderCode shaderCode = new RawShaderCode(loadText(diffuseVertFile), loadText(diffuseFragFile)); + + // Like the mesh, create the shader and compile it using the shader code. IShader shader = renderingEngine.createShader(); - shader.compile(new RawShaderCode(loadText(diffuseVertFile), loadText(diffuseFragFile))); + shader.compile(shaderCode); - ITexture texture = renderingEngine.createTexture(); + // Now for the texture. We can load a texture in the same manner as loading a + // model, using the TextureLoader object. We also want to change the sampling + // mode to NEAREST, to keep that "pixel-y" look. TextureData textureData = TextureLoader.loadTexture(grassFile); textureData.setSampleMode(SampleMode.NEAREST); + + // Now we can compile the texture data into a texture. + ITexture texture = renderingEngine.createTexture(); texture.update(textureData); + // After gathering both the shader and the textures, we can use those to create + // a material which represents our grass. Here, "diffuse" is the name of the + // texture within the shader code, and corresponds to the grass texture. Material material = new Material(shader); material.setTextures(new ITexture[] {texture}, new String[] {"diffuse"}); + // All the pieces are set up! Now lets create the game object which represents + // the cube. + GameObject gameObject = new GameObject(); + gameObject.setName("Cube"); + + // A Behavior is a piece of logic which is attached to a game object to change + // it's behavior. A render behavoir is an example of that which can be used to + // get an object to render. You simplely need to provide it with a mesh and a + // material to work with. Once added to the cube, the cube should be appear on + // screen. RenderBehavior renderBehavior = new RenderBehavior(); renderBehavior.setMesh(mesh); renderBehavior.setMaterial(material); - - GameObject gameObject = new GameObject(); - gameObject.setName("Cube"); gameObject.addBehavior(renderBehavior); - gameObject.addBehavior(new SpinCube(timer)); + + // We also want to add another behavior for making our cube spin. The custom + // class for that logic can be seen below. Here we just create a new instance + // and add it to the cube. + gameObject.addBehavior(new SpinCube()); return gameObject; } + /** + * This class contains a simple set of logic which will rotate the cube forever, + * updating the rotation each frame before rendering. + */ private static class SpinCube extends AbstractBehavior implements IUpdateable { - private final Timer timer; - - SpinCube(Timer timer) - { - this.timer = timer; - } - @Override - public void update() + public void update(Timer timer) { + // Get the rotation component of the game object's transform. Quaternionf rot = getGameObject().getTransform() .getRotation(); + // Get the amount of time, in seconds, that passed since the timer was started. + // (At the beginning of the game loop.) float t = (float) timer.getElapsedTime(); + + // Reset the rotation, and apply the new rotation values to it. rot.identity(); rot.rotateLocalX(t * 2.2369f); rot.rotateLocalY(t * 1.4562f);