Java tutorial
/* * Copyright 2016 Nathan Howard * * This file is part of OpenGrave * * OpenGrave is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenGrave is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenGrave. If not, see <http://www.gnu.org/licenses/>. */ package com.opengrave.og; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.GL_TRUE; import static org.lwjgl.system.MemoryUtil.NULL; import java.awt.BorderLayout; import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.util.ArrayList; import java.util.Scanner; import javax.swing.*; import javax.swing.text.*; import org.lwjgl.glfw.GLFWFramebufferSizeCallback; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL20; import com.opengrave.common.*; import com.opengrave.common.DebugWindowThread.DebugType; import com.opengrave.common.config.Config; import com.opengrave.common.event.DebugWindowTextInputEvent; import com.opengrave.common.event.EventDispatcher; import com.opengrave.common.packet.Packet; import com.opengrave.common.xml.HGXMLThread; import com.opengrave.og.base.Renderable; import com.opengrave.og.input.InputMain; import com.opengrave.og.light.CubeData; import com.opengrave.og.resources.GUIXML; import com.opengrave.og.resources.Resources; import com.opengrave.og.states.BaseState; import com.opengrave.og.states.LoadingState; import com.opengrave.og.states.ProfileState; import com.opengrave.og.states.waitables.PreMenuLoader; import com.opengrave.og.util.Vector3f; public class MainThread extends Thread implements MainThreadInterface { public static String USERNAME, PASSWORD, TOKEN, SERVERLIST; public static int hgver = -1, assetver = -1; public static boolean running; public static MainThread main; public static ProfileState profileState; private static ServerConnection serverConnectionData = null; private static BaseState state = null, nextState = null; public InputMain input; public static Config config; public static File cache; private static ArrayList<Runnable> toDoList = new ArrayList<Runnable>(); public int fpsCap; private long lastCheckIn = 0; private long checkInInter = 1000 * 60 * 10; // Check in every 10 minutes private boolean needsConfigUpdate = false; private ModSession session; private GLFWFramebufferSizeCallback framebufferSizeCallback; // private SoundSystem soundSystem; public static long window; // private GLFWFramebufferSizeCallback framebufferSizeCallback; public static JFrame debugWindow; static JTextPane debugText; static AttributeSet errStyle, outStyle; static JTabbedPane debugTabs; public static Object debugLock = new Object(); public static int lastW = 0; public static int lastH = 0; public static int SHADOWSIZE = 1024; public static PrintStream oldErr; public void run() { Thread.currentThread().setName("OpenGL and game"); running = true; main = this; config = new Config("hg.config"); // Attempt to get HG and assets build version for reporting bugs try (Scanner s = new Scanner(new File(cache, "hgjava.ver"))) { hgver = s.nextInt(); } catch (FileNotFoundException e) { } try (Scanner s = new Scanner(new File(cache, "hgasset.ver"));) { assetver = s.nextInt(); } catch (FileNotFoundException e) { } // Set FPS Cap if (!config.getBoolean("capfps", true)) { fpsCap = -1; } else { fpsCap = config.getInteger("fpslimit", 60); } initDebugWindow(); // Start Event thread new LoadingThread(); EventDispatcher.events.beginEventThread(); // Prepare known packets for Client->GameServer and opposite Packet.init(); // Prepare OpenGL. initGL(); // initSound(); // Prepare input handling input = new InputMain(); // Set a first state. Prepare mods and then load menu changeState(new LoadingState(new PreMenuLoader())); startAuthClient(); // And we're away! gameLoop(); } /* * private void initSound() { * try { * SoundSystemConfig.setCodec("ogg", CodecJOgg.class); * } catch (SoundSystemException e) { * e.printStackTrace(); * } * try { * soundSystem = new SoundSystem(LibraryLWJGLOpenAL.class); * soundSystem.switchLibrary(LibraryLWJGLOpenAL.class); * } catch (SoundSystemException e) { * e.printStackTrace(); * } * * // loadSoundFile("midi/test.mid"); * // playSoundFile("midi/test.mid"); * } * * public void playSoundFile(String string) { * String label = string; * label = label.replaceAll("/[^a-zA-Z0-9]/", ""); * soundSystem.play(label); * soundSystem.setVolume(label, 1f); * } * * public void loadSoundFile(String file) { * String label = file; * label = label.replaceAll("/[^a-zA-Z0-9]/", ""); * File f = new File(cache, file); * try { * soundSystem.newStreamingSource(true, file, new URL("file://" + f.getAbsolutePath()), label, true, 0, 0, 0, SoundSystemConfig.ATTENUATION_NONE, 0); * } catch (MalformedURLException e) { * e.printStackTrace(); * } * } */ private void startAuthClient() { HGXMLThread.requestAuthClient(); HGXMLThread.authURL = SERVERLIST; Thread t = new Thread(new HGXMLThread(), "XML Communication Thread"); t.start(); } public void startApp(File cache, String userName, String passWord, String serverList) { USERNAME = userName; PASSWORD = passWord; SERVERLIST = serverList; MainThread.cache = cache; Resources.cache = cache; start(); } public void checkIn() { long timeNow = System.currentTimeMillis(); if (lastCheckIn + checkInInter < timeNow) { checkInNow(); } } public void checkInNow() { long timeNow = System.currentTimeMillis(); lastCheckIn = timeNow; HGXMLThread.requestClientCheckIn(); } public void gameLoop() { Util.checkErr(); double lasttime = getTime(), lastFPS = getTime(); int fps = 0; while (running) { if (needsConfigUpdate) { createConfig(); needsConfigUpdate = false; } synchronized (toDoList) { while (toDoList.size() > 0) { Runnable runnable = toDoList.remove(0); runnable.run(); } } if (nextState != null) { // Something has caused us to switch states, stop the last // cleanly and start the next if (state != null) { state.stop(); state.finalise(); } state = nextState; state.prestart(); state.start(); nextState = null; } // Update FPS counter in window title TODO: Remove from window title // and place into a debug context ingame if (getTime() - lastFPS > 1000) { // use fps value fps = 0; lastFPS += 1000; } fps++; // Prepare time delta for updating graphics double time = getTime(); float delta = (float) (time - lasttime); // TODO Change delta to double as a rule? lasttime = time; input.doEet(state, delta); Util.checkErr(); state.updateGUI(delta); Util.checkErr(); state.update(delta); // Pre-render update Util.checkErr(); // Clear frame ready for next frame GL11.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_COLOR_BUFFER_BIT); GL11.glViewport(0, 0, lastW, lastH); set2D(); state.renderGUI(); if (glfwWindowShouldClose(window) == GL_TRUE) { running = false; } glfwPollEvents(); // if (fpsCap > 10) { glfwSwapBuffers(window); // } Util.checkErr(); } running = false; // soundSystem.cleanup(); System.exit(1); } public static void set2D() { GL11.glDisable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glDepthFunc(GL11.GL_LEQUAL); } protected void initGL() { if (glfwInit() != GLFW_TRUE) { System.exit(1); } glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow(800, 600, "HiddenGrave", NULL, NULL); if (window == NULL) { System.exit(1); } glfwMakeContextCurrent(window); glfwSwapInterval(1); // TODO condig of vsync. Enable vsync GL.createCapabilities(); glfwSetFramebufferSizeCallback(window, (framebufferSizeCallback = new GLFWFramebufferSizeCallback() { @Override public void invoke(long window, int width, int height) { onResize(width, height); } })); onResize(800, 600); // TODO Check all extensions. TEX 2D ARRAY, GLSL 130 createConfig(); Util.initMatrices(); Renderable.init(); GUIXML.init(); // Prepare Lighting initLighting(); // Default Values GL11.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // sets background to grey Util.checkErr(); GL11.glClearDepth(1.0f); // clear depth buffer Util.checkErr(); GL11.glEnable(GL11.GL_DEPTH_TEST); // Enables depth testing Util.checkErr(); GL11.glDepthFunc(GL11.GL_LEQUAL); // sets the type of test to use for // depth testing GL11.glEnable(GL11.GL_BLEND); Resources.loadTextures(); // Reconsider positioning. Other than GUI // texture we could offset these in another // thread... Possibly? Resources.loadModels(); Resources.loadFonts(); } protected void onResize(int width, int height) { DisplayRes dr = DisplayRes.get(); if (width != lastW || height != lastH) { lastH = height; lastW = width; Util.update2DProjection(); } } public void createConfig() { boolean isSet = config.getBoolean("set", false); System.out.println("Running OpenGL : " + GL11.glGetString(GL11.GL_VERSION)); String glsl = GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION).split(" ")[0]; System.out.println(glsl + " " + Float.parseFloat(glsl)); if (Float.parseFloat(glsl) < 1.299f) { // Stupid float accuracy. < 1.30 System.out.println("This program requires GLSL 1.30 as an absolute minimum"); System.exit(130); } if (!isSet) { int textures = GL11.glGetInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS) - 3; System.out.println("Maximum spare textures (minus reserved) : " + textures); config.setBoolean("shadows", true); if (textures < 4) { System.out.println("Setting no point-light shadows"); config.setInteger("lightCount", 0); } else if (textures < 8) { System.out.println("Setting 4 point-lights with shadows"); config.setInteger("lightCount", 4); } else if (textures < 16) { System.out.println("Setting 8 point-lights with shadows"); config.setInteger("lightCount", 8); } else { System.out.println("Setting 16 point-lights with shadows"); // config.setInteger("lightCount", 16); // TODO Create lighting-more config.setInteger("lightCount", 8); } // TODO Check for GL 3.0 and turn off shadows/lightCount if below // if (!GLContext.getCapabilities().OpenGL30) { // System.out.println("This program does not support OpenGL versions before 3.0. Setting absolute minimum detail"); // if (!isSet) { // // We'll assume the worst. // config.setInteger("lightCount", 0); // config.setBoolean("shadows", false); // } // } Resources.removeShadersWithLighting(); config.setBoolean("set", true); } } private void initLighting() { CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_X, new Vector3f(1f, 0f, 0f), new Vector3f(0f, -1f, 0f))); CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, new Vector3f(-1f, 0f, 0f), new Vector3f(0f, -1f, 0f))); CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, new Vector3f(0f, 1f, 0f), new Vector3f(0f, 0f, 1f))); CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, new Vector3f(0f, -1f, 0f), new Vector3f(0f, 0f, -1f))); CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, new Vector3f(0f, 0f, 1f), new Vector3f(0f, -1f, 0f))); CubeData.data.add(new CubeData(GL13.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, new Vector3f(0f, 0f, -1f), new Vector3f(0f, -1f, 0f))); } public static void changeState(BaseState state) { nextState = state; } public double getTime() { double f = (glfwGetTime() * 1000); return f; } public static BaseState getGameState() { return state; } public static void sendPacket(Packet packet) { getConnection().sendPacket(packet); } public static void changeServerConnection(ServerData sd) { // TODO Check assumption that // "We only run this code when the last connection is completely dead/closed" if (serverConnectionData != null) { serverConnectionData.killConnection(); } serverConnectionData = new ServerConnection(sd.getIP(), sd.getPort(), sd.getId()); // Don't connect yet. So far this is only information about how and // where to connect // We need to make sure we double check asset loading and mod loading // before we begin an attempted connect. } public static void startConnectionThread() { serverConnectionData.connect(); } public static ServerConnection getConnection() { return serverConnectionData; } public void newConfig() { needsConfigUpdate = true; } public static void showDebugWindow(boolean b) { debugWindow.setVisible(b); } private void initDebugWindow() { debugWindow = new JFrame("HG Debug"); debugText = new JTextPane(); debugText.setEditable(false); StyleContext sc = StyleContext.getDefaultStyleContext(); errStyle = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.RED); outStyle = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, Color.BLUE); final JTextField input = new JTextField(); input.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { String text = input.getText(); EventDispatcher.dispatchEvent(new DebugWindowTextInputEvent(text)); input.setText(""); } }); JScrollPane scroll = new JScrollPane(debugText); JPanel panel = new JPanel(new BorderLayout()); panel.add(scroll, BorderLayout.CENTER); debugTabs = new JTabbedPane(); debugTabs.addTab("Debug", panel); debugWindow.add(debugTabs, BorderLayout.CENTER); debugWindow.add(input, BorderLayout.SOUTH); debugWindow.pack(); debugWindow.setSize(400, 400); debugWindow.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // TODO Also place debug into text file. try { PipedOutputStream pOut = new PipedOutputStream(); PipedInputStream pIn = new PipedInputStream(pOut); PrintStream oldOut = System.out; System.setOut(new PrintStream(pOut, true)); BufferedReader reader = new BufferedReader(new InputStreamReader(pIn)); new DebugWindowThread(reader, oldOut, DebugType.OUT); } catch (IOException e) { new DebugExceptionHandler(e); } try { PipedOutputStream pErr = new PipedOutputStream(); PipedInputStream pInErr = new PipedInputStream(pErr); oldErr = System.err; System.setErr(new PrintStream(pErr, true)); BufferedReader readerErr = new BufferedReader(new InputStreamReader(pInErr)); new DebugWindowThread(readerErr, oldErr, DebugType.ERROR); } catch (IOException e) { new DebugExceptionHandler(e); } Thread.setDefaultUncaughtExceptionHandler(new DebugUncaughtExceptionHandler()); showDebugWindow(true); } public static void addDebugLine(String line, DebugType type) { AttributeSet x = outStyle; if (type == DebugType.ERROR) { x = errStyle; } synchronized (debugText) { StyledDocument doc = debugText.getStyledDocument(); try { doc.insertString(doc.getLength(), line + "\n", x); debugText.setCaretPosition(doc.getLength()); } catch (BadLocationException e) { new DebugExceptionHandler(e); } } } public static void addDebugException(DebugExceptionHandler handler) { synchronized (debugTabs) { debugTabs.addTab(handler.getName(), handler.makeGUI()); } } public static void removeDebugException(DebugExceptionHandler handler) { synchronized (debugTabs) { debugTabs.remove(handler.makeGUI()); } } /** * Used to push GL-context-only commands into the GL thread. */ public static void addToGLCommands(Runnable run) { synchronized (toDoList) { toDoList.add(run); } } public static void newSession() { main.session = new ModSession(); } public static ModSession getSession() { return main.session; } }