From 292abf9a577b44ad876ea7c4ac2c430b82169150 Mon Sep 17 00:00:00 2001 From: "Florian Richer (MrDev023)" Date: Sun, 6 Mar 2016 13:45:34 +0100 Subject: [PATCH] First update --- Diffuse light/.classpath | 7 + Diffuse light/.project | 17 ++ Diffuse light/bin/.gitignore | 1 + Diffuse light/res/shaders/light.frag | 78 ++++++ Diffuse light/res/shaders/light.vert | 23 ++ Diffuse light/res/textures/wooden-crate.jpg | Bin 0 -> 49767 bytes Diffuse light/src/fr/technicalgames/Main.java | 103 ++++++++ .../src/fr/technicalgames/game/Game.java | 26 ++ .../src/fr/technicalgames/game/MainGame.java | 71 +++++ .../src/fr/technicalgames/input/IO.java | 17 ++ .../src/fr/technicalgames/input/Input.java | 245 ++++++++++++++++++ .../light/DirectionalLight.java | 18 ++ .../src/fr/technicalgames/light/Light.java | 74 ++++++ .../fr/technicalgames/light/SpotLight.java | 17 ++ .../material/DefaultMaterial.java | 11 + .../fr/technicalgames/material/Material.java | 31 +++ .../src/fr/technicalgames/math/Color4f.java | 110 ++++++++ .../src/fr/technicalgames/math/Mathf.java | 67 +++++ .../src/fr/technicalgames/math/Matrix4f.java | 189 ++++++++++++++ .../fr/technicalgames/math/Quaternion.java | 132 ++++++++++ .../src/fr/technicalgames/math/Vector2f.java | 42 +++ .../src/fr/technicalgames/math/Vector3f.java | 104 ++++++++ .../src/fr/technicalgames/math/Vector4f.java | 56 ++++ .../src/fr/technicalgames/render/Asset.java | 133 ++++++++++ .../src/fr/technicalgames/render/Camera.java | 67 +++++ .../technicalgames/render/DisplayManager.java | 55 ++++ .../src/fr/technicalgames/render/Shaders.java | 96 +++++++ .../src/fr/technicalgames/render/Texture.java | 96 +++++++ 28 files changed, 1886 insertions(+) create mode 100644 Diffuse light/.classpath create mode 100644 Diffuse light/.project create mode 100644 Diffuse light/bin/.gitignore create mode 100644 Diffuse light/res/shaders/light.frag create mode 100644 Diffuse light/res/shaders/light.vert create mode 100644 Diffuse light/res/textures/wooden-crate.jpg create mode 100644 Diffuse light/src/fr/technicalgames/Main.java create mode 100644 Diffuse light/src/fr/technicalgames/game/Game.java create mode 100644 Diffuse light/src/fr/technicalgames/game/MainGame.java create mode 100644 Diffuse light/src/fr/technicalgames/input/IO.java create mode 100644 Diffuse light/src/fr/technicalgames/input/Input.java create mode 100644 Diffuse light/src/fr/technicalgames/light/DirectionalLight.java create mode 100644 Diffuse light/src/fr/technicalgames/light/Light.java create mode 100644 Diffuse light/src/fr/technicalgames/light/SpotLight.java create mode 100644 Diffuse light/src/fr/technicalgames/material/DefaultMaterial.java create mode 100644 Diffuse light/src/fr/technicalgames/material/Material.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Color4f.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Mathf.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Matrix4f.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Quaternion.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Vector2f.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Vector3f.java create mode 100644 Diffuse light/src/fr/technicalgames/math/Vector4f.java create mode 100644 Diffuse light/src/fr/technicalgames/render/Asset.java create mode 100644 Diffuse light/src/fr/technicalgames/render/Camera.java create mode 100644 Diffuse light/src/fr/technicalgames/render/DisplayManager.java create mode 100644 Diffuse light/src/fr/technicalgames/render/Shaders.java create mode 100644 Diffuse light/src/fr/technicalgames/render/Texture.java diff --git a/Diffuse light/.classpath b/Diffuse light/.classpath new file mode 100644 index 0000000..bab40ed --- /dev/null +++ b/Diffuse light/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Diffuse light/.project b/Diffuse light/.project new file mode 100644 index 0000000..22ba6be --- /dev/null +++ b/Diffuse light/.project @@ -0,0 +1,17 @@ + + + Diffuse light + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/Diffuse light/bin/.gitignore b/Diffuse light/bin/.gitignore new file mode 100644 index 0000000..44fde90 --- /dev/null +++ b/Diffuse light/bin/.gitignore @@ -0,0 +1 @@ +/fr/ diff --git a/Diffuse light/res/shaders/light.frag b/Diffuse light/res/shaders/light.frag new file mode 100644 index 0000000..eec69e8 --- /dev/null +++ b/Diffuse light/res/shaders/light.frag @@ -0,0 +1,78 @@ +#version 150 +//precision highp float; +uniform mat4 transform; +uniform sampler2D materialTex; +uniform float materialShininess; +uniform vec3 materialSpecularColor; +uniform vec3 cameraPosition; + +#define MAX_LIGHTS 10 +uniform int numLights; +uniform struct Light { + vec4 position; + vec3 intensities; //a.k.a the color of the light + float attenuation; + float ambientCoefficient; + float coneAngle; + vec3 coneDirection; +} allLights[MAX_LIGHTS]; + +in vec2 fragTexCoord; +in vec3 fragNormal; +in vec3 fragVert; + +out vec4 finalColor; + +vec3 ApplyLight(Light light, vec3 surfaceColor, vec3 normal, vec3 surfacePos, vec3 surfaceToCamera) { + vec3 surfaceToLight; + float attenuation = 1.0; + if(light.position.w == 0.0) { + //directional light + surfaceToLight = normalize(light.position.xyz); + attenuation = 1.0; //no attenuation for directional lights + } else { + //point light + surfaceToLight = normalize(light.position.xyz - surfacePos); + float distanceToLight = length(light.position.xyz - surfacePos); + attenuation = 1.0 / (1.0 + light.attenuation * pow(distanceToLight, 2)); + + //cone restrictions (affects attenuation) + float lightToSurfaceAngle = degrees(acos(dot(-surfaceToLight, normalize(light.coneDirection)))); + if(lightToSurfaceAngle > light.coneAngle){ + attenuation = 0.0; + } + } + + //ambient + vec3 ambient = light.ambientCoefficient * surfaceColor.rgb * light.intensities; + + //diffuse + float diffuseCoefficient = max(0.0, dot(normal, surfaceToLight)); + vec3 diffuse = diffuseCoefficient * surfaceColor.rgb * light.intensities; + + //specular + float specularCoefficient = 0.0; + if(diffuseCoefficient > 0.0) + specularCoefficient = pow(max(0.0, dot(surfaceToCamera, reflect(-surfaceToLight, normal))), materialShininess); + vec3 specular = specularCoefficient * materialSpecularColor * light.intensities; + + //linear color (color before gamma correction) + return ambient + attenuation*(diffuse + specular); +} + +void main() { + vec3 normal = normalize(transpose(inverse(mat3(transform))) * fragNormal); + vec3 surfacePos = vec3(transform * vec4(fragVert, 1)); + vec4 surfaceColor = texture(materialTex, fragTexCoord); + vec3 surfaceToCamera = normalize(cameraPosition - surfacePos); + + //combine color from all the lights + vec3 linearColor = vec3(0); + for(int i = 0; i < numLights; ++i){ + linearColor += ApplyLight(allLights[i], surfaceColor.rgb, normal, surfacePos, surfaceToCamera); + } + + //final color (after gamma correction) + vec3 gamma = vec3(1.0/2.2); + finalColor = vec4(pow(linearColor, gamma), surfaceColor.a); +} \ No newline at end of file diff --git a/Diffuse light/res/shaders/light.vert b/Diffuse light/res/shaders/light.vert new file mode 100644 index 0000000..ee3c9f5 --- /dev/null +++ b/Diffuse light/res/shaders/light.vert @@ -0,0 +1,23 @@ +#version 150 + +uniform mat4 projection; +uniform mat4 camera; +uniform mat4 transform; + +in vec3 vert; +in vec2 vertTexCoord; +in vec3 vertNormal; + +out vec3 fragVert; +out vec2 fragTexCoord; +out vec3 fragNormal; + +void main() { + // Pass some variables to the fragment shader + fragTexCoord = vertTexCoord; + fragNormal = vertNormal; + fragVert = vert; + + // Apply all matrix transformations to vert + gl_Position = projection * camera * transform * vec4(vert, 1); +} \ No newline at end of file diff --git a/Diffuse light/res/textures/wooden-crate.jpg b/Diffuse light/res/textures/wooden-crate.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d1c87341ca55543742aeefa9145fea732c166468 GIT binary patch literal 49767 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 1000/60){//Update TICKS + glfwPollEvents(); + Input.update(); + game.update(); + previousTicks = System.currentTimeMillis(); + delta = (float)(System.currentTimeMillis() - previous)/1000.0f; + previous = System.currentTimeMillis(); + TICKS++; + }else{//Update FPS + DisplayManager.clear(); + DisplayManager.preRender3D(); + DisplayManager.render3D(); + DisplayManager.preRender2D(); + DisplayManager.render2D(); + DisplayManager.preRenderGUI(); + DisplayManager.renderGUI(); + glfwSwapBuffers(windowID); + FPS++; + } + + if(System.currentTimeMillis() - previousInfo >= 1000){ + glfwSetWindowTitle(windowID, TITLE + " | FPS:" + FPS + " TICKS:" + TICKS); + FPS = 0; + TICKS = 0; + previousInfo = System.currentTimeMillis(); + } + } + + glfwDestroyWindow(windowID); + glfwTerminate(); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/game/Game.java b/Diffuse light/src/fr/technicalgames/game/Game.java new file mode 100644 index 0000000..a366624 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/game/Game.java @@ -0,0 +1,26 @@ +package fr.technicalgames.game; + +import java.util.*; + +import fr.technicalgames.light.*; +import fr.technicalgames.render.*; + +public abstract class Game { + + public static ArrayList assets = new ArrayList(); + public static ArrayList lights = new ArrayList(); + + public Game(){ + init(); + System.out.println(this.getClass().getSimpleName() + " loaded with " + assets.size() + " assets and with " + lights.size() + " lights !"); + } + + public abstract void init(); + public abstract void update(); + public abstract void render2D(); + public abstract void render3D(); + public abstract void renderGUI(); + public abstract void destroy(); + + +} diff --git a/Diffuse light/src/fr/technicalgames/game/MainGame.java b/Diffuse light/src/fr/technicalgames/game/MainGame.java new file mode 100644 index 0000000..9ac5666 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/game/MainGame.java @@ -0,0 +1,71 @@ +package fr.technicalgames.game; + +import fr.technicalgames.*; +import fr.technicalgames.light.*; +import fr.technicalgames.math.*; +import fr.technicalgames.render.*; + +public class MainGame extends Game{ + + private float value = 0; + + @Override + public void init() { + + lights.add(new SpotLight(new Vector3f(-4,0,10),new Vector3f(2,2,2),0.1f,0.0f,15.0f,new Vector3f(0,0,-1))); + lights.add(new DirectionalLight(new Vector3f(4,0,-10), new Vector3f(0.4f,0.3f,0.1f), 0.06f)); + + + Asset as = new Asset(); + as.transform = (new Matrix4f()); + assets.add(as); + as = new Asset(); + as.transform = (new Matrix4f()).tranlate(0, -4, 0).scale(1, 2, 1); + assets.add(as); + as = new Asset(); + as.transform = (new Matrix4f()).tranlate(-8,0,0).scale(1, 6, 1); + assets.add(as); + as = new Asset(); + as.transform = (new Matrix4f()).tranlate(-4,0,0).scale(1, 6, 1); + assets.add(as); + as = new Asset(); + as.transform = (new Matrix4f()).tranlate(-6,0,0).scale(2,1,0.8f); + assets.add(as); + as = null; + } + + @Override + public void update() { + Camera.update(); + Camera.transform(); + + lights.get(0).position.y = Mathf.cos(value) * 6f; + lights.get(0).position.x = Mathf.sin(value) * 3f - 4f; + value += Main.delta * 1.0f; + } + + @Override + public void render2D() { + + } + + @Override + public void render3D() { + for(Asset a : assets){ + a.render(lights); + } + } + + @Override + public void renderGUI() { + + } + + @Override + public void destroy() { + for(Asset a : assets){ + a.destroy(); + } + } + +} diff --git a/Diffuse light/src/fr/technicalgames/input/IO.java b/Diffuse light/src/fr/technicalgames/input/IO.java new file mode 100644 index 0000000..eefd3c3 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/input/IO.java @@ -0,0 +1,17 @@ +package fr.technicalgames.input; +import java.io.*; + +public class IO { + + public static String loadFile(String path) throws Exception{ + String r = ""; + BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path))); + String buffer = ""; + while ((buffer = reader.readLine()) != null) { + r += buffer + "\n"; + } + reader.close(); + return r; + } + +} diff --git a/Diffuse light/src/fr/technicalgames/input/Input.java b/Diffuse light/src/fr/technicalgames/input/Input.java new file mode 100644 index 0000000..c1ee0fe --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/input/Input.java @@ -0,0 +1,245 @@ +package fr.technicalgames.input; + + +import static org.lwjgl.glfw.GLFW.*; + +import java.util.*; +import java.util.Map.*; + +import org.lwjgl.glfw.*; + +import fr.technicalgames.*; +import fr.technicalgames.math.*; + +public class Input{ + + public static GLFWScrollCallback scroll; + public static GLFWCursorPosCallback mousePos; + + private static Vector2f mousePosition = new Vector2f(); + private static Vector2f dMouse = new Vector2f(); + private static Vector2f previousDMouse = new Vector2f(); + + public static final int NONE = 0,PRESSED = 1,RELEASED = 2,REPEATED = 3,UP = 4,DOWN = 5, + NBRE_KEY = 0x15D,NBRE_BUTTON = 10, + MOUSE_OFFSET = NBRE_KEY + 1,MOUSE_WHEEL_OFFSET = MOUSE_OFFSET + 1; + + private static HashMap state = new HashMap(); + + private static double ywheel = 0; + + public static void init(){ + glfwSetScrollCallback(Main.windowID, scroll = new GLFWScrollCallback() { + public void invoke(long window, double xoffset, double yoffset) { + scroll(window, xoffset, yoffset); + } + }); + glfwSetCursorPosCallback(Main.windowID, mousePos = new GLFWCursorPosCallback() { + public void invoke(long window, double xpos, double ypos) { + mousepos(window, xpos, ypos); + } + }); + for(int i = 0;i < NBRE_KEY;i++){ + state.put(i, NONE); + } + for(int i = 0;i < NBRE_BUTTON;i++){ + state.put(i + MOUSE_OFFSET, NONE); + } + state.put(MOUSE_WHEEL_OFFSET, NONE); + } + + public static void update(){ + for(Entry set : state.entrySet()){ + int i = set.getKey(); + int st = set.getValue(); + if(i > -1 && i < NBRE_KEY){ + if(glfwGetKey(Main.windowID, i) == 0 && st == NONE)continue; + if(glfwGetKey(Main.windowID, i) == 1 && st == NONE){ + state.replace(i, PRESSED); + }else if(glfwGetKey(Main.windowID, i) == 1 && st == PRESSED){ + state.replace(i, REPEATED); + }else if(glfwGetKey(Main.windowID, i) == 0 && (st == PRESSED || st == REPEATED)){ + state.replace(i, RELEASED); + }else if(glfwGetKey(Main.windowID, i) == 0 && st == RELEASED){ + state.replace(i, NONE); + } + }else if(i >= MOUSE_OFFSET && i < MOUSE_OFFSET + NBRE_BUTTON){ + if(glfwGetMouseButton(Main.windowID, i - MOUSE_OFFSET) == 0 && st == NONE)continue; + if(glfwGetMouseButton(Main.windowID, i - MOUSE_OFFSET) == 1 && st == NONE){ + state.replace(i, PRESSED); + }else if(glfwGetMouseButton(Main.windowID, i - MOUSE_OFFSET) == 1 && st == PRESSED){ + state.replace(i, REPEATED); + }else if(glfwGetMouseButton(Main.windowID, i - MOUSE_OFFSET) == 0 && (st == PRESSED || st == REPEATED)){ + state.replace(i, RELEASED); + }else if(glfwGetMouseButton(Main.windowID, i - MOUSE_OFFSET) == 0 && st == RELEASED){ + state.replace(i, NONE); + } + } + } + int st = state.get(MOUSE_WHEEL_OFFSET); + if(ywheel > 0 && (st == NONE || st == UP)){ + state.replace(MOUSE_WHEEL_OFFSET, UP); + }else if(ywheel < 0 && (st == NONE || st == DOWN)){ + state.replace(MOUSE_WHEEL_OFFSET, DOWN); + }else if(ywheel == 0 && (st == DOWN || st == UP)){ + state.replace(MOUSE_WHEEL_OFFSET, NONE); + } + ywheel = 0; + if(dMouse.equals(previousDMouse)){ + dMouse = new Vector2f(); + }else{ + previousDMouse = dMouse; + } + } + + public static void destroy(){ + mousePos.release(); + scroll.release(); + } + + public static void scroll(long window, double xoffset, double yoffset) { + ywheel = yoffset; + } + + public static void mousepos(long window, double xpos, double ypos) { + dMouse.x = (float) (xpos - mousePosition.x); + dMouse.y = (float) (ypos - mousePosition.y); + mousePosition.x = (float) xpos; + mousePosition.y = (float) ypos; + } + + public static boolean isButtonDown(int button){ + return state.get(button + MOUSE_OFFSET) == PRESSED; + } + + public static boolean isButtonUp(int button){ + return state.get(button + MOUSE_OFFSET) == RELEASED; + } + + public static boolean isButton(int button){ + return state.get(button + MOUSE_OFFSET) == PRESSED || state.get(button + MOUSE_OFFSET) == REPEATED; + } + + public static int isButtonState(int button){ + return state.get(button + MOUSE_OFFSET); + } + + public static boolean isKeyDown(int key){ + return state.get(key) == PRESSED; + } + + public static boolean isKeyUp(int key){ + return state.get(key) == RELEASED; + } + + public static boolean isKey(int key){ + return state.get(key) == PRESSED || state.get(key) == REPEATED; + } + + public static int isKeyState(int key){ + return state.get(key); + } + + public static int isMouseWheelState(){ + return state.get(MOUSE_WHEEL_OFFSET); + } + + public static boolean isMouseWheelUp(){ + return state.get(MOUSE_WHEEL_OFFSET) == UP; + } + + public static boolean isMouseWheelDown(){ + return state.get(MOUSE_WHEEL_OFFSET) == DOWN; + } + + public static GLFWScrollCallback getScroll() { + return scroll; + } + + public static void setScroll(GLFWScrollCallback scroll) { + Input.scroll = scroll; + } + + public static GLFWCursorPosCallback getMousePos() { + return mousePos; + } + + public static void setMousePos(GLFWCursorPosCallback mousePos) { + Input.mousePos = mousePos; + } + + public static Vector2f getMousePosition() { + return mousePosition; + } + + public static void setMousePosition(Vector2f mousePosition) { + Input.mousePosition = mousePosition; + } + + public static Vector2f getDMouse() { + return dMouse; + } + + public static void setDMouse(Vector2f dMouse) { + Input.dMouse = dMouse; + } + + public static HashMap getState() { + return state; + } + + public static void setState(HashMap state) { + Input.state = state; + } + + public static double getYwheel() { + return ywheel; + } + + public static void setYwheel(double ywheel) { + Input.ywheel = ywheel; + } + + public static int getNone() { + return NONE; + } + + public static int getPressed() { + return PRESSED; + } + + public static int getReleased() { + return RELEASED; + } + + public static int getRepeated() { + return REPEATED; + } + + public static int getUp() { + return UP; + } + + public static int getDown() { + return DOWN; + } + + public static int getNbreKey() { + return NBRE_KEY; + } + + public static int getNbreButton() { + return NBRE_BUTTON; + } + + public static int getMouseOffset() { + return MOUSE_OFFSET; + } + + public static int getMouseWheelOffset() { + return MOUSE_WHEEL_OFFSET; + } + + + +} diff --git a/Diffuse light/src/fr/technicalgames/light/DirectionalLight.java b/Diffuse light/src/fr/technicalgames/light/DirectionalLight.java new file mode 100644 index 0000000..2693dd3 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/light/DirectionalLight.java @@ -0,0 +1,18 @@ +package fr.technicalgames.light; + +import fr.technicalgames.math.*; + +public class DirectionalLight extends Light{ + + public DirectionalLight(Vector3f position, Vector3f intensities,float ambientCoefficient) { + super(new Vector4f(position,0), intensities, 1.0f, ambientCoefficient, 0.0f, new Vector3f()); + } + + @Override + public void update() { + + } + + + +} diff --git a/Diffuse light/src/fr/technicalgames/light/Light.java b/Diffuse light/src/fr/technicalgames/light/Light.java new file mode 100644 index 0000000..8e2449f --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/light/Light.java @@ -0,0 +1,74 @@ +package fr.technicalgames.light; +import fr.technicalgames.math.*; + +public abstract class Light { + + public Vector4f position;//w == 0 si c une directional light + public Vector3f intensities; + public float attenuation; + public float ambientCoefficient; + public float coneAngle; + public Vector3f coneDirection; + + public Light(Vector4f position,Vector3f intensities,float attenuation,float ambientCoefficient,float coneAngle,Vector3f coneDirection){ + this.position = position; + this.intensities = intensities; + this.attenuation = attenuation; + this.ambientCoefficient = ambientCoefficient; + this.coneAngle = coneAngle; + this.coneDirection = coneDirection; + } + + public abstract void update(); + + public Vector4f getPosition() { + return position; + } + + public void setPosition(Vector4f position) { + this.position = position; + } + + public Vector3f getIntensities() { + return intensities; + } + + public void setIntensities(Vector3f intensities) { + this.intensities = intensities; + } + + public float getAttenuation() { + return attenuation; + } + + public void setAttenuation(float attenuation) { + this.attenuation = attenuation; + } + + public float getAmbientCoefficient() { + return ambientCoefficient; + } + + public void setAmbientCoefficient(float ambientCoefficient) { + this.ambientCoefficient = ambientCoefficient; + } + + public float getConeAngle() { + return coneAngle; + } + + public void setConeAngle(float coneAngle) { + this.coneAngle = coneAngle; + } + + public Vector3f getConeDirection() { + return coneDirection; + } + + public void setConeDirection(Vector3f coneDirection) { + this.coneDirection = coneDirection; + } + + + +} diff --git a/Diffuse light/src/fr/technicalgames/light/SpotLight.java b/Diffuse light/src/fr/technicalgames/light/SpotLight.java new file mode 100644 index 0000000..5dba452 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/light/SpotLight.java @@ -0,0 +1,17 @@ +package fr.technicalgames.light; + +import fr.technicalgames.math.*; + +public class SpotLight extends Light{ + + public SpotLight(Vector3f position, Vector3f intensities, float attenuation, float ambientCoefficient, + float coneAngle, Vector3f coneDirection) { + super(new Vector4f(position,1), intensities, attenuation, ambientCoefficient, coneAngle, coneDirection); + } + + @Override + public void update() { + + } + +} diff --git a/Diffuse light/src/fr/technicalgames/material/DefaultMaterial.java b/Diffuse light/src/fr/technicalgames/material/DefaultMaterial.java new file mode 100644 index 0000000..9e6423f --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/material/DefaultMaterial.java @@ -0,0 +1,11 @@ +package fr.technicalgames.material; + +import fr.technicalgames.math.*; + +public class DefaultMaterial extends Material{ + + public DefaultMaterial() { + super(80.0f, new Vector3f(1,1,1)); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/material/Material.java b/Diffuse light/src/fr/technicalgames/material/Material.java new file mode 100644 index 0000000..060a32d --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/material/Material.java @@ -0,0 +1,31 @@ +package fr.technicalgames.material; + +import fr.technicalgames.math.*; + +public abstract class Material { + + public float shininess; + public Vector3f specularColor; + + public Material(float shininess,Vector3f specularColor){ + this.shininess = shininess; + this.specularColor = specularColor; + } + + public float getShininess() { + return shininess; + } + + public void setShininess(float shininess) { + this.shininess = shininess; + } + + public Vector3f getSpecularColor() { + return specularColor; + } + + public void setSpecularColor(Vector3f specularColor) { + this.specularColor = specularColor; + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Color4f.java b/Diffuse light/src/fr/technicalgames/math/Color4f.java new file mode 100644 index 0000000..aefea0d --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Color4f.java @@ -0,0 +1,110 @@ +package fr.technicalgames.math; + + +import static org.lwjgl.opengl.GL11.*; + +public class Color4f { + + public static final Color4f + RED = new Color4f(1,0,0,1), + BLUE = new Color4f(0,0,1,1), + GREEN = new Color4f(0,1,0,1), + YELLOW = new Color4f(1,1,0,1), + PURPLE = new Color4f(1,0,1,1), + CYAN = new Color4f(0,1,1,1), + BLACK = new Color4f(0,0,0,1), + WHITE = new Color4f(1,1,1,1); + + public float r,g,b,a; + + public Color4f(float r,float g,float b,float a){ + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public static Color4f mul (Color4f a, float b){ + return new Color4f(a.r * b,a.g * b,a.b * b,a.a * b); + } + + public static Color4f mul (float o,Color4f... a){ + float r = 0; + float b = 0; + float g = 0; + float al = 0; + for(Color4f c : a){ + r += c.r; + g += c.g; + b += c.b; + al += c.a; + } + r /= a.length; + g /= a.length; + b /= a.length; + al /= a.length; + return new Color4f(r * o,g * o,b * o,al * o); + } + + public static Color4f mul (Color4f... a){ + float r = 0; + float b = 0; + float g = 0; + float al = 0; + for(Color4f c : a){ + r += c.r; + g += c.g; + b += c.b; + al += c.a; + } + r /= a.length; + g /= a.length; + b /= a.length; + al /= a.length; + return new Color4f(r,g,b,al); + } + + public Color4f() { + } + + public float getR() { + return r; + } + + public void setR(float r) { + this.r = r; + } + + public float getG() { + return g; + } + + public void setG(float g) { + this.g = g; + } + + public float getB() { + return b; + } + + public void setB(float b) { + this.b = b; + } + + public float getA() { + return a; + } + + public void setA(float a) { + this.a = a; + } + + public void bind(){ + glColor4f(r,g,b,a); + } + + public void unbind(){ + BLACK.bind(); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Mathf.java b/Diffuse light/src/fr/technicalgames/math/Mathf.java new file mode 100644 index 0000000..608de0a --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Mathf.java @@ -0,0 +1,67 @@ +package fr.technicalgames.math; + + +public class Mathf { + + public static final float PI = 3.14159265358979323846f; + public static final float EPSILON = 1.401298e-45f; + + public static float cos(float angle){ + return (float)Math.cos(angle); + } + + public static float acos(float angle){ + return (float)Math.acos(angle); + } + + public static float sin(float angle){ + return (float)Math.sin(angle); + } + + public static float asin(float angle){ + return (float)Math.asin(angle); + } + + public static float toRadians(float angle){ + return (float)Math.toRadians(angle); + } + + public static float toDegrees(float angle){ + return (float)Math.toDegrees(angle); + } + + public static float atan2(float a,float b){ + return (float)Math.atan2(a,b); + } + + public static float cut(float nbre,float a){ + return (float)((int)(nbre*Math.pow(10, a))/Math.pow(10, a)); + } + + public static boolean equals(float a,float b,float tolerance){ + return (a + tolerance >= b) && (a - tolerance <= b); + } + + public static float sqrt(float a){ + return (float)Math.sqrt(a); + } + + public static float exp(float a){ + return (float)Math.sqrt(a); + } + + public static float log(float a){ + return (float)Math.log(a); + } + + public static float clamp(float value, float min, float max) { + if(value < min){ + value = min; + } + if(value > max){ + value = max; + } + return value; + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Matrix4f.java b/Diffuse light/src/fr/technicalgames/math/Matrix4f.java new file mode 100644 index 0000000..1ca1e24 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Matrix4f.java @@ -0,0 +1,189 @@ +package fr.technicalgames.math; + + +import java.nio.*; +import java.util.*; + +import org.lwjgl.*; + +public class Matrix4f { + + public float[][] m = null; + + public Matrix4f(){ + m = new float[][]{ + {1,0,0,0}, + {0,1,0,0}, + {0,0,1,0}, + {0,0,0,1} + }; + } + + public Matrix4f(float[][] m){ + this.m = m; + } + + public Matrix4f loadIdentity(){ + m = new float[][]{ + {1,0,0,0}, + {0,1,0,0}, + {0,0,1,0}, + {0,0,0,1} + }; + return this; + } + + public Matrix4f rotate(Quaternion q){ + Matrix4f rot = q.toMatrixRotation(); + m = mul(rot).getM(); + return this; + } + + + public void rotate(float x,float y,float z){ + x = Mathf.toRadians(x); + y = Mathf.toRadians(y); + z = Mathf.toRadians(z); + Matrix4f rx = new Matrix4f(new float[][]{ + {1,0,0,0}, + {0,Mathf.cos(x),-Mathf.sin(x),0}, + {0,Mathf.sin(x),Mathf.cos(x),0}, + {0,0,0,1} + }); + + Matrix4f ry = new Matrix4f(new float[][]{ + {Mathf.cos(y),0,Mathf.sin(y),0}, + {0,1,0,0}, + {-Mathf.sin(y),0,Mathf.cos(y),0}, + {0,0,0,1} + }); + + Matrix4f rz = new Matrix4f(new float[][]{ + {Mathf.cos(z),-Mathf.sin(z),0,0}, + {Mathf.sin(z),Mathf.cos(z),0,0}, + {0,0,1,0}, + {0,0,0,1} + }); + Matrix4f m1 = (rz.mul(ry.mul(rx))); + m = mul(m1).getM(); + } + + public static Matrix4f rotate(Vector3f forward, Vector3f up, Vector3f right) + { + Matrix4f mat = new Matrix4f(new float[][]{ + {right.getX(), right.getY(), right.getZ() ,0}, + {up.getX(), up.getY(), up.getZ() ,0}, + {forward.getX(),forward.getY(), forward.getZ() ,0}, + {0,0,0,1} + }); + return mat; + } + + public Matrix4f tranlate(float x,float y,float z){ + Matrix4f mat = new Matrix4f(new float[][]{ + {1,0,0,x}, + {0,1,0,y}, + {0,0,1,z}, + {0,0,0,1} + }); + m = mul(mat).getM(); + return this; + } + + public Matrix4f scale(float x,float y,float z){ + Matrix4f mat = new Matrix4f(new float[][]{ + {x,0,0,0}, + {0,y,0,0}, + {0,0,z,0}, + {0,0,0,1} + }); + m = mul(mat).getM(); + return this; + } + + public Matrix4f mul(Matrix4f mat){ + Matrix4f ma = new Matrix4f(); + for(int i = 0;i < 4;i++){ + for(int j = 0;j < 4;j++){ + ma.m[i][j] = m[i][0] * mat.m[0][j] + + m[i][1] * mat.m[1][j] + + m[i][2] * mat.m[2][j] + + m[i][3] * mat.m[3][j]; + } + } + return ma; + } + + public Matrix4f Ortho2D(float left, float right, float bottom, float top, float near, float far) + { + float width = right - left; + float height = top - bottom; + float depth = far - near; + + m = new float[][]{ + {2/width,0,0,-(right + left)/width}, + {0,2/height,0,-(top + bottom)/height}, + {0,0,-2/depth,-(far + near)/depth}, + {0,0,0,1} + }; + + return this; + } + + public Matrix4f perspective(float fov, float aspectRatio, float zNear, float zFar) + { + float f = fov; + fov = Mathf.toRadians(f); + float tanHalfFOV = (float)Math.tan(fov / 2); + float zRange = zNear - zFar; + + m = new float[][]{ + {1.0f / (tanHalfFOV * aspectRatio),0,0,0}, + {0,1.0f / tanHalfFOV,0,0}, + {0,0,(-zNear -zFar)/zRange,2.0f * zFar * zNear / zRange}, + {0,0,1,0} + }; + + return this; + } + + public FloatBuffer getBuffer(){ + FloatBuffer buffer = BufferUtils.createFloatBuffer(4 * 4); + for(int i = 0;i < 4;i++){ + buffer.put(m[i]); + } + buffer.flip(); + return buffer; + } + + public String toString(){ + int size = 3; + int max = 10; + StringJoiner st = new StringJoiner("\n","--------Mat4-Begin--------\n","\n--------Mat4-End----------"); + for(int i = 0;i < 4;i++){ + StringJoiner st2 = new StringJoiner(" | "); + for(int j = 0;j < 4;j++){ + String value = Mathf.cut(m[i][j], size) + ""; + for(int k = value.length();k < max;k++){ + value += " "; + } + st2.add(value); + } + st.add(st2.toString()); + } + return st.toString(); + } + + public float[][] getM() { + return m; + } + + public void setM(float[][] m) { + this.m = m; + } + + public Matrix4f copy(){ + return new Matrix4f(this.getM()); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Quaternion.java b/Diffuse light/src/fr/technicalgames/math/Quaternion.java new file mode 100644 index 0000000..4304585 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Quaternion.java @@ -0,0 +1,132 @@ +package fr.technicalgames.math; + + +public class Quaternion { + + public float x,y,z,w; + + public Quaternion(){ + x = 0; + y = 0; + z = 0; + w = 0; + } + + public Quaternion(Vector3f axis,float angle){ + float sin = Mathf.sin(Mathf.toRadians(angle/2.0f)); + float cos = Mathf.cos(Mathf.toRadians(angle/2.0f)); + x = axis.getX() * sin; + y = axis.getY() * sin; + z = axis.getZ() * sin; + w = cos; + } + + public Quaternion(Vector3f rot){ + this(rot.x,rot.y,rot.z); + } + + public Quaternion (float yaw, float roll, float pitch) { + yaw = Mathf.toRadians(yaw); + roll = Mathf.toRadians(roll); + pitch = Mathf.toRadians(pitch); + float angle; + float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw; + angle = pitch * 0.5f; + sinPitch = Mathf.sin(angle); + cosPitch = Mathf.cos(angle); + angle = roll * 0.5f; + sinRoll = Mathf.sin(angle); + cosRoll = Mathf.cos(angle); + angle = yaw * 0.5f; + sinYaw = Mathf.sin(angle); + cosYaw = Mathf.cos(angle); + + // variables used to reduce multiplication calls. + float cosRollXcosPitch = cosRoll * cosPitch; + float sinRollXsinPitch = sinRoll * sinPitch; + float cosRollXsinPitch = cosRoll * sinPitch; + float sinRollXcosPitch = sinRoll * cosPitch; + + w = (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw); + x = (cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw); + y = (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw); + z = (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw); + + normalize(); + } + + public void normalize(){ + float n = (float)(1.0/Math.sqrt(norm())); + x *= n; + y *= n; + z *= n; + w *= n; + } + + public float norm(){ + return w * w + x * x + y * y + z * z; + } + + public Quaternion Euler(Vector3f rot) { + x = Mathf.toRadians(rot.x); + y = Mathf.toRadians(rot.y); + z = Mathf.toRadians(rot.z); + float c1 = Mathf.cos(y/2); + float s1 = Mathf.sin(y/2); + float c2 = Mathf.cos(z/2); + float s2 = Mathf.sin(z/2); + float c3 = Mathf.cos(x/2); + float s3 = Mathf.sin(x/2); + float c1c2 = c1*c2; + float s1s2 = s1*s2; + this.w =c1c2*c3 - s1s2*s3; + this.x =c1c2*s3 + s1s2*c3; + this.y =s1*c2*c3 + c1*s2*s3; + this.z =c1*s2*c3 - s1*c2*s3; + return new Quaternion(x, y, z, w); + } + + public Vector3f toEulerAngles(){ + Vector3f euler = new Vector3f(); + + float sqw = w * w; + float sqx = x * x; + float sqy = y * y; + float sqz = z * z; + float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise + // is correction factor + float test = x * y + z * w; + if (test > 0.499 * unit) { // singularity at north pole + euler.y = 2 * Mathf.atan2(x, w); + euler.z = Mathf.PI/2.0f; + euler.x = 0; + } else if (test < -0.499 * unit) { // singularity at south pole + euler.y = -2 * Mathf.atan2(x, w); + euler.z = -Mathf.PI/2.0f; + euler.x = 0; + } else { + euler.y = Mathf.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // roll or heading + euler.z = Mathf.asin(2 * test / unit); // pitch or attitude + euler.x = Mathf.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // yaw or bank + } + return euler.toDegrees(); + } + + public Quaternion(float axisX,float axisY,float axisZ,float angle){ + float sin = Mathf.sin(Mathf.toRadians(angle/2.0f)); + float cos = Mathf.cos(Mathf.toRadians(angle/2.0f)); + x = axisX * sin; + y = axisY * sin; + z = axisZ * sin; + w = cos; + } + + public Matrix4f toMatrixRotation(){ + Vector3f forward = new Vector3f(2.0f * (x * z - w * y), 2.0f * (y * z + w * x), 1.0f - 2.0f * (x * x + y * y)); + Vector3f up = new Vector3f(2.0f * (x * y + w * z), 1.0f - 2.0f * (x * x + z * z), 2.0f * (y * z - w * x)); + Vector3f right = new Vector3f(1.0f - 2.0f * (y * y + z * z), 2.0f * (x * y - w * z), 2.0f * (x * z + w * y)); + + return Matrix4f.rotate(forward, up, right); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Vector2f.java b/Diffuse light/src/fr/technicalgames/math/Vector2f.java new file mode 100644 index 0000000..0543543 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Vector2f.java @@ -0,0 +1,42 @@ +package fr.technicalgames.math; + + +import java.util.*; + +public class Vector2f { + + public float x,y; + + public Vector2f(){ + x = 0; + y = 0; + } + + public Vector2f(float x,float y){ + this.x = x; + this.y = y; + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public String toString(){ + StringJoiner st = new StringJoiner(",","vec2(",")"); + st.add("" + x); + st.add("" + y); + return st.toString(); + } +} diff --git a/Diffuse light/src/fr/technicalgames/math/Vector3f.java b/Diffuse light/src/fr/technicalgames/math/Vector3f.java new file mode 100644 index 0000000..5c78928 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Vector3f.java @@ -0,0 +1,104 @@ +package fr.technicalgames.math; + + +import java.util.*; + +public class Vector3f { + + public float x,y,z; + + public Vector3f(){ + x = 0; + y = 0; + z = 0; + } + + public Vector3f(float x,float y,float z){ + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3f(Vector2f vec,float z){ + this(vec.x,vec.y,z); + } + + public Vector3f(Vector3f vec){ + this(vec.x,vec.y,vec.z); + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + public float length(){ + return Mathf.sqrt(x * x + y * y + z * z); + } + + public Vector3f lookAt(Vector3f d){ + Vector3f rot = new Vector3f(); + float x1 = d.x - x; + float y1 = d.y - y; + float z1 = d.z - z; + + return rot; + } + + public Vector3f normalize(){ + float length = length(); + x /= length; + y /= length; + z /= length; + return this; + } + + public Vector3f mul(float m){ + x *= m; + y *= m; + z *= m; + return this; + } + + public String toString(){ + StringJoiner st = new StringJoiner(",","vec3(",")"); + st.add("" + x); + st.add("" + y); + st.add("" + z); + return st.toString(); + } + + public Vector3f toRadians() { + x = Mathf.toRadians(x); + y = Mathf.toRadians(y); + z = Mathf.toRadians(z); + return this; + } + + public Vector3f toDegrees() { + x = Mathf.toDegrees(x); + y = Mathf.toDegrees(y); + z = Mathf.toDegrees(z); + return this; + } + +} diff --git a/Diffuse light/src/fr/technicalgames/math/Vector4f.java b/Diffuse light/src/fr/technicalgames/math/Vector4f.java new file mode 100644 index 0000000..bb869c2 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/math/Vector4f.java @@ -0,0 +1,56 @@ +package fr.technicalgames.math; + +public class Vector4f { + + public float x,y,z,w; + + public Vector4f(float x,float y,float z,float w){ + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public Vector4f(Vector3f v,float w){ + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = w; + } + + public float getX() { + return x; + } + + public void setX(float x) { + this.x = x; + } + + public float getY() { + return y; + } + + public void setY(float y) { + this.y = y; + } + + public float getZ() { + return z; + } + + public void setZ(float z) { + this.z = z; + } + + public float getW() { + return w; + } + + public void setW(float w) { + this.w = w; + } + + + + +} diff --git a/Diffuse light/src/fr/technicalgames/render/Asset.java b/Diffuse light/src/fr/technicalgames/render/Asset.java new file mode 100644 index 0000000..64bff13 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/render/Asset.java @@ -0,0 +1,133 @@ +package fr.technicalgames.render; +import java.nio.*; +import java.util.*; + +import org.lwjgl.*; +import org.lwjgl.opengl.*; + +import fr.technicalgames.light.*; +import fr.technicalgames.material.*; +import fr.technicalgames.math.*; + +public class Asset { + + public int vbo,vao; + public Texture texture; + public Matrix4f transform; + public Material material; + private int size; + + public Asset(){ + texture = Texture.WOOD; + transform = new Matrix4f(); + material = new DefaultMaterial(); + vao = GL30.glGenVertexArrays(); + vbo = GL15.glGenBuffers(); + float[] a = new float[]{ + // X Y Z U V Normal + // bottom + -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, + 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, + -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, + 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, + 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f, + -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, + + // top + -1.0f, 1.0f,-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, + + // front + -1.0f,-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + + // back + -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, + -1.0f, 1.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, + 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, + 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f, + -1.0f, 1.0f,-1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, + 1.0f, 1.0f,-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f, + + // left + -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f,-1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f,-1.0f,-1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f,-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f,-1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, + + // right + 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 1.0f,-1.0f,-1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f,-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f,-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f + }; + FloatBuffer buffer = BufferUtils.createFloatBuffer(a.length); + buffer.put(a).flip(); + size = a.length/(3+2+3); + GL30.glBindVertexArray(vao); + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); + GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW); + + GL20.glEnableVertexAttribArray(Shaders.MAIN_LIGHT.getAttribLocation("vert")); + GL20.glVertexAttribPointer(Shaders.MAIN_LIGHT.getAttribLocation("vert"), 3, GL11.GL_FLOAT, false, 8*4, 0); + + GL20.glEnableVertexAttribArray(Shaders.MAIN_LIGHT.getAttribLocation("vertTexCoord")); + GL20.glVertexAttribPointer(Shaders.MAIN_LIGHT.getAttribLocation("vertTexCoord"), 2, GL11.GL_FLOAT, true, 8*4, 3*4); + + GL20.glEnableVertexAttribArray(Shaders.MAIN_LIGHT.getAttribLocation("vertNormal")); + GL20.glVertexAttribPointer(Shaders.MAIN_LIGHT.getAttribLocation("vertNormal"), 3, GL11.GL_FLOAT, true, 8*4, 5*4); + + GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + GL30.glBindVertexArray(0); + } + + public void render(ArrayList lights){ + Shaders.MAIN_LIGHT.bind(); + Shaders.MAIN_LIGHT.uniform("camera", Camera.matrix); + Shaders.MAIN_LIGHT.uniform("transform", transform); + Shaders.MAIN_LIGHT.uniform("projection", DisplayManager.projection); + Shaders.MAIN_LIGHT.uniform("materialTex", 0); //set to 0 because the texture will be bound to GL_TEXTURE0 + Shaders.MAIN_LIGHT.uniform("materialShininess", material.shininess); + Shaders.MAIN_LIGHT.uniform("materialSpecularColor", material.specularColor); + Shaders.MAIN_LIGHT.uniform("numLights", lights.size()); + Shaders.MAIN_LIGHT.uniform("cameraPosition", Camera.pos); + + for(int i = 0;i < lights.size();i++){ + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].position", lights.get(i).position); + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].intensities", lights.get(i).intensities); + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].attenuation", lights.get(i).attenuation); + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].ambientCoefficient", lights.get(i).ambientCoefficient); + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].coneAngle", lights.get(i).coneAngle); + Shaders.MAIN_LIGHT.uniform("allLights["+i+"].coneDirection", lights.get(i).coneDirection); + } + + GL13.glActiveTexture(GL13.GL_TEXTURE0); + texture.bind(); + + GL30.glBindVertexArray(vao); + GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, size); + GL30.glBindVertexArray(0); + texture.unbind(); + Shaders.MAIN_LIGHT.unbind(); + } + + public void destroy(){ + GL15.glDeleteBuffers(vbo); + GL30.glBindVertexArray(vao); + texture.destroy(); + transform = null; + } + +} diff --git a/Diffuse light/src/fr/technicalgames/render/Camera.java b/Diffuse light/src/fr/technicalgames/render/Camera.java new file mode 100644 index 0000000..1306752 --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/render/Camera.java @@ -0,0 +1,67 @@ +package fr.technicalgames.render; +import static org.lwjgl.glfw.GLFW.*; + +import org.lwjgl.glfw.*; +import org.lwjgl.opengl.*; + +import fr.technicalgames.*; +import fr.technicalgames.input.*; +import fr.technicalgames.math.*; + + + +public class Camera { + + public static Matrix4f matrix = new Matrix4f(); + public static final float SPEED = 1.0f; + public static final float sens = 0.5f; + public static float speed = 1.0f; + + + + public static Vector3f rot = new Vector3f(); + public static Vector3f pos = new Vector3f(); + + public static void update(){ + speed = SPEED * Main.delta; + if(Input.isKey(GLFW_KEY_LEFT_CONTROL))speed *= 2.0f; + rot.x += -Input.getDMouse().getY() * sens; + rot.y += -Input.getDMouse().getX() * sens; + if(rot.x > 90)rot.x = 90; + if(rot.x < -90)rot.x = -90; + if(Input.isKey(GLFW.GLFW_KEY_W)){ + pos.x += Mathf.cos(Mathf.toRadians(rot.y + 90)) * speed; + pos.z += Mathf.sin(Mathf.toRadians(rot.y + 90)) * speed; + } + if(Input.isKey(GLFW.GLFW_KEY_S)){ + pos.x += -Mathf.cos(Mathf.toRadians(rot.y + 90)) * speed; + pos.z += -Mathf.sin(Mathf.toRadians(rot.y + 90)) * speed; + } + if(Input.isKey(GLFW.GLFW_KEY_A)){ + pos.x += -Mathf.cos(Mathf.toRadians(rot.y)) * speed; + pos.z += -Mathf.sin(Mathf.toRadians(rot.y)) * speed; + } + if(Input.isKey(GLFW.GLFW_KEY_D)){ + pos.x += Mathf.cos(Mathf.toRadians(rot.y)) * speed; + pos.z += Mathf.sin(Mathf.toRadians(rot.y)) * speed; + } + if(Input.isKey(GLFW.GLFW_KEY_LEFT_SHIFT)){ + pos.y -= speed; + } + if(Input.isKey(GLFW.GLFW_KEY_SPACE)){ + pos.y += speed; + } + if(Input.isKeyDown(GLFW_KEY_ESCAPE))glfwSetWindowShouldClose(Main.windowID, GL11.GL_TRUE); + if(Input.isButtonDown(0))glfwSetInputMode(Main.windowID, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + if(Input.isButtonDown(1))glfwSetInputMode(Main.windowID, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + } + + public static void transform(){ + matrix.loadIdentity(); + matrix.rotate(new Quaternion(new Vector3f(1,0,0),rot.x)); + matrix.rotate(new Quaternion(new Vector3f(0,1,0),rot.y)); + matrix.rotate(new Quaternion(new Vector3f(0,0,1),rot.z)); + matrix.tranlate(-pos.x, -pos.y, -pos.z); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/render/DisplayManager.java b/Diffuse light/src/fr/technicalgames/render/DisplayManager.java new file mode 100644 index 0000000..1ce832b --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/render/DisplayManager.java @@ -0,0 +1,55 @@ +package fr.technicalgames.render; + +import static org.lwjgl.opengl.GL11.*; + +import fr.technicalgames.*; +import fr.technicalgames.math.*; + +public class DisplayManager { + + public static Matrix4f projection = new Matrix4f(); + + public static void clear(){ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + public static void preRender2D(){ + projection.loadIdentity(); + projection.Ortho2D(0, Main.WIDTH, 0, Main.HEIGHT, -1, 1); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + public static void preRender3D(){ + projection.loadIdentity(); + projection.perspective(50.0f, (float)Main.WIDTH/(float)Main.HEIGHT, 0.1f,100.0f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + public static void preRenderGUI(){ + projection.loadIdentity(); + projection.Ortho2D(0, Main.WIDTH, 0, Main.HEIGHT, -1, 1); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + public static void render2D(){ + Main.game.render2D(); + } + + public static void render3D(){ + Main.game.render3D(); + } + + public static void renderGUI(){ + Main.game.renderGUI(); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/render/Shaders.java b/Diffuse light/src/fr/technicalgames/render/Shaders.java new file mode 100644 index 0000000..93388fb --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/render/Shaders.java @@ -0,0 +1,96 @@ +package fr.technicalgames.render; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import fr.technicalgames.input.*; +import fr.technicalgames.math.*; + +public class Shaders { + + public int program; + + public static Shaders MAIN_LIGHT; + + static{ + try { + MAIN_LIGHT = new Shaders("res/shaders/light.vert","res/shaders/light.frag"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public Shaders(String vertexFile,String fragmentFile) throws Exception{ + String fragmentShader = IO.loadFile(fragmentFile); + String vertexShader = IO.loadFile(vertexFile); + + if(program != -1)glDeleteProgram(program); + program = glCreateProgram(); + int vert = glCreateShader(GL_VERTEX_SHADER); + int frag = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(vert, vertexShader); + glShaderSource(frag, fragmentShader); + glCompileShader(vert); + if (glGetShaderi(vert, GL_COMPILE_STATUS) == GL_FALSE) { + System.err.println(glGetShaderInfoLog(vert, 2048)); + System.exit(1); + }else{ + System.out.println("Vertex compiled !"); + } + glCompileShader(frag); + if (glGetShaderi(frag, GL_COMPILE_STATUS) == GL_FALSE) { + System.err.println(glGetShaderInfoLog(frag, 2048)); + System.exit(1); + }else{ + System.out.println("Fragment compiled !"); + } + glAttachShader(program, vert); + glAttachShader(program, frag); + glLinkProgram(program); + glValidateProgram(program); + glDeleteShader(frag); + glDeleteShader(vert); + } + + public void bind(){ + glUseProgram(program); + } + + public void unbind(){ + glUseProgram(0); + } + + public int getAttribLocation(String name){ + return glGetAttribLocation(program, name); + } + + public void destroy(){ + if(program == 0)return; + if(glIsProgram(program))unbind(); + glDeleteProgram(program); + } + + public void uniform(String name,float v){ + glUniform1f(glGetUniformLocation(program, name), v); + } + + public void uniform(String name,Vector3f vec){ + glUniform3f(glGetUniformLocation(program, name), vec.x,vec.y,vec.z); + } + + public void uniform(String name,Vector4f vec){ + glUniform4f(glGetUniformLocation(program, name), vec.x,vec.y,vec.z,vec.w); + } + + public void uniform(String name,Matrix4f mat){ + glUniformMatrix4fv(glGetUniformLocation(program, name),true, mat.getBuffer()); + } + + public void uniform(String name, Color4f v) { + glUniform4f(glGetUniformLocation(program, name), v.getR(),v.getG(),v.getB(),v.getA()); + } + + public void uniform(String name,int v){ + glUniform1i(glGetUniformLocation(program,name), v); + } + +} diff --git a/Diffuse light/src/fr/technicalgames/render/Texture.java b/Diffuse light/src/fr/technicalgames/render/Texture.java new file mode 100644 index 0000000..d70df7e --- /dev/null +++ b/Diffuse light/src/fr/technicalgames/render/Texture.java @@ -0,0 +1,96 @@ +package fr.technicalgames.render; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL12.*; + +import java.awt.image.*; +import java.io.*; +import java.nio.*; + +import javax.imageio.*; + +import org.lwjgl.*; + +public class Texture { + + + public static Texture WOOD = loadTexture("res/textures/wooden-crate.jpg"); + + int width, height; + int id; + + public Texture(int width,int height,int id){ + this.id = id; + this.width = width; + this.height = height; + } + + public static Texture loadTexture(String path){ + try { + BufferedImage image = ImageIO.read(new File(path)); + int width = image.getWidth(); + int height = image.getHeight(); + int[] pixels = new int[width * height]; + + image.getRGB(0, 0, width, height, pixels, 0,width); + + int[] data = new int[pixels.length]; + for (int i = 0; i < data.length; i++) { + int a = (pixels[i] & 0xff000000) >> 24; + int r = (pixels[i] & 0xff0000) >> 16; + int g = (pixels[i] & 0xff00) >> 8; + int b = (pixels[i] & 0xff); + + data[i] = a << 24 | b << 16 | g << 8 | r; + } + + IntBuffer buffer = BufferUtils.createIntBuffer(data.length); + buffer.put(data); + buffer.flip(); + + int id = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + glBindTexture(GL_TEXTURE_2D, 0); + + System.out.println("Texture loaded ! " + width + "x" + height + " id:" + id); + + return new Texture(width, height, id); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getID(){ + return id; + } + + public void bind(){ + glBindTexture(GL_TEXTURE_2D, id); + } + + public void unbind(){ + glBindTexture(GL_TEXTURE_2D, 0); + } + + public void destroy(){ + glDeleteTextures(id); + } + +}