Java tutorial
/** ** This file is part of the project https://github.com/toss-dev/VoxelEngine ** ** License is available here: https://raw.githubusercontent.com/toss-dev/VoxelEngine/master/LICENSE.md ** ** PEREIRA Romain ** 4-----7 ** /| /| ** 0-----3 | ** | 5___|_6 ** |/ | / ** 1-----2 */ package com.grillecube.client.opengl.window; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWCharCallback; import org.lwjgl.glfw.GLFWCursorPosCallback; import org.lwjgl.glfw.GLFWKeyCallback; import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.glfw.GLFWScrollCallback; import org.lwjgl.glfw.GLFWWindowFocusCallback; import org.lwjgl.glfw.GLFWWindowSizeCallback; import org.lwjgl.opengl.GL11; import com.grillecube.client.opengl.GLCursor; import com.grillecube.client.opengl.GLH; import com.grillecube.client.opengl.GLIcon; import com.grillecube.client.opengl.GLObject; import com.grillecube.client.opengl.window.event.GLFWEvent; import com.grillecube.client.opengl.window.event.GLFWEventChar; import com.grillecube.client.opengl.window.event.GLFWEventKeyPress; import com.grillecube.client.opengl.window.event.GLFWEventKeyRelease; import com.grillecube.client.opengl.window.event.GLFWEventMouseCursor; import com.grillecube.client.opengl.window.event.GLFWEventMouseEnter; import com.grillecube.client.opengl.window.event.GLFWEventMouseExit; import com.grillecube.client.opengl.window.event.GLFWEventMousePress; import com.grillecube.client.opengl.window.event.GLFWEventMouseRelease; import com.grillecube.client.opengl.window.event.GLFWEventMouseScroll; import com.grillecube.client.opengl.window.event.GLFWEventWindowResize; import com.grillecube.client.opengl.window.event.GLFWListener; /** * * HOW TO USE: * * create a new instance (GLFWWindow window = new GLFWWindow()) * * call 'window.create()' to set GLContext + resize and error callback * * window.prepareScreen(); //clear screen ... // rendering stuff goes here * window.flushScreen(); //swap buffer + update fps counter */ public class GLFWWindow implements GLObject { public static final int OS_LINUX = 0; public static final int OS_WINDOWS = 1; public static final int OS_MAC = 2; public static final String OS_NAME = System.getProperty("os.name"); private static final String _OS_NAME = OS_NAME.toLowerCase(); public static final int OS = _OS_NAME.contains("win") ? OS_WINDOWS : _OS_NAME.contains("mac") ? OS_MAC : OS_LINUX; /** window pointer */ private long windowPtr; /** window size (in pixels) */ private int width; private int height; private float aspectRatio; private double mouseX; private double mouseY; private double prevmouseX; private double prevmouseY; /** frames data */ private long prevFrame; // previous frame timer private long frames; // total frames flushed private int fps_counter; // frame per second counter private int fps; // last frame per second calculated /** events handler */ private HashMap<Class<? extends GLFWEvent>, ArrayList<GLFWListener<? extends GLFWEvent>>> listeners; /** cursor used */ private GLCursor cursor; /** events callback for garbage collector */ private GLFWScrollCallback callback_scroll; private GLFWCursorPosCallback callback_cursor_pos; private GLFWMouseButtonCallback callback_mouse_button; private GLFWKeyCallback callback_key; private GLFWCharCallback callback_char; private GLFWWindowSizeCallback callback_resize; private GLFWWindowFocusCallback callback_focus; private static final int STATE_HOVERED = (1 << 0); private static final int STATE_FOCUSED = (1 << 1); private int state; public GLFWWindow() { } /** create the window and initialize gl context */ public void create(int width, int height, String title) { if (width == 0) { width = 1; } if (height == 0) { height = 1; } if (GLFWWindow.OS == GLFWWindow.OS_LINUX || GLFWWindow.OS == GLFWWindow.OS_MAC) { GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 3); GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 3); GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE); } this.windowPtr = GLFW.glfwCreateWindow(width, height, title, 0, 0); if (this.windowPtr == 0) { System.err.println("Couldnt create glfw window"); return; } this.width = width; this.height = height; this.aspectRatio = width / (float) height; this.mouseX = width / 2; this.mouseY = height / 2; this.prevmouseX = width / 2; this.prevmouseY = height / 2; this.prevFrame = System.currentTimeMillis(); this.frames = 0; this.fps_counter = 0; this.fps = 0; this.setCursor(new GLCursor()); this.initEvents(); } private final void initEvents() { this.initWindowEvents(); this.initKeyEvents(); this.initMouseEvents(); this.addListener(new GLFWListener<GLFWEventKeyPress>() { @Override public void invoke(GLFWEventKeyPress event) { // if (event.getKey() == GLFW.GLFW_KEY_ESCAPE) { // close(); // } if (event.getKey() == GLFW.GLFW_KEY_N) { setCursor(!isCursorEnabled()); } } }); } /** * set the cursor to be rendered. NB: the previous cursor isn't freed! care for * memory leaks when using this function * * @param cursor */ public final void setCursor(GLCursor cursor) { if (this.cursor != cursor || this.cursor.getID() != cursor.getID()) { GLFW.glfwSetCursor(this.windowPtr, cursor.getID()); this.cursor = cursor; } } /** * get the current rendered cursor */ public final GLCursor getCursor() { return (this.cursor); } private final void initWindowEvents() { this.callback_resize = new GLFWWindowSizeCallback() { @Override public void invoke(long window, int width, int height) { resize(width, height); GLFWWindow.this.invokeEvent(new GLFWEventWindowResize(GLFWWindow.this)); } }; GLFW.glfwSetWindowSizeCallback(this.windowPtr, this.callback_resize); this.callback_focus = new GLFWWindowFocusCallback() { @Override public void invoke(long window, boolean focused) { setState(STATE_FOCUSED, focused); } }; GLFW.glfwSetWindowFocusCallback(this.windowPtr, this.callback_focus); } /** invoke a glfw event */ @SuppressWarnings({ "rawtypes", "unchecked" }) public final void invokeEvent(GLFWEvent glfwEvent) { if (this.listeners == null) { return; } ArrayList<GLFWListener<?>> listeners = this.listeners.get(glfwEvent.getClass()); if (listeners == null) { return; } for (GLFWListener listener : listeners) { listener.invoke(glfwEvent); } } /** add listeners */ public final void addListener(GLFWListener<? extends GLFWEvent> listener) { if (this.listeners == null) { this.listeners = new HashMap<Class<? extends GLFWEvent>, ArrayList<GLFWListener<? extends GLFWEvent>>>(); } ArrayList<GLFWListener<? extends GLFWEvent>> lst = this.listeners.get(listener.getEventClass()); if (lst == null) { lst = new ArrayList<GLFWListener<? extends GLFWEvent>>(); this.listeners.put(listener.getEventClass(), lst); } lst.add(listener); } /** remove listeners */ public final void removeListener(GLFWListener<? extends GLFWEvent> listener) { if (this.listeners == null) { return; } ArrayList<GLFWListener<? extends GLFWEvent>> lst = this.listeners.get(listener.getEventClass()); if (lst == null) { return; } lst.remove(listener); if (lst.size() == 0) { this.listeners.remove(listener.getEventClass()); } if (this.listeners.size() == 0) { this.listeners = null; } } private final void initKeyEvents() { this.callback_char = new GLFWCharCallback() { @Override public void invoke(long window, int codepoint) { GLFWWindow.this.invokeEvent(new GLFWEventChar(GLFWWindow.this, codepoint)); } }; GLFW.glfwSetCharCallback(this.windowPtr, this.callback_char); this.callback_key = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { if (action == GLFW.GLFW_PRESS) { GLFWWindow.this.invokeEvent(new GLFWEventKeyPress(GLFWWindow.this, key, scancode, mods)); } else if (action == GLFW.GLFW_RELEASE) { GLFWWindow.this.invokeEvent(new GLFWEventKeyRelease(GLFWWindow.this, key, scancode, mods)); } } }; GLFW.glfwSetKeyCallback(this.windowPtr, this.callback_key); } private final void initMouseEvents() { this.callback_scroll = new GLFWScrollCallback() { @Override public void invoke(long window, double xpos, double ypos) { GLFWWindow.this.invokeEvent(new GLFWEventMouseScroll(GLFWWindow.this, xpos, ypos)); } }; this.callback_cursor_pos = new GLFWCursorPosCallback() { @Override public void invoke(long window, double xpos, double ypos) { GLFWWindow glfWWindow = GLFWWindow.this; glfWWindow.prevmouseX = glfWWindow.mouseX; glfWWindow.prevmouseY = glfWWindow.mouseY; glfWWindow.mouseX = xpos; glfWWindow.mouseY = ypos; GLFWWindow.this.invokeEvent(new GLFWEventMouseCursor(glfWWindow)); } }; this.callback_mouse_button = new GLFWMouseButtonCallback() { @Override public void invoke(long window, int button, int action, int mods) { if (action == GLFW.GLFW_PRESS) { GLFWWindow.this.invokeEvent(new GLFWEventMousePress(GLFWWindow.this, button, mods)); } else { GLFWWindow.this.invokeEvent(new GLFWEventMouseRelease(GLFWWindow.this, button, mods)); } } }; GLFW.glfwSetScrollCallback(this.windowPtr, this.callback_scroll); GLFW.glfwSetCursorPosCallback(this.windowPtr, this.callback_cursor_pos); GLFW.glfwSetMouseButtonCallback(this.windowPtr, this.callback_mouse_button); } /** in pixels */ public void setScreenPosition(int px, int py) { GLFW.glfwSetWindowPos(this.getPointer(), px, py); } /** enable or disable cursor */ public void setCursor(boolean enable) { int value = (enable) ? GLFW.GLFW_CURSOR_NORMAL : GLFW.GLFW_CURSOR_DISABLED; GLFW.glfwSetInputMode(this.windowPtr, GLFW.GLFW_CURSOR, value); } public boolean isCursorEnabled() { return (GLFW.glfwGetInputMode(this.windowPtr, GLFW.GLFW_CURSOR) != GLFW.GLFW_CURSOR_DISABLED); } /** center the mouse on the screen */ public void setCursorCenter() { this.setCursorPos(this.width / 2, this.height / 2); } /** set cursor position */ public void setCursorPos(double x, double y) { GLFW.glfwSetCursorPos(this.windowPtr, x, y); } /** set window title */ public void setTitle(String title) { GLFW.glfwSetWindowTitle(this.windowPtr, title); } /** enable or disable vsync (0 == disable, 1 == enable) */ public void swapInterval(int v) { GLFW.glfwSwapInterval(v); } /** stop the window */ @Override public void delete() { GLFW.glfwDestroyWindow(this.windowPtr); } public float getAspectRatio() { return (this.aspectRatio); } private void resize(int width, int height) { this.width = width; this.height = height; this.aspectRatio = this.width / (float) this.height; } public final int getWidth() { return (this.width); } public final int getHeight() { return (this.height); } public void setClearColor(float r, float g, float b, float a) { GL11.glClearColor(r, g, b, a); } /** should be call before rendering */ public void clearScreen() { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); } private void setHovered(boolean b) { this.setState(STATE_HOVERED, b); } public final boolean isHovered() { return (this.hasState(STATE_HOVERED)); } /** should be call after rendering */ public final void flushScreen() { GLFW.glfwSwapBuffers(this.windowPtr); this.updateFpsCounter(); GLH.glhCheckError("GLFWWindow.flushScreen()"); } /** processed polled events */ public final void pollEvents() { GLFW.glfwPollEvents(); boolean mouseIn = this.getMouseX() >= 0 && this.getMouseX() <= this.getWidth() && this.getMouseY() >= 0 && this.getMouseY() <= this.getHeight(); if (mouseIn && !this.isHovered()) { this.setHovered(true); this.invokeEvent(new GLFWEventMouseEnter(this)); } else if (!mouseIn && this.isHovered()) { this.setHovered(false); this.invokeEvent(new GLFWEventMouseExit(this)); } } private final void updateFpsCounter() { if (System.currentTimeMillis() - this.prevFrame >= 1000) { this.fps = this.fps_counter; this.fps_counter = 0; this.prevFrame = System.currentTimeMillis(); } else { this.fps_counter++; } this.frames++; } /** return true if glfw was close-requested */ public final boolean shouldClose() { return (GLFW.glfwWindowShouldClose(this.windowPtr)); } /** get window GLFW pointer */ public final long getPointer() { return (this.windowPtr); } /** get mouse X coordinate */ public final double getMouseX() { return (this.mouseX); } /** get mouse Y coordinate */ public final double getMouseY() { return (this.mouseY); } public final double getPrevMouseX() { return (this.prevmouseX); } public final double getPrevMouseY() { return (this.prevmouseY); } /** * return mouse delta X since last cursor move was made and current position */ public final double getMouseDX() { return (this.mouseX - this.prevmouseX); } /** * return mouse delta Y since last cursor move was made and current position */ public final double getMouseDY() { return (this.mouseY - this.prevmouseY); } /** get total frames flushed */ public final long getTotalFramesFlushed() { return (this.frames); } public final boolean isKeyPressed(int key) { return (GLFW.glfwGetKey(this.getPointer(), key) == GLFW.GLFW_PRESS); } public final boolean isKeyReleased(int key) { return (GLFW.glfwGetKey(this.getPointer(), key) == GLFW.GLFW_RELEASE); } public final boolean isMousePressed(int button) { return (GLFW.glfwGetMouseButton(this.getPointer(), button) == GLFW.GLFW_PRESS); } public final boolean isMouseRightPressed() { return (GLFW.glfwGetMouseButton(this.getPointer(), GLFW.GLFW_MOUSE_BUTTON_RIGHT) == GLFW.GLFW_PRESS); } public final boolean isMouseLeftPressed() { return (GLFW.glfwGetMouseButton(this.getPointer(), GLFW.GLFW_MOUSE_BUTTON_LEFT) == GLFW.GLFW_PRESS); } public final int getFPS() { return (this.fps); } public final void setSize(int width, int height) { GLFW.glfwSetWindowSize(this.getPointer(), width, height); } public final void close() { GLFW.glfwSetWindowShouldClose(this.windowPtr, true); } public final boolean hasFocus() { return (this.hasState(STATE_FOCUSED)); } public final void focus(boolean focus) { if (focus) { GLFW.glfwFocusWindow(this.getPointer()); } this.setState(STATE_FOCUSED, focus); } /** set the icon of this window */ public final void setIcon(File file) { this.setIcon(file.getAbsolutePath()); } /** set the icon of this window */ public final void setIcon(String filepath) { GLIcon glIcon = GLH.glhCreateIcon(filepath); this.setIcon(glIcon); GLH.glhDeleteObject(glIcon); } public final void setIcon(GLIcon glIcon) { GLFW.glfwSetWindowIcon(this.windowPtr, glIcon.getBuffer()); } private final boolean hasState(int state) { return ((this.state & state) == state); } private final void setState(int state) { this.state = this.state | state; } private final void setState(int state, boolean enabled) { if (enabled) { this.setState(state); } else { this.unsetState(state); } } private final void unsetState(int state) { this.state = this.state & ~state; } @SuppressWarnings("unused") private final void swapState(int state) { this.state = this.state ^ state; } }