├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs └── org.eclipse.m2e.core.prefs ├── Images ├── ModelViewer.PNG ├── Screenshot00.PNG ├── Screenshot01.PNG ├── Screenshot02.PNG ├── Screenshot03.PNG ├── Screenshot04.PNG └── SpaceshipGame.PNG ├── LICENSE ├── README.md ├── Tests ├── ModelViewer.jar └── SpaceshipGame.jar ├── pom.xml └── src ├── main └── java │ └── com │ └── johnsproject │ └── jgameengine │ ├── Engine.java │ ├── EngineStatistics.java │ ├── EngineWindow.java │ ├── GraphicsEngine.java │ ├── InputEngine.java │ ├── PhysicsEngine.java │ ├── event │ ├── EngineEvent.java │ ├── EngineKeyListener.java │ ├── EngineListener.java │ └── EngineMouseListener.java │ ├── io │ └── OBJImporter.java │ ├── model │ ├── Animation.java │ ├── AnimationFrame.java │ ├── Armature.java │ ├── Camera.java │ ├── Face.java │ ├── FrameBuffer.java │ ├── Frustum.java │ ├── FrustumType.java │ ├── Light.java │ ├── LightType.java │ ├── Material.java │ ├── Mesh.java │ ├── Model.java │ ├── RigidBody.java │ ├── Scene.java │ ├── SceneObject.java │ ├── Texture.java │ ├── Transform.java │ ├── Vertex.java │ └── VertexGroup.java │ ├── rasterization │ ├── LinearRasterizer2.java │ ├── LinearRasterizer4.java │ ├── LinearRasterizer6.java │ └── Rasterizer.java │ ├── shading │ ├── BasicShader.java │ ├── BasicThreadedShader.java │ ├── DirectionalLightShadowShader.java │ ├── FlatShader.java │ ├── ForwardShaderBuffer.java │ ├── GouraudShader.java │ ├── PhongShader.java │ ├── Shader.java │ ├── ShaderBuffer.java │ ├── SpotLightShadowShader.java │ └── ThreadedShader.java │ └── util │ ├── ColorUtils.java │ ├── FileUtils.java │ ├── FixedPointUtils.java │ ├── MatrixUtils.java │ ├── TransformationUtils.java │ └── VectorUtils.java └── test ├── java └── com │ └── johnsproject │ └── jgameengine │ ├── ModelViewer.java │ ├── SpaceshipGame.java │ ├── io │ └── OBJImporterTest.java │ └── util │ ├── FixedPointUtilsTest.java │ ├── MatrixUtilsTest.java │ └── VectorUtilsTest.java └── resources ├── DefaultTest.mtl ├── DefaultTest.obj ├── JohnsProjectLogo.png ├── SpaceshipGame ├── Spaceship.mtl ├── Spaceship.obj ├── SpaceshipFire.mtl ├── SpaceshipFire.obj ├── Terrain.mtl └── Terrain.obj ├── TestOBJ.mtl └── TestOBJ.obj /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .classpath 3 | .project 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | JGameEngine 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 | -------------------------------------------------------------------------------- /.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/=UTF-8 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.5 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 14 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 15 | org.eclipse.jdt.core.compiler.release=disabled 16 | org.eclipse.jdt.core.compiler.source=1.5 17 | -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /Images/ModelViewer.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/ModelViewer.PNG -------------------------------------------------------------------------------- /Images/Screenshot00.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/Screenshot00.PNG -------------------------------------------------------------------------------- /Images/Screenshot01.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/Screenshot01.PNG -------------------------------------------------------------------------------- /Images/Screenshot02.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/Screenshot02.PNG -------------------------------------------------------------------------------- /Images/Screenshot03.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/Screenshot03.PNG -------------------------------------------------------------------------------- /Images/Screenshot04.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/Screenshot04.PNG -------------------------------------------------------------------------------- /Images/SpaceshipGame.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Images/SpaceshipGame.PNG -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John's Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JGameEngine 2 | 3 | is a lightweight 3D game engine written in Java. 4 | 5 | ## TODO 6 | * [x] No dependencies 7 | * [x] Java 1.5+ compatibility 8 | * [x] Fixed point math only 9 | * [ ] Multithreading 10 | * [x] Graphics engine 11 | * [ ] Physics engine 12 | * [ ] Audio engine 13 | * [ ] Networking engine 14 | * [x] Input engine 15 | 16 | Graphics engine 17 | * [x] Scanline triangle rasterization (linear interpolation, no perspective needed as big triangles are culled) 18 | * [x] Shaders (flat, gouraud and phong shaders) 19 | * [x] Multithreaded shaders 20 | * [x] Point, directional and spot lights 21 | * [x] Directional and spot light shadow mapping 22 | * [x] Skeletal animation (experimental) 23 | * [ ] Skybox 24 | * [x] Custom Wavefront OBJ `.obj` importer 25 | * [ ] Animation importer 26 | 27 | Input engine 28 | * [x] Keyboard 29 | * [x] Mouse 30 | * [ ] Gamepad 31 | * [ ] Touch 32 | 33 | ## Tests 34 | 35 | There are some built in tests for the game engine that can be found in the `Tests` folder. 36 | To run the test just double click the `.jar` file. 37 | 38 | Or open a terminal and type ``java -jar TestFile.jar`` 39 | 40 | The window size can be configured using the ``window640x480`` argument. 41 | 42 | The render resolution can be set as a percentage of the window size using the ``render75`` argument. 43 | 44 | ## Videos 45 | 46 | Spaceship game 47 | 48 | [![Spaceship game](Images/SpaceshipGame.PNG)](https://www.youtube.com/watch?v=jaY7MnLMf94) 49 | 50 | Model viewer 51 | 52 | [![Model viewer](Images/ModelViewer.PNG)](https://www.youtube.com/watch?v=6UVMvJErhTc) 53 | 54 | ## Screenshots 55 | 56 | Single threaded basic shader (without lights) 57 | 58 | ![Screenshot](Images/Screenshot00.PNG "Screenshot00") 59 | 60 | Multithreaded basic shader (without lights) 61 | 62 | ![Screenshot](Images/Screenshot01.PNG "Screenshot01") 63 | 64 | Multithreaded flat shading (directional, spot and point lights and directional and spot light shadows) 65 | 66 | ![Screenshot](Images/Screenshot02.PNG "Screenshot02") 67 | 68 | Multithreaded gouraud shading (directional, spot and point lights and directional and spot light shadows) 69 | 70 | ![Screenshot](Images/Screenshot03.PNG "Screenshot03") 71 | 72 | Multithreaded phong shading (directional, spot and point lights and directional and spot light shadows) 73 | 74 | ![Screenshot](Images/Screenshot04.PNG "Screenshot04") -------------------------------------------------------------------------------- /Tests/ModelViewer.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Tests/ModelViewer.jar -------------------------------------------------------------------------------- /Tests/SpaceshipGame.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/Tests/SpaceshipGame.jar -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.johnsproject 7 | JGameEngine 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | JGameEngine 12 | http://maven.apache.org 13 | 14 | 15 | UTF-8 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 4.12 23 | test 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/Engine.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.johnsproject.jgameengine.event.EngineEvent; 7 | import com.johnsproject.jgameengine.event.EngineListener; 8 | import com.johnsproject.jgameengine.model.Scene; 9 | import com.johnsproject.jgameengine.util.FixedPointUtils; 10 | 11 | public final class Engine { 12 | 13 | private static Engine engine = new Engine(); 14 | 15 | public static Engine getInstance() { 16 | return engine; 17 | } 18 | 19 | private final List engineListeners; 20 | private Scene scene; 21 | private Thread engineThread; 22 | private int maxUpdateSkip; 23 | private int updateRate; 24 | private boolean limitUpdateRate; 25 | private volatile boolean running; 26 | private long currentTime; 27 | private long previousTime; 28 | private long elapsedTime; 29 | private long updateTime; 30 | private int loops; 31 | 32 | private Engine() { 33 | this.running = false; 34 | this.updateRate = 30; 35 | this.maxUpdateSkip = 5; 36 | this.limitUpdateRate = false; 37 | this.engineListeners = new ArrayList(); 38 | startEngineLoop(); 39 | } 40 | 41 | public void start() { 42 | currentTime = getTime(); 43 | running = true; 44 | } 45 | 46 | public void stop() { 47 | running = false; 48 | } 49 | 50 | private void startEngineLoop() { 51 | engineThread = new Thread(new Runnable() { 52 | public void run() { 53 | updateEngine(); 54 | } 55 | }); 56 | engineThread.setName("JGameEngine"); 57 | engineThread.start(); 58 | } 59 | 60 | private void updateEngine() { 61 | while (true) { 62 | if (running) { 63 | elapsedTime = getTime() - previousTime; 64 | previousTime = getTime(); 65 | updateTime = 1000 / updateRate; 66 | loops = 0; 67 | callFixedUpdate(); 68 | callDynamicUpdate(loops << FixedPointUtils.FP_BIT); 69 | limitUpdateRateSleep(updateTime - elapsedTime); 70 | } else { 71 | sleep(); 72 | } 73 | } 74 | } 75 | 76 | private void callFixedUpdate() { 77 | EngineEvent event = new EngineEvent(scene, (int) elapsedTime, 0, 0); 78 | while (((currentTime - getTime()) < 0) && (loops < maxUpdateSkip)) { 79 | for (int i = 0; i < engineListeners.size(); i++) { 80 | engineListeners.get(i).fixedUpdate(event); 81 | } 82 | currentTime += updateTime; 83 | loops++; 84 | } 85 | } 86 | 87 | private void callDynamicUpdate(int deltaTime) { 88 | EngineEvent event = new EngineEvent(scene, (int) elapsedTime, 0, deltaTime); 89 | for (int i = 0; i < engineListeners.size(); i++) { 90 | engineListeners.get(i).dynamicUpdate(event); 91 | } 92 | } 93 | 94 | private void limitUpdateRateSleep(long sleepTime) { 95 | if(limitUpdateRate) { 96 | if (sleepTime > 0) { 97 | try { 98 | Thread.sleep(sleepTime); 99 | } catch (InterruptedException e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | } 104 | } 105 | 106 | private void sleep() { 107 | try { 108 | Thread.sleep(30); 109 | } catch (InterruptedException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | 114 | private long getTime() { 115 | return System.currentTimeMillis(); 116 | } 117 | 118 | public void addEngineListener(EngineListener listener) { 119 | listener.initialize(new EngineEvent(scene, 0, 0, 0)); 120 | engineListeners.add(listener); 121 | sortListeners(); 122 | } 123 | 124 | public void removeEngineListener(EngineListener listener) { 125 | engineListeners.remove(listener); 126 | sortListeners(); 127 | } 128 | 129 | public List getEngineListeners() { 130 | return engineListeners; 131 | } 132 | 133 | public int getUpdateRate() { 134 | return updateRate; 135 | } 136 | 137 | public void setUpdateRate(int updateRate) { 138 | this.updateRate = updateRate; 139 | } 140 | 141 | public boolean limitUpdateRate() { 142 | return limitUpdateRate; 143 | } 144 | 145 | public void limitUpdateRate(boolean limitUpdateRate) { 146 | this.limitUpdateRate = limitUpdateRate; 147 | } 148 | 149 | public int getMaxUpdateSkip() { 150 | return maxUpdateSkip; 151 | } 152 | 153 | public void setMaxUpdateSkip(int maxUpdateSkip) { 154 | this.maxUpdateSkip = maxUpdateSkip; 155 | } 156 | 157 | public Scene getScene() { 158 | return scene; 159 | } 160 | 161 | public void setScene(Scene scene) { 162 | this.scene = scene; 163 | } 164 | 165 | private void sortListeners() { 166 | final int listenerCount = engineListeners.size(); 167 | for (int i = 0; i < listenerCount; i++) { 168 | int min_i = i; 169 | for (int j = i + 1; j < listenerCount; j++) { 170 | if (engineListeners.get(j).getLayer() < engineListeners.get(min_i).getLayer()) { 171 | min_i = j; 172 | } 173 | } 174 | EngineListener temp = engineListeners.get(min_i); 175 | engineListeners.set(min_i, engineListeners.get(i)); 176 | engineListeners.set(i, temp); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/EngineStatistics.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import java.awt.Color; 4 | import java.awt.TextArea; 5 | import java.util.List; 6 | 7 | import com.johnsproject.jgameengine.event.EngineEvent; 8 | import com.johnsproject.jgameengine.event.EngineListener; 9 | import com.johnsproject.jgameengine.model.FrameBuffer; 10 | import com.johnsproject.jgameengine.model.Model; 11 | 12 | public class EngineStatistics implements EngineListener { 13 | 14 | private static final int STATISTICS_X = 10; 15 | private static final int STATISTICS_Y = 30; 16 | private static final int STATISTICS_WIDTH = 180; 17 | private static final int STATISTICS_HEIGHT = 130; 18 | private static final Color STATISTICS_BACKROUND = Color.WHITE; 19 | 20 | private static final long BYTE_TO_MEGABYTE = 1024L * 1024L; 21 | 22 | private final TextArea textArea; 23 | private GraphicsEngine graphicsEngine; 24 | private long averageUpdates; 25 | private long loops; 26 | 27 | public EngineStatistics(EngineWindow window) { 28 | this.textArea = new TextArea("", 0, 0, TextArea.SCROLLBARS_NONE); 29 | window.add(textArea, 0); 30 | } 31 | 32 | public void initialize(EngineEvent e) { 33 | textArea.setLocation(STATISTICS_X, STATISTICS_Y); 34 | textArea.setSize(STATISTICS_WIDTH, STATISTICS_HEIGHT); 35 | textArea.setEditable(false); 36 | textArea.setBackground(STATISTICS_BACKROUND); 37 | graphicsEngine = getGraphicsEngine(); 38 | } 39 | 40 | private GraphicsEngine getGraphicsEngine() { 41 | GraphicsEngine graphicsEngine = null; 42 | final List engineListeners = Engine.getInstance().getEngineListeners(); 43 | for (int i = 0; i < engineListeners.size(); i++) { 44 | final EngineListener engineListener = engineListeners.get(i); 45 | if(engineListener instanceof GraphicsEngine) { 46 | graphicsEngine = (GraphicsEngine) engineListener; 47 | } 48 | } 49 | return graphicsEngine; 50 | } 51 | 52 | public void fixedUpdate(EngineEvent e) { 53 | final String output = getOutput(e); 54 | textArea.setText(output); 55 | } 56 | 57 | public void dynamicUpdate(EngineEvent e) { } 58 | 59 | public int getLayer() { 60 | return GRAPHICS_ENGINE_LAYER - 1; 61 | } 62 | 63 | private String getOutput(EngineEvent e) { 64 | final List models = e.getScene().getModels(); 65 | 66 | String output = "== ENGINE STATISTICS ==\n"; 67 | output += getRAMUsage(); 68 | output += getCPUTime(e.getElapsedUpdateTime() + 1); 69 | output += getFrameBufferSize(); 70 | output += getVertexCount(models); 71 | output += getTriangleCount(models); 72 | return output; 73 | } 74 | 75 | private String getRAMUsage() { 76 | final Runtime runtime = Runtime.getRuntime(); 77 | final long totalRAM = runtime.totalMemory() / BYTE_TO_MEGABYTE; 78 | final long usedRAM = (runtime.totalMemory() - runtime.freeMemory()) / BYTE_TO_MEGABYTE; 79 | return "RAM usage\t" + usedRAM + " / " + totalRAM + " MB\n"; 80 | } 81 | 82 | private String getCPUTime(long elapsedTime) { 83 | final long updates = 1000 / elapsedTime; 84 | averageUpdates += updates; 85 | loops++; 86 | if(loops >= 100) { 87 | averageUpdates = averageUpdates / loops; 88 | loops = 1; 89 | } 90 | String cpuTime = "CPU time\t" + elapsedTime + " ms\n"; 91 | cpuTime += "Updates / s\t" + updates + "\n"; 92 | cpuTime += "Average U / s\t" + (averageUpdates / loops) + "\n"; 93 | return cpuTime; 94 | } 95 | 96 | private String getFrameBufferSize() { 97 | final FrameBuffer frameBuffer = graphicsEngine.getFrameBuffer(); 98 | return "Framebuffer\t" + frameBuffer.getWidth() + "x" + frameBuffer.getHeight() + "\n"; 99 | } 100 | 101 | private String getVertexCount(List models) { 102 | int vertexCount = 0; 103 | for (int i = 0; i < models.size(); i++) 104 | vertexCount += models.get(i).getMesh().getVertices().length; 105 | 106 | return "Vertices\t\t" + vertexCount + "\n"; 107 | } 108 | 109 | private String getTriangleCount(List models) { 110 | int triangleCount = 0; 111 | for (int i = 0; i < models.size(); i++) 112 | triangleCount += models.get(i).getMesh().getFaces().length; 113 | 114 | return "Triangles\t" + triangleCount + "\n"; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/EngineWindow.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import java.awt.Canvas; 4 | import java.awt.Frame; 5 | import java.awt.Graphics; 6 | import java.awt.event.ComponentAdapter; 7 | import java.awt.event.ComponentEvent; 8 | import java.awt.event.WindowAdapter; 9 | import java.awt.event.WindowEvent; 10 | import java.awt.image.BufferStrategy; 11 | 12 | import com.johnsproject.jgameengine.event.EngineEvent; 13 | import com.johnsproject.jgameengine.event.EngineListener; 14 | import com.johnsproject.jgameengine.model.FrameBuffer; 15 | 16 | public class EngineWindow extends Frame implements EngineListener { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | private final Canvas canvas; 21 | private BufferStrategy bufferStrategy; 22 | private Graphics graphics; 23 | private FrameBuffer frameBuffer; 24 | 25 | public EngineWindow(FrameBuffer frameBuffer) { 26 | canvas = new Canvas(); 27 | setLayout(null); 28 | setFrameBuffer(frameBuffer); 29 | setTitle("JGameEngine"); 30 | addWindowListener(handleWindowClose()); 31 | addComponentListener(handleWindowResize()); 32 | add(canvas); 33 | setVisible(true); 34 | } 35 | 36 | private WindowAdapter handleWindowClose() { 37 | return new WindowAdapter() { 38 | public void windowClosing(WindowEvent we) { 39 | System.exit(0); 40 | } 41 | }; 42 | } 43 | 44 | public ComponentAdapter handleWindowResize() { 45 | return new ComponentAdapter() { 46 | public void componentResized(ComponentEvent componentEvent) { 47 | recreateCanvasBuffer(); 48 | } 49 | }; 50 | } 51 | 52 | public void initialize(EngineEvent e) {} 53 | 54 | public void fixedUpdate(EngineEvent e) {} 55 | 56 | public void dynamicUpdate(EngineEvent e) { 57 | synchronized (canvas) { 58 | final int width = getWidth(); 59 | final int height = getHeight(); 60 | graphics.clearRect(0, 0, width, height); 61 | graphics.drawImage(frameBuffer.getImage(), 0, 0, width, height, null); 62 | bufferStrategy.show(); 63 | } 64 | } 65 | 66 | @Override 67 | public void setVisible(boolean isVisible) { 68 | super.setVisible(isVisible); 69 | recreateCanvasBuffer(); 70 | } 71 | 72 | private void recreateCanvasBuffer() { 73 | synchronized (canvas) { 74 | if(isVisible()) { 75 | canvas.setSize(getWidth(), getHeight()); 76 | canvas.createBufferStrategy(2); 77 | if(bufferStrategy != null) { 78 | graphics.dispose(); 79 | bufferStrategy.dispose(); 80 | } 81 | bufferStrategy = canvas.getBufferStrategy(); 82 | graphics = bufferStrategy.getDrawGraphics(); 83 | } 84 | } 85 | } 86 | 87 | public void setFullscreen(boolean isFullscreen) { 88 | dispose(); 89 | if(isFullscreen) 90 | setExtendedState(Frame.MAXIMIZED_BOTH); 91 | else 92 | setExtendedState(Frame.NORMAL); 93 | setVisible(true); 94 | } 95 | 96 | public boolean isFullscreen() { 97 | return getExtendedState() != Frame.NORMAL; 98 | } 99 | 100 | public void setBorders(boolean hasBorders) { 101 | dispose(); 102 | setUndecorated(!hasBorders); 103 | setVisible(true); 104 | } 105 | 106 | public boolean hasBorders() { 107 | return !isUndecorated(); 108 | } 109 | 110 | public void setFrameBuffer(FrameBuffer frameBuffer) { 111 | setSize(frameBuffer.getWidth(), frameBuffer.getHeight()); 112 | this.frameBuffer = frameBuffer; 113 | } 114 | 115 | public FrameBuffer getFrameBuffer() { 116 | return frameBuffer; 117 | } 118 | 119 | public int getLayer() { 120 | return GRAPHICS_ENGINE_LAYER + 1; 121 | } 122 | 123 | public Canvas getCanvas() { 124 | return canvas; 125 | } 126 | } -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/GraphicsEngine.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.johnsproject.jgameengine.event.EngineEvent; 7 | import com.johnsproject.jgameengine.event.EngineListener; 8 | import com.johnsproject.jgameengine.model.AnimationFrame; 9 | import com.johnsproject.jgameengine.model.Armature; 10 | import com.johnsproject.jgameengine.model.Camera; 11 | import com.johnsproject.jgameengine.model.Face; 12 | import com.johnsproject.jgameengine.model.FrameBuffer; 13 | import com.johnsproject.jgameengine.model.Material; 14 | import com.johnsproject.jgameengine.model.Mesh; 15 | import com.johnsproject.jgameengine.model.Model; 16 | import com.johnsproject.jgameengine.model.Scene; 17 | import com.johnsproject.jgameengine.model.Transform; 18 | import com.johnsproject.jgameengine.model.Vertex; 19 | import com.johnsproject.jgameengine.model.VertexGroup; 20 | import com.johnsproject.jgameengine.shading.DirectionalLightShadowShader; 21 | import com.johnsproject.jgameengine.shading.ForwardShaderBuffer; 22 | import com.johnsproject.jgameengine.shading.GouraudShader; 23 | import com.johnsproject.jgameengine.shading.Shader; 24 | import com.johnsproject.jgameengine.shading.ShaderBuffer; 25 | import com.johnsproject.jgameengine.shading.SpotLightShadowShader; 26 | import com.johnsproject.jgameengine.util.VectorUtils; 27 | 28 | public class GraphicsEngine implements EngineListener { 29 | 30 | private Shader defaultShader; 31 | private final List shaders; 32 | private ShaderBuffer shaderBuffer; 33 | private FrameBuffer frameBuffer; 34 | private final int[] locationVector; 35 | private final int[] normalVector; 36 | private final int[] multiplyVector; 37 | 38 | public GraphicsEngine(FrameBuffer frameBuffer) { 39 | this.shaderBuffer = new ForwardShaderBuffer(); 40 | this.shaders = new ArrayList(); 41 | this.frameBuffer = frameBuffer; 42 | this.locationVector = VectorUtils.emptyVector(); 43 | this.normalVector = VectorUtils.emptyVector(); 44 | this.multiplyVector = VectorUtils.emptyVector(); 45 | defaultShader = new GouraudShader(); 46 | addShader(new DirectionalLightShadowShader()); 47 | addShader(new SpotLightShadowShader()); 48 | addShader(defaultShader); 49 | } 50 | 51 | public void initialize(EngineEvent e) {} 52 | 53 | public void fixedUpdate(EngineEvent e) { 54 | Scene scene = e.getScene(); 55 | for (int i = 0; i < scene.getModels().size(); i++) { 56 | Model model = scene.getModels().get(i); 57 | final Armature armature = model.getArmature(); 58 | if(armature != null) { 59 | armature.nextFrame(); 60 | } 61 | } 62 | } 63 | 64 | public void dynamicUpdate(EngineEvent e) { 65 | Scene scene = e.getScene(); 66 | frameBuffer.getColorBuffer().fill(0); 67 | frameBuffer.getDepthBuffer().fill(Integer.MAX_VALUE); 68 | frameBuffer.getStencilBuffer().fill(0); 69 | localToWorldSpace(scene); 70 | renderForEachCamera(scene); 71 | } 72 | 73 | private void localToWorldSpace(Scene scene) { 74 | for (int i = 0; i < scene.getModels().size(); i++) { 75 | Model model = scene.getModels().get(i); 76 | if(!model.isActive()) 77 | continue; 78 | final Mesh mesh = model.getMesh(); 79 | final Armature armature = model.getArmature(); 80 | final Transform transform = model.getTransform(); 81 | transformVertices(mesh, transform, armature); 82 | transformFaces(mesh, transform); 83 | } 84 | } 85 | 86 | private void transformVertices(Mesh mesh, Transform transform, Armature armature) { 87 | AnimationFrame animationFrame = null; 88 | if(armature != null) { 89 | animationFrame = armature.getCurrentAnimationFrame(); 90 | } 91 | for (int v = 0; v < mesh.getVertices().length; v++) { 92 | final Vertex vertex = mesh.getVertex(v); 93 | VectorUtils.copy(vertex.getWorldLocation(), vertex.getLocalLocation()); 94 | VectorUtils.copy(vertex.getWorldNormal(), VectorUtils.VECTOR_ZERO); 95 | animateVertex(armature, animationFrame, vertex); 96 | VectorUtils.multiply(vertex.getWorldLocation(), transform.getSpaceExitMatrix()); 97 | } 98 | } 99 | 100 | private void animateVertex(Armature armature, AnimationFrame animationFrame, Vertex vertex) { 101 | if(animationFrame != null) { 102 | VectorUtils.copy(locationVector, VectorUtils.VECTOR_ZERO); 103 | VectorUtils.copy(normalVector, VectorUtils.VECTOR_ZERO); 104 | for (int i = 0; i < armature.getVertexGroups().length; i++) { 105 | final VertexGroup vertexGroup = armature.getVertexGroup(i); 106 | final int boneWeight = vertexGroup.getWeight(vertex); 107 | if(boneWeight != -1) { 108 | final int[][] boneMatrix = animationFrame.getBoneMatrix(vertexGroup.getBoneIndex()); 109 | applyBone(vertex, boneWeight, boneMatrix); 110 | } 111 | } 112 | VectorUtils.copy(vertex.getWorldLocation(), locationVector); 113 | VectorUtils.copy(vertex.getWorldNormal(), normalVector); 114 | } 115 | } 116 | 117 | private void applyBone(Vertex vertex, int boneWeight, int[][] boneMatrix) { 118 | VectorUtils.copy(multiplyVector, vertex.getWorldLocation()); 119 | VectorUtils.multiply(multiplyVector, boneMatrix); 120 | VectorUtils.multiply(multiplyVector, boneWeight); 121 | VectorUtils.add(locationVector, multiplyVector); 122 | VectorUtils.copy(multiplyVector, vertex.getWorldNormal()); 123 | VectorUtils.multiply(multiplyVector, boneMatrix); 124 | VectorUtils.multiply(multiplyVector, boneWeight); 125 | VectorUtils.add(normalVector, multiplyVector); 126 | } 127 | 128 | private void transformFaces(Mesh mesh, Transform transform) { 129 | for (int f = 0; f < mesh.getFaces().length; f++) { 130 | final Face face = mesh.getFace(f); 131 | VectorUtils.copy(face.getWorldNormal(), face.getLocalNormal()); 132 | VectorUtils.multiply(face.getWorldNormal(), transform.getSpaceExitNormalMatrix()); 133 | // calculate vertex normals, just add the face the normals of the faces this vertex is a part of 134 | VectorUtils.add(face.getVertex(0).getWorldNormal(), face.getWorldNormal()); 135 | VectorUtils.add(face.getVertex(1).getWorldNormal(), face.getWorldNormal()); 136 | VectorUtils.add(face.getVertex(2).getWorldNormal(), face.getWorldNormal()); 137 | } 138 | } 139 | 140 | private void renderForEachCamera(Scene scene) { 141 | for (int c = 0; c < scene.getCameras().size(); c++) { 142 | Camera camera = scene.getCameras().get(c); 143 | if(!camera.isActive()) 144 | continue; 145 | camera.setRenderTarget(frameBuffer); 146 | shaderBuffer.initialize(camera, scene.getLights()); 147 | renderModels(scene); 148 | } 149 | } 150 | 151 | private void renderModels(Scene scene) { 152 | for (int s = 0; s < shaders.size(); s++) { 153 | Shader shader = shaders.get(s); 154 | shader.initialize(shaderBuffer); 155 | for (int m = 0; m < scene.getModels().size(); m++) { 156 | Model model = scene.getModels().get(m); 157 | if(!model.isActive() || model.isCulled()) 158 | continue; 159 | final Mesh mesh = model.getMesh(); 160 | shadeVertices(mesh, shader); 161 | shader.waitForVertexQueue(); 162 | 163 | shadeFaces(mesh, shader); 164 | shader.waitForGeometryQueue(); 165 | } 166 | } 167 | } 168 | 169 | private void shadeVertices(Mesh mesh, Shader shader) { 170 | for (int v = 0; v < mesh.getVertices().length; v++) { 171 | final Vertex vertex = mesh.getVertex(v); 172 | final Material material = vertex.getMaterial(); 173 | if(canUseShader(shader, material)) { 174 | shader.vertex(vertex); 175 | } 176 | } 177 | } 178 | 179 | private void shadeFaces(Mesh mesh, Shader shader) { 180 | for (int f = 0; f < mesh.getFaces().length; f++) { 181 | final Face face = mesh.getFace(f); 182 | final Material material = face.getMaterial(); 183 | if(canUseShader(shader, material)) { 184 | shader.geometry(face); 185 | } 186 | } 187 | } 188 | 189 | private boolean canUseShader(Shader shader, Material material) { 190 | return ((material.getShader() == null) && shader.equals(defaultShader)) 191 | || shader.equals(material.getShader()) 192 | || shader.isGlobal(); 193 | } 194 | 195 | public int getLayer() { 196 | return GRAPHICS_ENGINE_LAYER; 197 | } 198 | 199 | public ShaderBuffer getShaderBuffer() { 200 | return shaderBuffer; 201 | } 202 | 203 | public void initialize(ShaderBuffer shaderBuffer) { 204 | this.shaderBuffer = shaderBuffer; 205 | } 206 | 207 | public void setFrameBuffer(FrameBuffer frameBuffer) { 208 | this.frameBuffer = frameBuffer; 209 | } 210 | 211 | public FrameBuffer getFrameBuffer() { 212 | return frameBuffer; 213 | } 214 | 215 | public List getShaders() { 216 | return shaders; 217 | } 218 | 219 | public Shader getShader(int index) { 220 | return shaders.get(index); 221 | } 222 | 223 | public void addShader(Shader shader) { 224 | shaders.add(shader); 225 | } 226 | 227 | public void removeShader(Shader shader) { 228 | shaders.remove(shader); 229 | } 230 | 231 | public Shader getDefaultShader() { 232 | return defaultShader; 233 | } 234 | 235 | public void setDefaultShader(Shader defaultShader) { 236 | this.defaultShader = defaultShader; 237 | } 238 | } -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/InputEngine.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import java.awt.AWTEvent; 4 | import java.awt.Point; 5 | import java.awt.Toolkit; 6 | import java.awt.event.AWTEventListener; 7 | import java.awt.event.KeyEvent; 8 | import java.awt.event.MouseEvent; 9 | import java.awt.event.MouseMotionListener; 10 | import java.awt.event.MouseWheelEvent; 11 | import java.awt.event.MouseWheelListener; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import com.johnsproject.jgameengine.event.EngineEvent; 16 | import com.johnsproject.jgameengine.event.EngineKeyListener; 17 | import com.johnsproject.jgameengine.event.EngineListener; 18 | import com.johnsproject.jgameengine.event.EngineMouseListener; 19 | 20 | public class InputEngine implements EngineListener { 21 | 22 | private static final long AWT_EVENT_MASK = AWTEvent.KEY_EVENT_MASK 23 | + AWTEvent.MOUSE_EVENT_MASK 24 | + AWTEvent.MOUSE_MOTION_EVENT_MASK 25 | + AWTEvent.MOUSE_WHEEL_EVENT_MASK; 26 | 27 | private static final int KEY_HOLD = KeyEvent.KEY_LAST + 1; 28 | private static final int MOUSE_HOLD = KeyEvent.KEY_LAST + 2; 29 | 30 | private static class InputEvent { 31 | 32 | private final int type; 33 | private final KeyEvent keyEvent; 34 | private final MouseEvent mouseEvent; 35 | private final MouseWheelEvent mouseWheelEvent; 36 | 37 | public InputEvent(int type, KeyEvent keyEvent, MouseEvent mouseEvent, MouseWheelEvent mouseWheelEvent) { 38 | this.type = type; 39 | this.keyEvent = keyEvent; 40 | this.mouseEvent = mouseEvent; 41 | this.mouseWheelEvent = mouseWheelEvent; 42 | } 43 | 44 | public int getType() { 45 | return type; 46 | } 47 | 48 | public KeyEvent getKeyEvent() { 49 | return keyEvent; 50 | } 51 | 52 | public MouseEvent getMouseEvent() { 53 | return mouseEvent; 54 | } 55 | 56 | public MouseWheelEvent getMouseWheelEvent() { 57 | return mouseWheelEvent; 58 | } 59 | } 60 | 61 | private Point mouseLocation; 62 | private Point mouseLocationOnScreen; 63 | private final List keyListeners; 64 | private final List mouseListeners; 65 | private final List motionListeners; 66 | private final List wheelListeners; 67 | private final List inputEvents; 68 | 69 | public InputEngine() { 70 | this.mouseLocation = new Point(); 71 | this.mouseLocationOnScreen = new Point(); 72 | this.inputEvents = new ArrayList(); 73 | this.keyListeners = new ArrayList(); 74 | this.mouseListeners = new ArrayList(); 75 | this.wheelListeners = new ArrayList(); 76 | this.motionListeners = new ArrayList(); 77 | } 78 | 79 | public Point getMouseLocation() { 80 | return mouseLocation; 81 | } 82 | 83 | public Point getMouseLocationOnScreen() { 84 | return mouseLocationOnScreen; 85 | } 86 | 87 | public boolean isKeyPressed(int keyCode) { 88 | for (int i = 0; i < inputEvents.size(); i++) { 89 | final InputEvent event = inputEvents.get(i); 90 | if((event.getType() == KEY_HOLD) && (event.getKeyEvent().getKeyCode() == keyCode)) 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | public boolean isMousePressed(int button) { 97 | for (int i = 0; i < inputEvents.size(); i++) { 98 | final InputEvent event = inputEvents.get(i); 99 | if((event.getType() == MOUSE_HOLD) && (event.getMouseEvent().getButton() == button)) 100 | return true; 101 | } 102 | return false; 103 | } 104 | 105 | public void addEngineKeyListener(EngineKeyListener listener) { 106 | keyListeners.add(listener); 107 | } 108 | 109 | public void removeEngineKeyListener(EngineKeyListener listener) { 110 | keyListeners.remove(listener); 111 | } 112 | 113 | public void addEngineMouseListener(EngineMouseListener listener) { 114 | mouseListeners.add(listener); 115 | } 116 | 117 | public void removeEngineMouseListener(EngineMouseListener listener) { 118 | mouseListeners.remove(listener); 119 | } 120 | 121 | public void addMouseWheelListener(MouseWheelListener listener) { 122 | wheelListeners.add(listener); 123 | } 124 | 125 | public void removeMouseWheelListener(MouseWheelListener listener) { 126 | wheelListeners.remove(listener); 127 | } 128 | 129 | public void addMouseMotionListener(MouseMotionListener listener) { 130 | motionListeners.add(listener); 131 | } 132 | 133 | public void removeMouseMotionListener(MouseMotionListener listener) { 134 | motionListeners.remove(listener); 135 | } 136 | 137 | public void initialize(EngineEvent e) { 138 | Toolkit.getDefaultToolkit().addAWTEventListener(handleEvent(), AWT_EVENT_MASK); 139 | } 140 | 141 | private AWTEventListener handleEvent() { 142 | return new AWTEventListener() { 143 | public void eventDispatched(AWTEvent event) { 144 | if (event instanceof KeyEvent) { 145 | KeyEvent e = (KeyEvent) event; 146 | handleKeyEvent(e); 147 | } 148 | if (event instanceof MouseEvent) { 149 | MouseEvent e = (MouseEvent) event; 150 | mouseLocation = e.getPoint(); 151 | mouseLocationOnScreen = e.getLocationOnScreen(); 152 | handleMouseEvent(e); 153 | handleMouseMotionEvent(e); 154 | } 155 | if (event instanceof MouseWheelEvent) { 156 | MouseWheelEvent e = (MouseWheelEvent) event; 157 | handleMouseWheelEvent(e); 158 | } 159 | } 160 | }; 161 | } 162 | 163 | private void handleKeyEvent(KeyEvent e) { 164 | final int eventType = e.getID(); 165 | int eventIndex; 166 | synchronized (inputEvents) { 167 | switch (eventType) { 168 | case KeyEvent.KEY_TYPED: 169 | eventIndex = getKeyEventIndex(e, KeyEvent.KEY_TYPED); 170 | if(eventIndex < 0) 171 | inputEvents.add(new InputEvent(eventType, e, null, null)); 172 | break; 173 | 174 | case KeyEvent.KEY_PRESSED: 175 | eventIndex = getKeyEventIndex(e, KEY_HOLD); 176 | if(eventIndex < 0) { 177 | inputEvents.add(new InputEvent(eventType, e, null, null)); 178 | inputEvents.add(new InputEvent(KEY_HOLD, e, null, null)); 179 | } 180 | break; 181 | 182 | case KeyEvent.KEY_RELEASED: 183 | eventIndex = getKeyEventIndex(e, KEY_HOLD); 184 | if(eventIndex >= 0) { 185 | inputEvents.remove(eventIndex); 186 | } 187 | inputEvents.add(new InputEvent(eventType, e, null, null)); 188 | break; 189 | } 190 | } 191 | } 192 | 193 | private int getKeyEventIndex(KeyEvent e, int eventType) { 194 | synchronized (inputEvents) { 195 | for (int i = 0; i < inputEvents.size(); i++) { 196 | final InputEvent event = inputEvents.get(i); 197 | if(event == null) 198 | continue; 199 | if((event.getType() == eventType) && (event.getKeyEvent().getKeyCode() == e.getKeyCode())) { 200 | return i; 201 | } 202 | } 203 | } 204 | return -1; 205 | } 206 | 207 | private void handleMouseEvent(MouseEvent e) { 208 | final int eventType = e.getID(); 209 | synchronized (inputEvents) { 210 | inputEvents.add(new InputEvent(eventType, null, e, null)); 211 | switch (eventType) { 212 | case MouseEvent.MOUSE_PRESSED: 213 | inputEvents.add(new InputEvent(MOUSE_HOLD, null, e, null)); 214 | break; 215 | 216 | case MouseEvent.MOUSE_RELEASED: 217 | for (int i = 0; i < inputEvents.size(); i++) { 218 | final InputEvent event = inputEvents.get(i); 219 | if(event == null) 220 | continue; 221 | if((event.getType() == MOUSE_HOLD) && (event.getMouseEvent().getButton() == e.getButton())) { 222 | inputEvents.remove(i); 223 | } 224 | } 225 | break; 226 | } 227 | } 228 | } 229 | 230 | private void handleMouseMotionEvent(MouseEvent e) { 231 | final int eventType = e.getID(); 232 | synchronized (inputEvents) { 233 | inputEvents.add(new InputEvent(eventType, null, e, null)); 234 | } 235 | } 236 | 237 | private void handleMouseWheelEvent(MouseWheelEvent e) { 238 | final int eventType = e.getID(); 239 | synchronized (inputEvents) { 240 | inputEvents.add(new InputEvent(eventType, null, null, e)); 241 | } 242 | } 243 | 244 | public void fixedUpdate(EngineEvent e) { 245 | synchronized (inputEvents) { 246 | for (int i = 0; i < inputEvents.size(); i++) { 247 | final InputEvent event = inputEvents.get(i); 248 | if(event == null) 249 | continue; 250 | switch (event.getType()) { 251 | case KeyEvent.KEY_PRESSED: 252 | for (int l = 0; l < keyListeners.size(); l++) { 253 | keyListeners.get(l).keyPressed(event.getKeyEvent()); 254 | } 255 | break; 256 | 257 | case KeyEvent.KEY_RELEASED: 258 | for (int l = 0; l < keyListeners.size(); l++) { 259 | keyListeners.get(l).keyReleased(event.getKeyEvent()); 260 | } 261 | break; 262 | 263 | case KeyEvent.KEY_TYPED: 264 | for (int l = 0; l < keyListeners.size(); l++) { 265 | keyListeners.get(l).keyTyped(event.getKeyEvent()); 266 | } 267 | break; 268 | 269 | case KEY_HOLD: 270 | for (int l = 0; l < keyListeners.size(); l++) { 271 | keyListeners.get(l).keyHold(event.getKeyEvent()); 272 | } 273 | break; 274 | 275 | case MouseEvent.MOUSE_PRESSED: 276 | for (int l = 0; l < mouseListeners.size(); l++) { 277 | mouseListeners.get(l).mousePressed(event.getMouseEvent()); 278 | } 279 | break; 280 | 281 | case MouseEvent.MOUSE_RELEASED: 282 | for (int l = 0; l < mouseListeners.size(); l++) { 283 | mouseListeners.get(l).mouseReleased(event.getMouseEvent()); 284 | } 285 | break; 286 | 287 | case MouseEvent.MOUSE_CLICKED: 288 | for (int l = 0; l < mouseListeners.size(); l++) { 289 | mouseListeners.get(l).mouseClicked(event.getMouseEvent()); 290 | } 291 | break; 292 | 293 | case MOUSE_HOLD: 294 | for (int l = 0; l < mouseListeners.size(); l++) { 295 | mouseListeners.get(l).mouseHold(event.getMouseEvent()); 296 | } 297 | break; 298 | 299 | case MouseEvent.MOUSE_EXITED: 300 | for (int l = 0; l < mouseListeners.size(); l++) { 301 | mouseListeners.get(l).mouseExited(event.getMouseEvent()); 302 | } 303 | break; 304 | 305 | case MouseEvent.MOUSE_ENTERED: 306 | for (int l = 0; l < mouseListeners.size(); l++) { 307 | mouseListeners.get(l).mouseEntered(event.getMouseEvent()); 308 | } 309 | break; 310 | case MouseEvent.MOUSE_MOVED: 311 | for (int l = 0; l < motionListeners.size(); l++) { 312 | motionListeners.get(l).mouseMoved(event.getMouseEvent()); 313 | } 314 | break; 315 | 316 | case MouseEvent.MOUSE_DRAGGED: 317 | for (int l = 0; l < motionListeners.size(); l++) { 318 | motionListeners.get(l).mouseDragged(event.getMouseEvent()); 319 | } 320 | break; 321 | 322 | case MouseWheelEvent.WHEEL_UNIT_SCROLL: 323 | for (int l = 0; l < wheelListeners.size(); l++) { 324 | wheelListeners.get(l).mouseWheelMoved(event.getMouseWheelEvent()); 325 | } 326 | break; 327 | 328 | case MouseWheelEvent.WHEEL_BLOCK_SCROLL: 329 | for (int l = 0; l < wheelListeners.size(); l++) { 330 | wheelListeners.get(l).mouseWheelMoved(event.getMouseWheelEvent()); 331 | } 332 | break; 333 | } 334 | if((event.getType() != KEY_HOLD) && (event.getType() != MOUSE_HOLD)) { 335 | inputEvents.remove(i); 336 | } 337 | } 338 | } 339 | } 340 | 341 | public void dynamicUpdate(EngineEvent e) { } 342 | 343 | public int getLayer() { 344 | return INPUT_ENGINE_LAYER; 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/PhysicsEngine.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import com.johnsproject.jgameengine.event.EngineEvent; 4 | import com.johnsproject.jgameengine.event.EngineListener; 5 | import com.johnsproject.jgameengine.model.RigidBody; 6 | import com.johnsproject.jgameengine.model.Scene; 7 | import com.johnsproject.jgameengine.model.SceneObject; 8 | import com.johnsproject.jgameengine.model.Transform; 9 | import com.johnsproject.jgameengine.util.FixedPointUtils; 10 | import com.johnsproject.jgameengine.util.VectorUtils; 11 | 12 | public class PhysicsEngine implements EngineListener { 13 | 14 | public static final int FP_EARTH_GRAVITY = FixedPointUtils.toFixedPoint(-9.81f); 15 | 16 | private final int[] vectorCache1; 17 | 18 | public PhysicsEngine() { 19 | this.vectorCache1 = VectorUtils.emptyVector(); 20 | } 21 | 22 | public void initialize(EngineEvent e) { 23 | 24 | } 25 | 26 | public void fixedUpdate(EngineEvent e) { 27 | // final Scene scene = e.getScene(); 28 | // for (int i = 0; i < scene.getSceneObjects().size(); i++) { 29 | // final SceneObject sceneObject = scene.getSceneObject(i); 30 | // if(!sceneObject.isActive()) 31 | // return; 32 | // final RigidBody rigidBody = sceneObject.getRigidBody(); 33 | // final Transform transform = sceneObject.getTransform(); 34 | // applyForces(sceneObject); 35 | // int[] linearAcceleration = VectorLibrary.divide(rigidBody.getForce(), rigidBody.getMass(), vectorCache1); 36 | //// VectorLibrary.multiply(linearAcceleration, e.getDeltaTime(), linearAcceleration); 37 | // int[] linearVelocity = VectorLibrary.add(rigidBody.getLinearVelocity(), linearAcceleration, rigidBody.getLinearVelocity()); 38 | // VectorLibrary.add(transform.getLocation(), linearVelocity, transform.getLocation()); 39 | // int[] angularAcceleration = VectorLibrary.divide(rigidBody.getTorque(), rigidBody.getMass(), vectorCache1); 40 | //// VectorLibrary.multiply(angularAcceleration, e.getDeltaTime(), angularAcceleration); 41 | // VectorLibrary.multiply(angularAcceleration, MathLibrary.FP_RAD_DEGREE, angularAcceleration); 42 | // int[] angularVelocity = VectorLibrary.add(rigidBody.getAngularVelocity(), angularAcceleration, rigidBody.getAngularVelocity()); 43 | // VectorLibrary.add(transform.getRotation(), angularVelocity, transform.getRotation()); 44 | // } 45 | } 46 | 47 | private void applyForces(SceneObject sceneObject) { 48 | final RigidBody rigidBody = sceneObject.getRigidBody(); 49 | if(!rigidBody.isKinematic()) { 50 | int gravity = FixedPointUtils.multiply(rigidBody.getMass(), FP_EARTH_GRAVITY); 51 | rigidBody.setForce(0, 0, gravity); 52 | } 53 | } 54 | 55 | public void dynamicUpdate(EngineEvent e) { 56 | final Scene scene = e.getScene(); 57 | for (int i = 0; i < scene.getSceneObjects().size(); i++) { 58 | SceneObject sceneObject = scene.getSceneObjects().get(i); 59 | if(!sceneObject.isActive()) 60 | return; 61 | final RigidBody rigidBody = sceneObject.getRigidBody(); 62 | final Transform transform = sceneObject.getTransform(); 63 | applyForces(sceneObject); 64 | int[] linearAcceleration = VectorUtils.copy(vectorCache1, rigidBody.getForce()); 65 | VectorUtils.divide(linearAcceleration, rigidBody.getMass()); 66 | VectorUtils.multiply(linearAcceleration, e.getDeltaTime()); 67 | rigidBody.addLinearVelocity(linearAcceleration); 68 | int[] linearVelocity = VectorUtils.copy(vectorCache1, rigidBody.getLinearVelocity()); 69 | VectorUtils.multiply(linearVelocity, e.getDeltaTime()); 70 | transform.worldTranslate(linearVelocity); 71 | int[] angularAcceleration = VectorUtils.copy(vectorCache1, rigidBody.getTorque()); 72 | VectorUtils.divide(angularAcceleration, rigidBody.getMass()); 73 | VectorUtils.multiply(angularAcceleration, e.getDeltaTime()); 74 | rigidBody.addAngularVelocity(angularAcceleration); 75 | int[] angularVelocity = VectorUtils.copy(vectorCache1, rigidBody.getAngularVelocity()); 76 | VectorUtils.multiply(angularVelocity, e.getDeltaTime()); 77 | VectorUtils.multiply(angularVelocity, FixedPointUtils.FP_RAD_DEGREE); 78 | transform.worldRotate(angularVelocity); 79 | } 80 | } 81 | 82 | public int getLayer() { 83 | return PHYSICS_ENGINE_LAYER; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/event/EngineEvent.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.event; 2 | 3 | import com.johnsproject.jgameengine.model.Scene; 4 | 5 | public class EngineEvent { 6 | 7 | private final Scene scene; 8 | private final int elapsedUpdateTime; 9 | private final int sleepTime; 10 | private final int deltaTime; 11 | 12 | public EngineEvent(Scene scene, int elapsedUpdateTime, int sleepTime, int deltaTime) { 13 | this.scene = scene; 14 | this.elapsedUpdateTime = elapsedUpdateTime; 15 | this.sleepTime = sleepTime; 16 | this.deltaTime = deltaTime; 17 | } 18 | 19 | public Scene getScene() { 20 | return scene; 21 | } 22 | 23 | public int getElapsedUpdateTime() { 24 | return elapsedUpdateTime; 25 | } 26 | 27 | public int getSleepTime() { 28 | return sleepTime; 29 | } 30 | 31 | public int getDeltaTime() { 32 | return deltaTime; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/event/EngineKeyListener.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.event; 2 | 3 | import java.awt.event.KeyEvent; 4 | import java.awt.event.KeyListener; 5 | 6 | public interface EngineKeyListener extends KeyListener { 7 | 8 | public void keyHold(KeyEvent e); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/event/EngineListener.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.event; 2 | 3 | public interface EngineListener { 4 | 5 | public static final int INPUT_ENGINE_LAYER = -100; 6 | public static final int DEFAULT_LAYER = 0; 7 | public static final int PHYSICS_ENGINE_LAYER = 99; 8 | public static final int GRAPHICS_ENGINE_LAYER = 100; 9 | 10 | public void initialize(EngineEvent e); 11 | 12 | public void fixedUpdate(EngineEvent e); 13 | 14 | public void dynamicUpdate(EngineEvent e); 15 | 16 | public int getLayer(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/event/EngineMouseListener.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.event; 2 | 3 | import java.awt.event.MouseEvent; 4 | import java.awt.event.MouseListener; 5 | 6 | public interface EngineMouseListener extends MouseListener { 7 | 8 | public void mouseHold(MouseEvent e); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Animation.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class Animation { 4 | 5 | private final String name; 6 | private final AnimationFrame[] frames; 7 | 8 | public Animation(String name, AnimationFrame[] frames) { 9 | this.name = name; 10 | this.frames = frames; 11 | } 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public AnimationFrame getFrame(int index) { 18 | return frames[index]; 19 | } 20 | 21 | public AnimationFrame[] getFrames() { 22 | return frames; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/AnimationFrame.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class AnimationFrame { 4 | 5 | private final int[][][] boneMatrices; 6 | 7 | public AnimationFrame(int[][][] boneMatrices) { 8 | this.boneMatrices = boneMatrices; 9 | } 10 | 11 | public int[][] getBoneMatrix(int index) { 12 | return boneMatrices[index]; 13 | } 14 | 15 | public int[][][] getBoneMatrices() { 16 | return boneMatrices; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Armature.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class Armature { 4 | 5 | private final VertexGroup[] vertexGroups; 6 | private final Animation[] animations; 7 | private Animation currentAnimation; 8 | private int currentFrame; 9 | private int animationSpeed; 10 | private boolean loopAnimation; 11 | 12 | public Armature(VertexGroup[] vertexGroups, Animation[] animations) { 13 | this.vertexGroups = vertexGroups; 14 | this.animations = animations; 15 | this.animationSpeed = 1; 16 | this.currentFrame = 0; 17 | } 18 | 19 | public VertexGroup getVertexGroup(int index) { 20 | return vertexGroups[index]; 21 | } 22 | 23 | public VertexGroup[] getVertexGroups() { 24 | return vertexGroups; 25 | } 26 | 27 | public Animation getAnimation(int index) { 28 | return animations[index]; 29 | } 30 | 31 | public Animation[] getAnimations() { 32 | return animations; 33 | } 34 | 35 | public void playAnimation(String name, boolean loop) { 36 | for (int i = 0; i < animations.length; i++) { 37 | if(animations[i].getName().equals(name)) { 38 | playAnimation(i, loop); 39 | } 40 | } 41 | } 42 | 43 | public void playAnimation(int index, boolean loop) { 44 | loopAnimation = loop; 45 | currentAnimation = animations[index]; 46 | } 47 | 48 | public Animation getCurrentAnimation() { 49 | return currentAnimation; 50 | } 51 | 52 | public AnimationFrame getCurrentAnimationFrame() { 53 | if(currentAnimation == null) { 54 | return null; 55 | } else { 56 | return currentAnimation.getFrame(getCurrentFrame()); 57 | } 58 | } 59 | 60 | public int getCurrentFrame() { 61 | return currentFrame; 62 | } 63 | 64 | public void nextFrame() { 65 | currentFrame += animationSpeed; 66 | if(!isPlaying()) { 67 | if(loopAnimation) { 68 | currentFrame = 0; 69 | } else { 70 | stopPlaying(); 71 | } 72 | } 73 | } 74 | 75 | public boolean isPlaying() { 76 | if(currentAnimation == null) { 77 | return false; 78 | } else { 79 | return getCurrentFrame() < currentAnimation.getFrames().length; 80 | } 81 | } 82 | 83 | public void stopPlaying() { 84 | currentFrame = 0; 85 | currentAnimation = null; 86 | } 87 | 88 | public int getAnimationSpeed() { 89 | return animationSpeed; 90 | } 91 | 92 | public void setAnimationSpeed(int animationSpeed) { 93 | this.animationSpeed = animationSpeed; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Camera.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_ONE; 4 | 5 | public class Camera extends SceneObject { 6 | 7 | public static final String CAMERA_TAG = "Camera"; 8 | 9 | private Frustum frustum; 10 | private FrameBuffer renderTarget; 11 | private int lightDistance; 12 | private boolean isMain; 13 | 14 | public Camera(String name, Transform transform) { 15 | super(name, transform); 16 | super.tag = CAMERA_TAG; 17 | super.rigidBody.setKinematic(true); 18 | this.renderTarget = null; 19 | this.frustum = new Frustum(0, FP_ONE, 0, FP_ONE, FP_ONE, FP_ONE * 1000); 20 | this.lightDistance = FP_ONE * 100; 21 | this.isMain = false; 22 | } 23 | 24 | public Camera(String name, Transform transform, Frustum frustum) { 25 | this(name, transform); 26 | this.frustum = frustum; 27 | } 28 | 29 | public Frustum getFrustum() { 30 | return frustum; 31 | } 32 | 33 | public FrameBuffer getRenderTarget() { 34 | return renderTarget; 35 | } 36 | 37 | public void setRenderTarget(FrameBuffer renderTarget) { 38 | if(this.renderTarget != renderTarget) { 39 | this.renderTarget = renderTarget; 40 | frustum.setRenderTargetSize(renderTarget.getWidth(), renderTarget.getHeight()); 41 | } 42 | } 43 | 44 | /** 45 | * Returns the max light to camera distance a light can have before the 46 | * light gets culled and doesn't affect the rendering of this camera anymore. 47 | * 48 | * @return The max light to camera distance. Default is 100. 49 | */ 50 | public int getMaxLightDistance() { 51 | return lightDistance; 52 | } 53 | 54 | /** 55 | * Sets the max light to camera distance a light can have before the 56 | * light gets culled and doesn't affect the rendering of this camera anymore. 57 | * 58 | * @param lightDistance fixed point value. Default is 100. 59 | */ 60 | public void setMaxLightDistance(int lightDistance) { 61 | this.lightDistance = lightDistance; 62 | } 63 | 64 | public boolean isMain() { 65 | return isMain; 66 | } 67 | 68 | public void setMain(boolean isMain) { 69 | this.isMain = isMain; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Face.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class Face { 4 | 5 | private final int index; 6 | private final Vertex[] vertices; 7 | private final int[] localNormal; 8 | private final int[] worldNormal; 9 | private final int[][] uvs; 10 | private final Material material; 11 | private int lightColor; 12 | 13 | public Face(int index, Vertex[] vertices, int[] normal, int[][] uvs, Material material) { 14 | this.index = index; 15 | this.vertices = vertices; 16 | this.localNormal = normal; 17 | this.worldNormal = normal.clone(); 18 | this.uvs = uvs; 19 | this.material = material; 20 | } 21 | 22 | public int getIndex() { 23 | return index; 24 | } 25 | 26 | public Vertex getVertex(int index) { 27 | return vertices[index]; 28 | } 29 | 30 | public Vertex[] getVertices() { 31 | return vertices; 32 | } 33 | 34 | public int[] getLocalNormal() { 35 | return localNormal; 36 | } 37 | 38 | public int[] getWorldNormal() { 39 | return worldNormal; 40 | } 41 | 42 | public int[] getUV(int index) { 43 | return uvs[index]; 44 | } 45 | 46 | public int[][] getUVs() { 47 | return uvs; 48 | } 49 | 50 | public Material getMaterial() { 51 | return material; 52 | } 53 | 54 | /** 55 | * Returns the light color of this {@link Face}. 56 | * The light color is the color of all lights that reach and affect the illumination of 57 | * this Face put together. 58 | * 59 | * @return The light color of this Face. 60 | */ 61 | public int getLightColor() { 62 | return lightColor; 63 | } 64 | 65 | /** 66 | * Sets the light color of this {@link Face}. 67 | * The light color is the color of all lights that reach and affect the illumination of 68 | * this Vertex put together. 69 | * 70 | * @param lightColor to set. 71 | */ 72 | public void setLightColor(int lightColor) { 73 | this.lightColor = lightColor; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/FrameBuffer.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.awt.image.DataBufferInt; 5 | 6 | import com.johnsproject.jgameengine.util.ColorUtils; 7 | 8 | public class FrameBuffer { 9 | 10 | private final int[] size; 11 | private final BufferedImage image; 12 | private final Texture colorBuffer; 13 | private final Texture depthBuffer; 14 | private final Texture stencilBuffer; 15 | 16 | public FrameBuffer(BufferedImage image) { 17 | this.size = new int[] {image.getWidth(), image.getHeight(), 0, 0}; 18 | this.size[2] = size[0] * size[1]; 19 | this.image = image; 20 | int[] pixelBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 21 | this.colorBuffer = new Texture(size[0], size[1], pixelBuffer); 22 | this.depthBuffer = new Texture(size[0], size[1]); 23 | this.stencilBuffer = new Texture(size[0], size[1]); 24 | } 25 | 26 | public FrameBuffer(int width, int height) { 27 | this.size = new int[] {width, height, 0, 0}; 28 | this.size[2] = width * height; 29 | this.image = new BufferedImage(width, height, ColorUtils.COLOR_TYPE); 30 | int[] pixelBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 31 | this.colorBuffer = new Texture(width, height, pixelBuffer); 32 | this.depthBuffer = new Texture(width, height); 33 | this.stencilBuffer = new Texture(width, height); 34 | } 35 | 36 | public BufferedImage getImage() { 37 | return image; 38 | } 39 | 40 | public Texture getColorBuffer() { 41 | return colorBuffer; 42 | } 43 | 44 | public Texture getDepthBuffer() { 45 | return depthBuffer; 46 | } 47 | 48 | public Texture getStencilBuffer() { 49 | return stencilBuffer; 50 | } 51 | 52 | public int getWidth() { 53 | return size[0]; 54 | } 55 | 56 | public int getHeight() { 57 | return size[1]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Frustum.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_ONE; 4 | 5 | import com.johnsproject.jgameengine.util.FixedPointUtils; 6 | import com.johnsproject.jgameengine.util.MatrixUtils; 7 | import com.johnsproject.jgameengine.util.TransformationUtils; 8 | 9 | public class Frustum { 10 | 11 | private int left; 12 | private int right; 13 | private int top; 14 | private int bottom; 15 | private int near; 16 | private int far; 17 | 18 | private int renderTargetLeft; 19 | private int renderTargetRight; 20 | private int renderTargetTop; 21 | private int renderTargetBottom; 22 | 23 | private int renderTargetWidth; 24 | private int renderTargetHeight; 25 | 26 | private int focalLength; 27 | private int[][] projectionMatrix; 28 | 29 | private FrustumType type; 30 | 31 | public Frustum() { 32 | this.focalLength = FP_ONE; 33 | this.projectionMatrix = MatrixUtils.indentityMatrix(); 34 | this.type = FrustumType.PERSPECTIVE; 35 | } 36 | 37 | public Frustum(int left, int right, int top, int bottom, int near, int far) { 38 | this(); 39 | this.left = left; 40 | this.right = right; 41 | this.top = top; 42 | this.bottom = bottom; 43 | this.near = near; 44 | this.far = far; 45 | recalculateFrustum(); 46 | } 47 | 48 | private void recalculateFrustum() { 49 | renderTargetLeft = FixedPointUtils.multiply(renderTargetWidth, left); 50 | renderTargetRight = FixedPointUtils.multiply(renderTargetWidth, right); 51 | renderTargetTop = FixedPointUtils.multiply(renderTargetHeight, top); 52 | renderTargetBottom = FixedPointUtils.multiply(renderTargetHeight, bottom); 53 | recalculateProjectionMatrix(); 54 | } 55 | 56 | private void recalculateProjectionMatrix() { 57 | switch (type) { 58 | case ORTHOGRAPHIC: 59 | TransformationUtils.orthographicMatrix(projectionMatrix, this); 60 | break; 61 | 62 | case PERSPECTIVE: 63 | TransformationUtils.perspectiveMatrix(projectionMatrix, this); 64 | break; 65 | } 66 | } 67 | 68 | public void setFrustum(int left, int right, int top, int bottom, int near, int far) { 69 | this.left = left; 70 | this.right = right; 71 | this.top = top; 72 | this.bottom = bottom; 73 | this.near = near; 74 | this.far = far; 75 | recalculateFrustum(); 76 | } 77 | 78 | public void setRenderTargetFrustum(int left, int right, int top, int bottom) { 79 | this.renderTargetLeft = left; 80 | this.renderTargetRight = right; 81 | this.renderTargetTop = top; 82 | this.renderTargetBottom = bottom; 83 | } 84 | 85 | public void setRenderTargetSize(int width, int height) { 86 | this.renderTargetWidth = width; 87 | this.renderTargetHeight = height; 88 | recalculateFrustum(); 89 | } 90 | 91 | public int getFocalLength() { 92 | return focalLength; 93 | } 94 | 95 | public void setFocalLength(int focalLength) { 96 | if(this.focalLength != focalLength) { 97 | this.focalLength = focalLength; 98 | recalculateProjectionMatrix(); 99 | } 100 | } 101 | 102 | public void setType(FrustumType type) { 103 | if(!this.type.equals(type)) { 104 | this.type = type; 105 | recalculateProjectionMatrix(); 106 | } 107 | } 108 | 109 | public int getLeft() { 110 | return left; 111 | } 112 | 113 | public int getRight() { 114 | return right; 115 | } 116 | 117 | public int getTop() { 118 | return top; 119 | } 120 | 121 | public int getBottom() { 122 | return bottom; 123 | } 124 | 125 | public int getNear() { 126 | return near; 127 | } 128 | 129 | public int getFar() { 130 | return far; 131 | } 132 | 133 | public int getRenderTargetLeft() { 134 | return renderTargetLeft; 135 | } 136 | 137 | public int getRenderTargetRight() { 138 | return renderTargetRight; 139 | } 140 | 141 | public int getRenderTargetTop() { 142 | return renderTargetTop; 143 | } 144 | 145 | public int getRenderTargetBottom() { 146 | return renderTargetBottom; 147 | } 148 | 149 | public int getRenderTargetWidth() { 150 | return renderTargetWidth; 151 | } 152 | 153 | public int getRenderTargetHeight() { 154 | return renderTargetHeight; 155 | } 156 | 157 | public int[][] getProjectionMatrix() { 158 | return projectionMatrix; 159 | } 160 | 161 | public FrustumType getType() { 162 | return type; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/FrustumType.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public enum FrustumType { 4 | 5 | ORTHOGRAPHIC, 6 | PERSPECTIVE 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Light.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import com.johnsproject.jgameengine.util.ColorUtils; 4 | import com.johnsproject.jgameengine.util.FixedPointUtils; 5 | import com.johnsproject.jgameengine.util.TransformationUtils; 6 | import com.johnsproject.jgameengine.util.VectorUtils; 7 | 8 | public class Light extends SceneObject { 9 | 10 | public static final String LIGHT_TAG = "Light"; 11 | 12 | private static final int DIRECTIONAL_BIAS = FixedPointUtils.toFixedPoint(0.05f); 13 | private static final int SPOT_BIAS = FixedPointUtils.toFixedPoint(2f); 14 | 15 | private LightType type; 16 | private int intensity; 17 | private int color; 18 | private int ambientColor; 19 | private final int[] directionRotation; 20 | private final int[] direction; 21 | private int spotSize; 22 | private int spotSizeCos; 23 | private int innerSpotSize; 24 | private int innerSpotSizeCos; 25 | private int spotSoftness; 26 | private int constantAttenuation; 27 | private int linearAttenuation; 28 | private int quadraticAttenuation; 29 | private int shadowBias; 30 | private boolean hasShadow; 31 | private boolean isMain; 32 | 33 | public Light(String name, Transform transform) { 34 | super(name, transform); 35 | super.tag = LIGHT_TAG; 36 | super.rigidBody.setKinematic(true); 37 | this.type = LightType.DIRECTIONAL; 38 | this.intensity = FixedPointUtils.FP_ONE; 39 | this.color = ColorUtils.WHITE; 40 | this.ambientColor = ColorUtils.toColor(30, 30, 30); 41 | this.directionRotation = VectorUtils.emptyVector(); 42 | this.direction = VectorUtils.VECTOR_FORWARD.clone(); 43 | this.constantAttenuation = FixedPointUtils.toFixedPoint(1); 44 | this.linearAttenuation = FixedPointUtils.toFixedPoint(0.09); 45 | this.quadraticAttenuation = FixedPointUtils.toFixedPoint(0.032); 46 | this.shadowBias = DIRECTIONAL_BIAS; 47 | this.hasShadow = true; 48 | this.isMain = false; 49 | setSpotSize(FixedPointUtils.toFixedPoint(45)); 50 | setInnerSpotSize(FixedPointUtils.toFixedPoint(35)); 51 | } 52 | 53 | public LightType getType() { 54 | return type; 55 | } 56 | 57 | public void setType(LightType type) { 58 | this.type = type; 59 | if(hasShadowBiasDefaultValue()) { 60 | switch (type) { 61 | case DIRECTIONAL: 62 | shadowBias = DIRECTIONAL_BIAS; 63 | break; 64 | case SPOT: 65 | shadowBias = SPOT_BIAS; 66 | break; 67 | default: 68 | break; 69 | } 70 | } 71 | } 72 | 73 | private boolean hasShadowBiasDefaultValue() { 74 | return (shadowBias == DIRECTIONAL_BIAS) 75 | || (shadowBias == SPOT_BIAS); 76 | } 77 | 78 | public int getIntensity() { 79 | return intensity; 80 | } 81 | 82 | public void setIntensity(int strength) { 83 | this.intensity = strength; 84 | } 85 | 86 | public int getColor() { 87 | return color; 88 | } 89 | 90 | public void setColor(int color) { 91 | this.color = color; 92 | } 93 | 94 | public int getAmbientColor() { 95 | return ambientColor; 96 | } 97 | 98 | public void setAmbientColor(int ambientColor) { 99 | this.ambientColor = ambientColor; 100 | } 101 | 102 | /** 103 | * Returns the direction of this {@link Light}. 104 | * The direction is calculated based on the light's rotation. 105 | * 106 | * @return The direction of this Light. 107 | */ 108 | public int[] getDirection() { 109 | if(!VectorUtils.equals(directionRotation, transform.getRotation())) { 110 | synchronized (directionRotation) { 111 | VectorUtils.copy(directionRotation, transform.getRotation()); 112 | VectorUtils.copy(direction, VectorUtils.VECTOR_FORWARD); 113 | TransformationUtils.rotateX(direction, directionRotation[VectorUtils.VECTOR_X]); 114 | TransformationUtils.rotateY(direction, directionRotation[VectorUtils.VECTOR_Y]); 115 | TransformationUtils.rotateZ(direction, directionRotation[VectorUtils.VECTOR_Z]); 116 | } 117 | } 118 | return direction; 119 | } 120 | 121 | /** 122 | * Returns the cosine of the spot size of this {@link Light}. 123 | * This is needed for lighting calculations. 124 | * 125 | * @return The cosine of the spot size. 126 | */ 127 | public int getSpotSizeCosine() { 128 | return spotSizeCos; 129 | } 130 | 131 | public int getSpotSize() { 132 | return spotSize; 133 | } 134 | 135 | public void setSpotSize(int degrees) { 136 | this.spotSize = degrees; 137 | // divide by 2 so the size is the size of the whole spot 138 | this.spotSizeCos = FixedPointUtils.cos(degrees >> 1); 139 | calculateSpotSoftness(); 140 | } 141 | 142 | /** 143 | * Returns the cosine of the inner spot size of this {@link Light}. 144 | * This is needed for lighting calculations. 145 | * 146 | * @return The cosine of the inner spot size. 147 | */ 148 | public int getInnerSpotSizeCosine() { 149 | return innerSpotSizeCos; 150 | } 151 | 152 | public int getInnerSpotSize() { 153 | return innerSpotSize; 154 | } 155 | 156 | public void setInnerSpotSize(int degrees) { 157 | this.innerSpotSize = degrees; 158 | this.innerSpotSizeCos = FixedPointUtils.cos(degrees >> 1); 159 | calculateSpotSoftness(); 160 | } 161 | 162 | /** 163 | * Returns the difference between {@link #getInnerSpotSizeCosine()} and {@link #getSpotSizeCosine()}. 164 | * This is needed for lighting calculations. 165 | * 166 | * @return The difference between innerSpotSize and spotSize. 167 | */ 168 | public int getSpotSoftness() { 169 | return spotSoftness; 170 | } 171 | 172 | private void calculateSpotSoftness() { 173 | // + 1 because it can't be 0 174 | spotSoftness = (innerSpotSizeCos - spotSizeCos) + 1; 175 | } 176 | 177 | public int getConstantAttenuation() { 178 | return constantAttenuation; 179 | } 180 | 181 | public void setConstantAttenuation(int constantAttenuation) { 182 | this.constantAttenuation = constantAttenuation; 183 | } 184 | 185 | public int getLinearAttenuation() { 186 | return linearAttenuation; 187 | } 188 | 189 | public void setLinearAttenuation(int linearAttenuation) { 190 | this.linearAttenuation = linearAttenuation; 191 | } 192 | 193 | public int getQuadraticAttenuation() { 194 | return quadraticAttenuation; 195 | } 196 | 197 | public void setQuadraticAttenuation(int quadraticAttenuation) { 198 | this.quadraticAttenuation = quadraticAttenuation; 199 | } 200 | 201 | public int getShadowBias() { 202 | return shadowBias; 203 | } 204 | 205 | public void setShadowBias(int shadowBias) { 206 | this.shadowBias = shadowBias; 207 | } 208 | 209 | public boolean hasShadow() { 210 | return hasShadow; 211 | } 212 | 213 | public void setShadow(boolean hasShadow) { 214 | this.hasShadow = hasShadow; 215 | } 216 | 217 | public boolean isMain() { 218 | return isMain; 219 | } 220 | 221 | public void setMain(boolean isMain) { 222 | this.isMain = isMain; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/LightType.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public enum LightType { 4 | 5 | DIRECTIONAL, 6 | POINT, 7 | SPOT 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Material.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import com.johnsproject.jgameengine.shading.Shader; 4 | 5 | public class Material { 6 | 7 | private final int index; 8 | private final String name; 9 | private Shader shader; 10 | private int shininess; 11 | private int diffuseColor; 12 | private int specularColor; 13 | private Texture texture; 14 | 15 | public Material(int index, String name) { 16 | this.index = index; 17 | this.name = name; 18 | this.shader = null; 19 | this.shininess = 0; 20 | this.texture = null; 21 | } 22 | 23 | public int getIndex() { 24 | return index; 25 | } 26 | 27 | public String getName() { 28 | return name; 29 | } 30 | 31 | public Shader getShader() { 32 | return shader; 33 | } 34 | 35 | public void setShader(Shader shader) { 36 | this.shader = shader; 37 | } 38 | 39 | public int getShininess() { 40 | return shininess; 41 | } 42 | 43 | public void setShininess(int shininess) { 44 | this.shininess = shininess; 45 | } 46 | 47 | public int getDiffuseColor() { 48 | return diffuseColor; 49 | } 50 | 51 | public void setDiffuseColor(int diffuseColor) { 52 | this.diffuseColor = diffuseColor; 53 | } 54 | 55 | public int getSpecularColor() { 56 | return specularColor; 57 | } 58 | 59 | public void setSpecularColor(int specularColor) { 60 | this.specularColor = specularColor; 61 | } 62 | 63 | public Texture getTexture() { 64 | return texture; 65 | } 66 | 67 | public void setTexture(Texture texture) { 68 | this.texture = texture; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Mesh.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class Mesh { 4 | 5 | private final Vertex[] vertices; 6 | private final Face[] faces; 7 | private final Material[] materials; 8 | 9 | public Mesh(Vertex[] vertices, Face[] faces, Material[] materials) { 10 | this.vertices = vertices; 11 | this.faces = faces; 12 | this.materials = materials; 13 | } 14 | 15 | public Vertex[] getVertices(){ 16 | return vertices; 17 | } 18 | 19 | public Vertex getVertex(int index){ 20 | return vertices[index]; 21 | } 22 | 23 | public Face[] getFaces() { 24 | return faces; 25 | } 26 | 27 | public Face getFace(int index) { 28 | return faces[index]; 29 | } 30 | 31 | public Material[] getMaterials() { 32 | return materials; 33 | } 34 | 35 | public Material getMaterial(int index) { 36 | return materials[index]; 37 | } 38 | 39 | public Material getMaterial(String name) { 40 | for (int i = 0; i < materials.length; i++) { 41 | Material material = materials[i]; 42 | if (material.getName().equals(name)) { 43 | return material; 44 | } 45 | } 46 | return materials[0]; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Model.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class Model extends SceneObject { 4 | 5 | public static final String MODEL_TAG = "Model"; 6 | 7 | private final Mesh mesh; 8 | private final Armature armature; 9 | 10 | public Model (String name, Transform transform, Mesh mesh) { 11 | super(name, transform); 12 | super.tag = MODEL_TAG; 13 | this.mesh = mesh; 14 | this.armature = null; 15 | } 16 | 17 | public Model (String name, Transform transform, Mesh mesh, Armature armature) { 18 | super(name, transform); 19 | super.tag = MODEL_TAG; 20 | this.mesh = mesh; 21 | this.armature = armature; 22 | } 23 | 24 | public Mesh getMesh() { 25 | return mesh; 26 | } 27 | 28 | public Armature getArmature() { 29 | return armature; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/RigidBody.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import static com.johnsproject.jgameengine.util.VectorUtils.*; 4 | 5 | import com.johnsproject.jgameengine.util.FixedPointUtils; 6 | import com.johnsproject.jgameengine.util.MatrixUtils; 7 | import com.johnsproject.jgameengine.util.VectorUtils; 8 | 9 | public class RigidBody { 10 | 11 | private boolean kinematic; 12 | private int mass; 13 | private final int[] force; 14 | private final int[] torque; 15 | private final int[] linearVelocity; 16 | private final int[] angularVelocity; 17 | private final int[][] momentOfInertia; 18 | 19 | public RigidBody() { 20 | this.kinematic = false; 21 | this.mass = FixedPointUtils.FP_ONE; 22 | this.force = VectorUtils.emptyVector(); 23 | this.torque = VectorUtils.emptyVector(); 24 | this.linearVelocity = VectorUtils.emptyVector(); 25 | this.angularVelocity = VectorUtils.emptyVector(); 26 | this.momentOfInertia = MatrixUtils.indentityMatrix(); 27 | } 28 | 29 | public boolean isKinematic() { 30 | return kinematic; 31 | } 32 | 33 | public void setKinematic(boolean kinematic) { 34 | this.kinematic = kinematic; 35 | } 36 | 37 | public int getMass() { 38 | return mass; 39 | } 40 | 41 | public void setMass(int mass) { 42 | this.mass = mass; 43 | } 44 | 45 | public int[] getForce() { 46 | return force; 47 | } 48 | 49 | public int[] getTorque() { 50 | return torque; 51 | } 52 | 53 | public int[] getLinearVelocity() { 54 | return linearVelocity; 55 | } 56 | 57 | public int[] getAngularVelocity() { 58 | return angularVelocity; 59 | } 60 | 61 | public int[][] getMomentOfInertia() { 62 | return momentOfInertia; 63 | } 64 | 65 | public void setForce(int x, int y, int z) { 66 | force[VECTOR_X] = x; 67 | force[VECTOR_Y] = y; 68 | force[VECTOR_Z] = z; 69 | } 70 | 71 | public void addForce(int x, int y, int z) { 72 | force[VECTOR_X] += x; 73 | force[VECTOR_Y] += y; 74 | force[VECTOR_Z] += z; 75 | } 76 | 77 | public void addForce(int[] vector) { 78 | force[VECTOR_X] += vector[VECTOR_X]; 79 | force[VECTOR_Y] += vector[VECTOR_Y]; 80 | force[VECTOR_Z] += vector[VECTOR_Z]; 81 | } 82 | 83 | public void setTorque(int x, int y, int z) { 84 | torque[VECTOR_X] = x; 85 | torque[VECTOR_Y] = y; 86 | torque[VECTOR_Z] = z; 87 | } 88 | 89 | public void addTorque(int x, int y, int z) { 90 | torque[VECTOR_X] += x; 91 | torque[VECTOR_Y] += y; 92 | torque[VECTOR_Z] += z; 93 | } 94 | 95 | public void addTorque(int[] vector) { 96 | torque[VECTOR_X] += vector[VECTOR_X]; 97 | torque[VECTOR_Y] += vector[VECTOR_Y]; 98 | torque[VECTOR_Z] += vector[VECTOR_Z]; 99 | } 100 | 101 | public void setLinearVelocity(int x, int y, int z) { 102 | linearVelocity[VECTOR_X] = x; 103 | linearVelocity[VECTOR_Y] = y; 104 | linearVelocity[VECTOR_Z] = z; 105 | } 106 | 107 | public void addLinearVelocity(int x, int y, int z) { 108 | linearVelocity[VECTOR_X] += x; 109 | linearVelocity[VECTOR_Y] += y; 110 | linearVelocity[VECTOR_Z] += z; 111 | } 112 | 113 | public void addLinearVelocity(int[] vector) { 114 | linearVelocity[VECTOR_X] += vector[VECTOR_X]; 115 | linearVelocity[VECTOR_Y] += vector[VECTOR_Y]; 116 | linearVelocity[VECTOR_Z] += vector[VECTOR_Z]; 117 | } 118 | 119 | // needs to be converted to radians 120 | public void setAngularVelocity(int x, int y, int z) { 121 | angularVelocity[VECTOR_X] = x; 122 | angularVelocity[VECTOR_Y] = y; 123 | angularVelocity[VECTOR_Z] = z; 124 | } 125 | 126 | public void addAngularVelocity(int x, int y, int z) { 127 | angularVelocity[VECTOR_X] += x; 128 | angularVelocity[VECTOR_Y] += y; 129 | angularVelocity[VECTOR_Z] += z; 130 | } 131 | 132 | public void addAngularVelocity(int[] vector) { 133 | angularVelocity[VECTOR_X] += vector[VECTOR_X]; 134 | angularVelocity[VECTOR_Y] += vector[VECTOR_Y]; 135 | angularVelocity[VECTOR_Z] += vector[VECTOR_Z]; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Scene.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class Scene { 6 | 7 | private Camera mainCamera; 8 | private Light mainLight; 9 | private final ArrayList sceneObjects; 10 | private final ArrayList models; 11 | private final ArrayList cameras; 12 | private final ArrayList lights; 13 | 14 | public Scene() { 15 | this.sceneObjects = new ArrayList(); 16 | this.models = new ArrayList(); 17 | this.cameras = new ArrayList(); 18 | this.lights = new ArrayList(); 19 | } 20 | 21 | public ArrayList getSceneObjects() { 22 | return sceneObjects; 23 | } 24 | 25 | public SceneObject getSceneObject(String name) { 26 | for (int i = 0; i < sceneObjects.size(); i++) { 27 | SceneObject sceneObject = sceneObjects.get(i); 28 | if(sceneObject.getName().equals(name)) { 29 | return sceneObject; 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | private void removeSceneObject(String name) { 36 | for (int i = 0; i < sceneObjects.size(); i++) { 37 | if(sceneObjects.get(i).getName().equals(name)) { 38 | sceneObjects.remove(i); 39 | } 40 | } 41 | } 42 | 43 | public void addModel(Model model){ 44 | sceneObjects.add(model); 45 | models.add(model); 46 | } 47 | 48 | public void removeModel(String name){ 49 | removeSceneObject(name); 50 | for (int i = 0; i < models.size(); i++) { 51 | if(models.get(i).getName().equals(name)) { 52 | models.remove(i); 53 | } 54 | } 55 | } 56 | 57 | public ArrayList getModels() { 58 | return models; 59 | } 60 | 61 | public Model getModel(String name) { 62 | return (Model)getSceneObject(name); 63 | } 64 | 65 | public void addLight(Light light){ 66 | if(mainLight == null) { 67 | setMainDirectionalLight(light); 68 | } 69 | sceneObjects.add(light); 70 | lights.add(light); 71 | } 72 | 73 | public void removeLight(String name){ 74 | removeSceneObject(name); 75 | for (int i = 0; i < lights.size(); i++) { 76 | if(lights.get(i).getName().equals(name)) { 77 | lights.remove(i); 78 | } 79 | } 80 | } 81 | 82 | public ArrayList getLights() { 83 | return lights; 84 | } 85 | 86 | public Light getLight(String name) { 87 | return (Light)getSceneObject(name); 88 | } 89 | 90 | public Light getMainDirectionalLight() { 91 | return mainLight; 92 | } 93 | 94 | public void setMainDirectionalLight(Light mainLight) { 95 | if(this.mainLight != null) { 96 | this.mainLight.setMain(false); 97 | } 98 | mainLight.setMain(true); 99 | this.mainLight = mainLight; 100 | } 101 | 102 | public void addCamera(Camera camera){ 103 | if(mainCamera == null) { 104 | setMainCamera(camera); 105 | } 106 | sceneObjects.add(camera); 107 | cameras.add(camera); 108 | } 109 | 110 | public void removeCamera(String name){ 111 | removeSceneObject(name); 112 | for (int i = 0; i < cameras.size(); i++) { 113 | if(cameras.get(i).getName().equals(name)) { 114 | cameras.remove(i); 115 | } 116 | } 117 | } 118 | 119 | public ArrayList getCameras() { 120 | return cameras; 121 | } 122 | 123 | public Camera getCamera(String name) { 124 | return (Camera)getSceneObject(name); 125 | } 126 | 127 | public Camera getMainCamera() { 128 | return mainCamera; 129 | } 130 | 131 | public void setMainCamera(Camera mainCamera) { 132 | if(this.mainCamera != null) { 133 | this.mainCamera.setMain(false); 134 | } 135 | mainCamera.setMain(true); 136 | this.mainCamera = mainCamera; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/SceneObject.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class SceneObject { 4 | 5 | protected String tag; 6 | protected boolean active; 7 | protected boolean culled; 8 | protected final String name; 9 | protected final Transform transform; 10 | protected final RigidBody rigidBody; 11 | 12 | public SceneObject(String name, Transform transform) { 13 | this.tag = ""; 14 | this.name = name; 15 | this.transform = transform; 16 | this.active = true; 17 | this.culled = false; 18 | this.rigidBody = new RigidBody(); 19 | } 20 | 21 | public Transform getTransform() { 22 | return this.transform; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public RigidBody getRigidBody() { 30 | return rigidBody; 31 | } 32 | 33 | public boolean isActive() { 34 | return active; 35 | } 36 | 37 | public void setActive(boolean active) { 38 | this.active = active; 39 | } 40 | 41 | public String getTag() { 42 | return tag; 43 | } 44 | 45 | public void setTag(String tag) { 46 | this.tag = tag; 47 | } 48 | 49 | public boolean isCulled() { 50 | return culled; 51 | } 52 | 53 | public void setCulled(boolean culled) { 54 | this.culled = culled; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Texture.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.awt.image.DataBufferInt; 5 | 6 | public class Texture { 7 | 8 | private final int[] pixels; 9 | private final int[] size; 10 | 11 | public Texture (BufferedImage bufferedImage){ 12 | int width = bufferedImage.getWidth(); 13 | int height = bufferedImage.getHeight(); 14 | this.size = new int[] {width, height, width * height, 0}; 15 | this.pixels = ((DataBufferInt)bufferedImage.getRaster().getDataBuffer()).getData(); 16 | } 17 | 18 | public Texture (int width, int height, int[] pixels){ 19 | this.size = new int[] {width, height, width * height, 0}; 20 | this.pixels = pixels; 21 | } 22 | 23 | public Texture (int width, int height){ 24 | this.size = new int[] {width, height, width * height, 0}; 25 | this.pixels = new int[size[2]]; 26 | } 27 | 28 | public int[] getPixels() { 29 | return pixels; 30 | } 31 | 32 | public int getWidth() { 33 | return size[0]; 34 | } 35 | 36 | public int getHeight() { 37 | return size[1]; 38 | } 39 | 40 | public int getPixel(int x, int y) { 41 | x = x >= 0 ? x : 0; 42 | x = x < size[0] ? x : size[0] - 1; 43 | y = y >= 0 ? y : 0; 44 | y = y < size[1] ? y : size[1] - 1; 45 | return pixels[x + (y * size[0])]; 46 | } 47 | 48 | public void setPixel(int x, int y, int value) { 49 | x = x >= 0 ? x : 0; 50 | x = x < size[0] ? x : size[0] - 1; 51 | y = y >= 0 ? y : 0; 52 | y = y < size[1] ? y : size[1] - 1; 53 | pixels[x + (y * size[0])] = value; 54 | } 55 | 56 | public void fill(int value) { 57 | int[] pixelBuffer = getPixels(); 58 | for (int i = 0; i < pixelBuffer.length; i++) { 59 | pixelBuffer[i] = value; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Transform.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | 4 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_ONE; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 7 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 8 | 9 | import com.johnsproject.jgameengine.util.FixedPointUtils; 10 | import com.johnsproject.jgameengine.util.MatrixUtils; 11 | import com.johnsproject.jgameengine.util.TransformationUtils; 12 | import com.johnsproject.jgameengine.util.VectorUtils; 13 | 14 | public class Transform { 15 | 16 | private final int[] location; 17 | private final int[] rotation; 18 | private final int[] scale; 19 | 20 | private final int[][] matrixCache1; 21 | private final int[][] matrixCache2; 22 | private final int[][] spaceEnterMatrix; 23 | private final int[][] spaceEnterNormalMatrix; 24 | private final int[][] spaceExitMatrix; 25 | private final int[][] spaceExitNormalMatrix; 26 | 27 | public Transform() { 28 | this(VectorUtils.emptyVector(), VectorUtils.emptyVector(), VectorUtils.VECTOR_ONE.clone()); 29 | } 30 | 31 | public Transform(int[] location, int[] rotation, int[] scale) { 32 | this.location = location; 33 | this.rotation = rotation; 34 | this.scale = scale; 35 | this.matrixCache1 = MatrixUtils.indentityMatrix(); 36 | this.matrixCache2 = MatrixUtils.indentityMatrix(); 37 | this.spaceEnterMatrix = MatrixUtils.indentityMatrix(); 38 | this.spaceEnterNormalMatrix = MatrixUtils.indentityMatrix(); 39 | this.spaceExitMatrix = MatrixUtils.indentityMatrix(); 40 | this.spaceExitNormalMatrix = MatrixUtils.indentityMatrix(); 41 | recalculateMatrices(); 42 | } 43 | 44 | private void recalculateMatrices() { 45 | spaceExitMatrix(); 46 | spaceExitNormalMatrix(); 47 | spaceEnterMatrix(); 48 | spaceEnterNormalMatrix(); 49 | } 50 | 51 | private void spaceExitMatrix() { 52 | MatrixUtils.copy(spaceExitMatrix, MatrixUtils.MATRIX_IDENTITY); 53 | scale(spaceExitMatrix); 54 | rotateX(spaceExitMatrix, rotation[VECTOR_X]); 55 | rotateY(spaceExitMatrix, rotation[VECTOR_Y]); 56 | rotateZ(spaceExitMatrix, rotation[VECTOR_Z]); 57 | translate(spaceExitMatrix); 58 | } 59 | 60 | private void spaceExitNormalMatrix() { 61 | MatrixUtils.copy(spaceExitNormalMatrix, MatrixUtils.MATRIX_IDENTITY); 62 | scale(spaceExitNormalMatrix); 63 | rotateX(spaceExitNormalMatrix, rotation[VECTOR_X]); 64 | rotateY(spaceExitNormalMatrix, rotation[VECTOR_Y]); 65 | rotateZ(spaceExitNormalMatrix, rotation[VECTOR_Z]); 66 | if ((scale[VECTOR_X] != scale[VECTOR_Y]) || (scale[VECTOR_Y] != scale[VECTOR_Z])) { 67 | MatrixUtils.inverse(spaceExitNormalMatrix, matrixCache2); 68 | MatrixUtils.transpose(matrixCache2, spaceExitNormalMatrix); 69 | } 70 | } 71 | 72 | private void spaceEnterMatrix() { 73 | int scaleX = FixedPointUtils.divide(FP_ONE, scale[VECTOR_X] == 0 ? 1 : scale[VECTOR_X]); 74 | int scaleY = FixedPointUtils.divide(FP_ONE, scale[VECTOR_Y] == 0 ? 1 : scale[VECTOR_Y]); 75 | int scaleZ = FixedPointUtils.divide(FP_ONE, scale[VECTOR_Z] == 0 ? 1 : scale[VECTOR_Z]); 76 | VectorUtils.invert(location); 77 | VectorUtils.invert(rotation); 78 | MatrixUtils.copy(spaceEnterMatrix, MatrixUtils.MATRIX_IDENTITY); 79 | translate(spaceEnterMatrix); 80 | rotateZ(spaceEnterMatrix, rotation[VECTOR_Z]); 81 | rotateY(spaceEnterMatrix, rotation[VECTOR_Y]); 82 | rotateX(spaceEnterMatrix, rotation[VECTOR_X]); 83 | scale(spaceEnterMatrix, scaleX, scaleY, scaleZ); 84 | VectorUtils.invert(location); 85 | VectorUtils.invert(rotation); 86 | } 87 | 88 | private void spaceEnterNormalMatrix() { 89 | int scaleX = FixedPointUtils.divide(FP_ONE, scale[VECTOR_X] == 0 ? 1 : scale[VECTOR_X]); 90 | int scaleY = FixedPointUtils.divide(FP_ONE, scale[VECTOR_Y] == 0 ? 1 : scale[VECTOR_Y]); 91 | int scaleZ = FixedPointUtils.divide(FP_ONE, scale[VECTOR_Z] == 0 ? 1 : scale[VECTOR_Z]); 92 | VectorUtils.invert(rotation); 93 | MatrixUtils.copy(spaceEnterNormalMatrix, MatrixUtils.MATRIX_IDENTITY); 94 | rotateZ(spaceEnterNormalMatrix, rotation[VECTOR_Z]); 95 | rotateY(spaceEnterNormalMatrix, rotation[VECTOR_Y]); 96 | rotateX(spaceEnterNormalMatrix, rotation[VECTOR_X]); 97 | scale(spaceEnterNormalMatrix, scaleX, scaleY, scaleZ); 98 | VectorUtils.invert(rotation); 99 | if ((scale[VECTOR_X] != scale[VECTOR_Y]) || (scale[VECTOR_Y] != scale[VECTOR_Z])) { 100 | MatrixUtils.inverse(spaceEnterNormalMatrix, matrixCache2); 101 | MatrixUtils.transpose(matrixCache2, spaceEnterNormalMatrix); 102 | } 103 | } 104 | 105 | private void translate(int[][] matrix) { 106 | TransformationUtils.translationMatrix(matrixCache1, location); 107 | MatrixUtils.copy(matrixCache2, matrix); 108 | MatrixUtils.multiply(matrixCache1, matrixCache2, matrix); 109 | } 110 | 111 | private void scale(int[][] matrix) { 112 | scale(matrix, scale[VECTOR_X], scale[VECTOR_Y], scale[VECTOR_Z]); 113 | } 114 | 115 | private void scale(int[][] matrix, int x, int y, int z) { 116 | TransformationUtils.scaleMatrix(matrixCache1, x, y, z); 117 | MatrixUtils.copy(matrixCache2, matrix); 118 | MatrixUtils.multiply(matrixCache1, matrixCache2, matrix); 119 | } 120 | 121 | private void rotateX(int[][] matrix, int angle) { 122 | TransformationUtils.xRotationMatrix(matrixCache1, angle); 123 | MatrixUtils.copy(matrixCache2, matrix); 124 | MatrixUtils.multiply(matrixCache1, matrixCache2, matrix); 125 | } 126 | 127 | private void rotateY(int[][] matrix, int angle) { 128 | TransformationUtils.yRotationMatrix(matrixCache1, angle); 129 | MatrixUtils.copy(matrixCache2, matrix); 130 | MatrixUtils.multiply(matrixCache1, matrixCache2, matrix); 131 | } 132 | 133 | private void rotateZ(int[][] matrix, int angle) { 134 | TransformationUtils.zRotationMatrix(matrixCache1, angle); 135 | MatrixUtils.copy(matrixCache2, matrix); 136 | MatrixUtils.multiply(matrixCache1, matrixCache2, matrix); 137 | } 138 | 139 | public void setLocation(int x, int y, int z) { 140 | location[VECTOR_X] = x; 141 | location[VECTOR_Y] = y; 142 | location[VECTOR_Z] = z; 143 | recalculateMatrices(); 144 | } 145 | 146 | public void setRotation(int x, int y, int z) { 147 | rotation[VECTOR_X] = x; 148 | rotation[VECTOR_Y] = y; 149 | rotation[VECTOR_Z] = z; 150 | recalculateMatrices(); 151 | } 152 | 153 | public void setScale(int x, int y, int z) { 154 | scale[VECTOR_X] = x; 155 | scale[VECTOR_Y] = y; 156 | scale[VECTOR_Z] = z; 157 | recalculateMatrices(); 158 | } 159 | 160 | public void worldTranslate(int x, int y, int z) { 161 | setLocation(location[VECTOR_X] + x, location[VECTOR_Y] + y, location[VECTOR_Z] + z); 162 | } 163 | 164 | public void worldTranslate(int[] vector) { 165 | worldTranslate(vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 166 | } 167 | 168 | public void localTranslate(int x, int y, int z) { 169 | worldTranslate(localToWorld(x, y, z)); 170 | } 171 | 172 | public void localTranslate(int[] vector) { 173 | localTranslate(vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 174 | } 175 | 176 | public void worldRotate(int x, int y, int z) { 177 | setRotation(rotation[VECTOR_X] + x, rotation[VECTOR_Y] + y, rotation[VECTOR_Z] + z); 178 | } 179 | 180 | public void worldRotate(int[] angles) { 181 | worldRotate(angles[VECTOR_X], angles[VECTOR_Y], angles[VECTOR_Z]); 182 | } 183 | 184 | public void localRotate(int x, int y, int z) { 185 | worldRotate(localToWorld(x, y, z)); 186 | } 187 | 188 | public void localRotate(int[] angles) { 189 | localRotate(angles[VECTOR_X], angles[VECTOR_Y], angles[VECTOR_Z]); 190 | } 191 | 192 | public void worldScale(int x, int y, int z) { 193 | setScale(scale[VECTOR_X] + x, scale[VECTOR_Y] + y, scale[VECTOR_Z] + z); 194 | } 195 | 196 | public void worldScale(int[] vector) { 197 | worldScale(vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 198 | } 199 | 200 | public void localScale(int x, int y, int z) { 201 | worldScale(localToWorld(x, y, z)); 202 | } 203 | 204 | public void localScale(int[] vector) { 205 | localScale(vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 206 | } 207 | 208 | private int[] localToWorld(int x, int y, int z) { 209 | final int[] vector = matrixCache1[0]; 210 | VectorUtils.copy(vector, VectorUtils.VECTOR_ZERO); 211 | vector[VECTOR_X] = x; 212 | vector[VECTOR_Y] = y; 213 | vector[VECTOR_Z] = z; 214 | TransformationUtils.rotateX(vector, rotation[VectorUtils.VECTOR_X]); 215 | TransformationUtils.rotateY(vector, rotation[VectorUtils.VECTOR_Y]); 216 | TransformationUtils.rotateZ(vector, rotation[VectorUtils.VECTOR_Z]); 217 | return vector; 218 | } 219 | 220 | public int[] getLocation() { 221 | return location; 222 | } 223 | 224 | public int[] getRotation() { 225 | return rotation; 226 | } 227 | 228 | public int[] getScale() { 229 | return scale; 230 | } 231 | 232 | public int[][] getSpaceEnterMatrix() { 233 | return spaceEnterMatrix; 234 | } 235 | 236 | public int[][] getSpaceEnterNormalMatrix() { 237 | return spaceEnterNormalMatrix; 238 | } 239 | 240 | public int[][] getSpaceExitMatrix() { 241 | return spaceExitMatrix; 242 | } 243 | 244 | public int[][] getSpaceExitNormalMatrix() { 245 | return spaceExitNormalMatrix; 246 | } 247 | } -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/Vertex.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | import com.johnsproject.jgameengine.util.VectorUtils; 4 | 5 | public class Vertex { 6 | 7 | private final int index; 8 | private final int[] localLocation; 9 | private final int[] worldLocation; 10 | private final int[] worldNormal; 11 | private final int[] location; 12 | private final Material material; 13 | private int lightColor; 14 | 15 | public Vertex(int index, int[] location, Material material) { 16 | this.index = index; 17 | this.localLocation = location; 18 | this.worldLocation = VectorUtils.emptyVector(); 19 | this.worldNormal = VectorUtils.emptyVector(); 20 | this.location = VectorUtils.emptyVector(); 21 | this.material = material; 22 | } 23 | 24 | public int getIndex() { 25 | return index; 26 | } 27 | 28 | public int[] getLocalLocation() { 29 | return localLocation; 30 | } 31 | 32 | public int[] getWorldLocation() { 33 | return worldLocation; 34 | } 35 | 36 | public int[] getWorldNormal() { 37 | return worldNormal; 38 | } 39 | 40 | public int[] getLocation() { 41 | return location; 42 | } 43 | 44 | public Material getMaterial() { 45 | return material; 46 | } 47 | 48 | /** 49 | * Returns the light color of this {@link Vertex}. 50 | * The light color is the color of all lights that reach and affect the illumination of 51 | * this Vertex put together. 52 | * 53 | * @return The light color of this Vertex. 54 | */ 55 | public int getLightColor() { 56 | return lightColor; 57 | } 58 | 59 | /** 60 | * Sets the light color of this {@link Vertex}. 61 | * The light color is the color of all lights that reach and affect the illumination of 62 | * this Vertex put together. 63 | * 64 | * @param lightColor to set. 65 | */ 66 | public void setLightColor(int lightColor) { 67 | this.lightColor = lightColor; 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/model/VertexGroup.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.model; 2 | 3 | public class VertexGroup { 4 | 5 | private final int boneIndex; 6 | private final Vertex[] vertices; 7 | private final int[] weights; 8 | 9 | public VertexGroup(int boneIndex, Vertex[] vertices, int[] weights) { 10 | this.boneIndex = boneIndex; 11 | this.vertices = vertices; 12 | this.weights = weights; 13 | } 14 | 15 | public int getBoneIndex() { 16 | return boneIndex; 17 | } 18 | 19 | public Vertex getVertex(int index) { 20 | return vertices[index]; 21 | } 22 | 23 | public Vertex[] getVertices() { 24 | return vertices; 25 | } 26 | 27 | public int getWeight(Vertex vertex) { 28 | for (int i = 0; i < vertices.length; i++) { 29 | if(vertices[i].getIndex() == vertex.getIndex()) { 30 | return getWeight(i); 31 | } 32 | } 33 | return -1; 34 | } 35 | 36 | public int getWeight(int index) { 37 | return weights[index]; 38 | } 39 | 40 | public int[] getWeights() { 41 | return weights; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/rasterization/Rasterizer.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.rasterization; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_BIT; 4 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_ONE; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 7 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 8 | 9 | import com.johnsproject.jgameengine.model.Face; 10 | import com.johnsproject.jgameengine.model.Frustum; 11 | import com.johnsproject.jgameengine.shading.Shader; 12 | import com.johnsproject.jgameengine.util.FixedPointUtils; 13 | import com.johnsproject.jgameengine.util.VectorUtils; 14 | 15 | public class Rasterizer { 16 | 17 | protected final Shader shader; 18 | protected final int[] location; 19 | protected final int[] location0; 20 | protected final int[] location1; 21 | protected final int[] location2; 22 | protected final int[] location3; 23 | 24 | protected int sortY00, sortY01; 25 | protected int sortY10, sortY11; 26 | protected int sortY20, sortY21; 27 | 28 | protected int y2y1, y3y1, y3y2; 29 | protected int dx1, dx2; 30 | protected int dz1, dz2; 31 | protected int x1, x2; 32 | protected int y1, y2; 33 | protected int z; 34 | protected int dxdx; 35 | protected int dz; 36 | 37 | private int renderTargetLeft; 38 | private int renderTargetRight; 39 | private int renderTargetTop; 40 | private int renderTargetBottom; 41 | private boolean frustumCull; 42 | private int faceCull; 43 | 44 | 45 | public Rasterizer(Shader shader) { 46 | this.shader = shader; 47 | this.location = VectorUtils.emptyVector(); 48 | this.location0 = VectorUtils.emptyVector(); 49 | this.location1 = VectorUtils.emptyVector(); 50 | this.location2 = VectorUtils.emptyVector(); 51 | this.location3 = VectorUtils.emptyVector(); 52 | this.frustumCull = true; 53 | this.faceCull = -1; 54 | } 55 | 56 | public void setFrustumCull(boolean frustumCull) { 57 | this.frustumCull = frustumCull; 58 | } 59 | 60 | public void setFaceCull(int faceCull) { 61 | this.faceCull = faceCull; 62 | } 63 | 64 | public void draw(Face face, Frustum frustum) { 65 | copyLocations(face); 66 | copyFrustum(frustum); 67 | if(isCulled()) 68 | return; 69 | sortY(); 70 | if (location1[VECTOR_Y] == location2[VECTOR_Y]) { 71 | drawBottomTriangle(); 72 | } else if (location0[VECTOR_Y] == location1[VECTOR_Y]) { 73 | drawTopTriangle(); 74 | } else { 75 | splitTriangle(); 76 | swapSplitedBottomTriangle(); 77 | drawBottomTriangle(); 78 | swapSplitedTopTriangle(); 79 | drawTopTriangle(); 80 | } 81 | } 82 | 83 | protected void copyLocations(Face face) { 84 | VectorUtils.copy(location0, face.getVertex(0).getLocation()); 85 | VectorUtils.copy(location1, face.getVertex(1).getLocation()); 86 | VectorUtils.copy(location2, face.getVertex(2).getLocation()); 87 | } 88 | 89 | protected void copyFrustum(Frustum frustum) { 90 | renderTargetLeft = frustum.getRenderTargetLeft(); 91 | renderTargetRight = frustum.getRenderTargetRight(); 92 | renderTargetTop = frustum.getRenderTargetTop(); 93 | renderTargetBottom = frustum.getRenderTargetBottom(); 94 | } 95 | 96 | protected boolean isCulled() { 97 | return isBiggerThanRenderTarget() || isOutOfFrustum() || isBackface(); 98 | } 99 | 100 | private boolean isBiggerThanRenderTarget() { 101 | final int width0 = Math.abs(location0[VECTOR_X] - location1[VECTOR_X]); 102 | final int width1 = Math.abs(location2[VECTOR_X] - location1[VECTOR_X]); 103 | final int width2 = Math.abs(location2[VECTOR_X] - location0[VECTOR_X]); 104 | final int height0 = Math.abs(location0[VECTOR_Y] - location1[VECTOR_Y]); 105 | final int height1 = Math.abs(location2[VECTOR_Y] - location1[VECTOR_Y]); 106 | final int height2 = Math.abs(location2[VECTOR_Y] - location0[VECTOR_Y]); 107 | final int width = Math.max(width0, Math.max(width1, width2)); 108 | final int height = Math.max(height0, Math.max(height1, height2)); 109 | if((width > (renderTargetRight - renderTargetLeft)) || (height > (renderTargetBottom - renderTargetTop))) { 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | private boolean isOutOfFrustum() { 116 | if(frustumCull) { 117 | final boolean insideWidth1 = (location0[VECTOR_X] > renderTargetLeft) && (location0[VECTOR_X] < renderTargetRight); 118 | final boolean insideWidth2 = (location1[VECTOR_X] > renderTargetLeft) && (location1[VECTOR_X] < renderTargetRight); 119 | final boolean insideWidth3 = (location2[VECTOR_X] > renderTargetLeft) && (location2[VECTOR_X] < renderTargetRight); 120 | final boolean insideHeight1 = (location0[VECTOR_Y] > renderTargetTop) && (location0[VECTOR_Y] < renderTargetBottom); 121 | final boolean insideHeight2 = (location1[VECTOR_Y] > renderTargetTop) && (location1[VECTOR_Y] < renderTargetBottom); 122 | final boolean insideHeight3 = (location2[VECTOR_Y] > renderTargetTop) && (location2[VECTOR_Y] < renderTargetBottom); 123 | final boolean insideDepth1 = (location0[VECTOR_Z] > 0) && (location0[VECTOR_Z] < FP_ONE); 124 | final boolean insideDepth2 = (location1[VECTOR_Z] > 0) && (location1[VECTOR_Z] < FP_ONE); 125 | final boolean insideDepth3 = (location2[VECTOR_Z] > 0) && (location2[VECTOR_Z] < FP_ONE); 126 | if ((!insideDepth1 && !insideDepth2 && !insideDepth3) 127 | || (!insideHeight1 && !insideHeight2 && !insideHeight3) 128 | || (!insideWidth1 && !insideWidth2 && !insideWidth3)) { 129 | return true; 130 | } 131 | } 132 | return false; 133 | } 134 | 135 | private boolean isBackface() { 136 | final int x0 = location0[VECTOR_X]; 137 | final int x1 = location1[VECTOR_X]; 138 | final int x2 = location2[VECTOR_X]; 139 | final int y0 = location0[VECTOR_Y]; 140 | final int y1 = location1[VECTOR_Y]; 141 | final int y2 = location2[VECTOR_Y]; 142 | final int triangleSize = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0); 143 | return triangleSize * faceCull < 0; 144 | } 145 | 146 | protected void sortY() { 147 | sortY00 = location0[VECTOR_Y]; 148 | sortY01 = location1[VECTOR_Y]; 149 | if (sortY00 > sortY01) { 150 | VectorUtils.swap(location0, location1); 151 | } 152 | sortY10 = location1[VECTOR_Y]; 153 | sortY11 = location2[VECTOR_Y]; 154 | if (sortY10 > sortY11) { 155 | VectorUtils.swap(location1, location2); 156 | } 157 | sortY20 = location0[VECTOR_Y]; 158 | sortY21 = location1[VECTOR_Y]; 159 | if (sortY20 > sortY21) { 160 | VectorUtils.swap(location0, location1); 161 | } 162 | } 163 | 164 | protected int splitTriangle() { 165 | int dy = FixedPointUtils.divide(location1[VECTOR_Y] - location0[VECTOR_Y], location2[VECTOR_Y] - location0[VECTOR_Y]); 166 | location3[VECTOR_X] = location0[VECTOR_X] + FixedPointUtils.multiply(dy, location2[VECTOR_X] - location0[VECTOR_X]); 167 | location3[VECTOR_Y] = location1[VECTOR_Y]; 168 | location3[VECTOR_Z] = location0[VECTOR_Z] + FixedPointUtils.multiply(dy, location2[VECTOR_Z] - location0[VECTOR_Z]); 169 | return dy; 170 | } 171 | 172 | protected void swapSplitedBottomTriangle() { 173 | VectorUtils.swap(location3, location2); 174 | } 175 | 176 | protected void swapSplitedTopTriangle() { 177 | VectorUtils.swap(location3, location2); 178 | VectorUtils.swap(location0, location1); 179 | VectorUtils.swap(location1, location3); 180 | } 181 | 182 | private void drawBottomTriangle() { 183 | initializeBottomTriangle(); 184 | if(dx1 < dx2) { 185 | initializeDx2GreaterDx1(); 186 | for (; y1 <= y2; y1++) { 187 | drawScanline(x1, x2, y1, z, dz); 188 | incrementBottomDx2GreaterDx1(); 189 | } 190 | } else { 191 | initializeDx1GreaterDx2(); 192 | for (; y1 <= y2; y1++) { 193 | drawScanline(x1, x2, y1, z, dz); 194 | incrementBottomDx1GreaterDx2(); 195 | } 196 | } 197 | } 198 | 199 | protected void initializeBottomTriangle() { 200 | final int xShifted = location0[VECTOR_X] << FP_BIT; 201 | y2y1 = location1[VECTOR_Y] - location0[VECTOR_Y]; 202 | y2y1 = y2y1 == 0 ? 1 : y2y1; 203 | y3y1 = y2y1; 204 | y2y1 = FixedPointUtils.divide(FP_ONE, y2y1); 205 | y3y1 = FixedPointUtils.divide(FP_ONE, y3y1); 206 | dx1 = FixedPointUtils.multiply(location1[VECTOR_X] - location0[VECTOR_X], y2y1); 207 | dx2 = FixedPointUtils.multiply(location2[VECTOR_X] - location0[VECTOR_X], y3y1); 208 | dz1 = FixedPointUtils.multiply(location1[VECTOR_Z] - location0[VECTOR_Z], y2y1); 209 | dz2 = FixedPointUtils.multiply(location2[VECTOR_Z] - location0[VECTOR_Z], y3y1); 210 | x1 = xShifted; 211 | x2 = xShifted; 212 | y1 = location0[VECTOR_Y]; 213 | y2 = location1[VECTOR_Y]; 214 | z = location0[VECTOR_Z] << FP_BIT; 215 | } 216 | 217 | protected void incrementBottomDx2GreaterDx1() { 218 | x1 += dx1; 219 | x2 += dx2; 220 | z += dz1; 221 | } 222 | 223 | protected void incrementBottomDx1GreaterDx2() { 224 | x1 += dx2; 225 | x2 += dx1; 226 | z += dz2; 227 | } 228 | 229 | private void drawTopTriangle() { 230 | initializeTopTriangle(); 231 | if (dx1 > dx2) { 232 | initializeDx1GreaterDx2(); 233 | for (; y1 > y2; y1--) { 234 | drawScanline(x1, x2, y1, z, dz); 235 | incrementTopDx2GreaterDx1(); 236 | } 237 | } else { 238 | initializeDx2GreaterDx1(); 239 | for (; y1 > y2; y1--) { 240 | drawScanline(x1, x2, y1, z, dz); 241 | incrementTopDx1GreaterDx2(); 242 | } 243 | } 244 | } 245 | 246 | protected void initializeTopTriangle() { 247 | final int xShifted = location2[VECTOR_X] << FP_BIT; 248 | y3y1 = location2[VECTOR_Y] - location0[VECTOR_Y]; 249 | y3y2 = location2[VECTOR_Y] - location1[VECTOR_Y]; 250 | y3y1 = y3y1 == 0 ? 1 : y3y1; 251 | y3y2 = y3y2 == 0 ? 1 : y3y2; 252 | y3y1 = FixedPointUtils.divide(FP_ONE, y3y1); 253 | y3y2 = FixedPointUtils.divide(FP_ONE, y3y2); 254 | dx1 = FixedPointUtils.multiply(location2[VECTOR_X] - location0[VECTOR_X], y3y1); 255 | dx2 = FixedPointUtils.multiply(location2[VECTOR_X] - location1[VECTOR_X], y3y2); 256 | dz1 = FixedPointUtils.multiply(location2[VECTOR_Z] - location0[VECTOR_Z], y3y1); 257 | dz2 = FixedPointUtils.multiply(location2[VECTOR_Z] - location1[VECTOR_Z], y3y2); 258 | x1 = xShifted; 259 | x2 = xShifted; 260 | y1 = location2[VECTOR_Y]; 261 | y2 = location0[VECTOR_Y]; 262 | z = location2[VECTOR_Z] << FP_BIT; 263 | } 264 | 265 | protected void incrementTopDx2GreaterDx1() { 266 | x1 -= dx1; 267 | x2 -= dx2; 268 | z -= dz1; 269 | } 270 | 271 | protected void incrementTopDx1GreaterDx2() { 272 | x1 -= dx2; 273 | x2 -= dx1; 274 | z -= dz2; 275 | } 276 | 277 | protected void initializeDx2GreaterDx1() { 278 | dxdx = dx2 - dx1; 279 | dxdx = dxdx == 0 ? 1 : dxdx; 280 | dxdx = FixedPointUtils.divide(FP_ONE, dxdx); 281 | dz = FixedPointUtils.multiply(dz2 - dz1, dxdx); 282 | } 283 | 284 | protected void initializeDx1GreaterDx2() { 285 | dxdx = dx1 - dx2; 286 | dxdx = dxdx == 0 ? 1 : dxdx; 287 | dxdx = FixedPointUtils.divide(FP_ONE, dxdx); 288 | dz = FixedPointUtils.multiply(dz1 - dz2, dxdx); 289 | } 290 | 291 | private void drawScanline(int x1, int x2, int y, int z, int dz) { 292 | x1 >>= FP_BIT; 293 | x2 >>= FP_BIT; 294 | for (; x1 <= x2; x1++) { 295 | location[VECTOR_X] = x1; 296 | location[VECTOR_Y] = y; 297 | location[VECTOR_Z] = z >> FP_BIT; 298 | shader.fragment(); 299 | z += dz; 300 | } 301 | } 302 | 303 | public int[] getLocation() { 304 | return location; 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/BasicShader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_BIT; 4 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 7 | 8 | import com.johnsproject.jgameengine.model.Camera; 9 | import com.johnsproject.jgameengine.model.Face; 10 | import com.johnsproject.jgameengine.model.Frustum; 11 | import com.johnsproject.jgameengine.model.Material; 12 | import com.johnsproject.jgameengine.model.Texture; 13 | import com.johnsproject.jgameengine.model.Vertex; 14 | import com.johnsproject.jgameengine.rasterization.LinearRasterizer2; 15 | import com.johnsproject.jgameengine.util.ColorUtils; 16 | import com.johnsproject.jgameengine.util.TransformationUtils; 17 | import com.johnsproject.jgameengine.util.VectorUtils; 18 | 19 | /** 20 | * The BasicShader is a shader that only contains the core functionality needed to show models on the screen, 21 | * it draws the models without illumination using it's diffuse color or texture and depth tests each fragment before drawing it. 22 | * It's purpose is to help people learn how the shaders work. 23 | * 24 | * @author John Ferraz Salomon 25 | */ 26 | public class BasicShader implements Shader { 27 | 28 | private ForwardShaderBuffer shaderBuffer; 29 | 30 | // rasterizer used to draw the faces 31 | private LinearRasterizer2 rasterizer; 32 | // camera that the graphics engine is currently rendering to 33 | private Camera camera; 34 | // frustum that vertices will be projected to 35 | private Frustum frustum; 36 | // the color of the face to draw 37 | private int diffuseColor; 38 | // texture of the face to draw 39 | private Texture texture; 40 | 41 | public BasicShader() { 42 | rasterizer = new LinearRasterizer2(this); 43 | } 44 | 45 | public void initialize(ShaderBuffer shaderBuffer) { 46 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 47 | this.camera = shaderBuffer.getCamera(); 48 | this.frustum = camera.getFrustum(); 49 | } 50 | 51 | public void vertex(Vertex vertex) { 52 | final int[] location = vertex.getLocation(); 53 | // reset the location of the vertex 54 | VectorUtils.copy(location, vertex.getWorldLocation()); 55 | // transform the vertex to camera space 56 | VectorUtils.multiply(location, camera.getTransform().getSpaceEnterMatrix()); 57 | // transform the vertex to screen space 58 | VectorUtils.multiply(location, frustum.getProjectionMatrix()); 59 | // port the vertex to the center of the screen 60 | TransformationUtils.screenportVector(location, frustum); 61 | } 62 | 63 | // nothing to do here 64 | public void waitForVertexQueue() {} 65 | 66 | public void geometry(Face face) { 67 | final Material material = face.getMaterial(); 68 | diffuseColor = material.getDiffuseColor(); 69 | texture = material.getTexture(); 70 | // set the texture space location of the vertices to the rasterizer so they're interpolated 71 | setUVs(face); 72 | // draw face 73 | rasterizer.linearDraw2(face, frustum); 74 | } 75 | 76 | private void setUVs(Face face) { 77 | if(texture != null) { 78 | // port uvs to texture space, don't use FixedPointUtils.multiply as the rasterizer interpolates fixed point vectors 79 | int[] uv = face.getUV(0); 80 | int u = uv[VECTOR_X] * texture.getWidth(); 81 | int v = uv[VECTOR_Y] * texture.getHeight(); 82 | rasterizer.setVector00(u, v, 0); 83 | 84 | uv = face.getUV(1); 85 | u = uv[VECTOR_X] * texture.getWidth(); 86 | v = uv[VECTOR_Y] * texture.getHeight(); 87 | rasterizer.setVector01(u, v, 0); 88 | 89 | uv = face.getUV(2); 90 | u = uv[VECTOR_X] * texture.getWidth(); 91 | v = uv[VECTOR_Y] * texture.getHeight(); 92 | rasterizer.setVector02(u, v, 0); 93 | } 94 | } 95 | 96 | // nothing to do here 97 | public void waitForGeometryQueue() {} 98 | 99 | public void fragment() { 100 | final Texture depthBuffer = shaderBuffer.getCamera().getRenderTarget().getDepthBuffer(); 101 | final Texture colorBuffer = shaderBuffer.getCamera().getRenderTarget().getColorBuffer(); 102 | // get the location of this fragment in screen space 103 | final int x = rasterizer.getLocation()[VECTOR_X]; 104 | final int y = rasterizer.getLocation()[VECTOR_Y]; 105 | final int z = rasterizer.getLocation()[VECTOR_Z]; 106 | // test if there is a fragment front of this fragment, if not draw this fragment 107 | if (depthBuffer.getPixel(x, y) > z) { 108 | // get location of this fragment in texture space 109 | final int[] uv = rasterizer.getVector0(); 110 | // get texture color of this fragment 111 | final int textureColor = getFragmentTexelColor(uv); 112 | // calculate the color of this fragment 113 | final int color = ColorUtils.multiplyColor(textureColor, diffuseColor); 114 | // update the color and depth buffers 115 | colorBuffer.setPixel(x, y, color); 116 | depthBuffer.setPixel(x, y, z); 117 | } 118 | } 119 | 120 | private int getFragmentTexelColor(int[] uv) { 121 | if(texture == null) { 122 | return ColorUtils.WHITE; 123 | } else { 124 | // The result will be, but pixels are not accessed with fixed point 125 | final int u = uv[VECTOR_X] >> FP_BIT; 126 | final int v = uv[VECTOR_Y] >> FP_BIT; 127 | return texture.getPixel(u, v); 128 | } 129 | } 130 | 131 | public ShaderBuffer getShaderBuffer() { 132 | return shaderBuffer; 133 | } 134 | 135 | public boolean isGlobal() { 136 | // global shaders are applied to all models 137 | return false; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/BasicThreadedShader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_BIT; 4 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 7 | 8 | import com.johnsproject.jgameengine.model.Camera; 9 | import com.johnsproject.jgameengine.model.Face; 10 | import com.johnsproject.jgameengine.model.FrameBuffer; 11 | import com.johnsproject.jgameengine.model.Frustum; 12 | import com.johnsproject.jgameengine.model.Material; 13 | import com.johnsproject.jgameengine.model.Texture; 14 | import com.johnsproject.jgameengine.model.Vertex; 15 | import com.johnsproject.jgameengine.rasterization.LinearRasterizer2; 16 | import com.johnsproject.jgameengine.util.ColorUtils; 17 | import com.johnsproject.jgameengine.util.TransformationUtils; 18 | import com.johnsproject.jgameengine.util.VectorUtils; 19 | 20 | /** 21 | * The BasicThreadedShader is a shader that only contains the core functionality needed to show models on the screen using 22 | * the multithreaded shading system, it draws the models without illumination using it's diffuse color or texture 23 | * and depth tests each fragment before drawing it. It's purpose is to help people learn how the multithreaded shaders work. 24 | * 25 | * @author John Ferraz Salomon 26 | */ 27 | public class BasicThreadedShader extends ThreadedShader { 28 | 29 | public boolean isGlobal() { 30 | // global shaders are applied to all models 31 | return false; 32 | } 33 | 34 | @Override 35 | public ThreadedVertexShader[] createVertexShaders(int count) { 36 | // create shaders used by the threads 37 | final ThreadedVertexShader[] shaders = new VertexShader[count]; 38 | for (int i = 0; i < shaders.length; i++) 39 | shaders[i] = new VertexShader(); 40 | return shaders; 41 | } 42 | 43 | @Override 44 | public ThreadedGeometryShader[] createGeometryShaders(int count) { 45 | // create shaders used by the threads 46 | final ThreadedGeometryShader[] shaders = new GeometryShader[count]; 47 | for (int i = 0; i < shaders.length; i++) 48 | shaders[i] = new GeometryShader(); 49 | return shaders; 50 | } 51 | 52 | private static class VertexShader extends ThreadedVertexShader { 53 | 54 | private ForwardShaderBuffer shaderBuffer; 55 | 56 | // camera that the graphics engine is currently rendering to 57 | private Camera camera; 58 | // frustum that vertices will be projected to 59 | private Frustum frustum; 60 | 61 | public void initialize(ShaderBuffer shaderBuffer) { 62 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 63 | this.camera = shaderBuffer.getCamera(); 64 | this.frustum = camera.getFrustum(); 65 | } 66 | 67 | public void vertex(Vertex vertex) { 68 | final int[] location = vertex.getLocation(); 69 | // reset the location of the vertex 70 | VectorUtils.copy(location, vertex.getWorldLocation()); 71 | // transform the vertex to camera space 72 | VectorUtils.multiply(location, camera.getTransform().getSpaceEnterMatrix()); 73 | // transform the vertex to screen space 74 | VectorUtils.multiply(location, frustum.getProjectionMatrix()); 75 | // port the vertex to the center of the screen 76 | TransformationUtils.screenportVector(location, frustum); 77 | } 78 | 79 | public ShaderBuffer getShaderBuffer() { 80 | return shaderBuffer; 81 | } 82 | } 83 | 84 | private static class GeometryShader extends ThreadedGeometryShader { 85 | 86 | private ForwardShaderBuffer shaderBuffer; 87 | 88 | // rasterizer used to draw the faces 89 | private LinearRasterizer2 rasterizer; 90 | // camera that the graphics engine is currently rendering to 91 | private Camera camera; 92 | // frustum used to cull the faces before drawing 93 | private Frustum frustum; 94 | // the frame buffer the faces will be drawn to 95 | private FrameBuffer frameBuffer; 96 | // the color of the face to draw 97 | private int diffuseColor; 98 | // texture of the face to draw 99 | private Texture texture; 100 | 101 | public GeometryShader() { 102 | rasterizer = new LinearRasterizer2(this); 103 | } 104 | 105 | public void initialize(ShaderBuffer shaderBuffer) { 106 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 107 | this.camera = shaderBuffer.getCamera(); 108 | this.frustum = camera.getFrustum(); 109 | this.frameBuffer = camera.getRenderTarget(); 110 | } 111 | 112 | public void geometry(Face face) { 113 | final Material material = face.getMaterial(); 114 | diffuseColor = material.getDiffuseColor(); 115 | texture = material.getTexture(); 116 | // set the texture space location of the vertices to the rasterizer so they're interpolated 117 | setUVs(face); 118 | // draw face 119 | rasterizer.linearDraw2(face, frustum); 120 | } 121 | 122 | private void setUVs(Face face) { 123 | if(texture != null) { 124 | // port uvs to texture space, don't use FixedPointUtils.multiply as the rasterizer interpolates fixed point vectors 125 | int[] uv = face.getUV(0); 126 | int u = uv[VECTOR_X] * texture.getWidth(); 127 | int v = uv[VECTOR_Y] * texture.getHeight(); 128 | rasterizer.setVector00(u, v, 0); 129 | 130 | uv = face.getUV(1); 131 | u = uv[VECTOR_X] * texture.getWidth(); 132 | v = uv[VECTOR_Y] * texture.getHeight(); 133 | rasterizer.setVector01(u, v, 0); 134 | 135 | uv = face.getUV(2); 136 | u = uv[VECTOR_X] * texture.getWidth(); 137 | v = uv[VECTOR_Y] * texture.getHeight(); 138 | rasterizer.setVector02(u, v, 0); 139 | } 140 | } 141 | 142 | public void fragment() { 143 | final Texture depthBuffer = frameBuffer.getDepthBuffer(); 144 | final Texture colorBuffer = frameBuffer.getColorBuffer(); 145 | // get the location of this fragment in screen space 146 | final int x = rasterizer.getLocation()[VECTOR_X]; 147 | final int y = rasterizer.getLocation()[VECTOR_Y]; 148 | final int z = rasterizer.getLocation()[VECTOR_Z]; 149 | // test if there is a fragment front of this fragment, if not draw this fragment 150 | if (depthBuffer.getPixel(x, y) > z) { 151 | // get location of this fragment in texture space 152 | final int[] uv = rasterizer.getVector0(); 153 | // get texture color of this fragment 154 | final int textureColor = getFragmentTexelColor(uv); 155 | // calculate the color of this fragment 156 | final int color = ColorUtils.multiplyColor(textureColor, diffuseColor); 157 | // update the color and depth buffers 158 | colorBuffer.setPixel(x, y, color); 159 | depthBuffer.setPixel(x, y, z); 160 | } 161 | } 162 | 163 | private int getFragmentTexelColor(int[] uv) { 164 | if(texture == null) { 165 | return ColorUtils.WHITE; 166 | } else { 167 | // The result will be, but pixels are not accessed with fixed point 168 | final int u = uv[VECTOR_X] >> FP_BIT; 169 | final int v = uv[VECTOR_Y] >> FP_BIT; 170 | return texture.getPixel(u, v); 171 | } 172 | } 173 | 174 | public ShaderBuffer getShaderBuffer() { 175 | return shaderBuffer; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/DirectionalLightShadowShader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_ONE; 4 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 7 | 8 | import com.johnsproject.jgameengine.model.Camera; 9 | import com.johnsproject.jgameengine.model.Face; 10 | import com.johnsproject.jgameengine.model.Frustum; 11 | import com.johnsproject.jgameengine.model.Light; 12 | import com.johnsproject.jgameengine.model.Texture; 13 | import com.johnsproject.jgameengine.model.Vertex; 14 | import com.johnsproject.jgameengine.rasterization.Rasterizer; 15 | import com.johnsproject.jgameengine.util.MatrixUtils; 16 | import com.johnsproject.jgameengine.util.TransformationUtils; 17 | import com.johnsproject.jgameengine.util.VectorUtils; 18 | 19 | public class DirectionalLightShadowShader extends ThreadedShader { 20 | 21 | public boolean isGlobal() { 22 | return true; 23 | } 24 | 25 | @Override 26 | public ThreadedVertexShader[] createVertexShaders(int count) { 27 | final ThreadedVertexShader[] shaders = new VertexShader[count]; 28 | for (int i = 0; i < shaders.length; i++) 29 | shaders[i] = new VertexShader(); 30 | return shaders; 31 | } 32 | 33 | @Override 34 | public ThreadedGeometryShader[] createGeometryShaders(int count) { 35 | final ThreadedGeometryShader[] shaders = new GeometryShader[count]; 36 | for (int i = 0; i < shaders.length; i++) 37 | shaders[i] = new GeometryShader(); 38 | return shaders; 39 | } 40 | 41 | private static class VertexShader extends ThreadedVertexShader { 42 | 43 | private ForwardShaderBuffer shaderBuffer; 44 | private Frustum lightFrustum; 45 | 46 | public void initialize(ShaderBuffer shaderBuffer) { 47 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 48 | initialize(); 49 | } 50 | 51 | private void initialize() { 52 | lightFrustum = shaderBuffer.getDirectionalLightFrustum(); 53 | } 54 | 55 | public void vertex(Vertex vertex) { 56 | if(shaderBuffer.getShadowDirectionalLight() != null) { 57 | final int[] location = vertex.getLocation(); 58 | VectorUtils.copy(location, vertex.getWorldLocation()); 59 | VectorUtils.multiply(location, lightFrustum.getProjectionMatrix()); 60 | TransformationUtils.screenportVector(location, lightFrustum); 61 | } 62 | } 63 | 64 | public ShaderBuffer getShaderBuffer() { 65 | return shaderBuffer; 66 | } 67 | } 68 | 69 | private static class GeometryShader extends ThreadedGeometryShader { 70 | 71 | private ForwardShaderBuffer shaderBuffer; 72 | private final Rasterizer rasterizer = new Rasterizer(this); 73 | private Light light; 74 | private Frustum lightFrustum; 75 | private Texture shadowMap; 76 | private int shadowBias; 77 | 78 | private Frustum cameraFrustum; 79 | private int[][] cameraMatrix = MatrixUtils.indentityMatrix(); 80 | private int renderTargetLeft; 81 | private int renderTargetRight; 82 | private int renderTargetTop; 83 | private int renderTargetBottom; 84 | private int[] location0 = VectorUtils.emptyVector(); 85 | private int[] location1 = VectorUtils.emptyVector(); 86 | private int[] location2 = VectorUtils.emptyVector(); 87 | 88 | public void initialize(ShaderBuffer shaderBuffer) { 89 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 90 | initialize(); 91 | } 92 | 93 | private void initialize() { 94 | light = shaderBuffer.getShadowDirectionalLight(); 95 | lightFrustum = shaderBuffer.getDirectionalLightFrustum(); 96 | shadowMap = shaderBuffer.getDirectionalShadowMap(); 97 | if(light != null) { 98 | shadowBias = light.getShadowBias() >> 10; 99 | final Camera camera = shaderBuffer.getCamera(); 100 | cameraFrustum = camera.getFrustum(); 101 | final int[][] cameraSpaceMatrix = camera.getTransform().getSpaceEnterMatrix(); 102 | final int[][] projectionMatrix = cameraFrustum.getProjectionMatrix(); 103 | 104 | MatrixUtils.multiply(projectionMatrix, cameraSpaceMatrix, cameraMatrix); 105 | 106 | final int tolerance = 1024; 107 | renderTargetLeft = cameraFrustum.getRenderTargetLeft() - tolerance; 108 | renderTargetRight = cameraFrustum.getRenderTargetRight() + tolerance; 109 | renderTargetTop = cameraFrustum.getRenderTargetTop() - tolerance; 110 | renderTargetBottom = cameraFrustum.getRenderTargetBottom() + tolerance; 111 | } 112 | } 113 | 114 | public void geometry(Face face) { 115 | if((light != null) && isInCameraView(face)) { 116 | rasterizer.setFrustumCull(false); 117 | rasterizer.draw(face, lightFrustum); 118 | } 119 | } 120 | 121 | private boolean isInCameraView(Face face) { 122 | VectorUtils.copy(location0, face.getVertex(0).getWorldLocation()); 123 | VectorUtils.multiply(location0, cameraMatrix); 124 | TransformationUtils.screenportVector(location0, cameraFrustum); 125 | 126 | VectorUtils.copy(location1, face.getVertex(1).getWorldLocation()); 127 | VectorUtils.multiply(location1, cameraMatrix); 128 | TransformationUtils.screenportVector(location1, cameraFrustum); 129 | 130 | VectorUtils.copy(location2, face.getVertex(2).getWorldLocation()); 131 | VectorUtils.multiply(location2, cameraMatrix); 132 | TransformationUtils.screenportVector(location2, cameraFrustum); 133 | 134 | final boolean insideWidth1 = (location0[VECTOR_X] > renderTargetLeft) && (location0[VECTOR_X] < renderTargetRight); 135 | final boolean insideWidth2 = (location1[VECTOR_X] > renderTargetLeft) && (location1[VECTOR_X] < renderTargetRight); 136 | final boolean insideWidth3 = (location2[VECTOR_X] > renderTargetLeft) && (location2[VECTOR_X] < renderTargetRight); 137 | final boolean insideHeight1 = (location0[VECTOR_Y] > renderTargetTop) && (location0[VECTOR_Y] < renderTargetBottom); 138 | final boolean insideHeight2 = (location1[VECTOR_Y] > renderTargetTop) && (location1[VECTOR_Y] < renderTargetBottom); 139 | final boolean insideHeight3 = (location2[VECTOR_Y] > renderTargetTop) && (location2[VECTOR_Y] < renderTargetBottom); 140 | final boolean insideDepth1 = (location0[VECTOR_Z] > 0) && (location0[VECTOR_Z] < FP_ONE); 141 | final boolean insideDepth2 = (location1[VECTOR_Z] > 0) && (location1[VECTOR_Z] < FP_ONE); 142 | final boolean insideDepth3 = (location2[VECTOR_Z] > 0) && (location2[VECTOR_Z] < FP_ONE); 143 | if ((!insideDepth1 && !insideDepth2 && !insideDepth3) 144 | || (!insideHeight1 && !insideHeight2 && !insideHeight3) 145 | || (!insideWidth1 && !insideWidth2 && !insideWidth3)) { 146 | return false; 147 | } 148 | return true; 149 | } 150 | 151 | public void fragment() { 152 | final int x = rasterizer.getLocation()[VECTOR_X]; 153 | final int y = rasterizer.getLocation()[VECTOR_Y]; 154 | final int z = rasterizer.getLocation()[VECTOR_Z] + shadowBias; 155 | if (shadowMap.getPixel(x, y) > z) { 156 | shadowMap.setPixel(x, y, z); 157 | } 158 | } 159 | 160 | public ShaderBuffer getShaderBuffer() { 161 | return shaderBuffer; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/ForwardShaderBuffer.java: -------------------------------------------------------------------------------- 1 | 2 | package com.johnsproject.jgameengine.shading; 3 | 4 | import static com.johnsproject.jgameengine.util.FixedPointUtils.*; 5 | 6 | import java.util.List; 7 | 8 | import com.johnsproject.jgameengine.model.Camera; 9 | import com.johnsproject.jgameengine.model.Frustum; 10 | import com.johnsproject.jgameengine.model.FrustumType; 11 | import com.johnsproject.jgameengine.model.Light; 12 | import com.johnsproject.jgameengine.model.Texture; 13 | import com.johnsproject.jgameengine.util.MatrixUtils; 14 | import com.johnsproject.jgameengine.util.VectorUtils; 15 | 16 | public class ForwardShaderBuffer implements ShaderBuffer { 17 | 18 | private Camera camera; 19 | private List lights; 20 | 21 | private final int[][] projectionMatrix; 22 | 23 | private Light shadowDirectionalLight; 24 | private final Frustum directionalLightFrustum; 25 | private final Texture directionalShadowMap; 26 | 27 | private Light shadowSpotLight; 28 | private final Frustum spotLightFrustum; 29 | private final Texture spotShadowMap; 30 | 31 | private boolean foundMainDirectionalLight; 32 | private long spotLightDistance; 33 | 34 | public ForwardShaderBuffer() { 35 | this.projectionMatrix = MatrixUtils.indentityMatrix(); 36 | 37 | this.directionalLightFrustum = new Frustum(0, FP_ONE, 0, FP_ONE, FP_ONE, FP_ONE * 10000); 38 | this.directionalLightFrustum.setType(FrustumType.ORTHOGRAPHIC); 39 | this.directionalLightFrustum.setFocalLength(FP_ONE >> 3); 40 | this.directionalShadowMap = new Texture(2048, 2048); 41 | 42 | this.spotLightFrustum = new Frustum(0, FP_ONE, 0, FP_ONE, FP_HALF, FP_ONE * 1000); 43 | this.spotLightFrustum.setFocalLength(FP_ONE >> 2); 44 | this.spotShadowMap = new Texture(1024, 1024); 45 | } 46 | 47 | public void initialize(Camera camera, List lights) { 48 | this.camera = camera; 49 | this.lights = lights; 50 | final int[] cameraLocation = camera.getTransform().getLocation(); 51 | long maxLightDistance = camera.getMaxLightDistance(); 52 | // square the far distance because square distance calculation is used to save performance 53 | maxLightDistance = (maxLightDistance * maxLightDistance) >> FP_BIT; 54 | resetLightIndices(); 55 | for(int i = 0; i < lights.size(); i++) { 56 | final Light light = lights.get(i); 57 | if(!light.isActive()) 58 | continue; 59 | final int[] lightLocation = light.getTransform().getLocation(); 60 | final long lightDistance = VectorUtils.squaredDistance(lightLocation, cameraLocation); 61 | light.setCulled(lightDistance > maxLightDistance); 62 | if(light.isCulled() || !light.hasShadow()) 63 | continue; 64 | searchNearestLights(light, lightDistance); 65 | } 66 | initializeLightMatrices(); 67 | } 68 | 69 | private void resetLightIndices() { 70 | shadowDirectionalLight = null; 71 | shadowSpotLight = null; 72 | foundMainDirectionalLight = false; 73 | spotLightDistance = Integer.MAX_VALUE; 74 | } 75 | 76 | private void searchNearestLights(Light light, long lightDistance) { 77 | switch (light.getType()) { 78 | case DIRECTIONAL: 79 | searchNearestDirectionalLight(light, lightDistance); 80 | break; 81 | case SPOT: 82 | searchNearestSpotLight(light, lightDistance); 83 | break; 84 | default: 85 | break; 86 | } 87 | } 88 | 89 | private void searchNearestDirectionalLight(Light light, long lightDistance) { 90 | if(!foundMainDirectionalLight) { 91 | shadowDirectionalLight = light; 92 | if(light.isMain()) { 93 | foundMainDirectionalLight = true; 94 | } 95 | } 96 | } 97 | 98 | private void searchNearestSpotLight(Light light, long lightDistance) { 99 | if(spotLightDistance == Integer.MIN_VALUE) 100 | return; 101 | if(lightDistance < spotLightDistance) { 102 | spotLightDistance = lightDistance; 103 | shadowSpotLight = light; 104 | if(light.isMain()) { 105 | spotLightDistance = Integer.MIN_VALUE; 106 | } 107 | } 108 | } 109 | 110 | private void initializeLightMatrices() { 111 | if(camera.isMain()) { 112 | initializeDirectionalLightMatrix(); 113 | initializeSpotLightMatrix(); 114 | } 115 | } 116 | 117 | private void initializeDirectionalLightMatrix() { 118 | if (shadowDirectionalLight != null) { 119 | final int portWidth = directionalShadowMap.getWidth(); 120 | final int portHeight = directionalShadowMap.getHeight(); 121 | directionalLightFrustum.setRenderTargetSize(portWidth, portHeight); 122 | directionalShadowMap.fill(Integer.MAX_VALUE); 123 | final int[][] lightSpaceMatrix = shadowDirectionalLight.getTransform().getSpaceEnterMatrix(); 124 | final int[][] frustumProjectionMatrix = directionalLightFrustum.getProjectionMatrix(); 125 | MatrixUtils.copy(projectionMatrix, frustumProjectionMatrix); 126 | MatrixUtils.multiply(projectionMatrix, lightSpaceMatrix, frustumProjectionMatrix); 127 | } 128 | } 129 | 130 | private void initializeSpotLightMatrix() { 131 | if (shadowSpotLight != null) { 132 | final int portWidth = spotShadowMap.getWidth(); 133 | final int portHeight = spotShadowMap.getHeight(); 134 | spotLightFrustum.setRenderTargetSize(portWidth, portHeight); 135 | spotShadowMap.fill(Integer.MAX_VALUE); 136 | final int[][] lightSpaceMatrix = shadowSpotLight.getTransform().getSpaceEnterMatrix(); 137 | final int[][] frustumProjectionMatrix = spotLightFrustum.getProjectionMatrix(); 138 | MatrixUtils.copy(projectionMatrix, frustumProjectionMatrix); 139 | MatrixUtils.multiply(projectionMatrix, lightSpaceMatrix, frustumProjectionMatrix); 140 | } 141 | } 142 | 143 | public Camera getCamera() { 144 | return camera; 145 | } 146 | 147 | public List getLights() { 148 | return lights; 149 | } 150 | 151 | public Light getShadowDirectionalLight() { 152 | return shadowDirectionalLight; 153 | } 154 | 155 | public Texture getDirectionalShadowMap() { 156 | return directionalShadowMap; 157 | } 158 | 159 | public Frustum getDirectionalLightFrustum() { 160 | return directionalLightFrustum; 161 | } 162 | 163 | public Light getShadowSpotLight() { 164 | return shadowSpotLight; 165 | } 166 | 167 | public Texture getSpotShadowMap() { 168 | return spotShadowMap; 169 | } 170 | 171 | public Frustum getSpotLightFrustum() { 172 | return spotLightFrustum; 173 | } 174 | } -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/Shader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import com.johnsproject.jgameengine.model.Face; 4 | import com.johnsproject.jgameengine.model.Vertex; 5 | 6 | public interface Shader { 7 | 8 | void initialize(ShaderBuffer shaderBuffer); 9 | 10 | void vertex(Vertex vertex); 11 | 12 | /** 13 | * Used to notify the {@link ThreadedShader} that all vertices have been shaded 14 | * and it should wait until the queue is empty. No implementation is required. 15 | */ 16 | void waitForVertexQueue(); 17 | 18 | void geometry(Face face); 19 | 20 | /** 21 | * Used to notify the {@link ThreadedShader} that all faces have been shaded 22 | * and it should wait until the queue is empty. No implementation is required. 23 | */ 24 | void waitForGeometryQueue(); 25 | 26 | void fragment(); 27 | 28 | ShaderBuffer getShaderBuffer(); 29 | 30 | /** 31 | * Is this shader a global shader? 32 | * Global shaders are used on all Models. 33 | * 34 | * @return If this shader is a global shader. 35 | */ 36 | boolean isGlobal(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/ShaderBuffer.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import java.util.List; 4 | 5 | import com.johnsproject.jgameengine.model.Camera; 6 | import com.johnsproject.jgameengine.model.Light; 7 | 8 | public interface ShaderBuffer { 9 | 10 | public void initialize(Camera camera, List lights); 11 | 12 | public List getLights(); 13 | 14 | public Camera getCamera(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/SpotLightShadowShader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 4 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 6 | 7 | import com.johnsproject.jgameengine.model.Face; 8 | import com.johnsproject.jgameengine.model.Frustum; 9 | import com.johnsproject.jgameengine.model.Light; 10 | import com.johnsproject.jgameengine.model.Texture; 11 | import com.johnsproject.jgameengine.model.Vertex; 12 | import com.johnsproject.jgameengine.rasterization.Rasterizer; 13 | import com.johnsproject.jgameengine.util.TransformationUtils; 14 | import com.johnsproject.jgameengine.util.VectorUtils; 15 | 16 | public class SpotLightShadowShader extends ThreadedShader { 17 | 18 | public boolean isGlobal() { 19 | return true; 20 | } 21 | 22 | @Override 23 | public ThreadedVertexShader[] createVertexShaders(int count) { 24 | final ThreadedVertexShader[] shaders = new VertexShader[count]; 25 | for (int i = 0; i < shaders.length; i++) 26 | shaders[i] = new VertexShader(); 27 | return shaders; 28 | } 29 | 30 | @Override 31 | public ThreadedGeometryShader[] createGeometryShaders(int count) { 32 | final ThreadedGeometryShader[] shaders = new GeometryShader[count]; 33 | for (int i = 0; i < shaders.length; i++) 34 | shaders[i] = new GeometryShader(); 35 | return shaders; 36 | } 37 | 38 | private static class VertexShader extends ThreadedVertexShader { 39 | 40 | private ForwardShaderBuffer shaderBuffer; 41 | private Frustum lightFrustum; 42 | 43 | public void initialize(ShaderBuffer shaderBuffer) { 44 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 45 | initialize(); 46 | } 47 | 48 | private void initialize() { 49 | lightFrustum = shaderBuffer.getSpotLightFrustum(); 50 | } 51 | 52 | public void vertex(Vertex vertex) { 53 | if(shaderBuffer.getShadowSpotLight() != null) { 54 | final int[] location = vertex.getLocation(); 55 | VectorUtils.copy(location, vertex.getWorldLocation()); 56 | VectorUtils.multiply(location, lightFrustum.getProjectionMatrix()); 57 | TransformationUtils.screenportVector(location, lightFrustum); 58 | } 59 | } 60 | 61 | public ShaderBuffer getShaderBuffer() { 62 | return shaderBuffer; 63 | } 64 | } 65 | 66 | private static class GeometryShader extends ThreadedGeometryShader { 67 | 68 | private ForwardShaderBuffer shaderBuffer; 69 | private final Rasterizer rasterizer; 70 | private Light light; 71 | private Frustum lightFrustum; 72 | private Texture shadowMap; 73 | private int shadowBias; 74 | 75 | 76 | public GeometryShader() { 77 | rasterizer = new Rasterizer(this); 78 | } 79 | 80 | public void initialize(ShaderBuffer shaderBuffer) { 81 | this.shaderBuffer = (ForwardShaderBuffer) shaderBuffer; 82 | initialize(); 83 | } 84 | 85 | private void initialize() { 86 | light = shaderBuffer.getShadowSpotLight(); 87 | lightFrustum = shaderBuffer.getSpotLightFrustum(); 88 | shadowMap = shaderBuffer.getSpotShadowMap(); 89 | if(light != null) 90 | shadowBias = light.getShadowBias() >> 10; 91 | } 92 | 93 | public void geometry(Face face) { 94 | if(light != null) { 95 | rasterizer.setFrustumCull(false); 96 | rasterizer.draw(face, lightFrustum); 97 | } 98 | } 99 | 100 | public void fragment() { 101 | final int x = rasterizer.getLocation()[VECTOR_X]; 102 | final int y = rasterizer.getLocation()[VECTOR_Y]; 103 | final int z = rasterizer.getLocation()[VECTOR_Z] + shadowBias; 104 | if (shadowMap.getPixel(x, y) > z) { 105 | shadowMap.setPixel(x, y, z); 106 | } 107 | } 108 | 109 | public ShaderBuffer getShaderBuffer() { 110 | return shaderBuffer; 111 | } 112 | 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/shading/ThreadedShader.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.shading; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | 6 | import com.johnsproject.jgameengine.model.Face; 7 | import com.johnsproject.jgameengine.model.Vertex; 8 | 9 | public abstract class ThreadedShader implements Shader { 10 | 11 | private final BlockingQueue vertexQueue; 12 | private final BlockingQueue geometryQueue; 13 | private final ThreadedVertexShader[] vertexShaders; 14 | private final ThreadedGeometryShader[] geometryShaders; 15 | 16 | public ThreadedShader() { 17 | final int coreCount = Runtime.getRuntime().availableProcessors(); 18 | vertexQueue = new ArrayBlockingQueue(coreCount * 32); 19 | geometryQueue = new ArrayBlockingQueue(coreCount * 32); 20 | 21 | vertexShaders = createVertexShaders(coreCount); 22 | for (int i = 0; i < vertexShaders.length; i++) { 23 | vertexShaders[i].setQueue(vertexQueue); 24 | vertexShaders[i].start(); 25 | } 26 | 27 | geometryShaders = createGeometryShaders(coreCount); 28 | for (int i = 0; i < geometryShaders.length; i++) { 29 | geometryShaders[i].setQueue(geometryQueue); 30 | geometryShaders[i].start(); 31 | } 32 | } 33 | 34 | public abstract ThreadedVertexShader[] createVertexShaders(int count); 35 | 36 | public abstract ThreadedGeometryShader[] createGeometryShaders(int count); 37 | 38 | public void initialize(ShaderBuffer shaderBuffer) { 39 | for (int i = 0; i < vertexShaders.length; i++) 40 | vertexShaders[i].initialize(shaderBuffer); 41 | 42 | for (int i = 0; i < geometryShaders.length; i++) 43 | geometryShaders[i].initialize(shaderBuffer); 44 | } 45 | 46 | public void vertex(Vertex vertex) { 47 | try { 48 | vertexQueue.put(vertex); 49 | } catch (InterruptedException e) { 50 | e.printStackTrace(); 51 | } 52 | } 53 | 54 | public void geometry(Face face) { 55 | try { 56 | geometryQueue.put(face); 57 | } catch (InterruptedException e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | public void fragment() {} 63 | 64 | public ShaderBuffer getShaderBuffer() { 65 | return vertexShaders[0].getShaderBuffer(); 66 | } 67 | 68 | public void waitForVertexQueue() { 69 | for (int i = 0; i < vertexShaders.length; i++) 70 | if((!vertexQueue.isEmpty()) || (vertexShaders[i].getState() != Thread.State.WAITING)) 71 | i = 0; 72 | } 73 | 74 | public void waitForGeometryQueue() { 75 | for (int i = 0; i < geometryShaders.length; i++) 76 | if((!geometryQueue.isEmpty()) || (geometryShaders[i].getState() != Thread.State.WAITING)) 77 | i = 0; 78 | } 79 | 80 | protected static abstract class ThreadedVertexShader extends Thread implements Shader { 81 | 82 | private BlockingQueue queue; 83 | 84 | public ThreadedVertexShader() { 85 | super("VertexShaderThread"); 86 | } 87 | 88 | public void waitForVertexQueue() { } 89 | 90 | public void geometry(Face face) { } 91 | 92 | public void waitForGeometryQueue() { } 93 | 94 | public void fragment() { } 95 | 96 | public void run() { 97 | while(true) { 98 | try { 99 | vertex(queue.take()); 100 | } catch (InterruptedException e) { 101 | e.printStackTrace(); 102 | } 103 | } 104 | } 105 | 106 | private void setQueue(BlockingQueue queue) { 107 | this.queue = queue; 108 | } 109 | 110 | public boolean isGlobal() { 111 | return false; 112 | } 113 | } 114 | 115 | protected static abstract class ThreadedGeometryShader extends Thread implements Shader { 116 | 117 | private BlockingQueue queue; 118 | 119 | public ThreadedGeometryShader() { 120 | super("GeometryShaderThread"); 121 | } 122 | 123 | public void vertex(Vertex vertex) { } 124 | 125 | public void waitForVertexQueue() { } 126 | 127 | public void waitForGeometryQueue() { } 128 | 129 | public void run() { 130 | while(true) { 131 | try { 132 | geometry(queue.take()); 133 | } catch (InterruptedException e) { 134 | e.printStackTrace(); 135 | } 136 | } 137 | } 138 | 139 | private void setQueue(BlockingQueue queue) { 140 | this.queue = queue; 141 | } 142 | 143 | public boolean isGlobal() { 144 | return false; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/util/ColorUtils.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.util; 2 | 3 | import java.awt.Color; 4 | import java.awt.image.BufferedImage; 5 | 6 | /** 7 | * The ColorUtils class contains methods for generating integer sRGB colors 8 | * and performing integer sRGB color operations such as lerp, multiply and add. 9 | * 10 | * @author John Ferraz Salomon 11 | */ 12 | public final class ColorUtils { 13 | 14 | /** 15 | * Some default integer sRGB colors. 16 | */ 17 | public static final int WHITE = Color.white.getRGB(), 18 | BLUE = Color.blue.getRGB(), 19 | CYAN = Color.cyan.getRGB(), 20 | GREEN = Color.green.getRGB(), 21 | ORANGE = Color.orange.getRGB(), 22 | RED = Color.red.getRGB(), 23 | YELLOW = Color.yellow.getRGB(), 24 | MAGENTA = Color.magenta.getRGB(), 25 | PINK = Color.pink.getRGB(), 26 | GRAY = Color.gray.getRGB(), 27 | BLACK = Color.black.getRGB(); 28 | 29 | 30 | /** 31 | * Bits count representing each color value. 32 | */ 33 | public static final byte COLOR_BITS = 8; 34 | 35 | /** 36 | * Value representing the range of each color value. (0 - {@value #COLOR_ONE}) 37 | */ 38 | public static final int COLOR_ONE = (1 << COLOR_BITS) - 1; 39 | 40 | /** 41 | * Number representing the BufferedImage color type that the ColorProcessor handles. 42 | */ 43 | public static final byte COLOR_TYPE = BufferedImage.TYPE_INT_ARGB; 44 | 45 | private static final byte GREENSHIFT = COLOR_BITS; 46 | private static final byte REDSHIFT = COLOR_BITS * 2; 47 | private static final byte ALPHASHIFT = COLOR_BITS * 3; 48 | 49 | private ColorUtils() { } 50 | 51 | /** 52 | * Returns a integer sRGB color. 53 | * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue) 54 | * 55 | * @param a 56 | * @param r 57 | * @param g 58 | * @param b 59 | * @return 60 | */ 61 | public static int toColor(int a, int r, int g, int b) { 62 | int color = 0; 63 | color |= Math.min(COLOR_ONE, Math.max(a, 0)) << ALPHASHIFT; 64 | color |= Math.min(COLOR_ONE, Math.max(r, 0)) << REDSHIFT; 65 | color |= Math.min(COLOR_ONE, Math.max(g, 0)) << GREENSHIFT; 66 | color |= Math.min(COLOR_ONE, Math.max(b, 0)); 67 | return color; 68 | } 69 | 70 | /** 71 | * Returns a integer sRGB color, with alpha = 255. 72 | * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue) 73 | * 74 | * @param r 75 | * @param g 76 | * @param b 77 | * @return 78 | */ 79 | public static int toColor(int r, int g, int b) { 80 | int color = 0; 81 | color |= (255) << ALPHASHIFT; 82 | color |= Math.min(COLOR_ONE, Math.max(r, 0)) << REDSHIFT; 83 | color |= Math.min(COLOR_ONE, Math.max(g, 0)) << GREENSHIFT; 84 | color |= Math.min(COLOR_ONE, Math.max(b, 0)); 85 | return color; 86 | } 87 | 88 | /** 89 | * Returns the blue component of the given color. 90 | * The color should be in sRGB color space. 91 | * 92 | * @param color 93 | * @return 94 | */ 95 | public static int getBlue(int color) { 96 | return (color) & COLOR_ONE; 97 | } 98 | 99 | /** 100 | * Returns the green component of the given color. 101 | * The color should be in sRGB color space. 102 | * 103 | * @param color 104 | * @return 105 | */ 106 | public static int getGreen(int color) { 107 | return (color >> GREENSHIFT) & COLOR_ONE; 108 | } 109 | 110 | /** 111 | * Returns the red component of the given color. 112 | * The color should be in sRGB color space. 113 | * 114 | * @param color 115 | * @return 116 | */ 117 | public static int getRed(int color) { 118 | return (color >> REDSHIFT) & COLOR_ONE; 119 | } 120 | 121 | /** 122 | * Returns the alpha component of the given color. 123 | * The color should be in sRGB color space. 124 | * 125 | * @param color 126 | * @return 127 | */ 128 | public static int getAlpha(int color) { 129 | return (color >> ALPHASHIFT) & COLOR_ONE; 130 | } 131 | 132 | 133 | /** 134 | * Returns the result of the multiplication of color and factor. 135 | * This method only changes the RGB values of the color. 136 | * 137 | * @param color 138 | * @param factor a fixed point value between 0 and {@link FixedPointUtils#FP_ONE} 139 | * @return 140 | */ 141 | public static int multiply(int color, int factor) { 142 | int r = getRed(color), g = getGreen(color), b = getBlue(color), a = getAlpha(color); 143 | r = FixedPointUtils.multiply(r, factor); 144 | g = FixedPointUtils.multiply(g, factor); 145 | b = FixedPointUtils.multiply(b, factor); 146 | return toColor(a, r, g, b); 147 | } 148 | 149 | /** 150 | * Returns the result of the multiplication of color and factor. 151 | * 152 | * @param color 153 | * @param factor a fixed point value between 0 and {@link FixedPointUtils#FP_ONE} 154 | * @return 155 | */ 156 | public static int multiplyARGB(int color, int factor) { 157 | int r = getRed(color), g = getGreen(color), b = getBlue(color), a = getAlpha(color); 158 | r = FixedPointUtils.multiply(r, factor); 159 | g = FixedPointUtils.multiply(g, factor); 160 | b = FixedPointUtils.multiply(b, factor); 161 | a = FixedPointUtils.multiply(a, factor); 162 | return toColor(a, r, g, b); 163 | } 164 | 165 | /** 166 | * Returns a color containing the added components of color1 and color2. 167 | * This method only changes the RGB values of the color. 168 | * 169 | * @param color1 170 | * @param color2 171 | * @return 172 | */ 173 | public static int add(int color1, int color2) { 174 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 175 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2); 176 | int r = (r1 + r2); 177 | int g = (g1 + g2); 178 | int b = (b1 + b2); 179 | return toColor(a1, r, g, b); 180 | } 181 | 182 | /** 183 | * Returns a color containing the added components of color1 and color2. 184 | * 185 | * @param color1 186 | * @param color2 187 | * @return 188 | */ 189 | public static int addARGB(int color1, int color2) { 190 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 191 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2), a2 = getAlpha(color2); 192 | int r = (r1 + r2); 193 | int g = (g1 + g2); 194 | int b = (b1 + b2); 195 | int a = (a1 + a2); 196 | return toColor(a, r, g, b); 197 | } 198 | 199 | /** 200 | * Returns a color containing the multiplied components of color1 and color2. 201 | * This method only changes the RGB values of the color. 202 | * 203 | * @param color1 204 | * @param color2 205 | * @return 206 | */ 207 | public static int multiplyColor(int color1, int color2) { 208 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 209 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2); 210 | int r = (r1 * r2) >> COLOR_BITS; 211 | int g = (g1 * g2) >> COLOR_BITS; 212 | int b = (b1 * b2) >> COLOR_BITS; 213 | return toColor(a1, r, g, b); 214 | } 215 | 216 | /** 217 | * Returns a color containing the multiplied components of color1 and color2. 218 | * 219 | * @param color1 220 | * @param color2 221 | * @return 222 | */ 223 | public static int multiplyColorARGB(int color1, int color2) { 224 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 225 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2), a2 = getAlpha(color2); 226 | int r = (r1 * r2) >> COLOR_BITS; 227 | int g = (g1 * g2) >> COLOR_BITS; 228 | int b = (b1 * b2) >> COLOR_BITS; 229 | int a = (a1 * a2) >> COLOR_BITS; 230 | return toColor(a, r, g, b); 231 | } 232 | 233 | /** 234 | * Returns a color that is the linear interpolation of color1 and color2 by the given factor. 235 | * The factor should be in the range 0-{@value #COLOR_ONE}. 236 | * This method only changes the RGB values of the color. 237 | * 238 | * @param color1 239 | * @param color2 240 | * @param factor 241 | * @return 242 | */ 243 | public static int lerp(int color1, int color2, int factor) { 244 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 245 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2); 246 | int r = (r1 + (((r2 - r1) * factor) >> COLOR_BITS)); 247 | int g = (g1 + (((g2 - g1) * factor) >> COLOR_BITS)); 248 | int b = (b1 + (((b2 - b1) * factor) >> COLOR_BITS)); 249 | return toColor(a1, r, g, b); 250 | } 251 | 252 | /** 253 | * Returns a color that is the linear interpolation of color1 and color2 by the given factor. 254 | * The factor should be in the range 0-{@value #COLOR_ONE}. 255 | * 256 | * @param color1 257 | * @param color2 258 | * @param factor 259 | * @return 260 | */ 261 | public static int lerpARGB(int color1, int color2, int factor) { 262 | int r1 = getRed(color1), g1 = getGreen(color1), b1 = getBlue(color1), a1 = getAlpha(color1); 263 | int r2 = getRed(color2), g2 = getGreen(color2), b2 = getBlue(color2), a2 = getAlpha(color2); 264 | int r = (r1 + (((r2 - r1) * factor) >> COLOR_BITS)); 265 | int g = (g1 + (((g2 - g1) * factor) >> COLOR_BITS)); 266 | int b = (b1 + (((b2 - b1) * factor) >> COLOR_BITS)); 267 | int a = (a1 + (((a2 - a1) * factor) >> COLOR_BITS)); 268 | return toColor(a, r, g, b); 269 | } 270 | 271 | /** 272 | * Returns a string containing the data of the given color. 273 | * 274 | * @param color 275 | * @return 276 | */ 277 | public static String toString(int color) { 278 | int r1 = getRed(color), g1 = getGreen(color), b1 = getBlue(color), a1 = getAlpha(color); 279 | return "(" + a1 + ", " + g1 + ", " + b1 + ", " + r1 + ")"; 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.util; 2 | 3 | import java.awt.Image; 4 | import java.awt.image.BufferedImage; 5 | import java.io.BufferedReader; 6 | import java.io.FileInputStream; 7 | import java.io.FileNotFoundException; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.io.ObjectInputStream; 13 | import java.io.ObjectOutputStream; 14 | import java.io.Serializable; 15 | 16 | import javax.imageio.ImageIO; 17 | 18 | /** 19 | * The FileUtils class contains methods for writing/reading files from the file system. 20 | * 21 | * @author John Ferraz Salomon 22 | */ 23 | public final class FileUtils { 24 | 25 | private FileUtils() {} 26 | 27 | /** 28 | * Reads the content of the file at the specified path and returns it. 29 | * 30 | * @param path of the file 31 | * @return String representation of the data in the specified file. 32 | * @throws IOException If the file does not exist, is a directory rather than a regular file, 33 | * or for some other reason cannot be opened for reading. 34 | */ 35 | public static String readFile(String path) throws IOException { 36 | String content = null; 37 | FileInputStream fileInputStream = null; 38 | try { 39 | fileInputStream = new FileInputStream(path); 40 | content = readStream(fileInputStream); 41 | } finally { 42 | if (fileInputStream != null) { 43 | fileInputStream.close(); 44 | } 45 | } 46 | return content; 47 | } 48 | 49 | /** 50 | * Writes the object to the file at the specified path using serialization. 51 | * Only objects that implement the {@link Serializable} interface can be written. 52 | * 53 | * @param path file path. 54 | * @param obj object to write. Has to implement Serializable interface. 55 | * @throws IOException If an I/O error occurs while writing stream header. 56 | */ 57 | public static void writeObjectToFile(String path, Object obj) throws IOException { 58 | FileOutputStream fileOutputStream = null; 59 | try { 60 | fileOutputStream = new FileOutputStream(path); 61 | ObjectOutputStream out = new ObjectOutputStream(fileOutputStream); 62 | out.writeObject(obj); 63 | out.close(); 64 | fileOutputStream.close(); 65 | }catch (FileNotFoundException e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | 70 | /** 71 | * Reads the object from the file at the specified path using serialization. 72 | * Only objects written using {@link #writeObjectToFile} 73 | * or {@link ObjectOutputStream} can be parsed. 74 | * 75 | * @param path file path. 76 | * @return The object in the specified file. 77 | * @throws IOException If an I/O error occurs while reading stream header. 78 | */ 79 | public static Object readObjectFromFile(String path) throws IOException { 80 | Object result = null; 81 | FileInputStream fileInputStream = null; 82 | try { 83 | fileInputStream = new FileInputStream(path); 84 | ObjectInputStream in = new ObjectInputStream(fileInputStream); 85 | result = in.readObject(); 86 | in.close(); 87 | fileInputStream.close(); 88 | }catch (FileNotFoundException e) { 89 | e.printStackTrace(); 90 | } catch (ClassNotFoundException e) { 91 | e.printStackTrace(); 92 | } 93 | return result; 94 | } 95 | 96 | /** 97 | * Reads the content of the specified {@link InputStream} and returns it. 98 | * 99 | * @param stream InputStream to read from. 100 | * @return String representation of the data in the specified InputStream. 101 | * @throws IOException If an I/O error occurs. 102 | */ 103 | public static String readStream(InputStream stream) throws IOException { 104 | BufferedReader in = new BufferedReader(new InputStreamReader(stream)); 105 | 106 | StringBuilder stringBuilder = new StringBuilder(); 107 | for (String line = in.readLine(); line != null; line = in.readLine()) { 108 | stringBuilder.append(line); 109 | stringBuilder.append("\n"); 110 | } 111 | 112 | in.close(); 113 | return stringBuilder.toString(); 114 | } 115 | 116 | /** 117 | * Loads the image at the specified path and returns it as a {@link BufferedImage}. 118 | * 119 | * @param path image path. 120 | * @return Loaded image as a BufferedImage. 121 | * @throws IOException If an error occurs while loading the image. 122 | */ 123 | public static BufferedImage loadImage(String path) throws IOException { 124 | BufferedImage image = null; 125 | FileInputStream fileInputStream = null; 126 | try { 127 | fileInputStream = new FileInputStream(path); 128 | BufferedImage tmp = ImageIO.read(fileInputStream); 129 | image = new BufferedImage(tmp.getWidth(), tmp.getHeight(), ColorUtils.COLOR_TYPE); 130 | image.createGraphics().drawImage(tmp, 0, 0, null); 131 | image.createGraphics().dispose(); 132 | } finally { 133 | if (fileInputStream != null) { 134 | fileInputStream.close(); 135 | } 136 | } 137 | image.flush(); 138 | return image; 139 | } 140 | 141 | /** 142 | * Loads an image from the specified {@link InputStream} and returns it as a {@link BufferedImage}. 143 | * 144 | * @param stream InputStream to read from. 145 | * @return Loaded image as a BufferedImage. 146 | * @throws IOException If an error occurs while loading the image. 147 | */ 148 | public static BufferedImage loadImage(InputStream stream) throws IOException { 149 | BufferedImage image = null; 150 | try { 151 | BufferedImage tmp = ImageIO.read(stream); 152 | image = new BufferedImage(tmp.getWidth(), tmp.getHeight(), ColorUtils.COLOR_TYPE); 153 | image.createGraphics().drawImage(tmp, 0, 0, null); 154 | image.createGraphics().dispose(); 155 | } finally {} 156 | image.flush(); 157 | return image; 158 | } 159 | 160 | /** 161 | * Loads the image at the specified path and returns it as a {@link BufferedImage} 162 | * with the specified size. 163 | * 164 | * @param path image path. 165 | * @param width destination width. 166 | * @param height destination height. 167 | * @return Loaded image as a BufferedImage. 168 | * @throws IOException If an error occurs while loading the image. 169 | */ 170 | public static BufferedImage loadImage(String path, int width, int height) throws IOException { 171 | BufferedImage image = null; 172 | FileInputStream fileInputStream = null; 173 | try { 174 | fileInputStream = new FileInputStream(path); 175 | image = ImageIO.read(fileInputStream); 176 | } finally { 177 | if (fileInputStream != null) { 178 | fileInputStream.close(); 179 | } 180 | } 181 | Image tmp = image.getScaledInstance(width, height, Image.SCALE_SMOOTH); 182 | BufferedImage resized = new BufferedImage(width, height, ColorUtils.COLOR_TYPE); 183 | resized.createGraphics().drawImage(tmp, 0, 0, null); 184 | resized.createGraphics().dispose(); 185 | return resized; 186 | } 187 | 188 | /** 189 | * Loads an image from the specified {@link InputStream} and returns it as a {@link BufferedImage} 190 | * with the specified size. 191 | * 192 | * @param stream InputStream to read from. 193 | * @param width destination width. 194 | * @param height destination height. 195 | * @return Loaded image as a BufferedImage. 196 | * @throws IOException If an error occurs while loading the image. 197 | */ 198 | public static BufferedImage loadImage(InputStream stream, int width, int height) throws IOException { 199 | BufferedImage image = new BufferedImage(1, 1, 1); 200 | try { 201 | image = ImageIO.read(stream); 202 | } finally { } 203 | Image tmp = image.getScaledInstance(width, height, Image.SCALE_SMOOTH); 204 | BufferedImage resized = new BufferedImage(width, height, ColorUtils.COLOR_TYPE); 205 | resized.createGraphics().drawImage(tmp, 0, 0, null); 206 | resized.createGraphics().dispose(); 207 | return resized; 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/util/FixedPointUtils.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.util; 2 | 3 | /** 4 | * The FixedPointUtils class contains methods for generating fixed point numbers and 5 | * performing fixed point math operations such as power, square root, multiply, 6 | * divide, and some trigonometric functions. 7 | * 8 | * @author John Ferraz Salomon 9 | */ 10 | public final class FixedPointUtils { 11 | 12 | /** 13 | * This is the bit representation of the default fixed point precision value. 14 | * That means that the 'point' is at the {@value #FP_BIT}th bit of the integer. 15 | */ 16 | public static final byte FP_BIT = 15; 17 | 18 | /** 19 | * This is the integer representation of the default fixed point precision value. 20 | * It is the same as the fixed point '1'. 21 | */ 22 | public static final int FP_ONE = 1 << FP_BIT; 23 | 24 | /** 25 | * It is the same as the fixed point '0.5'. 26 | */ 27 | public static final int FP_HALF = FP_ONE >> 1; 28 | 29 | public static final int FP_DEGREE_RAD = toFixedPoint(Math.PI / 180.0f); 30 | public static final int FP_RAD_DEGREE = toFixedPoint(180.0f / Math.PI); 31 | 32 | private static final short[] sinLUT = new short[] { 33 | 0, 572, 1144, 1715, 2286, 2856, 3425, 3993, 4560, 5126, 5690, 6252, 6813, 7371, 7927, 8481, 34 | 9032, 9580, 10126, 10668, 11207, 11743, 12275, 12803, 13328, 13848, 14365, 14876, 15384, 35 | 15886, 16384, 16877, 17364, 17847, 18324, 18795, 19261, 19720, 20174, 20622, 21063, 21498, 36 | 21926, 22348, 22763, 23170, 23571, 23965, 24351, 24730, 25102, 25466, 25822, 26170, 26510, 37 | 26842, 27166, 27482, 27789, 28088, 28378, 28660, 28932, 29197, 29452, 29698, 29935, 30163, 38 | 30382, 30592, 30792, 30983, 31164, 31336, 31499, 31651, 31795, 31928, 32052, 32166, 32270, 39 | 32365, 32449, 32524, 32588, 32643, 32688, 32723, 32748, 32763, 32767 40 | }; 41 | 42 | private FixedPointUtils() { } 43 | 44 | /** 45 | * Returns the fixed point representation of value. 46 | * 47 | * @param value 48 | * @return 49 | */ 50 | public static int toFixedPoint(String value) { 51 | return toFixedPoint(Float.parseFloat(value)); 52 | } 53 | 54 | /** 55 | * Returns the fixed point representation of value. 56 | * 57 | * @param value 58 | * @return 59 | */ 60 | public static int toFixedPoint(double value) { 61 | return (int)Math.round(value * FP_ONE); 62 | } 63 | 64 | /** 65 | * Returns the floating point representation of value. 66 | * 67 | * @param value fixed point value. 68 | * @return 69 | */ 70 | public static double toDouble(long value) { 71 | return (double)value / FP_ONE; 72 | } 73 | 74 | /** 75 | * Returns the degrees converted to radians. 76 | * 77 | * @param degrees 78 | * @return 79 | */ 80 | public static int toRadians(int degrees) { 81 | return multiply(degrees, FP_DEGREE_RAD); 82 | } 83 | 84 | 85 | /** 86 | * Returns the radians converted to degrees. 87 | * 88 | * @param value 89 | * @return 90 | */ 91 | public static int toDegrees(int radians) { 92 | return multiply(radians, FP_RAD_DEGREE); 93 | } 94 | 95 | /** 96 | * Returns the fixed point sine of the specified angle. 97 | * 98 | * @param degrees 99 | * @return The sine of the specified angle. 100 | */ 101 | public static int sin(int degrees) { 102 | degrees >>= FP_BIT; 103 | degrees = ((degrees % 360) + 360) % 360; 104 | final int quadrant = degrees; 105 | degrees %= 90; 106 | if (quadrant < 90) { 107 | return sinLUT[degrees]; 108 | } 109 | if (quadrant < 180) { 110 | return sinLUT[90 - degrees]; 111 | } 112 | if (quadrant < 270) { 113 | return -sinLUT[degrees]; 114 | } 115 | if (quadrant < 360) { 116 | return -sinLUT[90 - degrees]; 117 | } 118 | return 0; 119 | } 120 | 121 | /** 122 | * Returns the fixed point cosine of the specified angle. 123 | * 124 | * @param degrees 125 | * @return The cosine of the specified angle. 126 | */ 127 | public static int cos(int degrees) { 128 | degrees >>= FP_BIT; 129 | degrees = ((degrees % 360) + 360) % 360; 130 | final int quadrant = degrees; 131 | degrees %= 90; 132 | if (quadrant < 90) { 133 | return sinLUT[90 - degrees]; 134 | } 135 | if (quadrant < 180) { 136 | return -sinLUT[degrees]; 137 | } 138 | if (quadrant < 270) { 139 | return -sinLUT[90 - degrees]; 140 | } 141 | if (quadrant < 360) { 142 | return sinLUT[degrees]; 143 | } 144 | return 0; 145 | } 146 | 147 | /** 148 | * Returns the fixed point tangent of the specified angle. 149 | * 150 | * @param degrees 151 | * @return The tangent of the specified angle. 152 | */ 153 | public static int tan(int degrees) { 154 | // no need to convert to long, the range of sine is 0 - 1 155 | return (sin(degrees) << FP_BIT) / cos(degrees); 156 | } 157 | 158 | /** 159 | * Returns the fixed point angle in degrees of the specified fixed point sine. 160 | * The angle is in the range -90 - 90. 161 | * 162 | * @param sine 163 | * @return The angle of the specified sine. 164 | */ 165 | public static int asin(int sine) { 166 | final int absSine = Math.abs(sine); 167 | for (int i = 1; i < sinLUT.length; i++) { 168 | final int lut0 = sinLUT[i - 1]; 169 | final int lut1 = sinLUT[i]; 170 | if((absSine > lut0) && (absSine <= lut1)) { 171 | int degrees = i; 172 | // if the sine is more like lut1 than lut0 the angle is i 173 | // else it's i - 1 174 | final int half = (lut1 - lut0) >> 1; 175 | if(absSine < lut0 + half) { 176 | degrees--; 177 | } 178 | degrees <<= FP_BIT; 179 | if(sine > 0) 180 | return degrees; 181 | else 182 | return -degrees; 183 | } 184 | } 185 | return 0; 186 | } 187 | 188 | /** 189 | * Returns the fixed point angle in degrees of the specified fixed point cosine. 190 | * The angle is in the range 0 - 180. 191 | * 192 | * @param cosine 193 | * @return The angle of the specified cosine. 194 | */ 195 | public static int acos(int cosine) { 196 | final int absCosine = Math.abs(cosine); 197 | for (int i = 1; i < sinLUT.length; i++) { 198 | final int lut0 = sinLUT[i - 1]; 199 | final int lut1 = sinLUT[i]; 200 | if((absCosine >= lut0) && (absCosine <= lut1)) { 201 | int degrees = i; 202 | final int half = (lut1 - lut0) >> 1; 203 | if(absCosine < lut0 + half) { 204 | degrees--; 205 | } 206 | if(cosine < 0) 207 | return (degrees + 90) << FP_BIT; 208 | else 209 | return (90 - degrees) << FP_BIT; 210 | } 211 | } 212 | return 0; 213 | } 214 | 215 | /** 216 | * Returns the given value in the range min-max. 217 | * 218 | * @param value value to wrap. 219 | * @param min 220 | * @param max 221 | * @return 222 | */ 223 | public static int normalize(int value, int min, int max) { 224 | int range = max - min; 225 | return (min + ((((value - min) % range) + range) % range)); 226 | } 227 | 228 | /** 229 | * Returns the given value from min to max. if value < min return min. if value 230 | * > max return max. 231 | * 232 | * @param value 233 | * @param min 234 | * @param max 235 | * @return 236 | */ 237 | public static int clamp(int value, int min, int max) { 238 | return Math.min(max, Math.max(value, min)); 239 | } 240 | 241 | /** 242 | * Returns a pseudo generated random value. 243 | * 244 | * @return 245 | */ 246 | public static int random(int seed) { 247 | seed += seed + (seed & seed); 248 | return seed; 249 | } 250 | 251 | /** 252 | * Returns a pseudo generated random value in the range min-max. 253 | * 254 | * @param min lowest random value. 255 | * @param max highest random value. 256 | * @return 257 | */ 258 | public static int random(int seed, int min, int max) { 259 | return normalize(random(seed), min, max); 260 | } 261 | 262 | /** 263 | * Returns the square root of the given number. If number < 0 the method returns 264 | * 0. 265 | * 266 | * @param number fixed point number. 267 | * @return fixed point result. 268 | */ 269 | public static int sqrt(long number) { 270 | // integral part 271 | int num = (int)(number >> FP_BIT); 272 | if(num < 0) { 273 | return 0; 274 | } 275 | int c = 1 << 15; 276 | int g = c; 277 | for (int i = 0; i < 16; i++) { 278 | if (g * g > num) { 279 | g ^= c; 280 | } 281 | c >>= 1; 282 | if (c == 0) { 283 | break; 284 | } 285 | g |= c; 286 | } 287 | long result = (g << FP_BIT); 288 | // fractional part 289 | final short increment = FP_ONE >> 7; 290 | for (; (result * result + FP_HALF) >> FP_BIT < number; result += increment); 291 | result -= increment; 292 | return (int)result; 293 | } 294 | 295 | /** 296 | * Returns the power of the given number. 297 | * 298 | * @param base fixed point number. 299 | * @param exp fixed point number. 300 | * @return fixed point result. 301 | */ 302 | public static int pow(long base, int exp) { 303 | exp >>= FP_BIT; 304 | long result = FP_ONE; 305 | while (exp != 0) { 306 | if ((exp & 1) == 1) { 307 | result = (result * base + FP_HALF) >> FP_BIT; 308 | } 309 | exp >>= 1; 310 | base = (base * base + FP_HALF) >> FP_BIT; 311 | } 312 | return (int) result; 313 | } 314 | 315 | /** 316 | * Returns the product of the multiplication of value1 and value2. 317 | * 318 | * @param value1 fixed point number. 319 | * @param value2 fixed point number. 320 | * @return fixed point result. 321 | */ 322 | public static int multiply(long value1, long value2) { 323 | long result = value1 * value2 + FP_HALF; 324 | return (int) (result >> FP_BIT); 325 | } 326 | 327 | /** 328 | * Returns the quotient of the division. 329 | * 330 | * @param dividend fixed point number. 331 | * @param divisor not fixed point number. 332 | * @return fixed point result. 333 | */ 334 | public static int divide(long dividend, long divisor) { 335 | long result = dividend << FP_BIT; 336 | result /= divisor; 337 | return (int) result; 338 | } 339 | 340 | /** 341 | * Returns a string containing the data of the given fixed point value. 342 | * 343 | * @param vector fixed point number. 344 | * @return 345 | */ 346 | public static String toString(int value) { 347 | float floatValue = (float)value / FP_ONE; 348 | return "" + floatValue; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/main/java/com/johnsproject/jgameengine/util/TransformationUtils.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.util; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.*; 4 | import static com.johnsproject.jgameengine.util.MatrixUtils.*; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.*; 6 | 7 | import com.johnsproject.jgameengine.model.Frustum; 8 | 9 | public final class TransformationUtils { 10 | 11 | private TransformationUtils() { } 12 | 13 | public static int[][] orthographicMatrix(int[][] matrix, Frustum frustum) { 14 | final int top = frustum.getRenderTargetTop(); 15 | final int bottom = frustum.getRenderTargetBottom(); 16 | final int near = frustum.getNear(); 17 | final int far = frustum.getFar(); 18 | final int scaleFactor = FixedPointUtils.multiply(frustum.getFocalLength(), bottom - top + 1); 19 | final int[][] projectionMatrix = MatrixUtils.copy(matrix, MatrixUtils.MATRIX_IDENTITY); 20 | projectionMatrix[0][0] = scaleFactor >> 5; 21 | projectionMatrix[1][1] = -scaleFactor >> 5; 22 | projectionMatrix[2][2] = -FixedPointUtils.divide(FP_ONE, far); 23 | projectionMatrix[3][2] = -FixedPointUtils.divide(near, far); 24 | projectionMatrix[3][3] = FP_ONE; 25 | return projectionMatrix; 26 | } 27 | 28 | public static int[][] perspectiveMatrix(int[][] matrix, Frustum frustum) { 29 | final int top = frustum.getRenderTargetTop(); 30 | final int bottom = frustum.getRenderTargetBottom(); 31 | final int near = frustum.getNear(); 32 | final int far = frustum.getFar(); 33 | final int farNear = far - near; 34 | final int scaleFactor = FixedPointUtils.multiply(frustum.getFocalLength(), bottom - top + 1); 35 | final int[][] projectionMatrix = MatrixUtils.copy(matrix, MatrixUtils.MATRIX_IDENTITY); 36 | projectionMatrix[0][0] = scaleFactor; 37 | projectionMatrix[1][1] = -scaleFactor; 38 | projectionMatrix[2][2] = -FixedPointUtils.divide(far, farNear); 39 | projectionMatrix[3][2] = -FixedPointUtils.divide(FixedPointUtils.multiply(near, far), farNear); 40 | projectionMatrix[2][3] = -FP_ONE; 41 | projectionMatrix[3][3] = 0; 42 | return projectionMatrix; 43 | } 44 | 45 | public static int[] screenportVector(int[] location, Frustum frustum) { 46 | final int left = frustum.getRenderTargetLeft(); 47 | final int right = frustum.getRenderTargetRight(); 48 | final int top = frustum.getRenderTargetTop(); 49 | final int bottom = frustum.getRenderTargetBottom(); 50 | final int halfWidth = left + ((right - left) >> 1); 51 | final int halfHeight = top + ((bottom - top) >> 1); 52 | location[VECTOR_X] += halfWidth; 53 | location[VECTOR_Y] += halfHeight; 54 | return location; 55 | } 56 | 57 | public static int[] translate(int[] vector, int[] direction) { 58 | return translate(vector, direction[VECTOR_X], direction[VECTOR_Y], direction[VECTOR_Z]); 59 | } 60 | 61 | public static int[] translate(int[] vector, int x, int y, int z) { 62 | vector[VECTOR_X] = vector[VECTOR_X] + x; 63 | vector[VECTOR_Y] = vector[VECTOR_Y] + y; 64 | vector[VECTOR_Z] = vector[VECTOR_Z] + z; 65 | return vector; 66 | } 67 | 68 | public static int[] scale(int[] vector, int factor) { 69 | vector[VECTOR_X] = FixedPointUtils.multiply(vector[VECTOR_X], factor); 70 | vector[VECTOR_Y] = FixedPointUtils.multiply(vector[VECTOR_Y], factor); 71 | vector[VECTOR_Z] = FixedPointUtils.multiply(vector[VECTOR_Z], factor); 72 | return vector; 73 | } 74 | 75 | /** 76 | * Sets result equals the vector reflected across reflectionVector. 77 | * 78 | * @param vector 79 | * @param reflectionVector 80 | * @param result 81 | */ 82 | public static int[] reflect(int[] vector, int[] reflectionVector) { 83 | final int x = reflectionVector[VECTOR_X]; 84 | final int y = reflectionVector[VECTOR_Y]; 85 | final int z = reflectionVector[VECTOR_Z]; 86 | final int dot = (int)(2 * VectorUtils.dotProduct(vector, reflectionVector)); 87 | VectorUtils.multiply(reflectionVector, dot); 88 | VectorUtils.subtract(vector, reflectionVector); 89 | reflectionVector[VECTOR_X] = x; 90 | reflectionVector[VECTOR_Y] = y; 91 | reflectionVector[VECTOR_Z] = z; 92 | return vector; 93 | } 94 | 95 | /** 96 | * Sets result equals the vector rotated around (0, 0, 0) at x axis by the given angle. 97 | * 98 | * @param vector 99 | * @param angle 100 | * @param result 101 | * @return 102 | */ 103 | public static int[] rotateX(int[] vector, int angle) { 104 | final int sin = FixedPointUtils.sin(angle); 105 | final int cos = FixedPointUtils.cos(angle); 106 | final int x = vector[VECTOR_X]; 107 | final int y = vector[VECTOR_Y]; 108 | final int z = vector[VECTOR_Z]; 109 | vector[VECTOR_X] = x; 110 | vector[VECTOR_Y] = FixedPointUtils.multiply(y, cos); 111 | vector[VECTOR_Y] -= FixedPointUtils.multiply(z, sin); 112 | vector[VECTOR_Z] = FixedPointUtils.multiply(z, cos); 113 | vector[VECTOR_Z] += FixedPointUtils.multiply(y, sin); 114 | return vector; 115 | } 116 | 117 | /** 118 | * Sets result equals the vector rotated around (0, 0, 0) at y axis by the given angle. 119 | * 120 | * @param vector 121 | * @param angle 122 | * @param result 123 | * @return 124 | */ 125 | public static int[] rotateY(int[] vector, int angle) { 126 | final int sin = FixedPointUtils.sin(angle); 127 | final int cos = FixedPointUtils.cos(angle); 128 | final int x = vector[VECTOR_X]; 129 | final int y = vector[VECTOR_Y]; 130 | final int z = vector[VECTOR_Z]; 131 | vector[VECTOR_X] = FixedPointUtils.multiply(x, cos); 132 | vector[VECTOR_X] += FixedPointUtils.multiply(z, sin); 133 | vector[VECTOR_Y] = y; 134 | vector[VECTOR_Z] = FixedPointUtils.multiply(z, cos); 135 | vector[VECTOR_Z] -= FixedPointUtils.multiply(x, sin); 136 | return vector; 137 | } 138 | 139 | /** 140 | * Sets result equals the vector rotated around (0, 0, 0) at z axis by the given angle. 141 | * 142 | * @param vector 143 | * @param angle 144 | * @param result 145 | * @return 146 | */ 147 | public static int[] rotateZ(int[] vector, int angle) { 148 | final int sin = FixedPointUtils.sin(angle); 149 | final int cos = FixedPointUtils.cos(angle); 150 | final int x = vector[VECTOR_X]; 151 | final int y = vector[VECTOR_Y]; 152 | final int z = vector[VECTOR_Z]; 153 | vector[VECTOR_X] = FixedPointUtils.multiply(x, cos); 154 | vector[VECTOR_X] += FixedPointUtils.multiply(y, sin); 155 | vector[VECTOR_Y] = FixedPointUtils.multiply(y, cos); 156 | vector[VECTOR_Y] -= FixedPointUtils.multiply(x, sin); 157 | vector[VECTOR_Z] = z; 158 | return vector; 159 | } 160 | 161 | /** 162 | * Sets result equals the translated matrix. 163 | * 164 | * @param matrix 165 | * @param x 166 | * @param y 167 | * @param z 168 | * @param result 169 | */ 170 | public static int[][] translationMatrix(int[][] matrix, int[] vector) { 171 | return translationMatrix(matrix, vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 172 | } 173 | 174 | /** 175 | * Sets result equals the translated matrix. 176 | * 177 | * @param matrix 178 | * @param x 179 | * @param y 180 | * @param z 181 | * @param result 182 | */ 183 | public static int[][] translationMatrix(int[][] matrix, int x, int y , int z) { 184 | MatrixUtils.copy(matrix, MATRIX_IDENTITY); 185 | matrix[3][0] = x; 186 | matrix[3][1] = y; 187 | matrix[3][2] = z; 188 | return matrix; 189 | } 190 | 191 | public static int[][] scaleMatrix(int[][] matrix, int[] vector) { 192 | return scaleMatrix(matrix, vector[VECTOR_X], vector[VECTOR_Y], vector[VECTOR_Z]); 193 | } 194 | 195 | public static int[][] scaleMatrix(int[][] matrix, int x, int y, int z) { 196 | MatrixUtils.copy(matrix, MATRIX_IDENTITY); 197 | matrix[0][0] = x; 198 | matrix[1][1] = y; 199 | matrix[2][2] = z; 200 | return matrix; 201 | } 202 | 203 | public static int[][] xRotationMatrix(int[][] matrix, int angle) { 204 | MatrixUtils.copy(matrix, MATRIX_IDENTITY); 205 | final int cos = FixedPointUtils.cos(angle); 206 | final int sin = FixedPointUtils.sin(angle); 207 | matrix[1][1] = cos; 208 | matrix[1][2] = sin; 209 | matrix[2][1] = -sin; 210 | matrix[2][2] = cos; 211 | return matrix; 212 | } 213 | 214 | public static int[][] yRotationMatrix(int[][] matrix, int angle) { 215 | MatrixUtils.copy(matrix, MATRIX_IDENTITY); 216 | final int cos = FixedPointUtils.cos(angle); 217 | final int sin = FixedPointUtils.sin(angle); 218 | matrix[0][0] = cos; 219 | matrix[0][2] = -sin; 220 | matrix[2][0] = sin; 221 | matrix[2][2] = cos; 222 | return matrix; 223 | } 224 | 225 | public static int[][] zRotationMatrix(int[][] matrix, int angle) { 226 | MatrixUtils.copy(matrix, MATRIX_IDENTITY); 227 | final int cos = FixedPointUtils.cos(angle); 228 | final int sin = FixedPointUtils.sin(angle); 229 | matrix[0][0] = cos; 230 | matrix[0][1] = sin; 231 | matrix[1][0] = -sin; 232 | matrix[1][1] = cos; 233 | return matrix; 234 | } 235 | } -------------------------------------------------------------------------------- /src/test/java/com/johnsproject/jgameengine/SpaceshipGame.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine; 2 | 3 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_BIT; 4 | import static com.johnsproject.jgameengine.util.FixedPointUtils.FP_HALF; 5 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_X; 6 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Y; 7 | import static com.johnsproject.jgameengine.util.VectorUtils.VECTOR_Z; 8 | 9 | import java.awt.event.KeyEvent; 10 | import java.io.IOException; 11 | 12 | import com.johnsproject.jgameengine.event.EngineEvent; 13 | import com.johnsproject.jgameengine.event.EngineListener; 14 | import com.johnsproject.jgameengine.io.OBJImporter; 15 | import com.johnsproject.jgameengine.model.Camera; 16 | import com.johnsproject.jgameengine.model.FrameBuffer; 17 | import com.johnsproject.jgameengine.model.Light; 18 | import com.johnsproject.jgameengine.model.Mesh; 19 | import com.johnsproject.jgameengine.model.Model; 20 | import com.johnsproject.jgameengine.model.Scene; 21 | import com.johnsproject.jgameengine.model.Texture; 22 | import com.johnsproject.jgameengine.model.Transform; 23 | import com.johnsproject.jgameengine.shading.BasicThreadedShader; 24 | import com.johnsproject.jgameengine.shading.DirectionalLightShadowShader; 25 | import com.johnsproject.jgameengine.shading.FlatShader; 26 | import com.johnsproject.jgameengine.shading.GouraudShader; 27 | import com.johnsproject.jgameengine.shading.PhongShader; 28 | import com.johnsproject.jgameengine.util.FileUtils; 29 | import com.johnsproject.jgameengine.util.FixedPointUtils; 30 | import com.johnsproject.jgameengine.util.VectorUtils; 31 | 32 | @SuppressWarnings("unused") 33 | public class SpaceshipGame implements EngineListener { 34 | 35 | private static final int WINDOW_WIDTH = 1920; 36 | private static final int WINDOW_HEIGHT = 1080; 37 | 38 | private final FrameBuffer frameBuffer; 39 | private final EngineWindow window; 40 | private final GraphicsEngine graphicsEngine; 41 | private final EngineStatistics engineStats; 42 | private final InputEngine inputEngine = new InputEngine(); 43 | private final Scene scene = new Scene(); 44 | 45 | private Transform cameraTransform; 46 | private Transform lightTransform; 47 | private Transform spaceshipTransform; 48 | 49 | private Model spaceshipFireModel; 50 | private Transform spaceshipFireTransform; 51 | 52 | private int spaceshipFireScale = 2000; 53 | 54 | public static void main(String[] args) { 55 | int width = WINDOW_WIDTH; 56 | int height = WINDOW_HEIGHT; 57 | int scaling = 100; 58 | for (int i = 0; i < args.length; i++) { 59 | final String setting = args[i]; 60 | if(setting.contains("window")) { 61 | final String[] resolution = setting.replace("window", "").split("x"); 62 | width = Integer.parseInt(resolution[0]); 63 | height = Integer.parseInt(resolution[1]); 64 | } 65 | else if(setting.contains("render")) { 66 | scaling = Integer.parseInt(setting.replace("render", "")); 67 | } 68 | } 69 | new SpaceshipGame(width, height, scaling); 70 | } 71 | 72 | public SpaceshipGame(int width, int height, int scaling) { 73 | frameBuffer = new FrameBuffer((width * scaling) / 100, (height * scaling) / 100); 74 | window = new EngineWindow(frameBuffer); 75 | graphicsEngine = new GraphicsEngine(frameBuffer); 76 | engineStats = new EngineStatistics(window); 77 | window.setSize(width, height); 78 | 79 | Engine.getInstance().setScene(scene); 80 | Engine.getInstance().addEngineListener(this); 81 | Engine.getInstance().addEngineListener(graphicsEngine); 82 | Engine.getInstance().addEngineListener(inputEngine); 83 | Engine.getInstance().addEngineListener(window); 84 | Engine.getInstance().addEngineListener(engineStats); 85 | Engine.getInstance().start(); 86 | } 87 | 88 | public void initialize(EngineEvent e) { 89 | window.setTitle("Spaceship Game"); 90 | 91 | // remove unused shaders to save performance 92 | graphicsEngine.getShaders().clear(); 93 | graphicsEngine.addShader(new DirectionalLightShadowShader()); 94 | graphicsEngine.addShader(new GouraudShader()); 95 | graphicsEngine.setDefaultShader(graphicsEngine.getShader(1)); 96 | try { 97 | final ClassLoader classLoader = this.getClass().getClassLoader(); 98 | loadTerrain(classLoader); 99 | loadSpaceshipFire(classLoader); 100 | loadSpaceship(classLoader); 101 | createCamera(); 102 | createLight(); 103 | } catch (IOException ioE) { 104 | ioE.printStackTrace(); 105 | } 106 | moveCameraToSpaceship(); 107 | } 108 | 109 | private void loadTerrain(ClassLoader classLoader) throws IOException { 110 | final Model terrainModel = OBJImporter.parseResource(classLoader, "SpaceshipGame/Terrain.obj"); 111 | scene.addModel(terrainModel); 112 | 113 | terrainModel.getTransform().setScale(3 << FP_BIT, 3 << FP_BIT, 3 << FP_BIT); 114 | 115 | final Texture texture = new Texture(FileUtils.loadImage(this.getClass().getResourceAsStream("/JohnsProjectLogo.png"))); 116 | terrainModel.getMesh().getMaterial(0).setTexture(texture); 117 | } 118 | 119 | private void loadSpaceshipFire(ClassLoader classLoader) throws IOException { 120 | spaceshipFireModel = OBJImporter.parseResource(classLoader, "SpaceshipGame/SpaceshipFire.obj"); 121 | scene.addModel(spaceshipFireModel); 122 | 123 | spaceshipFireTransform = spaceshipFireModel.getTransform(); 124 | spaceshipFireTransform.setScale(FP_HALF, FP_HALF, FP_HALF); 125 | 126 | // the basic shader has no illumination so it will have a glow like effect 127 | final BasicThreadedShader basicShader = new BasicThreadedShader(); 128 | graphicsEngine.addShader(basicShader); 129 | spaceshipFireModel.getMesh().getMaterial(0).setShader(basicShader); 130 | } 131 | 132 | private void loadSpaceship(ClassLoader classLoader) throws IOException { 133 | final Model spaceshipModel = OBJImporter.parseResource(classLoader, "SpaceshipGame/Spaceship.obj"); 134 | scene.addModel(spaceshipModel); 135 | 136 | spaceshipTransform = spaceshipModel.getTransform(); 137 | spaceshipTransform.worldTranslate(0, 5 << FP_BIT, 0); 138 | spaceshipTransform.setScale(FP_HALF, FP_HALF, FP_HALF); 139 | } 140 | 141 | private void createCamera() { 142 | cameraTransform = new Transform(); 143 | cameraTransform.worldRotate(-45 << FP_BIT, 0, 0); 144 | final Camera camera = new Camera("Camera", cameraTransform); 145 | scene.addCamera(camera); 146 | } 147 | 148 | private void createLight() { 149 | lightTransform = new Transform(); 150 | lightTransform.worldRotate(-60 << FP_BIT, 25 << FP_BIT, 0); 151 | final Light directionalLight = new Light("DirectionalLight", lightTransform); 152 | scene.addLight(directionalLight); 153 | } 154 | 155 | public void fixedUpdate(EngineEvent e) { 156 | spaceshipFireScale = -spaceshipFireScale; 157 | spaceshipFireTransform.worldScale(spaceshipFireScale, spaceshipFireScale, spaceshipFireScale); 158 | } 159 | 160 | public void dynamicUpdate(EngineEvent e) { 161 | final boolean isAnyMoveKeyPressed = inputEngine.isKeyPressed(KeyEvent.VK_W) || inputEngine.isKeyPressed(KeyEvent.VK_S) 162 | || inputEngine.isKeyPressed(KeyEvent.VK_A) || inputEngine.isKeyPressed(KeyEvent.VK_D); 163 | 164 | if(isAnyMoveKeyPressed) { 165 | moveSpaceshipWithKeyboard(e.getDeltaTime()); 166 | moveFireToSpaceship(); 167 | moveCameraToSpaceship(); 168 | moveLightToCamera(); 169 | } 170 | spaceshipFireModel.setActive(isAnyMoveKeyPressed); 171 | } 172 | 173 | private void moveSpaceshipWithKeyboard(int deltaTime) { 174 | if(inputEngine.isKeyPressed(KeyEvent.VK_W) && inputEngine.isKeyPressed(KeyEvent.VK_A)) { 175 | spaceshipTransform.setRotation(0, 45 << FP_BIT, 0); 176 | } 177 | else if(inputEngine.isKeyPressed(KeyEvent.VK_W) && inputEngine.isKeyPressed(KeyEvent.VK_D)) { 178 | spaceshipTransform.setRotation(0, -45 << FP_BIT, 0); 179 | } 180 | else if(inputEngine.isKeyPressed(KeyEvent.VK_S) && inputEngine.isKeyPressed(KeyEvent.VK_A)) { 181 | spaceshipTransform.setRotation(0, 135 << FP_BIT, 0); 182 | } 183 | else if(inputEngine.isKeyPressed(KeyEvent.VK_S) && inputEngine.isKeyPressed(KeyEvent.VK_D)) { 184 | spaceshipTransform.setRotation(0, -135 << FP_BIT, 0); 185 | } 186 | else if(inputEngine.isKeyPressed(KeyEvent.VK_W)) { 187 | spaceshipTransform.setRotation(0, 0, 0); 188 | } 189 | else if(inputEngine.isKeyPressed(KeyEvent.VK_S)) { 190 | spaceshipTransform.setRotation(0, 180 << FP_BIT, 0); 191 | } 192 | else if(inputEngine.isKeyPressed(KeyEvent.VK_A)) { 193 | spaceshipTransform.setRotation(0, 90 << FP_BIT, 0); 194 | } 195 | else if(inputEngine.isKeyPressed(KeyEvent.VK_D)) { 196 | spaceshipTransform.setRotation(0, -90 << FP_BIT, 0); 197 | } 198 | spaceshipTransform.localTranslate(0, 0, -FixedPointUtils.multiply(FP_HALF, deltaTime)); 199 | } 200 | 201 | 202 | private void moveFireToSpaceship() { 203 | spaceshipFireTransform.setLocation(0, 0, 4 << FP_BIT); 204 | VectorUtils.multiply(spaceshipFireTransform.getLocation(), spaceshipTransform.getSpaceExitMatrix()); 205 | 206 | final int[] spaceshipRotation = spaceshipTransform.getRotation(); 207 | spaceshipFireTransform.setRotation(spaceshipRotation[VECTOR_X], spaceshipRotation[VECTOR_Y], spaceshipRotation[VECTOR_Z]); 208 | } 209 | 210 | private void moveCameraToSpaceship() { 211 | final int[] spaceshipLocation = spaceshipTransform.getLocation(); 212 | cameraTransform.setLocation(spaceshipLocation[VECTOR_X], spaceshipLocation[VECTOR_Y], spaceshipLocation[VECTOR_Z]); 213 | cameraTransform.worldTranslate(0, 20 << FP_BIT, 15 << FP_BIT); 214 | } 215 | 216 | private void moveLightToCamera() { 217 | final int[] cameraLocation = cameraTransform.getLocation(); 218 | lightTransform.setLocation(cameraLocation[VECTOR_X], cameraLocation[VECTOR_Y], cameraLocation[VECTOR_Z]); 219 | } 220 | 221 | public int getLayer() { 222 | return DEFAULT_LAYER; 223 | } 224 | } -------------------------------------------------------------------------------- /src/test/java/com/johnsproject/jgameengine/io/OBJImporterTest.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.io; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotNull; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.List; 9 | 10 | import org.junit.Test; 11 | 12 | import com.johnsproject.jgameengine.model.Face; 13 | import com.johnsproject.jgameengine.model.Material; 14 | import com.johnsproject.jgameengine.model.Mesh; 15 | import com.johnsproject.jgameengine.model.Vertex; 16 | import com.johnsproject.jgameengine.util.ColorUtils; 17 | import com.johnsproject.jgameengine.util.FileUtils; 18 | import com.johnsproject.jgameengine.util.FixedPointUtils; 19 | import com.johnsproject.jgameengine.util.VectorUtils; 20 | 21 | public class OBJImporterTest { 22 | 23 | @Test 24 | public void parseMaterialsTest() throws Exception { 25 | final String data = readResource("TestOBJ.mtl"); 26 | final List materials = OBJImporter.parseMaterials(data); 27 | assert(materials.size() == 2); 28 | 29 | Material material = materials.get(0); 30 | assert(material.getIndex() == 0); 31 | assertEquals(material.getName(), "Material"); 32 | 33 | int shininess = FixedPointUtils.toFixedPoint(17.647059); 34 | assert(material.getShininess() == shininess); 35 | 36 | int r = Math.round(0.8f * ColorUtils.COLOR_ONE); 37 | int g = Math.round(0.8f * ColorUtils.COLOR_ONE); 38 | int b = Math.round(0.8f * ColorUtils.COLOR_ONE); 39 | int color = ColorUtils.toColor(r, g, b); 40 | assert(material.getDiffuseColor() == color); 41 | 42 | r = Math.round(0.5f * ColorUtils.COLOR_ONE); 43 | g = Math.round(0.5f * ColorUtils.COLOR_ONE); 44 | b = Math.round(0.5f * ColorUtils.COLOR_ONE); 45 | color = ColorUtils.toColor(r, g, b); 46 | assert(material.getSpecularColor() == color); 47 | 48 | material = materials.get(1); 49 | assert(material.getIndex() == 1); 50 | assertEquals(material.getName(), "Material.001"); 51 | 52 | shininess = FixedPointUtils.toFixedPoint(96.078431); 53 | assert(material.getShininess() == shininess); 54 | 55 | r = Math.round(0.64f * ColorUtils.COLOR_ONE); 56 | g = Math.round(0.64f * ColorUtils.COLOR_ONE); 57 | b = Math.round(0.64f * ColorUtils.COLOR_ONE); 58 | color = ColorUtils.toColor(r, g, b); 59 | assert(material.getDiffuseColor() == color); 60 | 61 | r = Math.round(0.5f * ColorUtils.COLOR_ONE); 62 | g = Math.round(0.5f * ColorUtils.COLOR_ONE); 63 | b = Math.round(0.5f * ColorUtils.COLOR_ONE); 64 | color = ColorUtils.toColor(r, g, b); 65 | assert(material.getSpecularColor() == color); 66 | } 67 | 68 | @Test 69 | public void parseVerticesTest() throws Exception { 70 | final String data = readResource("TestOBJ.obj"); 71 | final List vertices = OBJImporter.parseVertices(data); 72 | assert(vertices.size() == 16); 73 | 74 | OBJImporter.VertexData vertex = vertices.get(0); 75 | int x = FixedPointUtils.toFixedPoint(-4.198291); 76 | int y = FixedPointUtils.toFixedPoint(2.563459); 77 | int z = FixedPointUtils.toFixedPoint(-1.020053); 78 | assert(vertex.location[VectorUtils.VECTOR_X] == x); 79 | assert(vertex.location[VectorUtils.VECTOR_Y] == y); 80 | assert(vertex.location[VectorUtils.VECTOR_Z] == z); 81 | 82 | vertex = vertices.get(3); 83 | x = FixedPointUtils.toFixedPoint(-6.198290); 84 | y = FixedPointUtils.toFixedPoint(2.563459); 85 | z = FixedPointUtils.toFixedPoint(-1.020054); 86 | assert(vertex.location[VectorUtils.VECTOR_X] == x); 87 | assert(vertex.location[VectorUtils.VECTOR_Y] == y); 88 | assert(vertex.location[VectorUtils.VECTOR_Z] == z); 89 | 90 | vertex = vertices.get(15); 91 | x = FixedPointUtils.toFixedPoint(-1); 92 | y = FixedPointUtils.toFixedPoint(1); 93 | z = FixedPointUtils.toFixedPoint(-1); 94 | assert(vertex.location[VectorUtils.VECTOR_X] == x); 95 | assert(vertex.location[VectorUtils.VECTOR_Y] == y); 96 | assert(vertex.location[VectorUtils.VECTOR_Z] == z); 97 | } 98 | 99 | 100 | @Test 101 | public void parseFaceNormalsTest() throws Exception { 102 | final String data = readResource("TestOBJ.obj"); 103 | final List faceNormals = OBJImporter.parseFaceNormals(data); 104 | assert(faceNormals.size() == 12); 105 | 106 | OBJImporter.FaceNormal faceNormal = faceNormals.get(0); 107 | int x = FixedPointUtils.toFixedPoint(0); 108 | int y = FixedPointUtils.toFixedPoint(-1); 109 | int z = FixedPointUtils.toFixedPoint(0); 110 | assert(faceNormal.normal[VectorUtils.VECTOR_X] == x); 111 | assert(faceNormal.normal[VectorUtils.VECTOR_Y] == y); 112 | assert(faceNormal.normal[VectorUtils.VECTOR_Z] == z); 113 | 114 | faceNormal = faceNormals.get(11); 115 | x = FixedPointUtils.toFixedPoint(0); 116 | y = FixedPointUtils.toFixedPoint(0); 117 | z = FixedPointUtils.toFixedPoint(-1); 118 | assert(faceNormal.normal[VectorUtils.VECTOR_X] == x); 119 | assert(faceNormal.normal[VectorUtils.VECTOR_Y] == y); 120 | assert(faceNormal.normal[VectorUtils.VECTOR_Z] == z); 121 | } 122 | 123 | @Test 124 | public void parseUVsTest() throws Exception { 125 | final String data = readResource("TestOBJ.obj"); 126 | final List faceUVs = OBJImporter.parseFaceUVs(data); 127 | 128 | OBJImporter.FaceUV faceUV = faceUVs.get(0); 129 | int x = FixedPointUtils.toFixedPoint(1); 130 | int y = FixedPointUtils.toFixedPoint(0); 131 | assert(faceUV.uv[VectorUtils.VECTOR_X] == x); 132 | assert(faceUV.uv[VectorUtils.VECTOR_Y] == y); 133 | 134 | faceUV = faceUVs.get(39); 135 | x = FixedPointUtils.toFixedPoint(1); 136 | y = FixedPointUtils.toFixedPoint(1); 137 | assert(faceUV.uv[VectorUtils.VECTOR_X] == x); 138 | assert(faceUV.uv[VectorUtils.VECTOR_Y] == y); 139 | } 140 | 141 | @Test 142 | public void parseFacesTest() throws Exception { 143 | final String materialData = readResource("TestOBJ.mtl"); 144 | final String objectData = readResource("TestOBJ.obj"); 145 | final List materials = OBJImporter.parseMaterials(materialData); 146 | final List faces = OBJImporter.parseFaces(objectData, materials); 147 | assert(faces.size() == 24); 148 | 149 | OBJImporter.FaceData face = faces.get(0); 150 | assert(face.index == 0); 151 | assert(face.vertexIndices[0] == 1); 152 | assert(face.vertexIndices[1] == 3); 153 | assert(face.vertexIndices[2] == 0); 154 | assert(face.normalIndices[0] == 0); 155 | assert(face.normalIndices[1] == 0); 156 | assert(face.normalIndices[2] == 0); 157 | assert(face.uvIndices[0] == 0); 158 | assert(face.uvIndices[1] == 1); 159 | assert(face.uvIndices[2] == 2); 160 | assert(face.material == materials.get(1)); 161 | 162 | face = faces.get(5); 163 | assert(face.index == 5); 164 | assert(face.vertexIndices[0] == 0); 165 | assert(face.vertexIndices[1] == 7); 166 | assert(face.vertexIndices[2] == 4); 167 | assert(face.normalIndices[0] == 5); 168 | assert(face.normalIndices[1] == 5); 169 | assert(face.normalIndices[2] == 5); 170 | assert(face.uvIndices[0] == 13); 171 | assert(face.uvIndices[1] == 14); 172 | assert(face.uvIndices[2] == 5); 173 | assert(face.material == materials.get(1)); 174 | 175 | face = faces.get(23); 176 | assert(face.index == 23); 177 | assert(face.vertexIndices[0] == 8); 178 | assert(face.vertexIndices[1] == 11); 179 | assert(face.vertexIndices[2] == 15); 180 | assert(face.normalIndices[0] == 11); 181 | assert(face.normalIndices[1] == 11); 182 | assert(face.normalIndices[2] == 11); 183 | assert(face.uvIndices[0] == 33); 184 | assert(face.uvIndices[1] == 39); 185 | assert(face.uvIndices[2] == 34); 186 | assert(face.material == materials.get(0)); 187 | } 188 | 189 | @Test 190 | public void parseMeshTest() throws Exception { 191 | final String materialData = readResource("TestOBJ.mtl"); 192 | final String objectData = readResource("TestOBJ.obj"); 193 | final Mesh mesh = OBJImporter.parseMesh(objectData, materialData); 194 | 195 | assert(mesh.getMaterials().length == 2); 196 | assert(mesh.getVertices().length == 16); 197 | assert(mesh.getFaces().length == 24); 198 | 199 | assertNotNull(mesh.getMaterial("Material")); 200 | assertNotNull(mesh.getMaterial("Material.001")); 201 | 202 | Vertex vertex = mesh.getVertex(0); 203 | assert(vertex.getIndex() == 0); 204 | int x = FixedPointUtils.toFixedPoint(-4.198291); 205 | int y = FixedPointUtils.toFixedPoint(2.563459); 206 | int z = FixedPointUtils.toFixedPoint(-1.020053); 207 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_X] == x); 208 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_Y] == y); 209 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_Z] == z); 210 | assert(vertex.getMaterial() == mesh.getMaterial("Material.001")); 211 | 212 | vertex = mesh.getVertex(15); 213 | assert(vertex.getIndex() == 15); 214 | x = FixedPointUtils.toFixedPoint(-1); 215 | y = FixedPointUtils.toFixedPoint(1); 216 | z = FixedPointUtils.toFixedPoint(-1); 217 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_X] == x); 218 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_Y] == y); 219 | assert(vertex.getLocalLocation()[VectorUtils.VECTOR_Z] == z); 220 | assert(vertex.getMaterial() == mesh.getMaterial("Material")); 221 | 222 | Face face = mesh.getFace(0); 223 | assert(face.getIndex() == 0); 224 | assert(face.getVertex(0) == mesh.getVertex(1)); 225 | assert(face.getVertex(1) == mesh.getVertex(3)); 226 | assert(face.getVertex(2) == mesh.getVertex(0)); 227 | 228 | x = FixedPointUtils.toFixedPoint(0); 229 | y = FixedPointUtils.toFixedPoint(-1); 230 | z = FixedPointUtils.toFixedPoint(0); 231 | assert(face.getLocalNormal()[VectorUtils.VECTOR_X] == x); 232 | assert(face.getLocalNormal()[VectorUtils.VECTOR_Y] == y); 233 | assert(face.getLocalNormal()[VectorUtils.VECTOR_Z] == z); 234 | 235 | x = FixedPointUtils.toFixedPoint(1); 236 | y = FixedPointUtils.toFixedPoint(0); 237 | assert(face.getUV(0)[VectorUtils.VECTOR_X] == x); 238 | assert(face.getUV(0)[VectorUtils.VECTOR_Y] == y); 239 | 240 | x = FixedPointUtils.toFixedPoint(0); 241 | y = FixedPointUtils.toFixedPoint(1); 242 | assert(face.getUV(1)[VectorUtils.VECTOR_X] == x); 243 | assert(face.getUV(1)[VectorUtils.VECTOR_Y] == y); 244 | 245 | x = FixedPointUtils.toFixedPoint(0); 246 | y = FixedPointUtils.toFixedPoint(0); 247 | assert(face.getUV(2)[VectorUtils.VECTOR_X] == x); 248 | assert(face.getUV(2)[VectorUtils.VECTOR_Y] == y); 249 | 250 | assert(face.getMaterial() == mesh.getMaterial("Material.001")); 251 | } 252 | 253 | private String readResource(String path) throws IOException { 254 | final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 255 | final InputStream inputStream = classLoader.getResourceAsStream(path); 256 | return FileUtils.readStream(inputStream); 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /src/test/java/com/johnsproject/jgameengine/util/FixedPointUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.johnsproject.jgameengine.util; 2 | 3 | import org.junit.Test; 4 | 5 | import com.johnsproject.jgameengine.util.FixedPointUtils; 6 | 7 | public class FixedPointUtilsTest { 8 | 9 | // @Test 10 | // public void genLookupTableTest() throws Exception { 11 | // for (int angle = 0; angle < 91; angle++) { 12 | // System.out.print((int)Math.round(Math.sin(Math.toRadians(angle)) * FixedPointUtils.FP_ONE) + ", "); 13 | // } 14 | // } 15 | 16 | // @Test 17 | // public void genLookupTableTest() throws Exception { 18 | // for (int angle = 0; angle < 361; angle++) { 19 | // System.out.print((int)Math.round(Math.cos(Math.toRadians(angle)) * FixedPointUtils.FP_ONE) + ", "); 20 | // } 21 | // } 22 | 23 | @Test 24 | public void toDegreeTest() throws Exception { 25 | for (int i = 1; i < 360; i++) { 26 | double precision = 0.001; 27 | int fpRad = FixedPointUtils.toFixedPoint(Math.toRadians(i)); 28 | double fpDegree = FixedPointUtils.toDegrees(fpRad); 29 | fpDegree = FixedPointUtils.toDouble((int)fpDegree); 30 | double mathDegree = i; 31 | assert((fpDegree >= mathDegree - precision) && (fpDegree <= mathDegree + precision)); 32 | } 33 | } 34 | 35 | @Test 36 | public void toRadiansTest() throws Exception { 37 | for (int i = 1; i < 360; i++) { 38 | double precision = 0.1; 39 | int fpDegree = FixedPointUtils.toFixedPoint(Math.toDegrees(i)); 40 | double fpRad = FixedPointUtils.toRadians(fpDegree); 41 | fpRad = FixedPointUtils.toDouble((int)fpRad); 42 | double mathRad = i; 43 | assert((fpRad >= mathRad - precision) && (fpRad <= mathRad + precision)); 44 | } 45 | } 46 | 47 | @Test 48 | public void sinTest() throws Exception { 49 | for (int i = 0; i < 361; i++) { 50 | double precision = 0.0001; 51 | int fpAngle = FixedPointUtils.toFixedPoint(i); 52 | double fpSin = FixedPointUtils.sin(fpAngle); 53 | fpSin = FixedPointUtils.toDouble((int)fpSin); 54 | double sin = Math.sin(Math.toRadians(i)); 55 | assert ((fpSin >= sin - precision) && (fpSin <= sin + precision)); 56 | } 57 | for (int i = 0; i > -361; i--) { 58 | double precision = 0.0001; 59 | int fpAngle = FixedPointUtils.toFixedPoint(i); 60 | double fpSin = FixedPointUtils.sin(fpAngle); 61 | fpSin = FixedPointUtils.toDouble((int)fpSin); 62 | double sin = Math.sin(Math.toRadians(i)); 63 | assert ((fpSin >= sin - precision) && (fpSin <= sin + precision)); 64 | } 65 | } 66 | 67 | @Test 68 | public void cosTest() throws Exception { 69 | for (int i = 0; i < 361; i++) { 70 | double precision = 0.0001; 71 | int fpAngle = FixedPointUtils.toFixedPoint(i); 72 | double fpCos = FixedPointUtils.cos(fpAngle); 73 | fpCos = FixedPointUtils.toDouble((int)fpCos); 74 | double cos = Math.cos(Math.toRadians(i)); 75 | assert ((fpCos >= cos - precision) && (fpCos <= cos + precision)); 76 | } 77 | for (int i = 0; i > -361; i--) { 78 | double precision = 0.0001; 79 | int fpAngle = FixedPointUtils.toFixedPoint(i); 80 | double fpCos = FixedPointUtils.cos(fpAngle); 81 | fpCos = FixedPointUtils.toDouble((int)fpCos); 82 | double cos = Math.cos(Math.toRadians(i)); 83 | assert ((fpCos >= cos - precision) && (fpCos <= cos + precision)); 84 | } 85 | } 86 | 87 | @Test 88 | public void tanTest() throws Exception { 89 | for (int i = 0; i < 90; i++) { 90 | double precision = 0.1; 91 | int fpAngle = FixedPointUtils.toFixedPoint(i); 92 | double fpTan = FixedPointUtils.tan(fpAngle); 93 | fpTan = FixedPointUtils.toDouble((int)fpTan); 94 | double tan = Math.tan(Math.toRadians(i)); 95 | assert ((fpTan >= tan - precision) && (fpTan <= tan + precision)); 96 | } 97 | } 98 | 99 | @Test 100 | public void asinTest() throws Exception { 101 | for (int i = 0; i < 361; i++) { 102 | double precision = 0.0001; 103 | double fpAngle = FixedPointUtils.toFixedPoint(i); 104 | int fpSin = FixedPointUtils.sin((int)fpAngle); 105 | fpAngle = FixedPointUtils.asin(fpSin); 106 | fpAngle = FixedPointUtils.toDouble((int)fpAngle); 107 | double sin = Math.sin(Math.toRadians(i)); 108 | double angle = Math.toDegrees(Math.asin(sin)); 109 | assert ((fpAngle >= angle - precision) && (fpAngle <= angle + precision)); 110 | } 111 | for (int i = 0; i > -361; i--) { 112 | double precision = 0.0001; 113 | double fpAngle = FixedPointUtils.toFixedPoint(i); 114 | int fpSin = FixedPointUtils.sin((int)fpAngle); 115 | fpAngle = FixedPointUtils.asin(fpSin); 116 | fpAngle = FixedPointUtils.toDouble((int)fpAngle); 117 | double sin = Math.sin(Math.toRadians(i)); 118 | double angle = Math.toDegrees(Math.asin(sin)); 119 | assert ((fpAngle >= angle - precision) && (fpAngle <= angle + precision)); 120 | } 121 | } 122 | 123 | @Test 124 | public void acosTest() throws Exception { 125 | for (int i = 0; i < 361; i++) { 126 | double precision = 0.0001; 127 | double fpAngle = FixedPointUtils.toFixedPoint(i); 128 | int fpCos = FixedPointUtils.cos((int)fpAngle); 129 | fpAngle = FixedPointUtils.acos(fpCos); 130 | fpAngle = FixedPointUtils.toDouble((int)fpAngle); 131 | double cos = Math.cos(Math.toRadians(i)); 132 | double angle = Math.toDegrees(Math.acos(cos)); 133 | assert ((fpAngle >= angle - precision) && (fpAngle <= angle + precision)); 134 | } 135 | for (int i = 0; i > -361; i--) { 136 | double precision = 0.0001; 137 | double fpAngle = FixedPointUtils.toFixedPoint(i); 138 | int fpCos = FixedPointUtils.cos((int)fpAngle); 139 | fpAngle = FixedPointUtils.acos(fpCos); 140 | fpAngle = FixedPointUtils.toDouble((int)fpAngle); 141 | double cos = Math.cos(Math.toRadians(i)); 142 | double angle = Math.toDegrees(Math.acos(cos)); 143 | assert ((fpAngle >= angle - precision) && (fpAngle <= angle + precision)); 144 | } 145 | } 146 | 147 | @Test 148 | public void basicOperationsTest() throws Exception { 149 | // 255 because 256 * 256 = 65536 and will cause overflow of integer part of fixed point 150 | for (int i = 1; i < 256; i++) { 151 | double precision = 0.000000000000000000000000000000000001; 152 | int fpValue1 = FixedPointUtils.toFixedPoint(i); 153 | int fpValue2 = FixedPointUtils.toFixedPoint(i); 154 | double fpMultiply = FixedPointUtils.multiply(fpValue1, fpValue2); 155 | double fpDivide = FixedPointUtils.divide(fpValue1, fpValue2); 156 | fpMultiply = FixedPointUtils.toDouble((int)fpMultiply); 157 | fpDivide = FixedPointUtils.toDouble((int)fpDivide); 158 | double value1 = i; 159 | double value2 = i; 160 | double mathMultiply = value1 * value2; 161 | double mathDivide = value1 / value2; 162 | assert ((fpMultiply >= mathMultiply - precision) && (fpMultiply <= mathMultiply + precision)); 163 | assert ((fpDivide >= mathDivide - precision) && (fpDivide <= mathDivide + precision)); 164 | } 165 | } 166 | 167 | @Test 168 | public void normalizeTest() throws Exception { 169 | for (int i = 2; i < FixedPointUtils.FP_ONE; i++) { 170 | int fpMin = FixedPointUtils.toFixedPoint(i); 171 | int fpMax = FixedPointUtils.toFixedPoint(i + 2); 172 | int fpValue = FixedPointUtils.toFixedPoint(i + 5); 173 | int fpNormalizedValue = FixedPointUtils.normalize(fpValue, fpMin, fpMax); 174 | assert(fpNormalizedValue >= fpMin && fpNormalizedValue <= fpMax); 175 | } 176 | } 177 | 178 | @Test 179 | public void clampTest() throws Exception { 180 | for (int i = 0; i < FixedPointUtils.FP_ONE; i++) { 181 | int fpMin = FixedPointUtils.toFixedPoint(i); 182 | int fpMax = FixedPointUtils.toFixedPoint(i + 5); 183 | int fpValue = FixedPointUtils.toFixedPoint(i + 10); 184 | int fpNormalizedValue = FixedPointUtils.clamp(fpValue, fpMin, fpMax); 185 | assert(fpNormalizedValue >= fpMin && fpNormalizedValue <= fpMax); 186 | } 187 | } 188 | 189 | @Test 190 | public void randomTest() throws Exception { 191 | int lastRandomValue = 0; 192 | for (int i = 1; i < FixedPointUtils.FP_ONE; i++) { 193 | int randomValue = FixedPointUtils.random(lastRandomValue); 194 | lastRandomValue = randomValue; 195 | assert(randomValue != FixedPointUtils.random(i)); 196 | } 197 | } 198 | @Test 199 | public void minMaxRandomTest() throws Exception { 200 | int lastRandomValue = 0; 201 | for (int i = 1; i < FixedPointUtils.FP_ONE; i++) { 202 | int fpMin = FixedPointUtils.toFixedPoint(0); 203 | int fpMax = FixedPointUtils.toFixedPoint(100); 204 | int randomValue = FixedPointUtils.random(lastRandomValue, fpMin, fpMax); 205 | lastRandomValue = randomValue; 206 | assert(randomValue != FixedPointUtils.random(i, fpMin, fpMax)); 207 | assert((randomValue <= fpMax) && (randomValue >= fpMin)); 208 | assert((randomValue <= fpMax) && (randomValue >= fpMin)); 209 | } 210 | } 211 | 212 | @Test 213 | public void powTest() throws Exception { 214 | // max pow is 15 because because integer part of fixed point has 15 bits 215 | for (int i = 0; i < 16; i++) { 216 | double precision = 0.000000000001; 217 | int value = 2; 218 | int fpValue = FixedPointUtils.toFixedPoint(value); 219 | double fpPow = FixedPointUtils.pow(fpValue, FixedPointUtils.toFixedPoint(i)); 220 | fpPow = FixedPointUtils.toDouble((int)fpPow); 221 | double mathPow = Math.pow(value, i); 222 | assert((fpPow >= mathPow - precision) && (fpPow <= mathPow + precision)); 223 | } 224 | } 225 | 226 | @Test 227 | public void sqrtTest() throws Exception { 228 | for (int i = 1; i < FixedPointUtils.FP_ONE; i++) { 229 | double precision = 0.01; 230 | double fpSqrt = FixedPointUtils.sqrt(FixedPointUtils.toFixedPoint(i)); 231 | fpSqrt = FixedPointUtils.toDouble((int)fpSqrt); 232 | double mathSqrt = Math.sqrt(i); 233 | assert((fpSqrt >= mathSqrt - precision) && (fpSqrt <= mathSqrt + precision)); 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/test/resources/DefaultTest.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'exporterTest.blend' 2 | # Material Count: 5 3 | 4 | newmtl Material 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.000000 0.640000 0.004048 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.001 15 | Ns 96.078431 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.000000 0.026653 0.640000 18 | Ks 0.000000 0.500000 0.126628 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | 24 | newmtl Material.002 25 | Ns 17.647059 26 | Ka 1.000000 1.000000 1.000000 27 | Kd 0.640000 0.382606 0.025754 28 | Ks 0.500000 0.500000 0.500000 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.000000 31 | d 1.000000 32 | illum 2 33 | 34 | newmtl Material.006 35 | Ns 96.078431 36 | Ka 1.000000 1.000000 1.000000 37 | Kd 0.640000 0.640000 0.640000 38 | Ks 0.500000 0.500000 0.500000 39 | Ke 0.000000 0.000000 0.000000 40 | Ni 1.000000 41 | d 1.000000 42 | illum 2 43 | 44 | newmtl Material.007 45 | Ns 96.078431 46 | Ka 1.000000 1.000000 1.000000 47 | Kd 0.640000 0.002034 0.000000 48 | Ks 0.500000 0.500000 0.500000 49 | Ke 0.000000 0.000000 0.000000 50 | Ni 1.000000 51 | d 1.000000 52 | illum 2 53 | -------------------------------------------------------------------------------- /src/test/resources/JohnsProjectLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JohnsProject/JGameEngine/58ab2a68198f234b96fc12aa1f8ad9eb73ae8a0e/src/test/resources/JohnsProjectLogo.png -------------------------------------------------------------------------------- /src/test/resources/SpaceshipGame/Spaceship.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 2 3 | 4 | newmtl Material 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.001 15 | Ns 96.078431 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.000000 0.484832 0.640000 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | -------------------------------------------------------------------------------- /src/test/resources/SpaceshipGame/SpaceshipFire.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl Material.002 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.442716 0.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | -------------------------------------------------------------------------------- /src/test/resources/SpaceshipGame/SpaceshipFire.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib SpaceshipFire.mtl 4 | v -0.410265 -0.410265 1.427912 5 | v -0.410265 0.410265 1.427912 6 | v -1.000000 -1.000000 -1.000000 7 | v -1.000000 1.000000 -1.000000 8 | v 0.410265 -0.410265 1.427912 9 | v 0.410265 0.410265 1.427912 10 | v 1.000000 -1.000000 -1.000000 11 | v 1.000000 1.000000 -1.000000 12 | v -0.281288 0.281288 -2.043880 13 | v -0.281288 -0.281288 -2.043880 14 | v 0.281288 -0.281288 -2.043880 15 | v 0.281288 0.281288 -2.043880 16 | vn -0.9717 0.0000 0.2360 17 | vn -0.8237 0.0000 -0.5671 18 | vn 0.9717 0.0000 0.2360 19 | vn 0.0000 0.0000 1.0000 20 | vn 0.0000 -0.9717 0.2360 21 | vn 0.0000 0.9717 0.2360 22 | vn 0.0000 0.0000 -1.0000 23 | vn 0.8237 0.0000 -0.5671 24 | vn 0.0000 -0.8237 -0.5671 25 | vn 0.0000 0.8237 -0.5671 26 | usemtl Material.002 27 | s off 28 | f 2//1 3//1 1//1 29 | f 4//2 10//2 3//2 30 | f 8//3 5//3 7//3 31 | f 6//4 1//4 5//4 32 | f 7//5 1//5 3//5 33 | f 4//6 6//6 8//6 34 | f 9//7 11//7 10//7 35 | f 7//8 12//8 8//8 36 | f 3//9 11//9 7//9 37 | f 8//10 9//10 4//10 38 | f 2//1 4//1 3//1 39 | f 4//2 9//2 10//2 40 | f 8//3 6//3 5//3 41 | f 6//4 2//4 1//4 42 | f 7//5 5//5 1//5 43 | f 4//6 2//6 6//6 44 | f 9//7 12//7 11//7 45 | f 7//8 11//8 12//8 46 | f 3//9 10//9 11//9 47 | f 8//10 12//10 9//10 48 | -------------------------------------------------------------------------------- /src/test/resources/SpaceshipGame/Terrain.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 2 3 | 4 | newmtl Material.001 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.002 15 | Ns 96.078431 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.640000 0.640000 0.640000 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | -------------------------------------------------------------------------------- /src/test/resources/TestOBJ.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 2 3 | 4 | newmtl Material 5 | Ns 17.647059 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.800000 0.800000 0.800000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.001 15 | Ns 96.078431 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.640000 0.640000 0.640000 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | -------------------------------------------------------------------------------- /src/test/resources/TestOBJ.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.79 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib test.mtl 4 | v -4.198291 2.563459 -1.020053 5 | v -4.198291 2.563459 0.979947 6 | v -6.198291 2.563459 0.979947 7 | v -6.198290 2.563459 -1.020054 8 | v -4.198290 4.563459 -1.020053 9 | v -4.198291 4.563459 0.979947 10 | v -6.198291 4.563459 0.979946 11 | v -6.198291 4.563459 -1.020053 12 | vt 1.000000 0.000000 13 | vt 0.000000 1.000000 14 | vt 0.000000 0.000000 15 | vt 1.000000 0.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 1.000000 0.000000 21 | vt 0.000000 1.000000 22 | vt 0.000000 0.000000 23 | vt 0.000000 0.000000 24 | vt 1.000000 1.000000 25 | vt 1.000000 0.000000 26 | vt 0.000000 1.000000 27 | vt 1.000000 1.000000 28 | vt 1.000000 1.000000 29 | vt 1.000000 1.000000 30 | vt 1.000000 0.000000 31 | vt 1.000000 1.000000 32 | vn 0.0000 -1.0000 0.0000 33 | vn 0.0000 1.0000 -0.0000 34 | vn 1.0000 0.0000 0.0000 35 | vn -0.0000 -0.0000 1.0000 36 | vn -1.0000 0.0000 -0.0000 37 | vn 0.0000 0.0000 -1.0000 38 | usemtl Material.001 39 | s off 40 | f 2/1/1 4/2/1 1/3/1 41 | f 8/4/2 6/5/2 5/6/2 42 | f 5/7/3 2/8/3 1/3/3 43 | f 6/9/4 3/10/4 2/11/4 44 | f 3/12/5 8/13/5 4/2/5 45 | f 1/14/6 8/15/6 5/6/6 46 | f 2/1/1 3/16/1 4/2/1 47 | f 8/4/2 7/17/2 6/5/2 48 | f 5/7/3 6/18/3 2/8/3 49 | f 6/9/4 7/17/4 3/10/4 50 | f 3/12/5 7/19/5 8/13/5 51 | f 1/14/6 4/20/6 8/15/6 52 | v 1.000000 -1.000000 -1.000000 53 | v 1.000000 -1.000000 1.000000 54 | v -1.000000 -1.000000 1.000000 55 | v -1.000000 -1.000000 -1.000000 56 | v 1.000000 1.000000 -0.999999 57 | v 0.999999 1.000000 1.000001 58 | v -1.000000 1.000000 1.000000 59 | v -1.000000 1.000000 -1.000000 60 | vt 1.000000 0.000000 61 | vt 0.000000 1.000000 62 | vt 0.000000 0.000000 63 | vt 1.000000 0.000000 64 | vt 0.000000 1.000000 65 | vt 0.000000 0.000000 66 | vt 1.000000 0.000000 67 | vt 0.000000 1.000000 68 | vt 1.000000 0.000000 69 | vt 0.000000 1.000000 70 | vt 0.000000 0.000000 71 | vt 0.000000 0.000000 72 | vt 1.000000 1.000000 73 | vt 1.000000 0.000000 74 | vt 0.000000 1.000000 75 | vt 1.000000 1.000000 76 | vt 1.000000 1.000000 77 | vt 1.000000 1.000000 78 | vt 1.000000 0.000000 79 | vt 1.000000 1.000000 80 | vn 0.0000 -1.0000 0.0000 81 | vn 0.0000 1.0000 0.0000 82 | vn 1.0000 -0.0000 0.0000 83 | vn 0.0000 -0.0000 1.0000 84 | vn -1.0000 -0.0000 -0.0000 85 | vn 0.0000 0.0000 -1.0000 86 | usemtl Material 87 | s off 88 | f 10/21/7 12/22/7 9/23/7 89 | f 16/24/8 14/25/8 13/26/8 90 | f 13/27/9 10/28/9 9/23/9 91 | f 14/29/10 11/30/10 10/31/10 92 | f 11/32/11 16/33/11 12/22/11 93 | f 9/34/12 16/35/12 13/26/12 94 | f 10/21/7 11/36/7 12/22/7 95 | f 16/24/8 15/37/8 14/25/8 96 | f 13/27/9 14/38/9 10/28/9 97 | f 14/29/10 15/37/10 11/30/10 98 | f 11/32/11 15/39/11 16/33/11 99 | f 9/34/12 12/40/12 16/35/12 100 | --------------------------------------------------------------------------------