├── renovate.json ├── wiki ├── terrain.webp └── spinning-cube.webp ├── Demos ├── res │ ├── grass.png │ ├── terrain.png │ ├── diffuse.frag │ ├── diffuse.vert │ └── cube.obj ├── .settings │ ├── org.eclipse.jdt.apt.core.prefs │ ├── org.eclipse.core.resources.prefs │ ├── org.eclipse.m2e.core.prefs │ └── org.eclipse.jdt.core.prefs ├── .project ├── pom.xml └── .classpath ├── .settings ├── org.eclipse.jdt.apt.core.prefs ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── src ├── test │ ├── resources │ │ ├── grass.png │ │ └── log4j2.properties │ └── java │ │ └── unit │ │ ├── engine │ │ ├── main │ │ │ ├── TimerActionTest.java │ │ │ ├── CullGameObjectsActionTest.java │ │ │ ├── GameLoopTest.java │ │ │ ├── UpdatePipelineActionTest.java │ │ │ ├── PhysicsPipelineActionTest.java │ │ │ ├── AbstractBehaviorTest.java │ │ │ ├── TimerTest.java │ │ │ └── SceneGameLoopTest.java │ │ ├── window │ │ │ ├── InputEndFrameActionTest.java │ │ │ ├── PollEventsActionTest.java │ │ │ ├── FakeWindow.java │ │ │ ├── WindowCloseHandlerTest.java │ │ │ └── ScreenTest.java │ │ ├── rendering │ │ │ ├── ScreenClearPipelineTest.java │ │ │ ├── OpenGLRenderingEngineTest.java │ │ │ ├── CameraTest.java │ │ │ ├── GLScreenClearTest.java │ │ │ ├── RenderPipelineActionTest.java │ │ │ ├── TextureDataTest.java │ │ │ ├── ColorTest.java │ │ │ └── MaterialTest.java │ │ ├── resource │ │ │ └── TextureLoaderTest.java │ │ ├── net │ │ │ ├── packets │ │ │ │ ├── PacketDataHandlerTest.java │ │ │ │ └── PacketFactoryTest.java │ │ │ └── server │ │ │ │ └── ServerClientTest.java │ │ └── util │ │ │ ├── TupleTest.java │ │ │ └── OutputStreamWrapperTest.java │ │ └── library │ │ └── actions │ │ └── HandleServerPacketsActionTest.java └── main │ └── java │ └── net │ └── whg │ ├── we │ ├── net │ │ ├── IPacketSender.java │ │ ├── IPacket.java │ │ ├── server │ │ │ ├── IClientHandler.java │ │ │ ├── IConnectedClient.java │ │ │ ├── ServerClient.java │ │ │ └── IServer.java │ │ ├── packets │ │ │ ├── IBinaryPacket.java │ │ │ ├── PacketDataHandler.java │ │ │ ├── IPacketInitializer.java │ │ │ └── PacketFactory.java │ │ ├── ConnectionData.java │ │ ├── IDataHandler.java │ │ ├── IServerSocket.java │ │ └── ISocket.java │ ├── rendering │ │ ├── CullingMode.java │ │ ├── IRenderResource.java │ │ ├── opengl │ │ │ ├── GLException.java │ │ │ ├── GLScreenClear.java │ │ │ ├── OpenGLRenderingEngine.java │ │ │ └── GLMesh.java │ │ ├── ITexture.java │ │ ├── ScreenClearPipeline.java │ │ ├── IMesh.java │ │ ├── RawShaderCode.java │ │ ├── IScreenClearHandler.java │ │ ├── IRenderingEngine.java │ │ ├── RenderPipeline.java │ │ ├── IShader.java │ │ ├── RenderBehavior.java │ │ ├── Camera.java │ │ └── Color.java │ ├── main │ │ ├── IFixedUpdatable.java │ │ ├── IUpdatable.java │ │ ├── ITimeSupplier.java │ │ ├── TimerAction.java │ │ ├── CullGameObjectsPipeline.java │ │ ├── PollEventsPipeline.java │ │ ├── ILoopAction.java │ │ ├── ISceneListener.java │ │ ├── UpdatePipeline.java │ │ ├── PhysicsPipeline.java │ │ ├── IPipelineAction.java │ │ ├── AbstractBehavior.java │ │ ├── GameLoop.java │ │ ├── SceneGameLoop.java │ │ └── Timer.java │ ├── resource │ │ ├── assimp │ │ │ ├── AssimpException.java │ │ │ ├── IAssimp.java │ │ │ ├── IAssimpScene.java │ │ │ └── IAssimpMesh.java │ │ ├── Resource.java │ │ ├── TextureLoader.java │ │ └── ModelLoader.java │ ├── window │ │ ├── glfw │ │ │ └── GLFWException.java │ │ ├── InputEndFrameAction.java │ │ ├── IWindowAdapter.java │ │ ├── WindowReceiver.java │ │ ├── WindowCloseHandler.java │ │ ├── Screen.java │ │ ├── IWindow.java │ │ └── IWindowListener.java │ ├── util │ │ ├── IDisposable.java │ │ ├── ILogger.java │ │ └── OutputStreamWrapper.java │ └── external │ │ ├── TimeSupplierAPI.java │ │ ├── LoggerAPI.java │ │ ├── ServerSocketAPI.java │ │ └── SocketAPI.java │ └── lib │ ├── packets │ ├── HeartbeatPacketInitializer.java │ └── HeartbeatPacket.java │ └── actions │ ├── HandleServerPacketsAction.java │ └── FramerateLimiterAction.java ├── .bettercodehub.yml ├── .gitignore ├── docs └── index.html ├── .github ├── ISSUE_TEMPLATE │ ├── question.md │ ├── code-quality---documentation.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── maven.yml ├── .vscode └── settings.json ├── .project ├── CONTRIBUTING.md ├── .classpath └── CODE_OF_CONDUCT.md /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /wiki/terrain.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDudeFromCI/WraithEngine/HEAD/wiki/terrain.webp -------------------------------------------------------------------------------- /Demos/res/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDudeFromCI/WraithEngine/HEAD/Demos/res/grass.png -------------------------------------------------------------------------------- /Demos/res/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDudeFromCI/WraithEngine/HEAD/Demos/res/terrain.png -------------------------------------------------------------------------------- /wiki/spinning-cube.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDudeFromCI/WraithEngine/HEAD/wiki/spinning-cube.webp -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /src/test/resources/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheDudeFromCI/WraithEngine/HEAD/src/test/resources/grass.png -------------------------------------------------------------------------------- /.bettercodehub.yml: -------------------------------------------------------------------------------- 1 | component_depth: 7 2 | languages: 3 | - java 4 | exclude: 5 | - /src/java/net/whg/we/external/.* 6 | -------------------------------------------------------------------------------- /Demos/.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /Demos/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding/=UTF-8 4 | -------------------------------------------------------------------------------- /Demos/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/test/java=UTF-8 4 | encoding//src/test/resources=UTF-8 5 | encoding/=UTF-8 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | /target 15 | /Demos/target 16 | *.code-workspace 17 | bin 18 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WraithEngine | Home 6 | 7 | 8 | 9 |

Welcome to the WraithEngine documentation website!

10 |

This is a place holder for the upcoming website. Check back soon as the website is developed. In the mean time, please have a wonderful day.

