From 1af5f015204995d4c34d64f350bf5b3ff2ed3461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 30 Mar 2014 11:10:43 +0200 Subject: [PATCH] Removed junk & added Easing, Event system and others --- src/mightypork/rogue/App.java | 457 ++++++++---------- src/mightypork/rogue/Const.java | 9 +- src/mightypork/rogue/CrashHandler.java | 1 + src/mightypork/rogue/Screen.java | 200 -------- .../rogue/animations/EmptyAnimator.java | 30 -- .../rogue/animations/GUIRenderer.java | 13 - .../rogue/display/DisplaySystem.java | 200 ++++++++ src/mightypork/rogue/display/Screen.java | 214 ++++++++ .../rogue/display/ScreenSplash.java | 168 +++++++ .../display/events/ScreenChangeEvent.java | 50 ++ src/mightypork/rogue/fonts/FontManager.java | 182 +++---- src/mightypork/rogue/fonts/LoadedFont.java | 27 -- src/mightypork/rogue/input/InputHandler.java | 52 -- src/mightypork/rogue/input/InputSystem.java | 126 +++++ src/mightypork/rogue/input/KeyBinder.java | 21 + src/mightypork/rogue/input/KeyBinding.java | 48 ++ .../rogue/input/KeyBindingPool.java | 68 +++ src/mightypork/rogue/input/KeyStroke.java | 114 +++++ src/mightypork/rogue/input/Keys.java | 122 ----- .../rogue/input/events/KeyboardEvent.java | 85 ++++ .../rogue/input/events/MouseButtonEvent.java | 119 +++++ .../rogue/input/events/MouseMotionEvent.java | 54 +++ .../rogue/screens/ScreenSplash.java | 82 ---- src/mightypork/rogue/sounds/AudioPlayer.java | 77 --- src/mightypork/rogue/sounds/AudioX.java | 203 +++++--- .../rogue/sounds/BaseAudioPlayer.java | 77 +++ src/mightypork/rogue/sounds/EffectPlayer.java | 8 +- src/mightypork/rogue/sounds/JointVolume.java | 22 +- src/mightypork/rogue/sounds/LoopPlayer.java | 6 +- src/mightypork/rogue/sounds/SoundManager.java | 179 ------- src/mightypork/rogue/sounds/SoundSystem.java | 309 ++++++++++++ .../rogue/tasks/TaskTakeScreenshot.java | 61 +++ .../rogue/threads/ThreadSaveScreenshot.java | 91 ---- .../threads/ThreadScreenshotTrigger.java | 32 -- src/mightypork/rogue/util/RenderUtils.java | 94 ++-- src/mightypork/rogue/util/Utils.java | 5 + src/mightypork/utils/logging/Log.java | 2 +- src/mightypork/utils/math/Calc.java | 47 +- src/mightypork/utils/math/Polar.java | 132 +++-- src/mightypork/utils/math/PolarDeg.java | 111 ----- src/mightypork/utils/math/coord/Coord.java | 385 +++++++++------ .../utils/math/coord/CoordAnimated.java | 94 ++++ src/mightypork/utils/math/coord/CoordI.java | 398 --------------- src/mightypork/utils/math/coord/Rect.java | 46 +- src/mightypork/utils/math/coord/Vec.java | 326 ------------- src/mightypork/utils/math/easing/Easing.java | 322 ++++++++++++ src/mightypork/utils/objects/Convertor.java | 4 +- src/mightypork/utils/objects/Mutable.java | 44 +- .../utils/patterns/Destroyable.java | 14 + .../utils/patterns/Initializable.java | 14 + .../patterns/subscription/Handleable.java | 18 + .../patterns/subscription/MessageBus.java | 121 +++++ .../patterns/subscription/MessageChannel.java | 156 ++++++ .../patterns/subscription/Subscribable.java | 26 + src/mightypork/utils/time/AnimDouble.java | 138 ++++-- src/mightypork/utils/time/AnimDoubleDeg.java | 41 +- src/mightypork/utils/time/AnimDoubleRad.java | 50 ++ 57 files changed, 3589 insertions(+), 2506 deletions(-) delete mode 100644 src/mightypork/rogue/Screen.java delete mode 100644 src/mightypork/rogue/animations/EmptyAnimator.java delete mode 100644 src/mightypork/rogue/animations/GUIRenderer.java create mode 100644 src/mightypork/rogue/display/DisplaySystem.java create mode 100644 src/mightypork/rogue/display/Screen.java create mode 100644 src/mightypork/rogue/display/ScreenSplash.java create mode 100644 src/mightypork/rogue/display/events/ScreenChangeEvent.java delete mode 100644 src/mightypork/rogue/input/InputHandler.java create mode 100644 src/mightypork/rogue/input/InputSystem.java create mode 100644 src/mightypork/rogue/input/KeyBinder.java create mode 100644 src/mightypork/rogue/input/KeyBinding.java create mode 100644 src/mightypork/rogue/input/KeyBindingPool.java create mode 100644 src/mightypork/rogue/input/KeyStroke.java delete mode 100644 src/mightypork/rogue/input/Keys.java create mode 100644 src/mightypork/rogue/input/events/KeyboardEvent.java create mode 100644 src/mightypork/rogue/input/events/MouseButtonEvent.java create mode 100644 src/mightypork/rogue/input/events/MouseMotionEvent.java delete mode 100644 src/mightypork/rogue/screens/ScreenSplash.java delete mode 100644 src/mightypork/rogue/sounds/AudioPlayer.java create mode 100644 src/mightypork/rogue/sounds/BaseAudioPlayer.java delete mode 100644 src/mightypork/rogue/sounds/SoundManager.java create mode 100644 src/mightypork/rogue/sounds/SoundSystem.java create mode 100644 src/mightypork/rogue/tasks/TaskTakeScreenshot.java delete mode 100644 src/mightypork/rogue/threads/ThreadSaveScreenshot.java delete mode 100644 src/mightypork/rogue/threads/ThreadScreenshotTrigger.java delete mode 100644 src/mightypork/utils/math/PolarDeg.java create mode 100644 src/mightypork/utils/math/coord/CoordAnimated.java delete mode 100644 src/mightypork/utils/math/coord/CoordI.java delete mode 100644 src/mightypork/utils/math/coord/Vec.java create mode 100644 src/mightypork/utils/math/easing/Easing.java create mode 100644 src/mightypork/utils/patterns/Destroyable.java create mode 100644 src/mightypork/utils/patterns/Initializable.java create mode 100644 src/mightypork/utils/patterns/subscription/Handleable.java create mode 100644 src/mightypork/utils/patterns/subscription/MessageBus.java create mode 100644 src/mightypork/utils/patterns/subscription/MessageChannel.java create mode 100644 src/mightypork/utils/patterns/subscription/Subscribable.java create mode 100644 src/mightypork/utils/time/AnimDoubleRad.java diff --git a/src/mightypork/rogue/App.java b/src/mightypork/rogue/App.java index b3f18c6..98203cb 100644 --- a/src/mightypork/rogue/App.java +++ b/src/mightypork/rogue/App.java @@ -1,93 +1,57 @@ package mightypork.rogue; -import static org.lwjgl.opengl.GL11.*; - import java.io.File; import java.io.RandomAccessFile; -import java.nio.ByteBuffer; import java.nio.channels.FileLock; import javax.swing.JOptionPane; -import mightypork.rogue.input.Keys; -import mightypork.rogue.screens.ScreenSplash; -import mightypork.rogue.sounds.SoundManager; -import mightypork.rogue.threads.ThreadSaveScreenshot; -import mightypork.rogue.threads.ThreadScreenshotTrigger; +import mightypork.rogue.display.DisplaySystem; +import mightypork.rogue.display.Screen; +import mightypork.rogue.display.ScreenSplash; +import mightypork.rogue.input.InputSystem; +import mightypork.rogue.input.KeyStroke; +import mightypork.rogue.input.events.MouseMotionEvent; +import mightypork.rogue.sounds.SoundSystem; +import mightypork.rogue.tasks.TaskTakeScreenshot; +import mightypork.rogue.util.Utils; import mightypork.utils.logging.Log; import mightypork.utils.logging.LogInstance; -import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.patterns.subscription.MessageBus; import mightypork.utils.time.TimerDelta; import mightypork.utils.time.TimerInterpolating; -import org.lwjgl.BufferUtils; -import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import org.lwjgl.openal.AL; import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.DisplayMode; -public class App { +public class App implements Destroyable { - /** instance */ - public static App inst; - /** Current delta time (secs since last render) */ - public static double currentDelta = 0; + /** instance pointer */ + private static App inst; - private static DisplayMode windowDisplayMode = null; + private InputSystem input; + private SoundSystem sounds; + private DisplaySystem display; + private MessageBus events; /** current screen */ - public static Screen screen = null; + private Screen screen; /** Flag that screenshot is scheduled to be taken next loop */ - public static boolean scheduledScreenshot = false; - - - private static boolean lockInstance() - { - if (Config.SINGLE_INSTANCE == false) return true; // bypass lock - - final File lockFile = new File(Paths.WORKDIR, ".lock"); - try { - final RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rw"); - final FileLock fileLock = randomAccessFile.getChannel().tryLock(); - if (fileLock != null) { - Runtime.getRuntime().addShutdownHook(new Thread() { - - @Override - public void run() - { - try { - fileLock.release(); - randomAccessFile.close(); - lockFile.delete(); - } catch (Exception e) { - System.out.println("Unable to remove lock file."); - e.printStackTrace(); - } - } - }); - return true; - } - } catch (Exception e) { - System.out.println("Unable to create and/or lock file."); - e.printStackTrace(); - } - return false; - } + private boolean scheduledScreenshot = false; /** - * Is if FS + * Get the instance * - * @return is in fs + * @return instance of App */ - public static boolean isFullscreen() + public static App inst() { - return Display.isFullscreen(); + return inst; } @@ -116,24 +80,19 @@ public class App { */ public static void onCrash(Throwable error) { - Log.e("The game has crashed."); - - Log.e(error); + Log.e("The game has crashed.", error); - try { - inst.deinit(); - } catch (Throwable t) { - // ignore - } + inst.exit(); } /** - * Quit to OS + * Quit to OS
+ * Destroy app & exit VM */ public void exit() { - deinit(); + destroy(); System.exit(0); } @@ -143,58 +102,36 @@ public class App { * * @return screen */ - public Screen getScreen() + public Screen getCurrentScreen() { return screen; } - /** - * Get screen size - * - * @return size - */ - public Coord getSize() + public void initialize() { - return new Coord(Display.getWidth(), Display.getHeight()); + Log.i("Initializing subsystems"); + initLock(); + initBus(); + initLogger(); + initDisplay(); + initSound(); + initInput(); } - private void init() throws LWJGLException + @Override + public void destroy() { - // initialize main logger - LogInstance li = Log.create("runtime", Paths.LOGS, 10); - li.enable(Config.LOGGING_ENABLED); - li.enableSysout(Config.LOG_TO_STDOUT); - - // initialize display - Display.setDisplayMode(windowDisplayMode = new DisplayMode(Const.WINDOW_SIZE_X, Const.WINDOW_SIZE_Y)); - Display.setResizable(true); - Display.setVSyncEnabled(true); - Display.setTitle(Const.TITLEBAR); - Display.create(); - - if (Config.START_IN_FS) { - switchFullscreen(); - Display.update(); - } - - // initialize inputs - Mouse.create(); - Keyboard.create(); - Keyboard.enableRepeatEvents(false); - - // initialize sound system - SoundManager.get().setListener(Const.LISTENER_POS); - SoundManager.get().setMasterVolume(1F); - - // start async screenshot trigger listener - (new ThreadScreenshotTrigger()).start(); + if (sounds != null) sounds.destroy(); + if (input != null) input.destroy(); + if (display != null) display.destroy(); } - private void start() throws LWJGLException + private void initLock() { + if (!Config.SINGLE_INSTANCE) return; if (!lockInstance()) { System.out.println("Working directory is locked.\nOnly one instance can run at a time."); @@ -211,207 +148,235 @@ public class App { exit(); return; } + } - init(); - mainLoop(); - deinit(); + + private boolean lockInstance() + { + final File lockFile = new File(Paths.WORKDIR, ".lock"); + try { + final RandomAccessFile randomAccessFile = new RandomAccessFile(lockFile, "rw"); + final FileLock fileLock = randomAccessFile.getChannel().tryLock(); + if (fileLock != null) { + Runtime.getRuntime().addShutdownHook(new Thread() { + + @Override + public void run() + { + try { + fileLock.release(); + randomAccessFile.close(); + lockFile.delete(); + } catch (Exception e) { + System.err.println("Unable to remove lock file."); + e.printStackTrace(); + } + } + }); + return true; + } + } catch (Exception e) { + System.err.println("Unable to create and/or lock file."); + e.printStackTrace(); + } + return false; } - private void deinit() + /** + * initialize inputs + */ + private void initBus() { - Display.destroy(); - Mouse.destroy(); - Keyboard.destroy(); - SoundManager.get().destroy(); - AL.destroy(); + events = new MessageBus(); + events.addSubscriber(this); + } + + + /** + * initialize sound system + */ + private void initSound() + { + sounds = new SoundSystem(); + sounds.setMasterVolume(1); + } + + + /** + * initialize inputs + */ + private void initInput() + { + input = new InputSystem(); + + input.bindKeyStroke(new KeyStroke(Keyboard.KEY_F2), new Runnable() { + + @Override + public void run() + { + Log.f3("F2, taking screenshot."); + scheduledScreenshot = true; + } + }); + + input.bindKeyStroke(new KeyStroke(false, Keyboard.KEY_F11), new Runnable() { + + @Override + public void run() + { + Log.f3("F11, toggling fullscreen."); + display.switchFullscreen(); + } + }); + + input.bindKeyStroke(new KeyStroke(Keyboard.KEY_LCONTROL, Keyboard.KEY_Q), new Runnable() { + + @Override + public void run() + { + Log.f3("CTRL+Q, shutting down."); + exit(); + } + }); + } + + + /** + * initialize display + */ + private void initDisplay() + { + display = new DisplaySystem(); + display.createMainWindow(Const.WINDOW_W, Const.WINDOW_H, true, Config.START_IN_FS, Const.TITLEBAR); + display.setTargetFps(Const.FPS_RENDER); + } + + + /** + * initialize main logger + */ + private void initLogger() + { + LogInstance li = Log.create("runtime", Paths.LOGS, 10); + li.enable(Config.LOGGING_ENABLED); + li.enableSysout(Config.LOG_TO_STDOUT); + } + + + private void start() + { + initialize(); + mainLoop(); + exit(); } /** timer */ private TimerDelta timerRender; private TimerInterpolating timerGui; - private int timerAfterResize = 0; - private void mainLoop() { screen = new ScreenSplash(); - screen.init(); + screen.setActive(true); timerRender = new TimerDelta(); timerGui = new TimerInterpolating(Const.FPS_GUI_UPDATE); - while (!Display.isCloseRequested()) { - glLoadIdentity(); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + while (!display.isCloseRequested()) { + display.beginFrame(); + // gui update timerGui.sync(); - int ticks = timerGui.getSkipped(); - if (ticks >= 1) { - screen.updateGui(); + input.poll(); timerGui.startNewFrame(); } - currentDelta = timerRender.getDelta(); + double delta = timerRender.getDelta(); - // RENDER - screen.render(currentDelta); - SoundManager.get().update(currentDelta); + sounds.update(delta); - Display.update(); + // Screen + screen.update(delta); if (scheduledScreenshot) { takeScreenshot(); scheduledScreenshot = false; } - if (Keys.justPressed(Keyboard.KEY_F11)) { - Log.f2("F11, toggle fullscreen."); - switchFullscreen(); - screen.onFullscreenChange(); - Keys.destroyChangeState(Keyboard.KEY_F11); - } - - if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) { - if (Keyboard.isKeyDown(Keyboard.KEY_Q)) { - Log.f2("Ctrl+Q, force quit."); - Keys.destroyChangeState(Keyboard.KEY_Q); - exit(); - return; - } - -// if (Keyboard.isKeyDown(Keyboard.KEY_M)) { -// Log.f2("Ctrl+M, go to main menu."); -// Keys.destroyChangeState(Keyboard.KEY_M); -// replaceScreen(new ScreenMenuMain()); -// } - - if (Keyboard.isKeyDown(Keyboard.KEY_F)) { - Log.f2("Ctrl+F, switch fullscreen."); - Keys.destroyChangeState(Keyboard.KEY_F); - switchFullscreen(); - screen.onFullscreenChange(); - } - } - - if (Display.wasResized()) { - screen.onWindowResize(); - timerAfterResize = 0; - } else { // ensure screen has even size - timerAfterResize++; - if (timerAfterResize > Const.FPS_GUI_UPDATE * 0.3) { - timerAfterResize = 0; - int x = Display.getX(); - int y = Display.getY(); - - int w = Display.getWidth(); - int h = Display.getHeight(); - if (w % 2 != 0 || h % 2 != 0) { - try { - Display.setDisplayMode(windowDisplayMode = new DisplayMode(w - w % 2, h - h % 2)); - screen.onWindowResize(); - Display.setLocation(x, y); - } catch (LWJGLException e) { - e.printStackTrace(); - } - } - } - } - - try { - Display.sync(Const.FPS_RENDER); - } catch (Throwable t) { - Log.e("Your graphics card driver does not support fullscreen properly.", t); - - try { - Display.setDisplayMode(windowDisplayMode); - } catch (LWJGLException e) { - Log.e("Error going back from corrupted fullscreen."); - onCrash(e); - } - } + display.endFrame(); } } -// UPDATE LOOP END - /** * Do take a screenshot */ public void takeScreenshot() { - //Effects.play("gui.screenshot"); + sounds.getEffect("gui.shutter").play(1); + Utils.runAsThread(new TaskTakeScreenshot()); + } + - glReadBuffer(GL_FRONT); - int width = Display.getDisplayMode().getWidth(); - int height = Display.getDisplayMode().getHeight(); - int bpp = 4; // Assuming a 32-bit display with a byte each for red, green, blue, and alpha. - ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + // + // static accessors + // - (new ThreadSaveScreenshot(buffer, width, height, bpp)).start(); + /** + * @return sound system of the running instance + */ + public static SoundSystem soundsys() + { + return inst.sounds; } /** - * Replace screen - * - * @param newScreen new screen + * @return input system of the running instance */ - public void replaceScreen(Screen newScreen) + public static InputSystem input() { - screen = newScreen; - screen.init(); + return inst.input; } /** - * Replace screen, don't init it - * - * @param newScreen new screen + * @return display system of the running instance */ - public void replaceScreenNoInit(Screen newScreen) + public static DisplaySystem disp() { - screen = newScreen; + return inst.display; } /** - * Toggle FS if possible + * @return event bus of the running instance */ - public void switchFullscreen() + public static MessageBus msgbus() { - try { - if (!Display.isFullscreen()) { - Log.f3("Entering fullscreen."); - // save window resize - windowDisplayMode = new DisplayMode(Display.getWidth(), Display.getHeight()); - - Display.setDisplayMode(Display.getDesktopDisplayMode()); - Display.setFullscreen(true); - Display.update(); -// -// -// DisplayMode mode = Display.getDesktopDisplayMode(); //findDisplayMode(WIDTH, HEIGHT); -// Display.setDisplayModeAndFullscreen(mode); - } else { - Log.f3("Leaving fullscreen."); - Display.setDisplayMode(windowDisplayMode); - Display.update(); - } - } catch (Throwable t) { - Log.e("Failed to toggle fullscreen mode.", t); - try { - Display.setDisplayMode(windowDisplayMode); - Display.update(); - } catch (Throwable t1) { - onCrash(t1); - } - } + return inst.events; } + + + /** + * @return screen of the running instance + */ + public static Screen screen() + { + return inst.getCurrentScreen(); + } + + + public static boolean broadcast(Object message) + { + boolean was = msgbus().broadcast(message); + if (!was) Log.w("Message not accepted by any channel: " + message); + return was; + } + } diff --git a/src/mightypork/rogue/Const.java b/src/mightypork/rogue/Const.java index ca7da92..1500e1b 100644 --- a/src/mightypork/rogue/Const.java +++ b/src/mightypork/rogue/Const.java @@ -1,9 +1,6 @@ package mightypork.rogue; -import mightypork.utils.math.coord.Coord; - - /** * Application constants * @@ -18,12 +15,10 @@ public class Const { public static final String TITLEBAR = APP_NAME + " v." + VERSION; // AUDIO - public static final Coord LISTENER_POS = Coord.ZERO; - public static final int FPS_RENDER = 200; // max public static final long FPS_GUI_UPDATE = 60; // INITIAL WINDOW SIZE - public static final int WINDOW_SIZE_X = 1024; - public static final int WINDOW_SIZE_Y = 768; + public static final int WINDOW_W = 1024; + public static final int WINDOW_H = 768; } diff --git a/src/mightypork/rogue/CrashHandler.java b/src/mightypork/rogue/CrashHandler.java index fe9aa55..147a50c 100644 --- a/src/mightypork/rogue/CrashHandler.java +++ b/src/mightypork/rogue/CrashHandler.java @@ -9,6 +9,7 @@ public class CrashHandler implements UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { + e.printStackTrace(); App.onCrash(e); } diff --git a/src/mightypork/rogue/Screen.java b/src/mightypork/rogue/Screen.java deleted file mode 100644 index e726810..0000000 --- a/src/mightypork/rogue/Screen.java +++ /dev/null @@ -1,200 +0,0 @@ -package mightypork.rogue; - - -import static org.lwjgl.opengl.GL11.*; - -import java.util.Random; - -import mightypork.rogue.animations.GUIRenderer; -import mightypork.rogue.input.InputHandler; -import mightypork.rogue.input.Keys; -import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.Vec; - -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; - - -/** - * Screen class.
- * Screen animates 3D world, while contained panels render 2D overlays, process - * inputs and run the game logic. - * - * @author MightyPork - */ -public abstract class Screen implements GUIRenderer, InputHandler { - - /** RNG */ - protected static Random rand = new Random(); - - - /** - * handle fullscreen change - */ - @Override - public final void onFullscreenChange() - { - onWindowResize(); - onViewportChanged(); - } - - - protected abstract void onViewportChanged(); - - - /** - * handle window resize. - */ - public final void onWindowResize() - { - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - Coord s = App.inst.getSize(); - - glViewport(0, 0, s.xi(), s.yi()); - - glOrtho(0, s.x, 0, s.y, -1000, 1000); - - glMatrixMode(GL_MODELVIEW); - - glLoadIdentity(); - - glEnable(GL_BLEND); - //glDisable(GL_DEPTH_TEST); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_TEXTURE_2D); - } - - - /** - * Initialize screen - */ - public void init() - { - onWindowResize(); - - initScreen(); - - // SETUP LIGHTS - glDisable(GL_LIGHTING); - - // OTHER SETTINGS - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - glClearDepth(1f); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - - glEnable(GL_NORMALIZE); - - glShadeModel(GL_SMOOTH); - glDisable(GL_TEXTURE_2D); - } - - - /** - * Here you can initialize the screen. - */ - public abstract void initScreen(); - - - /** - * Update tick - */ - @Override - public final void updateGui() - { - Mouse.poll(); - Keyboard.poll(); - checkInputEvents(); - - onGuiUpdate(); - } - - - protected abstract void onGuiUpdate(); - - - /** - * Render screen - * - * @param delta delta time (position between two update ticks, to allow - * super-smooth animations) - */ - @Override - public final void render(double delta) - { - glPushAttrib(GL_ENABLE_BIT); - - // draw the directly rendered 3D stuff - render3D(); - - glPopAttrib(); - } - - - protected abstract void render3D(); - - - /** - * Check input events and process them. - */ - private final void checkInputEvents() - { - while (Keyboard.next()) { - int key = Keyboard.getEventKey(); - boolean down = Keyboard.getEventKeyState(); - char c = Keyboard.getEventCharacter(); - Keys.onKey(key, down); - onKey(key, c, down); - } - while (Mouse.next()) { - int button = Mouse.getEventButton(); - boolean down = Mouse.getEventButtonState(); - Coord delta = new Coord(Mouse.getEventDX(), Mouse.getEventDY()); - Coord pos = new Coord(Mouse.getEventX(), Mouse.getEventY()); - int wheeld = Mouse.getEventDWheel(); - - onMouseButton(button, down, wheeld, pos, delta); - } - - int xc = Mouse.getX(); - int yc = Mouse.getY(); - int xd = Mouse.getDX(); - int yd = Mouse.getDY(); - int wd = Mouse.getDWheel(); - - if (Math.abs(xd) > 0 || Math.abs(yd) > 0 || Math.abs(wd) > 0) { - onMouseMove(new Coord(xc, yc), new Vec(xd, yd), wd); - } - - handleKeyStates(); - } - - - @Override - public abstract void onKey(int key, char c, boolean down); - - - @Override - public abstract void onMouseButton(int button, boolean down, int wheeld, Coord pos, Coord delta); - - - @Override - public abstract void handleKeyStates(); - - - @Override - public abstract void onMouseMove(Coord coord, Vec vec, int wd); - - - /** - * Render background 2D (all is ready for rendering) - * - * @param delta delta time - */ - protected abstract void render2D(double delta); - -} diff --git a/src/mightypork/rogue/animations/EmptyAnimator.java b/src/mightypork/rogue/animations/EmptyAnimator.java deleted file mode 100644 index feb9f75..0000000 --- a/src/mightypork/rogue/animations/EmptyAnimator.java +++ /dev/null @@ -1,30 +0,0 @@ -package mightypork.rogue.animations; - - -/** - * Empty animation (no effect) - * - * @author MightyPork - */ -public class EmptyAnimator implements GUIRenderer { - - /** - * New empty animation - */ - public EmptyAnimator() {} - - - @Override - public void updateGui() - {} - - - @Override - public void render(double delta) - {} - - - @Override - public void onFullscreenChange() - {} -} diff --git a/src/mightypork/rogue/animations/GUIRenderer.java b/src/mightypork/rogue/animations/GUIRenderer.java deleted file mode 100644 index 112c5cd..0000000 --- a/src/mightypork/rogue/animations/GUIRenderer.java +++ /dev/null @@ -1,13 +0,0 @@ -package mightypork.rogue.animations; - - -public interface GUIRenderer { - - public void updateGui(); - - - public void render(double delta); - - - public void onFullscreenChange(); -} diff --git a/src/mightypork/rogue/display/DisplaySystem.java b/src/mightypork/rogue/display/DisplaySystem.java new file mode 100644 index 0000000..5c07564 --- /dev/null +++ b/src/mightypork/rogue/display/DisplaySystem.java @@ -0,0 +1,200 @@ +package mightypork.rogue.display; + + +import static org.lwjgl.opengl.GL11.*; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.DisplayMode; + +import mightypork.rogue.App; +import mightypork.rogue.Const; +import mightypork.rogue.display.events.ScreenChangeEvent; +import mightypork.utils.logging.Log; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.patterns.Initializable; +import mightypork.utils.time.Updateable; + + +public class DisplaySystem implements Initializable, Destroyable { + + private boolean initialized; + + private DisplayMode windowDisplayMode; + private int targetFps; + + + public DisplaySystem() { + initialize(); + } + + @Override + public void initialize() + { + if(initialized) return; + + initChannels(); + + initialized = true; + } + + /** + * Initialize event channels + */ + private void initChannels() + { + App.msgbus().registerMessageType(ScreenChangeEvent.class, ScreenChangeEvent.Listener.class); + } + + @Override + public void destroy() + { + Display.destroy(); + } + + + public void setTargetFps(int fps) + { + this.targetFps = fps; + } + + + public void createMainWindow(int width, int height, boolean resizable, boolean fullscreen, String title) + { + try { + Display.setDisplayMode(windowDisplayMode = new DisplayMode(width, height)); + Display.setResizable(resizable); + Display.setVSyncEnabled(true); + Display.setTitle(title); + Display.create(); + + if (fullscreen) { + switchFullscreen(); + Display.update(); + } + } catch (LWJGLException e) { + throw new RuntimeException("Could not initialize screen", e); + } + } + + + /** + * Toggle FS if possible + */ + public void switchFullscreen() + { + try { + + if (!Display.isFullscreen()) { + Log.f3("Entering fullscreen."); + // save window resize + windowDisplayMode = new DisplayMode(Display.getWidth(), Display.getHeight()); + + Display.setDisplayMode(Display.getDesktopDisplayMode()); + Display.setFullscreen(true); + Display.update(); + } else { + Log.f3("Leaving fullscreen."); + Display.setDisplayMode(windowDisplayMode); + Display.update(); + } + + App.broadcast(new ScreenChangeEvent(true, Display.isFullscreen(), getSize())); + + } catch (Throwable t) { + Log.e("Failed to toggle fullscreen mode.", t); + try { + Display.setDisplayMode(windowDisplayMode); + Display.update(); + } catch (Throwable t1) { + throw new RuntimeException("Failed to revert failed fullscreen toggle.", t1); + } + } + } + + + public BufferedImage takeScreenshot() + { + glReadBuffer(GL_FRONT); + int width = Display.getDisplayMode().getWidth(); + int height = Display.getDisplayMode().getHeight(); + int bpp = 4; // Assuming a 32-bit display with a byte each for red, green, blue, and alpha. + ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + // convert to a buffered image + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int i = (x + (width * y)) * bpp; + int r = buffer.get(i) & 0xFF; + int g = buffer.get(i + 1) & 0xFF; + int b = buffer.get(i + 2) & 0xFF; + image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); + } + } + + return image; + } + + + /** + * @return true if close was requested (i.e. click on cross) + */ + public boolean isCloseRequested() + { + return Display.isCloseRequested(); + } + + + /** + * Get fullscreen state + * + * @return is fullscreen + */ + public boolean isFullscreen() + { + return Display.isFullscreen(); + } + + + /** + * Get screen size + * + * @return size + */ + public Coord getSize() + { + return new Coord(Display.getWidth(), Display.getHeight()); + } + + + /** + * Start a OpenGL frame + */ + public void beginFrame() + { + if(Display.wasResized()) { + App.broadcast(new ScreenChangeEvent(false, Display.isFullscreen(), getSize())); + } + + glLoadIdentity(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + + /** + * End an OpenGL frame, flip buffers, sync to fps. + */ + public void endFrame() + { + Display.update(false); // don't poll input devices + Display.sync(targetFps); + } +} diff --git a/src/mightypork/rogue/display/Screen.java b/src/mightypork/rogue/display/Screen.java new file mode 100644 index 0000000..5d93e24 --- /dev/null +++ b/src/mightypork/rogue/display/Screen.java @@ -0,0 +1,214 @@ +package mightypork.rogue.display; + + +import static org.lwjgl.opengl.GL11.*; + +import java.util.Random; + +import mightypork.rogue.App; +import mightypork.rogue.display.events.ScreenChangeEvent; +import mightypork.rogue.input.KeyBinder; +import mightypork.rogue.input.KeyBindingPool; +import mightypork.rogue.input.KeyStroke; +import mightypork.rogue.input.events.KeyboardEvent; +import mightypork.rogue.input.events.MouseMotionEvent; +import mightypork.rogue.input.events.MouseButtonEvent; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.patterns.Initializable; +import mightypork.utils.time.Updateable; + + +/** + * Screen class.
+ * Screen animates 3D world, while contained panels render 2D overlays, process + * inputs and run the game logic. + * + * @author MightyPork + */ +public abstract class Screen implements KeyBinder, Updateable, Initializable, KeyboardEvent.Listener, MouseMotionEvent.Listener, MouseButtonEvent.Listener, ScreenChangeEvent.Listener { + + private KeyBindingPool keybindings = new KeyBindingPool(); + + private boolean active; + + + public Screen() { + initialize(); + } + + + @Override + public void bindKeyStroke(KeyStroke stroke, Runnable task) + { + keybindings.bindKeyStroke(stroke, task); + } + + + @Override + public void unbindKeyStroke(KeyStroke stroke) + { + keybindings.unbindKeyStroke(stroke); + } + + + /** + * Prepare for being shown + * @param shown true to show, false to hide + */ + public final void setActive(boolean shown) + { + if (shown) { + active = true; + setupGraphics(); + setupViewport(); + onSizeChanged(App.disp().getSize()); + onEnter(); + App.msgbus().addSubscriber(this); + } else { + active = false; + onLeave(); + App.msgbus().removeSubscriber(this); + } + } + + + private void setupGraphics() + { + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_LIGHTING); + + glClearDepth(1f); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + glEnable(GL_NORMALIZE); + + glShadeModel(GL_SMOOTH); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDisable(GL_TEXTURE_2D); + + setupViewport(); + } + + + private void setupViewport() + { + // fix projection for changed size + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + Coord s = App.disp().getSize(); + glViewport(0, 0, s.xi(), s.yi()); + glOrtho(0, s.x, 0, s.y, -1000, 1000); + + // back to modelview + glMatrixMode(GL_MODELVIEW); + } + + + /** + * Initialize screen layout and key bindings.
+ * Called when the screen is created, not when it comes to front. For that, use onEnter(). + */ + @Override + public abstract void initialize(); + + /** + * Called when the screen becomes active + */ + protected abstract void onEnter(); + + + /** + * Called when the screen is no longer active + */ + protected abstract void onLeave(); + + + /** + * Update GUI for new screen size + * + * @param size screen size + */ + protected abstract void onSizeChanged(Coord size); + + + /** + * Render screen contents (context is ready for 2D rendering) + */ + protected abstract void renderScreen(); + + + /** + * Update animations and timing + * + * @param delta time elapsed + */ + protected abstract void updateScreen(double delta); + + + /** + * Render screen + */ + private final void renderBegin() + { + glPushAttrib(GL_ENABLE_BIT); + glPushMatrix(); + } + + + /** + * Render screen + */ + private final void renderEnd() + { + glPopAttrib(); + glPopMatrix(); + } + + + @Override + public final void update(double delta) + { + if (!isActive()) return; + + updateScreen(delta); + renderBegin(); + renderScreen(); + renderEnd(); + }; + + + /** + * @return true if screen is the curretn screen + */ + protected final boolean isActive() + { + return active; + } + + + @Override + public final void receive(ScreenChangeEvent event) + { + if (!isActive()) return; + + setupViewport(); + + onSizeChanged(event.getScreenSize()); + } + + + @Override + public final void receive(KeyboardEvent event) + { + if (!isActive()) return; + keybindings.receive(event); + } + +} diff --git a/src/mightypork/rogue/display/ScreenSplash.java b/src/mightypork/rogue/display/ScreenSplash.java new file mode 100644 index 0000000..c93f1b5 --- /dev/null +++ b/src/mightypork/rogue/display/ScreenSplash.java @@ -0,0 +1,168 @@ +package mightypork.rogue.display; + + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; + +import mightypork.rogue.App; +import mightypork.rogue.input.KeyStroke; +import mightypork.rogue.input.events.MouseButtonEvent; +import mightypork.rogue.input.events.MouseMotionEvent; +import mightypork.rogue.util.RenderUtils; +import mightypork.utils.math.Polar; +import mightypork.utils.math.color.RGB; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.math.easing.Easing; +import mightypork.utils.time.AnimDouble; +import mightypork.utils.time.AnimDoubleDeg; + + +public class ScreenSplash extends Screen { + + private AnimDoubleDeg degAnim = new AnimDoubleDeg(0, Easing.SINE); + + //@formatter:off + private AnimDouble[] anims = new AnimDouble[] { + new AnimDouble(0, Easing.NONE), + new AnimDouble(0, Easing.LINEAR), + + new AnimDouble(0, Easing.QUADRATIC_IN), + new AnimDouble(0, Easing.QUADRATIC_OUT), + new AnimDouble(0, Easing.QUADRATIC), + + new AnimDouble(0, Easing.CUBIC_IN), + new AnimDouble(0, Easing.CUBIC_OUT), + new AnimDouble(0, Easing.CUBIC), + + new AnimDouble(0, Easing.QUADRATIC_IN), + new AnimDouble(0, Easing.QUADRATIC_OUT), + new AnimDouble(0, Easing.QUADRATIC), + + new AnimDouble(0, Easing.QUINTIC_IN), + new AnimDouble(0, Easing.QUINTIC_OUT), + new AnimDouble(0, Easing.QUINTIC_IN_OUT), + + new AnimDouble(0, Easing.EXPO_IN), + new AnimDouble(0, Easing.EXPO_OUT), + new AnimDouble(0, Easing.EXPO), + + new AnimDouble(0, Easing.SINE_IN), + new AnimDouble(0, Easing.SINE_OUT), + new AnimDouble(0, Easing.SINE), + + new AnimDouble(0, Easing.CIRC_IN), + new AnimDouble(0, Easing.CIRC_OUT), + new AnimDouble(0, Easing.CIRC), + }; + //@formatter:on + + @Override + public void initialize() + { + bindKeyStroke(new KeyStroke(Keyboard.KEY_RIGHT), new Runnable() { + + @Override + public void run() + { + for (AnimDouble a : anims) { + a.animate(0, 1, 3); + } + } + }); + + bindKeyStroke(new KeyStroke(Keyboard.KEY_LEFT), new Runnable() { + + @Override + public void run() + { + for (AnimDouble a : anims) { + a.animate(1, 0, 3); + } + } + }); + } + + + @Override + protected void renderScreen() + { + double screenH = Display.getHeight(); + double screenW = Display.getWidth(); + double perBoxH = screenH / anims.length; + double padding = perBoxH*0.1; + double boxSide = perBoxH-padding*2; + + for (int i = 0; i < anims.length; i++) { + AnimDouble a = anims[i]; + + RenderUtils.setColor(i%3==0?RGB.GREEN:RGB.BLUE); + RenderUtils.quadSize( + padding + a.getCurrentValue() * (screenW - perBoxH - padding*2), + screenH - perBoxH * i - perBoxH + padding, + boxSide, + boxSide + ); + } + + RenderUtils.setColor(RGB.YELLOW); + RenderUtils.translate(new Coord(Display.getWidth() / 2, Display.getHeight() / 2)); + RenderUtils.rotateZ(degAnim.getCurrentValue()); + RenderUtils.quadSize(-10, -10, 20, 200); + } + + + @Override + public void receive(MouseMotionEvent event) + { + } + + + @Override + public void receive(MouseButtonEvent event) + { + if(event.isDown()) { + Coord vec = App.disp().getSize().half().vecTo(event.getPos()); + + Polar p = Polar.fromCoord(vec); + + degAnim.fadeTo(p.getAngleDeg() - 90, 0.2); + } + } + + + @Override + protected void onEnter() + { + // TODO Auto-generated method stub + + } + + + @Override + protected void onLeave() + { + // TODO Auto-generated method stub + + } + + + @Override + protected void onSizeChanged(Coord size) + { + // TODO Auto-generated method stub + + } + + + @Override + protected void updateScreen(double delta) + { + degAnim.update(delta); + + for (AnimDouble a : anims) { + a.update(delta); + } + } + +} diff --git a/src/mightypork/rogue/display/events/ScreenChangeEvent.java b/src/mightypork/rogue/display/events/ScreenChangeEvent.java new file mode 100644 index 0000000..b3e881e --- /dev/null +++ b/src/mightypork/rogue/display/events/ScreenChangeEvent.java @@ -0,0 +1,50 @@ +package mightypork.rogue.display.events; + + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.subscription.Handleable; + + +public class ScreenChangeEvent implements Handleable { + + private boolean fullscreen; + private Coord screenSize; + private boolean fsChanged; + + + public ScreenChangeEvent(boolean fsChanged, boolean fullscreen, Coord size) { + this.fullscreen = fullscreen; + this.screenSize = size; + this.fsChanged = fsChanged; + } + + + public boolean isFullscreen() + { + return fullscreen; + } + + + public boolean fullscreenChanged() + { + return fsChanged; + } + + + public Coord getScreenSize() + { + return screenSize; + } + + + @Override + public void handleBy(Listener handler) + { + handler.receive(this); + } + + public interface Listener { + + public void receive(ScreenChangeEvent event); + } +} diff --git a/src/mightypork/rogue/fonts/FontManager.java b/src/mightypork/rogue/fonts/FontManager.java index 7185479..77bf1f3 100644 --- a/src/mightypork/rogue/fonts/FontManager.java +++ b/src/mightypork/rogue/fonts/FontManager.java @@ -112,96 +112,96 @@ public class FontManager { OUTLINE; } - /** - * Preloaded font identifier [name, size, style] - * - * @author MightyPork - */ - public static class FontId { - - /** font size (pt) */ - public float size = 24; - /** font name, registered with registerFile */ - public String name = ""; - /** font style. The given style must be in a file. */ - public Style style; - - /** Set of glyphs in this ID */ - public String glyphs = ""; - - /** Index for faster comparision of glyph ids. */ - public int glyphset_id = 0; - - - /** - * Preloaded font identifier - * - * @param name font name (registerFile) - * @param size font size (pt) - * @param style font style - * @param glyphs glyphs to load - */ - public FontId(String name, double size, Style style, String glyphs) { - this.name = name; - this.size = (float) size; - this.style = style; - - if (glyphs.equals(Glyphs.basic)) { - glyphset_id = 1; - } else if (glyphs.equals(Glyphs.alnum)) { - glyphset_id = 2; - } else if (glyphs.equals(Glyphs.basic_text)) { - glyphset_id = 3; - } else if (glyphs.equals(Glyphs.numbers)) { - glyphset_id = 4; - } else if (glyphs.equals(Glyphs.alpha)) { - glyphset_id = 5; - } else if (glyphs.equals(Glyphs.all)) { - glyphset_id = 6; - } else if (glyphs.equals(Glyphs.alnum_extra)) { - glyphset_id = 7; - } else if (glyphs.equals(Glyphs.signs)) { - glyphset_id = 8; - } else if (glyphs.equals(Glyphs.signs_extra)) { - glyphset_id = 9; - } else { - this.glyphs = glyphs; - } - } - - - @Override - public boolean equals(Object obj) - { - if (obj == null) return false; - if (!(obj.getClass().isAssignableFrom(getClass()))) return false; - if (obj instanceof FontId) { - if (obj == this) return true; - FontId id2 = ((FontId) obj); - boolean flag = true; - flag &= id2.size == size; - flag &= id2.name.equals(name); - flag &= id2.style == style; - flag &= ((id2.glyphset_id != -1 && id2.glyphset_id == glyphset_id) || id2.glyphs.equals(glyphs)); - return flag; - } - return false; - } - - - @Override - public int hashCode() - { - return (new Float(size).hashCode()) ^ name.hashCode() ^ style.hashCode() ^ glyphset_id; - } - - - @Override - public String toString() - { - return "[" + name + ", " + size + ", " + style + (glyphset_id > 0 ? ", g=" + glyphset_id : ", g=custom") + "]"; - } - } +// /** +// * Preloaded font identifier [name, size, style] +// * +// * @author MightyPork +// */ +// public static class FontId { +// +// /** font size (pt) */ +// public float size = 24; +// /** font name, registered with registerFile */ +// public String name = ""; +// /** font style. The given style must be in a file. */ +// public Style style; +// +// /** Set of glyphs in this ID */ +// public String glyphs = ""; +// +// /** Index for faster comparision of glyph ids. */ +// public int glyphset_id = 0; +// +// +// /** +// * Preloaded font identifier +// * +// * @param name font name (registerFile) +// * @param size font size (pt) +// * @param style font style +// * @param glyphs glyphs to load +// */ +// public FontId(String name, double size, Style style, String glyphs) { +// this.name = name; +// this.size = (float) size; +// this.style = style; +// +// if (glyphs.equals(Glyphs.basic)) { +// glyphset_id = 1; +// } else if (glyphs.equals(Glyphs.alnum)) { +// glyphset_id = 2; +// } else if (glyphs.equals(Glyphs.basic_text)) { +// glyphset_id = 3; +// } else if (glyphs.equals(Glyphs.numbers)) { +// glyphset_id = 4; +// } else if (glyphs.equals(Glyphs.alpha)) { +// glyphset_id = 5; +// } else if (glyphs.equals(Glyphs.all)) { +// glyphset_id = 6; +// } else if (glyphs.equals(Glyphs.alnum_extra)) { +// glyphset_id = 7; +// } else if (glyphs.equals(Glyphs.signs)) { +// glyphset_id = 8; +// } else if (glyphs.equals(Glyphs.signs_extra)) { +// glyphset_id = 9; +// } else { +// this.glyphs = glyphs; +// } +// } +// +// +// @Override +// public boolean equals(Object obj) +// { +// if (obj == null) return false; +// if (!(obj.getClass().isAssignableFrom(getClass()))) return false; +// if (obj instanceof FontId) { +// if (obj == this) return true; +// FontId id2 = ((FontId) obj); +// boolean flag = true; +// flag &= id2.size == size; +// flag &= id2.name.equals(name); +// flag &= id2.style == style; +// flag &= ((id2.glyphset_id != -1 && id2.glyphset_id == glyphset_id) || id2.glyphs.equals(glyphs)); +// return flag; +// } +// return false; +// } +// +// +// @Override +// public int hashCode() +// { +// return (new Float(size).hashCode()) ^ name.hashCode() ^ style.hashCode() ^ glyphset_id; +// } +// +// +// @Override +// public String toString() +// { +// return "[" + name + ", " + size + ", " + style + (glyphset_id > 0 ? ", g=" + glyphset_id : ", g=custom") + "]"; +// } +// } /** * Group of styles of one font. @@ -255,7 +255,7 @@ public class FontManager { */ public static LoadedFont loadFont(String name, double size, Style style, String glyphs) { - return loadFont(name, size, style, glyphs, 9, 8, Coord.ONE, 0, 0); + return loadFont(name, size, style, glyphs, 9, 8, Coord.one(), 0, 0); } diff --git a/src/mightypork/rogue/fonts/LoadedFont.java b/src/mightypork/rogue/fonts/LoadedFont.java index fc79201..f459836 100644 --- a/src/mightypork/rogue/fonts/LoadedFont.java +++ b/src/mightypork/rogue/fonts/LoadedFont.java @@ -21,7 +21,6 @@ import mightypork.rogue.Config; import mightypork.utils.logging.Log; import mightypork.utils.math.color.RGB; import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.CoordI; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; @@ -598,38 +597,12 @@ public class LoadedFont { } - /** - * Draw string with font. - * - * @param pos coord - * @param text text to draw - * @param color render color - * @param align (-1,0,1) - */ - public void draw(CoordI pos, String text, RGB color, int align) - { - drawString(pos.x, pos.y, text, 1, 1, color, align); - } - - public void drawFuzzy(Coord pos, String text, int align, RGB textColor, RGB blurColor, int blurSize) { drawFuzzy(pos, text, align, textColor, blurColor, blurSize, true); } - public void drawFuzzy(CoordI pos, String text, int align, RGB textColor, RGB blurColor, int blurSize) - { - drawFuzzy(pos.toCoord(), text, align, textColor, blurColor, blurSize, true); - } - - - public void drawFuzzy(CoordI pos, String text, int align, RGB textColor, RGB blurColor, int blurSize, boolean smooth) - { - drawFuzzy(pos.toCoord(), text, align, textColor, blurColor, blurSize, smooth); - } - - public void drawFuzzy(Coord pos, String text, int align, RGB textColor, RGB blurColor, int blurSize, boolean smooth) { glPushMatrix(); diff --git a/src/mightypork/rogue/input/InputHandler.java b/src/mightypork/rogue/input/InputHandler.java deleted file mode 100644 index 8e9154c..0000000 --- a/src/mightypork/rogue/input/InputHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -package mightypork.rogue.input; - - -import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.Vec; - - -/** - * Input event handler - * - * @author MightyPork - */ -public interface InputHandler { - - /** - * Called each update tick, if the mouse position was changed. - * - * @param pos mouse position - * @param move mouse motion - * @param wheelDelta mouse wheel delta - */ - public void onMouseMove(Coord pos, Vec move, int wheelDelta); - - - /** - * Mouse event handler. - * - * @param button button which caused this event - * @param down true = down, false = up - * @param wheelDelta number of steps the wheel turned since last event - * @param pos mouse position - * @param deltaPos delta mouse position - */ - public void onMouseButton(int button, boolean down, int wheelDelta, Coord pos, Coord deltaPos); - - - /** - * Key event handler. - * - * @param key key index, constant Keyboard.KEY_??? - * @param c character typed, if any - * @param down true = down, false = up - */ - public void onKey(int key, char c, boolean down); - - - /** - * In this method screen can handle static inputs, that is: - * Keyboard.isKeyDown, Mouse.isButtonDown etc. - */ - public void handleKeyStates(); -} diff --git a/src/mightypork/rogue/input/InputSystem.java b/src/mightypork/rogue/input/InputSystem.java new file mode 100644 index 0000000..0bd3d6a --- /dev/null +++ b/src/mightypork/rogue/input/InputSystem.java @@ -0,0 +1,126 @@ +package mightypork.rogue.input; + + +import mightypork.rogue.App; +import mightypork.rogue.input.events.KeyboardEvent; +import mightypork.rogue.input.events.MouseButtonEvent; +import mightypork.rogue.input.events.MouseMotionEvent; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.patterns.Initializable; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; + + +public class InputSystem implements KeyBinder, Destroyable, Initializable { + + private boolean initialized; + + // listeners + private KeyBindingPool keybindings; + + + public InputSystem() { + initialize(); + } + + + @Override + public void initialize() + { + if (initialized) return; + + initDevices(); + + initChannels(); + + keybindings = new KeyBindingPool(); + + App.msgbus().addSubscriber(keybindings); + } + + + private void initDevices() + { + try { + Mouse.create(); + Keyboard.create(); + Keyboard.enableRepeatEvents(false); + } catch (LWJGLException e) { + throw new RuntimeException("Failed to initialize input devices.", e); + } + } + + + private void initChannels() + { + App.msgbus().registerMessageType(KeyboardEvent.class, KeyboardEvent.Listener.class); + App.msgbus().registerMessageType(MouseMotionEvent.class, MouseMotionEvent.Listener.class); + App.msgbus().registerMessageType(MouseButtonEvent.class, MouseButtonEvent.Listener.class); + } + + + @Override + public void destroy() + { + Mouse.destroy(); + Keyboard.destroy(); + } + + + @Override + public void bindKeyStroke(KeyStroke stroke, Runnable task) + { + keybindings.bindKeyStroke(stroke, task); + } + + + @Override + public void unbindKeyStroke(KeyStroke stroke) + { + keybindings.unbindKeyStroke(stroke); + } + + + /** + * Update inputs + */ + public final void poll() + { + Display.processMessages(); // redundant if Display.update() is called in main loop + Mouse.poll(); + Keyboard.poll(); + + while (Mouse.next()) { + onMouseEvent(); + } + + while (Keyboard.next()) { + onKeyEvent(); + } + } + + + private void onMouseEvent() + { + int button = Mouse.getEventButton(); + boolean down = Mouse.getEventButtonState(); + Coord pos = new Coord(Mouse.getEventX(), Mouse.getEventY()); + Coord move = new Coord(Mouse.getEventDX(), Mouse.getEventDY()); + int wheeld = Mouse.getEventDWheel(); + + if (button != -1 || wheeld != 0) App.broadcast(new MouseButtonEvent(pos, button, down, wheeld)); + if(!move.isZero()) App.broadcast(new MouseMotionEvent(pos, move)); + } + + + private void onKeyEvent() + { + int key = Keyboard.getEventKey(); + boolean down = Keyboard.getEventKeyState(); + char c = Keyboard.getEventCharacter(); + App.broadcast(new KeyboardEvent(key, c, down)); + } +} diff --git a/src/mightypork/rogue/input/KeyBinder.java b/src/mightypork/rogue/input/KeyBinder.java new file mode 100644 index 0000000..e1edb61 --- /dev/null +++ b/src/mightypork/rogue/input/KeyBinder.java @@ -0,0 +1,21 @@ +package mightypork.rogue.input; + + +public interface KeyBinder { + + /** + * Bind handler to a keystroke, replace current handler if any + * + * @param stroke trigger keystroke + * @param task handler + */ + abstract void bindKeyStroke(KeyStroke stroke, Runnable task); + + + /** + * Remove handler from a keystroke (id any) + * @param stroke stroke + */ + abstract void unbindKeyStroke(KeyStroke stroke); + +} diff --git a/src/mightypork/rogue/input/KeyBinding.java b/src/mightypork/rogue/input/KeyBinding.java new file mode 100644 index 0000000..8f31420 --- /dev/null +++ b/src/mightypork/rogue/input/KeyBinding.java @@ -0,0 +1,48 @@ +package mightypork.rogue.input; + + +import mightypork.rogue.input.events.KeyboardEvent; + + +public class KeyBinding implements KeyboardEvent.Listener { + + private KeyStroke keystroke; + private Runnable handler; + private boolean wasActive = false; + + + public KeyBinding(KeyStroke stroke, Runnable handler) { + this.keystroke = stroke; + this.handler = handler; + + wasActive = keystroke.isActive(); + } + + + public boolean matches(KeyStroke stroke) + { + return this.keystroke.equals(stroke); + } + + + public void setHandler(Runnable handler) + { + this.handler = handler; + } + + + @Override + public void receive(KeyboardEvent event) + { + // ignore unrelated events + if(!keystroke.getKeys().contains(event.getKey())) return; + + // run handler when event was met + if (keystroke.isActive() && !wasActive) { + handler.run(); + } + + wasActive = keystroke.isActive(); + } + +} diff --git a/src/mightypork/rogue/input/KeyBindingPool.java b/src/mightypork/rogue/input/KeyBindingPool.java new file mode 100644 index 0000000..9b71d0c --- /dev/null +++ b/src/mightypork/rogue/input/KeyBindingPool.java @@ -0,0 +1,68 @@ +package mightypork.rogue.input; + + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import mightypork.rogue.input.events.KeyboardEvent; +import mightypork.utils.logging.Log; + + +/** + * Key binding pool + * + * @author MightyPork + */ +public class KeyBindingPool implements KeyBinder, KeyboardEvent.Listener { + + private Set bindings = new HashSet(); + + + /** + * Bind handler to a keystroke, replace current handler if any + * + * @param stroke trigger keystroke + * @param task handler + */ + @Override + public void bindKeyStroke(KeyStroke stroke, Runnable task) + { + for (KeyBinding kb : bindings) { + if (kb.matches(stroke)) { + Log.w("Duplicate KeyBinding ("+stroke+"), using newest handler."); + kb.setHandler(task); + return; + } + } + + bindings.add(new KeyBinding(stroke, task)); + } + + + /** + * Remove handler from keystroke (id any) + * @param stroke stroke + */ + @Override + public void unbindKeyStroke(KeyStroke stroke) + { + Iterator iter = bindings.iterator(); + + while (iter.hasNext()) { + KeyBinding kb = iter.next(); + if (kb.matches(stroke)) { + iter.remove(); + return; + } + } + } + + @Override + public void receive(KeyboardEvent event) + { + for(KeyBinding kb: bindings) { + kb.receive(event); + } + } +} diff --git a/src/mightypork/rogue/input/KeyStroke.java b/src/mightypork/rogue/input/KeyStroke.java new file mode 100644 index 0000000..4faea8e --- /dev/null +++ b/src/mightypork/rogue/input/KeyStroke.java @@ -0,0 +1,114 @@ +package mightypork.rogue.input; + + +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.lwjgl.input.Keyboard; + + +public class KeyStroke { + + private Set keys = new LinkedHashSet(); + private boolean down = true; + + + /** + * KeyStroke + * + * @param down true for falling edge, up for rising edge + * @param keys keys that must be pressed + */ + public KeyStroke(boolean down, int... keys) { + this.down = down; + for (int k : keys) { + this.keys.add(k); + } + } + + + /** + * Falling edge keystroke + * + * @param keys + */ + public KeyStroke(int... keys) { + for (int k : keys) { + this.keys.add(k); + } + } + + + public boolean isActive() + { + boolean st = true; + for (int k : keys) { + st &= Keyboard.isKeyDown(k); + } + + return down ? st : !st; + } + + + public void setKeys(Set keys) + { + this.keys = keys; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((keys == null) ? 0 : keys.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof KeyStroke)) return false; + KeyStroke other = (KeyStroke) obj; + + if (keys == null) { + if (other.keys != null) return false; + } else if (!keys.equals(other.keys)) { + return false; + } + + if (down != other.down) return false; + + return true; + } + + + @Override + public String toString() + { + String s = "("; + + int cnt = 0; + Iterator i = keys.iterator(); + for (; i.hasNext(); cnt++) { + if (cnt > 0) s += "+"; + s += Keyboard.getKeyName(i.next()); + } + + s += down ? ",DOWN" : "UP"; + + s += ")"; + + return s; + } + + + public Set getKeys() + { + return keys; + } +} diff --git a/src/mightypork/rogue/input/Keys.java b/src/mightypork/rogue/input/Keys.java deleted file mode 100644 index b734af3..0000000 --- a/src/mightypork/rogue/input/Keys.java +++ /dev/null @@ -1,122 +0,0 @@ -package mightypork.rogue.input; - - -import org.lwjgl.input.Keyboard; - - -/** - * Key state handler - */ -public class Keys { - - private static boolean[] prevKeys; - private static boolean[] keys; - - - /** - * initialize key state handler - */ - private static void init() - { - - if (keys == null) { - keys = new boolean[Keyboard.KEYBOARD_SIZE]; - } - - if (prevKeys == null) { - prevKeys = new boolean[Keyboard.KEYBOARD_SIZE]; - } - } - - - /** - * method called when key event was detected in Screen class. - * - * @param key - * @param down - */ - public static void onKey(int key, boolean down) - { - - init(); - - prevKeys[key] = keys[key]; - keys[key] = down; - } - - - /** - * Check if key is down - * - * @param key key index - * @return is down - */ - public static boolean isDown(int key) - { - - init(); - - return keys[key]; - } - - - /** - * Check if key is up - * - * @param key key index - * @return is up - */ - public static boolean isUp(int key) - { - - init(); - - return !keys[key]; - } - - - /** - * Check if key was just pressed (changed state since last event on this - * key) - * - * @param key key index - * @return true if changed state to DOWN - */ - public static boolean justPressed(int key) - { - - init(); - - return !prevKeys[key] && keys[key]; - } - - - /** - * Check if key was just released (changed state since last event on this - * key) - * - * @param key key index - * @return true if changed state to UP - */ - public static boolean justReleased(int key) - { - - init(); - - return prevKeys[key] && !keys[key]; - } - - - /** - * Destroy "just" flag for a key. - * - * @param key key index - */ - public static void destroyChangeState(int key) - { - - init(); - - prevKeys[key] = keys[key]; - } -} diff --git a/src/mightypork/rogue/input/events/KeyboardEvent.java b/src/mightypork/rogue/input/events/KeyboardEvent.java new file mode 100644 index 0000000..cc478da --- /dev/null +++ b/src/mightypork/rogue/input/events/KeyboardEvent.java @@ -0,0 +1,85 @@ +package mightypork.rogue.input.events; + + +import org.lwjgl.input.Keyboard; + +import mightypork.utils.patterns.subscription.Handleable; + + +/** + * A keyboard event + * + * @author MightyPork + */ +public class KeyboardEvent implements Handleable { + + private int key; + private boolean down; + private char c; + + + public KeyboardEvent(int key, char c, boolean down) { + this.key = key; + this.c = c; + this.down = down; + } + + + /** + * @return key code (see {@link org.lwjgl.input.Keyboard}) + */ + public int getKey() + { + return key; + } + + + /** + * @return true if key was just pressed + */ + public boolean isDown() + { + return down; + } + + /** + * @return true if key was just released + */ + public boolean isUp() + { + return !down; + } + + + /** + * @return event character (if any) + */ + public char getChar() + { + return c; + } + + + @Override + public void handleBy(Listener keh) + { + keh.receive(this); + } + + public interface Listener { + + /** + * Handle an event + * + * @param event event + */ + public void receive(KeyboardEvent event); + } + + @Override + public String toString() + { + return Keyboard.getKeyName(key)+":"+(down?"DOWN":"UP"); + } + +} diff --git a/src/mightypork/rogue/input/events/MouseButtonEvent.java b/src/mightypork/rogue/input/events/MouseButtonEvent.java new file mode 100644 index 0000000..8aa7225 --- /dev/null +++ b/src/mightypork/rogue/input/events/MouseButtonEvent.java @@ -0,0 +1,119 @@ +package mightypork.rogue.input.events; + + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.subscription.Handleable; + + +/** + * Mouse button / wheel event + * + * @author MightyPork + */ +public class MouseButtonEvent implements Handleable { + + public static final int BUTTON_LEFT = 0; + public static final int BUTTON_MIDDLE = 1; + public static final int BUTTON_RIGHT = 2; + + private int button; + private int wheeld; + private Coord pos; + private boolean down; + + + /** + * Mouse button event + * + * @param pos event position + * @param button button id + * @param down button pressed + * @param wheeld wheel change + */ + public MouseButtonEvent(Coord pos, int button, boolean down, int wheeld) { + this.button = button; + this.down = down; + this.pos = pos; + this.wheeld = wheeld; + } + + + /** + * @return true if the event was caused by a button state change + */ + public boolean isButtonEvent() + { + return button != -1; + } + + + /** + * @return true if the event was caused by a wheel change + */ + public boolean isWheelEvent() + { + return wheeld != 0; + } + + + /** + * @return button id or -1 if none was pressed + */ + public int getButton() + { + return button; + } + + + /** + * @return number of steps the wheel changed since last event + */ + public int getWheelDelta() + { + return wheeld; + } + + + /** + * @return mouse position when the event occurred + */ + public Coord getPos() + { + return pos; + } + + + /** + * @return true if button was just pressed + */ + public boolean isDown() + { + return button != -1 && down; + } + + + /** + * @return true if button was just released + */ + public boolean isUp() + { + return button != -1 && !down; + } + + + @Override + public void handleBy(Listener handler) + { + handler.receive(this); + } + + public interface Listener { + + /** + * Handle an event + * + * @param event event + */ + public void receive(MouseButtonEvent event); + } +} diff --git a/src/mightypork/rogue/input/events/MouseMotionEvent.java b/src/mightypork/rogue/input/events/MouseMotionEvent.java new file mode 100644 index 0000000..0d36624 --- /dev/null +++ b/src/mightypork/rogue/input/events/MouseMotionEvent.java @@ -0,0 +1,54 @@ +package mightypork.rogue.input.events; + + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.subscription.Handleable; + + +public class MouseMotionEvent implements Handleable { + + private Coord move; + private Coord pos; + + + public MouseMotionEvent(Coord pos, Coord move) { + this.move = move; + this.pos = pos; + } + + + /** + * @return movement since last {@link MouseMotionEvent} + */ + public Coord getPosDelta() + { + return move; + } + + + /** + * @return current mouse position + */ + public Coord getPos() + { + return pos; + } + + + @Override + public void handleBy(Listener keh) + { + keh.receive(this); + } + + public interface Listener { + + /** + * Handle an event + * + * @param event event + */ + public void receive(MouseMotionEvent event); + } + +} diff --git a/src/mightypork/rogue/screens/ScreenSplash.java b/src/mightypork/rogue/screens/ScreenSplash.java deleted file mode 100644 index be4967f..0000000 --- a/src/mightypork/rogue/screens/ScreenSplash.java +++ /dev/null @@ -1,82 +0,0 @@ -package mightypork.rogue.screens; - - -import mightypork.rogue.Screen; -import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.Vec; - - -public class ScreenSplash extends Screen { - - @Override - protected void onViewportChanged() - { - // TODO Auto-generated method stub - - } - - - @Override - public void initScreen() - { - // TODO Auto-generated method stub - - } - - - @Override - protected void onGuiUpdate() - { - // TODO Auto-generated method stub - - } - - - @Override - protected void render3D() - { - // TODO Auto-generated method stub - - } - - - @Override - public void onKey(int key, char c, boolean down) - { - // TODO Auto-generated method stub - - } - - - @Override - public void onMouseButton(int button, boolean down, int wheeld, Coord pos, Coord delta) - { - // TODO Auto-generated method stub - - } - - - @Override - public void handleKeyStates() - { - // TODO Auto-generated method stub - - } - - - @Override - public void onMouseMove(Coord coord, Vec vec, int wd) - { - // TODO Auto-generated method stub - - } - - - @Override - protected void render2D(double delta) - { - // TODO Auto-generated method stub - - } - -} diff --git a/src/mightypork/rogue/sounds/AudioPlayer.java b/src/mightypork/rogue/sounds/AudioPlayer.java deleted file mode 100644 index b2070ae..0000000 --- a/src/mightypork/rogue/sounds/AudioPlayer.java +++ /dev/null @@ -1,77 +0,0 @@ -package mightypork.rogue.sounds; - - -import mightypork.utils.objects.Mutable; - - -public abstract class AudioPlayer { - - /** base gain for sfx */ - private double baseGain = 1; - - /** the track */ - private AudioX track; - - /** base pitch for sfx */ - private double basePitch = 1; - - /** dedicated volume control */ - private Mutable gainMultiplier = null; - - - public AudioPlayer(AudioX track, double baseGain, Mutable gainMultiplier) { - this(track, 1, baseGain, gainMultiplier); - } - - - public AudioPlayer(AudioX track, double basePitch, double baseGain, Mutable gainMultiplier) { - this.track = track; - - this.baseGain = baseGain; - this.basePitch = basePitch; - - this.gainMultiplier = gainMultiplier; - } - - - public void destroy() - { - track.release(); - track = null; - } - - - protected AudioX getAudio() - { - return track; - } - - - protected float getGain(double multiplier) - { - return (float) (baseGain * gainMultiplier.get() * multiplier); - } - - - protected float getPitch(double multiplier) - { - return (float) (basePitch * multiplier); - } - - - /** - * Get if audio is valid - * - * @return is valid - */ - protected boolean canPlay() - { - return (track != null); - } - - - public void load() - { - if (canPlay()) track.load(); - } -} diff --git a/src/mightypork/rogue/sounds/AudioX.java b/src/mightypork/rogue/sounds/AudioX.java index f7de478..5cf31c7 100644 --- a/src/mightypork/rogue/sounds/AudioX.java +++ b/src/mightypork/rogue/sounds/AudioX.java @@ -4,6 +4,7 @@ package mightypork.rogue.sounds; import mightypork.utils.files.FileUtils; import mightypork.utils.logging.Log; import mightypork.utils.math.coord.Coord; +import mightypork.utils.patterns.Destroyable; import org.newdawn.slick.openal.Audio; import org.newdawn.slick.openal.SoundStore; @@ -14,7 +15,7 @@ import org.newdawn.slick.openal.SoundStore; * * @author MightyPork */ -public class AudioX implements Audio { +public class AudioX implements Destroyable { private enum PlayMode { @@ -22,14 +23,26 @@ public class AudioX implements Audio { }; private Audio audio = null; - private float pauseLoopPosition = 0; + private double pauseLoopPosition = 0; private boolean looping = false; private boolean paused = false; private PlayMode mode = PlayMode.EFFECT; - private float pitch = 1; - private float gain = 1; + private double lastPlayPitch = 1; + private double lastPlayGain = 1; - private String resourcePath; + private final String resourcePath; + private boolean loadFailed = false; + + + /** + * Create deferred primitive audio player + * + * @param resourceName resource to load when needed + */ + public AudioX(String resourceName) { + this.audio = null; + this.resourcePath = resourceName; + } /** @@ -37,7 +50,7 @@ public class AudioX implements Audio { */ public void pauseLoop() { - if (!ensureLoaded()) return; + if (!load()) return; if (isPlaying() && looping) { pauseLoopPosition = audio.getPosition(); @@ -54,16 +67,16 @@ public class AudioX implements Audio { */ public int resumeLoop() { - if (!ensureLoaded()) return -1; + if (!load()) return -1; int source = -1; if (looping && paused) { if (mode == PlayMode.MUSIC) { - source = audio.playAsMusic(pitch, gain, true); + source = audio.playAsMusic((float) lastPlayPitch, (float) lastPlayGain, true); } else { - source = audio.playAsSoundEffect(pitch, gain, true); + source = audio.playAsSoundEffect((float) lastPlayPitch, (float) lastPlayGain, true); } - audio.setPosition(pauseLoopPosition); + audio.setPosition((float) pauseLoopPosition); paused = false; } return source; @@ -71,34 +84,27 @@ public class AudioX implements Audio { /** - * Create deferred primitive audio player + * Check if resource is loaded * - * @param resourceName resource to load when needed + * @return resource is loaded */ - public AudioX(String resourceName) { - this.audio = null; - this.resourcePath = resourceName; + private boolean isLoaded() + { + return audio != null; } /** - * Check if can play, if not, try to load sound. + * Try to load if not loaded already * - * @return can now play + * @return is loaded */ - private boolean ensureLoaded() - { - load(); - - return audio != null; - } - - - public void load() + public boolean load() { - if (audio != null) return; // already loaded - if (resourcePath == null) return; // not loaded, but can't load anyway + if (isLoaded()) return true; // already loaded + if (loadFailed || resourcePath == null) return false; // not loaded, but can't load anyway + loadFailed = false; try { String ext = FileUtils.getExtension(resourcePath); @@ -112,119 +118,168 @@ public class AudioX implements Audio { } else if (ext.equalsIgnoreCase("mod")) { audio = SoundStore.get().getMOD(resourcePath); } else { - resourcePath = null; // don't try next time Log.e("Invalid audio file extension: " + resourcePath); + loadFailed = true; // don't try next time } } catch (Exception e) { Log.e("Could not load " + resourcePath, e); - resourcePath = null; // don't try next time + loadFailed = true; // don't try next time } + + return isLoaded(); } - @Override public void stop() { - if (!ensureLoaded()) return; + if (!isLoaded()) return; audio.stop(); paused = false; } - @Override - public int getBufferID() - { - if (!ensureLoaded()) return -1; - - return audio.getBufferID(); - } - - - @Override public boolean isPlaying() { - if (!ensureLoaded()) return false; + if (!isLoaded()) return false; return audio.isPlaying(); } - @Override public boolean isPaused() { - if (!ensureLoaded()) return false; + if (!isLoaded()) return false; return audio.isPaused(); } - @Override - public int playAsSoundEffect(float pitch, float gain, boolean loop) + /** + * Play as sound effect at listener position + * + * @param pitch pitch (1 = default) + * @param gain gain (0-1) + * @param loop looping + * @return source id + */ + public int playAsEffect(double pitch, double gain, boolean loop) { - return playAsSoundEffect(pitch, gain, loop, SoundManager.get().listener); + return playAsEffect(pitch, gain, loop, SoundSystem.getListener()); } - @Override - public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) + /** + * Play as sound effect at given X-Y position + * + * @param pitch pitch (1 = default) + * @param gain gain (0-1) + * @param loop looping + * @param x + * @param y + * @return source id + */ + public int playAsEffect(double pitch, double gain, boolean loop, double x, double y) + { + return playAsEffect(pitch, gain, loop, x, y, SoundSystem.getListener().z); + } + + + /** + * Play as sound effect at given position + * + * @param pitch pitch (1 = default) + * @param gain gain (0-1) + * @param loop looping + * @param x + * @param y + * @param z + * @return source id + */ + public int playAsEffect(double pitch, double gain, boolean loop, double x, double y, double z) { - if (!ensureLoaded()) return -1; + if (!load()) return -1; - this.pitch = pitch; - this.gain = gain; + this.lastPlayPitch = pitch; + this.lastPlayGain = gain; looping = loop; mode = PlayMode.EFFECT; - return audio.playAsSoundEffect(pitch, gain, loop, x, y, z); + return audio.playAsSoundEffect((float) pitch, (float) gain, loop, (float) x, (float) y, (float) z); } /** - * Play this sound as a sound effect + * Play as sound effect at given position * - * @param pitch The pitch of the play back - * @param gain The gain of the play back - * @param loop True if we should loop - * @param pos The position of the sound - * @return The ID of the source playing the sound + * @param pitch pitch (1 = default) + * @param gain gain (0-1) + * @param loop looping + * @param pos coord + * @return source id */ - public int playAsSoundEffect(float pitch, float gain, boolean loop, Coord pos) + public int playAsEffect(double pitch, double gain, boolean loop, Coord pos) { - return playAsSoundEffect(pitch, gain, loop, (float) pos.x, (float) pos.y, (float) pos.z); + if (!load()) return -1; + + return playAsEffect(pitch, gain, loop, pos.x, pos.y, pos.z); } - @Override - public int playAsMusic(float pitch, float gain, boolean loop) + /** + * Play as music using source 0.
+ * Discouraged, since this does not allow cross-fading. + * + * @param pitch play pitch + * @param gain play gain + * @param loop looping + * @return source + */ + public int playAsMusic(double pitch, double gain, boolean loop) { - this.pitch = pitch; - this.gain = gain; + if (!load()) return -1; + + this.lastPlayPitch = (float) pitch; + this.lastPlayGain = (float) gain; looping = loop; mode = PlayMode.MUSIC; - return audio.playAsMusic(pitch, gain, loop); + return audio.playAsMusic((float) pitch, (float) gain, loop); } @Override - public boolean setPosition(float position) + public void destroy() { - return audio.setPosition(position); + if (!isLoaded()) return; + + audio.release(); + audio = null; } @Override - public float getPosition() + public int hashCode() { - return audio.getPosition(); + final int prime = 31; + int result = 1; + result = prime * result + ((resourcePath == null) ? 0 : resourcePath.hashCode()); + return result; } @Override - public void release() + public boolean equals(Object obj) { - audio.release(); - audio = null; + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof AudioX)) return false; + AudioX other = (AudioX) obj; + if (resourcePath == null) { + if (other.resourcePath != null) return false; + } else if (!resourcePath.equals(other.resourcePath)) { + return false; + } + return true; } } diff --git a/src/mightypork/rogue/sounds/BaseAudioPlayer.java b/src/mightypork/rogue/sounds/BaseAudioPlayer.java new file mode 100644 index 0000000..ac4a152 --- /dev/null +++ b/src/mightypork/rogue/sounds/BaseAudioPlayer.java @@ -0,0 +1,77 @@ +package mightypork.rogue.sounds; + + +import mightypork.utils.objects.Mutable; + + +public abstract class BaseAudioPlayer { + + /** the track */ + private AudioX audio; + + /** base gain for sfx */ + private double baseGain = 1; + + /** base pitch for sfx */ + private double basePitch = 1; + + /** dedicated volume control */ + private Mutable gainMultiplier = null; + + + public BaseAudioPlayer(AudioX track, double baseGain, Mutable gainMultiplier) { + this(track, 1, baseGain, gainMultiplier); + } + + + public BaseAudioPlayer(AudioX track, double basePitch, double baseGain, Mutable gainMultiplier) { + this.audio = track; + + this.baseGain = baseGain; + this.basePitch = basePitch; + + this.gainMultiplier = gainMultiplier; + } + + + public void destroy() + { + audio.destroy(); + audio = null; + } + + + protected AudioX getAudio() + { + return audio; + } + + + protected double getGain(double multiplier) + { + return baseGain * gainMultiplier.get() * multiplier; + } + + + protected double getPitch(double multiplier) + { + return basePitch * multiplier; + } + + + /** + * Get if audio is valid + * + * @return is valid + */ + protected boolean canPlay() + { + return (audio != null); + } + + + public void load() + { + if (canPlay()) audio.load(); + } +} diff --git a/src/mightypork/rogue/sounds/EffectPlayer.java b/src/mightypork/rogue/sounds/EffectPlayer.java index dfb4edf..ee26718 100644 --- a/src/mightypork/rogue/sounds/EffectPlayer.java +++ b/src/mightypork/rogue/sounds/EffectPlayer.java @@ -5,9 +5,9 @@ import mightypork.utils.math.coord.Coord; import mightypork.utils.objects.Mutable; -public class EffectPlayer extends AudioPlayer { +public class EffectPlayer extends BaseAudioPlayer { - public EffectPlayer(AudioX track, double basePitch, double baseGain, Mutable gainMultiplier) { + public EffectPlayer(AudioX track, double basePitch, double baseGain, Mutable gainMultiplier) { super(track, (float) basePitch, (float) baseGain, gainMultiplier); } @@ -16,7 +16,7 @@ public class EffectPlayer extends AudioPlayer { { if (!canPlay()) return -1; - return getAudio().playAsSoundEffect(getPitch(pitch), getGain(gain), false); + return getAudio().playAsEffect(getPitch(pitch), getGain(gain), false); } @@ -30,7 +30,7 @@ public class EffectPlayer extends AudioPlayer { { if (!canPlay()) return -1; - return getAudio().playAsSoundEffect(getPitch(pitch), getGain(gain), false, pos); + return getAudio().playAsEffect(getPitch(pitch), getGain(gain), false, pos); } } diff --git a/src/mightypork/rogue/sounds/JointVolume.java b/src/mightypork/rogue/sounds/JointVolume.java index 7cb47bf..a314b3f 100644 --- a/src/mightypork/rogue/sounds/JointVolume.java +++ b/src/mightypork/rogue/sounds/JointVolume.java @@ -10,18 +10,18 @@ import mightypork.utils.objects.Mutable; * * @author MightyPork */ -public class JointVolume extends Mutable { +public class JointVolume extends Mutable { - private Mutable[] volumes; + private Mutable[] volumes; /** - * CReate joint volume with master gain of 1 + * Create joint volume with master gain of 1 * * @param volumes individual volumes to join */ - public JointVolume(Mutable... volumes) { - super(1F); + public JointVolume(Mutable... volumes) { + super(1D); this.volumes = volumes; } @@ -30,13 +30,13 @@ public class JointVolume extends Mutable { * Get combined gain (multiplied) */ @Override - public Float get() + public Double get() { - float f = super.get(); - for (Mutable v : volumes) - f *= v.get(); + double d = super.get(); + for (Mutable v : volumes) + d *= v.get(); - return Calc.clampf(f, 0, 1); + return Calc.clampd(d, 0, 1); } @@ -44,7 +44,7 @@ public class JointVolume extends Mutable { * Set master gain */ @Override - public void set(Float o) + public void set(Double o) { super.set(o); } diff --git a/src/mightypork/rogue/sounds/LoopPlayer.java b/src/mightypork/rogue/sounds/LoopPlayer.java index d894df3..858ae49 100644 --- a/src/mightypork/rogue/sounds/LoopPlayer.java +++ b/src/mightypork/rogue/sounds/LoopPlayer.java @@ -9,7 +9,7 @@ import mightypork.utils.time.Updateable; import org.lwjgl.openal.AL10; -public class LoopPlayer extends AudioPlayer implements Updateable, Pauseable { +public class LoopPlayer extends BaseAudioPlayer implements Updateable, Pauseable { private int sourceID = -1; @@ -28,7 +28,7 @@ public class LoopPlayer extends AudioPlayer implements Updateable, Pauseable { private double outTime = 1; - public LoopPlayer(AudioX track, double pitch, double baseGain, Mutable gainMultiplier) { + public LoopPlayer(AudioX track, double pitch, double baseGain, Mutable gainMultiplier) { super(track, (float) pitch, (float) baseGain, gainMultiplier); paused = true; @@ -45,7 +45,7 @@ public class LoopPlayer extends AudioPlayer implements Updateable, Pauseable { private void initLoop() { if (!canPlay() && sourceID == -1) { - sourceID = getAudio().playAsSoundEffect(getPitch(1), getGain(1), true); + sourceID = getAudio().playAsEffect(getPitch(1), getGain(1), true); getAudio().pauseLoop(); } } diff --git a/src/mightypork/rogue/sounds/SoundManager.java b/src/mightypork/rogue/sounds/SoundManager.java deleted file mode 100644 index 72b9c36..0000000 --- a/src/mightypork/rogue/sounds/SoundManager.java +++ /dev/null @@ -1,179 +0,0 @@ -package mightypork.rogue.sounds; - - -import java.nio.FloatBuffer; -import java.util.HashMap; -import java.util.Map; - -import mightypork.utils.math.Calc.Buffers; -import mightypork.utils.math.coord.Coord; -import mightypork.utils.objects.Mutable; -import mightypork.utils.time.Updateable; - -import org.lwjgl.openal.AL10; -import org.newdawn.slick.openal.SoundStore; - - -/** - * Preloaded sounds. - * - * @author MightyPork - */ -public class SoundManager implements Updateable { - - private static SoundManager inst = new SoundManager(); - public Mutable masterVolume = new Mutable(1F); - @SuppressWarnings("unchecked") - public Mutable effectsVolume = new JointVolume(masterVolume); - @SuppressWarnings("unchecked") - public Mutable loopsVolume = new JointVolume(masterVolume); - public Coord listener = new Coord(Coord.ZERO); - private Map effects = new HashMap(); - private Map loops = new HashMap(); - - - /** - * Singleton constructor - */ - private SoundManager() { - SoundStore.get().setMaxSources(256); - SoundStore.get().init(); - } - - - public static SoundManager get() - { - return inst; - } - - - public void addEffect(String key, String resource, float pitch, float gain) - { - EffectPlayer p = new EffectPlayer(new AudioX(resource), pitch, gain, effectsVolume); - effects.put(key, p); - } - - - public void addLoop(String key, String resource, float pitch, float gain, double fadeIn, double fadeOut) - { - LoopPlayer p = new LoopPlayer(new AudioX(resource), pitch, gain, loopsVolume); - p.setFadeTimes(fadeIn, fadeOut); - loops.put(key, p); - } - - - public LoopPlayer getLoop(String key) - { - LoopPlayer p = loops.get(key); - if (p == null) { - throw new IllegalArgumentException("Requesting unknown sound loop \"" + key + "\"."); - } - return p; - } - - - public EffectPlayer getEffect(String key) - { - EffectPlayer p = effects.get(key); - if (p == null) { - throw new IllegalArgumentException("Requesting unknown sound effect \"" + key + "\"."); - } - return p; - } - - - public void fadeOutAllLoops() - { - for (LoopPlayer p : loops.values()) { - p.fadeOut(); - } - } - - - public void fadeInLoop(String key) - { - getLoop(key).fadeIn(); - } - - - public void fadeInLoop(String key, double seconds) - { - getLoop(key).fadeIn(seconds); - } - - - public void pauseLoop(String key) - { - getLoop(key).pause(); - } - - - public void pauseAllLoops() - { - for (LoopPlayer p : loops.values()) { - p.pause(); - } - } - - - public void resumeLoop(String key) - { - getLoop(key).resume(); - } - - - /** - * Set listener pos - * - * @param pos - */ - public void setListener(Coord pos) - { - listener.setTo(pos); - FloatBuffer buf3 = Buffers.alloc(3); - FloatBuffer buf6 = Buffers.alloc(6); - buf3.clear(); - Buffers.fill(buf3, (float) pos.x, (float) pos.y, (float) pos.z); - AL10.alListener(AL10.AL_POSITION, buf3); - buf3.clear(); - Buffers.fill(buf3, 0, 0, 0); - AL10.alListener(AL10.AL_VELOCITY, buf3); - buf6.clear(); - Buffers.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); - AL10.alListener(AL10.AL_ORIENTATION, buf6); - buf3 = buf6 = null; - } - - - @Override - public void update(double delta) - { - for (LoopPlayer lp : loops.values()) { - lp.update(delta); - } - } - - - public void setMasterVolume(float f) - { - masterVolume.set(f); - } - - - public void setEffectsVolume(float f) - { - effectsVolume.set(f); - } - - - public void setMusicVolume(float f) - { - loopsVolume.set(f); - } - - - public void destroy() - { - SoundStore.get().clear(); - } -} diff --git a/src/mightypork/rogue/sounds/SoundSystem.java b/src/mightypork/rogue/sounds/SoundSystem.java new file mode 100644 index 0000000..f828271 --- /dev/null +++ b/src/mightypork/rogue/sounds/SoundSystem.java @@ -0,0 +1,309 @@ +package mightypork.rogue.sounds; + + +import java.nio.FloatBuffer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import mightypork.utils.math.Calc.Buffers; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.objects.Mutable; +import mightypork.utils.patterns.Destroyable; +import mightypork.utils.time.Updateable; + +import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL10; +import org.newdawn.slick.openal.SoundStore; + + +/** + * Sound system class (only one instance should be made per application) + * + * @author MightyPork + */ +@SuppressWarnings("unchecked") +// needed for JointVolume +public class SoundSystem implements Updateable, Destroyable { + + // static + + private static final Coord INITIAL_LISTENER_POS = new Coord(0, 0, 0); + private static final int MAX_SOURCES = 256; + + private static Coord listener = new Coord(); + + + static { + // initialize sound system + SoundStore.get().setMaxSources(MAX_SOURCES); + SoundStore.get().init(); + + setListener(INITIAL_LISTENER_POS); + } + + + /** + * Set listener pos + * + * @param pos + */ + public static void setListener(Coord pos) + { + listener.setTo(pos); + FloatBuffer buf3 = Buffers.alloc(3); + FloatBuffer buf6 = Buffers.alloc(6); + buf3.clear(); + Buffers.fill(buf3, (float) pos.x, (float) pos.y, (float) pos.z); + AL10.alListener(AL10.AL_POSITION, buf3); + buf3.clear(); + Buffers.fill(buf3, 0, 0, 0); + AL10.alListener(AL10.AL_VELOCITY, buf3); + buf6.clear(); + Buffers.fill(buf6, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); + AL10.alListener(AL10.AL_ORIENTATION, buf6); + buf3 = buf6 = null; + } + + + public static Coord getListener() + { + return listener; + } + + // instance + + public Mutable masterVolume = new Mutable(1D); + public Mutable effectsVolume = new JointVolume(masterVolume); + public Mutable loopsVolume = new JointVolume(masterVolume); + + private Map effects = new HashMap(); + private Map loops = new HashMap(); + private Set resources = new HashSet(); + + + /** + * Register effect resource + * + * @param key sound key + * @param resource resource path + * @param pitch default pitch (1 = unchanged) + * @param gain default gain (0-1) + */ + public void addEffect(String key, String resource, double pitch, double gain) + { + EffectPlayer p = new EffectPlayer(getResource(resource), pitch, gain, effectsVolume); + effects.put(key, p); + } + + + /** + * Register loop resource (music / effect loop) + * + * @param key sound key + * @param resource resource path + * @param pitch default pitch (1 = unchanged) + * @param gain default gain (0-1) + * @param fadeIn default time for fadeIn + * @param fadeOut default time for fadeOut + */ + public void addLoop(String key, String resource, double pitch, double gain, double fadeIn, double fadeOut) + { + LoopPlayer p = new LoopPlayer(getResource(resource), pitch, gain, loopsVolume); + p.setFadeTimes(fadeIn, fadeOut); + loops.put(key, p); + } + + + /** + * Create {@link AudioX} for a resource + * @param res a resource name + * @return the resource + * + * @throws IllegalArgumentException if resource is already registered + */ + private AudioX getResource(String res) { + AudioX a = new AudioX(res); + if(resources.contains(a)) throw new IllegalArgumentException("Sound resource "+res+" is already registered."); + resources.add(a); + return a; + } + + + /** + * Get a loop player for key + * + * @param key sound key + * @return loop player + */ + public LoopPlayer getLoop(String key) + { + LoopPlayer p = loops.get(key); + if (p == null) { + throw new IllegalArgumentException("Requesting unknown sound loop \"" + key + "\"."); + } + return p; + } + + + /** + * Get a effect player for key + * + * @param key sound key + * @return effect player + */ + public EffectPlayer getEffect(String key) + { + EffectPlayer p = effects.get(key); + if (p == null) { + throw new IllegalArgumentException("Requesting unknown sound effect \"" + key + "\"."); + } + return p; + } + + + /** + * Fade out all loops (ie. for screen transitions) + */ + public void fadeOutAllLoops() + { + for (LoopPlayer p : loops.values()) { + p.fadeOut(); + } + } + + + /** + * Fade in a loop (with default time) + * + * @param key sound key + */ + public void fadeInLoop(String key) + { + getLoop(key).fadeIn(); + } + + + /** + * Fade in a loop + * + * @param key sound key + * @param seconds fade-in duration + */ + public void fadeInLoop(String key, double seconds) + { + getLoop(key).fadeIn(seconds); + } + + + /** + * Fade out a loop (with default time) + * + * @param key sound key + */ + public void fadeOutLoop(String key) + { + getLoop(key).fadeOut(); + } + + + /** + * Fade out a loop + * + * @param key sound key + * @param seconds fade-out duration + */ + public void fadeOutLoop(String key, double seconds) + { + getLoop(key).fadeOut(seconds); + } + + + /** + * Pause a loop + * + * @param key sound key + */ + public void pauseLoop(String key) + { + getLoop(key).pause(); + } + + + /** + * Pause all loops (leave volume unchanged) + */ + public void pauseAllLoops() + { + for (LoopPlayer p : loops.values()) { + p.pause(); + } + } + + + /** + * Resume a loop + * + * @param key sound key + */ + public void resumeLoop(String key) + { + getLoop(key).resume(); + } + + + @Override + public void update(double delta) + { + for (LoopPlayer lp : loops.values()) { + lp.update(delta); + } + } + + + /** + * Set level of master volume + * + * @param d level + */ + public void setMasterVolume(double d) + { + masterVolume.set(d); + } + + + /** + * Set level of effects volume + * + * @param d level + */ + public void setEffectsVolume(double d) + { + effectsVolume.set(d); + } + + + /** + * Set level of music volume + * + * @param d level + */ + public void setMusicVolume(double d) + { + loopsVolume.set(d); + } + + + @Override + public void destroy() + { + for(AudioX r: resources) { + r.destroy(); + } + + SoundStore.get().clear(); + AL.destroy(); + } + +} diff --git a/src/mightypork/rogue/tasks/TaskTakeScreenshot.java b/src/mightypork/rogue/tasks/TaskTakeScreenshot.java new file mode 100644 index 0000000..dde9961 --- /dev/null +++ b/src/mightypork/rogue/tasks/TaskTakeScreenshot.java @@ -0,0 +1,61 @@ +package mightypork.rogue.tasks; + + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.imageio.ImageIO; + +import mightypork.rogue.App; +import mightypork.rogue.Paths; +import mightypork.utils.logging.Log; + + +public class TaskTakeScreenshot implements Runnable { + + private BufferedImage image; + + + public TaskTakeScreenshot() { + this.image = App.disp().takeScreenshot(); + } + + + @Override + public void run() + { + String fname = getUniqueScreenshotName(); + + // generate unique filename + File file; + int index = 0; + while (true) { + file = new File(Paths.SCREENSHOTS, fname + (index > 0 ? "-" + index : "") + ".png"); + if (!file.exists()) break; + index++; + } + + Log.f3("Saving screenshot to file: " + file); + + String format = "PNG"; + + // save to disk + try { + ImageIO.write(image, format, file); + } catch (IOException e) { + Log.e("Failed to save screenshot.", e); + } + } + + + private String getUniqueScreenshotName() + { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); + return df.format(new Date()); + } + +} diff --git a/src/mightypork/rogue/threads/ThreadSaveScreenshot.java b/src/mightypork/rogue/threads/ThreadSaveScreenshot.java deleted file mode 100644 index 0d04c57..0000000 --- a/src/mightypork/rogue/threads/ThreadSaveScreenshot.java +++ /dev/null @@ -1,91 +0,0 @@ -package mightypork.rogue.threads; - - -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - -import javax.imageio.ImageIO; - -import mightypork.rogue.Paths; -import mightypork.utils.logging.Log; - - -/** - * Thread for saving screenshot - * - * @author MightyPork - */ -public class ThreadSaveScreenshot extends Thread { - - private ByteBuffer buffer; - private int width; - private int height; - private int bpp; - - - /** - * Save screenshot thread - * - * @param buffer byte buffer with image data - * @param width screen width - * @param height screen height - * @param bpp bits per pixel - */ - public ThreadSaveScreenshot(ByteBuffer buffer, int width, int height, int bpp) { - this.buffer = buffer; - this.width = width; - this.height = height; - this.bpp = bpp; - } - - - private String getUniqueScreenshotName() - { - DateFormat df = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); - return df.format(new Date()); - } - - - @Override - public void run() - { - String fname = getUniqueScreenshotName(); - - // generate unique filename - File file; - int index = 0; - while (true) { - file = new File(Paths.SCREENSHOTS, fname + (index > 0 ? "-" + index : "") + ".png"); - if (!file.exists()) break; - index++; - } - - Log.f3("Saving screenshot to file: " + file); - - String format = "PNG"; - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - - // convert to a buffered image - for (int x = 0; x < width; x++) { - for (int y = 0; y < height; y++) { - int i = (x + (width * y)) * bpp; - int r = buffer.get(i) & 0xFF; - int g = buffer.get(i + 1) & 0xFF; - int b = buffer.get(i + 2) & 0xFF; - image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b); - } - } - - // save to disk - try { - ImageIO.write(image, format, file); - } catch (IOException e) { - Log.e("Failed to save screenshot.", e); - } - } -} diff --git a/src/mightypork/rogue/threads/ThreadScreenshotTrigger.java b/src/mightypork/rogue/threads/ThreadScreenshotTrigger.java deleted file mode 100644 index 8b1bc88..0000000 --- a/src/mightypork/rogue/threads/ThreadScreenshotTrigger.java +++ /dev/null @@ -1,32 +0,0 @@ -package mightypork.rogue.threads; - - -import mightypork.rogue.App; -import mightypork.rogue.input.Keys; -import mightypork.utils.logging.Log; - -import org.lwjgl.input.Keyboard; - - -/** - * @author MightyPork - */ -public class ThreadScreenshotTrigger extends Thread { - - @Override - public void run() - { - while (true) { - if (Keys.justPressed(Keyboard.KEY_F2)) { - Log.f2("F2, taking screenshot."); - App.scheduledScreenshot = true; - Keys.destroyChangeState(Keyboard.KEY_F2); - } - try { - sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } -} diff --git a/src/mightypork/rogue/util/RenderUtils.java b/src/mightypork/rogue/util/RenderUtils.java index 8747797..0643515 100644 --- a/src/mightypork/rogue/util/RenderUtils.java +++ b/src/mightypork/rogue/util/RenderUtils.java @@ -4,6 +4,7 @@ package mightypork.rogue.util; import static org.lwjgl.opengl.GL11.*; import mightypork.rogue.textures.TextureManager; import mightypork.rogue.textures.TxQuad; +import mightypork.utils.math.Calc.Deg; import mightypork.utils.math.color.HSV; import mightypork.utils.math.color.RGB; import mightypork.utils.math.coord.Coord; @@ -18,6 +19,10 @@ import org.newdawn.slick.opengl.Texture; * @author MightyPork */ public class RenderUtils { + + private static final Coord AXIS_X = new Coord(1, 0, 0); + private static final Coord AXIS_Y = new Coord(0, 1, 0); + private static final Coord AXIS_Z = new Coord(0, 0, 1); /** * Render quad 2D @@ -471,15 +476,15 @@ public class RenderUtils { */ public static void quadTexturedAbs(Rect quad, Rect textureCoords) { - double left = quad.x1(); - double bottom = quad.y2(); - double right = quad.x2(); - double top = quad.y1(); + double left = quad.xMin(); + double bottom = quad.yMax(); + double right = quad.xMax(); + double top = quad.yMin(); - double tleft = textureCoords.x1(); - double tbottom = textureCoords.y1(); - double tright = textureCoords.x2(); - double ttop = textureCoords.y2(); + double tleft = textureCoords.xMin(); + double tbottom = textureCoords.yMin(); + double tright = textureCoords.xMax(); + double ttop = textureCoords.yMax(); glBegin(GL_QUADS); glTexCoord2d(tleft, ttop); @@ -584,18 +589,18 @@ public class RenderUtils { int yOffset = yOffsetTimes * frameHeight; - double x1 = quadRect.x1(); - double y1 = quadRect.y1(); - double x2 = quadRect.x2(); - double y2 = quadRect.y2(); + double x1 = quadRect.xMin(); + double y1 = quadRect.yMin(); + double x2 = quadRect.xMax(); + double y2 = quadRect.yMax(); double w = x2 - x1; double h = y2 - y1; - double tx1 = textureRect.x1(); - double ty1 = textureRect.y1(); - double tx2 = textureRect.x2(); - double ty2 = textureRect.y2(); + double tx1 = textureRect.xMin(); + double ty1 = textureRect.yMin(); + double tx2 = textureRect.xMax(); + double ty2 = textureRect.yMax(); double halfY = h / 2D; double halfX = w / 2D; @@ -713,15 +718,15 @@ public class RenderUtils { { Texture tx = texture; - double x1 = quadRect.x1(); - double y1 = quadRect.y1(); - double x2 = quadRect.x2(); - double y2 = quadRect.y2(); + double x1 = quadRect.xMin(); + double y1 = quadRect.yMin(); + double x2 = quadRect.xMax(); + double y2 = quadRect.yMax(); - double tx1 = textureRect.x1(); - double ty1 = textureRect.y1(); - double tx2 = textureRect.x2(); - double ty2 = textureRect.y2(); + double tx1 = textureRect.xMin(); + double ty1 = textureRect.yMin(); + double tx2 = textureRect.xMax(); + double ty2 = textureRect.yMax(); // lt, mi, rt @@ -802,15 +807,15 @@ public class RenderUtils { { Texture tx = texture; - double x1 = quadRect.x1(); - double y1 = quadRect.y1(); - double x2 = quadRect.x2(); - double y2 = quadRect.y2(); + double x1 = quadRect.xMin(); + double y1 = quadRect.yMin(); + double x2 = quadRect.xMax(); + double y2 = quadRect.yMax(); - double tx1 = textureRect.x1(); - double ty1 = textureRect.y1(); - double tx2 = textureRect.x2(); - double ty2 = textureRect.y2(); + double tx1 = textureRect.xMin(); + double ty1 = textureRect.yMin(); + double tx2 = textureRect.xMax(); + double ty2 = textureRect.yMax(); // tp // mi @@ -916,4 +921,29 @@ public class RenderUtils { { glTranslated(coord.x, coord.y, coord.z); } + + + public static void rotateX(double angle) + { + rotate(angle, AXIS_X); + } + + + public static void rotateY(double angle) + { + rotate(angle, AXIS_Y); + } + + + public static void rotateZ(double angle) + { + rotate(angle, AXIS_Z); + } + + + public static void rotate(double angle, Coord axis) + { + Coord vec = axis.norm(1); + glRotated(angle, vec.x, vec.y, vec.z); + } } diff --git a/src/mightypork/rogue/util/Utils.java b/src/mightypork/rogue/util/Utils.java index 3ac307a..eadec42 100644 --- a/src/mightypork/rogue/util/Utils.java +++ b/src/mightypork/rogue/util/Utils.java @@ -7,4 +7,9 @@ package mightypork.rogue.util; * @author MightyPork */ public class Utils { + public static Thread runAsThread(Runnable r) { + Thread t = new Thread(r); + t.start(); + return t; + } } diff --git a/src/mightypork/utils/logging/Log.java b/src/mightypork/utils/logging/Log.java index 6b9657c..ba7c858 100644 --- a/src/mightypork/utils/logging/Log.java +++ b/src/mightypork/utils/logging/Log.java @@ -8,7 +8,7 @@ import java.util.HashMap; public class Log { /** enable static logging */ - private static boolean esl; + private static boolean esl = true; /** diff --git a/src/mightypork/utils/math/Calc.java b/src/mightypork/utils/math/Calc.java index e3fbd66..c8ff741 100644 --- a/src/mightypork/utils/math/Calc.java +++ b/src/mightypork/utils/math/Calc.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Random; import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.Vec; +import mightypork.utils.math.easing.Easing; import org.lwjgl.BufferUtils; @@ -31,7 +31,7 @@ public class Calc { * @param point point coordinate * @return distance */ - public static double linePointDist(Vec lineDirVec, Coord linePoint, Coord point) + public static double linePointDist(Coord lineDirVec, Coord linePoint, Coord point) { // line point L[lx,ly] double lx = linePoint.x; @@ -59,9 +59,9 @@ public class Calc { * @param point point coordinate * @return distance */ - public static double linePointDistXZ(Vec lineDirVec, Coord linePoint, Coord point) + public static double linePointDistXZ(Coord lineDirVec, Coord linePoint, Coord point) { - return linePointDist(new Vec(lineDirVec.x, lineDirVec.z), new Coord(linePoint.x, linePoint.z), new Coord(point.x, point.z)); + return linePointDist(new Coord(lineDirVec.x, lineDirVec.z), new Coord(linePoint.x, linePoint.z), new Coord(point.x, point.z)); } @@ -739,28 +739,45 @@ public class Calc { /** * Get number from A to B at delta time (tween A to B) * - * @param last last number - * @param now new number - * @param dtime delta time + * @param from last number + * @param to new number + * @param time time 0..1 + * @param easing easing function * @return current number to render */ - public static double interpolate(double last, double now, double dtime) - { - return last + (now - last) * dtime; + public static double interpolate(double from, double to, double time, Easing easing) + { + return from + (to - from) * easing.get(time); } /** * Get angle [degrees] from A to B at delta time (tween A to B) * - * @param last last angle - * @param now new angle - * @param delta delta time + * @param from last angle + * @param to new angle + * @param time time 0..1 + * @param easing easing function + * @return current angle to render + */ + public static double interpolateDeg(double from, double to, double time, Easing easing) + { + return Deg.norm(from - Deg.delta(to, from) * easing.get(time)); + } + + + /** + * Get angle [radians] from A to B at delta time (tween A to B) + * + * @param from last angle + * @param to new angle + * @param time time 0..1 + * @param easing easing function * @return current angle to render */ - public static double interpolateDeg(double last, double now, double delta) + public static double interpolateRad(double from, double to, double time, Easing easing) { - return Deg.norm(last + Deg.delta(now, last) * delta); + return Rad.norm(from - Rad.delta(to, from) * easing.get(time)); } diff --git a/src/mightypork/utils/math/Polar.java b/src/mightypork/utils/math/Polar.java index d7739d1..65f307c 100644 --- a/src/mightypork/utils/math/Polar.java +++ b/src/mightypork/utils/math/Polar.java @@ -1,6 +1,8 @@ package mightypork.utils.math; +import mightypork.utils.math.Calc.Deg; +import mightypork.utils.math.Calc.Rad; import mightypork.utils.math.coord.Coord; @@ -12,18 +14,87 @@ import mightypork.utils.math.coord.Coord; public class Polar { /** angle in radians */ - public double angle = 0; + private double angle = 0; + /** distance in units */ - public double distance = 0; + private double radius = 0; /** - * @param angle angle in radians + * Create a polar + * + * @param angle angle in RAD * @param distance distance from origin */ public Polar(double angle, double distance) { + this(angle, false, distance); + } + + + /** + * Create a polar + * + * @param angle angle + * @param deg angle is in DEG + * @param distance radius + */ + public Polar(double angle, boolean deg, double distance) { + this.radius = distance; + this.angle = deg ? Deg.toRad(angle) : angle; + } + + + /** + * @return angle in RAD + */ + public double getAngle() + { + return angle; + } + + + /** + * @return angle in DEG + */ + public double getAngleDeg() + { + return Rad.toDeg(angle); + } + + + /** + * @param angle angle in RAD + */ + public void setAngle(double angle) + { this.angle = angle; - this.distance = distance; + } + + + /** + * @param angle angle in DEG + */ + public void setAngleDeg(double angle) + { + this.angle = Deg.toRad(angle); + } + + + /** + * @return radius + */ + public double getRadius() + { + return radius; + } + + + /** + * @param r radius + */ + public void setRadius(double r) + { + this.radius = r; } @@ -52,19 +123,6 @@ public class Polar { } - /** - * Make polar from coords - * - * @param x x coord - * @param z z coord - * @return polar - */ - public static Polar fromCoordXZ(double x, double z) - { - return Polar.fromCoordXZ(new Coord(x, 0, z)); - } - - /** * Get coord from polar * @@ -72,36 +130,40 @@ public class Polar { */ public Coord toCoord() { - return new Coord(distance * Math.cos(angle), distance * Math.sin(angle)); + return new Coord(radius * Math.cos(angle), radius * Math.sin(angle)); } - /** - * Get X,0,Y coord from polar - * - * @return coord - */ - public Coord toCoordXZ() + @Override + public String toString() { - return new Coord(distance * Math.cos(angle), 0, distance * Math.sin(angle)); + return "Polar(" + angle + "rad, " + radius + ")"; } @Override - public String toString() + public int hashCode() { - return "Polar(theta=" + angle + ", r=" + distance + ")"; + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(angle); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(radius); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; } - /** - * Build polar from X,Z instead of X,Y - * - * @param coord cpprd with X,Z - * @return polar - */ - public static Polar fromCoordXZ(Coord coord) + @Override + public boolean equals(Object obj) { - return fromCoord(coord.x, coord.z); + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Polar)) return false; + Polar other = (Polar) obj; + if (Double.doubleToLongBits(angle) != Double.doubleToLongBits(other.angle)) return false; + if (Double.doubleToLongBits(radius) != Double.doubleToLongBits(other.radius)) return false; + return true; } } diff --git a/src/mightypork/utils/math/PolarDeg.java b/src/mightypork/utils/math/PolarDeg.java deleted file mode 100644 index 8a244c7..0000000 --- a/src/mightypork/utils/math/PolarDeg.java +++ /dev/null @@ -1,111 +0,0 @@ -package mightypork.utils.math; - - -import mightypork.utils.math.Calc.Deg; -import mightypork.utils.math.Calc.Rad; -import mightypork.utils.math.coord.Coord; - - -/** - * Polar coordinate in degrees - * - * @author MightyPork - */ -public class PolarDeg { - - /** angle in degrees */ - public double angle = 0; - /** distance in units */ - public double distance = 0; - - - /** - * Polar coordinate in degrees - * - * @param angle angle in degrees - * @param distance distance from origin - */ - public PolarDeg(double angle, double distance) { - this.angle = angle; - this.distance = distance; - } - - - /** - * Make polar from coord - * - * @param coord coord - * @return polar - */ - public static PolarDeg fromCoord(Coord coord) - { - return new PolarDeg(Rad.toDeg(Math.atan2(coord.y, coord.x)), Math.sqrt(Calc.square(coord.x) + Calc.square(coord.y))); - } - - - /** - * Make polar from coords - * - * @param x x coord - * @param y y coord - * @return polar - */ - public static PolarDeg fromCoord(double x, double y) - { - return PolarDeg.fromCoord(new Coord(x, y)); - } - - - /** - * Make polar from coords - * - * @param x x coord - * @param z y coord - * @return polar - */ - public static PolarDeg fromCoordXZ(double x, double z) - { - return PolarDeg.fromCoordXZ(new Coord(x, 0, z)); - } - - - /** - * Get coord from polar - * - * @return coord - */ - public Coord toCoord() - { - return new Coord(distance * Math.cos(Deg.toRad(angle)), distance * Math.sin(Deg.toRad(angle))); - } - - - /** - * Get X,0,Y coord from polar - * - * @return coord - */ - public Coord toCoordXZ() - { - return new Coord(distance * Math.cos(Deg.toRad(angle)), 0, distance * Math.sin(Deg.toRad(angle))); - } - - - @Override - public String toString() - { - return "Polar(theta=" + angle + ", r=" + distance + ")"; - } - - - /** - * Build polar from X,Z instead of X,Y - * - * @param coord cpprd with X,Z - * @return polar - */ - public static PolarDeg fromCoordXZ(Coord coord) - { - return fromCoord(coord.x, coord.z); - } -} diff --git a/src/mightypork/utils/math/coord/Coord.java b/src/mightypork/utils/math/coord/Coord.java index 21e5172..7e40402 100644 --- a/src/mightypork/utils/math/coord/Coord.java +++ b/src/mightypork/utils/math/coord/Coord.java @@ -4,7 +4,6 @@ package mightypork.utils.math.coord; import java.util.Random; import mightypork.utils.math.Calc; -import mightypork.utils.time.Updateable; /** @@ -12,13 +11,7 @@ import mightypork.utils.time.Updateable; * * @author MightyPork */ -public class Coord implements Updateable { - - /** Coord [1;1;1] */ - public static final Coord ONE = new Coord(1, 1, 1); - - /** Zero Coord */ - public static final Coord ZERO = new Coord(0, 0); +public class Coord { /** RNG */ protected static Random rand = new Random(); @@ -63,14 +56,6 @@ public class Coord implements Updateable { (rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min))); } - private double animTime = 0; - - private Vec offs; - - private Coord start; - - private double time = 0; - /** X coordinate */ public double x = 0; @@ -130,7 +115,7 @@ public class Coord implements Updateable { */ public Coord add(Coord vec) { - return copy().add_ip(vec); + return getCopy().add_ip(vec); } @@ -143,7 +128,7 @@ public class Coord implements Updateable { */ public Coord add(Number x, Number y) { - return copy().add_ip(x, y); + return getCopy().add_ip(x, y); } @@ -157,7 +142,7 @@ public class Coord implements Updateable { */ public Coord add(Number x, Number y, Number z) { - return copy().add_ip(x, y, z); + return getCopy().add_ip(x, y, z); } @@ -208,25 +193,10 @@ public class Coord implements Updateable { } - /** - * Start animation - * - * @param time anim length - */ - public void animate(double time) - { - if (start == null) start = new Coord(); - if (offs == null) offs = new Vec(); - this.time = time; - animTime = 0; - offs = start.vecTo(this); - } - - /** * @return copy of this vector */ - public Coord copy() + public Coord getCopy() { return new Coord(x, y, z); } @@ -252,7 +222,7 @@ public class Coord implements Updateable { */ public Coord div(double d) { - return copy().div_ip(d); + return getCopy().div_ip(d); } @@ -272,48 +242,6 @@ public class Coord implements Updateable { } - @Override - public boolean equals(Object obj) - { - if (obj == null) return false; - if (!obj.getClass().isAssignableFrom(Coord.class)) return false; - Coord other = (Coord) obj; - return x == other.x && y == other.y && z == other.z; - } - - - /** - * Get current value (animated) - * - * @return curent value - */ - public Coord getDelta() - { - if (start == null) start = new Coord(); - if (offs == null) offs = new Vec(); - if (isFinished()) return this; - return new Coord(start.add(offs.scale(animTime / time))); - } - - - @Override - public int hashCode() - { - return Double.valueOf(x).hashCode() ^ Double.valueOf(y).hashCode() ^ Double.valueOf(z).hashCode(); - } - - - /** - * Get if animation is finished - * - * @return is finished - */ - public boolean isFinished() - { - return animTime >= time; - } - - /** * Check if this rectangle in inside a rectangular zone * @@ -347,7 +275,7 @@ public class Coord implements Updateable { */ public Coord midTo(Coord other) { - return add(vecTo(other).scale(0.5)); + return add(vecTo(other).mul_ip(0.5)); } @@ -359,7 +287,7 @@ public class Coord implements Updateable { */ public Coord mul(double d) { - return copy().mul_ip(d); + return getCopy().mul_ip(d); } @@ -373,7 +301,7 @@ public class Coord implements Updateable { */ public Coord mul(double xd, double yd, double zd) { - return copy().mul_ip(xd, yd, zd); + return getCopy().mul_ip(xd, yd, zd); } @@ -417,10 +345,9 @@ public class Coord implements Updateable { */ public Coord random_offset(double max) { - Coord r = random(1); - Vec v = new Vec(r); - v.norm_ip(0.00001 + rand.nextDouble() * max); - return copy().add_ip(v); + Coord v = random(1).norm_ip(0.00001 + rand.nextDouble() * max); + + return getCopy().add_ip(v); } @@ -433,7 +360,7 @@ public class Coord implements Updateable { */ public Coord random_offset(double min, double max) { - return copy().add_ip(random(min, max)); + return getCopy().add_ip(random(min, max)); } @@ -463,17 +390,6 @@ public class Coord implements Updateable { } - /** - * Remember position (other changes will be for animation) - */ - public void remember() - { - if (start == null) start = new Coord(); - if (offs == null) offs = new Vec(); - start.setTo(this); - } - - /** * Get a copy with rounded coords * @@ -481,7 +397,7 @@ public class Coord implements Updateable { */ public Coord round() { - return copy().round_ip(); + return getCopy().round_ip(); } @@ -577,7 +493,7 @@ public class Coord implements Updateable { */ public Coord setX(Number x) { - return copy().setX_ip(x); + return getCopy().setX_ip(x); } @@ -602,7 +518,7 @@ public class Coord implements Updateable { */ public Coord setY(Number y) { - return copy().setY_ip(y); + return getCopy().setY_ip(y); } @@ -627,7 +543,7 @@ public class Coord implements Updateable { */ public Coord setZ(Number z) { - return copy().setZ_ip(z); + return getCopy().setZ_ip(z); } @@ -644,17 +560,6 @@ public class Coord implements Updateable { } - /** - * Get size - * - * @return size - */ - public double size() - { - return new Vec(this).size(); - } - - /** * Get a copy subtracted by vector * @@ -663,7 +568,7 @@ public class Coord implements Updateable { */ public Coord sub(Coord vec) { - return copy().sub_ip(vec); + return getCopy().sub_ip(vec); } @@ -676,7 +581,7 @@ public class Coord implements Updateable { */ public Coord sub(Number x, Number y) { - return copy().sub_ip(x, y); + return getCopy().sub_ip(x, y); } @@ -690,7 +595,7 @@ public class Coord implements Updateable { */ public Coord sub(Number x, Number y, Number z) { - return copy().sub_ip(x, y, z); + return getCopy().sub_ip(x, y, z); } @@ -741,52 +646,15 @@ public class Coord implements Updateable { } - /** - * Convert X and Y coordinates of this coord to a new CoordI. - * - * @return the new CoordI - */ - public CoordI toCoordI() - { - return new CoordI((int) Math.round(x), (int) Math.round(y)); - } - - - @Override - public String toString() - { - return "[ " + x + " ; " + y + " ; " + z + " ]"; - } - - - /** - * Update delta timing - * - * @param delta delta time to add - */ - @Override - public void update(double delta) - { - if (start == null) start = new Coord(); - if (offs == null) offs = new Vec(); - animTime = Calc.clampd(animTime + delta, 0, time); - if (isFinished()) { - time = 0; - animTime = 0; - start.setTo(this); - } - } - - /** * Create vector from this point to other point * * @param point second point * @return vector */ - public Vec vecTo(Coord point) + public Coord vecTo(Coord point) { - return (Vec) (new Vec(point)).add(new Vec(this).neg()); + return point.sub(this); } @@ -896,4 +764,213 @@ public class Coord implements Updateable { { return (int) Math.round(z); } + + + /** + * Get cross product of two vectors + * + * @param a 1st vector + * @param b 2nd vector + * @return cross product + */ + public static Coord cross(Coord a, Coord b) + { + return a.cross(b); + } + + + /** + * Get dot product of two vectors + * + * @param a 1st vector + * @param b 2nd vector + * @return dot product + */ + public static double dot(Coord a, Coord b) + { + return a.dot(b); + } + + + /** + * Multiply by other vector, vector multiplication + * + * @param vec other vector + * @return copy multiplied + */ + public Coord cross(Coord vec) + { + return getCopy().cross_ip(vec); + } + + + /** + * Multiply by other vector, vector multiplication; in place + * + * @param vec other vector + * @return this + */ + public Coord cross_ip(Coord vec) + { + setTo(y * vec.z - z * vec.y, z * vec.x - x * vec.z, x * vec.y - y * vec.x); + return this; + } + + + /** + * Get dot product + * + * @param vec other vector + * @return dot product + */ + public double dot(Coord vec) + { + return x * vec.x + y * vec.y + z * vec.z; + } + + + /** + * Negate all coordinates (* -1) + * + * @return negated coordinate + */ + public Coord neg() + { + return getCopy().neg_ip(); + } + + + /** + * Negate all coordinates (* -1), in place + * + * @return this + */ + public Coord neg_ip() + { + mul_ip(-1); + return this; + } + + + /** + * Scale vector to given size + * + * @param size size we need + * @return scaled vector + */ + public Coord norm(double size) + { + return getCopy().norm_ip(size); + } + + + /** + * Scale vector to given size, in place + * + * @param size size we need + * @return scaled vector + */ + public Coord norm_ip(double size) + { + if (size() == 0) { + z = -1; + } + if (size == 0) { + setTo(0, 0, 0); + return this; + } + double k = size / size(); + mul_ip(k); + return this; + } + + + /** + * Get vector size + * + * @return vector size in units + */ + public double size() + { + return Math.sqrt(x * x + y * y + z * z); + } + + + /** + * Get copy divided by two + * @return copy halved + */ + public Coord half() + { + return getCopy().half_ip(); + } + + + /** + * Divide in place by two + * @return this + */ + public Coord half_ip() + { + mul_ip(0.5); + return this; + } + + + @Override + public String toString() + { + return "[" + x + ", " + y + ", " + z + "]"; + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(x); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(y); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(z); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Coord)) return false; + Coord other = (Coord) obj; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) return false; + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) return false; + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) return false; + return true; + } + + + /** + * @return true if this coord is a zero coord + */ + public boolean isZero() + { + return x == 0 && y == 0 && z == 0; + } + + + public static Coord zero() + { + return new Coord(0, 0, 0); + } + + + public static Coord one() + { + return new Coord(1, 1, 1); + } } diff --git a/src/mightypork/utils/math/coord/CoordAnimated.java b/src/mightypork/utils/math/coord/CoordAnimated.java new file mode 100644 index 0000000..5605fad --- /dev/null +++ b/src/mightypork/utils/math/coord/CoordAnimated.java @@ -0,0 +1,94 @@ +package mightypork.utils.math.coord; + +import mightypork.utils.math.Calc; +import mightypork.utils.time.Updateable; + + +public class CoordAnimated extends Coord implements Updateable { + + private double animTime = 0; + private Coord offs; + private Coord start; + private double time = 0; + + /** + * Update delta timing + * + * @param delta delta time to add + */ + @Override + public void update(double delta) + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Coord(); + animTime = Calc.clampd(animTime + delta, 0, time); + if (animIsFinished()) { + time = 0; + animTime = 0; + start.setTo(this); + } + } + + /** + * Remember position (other changes will be for animation) + */ + public void animRemember() + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Coord(); + start.setTo(this); + offs = Coord.zero(); + } + + /** + * Start animation + * + * @param time anim length + */ + public void animStart(double time) + { + if (start == null) start = new Coord(); + if (offs == null) offs = new Coord(); + this.time = time; + animTime = 0; + offs = start.vecTo(this); + } + + /** + * Stop animation, assign to current value + */ + public void animStop() + { + setTo(animGetCurrent()); + animRemember(); + animTime = 0; + } + + /** + * Get if animation is finished + * + * @return is finished + */ + public boolean animIsFinished() + { + return animTime >= time; + } + + /** + * Get current value (animated) + * + * @return curent value + */ + public Coord animGetCurrent() + { + if (time == 0) return getCopy(); // avoid zero division + + if (start == null) start = new Coord(); + if (offs == null) offs = new Coord(); + + if (animIsFinished()) return this; + + return start.add(offs.mul(animTime / time)); + } + +} diff --git a/src/mightypork/utils/math/coord/CoordI.java b/src/mightypork/utils/math/coord/CoordI.java deleted file mode 100644 index 16fa958..0000000 --- a/src/mightypork/utils/math/coord/CoordI.java +++ /dev/null @@ -1,398 +0,0 @@ -package mightypork.utils.math.coord; - - -/** - * Simple integer coordinate class
- * Unlike Coord, this is suitable for using in array indices etc. - * - * @author MightyPork - */ -public class CoordI { - - /** X coordinate */ - public int x = 0; - /** Y coordinate */ - public int y = 0; - /** Z coordinate */ - public int z = 0; - - - /** - * Create CoordI as copy of other - * - * @param other coord to copy - */ - public CoordI(CoordI other) { - setTo(other); - } - - - /** - * Integer 2D Coord - * - * @param x x coord - * @param y y coord - */ - public CoordI(int x, int y) { - this.x = x; - this.y = y; - } - - - /** - * Integer 3D Coord - * - * @param x x coord - * @param y y coord - * @param z z coord - */ - public CoordI(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - - /** - * Empty cobnstructor 0,0,0 - */ - public CoordI() { - x = 0; - y = 0; - z = 0; - } - - - /** - * Add other coordI coordinates in a copy - * - * @param other coordI to add - * @return copy modified - */ - public CoordI add(CoordI other) - { - return copy().add_ip(other); - } - - - /** - * Add coords in copy - * - * @param x x coord - * @param y y coord - * @return the copy - */ - public CoordI add(int x, int y) - { - return copy().add_ip(x, y, 0); - } - - - /** - * Add coords in copy - * - * @param x x coord - * @param y y coord - * @param z z coord - * @return the copy - */ - public CoordI add(int x, int y, int z) - { - return copy().add_ip(x, y, z); - } - - - /** - * Add other coordI coordinates in place - * - * @param move coordI to add - * @return this - */ - public CoordI add_ip(CoordI move) - { - x += move.x; - y += move.y; - z += move.z; - return this; - } - - - /** - * Add coords in place - * - * @param x x coord - * @param y y coord - * @return this - */ - public CoordI add_ip(int x, int y) - { - this.x += x; - this.y += y; - return this; - } - - - /** - * Add coords in place - * - * @param x x coord - * @param y y coord - * @param z z coord - * @return this - */ - public CoordI add_ip(int x, int y, int z) - { - this.x += x; - this.y += y; - this.z += z; - return this; - } - - - /** - * Get copy - * - * @return copy - */ - public CoordI copy() - { - return new CoordI(x, y, z); - } - - - @Override - public boolean equals(Object obj) - { - if (obj == null) return false; - if (obj instanceof CoordI) return ((CoordI) obj).x == x && ((CoordI) obj).y == y && ((CoordI) obj).z == z; - return false; - } - - - @Override - public int hashCode() - { - return x ^ y ^ z; - } - - - /** - * Middle of this and other coordinate, rounded to CoordI - integers - * - * @param other other coordI - * @return middle CoordI - */ - public CoordI midTo(CoordI other) - { - return new CoordI((x + other.x) / 2, (y + other.y) / 2, (z + other.z) / 2); - } - - - /** - * Multiply in copy 2D - * - * @param x x coord - * @param y y coord - * @return the copy - */ - public CoordI mul(double x, double y) - { - return copy().mul_ip(x, y); - } - - - /** - * Multiply in copy - * - * @param x x coord - * @param y y coord - * @param z z coord - * @return the copy - */ - public CoordI mul(double x, double y, double z) - { - return copy().mul_ip(x, y, z); - } - - - /** - * Multiply in place 2D - * - * @param x x coord - * @param y y coord - * @return this - */ - public CoordI mul_ip(double x, double y) - { - this.x *= x; - this.y *= y; - return this; - } - - - /** - * Multiply in place - * - * @param x x coord - * @param y y coord - * @param z z coord - * @return this - */ - public CoordI mul_ip(double x, double y, double z) - { - this.x *= x; - this.y *= y; - this.z *= z; - return this; - } - - - /** - * Set to coords from other coord - * - * @param other source coord - */ - public void setTo(CoordI other) - { - if (other == null) { - setTo(0, 0, 0); - return; - } - this.x = other.x; - this.y = other.y; - this.z = other.z; - } - - - /** - * Set coords to - * - * @param x x coord to set - * @param y y coord to set - */ - public void setTo(int x, int y) - { - this.x = x; - this.y = y; - } - - - /** - * Set coords to - * - * @param x x coord to set - * @param y y coord to set - * @param z z coord to set - */ - public void setTo(int x, int y, int z) - { - this.x = x; - this.y = y; - this.z = z; - } - - - /** - * Subtract other coordI coordinates in a copy - * - * @param other coordI to subtract - * @return copy subtracted - */ - public CoordI sub(CoordI other) - { - return copy().sub_ip(other); - } - - - /** - * Subtract x,y in a copy - * - * @param x x to subtract - * @param y y to subtract - * @return copy subtracted - */ - public CoordI sub(int x, int y) - { - return copy().sub_ip(new CoordI(x, y)); - } - - - /** - * Subtract x,y,z in a copy - * - * @param x x to subtract - * @param y y to subtract - * @param z z to subtract - * @return copy subtracted - */ - public CoordI sub(int x, int y, int z) - { - return copy().sub_ip(new CoordI(x, y, z)); - } - - - /** - * Subtract other coordI coordinates in place - * - * @param move coordI to subtract - * @return this - */ - public CoordI sub_ip(CoordI move) - { - x -= move.x; - y -= move.y; - z -= move.z; - return this; - } - - - /** - * Sub coords in place - * - * @param x x coord - * @param y y coord - * @return this - */ - public CoordI sub_ip(int x, int y) - { - this.x -= x; - this.y -= y; - return this; - } - - - /** - * Sub coords in place - * - * @param x x coord - * @param y y coord - * @param z z coord - * @return this - */ - public CoordI sub_ip(int x, int y, int z) - { - this.x -= x; - this.y -= y; - this.z -= z; - return this; - } - - - /** - * Convert to double Coord - * - * @return coord with X and y from this CoordI - */ - public Coord toCoord() - { - return new Coord(x, y); - } - - - @Override - public String toString() - { - return "[ " + x + " ; " + y + " ; " + z + " ]"; - } - -} diff --git a/src/mightypork/utils/math/coord/Rect.java b/src/mightypork/utils/math/coord/Rect.java index 8eed249..cabfe9a 100644 --- a/src/mightypork/utils/math/coord/Rect.java +++ b/src/mightypork/utils/math/coord/Rect.java @@ -38,7 +38,7 @@ public class Rect { * @param height size y * @return the new rect */ - public static Rect fromSize(Coord min, int width, int height) + public static Rect fromSize(Coord min, double width, double height) { return new Rect(min, min.add(width, height)); } @@ -47,37 +47,37 @@ public class Rect { /** * Rectangle from size * - * @param i min X - * @param j min Y + * @param x min X + * @param y min Y * @param size rect size * @return the rect */ - public static Rect fromSize(int i, int j, CoordI size) + public static Rect fromSize(int x, int y, Coord size) { - return fromSize(i, j, size.x, size.y); + return fromSize(x, y, size.x, size.y); } /** * Make rect from min coord and size * - * @param x1 min x - * @param y1 min y + * @param xMin min x + * @param yMin min y * @param width size x * @param height size y * @return the new rect */ - public static Rect fromSize(int x1, int y1, int width, int height) + public static Rect fromSize(double xMin, double yMin, double width, double height) { - return new Rect(x1, y1, x1 + width, y1 + height); + return new Rect(xMin, yMin, xMin + width, yMin + height); } - /** Highest coordinates xy */ - protected Coord max = new Coord(); - /** Lowest coordinates xy */ protected Coord min = new Coord(); + /** Highest coordinates xy */ + protected Coord max = new Coord(); + /** * New Rect [0, 0, 0, 0] @@ -127,7 +127,7 @@ public class Rect { * @param x width * @param y height */ - public Rect(int x, int y) { + public Rect(double x, double y) { this(0, 0, x, y); } @@ -163,7 +163,7 @@ public class Rect { */ public Rect add(double x, double y) { - return add(new Vec(x, y)); + return add(new Coord(x, y)); } @@ -190,7 +190,7 @@ public class Rect { */ public Rect add_ip(double x, double y) { - return add_ip(new Vec(x, y)); + return add_ip(new Coord(x, y)); } @@ -718,7 +718,7 @@ public class Rect { */ public Rect sub(double x, double y) { - return sub(new Vec(x, y)); + return sub(new Coord(x, y)); } @@ -728,7 +728,7 @@ public class Rect { * @param move offset vector * @return offset copy */ - public Rect sub(Vec move) + public Rect sub(Coord move) { return copy().sub_ip(move); } @@ -743,7 +743,7 @@ public class Rect { */ public Rect sub_ip(double x, double y) { - return sub_ip(new Vec(x, y)); + return sub_ip(new Coord(x, y)); } @@ -753,7 +753,7 @@ public class Rect { * @param move offset vector * @return this */ - public Rect sub_ip(Vec move) + public Rect sub_ip(Coord move) { min.sub_ip(move); max.sub_ip(move); @@ -771,7 +771,7 @@ public class Rect { /** * @return lower x */ - public double x1() + public double xMin() { return min.x; } @@ -780,7 +780,7 @@ public class Rect { /** * @return upper x */ - public double x2() + public double xMax() { return max.x; } @@ -789,7 +789,7 @@ public class Rect { /** * @return lower y */ - public double y1() + public double yMin() { return min.y; } @@ -798,7 +798,7 @@ public class Rect { /** * @return upper y */ - public double y2() + public double yMax() { return max.y; } diff --git a/src/mightypork/utils/math/coord/Vec.java b/src/mightypork/utils/math/coord/Vec.java deleted file mode 100644 index e6f8e51..0000000 --- a/src/mightypork/utils/math/coord/Vec.java +++ /dev/null @@ -1,326 +0,0 @@ -package mightypork.utils.math.coord; - - -/** - * Vector in 2D/3D space. - * - * @author MightyPork - */ -public class Vec extends Coord { - - /** Vec [1;1;1] */ - @SuppressWarnings("hiding") - public static final Vec ONE = new Vec(1, 1, 1); - /** Zero vector */ - @SuppressWarnings("hiding") - public static final Vec ZERO = new Vec(0, 0, 0); - - - /** - * Get cross product of two vectors - * - * @param a 1st vector - * @param b 2nd vector - * @return cross product - */ - public static Vec cross(Vec a, Vec b) - { - return a.cross(b); - } - - - /** - * Get dot product of two vectors - * - * @param a 1st vector - * @param b 2nd vector - * @return dot product - */ - public static double dot(Vec a, Vec b) - { - return a.dot(b); - } - - - /** - * Generate random coord (gaussian) - * - * @param max max distance from 0 - * @return new coord - */ - public static Vec random(double max) - { - return new Vec(Coord.random(max)); - } - - - /** - * Generate random coord (min-max) - * - * @param max max distance from 0 - * @return new coord - */ - public static Vec random(double min, double max) - { - return new Vec(Coord.random(min, max)); - } - - - /** - * Scale vector - * - * @param a vector - * @param scale - * @return scaled copy - */ - public static Vec scale(Vec a, double scale) - { - return a.scale(scale); - } - - - /** - * Get vector size - * - * @param vec vector to get size of - * @return size in units - */ - public static double size(Vec vec) - { - return vec.size(); - } - - - /** - * Create zero vector - */ - public Vec() { - super(); - } - - - /** - * Create vector as a copy of another - * - * @param copied copied vector - */ - public Vec(Coord copied) { - super(copied); - } - - - /** - * Create 2D vector - * - * @param x x coordinate - * @param y y coordinate - */ - public Vec(Number x, Number y) { - super(x, y); - } - - - /** - * Create 3D vector - * - * @param x x coordinate - * @param y y coordinate - * @param z z coordinate - */ - public Vec(Number x, Number y, Number z) { - super(x, y, z); - } - - - @Override - public Vec copy() - { - return new Vec(this); - } - - - /** - * Multiply by other vector, vector multiplication - * - * @param vec other vector - * @return copy multiplied - */ - public Vec cross(Vec vec) - { - return copy().cross_ip(vec); - } - - - /** - * Multiply by other vector, vector multiplication; in place - * - * @param vec other vector - * @return this - */ - public Vec cross_ip(Vec vec) - { - setTo(y * vec.z - z * vec.y, z * vec.x - x * vec.z, x * vec.y - y * vec.x); - return this; - } - - - /** - * Get dot product - * - * @param vec other vector - * @return dot product - */ - public double dot(Vec vec) - { - return x * vec.x + y * vec.y + z * vec.z; - } - - - // STATIC - - /** - * Negate all coordinates (* -1) - * - * @return negated coordinate - */ - public Vec neg() - { - return copy().neg_ip(); - } - - - /** - * Negate all coordinates (* -1), in place - * - * @return this - */ - public Vec neg_ip() - { - scale_ip(-1); - return this; - } - - - /** - * Scale vector to given size - * - * @param size size we need - * @return scaled vector - */ - public Vec norm(double size) - { - return copy().norm_ip(size); - } - - - /** - * Scale vector to given size, in place - * - * @param size size we need - * @return scaled vector - */ - public Vec norm_ip(double size) - { - if (size() == 0) { - z = -1; - } - if (size == 0) { - setTo(0, 0, 0); - return this; - } - double k = size / size(); - scale_ip(k); - return this; - } - - - /** - * offset randomly - * - * @param max max +- offset - * @return offset coord - */ - @Override - public Vec random_offset(double max) - { - return (Vec) super.random_offset(max); - } - - - /** - * offset randomly - * - * @param min min offset - * @param max max offset - * @return offset coord - */ - @Override - public Vec random_offset(double min, double max) - { - return (Vec) super.random_offset(min, max); - } - - - /** - * offset randomly in place - * - * @param max max +- offset - * @return this - */ - @Override - public Vec random_offset_ip(double max) - { - return (Vec) super.random_offset_ip(max); - } - - - /** - * offset randomly in place - * - * @param min min offset - * @param max max offset - * @return this - */ - @Override - public Vec random_offset_ip(double min, double max) - { - return (Vec) super.random_offset_ip(min, max); - } - - - /** - * Multiply all coordinates by factor; scalar multiplication - * - * @param factor multiplier - * @return copy multiplied - */ - public Vec scale(double factor) - { - return copy().scale_ip(factor); - } - - - /** - * Multiply all coordinates by factor, in place - * - * @param factor multiplier - * @return this - */ - public Vec scale_ip(double factor) - { - return (Vec) mul_ip(factor); - } - - - /** - * Get vector size - * - * @return vector size in units - */ - @Override - public double size() - { - return Math.sqrt(x * x + y * y + z * z); - } - -} diff --git a/src/mightypork/utils/math/easing/Easing.java b/src/mightypork/utils/math/easing/Easing.java new file mode 100644 index 0000000..cf5b51e --- /dev/null +++ b/src/mightypork/utils/math/easing/Easing.java @@ -0,0 +1,322 @@ +package mightypork.utils.math.easing; + + +import mightypork.utils.math.Calc; + + +/** + * Easing function.
+ * The easing function must be time-invariant and it's domain is [0-1]. + * + * @author MightyPork + */ +public interface Easing { + + /** + * Get value of the easing function at given time. + * + * @param time number in range 0..1 + * @return value at given time + */ + public double get(double time); + + public static final Easing NONE = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return (t < 0.5 ? 0 : 1); + } + }; + + public static final Easing LINEAR = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return t; + } + }; + + public static final Easing QUADRATIC_IN = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return t * t; + } + }; + + public static final Easing QUADRATIC_OUT = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return 1 - (t - 1) * (t - 1); + } + }; + + public static final Easing QUADRATIC = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + if (t < 0.5) return QUADRATIC_IN.get(2 * t) * 0.5; + return 0.5 + QUADRATIC_OUT.get(2 * t - 1) * 0.5; + } + }; + + public static final Easing CUBIC_IN = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return t * t * t; + } + }; + public static final Easing CUBIC_OUT = new Easing() { + + @Override + public double get(double time) + { + double t = Calc.clampd(time, 0, 1); + + return (t - 1) * (t - 1) * (t - 1) + 1; + } + }; + public static final Easing CUBIC = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d / 2; + if (t < 1) return c / 2 * t * t * t + b; + t -= 2; + return c / 2 * (t * t * t + 2) + b; + } + }; + public static final Easing QUARTIC_IN = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d; + return c * t * t * t * t + b; + } + }; + public static final Easing QUARTIC_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + t /= d; + t--; + return -c * (t * t * t * t - 1) + b; + } + }; + public static final Easing QUARTIC = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + t /= d / 2; + if (t < 1) return c / 2 * t * t * t * t + b; + t -= 2; + return -c / 2 * (t * t * t * t - 2) + b; + } + }; + public static final Easing QUINTIC_IN = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d; + return c * t * t * t * t * t + b; + } + }; + public static final Easing QUINTIC_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d; + t--; + return c * (t * t * t * t * t + 1) + b; + } + }; + public static final Easing QUINTIC_IN_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d / 2; + if (t < 1) return c / 2 * t * t * t * t * t + b; + t -= 2; + return c / 2 * (t * t * t * t * t + 2) + b; + } + }; + public static final Easing SINE_IN = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + }; + public static final Easing SINE_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + return c * Math.sin(t / d * (Math.PI / 2)) + b; + } + }; + public static final Easing SINE = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + } + }; + public static final Easing EXPO_IN = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + return c * Math.pow(2, 10 * (t / d - 1)) + b; + } + }; + public static final Easing EXPO_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + return c * (-Math.pow(2, -10 * t / d) + 1) + b; + } + }; + public static final Easing EXPO = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + t /= d / 2; + if (t < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + t--; + return c / 2 * (-Math.pow(2, -10 * t) + 2) + b; + } + }; + public static final Easing CIRC_IN = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + t /= d; + return -c * (Math.sqrt(1 - t * t) - 1) + b; + } + }; + public static final Easing CIRC_OUT = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + t--; + return c * Math.sqrt(1 - t * t) + b; + } + }; + public static final Easing CIRC = new Easing() { + + @Override + public double get(double time) + { + double d = 1; + double t = time; + double b = 0; + double c = (1 - 0); + + t /= d / 2; + if (t < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + t -= 2; + return c / 2 * (Math.sqrt(1 - t * t) + 1) + b; + } + }; +} diff --git a/src/mightypork/utils/objects/Convertor.java b/src/mightypork/utils/objects/Convertor.java index 3b1f8db..c8c36f5 100644 --- a/src/mightypork/utils/objects/Convertor.java +++ b/src/mightypork/utils/objects/Convertor.java @@ -4,7 +4,6 @@ package mightypork.utils.objects; import mightypork.utils.logging.Log; import mightypork.utils.math.Range; import mightypork.utils.math.coord.Coord; -import mightypork.utils.math.coord.CoordI; /** @@ -154,7 +153,6 @@ public class Convertor { return new Coord(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim())); } if (o instanceof Coord) return new Coord((Coord) o); - if (o instanceof CoordI) return ((CoordI) o).toCoord(); } catch (NumberFormatException e) { // ignore } @@ -267,7 +265,7 @@ public class Convertor { */ public static Coord getCoord(Object o) { - return getCoord(o, Coord.ZERO.copy()); + return getCoord(o, Coord.zero()); } diff --git a/src/mightypork/utils/objects/Mutable.java b/src/mightypork/utils/objects/Mutable.java index 0440cbe..5cc59c5 100644 --- a/src/mightypork/utils/objects/Mutable.java +++ b/src/mightypork/utils/objects/Mutable.java @@ -10,17 +10,11 @@ package mightypork.utils.objects; public class Mutable { /** The wrapped value */ - public T o = null; + private T o = null; /** - * Implicint constructor - */ - public Mutable() {} - - - /** - * new mutable object + * New mutable object * * @param o value */ @@ -49,4 +43,38 @@ public class Mutable { { this.o = o; } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((o == null) ? 0 : o.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof Mutable)) return false; + + Mutable other = (Mutable) obj; + if (o == null) { + if (other.o != null) return false; + } else if (!o.equals(other.o)) { + return false; + } + return true; + } + + @Override + public String toString() + { + if(o == null) return ""; + return o.toString(); + } } diff --git a/src/mightypork/utils/patterns/Destroyable.java b/src/mightypork/utils/patterns/Destroyable.java new file mode 100644 index 0000000..17569be --- /dev/null +++ b/src/mightypork/utils/patterns/Destroyable.java @@ -0,0 +1,14 @@ +package mightypork.utils.patterns; + + +/** + * Object that can be destroyed (free resources etc) + * + * @author MightyPork + */ +public interface Destroyable { + /** + * Destroy this object + */ + public void destroy(); +} diff --git a/src/mightypork/utils/patterns/Initializable.java b/src/mightypork/utils/patterns/Initializable.java new file mode 100644 index 0000000..12d50cc --- /dev/null +++ b/src/mightypork/utils/patterns/Initializable.java @@ -0,0 +1,14 @@ +package mightypork.utils.patterns; + + +/** + * Object that can be initialized + * + * @author MightyPork + */ +public interface Initializable { + /** + * Initialize if not initialized yet + */ + public void initialize(); +} diff --git a/src/mightypork/utils/patterns/subscription/Handleable.java b/src/mightypork/utils/patterns/subscription/Handleable.java new file mode 100644 index 0000000..815b323 --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/Handleable.java @@ -0,0 +1,18 @@ +package mightypork.utils.patterns.subscription; + + +/** + * Something that can be handled by HANDLER. + * + * @author MightyPork + * @param handler type + */ +public interface Handleable { + + /** + * Ask handler to handle this message. + * + * @param handler handler instance + */ + public void handleBy(HANDLER handler); +} diff --git a/src/mightypork/utils/patterns/subscription/MessageBus.java b/src/mightypork/utils/patterns/subscription/MessageBus.java new file mode 100644 index 0000000..1d6afe7 --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/MessageBus.java @@ -0,0 +1,121 @@ +package mightypork.utils.patterns.subscription; + +import java.util.LinkedHashSet; +import java.util.Set; + +import mightypork.utils.logging.Log; + + +/** + * An event bus, accommodating multiple {@link MessageChannel}s. + * + * @author MightyPork + */ +public class MessageBus implements Subscribable { + + private Set> channels = new LinkedHashSet>(); + private Set clients = new LinkedHashSet(); + + + /** + * Add a {@link MessageChannel} to this bus.
+ * If a channel of matching types is already added, it is returned instead. + * + * @param channel channel to be added + * @return the channel that's now in the bus + */ + public MessageChannel addChannel(MessageChannel channel) + { + // if the channel already exists, return this instance instead. + for (MessageChannel ch : channels) { + if (ch.equals(channel)) { + Log.w("Channel of type "+channel+" already registered."); + return ch; + } + } + + channels.add(channel); + + for (Object c : clients) { + channel.addSubscriber(c); + } + + return channel; + } + + + /** + * Remove a {@link MessageChannel} from this bus + * + * @param channel true if channel was removed + */ + public void removeChannel(MessageChannel channel) + { + channels.remove(channel); + + for (Object c : clients) { + channel.removeSubscriber(c); + } + } + + + /** + * Broadcast a message + * + * @param message message + * @return true if message was accepted by at least one channel + */ + public boolean broadcast(Object message) + { + boolean sent = false; + for (MessageChannel b : channels) { + sent |= b.broadcast(message); + } + return sent; + } + + + /** + * Subscribe a client to the bus. The client will be connected to all + * current and future channels, until removed from the bus. + * + * @return true + */ + @Override + public boolean addSubscriber(Object client) + { + for (MessageChannel b : channels) { + b.addSubscriber(client); + } + + clients.add(client); + + return true; + } + + + @Override + public void removeSubscriber(Object client) + { + for (MessageChannel b : channels) { + b.removeSubscriber(client); + } + + clients.remove(client); + } + + + /** + * Add a channel for given message and client type. + * + * @param messageClass message type + * @param clientClass client type + * @return the created channel instance + */ + public , F_CLIENT> MessageChannel registerMessageType(Class messageClass, Class clientClass) + { + MessageChannel bc = new MessageChannel(messageClass, clientClass); + return addChannel(bc); + } + +} diff --git a/src/mightypork/utils/patterns/subscription/MessageChannel.java b/src/mightypork/utils/patterns/subscription/MessageChannel.java new file mode 100644 index 0000000..dd96dde --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/MessageChannel.java @@ -0,0 +1,156 @@ +package mightypork.utils.patterns.subscription; + + +import java.util.HashSet; +import java.util.Set; + + +/** + * Message subsystem (broadcaster with clients) to which clients can subscribe.
+ * If more than one type of message is needed, {@link MessageBus} is a better + * choice. + * + * @author MightyPork + * @param message type + * @param client (subscriber) type + */ +public final class MessageChannel, CLIENT> implements Subscribable { + + private Set clients = new HashSet(); + + private Class clientClass; + private Class messageClass; + + + public MessageChannel(Class messageClass, Class clientClass) { + + if(messageClass == null || clientClass == null) throw new IllegalArgumentException("Null Message or Client class."); + + this.clientClass = clientClass; + this.messageClass = messageClass; + } + + + @Override + public boolean addSubscriber(Object client) + { + if (!canSubscribe(client)) return false; + + clients.add(clientClass.cast(client)); + return true; + } + + + @Override + public void removeSubscriber(Object client) + { + clients.remove(client); + } + + + /** + * Try to broadcast a message.
+ * If message is of wrong type, false is returned. + * + * @param message a message to send + * @return true if message was sent + */ + public boolean broadcast(Object message) + { + + if (!canBroadcast(message)) return false; + + MESSAGE evt = messageClass.cast(message); + + for (CLIENT client : clients) { + sendTo(client, evt); + } + + return true; + } + + + /** + * Send a message to a client + * + * @param client target client + * @param message message to send + */ + private void sendTo(CLIENT client, MESSAGE message) + { + ((Handleable) message).handleBy(client); + } + + + /** + * Check if the given message can be broadcasted by this + * {@link MessageChannel} + * + * @param maybeMessage event object + * @return can be broadcasted + */ + private boolean canBroadcast(Object maybeMessage) + { + return messageClass.isInstance(maybeMessage); + } + + + /** + * Check if a client can subscribe to this {@link MessageChannel} + * + * @param maybeClient client asking for subscription + * @return can subscribe + */ + public boolean canSubscribe(Object maybeClient) + { + return clientClass.isInstance(maybeClient); + } + + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + clientClass.getName().hashCode(); + result = prime * result + messageClass.getName().hashCode(); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof MessageChannel)) return false; + + MessageChannel other = (MessageChannel) obj; + + if (!clientClass.getName().equals(other.clientClass.getName())) return false; + + if (!messageClass.getName().equals(other.messageClass.getName())) return false; + + return true; + } + + @Override + public String toString() + { + return "CHANNEL( "+messageClass.getSimpleName()+" -> "+clientClass.getSimpleName()+" )"; + } + + + /** + * Create an instance for given types + * + * @param messageClass event class + * @param clientClass client class + * @return the broadcaster + */ + public static , F_CLIENT> MessageChannel create(Class messageClass, Class clientClass) + { + return new MessageChannel(messageClass, clientClass); + } + +} diff --git a/src/mightypork/utils/patterns/subscription/Subscribable.java b/src/mightypork/utils/patterns/subscription/Subscribable.java new file mode 100644 index 0000000..3c539fd --- /dev/null +++ b/src/mightypork/utils/patterns/subscription/Subscribable.java @@ -0,0 +1,26 @@ +package mightypork.utils.patterns.subscription; + + +/** + * Subscribable object + * + * @author MightyPork + */ +public interface Subscribable { + + /** + * Subscribe a client to messages from this object + * + * @param client a subscribing client + * @return true if client is now subscribed + */ + public boolean addSubscriber(Object client); + + + /** + * Unsubscribe a client from from this object + * + * @param client a clientto unsubscribe + */ + public void removeSubscriber(Object client); +} diff --git a/src/mightypork/utils/time/AnimDouble.java b/src/mightypork/utils/time/AnimDouble.java index 48d9648..7148e26 100644 --- a/src/mightypork/utils/time/AnimDouble.java +++ b/src/mightypork/utils/time/AnimDouble.java @@ -2,6 +2,7 @@ package mightypork.utils.time; import mightypork.utils.math.Calc; +import mightypork.utils.math.easing.Easing; /** @@ -12,10 +13,10 @@ import mightypork.utils.math.Calc; public class AnimDouble implements Updateable, Pauseable { /** target double */ - protected double endValue = 0; + protected double to = 0; /** last tick double */ - protected double startValue = 0; + protected double from = 0; /** how long the transition should last */ protected double duration = 0; @@ -26,51 +27,91 @@ public class AnimDouble implements Updateable, Pauseable { /** True if this animator is paused */ protected boolean paused = false; + /** Easing fn */ + protected Easing easing = Easing.LINEAR; + /** - * @param value value + * Create linear animator + * + * @param value initial value */ public AnimDouble(double value) { setTo(value); } + /** + * Create animator with easing + * + * @param value initial value + * @param easing easing function + */ + public AnimDouble(double value, Easing easing) { + setTo(value); + setEasing(easing); + } + + + /** + * Create as copy of another + * + * @param other other animator + */ public AnimDouble(AnimDouble other) { setTo(other); } + /** + * @return easing function + */ + public Easing getEasing() + { + return easing; + } + + + /** + * @param easing easing function + */ + public void setEasing(Easing easing) + { + this.easing = easing; + } + + /** * Get start value * * @return number */ - public double getStartValue() + public double getFrom() { - return startValue; + return from; } /** - * Get value at delta time + * Get end value * - * @return the value + * @return number */ - public double getCurrentValue() + public double getTo() { - if (duration == 0) return endValue; - return Calc.interpolate(startValue, endValue, elapsedTime / duration); + return to; } /** - * Get end value + * Get value at delta time * - * @return number + * @return the value */ - public double getEndValue() + public double getCurrentValue() { - return endValue; + if (duration == 0) return to; + return Calc.interpolate(from, to, (elapsedTime / duration), easing); } @@ -89,11 +130,13 @@ public class AnimDouble implements Updateable, Pauseable { @Override public void update(double delta) { + if (paused) return; + elapsedTime = Calc.clampd(elapsedTime + delta, 0, duration); if (isFinished()) { duration = 0; elapsedTime = 0; - startValue = endValue; + from = to; } } @@ -116,7 +159,7 @@ public class AnimDouble implements Updateable, Pauseable { */ public void setTo(double value) { - startValue = endValue = value; + from = to = value; elapsedTime = 0; duration = 0; } @@ -129,52 +172,61 @@ public class AnimDouble implements Updateable, Pauseable { */ public void setTo(AnimDouble other) { - this.startValue = other.startValue; - this.endValue = other.endValue; + this.from = other.from; + this.to = other.to; this.duration = other.duration; this.elapsedTime = other.elapsedTime; this.paused = other.paused; + this.easing = other.easing; } /** - * Animate between two states, discard current state + * Animate between two states, start from current value (if it's in between) * - * @param from initial state + * @param from start value * @param to target state * @param time animation time (secs) */ public void animate(double from, double to, double time) { - startValue = from; - endValue = to; - duration = time; - elapsedTime = 0; + double current = getCurrentValue(); + + this.from = from; + this.to = to; + this.duration = time * (1 - getProgressFromValue(current)); + this.elapsedTime = 0; + } + + + protected double getProgressFromValue(double value) + { + double p = 0; + + if (value >= from && value <= to) { // up + p = ((value - from) / (to - from)); + } else if (value >= to && value <= from) { // down + p = ((from - value) / (from - to)); + } + + return p; } /** - * Animate between two states, start from current value (if it's in between) + * Animate to a value from curretn value * - * @param from start value * @param to target state * @param time animation time (secs) */ - public void fadeTo(double from, double to, double time) + public void fadeTo(double to, double time) { double current = getCurrentValue(); - startValue = from; - endValue = to; - duration = time; - elapsedTime = 0; - - // if in between, pick up from where it is - if (current >= from && current <= to) { // up - elapsedTime = ((current - from) / (to - from)) * time; - } else if (current >= to && current <= from) { // down - elapsedTime = ((from - current) / (from - to)) * time; - } + this.from = current; + this.to = to; + this.duration = time; + this.elapsedTime = 0; } @@ -185,7 +237,7 @@ public class AnimDouble implements Updateable, Pauseable { */ public void fadeIn(double time) { - fadeTo(0, 1, time); + animate(0, 1, time); } @@ -196,7 +248,7 @@ public class AnimDouble implements Updateable, Pauseable { */ public void fadeOut(double time) { - fadeTo(1, 0, time); + animate(1, 0, time); } @@ -214,7 +266,7 @@ public class AnimDouble implements Updateable, Pauseable { @Override public String toString() { - return "Animation(" + startValue + " -> " + endValue + ", t=" + duration + "s, elapsed=" + elapsedTime + "s)"; + return "Animation(" + from + " -> " + to + ", t=" + duration + "s, elapsed=" + elapsedTime + "s)"; } @@ -223,7 +275,7 @@ public class AnimDouble implements Updateable, Pauseable { */ public void clear() { - startValue = endValue = 0; + from = to = 0; elapsedTime = 0; duration = 0; paused = false; @@ -235,7 +287,7 @@ public class AnimDouble implements Updateable, Pauseable { */ public void stop() { - startValue = endValue = getCurrentValue(); + from = to = getCurrentValue(); elapsedTime = 0; duration = 0; } diff --git a/src/mightypork/utils/time/AnimDoubleDeg.java b/src/mightypork/utils/time/AnimDoubleDeg.java index 455fb3e..dfcd7ae 100644 --- a/src/mightypork/utils/time/AnimDoubleDeg.java +++ b/src/mightypork/utils/time/AnimDoubleDeg.java @@ -2,40 +2,49 @@ package mightypork.utils.time; import mightypork.utils.math.Calc; +import mightypork.utils.math.Calc.Deg; +import mightypork.utils.math.easing.Easing; /** - * Double which supports delta timing + * Degree animator * * @author MightyPork */ public class AnimDoubleDeg extends AnimDouble { - /** - * new AnimDoubleDeg - * - * @param d value - */ - public AnimDoubleDeg(double d) { - super(d); + public AnimDoubleDeg(AnimDouble other) { + super(other); + } + + + public AnimDoubleDeg(double value) { + super(value); + } + + + public AnimDoubleDeg(double value, Easing easing) { + super(value, easing); } - /** - * Get value at delta time - * - * @return the value - */ @Override public double getCurrentValue() { - return Calc.interpolateDeg(startValue, endValue, elapsedTime / duration); + if (duration == 0) return Deg.norm(to); + return Calc.interpolateDeg(from, to, (elapsedTime / duration), easing); } @Override - public void fadeTo(double from, double to, double time) + protected double getProgressFromValue(double value) { - throw new UnsupportedOperationException("Cannot fadeTo in AnimDoubleDeg. Use animate() instead."); + double whole = Deg.diff(from, to); + if (Deg.diff(value, from) < whole && Deg.diff(value, to) < whole) { + double partial = Deg.diff(from, value); + return partial / whole; + } + + return 0; } } diff --git a/src/mightypork/utils/time/AnimDoubleRad.java b/src/mightypork/utils/time/AnimDoubleRad.java new file mode 100644 index 0000000..d2447e0 --- /dev/null +++ b/src/mightypork/utils/time/AnimDoubleRad.java @@ -0,0 +1,50 @@ +package mightypork.utils.time; + + +import mightypork.utils.math.Calc; +import mightypork.utils.math.Calc.Rad; +import mightypork.utils.math.easing.Easing; + + +/** + * Radians animator + * + * @author MightyPork + */ +public class AnimDoubleRad extends AnimDouble { + + public AnimDoubleRad(AnimDouble other) { + super(other); + } + + + public AnimDoubleRad(double value) { + super(value); + } + + + public AnimDoubleRad(double value, Easing easing) { + super(value, easing); + } + + + @Override + public double getCurrentValue() + { + if (duration == 0) return Rad.norm(to); + return Calc.interpolateRad(from, to, (elapsedTime / duration), easing); + } + + + @Override + protected double getProgressFromValue(double value) + { + double whole = Rad.diff(from, to); + if (Rad.diff(value, from) < whole && Rad.diff(value, to) < whole) { + double partial = Rad.diff(from, value); + return partial / whole; + } + + return 0; + } +}