11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demos/res/diffuse.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | const vec3 lightDir = vec3(0.12039, 0.963, 0.24077); 4 | 5 | uniform sampler2D diffuse; 6 | 7 | in vec3 pass_normal; 8 | in vec2 pass_uv; 9 | 10 | out vec4 color; 11 | 12 | void main() 13 | { 14 | vec3 col = texture(diffuse, pass_uv).rgb; 15 | col *= dot(pass_normal, lightDir) * 0.25 + 0.75; 16 | color = vec4(col, 1.0); 17 | } -------------------------------------------------------------------------------- /Demos/res/diffuse.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform mat4 mvp; 4 | 5 | layout(location = 0) in vec3 pos; 6 | layout(location = 1) in vec3 normal; 7 | layout(location = 2) in vec3 tangent; 8 | layout(location = 3) in vec3 bitangent; 9 | layout(location = 4) in vec2 uv; 10 | 11 | out vec3 pass_normal; 12 | out vec2 pass_uv; 13 | 14 | void main() 15 | { 16 | gl_Position = mvp * vec4(pos, 1.0); 17 | 18 | pass_normal = normal; 19 | pass_uv = uv; 20 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Have a question about WraithEngine? Ask away! 4 | title: '' 5 | labels: Question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What's your question?** 11 | Write your question here. Please be as detailed as possible. 12 | 13 | **What have you tried? (If Applicable)** 14 | If this is a question about the usage of WraithEngine, please let us know what you've already tried and where you've looked. 15 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/IPacketSender.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Represents a connection wrapper which has sent a packet to this library. 7 | */ 8 | public interface IPacketSender 9 | { 10 | /** 11 | * Sends a packet to this packet sender. 12 | * 13 | * @param packet 14 | * - The packet to send. 15 | */ 16 | void sendPacket(IPacket packet) throws IOException; 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "target": true 9 | }, 10 | "cSpell.words": [ 11 | "Assimp", 12 | "Dskip", 13 | "GLAPI", 14 | "Glfw", 15 | "LWJGL", 16 | "Quaternionf", 17 | "Trilinearpolation", 18 | "fullscreen", 19 | "thedudefromci", 20 | "webp", 21 | "wraithengine" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/CullingMode.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * The mode to use when culling faces during rendering. 5 | */ 6 | public enum CullingMode 7 | { 8 | /** 9 | * Never cull any faces. 10 | */ 11 | NONE, 12 | 13 | /** 14 | * Only cull triangles facing towards the camera. 15 | */ 16 | FRONT_FACING, 17 | 18 | /** 19 | * Only cull triangles facing away from the camera. 20 | */ 21 | BACK_FACING 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | status = info 2 | name = PropertiesConfig 3 | 4 | appender.console.type = Console 5 | appender.console.name = STDOUT 6 | appender.console.layout.type = PatternLayout 7 | appender.console.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss}][%-5p] %m%n 8 | appender.console.filter.threshold.type = ThresholdFilter 9 | appender.console.filter.threshold.level = trace 10 | 11 | rootLogger.level = debug 12 | rootLogger.appenderRefs = stdout 13 | rootLogger.appenderRef.stdout.ref = STDOUT -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/IFixedUpdatable.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * A fixed updatable class is triggered to update once for each physics update. 5 | * This allows for complex actions to occur which must occur on an accurate 6 | * timestamp, such as AI updates, physics updates, player movement, etc. 7 | */ 8 | public interface IFixedUpdatable 9 | { 10 | /** 11 | * Called once on each physics update to update this object. 12 | */ 13 | void fixedUpdate(); 14 | } 15 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=13 3 | org.eclipse.jdt.core.compiler.compliance=13 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 8 | org.eclipse.jdt.core.compiler.release=disabled 9 | org.eclipse.jdt.core.compiler.source=13 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/code-quality---documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Code Quality & Documentation 3 | about: Concerns or requests about code quality and documentation. 4 | title: '' 5 | labels: Code Quality 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What does this refer to?** 11 | Is this about code quality? Test coverage? Pull requests? Poor documentation? Demos and examples? 12 | 13 | **What's wrong with it?** 14 | Explain the issue in detail. 15 | 16 | **How can it be improved?** 17 | What do you recommend should be done to address the issue? 18 | -------------------------------------------------------------------------------- /Demos/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=13 3 | org.eclipse.jdt.core.compiler.compliance=13 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 8 | org.eclipse.jdt.core.compiler.release=disabled 9 | org.eclipse.jdt.core.compiler.source=13 10 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/IPacket.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | /** 4 | * A data object which was received over a network socket to be handled. 5 | */ 6 | public interface IPacket 7 | { 8 | /** 9 | * Called from the main thread to handle this packet. 10 | */ 11 | void handle(); 12 | 13 | /** 14 | * Gets the socket which sent this socket. public 15 | * 16 | * @return The packet sender, or null if this packet does not have a sender. 17 | * (I.e. Generated to be sent.) 18 | */ 19 | IPacketSender getSender(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/assimp/AssimpException.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource.assimp; 2 | 3 | /** 4 | * An exception which is thrown while working with the Assimp library. 5 | */ 6 | public class AssimpException extends RuntimeException 7 | { 8 | private static final long serialVersionUID = 1025315563896250360L; 9 | 10 | /** 11 | * Creates a new Assimp Exception. 12 | * 13 | * @param message 14 | * - The message for this exception. 15 | */ 16 | public AssimpException(String message) 17 | { 18 | super(message); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/glfw/GLFWException.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window.glfw; 2 | 3 | /** 4 | * An exception which is thrown by the GLFW window handler, while attempting to 5 | * manipulate a window. 6 | */ 7 | public class GLFWException extends RuntimeException 8 | { 9 | private static final long serialVersionUID = 10293840921L; 10 | 11 | /** 12 | * Creates a new GLFW Exception with the given error message. 13 | * 14 | * @param message 15 | * - The error message. 16 | */ 17 | public GLFWException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | wraithengine 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/IUpdatable.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * An updatable class is any class which listens for an event which is triggered 5 | * each time a frame is rendered. This is used to prepare the objects in a scene 6 | * for rendering, such as animations or camera movements which occur each frame. 7 | */ 8 | public interface IUpdatable 9 | { 10 | /** 11 | * Called once each frame to update this object. 12 | * 13 | * @param timer 14 | * - The timer associated with the update pipeline. Used to retrieve delta 15 | * times. 16 | */ 17 | void update(Timer timer); 18 | } 19 | -------------------------------------------------------------------------------- /Demos/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | wraithengine-demos 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/assimp/IAssimp.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource.assimp; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * This interface is used as a wrapper for communicating with Assimp. It is used 7 | * to add an extra layer of abstraction between WraithEngine and Assimp, to 8 | * allow the code to be more testable. 9 | */ 10 | public interface IAssimp 11 | { 12 | /** 13 | * Loads the scene within the given file. 14 | * 15 | * @param file 16 | * - The file to load. 17 | * @return The loaded scene, or null if the scene could not be loaded. 18 | */ 19 | IAssimpScene loadScene(File file); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/IRenderResource.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import net.whg.we.util.IDisposable; 4 | 5 | /** 6 | * A render resource is any resource created or maintained by the rendering 7 | * engine, to interact with it. 8 | */ 9 | public interface IRenderResource extends IDisposable 10 | { 11 | /** 12 | * Checks if this resource has been created or not. A resource is created after 13 | * the first call to ant "update" mathod is made. Before creation, render 14 | * resources cannot be used. 15 | * 16 | * @return Whether or not this resource has been created. 17 | */ 18 | boolean isCreated(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/whg/lib/packets/HeartbeatPacketInitializer.java: -------------------------------------------------------------------------------- 1 | package net.whg.lib.packets; 2 | 3 | import java.io.DataInput; 4 | import java.io.IOException; 5 | import net.whg.we.net.IPacketSender; 6 | import net.whg.we.net.packets.IPacketInitializer; 7 | 8 | public class HeartbeatPacketInitializer implements IPacketInitializer 9 | { 10 | @Override 11 | public HeartbeatPacket loadPacket(DataInput input, IPacketSender sender) throws IOException 12 | { 13 | return new HeartbeatPacket(sender); 14 | } 15 | 16 | @Override 17 | public long getPacketID() 18 | { 19 | return HeartbeatPacket.PACKET_ID; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/opengl/GLException.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering.opengl; 2 | 3 | /** 4 | * A GL Exception represents an exception that occurs when handling OpenGL level 5 | * operations, such as compiling, binding, or destroying objects. 6 | */ 7 | public class GLException extends RuntimeException 8 | { 9 | private static final long serialVersionUID = 1213904820398L; 10 | 11 | /** 12 | * Creates a new GLException with the given message. 13 | * 14 | * @param message 15 | * - The message for the exception. 16 | */ 17 | public GLException(String message) 18 | { 19 | super(message); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature Request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/ITimeSupplier.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * The time supplier is a class which severs the purpose of retrieving the 5 | * system time in nanoseconds. Used for time-based utilities. 6 | */ 7 | public interface ITimeSupplier 8 | { 9 | /** 10 | * Gets the current system time in nano seconds. 11 | * 12 | * @return The time in nano seconds. 13 | */ 14 | long nanoTime(); 15 | 16 | /** 17 | * Causes the thread to sleep for a given period of time. 18 | * 19 | * @param ms 20 | * - The number of milliseconds to sleep. 21 | * @param ns 22 | * - The number of nanoseconds to sleep. 23 | */ 24 | void sleep(long ms, int ns); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/util/IDisposable.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.util; 2 | 3 | /** 4 | * A class which inherits this interface is able to be disposed and have it's 5 | * memory cleaned up to prevent memory leaks, despite a reference to the top 6 | * level object still being present. A disposable object cannot be used again 7 | * once disposed. 8 | */ 9 | public interface IDisposable 10 | { 11 | /** 12 | * Disposes this object. Does nothing if the object is already disposed. 13 | */ 14 | void dispose(); 15 | 16 | /** 17 | * Checks if this object has been disposed or not. 18 | * 19 | * @return True if this object has already been disposed. False otherwise. 20 | */ 21 | boolean isDisposed(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/Resource.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource; 2 | 3 | /** 4 | * A resource represents a set of information loaded from a file. This is loaded 5 | * in some way and contains a data object. 6 | */ 7 | public class Resource 8 | { 9 | private final Object data; 10 | 11 | /** 12 | * Creates a new resource object. 13 | * 14 | * @param data 15 | * - The data this resource represents. 16 | */ 17 | public Resource(Object data) 18 | { 19 | this.data = data; 20 | } 21 | 22 | /** 23 | * Gets the data object for this resource. 24 | * 25 | * @return The data. 26 | */ 27 | public Object getData() 28 | { 29 | return data; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/TimerAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * A timer action is a game loop action which updates a reliable timer. 5 | * 6 | * @see Timer 7 | */ 8 | public class TimerAction implements ILoopAction 9 | { 10 | private final Timer timer; 11 | 12 | /** 13 | * Creates a new timer action. 14 | * 15 | * @param timer 16 | * - The timer to update each frame. 17 | */ 18 | public TimerAction(Timer timer) 19 | { 20 | this.timer = timer; 21 | } 22 | 23 | @Override 24 | public void run() 25 | { 26 | timer.beginFrame(); 27 | } 28 | 29 | @Override 30 | public int getPriority() 31 | { 32 | return PipelineConstants.CALCULATE_TIMESTAMPS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/InputEndFrameAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | import net.whg.we.main.ILoopAction; 4 | import net.whg.we.main.PipelineConstants; 5 | 6 | /** 7 | * This action triggers the end-frame method for input objects near the end of 8 | * the loop to prepare the object for recieving input updates. 9 | */ 10 | public class InputEndFrameAction implements ILoopAction 11 | { 12 | private final Input input; 13 | 14 | public InputEndFrameAction(Input input) 15 | { 16 | this.input = input; 17 | } 18 | 19 | @Override 20 | public void run() 21 | { 22 | input.endFrame(); 23 | } 24 | 25 | @Override 26 | public int getPriority() 27 | { 28 | return PipelineConstants.ENDFRAME; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/server/IClientHandler.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.server; 2 | 3 | /** 4 | * Used as a bridge for handling network IO from the server side. 5 | */ 6 | public interface IClientHandler 7 | { 8 | /** 9 | * Called when a client connects to the server. 10 | * 11 | * @param client 12 | * - The client. 13 | */ 14 | void onClientConnected(IConnectedClient client); 15 | 16 | /** 17 | * Called when a client disconnects from the server. 18 | * 19 | * @param client 20 | * - The client. 21 | */ 22 | void onClientDisconnected(IConnectedClient client); 23 | 24 | /** 25 | * Read incoming data from the given client, triggering events as necessary. 26 | */ 27 | void readData(IConnectedClient client); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/packets/IBinaryPacket.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.packets; 2 | 3 | import java.io.DataOutput; 4 | import java.io.IOException; 5 | import net.whg.we.net.IPacket; 6 | 7 | /** 8 | * A binary encoded version of a packet. 9 | */ 10 | public interface IBinaryPacket extends IPacket 11 | { 12 | /** 13 | * Gets the unique packet type ID for this packet. All packets of this type 14 | * should return this same value. 15 | * 16 | * @return The packet type ID. 17 | */ 18 | long getPacketID(); 19 | 20 | /** 21 | * Writes this packet's contents to the data output. 22 | * 23 | * @param out 24 | * - The data output to write to. 25 | * @throws IOException 26 | * If an error occurs while writing to the data output. 27 | */ 28 | void write(DataOutput out) throws IOException; 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/TimerActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import org.junit.Test; 7 | import net.whg.we.main.PipelineConstants; 8 | import net.whg.we.main.Timer; 9 | import net.whg.we.main.TimerAction; 10 | 11 | public class TimerActionTest 12 | { 13 | @Test 14 | public void beginFrameOnRun() 15 | { 16 | Timer timer = mock(Timer.class); 17 | TimerAction action = new TimerAction(timer); 18 | 19 | action.run(); 20 | 21 | verify(timer).beginFrame(); 22 | } 23 | 24 | @Test 25 | public void priorityIs_Negative1000000() 26 | { 27 | assertEquals(PipelineConstants.CALCULATE_TIMESTAMPS, new TimerAction(mock(Timer.class)).getPriority()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/window/InputEndFrameActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.window; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import org.junit.Test; 7 | import net.whg.we.main.PipelineConstants; 8 | import net.whg.we.window.Input; 9 | import net.whg.we.window.InputEndFrameAction; 10 | 11 | public class InputEndFrameActionTest 12 | { 13 | @Test 14 | public void defaultPriority() 15 | { 16 | assertEquals(PipelineConstants.ENDFRAME, new InputEndFrameAction(mock(Input.class)).getPriority()); 17 | } 18 | 19 | @Test 20 | public void endFrame() 21 | { 22 | Input input = mock(Input.class); 23 | InputEndFrameAction action = new InputEndFrameAction(input); 24 | 25 | action.run(); 26 | 27 | verify(input).endFrame(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/window/PollEventsActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.window; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import org.junit.Test; 7 | import net.whg.we.main.PipelineConstants; 8 | import net.whg.we.main.PollEventsPipeline; 9 | import net.whg.we.window.IWindow; 10 | 11 | public class PollEventsActionTest 12 | { 13 | @Test 14 | public void defaultPriority() 15 | { 16 | assertEquals(PipelineConstants.POLL_WINDOW_EVENTS, new PollEventsPipeline(mock(IWindow.class)).getPriority()); 17 | } 18 | 19 | @Test 20 | public void pollEvents() 21 | { 22 | IWindow window = mock(IWindow.class); 23 | PollEventsPipeline action = new PollEventsPipeline(window); 24 | 25 | action.run(); 26 | 27 | verify(window).pollEvents(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/assimp/IAssimpScene.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource.assimp; 2 | 3 | import net.whg.we.util.IDisposable; 4 | 5 | /** 6 | * An assimp scene is a collection of resources which are loaded together in a 7 | * single file. 8 | */ 9 | public interface IAssimpScene extends IDisposable 10 | { 11 | /** 12 | * Counts the number of meshes in this scene. 13 | * 14 | * @return The number of meshes. 15 | */ 16 | int countMeshes(); 17 | 18 | /** 19 | * Gets the mesh at the specified index. This method creates a new mesh object 20 | * with each call. 21 | * 22 | * @param index 23 | * - The index of the mesh. 24 | * @return A new instance of the mesh. 25 | * @throws ArrayIndexOutOfBoundsException 26 | * If the index is less than 0, or greater than or equal to the mesh count. 27 | */ 28 | IAssimpMesh getMesh(int index); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/ConnectionData.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | /** 4 | * Contains details for an option socket connection. 5 | */ 6 | public class ConnectionData 7 | { 8 | private final String ip; 9 | private final int port; 10 | 11 | /** 12 | * Creates a new connection data object. 13 | * 14 | * @param ip 15 | * - The ip of the connected socket. 16 | * @param port 17 | * - The port of the connected socket. 18 | */ 19 | public ConnectionData(String ip, int port) 20 | { 21 | this.ip = ip; 22 | this.port = port; 23 | } 24 | 25 | /** 26 | * Gets the socket connection IP address. 27 | */ 28 | public String getIP() 29 | { 30 | return ip; 31 | } 32 | 33 | /** 34 | * Gets the port of the connected socket. 35 | */ 36 | public int getPort() 37 | { 38 | return port; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/external/TimeSupplierAPI.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.external; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import net.whg.we.main.ITimeSupplier; 6 | 7 | /** 8 | * The implementation of the timer supplier interface. 9 | */ 10 | public class TimeSupplierAPI implements ITimeSupplier 11 | { 12 | private static final Logger logger = LoggerFactory.getLogger(TimeSupplierAPI.class); 13 | 14 | @Override 15 | public long nanoTime() 16 | { 17 | return System.nanoTime(); 18 | } 19 | 20 | @Override 21 | public void sleep(long ms, int ns) 22 | { 23 | try 24 | { 25 | Thread.sleep(ms, ns); 26 | } 27 | catch (InterruptedException e) 28 | { 29 | logger.error("Sleep operation interrupted!", e); 30 | 31 | Thread.currentThread() 32 | .interrupt(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/ITexture.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * A texture represents any image file which exists on the GPU and is used 5 | * within materials in order to render them. 6 | */ 7 | public interface ITexture extends IRenderResource 8 | { 9 | /** 10 | * Binds this texture to the given texture slot. 11 | * 12 | * @param slot 13 | * - The texture slot to bind this texture to. 14 | * @throws IllegalArgumentException 15 | * If the slot is less than 0 or more than 23. 16 | */ 17 | void bind(int slot); 18 | 19 | /** 20 | * Creates or updates this texture to use the matching texture data. 21 | * 22 | * @param textureData 23 | * - The new texture data to replace this texture with. 24 | * @throws IllegalArgumentException 25 | * If the texture data is null. 26 | */ 27 | void update(TextureData textureData); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/external/LoggerAPI.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.external; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import net.whg.we.util.ILogger; 6 | 7 | /** 8 | * The implementation of the logger api. 9 | */ 10 | public class LoggerAPI implements ILogger 11 | { 12 | private static final Logger logger = LoggerFactory.getLogger(LoggerAPI.class); 13 | 14 | @Override 15 | public void trace(String msg) 16 | { 17 | logger.trace(msg); 18 | } 19 | 20 | @Override 21 | public void debug(String msg) 22 | { 23 | logger.debug(msg); 24 | } 25 | 26 | @Override 27 | public void info(String msg) 28 | { 29 | logger.info(msg); 30 | } 31 | 32 | @Override 33 | public void warn(String msg) 34 | { 35 | logger.warn(msg); 36 | } 37 | 38 | @Override 39 | public void error(String msg) 40 | { 41 | logger.error(msg); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/CullGameObjectsPipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | public class CullGameObjectsPipeline implements IPipelineAction 7 | { 8 | private final List gameObjects = new CopyOnWriteArrayList<>(); 9 | 10 | @Override 11 | public void run() 12 | { 13 | for (GameObject go : gameObjects) 14 | if (go.isMarkedForRemoval()) 15 | go.dispose(); 16 | } 17 | 18 | @Override 19 | public void enableGameObject(GameObject gameObject) 20 | { 21 | gameObjects.add(gameObject); 22 | } 23 | 24 | @Override 25 | public void disableGameObject(GameObject gameObject) 26 | { 27 | gameObjects.remove(gameObject); 28 | } 29 | 30 | @Override 31 | public int getPriority() 32 | { 33 | return PipelineConstants.DISPOSE_GAMEOBJECTS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/PollEventsPipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import net.whg.we.window.IWindow; 4 | 5 | /** 6 | * The poll events action is used to trigger a window to poll events and swap 7 | * the render buffer, which needs to be done at the end of every frame in order 8 | * to render the game to the screen. 9 | */ 10 | public class PollEventsPipeline implements ILoopAction 11 | { 12 | private final IWindow window; 13 | 14 | /** 15 | * Creates a new poll-events action. 16 | * 17 | * @param window 18 | * - The window to poll the events for each frame. 19 | */ 20 | public PollEventsPipeline(IWindow window) 21 | { 22 | this.window = window; 23 | } 24 | 25 | @Override 26 | public void run() 27 | { 28 | window.pollEvents(); 29 | } 30 | 31 | @Override 32 | public int getPriority() 33 | { 34 | return PipelineConstants.POLL_WINDOW_EVENTS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/whg/lib/actions/HandleServerPacketsAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.lib.actions; 2 | 3 | import net.whg.we.main.ILoopAction; 4 | import net.whg.we.main.PipelineConstants; 5 | import net.whg.we.net.server.IServer; 6 | 7 | /** 8 | * Calls the server to handle all currently received packets since the previous 9 | * frame. 10 | */ 11 | public class HandleServerPacketsAction implements ILoopAction 12 | { 13 | private final IServer server; 14 | 15 | /** 16 | * Creates a new action for the given server. 17 | * 18 | * @param server 19 | * - The server to handle. 20 | */ 21 | public HandleServerPacketsAction(IServer server) 22 | { 23 | this.server = server; 24 | } 25 | 26 | @Override 27 | public void run() 28 | { 29 | server.handlePackets(); 30 | } 31 | 32 | @Override 33 | public int getPriority() 34 | { 35 | return PipelineConstants.PHYSICS_UPDATES - 1000; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/ILoopAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * A loop action is used to determine how a game loop should operate. Loop 5 | * actions are attached to a game loop, and are called, in sequence, each frame 6 | * until the game loop is stopped. 7 | */ 8 | public interface ILoopAction 9 | { 10 | /** 11 | * Runs this loop action. This is called once per frame. 12 | */ 13 | void run(); 14 | 15 | /** 16 | * Gets the priority level of this loop action. This value should not change. 17 | * When a loop action is first added to a game loop, all loop actions are sorted 18 | * based on their priority level. Actions with a higher priority level are 19 | * called after actions with a lower priority level. 20 | *

21 | * The default priority level of an action is 0. 22 | * 23 | * @return The priority level of this action. 24 | */ 25 | default int getPriority() 26 | { 27 | return 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/ScreenClearPipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import net.whg.we.main.IPipelineAction; 4 | import net.whg.we.main.PipelineConstants; 5 | 6 | /** 7 | * The screen clear pipeline simply clears the screen at the begining of the 8 | * render phase of a frame. 9 | */ 10 | public class ScreenClearPipeline implements IPipelineAction 11 | { 12 | private final IScreenClearHandler screenClear; 13 | 14 | /** 15 | * Creates a new screen clear pipeline object. 16 | * 17 | * @param screenClear 18 | * - The screen clear handler to trigger each frame. 19 | */ 20 | public ScreenClearPipeline(IScreenClearHandler screenClear) 21 | { 22 | this.screenClear = screenClear; 23 | } 24 | 25 | @Override 26 | public void run() 27 | { 28 | screenClear.clearScreen(); 29 | } 30 | 31 | @Override 32 | public int getPriority() 33 | { 34 | return PipelineConstants.CLEAR_SCREEN; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/ScreenClearPipelineTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import org.junit.Test; 7 | import net.whg.we.main.PipelineConstants; 8 | import net.whg.we.rendering.IScreenClearHandler; 9 | import net.whg.we.rendering.ScreenClearPipeline; 10 | 11 | public class ScreenClearPipelineTest 12 | { 13 | @Test 14 | public void defaultPriority() 15 | { 16 | assertEquals(PipelineConstants.CLEAR_SCREEN, 17 | new ScreenClearPipeline(mock(IScreenClearHandler.class)).getPriority()); 18 | } 19 | 20 | @Test 21 | public void clearScreen() 22 | { 23 | IScreenClearHandler screenClear = mock(IScreenClearHandler.class); 24 | ScreenClearPipeline pipeline = new ScreenClearPipeline(screenClear); 25 | 26 | pipeline.run(); 27 | 28 | verify(screenClear).clearScreen(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: Bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/resource/TextureLoaderTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.resource; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import org.junit.Test; 8 | import net.whg.we.rendering.Color; 9 | import net.whg.we.rendering.TextureData; 10 | import net.whg.we.resource.TextureLoader; 11 | 12 | public class TextureLoaderTest 13 | { 14 | @Test 15 | public void loadTexture() throws IOException 16 | { 17 | TextureData data = TextureLoader.loadTexture(new File("src/test/resources/grass.png")); 18 | 19 | assertEquals(16, data.getWidth()); 20 | assertEquals(16, data.getHeight()); 21 | assertEquals(new Color(0.1882353f, 0.32156864f, 0.19215687f), data.getPixelAsColor(6, 8)); 22 | } 23 | 24 | @Test(expected = FileNotFoundException.class) 25 | public void loadTexture_fileNotFound() throws IOException 26 | { 27 | TextureLoader.loadTexture(new File("./not-a-real-file")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/IMesh.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * A mesh is a collection of vertices and indices which make up a triangle mesh 5 | * on the graphics card. A mesh is used to trigger a draw-call to the graphics 6 | * card to render the given mesh with the currently bound material. 7 | */ 8 | public interface IMesh extends IRenderResource 9 | { 10 | /** 11 | * This is used to call for the mesh to be rendered. If this mesh has not yet 12 | * been bound with any vertex data, this function does nothing. 13 | */ 14 | void render(); 15 | 16 | /** 17 | * Populates or updates this mesh to bind to the given vertex data. If this mesh 18 | * has not yet been created, this function creates the mesh with the given data. 19 | * If the mesh already exists, then this function updates the mesh to use the 20 | * new vertex data instead. 21 | * 22 | * @param vertexData 23 | * - The vertex data to update this mesh with. 24 | */ 25 | void update(VertexData vertexData); 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/unit/library/actions/HandleServerPacketsActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.library.actions; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import org.junit.Test; 7 | import net.whg.lib.actions.HandleServerPacketsAction; 8 | import net.whg.we.main.PipelineConstants; 9 | import net.whg.we.net.server.IServer; 10 | 11 | public class HandleServerPacketsActionTest 12 | { 13 | @Test 14 | public void handlePackets() 15 | { 16 | var server = mock(IServer.class); 17 | var handler = new HandleServerPacketsAction(server); 18 | 19 | handler.run(); 20 | 21 | verify(server).handlePackets(); 22 | } 23 | 24 | @Test 25 | public void defaultPriority() 26 | { 27 | var server = mock(IServer.class); 28 | var handler = new HandleServerPacketsAction(server); 29 | 30 | assertTrue(handler.getPriority() < PipelineConstants.PHYSICS_UPDATES); 31 | assertTrue(handler.getPriority() > PipelineConstants.CALCULATE_TIMESTAMPS); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/server/IConnectedClient.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.server; 2 | 3 | import java.io.IOException; 4 | import net.whg.we.net.ConnectionData; 5 | import net.whg.we.net.IPacket; 6 | import net.whg.we.net.IPacketSender; 7 | 8 | /** 9 | * Represents a connected client socket within a server. 10 | */ 11 | public interface IConnectedClient extends IPacketSender 12 | { 13 | /** 14 | * Gets details about the client connection. 15 | * 16 | * @return The connection details. 17 | */ 18 | ConnectionData getConnection(); 19 | 20 | /** 21 | * Kicks this client from the server. 22 | * 23 | * @throws IOException 24 | * If an exception occurs while preforming this action. 25 | */ 26 | void kick() throws IOException; 27 | 28 | /** 29 | * Called from the client thread to retrieve the next packet which was received 30 | * by this client. 31 | * 32 | * @return The next packet. 33 | * @throws IOException 34 | * If an exception occurs while preforming this action. 35 | */ 36 | IPacket readPacket() throws IOException; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/IWindowAdapter.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | public abstract class IWindowAdapter implements IWindowListener 4 | { 5 | @Override 6 | public void onKeyPressed(IWindow window, int keyCode) 7 | {} 8 | 9 | @Override 10 | public void onKeyReleased(IWindow window, int keyCode) 11 | {} 12 | 13 | @Override 14 | public void onMouseMove(IWindow window, float newX, float newY) 15 | {} 16 | 17 | @Override 18 | public void onMousePressed(IWindow window, int mouseButton) 19 | {} 20 | 21 | @Override 22 | public void onMouseReleased(IWindow window, int mouseButton) 23 | {} 24 | 25 | @Override 26 | public void onMouseWheel(IWindow window, float scrollX, float scrollY) 27 | {} 28 | 29 | @Override 30 | public void onWindowDestroyed(IWindow window) 31 | {} 32 | 33 | @Override 34 | public void onWindowRequestClose(IWindow window) 35 | {} 36 | 37 | @Override 38 | public void onWindowResized(IWindow window, int width, int height) 39 | {} 40 | 41 | @Override 42 | public void onWindowUpdated(IWindow window) 43 | {} 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/IDataHandler.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | import java.io.DataInput; 4 | import java.io.DataOutput; 5 | import java.io.IOException; 6 | 7 | /** 8 | * Used to buffer an incoming data stream to convert it to a usable packet. 9 | */ 10 | public interface IDataHandler 11 | { 12 | /** 13 | * Reads the input stream and converts the incoming data as a packet. 14 | * 15 | * @param stream 16 | * - The input stream to read from. 17 | * @param sender 18 | * - The packet sender managing this input stream. 19 | * @return The packet. 20 | * @throws IOException 21 | * If an exception occurs while preforming this action. 22 | */ 23 | IPacket readPacket(DataInput stream, IPacketSender sender) throws IOException; 24 | 25 | /** 26 | * Writes a packet to the output stream. 27 | * 28 | * @param stream 29 | * - The output stream to write to. 30 | * @param packet 31 | * - The packet to write. 32 | * @throws IOException 33 | * If an exception occurs while preforming this action. 34 | */ 35 | void writePacket(DataOutput stream, IPacket packet) throws IOException; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/util/ILogger.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.util; 2 | 3 | /** 4 | * This interface seeks to add a layer of abstraction between the output stream 5 | * logic and the logger, to make it more reusable and more testable. 6 | */ 7 | public interface ILogger 8 | { 9 | /** 10 | * Logs a message using the trace logging level. 11 | * 12 | * @param msg 13 | * - The message to log. 14 | */ 15 | void trace(String msg); 16 | 17 | /** 18 | * Logs a message using the debug logging level. 19 | * 20 | * @param msg 21 | * - The message to log. 22 | */ 23 | void debug(String msg); 24 | 25 | /** 26 | * Logs a message using the info logging level. 27 | * 28 | * @param msg 29 | * - The message to log. 30 | */ 31 | void info(String msg); 32 | 33 | /** 34 | * Logs a message using the warn logging level. 35 | * 36 | * @param msg 37 | * - The message to log. 38 | */ 39 | void warn(String msg); 40 | 41 | /** 42 | * Logs a message using the error logging level. 43 | * 44 | * @param msg 45 | * - The message to log. 46 | */ 47 | void error(String msg); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/packets/PacketDataHandler.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.packets; 2 | 3 | import java.io.DataInput; 4 | import java.io.DataOutput; 5 | import java.io.IOException; 6 | import net.whg.we.net.IDataHandler; 7 | import net.whg.we.net.IPacket; 8 | import net.whg.we.net.IPacketSender; 9 | 10 | /** 11 | * A simple data handler for reading and writing binary packets. 12 | */ 13 | public class PacketDataHandler implements IDataHandler 14 | { 15 | private final PacketFactory factory; 16 | 17 | /** 18 | * Creates a new packet data handler. 19 | * 20 | * @param factory 21 | * - The packet factory to use when receiving new packets. 22 | */ 23 | public PacketDataHandler(PacketFactory factory) 24 | { 25 | this.factory = factory; 26 | } 27 | 28 | @Override 29 | public IBinaryPacket readPacket(DataInput stream, IPacketSender sender) throws IOException 30 | { 31 | return factory.getPacket(stream, sender); 32 | } 33 | 34 | @Override 35 | public void writePacket(DataOutput stream, IPacket packet) throws IOException 36 | { 37 | var binary = (IBinaryPacket) packet; 38 | 39 | stream.writeLong(binary.getPacketID()); 40 | binary.write(stream); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Demos/res/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.81 (sub 16) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 1.000000 -1.000000 6 | v 1.000000 -1.000000 -1.000000 7 | v 1.000000 1.000000 1.000000 8 | v 1.000000 -1.000000 1.000000 9 | v -1.000000 1.000000 -1.000000 10 | v -1.000000 -1.000000 -1.000000 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 -1.000000 1.000000 13 | vt 0.000000 0.000000 14 | vt 1.000000 0.000000 15 | vt 1.000000 1.000000 16 | vt 0.000000 1.000000 17 | vt 0.000000 0.000000 18 | vt 1.000000 0.000000 19 | vt 0.000000 1.000000 20 | vt 0.000000 0.000000 21 | vt 1.000000 0.000000 22 | vt 1.000000 1.000000 23 | vt 0.000000 1.000000 24 | vt 0.000000 0.000000 25 | vt 1.000000 0.000000 26 | vt 1.000000 1.000000 27 | vt 0.000000 0.000000 28 | vt 1.000000 0.000000 29 | vt 1.000000 1.000000 30 | vt 0.000000 1.000000 31 | vt 1.000000 1.000000 32 | vt 0.000000 1.000000 33 | vn 0.0000 1.0000 0.0000 34 | vn 0.0000 0.0000 1.0000 35 | vn -1.0000 0.0000 0.0000 36 | vn 0.0000 -1.0000 0.0000 37 | vn 1.0000 0.0000 0.0000 38 | vn 0.0000 0.0000 -1.0000 39 | usemtl Material 40 | s off 41 | f 1/1/1 5/2/1 7/3/1 3/4/1 42 | f 4/5/2 3/6/2 7/3/2 8/7/2 43 | f 8/8/3 7/9/3 5/10/3 6/11/3 44 | f 6/12/4 2/13/4 4/14/4 8/7/4 45 | f 2/15/5 1/16/5 3/17/5 4/18/5 46 | f 6/12/6 5/2/6 1/19/6 2/20/6 47 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/CullGameObjectsActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import org.junit.Test; 6 | import net.whg.we.main.CullGameObjectsPipeline; 7 | import net.whg.we.main.GameObject; 8 | import net.whg.we.main.PipelineConstants; 9 | import net.whg.we.main.Scene; 10 | 11 | public class CullGameObjectsActionTest 12 | { 13 | @Test 14 | public void runAction() 15 | { 16 | GameObject go1 = new GameObject(); 17 | GameObject go2 = new GameObject(); 18 | GameObject go3 = new GameObject(); 19 | 20 | Scene scene = new Scene(); 21 | scene.addGameObject(go1); 22 | scene.addGameObject(go2); 23 | scene.addGameObject(go3); 24 | go2.markForRemoval(); 25 | 26 | CullGameObjectsPipeline action = new CullGameObjectsPipeline(); 27 | scene.addPipelineAction(action); 28 | 29 | action.run(); 30 | 31 | assertFalse(scene.gameObjects() 32 | .contains(go2)); 33 | } 34 | 35 | @Test 36 | public void defaultPriority() 37 | { 38 | assertEquals(PipelineConstants.DISPOSE_GAMEOBJECTS, new CullGameObjectsPipeline().getPriority()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/external/ServerSocketAPI.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.external; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | import net.whg.we.net.IServerSocket; 6 | import net.whg.we.net.ISocket; 7 | 8 | public class ServerSocketAPI implements IServerSocket 9 | { 10 | private ServerSocket socket; 11 | 12 | @Override 13 | public void close() throws IOException 14 | { 15 | if (!isClosed()) 16 | socket.close(); 17 | } 18 | 19 | @Override 20 | public void start(int port) throws IOException 21 | { 22 | if (!isClosed()) 23 | throw new IOException("Server already open!"); 24 | 25 | socket = new ServerSocket(port); 26 | } 27 | 28 | @Override 29 | public ISocket waitForClient() throws IOException 30 | { 31 | if (isClosed()) 32 | throw new IOException("Server socket not open!"); 33 | 34 | return new SocketAPI(socket.accept()); 35 | } 36 | 37 | @Override 38 | public boolean isClosed() 39 | { 40 | return socket == null || socket.isClosed(); 41 | } 42 | 43 | @Override 44 | public int getLocalPort() 45 | { 46 | if (isClosed()) 47 | throw new IllegalStateException(); 48 | 49 | return socket.getLocalPort(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/packets/IPacketInitializer.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.packets; 2 | 3 | import java.io.DataInput; 4 | import java.io.IOException; 5 | import net.whg.we.net.IPacketSender; 6 | 7 | /** 8 | * Given to a packet factory in order to create new instances of a packet as 9 | * needed. 10 | * 11 | * @param 12 | * - The type of packet being created. 13 | */ 14 | public interface IPacketInitializer 15 | { 16 | /** 17 | * Creates a new packet from the given input stream. This method can be called 18 | * from multiple threads at once. 19 | * 20 | * @param input 21 | * - The data input to read from. 22 | * @param sender 23 | * - The sender we received this packet from. 24 | * @return The new packet object. 25 | * @throws IOException 26 | * If an error occurs while preforming this action. 27 | */ 28 | T loadPacket(DataInput input, IPacketSender sender) throws IOException; 29 | 30 | /** 31 | * Gets the unique packet identifier for the packet type this initializer 32 | * generates. This value should match the packet ID returned by 33 | * {@link IBinaryPacket#getPacketID()} of all packets generated by this 34 | * initializer. 35 | * 36 | * @return The packet type ID. 37 | */ 38 | long getPacketID(); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/whg/lib/actions/FramerateLimiterAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.lib.actions; 2 | 3 | import net.whg.we.main.ILoopAction; 4 | import net.whg.we.main.Timer; 5 | import net.whg.we.main.PipelineConstants; 6 | 7 | /** 8 | * An action which can be used to limit the framerate of the application to 9 | * prevent it from exceeding a target framerate. 10 | */ 11 | public class FramerateLimiterAction implements ILoopAction 12 | { 13 | private final Timer timer; 14 | private final double targetFPS; 15 | private double smoothing; 16 | 17 | /** 18 | * Creates a new framerate limit action. 19 | * 20 | * @param timer 21 | * - The timer backing this action. 22 | * @param targetFPS 23 | * - The framerate cap. 24 | */ 25 | public FramerateLimiterAction(Timer timer, float targetFPS) 26 | { 27 | this.timer = timer; 28 | this.targetFPS = targetFPS + 0.05; 29 | } 30 | 31 | @Override 32 | public void run() 33 | { 34 | double delta = timer.getDeltaTime(); 35 | double toWait = (smoothing + delta) / 2; 36 | smoothing = delta; 37 | 38 | var seconds = (float) ((2 / targetFPS) - toWait); 39 | timer.sleep(seconds); 40 | } 41 | 42 | @Override 43 | public int getPriority() 44 | { 45 | return PipelineConstants.FRAMERATE_LIMITER; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/IServerSocket.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | /** 7 | * A testable wrapper for the Java server socket. 8 | */ 9 | public interface IServerSocket extends Closeable 10 | { 11 | /** 12 | * Opens this server socket on the given port. 13 | * 14 | * @param port 15 | * - The port to open the server on, or 0 to let the system chose. 16 | * @throws IOException 17 | * If an error occurs while preforming this action. 18 | */ 19 | void start(int port) throws IOException; 20 | 21 | /** 22 | * Blocks this thread while waiting for a new client to connect. 23 | * 24 | * @return The newly connected client. 25 | * @throws IOException 26 | * If an error occurs while preforming this action. 27 | */ 28 | ISocket waitForClient() throws IOException; 29 | 30 | /** 31 | * Checks whether or not this server socket has been closed. 32 | * 33 | * @return True if the socket is closed or hasn't been opened. False otherwise. 34 | */ 35 | boolean isClosed(); 36 | 37 | /** 38 | * Gets the local port this socket was started on. 39 | * 40 | * @return The local port. 41 | * @throws IllegalStateException 42 | * If the server socket is closed. 43 | */ 44 | int getLocalPort(); 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/window/FakeWindow.java: -------------------------------------------------------------------------------- 1 | package unit.engine.window; 2 | 3 | import net.whg.we.rendering.IRenderingEngine; 4 | import net.whg.we.window.IWindow; 5 | import net.whg.we.window.IWindowListener; 6 | import net.whg.we.window.WindowSettings; 7 | 8 | public class FakeWindow implements IWindow 9 | { 10 | public IWindowListener listener; 11 | public WindowSettings settings = new WindowSettings(); 12 | 13 | @Override 14 | public void dispose() 15 | {} 16 | 17 | @Override 18 | public boolean isDisposed() 19 | { 20 | return false; 21 | } 22 | 23 | @Override 24 | public void setProperties(WindowSettings settings) 25 | {} 26 | 27 | @Override 28 | public WindowSettings getProperties() 29 | { 30 | return settings; 31 | } 32 | 33 | @Override 34 | public IRenderingEngine getRenderingEngine() 35 | { 36 | return null; 37 | } 38 | 39 | @Override 40 | public void addWindowListener(IWindowListener listener) 41 | { 42 | this.listener = listener; 43 | } 44 | 45 | @Override 46 | public void removeWindowListener(IWindowListener listener) 47 | { 48 | this.listener = null; 49 | } 50 | 51 | @Override 52 | public void pollEvents() 53 | {} 54 | 55 | @Override 56 | public long getWindowId() 57 | { 58 | return 1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/whg/lib/packets/HeartbeatPacket.java: -------------------------------------------------------------------------------- 1 | package net.whg.lib.packets; 2 | 3 | import java.io.DataOutput; 4 | import net.whg.we.net.IPacketSender; 5 | import net.whg.we.net.packets.IBinaryPacket; 6 | 7 | /** 8 | * Sent periodically over the network to indicate the connection is still 9 | * active. 10 | */ 11 | public class HeartbeatPacket implements IBinaryPacket 12 | { 13 | public static final long PACKET_ID = 8716237733363712L; 14 | 15 | private final IPacketSender sender; 16 | 17 | /** 18 | * Creates a new heartbeat packet to be sent. 19 | */ 20 | public HeartbeatPacket() 21 | { 22 | this(null); 23 | } 24 | 25 | /** 26 | * Creates a new received heartbeat packet from the given sender. 27 | * 28 | * @param sender 29 | * - The packet sender. 30 | */ 31 | HeartbeatPacket(IPacketSender sender) 32 | { 33 | this.sender = sender; 34 | } 35 | 36 | @Override 37 | public void handle() 38 | { 39 | // TODO Mark sender as still active. 40 | } 41 | 42 | @Override 43 | public long getPacketID() 44 | { 45 | return PACKET_ID; 46 | } 47 | 48 | @Override 49 | public void write(DataOutput out) 50 | { 51 | // Contains no data. 52 | } 53 | 54 | @Override 55 | public IPacketSender getSender() 56 | { 57 | return sender; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/ISceneListener.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * A scene listener can be used to listen for common events which are triggered 5 | * by a scene. 6 | */ 7 | public interface ISceneListener 8 | { 9 | /** 10 | * Called when a game object is added to a scene. This event is called after the 11 | * game object has been added. 12 | * 13 | * @param go 14 | * - The game object which was added. 15 | */ 16 | void onGameObjectAdded(GameObject go); 17 | 18 | /** 19 | * Called when a game object has been removed from a scene. This event is called 20 | * after the game object has been removed. 21 | * 22 | * @param go 23 | * - The game object which was removed. 24 | */ 25 | void onGameObjectRemoved(GameObject go); 26 | 27 | /** 28 | * Called when a pipeline action has been added to the scene. This event is 29 | * called after the pipeline has been added. 30 | * 31 | * @param action 32 | * - The pipeline action which was added. 33 | */ 34 | void onPipelineAdded(IPipelineAction action); 35 | 36 | /** 37 | * Called when a pipeline action has been removed from the scene. This event is 38 | * called after the pipeline has been removed. 39 | * 40 | * @param action 41 | * - The pipeline action which was removed. 42 | */ 43 | void onPipelineRemoved(IPipelineAction action); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/UpdatePipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | /** 7 | * The update pipeline is triggered each frame to prepare a scene to be 8 | * rendered, or run logic which needs to be executed every frame. 9 | */ 10 | public class UpdatePipeline implements IPipelineAction 11 | { 12 | private final List objects = new CopyOnWriteArrayList<>(); 13 | private final Timer timer; 14 | 15 | /** 16 | * Creates a new update pipeline object. 17 | * 18 | * @param timer 19 | * - The timer associated with this update pipeline. 20 | */ 21 | public UpdatePipeline(Timer timer) 22 | { 23 | this.timer = timer; 24 | } 25 | 26 | @Override 27 | public void run() 28 | { 29 | for (IUpdatable obj : objects) 30 | obj.update(timer); 31 | } 32 | 33 | @Override 34 | public void enableBehavior(AbstractBehavior behavior) 35 | { 36 | if (behavior instanceof IUpdatable) 37 | objects.add((IUpdatable) behavior); 38 | } 39 | 40 | @Override 41 | public void disableBehavior(AbstractBehavior behavior) 42 | { 43 | if (behavior instanceof IUpdatable) 44 | objects.remove((IUpdatable) behavior); 45 | } 46 | 47 | @Override 48 | public int getPriority() 49 | { 50 | return PipelineConstants.FRAME_UPDATES; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/GameLoopTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.mock; 6 | import org.junit.Test; 7 | import net.whg.we.main.GameLoop; 8 | import net.whg.we.main.ILoopAction; 9 | 10 | public class GameLoopTest 11 | { 12 | @Test 13 | public void addAction() 14 | { 15 | ILoopAction action = mock(ILoopAction.class); 16 | 17 | GameLoop gameLoop = new GameLoop(); 18 | gameLoop.addAction(action); // Original 19 | gameLoop.addAction(action); // Duplicate 20 | gameLoop.addAction(null); // Null 21 | 22 | assertEquals(1, gameLoop.loopActions() 23 | .size()); 24 | 25 | gameLoop.removeAction(null); 26 | assertEquals(1, gameLoop.loopActions() 27 | .size()); 28 | 29 | gameLoop.removeAction(action); 30 | assertEquals(0, gameLoop.loopActions() 31 | .size()); 32 | } 33 | 34 | @Test(timeout = 1000) 35 | public void loop_stop() 36 | { 37 | GameLoop gameLoop = new GameLoop(); 38 | 39 | int[] i = new int[] {10}; 40 | gameLoop.addAction(() -> 41 | { 42 | i[0]--; 43 | if (i[0] == 0) 44 | gameLoop.stop(); 45 | }); 46 | 47 | gameLoop.loop(); 48 | assertTrue(i[0] == 0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/RawShaderCode.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * This class contains a set of code which is intended to be sent to the GPU as 5 | * a shader but has not yet been compiled. 6 | */ 7 | public class RawShaderCode 8 | { 9 | private final String vertexShader; 10 | private final String fragmentShader; 11 | 12 | /** 13 | * Creates a new raw shader code object. 14 | * 15 | * @param vertexShader 16 | * - The vertex shader code. 17 | * @param fragmentShader 18 | * - The fragment shader code. 19 | */ 20 | public RawShaderCode(String vertexShader, String fragmentShader) 21 | { 22 | if (vertexShader == null) 23 | throw new IllegalArgumentException("Vertex Shader cannot be null!"); 24 | 25 | if (fragmentShader == null) 26 | throw new IllegalArgumentException("Fragment Shader cannot be null!"); 27 | 28 | this.vertexShader = vertexShader; 29 | this.fragmentShader = fragmentShader; 30 | } 31 | 32 | /** 33 | * Gets the code for the vertex shader. 34 | * 35 | * @return The vertex shader. 36 | */ 37 | public String getVertexShader() 38 | { 39 | return vertexShader; 40 | } 41 | 42 | /** 43 | * Gets the code for the fragment shader. 44 | * 45 | * @return The fragment shader. 46 | */ 47 | public String getFragmentShader() 48 | { 49 | return fragmentShader; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Demos/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 4.0.0 6 | net.whg 7 | wraithengine-demos 8 | dev_build 9 | A collection of demo projects built using WraithEngine. 10 | 11 | 12 | 13 13 | 13 14 | UTF-8 15 | 16 | 17 | 18 | 19 | net.whg 20 | wraithengine 21 | ${project.version} 22 | 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-install-plugin 30 | 3.0.0-M1 31 | 32 | ${project.basedir}/../target/wraithengine-${project.version}.jar 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/WindowReceiver.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | import net.whg.we.util.IDisposable; 4 | 5 | /** 6 | * A simple handler for allowing a window receiver to connect to a window 7 | * seamlessly and listen for all window events as they occur. 8 | */ 9 | public abstract class WindowReceiver implements IDisposable 10 | { 11 | private IWindow window; 12 | private IWindowListener listener; 13 | private boolean disposed = true; 14 | 15 | /** 16 | * Marks this receiver to listen to the given window. If this object is 17 | * currently disposed, it will be reset. If it is already listening to another 18 | * window, it will be removed from that window. 19 | * 20 | * @param window 21 | * - The window to listen to. 22 | * @param listener 23 | * - The listener for accepting events. 24 | */ 25 | protected void listenTo(IWindow window, IWindowListener listener) 26 | { 27 | if (!isDisposed()) 28 | dispose(); 29 | 30 | this.window = window; 31 | this.listener = listener; 32 | disposed = false; 33 | 34 | window.addWindowListener(listener); 35 | } 36 | 37 | @Override 38 | public void dispose() 39 | { 40 | if (isDisposed()) 41 | return; 42 | 43 | disposed = true; 44 | window.removeWindowListener(listener); 45 | window = null; 46 | } 47 | 48 | @Override 49 | public boolean isDisposed() 50 | { 51 | return disposed; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/opengl/GLScreenClear.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering.opengl; 2 | 3 | import net.whg.we.rendering.IScreenClearHandler; 4 | import net.whg.we.rendering.Color; 5 | 6 | public class GLScreenClear implements IScreenClearHandler 7 | { 8 | private final IOpenGL opengl; 9 | private Color clearColor; 10 | private boolean clearDepth; 11 | 12 | /** 13 | * Creates a new GLScreenClear object. 14 | * 15 | * @param opengl 16 | * - The OpenGL instance to send screen clear requests to. 17 | */ 18 | GLScreenClear(IOpenGL opengl) 19 | { 20 | this.opengl = opengl; 21 | } 22 | 23 | @Override 24 | public void clearScreen() 25 | { 26 | if (clearColor == null && !clearDepth) 27 | return; 28 | 29 | opengl.clearScreen(clearColor != null, clearDepth); 30 | } 31 | 32 | @Override 33 | public void setClearColor(Color clearColor) 34 | { 35 | this.clearColor = clearColor; 36 | 37 | if (clearColor != null) 38 | opengl.setClearColor(clearColor.getRed(), clearColor.getGreen(), clearColor.getBlue(), 39 | clearColor.getAlpha()); 40 | } 41 | 42 | @Override 43 | public void setClearDepth(boolean clearDepth) 44 | { 45 | this.clearDepth = clearDepth; 46 | } 47 | 48 | @Override 49 | public Color getClearColor() 50 | { 51 | return clearColor; 52 | } 53 | 54 | @Override 55 | public boolean getClearDepth() 56 | { 57 | return clearDepth; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/net/packets/PacketDataHandlerTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.net.packets; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.verify; 6 | import static org.mockito.Mockito.when; 7 | import java.io.DataInput; 8 | import java.io.DataOutput; 9 | import java.io.IOException; 10 | import org.junit.Test; 11 | import net.whg.we.net.packets.IBinaryPacket; 12 | import net.whg.we.net.packets.PacketDataHandler; 13 | import net.whg.we.net.packets.PacketFactory; 14 | 15 | public class PacketDataHandlerTest 16 | { 17 | @Test 18 | public void readPacket() throws IOException 19 | { 20 | var packet = mock(IBinaryPacket.class); 21 | var factory = mock(PacketFactory.class); 22 | var stream = mock(DataInput.class); 23 | 24 | when(factory.getPacket(stream, null)).thenReturn(packet); 25 | 26 | var handler = new PacketDataHandler(factory); 27 | assertEquals(packet, handler.readPacket(stream, null)); 28 | } 29 | 30 | @Test 31 | public void writePacket() throws IOException 32 | { 33 | var packetID = 120938L; 34 | var packet = mock(IBinaryPacket.class); 35 | when(packet.getPacketID()).thenReturn(packetID); 36 | 37 | var factory = mock(PacketFactory.class); 38 | var stream = mock(DataOutput.class); 39 | 40 | var handler = new PacketDataHandler(factory); 41 | handler.writePacket(stream, packet); 42 | 43 | verify(packet).write(stream); 44 | verify(stream).writeLong(packetID); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/util/TupleTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.util; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import java.util.ArrayList; 5 | import org.junit.Test; 6 | import net.whg.we.util.Tuple; 7 | 8 | public class TupleTest 9 | { 10 | @Test 11 | public void tuple2() 12 | { 13 | var a = "Hello"; 14 | var b = 1234; 15 | 16 | var t = Tuple.of(a, b); 17 | 18 | assertEquals(a, t.a); 19 | assertEquals(b, (int) t.b); 20 | } 21 | 22 | @Test 23 | public void tuple3() 24 | { 25 | var a = true; 26 | var b = "meh"; 27 | var c = new Object(); 28 | 29 | var t = Tuple.of(a, b, c); 30 | 31 | assertEquals(a, t.a); 32 | assertEquals(b, t.b); 33 | assertEquals(c, t.c); 34 | } 35 | 36 | @Test 37 | public void tuple4() 38 | { 39 | var a = '3'; 40 | var b = new ArrayList(); 41 | var c = "27"; 42 | var d = 564.15; 43 | 44 | var t = Tuple.of(a, b, c, d); 45 | 46 | assertEquals(a, (char) t.a); 47 | assertEquals(b, t.b); 48 | assertEquals(c, t.c); 49 | assertEquals(d, (double) t.d, 0); 50 | } 51 | 52 | @Test 53 | public void tuple5() 54 | { 55 | var a = -234f; 56 | var b = 71; 57 | var c = false; 58 | var d = '\n'; 59 | var e = "watermark"; 60 | 61 | var t = Tuple.of(a, b, c, d, e); 62 | 63 | assertEquals(a, (float) t.a, 0); 64 | assertEquals(b, (int) t.b); 65 | assertEquals(c, t.c); 66 | assertEquals(d, (char) t.d); 67 | assertEquals(e, t.e); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/IScreenClearHandler.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * A screen clear handler is a rendering-specific operation which allows for the 5 | * screen to be cleared using a specific set of parameters and configurations. 6 | */ 7 | public interface IScreenClearHandler 8 | { 9 | /** 10 | * Clears the screen for rendering. This is usually called at the begining of 11 | * the frame. This function uses the given configuration for the color and depth 12 | * clearing options. 13 | */ 14 | void clearScreen(); 15 | 16 | /** 17 | * Sets what color should be used to clear the screen with. 18 | *

19 | * Default value is the color black. 20 | * 21 | * @param color 22 | * - The color to clear the screen with, or null to disable clearing the 23 | * color channel of the screen. 24 | */ 25 | void setClearColor(Color color); 26 | 27 | /** 28 | * Sets whether or not depth should be cleared. 29 | *

30 | * Default value is true. 31 | * 32 | * @param clearDepth 33 | * - Whether or not depth should be cleared. 34 | */ 35 | void setClearDepth(boolean clearDepth); 36 | 37 | /** 38 | * Gets the current color used for clearing the screen. 39 | * 40 | * @return The screen clear color, or null if clearing the color channel is 41 | * disabled. 42 | */ 43 | Color getClearColor(); 44 | 45 | /** 46 | * Gets whether or not clearing the depth channel is enabled or not. 47 | * 48 | * @return True if the screen currently clears depth, false otherwise. 49 | */ 50 | boolean getClearDepth(); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/TextureLoader.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import javax.imageio.ImageIO; 8 | import net.whg.we.rendering.TextureData; 9 | 10 | public final class TextureLoader 11 | { 12 | private TextureLoader() 13 | {} 14 | 15 | /** 16 | * Loads a texture from a file image file. The texture is loaded with default 17 | * settings applied. This operation supports the image formats of: 18 | *

    19 | *
  • JPEG
  • 20 | *
  • PNG
  • 21 | *
  • BMP
  • 22 | *
  • WBMP
  • 23 | *
  • GIF
  • 24 | *
25 | * For GIF file formats, only the first frame is sampled. 26 | * 27 | * @param file 28 | * - The image file to load. 29 | * @return The loaded texture data file. 30 | * @throws FileNotFoundException 31 | * If the file cannot be found. 32 | * @throws IOException 33 | * If the file cannot be read. 34 | */ 35 | public static TextureData loadTexture(File file) throws IOException 36 | { 37 | if (!file.exists()) 38 | throw new FileNotFoundException(file.getAbsolutePath()); 39 | 40 | BufferedImage image = ImageIO.read(file); 41 | 42 | int width = image.getWidth(); 43 | int height = image.getHeight(); 44 | 45 | TextureData data = new TextureData(width, height); 46 | 47 | for (int x = 0; x < width; x++) 48 | for (int y = 0; y < height; y++) 49 | data.setPixel(x, y, image.getRGB(x, y)); 50 | 51 | return data; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/IRenderingEngine.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import net.whg.we.util.IDisposable; 4 | 5 | /** 6 | * The rendering engine object is used to draw to the window, and is the heart 7 | * of all visuals within the engine. 8 | */ 9 | public interface IRenderingEngine extends IDisposable 10 | { 11 | /** 12 | * Initializes this rendering engine. 13 | */ 14 | void init(); 15 | 16 | /** 17 | * Gets the handler in charge of clearing the screen. 18 | * 19 | * @return The handler. 20 | */ 21 | IScreenClearHandler getScreenClearHandler(); 22 | 23 | /** 24 | * This function is used to create a new empty mesh object. 25 | * 26 | * @return A new empty mesh. 27 | */ 28 | IMesh createMesh(); 29 | 30 | /** 31 | * Creates a new shader object which can be compiled to render materials to the 32 | * screen. 33 | * 34 | * @return A new shader object. 35 | */ 36 | IShader createShader(); 37 | 38 | /** 39 | * Sets the depth testing state to use when rendering objects. 40 | * 41 | * @param depthTesting 42 | * - Whether or not depth testing should be used. 43 | */ 44 | void setDepthTesting(boolean depthTesting); 45 | 46 | /** 47 | * Sets the culling mode to use when rendering triangles. 48 | * 49 | * @param cullingMode 50 | * - The new culling mode. 51 | */ 52 | void setCullingMode(CullingMode cullingMode); 53 | 54 | /** 55 | * Creates a new texture object which can have pixel data uploaded to it. 56 | * 57 | * @return A new texture object. 58 | */ 59 | ITexture createTexture(); 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/UpdatePipelineActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.mock; 6 | import org.junit.Test; 7 | import net.whg.we.main.AbstractBehavior; 8 | import net.whg.we.main.IUpdatable; 9 | import net.whg.we.main.PipelineConstants; 10 | import net.whg.we.main.Timer; 11 | import net.whg.we.main.UpdatePipeline; 12 | 13 | public class UpdatePipelineActionTest 14 | { 15 | @Test 16 | public void ensurePipelinePriority() 17 | { 18 | assertEquals(PipelineConstants.FRAME_UPDATES, new UpdatePipeline(mock(Timer.class)).getPriority()); 19 | } 20 | 21 | @Test 22 | public void updateBehaviors() 23 | { 24 | Timer timer = mock(Timer.class); 25 | UpdatePipeline action = new UpdatePipeline(timer); 26 | action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur 27 | 28 | UpdatableAction behavior = new UpdatableAction(); 29 | assertEquals(0, behavior.calls); 30 | 31 | action.enableBehavior(behavior); 32 | action.run(); 33 | assertEquals(1, behavior.calls); 34 | assertTrue(timer == behavior.timer); 35 | 36 | action.disableBehavior(behavior); 37 | action.run(); 38 | assertEquals(1, behavior.calls); 39 | } 40 | 41 | class UpdatableAction extends AbstractBehavior implements IUpdatable 42 | { 43 | Timer timer; 44 | int calls = 0; 45 | 46 | @Override 47 | public void update(Timer timer) 48 | { 49 | this.timer = timer; 50 | calls++; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/window/WindowCloseHandlerTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.window; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.times; 5 | import static org.mockito.Mockito.verify; 6 | import static org.mockito.Mockito.any; 7 | import static org.mockito.Mockito.doAnswer; 8 | import org.junit.Test; 9 | import net.whg.we.main.GameLoop; 10 | import net.whg.we.window.IWindow; 11 | import net.whg.we.window.IWindowListener; 12 | import net.whg.we.window.WindowCloseHandler; 13 | 14 | public class WindowCloseHandlerTest 15 | { 16 | @Test 17 | public void bindToWindow() 18 | { 19 | IWindow window = mock(IWindow.class); 20 | GameLoop loop = mock(GameLoop.class); 21 | 22 | WindowCloseHandler.bindToWindow(window, loop); 23 | 24 | verify(window).addWindowListener(any()); 25 | } 26 | 27 | @Test 28 | public void dispose() 29 | { 30 | IWindow window = mock(IWindow.class); 31 | GameLoop loop = mock(GameLoop.class); 32 | 33 | WindowCloseHandler handler = WindowCloseHandler.bindToWindow(window, loop); 34 | 35 | handler.dispose(); 36 | handler.dispose(); 37 | 38 | verify(window, times(1)).removeWindowListener(any()); 39 | } 40 | 41 | @Test 42 | public void onClose() 43 | { 44 | IWindow window = mock(IWindow.class); 45 | GameLoop loop = mock(GameLoop.class); 46 | 47 | IWindowListener[] l = new IWindowListener[1]; 48 | doAnswer(a -> 49 | { l[0] = a.getArgument(0); return null; }).when(window) 50 | .addWindowListener(any()); 51 | 52 | WindowCloseHandler.bindToWindow(window, loop); 53 | 54 | l[0].onWindowRequestClose(window); 55 | 56 | verify(loop).stop(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | When contributing, please make sure to write javadocs and unit tests as needed to cover what you make. It helps us all greatly and improves the quality of newly contributed code. :) 4 | 5 | All pull requests are scanned by SonarCloud and BetterCodeHub to aid in keeping code maintainability high. If your pull request drops maintainability by a small amount, the PR might still be merged and refactored later. If there is significant dropping of maintainability, the PR will not be merged right away while those issues are being fixed. 6 | 7 | There is no limit to how often you can submit pull requests, so have fun and go all out! 8 | 9 | #### Steps 10 | 11 | To contribute to this project: 12 | 13 | * Fork the repository. 14 | * Make changes to fork locally. 15 | * Submit a pull request. 16 | 17 | #### What can be contributed? 18 | 19 | Most features, tweaks, and bug fixes will be accepted if they follow the direction WraithEngine is being developed on. The guidelines for what can and cannot be developed are not too strict, however, contributions should maintain the overal goals of WraithEngine. For example, new features such as terrain editing tools, new UI elements, better model rendering, rigging support, or API improvements are great features and eagerly accepted. Meanwhile, features such as voxel map editors or character models might be better as a mod for WraithEngine instead. 20 | 21 | WraithEngine should be maintained as a general purpose game engine to avoid bloating the repository size with more project-specific features. The general guidelines for what does or doesn't qualify are on a case-by-case basis. If you're not sure, feel free to ask in the issues tab! 22 | 23 | It's also worth noting that any changes which violate the [Comminity Guidelines](https://github.com/TheDudeFromCI/WraithEngine/blob/master/CODE_OF_CONDUCT.md) will not be accepted. 24 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/ISocket.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | 8 | /** 9 | * A testable wrapper for the Java socket. 10 | */ 11 | public interface ISocket extends Closeable 12 | { 13 | /** 14 | * Attempts to connect to a server. 15 | * 16 | * @param ip 17 | * - The ip to connect to. 18 | * @param port 19 | * - The port to connect to. 20 | * @throws IOException 21 | * If an error occurs while preforming this action. 22 | */ 23 | void connect(String ip, int port) throws IOException; 24 | 25 | /** 26 | * Gets the input stream attached to this socket. 27 | * 28 | * @return The input stream. 29 | * @throws IOException 30 | * If an error occurs while preforming this action. 31 | */ 32 | InputStream getInputStream() throws IOException; 33 | 34 | /** 35 | * Gets the output stream attached to this socket. 36 | * 37 | * @return The output stream. 38 | * @throws IOException 39 | * If an error occurs while preforming this action. 40 | */ 41 | OutputStream getOutputStream() throws IOException; 42 | 43 | /** 44 | * Gets whether or not this socket is open. 45 | * 46 | * @return True if the socket is closed or hasn't been opened yet. False 47 | * otherwise. 48 | */ 49 | boolean isClosed(); 50 | 51 | /** 52 | * Gets the IP of other side of this socket. 53 | * 54 | * @throws IllegalStateException 55 | * If the socket is closed. 56 | */ 57 | String getIP(); 58 | 59 | /** 60 | * Gets the port of the other side of this socket. 61 | * 62 | * @throws IllegalStateException 63 | * If the socket is closed. 64 | */ 65 | int getPort(); 66 | } 67 | -------------------------------------------------------------------------------- /Demos/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/server/ServerClient.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.server; 2 | 3 | import java.io.DataInput; 4 | import java.io.DataInputStream; 5 | import java.io.DataOutput; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import net.whg.we.net.ConnectionData; 9 | import net.whg.we.net.IDataHandler; 10 | import net.whg.we.net.IPacket; 11 | import net.whg.we.net.ISocket; 12 | 13 | /** 14 | * A basic implementation of the connection client interface. 15 | */ 16 | public class ServerClient implements IConnectedClient 17 | { 18 | private final ISocket socket; 19 | private final ConnectionData connection; 20 | private final IDataHandler dataHandler; 21 | private final DataInput dataInput; 22 | private final DataOutput dataOutput; 23 | 24 | /** 25 | * Creates a new server client wrapper. 26 | * 27 | * @param socket 28 | * - The socket this client is wrapping. 29 | * @param dataHandler 30 | * - The data handler for parsing byte data. 31 | * @throws IOException 32 | */ 33 | public ServerClient(ISocket socket, IDataHandler dataHandler) throws IOException 34 | { 35 | this.socket = socket; 36 | this.dataHandler = dataHandler; 37 | 38 | this.dataInput = new DataInputStream(socket.getInputStream()); 39 | this.dataOutput = new DataOutputStream(socket.getOutputStream()); 40 | 41 | var ip = socket.getIP(); 42 | var port = socket.getPort(); 43 | connection = new ConnectionData(ip, port); 44 | } 45 | 46 | @Override 47 | public ConnectionData getConnection() 48 | { 49 | return connection; 50 | } 51 | 52 | @Override 53 | public void kick() throws IOException 54 | { 55 | socket.close(); 56 | } 57 | 58 | @Override 59 | public IPacket readPacket() throws IOException 60 | { 61 | return dataHandler.readPacket(dataInput, this); 62 | } 63 | 64 | @Override 65 | public void sendPacket(IPacket packet) throws IOException 66 | { 67 | dataHandler.writePacket(dataOutput, packet); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/PhysicsPipelineActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.when; 6 | import org.junit.Test; 7 | import net.whg.we.main.AbstractBehavior; 8 | import net.whg.we.main.IFixedUpdatable; 9 | import net.whg.we.main.PhysicsPipeline; 10 | import net.whg.we.main.PipelineConstants; 11 | import net.whg.we.main.Timer; 12 | 13 | public class PhysicsPipelineActionTest 14 | { 15 | @Test 16 | public void ensurePipelinePriority() 17 | { 18 | assertEquals(PipelineConstants.PHYSICS_UPDATES, new PhysicsPipeline(mock(Timer.class), 1f).getPriority()); 19 | } 20 | 21 | @Test 22 | public void updateBehaviors() 23 | { 24 | Timer timer = mock(Timer.class); 25 | when(timer.getElapsedTime()).thenReturn(0.0) 26 | .thenReturn(1.0); 27 | 28 | PhysicsPipeline action = new PhysicsPipeline(timer, 1f); 29 | action.enableBehavior(mock(AbstractBehavior.class)); // To make sure no casting issues occur 30 | 31 | UpdatableAction behavior = new UpdatableAction(); 32 | assertEquals(0, behavior.calls); 33 | 34 | action.enableBehavior(behavior); 35 | action.run(); 36 | assertEquals(1, behavior.calls); 37 | 38 | action.disableBehavior(behavior); 39 | action.run(); 40 | assertEquals(1, behavior.calls); 41 | } 42 | 43 | @Test 44 | public void update_twice() 45 | { 46 | Timer timer = mock(Timer.class); 47 | when(timer.getElapsedTime()).thenReturn(1.0); 48 | 49 | PhysicsPipeline action = new PhysicsPipeline(timer, 1f); 50 | UpdatableAction behavior = new UpdatableAction(); 51 | action.enableBehavior(behavior); 52 | 53 | action.run(); 54 | assertEquals(2, behavior.calls); 55 | } 56 | 57 | class UpdatableAction extends AbstractBehavior implements IFixedUpdatable 58 | { 59 | int calls = 0; 60 | 61 | @Override 62 | public void fixedUpdate() 63 | { 64 | calls++; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/external/SocketAPI.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.external; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.Socket; 7 | import net.whg.we.net.ISocket; 8 | 9 | /** 10 | * An implementation of the ISocket API. 11 | */ 12 | public class SocketAPI implements ISocket 13 | { 14 | private static final String SOCKET_NOT_OPEN_WARNING = "Socket not open!"; 15 | 16 | private Socket socket; 17 | 18 | /** 19 | * Creates a new socket API. 20 | */ 21 | public SocketAPI() 22 | { 23 | this(null); 24 | } 25 | 26 | /** 27 | * Creates a new socket API which wraps an existing Java socket. 28 | */ 29 | public SocketAPI(Socket socket) 30 | { 31 | this.socket = socket; 32 | } 33 | 34 | @Override 35 | public void close() throws IOException 36 | { 37 | if (!isClosed()) 38 | socket.close(); 39 | } 40 | 41 | @Override 42 | public void connect(String ip, int port) throws IOException 43 | { 44 | if (!isClosed()) 45 | throw new IllegalStateException("Socket already open!"); 46 | 47 | socket = new Socket(ip, port); 48 | } 49 | 50 | @Override 51 | public InputStream getInputStream() throws IOException 52 | { 53 | if (isClosed()) 54 | throw new IOException(SOCKET_NOT_OPEN_WARNING); 55 | 56 | return socket.getInputStream(); 57 | } 58 | 59 | @Override 60 | public OutputStream getOutputStream() throws IOException 61 | { 62 | if (isClosed()) 63 | throw new IOException(SOCKET_NOT_OPEN_WARNING); 64 | 65 | return socket.getOutputStream(); 66 | } 67 | 68 | @Override 69 | public boolean isClosed() 70 | { 71 | return socket == null || socket.isClosed(); 72 | } 73 | 74 | @Override 75 | public String getIP() 76 | { 77 | if (isClosed()) 78 | return ""; 79 | 80 | return socket.getInetAddress() 81 | .toString(); 82 | } 83 | 84 | @Override 85 | public int getPort() 86 | { 87 | if (isClosed()) 88 | return -1; 89 | 90 | return socket.getPort(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/AbstractBehaviorTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import org.junit.Test; 6 | import net.whg.we.main.AbstractBehavior; 7 | import net.whg.we.main.GameObject; 8 | 9 | public class AbstractBehaviorTest 10 | { 11 | class FakeBehavior extends AbstractBehavior 12 | { 13 | int onInitCalled = 0; 14 | int onDisposeCalled = 0; 15 | 16 | @Override 17 | protected void onInit() 18 | { 19 | onInitCalled++; 20 | } 21 | 22 | @Override 23 | protected void onDispose() 24 | { 25 | onDisposeCalled++; 26 | } 27 | } 28 | 29 | @Test 30 | public void init_onInitCalled() 31 | { 32 | FakeBehavior behavior = new FakeBehavior(); 33 | 34 | GameObject go = new GameObject(); 35 | assertEquals(0, behavior.onInitCalled); 36 | 37 | go.addBehavior(behavior); 38 | 39 | assertEquals(1, behavior.onInitCalled); 40 | } 41 | 42 | @Test(expected = IllegalStateException.class) 43 | public void init_alreadyInitialized_anotherObject_invalid() 44 | { 45 | FakeBehavior behavior = new FakeBehavior(); 46 | 47 | GameObject go1 = new GameObject(); 48 | GameObject go2 = new GameObject(); 49 | 50 | go1.addBehavior(behavior); 51 | go2.addBehavior(behavior); 52 | } 53 | 54 | @Test 55 | public void init_alreadyInitialized_sameObject_valid() 56 | { 57 | FakeBehavior behavior = new FakeBehavior(); 58 | 59 | GameObject go = new GameObject(); 60 | 61 | go.addBehavior(behavior); 62 | go.addBehavior(behavior); 63 | 64 | assertEquals(1, behavior.onInitCalled); 65 | } 66 | 67 | @Test 68 | public void dispose_onDisposedCalled() 69 | { 70 | FakeBehavior behavior = new FakeBehavior(); 71 | 72 | GameObject go = new GameObject(); 73 | 74 | go.addBehavior(behavior); 75 | go.removeBehavior(behavior); 76 | 77 | assertEquals(1, behavior.onDisposeCalled); 78 | assertTrue(behavior.isDisposed()); 79 | 80 | behavior.dispose(); 81 | assertEquals(1, behavior.onDisposeCalled); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/PhysicsPipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | /** 7 | * The physics pipeline action is an action in charge of triggering physics 8 | * updates each frame based on the physics framerate. 9 | */ 10 | public class PhysicsPipeline implements IPipelineAction 11 | { 12 | private final List objects = new CopyOnWriteArrayList<>(); 13 | private final Timer timer; 14 | private final float framerate; 15 | private long physicsFrame; 16 | 17 | /** 18 | * Creates a new Physics pipeline action. 19 | * 20 | * @param timer 21 | * - The timer to being this action to. 22 | * @param framerate 23 | * - The number of physics frames per second. 24 | */ 25 | public PhysicsPipeline(Timer timer, float framerate) 26 | { 27 | if (framerate < 0f) 28 | throw new IllegalArgumentException("Physics framerate cannot be negative!"); 29 | 30 | this.timer = timer; 31 | this.framerate = framerate; 32 | } 33 | 34 | @Override 35 | public void run() 36 | { 37 | long idealFrame = (long) (timer.getElapsedTime() * framerate) + 1; 38 | while (physicsFrame < idealFrame) 39 | { 40 | physicsFrame++; 41 | 42 | for (IFixedUpdatable obj : objects) 43 | obj.fixedUpdate(); 44 | } 45 | } 46 | 47 | @Override 48 | public void enableBehavior(AbstractBehavior behavior) 49 | { 50 | if (behavior instanceof IFixedUpdatable) 51 | objects.add((IFixedUpdatable) behavior); 52 | } 53 | 54 | @Override 55 | public void disableBehavior(AbstractBehavior behavior) 56 | { 57 | if (behavior instanceof IFixedUpdatable) 58 | objects.remove((IFixedUpdatable) behavior); 59 | } 60 | 61 | @Override 62 | public int getPriority() 63 | { 64 | return PipelineConstants.PHYSICS_UPDATES; 65 | } 66 | 67 | /** 68 | * Gets the number of physics frames per second. 69 | * 70 | * @return The number of physics frames which should be run each second. 71 | */ 72 | public float getFramerate() 73 | { 74 | return framerate; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/OpenGLRenderingEngineTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.times; 6 | import static org.mockito.Mockito.verify; 7 | import org.junit.Test; 8 | import net.whg.we.rendering.CullingMode; 9 | import net.whg.we.rendering.opengl.IOpenGL; 10 | import net.whg.we.rendering.opengl.OpenGLRenderingEngine; 11 | 12 | public class OpenGLRenderingEngineTest 13 | { 14 | @Test 15 | public void init() 16 | { 17 | IOpenGL opengl = mock(IOpenGL.class); 18 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 19 | 20 | renderingEngine.init(); 21 | 22 | verify(opengl).init(); 23 | verify(opengl).setDepthTesting(true); 24 | verify(opengl).setCullingMode(CullingMode.BACK_FACING); 25 | } 26 | 27 | @Test 28 | public void dispose() 29 | { 30 | IOpenGL opengl = mock(IOpenGL.class); 31 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 32 | renderingEngine.init(); 33 | 34 | renderingEngine.dispose(); 35 | renderingEngine.dispose(); 36 | 37 | assertTrue(renderingEngine.isDisposed()); 38 | verify(opengl, times(1)).dispose(); 39 | } 40 | 41 | @Test 42 | public void setCullingMode_none() 43 | { 44 | IOpenGL opengl = mock(IOpenGL.class); 45 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 46 | renderingEngine.init(); 47 | 48 | renderingEngine.setCullingMode(CullingMode.NONE); 49 | renderingEngine.setCullingMode(CullingMode.NONE); 50 | 51 | verify(opengl).init(); 52 | verify(opengl, times(1)).setCullingMode(CullingMode.NONE); 53 | } 54 | 55 | @Test 56 | public void setDepthTesting_false() 57 | { 58 | IOpenGL opengl = mock(IOpenGL.class); 59 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 60 | renderingEngine.init(); 61 | 62 | renderingEngine.setDepthTesting(false); 63 | renderingEngine.setDepthTesting(false); 64 | 65 | verify(opengl).init(); 66 | verify(opengl, times(1)).setDepthTesting(false); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/CameraTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.when; 7 | import org.joml.Matrix4f; 8 | import org.joml.Quaternionf; 9 | import org.joml.Vector3f; 10 | import org.junit.Test; 11 | import net.whg.we.main.Transform3D; 12 | import net.whg.we.rendering.Camera; 13 | import net.whg.we.window.Screen; 14 | 15 | public class CameraTest 16 | { 17 | @Test 18 | public void defaultProperties() 19 | { 20 | Camera camera = new Camera(mock(Screen.class)); 21 | 22 | assertEquals(0.1f, camera.getNearClip(), 0f); 23 | assertEquals(1000f, camera.getFarClip(), 0f); 24 | assertEquals(Math.PI / 2f, camera.getFov(), 0.0001f); 25 | 26 | assertEquals(new Vector3f(), camera.getTransform() 27 | .getPosition()); 28 | assertEquals(new Quaternionf(), camera.getTransform() 29 | .getRotation()); 30 | assertEquals(new Vector3f(1f), camera.getTransform() 31 | .getSize()); 32 | } 33 | 34 | @Test 35 | public void setProperties() 36 | { 37 | Camera camera = new Camera(mock(Screen.class)); 38 | 39 | camera.setClippingDistance(15f, 30f); 40 | assertEquals(15f, camera.getNearClip(), 0f); 41 | assertEquals(30f, camera.getFarClip(), 0f); 42 | 43 | camera.setFov(0.5f); 44 | assertEquals(0.5f, camera.getFov(), 0f); 45 | } 46 | 47 | @Test 48 | public void getProjectionMatrix() 49 | { 50 | Screen screen = mock(Screen.class); 51 | when(screen.getAspect()).thenReturn(4f / 3f); 52 | Camera camera = new Camera(screen); 53 | 54 | Matrix4f mat = new Matrix4f(); 55 | mat.perspective((float) Math.PI / 2f, 4f / 3f, 0.1f, 1000f); 56 | 57 | assertTrue(mat.equals(camera.getProjectionMatrix(), 0.0001f)); 58 | } 59 | 60 | @Test 61 | public void externalTransform() 62 | { 63 | Transform3D transform = new Transform3D(); 64 | Camera camera = new Camera(transform, mock(Screen.class)); 65 | 66 | assertTrue(transform == camera.getTransform()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/server/IServer.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.server; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import net.whg.we.net.IDataHandler; 6 | import net.whg.we.net.IServerSocket; 7 | 8 | /** 9 | * Used as a bridge for handling network IO from the server side. 10 | */ 11 | public interface IServer 12 | { 13 | /** 14 | * Starts the server. 15 | * 16 | * @param socket 17 | * - The server socket to open this start with. 18 | * @param port 19 | * - The port to open the socket on or 0 to let the system choose. 20 | * @throws IOException 21 | * If the server could not be started. 22 | * @throws IllegalArgumentException 23 | * If the socket is already open. 24 | * @throws IllegalStateException 25 | * If this server is already running. 26 | */ 27 | void start(IServerSocket socket, int port) throws IOException; 28 | 29 | /** 30 | * Stops the server if it is currently running. If the server is not running, no 31 | * action is preformed. 32 | */ 33 | void stop(); 34 | 35 | /** 36 | * Checks whether or not the server is currently running. 37 | * 38 | * @return True if the server is running. False otherwise. 39 | */ 40 | boolean isRunning(); 41 | 42 | /** 43 | * Assigns the client handler to use for this server. 44 | * 45 | * @param handler 46 | * - The handler. 47 | * @throws IllegalStateException 48 | * If the server is running. 49 | */ 50 | void setClientHandler(IClientHandler handler); 51 | 52 | /** 53 | * Assigns the data handler to use for this server. 54 | * 55 | * @param handler 56 | * - The handler. 57 | * @throws IllegalStateException 58 | * If the server is running. 59 | */ 60 | void setDataHandler(IDataHandler handler); 61 | 62 | /** 63 | * Returns a read-only list of all connected clients. This method allocates 64 | * memory, so it is recommended to store the list for reuse if frequent access 65 | * is required. 66 | * 67 | * @return The list of connected clients. 68 | */ 69 | List getConnectedClients(); 70 | 71 | /** 72 | * Handles and disposes all currently queued packets. 73 | */ 74 | void handlePackets(); 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/util/OutputStreamWrapperTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.util; 2 | 3 | import static org.mockito.ArgumentMatchers.anyString; 4 | import static org.mockito.Mockito.mock; 5 | import static org.mockito.Mockito.never; 6 | import static org.mockito.Mockito.verify; 7 | import java.io.IOException; 8 | import java.io.PrintStream; 9 | import org.junit.Test; 10 | import net.whg.we.util.ILogger; 11 | import net.whg.we.util.OutputStreamWrapper; 12 | import net.whg.we.util.OutputStreamWrapper.LogLevel; 13 | 14 | public class OutputStreamWrapperTest 15 | { 16 | @Test 17 | @SuppressWarnings("resource") 18 | public void writeLine() 19 | { 20 | ILogger logger = mock(ILogger.class); 21 | OutputStreamWrapper wrapper = new OutputStreamWrapper(LogLevel.INFO, logger); 22 | 23 | PrintStream stream = new PrintStream(wrapper); 24 | stream.println("Hello Steve."); 25 | stream.println("What's up?"); 26 | 27 | verify(logger).info("Hello Steve."); 28 | verify(logger).info("What's up?"); 29 | } 30 | 31 | @Test 32 | @SuppressWarnings("resource") 33 | public void writeTwoLines_oneString() 34 | { 35 | ILogger logger = mock(ILogger.class); 36 | OutputStreamWrapper wrapper = new OutputStreamWrapper(LogLevel.DEBUG, logger); 37 | 38 | PrintStream stream = new PrintStream(wrapper); 39 | stream.println("Abc\n123"); 40 | 41 | verify(logger).debug("Abc"); 42 | verify(logger).debug("123"); 43 | } 44 | 45 | @Test 46 | @SuppressWarnings("resource") 47 | public void writePartialLine() 48 | { 49 | ILogger logger = mock(ILogger.class); 50 | OutputStreamWrapper wrapper = new OutputStreamWrapper(LogLevel.WARN, logger); 51 | 52 | PrintStream stream = new PrintStream(wrapper); 53 | stream.print("Apple-"); 54 | 55 | verify(logger, never()).warn(anyString()); 56 | } 57 | 58 | @Test 59 | @SuppressWarnings("resource") 60 | public void writeByte() throws IOException 61 | { 62 | ILogger logger = mock(ILogger.class); 63 | 64 | OutputStreamWrapper wrapper = new OutputStreamWrapper(LogLevel.TRACE, logger); 65 | 66 | wrapper.write((byte) 'A'); 67 | verify(logger, never()).trace(anyString()); 68 | 69 | wrapper.write((byte) '\n'); 70 | verify(logger).trace("A"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/TimerTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.mockito.Mockito.mock; 7 | import static org.mockito.Mockito.when; 8 | import org.junit.Test; 9 | import net.whg.we.main.ITimeSupplier; 10 | import net.whg.we.main.Timer; 11 | 12 | public class TimerTest 13 | { 14 | @Test(expected = IllegalStateException.class) 15 | public void mustStartToUpdate() 16 | { 17 | new Timer(mock(ITimeSupplier.class)).beginFrame(); 18 | } 19 | 20 | @Test 21 | public void timerIsRunning() 22 | { 23 | Timer timer = new Timer(mock(ITimeSupplier.class)); 24 | assertFalse(timer.isRunning()); 25 | 26 | timer.startTimer(); 27 | assertTrue(timer.isRunning()); 28 | 29 | timer.stopTimer(); 30 | assertFalse(timer.isRunning()); 31 | } 32 | 33 | @Test 34 | public void elapsedTime() 35 | { 36 | ITimeSupplier timeSupplier = mock(ITimeSupplier.class); 37 | when(timeSupplier.nanoTime()).thenReturn(1000000000L) 38 | .thenReturn(2000000000L); 39 | 40 | Timer timer = new Timer(timeSupplier); 41 | 42 | timer.startTimer(); 43 | assertEquals(0f, timer.getElapsedTime(), 0f); 44 | 45 | timer.beginFrame(); 46 | assertEquals(1f, timer.getElapsedTime(), 0.000001f); 47 | } 48 | 49 | @Test 50 | public void getFps() 51 | { 52 | ITimeSupplier timeSupplier = mock(ITimeSupplier.class); 53 | when(timeSupplier.nanoTime()).thenReturn(1000000000L) 54 | .thenReturn(1016666666L); 55 | 56 | Timer timer = new Timer(timeSupplier); 57 | timer.startTimer(); 58 | 59 | timer.beginFrame(); 60 | assertEquals(60f, timer.getFps(), 0.001f); 61 | } 62 | 63 | @Test 64 | public void getDeltaTime() 65 | { 66 | ITimeSupplier timeSupplier = mock(ITimeSupplier.class); 67 | when(timeSupplier.nanoTime()).thenReturn(1000000000L) 68 | .thenReturn(1100000000L); 69 | 70 | Timer timer = new Timer(timeSupplier); 71 | timer.startTimer(); 72 | 73 | timer.beginFrame(); 74 | assertEquals(0.1f, timer.getDeltaTime(), 0.00001f); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/GLScreenClearTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.mockito.ArgumentMatchers.anyBoolean; 4 | import static org.mockito.ArgumentMatchers.anyFloat; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.never; 7 | import static org.mockito.Mockito.reset; 8 | import static org.mockito.Mockito.verify; 9 | import org.junit.Test; 10 | import net.whg.we.rendering.IScreenClearHandler; 11 | import net.whg.we.rendering.opengl.IOpenGL; 12 | import net.whg.we.rendering.opengl.OpenGLRenderingEngine; 13 | import net.whg.we.rendering.Color; 14 | 15 | public class GLScreenClearTest 16 | { 17 | @Test 18 | public void clearScreen_defaultValues() 19 | { 20 | IOpenGL opengl = mock(IOpenGL.class); 21 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 22 | renderingEngine.init(); 23 | 24 | IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); 25 | screenClear.clearScreen(); 26 | 27 | verify(opengl).setClearColor(0f, 0f, 0f, 1f); 28 | verify(opengl).clearScreen(true, true); 29 | } 30 | 31 | @Test 32 | public void clearScreen_colorRed_withDepth() 33 | { 34 | IOpenGL opengl = mock(IOpenGL.class); 35 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 36 | renderingEngine.init(); 37 | 38 | IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); 39 | screenClear.setClearColor(new Color(1f, 0f, 0f)); 40 | screenClear.clearScreen(); 41 | 42 | verify(opengl).setClearColor(1f, 0f, 0f, 1f); 43 | verify(opengl).clearScreen(true, true); 44 | } 45 | 46 | @Test 47 | public void clearScreen_noColor_noDepth() 48 | { 49 | IOpenGL opengl = mock(IOpenGL.class); 50 | OpenGLRenderingEngine renderingEngine = new OpenGLRenderingEngine(opengl); 51 | renderingEngine.init(); 52 | 53 | reset(opengl); 54 | 55 | IScreenClearHandler screenClear = renderingEngine.getScreenClearHandler(); 56 | screenClear.setClearColor(null); 57 | screenClear.setClearDepth(false); 58 | screenClear.clearScreen(); 59 | 60 | verify(opengl, never()).setClearColor(anyFloat(), anyFloat(), anyFloat(), anyFloat()); 61 | verify(opengl, never()).clearScreen(anyBoolean(), anyBoolean()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/net/packets/PacketFactory.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.net.packets; 2 | 3 | import java.io.DataInput; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import net.whg.we.net.IPacketSender; 9 | 10 | /** 11 | * Used to build packets based on a given input stream using a set of registered 12 | * packet initializers. 13 | */ 14 | public class PacketFactory 15 | { 16 | private final List> initializers = Collections.synchronizedList(new ArrayList<>()); 17 | 18 | /** 19 | * Initializes a new packet initializer. 20 | * 21 | * @param initializer 22 | * - The initializer to register. 23 | * @throws IllegalArgumentException 24 | * If an initializer for the given packet ID is already registered. 25 | */ 26 | public void register(IPacketInitializer initializer) 27 | { 28 | long id = initializer.getPacketID(); 29 | if (getInitializer(id) != null) 30 | throw new IllegalArgumentException("An initializer for the given packet ID is already registered!"); 31 | 32 | initializers.add(initializer); 33 | } 34 | 35 | /** 36 | * @param input 37 | * - The data input to read from. 38 | * @param sender 39 | * - The sender that managing this data input stream. 40 | * @return The newly loaded packet. 41 | * @throws IOException 42 | * If an error occurs while reading from the input stream. 43 | */ 44 | public IBinaryPacket getPacket(DataInput input, IPacketSender sender) throws IOException 45 | { 46 | var id = input.readLong(); 47 | var initializer = getInitializer(id); 48 | 49 | if (initializer == null) 50 | throw new IOException("Unknown packet ID '" + id + "'!"); 51 | 52 | return initializer.loadPacket(input, sender); 53 | } 54 | 55 | /** 56 | * Returns the given packet initializer for the given packet ID. 57 | * 58 | * @param id 59 | * - The packet ID. 60 | * @return The packet initializer, or null if there is no initializer for the 61 | * given packet ID. 62 | */ 63 | private IPacketInitializer getInitializer(long id) 64 | { 65 | for (var initializer : initializers) 66 | if (initializer.getPacketID() == id) 67 | return initializer; 68 | 69 | return null; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/WindowCloseHandler.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | import net.whg.we.main.GameLoop; 4 | import net.whg.we.util.IDisposable; 5 | 6 | /** 7 | * This class is a simple utility which listens for when a window close request 8 | * is triggered and stops the connected game loop, allowing the window to close. 9 | *

10 | * The window binding can be removed by disposing this object. This object will 11 | * also automatially be disposed after stopping the game loop. 12 | */ 13 | public class WindowCloseHandler implements IDisposable 14 | { 15 | /** 16 | * Creates a new window close handler. This will automatically bind the listener 17 | * to the window. 18 | * 19 | * @param window 20 | * - The window to bind to. 21 | * @param gameLoop 22 | * - The game loop to stop when the window requests to close. 23 | * @return A reference to the newly created window close handler. 24 | */ 25 | public static WindowCloseHandler bindToWindow(IWindow window, GameLoop gameLoop) 26 | { 27 | return new WindowCloseHandler(window, gameLoop); 28 | } 29 | 30 | /** 31 | * A simple listener which listens for a window close request. 32 | */ 33 | private class WindowCloseListener extends IWindowAdapter 34 | { 35 | @Override 36 | public void onWindowRequestClose(IWindow window) 37 | { 38 | gameLoop.stop(); 39 | dispose(); 40 | } 41 | } 42 | 43 | private final WindowCloseListener listener = new WindowCloseListener(); 44 | private final IWindow window; 45 | private final GameLoop gameLoop; 46 | private boolean disposed; 47 | 48 | /** 49 | * Creates a new window close handler. This constructor handles the window 50 | * binding automatically. 51 | * 52 | * @param window 53 | * - The window to bind to. 54 | * @param gameLoop 55 | * - The game loop to stop when the window requests to close. 56 | */ 57 | private WindowCloseHandler(IWindow window, GameLoop gameLoop) 58 | { 59 | this.window = window; 60 | this.gameLoop = gameLoop; 61 | 62 | window.addWindowListener(listener); 63 | } 64 | 65 | @Override 66 | public void dispose() 67 | { 68 | if (isDisposed()) 69 | return; 70 | 71 | disposed = true; 72 | window.removeWindowListener(listener); 73 | } 74 | 75 | @Override 76 | public boolean isDisposed() 77 | { 78 | return disposed; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/Screen.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | /** 4 | * The screen object is an object which can be used to determine various 5 | * information about the state of the game screen, as well as modify the screen 6 | * in certain ways. Each screen is bound to a single window. 7 | */ 8 | public class Screen extends WindowReceiver 9 | { 10 | /** 11 | * A private listener class which received events from the window to store 12 | * within the screen object. 13 | */ 14 | private class ScreenListener extends IWindowAdapter 15 | { 16 | @Override 17 | public void onWindowResized(IWindow window, int width, int height) 18 | { 19 | Screen.this.width = width; 20 | Screen.this.height = height; 21 | } 22 | 23 | @Override 24 | public void onWindowUpdated(IWindow window) 25 | { 26 | WindowSettings settings = window.getProperties(); 27 | Screen.this.width = settings.getWidth(); 28 | Screen.this.height = settings.getHeight(); 29 | } 30 | } 31 | 32 | private int width; 33 | private int height; 34 | 35 | /** 36 | * Creates a new screen object and binds to the given window. 37 | * 38 | * @param window 39 | * - The window to bind to. 40 | */ 41 | public Screen(IWindow window) 42 | { 43 | var listener = new ScreenListener(); 44 | listenTo(window, listener); 45 | 46 | listener.onWindowUpdated(window); 47 | } 48 | 49 | /** 50 | * Gets the current width of the window. 51 | * 52 | * @return The width. 53 | */ 54 | public int getWidth() 55 | { 56 | checkDisposed(); 57 | return width; 58 | } 59 | 60 | /** 61 | * Gets the current height of the window. 62 | * 63 | * @return The height. 64 | */ 65 | public int getHeight() 66 | { 67 | checkDisposed(); 68 | return height; 69 | } 70 | 71 | /** 72 | * Gets the current aspect of the window. 73 | * 74 | * @return The aspect ratio. 75 | */ 76 | public float getAspect() 77 | { 78 | checkDisposed(); 79 | return (float) width / height; 80 | } 81 | 82 | /** 83 | * Checks if this screen is currently disposed or not. 84 | * 85 | * @throws IllegalStateException 86 | * If this screen is disposed. 87 | */ 88 | private void checkDisposed() 89 | { 90 | if (isDisposed()) 91 | throw new IllegalStateException("Screen already disposed!"); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/IWindow.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | import net.whg.we.rendering.IRenderingEngine; 4 | import net.whg.we.util.IDisposable; 5 | 6 | /** 7 | * The window class is in charge of managing a window which is displayed to the 8 | * screen. This can be modified and closed at any time. Windows have their own 9 | * rendering engine attached which can be used to render 2D or 3D graphics with. 10 | * When disposing this window, the rendering engine is automatically disposed as 11 | * well. The window is also in charge of receiving input events from the user. 12 | */ 13 | public interface IWindow extends IDisposable 14 | { 15 | /** 16 | * Sets the properties of this window. This method calls for the window to be 17 | * rebuilt or modified to match the given window settings. 18 | * 19 | * @param settings 20 | * - The new settings the window should have. 21 | * @throws UnsupportedOperationException 22 | * If the window settings contains a change which is not supported by the 23 | * window handler. 24 | */ 25 | void setProperties(WindowSettings settings); 26 | 27 | /** 28 | * Gets the current properties of this window. 29 | * 30 | * @return The properties of this window. 31 | */ 32 | WindowSettings getProperties(); 33 | 34 | /** 35 | * Gets the rendering engine that is used by this window. 36 | * 37 | * @return The rendering engine. 38 | */ 39 | IRenderingEngine getRenderingEngine(); 40 | 41 | /** 42 | * Adds a window listener to this window, to receive events. This method does 43 | * nothing if the listener is null, or is already attached to this window. 44 | * 45 | * @param listener 46 | * - The listener to add. 47 | */ 48 | void addWindowListener(IWindowListener listener); 49 | 50 | /** 51 | * Removes a window listener from this window. This method does nothing if the 52 | * listener is null, or is not attached to this window. 53 | * 54 | * @param listener 55 | * - The listener to remove. 56 | */ 57 | void removeWindowListener(IWindowListener listener); 58 | 59 | /** 60 | * This function is used to poll events currently queued by the window. Calling 61 | * this method will trigger all events and pass them to all attached window 62 | * listeners. This method must be called on the main thread at the end of each 63 | * frame. 64 | */ 65 | void pollEvents(); 66 | 67 | /** 68 | * Gets the ID of the window. 69 | * 70 | * @return The active window ID, or -1 if this window is disposed. 71 | */ 72 | long getWindowId(); 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/window/ScreenTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.window; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNull; 5 | import org.junit.Test; 6 | import net.whg.we.window.Screen; 7 | 8 | public class ScreenTest 9 | { 10 | @Test 11 | public void resizeScreen() 12 | { 13 | FakeWindow window = new FakeWindow(); 14 | Screen screen = new Screen(window); 15 | 16 | window.listener.onWindowResized(window, 320, 240); 17 | 18 | assertEquals(320, screen.getWidth()); 19 | assertEquals(240, screen.getHeight()); 20 | assertEquals(4f / 3f, screen.getAspect(), 0.0001f); 21 | } 22 | 23 | @Test 24 | public void resizeScreen_updateTrigger() 25 | { 26 | FakeWindow window = new FakeWindow(); 27 | window.settings.setSize(1600, 900); 28 | 29 | Screen screen = new Screen(window); 30 | window.listener.onWindowUpdated(window); 31 | 32 | assertEquals(1600, screen.getWidth()); 33 | assertEquals(900, screen.getHeight()); 34 | assertEquals(16f / 9f, screen.getAspect(), 0.0001f); 35 | } 36 | 37 | @Test(expected = IllegalStateException.class) 38 | public void getWidth_alreadyDisposed() 39 | { 40 | FakeWindow window = new FakeWindow(); 41 | Screen screen = new Screen(window); 42 | screen.dispose(); 43 | 44 | screen.getWidth(); 45 | } 46 | 47 | @Test(expected = IllegalStateException.class) 48 | public void getHeight_alreadyDisposed() 49 | { 50 | FakeWindow window = new FakeWindow(); 51 | Screen screen = new Screen(window); 52 | screen.dispose(); 53 | 54 | screen.getHeight(); 55 | } 56 | 57 | @Test(expected = IllegalStateException.class) 58 | public void getAspect_alreadyDisposed() 59 | { 60 | FakeWindow window = new FakeWindow(); 61 | Screen screen = new Screen(window); 62 | screen.dispose(); 63 | 64 | screen.getAspect(); 65 | } 66 | 67 | @Test 68 | public void dipose() 69 | { 70 | FakeWindow window = new FakeWindow(); 71 | 72 | Screen screen = new Screen(window); 73 | screen.dispose(); 74 | screen.dispose(); // To make sure nothing happens when you dispose twice 75 | 76 | assertNull(window.listener); 77 | } 78 | 79 | @Test 80 | public void initializeOnCreation() 81 | { 82 | FakeWindow window = new FakeWindow(); 83 | window.settings.setSize(400, 300); 84 | 85 | Screen screen = new Screen(window); 86 | 87 | assertEquals(400, screen.getWidth()); 88 | assertEquals(300, screen.getHeight()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/IPipelineAction.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * A pipeline action is a large factory object which is used to control how 5 | * objects within a scene should behave. These actions usually control groups of 6 | * objects which behave in a specific way within the scene. 7 | *

8 | * Within a pipeline action, objects are selected based solely on attached 9 | * behavors, where a single behavior may be in multiple actions at once, and a 10 | * game object may have behaviors which interact with different actions. 11 | *

12 | * Pipeline actions should also override the default priority level for loop 13 | * actions to ensure pipeline actions occur in the desired order. Default 14 | * priorities are defined in the {@link PipelineConstants}. 15 | */ 16 | public interface IPipelineAction extends ILoopAction 17 | { 18 | /** 19 | * This event is called each time a new behavior is added to the scene in some 20 | * way. This could be triggered by a game object being initialized, or by a new 21 | * behavior being attached to an existing game object. This event is also 22 | * triggered when a game object switches to the scene this pipeline action 23 | * exists within. 24 | * 25 | * @param behavior 26 | * - The behavior. 27 | */ 28 | default void enableBehavior(AbstractBehavior behavior) 29 | {} 30 | 31 | /** 32 | * This event is called each time a behavior is removed from the scene in some 33 | * way. This could be triggered by a game object being destroyed, or by a 34 | * behavior being removed to an existing game object. This event is also 35 | * triggered when a game object switches from the scene this pipeline action 36 | * exists within. 37 | * 38 | * @param behavior 39 | * - The behavior. 40 | */ 41 | default void disableBehavior(AbstractBehavior behavior) 42 | {} 43 | 44 | /** 45 | * This event is called each time a game object is added to the scene. This 46 | * event is also triggered when a game object switches to the scene this 47 | * pipeline action exists within. 48 | * 49 | * @param gameObject 50 | * = The game object which was added to the scene. 51 | */ 52 | default void enableGameObject(GameObject gameObject) 53 | {} 54 | 55 | /** 56 | * This event is called each time a game object is removed from the scene. This 57 | * event is also triggered when a game object switches from the scene this 58 | * pipeline action exists within. 59 | * 60 | * @param gameObject 61 | * = The game object which was removed to the scene. 62 | */ 63 | default void disableGameObject(GameObject gameObject) 64 | {} 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/RenderPipelineActionTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.never; 7 | import static org.mockito.Mockito.verify; 8 | import org.junit.Test; 9 | import net.whg.we.main.GameObject; 10 | import net.whg.we.main.PipelineConstants; 11 | import net.whg.we.rendering.RenderBehavior; 12 | import net.whg.we.rendering.RenderPipeline; 13 | import net.whg.we.window.Screen; 14 | import net.whg.we.rendering.Camera; 15 | import net.whg.we.rendering.IMesh; 16 | import net.whg.we.rendering.Material; 17 | 18 | public class RenderPipelineActionTest 19 | { 20 | @Test 21 | public void addBehavior() 22 | { 23 | RenderBehavior behavior = new RenderBehavior(); 24 | RenderPipeline action = new RenderPipeline(); 25 | action.enableBehavior(behavior); 26 | 27 | assertEquals(1, action.renderBehaviors() 28 | .size()); 29 | assertTrue(action.renderBehaviors() 30 | .contains(behavior)); 31 | } 32 | 33 | @Test 34 | public void renderElements() 35 | { 36 | IMesh mesh = mock(IMesh.class); 37 | Material material = mock(Material.class); 38 | 39 | GameObject go = new GameObject(); 40 | 41 | RenderBehavior behavior = new RenderBehavior(); 42 | behavior.setMesh(mesh); 43 | behavior.setMaterial(material); 44 | go.addBehavior(behavior); 45 | 46 | RenderPipeline action = new RenderPipeline(); 47 | action.setCamera(new Camera(mock(Screen.class))); 48 | action.enableBehavior(behavior); 49 | 50 | action.run(); 51 | 52 | verify(mesh).render(); 53 | } 54 | 55 | @Test 56 | public void renderElements_noCamera() 57 | { 58 | IMesh mesh = mock(IMesh.class); 59 | Material material = mock(Material.class); 60 | 61 | GameObject go = new GameObject(); 62 | 63 | RenderBehavior behavior = new RenderBehavior(); 64 | behavior.setMesh(mesh); 65 | behavior.setMaterial(material); 66 | go.addBehavior(behavior); 67 | 68 | RenderPipeline action = new RenderPipeline(); 69 | action.enableBehavior(behavior); 70 | 71 | action.run(); 72 | 73 | verify(mesh, never()).render(); 74 | } 75 | 76 | @Test 77 | public void ensureCorrectPriority() 78 | { 79 | assertEquals(PipelineConstants.RENDER_SOLIDS, new RenderPipeline().getPriority()); 80 | } 81 | 82 | @Test 83 | public void initializeWithCamera() 84 | { 85 | Camera camera = mock(Camera.class); 86 | RenderPipeline pipeline = new RenderPipeline(camera); 87 | 88 | assertTrue(camera == pipeline.getCamera()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/RenderPipeline.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import net.whg.we.main.AbstractBehavior; 7 | import net.whg.we.main.IPipelineAction; 8 | import net.whg.we.main.PipelineConstants; 9 | 10 | /** 11 | * The renderer pipeline action is used to render elements within a scene. By 12 | * default, all behaviors which extend {@link RenderBehavior} are used. 13 | */ 14 | public class RenderPipeline implements IPipelineAction 15 | { 16 | private final List renderedObjects = new ArrayList<>(); 17 | private final List renderedObjectsReadOnly = Collections.unmodifiableList(renderedObjects); 18 | private Camera camera; 19 | 20 | /** 21 | * Creates a new render pipeline action. 22 | */ 23 | public RenderPipeline() 24 | { 25 | this(null); 26 | } 27 | 28 | /** 29 | * Creates a new render pipeline action and initializes it with a camera. 30 | * 31 | * @param camera 32 | * - The camera to attach to this render pipeline action. 33 | */ 34 | public RenderPipeline(Camera camera) 35 | { 36 | this.camera = camera; 37 | } 38 | 39 | @Override 40 | public void run() 41 | { 42 | if (camera == null) 43 | return; 44 | 45 | for (RenderBehavior behavior : renderedObjects) 46 | behavior.render(camera); 47 | } 48 | 49 | @Override 50 | public void enableBehavior(AbstractBehavior behavior) 51 | { 52 | if (behavior instanceof RenderBehavior) 53 | renderedObjects.add((RenderBehavior) behavior); 54 | } 55 | 56 | @Override 57 | public void disableBehavior(AbstractBehavior behavior) 58 | { 59 | renderedObjects.remove(behavior); 60 | } 61 | 62 | /** 63 | * Sets the camera to use when rendering the scene. If null, the scene is not 64 | * rendered. 65 | * 66 | * @param camera 67 | * - The camera to use. 68 | */ 69 | public void setCamera(Camera camera) 70 | { 71 | this.camera = camera; 72 | } 73 | 74 | /** 75 | * Gets the camera used when rendering the scene. 76 | * 77 | * @return The camera being used. 78 | */ 79 | public Camera getCamera() 80 | { 81 | return camera; 82 | } 83 | 84 | /** 85 | * Gets a read-only list of render behaviors currently maintained by this 86 | * action. 87 | * 88 | * @return A list of render behaviors. 89 | */ 90 | public List renderBehaviors() 91 | { 92 | return renderedObjectsReadOnly; 93 | } 94 | 95 | @Override 96 | public int getPriority() 97 | { 98 | return PipelineConstants.RENDER_SOLIDS; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/util/OutputStreamWrapper.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.util; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | /** 7 | * A simple utility class for streaming to the logger, via an output stream. 8 | * Intended as a replacement for using System.out and System.err, to make the 9 | * system more compatible with a logger. 10 | */ 11 | public class OutputStreamWrapper extends OutputStream 12 | { 13 | /** 14 | * A logging level is used to determine which channel to write the stream to. 15 | */ 16 | public enum LogLevel 17 | { 18 | TRACE, 19 | DEBUG, 20 | INFO, 21 | WARN, 22 | ERROR 23 | } 24 | 25 | private final StringBuilder buffer = new StringBuilder(); 26 | private final ILogger logger; 27 | private final LogLevel logLevel; 28 | 29 | /** 30 | * Creates a new output stream wrapper object. 31 | * 32 | * @param logLevel 33 | * - The log level this wrapper uses when writing. 34 | * @param logger 35 | * - The logger to write messages to. 36 | */ 37 | public OutputStreamWrapper(LogLevel logLevel, ILogger logger) 38 | { 39 | this.logLevel = logLevel; 40 | this.logger = logger; 41 | } 42 | 43 | @Override 44 | public void write(int b) throws IOException 45 | { 46 | char c = (char) (b & 0xFF); 47 | 48 | if (c == '\n') 49 | flush(); 50 | else 51 | buffer.append(c); 52 | } 53 | 54 | @Override 55 | public void flush() throws IOException 56 | { 57 | switch (logLevel) 58 | { 59 | case TRACE: 60 | logger.trace(buffer.toString()); 61 | break; 62 | 63 | case DEBUG: 64 | logger.debug(buffer.toString()); 65 | break; 66 | 67 | case INFO: 68 | logger.info(buffer.toString()); 69 | break; 70 | 71 | case WARN: 72 | logger.warn(buffer.toString()); 73 | break; 74 | 75 | case ERROR: 76 | logger.error(buffer.toString()); 77 | break; 78 | } 79 | 80 | buffer.delete(0, buffer.length()); 81 | } 82 | 83 | @Override 84 | public void close() throws IOException 85 | { 86 | flush(); 87 | } 88 | 89 | @Override 90 | public void write(byte[] b, int off, int len) throws IOException 91 | { 92 | String line = new String(b, off, len); 93 | 94 | int index; 95 | while ((index = line.indexOf('\n')) > -1) 96 | { 97 | buffer.append(line.substring(0, index)); 98 | flush(); 99 | 100 | line = line.substring(index + 1); 101 | } 102 | 103 | buffer.append(line); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/IShader.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import java.nio.FloatBuffer; 4 | 5 | /** 6 | * A shader is used to determine how an object should be rendered to the screen. 7 | * It contains driver-specific code to be executed on the graphics card to 8 | * manipulate vertices into a renderable form. Shaders are also used to apply 9 | * effects to objects such as textures and lighting. 10 | */ 11 | public interface IShader extends IRenderResource 12 | { 13 | /** 14 | * When called, this shader is bound to the graphics card, if not already bound, 15 | * so that future mesh renders will use this shader to render with. 16 | */ 17 | void bind(); 18 | 19 | /** 20 | * Used to compile or re-compile this shader with a new shader program. As a 21 | * shader program is comprised of a vertex shader and a fragment shader, 22 | * providing these two components to the shader can be used to compile the 23 | * shader with. Additional shaders may be added to the pipeline if the 24 | * underlaying rendering engine supports it. 25 | *

26 | * If a shader attributes object is defined, the shader will bind the new layout 27 | * when creating the shader. This will allow shaders to be initialized to read 28 | * vertex attribute data in the intended manner. If some attributes are 29 | * specified in the shader but not in the new layout, or attributes are defined 30 | * in the layout but not in the shader, unintended behaviors may occur when 31 | * rendering. If a shader directly requests attributes to be defined with a 32 | * given layout, that layout value is prioritized over the layout. 33 | * 34 | * @param shaderCode 35 | * - The shader code to send to the rendering engine, to compile. 36 | * @param shaderAttributes 37 | * - The new shader attributes layout this shader should use, or null if the 38 | * default shader attributes should be layed out automatically. 39 | * @throws IllegalArgumentException 40 | * If shader code object is null. 41 | */ 42 | void update(RawShaderCode shaderCode, ShaderAttributes shaderAttributes); 43 | 44 | /** 45 | * Assigns a uniform to this shader in the form of a 4x4 matrix. May also be 46 | * used to assign an array of matrix values. 47 | * 48 | * @param uniform 49 | * - The uniform name. 50 | * @param value 51 | * - The matrix values. 52 | * @throws IllegalArgumentException 53 | * If the float buffer has a length which is not an increment of 16. 54 | */ 55 | void setUniformMat4(String uniform, FloatBuffer value); 56 | 57 | /** 58 | * Assigns a uniform to this shader in the form of an int. 59 | * 60 | * @param uniform 61 | * - The uniform name. 62 | * @param value 63 | * - The value to assign. 64 | */ 65 | void setUniformInt(String uniform, int value); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/AbstractBehavior.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import net.whg.we.util.IDisposable; 4 | 5 | /** 6 | * A behavior is a component which is attached to a game object to define how it 7 | * acts and behaves. It contains a set of utility functions which are common for 8 | * behaviors to implement. 9 | *

10 | * A behavior may only be attached to a single game object during it's lifetime. 11 | */ 12 | public abstract class AbstractBehavior implements IDisposable 13 | { 14 | protected static final String OBJECT_DISPOSED = "Object already disposed!"; 15 | 16 | private GameObject gameObject; 17 | private boolean disposed; 18 | 19 | /** 20 | * Initializes this behavior and links it to a game object. 21 | * 22 | * @param gameObject 23 | * - The game object to link this behavior to. 24 | * @throws IllegalStateException 25 | * If this behavior is already attached to another game object. 26 | */ 27 | final void init(GameObject gameObject) 28 | { 29 | if (this.gameObject != null) 30 | throw new IllegalStateException("Behavior already bound to another game object!"); 31 | 32 | this.gameObject = gameObject; 33 | 34 | onInit(); 35 | } 36 | 37 | /** 38 | * Gets the game object this behavior is attached to. 39 | * 40 | * @return The game object, or null if this behavior has not yet been bound to a 41 | * game object. 42 | */ 43 | public final GameObject getGameObject() 44 | { 45 | return gameObject; 46 | } 47 | 48 | @Override 49 | public final void dispose() 50 | { 51 | if (isDisposed()) 52 | return; 53 | 54 | disposed = true; 55 | onDispose(); 56 | } 57 | 58 | @Override 59 | public final boolean isDisposed() 60 | { 61 | return disposed; 62 | } 63 | 64 | /** 65 | * This function is an event which is triggered when this object is first 66 | * initialized. It can be used to prepare the behavior. The default 67 | * implementation does nothing, and is desgined to be overriden. 68 | */ 69 | protected void onInit() 70 | {} 71 | 72 | /** 73 | * This event is triggered when a behavior object is destroyed and needs to be 74 | * cleaned up. This can occur when the behavior is removed from a game object, 75 | * or the game object is disposed. The default implementation does nothing, and 76 | * is desgined to be overriden. 77 | */ 78 | protected void onDispose() 79 | {} 80 | 81 | /** 82 | * This event is called when the game object this behavior is attached to 83 | * changes scenes. 84 | * 85 | * @param oldScene 86 | * - The scene the game object was previously in. 87 | * @param newScene 88 | * - The new scene the game object is now in. 89 | */ 90 | protected void onSceneChange(Scene oldScene, Scene newScene) 91 | {} 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/opengl/OpenGLRenderingEngine.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering.opengl; 2 | 3 | import net.whg.we.rendering.CullingMode; 4 | import net.whg.we.rendering.IMesh; 5 | import net.whg.we.rendering.IRenderingEngine; 6 | import net.whg.we.rendering.IScreenClearHandler; 7 | import net.whg.we.rendering.IShader; 8 | import net.whg.we.rendering.ITexture; 9 | import net.whg.we.rendering.Color; 10 | 11 | /** 12 | * This is the basic OpenGL implementation of the rendering engine. It uses 13 | * OpenGL 3.3, and renders on the main thread. 14 | */ 15 | public class OpenGLRenderingEngine implements IRenderingEngine 16 | { 17 | private final IOpenGL opengl; 18 | private final BindStates bindStates; 19 | private final IScreenClearHandler screenClearHandler; 20 | private boolean depthTesting = true; 21 | private CullingMode cullingMode = CullingMode.BACK_FACING; 22 | private boolean disposed; 23 | 24 | public OpenGLRenderingEngine(IOpenGL opengl) 25 | { 26 | this.opengl = opengl; 27 | bindStates = new BindStates(opengl); 28 | screenClearHandler = new GLScreenClear(opengl); 29 | } 30 | 31 | @Override 32 | public void init() 33 | { 34 | opengl.init(); 35 | opengl.setCullingMode(cullingMode); 36 | opengl.setDepthTesting(depthTesting); 37 | 38 | screenClearHandler.setClearColor(new Color(0f, 0f, 0f)); 39 | screenClearHandler.setClearDepth(true); 40 | } 41 | 42 | @Override 43 | public void dispose() 44 | { 45 | if (isDisposed()) 46 | return; 47 | 48 | disposed = true; 49 | opengl.dispose(); 50 | } 51 | 52 | @Override 53 | public boolean isDisposed() 54 | { 55 | return disposed; 56 | } 57 | 58 | @Override 59 | public IScreenClearHandler getScreenClearHandler() 60 | { 61 | return screenClearHandler; 62 | } 63 | 64 | @Override 65 | public IMesh createMesh() 66 | { 67 | return new GLMesh(bindStates, opengl); 68 | } 69 | 70 | @Override 71 | public IShader createShader() 72 | { 73 | return new GLShader(bindStates, opengl); 74 | } 75 | 76 | @Override 77 | public void setDepthTesting(boolean depthTesting) 78 | { 79 | if (this.depthTesting == depthTesting) 80 | return; 81 | 82 | this.depthTesting = depthTesting; 83 | opengl.setDepthTesting(depthTesting); 84 | } 85 | 86 | @Override 87 | public void setCullingMode(CullingMode cullingMode) 88 | { 89 | if (this.cullingMode == cullingMode) 90 | return; 91 | 92 | this.cullingMode = cullingMode; 93 | opengl.setCullingMode(cullingMode); 94 | } 95 | 96 | @Override 97 | public ITexture createTexture() 98 | { 99 | return new GLTexture(opengl, bindStates); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/net/packets/PacketFactoryTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.net.packets; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.when; 5 | import static org.junit.Assert.assertEquals; 6 | import static org.mockito.Mockito.any; 7 | import java.io.DataInput; 8 | import java.io.IOException; 9 | import org.junit.Test; 10 | import net.whg.we.net.packets.IBinaryPacket; 11 | import net.whg.we.net.packets.IPacketInitializer; 12 | import net.whg.we.net.packets.PacketFactory; 13 | 14 | public class PacketFactoryTest 15 | { 16 | @Test 17 | public void readPacket() throws IOException 18 | { 19 | var packetID = 1234L; 20 | var packet = mock(IBinaryPacket.class); 21 | var initializer = mock(IPacketInitializer.class); 22 | when(packet.getPacketID()).thenReturn(packetID); 23 | when(initializer.getPacketID()).thenReturn(packetID); 24 | when(initializer.loadPacket(any(), any())).thenReturn(packet); 25 | 26 | var input = mock(DataInput.class); 27 | when(input.readLong()).thenReturn(packetID); 28 | 29 | var factory = new PacketFactory(); 30 | factory.register(initializer); 31 | 32 | assertEquals(packet, factory.getPacket(input, null)); 33 | } 34 | 35 | @Test(expected = IOException.class) 36 | public void readPacket_UnknownID_Error() throws IOException 37 | { 38 | var input = mock(DataInput.class); 39 | when(input.readLong()).thenReturn(17L); 40 | 41 | var factory = new PacketFactory(); 42 | factory.getPacket(input, null); 43 | } 44 | 45 | @Test(expected = IllegalArgumentException.class) 46 | public void register_AlreadyRegistered_Error() 47 | { 48 | var init1 = mock(IPacketInitializer.class); 49 | var init2 = mock(IPacketInitializer.class); 50 | when(init1.getPacketID()).thenReturn(1L); 51 | when(init2.getPacketID()).thenReturn(1L); 52 | 53 | var factory = new PacketFactory(); 54 | factory.register(init1); 55 | factory.register(init2); 56 | } 57 | 58 | @Test 59 | public void readPacket_UsesCorrectInitializer() throws IOException 60 | { 61 | var packet1 = mock(IBinaryPacket.class); 62 | var packet2 = mock(IBinaryPacket.class); 63 | var packet3 = mock(IBinaryPacket.class); 64 | var factory = new PacketFactory(); 65 | 66 | var init1 = mock(IPacketInitializer.class); 67 | when(init1.getPacketID()).thenReturn(1L); 68 | when(init1.loadPacket(any(), any())).thenReturn(packet1); 69 | factory.register(init1); 70 | 71 | var init2 = mock(IPacketInitializer.class); 72 | when(init2.getPacketID()).thenReturn(2L); 73 | when(init2.loadPacket(any(), any())).thenReturn(packet2); 74 | factory.register(init2); 75 | 76 | var init3 = mock(IPacketInitializer.class); 77 | when(init3.getPacketID()).thenReturn(3L); 78 | when(init3.loadPacket(any(), any())).thenReturn(packet3); 79 | factory.register(init3); 80 | 81 | var input = mock(DataInput.class); 82 | when(input.readLong()).thenReturn(2L); 83 | 84 | assertEquals(packet2, factory.getPacket(input, null)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/RenderBehavior.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import org.joml.Matrix4f; 4 | import net.whg.we.main.AbstractBehavior; 5 | 6 | /** 7 | * This behavior is used as a method for rendering a mesh to the scene. When 8 | * attached to a game object, this behavior ties into the scene renderer the 9 | * game object is currently sitting in and allows for the game object to have a 10 | * renderable mesh attached to it. 11 | */ 12 | public class RenderBehavior extends AbstractBehavior 13 | { 14 | private final Matrix4f bufferMatrix = new Matrix4f(); 15 | private IMesh mesh; 16 | private Material material; 17 | 18 | /** 19 | * Sets the mesh to be rendered. 20 | * 21 | * @param mesh 22 | * - The mesh. 23 | */ 24 | public void setMesh(IMesh mesh) 25 | { 26 | if (isDisposed()) 27 | throw new IllegalStateException(OBJECT_DISPOSED); 28 | 29 | this.mesh = mesh; 30 | } 31 | 32 | /** 33 | * Gets the mesh this render behavior is rendering. 34 | * 35 | * @return The mesh. 36 | */ 37 | public IMesh getMesh() 38 | { 39 | return mesh; 40 | } 41 | 42 | /** 43 | * Sets the material used to render the mesh with. 44 | * 45 | * @param material 46 | * - The material. 47 | */ 48 | public void setMaterial(Material material) 49 | { 50 | if (isDisposed()) 51 | throw new IllegalStateException(OBJECT_DISPOSED); 52 | 53 | this.material = material; 54 | } 55 | 56 | /** 57 | * Gets the material used to render the mesh with. 58 | * 59 | * @return The material. 60 | */ 61 | public Material getMaterial() 62 | { 63 | return material; 64 | } 65 | 66 | /** 67 | * Checks if the mesh the material, and the game object have all been assigned. 68 | * 69 | * @return True if all components have been assigned. False otherwise. 70 | */ 71 | private boolean canRender() 72 | { 73 | return getGameObject() != null && mesh != null && material != null; 74 | } 75 | 76 | /** 77 | * Calls for this object to be rendered. This will bind the currently assigned 78 | * material and trigger a draw call for the mesh. This method does nothing if 79 | * this behavior has not yet been initialized, or if the mesh or material have 80 | * not yet been assigned. 81 | * 82 | * @throws IllegalStateException 83 | * If this behavior was already disposed. 84 | */ 85 | void render(Camera camera) 86 | { 87 | if (isDisposed()) 88 | throw new IllegalStateException(OBJECT_DISPOSED); 89 | 90 | if (canRender()) 91 | { 92 | getGameObject().getTransform() 93 | .getFullMatrix(bufferMatrix); 94 | 95 | material.bind(); 96 | material.setCameraMatrix(camera, bufferMatrix); 97 | mesh.render(); 98 | } 99 | } 100 | 101 | @Override 102 | protected void onDispose() 103 | { 104 | mesh = null; 105 | material = null; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/GameLoop.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.concurrent.CopyOnWriteArrayList; 6 | 7 | /** 8 | * The game loop object is a container for handling a standard game loop. A game 9 | * loop being per-frame actions such as updating physics, handling player input, 10 | * and rendering the screen until the game loop is stopped. 11 | */ 12 | public class GameLoop 13 | { 14 | private final List loopActions = new CopyOnWriteArrayList<>(); 15 | 16 | private final List loopActionsReadOnly = Collections.unmodifiableList(loopActions); 17 | 18 | private boolean running; 19 | 20 | /** 21 | * Adds a new loop action to this game loop. When a loop action is first added, 22 | * all actions in this game loop are sorted based on their priority, such that, 23 | * actions with a lower priority are executed first and actions with a higher 24 | * priority are executed last. The sorter used is a stable sorter, allowing 25 | * actions of the same priority to remain in the same order that they were added 26 | * in. This method does nothing if the loop action is null or is already in this 27 | * game loop. 28 | *

29 | * If this game loop is currently running, modifying game loop actions applies 30 | * on the next frame. 31 | * 32 | * @param loopAction 33 | * - The action to add. 34 | */ 35 | public void addAction(ILoopAction loopAction) 36 | { 37 | if (loopAction == null) 38 | return; 39 | 40 | if (loopActions.contains(loopAction)) 41 | return; 42 | 43 | loopActions.add(loopAction); 44 | loopActions.sort((a, b) -> a.getPriority() - b.getPriority()); 45 | } 46 | 47 | /** 48 | * Removes a loop action from this game loop. This method does nothing if the 49 | * loop action is null or is not in this game loop. 50 | *

51 | * If this game loop is currently running, modifying game loop actions applies 52 | * on the next frame. 53 | * 54 | * @param loopAction 55 | * - The loop action to remove. 56 | */ 57 | public void removeAction(ILoopAction loopAction) 58 | { 59 | if (loopAction == null) 60 | return; 61 | 62 | loopActions.remove(loopAction); 63 | } 64 | 65 | /** 66 | * Runs this game loop. This will start a loop which will run repeatedly until 67 | * the {@link #stop()} method is called. 68 | */ 69 | public void loop() 70 | { 71 | running = true; 72 | while (running) 73 | { 74 | for (ILoopAction action : loopActions) 75 | action.run(); 76 | } 77 | } 78 | 79 | /** 80 | * When this is called, the game loop will stop after the current frame. This 81 | * method does nothing if the game loop is not running. 82 | */ 83 | public void stop() 84 | { 85 | running = false; 86 | } 87 | 88 | /** 89 | * Gets a read-only list of loop actions within this game loop. 90 | * 91 | * @return A list of loop actions. 92 | */ 93 | public List loopActions() 94 | { 95 | return loopActionsReadOnly; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/SceneGameLoop.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.concurrent.CopyOnWriteArrayList; 6 | 7 | /** 8 | * The scene game loop is an auto-managed game loop which handles adding and 9 | * removing pipeline actions to the game loop through the act of adding and 10 | * removing scenes. Multiple scenes can be handled simultaneously. 11 | */ 12 | public class SceneGameLoop extends GameLoop 13 | { 14 | /** 15 | * A simple listener object which listens for when pipeline actions are added or 16 | * removed from a scene, and adds or removed the action from the game loop 17 | * accordingly. 18 | */ 19 | private class ScenePipelineListener implements ISceneListener 20 | { 21 | @Override 22 | public void onGameObjectAdded(GameObject go) 23 | { 24 | // Do nothing 25 | } 26 | 27 | @Override 28 | public void onGameObjectRemoved(GameObject go) 29 | { 30 | // Do nothing 31 | } 32 | 33 | @Override 34 | public void onPipelineAdded(IPipelineAction action) 35 | { 36 | addAction(action); 37 | } 38 | 39 | @Override 40 | public void onPipelineRemoved(IPipelineAction action) 41 | { 42 | removeAction(action); 43 | } 44 | } 45 | 46 | private final List scenes = new CopyOnWriteArrayList<>(); 47 | private final ScenePipelineListener listener = new ScenePipelineListener(); 48 | 49 | private final List scenesReadOnly = Collections.unmodifiableList(scenes); 50 | 51 | /** 52 | * Adds a new scene to be managed by this game loop. This function does nothing 53 | * if the scene is null or is already managed by this game loop. 54 | * 55 | * @param scene 56 | * - The scene to add. 57 | */ 58 | public void addScene(Scene scene) 59 | { 60 | if (scene == null) 61 | return; 62 | 63 | if (scenes.contains(scene)) 64 | return; 65 | 66 | scenes.add(scene); 67 | scene.addListener(listener); 68 | 69 | for (IPipelineAction action : scene.pipelineActions()) 70 | addAction(action); 71 | } 72 | 73 | /** 74 | * Removes a scene from this game loop. This function does nothing if the scene 75 | * is null or is not managed by this game loop. 76 | * 77 | * @param scene 78 | * - The scene to remove. 79 | */ 80 | public void removeScene(Scene scene) 81 | { 82 | if (scene == null) 83 | return; 84 | 85 | if (!scenes.contains(scene)) 86 | return; 87 | 88 | scenes.remove(scene); 89 | scene.removeListener(listener); 90 | 91 | for (IPipelineAction action : scene.pipelineActions()) 92 | removeAction(action); 93 | } 94 | 95 | /** 96 | * Gets a read-only list of all the scenes currently being managed by this game 97 | * loop. 98 | * 99 | * @return A list of scenes. 100 | */ 101 | public List scenes() 102 | { 103 | return scenesReadOnly; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at thedudefromci@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/net/server/ServerClientTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.net.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertThrows; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.verify; 7 | import static org.mockito.Mockito.when; 8 | import static org.mockito.Mockito.any; 9 | import static org.mockito.Mockito.doThrow; 10 | import static org.mockito.Mockito.eq; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.net.SocketException; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | import net.whg.we.net.IDataHandler; 18 | import net.whg.we.net.IPacket; 19 | import net.whg.we.net.ISocket; 20 | import net.whg.we.net.server.ServerClient; 21 | 22 | public class ServerClientTest 23 | { 24 | private final String IP_VALUE = "192.168.1.5"; 25 | private final int PORT = 3445; 26 | private ISocket socket; 27 | private IDataHandler dataHandler; 28 | 29 | @Before 30 | public void init() throws IOException 31 | { 32 | var inputStream = mock(InputStream.class); 33 | var outputStream = mock(OutputStream.class); 34 | 35 | socket = mock(ISocket.class); 36 | when(socket.getIP()).thenReturn(IP_VALUE); 37 | when(socket.getPort()).thenReturn(PORT); 38 | when(socket.getInputStream()).thenReturn(inputStream); 39 | when(socket.getOutputStream()).thenReturn(outputStream); 40 | 41 | dataHandler = mock(IDataHandler.class); 42 | } 43 | 44 | @Test 45 | public void getConnectionTest() throws IOException 46 | { 47 | var client = new ServerClient(socket, dataHandler); 48 | var connection = client.getConnection(); 49 | 50 | assertEquals(IP_VALUE, connection.getIP()); 51 | assertEquals(PORT, connection.getPort()); 52 | } 53 | 54 | @Test 55 | public void kickClient() throws IOException 56 | { 57 | var client = new ServerClient(socket, dataHandler); 58 | 59 | client.kick(); 60 | 61 | verify(socket).close(); 62 | } 63 | 64 | @Test 65 | public void readPacket() throws IOException 66 | { 67 | var client = new ServerClient(socket, dataHandler); 68 | 69 | var packet = mock(IPacket.class); 70 | when(dataHandler.readPacket(any(), any())).thenReturn(packet); 71 | 72 | assertEquals(packet, client.readPacket()); 73 | } 74 | 75 | @Test 76 | public void readPacket_socketClosed() throws IOException 77 | { 78 | var client = new ServerClient(socket, dataHandler); 79 | 80 | when(dataHandler.readPacket(any(), any())).thenThrow(new SocketException()); 81 | 82 | assertThrows(SocketException.class, () -> client.readPacket()); 83 | } 84 | 85 | @Test 86 | public void writePacket() throws IOException 87 | { 88 | var client = new ServerClient(socket, dataHandler); 89 | 90 | var packet = mock(IPacket.class); 91 | client.sendPacket(packet); 92 | 93 | verify(dataHandler).writePacket(any(), eq(packet)); 94 | } 95 | 96 | @Test 97 | public void writePacket_socketClosed() throws IOException 98 | { 99 | var client = new ServerClient(socket, dataHandler); 100 | 101 | doThrow(SocketException.class).when(dataHandler) 102 | .writePacket(any(), any()); 103 | 104 | var packet = mock(IPacket.class); 105 | assertThrows(SocketException.class, () -> client.sendPacket(packet)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/TextureDataTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import org.junit.Test; 5 | import net.whg.we.rendering.TextureData; 6 | import net.whg.we.rendering.TextureData.NormalMapMode; 7 | import net.whg.we.rendering.TextureData.SampleMode; 8 | import net.whg.we.rendering.TextureData.WrapMode; 9 | import net.whg.we.rendering.Color; 10 | 11 | public class TextureDataTest 12 | { 13 | @Test 14 | public void createTexture_defaultValues() 15 | { 16 | TextureData data = new TextureData(16, 16); 17 | 18 | assertEquals(16, data.getWidth()); 19 | assertEquals(16, data.getHeight()); 20 | assertEquals(0, data.getPixel(8, 8)); 21 | 22 | assertEquals(SampleMode.BILINEAR, data.getSampleMode()); 23 | assertEquals(NormalMapMode.NON_NORMALMAP, data.getNormalMapMode()); 24 | assertEquals(WrapMode.REPEAT, data.getWrapMode()); 25 | assertEquals(1, data.getAnisotropicFiltering()); 26 | assertEquals(false, data.hasMipmap()); 27 | assertEquals(true, data.issRGB()); 28 | } 29 | 30 | @Test 31 | public void assignValues() 32 | { 33 | TextureData data = new TextureData(8, 24); 34 | 35 | data.setSampleMode(SampleMode.TRILINEAR); 36 | assertEquals(SampleMode.TRILINEAR, data.getSampleMode()); 37 | 38 | data.setNormalMapMode(NormalMapMode.OPENGL_STYLE); 39 | assertEquals(NormalMapMode.OPENGL_STYLE, data.getNormalMapMode()); 40 | 41 | data.setWrapMode(WrapMode.CLAMP); 42 | assertEquals(WrapMode.CLAMP, data.getWrapMode()); 43 | 44 | data.setAnisotropicFiltering(4); 45 | assertEquals(4, data.getAnisotropicFiltering()); 46 | 47 | data.setMipmap(true); 48 | assertEquals(true, data.hasMipmap()); 49 | 50 | data.setsRGB(false); 51 | assertEquals(false, data.issRGB()); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void createTexture_negativeSizeX() 56 | { 57 | new TextureData(-11, 10); 58 | } 59 | 60 | @Test(expected = IllegalArgumentException.class) 61 | public void createTexture_negativeSizeY() 62 | { 63 | new TextureData(5, -4); 64 | } 65 | 66 | @Test(expected = IllegalArgumentException.class) 67 | public void setSampleMode_null() 68 | { 69 | new TextureData(1, 1).setSampleMode(null); 70 | ; 71 | } 72 | 73 | @Test(expected = IllegalArgumentException.class) 74 | public void setNormalMapMode_null() 75 | { 76 | new TextureData(1, 1).setNormalMapMode(null); 77 | ; 78 | } 79 | 80 | @Test(expected = IllegalArgumentException.class) 81 | public void setWrapMode_null() 82 | { 83 | new TextureData(1, 1).setWrapMode(null); 84 | ; 85 | } 86 | 87 | @Test(expected = IllegalArgumentException.class) 88 | public void setAniostropicFiltering_negative() 89 | { 90 | new TextureData(1, 1).setAnisotropicFiltering(-2); 91 | } 92 | 93 | @Test(expected = IllegalArgumentException.class) 94 | public void setAniostropicFiltering_tooHigh() 95 | { 96 | new TextureData(1, 1).setAnisotropicFiltering(19); 97 | } 98 | 99 | @Test 100 | public void setPixel() 101 | { 102 | TextureData data = new TextureData(8, 8); 103 | 104 | data.setPixel(5, 3, 370); 105 | assertEquals(370, data.getPixel(5, 3)); 106 | } 107 | 108 | @Test 109 | public void setPixel_asColor() 110 | { 111 | TextureData data = new TextureData(8, 8); 112 | 113 | data.setPixelAsColor(5, 3, new Color(0.2f, 0.4f, 0.8f)); 114 | assertEquals(new Color(0.2f, 0.4f, 0.8f), data.getPixelAsColor(5, 3)); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/opengl/GLMesh.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering.opengl; 2 | 3 | import java.nio.FloatBuffer; 4 | import java.nio.ShortBuffer; 5 | import org.lwjgl.BufferUtils; 6 | import net.whg.we.rendering.IMesh; 7 | import net.whg.we.rendering.ShaderAttributes; 8 | import net.whg.we.rendering.VertexData; 9 | 10 | public class GLMesh implements IMesh 11 | { 12 | private final IOpenGL opengl; 13 | private final BindStates bindStates; 14 | private int vboId; 15 | private int vaoId; 16 | private int indexId; 17 | private int indexCount; 18 | private boolean disposed; 19 | private boolean created; 20 | 21 | /** 22 | * Creates a new OpenGL mesh object. 23 | * 24 | * @param bindStates 25 | * - The container for binding GPU states. 26 | * @param opengl 27 | * - The OpenGL instance. 28 | */ 29 | GLMesh(BindStates bindStates, IOpenGL opengl) 30 | { 31 | this.bindStates = bindStates; 32 | this.opengl = opengl; 33 | } 34 | 35 | @Override 36 | public void dispose() 37 | { 38 | if (isDisposed()) 39 | return; 40 | 41 | disposed = true; 42 | 43 | if (created) 44 | { 45 | if (bindStates.getBoundVao() == vaoId) 46 | bindStates.bindVao(0); 47 | 48 | if (bindStates.getBoundBuffer() == indexId) 49 | bindStates.bindBuffer(true, 0); 50 | 51 | opengl.deleteBuffer(vboId); 52 | opengl.deleteBuffer(indexId); 53 | opengl.deleteVertexArray(vaoId); 54 | } 55 | } 56 | 57 | @Override 58 | public boolean isDisposed() 59 | { 60 | return disposed; 61 | } 62 | 63 | @Override 64 | public void render() 65 | { 66 | if (isDisposed()) 67 | throw new IllegalStateException("Mesh already disposed!"); 68 | 69 | if (!created) 70 | return; 71 | 72 | bindStates.bindVao(vaoId); 73 | bindStates.bindBuffer(true, indexId); 74 | opengl.drawElements(indexCount); 75 | } 76 | 77 | @Override 78 | public void update(VertexData vertexData) 79 | { 80 | if (isDisposed()) 81 | throw new IllegalStateException("Mesh already disposed!"); 82 | 83 | FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertexData.getDataArray().length); 84 | vertexBuffer.put(vertexData.getDataArray()); 85 | vertexBuffer.flip(); 86 | 87 | ShortBuffer indexBuffer = BufferUtils.createShortBuffer(vertexData.getTriangles().length); 88 | indexBuffer.put(vertexData.getTriangles()); 89 | indexBuffer.flip(); 90 | 91 | if (!created) 92 | { 93 | vaoId = opengl.generateVertexArray(); 94 | vboId = opengl.generateBuffer(); 95 | indexId = opengl.generateBuffer(); 96 | } 97 | 98 | bindStates.bindVao(vaoId); 99 | 100 | bindStates.bindBuffer(false, vboId); 101 | opengl.uploadBufferData(vertexBuffer); 102 | 103 | int stride = vertexData.getVertexByteSize(); 104 | int offset = 0; 105 | 106 | ShaderAttributes attributes = vertexData.getAttributeSizes(); 107 | for (int i = 0; i < attributes.getCount(); i++) 108 | { 109 | opengl.setVertexAttributePointer(i, attributes.getAttributeSize(i), stride, offset); 110 | offset += attributes.getAttributeSize(i) * 4; 111 | } 112 | 113 | bindStates.bindBuffer(true, indexId); 114 | opengl.uploadBufferData(indexBuffer); 115 | 116 | created = true; 117 | indexCount = vertexData.getTriangles().length; 118 | } 119 | 120 | @Override 121 | public boolean isCreated() 122 | { 123 | return created; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/main/SceneGameLoopTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.main; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | import static org.mockito.ArgumentMatchers.any; 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.times; 9 | import static org.mockito.Mockito.verify; 10 | import org.junit.Test; 11 | import net.whg.we.main.IPipelineAction; 12 | import net.whg.we.main.Scene; 13 | import net.whg.we.main.SceneGameLoop; 14 | 15 | public class SceneGameLoopTest 16 | { 17 | @Test 18 | public void addRemoveScene() 19 | { 20 | SceneGameLoop gameLoop = new SceneGameLoop(); 21 | assertTrue(gameLoop.scenes() 22 | .isEmpty()); 23 | 24 | Scene scene = mock(Scene.class); 25 | gameLoop.addScene(scene); 26 | gameLoop.addScene(scene); 27 | gameLoop.addScene(null); 28 | 29 | verify(scene, times(1)).addListener(any()); 30 | 31 | assertEquals(1, gameLoop.scenes() 32 | .size()); 33 | 34 | gameLoop.removeScene(scene); 35 | gameLoop.removeScene(scene); 36 | gameLoop.removeScene(null); 37 | 38 | verify(scene, times(1)).removeListener(any()); 39 | 40 | assertTrue(gameLoop.scenes() 41 | .isEmpty()); 42 | } 43 | 44 | @Test 45 | public void sceneModified_isPipelineAdded() 46 | { 47 | Scene scene = new Scene(); 48 | SceneGameLoop gameLoop = new SceneGameLoop(); 49 | gameLoop.addScene(scene); 50 | 51 | IPipelineAction action = mock(IPipelineAction.class); 52 | scene.addPipelineAction(action); 53 | assertEquals(1, gameLoop.loopActions() 54 | .size()); 55 | 56 | scene.removePipelineAction(action); 57 | assertEquals(0, gameLoop.loopActions() 58 | .size()); 59 | } 60 | 61 | @Test 62 | public void sceneAdded_addExistingActions() 63 | { 64 | IPipelineAction action1 = mock(IPipelineAction.class); 65 | IPipelineAction action2 = mock(IPipelineAction.class); 66 | IPipelineAction action3 = mock(IPipelineAction.class); 67 | 68 | Scene scene = new Scene(); 69 | scene.addPipelineAction(action1); 70 | scene.addPipelineAction(action2); 71 | scene.addPipelineAction(action3); 72 | 73 | SceneGameLoop gameLoop = new SceneGameLoop(); 74 | gameLoop.addScene(scene); 75 | 76 | assertTrue(gameLoop.loopActions() 77 | .contains(action1)); 78 | assertTrue(gameLoop.loopActions() 79 | .contains(action2)); 80 | assertTrue(gameLoop.loopActions() 81 | .contains(action3)); 82 | } 83 | 84 | @Test 85 | public void sceneRemoved_removeExistingActions() 86 | { 87 | Scene scene = new Scene(); 88 | SceneGameLoop gameLoop = new SceneGameLoop(); 89 | gameLoop.addScene(scene); 90 | 91 | IPipelineAction action1 = mock(IPipelineAction.class); 92 | IPipelineAction action2 = mock(IPipelineAction.class); 93 | IPipelineAction action3 = mock(IPipelineAction.class); 94 | scene.addPipelineAction(action1); 95 | scene.addPipelineAction(action2); 96 | scene.addPipelineAction(action3); 97 | 98 | gameLoop.removeScene(scene); 99 | 100 | assertFalse(gameLoop.loopActions() 101 | .contains(action1)); 102 | assertFalse(gameLoop.loopActions() 103 | .contains(action2)); 104 | assertFalse(gameLoop.loopActions() 105 | .contains(action3)); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/Camera.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | import org.joml.Matrix4f; 4 | import net.whg.we.main.Transform3D; 5 | import net.whg.we.window.Screen; 6 | 7 | /** 8 | * The camera is the object in charge of determing the projection and view 9 | * matrices for how a scene should be rendered. 10 | */ 11 | public class Camera 12 | { 13 | private final Matrix4f projectionMatrix = new Matrix4f(); 14 | private final Transform3D transform; 15 | private final Screen screen; 16 | private float fov = (float) Math.toRadians(90f); 17 | private float nearClip = 0.1f; 18 | private float farClip = 1000f; 19 | 20 | /** 21 | * Creates a new camera object with the default projection matrix. 22 | * 23 | * @param screen 24 | * - The screen this camera pulls information from. 25 | */ 26 | public Camera(Screen screen) 27 | { 28 | this(new Transform3D(), screen); 29 | } 30 | 31 | /** 32 | * Creates a new camera object with the default projection matrix. The transform 33 | * for this camera is maintained externally, such as being attached to a game 34 | * object, and will return the given transform when {@link #getTransform()} is 35 | * called. 36 | * 37 | * @param transform 38 | * - The transform this camera should use. 39 | * @param screen 40 | * - The screen this camera pulls information from. 41 | */ 42 | public Camera(Transform3D transform, Screen screen) 43 | { 44 | this.transform = transform; 45 | this.screen = screen; 46 | rebuildProjectionMatrix(); 47 | } 48 | 49 | /** 50 | * Updates the current projection matrix to match the current settings. 51 | */ 52 | private void rebuildProjectionMatrix() 53 | { 54 | float aspect = screen.getAspect(); 55 | 56 | projectionMatrix.identity(); 57 | projectionMatrix.perspective(fov, aspect, nearClip, farClip); 58 | } 59 | 60 | /** 61 | * Gets the current projection matrix generated by this camera. 62 | * 63 | * @return The projection matrix. 64 | */ 65 | public Matrix4f getProjectionMatrix() 66 | { 67 | return projectionMatrix; 68 | } 69 | 70 | /** 71 | * Gets the current field of view for this camera. 72 | * 73 | * @return The field of view. 74 | */ 75 | public float getFov() 76 | { 77 | return fov; 78 | } 79 | 80 | /** 81 | * Assigns a new field of view for this camera. 82 | * 83 | * @param fov 84 | * - The new field of view for this camera, in radians. 85 | */ 86 | public void setFov(float fov) 87 | { 88 | this.fov = fov; 89 | rebuildProjectionMatrix(); 90 | } 91 | 92 | /** 93 | * Gets the near clipping plane distance. 94 | * 95 | * @return The near clipping plane. 96 | */ 97 | public float getNearClip() 98 | { 99 | return nearClip; 100 | } 101 | 102 | /** 103 | * Gets the far clipping place distance. 104 | * 105 | * @return The far clipping plane. 106 | */ 107 | public float getFarClip() 108 | { 109 | return farClip; 110 | } 111 | 112 | /** 113 | * Assigns the distances for the clipping planes. 114 | * 115 | * @param near 116 | * - The distance to the near clipping plane. 117 | * @param far 118 | * - The distance to the far clipping plane. 119 | */ 120 | public void setClippingDistance(float near, float far) 121 | { 122 | nearClip = near; 123 | farClip = far; 124 | 125 | rebuildProjectionMatrix(); 126 | } 127 | 128 | /** 129 | * Gets the transformation object for this camera. 130 | * 131 | * @return The transformation object. 132 | */ 133 | public Transform3D getTransform() 134 | { 135 | return transform; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/ColorTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotEquals; 6 | import org.junit.Test; 7 | import net.whg.we.rendering.Color; 8 | 9 | public class ColorTest 10 | { 11 | @Test 12 | public void createColor() 13 | { 14 | Color color = new Color(1f, 0.75f, 0.5f); 15 | 16 | assertEquals(1f, color.getRed(), 0f); 17 | assertEquals(0.75f, color.getGreen(), 0f); 18 | assertEquals(0.5f, color.getBlue(), 0f); 19 | assertEquals(1f, color.getAlpha(), 0f); 20 | } 21 | 22 | @Test 23 | public void createColor_withAlpha() 24 | { 25 | Color color = new Color(0.2f, 0.4f, 0.6f, 0.23f); 26 | 27 | assertEquals(0.2f, color.getRed(), 0f); 28 | assertEquals(0.4f, color.getGreen(), 0f); 29 | assertEquals(0.6f, color.getBlue(), 0f); 30 | assertEquals(0.23f, color.getAlpha(), 0f); 31 | } 32 | 33 | @Test 34 | public void createColor_outOfBoundsChannel_error() 35 | { 36 | float[][] channels = new float[][] {{0.7f, -0.1f, 0.6f}, {-0.24f, -0.1f, 0.26f}, {10.24f, 0.51f, 0.216f}, 37 | {0.24f, 0.1f, 20.26f}, {0.24f, 5.1f, -0.26f}, {0.1f, 0.2f, 0.3f, -1f}, {0.4f, 0.5f, 0.6f, 100f}}; 38 | 39 | int captures = 0; 40 | for (int i = 0; i < channels.length; i++) 41 | { 42 | try 43 | { 44 | if (channels[i].length == 3) 45 | new Color(channels[i][0], channels[i][1], channels[i][2]); 46 | else 47 | new Color(channels[i][0], channels[i][1], channels[i][2], channels[i][3]); 48 | 49 | throw new RuntimeException(); 50 | } 51 | catch (IllegalArgumentException e) 52 | { 53 | captures++; 54 | } 55 | } 56 | 57 | assertEquals(channels.length, captures); 58 | } 59 | 60 | @Test 61 | public void equals_sameInstance() 62 | { 63 | Color a = new Color(0.5f, 0.15f, 0.8f); 64 | 65 | assertEquals(a, a); 66 | assertEquals(a.hashCode(), a.hashCode()); 67 | } 68 | 69 | @Test 70 | public void equals_diffInstance() 71 | { 72 | Color a = new Color(0.5f, 0.15f, 0.8f); 73 | Color b = new Color(0.5f, 0.15f, 0.8f); 74 | 75 | assertEquals(a, b); 76 | assertEquals(a.hashCode(), b.hashCode()); 77 | } 78 | 79 | @Test 80 | public void equals_diffInstance_diffData() 81 | { 82 | Color a = new Color(0.5f, 0.15f, 0.8f); 83 | Color b = new Color(0.2f, 0.3f, 0.4f); 84 | 85 | assertNotEquals(a, b); 86 | assertNotEquals(a.hashCode(), b.hashCode()); 87 | } 88 | 89 | @Test 90 | public void equals_diffInstance_diffData2() 91 | { 92 | assertNotEquals(new Color(0.1f, 0.2f, 0.3f, 0.4f), new Color(0.1f, 0.2f, 0.3f, 0.5f)); 93 | assertNotEquals(new Color(0.1f, 0.2f, 0.3f, 0.4f), new Color(0.1f, 0.2f, 0.5f, 0.4f)); 94 | assertNotEquals(new Color(0.1f, 0.2f, 0.3f, 0.4f), new Color(0.1f, 0.5f, 0.3f, 0.4f)); 95 | assertNotEquals(new Color(0.1f, 0.2f, 0.3f, 0.4f), new Color(0.5f, 0.2f, 0.3f, 0.4f)); 96 | 97 | assertFalse(new Color(0f, 0f, 0f).equals(null)); 98 | assertFalse(new Color(0f, 0f, 0f).equals(new Object())); 99 | } 100 | 101 | @Test 102 | public void toString_noAlpha() 103 | { 104 | Color c = new Color(0.2f, 0.4f, 0.8f); 105 | String target = "Color(0.2, 0.4, 0.8)"; 106 | 107 | assertEquals(target, c.toString()); 108 | } 109 | 110 | @Test 111 | public void toString_withAlpha() 112 | { 113 | Color c = new Color(0.2f, 0.4f, 0.8f, 0.5f); 114 | String target = "Color(0.2, 0.4, 0.8, 0.5)"; 115 | 116 | assertEquals(target, c.toString()); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/main/Timer.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.main; 2 | 3 | /** 4 | * The time class is used to contain information about the current time states 5 | * of the game. This includes time steps, running time, physics frame rate, and 6 | * frame delta times. 7 | */ 8 | public class Timer 9 | { 10 | private static final String TIMER_NOT_STARTED = "Timer not started!"; 11 | 12 | private final ITimeSupplier timeSupplier; 13 | private boolean running; 14 | private long startTime; 15 | private long elapsedTime; 16 | private long lastFrame; 17 | private float deltaTime; 18 | 19 | /** 20 | * Creates a new timer object. 21 | * 22 | * @param timeSupplier 23 | * - The object in charge of providing the system time. 24 | */ 25 | public Timer(ITimeSupplier timeSupplier) 26 | { 27 | this.timeSupplier = timeSupplier; 28 | } 29 | 30 | /** 31 | * Starts, (resetting all values if necessary), the timer. This will record the 32 | * current time in nano seconds and preform all time-based operations based on 33 | * this value. If attached to a game loop, this should be called right as the 34 | * game loop is started. If this timer is already running, it is simply reset 35 | * and allowed to continue running. 36 | */ 37 | public void startTimer() 38 | { 39 | running = true; 40 | startTime = timeSupplier.nanoTime(); 41 | 42 | elapsedTime = 0; 43 | deltaTime = 0; 44 | lastFrame = startTime; 45 | } 46 | 47 | /** 48 | * Stops the timer, allowing target framerate modifications to be made on this 49 | * timer. 50 | */ 51 | public void stopTimer() 52 | { 53 | running = false; 54 | } 55 | 56 | /** 57 | * Gets whether or not this timer is currently running. 58 | * 59 | * @return True if this timer is currently running. False otherwise. 60 | */ 61 | public boolean isRunning() 62 | { 63 | return running; 64 | } 65 | 66 | /** 67 | * Gets the amount of time which has passed since the timer was started. 68 | * 69 | * @return The number of seconds this timer has been running. 70 | */ 71 | public double getElapsedTime() 72 | { 73 | return elapsedTime / 1.0e9; 74 | } 75 | 76 | /** 77 | * Gets the time which has passed since the previous render frame. 78 | * 79 | * @return The time in seconds. 80 | */ 81 | public float getDeltaTime() 82 | { 83 | return deltaTime; 84 | } 85 | 86 | /** 87 | * Gets the current frames per second. This method only calculates the Fps based 88 | * on the current frame delta, and applies no smoothing. 89 | * 90 | * @return The current fps. 91 | */ 92 | public float getFps() 93 | { 94 | return 1f / deltaTime; 95 | } 96 | 97 | /** 98 | * Called at the beginning of each frame to calculate time updates such as delta 99 | * time, elapsed time, etc. 100 | * 101 | * @throws IllegalStateException 102 | * If the timer has not been started. 103 | */ 104 | public void beginFrame() 105 | { 106 | if (!running) 107 | throw new IllegalStateException(TIMER_NOT_STARTED); 108 | 109 | long time = timeSupplier.nanoTime(); 110 | elapsedTime = time - startTime; 111 | deltaTime = (float) ((time - lastFrame) / 1.0e9); 112 | 113 | lastFrame = time; 114 | } 115 | 116 | /** 117 | * Causes the current thread to sleep for the given number of seconds. 118 | * 119 | * @param seconds 120 | * - The number of seconds to sleep. 121 | */ 122 | public void sleep(float seconds) 123 | { 124 | if (seconds <= 0) 125 | return; 126 | 127 | long ms = (long) (seconds * 1000); 128 | int ns = (int) ((seconds % 0.001) * 1.0e+9); 129 | 130 | timeSupplier.sleep(ms, ns); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/window/IWindowListener.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.window; 2 | 3 | /** 4 | * The window listener object receives events from the window handler object as 5 | * these events occur. All events are called on the main thread during calls to 6 | * the window, such as polling events. 7 | */ 8 | public interface IWindowListener 9 | { 10 | /** 11 | * Called when the settings of a window have been updated from inside 12 | * WraithEngine. This includes actions such as requesting a title update, or 13 | * toggling fullscreen. 14 | * 15 | * @param window 16 | * - The window which was updated. 17 | */ 18 | void onWindowUpdated(IWindow window); 19 | 20 | /** 21 | * Called when the window is resized. 22 | * 23 | * @param window 24 | * - The window which was resized. 25 | * @param width 26 | * - The new window width. 27 | * @param height 28 | * - The new window height. 29 | */ 30 | void onWindowResized(IWindow window, int width, int height); 31 | 32 | /** 33 | * Called when the window has been disposed. This is called after the disposing 34 | * process is finished. 35 | * 36 | * @param window 37 | * - The window which was destroyed. 38 | */ 39 | void onWindowDestroyed(IWindow window); 40 | 41 | /** 42 | * Called when a request was made by the system to close the window. 43 | * 44 | * @param window 45 | * - The window in which this event occured. 46 | */ 47 | void onWindowRequestClose(IWindow window); 48 | 49 | /** 50 | * Called when the user moves their mouse over the window. 51 | * 52 | * @param window 53 | * - The window in which this event occured. 54 | * @param newX 55 | * - The new x position of the mouse relative to the window. 56 | * @param newY 57 | * - The new y position of the mouse relative to the window. 58 | */ 59 | void onMouseMove(IWindow window, float newX, float newY); 60 | 61 | /** 62 | * Called when the user presses a key while the window is focused. 63 | * 64 | * @param window 65 | * - The window in which this event occured. 66 | * @param keyCode 67 | * - The key code for the key which was pressed. 68 | */ 69 | void onKeyPressed(IWindow window, int keyCode); 70 | 71 | /** 72 | * Called when the user releases a key while the window is focused. 73 | * 74 | * @param window 75 | * - The window in which this event occured. 76 | * @param keyCode 77 | * - The key code for the key which was released. 78 | */ 79 | void onKeyReleased(IWindow window, int keyCode); 80 | 81 | /** 82 | * Called when the user presses a mouse button while the window is focused. 83 | * 84 | * @param window 85 | * - The window in which this event occured. 86 | * @param mouseButton 87 | * - The button index on the mouse which was pressed. 88 | */ 89 | void onMousePressed(IWindow window, int mouseButton); 90 | 91 | /** 92 | * Called when the user releases a mouse button while the window is focused. 93 | * 94 | * @param window 95 | * - The window in which this event occured. 96 | * @param mouseButton 97 | * - The button index on the mouse which was released. 98 | */ 99 | void onMouseReleased(IWindow window, int mouseButton); 100 | 101 | /** 102 | * Called when the user scrolls the verticle or horizontal mouse wheel while the 103 | * window is focused. 104 | * 105 | * @param window 106 | * - The window in which this event occured. 107 | * @param scrollX 108 | * - How much the mouse wheel was scrolled in the x direction. 109 | * @param scrollY 110 | * - How much the mouse wheel was scrolled in the y direction. 111 | */ 112 | void onMouseWheel(IWindow window, float scrollX, float scrollY); 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/rendering/Color.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.rendering; 2 | 3 | /** 4 | * The color object is used to store an RGBA color in a float-based data 5 | * storage. All values are stored on a scale of 0 to 1. 6 | */ 7 | public class Color 8 | { 9 | private float red; 10 | private float green; 11 | private float blue; 12 | private float alpha; 13 | 14 | /** 15 | * Creates a new color object, with the alpha set to 1. 16 | * 17 | * @param red 18 | * - The red component. 19 | * @param green 20 | * - The green component. 21 | * @param blue 22 | * - The blue component. 23 | * @throws IllegalArgumentException 24 | * If any of the components are out of the range 0-1, inclusive. 25 | */ 26 | public Color(float red, float green, float blue) 27 | { 28 | this(red, green, blue, 1f); 29 | } 30 | 31 | /** 32 | * Creates a new color object. 33 | * 34 | * @param red 35 | * - The red component. 36 | * @param green 37 | * - The green component. 38 | * @param blue 39 | * - The blue component. 40 | * @param alpha 41 | * - The alpha component. 42 | * @throws IllegalArgumentException 43 | * If any of the components are out of the range 0-1, inclusive. 44 | */ 45 | public Color(float red, float green, float blue, float alpha) 46 | { 47 | if (red < 0 || red > 1 || green < 0 || green > 1 || blue < 0 || blue > 1 || alpha < 0 || alpha > 1) 48 | throw new IllegalArgumentException("Components must be between 0 and 1!"); 49 | 50 | this.red = red; 51 | this.green = green; 52 | this.blue = blue; 53 | this.alpha = alpha; 54 | } 55 | 56 | /** 57 | * Gets the red component of this color. 58 | * 59 | * @return A value between 0 and 1, representing the red channel. 60 | */ 61 | public float getRed() 62 | { 63 | return red; 64 | } 65 | 66 | /** 67 | * Gets the green component of this color. 68 | * 69 | * @return A value between 0 and 1, representing the green channel. 70 | */ 71 | public float getGreen() 72 | { 73 | return green; 74 | } 75 | 76 | /** 77 | * Gets the blue component of this color. 78 | * 79 | * @return A value between 0 and 1, representing the blue channel. 80 | */ 81 | public float getBlue() 82 | { 83 | return blue; 84 | } 85 | 86 | /** 87 | * Gets the alpha component of this color. 88 | * 89 | * @return A value between 0 and 1, representing the alpha channel. 90 | */ 91 | public float getAlpha() 92 | { 93 | return alpha; 94 | } 95 | 96 | @Override 97 | public int hashCode() 98 | { 99 | final int prime = 31; 100 | int result = 1; 101 | result = prime * result + (int) Math.floor(alpha * 10000); 102 | result = prime * result + (int) Math.floor(red * 10000); 103 | result = prime * result + (int) Math.floor(green * 10000); 104 | result = prime * result + (int) Math.floor(blue * 10000); 105 | return result; 106 | } 107 | 108 | @Override 109 | public boolean equals(Object obj) 110 | { 111 | if (this == obj) 112 | return true; 113 | 114 | if (obj == null) 115 | return false; 116 | 117 | if (getClass() != obj.getClass()) 118 | return false; 119 | 120 | Color other = (Color) obj; 121 | 122 | final float epsilon = 0.0001f; 123 | if (Math.abs(alpha - other.alpha) >= epsilon) 124 | return false; 125 | 126 | if (Math.abs(red - other.red) >= epsilon) 127 | return false; 128 | 129 | if (Math.abs(green - other.green) >= epsilon) 130 | return false; 131 | 132 | return Math.abs(blue - other.blue) < epsilon; 133 | } 134 | 135 | @Override 136 | public String toString() 137 | { 138 | if (Math.abs(alpha - 1f) < 0.0001f) 139 | return String.format("Color(%s, %s, %s)", red, green, blue); 140 | 141 | return String.format("Color(%s, %s, %s, %s)", red, green, blue, alpha); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/assimp/IAssimpMesh.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource.assimp; 2 | 3 | /** 4 | * Represents a mesh which loaded from Assimp. 5 | */ 6 | public interface IAssimpMesh 7 | { 8 | /** 9 | * Counts the number of bones in this mesh. 10 | * 11 | * @return The number of bones. 12 | */ 13 | int countBones(); 14 | 15 | /** 16 | * Counts the number of vertices in this mesh. 17 | * 18 | * @return The number of vertices. 19 | */ 20 | int countVertices(); 21 | 22 | /** 23 | * Counts the number of triangles in this mesh. 24 | * 25 | * @return The number of triangles. 26 | */ 27 | int countTriangles(); 28 | 29 | /** 30 | * Counts the number of UV layers in this mesh. 31 | * 32 | * @return The number of UV layers. 33 | */ 34 | int countUVLayers(); 35 | 36 | /** 37 | * Gets the vertex position at the specified index, and places it into the data 38 | * array at the given position. A vertex position uses exactly three floats. 39 | * These are written to the array in order. 40 | * 41 | * @param data 42 | * - The data array to write the vertex position to. 43 | * @param pos 44 | * - The position within the array to write the vertex position to. 45 | * @param index 46 | * - The index of the vertex to get. 47 | */ 48 | void getVertexPosition(float[] data, int pos, int index); 49 | 50 | /** 51 | * Gets the vertex normal at the specified index, and places it into the data 52 | * array at the given position. A vertex normal uses exactly three floats. These 53 | * are written to the array in order. 54 | * 55 | * @param data 56 | * - The data array to write the vertex normal to. 57 | * @param pos 58 | * - The position within the array to write the vertex normal to. 59 | * @param index 60 | * - The index of the vertex to get. 61 | */ 62 | void getVertexNormal(float[] data, int pos, int index); 63 | 64 | /** 65 | * Gets the vertex tangent at the specified index, and places it into the data 66 | * array at the given position. A vertex tangent uses exactly three floats. 67 | * These are written to the array in order. 68 | * 69 | * @param data 70 | * - The data array to write the vertex tangent to. 71 | * @param pos 72 | * - The position within the array to write the vertex tangent to. 73 | * @param index 74 | * - The index of the vertex to get. 75 | */ 76 | void getVertexTangent(float[] data, int pos, int index); 77 | 78 | /** 79 | * Gets the vertex bitangent at the specified index, and places it into the data 80 | * array at the given position. A vertex bitangent uses exactly three floats. 81 | * These are written to the array in order. 82 | * 83 | * @param data 84 | * - The data array to write the vertex bitangent to. 85 | * @param pos 86 | * - The position within the array to write the vertex bitangent to. 87 | * @param index 88 | * - The index of the vertex to get. 89 | */ 90 | void getVertexBitangent(float[] data, int pos, int index); 91 | 92 | /** 93 | * Gets the vertex uv at the specified index and layer, and places it into the 94 | * data array at the given position. A vertex uv uses exactly two floats. These 95 | * are written to the array in order. 96 | * 97 | * @param data 98 | * - The data array to write the vertex bitangent to. 99 | * @param pos 100 | * - The position within the array to write the vertex uv to. 101 | * @param index 102 | * - The index of the vertex to get. 103 | * @param uvLayer 104 | * - The layer of the vertex uv to get. 105 | */ 106 | void getVertexUV(float[] data, int pos, int index, int uvLayer); 107 | 108 | /** 109 | * Gets the triangle at the specified index, and places it into the data array 110 | * at the given position. A triangle uses exactly three shorts, representing 111 | * pointers to vertex indices. These are written to the array in order. 112 | * 113 | * @param data 114 | * - The data array to write the triangle to. 115 | * @param pos 116 | * - The position within the array to write the triangle to. 117 | * @param index 118 | * - The index of the triangle to get. 119 | */ 120 | void getTriangle(short[] data, int pos, int index); 121 | } 122 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - "src/**" 7 | - "pom.xml" 8 | - ".github/**" 9 | pull_request: 10 | paths: 11 | - "src/**" 12 | - "pom.xml" 13 | - ".github/**" 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v2 23 | 24 | - name: Setup JDK13 25 | uses: actions/setup-java@v1 26 | with: 27 | java-version: 13 28 | java-package: jdk 29 | architecture: x64 30 | 31 | - name: Cache maven build 32 | uses: actions/cache@v2 33 | with: 34 | path: ~/.m2/repository 35 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 36 | restore-keys: | 37 | ${{ runner.os }}-maven- 38 | 39 | - name: Deep clone (For Sonar blame details) 40 | run: git fetch --prune --unshallow 41 | 42 | - name: Build with Maven 43 | run: | 44 | mvn -B clean javadoc:jar test jacoco:report sonar:sonar 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 48 | 49 | - name: Show Error Logs 50 | if: failure() 51 | run: | 52 | cat hs_err_pid* 2> /dev/null || true 53 | rm hs_err_pid* 2> /dev/null || true 54 | 55 | deploy: 56 | name: Deploy 57 | needs: build 58 | runs-on: ubuntu-latest 59 | if: github.ref == 'refs/heads/master' 60 | 61 | steps: 62 | - name: Generate build number 63 | id: build_number 64 | uses: einaregilsson/build-number@v2 65 | with: 66 | token: ${{secrets.github_token}} 67 | 68 | - name: Print build number 69 | run: echo "Build number is $BUILD_NUMBER" 70 | 71 | - name: Checkout repo 72 | uses: actions/checkout@v2 73 | 74 | - name: Setup JDK13 75 | uses: actions/setup-java@v1 76 | with: 77 | java-version: 13 78 | java-package: jdk 79 | architecture: x64 80 | 81 | - name: Cache maven build 82 | uses: actions/cache@v2 83 | with: 84 | path: ~/.m2/repository 85 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} 86 | restore-keys: | 87 | ${{ runner.os }}-maven- 88 | 89 | - name: Apply build number 90 | run: | 91 | sed -i "s|dev_build|build_$BUILD_NUMBER|g" pom.xml 92 | 93 | - name: Configure Maven Deploy 94 | env: 95 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 96 | USERNAME: thedudefromci 97 | run: | 98 | mkdir -p ~/.m2 99 | echo "github${USERNAME}${GITHUB_TOKEN}" > ~/.m2/settings.xml 100 | mvn -B -DskipTests deploy 101 | 102 | - name: Create Release Version 103 | id: create_release 104 | uses: actions/create-release@v1 105 | env: 106 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 107 | with: 108 | tag_name: build_${{ steps.build_number.outputs.build_number }} 109 | release_name: Build ${{ steps.build_number.outputs.build_number }} 110 | draft: false 111 | prerelease: true 112 | body: | 113 | Auto-generated Build ${{ steps.build_number.outputs.build_number }}. 114 | 115 | - name: Create Zip Release 116 | run: | 117 | zip -j build target/wraithengine-*.jar 118 | zip -j lib target/lib/*.jar 119 | 120 | - name: Upload Release Zip 121 | uses: actions/upload-release-asset@v1.0.2 122 | env: 123 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 124 | with: 125 | upload_url: ${{ steps.create_release.outputs.upload_url }} 126 | asset_path: ./build.zip 127 | asset_name: build_${{ steps.build_number.outputs.build_number }}.zip 128 | asset_content_type: application/zip 129 | 130 | - name: Upload Libraries Zip 131 | uses: actions/upload-release-asset@v1.0.2 132 | env: 133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 134 | with: 135 | upload_url: ${{ steps.create_release.outputs.upload_url }} 136 | asset_path: ./lib.zip 137 | asset_name: build_${{ steps.build_number.outputs.build_number }}_lib.zip 138 | asset_content_type: application/zip 139 | -------------------------------------------------------------------------------- /src/main/java/net/whg/we/resource/ModelLoader.java: -------------------------------------------------------------------------------- 1 | package net.whg.we.resource; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import net.whg.we.rendering.ShaderAttributes; 11 | import net.whg.we.rendering.VertexData; 12 | import net.whg.we.resource.assimp.AssimpException; 13 | import net.whg.we.resource.assimp.IAssimp; 14 | import net.whg.we.resource.assimp.IAssimpMesh; 15 | import net.whg.we.resource.assimp.IAssimpScene; 16 | 17 | /** 18 | * The model loader is a utilty for loading 3D model file formats. 19 | */ 20 | public final class ModelLoader 21 | { 22 | private static final Logger logger = LoggerFactory.getLogger(ModelLoader.class); 23 | 24 | private final IAssimp assimp; 25 | 26 | public ModelLoader(final IAssimp assimp) 27 | { 28 | this.assimp = assimp; 29 | } 30 | 31 | /** 32 | * Loads the scene within the given file as a list of resources. 33 | * 34 | * @param file 35 | * - The file to load. 36 | * @return A list of resources which were loaded from this file. 37 | * @throws AssimpException 38 | * If the scene fails to load. 39 | * @throws FileNotFoundException 40 | * If the file cannot be found. 41 | * @throws IOException 42 | * If the file cannot be read. 43 | */ 44 | public List loadScene(final File file) throws IOException 45 | { 46 | logger.info("Loading model file '{}'", file); 47 | 48 | if (!file.exists()) 49 | throw new FileNotFoundException("Cannot find file: " + file); 50 | 51 | if (!file.canRead()) 52 | throw new IOException("Cannot read file: " + file); 53 | 54 | final IAssimpScene scene = assimp.loadScene(file); 55 | 56 | if (scene == null) 57 | throw new AssimpException("Failed to load scene!"); 58 | 59 | final List resources = new ArrayList<>(); 60 | 61 | final int meshCount = scene.countMeshes(); 62 | for (int i = 0; i < meshCount; i++) 63 | resources.add(loadMesh(scene.getMesh(i))); 64 | 65 | scene.dispose(); 66 | return resources; 67 | } 68 | 69 | /** 70 | * Loads the given Assimp mesh object. 71 | */ 72 | private static Resource loadMesh(final IAssimpMesh mesh) 73 | { 74 | // Count mesh information 75 | final int boneCount = mesh.countBones(); 76 | final int vertexCount = mesh.countVertices(); 77 | final int triCount = mesh.countTriangles(); 78 | 79 | final ShaderAttributes attributes = new ShaderAttributes(); 80 | attributes.addAttribute(ShaderAttributes.ATTRIB_POSITION, 3); 81 | attributes.addAttribute(ShaderAttributes.ATTRIB_NORMAL, 3); 82 | attributes.addAttribute(ShaderAttributes.ATTRIB_TANGENT, 3); 83 | attributes.addAttribute(ShaderAttributes.ATTRIB_BITANGENT, 3); 84 | 85 | final int uvCount = mesh.countUVLayers(); 86 | for (int i = 1; i <= uvCount; i++) 87 | attributes.addAttribute(ShaderAttributes.getIndexedAttribute(ShaderAttributes.ATTRIB_UV, i), 2); 88 | 89 | if (boneCount > 0) 90 | { 91 | attributes.addAttribute(ShaderAttributes.ATTRIB_BONE_INDICES, 4); 92 | attributes.addAttribute(ShaderAttributes.ATTRIB_BONE_WEIGHTS, 4); 93 | } 94 | 95 | int index = 0; 96 | final float[] vertices = new float[vertexCount * attributes.getVertexSize()]; 97 | for (int v = 0; v < vertexCount; v++) 98 | { 99 | mesh.getVertexPosition(vertices, index, v); 100 | index += 3; 101 | 102 | mesh.getVertexNormal(vertices, index, v); 103 | index += 3; 104 | 105 | mesh.getVertexTangent(vertices, index, v); 106 | index += 3; 107 | 108 | mesh.getVertexBitangent(vertices, index, v); 109 | index += 3; 110 | 111 | for (int uv = 0; uv < uvCount; uv++) 112 | { 113 | mesh.getVertexUV(vertices, index, v, uv); 114 | index += 2; 115 | } 116 | 117 | if (boneCount > 0) 118 | index += 8; 119 | } 120 | 121 | final short[] triangles = new short[triCount * 3]; 122 | 123 | index = 0; 124 | for (int f = 0; f < triCount; f++) 125 | { 126 | mesh.getTriangle(triangles, index, f); 127 | index += 3; 128 | } 129 | 130 | logger.debug("Loaded mesh with {} vertices, {} triangles, and {}", vertexCount, triCount, attributes); 131 | return new Resource(new VertexData(vertices, triangles, attributes)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/unit/engine/rendering/MaterialTest.java: -------------------------------------------------------------------------------- 1 | package unit.engine.rendering; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.mockito.ArgumentMatchers.any; 6 | import static org.mockito.ArgumentMatchers.eq; 7 | import static org.mockito.Mockito.mock; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.when; 10 | import org.joml.Matrix4f; 11 | import org.junit.Test; 12 | import net.whg.we.rendering.Material; 13 | import net.whg.we.window.Screen; 14 | import net.whg.we.rendering.Camera; 15 | import net.whg.we.rendering.IShader; 16 | import net.whg.we.rendering.ITexture; 17 | 18 | public class MaterialTest 19 | { 20 | @Test 21 | public void createMaterial_getShader() 22 | { 23 | IShader shader = mock(IShader.class); 24 | Material material = new Material(shader); 25 | 26 | assertEquals(shader, material.getShader()); 27 | } 28 | 29 | @Test(expected = IllegalArgumentException.class) 30 | public void createMaterial_nullShader() 31 | { 32 | new Material(null); 33 | } 34 | 35 | @Test(expected = IllegalArgumentException.class) 36 | public void createMaterial_disposedShader() 37 | { 38 | IShader shader = mock(IShader.class); 39 | when(shader.isDisposed()).thenReturn(true); 40 | 41 | new Material(shader); 42 | } 43 | 44 | @Test 45 | public void bind_shaderIsBound() 46 | { 47 | IShader shader = mock(IShader.class); 48 | Material material = new Material(shader); 49 | 50 | material.bind(); 51 | 52 | verify(shader).bind(); 53 | } 54 | 55 | @Test 56 | public void cameraMatrix() 57 | { 58 | IShader shader = mock(IShader.class); 59 | Material material = new Material(shader); 60 | 61 | Camera camera = new Camera(mock(Screen.class)); 62 | Matrix4f matrix = new Matrix4f(); 63 | 64 | material.setCameraMatrix(camera, matrix); 65 | 66 | verify(shader).setUniformMat4(eq(Material.UNIFORM_MVP), any()); 67 | } 68 | 69 | @Test 70 | public void setTextures() 71 | { 72 | IShader shader = mock(IShader.class); 73 | Material material = new Material(shader); 74 | 75 | ITexture texture1 = mock(ITexture.class); 76 | ITexture texture2 = mock(ITexture.class); 77 | 78 | ITexture[] textures = {texture1, texture2}; 79 | String[] uniforms = {"diffuse", "normal"}; 80 | 81 | material.setTextures(textures, uniforms); 82 | 83 | assertArrayEquals(textures, material.getTextures()); 84 | assertArrayEquals(uniforms, material.getTextureUniformNames()); 85 | } 86 | 87 | @Test 88 | public void setTextures_isBound() 89 | { 90 | IShader shader = mock(IShader.class); 91 | Material material = new Material(shader); 92 | 93 | ITexture texture1 = mock(ITexture.class); 94 | ITexture texture2 = mock(ITexture.class); 95 | 96 | ITexture[] textures = {texture1, texture2}; 97 | String[] uniforms = {"diffuse", "normal"}; 98 | 99 | material.setTextures(textures, uniforms); 100 | 101 | material.bind(); 102 | 103 | verify(texture1).bind(0); 104 | verify(texture2).bind(1); 105 | verify(shader).setUniformInt("diffuse", 0); 106 | verify(shader).setUniformInt("normal", 1); 107 | } 108 | 109 | @Test(expected = IllegalArgumentException.class) 110 | public void setTextures_differentArrayLengths() 111 | { 112 | IShader shader = mock(IShader.class); 113 | Material material = new Material(shader); 114 | 115 | ITexture texture1 = mock(ITexture.class); 116 | ITexture texture2 = mock(ITexture.class); 117 | 118 | ITexture[] textures = {texture1, texture2}; 119 | String[] uniforms = {"specular"}; 120 | 121 | material.setTextures(textures, uniforms); 122 | } 123 | 124 | @Test(expected = NullPointerException.class) 125 | public void setTextures_nullTextures() 126 | { 127 | IShader shader = mock(IShader.class); 128 | Material material = new Material(shader); 129 | 130 | String[] uniforms = {"specular"}; 131 | 132 | material.setTextures(null, uniforms); 133 | } 134 | 135 | @Test(expected = NullPointerException.class) 136 | public void setTextures_nullTextureNames() 137 | { 138 | IShader shader = mock(IShader.class); 139 | Material material = new Material(shader); 140 | 141 | ITexture texture1 = mock(ITexture.class); 142 | ITexture texture2 = mock(ITexture.class); 143 | 144 | ITexture[] textures = {texture1, texture2}; 145 | 146 | material.setTextures(textures, null); 147 | } 148 | 149 | @Test(expected = IllegalArgumentException.class) 150 | public void setTextures_tooManyElements() 151 | { 152 | IShader shader = mock(IShader.class); 153 | Material material = new Material(shader); 154 | 155 | ITexture[] textures = new ITexture[25]; 156 | String[] uniforms = new String[25]; 157 | 158 | material.setTextures(textures, uniforms); 159 | } 160 | } 161 | --------------------------------------------------------------------------------