diff --git a/.classpath b/.classpath index 486b09c..628a8ab 100644 --- a/.classpath +++ b/.classpath @@ -2,16 +2,35 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 9d66a63..7f5350a 100644 --- a/README.md +++ b/README.md @@ -11,21 +11,18 @@ Goals Features -------- -**SUBJECT TO CHANGE!** - - Randomly generated floors -- Hybrid turn-based gameplay (lower and upper time limit) -- Automatic turn-based combat -- Health, hunger, experience meters; levels -- Entities with basic AI (Monsters) -- Items, weapons, armor -- Treasure chests -- Potions -- Inventory system with 12 slots (+ armor) +- Hybrid turn-based gameplay +- Stats and leveling +- Monsters +- Collectable items +- Potions, food +- Simple inventory system Used libraries -------------- -- libGDX -- mini2Dx \ No newline at end of file +- Slick2D +- NiftyGUI +- LWJGL \ No newline at end of file diff --git a/lib/OpenAL32.dll b/lib/OpenAL32.dll new file mode 100644 index 0000000..6dd2600 Binary files /dev/null and b/lib/OpenAL32.dll differ diff --git a/lib/OpenAL64.dll b/lib/OpenAL64.dll new file mode 100644 index 0000000..00c98c0 Binary files /dev/null and b/lib/OpenAL64.dll differ diff --git a/lib/gdx-backend-lwjgl-natives.jar b/lib/gdx-backend-lwjgl-natives.jar deleted file mode 100644 index f7f2119..0000000 Binary files a/lib/gdx-backend-lwjgl-natives.jar and /dev/null differ diff --git a/lib/gdx-backend-lwjgl-sources.jar b/lib/gdx-backend-lwjgl-sources.jar deleted file mode 100644 index f1182f0..0000000 Binary files a/lib/gdx-backend-lwjgl-sources.jar and /dev/null differ diff --git a/lib/gdx-backend-lwjgl.jar b/lib/gdx-backend-lwjgl.jar deleted file mode 100644 index f6de13d..0000000 Binary files a/lib/gdx-backend-lwjgl.jar and /dev/null differ diff --git a/lib/gdx-freetype-natives.jar b/lib/gdx-freetype-natives.jar deleted file mode 100644 index 8cb1a79..0000000 Binary files a/lib/gdx-freetype-natives.jar and /dev/null differ diff --git a/lib/gdx-freetype-sources.jar b/lib/gdx-freetype-sources.jar deleted file mode 100644 index 9d19032..0000000 Binary files a/lib/gdx-freetype-sources.jar and /dev/null differ diff --git a/lib/gdx-freetype.jar b/lib/gdx-freetype.jar deleted file mode 100644 index 7df6d4b..0000000 Binary files a/lib/gdx-freetype.jar and /dev/null differ diff --git a/lib/gdx-natives.jar b/lib/gdx-natives.jar deleted file mode 100644 index 51953b1..0000000 Binary files a/lib/gdx-natives.jar and /dev/null differ diff --git a/lib/gdx-sources.zip b/lib/gdx-sources.zip deleted file mode 100644 index 1a88c12..0000000 Binary files a/lib/gdx-sources.zip and /dev/null differ diff --git a/lib/gdx.jar b/lib/gdx.jar deleted file mode 100644 index 6e9560a..0000000 Binary files a/lib/gdx.jar and /dev/null differ diff --git a/lib/jinput-dx8.dll b/lib/jinput-dx8.dll new file mode 100644 index 0000000..6d27ad5 Binary files /dev/null and b/lib/jinput-dx8.dll differ diff --git a/lib/jinput-dx8_64.dll b/lib/jinput-dx8_64.dll new file mode 100644 index 0000000..6730589 Binary files /dev/null and b/lib/jinput-dx8_64.dll differ diff --git a/lib/jinput-raw.dll b/lib/jinput-raw.dll new file mode 100644 index 0000000..ce1d162 Binary files /dev/null and b/lib/jinput-raw.dll differ diff --git a/lib/jinput-raw_64.dll b/lib/jinput-raw_64.dll new file mode 100644 index 0000000..3d2b3ad Binary files /dev/null and b/lib/jinput-raw_64.dll differ diff --git a/lib/jinput.jar b/lib/jinput.jar new file mode 100644 index 0000000..7c2b6b0 Binary files /dev/null and b/lib/jinput.jar differ diff --git a/lib/jogg-0.0.7.jar b/lib/jogg-0.0.7.jar new file mode 100644 index 0000000..ecb0260 Binary files /dev/null and b/lib/jogg-0.0.7.jar differ diff --git a/lib/jorbis-0.0.15.jar b/lib/jorbis-0.0.15.jar new file mode 100644 index 0000000..4cf51f9 Binary files /dev/null and b/lib/jorbis-0.0.15.jar differ diff --git a/lib/libjinput-linux.so b/lib/libjinput-linux.so new file mode 100644 index 0000000..3cdc439 Binary files /dev/null and b/lib/libjinput-linux.so differ diff --git a/lib/libjinput-linux64.so b/lib/libjinput-linux64.so new file mode 100644 index 0000000..de1ee5f Binary files /dev/null and b/lib/libjinput-linux64.so differ diff --git a/lib/libjinput-osx.jnilib b/lib/libjinput-osx.jnilib new file mode 100644 index 0000000..59a3eab Binary files /dev/null and b/lib/libjinput-osx.jnilib differ diff --git a/lib/liblwjgl.jnilib b/lib/liblwjgl.jnilib new file mode 100644 index 0000000..cbb5b4c Binary files /dev/null and b/lib/liblwjgl.jnilib differ diff --git a/lib/liblwjgl.so b/lib/liblwjgl.so new file mode 100644 index 0000000..5a02874 Binary files /dev/null and b/lib/liblwjgl.so differ diff --git a/lib/liblwjgl64.so b/lib/liblwjgl64.so new file mode 100644 index 0000000..4572589 Binary files /dev/null and b/lib/liblwjgl64.so differ diff --git a/lib/libopenal.so b/lib/libopenal.so new file mode 100644 index 0000000..7742faf Binary files /dev/null and b/lib/libopenal.so differ diff --git a/lib/libopenal64.so b/lib/libopenal64.so new file mode 100644 index 0000000..d1e45e5 Binary files /dev/null and b/lib/libopenal64.so differ diff --git a/lib/lwjgl-source-2.8.4.zip b/lib/lwjgl-source-2.8.4.zip new file mode 100644 index 0000000..f48fd31 Binary files /dev/null and b/lib/lwjgl-source-2.8.4.zip differ diff --git a/lib/lwjgl.dll b/lib/lwjgl.dll new file mode 100644 index 0000000..6819404 Binary files /dev/null and b/lib/lwjgl.dll differ diff --git a/lib/lwjgl.jar b/lib/lwjgl.jar new file mode 100644 index 0000000..a0fb56d Binary files /dev/null and b/lib/lwjgl.jar differ diff --git a/lib/lwjgl64.dll b/lib/lwjgl64.dll new file mode 100644 index 0000000..e66ab2a Binary files /dev/null and b/lib/lwjgl64.dll differ diff --git a/lib/lwjgl_util.jar b/lib/lwjgl_util.jar new file mode 100644 index 0000000..9973b24 Binary files /dev/null and b/lib/lwjgl_util.jar differ diff --git a/lib/mini2Dx-core-source.zip b/lib/mini2Dx-core-source.zip deleted file mode 100644 index 204c444..0000000 Binary files a/lib/mini2Dx-core-source.zip and /dev/null differ diff --git a/lib/mini2Dx-core.jar b/lib/mini2Dx-core.jar deleted file mode 100644 index 68ca5e7..0000000 Binary files a/lib/mini2Dx-core.jar and /dev/null differ diff --git a/lib/mini2Dx-dependency-injection-desktop-source.zip b/lib/mini2Dx-dependency-injection-desktop-source.zip deleted file mode 100644 index 7de2dd5..0000000 Binary files a/lib/mini2Dx-dependency-injection-desktop-source.zip and /dev/null differ diff --git a/lib/mini2Dx-dependency-injection-desktop.jar b/lib/mini2Dx-dependency-injection-desktop.jar deleted file mode 100644 index 0e0101e..0000000 Binary files a/lib/mini2Dx-dependency-injection-desktop.jar and /dev/null differ diff --git a/lib/mini2Dx-dependency-injection-source.zip b/lib/mini2Dx-dependency-injection-source.zip deleted file mode 100644 index e3a0da9..0000000 Binary files a/lib/mini2Dx-dependency-injection-source.zip and /dev/null differ diff --git a/lib/mini2Dx-dependency-injection.jar b/lib/mini2Dx-dependency-injection.jar deleted file mode 100644 index e870ced..0000000 Binary files a/lib/mini2Dx-dependency-injection.jar and /dev/null differ diff --git a/lib/mini2Dx-ecs-source.zip b/lib/mini2Dx-ecs-source.zip deleted file mode 100644 index b8de3a9..0000000 Binary files a/lib/mini2Dx-ecs-source.zip and /dev/null differ diff --git a/lib/mini2Dx-ecs.jar b/lib/mini2Dx-ecs.jar deleted file mode 100644 index 91c5d54..0000000 Binary files a/lib/mini2Dx-ecs.jar and /dev/null differ diff --git a/lib/mini2Dx-tiled-source.zip b/lib/mini2Dx-tiled-source.zip deleted file mode 100644 index c0b8e5e..0000000 Binary files a/lib/mini2Dx-tiled-source.zip and /dev/null differ diff --git a/lib/mini2Dx-tiled.jar b/lib/mini2Dx-tiled.jar deleted file mode 100644 index 378c834..0000000 Binary files a/lib/mini2Dx-tiled.jar and /dev/null differ diff --git a/lib/slick-util-src.zip b/lib/slick-util-src.zip new file mode 100644 index 0000000..7ca6790 Binary files /dev/null and b/lib/slick-util-src.zip differ diff --git a/lib/slick-util.jar b/lib/slick-util.jar new file mode 100644 index 0000000..86a64a7 Binary files /dev/null and b/lib/slick-util.jar differ diff --git a/src/mightypork/rogue/App.java b/src/mightypork/rogue/App.java new file mode 100644 index 0000000..b3f18c6 --- /dev/null +++ b/src/mightypork/rogue/App.java @@ -0,0 +1,417 @@ +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.utils.logging.Log; +import mightypork.utils.logging.LogInstance; +import mightypork.utils.math.coord.Coord; +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 { + + /** instance */ + public static App inst; + /** Current delta time (secs since last render) */ + public static double currentDelta = 0; + + private static DisplayMode windowDisplayMode = null; + + /** current screen */ + public static Screen screen = null; + + /** 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; + } + + + /** + * Is if FS + * + * @return is in fs + */ + public static boolean isFullscreen() + { + return Display.isFullscreen(); + } + + + /** + * @param args + */ + public static void main(String[] args) + { + Thread.setDefaultUncaughtExceptionHandler(new CrashHandler()); + + inst = new App(); + + try { + inst.start(); + } catch (Throwable t) { + onCrash(t); + } + + } + + + /** + * Show crash report dialog with error stack trace. + * + * @param error + */ + public static void onCrash(Throwable error) + { + Log.e("The game has crashed."); + + Log.e(error); + + try { + inst.deinit(); + } catch (Throwable t) { + // ignore + } + } + + + /** + * Quit to OS + */ + public void exit() + { + deinit(); + System.exit(0); + } + + + /** + * Get current screen + * + * @return screen + */ + public Screen getScreen() + { + return screen; + } + + + /** + * Get screen size + * + * @return size + */ + public Coord getSize() + { + return new Coord(Display.getWidth(), Display.getHeight()); + } + + + private void init() throws LWJGLException + { + // 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(); + } + + + private void start() throws LWJGLException + { + + if (!lockInstance()) { + System.out.println("Working directory is locked.\nOnly one instance can run at a time."); + + //@formatter:off + JOptionPane.showMessageDialog( + null, + "The game is already running.", + "Instance error", + JOptionPane.ERROR_MESSAGE + ); + //@formatter:on + + exit(); + return; + } + + init(); + mainLoop(); + deinit(); + } + + + private void deinit() + { + Display.destroy(); + Mouse.destroy(); + Keyboard.destroy(); + SoundManager.get().destroy(); + AL.destroy(); + } + + /** timer */ + private TimerDelta timerRender; + private TimerInterpolating timerGui; + + private int timerAfterResize = 0; + + + private void mainLoop() + { + screen = new ScreenSplash(); + + screen.init(); + + timerRender = new TimerDelta(); + timerGui = new TimerInterpolating(Const.FPS_GUI_UPDATE); + + while (!Display.isCloseRequested()) { + glLoadIdentity(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + timerGui.sync(); + + int ticks = timerGui.getSkipped(); + + if (ticks >= 1) { + screen.updateGui(); + timerGui.startNewFrame(); + } + + currentDelta = timerRender.getDelta(); + + // RENDER + screen.render(currentDelta); + SoundManager.get().update(currentDelta); + + Display.update(); + + 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); + } + } + } + } + + +// UPDATE LOOP END + + /** + * Do take a screenshot + */ + public void takeScreenshot() + { + //Effects.play("gui.screenshot"); + + 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); + + (new ThreadSaveScreenshot(buffer, width, height, bpp)).start(); + } + + + /** + * Replace screen + * + * @param newScreen new screen + */ + public void replaceScreen(Screen newScreen) + { + screen = newScreen; + screen.init(); + } + + + /** + * Replace screen, don't init it + * + * @param newScreen new screen + */ + public void replaceScreenNoInit(Screen newScreen) + { + screen = newScreen; + } + + + /** + * 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(); +// +// +// 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); + } + } + } +} diff --git a/src/mightypork/rogue/Config.java b/src/mightypork/rogue/Config.java new file mode 100644 index 0000000..deaf02a --- /dev/null +++ b/src/mightypork/rogue/Config.java @@ -0,0 +1,81 @@ +package mightypork.rogue; + + +import mightypork.utils.files.PropertyManager; +import mightypork.utils.logging.Log; + + +/** + * Main Config class + * + * @author MightyPork + */ +public class Config { + + private static PropertyManager mgr; + + // opts + public static final int def_LAST_RUN_VERSION = 0; + public static int LAST_RUN_VERSION; + + public static final boolean def_START_IN_FS = false; + public static boolean START_IN_FS; + + // property keys + private static final String PK_LAST_RUN_VERSION = "status.last_run_version"; + private static final String PK_START_IN_FS = "cfg.start_in_fullscreen"; + + + /** + * Prepare config manager and load user settings + */ + public static void init() + { + Log.f2("Initializing configuration manager."); + + String comment = Const.APP_NAME + " config file"; + + mgr = new PropertyManager(Paths.CONFIG, comment); + + mgr.cfgNewlineBeforeComments(true); + mgr.cfgSeparateSections(true); + + mgr.putInteger(PK_LAST_RUN_VERSION, def_LAST_RUN_VERSION); + mgr.putBoolean(PK_START_IN_FS, def_START_IN_FS); + + load(); // load what has been "put" + } + + + /** + * Save changed fields to config file + */ + public static void save() + { + mgr.setValue(PK_LAST_RUN_VERSION, Const.VERSION); + mgr.setValue(PK_START_IN_FS, START_IN_FS); + + mgr.apply(); + } + + + /** + * Load config file and assign values to fields + */ + public static void load() + { + mgr.apply(); + + LAST_RUN_VERSION = mgr.getInteger(PK_LAST_RUN_VERSION); + START_IN_FS = mgr.getBoolean(PK_START_IN_FS); + } + + // options that can't be configured via config file + + public static boolean LOGGING_ENABLED = true; + public static boolean LOG_TO_STDOUT = true; + public static boolean SINGLE_INSTANCE = true; + + public static boolean LOG_FONTS = false; + +} diff --git a/src/mightypork/rogue/Const.java b/src/mightypork/rogue/Const.java new file mode 100644 index 0000000..ca7da92 --- /dev/null +++ b/src/mightypork/rogue/Const.java @@ -0,0 +1,29 @@ +package mightypork.rogue; + + +import mightypork.utils.math.coord.Coord; + + +/** + * Application constants + * + * @author MightyPork + */ +public class Const { + + // STRINGS + public static final int VERSION = 1; + + public static final String APP_NAME = "Rogue"; + 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; +} diff --git a/src/mightypork/rogue/CrashHandler.java b/src/mightypork/rogue/CrashHandler.java new file mode 100644 index 0000000..fe9aa55 --- /dev/null +++ b/src/mightypork/rogue/CrashHandler.java @@ -0,0 +1,15 @@ +package mightypork.rogue; + + +import java.lang.Thread.UncaughtExceptionHandler; + + +public class CrashHandler implements UncaughtExceptionHandler { + + @Override + public void uncaughtException(Thread t, Throwable e) + { + App.onCrash(e); + } + +} diff --git a/src/mightypork/rogue/Paths.java b/src/mightypork/rogue/Paths.java new file mode 100644 index 0000000..a171181 --- /dev/null +++ b/src/mightypork/rogue/Paths.java @@ -0,0 +1,22 @@ +package mightypork.rogue; + + +import java.io.File; + +import mightypork.utils.files.OsUtils; + + +public class Paths { + + private static final String APPDIR_NAME = "rogue"; + + public static final File WORKDIR = OsUtils.getWorkDir(APPDIR_NAME); + public static final File LOGS = OsUtils.getWorkDir(APPDIR_NAME, "logs"); + public static final File SCREENSHOTS = OsUtils.getWorkDir(APPDIR_NAME, "screenshots"); + public static final File CONFIG = new File(WORKDIR, "config.ini"); + + public static final String DIR_EFFECTS = "res/sounds/effects/"; + public static final String DIR_MUSIC = "res/sounds/music/"; + public static final String DIR_LOOPS = "res/sounds/loops/"; + +} diff --git a/src/mightypork/rogue/Screen.java b/src/mightypork/rogue/Screen.java new file mode 100644 index 0000000..e726810 --- /dev/null +++ b/src/mightypork/rogue/Screen.java @@ -0,0 +1,200 @@ +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 new file mode 100644 index 0000000..feb9f75 --- /dev/null +++ b/src/mightypork/rogue/animations/EmptyAnimator.java @@ -0,0 +1,30 @@ +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 new file mode 100644 index 0000000..112c5cd --- /dev/null +++ b/src/mightypork/rogue/animations/GUIRenderer.java @@ -0,0 +1,13 @@ +package mightypork.rogue.animations; + + +public interface GUIRenderer { + + public void updateGui(); + + + public void render(double delta); + + + public void onFullscreenChange(); +} diff --git a/src/mightypork/rogue/fonts/Align.java b/src/mightypork/rogue/fonts/Align.java new file mode 100644 index 0000000..171e620 --- /dev/null +++ b/src/mightypork/rogue/fonts/Align.java @@ -0,0 +1,21 @@ +package mightypork.rogue.fonts; + + +/** + * Alignment + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class Align { + + public static final int LEFT = -1; + public static final int RIGHT = 1; + public static final int TOP = 1; + public static final int BOTTOM = -1; + public static final int UP = 1; + public static final int DOWN = -1; + public static final int CENTER = 0; + public static final int MIDDLE = 0; + +} diff --git a/src/mightypork/rogue/fonts/FontManager.java b/src/mightypork/rogue/fonts/FontManager.java new file mode 100644 index 0000000..7185479 --- /dev/null +++ b/src/mightypork/rogue/fonts/FontManager.java @@ -0,0 +1,317 @@ +package mightypork.rogue.fonts; + + +import java.awt.Font; +import java.io.InputStream; +import java.util.HashMap; + +import mightypork.rogue.Config; +import mightypork.utils.logging.Log; +import mightypork.utils.math.coord.Coord; + +import org.newdawn.slick.util.ResourceLoader; + + +/** + * Remade universal font manager for Sector. + * + * @author MightyPork + */ +public class FontManager { + + private static final boolean DEBUG = Config.LOG_FONTS; + + /** + * Glyph tables. + * + * @author MightyPork + */ + public static class Glyphs { + + //@formatter:off + /** all glyphs */ + public static final String all = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]" + + "^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¢£¤" + + "¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" + + "æçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; + + /** letters and numbers, sufficient for basic messages etc. NO SPACE */ + public static final String alnum_nospace = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** letters and numbers, sufficient for basic messages etc. */ + public static final String alnum = + " "+alnum_nospace; + + /** letters and numbers with the most basic punctuation signs */ + public static final String basic_text = + " .-,.?!:;_"+alnum_nospace; + + /** letters */ + public static final String alpha = + " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + /** numbers */ + public static final String numbers = + " 0123456789.-,:"; + + /** non-standard variants of alnum */ + public static final String alnum_extra = + " ŒÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜŸÝßàáâãäåæçèéêëìíîïðñòóôõöøùúû" + + "üýþÿĚŠČŘŽŤŇĎŮěščřžťňďůŁłđ"; + + /** signs and punctuation */ + public static final String signs = + " !\"#$%&§'()*+,-./:;<=>?@[\\]^_{|}~"; + + /** extra signs and punctuation */ + public static final String signs_extra = + " ¥€£¢`ƒ„…†‡ˆ‰‹‘’“”•›¡¤¦¨ª«¬­¯°±²³´µ¶·¸¹º»¼½¾¿÷™©­®→↓←↑"; + + + /** basic character set. */ + public static final String basic = alnum + signs; + //@formatter:on + } + + /** + * Font style + * + * @author MightyPork + */ + public static enum Style + { + /** Normal */ + NORMAL, + /** Italic */ + ITALIC, + /** Stronger italic to left. */ + LEFT, + /** Stronger italic to right */ + RIGHT, + /** Monospace type */ + MONO, + /** Bold */ + BOLD, + /** Bold & Italic */ + BOLD_I, + /** Bold & Left */ + BOLD_L, + /** Bold & Right */ + BOLD_R, + /** Heavy style, stronger than bold. */ + HEAVY, + /** Light (lighter than normal) */ + LIGHT, + /** narrow style, similar to Light */ + NARROW, + /** Wide style, like Bold but with thinner lines */ + WIDE, + /** Outline variant of normal */ + 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") + "]"; + } + } + + /** + * Group of styles of one font. + * + * @author MightyPork + */ + public static class FontFamily extends HashMap { + } + + /** + * Table of font files. name → {style:file,style:file,style:file...} + */ + private static HashMap fontFiles = new HashMap(); + + + /** + * Register font file. + * + * @param path resource path (res/fonts/...) + * @param name font name (for binding) + * @param style font style in this file + */ + public static void registerFile(String path, String name, Style style) + { + if (fontFiles.containsKey(name)) { + if (fontFiles.get(name) != null) { + fontFiles.get(name).put(style, path); + return; + } + } + + // insert new table of styles to font name. + FontFamily family = new FontFamily(); + family.put(style, path); + fontFiles.put(name, family); + } + + /** Counter of loaded fonts */ + public static int loadedFontCounter = 0; + + + /** + * Preload font if needed, get preloaded font.
+ * If needed file is not available, throws runtime exception. + * + * @param name font name (registerFile) + * @param size font size (pt) + * @param style font style + * @param glyphs glyphs needed + * @return the loaded font. + */ + public static LoadedFont loadFont(String name, double size, Style style, String glyphs) + { + return loadFont(name, size, style, glyphs, 9, 8, Coord.ONE, 0, 0); + } + + + /** + * Preload font if needed, get preloaded font.
+ * If needed file is not available, throws runtime exception. + * + * @param name font name (registerFile) + * @param size font size (pt) + * @param style font style + * @param glyphs glyphs needed + * @param correctLeft left horizontal correction + * @param correctRight right horizontal correction + * @param scale font scale (changing aspect ratio) + * @param clipTop top clip (0-1) - top part of the font to be cut off + * @param clipBottom bottom clip (0-1) - bottom part of the font to be cut + * off + * @return the loaded font. + */ + public static LoadedFont loadFont(String name, double size, Style style, String glyphs, int correctLeft, int correctRight, Coord scale, double clipTop, double clipBottom) + { + String resourcePath; + try { + resourcePath = fontFiles.get(name).get(style); + if (resourcePath == null) { + Log.w("Font [" + name + "] does not have variant " + style + ".\nUsing NORMAL instead."); + resourcePath = fontFiles.get(name).get(Style.NORMAL); + if (resourcePath == null) { + throw new NullPointerException(); + } + } + } catch (NullPointerException npe) { + throw new RuntimeException("Font loading failed: no font file registered for name \"" + name + "\"."); + } + + InputStream in = ResourceLoader.getResourceAsStream(resourcePath); + + Font awtFont; + try { + awtFont = Font.createFont(Font.TRUETYPE_FONT, in); + } catch (Exception e) { + Log.e("Loading of font " + resourcePath + " failed.", e); + throw new RuntimeException(e); + } + + awtFont = awtFont.deriveFont((float) size); // set font size + LoadedFont font = new LoadedFont(awtFont, true, glyphs); + + font.setCorrection(correctLeft, correctRight); + font.setClip(clipTop, clipBottom); + font.setScale(scale.x, scale.y); + + loadedFontCounter++; + + if (DEBUG) Log.f3("Font from file \"" + resourcePath + "\" preloaded."); + + return font; + } +} diff --git a/src/mightypork/rogue/fonts/Fonts.java b/src/mightypork/rogue/fonts/Fonts.java new file mode 100644 index 0000000..69b0a0f --- /dev/null +++ b/src/mightypork/rogue/fonts/Fonts.java @@ -0,0 +1,61 @@ +package mightypork.rogue.fonts; + + +import static mightypork.rogue.fonts.FontManager.Style.*; +import mightypork.rogue.fonts.FontManager.Glyphs; +import mightypork.utils.logging.Log; + + +/** + * Global font preloader + * + * @author Rapus + */ +@SuppressWarnings("javadoc") +public class Fonts { + + public static LoadedFont splash_info; + + public static LoadedFont tooltip; + public static LoadedFont gui; + public static LoadedFont gui_title; + public static LoadedFont program_number; + public static LoadedFont menu_button; + public static LoadedFont menu_title; + public static LoadedFont tiny; + + + private static void registerFileNames() + { + FontManager.registerFile("res/fonts/4feb.ttf", "4feb", NORMAL); + } + + + /** + * Load fonts needed for splash. + */ + public static void loadForSplash() + { + registerFileNames(); + + gui = FontManager.loadFont("4feb", 24, NORMAL, Glyphs.basic).setCorrection(8, 7); + splash_info = FontManager.loadFont("4feb", 42, NORMAL, "Loading."); + } + + + /** + * Preload all fonts we will use in the game + */ + public static void load() + { + tooltip = FontManager.loadFont("4feb", 24, NORMAL, Glyphs.basic_text).setCorrection(8, 7); + gui_title = FontManager.loadFont("4feb", 30, NORMAL, Glyphs.basic_text); + menu_button = FontManager.loadFont("4feb", 36, NORMAL, Glyphs.basic_text); + menu_title = FontManager.loadFont("4feb", 34, NORMAL, Glyphs.basic_text); + program_number = FontManager.loadFont("4feb", 28, NORMAL, Glyphs.numbers); + tiny = FontManager.loadFont("4feb", 20, NORMAL, Glyphs.basic_text); + + Log.i("Fonts loaded = " + FontManager.loadedFontCounter); + + } +} diff --git a/src/mightypork/rogue/fonts/LoadedFont.java b/src/mightypork/rogue/fonts/LoadedFont.java new file mode 100644 index 0000000..fc79201 --- /dev/null +++ b/src/mightypork/rogue/fonts/LoadedFont.java @@ -0,0 +1,665 @@ +package mightypork.rogue.fonts; + + +import static mightypork.rogue.fonts.Align.*; +import static org.lwjgl.opengl.GL11.*; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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; +import org.lwjgl.util.glu.GLU; + + +/** + * A TrueType font implementation originally for Slick, edited for Bobjob's + * Engine + * + * @original author James Chambers (Jimmy) + * @original author Jeremy Adams (elias4444) + * @original author Kevin Glass (kevglass) + * @original author Peter Korzuszek (genail) + * @new version edited by David Aaron Muhar (bobjob) + * @new version edited by MightyPork + */ +public class LoadedFont { + + private static final boolean DEBUG = Config.LOG_FONTS; + + /** Map of user defined font characters (Character <-> IntObject) */ + private Map chars = new HashMap(100); + + /** Boolean flag on whether AntiAliasing is enabled or not */ + private boolean antiAlias; + + /** Font's size */ + private int fontSize = 0; + + /** Font's height */ + private int fontHeight = 0; + + /** Texture used to cache the font 0-255 characters */ + private int fontTextureID; + + /** Default font texture width */ + private int textureWidth = 2048; + + /** Default font texture height */ + private int textureHeight = 2048; + + /** A reference to Java's AWT Font that we create our font texture from */ + private Font font; + + /** The font metrics for our Java AWT font */ + private FontMetrics fontMetrics; + + private int correctL = 9, correctR = 8; + + private double defScaleX = 1, defScaleY = 1; + private double clipVerticalT = 0; + private double clipVerticalB = 0; + + private class CharStorageEntry { + + /** Character's width */ + public int width; + + /** Character's height */ + public int height; + + /** Character's stored x position */ + public int texPosX; + + /** Character's stored y position */ + public int texPosY; + } + + + public LoadedFont(Font font, boolean antiAlias, String charsNeeded) { + this.font = font; + this.fontSize = font.getSize() + 3; + this.antiAlias = antiAlias; + + createSet(charsNeeded.toCharArray()); + + fontHeight -= 1; + if (fontHeight <= 0) fontHeight = 1; + } + + + public void setCorrection(boolean on) + { + if (on) { + correctL = 9;//2 + correctR = 8;//1 + } else { + correctL = 0; + correctR = 0; + } + } + + + private BufferedImage getFontImage(char ch) + { + // Create a temporary image to extract the character's size + BufferedImage tempfontImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) tempfontImage.getGraphics(); + if (antiAlias == true) { + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + g.setFont(font); + fontMetrics = g.getFontMetrics(); + int charwidth = fontMetrics.charWidth(ch) + 8; + + if (charwidth <= 0) { + charwidth = 7; + } + int charheight = fontMetrics.getHeight() + 3; + if (charheight <= 0) { + charheight = fontSize; + } + + // Create another image holding the character we are creating + BufferedImage fontImage; + fontImage = new BufferedImage(charwidth, charheight, BufferedImage.TYPE_INT_ARGB); + Graphics2D gt = (Graphics2D) fontImage.getGraphics(); + if (antiAlias == true) { + gt.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + gt.setFont(font); + + gt.setColor(Color.WHITE); + int charx = 3; + int chary = 1; + gt.drawString(String.valueOf(ch), (charx), (chary) + fontMetrics.getAscent()); + + return fontImage; + + } + + + private void createSet(char[] charsToLoad) + { + try { + class LoadedGlyph { + + public char c; + public BufferedImage image; + public int width; + public int height; + + + public LoadedGlyph(char c, BufferedImage image) { + this.image = image; + this.c = c; + this.width = image.getWidth(); + this.height = image.getHeight(); + } + } + + List glyphs = new ArrayList(); + List loaded = new ArrayList(); + for (char ch : charsToLoad) { + if (!loaded.contains(ch)) { + glyphs.add(new LoadedGlyph(ch, getFontImage(ch))); + loaded.add(ch); + } + } + + Coord canvas = new Coord(128, 128); + double lineHeight = 0; + Coord begin = new Coord(0, 0); + boolean needsLarger = false; + + while (true) { + needsLarger = false; + + for (LoadedGlyph glyph : glyphs) { + if (begin.x + glyph.width > canvas.x) { + begin.y += lineHeight; + lineHeight = 0; + begin.x = 0; + } + + if (lineHeight < glyph.height) { + lineHeight = glyph.height; + } + + if (begin.y + lineHeight > canvas.y) { + needsLarger = true; + break; + } + + // draw. + begin.x += glyph.width; + } + + if (needsLarger) { + canvas.x *= 2; + canvas.y *= 2; + begin.setTo(0, 0); + lineHeight = 0; + } else { + if (DEBUG) Log.f3("Preparing texture " + canvas.x + "x" + canvas.y); + break; + } + } + + textureWidth = (int) canvas.x; + textureHeight = (int) canvas.y; + + BufferedImage imgTemp = new BufferedImage(textureWidth, textureHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = (Graphics2D) imgTemp.getGraphics(); + + g.setColor(new Color(0, 0, 0, 1)); + g.fillRect(0, 0, textureWidth, textureHeight); + + int rowHeight = 0; + int positionX = 0; + int positionY = 0; + + for (LoadedGlyph glyph : glyphs) { + CharStorageEntry storedChar = new CharStorageEntry(); + + storedChar.width = glyph.width; + storedChar.height = glyph.height; + + if (positionX + storedChar.width >= textureWidth) { + positionX = 0; + positionY += rowHeight; + rowHeight = 0; + } + + storedChar.texPosX = positionX; + storedChar.texPosY = positionY; + + if (storedChar.height > fontHeight) { + fontHeight = storedChar.height; + } + + if (storedChar.height > rowHeight) { + rowHeight = storedChar.height; + } + + // Draw it here + g.drawImage(glyph.image, positionX, positionY, null); + + positionX += storedChar.width; + + chars.put(glyph.c, storedChar); + } + + fontTextureID = loadImage(imgTemp); + + imgTemp = null; + + } catch (Exception e) { + System.err.println("Failed to create font."); + e.printStackTrace(); + } + } + + + private void drawQuad(double drawX, double drawY, double drawX2, double drawY2, CharStorageEntry charObj) + { + double srcX = charObj.texPosX + charObj.width; + double srcY = charObj.texPosY + charObj.height; + double srcX2 = charObj.texPosX; + double srcY2 = charObj.texPosY; + double DrawWidth = drawX2 - drawX; + double DrawHeight = drawY2 - drawY; + double TextureSrcX = srcX / textureWidth; + double TextureSrcY = srcY / textureHeight; + double SrcWidth = srcX2 - srcX; + double SrcHeight = srcY2 - srcY; + double RenderWidth = (SrcWidth / textureWidth); + double RenderHeight = (SrcHeight / textureHeight); + + drawY -= DrawHeight * clipVerticalB; + + GL11.glTexCoord2d(TextureSrcX, TextureSrcY); + GL11.glVertex2d(drawX, drawY); + GL11.glTexCoord2d(TextureSrcX, TextureSrcY + RenderHeight); + GL11.glVertex2d(drawX, drawY + DrawHeight); + GL11.glTexCoord2d(TextureSrcX + RenderWidth, TextureSrcY + RenderHeight); + GL11.glVertex2d(drawX + DrawWidth, drawY + DrawHeight); + GL11.glTexCoord2d(TextureSrcX + RenderWidth, TextureSrcY); + GL11.glVertex2d(drawX + DrawWidth, drawY); + } + + + public int getWidth(String whatchars) + { + if (whatchars == null) whatchars = ""; + int totalwidth = 0; + CharStorageEntry charStorage = null; + char currentChar = 0; + for (int i = 0; i < whatchars.length(); i++) { + currentChar = whatchars.charAt(i); + + charStorage = chars.get(currentChar); + + if (charStorage != null) { + totalwidth += charStorage.width - correctL; + } + } + return (int) (totalwidth * defScaleX); + } + + + public int getHeight() + { + return (int) (fontHeight * defScaleY * (1 - clipVerticalT - clipVerticalB)); + } + + + public int getLineHeight() + { + return getHeight(); + } + + + public void drawString(double x, double y, String text, double scaleX, double scaleY, RGB color) + { + drawString(x, y, text, 0, text.length() - 1, scaleX, scaleY, color, LEFT); + } + + + public void drawString(double x, double y, String text, double scaleX, double scaleY, RGB color, int align) + { + drawString(x, y, text, 0, text.length() - 1, scaleX, scaleY, color, align); + } + + + private void drawString(double x, double y, String text, int startIndex, int endIndex, double scaleX, double scaleY, RGB color, int align) + { + x = Math.round(x); + y = Math.round(y); + + scaleX *= defScaleX; + scaleY *= defScaleY; + + CharStorageEntry charStorage = null; + int charCurrent; + + int totalwidth = 0; + int i = startIndex, d = 1, c = correctL; + float startY = 0; + + switch (align) { + case RIGHT: { + d = -1; + c = correctR; + + while (i < endIndex) { + if (text.charAt(i) == '\n') startY -= getHeight(); + i++; + } + break; + } + case CENTER: { + for (int l = startIndex; l <= endIndex; l++) { + charCurrent = text.charAt(l); + if (charCurrent == '\n') break; + + charStorage = chars.get((char) charCurrent); + if (charStorage != null) { + totalwidth += charStorage.width - correctL; + } + } + totalwidth /= -2; + break; + } + case LEFT: + default: { + d = 1; + c = correctL; + break; + } + + } + + GL11.glPushAttrib(GL_ENABLE_BIT); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, fontTextureID); + GL11.glColor4d(color.r, color.g, color.b, color.a); + GL11.glBegin(GL11.GL_QUADS); + + while (i >= startIndex && i <= endIndex) { + charCurrent = text.charAt(i); + + charStorage = chars.get(new Character((char) charCurrent)); + + if (charStorage != null) { + if (d < 0) totalwidth += (charStorage.width - c) * d; + if (charCurrent == '\n') { + startY -= getHeight() * d; + totalwidth = 0; + if (align == CENTER) { + for (int l = i + 1; l <= endIndex; l++) { + charCurrent = text.charAt(l); + if (charCurrent == '\n') break; + + charStorage = chars.get((char) charCurrent); + + totalwidth += charStorage.width - correctL; + } + totalwidth /= -2; + } + //if center get next lines total width/2; + } else { + //@formatter:off + drawQuad( + (totalwidth + charStorage.width) * scaleX + x, + startY * scaleY + y, totalwidth * scaleX + x, + (startY + charStorage.height) * scaleY + y, + charStorage + ); + //@formatter:on + + if (d > 0) totalwidth += (charStorage.width - c) * d; + } + + } + + i += d; + } + GL11.glEnd(); + GL11.glPopAttrib(); + } + + + public static int loadImage(BufferedImage bufferedImage) + { + try { + short width = (short) bufferedImage.getWidth(); + short height = (short) bufferedImage.getHeight(); + //textureLoader.bpp = bufferedImage.getColorModel().hasAlpha() ? (byte)32 : (byte)24; + int bpp = (byte) bufferedImage.getColorModel().getPixelSize(); + ByteBuffer byteBuffer; + DataBuffer db = bufferedImage.getData().getDataBuffer(); + if (db instanceof DataBufferInt) { + int intI[] = ((DataBufferInt) (bufferedImage.getData().getDataBuffer())).getData(); + byte newI[] = new byte[intI.length * 4]; + for (int i = 0; i < intI.length; i++) { + byte b[] = intToByteArray(intI[i]); + int newIndex = i * 4; + + newI[newIndex] = b[1]; + newI[newIndex + 1] = b[2]; + newI[newIndex + 2] = b[3]; + newI[newIndex + 3] = b[0]; + } + + byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(newI); + } else { + byteBuffer = ByteBuffer.allocateDirect(width * height * (bpp / 8)).order(ByteOrder.nativeOrder()).put(((DataBufferByte) (bufferedImage.getData().getDataBuffer())).getData()); + } + byteBuffer.flip(); + + int internalFormat = GL11.GL_RGBA8, format = GL11.GL_RGBA; + IntBuffer textureId = BufferUtils.createIntBuffer(1); + + GL11.glGenTextures(textureId); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId.get(0)); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP); + + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + + GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE); + + GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, internalFormat, width, height, format, GL11.GL_UNSIGNED_BYTE, byteBuffer); + return textureId.get(0); + + } catch (Exception e) { + e.printStackTrace(); + System.exit(-1); + } + + return -1; + } + + + public static boolean isSupported(String fontname) + { + Font font[] = getFonts(); + for (int i = font.length - 1; i >= 0; i--) { + if (font[i].getName().equalsIgnoreCase(fontname)) return true; + } + return false; + } + + + public static Font[] getFonts() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + } + + + public static Font getFont(String fontname, int style, float size) + { + Font result = null; + GraphicsEnvironment graphicsenvironment = GraphicsEnvironment.getLocalGraphicsEnvironment(); + for (Font font : graphicsenvironment.getAllFonts()) { + if (font.getName().equalsIgnoreCase(fontname)) { + result = font.deriveFont(style, size); + break; + } + } + return result; + } + + + public static byte[] intToByteArray(int value) + { + return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value }; + } + + + public void destroy() + { + IntBuffer scratch = BufferUtils.createIntBuffer(1); + scratch.put(0, fontTextureID); + GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); + GL11.glDeleteTextures(scratch); + } + + + public LoadedFont setScale(double x, double y) + { + defScaleX = x; + defScaleY = y; + return this; + } + + + public LoadedFont setClip(double clipRatioTop, double clipRatioBottom) + { + clipVerticalT = clipRatioTop; + clipVerticalB = clipRatioBottom; + return this; + } + + + public LoadedFont setCorrection(int correctionLeft, int correctionRight) + { + correctL = correctionLeft; + correctR = correctionRight; + return this; + } + + + /** + * Draw string with font. + * + * @param x x coord + * @param y y coord + * @param text text to draw + * @param color render color + * @param align (-1,0,1) + */ + public void draw(double x, double y, String text, RGB color, int align) + { + drawString(x, y, text, 1, 1, color, align); + } + + + /** + * Draw string with font. + * + * @param pos coord + * @param text text to draw + * @param color render color + * @param align (-1,0,1) + */ + public void draw(Coord pos, String text, RGB color, int align) + { + drawString(pos.x, pos.y, text, 1, 1, color, align); + } + + + /** + * 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(); + + glTranslated(pos.x, pos.y, pos.z); + + //shadow + int sh = blurSize; + + int l = glGenLists(1); + + glNewList(l, GL_COMPILE); + draw(0, 0, text, blurColor, align); + glEndList(); + + for (int xx = -sh; xx <= sh; xx += (smooth ? 1 : sh)) { + for (int yy = -sh; yy <= sh; yy += (smooth ? 1 : sh)) { + if (xx == 0 && yy == 0) continue; + glPushMatrix(); + glTranslated(xx, yy, 0); + glCallList(l); + glPopMatrix(); + } + } + + glDeleteLists(l, 1); + + draw(0, 0, text, textColor, align); + + glPopMatrix(); + } + +} diff --git a/src/mightypork/rogue/input/InputHandler.java b/src/mightypork/rogue/input/InputHandler.java new file mode 100644 index 0000000..8e9154c --- /dev/null +++ b/src/mightypork/rogue/input/InputHandler.java @@ -0,0 +1,52 @@ +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/Keys.java b/src/mightypork/rogue/input/Keys.java new file mode 100644 index 0000000..b734af3 --- /dev/null +++ b/src/mightypork/rogue/input/Keys.java @@ -0,0 +1,122 @@ +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/screens/ScreenSplash.java b/src/mightypork/rogue/screens/ScreenSplash.java new file mode 100644 index 0000000..be4967f --- /dev/null +++ b/src/mightypork/rogue/screens/ScreenSplash.java @@ -0,0 +1,82 @@ +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 new file mode 100644 index 0000000..b2070ae --- /dev/null +++ b/src/mightypork/rogue/sounds/AudioPlayer.java @@ -0,0 +1,77 @@ +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 new file mode 100644 index 0000000..f7de478 --- /dev/null +++ b/src/mightypork/rogue/sounds/AudioX.java @@ -0,0 +1,230 @@ +package mightypork.rogue.sounds; + + +import mightypork.utils.files.FileUtils; +import mightypork.utils.logging.Log; +import mightypork.utils.math.coord.Coord; + +import org.newdawn.slick.openal.Audio; +import org.newdawn.slick.openal.SoundStore; + + +/** + * Wrapper class for slick audio + * + * @author MightyPork + */ +public class AudioX implements Audio { + + private enum PlayMode + { + EFFECT, MUSIC; + }; + + private Audio audio = null; + private float pauseLoopPosition = 0; + private boolean looping = false; + private boolean paused = false; + private PlayMode mode = PlayMode.EFFECT; + private float pitch = 1; + private float gain = 1; + + private String resourcePath; + + + /** + * Pause loop (remember position and stop playing) - if was looping + */ + public void pauseLoop() + { + if (!ensureLoaded()) return; + + if (isPlaying() && looping) { + pauseLoopPosition = audio.getPosition(); + stop(); + paused = true; + } + } + + + /** + * Resume loop (if was paused) + * + * @return source ID + */ + public int resumeLoop() + { + if (!ensureLoaded()) return -1; + + int source = -1; + if (looping && paused) { + if (mode == PlayMode.MUSIC) { + source = audio.playAsMusic(pitch, gain, true); + } else { + source = audio.playAsSoundEffect(pitch, gain, true); + } + audio.setPosition(pauseLoopPosition); + paused = false; + } + return source; + } + + + /** + * Create deferred primitive audio player + * + * @param resourceName resource to load when needed + */ + public AudioX(String resourceName) { + this.audio = null; + this.resourcePath = resourceName; + } + + + /** + * Check if can play, if not, try to load sound. + * + * @return can now play + */ + private boolean ensureLoaded() + { + load(); + + return audio != null; + } + + + public void load() + { + if (audio != null) return; // already loaded + if (resourcePath == null) return; // not loaded, but can't load anyway + + try { + String ext = FileUtils.getExtension(resourcePath); + + // java 6 can't use String switch :( + if (ext.equalsIgnoreCase("ogg")) { + audio = SoundStore.get().getOgg(resourcePath); + } else if (ext.equalsIgnoreCase("wav")) { + audio = SoundStore.get().getWAV(resourcePath); + } else if (ext.equalsIgnoreCase("aif")) { + audio = SoundStore.get().getAIF(resourcePath); + } 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); + } + + } catch (Exception e) { + Log.e("Could not load " + resourcePath, e); + resourcePath = null; // don't try next time + } + } + + + @Override + public void stop() + { + if (!ensureLoaded()) return; + + audio.stop(); + paused = false; + } + + + @Override + public int getBufferID() + { + if (!ensureLoaded()) return -1; + + return audio.getBufferID(); + } + + + @Override + public boolean isPlaying() + { + if (!ensureLoaded()) return false; + + return audio.isPlaying(); + } + + + @Override + public boolean isPaused() + { + if (!ensureLoaded()) return false; + + return audio.isPaused(); + } + + + @Override + public int playAsSoundEffect(float pitch, float gain, boolean loop) + { + return playAsSoundEffect(pitch, gain, loop, SoundManager.get().listener); + } + + + @Override + public int playAsSoundEffect(float pitch, float gain, boolean loop, float x, float y, float z) + { + if (!ensureLoaded()) return -1; + + this.pitch = pitch; + this.gain = gain; + looping = loop; + mode = PlayMode.EFFECT; + return audio.playAsSoundEffect(pitch, gain, loop, x, y, z); + } + + + /** + * Play this sound as a sound effect + * + * @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 + */ + public int playAsSoundEffect(float pitch, float gain, boolean loop, Coord pos) + { + return playAsSoundEffect(pitch, gain, loop, (float) pos.x, (float) pos.y, (float) pos.z); + } + + + @Override + public int playAsMusic(float pitch, float gain, boolean loop) + { + this.pitch = pitch; + this.gain = gain; + looping = loop; + mode = PlayMode.MUSIC; + return audio.playAsMusic(pitch, gain, loop); + } + + + @Override + public boolean setPosition(float position) + { + return audio.setPosition(position); + } + + + @Override + public float getPosition() + { + return audio.getPosition(); + } + + + @Override + public void release() + { + audio.release(); + audio = null; + } + +} diff --git a/src/mightypork/rogue/sounds/EffectPlayer.java b/src/mightypork/rogue/sounds/EffectPlayer.java new file mode 100644 index 0000000..dfb4edf --- /dev/null +++ b/src/mightypork/rogue/sounds/EffectPlayer.java @@ -0,0 +1,36 @@ +package mightypork.rogue.sounds; + + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.objects.Mutable; + + +public class EffectPlayer extends AudioPlayer { + + public EffectPlayer(AudioX track, double basePitch, double baseGain, Mutable gainMultiplier) { + super(track, (float) basePitch, (float) baseGain, gainMultiplier); + } + + + public int play(double pitch, double gain) + { + if (!canPlay()) return -1; + + return getAudio().playAsSoundEffect(getPitch(pitch), getGain(gain), false); + } + + + public int play(double gain) + { + return play(1, gain); + } + + + public int play(double pitch, double gain, Coord pos) + { + if (!canPlay()) return -1; + + return getAudio().playAsSoundEffect(getPitch(pitch), getGain(gain), false, pos); + } + +} diff --git a/src/mightypork/rogue/sounds/JointVolume.java b/src/mightypork/rogue/sounds/JointVolume.java new file mode 100644 index 0000000..7cb47bf --- /dev/null +++ b/src/mightypork/rogue/sounds/JointVolume.java @@ -0,0 +1,51 @@ +package mightypork.rogue.sounds; + + +import mightypork.utils.math.Calc; +import mightypork.utils.objects.Mutable; + + +/** + * Volume multiplex + * + * @author MightyPork + */ +public class JointVolume extends Mutable { + + private Mutable[] volumes; + + + /** + * CReate joint volume with master gain of 1 + * + * @param volumes individual volumes to join + */ + public JointVolume(Mutable... volumes) { + super(1F); + this.volumes = volumes; + } + + + /** + * Get combined gain (multiplied) + */ + @Override + public Float get() + { + float f = super.get(); + for (Mutable v : volumes) + f *= v.get(); + + return Calc.clampf(f, 0, 1); + } + + + /** + * Set master gain + */ + @Override + public void set(Float o) + { + super.set(o); + } +} diff --git a/src/mightypork/rogue/sounds/LoopPlayer.java b/src/mightypork/rogue/sounds/LoopPlayer.java new file mode 100644 index 0000000..d894df3 --- /dev/null +++ b/src/mightypork/rogue/sounds/LoopPlayer.java @@ -0,0 +1,132 @@ +package mightypork.rogue.sounds; + + +import mightypork.utils.objects.Mutable; +import mightypork.utils.time.AnimDouble; +import mightypork.utils.time.Pauseable; +import mightypork.utils.time.Updateable; + +import org.lwjgl.openal.AL10; + + +public class LoopPlayer extends AudioPlayer implements Updateable, Pauseable { + + private int sourceID = -1; + + /** animator for fade in and fade out */ + private AnimDouble fadeAnim = new AnimDouble(0); + + private double lastUpdateGain = 0; + + /** flag that track is paused */ + private boolean paused = true; + + /** Default fadeIn time */ + private double inTime = 1; + + /** Default fadeOut time */ + private double outTime = 1; + + + public LoopPlayer(AudioX track, double pitch, double baseGain, Mutable gainMultiplier) { + super(track, (float) pitch, (float) baseGain, gainMultiplier); + + paused = true; + } + + + public void setFadeTimes(double in, double out) + { + inTime = in; + outTime = out; + } + + + private void initLoop() + { + if (!canPlay() && sourceID == -1) { + sourceID = getAudio().playAsSoundEffect(getPitch(1), getGain(1), true); + getAudio().pauseLoop(); + } + } + + + @Override + public void pause() + { + if (!canPlay() || paused) return; + + initLoop(); + + getAudio().pauseLoop(); + paused = true; + } + + + @Override + public boolean isPaused() + { + return paused; + } + + + @Override + public void resume() + { + if (!canPlay() || paused) return; + + initLoop(); + + sourceID = getAudio().resumeLoop(); + paused = false; + } + + + @Override + public void update(double delta) + { + if (!canPlay() || paused) return; + + initLoop(); + + fadeAnim.update(delta); + + double gain = getGain(fadeAnim.getCurrentValue()); + if (!paused && gain != lastUpdateGain) { + AL10.alSourcef(sourceID, AL10.AL_GAIN, (float) gain); + lastUpdateGain = gain; + } + + if (gain == 0 && !paused) pause(); // pause on zero volume + } + + + public void fadeIn(double secs) + { + if (!canPlay()) return; + + resume(); + fadeAnim.fadeIn(secs); + } + + + public void fadeOut(double secs) + { + if (!canPlay()) return; + + fadeAnim.fadeOut(secs); + } + + + public void fadeIn() + { + fadeIn(inTime); + } + + + public void fadeOut() + { + fadeOut(outTime); + } + +} diff --git a/src/mightypork/rogue/sounds/SoundManager.java b/src/mightypork/rogue/sounds/SoundManager.java new file mode 100644 index 0000000..72b9c36 --- /dev/null +++ b/src/mightypork/rogue/sounds/SoundManager.java @@ -0,0 +1,179 @@ +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/textures/TextureManager.java b/src/mightypork/rogue/textures/TextureManager.java new file mode 100644 index 0000000..de2af43 --- /dev/null +++ b/src/mightypork/rogue/textures/TextureManager.java @@ -0,0 +1,76 @@ +package mightypork.rogue.textures; + + +import static org.lwjgl.opengl.GL11.*; + +import java.io.IOException; + +import mightypork.utils.logging.Log; + +import org.newdawn.slick.opengl.Texture; +import org.newdawn.slick.opengl.TextureLoader; +import org.newdawn.slick.util.ResourceLoader; + + +/** + * Texture manager + * + * @author MightyPork + */ +public class TextureManager { + + private static Texture lastBinded = null; + + + /** + * Load texture + * + * @param resourcePath + * @return the loaded texture + */ + public static Texture load(String resourcePath) + { + try { + String ext = resourcePath.substring(resourcePath.length() - 4); + + Texture texture = TextureLoader.getTexture(ext.toUpperCase(), ResourceLoader.getResourceAsStream(resourcePath)); + + if (texture != null) { + return texture; + } + + Log.w("Texture " + resourcePath + " could not be loaded."); + return null; + } catch (IOException e) { + Log.e("Loading of texture " + resourcePath + " failed.", e); + throw new RuntimeException(e); + } + + } + + + /** + * Bind texture + * + * @param texture the texture + * @throws RuntimeException if not loaded yet + */ + public static void bind(Texture texture) throws RuntimeException + { + if (texture != lastBinded) { + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, texture.getTextureID()); + lastBinded = texture; + } + } + + + /** + * Unbind all + */ + public static void unbind() + { + glBindTexture(GL_TEXTURE_2D, 0); + lastBinded = null; + } +} diff --git a/src/mightypork/rogue/textures/Textures.java b/src/mightypork/rogue/textures/Textures.java new file mode 100644 index 0000000..a8711e8 --- /dev/null +++ b/src/mightypork/rogue/textures/Textures.java @@ -0,0 +1,141 @@ +package mightypork.rogue.textures; + + +import static org.lwjgl.opengl.GL11.*; + +import org.newdawn.slick.opengl.Texture; + + +/** + * Texture loading class + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +// unrelevant +public class Textures { + + protected static Texture buttons_menu; + protected static Texture buttons_small; + + // patterns need raw access (are repeated) + public static Texture steel_small_dk; + public static Texture steel_small_lt; + public static Texture steel_big_dk; + public static Texture steel_big_lt; + public static Texture steel_big_scratched; + public static Texture steel_brushed; + public static Texture steel_rivet_belts; + public static Texture water; + + // particles need direct access + public static Texture snow; + public static Texture leaves; + public static Texture rain; + public static Texture circle; + + protected static Texture logo; + protected static Texture widgets; + public static Texture program; + protected static Texture icons; + + public static Texture map_tiles; + public static Texture map_shading; + + // spotted + public static Texture turtle_blue; + public static Texture turtle_green; + public static Texture turtle_purple; + public static Texture turtle_yellow; + + // misc + public static Texture turtle_red; + public static Texture turtle_brown_dk; + public static Texture turtle_brown_lt; + public static Texture turtle_brown_orn; + // a shadow sheet + public static Texture turtle_shadows; + + public static Texture food; + public static Texture food_shadows; + + private static final String GUI = "res/images/gui/"; + private static final String PATTERNS = "res/images/patterns/"; + private static final String PROGRAM = "res/images/program/"; + private static final String TILES = "res/images/tiles/"; + private static final String SPRITES = "res/images/sprites/"; + private static final String PARTICLES = "res/images/particles/"; + + + /** + * Load what's needed for splash + */ + public static void loadForSplash() + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + logo = TextureManager.load(GUI + "logo.png"); + steel_small_dk = TextureManager.load(PATTERNS + "steel.png"); + + Tx.initForSplash(); + } + + + /** + * Load textures + */ + public static void load() + { + // gui + buttons_menu = TextureManager.load(GUI + "buttons_menu.png"); + buttons_small = TextureManager.load(GUI + "buttons_small.png"); + widgets = TextureManager.load(GUI + "widgets.png"); + icons = TextureManager.load(GUI + "icons.png"); + + // patterns + + steel_small_lt = TextureManager.load(PATTERNS + "steel_light.png"); // XXX Unused texture + steel_big_dk = TextureManager.load(PATTERNS + "steel_big.png"); + steel_big_lt = TextureManager.load(PATTERNS + "steel_big_light.png"); + steel_big_scratched = TextureManager.load(PATTERNS + "steel_big_scratched.png"); + steel_brushed = TextureManager.load(PATTERNS + "steel_brushed.png"); + steel_rivet_belts = TextureManager.load(PATTERNS + "rivet_belts.png"); + water = TextureManager.load(PATTERNS + "water.png"); + + // particles + snow = TextureManager.load(PARTICLES + "snow.png"); + leaves = TextureManager.load(PARTICLES + "leaves.png"); + rain = TextureManager.load(PARTICLES + "rain.png"); + circle = TextureManager.load(PARTICLES + "circle.png"); + + // program tiles + program = TextureManager.load(PROGRAM + "program.png"); + + // map tiles + map_tiles = TextureManager.load(TILES + "tiles.png"); + map_shading = TextureManager.load(TILES + "shading.png"); + + // turtle + turtle_blue = TextureManager.load(SPRITES + "HD-blue.png"); + turtle_yellow = TextureManager.load(SPRITES + "HD-yellow.png"); + turtle_green = TextureManager.load(SPRITES + "HD-green.png"); + turtle_purple = TextureManager.load(SPRITES + "HD-purple.png"); + turtle_red = TextureManager.load(SPRITES + "HD-red.png"); + + turtle_brown_dk = TextureManager.load(SPRITES + "HD-brown-dk.png"); + turtle_brown_lt = TextureManager.load(SPRITES + "HD-brown-lt.png"); + turtle_brown_orn = TextureManager.load(SPRITES + "HD-brown-ornamental.png"); + turtle_shadows = TextureManager.load(SPRITES + "HD-shadow.png"); + + food = TextureManager.load(SPRITES + "food.png"); + food_shadows = TextureManager.load(SPRITES + "food-shadow.png"); + + glDisable(GL_TEXTURE_2D); + + Tx.init(); + } + +} diff --git a/src/mightypork/rogue/textures/Tx.java b/src/mightypork/rogue/textures/Tx.java new file mode 100644 index 0000000..a31f356 --- /dev/null +++ b/src/mightypork/rogue/textures/Tx.java @@ -0,0 +1,29 @@ +package mightypork.rogue.textures; + + +/** + * List of texture quads for GUIs + * + * @author MightyPork + */ +@SuppressWarnings("javadoc") +public class Tx { + + // logo + public static TxQuad LOGO; + + + public static void initForSplash() + { + // splash logo + LOGO = TxQuad.fromSize(Textures.logo, 15, 9, 226, 132); + } + + + public static void init() + { + // title image (word art) + + } + +} diff --git a/src/mightypork/rogue/textures/TxQuad.java b/src/mightypork/rogue/textures/TxQuad.java new file mode 100644 index 0000000..9682c44 --- /dev/null +++ b/src/mightypork/rogue/textures/TxQuad.java @@ -0,0 +1,72 @@ +package mightypork.rogue.textures; + + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.math.coord.Rect; + +import org.newdawn.slick.opengl.Texture; + + +/** + * Texture Quad (describing a part of a texture) + * + * @author MightyPork + */ +public class TxQuad { + + /** The texture */ + public Texture tx; + /** Coords in texture (pixels) */ + public Rect uvs; + /** Quad size */ + public Coord size; + + + /** + * Create TxQuad from left top coord and rect size + * + * @param tx texture + * @param x1 left top X + * @param y1 left top Y + * @param width area width + * @param height area height + * @return new TxQuad + */ + public static TxQuad fromSize(Texture tx, int x1, int y1, int width, int height) + { + return new TxQuad(tx, x1, y1, x1 + width, y1 + height); + } + + + /** + * @param tx Texture + * @param uvs Rect of texturwe UVs (pixels - from left top) + */ + public TxQuad(Texture tx, Rect uvs) { + this.tx = tx; + this.uvs = uvs.copy(); + this.size = uvs.getSize(); + } + + + /** + * Make of coords + * + * @param tx texture + * @param x1 x1 + * @param y1 y1 + * @param x2 x2 + * @param y2 y2 + */ + public TxQuad(Texture tx, int x1, int y1, int x2, int y2) { + this.tx = tx; + this.uvs = new Rect(x1, y1, x2, y2); + this.size = uvs.getSize(); + } + + + public TxQuad copy() + { + return new TxQuad(tx, uvs); + } +} diff --git a/src/mightypork/rogue/threads/ThreadSaveScreenshot.java b/src/mightypork/rogue/threads/ThreadSaveScreenshot.java new file mode 100644 index 0000000..0d04c57 --- /dev/null +++ b/src/mightypork/rogue/threads/ThreadSaveScreenshot.java @@ -0,0 +1,91 @@ +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 new file mode 100644 index 0000000..8b1bc88 --- /dev/null +++ b/src/mightypork/rogue/threads/ThreadScreenshotTrigger.java @@ -0,0 +1,32 @@ +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 new file mode 100644 index 0000000..8747797 --- /dev/null +++ b/src/mightypork/rogue/util/RenderUtils.java @@ -0,0 +1,919 @@ +package mightypork.rogue.util; + + +import static org.lwjgl.opengl.GL11.*; +import mightypork.rogue.textures.TextureManager; +import mightypork.rogue.textures.TxQuad; +import mightypork.utils.math.color.HSV; +import mightypork.utils.math.color.RGB; +import mightypork.utils.math.coord.Coord; +import mightypork.utils.math.coord.Rect; + +import org.newdawn.slick.opengl.Texture; + + +/** + * Render utilities + * + * @author MightyPork + */ +public class RenderUtils { + + /** + * Render quad 2D + * + * @param min min coord + * @param max max coord + */ + public static void quadCoord(Coord min, Coord max) + { + quadCoord(min.x, min.y, max.x, max.y); + } + + + /** + * Render quad with absolute coords (from screen bottom) + * + * @param left left x + * @param bottom bottom y + * @param right right x + * @param top top y + */ + public static void quadCoord(double left, double bottom, double right, double top) + { + glBegin(GL_QUADS); + glVertex2d(left, top); + glVertex2d(right, top); + glVertex2d(right, bottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Render quad with coloured border + * + * @param min min + * @param max max + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadCoordBorder(Coord min, Coord max, double border, RGB borderColor, RGB insideColor) + { + quadCoordBorder(min.x, min.y, max.x, max.y, border, borderColor, insideColor); + } + + + /** + * Render quad with coloured border + * + * @param minX min x + * @param minY min y + * @param maxX max x + * @param maxY max y + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadCoordBorder(double minX, double minY, double maxX, double maxY, double border, RGB borderColor, RGB insideColor) + { + double bdr = border; + + RenderUtils.setColor(borderColor); + + //@formatter:off + RenderUtils.quadCoord(minX, minY, minX + bdr, maxY); + RenderUtils.quadCoord(maxX - bdr, minY, maxX, maxY); + RenderUtils.quadCoord(minX + bdr, maxY - bdr, maxX - bdr, maxY); + RenderUtils.quadCoord(minX + bdr, minY, maxX - bdr, minY + bdr); + //@formatter:on + + if (insideColor != null) { + RenderUtils.setColor(insideColor); + RenderUtils.quadCoord(minX + bdr, minY + bdr, maxX - bdr, maxY - bdr); + } + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param min min coord + * @param max max coord + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadCoordGradH(Coord min, Coord max, RGB colorLeft, RGB colorRight) + { + quadCoordGradH(min.x, min.y, max.x, max.y, colorLeft, colorRight); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param minX units from left + * @param minY units from bottom + * @param maxX quad width + * @param maxY quad height + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadCoordGradH(double minX, double minY, double maxX, double maxY, RGB colorLeft, RGB colorRight) + { + glBegin(GL_QUADS); + setColor(colorLeft); + glVertex2d(minX, maxY); + setColor(colorRight); + glVertex2d(maxX, maxY); + + setColor(colorRight); + glVertex2d(maxX, minY); + setColor(colorLeft); + glVertex2d(minX, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param min min coord + * @param max max coord + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadCoordGradHBilinear(Coord min, Coord max, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradHBilinear(min.x, min.y, max.x, max.y, colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param minX min x + * @param minY min y + * @param maxX max x + * @param maxY max y + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadCoordGradHBilinear(double minX, double minY, double maxX, double maxY, RGB colorOuter, RGB colorMiddle) + { + glBegin(GL_QUADS); + setColor(colorOuter); + glVertex2d(minX, maxY); + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, maxY); + + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, minY); + setColor(colorOuter); + glVertex2d(minX, minY); + + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, maxY); + setColor(colorOuter); + glVertex2d(maxX, maxY); + + setColor(colorOuter); + glVertex2d(maxX, minY); + setColor(colorMiddle); + glVertex2d((minX + maxX) / 2, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param min min coord + * @param max max coord + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadCoordGradV(Coord min, Coord max, RGB colorTop, RGB colorBottom) + { + quadCoordGradV(min.x, min.y, max.x, max.y, colorTop, colorBottom); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param minX min X + * @param minY min Y + * @param maxX max X + * @param maxY max Y + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadCoordGradV(double minX, double minY, double maxX, double maxY, RGB colorTop, RGB colorBottom) + { + glBegin(GL_QUADS); + + setColor(colorTop); + glVertex2d(minX, maxY); + glVertex2d(maxX, maxY); + + setColor(colorBottom); + glVertex2d(maxX, minY); + glVertex2d(minX, minY); + glEnd(); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param min min coord + * @param max max coord + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadCoordGradVBilinear(Coord min, Coord max, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradVBilinear(min.x, min.y, max.x, max.y, colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param minX min X + * @param minY min Y + * @param maxX max X + * @param maxY max Y + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadCoordGradVBilinear(double minX, double minY, double maxX, double maxY, RGB colorOuter, RGB colorMiddle) + { + glBegin(GL_QUADS); + + setColor(colorOuter); + glVertex2d(minX, maxY); + glVertex2d(maxX, maxY); + + setColor(colorMiddle); + glVertex2d(maxX, (maxY + minY) / 2); + glVertex2d(minX, (maxY + minY) / 2); + + setColor(colorMiddle); + glVertex2d(minX, (maxY + minY) / 2); + glVertex2d(maxX, (maxY + minY) / 2); + + setColor(colorOuter); + glVertex2d(maxX, minY); + glVertex2d(minX, minY); + + glEnd(); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param min min coord + * @param max max coord + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadCoordOutset(Coord min, Coord max, double border, RGB fill, boolean inset) + { + quadCoordOutset(min.x, min.y, max.x, max.y, border, fill, inset); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param minX left X + * @param minY bottom Y + * @param maxX right X + * @param maxY top Y + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadCoordOutset(double minX, double minY, double maxX, double maxY, double border, RGB fill, boolean inset) + { + HSV hsv = fill.toHSV(); + HSV h; + + h = hsv.copy(); + h.s *= 0.6; + RGB a = h.toRGB(); + + h = fill.toHSV(); + h.v *= 0.6; + RGB b = h.toRGB(); + + RGB leftTopC, rightBottomC; + + if (!inset) { + leftTopC = a; + rightBottomC = b; + } else { + leftTopC = b; + rightBottomC = a; + } + + double bdr = border; + + RenderUtils.setColor(leftTopC); + + // left + top + glBegin(GL_QUADS); + glVertex2d(minX, minY); + glVertex2d(minX + bdr, minY + bdr); + glVertex2d(minX + bdr, maxY - bdr); + glVertex2d(minX, maxY); + + glVertex2d(minX, maxY); + glVertex2d(minX + bdr, maxY - bdr); + glVertex2d(maxX - bdr, maxY - bdr); + glVertex2d(maxX, maxY); + glEnd(); + + RenderUtils.setColor(rightBottomC); + + // right + bottom + glBegin(GL_QUADS); + glVertex2d(maxX, minY); + glVertex2d(maxX, maxY); + glVertex2d(maxX - bdr, maxY - bdr); + glVertex2d(maxX - bdr, minY + bdr); + + glVertex2d(minX, minY); + glVertex2d(maxX, minY); + glVertex2d(maxX - bdr, minY + bdr); + glVertex2d(minX + bdr, minY + bdr); + glEnd(); + + RenderUtils.setColor(fill); + RenderUtils.quadCoord(minX + bdr, minY + bdr, maxX - bdr, maxY - bdr); + } + + + /** + * Render quad 2D + * + * @param rect rectangle + */ + public static void quadRect(Rect rect) + { + quadCoord(rect.getMin(), rect.getMax()); + } + + + /** + * Render quad 2D + * + * @param rect rectangle + * @param color draw color + */ + public static void quadRect(Rect rect, RGB color) + { + setColor(color); + quadCoord(rect.getMin(), rect.getMax()); + } + + + /** + * Render quad with coloured border + * + * @param rect rect + * @param border border width + * @param borderColor border color + * @param insideColor filling color + */ + public static void quadBorder(Rect rect, double border, RGB borderColor, RGB insideColor) + { + quadCoordBorder(rect.getMin(), rect.getMax(), border, borderColor, insideColor); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param rect rect + * @param colorLeft color left + * @param colorRight color right + */ + public static void quadGradH(Rect rect, RGB colorLeft, RGB colorRight) + { + quadCoordGradH(rect.getMin(), rect.getMax(), colorLeft, colorRight); + } + + + /** + * Render quad 2D with gradient horizontal + * + * @param rect rect + * @param colorOuter color outer + * @param colorMiddle color inner + */ + public static void quadGradHBilinear(Rect rect, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradHBilinear(rect.getMin(), rect.getMax(), colorOuter, colorMiddle); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param rect rect + * @param colorTop top color + * @param colorBottom bottom color + */ + public static void quadGradV(Rect rect, RGB colorTop, RGB colorBottom) + { + quadCoordGradV(rect.getMin(), rect.getMax(), colorTop, colorBottom); + } + + + /** + * Render quad 2D with gradient vertical + * + * @param rect rect + * @param colorOuter outer color + * @param colorMiddle middle color + */ + public static void quadGradVBilinear(Rect rect, RGB colorOuter, RGB colorMiddle) + { + quadCoordGradVBilinear(rect.getMin(), rect.getMax(), colorOuter, colorMiddle); + } + + + /** + * Render quad with coloured border with outset effect + * + * @param rect rectangle + * @param border border width + * @param fill fill color + * @param inset true for inset, false for outset + */ + public static void quadRectOutset(Rect rect, double border, RGB fill, boolean inset) + { + quadCoordOutset(rect.getMin(), rect.getMax(), border, fill, inset); + } + + + /** + * Render textured rect (texture must be binded already) + * + * @param quad rectangle (px) + * @param textureCoords texture coords (0-1) + */ + 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 tleft = textureCoords.x1(); + double tbottom = textureCoords.y1(); + double tright = textureCoords.x2(); + double ttop = textureCoords.y2(); + + glBegin(GL_QUADS); + glTexCoord2d(tleft, ttop); + glVertex2d(left, top); + glTexCoord2d(tright, ttop); + glVertex2d(right, top); + glTexCoord2d(tright, tbottom); + glVertex2d(right, bottom); + glTexCoord2d(tleft, tbottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txCoords texture coords rectangle (px) + * @param texture texture instance + */ + public static void quadTextured(Rect quad, Rect txCoords, Texture texture) + { + quadTextured(quad, txCoords, texture, RGB.WHITE); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txCoords texture coords rectangle (px) + * @param texture texture instance + * @param tint color tint + */ + public static void quadTextured(Rect quad, Rect txCoords, Texture texture, RGB tint) + { + glEnable(GL_TEXTURE_2D); + setColor(tint); + TextureManager.bind(texture); + + quadTexturedAbs(quad, txCoords.div(texture.getImageHeight())); + + TextureManager.unbind(); + glDisable(GL_TEXTURE_2D); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txquad texture quad + */ + public static void quadTextured(Rect quad, TxQuad txquad) + { + quadTextured(quad, txquad, RGB.WHITE); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txquad texture instance + * @param tint color tint + */ + public static void quadTextured(Rect quad, TxQuad txquad, RGB tint) + { + quadTextured(quad, txquad.uvs, txquad.tx, tint); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param yOffsetTimes offset count (move frame down n times) + * @param texture the texture + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, int yOffsetTimes, Texture texture) + { + quadTexturedFrame(quadRect, textureRect, yOffsetTimes, texture, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param yOffsetTimes offset count (move frame down n times) + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, int yOffsetTimes, Texture texture, RGB tint) + { + Texture tx = texture; + + int frameHeight = (int) textureRect.getSize().y; + + int yOffset = yOffsetTimes * frameHeight; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + 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 halfY = h / 2D; + double halfX = w / 2D; + + // lt, rt + // lb, rb + + Rect verts, uvs; + + // lt + verts = new Rect(x1, y2, x1 + halfX, y2 - halfY); + uvs = new Rect(tx1, ty1, tx1 + halfX, ty1 + halfY); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // lb + verts = new Rect(x1, y2 - halfY, x1 + halfX, y1); + uvs = new Rect(tx1, ty2 - halfY, tx1 + halfX, ty2); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x1 + halfX, y2, x2, y2 - halfY); + uvs = new Rect(tx2 - halfX, ty1, tx2, ty1 + halfY); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + // rb + verts = new Rect(x1 + halfX, y2 - halfY, x2, y1); + uvs = new Rect(tx2 - halfX, ty2 - halfY, tx2, ty2); + uvs.add_ip(0, yOffset); + quadTextured(verts, uvs, tx, tint); + + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param texture the texture + */ + public static void quadTexturedFrame(Rect quadRect, Rect textureRect, Texture texture) + { + quadTexturedFrame(quadRect, textureRect, 0, texture, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad) + { + quadTexturedFrame(quadRect, txquad, 0, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param yOffsetTimes offset count (move frame down n times) + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad, int yOffsetTimes) + { + quadTexturedFrame(quadRect, txquad, yOffsetTimes, RGB.WHITE); + } + + + /** + * Render textured frame with borders + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param yOffsetTimes offset count (move frame down n times) + * @param tint color tint + */ + public static void quadTexturedFrame(Rect quadRect, TxQuad txquad, int yOffsetTimes, RGB tint) + { + quadTexturedFrame(quadRect, txquad.uvs, yOffsetTimes, txquad.tx, tint); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + */ + public static void quadTexturedStretchH(Rect quadRect, Rect textureRect, int borderSize, Texture texture) + { + quadTexturedStretchH(quadRect, textureRect, borderSize, texture, RGB.WHITE); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedStretchH(Rect quadRect, Rect textureRect, int borderSize, Texture texture, RGB tint) + { + Texture tx = texture; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + double tx1 = textureRect.x1(); + double ty1 = textureRect.y1(); + double tx2 = textureRect.x2(); + double ty2 = textureRect.y2(); + + // lt, mi, rt + + Rect verts, uvs; + + // lt + verts = new Rect(x1, y2, x1 + borderSize, y1); + uvs = new Rect(tx1, ty1, tx1 + borderSize, ty2); + quadTextured(verts, uvs, tx, tint); + +// // mi + verts = new Rect(x1 + borderSize, y2, x2 - borderSize, y1); + uvs = new Rect(tx1 + borderSize, ty1, tx2 - borderSize, ty2); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x2 - borderSize, y2, x2, y1); + uvs = new Rect(tx2 - borderSize, ty1, tx2, ty2); + quadTextured(verts, uvs, tx, tint); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + */ + public static void quadTexturedStretchH(Rect quadRect, TxQuad txquad, int borderSize) + { + quadTexturedStretchH(quadRect, txquad, borderSize, RGB.WHITE); + } + + + /** + * Render textured frame stretching horizontally (rect height = texture rect + * height) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + * @param tint color tint + */ + public static void quadTexturedStretchH(Rect quadRect, TxQuad txquad, int borderSize, RGB tint) + { + quadTexturedStretchH(quadRect, txquad.uvs, borderSize, txquad.tx, tint); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + */ + public static void quadTexturedStretchV(Rect quadRect, Rect textureRect, int borderSize, Texture texture) + { + quadTexturedStretchV(quadRect, textureRect, borderSize, texture, RGB.WHITE); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param textureRect rectangle in texture with the basic frame (px) + * @param borderSize size of the unstretched horizontal border + * @param texture the texture + * @param tint color tint + */ + public static void quadTexturedStretchV(Rect quadRect, Rect textureRect, int borderSize, Texture texture, RGB tint) + { + Texture tx = texture; + + double x1 = quadRect.x1(); + double y1 = quadRect.y1(); + double x2 = quadRect.x2(); + double y2 = quadRect.y2(); + + double tx1 = textureRect.x1(); + double ty1 = textureRect.y1(); + double tx2 = textureRect.x2(); + double ty2 = textureRect.y2(); + + // tp + // mi + // bn + + Rect verts, uvs; + + // tp + verts = new Rect(x1, y2, x2, y2 - borderSize); + uvs = new Rect(tx1, ty1, tx2, ty1 + borderSize); + quadTextured(verts, uvs, tx, tint); + + // mi + verts = new Rect(x1, y2 - borderSize, x2, y1 + borderSize); + uvs = new Rect(tx1, ty1 + borderSize, tx2, ty2 - borderSize); + quadTextured(verts, uvs, tx, tint); + + // rt + verts = new Rect(x1, y1 + borderSize, x2, y1); + uvs = new Rect(tx1, ty2 - borderSize, tx2, ty2); + quadTextured(verts, uvs, tx, tint); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + */ + public static void quadTexturedStretchV(Rect quadRect, TxQuad txquad, int borderSize) + { + quadTexturedStretchV(quadRect, txquad, borderSize, RGB.WHITE); + } + + + /** + * Render textured frame stretching vertically (rect width = texture rect + * width) + * + * @param quadRect drawn rectangle (px) + * @param txquad texture quad + * @param borderSize size of the unstretched horizontal border + * @param tint color tint + */ + public static void quadTexturedStretchV(Rect quadRect, TxQuad txquad, int borderSize, RGB tint) + { + quadTexturedStretchV(quadRect, txquad.uvs, borderSize, txquad.tx, tint); + + } + + + /** + * Render quad 2D + * + * @param left units from left + * @param bottom units from bottom + * @param width quad width + * @param height quad height + */ + public static void quadSize(double left, double bottom, double width, double height) + { + glBegin(GL_QUADS); + glVertex2d(left, bottom + height); + glVertex2d(left + width, bottom + height); + glVertex2d(left + width, bottom); + glVertex2d(left, bottom); + glEnd(); + } + + + /** + * Bind GL color + * + * @param color RGB color + */ + public static void setColor(RGB color) + { + if (color != null) glColor4d(color.r, color.g, color.b, color.a); + } + + + /** + * Bind GL color + * + * @param color RGB color + * @param alpha alpha multiplier + */ + public static void setColor(RGB color, double alpha) + { + if (color != null) glColor4d(color.r, color.g, color.b, color.a * alpha); + } + + + /** + * Translate with coord + * + * @param coord coord + */ + public static void translate(Coord coord) + { + glTranslated(coord.x, coord.y, coord.z); + } +} diff --git a/src/mightypork/rogue/util/Utils.java b/src/mightypork/rogue/util/Utils.java new file mode 100644 index 0000000..3ac307a --- /dev/null +++ b/src/mightypork/rogue/util/Utils.java @@ -0,0 +1,10 @@ +package mightypork.rogue.util; + + +/** + * Utils class + * + * @author MightyPork + */ +public class Utils { +} diff --git a/src/mightypork/utils/files/FileTreeDiff.java b/src/mightypork/utils/files/FileTreeDiff.java new file mode 100644 index 0000000..0653f17 --- /dev/null +++ b/src/mightypork/utils/files/FileTreeDiff.java @@ -0,0 +1,163 @@ +package mightypork.utils.files; + + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.zip.Adler32; +import java.util.zip.CheckedInputStream; +import java.util.zip.Checksum; + +import mightypork.utils.logging.Log; + + +public class FileTreeDiff { + + private static final byte[] BUFFER = new byte[2048]; + private Checksum ck1 = new Adler32(); + private Checksum ck2 = new Adler32(); + + private boolean logging = true; + + private List> compared = new ArrayList>(); + private Comparator fileFirstSorter = new Comparator() { + + @Override + public int compare(File o1, File o2) + { + if (!o1.isDirectory() && o2.isDirectory()) return -1; + if (o1.isDirectory() && !o2.isDirectory()) return 1; + + return o1.getName().compareTo(o2.getName()); + } + }; + + + public void enableLogging(boolean state) + { + logging = state; + } + + + public boolean areEqual(File dir1, File dir2) + { + if (logging) Log.f3("Comparing directory trees:\n 1. " + dir1 + "\n 2. " + dir2); + + try { + compared.clear(); + buildList(dir1, dir2); + + calcChecksum(); + + if (logging) Log.f3("No difference found."); + + return true; + + } catch (NotEqualException e) { + if (logging) Log.f3("Difference found:\n" + e.getMessage()); + + return false; + } + } + + + private void calcChecksum() throws NotEqualException + { + FileInputStream in1, in2; + CheckedInputStream cin1 = null, cin2 = null; + + for (Tuple pair : compared) { + try { + ck1.reset(); + ck2.reset(); + + in1 = new FileInputStream(pair.a); + in2 = new FileInputStream(pair.b); + + cin1 = new CheckedInputStream(in1, ck1); + cin2 = new CheckedInputStream(in2, ck2); + + while (true) { + int read1 = cin1.read(BUFFER); + int read2 = cin2.read(BUFFER); + + if (read1 != read2 || ck1.getValue() != ck2.getValue()) { + throw new NotEqualException("Bytes differ:\n" + pair.a + "\n" + pair.b); + } + + if (read1 == -1) break; + } + + } catch (IOException e) {} finally { + try { + if (cin1 != null) cin1.close(); + } catch (IOException e) { + // ignore + } + + try { + if (cin1 != null) cin1.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + + private void buildList(File f1, File f2) throws NotEqualException + { + if (f1.isDirectory() != f2.isDirectory()) throw new NotEqualException("isDirectory differs:\n" + f1 + "\n" + f2); + + if (f1.isFile() && f2.isFile()) { + if (f1.length() != f2.length()) throw new NotEqualException("Sizes differ:\n" + f1 + "\n" + f2); + } + + if (f1.isDirectory()) { + File[] children1 = f1.listFiles(); + File[] children2 = f2.listFiles(); + + Arrays.sort(children1, fileFirstSorter); + Arrays.sort(children2, fileFirstSorter); + + if (children1.length != children2.length) throw new NotEqualException("Child counts differ:\n" + f1 + "\n" + f2); + + for (int i = 0; i < children1.length; i++) { + File ch1 = children1[i]; + File ch2 = children2[i]; + + if (!ch1.getName().equals(ch2.getName())) throw new NotEqualException("Filenames differ:\n" + ch1 + "\n" + ch2); + + buildList(ch1, ch2); + } + + } else { + compared.add(new Tuple(f1, f2)); + } + } + + private class NotEqualException extends Exception { + + public NotEqualException(String msg) { + super(msg); + } + + } + + private class Tuple { + + public T a; + public T b; + + + public Tuple(T a, T b) { + this.a = a; + this.b = b; + } + } + +} diff --git a/src/mightypork/utils/files/FileUtils.java b/src/mightypork/utils/files/FileUtils.java new file mode 100644 index 0000000..6db870e --- /dev/null +++ b/src/mightypork/utils/files/FileUtils.java @@ -0,0 +1,527 @@ +package mightypork.utils.files; + + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +import mightypork.utils.logging.Log; +import mightypork.utils.string.StringUtils; +import mightypork.utils.string.validation.StringFilter; + + +public class FileUtils { + + /** + * Copy directory recursively. + * + * @param source source file + * @param target target file + * @throws IOException on error + */ + public static void copyDirectory(File source, File target) throws IOException + { + copyDirectory(source, target, null, null); + } + + + /** + * Copy directory recursively - advanced variant. + * + * @param source source file + * @param target target file + * @param filter filter accepting only files and dirs to be copied + * @param filesCopied list into which all the target files will be added + * @throws IOException on error + */ + public static void copyDirectory(File source, File target, FileFilter filter, List filesCopied) throws IOException + { + if (!source.exists()) return; + + if (source.isDirectory()) { + if (!target.exists()) { + target.mkdir(); + } + + String[] children = source.list(); + for (int i = 0; i < children.length; i++) { + copyDirectory(new File(source, children[i]), new File(target, children[i]), filter, filesCopied); + } + + } else { + if (filter != null && !filter.accept(source)) { + return; + } + + if (filesCopied != null) filesCopied.add(target); + copyFile(source, target); + } + } + + + /** + * List directory recursively + * + * @param source source file + * @param filter filter accepting only files and dirs to be copied (or null) + * @param files list of the found files + * @throws IOException on error + */ + public static void listDirectoryRecursive(File source, StringFilter filter, List files) throws IOException + { + if (source.isDirectory()) { + String[] children = source.list(); + for (int i = 0; i < children.length; i++) { + listDirectoryRecursive(new File(source, children[i]), filter, files); + } + + } else { + if (filter != null && !filter.accept(source.getAbsolutePath())) { + return; + } + + files.add(source); + } + } + + + /** + * Copy file using streams. Make sure target directory exists! + * + * @param source source file + * @param target target file + * @throws IOException on error + */ + public static void copyFile(File source, File target) throws IOException + { + InputStream in = null; + OutputStream out = null; + + try { + in = new FileInputStream(source); + out = new FileOutputStream(target); + + copyStream(in, out); + } finally { + try { + if (in != null) in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + if (out != null) out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + /** + * Copy bytes from input to output stream, leaving out stream open + * + * @param in input stream + * @param out output stream + * @throws IOException on error + */ + public static void copyStream(InputStream in, OutputStream out) throws IOException + { + if (in == null) { + throw new NullPointerException("Input stream is null"); + } + + if (out == null) { + throw new NullPointerException("Output stream is null"); + } + + byte[] buf = new byte[2048]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + + + /** + * Improved delete + * + * @param path deleted path + * @param recursive recursive delete + * @return success + */ + public static boolean delete(File path, boolean recursive) + { + if (!path.exists()) { + return true; + } + + if (!recursive || !path.isDirectory()) return path.delete(); + + String[] list = path.list(); + for (int i = 0; i < list.length; i++) { + if (!delete(new File(path, list[i]), true)) return false; + } + + return path.delete(); + } + + + /** + * Read entire file to a string. + * + * @param file file + * @return file contents + * @throws IOException + */ + public static String fileToString(File file) throws IOException + { + FileInputStream fin = new FileInputStream(file); + + return streamToString(fin); + } + + + /** + * Get files in a folder (create folder if needed) + * + * @param dir folder + * @return list of files + */ + public static List listDirectory(File dir) + { + return FileUtils.listDirectory(dir, null); + } + + + /** + * Get files in a folder (create folder if needed) + * + * @param dir folder + * @param filter file filter + * @return list of files + */ + public static List listDirectory(File dir, FileFilter filter) + { + try { + dir.mkdir(); + } catch (RuntimeException e) { + Log.e("Error creating folder " + dir, e); + } + + List list = new ArrayList(); + + try { + for (File f : dir.listFiles(filter)) { + list.add(f); + } + } catch (Exception e) { + Log.e("Error listing folder " + dir, e); + } + + return list; + } + + + /** + * Remove extension. + * + * @param file file + * @return filename without extension + */ + public static String[] getFilenameParts(File file) + { + return getFilenameParts(file.getName()); + } + + + public static String getExtension(File file) + { + return getExtension(file.getName()); + } + + + public static String getExtension(String file) + { + return StringUtils.fromLastChar(file, '.'); + } + + + /** + * Remove extension. + * + * @param filename + * @return filename and extension + */ + public static String[] getFilenameParts(String filename) + { + String ext, name; + + try { + ext = StringUtils.fromLastDot(filename); + } catch (StringIndexOutOfBoundsException e) { + ext = ""; + } + + try { + name = StringUtils.toLastDot(filename); + } catch (StringIndexOutOfBoundsException e) { + name = ""; + Log.w("Error extracting extension from file " + filename); + } + + return new String[] { name, ext }; + } + + + /** + * Read entire input stream to a string, and close it. + * + * @param in input stream + * @return file contents + */ + public static String streamToString(InputStream in) + { + return streamToString(in, -1); + } + + + /** + * Read input stream to a string, and close it. + * + * @param in input stream + * @param lines max number of lines (-1 to disable limit) + * @return file contents + */ + public static String streamToString(InputStream in, int lines) + { + if (in == null) { + Log.e(new NullPointerException("Null stream to be converted to String.")); + return ""; // to avoid NPE's + } + + BufferedReader br = null; + StringBuilder sb = new StringBuilder(); + + String line; + try { + int cnt = 0; + br = new BufferedReader(new InputStreamReader(in, "UTF-8")); + while ((line = br.readLine()) != null && (cnt < lines || lines <= 0)) { + sb.append(line + "\n"); + cnt++; + } + + if (cnt == lines && lines > 0) { + sb.append("--- end of preview ---\n"); + } + + } catch (IOException e) { + Log.e(e); + } finally { + try { + if (br != null) br.close(); + } catch (IOException e) { + // ignore + } + } + + return sb.toString(); + } + + + public static InputStream stringToStream(String text) + { + if (text == null) return null; + + try { + return new ByteArrayInputStream(text.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + Log.e(e); + return null; + } + } + + + public static InputStream getResource(String path) + { + return FileUtils.class.getResourceAsStream(path); + } + + + public static String getResourceAsString(String path) + { + return streamToString(FileUtils.class.getResourceAsStream(path)); + } + + + /** + * Save string to file + * + * @param file file + * @param text string + * @throws IOException on error + */ + public static void stringToFile(File file, String text) throws IOException + { + PrintStream out = null; + try { + out = new PrintStream(new FileOutputStream(file), false, "UTF-8"); + + out.print(text); + + out.flush(); + + } finally { + if (out != null) out.close(); + } + } + + + public static void deleteEmptyDirs(File base) + { + for (File f : listDirectory(base)) { + if (!f.isDirectory()) continue; + + deleteEmptyDirs(f); + + List children = listDirectory(f); + if (children.size() == 0) { + f.delete(); + continue; + } + } + + } + + + /** + * Replace special characters with place holders in a filename. + * + * @param filestring filename string + * @return escaped + */ + public static String escapeFileString(String filestring) + { + StringBuilder sb = new StringBuilder(); + + for (char c : filestring.toCharArray()) { + switch (c) { + case '%': + sb.append("%%"); + break; + + case '.': + sb.append("%d"); + break; + + default: + sb.append(c); + } + + } + + return sb.toString(); + } + + + /** + * Unescape filename string obtained by escapeFileString(). + * + * @param filestring escaped string + * @return clean string + */ + public static String unescapeFileString(String filestring) + { + filestring = filestring.replace("%d", "."); + filestring = filestring.replace("%%", "%"); + + return filestring; + } + + + /** + * Escape filename, keeping the same extension + * + * @param filename filename + * @return escaped + */ + public static String escapeFilename(String filename) + { + String[] parts = getFilenameParts(filename); + + return escapeFileString(parts[0]) + "." + parts[1]; + } + + + /** + * Unescape filename, keeping the same extension + * + * @param filename escaped filename + * @return unesaped + */ + public static String unescapeFilename(String filename) + { + String[] parts = getFilenameParts(filename); + + return unescapeFileString(parts[0]) + "." + parts[1]; + } + + + public static String getBasename(String name) + { + return StringUtils.toLastChar(StringUtils.fromLastChar(name, '/'), '.'); + } + + + public static String getFilename(String name) + { + return StringUtils.fromLastChar(name, '/'); + } + + + /** + * Copy resource to file + * + * @param resname resource name + * @param file out file + * @throws IOException + */ + public static void resourceToFile(String resname, File file) throws IOException + { + InputStream in = null; + OutputStream out = null; + + try { + in = FileUtils.getResource(resname); + out = new FileOutputStream(file); + + FileUtils.copyStream(in, out); + } finally { + try { + if (in != null) in.close(); + } catch (IOException e) { + // ignore + } + + try { + if (out != null) out.close(); + } catch (IOException e) { + // ignore + } + } + + } + + + /** + * Get resource as string, safely closing streams. + * + * @param resname resource name + * @return resource as string, empty string on failure + */ + public static String resourceToString(String resname) + { + InputStream in = FileUtils.getResource(resname); + return streamToString(in); + } +} diff --git a/src/mightypork/utils/files/OsUtils.java b/src/mightypork/utils/files/OsUtils.java new file mode 100644 index 0000000..650c1fd --- /dev/null +++ b/src/mightypork/utils/files/OsUtils.java @@ -0,0 +1,149 @@ +package mightypork.utils.files; + + +import java.io.File; + + +public class OsUtils { + + public static enum EnumOS + { + linux, macos, solaris, unknown, windows; + + public boolean isLinux() + { + return this == linux || this == solaris; + } + + + public boolean isMac() + { + return this == macos; + } + + + public boolean isWindows() + { + return this == windows; + } + } + + private static EnumOS cachedOs; + + + /** + * Get App dir, ensure it exists + * + * @param dirname + * @return app dir + */ + public static File getWorkDir(String dirname) + { + return getWorkDir(dirname, true); + } + + + /** + * Get App sub-folder + * + * @param dirname + * @param subfolderName + * @param create + * @return the folder + */ + public static File getWorkDir(String dirname, String subfolderName, boolean create) + { + File f = new File(getWorkDir(dirname), subfolderName); + + if (!f.exists() && create) { + if (!f.mkdirs()) { + throw new RuntimeException("Could not create."); + } + } + + return f; + } + + + /** + * Get App sub-folder, create + * + * @param dirname + * @param subfolderName + * @return the folder + */ + public static File getWorkDir(String dirname, String subfolderName) + { + return getWorkDir(dirname, subfolderName, true); + } + + + public static EnumOS getOs() + { + if (cachedOs != null) return cachedOs; + + String s = System.getProperty("os.name").toLowerCase(); + + if (s.contains("win")) { + cachedOs = EnumOS.windows; + + } else if (s.contains("mac")) { + cachedOs = EnumOS.macos; + + } else if (s.contains("linux") || s.contains("unix")) { + cachedOs = EnumOS.linux; + + } else if (s.contains("solaris") || s.contains("sunos")) { + cachedOs = EnumOS.solaris; + + } else { + cachedOs = EnumOS.unknown; + } + + return cachedOs; + } + + + private static File getWorkDir(String dirname, boolean create) + { + String userhome = System.getProperty("user.home", "."); + File file; + + switch (getOs()) { + case linux: + case solaris: + file = new File(userhome, "." + dirname + '/'); + break; + + case windows: + String appdata = System.getenv("APPDATA"); + + if (appdata != null) { + file = new File(appdata, "." + dirname + '/'); + } else { + file = new File(userhome, "." + dirname + '/'); + } + + break; + + case macos: + file = new File(userhome, "Library/Application Support/" + dirname); + break; + + default: + file = new File(userhome, dirname + "/"); + break; + } + + if (!file.exists() || !file.isDirectory()) { + if (create) { + if (!file.mkdirs()) { + throw new RuntimeException("Could not create working directory."); + } + } + } + + return file; + } + +} diff --git a/src/mightypork/utils/files/PropertyManager.java b/src/mightypork/utils/files/PropertyManager.java new file mode 100644 index 0000000..d0b65fc --- /dev/null +++ b/src/mightypork/utils/files/PropertyManager.java @@ -0,0 +1,963 @@ +package mightypork.utils.files; + + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +import mightypork.utils.math.Calc; + + +/** + * Property manager with advanced formatting and value checking.
+ * Methods starting with put are for filling. Most of the others are shortcuts + * to getters. + * + * @author MightyPork + */ +public class PropertyManager { + + /** + * Properties stored in file, alphabetically sorted.
+ * Property file is much cleaner than the normal java.util.Properties, + * newlines can be inserted to separate categories, and individual keys can + * have their own inline comments. + * + * @author MightyPork + */ + private static class SortedProperties extends Properties { + + /** A table of hex digits */ + private static final char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + + /** + * this is here because the original method is private. + * + * @param nibble + * @return hex char. + */ + private static char toHex(int nibble) + { + return hexChars[(nibble & 0xF)]; + } + + + private static void writeComments(BufferedWriter bw, String comm) throws IOException + { + String comments = comm.replace("\n\n", "\n \n"); + + int len = comments.length(); + int current = 0; + int last = 0; + char[] uu = new char[6]; + uu[0] = '\\'; + uu[1] = 'u'; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + + if (c > '\u00ff') { + uu[2] = toHex((c >> 12) & 0xf); + uu[3] = toHex((c >> 8) & 0xf); + uu[4] = toHex((c >> 4) & 0xf); + uu[5] = toHex(c & 0xf); + bw.write(new String(uu)); + } else { + bw.newLine(); + if (c == '\r' && current != len - 1 && comments.charAt(current + 1) == '\n') { + current++; + } + } + last = current + 1; + } + current++; + } + if (last != current) { + bw.write("# " + comments.substring(last, current)); + } + bw.newLine(); + bw.newLine(); + bw.newLine(); + } + + /** Option: put empty line before each comment. */ + public boolean cfgEmptyLineBeforeComment = true; + + /** + * Option: Separate sections by newline
+ * Section = string before first dot in key. + */ + public boolean cfgSeparateSectionsByEmptyLine = true; + + private boolean firstEntry = true; + + /** Comments for individual keys */ + private Hashtable keyComments = new Hashtable(); + + private String lastSectionBeginning = ""; + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public synchronized Enumeration keys() + { + Enumeration keysEnum = super.keys(); + Vector keyList = new Vector(); + while (keysEnum.hasMoreElements()) { + keyList.add(keysEnum.nextElement()); + } + Collections.sort(keyList); + return keyList.elements(); + } + + + private String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) + { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuffer outBuffer = new StringBuffer(bufLen); + + for (int x = 0; x < len; x++) { + char aChar = theString.charAt(x); + + // Handle common case first, selecting largest block that + // avoids the specials below + if ((aChar > 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); + outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + + switch (aChar) { + case ' ': + if (x == 0 || escapeSpace) { + outBuffer.append('\\'); + } + outBuffer.append(' '); + break; + + case '\t': + outBuffer.append('\\'); + outBuffer.append('t'); + break; + + case '\n': + outBuffer.append('\\'); + outBuffer.append('n'); + break; + + case '\r': + outBuffer.append('\\'); + outBuffer.append('r'); + break; + + case '\f': + outBuffer.append('\\'); + outBuffer.append('f'); + break; + + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); + outBuffer.append(aChar); + break; + + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { + outBuffer.append('\\'); + outBuffer.append('u'); + outBuffer.append(toHex((aChar >> 12) & 0xF)); + outBuffer.append(toHex((aChar >> 8) & 0xF)); + outBuffer.append(toHex((aChar >> 4) & 0xF)); + outBuffer.append(toHex(aChar & 0xF)); + } else { + outBuffer.append(aChar); + } + } + } + + return outBuffer.toString(); + } + + + /** + * Set additional comment to a key + * + * @param key key for comment + * @param comment the comment + */ + public void setKeyComment(String key, String comment) + { + keyComments.put(key, comment); + } + + + @SuppressWarnings("rawtypes") + @Override + public void store(OutputStream out, String comments) throws IOException + { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); + + boolean escUnicode = false; + + if (comments != null) { + writeComments(bw, comments); + } + + synchronized (this) { + for (Enumeration e = keys(); e.hasMoreElements();) { + boolean wasNewLine = false; + + String key = (String) e.nextElement(); + String val = (String) get(key); + key = saveConvert(key, true, escUnicode); + val = saveConvert(val, false, escUnicode); + + if (cfgSeparateSectionsByEmptyLine && !lastSectionBeginning.equals(key.split("[.]")[0])) { + if (!firstEntry) { + bw.newLine(); + bw.newLine(); + } + + wasNewLine = true; + lastSectionBeginning = key.split("[.]")[0]; + } + + if (keyComments.containsKey(key)) { + String cm = keyComments.get(key); + cm = cm.replace("\r", "\n"); + cm = cm.replace("\r\n", "\n"); + cm = cm.replace("\n\n", "\n \n"); + + String[] cmlines = cm.split("\n"); + + if (!wasNewLine && !firstEntry && cfgEmptyLineBeforeComment) { + bw.newLine(); + } + for (String cmline : cmlines) { + bw.write("# " + cmline); + bw.newLine(); + } + } + + bw.write(key + " = " + val); + bw.newLine(); + + firstEntry = false; + } + } + bw.flush(); + } + } + + /** + * Helper class which loads Properties from UTF-8 file (Properties use + * "ISO-8859-1" by default) + * + * @author Itay Maman + */ + private static class PropertiesLoader { + + private static String escapifyStr(String str) + { + StringBuilder result = new StringBuilder(); + + int len = str.length(); + for (int x = 0; x < len; x++) { + char ch = str.charAt(x); + if (ch <= 0x007e) { + result.append(ch); + continue; + } + + result.append('\\'); + result.append('u'); + result.append(hexDigit(ch, 12)); + result.append(hexDigit(ch, 8)); + result.append(hexDigit(ch, 4)); + result.append(hexDigit(ch, 0)); + } + return result.toString(); + } + + + private static char hexDigit(char ch, int offset) + { + int val = (ch >> offset) & 0xF; + if (val <= 9) { + return (char) ('0' + val); + } + + return (char) ('A' + val - 10); + } + + + public static SortedProperties loadProperties(SortedProperties props, InputStream is) throws IOException + { + return loadProperties(props, is, "utf-8"); + } + + + public static SortedProperties loadProperties(SortedProperties props, InputStream is, String encoding) throws IOException + { + StringBuilder sb = new StringBuilder(); + InputStreamReader isr = new InputStreamReader(is, encoding); + while (true) { + int temp = isr.read(); + if (temp < 0) { + break; + } + + char c = (char) temp; + sb.append(c); + } + + String read = sb.toString(); + + String inputString = escapifyStr(read); + byte[] bs = inputString.getBytes("ISO-8859-1"); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + + SortedProperties ps = props; + ps.load(bais); + return ps; + } + } + + /** + * Property entry in Property manager. + * + * @author MightyPork + */ + private class Property { + + public String entryComment; + + public String name; + + public boolean bool = false; + public boolean defbool = false; + + public double num = -1; + public double defnum = -1; + + public String defstr = ""; + public String str = ""; + + public PropertyType type; + + + /** + * Property + * + * @param key key + * @param default_value default value + * @param entry_type type + * @param entry_comment entry comment + */ + public Property(String key, boolean default_value, PropertyType entry_type, String entry_comment) { + name = key; + defbool = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Property entry + * + * @param key property key + * @param default_value default value + * @param entry_type property type from enum + * @param entry_comment property comment or null + */ + public Property(String key, double default_value, PropertyType entry_type, String entry_comment) { + name = key; + defnum = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Property + * + * @param key key + * @param default_value default value + * @param entry_type type + * @param entry_comment entry comment + */ + public Property(String key, String default_value, PropertyType entry_type, String entry_comment) { + name = key; + defstr = default_value; + type = entry_type; + entryComment = entry_comment; + } + + + /** + * Get boolean + * + * @return the boolean + */ + public boolean getBoolean() + { + return bool; + } + + + /** + * Get number + * + * @return the number + */ + public int getInteger() + { + return (int) Math.round(num); + } + + + /** + * Get number as double + * + * @return the number + */ + public double getDouble() + { + return num; + } + + + /** + * Get string + * + * @return the string + */ + public String getString() + { + return str; + } + + + /** + * Is this entry valid? + * + * @return is valid + */ + public boolean isValid() + { + switch (type) { + case STRING: + return str != null; + + case BOOLEAN: + case INT: + case DOUBLE: + return true; + + } + return false; + } + + + /** + * Load property value from a file + * + * @param string the string loaded + * @return was OK + */ + public boolean parse(String string) + { + switch (type) { + case INT: + + if (string == null) { + num = defnum; + return false; + } + + try { + num = Integer.parseInt(string.trim()); + } catch (NumberFormatException e) { + num = defnum; + } + + break; + + case DOUBLE: + + if (string == null) { + num = defnum; + return false; + } + + try { + num = Double.parseDouble(string.trim()); + } catch (NumberFormatException e) { + num = defnum; + } + + break; + + case STRING: + + if (string == null) { + str = defstr; + return false; + } + + str = string; + break; + + case BOOLEAN: + + if (string == null) { + bool = defbool; + return false; + } + + String string2 = string.toLowerCase(); + bool = string2.equals("yes") || string2.equals("true") || string2.equals("on") || string2.equals("enabled") || string2.equals("enable"); + } + + return true; + } + + + /** + * prepare the contents for insertion into Properties + * + * @return the string prepared, or null if type is invalid + */ + @Override + public String toString() + { + if (!isValid()) { + if (type == PropertyType.INT || type == PropertyType.DOUBLE) { + num = defnum; + } + } + + switch (type) { + case INT: + return Integer.toString((int) num); + + case DOUBLE: + return Calc.floatToString((float) num); + + case STRING: + return str; + + case BOOLEAN: + return bool ? "True" : "False"; + } + return null; + } + + + /** + * If this entry is not valid, change it to the dafault value. + */ + public void validate() + { + if (!isValid()) { + if (type == PropertyType.STRING) { + str = defstr; + } + } + } + } + + /** + * Property types + */ + private enum PropertyType + { + BOOLEAN, INT, STRING, DOUBLE; + } + + /** put newline before entry comments */ + private boolean cfgNewlineBeforeComments = true; + /** Disable entry validation */ + private boolean cfgNoValidate = true; + /** Put newline between sections. */ + private boolean cfgSeparateSections = true; + /** Force save, even if nothing changed (used to save changed comments) */ + private boolean cfgForceSave; + + private File file; + private String fileComment = ""; + + private TreeMap entries; + private TreeMap keyRename; + private TreeMap setValues; + private SortedProperties pr = new SortedProperties(); + + + /** + * Create property manager from file path and an initial comment. + * + * @param file file with the props + * @param comment the initial comment. Use \n in it if you want. + */ + public PropertyManager(File file, String comment) { + this.file = file; + this.entries = new TreeMap(); + this.setValues = new TreeMap(); + this.keyRename = new TreeMap(); + this.fileComment = comment; + } + + + /** + * Load, fix and write to file. + */ + public void apply() + { + boolean needsSave = false; + FileInputStream fis = null; + try { + new File(file.getParent()).mkdirs(); + fis = new FileInputStream(file); + pr = PropertiesLoader.loadProperties(pr, fis); + + } catch (IOException e) { + needsSave = true; + pr = new SortedProperties(); + } finally { + try { + if (fis != null) fis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + pr.cfgSeparateSectionsByEmptyLine = cfgSeparateSections; + pr.cfgEmptyLineBeforeComment = cfgNewlineBeforeComments; + + ArrayList keyList = new ArrayList(); + + // rename keys + for (Entry entry : keyRename.entrySet()) { + if (pr.getProperty(entry.getKey()) == null) { + continue; + } + pr.setProperty(entry.getValue(), pr.getProperty(entry.getKey())); + pr.remove(entry.getKey()); + needsSave = true; + } + + // set the override values into the freshly loaded properties file + for (Entry entry : setValues.entrySet()) { + pr.setProperty(entry.getKey(), entry.getValue()); + needsSave = true; + } + + // validate entries one by one, replace with default when needed + for (Property entry : entries.values()) { + keyList.add(entry.name); + + String propOrig = pr.getProperty(entry.name); + if (!entry.parse(propOrig)) needsSave = true; + if (!cfgNoValidate) { + entry.validate(); + } + + if (entry.entryComment != null) { + pr.setKeyComment(entry.name, entry.entryComment); + } + + if (propOrig == null || !entry.toString().equals(propOrig)) { + pr.setProperty(entry.name, entry.toString()); + + needsSave = true; + } + } + + // removed unused props + for (String propname : pr.keySet().toArray(new String[pr.size()])) { + if (!keyList.contains(propname)) { + pr.remove(propname); + needsSave = true; + } + + } + + // save if needed + if (needsSave || cfgForceSave) { + try { + pr.store(new FileOutputStream(file), fileComment); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + setValues.clear(); + keyRename.clear(); + } + + + /** + * @param newlineBeforeComments put newline before comments + */ + public void cfgNewlineBeforeComments(boolean newlineBeforeComments) + { + this.cfgNewlineBeforeComments = newlineBeforeComments; + } + + + /** + * @param separateSections do separate sections by newline + */ + public void cfgSeparateSections(boolean separateSections) + { + this.cfgSeparateSections = separateSections; + } + + + /** + * @param forceSave save even if unchanged. + */ + public void cfgForceSave(boolean forceSave) + { + this.cfgForceSave = forceSave; + } + + + /** + * @param validate enable validation + */ + public void enableValidation(boolean validate) + { + this.cfgNoValidate = !validate; + } + + + /** + * Get a property entry (rarely used) + * + * @param n key + * @return the entry + */ + private Property get(String n) + { + try { + return entries.get(n); + } catch (Throwable t) { + return null; + } + } + + + /** + * Get boolean property + * + * @param n key + * @return the boolean found, or false + */ + public Boolean getBoolean(String n) + { + try { + return entries.get(n).getBoolean(); + } catch (Throwable t) { + return false; + } + } + + + /** + * Get numeric property + * + * @param n key + * @return the int found, or null + */ + public Integer getInteger(String n) + { + try { + return get(n).getInteger(); + } catch (Throwable t) { + return -1; + } + } + + + /** + * Get numeric property as double + * + * @param n key + * @return the double found, or null + */ + public Double getDouble(String n) + { + try { + return get(n).getDouble(); + } catch (Throwable t) { + return -1D; + } + } + + + /** + * Get string property + * + * @param n key + * @return the string found, or null + */ + public String getString(String n) + { + try { + return get(n).getString(); + } catch (Throwable t) { + return null; + } + } + + + /** + * Add a boolean property + * + * @param n key + * @param d default value + */ + public void putBoolean(String n, boolean d) + { + entries.put(n, new Property(n, d, PropertyType.BOOLEAN, null)); + return; + } + + + /** + * Add a boolean property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putBoolean(String n, boolean d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.BOOLEAN, comment)); + return; + } + + + /** + * Add a numeric property (double) + * + * @param n key + * @param d default value + */ + public void putDouble(String n, int d) + { + entries.put(n, new Property(n, d, PropertyType.DOUBLE, null)); + return; + } + + + /** + * Add a numeric property (double) + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putDouble(String n, int d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.DOUBLE, comment)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + */ + public void putInteger(String n, int d) + { + entries.put(n, new Property(n, d, PropertyType.INT, null)); + return; + } + + + /** + * Add a numeric property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putInteger(String n, int d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.INT, comment)); + return; + } + + + /** + * Add a string property + * + * @param n key + * @param d default value + */ + public void putString(String n, String d) + { + entries.put(n, new Property(n, d, PropertyType.STRING, null)); + return; + } + + + /** + * Add a string property + * + * @param n key + * @param d default value + * @param comment the in-file comment + */ + public void putString(String n, String d, String comment) + { + entries.put(n, new Property(n, d, PropertyType.STRING, comment)); + return; + } + + + /** + * Rename key before doing "apply"; value is preserved + * + * @param oldKey old key + * @param newKey new key + */ + public void renameKey(String oldKey, String newKey) + { + keyRename.put(oldKey, newKey); + return; + } + + + /** + * Set value saved to certain key; use to save runtime-changed configuration + * values. + * + * @param key key + * @param value the saved value + */ + public void setValue(String key, Object value) + { + setValues.put(key, value.toString()); + return; + } + +} diff --git a/src/mightypork/utils/files/SimpleConfig.java b/src/mightypork/utils/files/SimpleConfig.java new file mode 100644 index 0000000..e265938 --- /dev/null +++ b/src/mightypork/utils/files/SimpleConfig.java @@ -0,0 +1,204 @@ +package mightypork.utils.files; + + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import mightypork.utils.logging.Log; + + +/** + * Utility for parsing simple config files
+ * # and // mark a comment
+ * empty lines and lines without "=" are ignored
+ * lines with "=" must have "key = value" format, or a warning is logged.
+ * use "NULL" to create empty value. + * + * @author MightyPork + */ +public class SimpleConfig { + + /** + * Load list from file + * + * @param file file + * @return map of keys and values + * @throws IOException + */ + public static List listFromFile(File file) throws IOException + { + String fileText = FileUtils.fileToString(file); + + return listFromString(fileText); + } + + + /** + * Load map from file + * + * @param file file + * @return map of keys and values + * @throws IOException + */ + public static Map mapFromFile(File file) throws IOException + { + String fileText = FileUtils.fileToString(file); + + return mapFromString(fileText); + } + + + /** + * Load list from string + * + * @param text text of the file + * @return map of keys and values + */ + public static List listFromString(String text) + { + List list = new ArrayList(); + + String[] groupsLines = text.split("\n"); + + for (String s : groupsLines) { + // ignore invalid lines + if (s.length() == 0) continue; + if (s.startsWith("#") || s.startsWith("//")) continue; + + // NULL value + if (s.equalsIgnoreCase("NULL")) s = null; + + if (s != null) s = s.replace("\\n", "\n"); + + // save extracted key-value pair + list.add(s); + } + + return list; + } + + + /** + * Load map from string + * + * @param text text of the file + * @return map of keys and values + */ + public static Map mapFromString(String text) + { + LinkedHashMap pairs = new LinkedHashMap(); + + String[] groupsLines = text.split("\n"); + + for (String s : groupsLines) { + // ignore invalid lines + if (s.length() == 0) continue; + if (s.startsWith("#") || s.startsWith("//")) continue; + if (!s.contains("=")) continue; + + // split and trim + String[] parts = s.split("="); + for (int i = 0; i < parts.length; i++) { + parts[i] = parts[i].trim(); + } + + // check if both parts are valid + if (parts.length == 0) { + Log.w("Bad line in config file: " + s); + continue; + } + + if (parts.length == 1) { + parts = new String[] { parts[0], "" }; + } + + if (parts.length != 2) { + Log.w("Bad line in config file: " + s); + continue; + } + + // NULL value + if (parts[0].equalsIgnoreCase("NULL")) parts[0] = null; + if (parts[1].equalsIgnoreCase("NULL")) parts[1] = null; + + if (parts[0] != null) parts[0] = parts[0].replace("\\n", "\n"); + if (parts[1] != null) parts[1] = parts[1].replace("\\n", "\n"); + + // save extracted key-value pair + pairs.put(parts[0], parts[1]); + } + + return pairs; + } + + + /** + * Save map to file + * + * @param target + * @param data + * @param allowNulls allow nulls. + * @throws IOException + */ + public static void mapToFile(File target, Map data, boolean allowNulls) throws IOException + { + List lines = new ArrayList(); + + for (Entry e : data.entrySet()) { + String key = e.getKey(); + String value = e.getValue(); + + if (!allowNulls && (key == null || value == null || key.length() == 0 || value.length() == 0)) continue; + + if (key == null) key = "NULL"; + if (value == null) value = "NULL"; + + key = key.replace("\n", "\\n"); + value = value.replace("\n", "\\n"); + + lines.add(key + " = " + value); + } + + String text = ""; //# File written by SimpleConfig + + for (String s : lines) { + if (text.length() > 0) text += "\n"; + + text += s; + } + + FileUtils.stringToFile(target, text); + + } + + + /** + * Save list to file + * + * @param target + * @param data + * @throws IOException + */ + public static void listToFile(File target, List data) throws IOException + { + String text = ""; //# File written by SimpleConfig + + for (String s : data) { + if (text.length() > 0) text += "\n"; + + if (s == null) s = "NULL"; + + s = s.replace("\n", "\\n"); + + text += s; + } + + FileUtils.stringToFile(target, text); + + } +} diff --git a/src/mightypork/utils/files/ZipBuilder.java b/src/mightypork/utils/files/ZipBuilder.java new file mode 100644 index 0000000..c088701 --- /dev/null +++ b/src/mightypork/utils/files/ZipBuilder.java @@ -0,0 +1,123 @@ +package mightypork.utils.files; + + +import java.io.*; +import java.util.HashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import mightypork.utils.logging.Log; + + +/** + * Class for building a zip file + * + * @author MightyPork + */ +public class ZipBuilder { + + private ZipOutputStream out; + private HashSet included = new HashSet(); + + + /** + * @param target target zip file + * @throws FileNotFoundException if the file is directory or cannot be + * created + */ + public ZipBuilder(File target) throws FileNotFoundException { + target.getParentFile().mkdirs(); + + FileOutputStream dest = new FileOutputStream(target); + out = new ZipOutputStream(new BufferedOutputStream(dest)); + } + + + /** + * Add stream to a path + * + * @param path path + * @param in stream + * @throws IOException + */ + public void addStream(String path, InputStream in) throws IOException + { + path = preparePath(path); + if (included.contains(path)) { + Log.f3("Zip already contains file " + path + ", skipping."); + return; // ignore + } + included.add(path); + + out.putNextEntry(new ZipEntry(path)); + + FileUtils.copyStream(in, out); + } + + + /** + * Add string as a file + * + * @param path path + * @param text text to write + * @throws IOException + */ + public void addString(String path, String text) throws IOException + { + path = preparePath(path); + if (included.contains(path)) return; // ignore + included.add(path); + + out.putNextEntry(new ZipEntry(path)); + + InputStream in = FileUtils.stringToStream(text); + FileUtils.copyStream(in, out); + } + + + /** + * Add resource obtained via FileUtils.getResource() + * + * @param path path + * @param resPath resource path + * @throws IOException + */ + public void addResource(String path, String resPath) throws IOException + { + path = preparePath(path); + if (included.contains(path)) return; // ignore + included.add(path); + + out.putNextEntry(new ZipEntry(path)); + + InputStream in = FileUtils.getResource(resPath); + FileUtils.copyStream(in, out); + } + + + /** + * Normalize path + * + * @param path original path + * @return normalized path + */ + private String preparePath(String path) + { + path = path.replace("\\", "/"); + + if (path.charAt(0) == '/') path = path.substring(1); + + return path; + } + + + /** + * Close the zip stream + * + * @throws IOException + */ + public void close() throws IOException + { + out.close(); + } +} diff --git a/src/mightypork/utils/files/ZipUtils.java b/src/mightypork/utils/files/ZipUtils.java new file mode 100644 index 0000000..56e86c4 --- /dev/null +++ b/src/mightypork/utils/files/ZipUtils.java @@ -0,0 +1,227 @@ +package mightypork.utils.files; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import mightypork.utils.string.validation.StringFilter; + + +/** + * Utilities for manipulating zip files + * + * @author MightyPork + */ +public class ZipUtils { + + private static final int BUFFER_SIZE = 2048; + + + /** + * Extract zip file to target directory + * + * @param file zip file + * @param outputDir target directory + * @param filter string filter (will be used to test entry names (paths)) + * @return list of entries extracted (paths) + * @throws IOException + */ + public static List extractZip(File file, File outputDir, StringFilter filter) throws IOException + { + ZipFile zip = null; + try { + zip = new ZipFile(file); + + return extractZip(zip, outputDir, filter); + + } finally { + try { + if (zip != null) zip.close(); + } catch (IOException e) { + //ignore + } + } + } + + + /** + * Extract zip file to target directory + * + * @param zip open zip file + * @param outputDir target directory + * @param filter string filter (will be used to test entry names (paths)) + * @return list of entries extracted (paths) + * @throws IOException + */ + public static List extractZip(ZipFile zip, File outputDir, StringFilter filter) throws IOException + { + ArrayList files = new ArrayList(); + + outputDir.mkdirs(); + + Enumeration zipFileEntries = zip.entries(); + + // process each entry + while (zipFileEntries.hasMoreElements()) { + ZipEntry entry = zipFileEntries.nextElement(); + + // parse filename and path + String entryPath = entry.getName(); + File destFile = new File(outputDir, entryPath); + File destinationParent = destFile.getParentFile(); + + if (entry.isDirectory() || (filter != null && !filter.accept(entryPath))) continue; + + // make sure directories exist + destinationParent.mkdirs(); + + if (!entry.isDirectory()) { + extractZipEntry(zip, entry, destFile); + files.add(entryPath); + } + } + + return files; + } + + + /** + * Read zip entries and add their paths to a list + * + * @param zipFile open zip file + * @return list of entry names + * @throws IOException on error + */ + public static List listZip(File zipFile) throws IOException + { + ZipFile zip = null; + try { + zip = new ZipFile(zipFile); + return listZip(zip); + } finally { + try { + if (zip != null) zip.close(); + } catch (IOException e) { + //ignore + } + } + } + + + /** + * Read zip entries and add their paths to a list + * + * @param zip open zip file + * @return list of entry names + * @throws IOException on error + */ + public static List listZip(ZipFile zip) throws IOException + { + ArrayList files = new ArrayList(); + + Enumeration zipFileEntries = zip.entries(); + + // process each entry + while (zipFileEntries.hasMoreElements()) { + ZipEntry entry = zipFileEntries.nextElement(); + + if (!entry.isDirectory()) { + files.add(entry.getName()); + } + } + + return files; + } + + + /** + * Extract one zip entry to target file + * + * @param zip open zip file + * @param entry entry from the zip file + * @param destFile destination file ((NOT directory!) + * @throws IOException on error + */ + public static void extractZipEntry(ZipFile zip, ZipEntry entry, File destFile) throws IOException + { + destFile.getParentFile().mkdirs(); + + BufferedInputStream is = null; + FileOutputStream fos = null; + BufferedOutputStream dest = null; + + try { + is = new BufferedInputStream(zip.getInputStream(entry)); + fos = new FileOutputStream(destFile); + dest = new BufferedOutputStream(fos, BUFFER_SIZE); + + FileUtils.copyStream(is, dest); + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + //ignore + } + + try { + if (dest != null) dest.close(); + } catch (IOException e) { + //ignore + } + + } + } + + + /** + * Load zip entry to String + * + * @param zip open zip file + * @param entry entry from the zip file + * @return loaded string + * @throws IOException on error + */ + public static String zipEntryToString(ZipFile zip, ZipEntry entry) throws IOException + { + BufferedInputStream is = null; + try { + is = new BufferedInputStream(zip.getInputStream(entry)); + String s = FileUtils.streamToString(is); + return s; + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + //ignore + } + } + } + + + public static boolean entryExists(File selectedFile, String string) + { + ZipFile zf = null; + + try { + zf = new ZipFile(selectedFile); + return zf.getEntry(string) != null; + } catch (Exception e) { + return false; + } finally { + try { + if (zf != null) zf.close(); + } catch (IOException e) { + //ignore + } + } + + } +} diff --git a/src/mightypork/utils/files/ion/AbstractIonList.java b/src/mightypork/utils/files/ion/AbstractIonList.java new file mode 100644 index 0000000..e336b0d --- /dev/null +++ b/src/mightypork/utils/files/ion/AbstractIonList.java @@ -0,0 +1,79 @@ +package mightypork.utils.files.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + + +/** + * Ionizable Arraylist + * + * @author MightyPork + * @param + */ +public abstract class AbstractIonList extends ArrayList implements Ionizable { + + @Override + public void ionRead(InputStream in) throws IonException + { + try { + while (true) { + byte b = StreamUtils.readByte(in); + + if (b == IonMarks.ENTRY) { + T value = (T) Ion.readObject(in); + add(value); + } else if (b == IonMarks.END) { + break; + } else { + throw new IonException("Unexpected mark in IonList: " + Integer.toHexString(b)); + } + } + ionReadCustomData(in); + } catch (IOException e) { + throw new IonException("Error reading ion list", e); + } + } + + + @Override + public void ionWrite(OutputStream out) throws IonException + { + try { + for (T entry : this) { + if (entry instanceof IonizableOptional && !((IonizableOptional) entry).ionShouldSave()) continue; + StreamUtils.writeByte(out, IonMarks.ENTRY); + Ion.writeObject(out, entry); + } + StreamUtils.writeByte(out, IonMarks.END); + ionWriteCustomData(out); + } catch (IOException e) { + throw new IonException("Error reading ion map", e); + } + } + + + /** + * Read custom data of this AbstractIonList implementation + * + * @param in input stream + */ + public void ionReadCustomData(InputStream in) + {} + + + /** + * Write custom data of this AbstractIonList implementation + * + * @param out output stream + */ + public void ionWriteCustomData(OutputStream out) + {} + + + @Override + public abstract byte ionMark(); + +} diff --git a/src/mightypork/utils/files/ion/AbstractIonMap.java b/src/mightypork/utils/files/ion/AbstractIonMap.java new file mode 100644 index 0000000..107c187 --- /dev/null +++ b/src/mightypork/utils/files/ion/AbstractIonMap.java @@ -0,0 +1,101 @@ +package mightypork.utils.files.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.LinkedHashMap; + + +/** + * Ionizable HashMap + * + * @author MightyPork + * @param + */ +public abstract class AbstractIonMap extends LinkedHashMap implements Ionizable { + + @Override + public V get(Object key) + { + return super.get(key); + } + + + @Override + public V put(String key, V value) + { + return super.put(key, value); + } + + + @Override + public void ionRead(InputStream in) throws IonException + { + try { + while (true) { + byte b = StreamUtils.readByte(in); + if (b == IonMarks.ENTRY) { + String key = StreamUtils.readStringBytes(in); + V value; + try { + value = (V) Ion.readObject(in); + put(key, value); + } catch (IonException e) { + e.printStackTrace(); + } + } else if (b == IonMarks.END) { + break; + } else { + throw new RuntimeException("Unexpected mark in IonMap: " + Integer.toHexString(b)); + } + } + ionReadCustomData(in); + } catch (IOException e) { + throw new IonException("Error reading ion map", e); + } + } + + + @Override + public void ionWrite(OutputStream out) throws IonException + { + try { + for (java.util.Map.Entry entry : entrySet()) { + StreamUtils.writeByte(out, IonMarks.ENTRY); + StreamUtils.writeStringBytes(out, entry.getKey()); + Ion.writeObject(out, entry.getValue()); + } + StreamUtils.writeByte(out, IonMarks.END); + ionWriteCustomData(out); + } catch (IOException e) { + throw new IonException("Error reading ion map", e); + } + } + + + /** + * Read custom data of this AbstractIonMap implementation + * + * @param in input stream + */ + public void ionReadCustomData(InputStream in) + {} + + + /** + * Write custom data of this AbstractIonMap implementation + * + * @param out output stream + */ + public void ionWriteCustomData(OutputStream out) + {} + + + @Override + public byte ionMark() + { + return IonMarks.MAP; + } + +} diff --git a/src/mightypork/utils/files/ion/Ion.java b/src/mightypork/utils/files/ion/Ion.java new file mode 100644 index 0000000..7149379 --- /dev/null +++ b/src/mightypork/utils/files/ion/Ion.java @@ -0,0 +1,298 @@ +package mightypork.utils.files.ion; + + +import java.io.*; +import java.util.HashMap; +import java.util.Map; + +import mightypork.utils.math.Calc; + + +/** + * Universal data storage system + * + * @author MightyPork + */ +public class Ion { + + /** Ionizables */ + private static Map> customIonizables = new HashMap>(); + + // register default ionizables + static { + try { + registerIonizable(IonMarks.MAP, IonMap.class); + registerIonizable(IonMarks.LIST, IonList.class); + } catch (IonException e) { + e.printStackTrace(); + } + } + + + /** + * Register new Ionizable for direct reconstructing. + * + * @param mark byte mark to be used, see {@link IonMarks} for reference. + * @param objClass class of the registered Ionizable + * @throws IonException + */ + public static void registerIonizable(byte mark, Class objClass) throws IonException + { + if (customIonizables.containsKey(mark)) { + throw new IonException("IonMark " + mark + " is already used."); + } + customIonizables.put(mark, objClass); + } + + + /** + * Load Ion object from file. + * + * @param file file path + * @return the loaded object + * @throws IonException + */ + public static Object fromFile(String file) throws IonException + { + return fromFile(new File(file)); + } + + + /** + * Load Ion object from file. + * + * @param file file + * @return the loaded object + * @throws IonException on failure + */ + public static Object fromFile(File file) throws IonException + { + InputStream in = null; + try { + in = new FileInputStream(file); + Object obj = fromStream(in); + return obj; + + } catch (IOException e) { + throw new IonException("Error loading ION file.", e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + + /** + * Load Ion object from stream. + * + * @param in input stream + * @return the loaded object + * @throws IonException + */ + public static Object fromStream(InputStream in) throws IonException + { + return readObject(in); + } + + + /** + * Store Ion object to file. + * + * @param path file path + * @param obj object to store + * @throws IonException + */ + public static void toFile(String path, Object obj) throws IonException + { + toFile(new File(path), obj); + } + + + /** + * Store Ion object to file. + * + * @param path file path + * @param obj object to store + * @throws IonException + */ + public static void toFile(File path, Object obj) throws IonException + { + OutputStream out = null; + try { + String f = path.toString(); + File dir = new File(f.substring(0, f.lastIndexOf(File.separator))); + + dir.mkdirs(); + + out = new FileOutputStream(path); + + toStream(out, obj); + + out.flush(); + out.close(); + } catch (Exception e) { + throw new IonException("Error writing to ION file.", e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + } + + + /** + * Store Ion object to output stream. + * + * @param out output stream * + * @param obj object to store + * @throws IonException + */ + public static void toStream(OutputStream out, Object obj) throws IonException + { + writeObject(out, obj); + } + + + /** + * Read single ionizable or primitive object from input stream + * + * @param in input stream + * @return the loaded object + * @throws IonException + */ + public static Object readObject(InputStream in) throws IonException + { + try { + int bi = in.read(); + if (bi == -1) throw new IonException("Unexpected end of stream."); + byte b = (byte) bi; + if (customIonizables.containsKey(b)) { + Ionizable ion; + try { + ion = ((Ionizable) customIonizables.get(b).newInstance()); + } catch (InstantiationException e) { + throw new IonException("Cound not instantiate " + customIonizables.get(b).getSimpleName(), e); + } catch (IllegalAccessException e) { + throw new IonException("Cound not instantiate " + customIonizables.get(b).getSimpleName(), e); + } + ion.ionRead(in); + return ion; + } + + switch (b) { + case IonMarks.BOOLEAN: + return StreamUtils.readBoolean(in); + case IonMarks.BYTE: + return StreamUtils.readByte(in); + case IonMarks.CHAR: + return StreamUtils.readChar(in); + case IonMarks.SHORT: + return StreamUtils.readShort(in); + case IonMarks.INT: + return StreamUtils.readInt(in); + case IonMarks.LONG: + return StreamUtils.readLong(in); + case IonMarks.FLOAT: + return StreamUtils.readFloat(in); + case IonMarks.DOUBLE: + return StreamUtils.readDouble(in); + case IonMarks.STRING: + String s = StreamUtils.readString(in); + return s; + default: + throw new IonException("Invalid Ion mark " + Integer.toHexString(bi)); + } + } catch (IOException e) { + throw new IonException("Error loading ION file: ", e); + } + } + + + /** + * Write single ionizable or primitive object to output stream + * + * @param out output stream + * @param obj stored object + * @throws IonException + */ + public static void writeObject(OutputStream out, Object obj) throws IonException + { + try { + if (obj instanceof Ionizable) { + out.write(((Ionizable) obj).ionMark()); + ((Ionizable) obj).ionWrite(out); + return; + } + + if (obj instanceof Boolean) { + out.write(IonMarks.BOOLEAN); + StreamUtils.writeBoolean(out, (Boolean) obj); + return; + } + + if (obj instanceof Byte) { + out.write(IonMarks.BYTE); + StreamUtils.writeByte(out, (Byte) obj); + return; + } + + if (obj instanceof Character) { + out.write(IonMarks.CHAR); + StreamUtils.writeChar(out, (Character) obj); + return; + } + + if (obj instanceof Short) { + out.write(IonMarks.SHORT); + StreamUtils.writeShort(out, (Short) obj); + return; + } + + if (obj instanceof Integer) { + out.write(IonMarks.INT); + StreamUtils.writeInt(out, (Integer) obj); + return; + } + + if (obj instanceof Long) { + out.write(IonMarks.LONG); + StreamUtils.writeLong(out, (Long) obj); + return; + } + + if (obj instanceof Float) { + out.write(IonMarks.FLOAT); + StreamUtils.writeFloat(out, (Float) obj); + return; + } + + if (obj instanceof Double) { + out.write(IonMarks.DOUBLE); + StreamUtils.writeDouble(out, (Double) obj); + return; + } + + if (obj instanceof String) { + out.write(IonMarks.STRING); + StreamUtils.writeString(out, (String) obj); + return; + } + + throw new IonException(Calc.cname(obj) + " can't be stored in Ion storage."); + + } catch (IOException e) { + throw new IonException("Could not store " + obj, e); + } + } + +} diff --git a/src/mightypork/utils/files/ion/IonException.java b/src/mightypork/utils/files/ion/IonException.java new file mode 100644 index 0000000..b3bdfd8 --- /dev/null +++ b/src/mightypork/utils/files/ion/IonException.java @@ -0,0 +1,25 @@ +package mightypork.utils.files.ion; + + +public class IonException extends Exception { + + public IonException() { + super(); + } + + + public IonException(String message, Throwable cause) { + super(message, cause); + } + + + public IonException(String message) { + super(message); + } + + + public IonException(Throwable cause) { + super(cause); + } + +} diff --git a/src/mightypork/utils/files/ion/IonList.java b/src/mightypork/utils/files/ion/IonList.java new file mode 100644 index 0000000..4b627e2 --- /dev/null +++ b/src/mightypork/utils/files/ion/IonList.java @@ -0,0 +1,157 @@ +package mightypork.utils.files.ion; + + +/** + * Ionizable Arraylist + * + * @author MightyPork + */ +public class IonList extends AbstractIonList implements Ionizable { + + public Ionizable getIonizable(int index) throws IonException + { + return (Ionizable) getCheckType(index, Ionizable.class); + } + + + public boolean getBoolean(int index) throws IonException + { + return (Boolean) getCheckType(index, Boolean.class); + } + + + public byte getByte(int index) throws IonException + { + return (Byte) getCheckType(index, Byte.class); + } + + + public char getChar(int index) throws IonException + { + return (Character) getCheckType(index, Character.class); + } + + + public short getShort(int index) throws IonException + { + return (Short) getCheckType(index, Short.class); + } + + + public int getInt(int index) throws IonException + { + return (Integer) getCheckType(index, Integer.class); + } + + + public long getLong(int index) throws IonException + { + return (Long) getCheckType(index, Long.class); + } + + + public float getFloat(int index) throws IonException + { + return (Float) getCheckType(index, Float.class); + } + + + public double getDouble(int index) throws IonException + { + return (Double) getCheckType(index, Double.class); + } + + + public String getString(int index) throws IonException + { + return (String) getCheckType(index, String.class); + } + + + public void addIonizable(Ionizable o) throws IonException + { + addCheckNull(o); + } + + + public void addBoolean(boolean o) throws IonException + { + addCheckNull(o); + } + + + public void addByte(byte o) throws IonException + { + addCheckNull(o); + } + + + public void addChar(char o) throws IonException + { + addCheckNull(o); + } + + + public void addShort(short o) throws IonException + { + addCheckNull(o); + } + + + public void addInt(int o) throws IonException + { + addCheckNull(o); + } + + + public void addLong(long o) throws IonException + { + addCheckNull(o); + } + + + public void addFloat(float o) throws IonException + { + addCheckNull(o); + } + + + public void addDouble(double o) throws IonException + { + addCheckNull(o); + } + + + public void addString(String o) throws IonException + { + addCheckNull(o); + } + + + public Object getCheckType(int index, Class type) throws IonException + { + try { + Object o = super.get(index); + if (o == null || !o.getClass().isAssignableFrom(type)) { + throw new IonException("Incorrect object type"); + } + return o; + } catch (IndexOutOfBoundsException e) { + throw new IonException("Out of bounds"); + } + } + + + private void addCheckNull(Object o) throws IonException + { + if (o == null) throw new IonException("Cannot store null"); + } + + + @Override + public byte ionMark() + { + return IonMarks.LIST; + } + +} diff --git a/src/mightypork/utils/files/ion/IonMap.java b/src/mightypork/utils/files/ion/IonMap.java new file mode 100644 index 0000000..7b02f2a --- /dev/null +++ b/src/mightypork/utils/files/ion/IonMap.java @@ -0,0 +1,156 @@ +package mightypork.utils.files.ion; + + +/** + * Ionizable HashMap + * + * @author MightyPork + */ +public class IonMap extends AbstractIonMap implements Ionizable { + + public boolean getBoolean(String key) + { + return (Boolean) get(key); + } + + + public boolean getBool(String key) + { + return (Boolean) get(key); + } + + + public byte getByte(String key) + { + return (Byte) get(key); + } + + + public char getChar(String key) + { + return (Character) get(key); + } + + + public short getShort(String key) + { + return (Short) get(key); + } + + + public int getInt(String key) + { + return (Integer) get(key); + } + + + public long getLong(String key) + { + return (Long) get(key); + } + + + public float getFloat(String key) + { + return (Float) get(key); + } + + + public double getDouble(String key) + { + return (Double) get(key); + } + + + public String getString(String key) + { + return (String) get(key); + } + + + @Override + public Object get(Object arg0) + { + return super.get(arg0); + } + + + public void putBoolean(String key, boolean num) + { + put(key, num); + } + + + public void putBool(String key, boolean num) + { + put(key, num); + } + + + public void putByte(String key, int num) + { + put(key, (byte) num); + } + + + public void putChar(String key, char num) + { + put(key, num); + } + + + public void putCharacter(String key, char num) + { + put(key, num); + } + + + public void putShort(String key, int num) + { + put(key, num); + } + + + public void putInt(String key, int num) + { + put(key, num); + } + + + public void putInteger(String key, int num) + { + put(key, num); + } + + + public void putLong(String key, long num) + { + put(key, num); + } + + + public void putFloat(String key, double num) + { + put(key, (float) num); + } + + + public void putDouble(String key, double num) + { + put(key, num); + } + + + public void putString(String key, String num) + { + put(key, num); + } + + + @Override + public byte ionMark() + { + return IonMarks.MAP; + } + +} diff --git a/src/mightypork/utils/files/ion/IonMarks.java b/src/mightypork/utils/files/ion/IonMarks.java new file mode 100644 index 0000000..a518d2b --- /dev/null +++ b/src/mightypork/utils/files/ion/IonMarks.java @@ -0,0 +1,57 @@ +package mightypork.utils.files.ion; + + +/** + * Byte marks used to structure data in Ion files. + * + * @author MightyPork + */ +public class IonMarks { + + /** Null value */ + public static final byte NULL = 0; + + /** Boolean value */ + public static final byte BOOLEAN = 1; + + /** Byte value */ + public static final byte BYTE = 2; + + /** Character value */ + public static final byte CHAR = 3; + + /** Short value */ + public static final byte SHORT = 4; + + /** Integer value */ + public static final byte INT = 5; + + /** Long value */ + public static final byte LONG = 6; + + /** Float value */ + public static final byte FLOAT = 7; + + /** Double value */ + public static final byte DOUBLE = 8; + + /** String value */ + public static final byte STRING = 9; + + /** List value (begin) - contains entries, ends with END */ + public static final byte LIST = 10; + + /** Map value (begin) - contains entries, ends with END */ + public static final byte MAP = 11; + + /** + * List / Map entry
+ * In list directly followed by entry value. In map followed by (string) key + * and the entry value. + */ + public static final byte ENTRY = 12; + + /** End of List / Map */ + public static final byte END = 13; + +} diff --git a/src/mightypork/utils/files/ion/Ionizable.java b/src/mightypork/utils/files/ion/Ionizable.java new file mode 100644 index 0000000..37941d2 --- /dev/null +++ b/src/mightypork/utils/files/ion/Ionizable.java @@ -0,0 +1,43 @@ +package mightypork.utils.files.ion; + + +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * Object that can be saved to and loaded from Ion file.
+ * All classes implementing Ionizable must be registered to {@link Ion} using + * Ion.registerIonizable(obj.class). + * + * @author MightyPork + */ +public interface Ionizable { + + /** + * Load data from the input stream. Mark has already been read, begin + * reading right after it. + * + * @param in input stream + * @throws IonException + */ + public void ionRead(InputStream in) throws IonException; + + + /** + * Store data to output stream. mark has already been written, begin right + * after it. + * + * @param out Output stream + * @throws IonException + */ + public void ionWrite(OutputStream out) throws IonException; + + + /** + * Get Ion mark byte. + * + * @return Ion mark byte. + */ + public byte ionMark(); +} diff --git a/src/mightypork/utils/files/ion/IonizableOptional.java b/src/mightypork/utils/files/ion/IonizableOptional.java new file mode 100644 index 0000000..35f9092 --- /dev/null +++ b/src/mightypork/utils/files/ion/IonizableOptional.java @@ -0,0 +1,17 @@ +package mightypork.utils.files.ion; + + +/** + * Optional ionizable + * + * @author MightyPork + */ +public interface IonizableOptional extends Ionizable { + + /** + * Get if this ionizable should be saved to a list + * + * @return should save + */ + public boolean ionShouldSave(); +} diff --git a/src/mightypork/utils/files/ion/StreamUtils.java b/src/mightypork/utils/files/ion/StreamUtils.java new file mode 100644 index 0000000..e5e68fc --- /dev/null +++ b/src/mightypork/utils/files/ion/StreamUtils.java @@ -0,0 +1,265 @@ +package mightypork.utils.files.ion; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + + +/** + * Utilities to store and load objects to streams. + * + * @author MightyPork + */ +public class StreamUtils { + + private static ByteBuffer bi = ByteBuffer.allocate(Integer.SIZE / 8); + private static ByteBuffer bd = ByteBuffer.allocate(Double.SIZE / 8); + private static ByteBuffer bf = ByteBuffer.allocate(Float.SIZE / 8); + private static ByteBuffer bc = ByteBuffer.allocate(Character.SIZE / 8); + private static ByteBuffer bl = ByteBuffer.allocate(Long.SIZE / 8); + private static ByteBuffer bs = ByteBuffer.allocate(Short.SIZE / 8); + + private static byte[] ai = new byte[Integer.SIZE / 8]; + private static byte[] ad = new byte[Double.SIZE / 8]; + private static byte[] af = new byte[Float.SIZE / 8]; + private static byte[] ac = new byte[Character.SIZE / 8]; + private static byte[] al = new byte[Long.SIZE / 8]; + private static byte[] as = new byte[Short.SIZE / 8]; + + + // CONVERSIONS + + private static byte[] convBool(boolean bool) + { + return new byte[] { (byte) (bool ? 1 : 0) }; + } + + + private static byte[] convByte(byte num) + { + return new byte[] { num }; + } + + + private static byte[] convChar(char num) + { + bc.clear(); + bc.putChar(num); + return bc.array(); + } + + + private static byte[] convShort(short num) + { + bs.clear(); + bs.putShort(num); + return bs.array(); + } + + + private static byte[] convInt(int num) + { + bi.clear(); + bi.putInt(num); + return bi.array(); + } + + + private static byte[] convLong(long num) + { + bl.clear(); + bl.putLong(num); + return bl.array(); + } + + + private static byte[] convFloat(float num) + { + bf.clear(); + bf.putFloat(num); + return bf.array(); + } + + + private static byte[] convDouble(double num) + { + bd.clear(); + bd.putDouble(num); + return bd.array(); + } + + + private static byte[] convString(String str) + { + char[] chars = str.toCharArray(); + + ByteBuffer bstr = ByteBuffer.allocate((Character.SIZE / 8) * chars.length + (Character.SIZE / 8)); + for (char c : chars) { + bstr.putChar(c); + } + + bstr.putChar((char) 0); + + return bstr.array(); + } + + + private static byte[] convString_b(String str) + { + char[] chars = str.toCharArray(); + ByteBuffer bstr = ByteBuffer.allocate((Byte.SIZE / 8) * chars.length + 1); + for (char c : chars) { + bstr.put((byte) c); + } + bstr.put((byte) 0); + + return bstr.array(); + } + + + public static void writeBoolean(OutputStream out, boolean num) throws IOException + { + out.write(convBool(num)); + } + + + public static void writeByte(OutputStream out, byte num) throws IOException + { + out.write(convByte(num)); + } + + + public static void writeChar(OutputStream out, char num) throws IOException + { + out.write(convChar(num)); + } + + + public static void writeShort(OutputStream out, short num) throws IOException + { + out.write(convShort(num)); + } + + + public static void writeInt(OutputStream out, int num) throws IOException + { + out.write(convInt(num)); + } + + + public static void writeLong(OutputStream out, long num) throws IOException + { + out.write(convLong(num)); + } + + + public static void writeFloat(OutputStream out, float num) throws IOException + { + out.write(convFloat(num)); + } + + + public static void writeDouble(OutputStream out, double num) throws IOException + { + out.write(convDouble(num)); + } + + + public static void writeString(OutputStream out, String str) throws IOException + { + out.write(convString(str)); + } + + + public static void writeStringBytes(OutputStream out, String str) throws IOException + { + out.write(convString_b(str)); + } + + + // READING + + public static boolean readBoolean(InputStream in) throws IOException + { + return in.read() > 0; + } + + + public static byte readByte(InputStream in) throws IOException + { + return (byte) in.read(); + } + + + public static char readChar(InputStream in) throws IOException + { + in.read(ac, 0, ac.length); + ByteBuffer buf = ByteBuffer.wrap(ac); + return buf.getChar(); + } + + + public static short readShort(InputStream in) throws IOException + { + in.read(as, 0, as.length); + ByteBuffer buf = ByteBuffer.wrap(as); + return buf.getShort(); + } + + + public static long readLong(InputStream in) throws IOException + { + in.read(al, 0, al.length); + ByteBuffer buf = ByteBuffer.wrap(al); + return buf.getLong(); + } + + + public static int readInt(InputStream in) throws IOException + { + in.read(ai, 0, ai.length); + ByteBuffer buf = ByteBuffer.wrap(ai); + return buf.getInt(); + } + + + public static float readFloat(InputStream in) throws IOException + { + in.read(af, 0, af.length); + ByteBuffer buf = ByteBuffer.wrap(af); + return buf.getFloat(); + } + + + public static double readDouble(InputStream in) throws IOException + { + in.read(ad, 0, ad.length); + ByteBuffer buf = ByteBuffer.wrap(ad); + return buf.getDouble(); + } + + + public static String readString(InputStream in) throws IOException + { + String s = ""; + char c; + while ((c = readChar(in)) > 0) { + s += c; + } + return s; + } + + + public static String readStringBytes(InputStream in) throws IOException + { + String s = ""; + byte b; + while ((b = readByte(in)) > 0) { + s += (char) b; + } + return s; + } + +} diff --git a/src/mightypork/utils/logging/Log.java b/src/mightypork/utils/logging/Log.java new file mode 100644 index 0000000..6b9657c --- /dev/null +++ b/src/mightypork/utils/logging/Log.java @@ -0,0 +1,159 @@ +package mightypork.utils.logging; + + +import java.io.File; +import java.util.HashMap; + + +public class Log { + + /** enable static logging */ + private static boolean esl; + + + /** + * Log FINE message in main logger + * + * @param msg message + */ + public static void f1(String msg) + { + if (esl && main != null) main.f1(msg); + } + + + /** + * Log FINER message in main logger + * + * @param msg message + */ + public static void f2(String msg) + { + if (esl && main != null) main.f2(msg); + } + + + /** + * Log FINEST message in main logger + * + * @param msg message + */ + public static void f3(String msg) + { + if (esl && main != null) main.f3(msg); + } + + + /** + * Log INFO message in main logger + * + * @param msg message + */ + public static void i(String msg) + { + if (esl && main != null) main.i(msg); + } + + + /** + * Log WARNING message in main logger + * + * @param msg message + */ + public static void w(String msg) + { + if (esl && main != null) main.w(msg); + } + + + /** + * Log ERROR message in main logger + * + * @param msg message + */ + public static void e(String msg) + { + if (esl && main != null) main.e(msg); + } + + + /** + * Log EXCEPTION and ERROR message in main logger + * + * @param msg message + * @param thrown thrown exception + */ + public static void e(String msg, Throwable thrown) + { + if (esl && main != null) main.e(msg, thrown); + } + + + /** + * Log EXCEPTION in main logger + * + * @param thrown thrown exception + */ + public static void e(Throwable thrown) + { + if (esl && main != null) main.e(thrown); + } + + + public static void enable(boolean flag) + { + if (esl && main != null) main.enable(flag); + } + + + /** + * Enable / disable static log delegate methods + * + * @param flag enable + */ + public static void enableStaticLogging(boolean flag) + { + esl = flag; + } + + private static HashMap logs = new HashMap(); + private static LogInstance main = null; + + + /** + * Create a logger. If this is the first logger made, then it'll be made + * available via the static methods. + * + * @param logName log name (used for filename, must be application-unique) + * @param logsDir directory to store logs in + * @param oldLogsCount number of old logs to keep, -1 for infinite, 0 for + * none. + * @return the created Log instance + */ + public static synchronized LogInstance create(String logName, File logsDir, int oldLogsCount) + { + if (logs.containsKey(logName)) return logs.get(logName); + LogInstance log = new LogInstance(logName, logsDir, oldLogsCount); + if (main == null) main = log; + logs.put(logName, log); + + return log; + } + + + public int addMonitor(LogMonitor mon) + { + if (main == null) throw new IllegalStateException("Main logger not initialized."); + + return main.addMonitor(mon); + } + + + public void removeMonitor(int id) + { + if (main == null) throw new IllegalStateException("Main logger not initialized."); + + main.removeMonitor(id); + } + +} diff --git a/src/mightypork/utils/logging/LogInstance.java b/src/mightypork/utils/logging/LogInstance.java new file mode 100644 index 0000000..a0447c3 --- /dev/null +++ b/src/mightypork/utils/logging/LogInstance.java @@ -0,0 +1,385 @@ +package mightypork.utils.logging; + + +import java.io.File; +import java.io.FileFilter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import mightypork.utils.files.FileUtils; + + +/** + * Static logger class. + * + * @author MightyPork + * @copy (c) 2014 + */ +public class LogInstance { + + /** log file */ + private File file; + + /** Log name */ + private String name; + + /** Number of old logs to keep */ + private int logs_to_keep; + + /** Logs dir */ + private File dir; + + /** Logger instance. */ + private Logger logger; + + /** Logging enabled */ + private boolean enabled = true; + + private boolean sysout = true; + + private int monitorId = 0; + private HashMap monitors = new HashMap(); + + private LogToSysoutMonitor sysoutMonitor; + + + public LogInstance(String name, File dir, int oldLogCount) { + this.name = name; + this.file = new File(dir, name + getSuffix()); + this.dir = dir; + this.logs_to_keep = oldLogCount; + + init(); + } + + + /** + * Prepare logs for logging + */ + private void init() + { + logger = Logger.getLogger(name); + + cleanup(); + + FileHandler handler = null; + + try { + handler = new FileHandler(file.getPath()); + } catch (Exception e) { + throw new RuntimeException("Failed to init log", e); + } + + handler.setFormatter(new LogFormatter()); + logger.addHandler(handler); + + enabled = true; + + sysoutMonitor = new LogToSysoutMonitor(); + + addMonitor(sysoutMonitor); + + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + logger.info("Main logger initialized."); + logger.info((new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")).format(new Date())); + } + + + private void cleanup() + { + for (File f : FileUtils.listDirectory(file.getParentFile())) { + if (!f.isFile()) continue; + if (f.equals(file)) { + Date d = new Date(f.lastModified()); + + String fname = name + '_' + (new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss")).format(d) + getSuffix(); + + File f2 = new File(dir, fname); + + f.renameTo(f2); + } + } + + List oldLogs = FileUtils.listDirectory(dir, new FileFilter() { + + @Override + public boolean accept(File f) + { + if (f.isDirectory()) return false; + if (!f.getName().endsWith(getSuffix())) return false; + if (!f.getName().startsWith(name)) return false; + + return true; + } + + }); + + Collections.sort(oldLogs, new Comparator() { + + @Override + public int compare(File o1, File o2) + { + return o1.getName().compareTo(o2.getName()); + } + }); + + for (int i = 0; i < oldLogs.size() - logs_to_keep; i++) { + oldLogs.get(i).delete(); + } + } + + + /** + * Add log monitor + * + * @param mon monitor + * @return assigned ID + */ + public synchronized int addMonitor(LogMonitor mon) + { + int id = monitorId; + monitorId++; + monitors.put(id, mon); + return id; + } + + + /** + * Remove a monitor by ID + * + * @param id monitor ID + */ + public synchronized void removeMonitor(int id) + { + monitors.remove(id); + } + + + /** + * Enable logging. + * + * @param flag do enable logging + */ + public void enable(boolean flag) + { + enabled = flag; + } + + + /** + * Enable printing logs to sysout + * + * @param flag do enable logging + */ + public void enableSysout(boolean flag) + { + sysout = flag; + sysoutMonitor.enable(sysout); + } + + + /** + * Log FINE message + * + * @param msg message + */ + public void f1(String msg) + { + if (enabled) logger.log(Level.FINE, msg); + } + + + /** + * Log FINER message + * + * @param msg message + */ + public void f2(String msg) + { + if (enabled) logger.log(Level.FINER, msg); + } + + + /** + * Log FINEST message + * + * @param msg message + */ + public void f3(String msg) + { + if (enabled) logger.log(Level.FINEST, msg); + } + + + /** + * Log INFO message + * + * @param msg message + */ + public void i(String msg) + { + if (enabled) logger.log(Level.INFO, msg); + } + + + /** + * Log WARNING message (less severe than ERROR) + * + * @param msg message + */ + public void w(String msg) + { + if (enabled) logger.log(Level.WARNING, msg); + } + + + /** + * Log ERROR message + * + * @param msg message + */ + public void e(String msg) + { + if (enabled) logger.log(Level.SEVERE, msg); + } + + + /** + * Log THROWING message + * + * @param msg message + * @param thrown thrown exception + */ + public void e(String msg, Throwable thrown) + { + if (enabled) logger.log(Level.SEVERE, msg + "\n" + getStackTrace(thrown)); + } + + + /** + * Log exception thrown + * + * @param thrown thrown exception + */ + public void e(Throwable thrown) + { + if (enabled) logger.log(Level.SEVERE, getStackTrace(thrown)); + } + + + /** + * Get stack trace from throwable + * + * @param t + * @return trace + */ + private String getStackTrace(Throwable t) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw, true); + t.printStackTrace(pw); + pw.flush(); + sw.flush(); + return sw.toString(); + } + + /** + * PowerCraft Log file formatter. + * + * @author MightyPork + * @copy (c) 2012 + */ + private class LogFormatter extends Formatter { + + /** Newline string constant */ + private final String nl = System.getProperty("line.separator"); + + + @Override + public String format(LogRecord record) + { + StringBuffer buf = new StringBuffer(180); + + if (record.getMessage().equals("\n")) { + return nl; + } + + if (record.getMessage().charAt(0) == '\n') { + buf.append(nl); + record.setMessage(record.getMessage().substring(1)); + } + + Level level = record.getLevel(); + String trail = "[ ? ]"; + if (level == Level.FINE) { + trail = "[ # ] "; + } + if (level == Level.FINER) { + trail = "[ - ] "; + } + if (level == Level.FINEST) { + trail = "[ ] "; + } + if (level == Level.INFO) { + trail = "[ i ] "; + } + if (level == Level.SEVERE) { + trail = "[!E!] "; + } + if (level == Level.WARNING) { + trail = "[!W!] "; + } + + record.setMessage(record.getMessage().replaceAll("\n", nl + trail)); + + buf.append(trail); + buf.append(formatMessage(record)); + + buf.append(nl); + + Throwable throwable = record.getThrown(); + if (throwable != null) { + buf.append("at "); + buf.append(record.getSourceClassName()); + buf.append('.'); + buf.append(record.getSourceMethodName()); + buf.append(nl); + + StringWriter sink = new StringWriter(); + throwable.printStackTrace(new PrintWriter(sink, true)); + buf.append(sink.toString()); + + buf.append(nl); + } + + String str = buf.toString(); + + for (LogMonitor mon : monitors.values()) { + mon.log(level, str); + } + + return str; + } + } + + + /** + * @return log filename suffix (incl. dot) + */ + protected String getSuffix() + { + return ".log"; + } +} diff --git a/src/mightypork/utils/logging/LogMonitor.java b/src/mightypork/utils/logging/LogMonitor.java new file mode 100644 index 0000000..b52da11 --- /dev/null +++ b/src/mightypork/utils/logging/LogMonitor.java @@ -0,0 +1,13 @@ +package mightypork.utils.logging; + + +import java.util.logging.Level; + + +public interface LogMonitor { + + public void log(Level level, String message); + + + public void enable(boolean enable); +} diff --git a/src/mightypork/utils/logging/LogToSysoutMonitor.java b/src/mightypork/utils/logging/LogToSysoutMonitor.java new file mode 100644 index 0000000..3f2e2c7 --- /dev/null +++ b/src/mightypork/utils/logging/LogToSysoutMonitor.java @@ -0,0 +1,31 @@ +package mightypork.utils.logging; + + +import java.util.logging.Level; + + +public class LogToSysoutMonitor implements LogMonitor { + + private boolean enabled = true; + + + @Override + public void log(Level level, String message) + { + if (!enabled) return; + + if (level == Level.FINE || level == Level.FINER || level == Level.FINEST || level == Level.INFO) { + System.out.print(message); + } else if (level == Level.SEVERE || level == Level.WARNING) { + System.err.print(message); + } + } + + + @Override + public void enable(boolean enable) + { + this.enabled = enable; + } + +} diff --git a/src/mightypork/utils/math/Calc.java b/src/mightypork/utils/math/Calc.java new file mode 100644 index 0000000..e3fbd66 --- /dev/null +++ b/src/mightypork/utils/math/Calc.java @@ -0,0 +1,931 @@ +package mightypork.utils.math; + + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import mightypork.utils.math.coord.Coord; +import mightypork.utils.math.coord.Vec; + +import org.lwjgl.BufferUtils; + + +/** + * Math helper + * + * @author MightyPork + */ +public class Calc { + + /** Square root of two */ + public static final double SQ2 = 1.41421356237; + + + /** + * Get distance from 2D line to 2D point [X,Y] + * + * @param lineDirVec line directional vector + * @param linePoint point of line + * @param point point coordinate + * @return distance + */ + public static double linePointDist(Vec lineDirVec, Coord linePoint, Coord point) + { + // line point L[lx,ly] + double lx = linePoint.x; + double ly = linePoint.y; + + // line equation ax+by+c=0 + double a = -lineDirVec.y; + double b = lineDirVec.x; + double c = -a * lx - b * ly; + + // checked point P[x,y] + double x = point.x; + double y = point.y; + + // distance + return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); + } + + + /** + * Get distance from 2D line to 2D point [X,Z] + * + * @param lineDirVec line directional vector + * @param linePoint point of line + * @param point point coordinate + * @return distance + */ + public static double linePointDistXZ(Vec 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)); + } + + + /** + * Get longest side of a right-angled triangle + * + * @param a side a (opposite) + * @param b side b (adjacent) + * @return longest side (hypotenuse) + */ + public static double pythC(double a, double b) + { + return Math.sqrt(square(a) + square(b)); + } + + + /** + * Get adjacent side of a right-angled triangle + * + * @param a side a (opposite) + * @param c side c (hypotenuse) + * @return side b (adjacent) + */ + public static double pythB(double a, double c) + { + return Math.sqrt(square(c) - square(a)); + } + + + /** + * Get opposite side of a right-angled triangle + * + * @param b side b (adjacent) + * @param c side c (hypotenuse) + * @return side a (opposite) + */ + public static double pythA(double b, double c) + { + return Math.sqrt(square(c) - square(b)); + } + + private static class Angles { + + public static double delta(double alpha, double beta, double a360) + { + while (Math.abs(alpha - beta) > a360 / 2D) { + alpha = norm(alpha + a360 / 2D, a360); + beta = norm(beta + a360 / 2D, a360); + } + + return beta - alpha; + } + + + public static double norm(double angle, double a360) + { + while (angle < 0) + angle += a360; + while (angle > a360) + angle -= a360; + return angle; + } + } + + /** + * Calc subclass with buffer utils. + * + * @author MightyPork + */ + public static class Buffers { + + /** + * Create java.nio.FloatBuffer of given floats, and flip it. + * + * @param obj floats or float array + * @return float buffer + */ + public static FloatBuffer mkFillBuff(float... obj) + { + return (FloatBuffer) BufferUtils.createFloatBuffer(obj.length).put(obj).flip(); + } + + + /** + * Fill java.nio.FloatBuffer with floats or float array + * + * @param buff + * @param obj + */ + public static void fill(FloatBuffer buff, float... obj) + { + buff.put(obj); + buff.flip(); + } + + + /** + * Create new java.nio.FloatBuffer of given length + * + * @param count elements + * @return the new java.nio.FloatBuffer + */ + public static FloatBuffer alloc(int count) + { + return BufferUtils.createFloatBuffer(count); + } + + } + + /** + * Angle calculations for degrees. + * + * @author MightyPork + */ + public static class Deg { + + /** 180° in degrees */ + public static final double a180 = 180; + /** 270° in degrees */ + public static final double a270 = 270; + /** 360° in degrees */ + public static final double a360 = 360; + /** 45° in degrees */ + public static final double a45 = 45; + /** 90° in degrees */ + public static final double a90 = 90; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in degrees + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Math.abs(Angles.delta(alpha, beta, a360)); + } + + + /** + * Cosinus in degrees + * + * @param deg angle in degrees + * @return cosinus + */ + public static double cos(double deg) + { + return Math.cos(toRad(deg)); + } + + + /** + * Sinus in degrees + * + * @param deg angle in degrees + * @return sinus + */ + public static double sin(double deg) + { + return Math.sin(toRad(deg)); + } + + + /** + * Tangents in degrees + * + * @param deg angle in degrees + * @return tangents + */ + public static double tan(double deg) + { + return Math.tan(toRad(deg)); + } + + + /** + * Angle normalized to 0-360 range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to radians + * + * @param deg degrees + * @return radians + */ + public static double toRad(double deg) + { + return Math.toRadians(deg); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @param x rounding increment (45 - round to 0,45,90...) + * @return rounded + */ + public static int roundX(double deg, double x) + { + double half = x / 2d; + deg += half; + deg = norm(deg); + int times = (int) Math.floor(deg / x); + double a = times * x; + if (a == 360) a = 0; + return (int) Math.round(a); + } + + + /** + * Round angle to 0,45,90,135... + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int round45(double deg) + { + return roundX(deg, 45); + } + + + /** + * Round angle to 0,90,180,270 + * + * @param deg angle in deg. to round + * @return rounded + */ + public static int round90(double deg) + { + return roundX(deg, 90); + } + + + /** + * Round angle to 0,15,30,45,60,75,90... + * + * @param deg angle in deg to round + * @return rounded + */ + public static int round15(double deg) + { + return roundX(deg, 15); + } + } + + /** + * Angle calculations for radians. + * + * @author MightyPork + */ + public static class Rad { + + /** 180° in radians */ + public static final double a180 = Math.PI; + /** 270° in radians */ + public static final double a270 = Math.PI * 1.5D; + /** 360° in radians */ + public static final double a360 = Math.PI * 2D; + /** 45° in radians */ + public static final double a45 = Math.PI / 4D; + /** 90° in radians */ + public static final double a90 = Math.PI / 2D; + + + /** + * Subtract two angles alpha - beta + * + * @param alpha first angle + * @param beta second angle + * @return (alpha - beta) in radians + */ + public static double delta(double alpha, double beta) + { + return Angles.delta(alpha, beta, a360); + } + + + /** + * Difference of two angles (absolute value of delta) + * + * @param alpha first angle + * @param beta second angle + * @return difference in radians + */ + public static double diff(double alpha, double beta) + { + return Math.abs(Angles.delta(alpha, beta, a360)); + } + + + /** + * Cos + * + * @param rad angle in rads + * @return cos + */ + public static double cos(double rad) + { + return Math.cos(rad); + } + + + /** + * Sin + * + * @param rad angle in rads + * @return sin + */ + public static double sin(double rad) + { + return Math.sin(rad); + } + + + /** + * Tan + * + * @param rad angle in rads + * @return tan + */ + public static double tan(double rad) + { + return Math.tan(rad); + } + + + /** + * Angle normalized to 0-2*PI range + * + * @param angle angle to normalize + * @return normalized angle + */ + public static double norm(double angle) + { + return Angles.norm(angle, a360); + } + + + /** + * Convert to degrees + * + * @param rad radians + * @return degrees + */ + public static double toDeg(double rad) + { + return Math.toDegrees(rad); + } + } + + private static Random rand = new Random(); + + + /** + * Get volume of a sphere + * + * @param radius sphere radius + * @return volume in cubic units + */ + public static double sphereGetVolume(double radius) + { + return (4D / 3D) * Math.PI * cube(radius); + } + + + /** + * Get radius of a sphere + * + * @param volume sphere volume + * @return radius in units + */ + public static double sphereGetRadius(double volume) + { + return Math.cbrt((3D * volume) / (4 * Math.PI)); + } + + + /** + * Get surface of a circle + * + * @param radius circle radius + * @return volume in square units + */ + public static double circleGetSurface(double radius) + { + return Math.PI * square(radius); + } + + + /** + * Get radius of a circle + * + * @param surface circle volume + * @return radius in units + */ + public static double circleGetRadius(double surface) + { + return Math.sqrt(surface / Math.PI); + } + + + /** + * Check if objects are equal (for equals function) + * + * @param a + * @param b + * @return are equal + */ + public static boolean areObjectsEqual(Object a, Object b) + { + return a == null ? b == null : a.equals(b); + } + + + /** + * Private clamping helper. + * + * @param number number to be clamped + * @param min min value + * @param max max value + * @return clamped double + */ + private static double clamp_double(Number number, Number min, Number max) + { + double n = number.doubleValue(); + double mind = min.doubleValue(); + double maxd = max.doubleValue(); + if (n > maxd) n = maxd; + if (n < mind) n = mind; + if (Double.isNaN(n)) return mind; + return n; + } + + + /** + * Private clamping helper. + * + * @param number number to be clamped + * @param min min value + * @return clamped double + */ + private static double clamp_double(Number number, Number min) + { + double n = number.doubleValue(); + double mind = min.doubleValue(); + if (n < mind) n = mind; + return n; + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static double clampd(Number number, Number min, Number max) + { + return clamp_double(number, min, max); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static float clampf(Number number, Number min, Number max) + { + return (float) clamp_double(number, min, max); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param min minimal allowed value + * @param max maximal allowed value + * @return result + */ + public static int clampi(Number number, Number min, Number max) + { + return (int) Math.round(clamp_double(number, min, max)); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param range range + * @return result + */ + public static int clampi(Number number, Range range) + { + return (int) Math.round(clamp_double(number, range.getMin(), range.getMax())); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param range range + * @return result + */ + public static double clampd(Number number, Range range) + { + return clamp_double(number, range.getMin(), range.getMax()); + } + + + /** + * Clamp number to min and max bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param range range + * @return result + */ + public static float clampf(Number number, Range range) + { + return (float) clamp_double(number, range.getMin(), range.getMax()); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * DOUBLE version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static double clampd(Number number, Number min) + { + return clamp_double(number, min); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * FLOAT version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static float clampf(Number number, Number min) + { + return (float) clamp_double(number, min); + } + + + /** + * Clamp number to min and infinite bounds, inclusive.
+ * INTEGER version + * + * @param number clamped number + * @param min minimal allowed value + * @return result + */ + public static int clampi(Number number, Number min) + { + return (int) Math.round(clamp_double(number, min)); + } + + + /** + * Get class simple name + * + * @param obj object + * @return simple name + */ + public static String cname(Object obj) + { + if (obj == null) return "NULL"; + return obj.getClass().getSimpleName(); + } + + + /** + * Cube a double + * + * @param a squared double + * @return square + */ + public static double cube(double a) + { + return a * a * a; + } + + + /** + * Convert double to string, remove the mess at the end. + * + * @param d double + * @return string + */ + public static String doubleToString(double d) + { + String s = Double.toString(d); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Convert float to string, remove the mess at the end. + * + * @param f float + * @return string + */ + public static String floatToString(float f) + { + String s = Float.toString(f); + s = s.replaceAll("([0-9]+\\.[0-9]+)00+[0-9]+", "$1"); + s = s.replaceAll("0+$", ""); + s = s.replaceAll("\\.$", ""); + return s; + } + + + /** + * Check if number is in range + * + * @param number checked + * @param left lower end + * @param right upper end + * @return is in range + */ + public static boolean inRange(double number, double left, double right) + { + return number >= left && number <= right; + } + + + /** + * 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 + * @return current number to render + */ + public static double interpolate(double last, double now, double dtime) + { + return last + (now - last) * dtime; + } + + + /** + * 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 + * @return current angle to render + */ + public static double interpolateDeg(double last, double now, double delta) + { + return Deg.norm(last + Deg.delta(now, last) * delta); + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static double max(double... numbers) + { + double highest = numbers[0]; + for (double num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static float max(float... numbers) + { + float highest = numbers[0]; + for (float num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get highest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static int max(int... numbers) + { + int highest = numbers[0]; + for (int num : numbers) { + if (num > highest) highest = num; + } + return highest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static double min(double... numbers) + { + double lowest = numbers[0]; + for (double num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static float min(float... numbers) + { + float lowest = numbers[0]; + for (float num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Get lowest number of a list + * + * @param numbers numbers + * @return lowest + */ + public static int min(int... numbers) + { + int lowest = numbers[0]; + for (int num : numbers) { + if (num < lowest) lowest = num; + } + return lowest; + } + + + /** + * Split comma separated list of integers. + * + * @param list String containing the list. + * @return array of integers or null. + */ + public static List parseIntList(String list) + { + if (list == null) { + return null; + } + String[] parts = list.split(","); + + ArrayList intList = new ArrayList(); + + for (String part : parts) { + try { + intList.add(Integer.parseInt(part)); + } catch (NumberFormatException e) {} + } + + return intList; + + } + + + /** + * Pick random element from a given list. + * + * @param list list of choices + * @return picked element + */ + public static Object pick(List list) + { + if (list.size() == 0) return null; + return list.get(rand.nextInt(list.size())); + } + + + /** + * Square a double + * + * @param a squared double + * @return square + */ + public static double square(double a) + { + return a * a; + } + + + /** + * Signum. + * + * @param number + * @return sign, -1,0,1 + */ + public static int sgn(double number) + { + return number > 0 ? 1 : number < 0 ? -1 : 0; + } + + + public static double frag(double d) + { + return d - Math.floor(d); + } + +} diff --git a/src/mightypork/utils/math/Polar.java b/src/mightypork/utils/math/Polar.java new file mode 100644 index 0000000..d7739d1 --- /dev/null +++ b/src/mightypork/utils/math/Polar.java @@ -0,0 +1,107 @@ +package mightypork.utils.math; + + +import mightypork.utils.math.coord.Coord; + + +/** + * Polar coordinate + * + * @author MightyPork + */ +public class Polar { + + /** angle in radians */ + public double angle = 0; + /** distance in units */ + public double distance = 0; + + + /** + * @param angle angle in radians + * @param distance distance from origin + */ + public Polar(double angle, double distance) { + this.angle = angle; + this.distance = distance; + } + + + /** + * Make polar from coord + * + * @param coord coord + * @return polar + */ + public static Polar fromCoord(Coord coord) + { + return new Polar(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 Polar fromCoord(double x, double y) + { + return Polar.fromCoord(new Coord(x, y)); + } + + + /** + * 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 + * + * @return coord + */ + public Coord toCoord() + { + return new Coord(distance * Math.cos(angle), distance * Math.sin(angle)); + } + + + /** + * Get X,0,Y coord from polar + * + * @return coord + */ + public Coord toCoordXZ() + { + return new Coord(distance * Math.cos(angle), 0, distance * Math.sin(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 Polar fromCoordXZ(Coord coord) + { + return fromCoord(coord.x, coord.z); + } +} diff --git a/src/mightypork/utils/math/PolarDeg.java b/src/mightypork/utils/math/PolarDeg.java new file mode 100644 index 0000000..8a244c7 --- /dev/null +++ b/src/mightypork/utils/math/PolarDeg.java @@ -0,0 +1,111 @@ +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/Range.java b/src/mightypork/utils/math/Range.java new file mode 100644 index 0000000..ab241c6 --- /dev/null +++ b/src/mightypork/utils/math/Range.java @@ -0,0 +1,197 @@ +package mightypork.utils.math; + + +import java.util.Random; + + +/** + * Numeric range, able to generate random numbers and give min/max values. + * + * @author MightyPork + */ +public class Range { + + private double min = 0; + private double max = 1; + + private static Random rand = new Random(); + + + /** + * Implicit range constructor 0-1 + */ + public Range() {} + + + /** + * Create new range + * + * @param min min number + * @param max max number + */ + public Range(double min, double max) { + if (min > max) { + double t = min; + min = max; + max = t; + } + this.min = min; + this.max = max; + } + + + /** + * Create new range + * + * @param minmax min = max number + */ + public Range(double minmax) { + this.min = minmax; + this.max = minmax; + } + + + /** + * Get random integer from range + * + * @return random int + */ + public int randInt() + { + return (int) (Math.round(min) + rand.nextInt((int) (Math.round(max) - Math.round(min)) + 1)); + } + + + /** + * Get random double from this range + * + * @return random double + */ + public double randDouble() + { + return min + rand.nextDouble() * (max - min); + } + + + /** + * Get min + * + * @return min number + */ + public double getMin() + { + return min; + } + + + /** + * Get max + * + * @return max number + */ + public double getMax() + { + return max; + } + + + /** + * Get min + * + * @return min number + */ + public int getMinI() + { + return (int) min; + } + + + /** + * Get max + * + * @return max number + */ + public int getMaxI() + { + return (int) max; + } + + + /** + * Set min + * + * @param min min value + */ + public void setMin(double min) + { + this.min = min; + } + + + /** + * Set max + * + * @param max max value + */ + public void setMax(double max) + { + this.max = max; + } + + + @Override + public String toString() + { + return "Range(" + min + ";" + max + ")"; + } + + + /** + * Get identical copy + * + * @return copy + */ + public Range copy() + { + return new Range(min, max); + } + + + /** + * Set to value of other range + * + * @param other copied range + */ + public void setTo(Range other) + { + if (other == null) return; + min = other.min; + max = other.max; + + if (min > max) { + double t = min; + min = max; + max = t; + } + } + + + /** + * Set to min-max values + * + * @param min min value + * @param max max value + */ + public void setTo(double min, double max) + { + if (min > max) { + double t = min; + min = max; + max = t; + } + + this.min = min; + this.max = max; + } + +} diff --git a/src/mightypork/utils/math/color/HSV.java b/src/mightypork/utils/math/color/HSV.java new file mode 100644 index 0000000..feac7d0 --- /dev/null +++ b/src/mightypork/utils/math/color/HSV.java @@ -0,0 +1,177 @@ +package mightypork.utils.math.color; + + +import java.awt.Color; + +import mightypork.utils.math.Calc; + + +/** + * HSV color + * + * @author MightyPork + */ +public class HSV { + + /** H */ + public double h; + /** S */ + public double s; + /** V */ + public double v; + + + /** + * Create black color 0,0,0 + */ + public HSV() {} + + + /** + * Color from HSV 0-1 + * + * @param h + * @param s + * @param v + */ + public HSV(Number h, Number s, Number v) { + this.h = h.doubleValue(); + this.s = s.doubleValue(); + this.v = v.doubleValue(); + norm(); + } + + + /** + * @return hue 0-1 + */ + public double h() + { + return h; + } + + + /** + * @return saturation 0-1 + */ + public double s() + { + return s; + } + + + /** + * @return value/brightness 0-1 + */ + public double v() + { + return v; + } + + + /** + * Set color to other color + * + * @param copied copied color + * @return this + */ + public HSV setTo(HSV copied) + { + h = copied.h; + s = copied.s; + v = copied.v; + + norm(); + return this; + } + + + /** + * Set to H,S,V 0-1 + * + * @param h hue + * @param s saturation + * @param v value + * @return this + */ + public HSV setTo(Number h, Number s, Number v) + { + this.h = h.doubleValue(); + this.s = s.doubleValue(); + this.v = v.doubleValue(); + norm(); + return this; + } + + + /** + * Fix numbers out of range 0-1 + */ + public void norm() + { + h = Calc.clampd(h, 0, 1); + s = Calc.clampd(s, 0, 1); + v = Calc.clampd(v, 0, 1); + } + + + /** + * Convert to RGB + * + * @return RGB representation + */ + public RGB toRGB() + { + norm(); + + int rgb = Color.HSBtoRGB((float) h, (float) s, (float) v); + + return RGB.fromHex(rgb); + } + + + /** + * Make from RGB + * + * @param color RGB + * @return HSV + */ + public static HSV fromRGB(RGB color) + { + return color.toHSV(); + } + + + @Override + public String toString() + { + return "HSV[" + h + ";" + s + ";" + v + "]"; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof HSV)) return false; + return ((HSV) obj).h == h && ((HSV) obj).s == s && ((HSV) obj).v == v; + } + + + @Override + public int hashCode() + { + return Double.valueOf(h).hashCode() ^ Double.valueOf(s).hashCode() ^ Double.valueOf(v).hashCode(); + } + + + /** + * Get a copy + * + * @return copy + */ + public HSV copy() + { + return new HSV().setTo(this); + } +} diff --git a/src/mightypork/utils/math/color/RGB.java b/src/mightypork/utils/math/color/RGB.java new file mode 100644 index 0000000..fabceb0 --- /dev/null +++ b/src/mightypork/utils/math/color/RGB.java @@ -0,0 +1,382 @@ +package mightypork.utils.math.color; + + +import java.awt.Color; + +import mightypork.utils.math.Calc; + + +/** + * RGB color + * + * @author MightyPork + */ +public class RGB { + + /** White */ + public static final RGB WHITE = new RGB(1, 1, 1); + /** Black */ + public static final RGB BLACK = new RGB(0, 0, 0); + /** Red */ + public static final RGB RED = new RGB(1, 0, 0); + /** Lime green */ + public static final RGB GREEN = new RGB(0, 1, 0); + /** Blue */ + public static final RGB BLUE = new RGB(0, 0, 1); + /** Yellow */ + public static final RGB YELLOW = new RGB(1, 1, 0); + /** Purple */ + public static final RGB PURPLE = new RGB(1, 0, 1); + /** Cyan */ + public static final RGB CYAN = new RGB(0, 1, 1); + /** orange */ + public static final RGB ORANGE = new RGB(1, 0.6, 0); + /** no color (alpha=0) */ + public static final RGB TRANSPARENT = new RGB(0, 0, 0, 0); + + /** R */ + public double r; + /** G */ + public double g; + /** B */ + public double b; + /** ALPHA */ + public double a = 1; + + + /** + * Create black color 0,0,0 + */ + public RGB() {} + + + /** + * Get copy with custom alpha + * + * @param alpha alpha to set + * @return copy w/ alpha + */ + public RGB setAlpha(double alpha) + { + return copy().setAlpha_ip(alpha); + } + + + /** + * set alpha IP + * + * @param alpha alpha to set + * @return this + */ + public RGB setAlpha_ip(double alpha) + { + a = alpha; + norm(); + return this; + } + + + /** + * Get copy. + * + * @return copy + */ + public RGB copy() + { + return new RGB(r, g, b, a); + } + + + /** + * Get copy with alpha multiplied by custom value + * + * @param alpha alpha to set + * @return copy w/ alpha + */ + public RGB mulAlpha(double alpha) + { + return copy().mulAlpha_ip(alpha); + } + + + /** + * Multiply alpha by given number + * + * @param alpha alpha multiplier + * @return this + */ + public RGB mulAlpha_ip(double alpha) + { + a *= alpha; + norm(); + return this; + } + + + /** + * Color from RGB 0-1 + * + * @param r red + * @param g green + * @param b blue + */ + public RGB(Number r, Number g, Number b) { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + norm(); + } + + + /** + * Color from RGB 0-1 + * + * @param r red + * @param g green + * @param b blue + * @param a alpha + */ + public RGB(Number r, Number g, Number b, Number a) { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = a.doubleValue(); + norm(); + } + + + /** + * Color from hex 0xRRGGBB + * + * @param hex hex integer + */ + public RGB(int hex) { + setTo(RGB.fromHex(hex)); + norm(); + } + + + /** + * Color from hex 0xRRGGBB + * + * @param hex hex integer + * @param alpha alpha color + */ + public RGB(int hex, double alpha) { + setTo(RGB.fromHex(hex)); + a = alpha; + norm(); + } + + + /** + * Color from other RGB and alpha channel + * + * @param color other RGB color + * @param alpha new alpha channel + */ + public RGB(RGB color, double alpha) { + setTo(color); + setAlpha_ip(alpha); + } + + + /** + * @return red channel 0-1 + */ + public double r() + { + return r; + } + + + /** + * @return green channel 0-1 + */ + public double g() + { + return g; + } + + + /** + * @return blue channel 0-1 + */ + public double b() + { + return b; + } + + + /** + * @return alpha 0-1 + */ + public double a() + { + return a; + } + + + /** + * Set color to other color + * + * @param copied copied color + * @return this + */ + public RGB setTo(RGB copied) + { + r = copied.r; + g = copied.g; + b = copied.b; + a = copied.a; + + norm(); + return this; + } + + + /** + * Set to represent hex color + * + * @param hex hex integer RRGGBB + * @return this + */ + public RGB setTo(int hex) + { + setTo(RGB.fromHex(hex)); + norm(); + return this; + } + + + /** + * Set to R,G,B 0-1 + * + * @param r red + * @param g green + * @param b blue + * @param a alpha + * @return this + */ + public RGB setTo(Number r, Number g, Number b, Number a) + { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = a.doubleValue(); + norm(); + return this; + } + + + /** + * Set to R,G,B 0-1 + * + * @param r red + * @param g green + * @param b blue + * @return this + */ + public RGB setTo(Number r, Number g, Number b) + { + this.r = r.doubleValue(); + this.g = g.doubleValue(); + this.b = b.doubleValue(); + this.a = 1; + norm(); + return this; + } + + + /** + * Fix numbers out of range 0-1 + * + * @return this + */ + public RGB norm() + { + r = Calc.clampd(r, 0, 1); + g = Calc.clampd(g, 0, 1); + b = Calc.clampd(b, 0, 1); + a = Calc.clampd(a, 0, 1); + return this; + } + + + /** + * Get hex value 0xRRGGBB + * + * @return hex value RRGGBB + */ + public int getHex() + { + int ri = (int) Math.round(r * 255); + int gi = (int) Math.round(g * 255); + int bi = (int) Math.round(b * 255); + return (ri << 16) | (gi << 8) | bi; + } + + + /** + * Convert to HSV + * + * @return HSV representation + */ + public HSV toHSV() + { + float[] hsv = { 0, 0, 0 }; + Color.RGBtoHSB((int) (r * 255), (int) (g * 255), (int) (b * 255), hsv); + return new HSV(hsv[0], hsv[1], hsv[2]); + } + + + /** + * Create color from hex 0xRRGGBB + * + * @param hex hex RRGGBB + * @return the new color + */ + public static RGB fromHex(int hex) + { + int bi = hex & 0xff; + int gi = (hex >> 8) & 0xff; + int ri = (hex >> 16) & 0xff; + return new RGB(ri / 255D, gi / 255D, bi / 255D); + } + + + /** + * Make from HSV + * + * @param color HSV color + * @return RGB + */ + public static RGB fromHSV(HSV color) + { + return color.toRGB(); + } + + + @Override + public String toString() + { + return "RGB[" + r + ";" + g + ";" + b + ";" + a + "]"; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof RGB)) return false; + return ((RGB) obj).r == r && ((RGB) obj).g == g && ((RGB) obj).b == b && ((RGB) obj).a == a; + } + + + @Override + public int hashCode() + { + return Double.valueOf(r).hashCode() ^ Double.valueOf(g).hashCode() ^ Double.valueOf(b).hashCode() ^ Double.valueOf(a).hashCode(); + } + +} diff --git a/src/mightypork/utils/math/coord/Coord.java b/src/mightypork/utils/math/coord/Coord.java new file mode 100644 index 0000000..21e5172 --- /dev/null +++ b/src/mightypork/utils/math/coord/Coord.java @@ -0,0 +1,899 @@ +package mightypork.utils.math.coord; + + +import java.util.Random; + +import mightypork.utils.math.Calc; +import mightypork.utils.time.Updateable; + + +/** + * Coordinate class, object with three or two double coordinates.
+ * + * @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); + + /** RNG */ + protected static Random rand = new Random(); + + + /** + * Get distance to other point + * + * @param a point a + * @param b point b + * @return distance in units + */ + public static double dist(Coord a, Coord b) + { + return a.distTo(b); + } + + + /** + * Generate random coord (gaussian) + * + * @param max max distance from 0 + * @return new coord + */ + public static Coord random(double max) + { + return new Coord(Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2), Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2), + Calc.clampd(rand.nextGaussian() * max, -max * 2, max * 2)); + } + + + /** + * Generate random coord (min-max) + * + * @param min min offset + * @param max max offset + * @return new coord + */ + public static Coord random(double min, double max) + { + return new Coord((rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min)), (rand.nextBoolean() ? -1 : 1) * (min + rand.nextDouble() * (max - min)), + (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; + + /** Y coordinate */ + public double y = 0; + + /** Z coordinate */ + public double z = 0; + + + /** + * Create zero coord + */ + public Coord() {} + + + /** + * Create coord as a copy of another + * + * @param copied copied coord + */ + public Coord(Coord copied) { + this.x = copied.x; + this.y = copied.y; + this.z = copied.z; + } + + + /** + * Create 2D coord + * + * @param x x coordinate + * @param y y coordinate + */ + public Coord(Number x, Number y) { + setTo(x, y); + } + + + /** + * Create 3D coord + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + */ + public Coord(Number x, Number y, Number z) { + setTo(x, y, z); + } + + + /** + * Get a copy offset by vector + * + * @param vec offset + * @return the offset copy + */ + public Coord add(Coord vec) + { + return copy().add_ip(vec); + } + + + /** + * Get a copy offset by 2D coordinate + * + * @param x x offset + * @param y y offset + * @return the offset copy + */ + public Coord add(Number x, Number y) + { + return copy().add_ip(x, y); + } + + + /** + * Get a copy offset by 3D coordinate + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return the offset copy + */ + public Coord add(Number x, Number y, Number z) + { + return copy().add_ip(x, y, z); + } + + + /** + * Offset by vector in place + * + * @param vec offset + * @return this + */ + public Coord add_ip(Coord vec) + { + this.x += vec.x; + this.y += vec.y; + this.z += vec.z; + return this; + } + + + /** + * Offset by 2D coordinate in place + * + * @param x x offset + * @param y y offset + * @return this + */ + public Coord add_ip(Number x, Number y) + { + this.x += x.doubleValue(); + this.y += y.doubleValue(); + return this; + } + + + /** + * Offset by 3D coordinate in place + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return this + */ + public Coord add_ip(Number x, Number y, Number z) + { + this.x += x.doubleValue(); + this.y += y.doubleValue(); + this.z += z.doubleValue(); + return this; + } + + + /** + * 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() + { + return new Coord(x, y, z); + } + + + /** + * Get distance to other point + * + * @param point other point + * @return distance in units + */ + public double distTo(Coord point) + { + return Math.sqrt((point.x - x) * (point.x - x) + (point.y - y) * (point.y - y) + (point.z - z) * (point.z - z)); + } + + + /** + * Get copy divided by number + * + * @param d number to divide by + * @return divided copy + */ + public Coord div(double d) + { + return copy().div_ip(d); + } + + + /** + * Divide by number in place + * + * @param d number to divide by + * @return this + */ + public Coord div_ip(double d) + { + if (d == 0) return this; + x /= d; + y /= d; + z /= d; + return this; + } + + + @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 + * + * @param min min coord + * @param max max coord + * @return is inside + */ + public boolean isInRect(Coord min, Coord max) + { + return (x >= min.x && x <= max.x) && (y >= min.y && y <= max.y) && (z >= min.z && z <= max.z); + } + + + /** + * Check if this rectangle in inside a rectangular zone + * + * @param rect checked rect. + * @return is inside + */ + public boolean isInRect(Rect rect) + { + return isInRect(rect.min, rect.max); + } + + + /** + * Get middle of line to other point + * + * @param other other point + * @return middle + */ + public Coord midTo(Coord other) + { + return add(vecTo(other).scale(0.5)); + } + + + /** + * Multiply by number + * + * @param d number + * @return multiplied copy + */ + public Coord mul(double d) + { + return copy().mul_ip(d); + } + + + /** + * Multiply coords by number + * + * @param xd x multiplier + * @param yd y multiplier + * @param zd z multiplier + * @return multiplied copy + */ + public Coord mul(double xd, double yd, double zd) + { + return copy().mul_ip(xd, yd, zd); + } + + + /** + * Multiply by number in place + * + * @param d multiplier + * @return this + */ + public Coord mul_ip(double d) + { + x *= d; + y *= d; + z *= d; + return this; + } + + + /** + * Multiply coords by number in place + * + * @param xd x multiplier + * @param yd y multiplier + * @param zd z multiplier + * @return this + */ + public Coord mul_ip(double xd, double yd, double zd) + { + x *= xd; + y *= yd; + z *= zd; + return this; + } + + + /** + * offset randomly + * + * @param max max +- offset + * @return offset coord + */ + 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); + } + + + /** + * offset randomly + * + * @param min min offset + * @param max max offset + * @return offset coord + */ + public Coord random_offset(double min, double max) + { + return copy().add_ip(random(min, max)); + } + + + /** + * offset randomly in place + * + * @param max max +- offset + * @return this + */ + public Coord random_offset_ip(double max) + { + return add(random(max)); + } + + + /** + * offset randomly in place + * + * @param min min offset + * @param max max offset + * @return this + */ + public Coord random_offset_ip(double min, double max) + { + add(random(min, max)); + return this; + } + + + /** + * 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 + * + * @return rounded copy + */ + public Coord round() + { + return copy().round_ip(); + } + + + /** + * Round in place + * + * @return this + */ + public Coord round_ip() + { + x = Math.round(x); + y = Math.round(y); + z = Math.round(z); + return this; + } + + + /** + * Set to max values of this and other coord + * + * @param other other coord + */ + public void setMax(Coord other) + { + x = Math.max(x, other.x); + y = Math.max(y, other.y); + z = Math.max(z, other.z); + } + + + /** + * Set to min values of this and other coord + * + * @param other other coord + */ + public void setMin(Coord other) + { + x = Math.min(x, other.x); + y = Math.min(y, other.y); + z = Math.min(z, other.z); + } + + + /** + * Set coordinates to match other coord + * + * @param copied coord whose coordinates are used + * @return this + */ + public Coord setTo(Coord copied) + { + setTo(copied.x, copied.y, copied.z); + return this; + } + + + /** + * Set 2D coordinates to + * + * @param x x coordinate + * @param y y coordinate + * @return this + */ + public Coord setTo(Number x, Number y) + { + setTo(x, y, 0); + return this; + } + + + /** + * Set 3D coordinates to + * + * @param x x coordinate + * @param y y coordinate + * @param z z coordinate + * @return this + */ + public Coord setTo(Number x, Number y, Number z) + { + this.x = x.doubleValue(); + this.y = y.doubleValue(); + this.z = z.doubleValue(); + return this; + } + + + /** + * Set X coordinate in a copy + * + * @param x x coordinate + * @return copy with set coordinate + */ + public Coord setX(Number x) + { + return copy().setX_ip(x); + } + + + /** + * Set X coordinate in place + * + * @param x x coordinate + * @return this + */ + public Coord setX_ip(Number x) + { + this.x = x.doubleValue(); + return this; + } + + + /** + * Set Y coordinate in a copy + * + * @param y y coordinate + * @return copy with set coordinate + */ + public Coord setY(Number y) + { + return copy().setY_ip(y); + } + + + /** + * Set Y coordinate in place + * + * @param y y coordinate + * @return this + */ + public Coord setY_ip(Number y) + { + this.y = y.doubleValue(); + return this; + } + + + /** + * Set Z coordinate in a copy + * + * @param z z coordinate + * @return copy with set coordinate + */ + public Coord setZ(Number z) + { + return copy().setZ_ip(z); + } + + + /** + * Set Z coordinate in place + * + * @param z z coordinate + * @return this + */ + public Coord setZ_ip(Number z) + { + this.z = z.doubleValue(); + return this; + } + + + /** + * Get size + * + * @return size + */ + public double size() + { + return new Vec(this).size(); + } + + + /** + * Get a copy subtracted by vector + * + * @param vec offset + * @return the offset copy + */ + public Coord sub(Coord vec) + { + return copy().sub_ip(vec); + } + + + /** + * Get a copy subtracted by 2D coordinate + * + * @param x x offset + * @param y y offset + * @return the offset copy + */ + public Coord sub(Number x, Number y) + { + return copy().sub_ip(x, y); + } + + + /** + * Get a copy subtracted by 3D coordinate + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return the offset copy + */ + public Coord sub(Number x, Number y, Number z) + { + return copy().sub_ip(x, y, z); + } + + + /** + * Offset by vector in place + * + * @param vec offset + * @return this + */ + public Coord sub_ip(Coord vec) + { + this.x -= vec.x; + this.y -= vec.y; + this.z -= vec.z; + return this; + } + + + /** + * Offset by 2D coordinate in place + * + * @param x x offset + * @param y y offset + * @return this + */ + public Coord sub_ip(Number x, Number y) + { + this.x -= x.doubleValue(); + this.y -= y.doubleValue(); + return this; + } + + + /** + * Offset by 3D coordinate in place + * + * @param x x offset + * @param y y offset + * @param z z offset + * @return this + */ + public Coord sub_ip(Number x, Number y, Number z) + { + this.x -= x.doubleValue(); + this.y -= y.doubleValue(); + this.z -= z.doubleValue(); + return this; + } + + + /** + * 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) + { + return (Vec) (new Vec(point)).add(new Vec(this).neg()); + } + + + /** + * @return X as double + */ + public double x() + { + return x; + } + + + /** + * @return X as double + */ + public double xd() + { + return x; + } + + + /** + * @return X as float + */ + public float xf() + { + return (float) x; + } + + + /** + * @return X as int + */ + public int xi() + { + return (int) Math.round(x); + } + + + /** + * @return Y as double + */ + public double y() + { + return y; + } + + + /** + * @return Y as double + */ + public double yd() + { + return y; + } + + + /** + * @return Y as float + */ + public float yf() + { + return (float) y; + } + + + /** + * @return Y as int + */ + public int yi() + { + return (int) Math.round(y); + } + + + /** + * @return Z as double + */ + public double z() + { + return z; + } + + + /** + * @return Z as double + */ + public double zd() + { + return z; + } + + + /** + * @return Z as float + */ + public float zf() + { + return (float) z; + } + + + /** + * @return Z as int + */ + public int zi() + { + return (int) Math.round(z); + } +} diff --git a/src/mightypork/utils/math/coord/CoordI.java b/src/mightypork/utils/math/coord/CoordI.java new file mode 100644 index 0000000..16fa958 --- /dev/null +++ b/src/mightypork/utils/math/coord/CoordI.java @@ -0,0 +1,398 @@ +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 new file mode 100644 index 0000000..8eed249 --- /dev/null +++ b/src/mightypork/utils/math/coord/Rect.java @@ -0,0 +1,805 @@ +package mightypork.utils.math.coord; + + +import mightypork.utils.math.Calc; + + +/** + * Rectangle determined by two coordinates - min and max. + * + * @author MightyPork + */ +public class Rect { + + /** Rect [0, 0, 1, 1] */ + public static final Rect ONE = new Rect(0, 0, 1, 1); + /** Rect all zeros */ + public static final Rect ZERO = new Rect(0, 0, 0, 0); + + + /** + * Rectangle from size + * + * @param min min coord + * @param size rect size + * @return the rect + */ + public static Rect fromSize(Coord min, Coord size) + { + return fromSize(min, size.xi(), size.yi()); + } + + + /** + * Make rect from min coord and size + * + * @param min min coord + * @param width size x + * @param height size y + * @return the new rect + */ + public static Rect fromSize(Coord min, int width, int height) + { + return new Rect(min, min.add(width, height)); + } + + + /** + * Rectangle from size + * + * @param i min X + * @param j min Y + * @param size rect size + * @return the rect + */ + public static Rect fromSize(int i, int j, CoordI size) + { + return fromSize(i, j, size.x, size.y); + } + + + /** + * Make rect from min coord and size + * + * @param x1 min x + * @param y1 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) + { + return new Rect(x1, y1, x1 + width, y1 + height); + } + + /** Highest coordinates xy */ + protected Coord max = new Coord(); + + /** Lowest coordinates xy */ + protected Coord min = new Coord(); + + + /** + * New Rect [0, 0, 0, 0] + */ + public Rect() { + this(0, 0, 0, 0); + } + + + /** + * Rect [0, 0, size.x, size.y] + * + * @param size size coord + */ + public Rect(Coord size) { + this(0, 0, size.x, size.y); + } + + + /** + * New rect of two coords + * + * @param c1 coord 1 + * @param c2 coord 2 + */ + public Rect(Coord c1, Coord c2) { + this(c1.x, c1.y, c2.x, c2.y); + } + + + /** + * New Rect + * + * @param x1 lower x + * @param y1 lower y + * @param x2 upper x + * @param y2 upper y + */ + public Rect(double x1, double y1, double x2, double y2) { + setTo(x1, y1, x2, y2); + } + + + /** + * Rect [0, 0, x, y] + * + * @param x width + * @param y height + */ + public Rect(int x, int y) { + this(0, 0, x, y); + } + + + /** + * New rect as a copy of other rect + * + * @param r other rect + */ + public Rect(Rect r) { + this(r.min.x, r.min.y, r.max.x, r.max.y); + } + + + /** + * Get offset copy (add) + * + * @param move offset vector + * @return offset copy + */ + public Rect add(Coord move) + { + return copy().add_ip(move); + } + + + /** + * Add X and Y to all coordinates in a copy + * + * @param x x to add + * @param y y to add + * @return copy changed + */ + public Rect add(double x, double y) + { + return add(new Vec(x, y)); + } + + + /** + * Offset in place (add) + * + * @param move offset vector + * @return this + */ + public Rect add_ip(Coord move) + { + min.add_ip(move); + max.add_ip(move); + return this; + } + + + /** + * Add X and Y to all coordinates in place + * + * @param x x to add + * @param y y to add + * @return this + */ + public Rect add_ip(double x, double y) + { + return add_ip(new Vec(x, y)); + } + + + /** + * Get a copy + * + * @return copy + */ + public Rect copy() + { + return new Rect(this); + } + + + /** + * Divide in copy + * + * @param factor divisor + * @return offset copy + */ + public Rect div(double factor) + { + return copy().div_ip(factor); + } + + + /** + * Divide coord in place + * + * @param factor divisor + * @return this + */ + public Rect div_ip(double factor) + { + min.div_ip(factor); + max.div_ip(factor); + return this; + } + + + /** + * Get copy with the same center and height=0 + * + * @return line + */ + public Rect getAxisH() + { + return new Rect(getCenterLeft(), getCenterRight()); + } + + + /** + * Get copy with the same center and width=0 + * + * @return line + */ + public Rect getAxisV() + { + return new Rect(getCenterDown(), getCenterTop()); + } + + + /** + * Get rect center + * + * @return center + */ + public Coord getCenter() + { + return min.midTo(max); + } + + + /** + * Get center of the lower edge. + * + * @return center + */ + public Coord getCenterDown() + { + return new Coord((max.x + min.x) / 2, min.y); + } + + + /** + * Get center of the left edge. + * + * @return center + */ + public Coord getCenterLeft() + { + return new Coord(min.x, (max.y + min.y) / 2); + } + + + /** + * Get center of the right edge. + * + * @return center + */ + public Coord getCenterRight() + { + return new Coord(max.x, (max.y + min.y) / 2); + } + + + /** + * Get center of the top edge. + * + * @return center + */ + public Coord getCenterTop() + { + return new Coord((max.x + min.x) / 2, max.y); + } + + + /** + * Get bottom edge rect + * + * @return line + */ + public Rect getEdgeBottom() + { + return new Rect(getLeftBottom(), getRightBottom()); + } + + + /** + * Get left edge rect + * + * @return line + */ + public Rect getEdgeLeft() + { + return new Rect(getLeftBottom(), getLeftTop()); + } + + + /** + * Get right edge rect + * + * @return line + */ + public Rect getEdgeRight() + { + return new Rect(getRightBottom(), getRightTop()); + } + + + /** + * Get top edge rect + * + * @return line + */ + public Rect getEdgeTop() + { + return new Rect(getLeftTop(), getRightTop()); + } + + + /** + * Get left bottom + * + * @return center + */ + public Coord getLeftBottom() + { + return new Coord(min.x, min.y); + } + + + /** + * Get left top + * + * @return center + */ + public Coord getLeftTop() + { + return new Coord(min.x, max.y); + } + + + /** + * @return highjest coordinates xy + */ + public Coord getMax() + { + return max; + } + + + /** + * @return lowest coordinates xy + */ + public Coord getMin() + { + return min; + } + + + /** + * Get right bottom + * + * @return center + */ + public Coord getRightBottom() + { + return new Coord(max.x, min.y); + } + + + /** + * Get right top + * + * @return center + */ + public Coord getRightTop() + { + return new Coord(max.x, max.y); + } + + + /** + * Get size (width, height) as (x,y) + * + * @return coord of width,height + */ + public Coord getSize() + { + return new Coord(Math.abs(min.x - max.x), Math.abs(min.y - max.y)); + } + + + /** + * Grow to sides in copy + * + * @param grow grow size (added to each side) + * @return grown copy + */ + public Rect grow(Coord grow) + { + return copy().grow_ip(grow); + } + + + /** + * Grow to sides in copy + * + * @param x x to add + * @param y y to add + * @return grown copy + */ + public Rect grow(double x, double y) + { + return copy().grow_ip(x, y); + } + + + /** + * Grow to sides in place + * + * @param grow grow size (added to each side) + * @return this + */ + public Rect grow_ip(Coord grow) + { + min.sub_ip(grow); + max.add_ip(grow); + return this; + } + + + /** + * Grow to sides in place + * + * @param x x to add + * @param y y to add + * @return this + */ + public Rect grow_ip(double x, double y) + { + min.sub_ip(x, y); + max.add_ip(x, y); + return this; + } + + + /** + * Grow down in copy + * + * @param down added pixels + * @return grown copy + */ + public Rect growDown(double down) + { + return copy().growDown_ip(down); + } + + + /** + * Grow down in place + * + * @param down added pixels + * @return this + */ + public Rect growDown_ip(double down) + { + min.sub_ip(0, down); + return this; + } + + + /** + * Grow to left in copy + * + * @param left added pixels + * @return grown copy + */ + public Rect growLeft(double left) + { + return copy().growLeft_ip(left); + } + + + /** + * Grow to left in place + * + * @param left added pixels + * @return this + */ + public Rect growLeft_ip(double left) + { + min.sub_ip(left, 0); + return this; + } + + + /** + * Grow to right in copy + * + * @param right added pixels + * @return grown copy + */ + public Rect growRight(double right) + { + return copy().growRight_ip(right); + } + + + /** + * Grow to right in place + * + * @param right added pixels + * @return this + */ + public Rect growRight_ip(double right) + { + max.add_ip(right, 0); + return this; + } + + + /** + * Grow up in copy + * + * @param add added pixels + * @return grown copy + */ + public Rect growUp(double add) + { + return copy().growUp_ip(add); + } + + + /** + * Grow up in place + * + * @param add added pixels + * @return this + */ + public Rect growUp_ip(double add) + { + max.add_ip(0, add); + return this; + } + + + /** + * Check if point is inside this rectangle + * + * @param point point to test + * @return is inside + */ + public boolean isInside(Coord point) + { + return Calc.inRange(point.x, min.x, max.x) && Calc.inRange(point.y, min.y, max.y); + } + + + /** + * Multiply in copy + * + * @param factor multiplier + * @return offset copy + */ + public Rect mul(double factor) + { + return copy().mul_ip(factor); + } + + + /** + * Multiply by number (useful for centered rects) + * + * @param x x multiplier + * @param y y multiplier + * @return copy multiplied + */ + public Rect mul(double x, double y) + { + return copy().mul_ip(x, y); + } + + + /** + * Multiply coord in place + * + * @param factor multiplier + * @return this + */ + public Rect mul_ip(double factor) + { + min.mul_ip(factor); + max.mul_ip(factor); + return this; + } + + + /** + * Multiply coord in place + * + * @param x multiplier x + * @param y multiplier y + * @return this + */ + public Rect mul_ip(double x, double y) + { + min.mul_ip(x, y, 1); + max.mul_ip(x, y, 1); + return this; + } + + + /** + * Round coords in copy + * + * @return copy, rounded + */ + public Rect round() + { + return new Rect(min.round(), max.round()); + } + + + /** + * Round this in place + * + * @return this + */ + public Rect round_ip() + { + min.round_ip(); + max.round_ip(); + return this; + } + + + /** + * Set to [0,0,coord.x,coord.y] + * + * @param coord size coord + */ + public void setTo(Coord coord) + { + setTo(0, 0, coord.x, coord.y); + } + + + /** + * Set to coordinates + * + * @param x1 lower x + * @param y1 lower y + * @param x2 upper x + * @param y2 upper y + */ + public void setTo(double x1, double y1, double x2, double y2) + { + min.x = Calc.min(x1, x2); + min.y = Calc.min(y1, y2); + max.x = Calc.max(x1, x2); + max.y = Calc.max(y1, y2); + } + + + /** + * Set to other rect's coordinates + * + * @param r other rect + */ + public void setTo(Rect r) + { + min.setTo(r.min); + max.setTo(r.max); + } + + + /** + * Subtract X and Y from all coordinates in a copy + * + * @param x x to subtract + * @param y y to subtract + * @return copy changed + */ + public Rect sub(double x, double y) + { + return sub(new Vec(x, y)); + } + + + /** + * Get offset copy (subtract) + * + * @param move offset vector + * @return offset copy + */ + public Rect sub(Vec move) + { + return copy().sub_ip(move); + } + + + /** + * Subtract X and Y from all coordinates in place + * + * @param x x to subtract + * @param y y to subtract + * @return this + */ + public Rect sub_ip(double x, double y) + { + return sub_ip(new Vec(x, y)); + } + + + /** + * Offset in place (subtract) + * + * @param move offset vector + * @return this + */ + public Rect sub_ip(Vec move) + { + min.sub_ip(move); + max.sub_ip(move); + return this; + } + + + @Override + public String toString() + { + return "rect{ " + min + " - " + max + " }"; + } + + + /** + * @return lower x + */ + public double x1() + { + return min.x; + } + + + /** + * @return upper x + */ + public double x2() + { + return max.x; + } + + + /** + * @return lower y + */ + public double y1() + { + return min.y; + } + + + /** + * @return upper y + */ + public double y2() + { + return max.y; + } +} diff --git a/src/mightypork/utils/math/coord/Vec.java b/src/mightypork/utils/math/coord/Vec.java new file mode 100644 index 0000000..e6f8e51 --- /dev/null +++ b/src/mightypork/utils/math/coord/Vec.java @@ -0,0 +1,326 @@ +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/objects/Convertor.java b/src/mightypork/utils/objects/Convertor.java new file mode 100644 index 0000000..3b1f8db --- /dev/null +++ b/src/mightypork/utils/objects/Convertor.java @@ -0,0 +1,285 @@ +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; + + +/** + * Utility for converting Object to data types; Can also convert strings to data + * types. + * + * @author MightyPork + */ +public class Convertor { + + /** + * Get INTEGER + * + * @param o object + * @param def default value + * @return integer + */ + public static int getInteger(Object o, Integer def) + { + try { + if (o == null) return def; + if (o instanceof String) return (int) Math.round(Double.parseDouble((String) o)); + if (o instanceof Number) return ((Number) o).intValue(); + if (o instanceof Range) return ((Range) o).randInt(); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (NumberFormatException e) {} + Log.w("Cannot convert " + o + " to Integer."); + return def; + } + + + /** + * Get DOUBLE + * + * @param o object + * @param def default value + * @return double + */ + public static double getDouble(Object o, Double def) + { + try { + if (o == null) return def; + if (o instanceof String) return Double.parseDouble((String) o); + if (o instanceof Number) return ((Number) o).doubleValue(); + if (o instanceof Range) return ((Range) o).randDouble(); + if (o instanceof Boolean) return ((Boolean) o) ? 1 : 0; + } catch (NumberFormatException e) {} + Log.w("Cannot convert " + o + " to Double."); + return def; + } + + + /** + * Get FLOAT + * + * @param o object + * @param def default value + * @return float + */ + public static double getFloat(Object o, Float def) + { + try { + if (o == null) return def; + if (o instanceof Number) return ((Number) o).floatValue(); + } catch (NumberFormatException e) {} + Log.w("Cannot convert " + o + " to Float."); + return def; + } + + + /** + * Get BOOLEAN + * + * @param o object + * @param def default value + * @return boolean + */ + public static boolean getBoolean(Object o, Boolean def) + { + if (o == null) return def; + + if (o instanceof String) { + String s = ((String) o).trim().toLowerCase(); + if (s.equals("0")) return false; + if (s.equals("1")) return true; + try { + double n = Double.parseDouble(s); + return n != 0; + } catch (NumberFormatException e) {} + + if (s.equals("true")) return true; + if (s.equals("yes")) return true; + if (s.equals("y")) return true; + if (s.equals("enabled")) return true; + + if (s.equals("false")) return false; + if (s.equals("no")) return false; + if (s.equals("n")) return false; + if (s.equals("disabled")) return true; + } + + if (o instanceof Boolean) return ((Boolean) o).booleanValue(); + if (o instanceof Number) return ((Number) o).intValue() != 0; + Log.w("Cannot convert " + o + " to Boolean."); + return def; + } + + + /** + * Get STRING + * + * @param o object + * @param def default value + * @return String + */ + public static String getString(Object o, String def) + { + if (o == null) return def; + if (o instanceof String) return ((String) o); + Log.w("Cannot convert " + o + " to String."); + return o.toString(); + } + + + /** + * Get AI_COORD
+ * Converts special constants to magic coordinate instances. + * + * @param o object + * @param def default value + * @return AiCoord + */ + public static Coord getCoord(Object o, Coord def) + { + try { + if (o == null) return def; + if (o instanceof String) { + String s = ((String) o).trim().toUpperCase(); + + // colon to semicolon + s = s.replace(':', ';'); + // comma to semicolon + s = s.replace(',', ';'); + // remove brackets if any + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + String[] parts = s.split("[;]"); + 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 + } + Log.w("Cannot convert " + o + " to Coord."); + return def; + } + + + /** + * Get RANGE + * + * @param o object + * @param def default value + * @return AiCoord + */ + public static Range getRange(Object o, Range def) + { + try { + if (o == null) return def; + if (o instanceof Number) return new Range(((Number) o).doubleValue(), ((Number) o).doubleValue()); + if (o instanceof String) { + String s = ((String) o).trim(); + + // colon to semicolon + s = s.replace(',', ':'); + // comma to dot. + s = s.replace(';', ':'); + // dash + s = s.replaceAll("([0-9])\\s?[\\-]", "$1:"); + // remove brackets if any + s = s.replaceAll("[\\(\\[\\{\\)\\]\\}]", ""); + String[] parts = s.split("[:]"); + if (parts.length == 2) return new Range(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[1].trim())); + return new Range(Double.parseDouble(parts[0].trim()), Double.parseDouble(parts[0].trim())); + + } + if (o instanceof Range) return (Range) o; + } catch (NumberFormatException e) {} + Log.w("Cannot convert " + o + " to Range."); + return def; + } + + + /** + * Get INTEGER + * + * @param o object + * @return integer + */ + public static int getInteger(Object o) + { + return getInteger(o, 0); + } + + + /** + * Get DOUBLE + * + * @param o object + * @return double + */ + public static double getDouble(Object o) + { + return getDouble(o, 0d); + } + + + /** + * Get FLOAT + * + * @param o object + * @return float + */ + public static double getFloat(Object o) + { + return getFloat(o, 0f); + } + + + /** + * Get BOOLEAN + * + * @param o object + * @return boolean + */ + public static boolean getBoolean(Object o) + { + return getBoolean(o, false); + } + + + /** + * Get STRING + * + * @param o object + * @return String + */ + public static String getString(Object o) + { + return getString(o, ""); + } + + + /** + * Get AI_COORD (if special string constant is present instead, build coord + * of it) + * + * @param o object + * @return AiCoord + */ + public static Coord getCoord(Object o) + { + return getCoord(o, Coord.ZERO.copy()); + } + + + /** + * Get RANGE + * + * @param o object + * @return AiCoord + */ + public static Range getRange(Object o) + { + return getRange(o, new Range()); + } + +} diff --git a/src/mightypork/utils/objects/Mutable.java b/src/mightypork/utils/objects/Mutable.java new file mode 100644 index 0000000..0440cbe --- /dev/null +++ b/src/mightypork/utils/objects/Mutable.java @@ -0,0 +1,52 @@ +package mightypork.utils.objects; + + +/** + * Mutable object + * + * @author MightyPork + * @param type + */ +public class Mutable { + + /** The wrapped value */ + public T o = null; + + + /** + * Implicint constructor + */ + public Mutable() {} + + + /** + * new mutable object + * + * @param o value + */ + public Mutable(T o) { + this.o = o; + } + + + /** + * Get the wrapped value + * + * @return value + */ + public T get() + { + return o; + } + + + /** + * Set value + * + * @param o new value to set + */ + public void set(T o) + { + this.o = o; + } +} diff --git a/src/mightypork/utils/objects/ObjectUtils.java b/src/mightypork/utils/objects/ObjectUtils.java new file mode 100644 index 0000000..eaa238e --- /dev/null +++ b/src/mightypork/utils/objects/ObjectUtils.java @@ -0,0 +1,111 @@ +package mightypork.utils.objects; + + +import java.util.*; +import java.util.Map.Entry; + + +/** + * Object utils class + * + * @author MightyPork + */ +public class ObjectUtils { + + public static Object fallback(Object... options) + { + for (Object o : options) { + if (o != null) return o; + } + return null; // error + } + + + /** + * Sort a map by keys, maintaining key-value pairs. + * + * @param map map to be sorted + * @param comparator a comparator, or null for natural ordering + * @return linked hash map with sorted entries + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Map sortByKeys(Map map, final Comparator comparator) + { + List keys = new LinkedList(map.keySet()); + + if (comparator == null) { + Collections.sort(keys); + } else { + Collections.sort(keys, comparator); + } + + // LinkedHashMap will keep the keys in the order they are inserted + // which is currently sorted on natural ordering + Map sortedMap = new LinkedHashMap(); + for (K key : keys) { + sortedMap.put(key, map.get(key)); + } + + return sortedMap; + } + + + /** + * Sort a map by values, maintaining key-value pairs. + * + * @param map map to be sorted + * @param comparator a comparator, or null for natural ordering + * @return linked hash map with sorted entries + */ + @SuppressWarnings("rawtypes") + public static Map sortByValues(Map map, final Comparator comparator) + { + List> entries = new LinkedList>(map.entrySet()); + + Collections.sort(entries, new Comparator>() { + + @Override + public int compare(Entry o1, Entry o2) + { + if (comparator == null) return o1.getValue().compareTo(o2.getValue()); + return comparator.compare(o1.getValue(), o2.getValue()); + } + }); + + // LinkedHashMap will keep the keys in the order they are inserted + // which is currently sorted on natural ordering + Map sortedMap = new LinkedHashMap(); + + for (Map.Entry entry : entries) { + sortedMap.put(entry.getKey(), entry.getValue()); + } + + return sortedMap; + } + + + public static String arrayToString(Object[] arr) + { + StringBuilder sb = new StringBuilder(); + + sb.append('['); + boolean first = true; + for (Object o : arr) { + if (!first) sb.append(','); + sb.append(o.toString()); + } + sb.append(']'); + + return sb.toString(); + } + + + public static List arrayToList(T[] objs) + { + ArrayList list = new ArrayList(); + for (T o : objs) { + list.add(o); + } + return list; + } +} diff --git a/src/mightypork/utils/objects/Pair.java b/src/mightypork/utils/objects/Pair.java new file mode 100644 index 0000000..822d8de --- /dev/null +++ b/src/mightypork/utils/objects/Pair.java @@ -0,0 +1,92 @@ +package mightypork.utils.objects; + + +import mightypork.utils.math.Calc; + + +/** + * Structure of 2 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + */ +public class Pair { + + /** + * 1st object + */ + public T1 first; + + /** + * 2nd object + */ + public T2 second; + + + /** + * Make structure of 2 objects + * + * @param first 1st object + * @param second 2nd object + */ + public Pair(T1 first, T2 second) { + this.first = first; + this.second = second; + } + + + /** + * @return 1st object + */ + public T1 getFirst() + { + return first; + } + + + /** + * @return 2nd object + */ + public T2 getSecond() + { + return second; + } + + + @Override + public boolean equals(Object obj) + { + if (obj == null) { + return false; + } + + if (!this.getClass().equals(obj.getClass())) { + return false; + } + + Pair t = (Pair) obj; + + return Calc.areObjectsEqual(first, t.first) && Calc.areObjectsEqual(second, t.second); + + } + + + @Override + public int hashCode() + { + int hash = 13; + hash += (first == null ? 0 : first.hashCode()); + hash += (second == null ? 0 : second.hashCode()); + return hash; + } + + + @Override + public String toString() + { + return "PAIR{" + first + "," + second + "}"; + } + +} diff --git a/src/mightypork/utils/objects/Triad.java b/src/mightypork/utils/objects/Triad.java new file mode 100644 index 0000000..027b90a --- /dev/null +++ b/src/mightypork/utils/objects/Triad.java @@ -0,0 +1,80 @@ +package mightypork.utils.objects; + + +import mightypork.utils.math.Calc; + + +/** + * Structure of 3 objects. + * + * @author MightyPork + * @copy (c) 2012 + * @param 1st object class + * @param 2nd object class + * @param 3rd object class + */ +public class Triad extends Pair { + + /** + * 3rd object + */ + public T3 third; + + + /** + * Make structure of 3 objects + * + * @param objA 1st object + * @param objB 2nd object + * @param objC 3rd object + */ + public Triad(T1 objA, T2 objB, T3 objC) { + super(objA, objB); + third = objC; + } + + + /** + * @return 3rd object + */ + public T3 getThird() + { + return third; + } + + + /** + * Set 1st object + * + * @param obj 1st object + */ + public void setThird(T3 obj) + { + third = obj; + } + + + @Override + public boolean equals(Object obj) + { + if (!super.equals(obj)) return false; + + return Calc.areObjectsEqual(third, ((Triad) obj).third); + + } + + + @Override + public int hashCode() + { + return super.hashCode() + (third == null ? 0 : third.hashCode()); + } + + + @Override + public String toString() + { + return "TRIAD{" + first + "," + second + "," + third + "}"; + } + +} diff --git a/src/mightypork/utils/objects/VarargsParser.java b/src/mightypork/utils/objects/VarargsParser.java new file mode 100644 index 0000000..56769c9 --- /dev/null +++ b/src/mightypork/utils/objects/VarargsParser.java @@ -0,0 +1,58 @@ +package mightypork.utils.objects; + + +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Varargs parser
+ * Converts an array of repeated "key, value" pairs to a LinkedHashMap.
+ * example: + * + *
+ * 
+ * 
+ * 
+ * 
+ * Object[] array = { "one", 1, "two", 4, "three", 9, "four", 16 };
+ * Map<String, Integer> args = new VarargsParser<String, Integer>().parse(array);
+ * 
+ * + * @author MightyPork + * @param Type for Map keys + * @param Type for Map values + */ +public class VarargsParser { + + /** + * Parse array of vararg key, value pairs to a LinkedHashMap. + * + * @param args varargs + * @return LinkedHashMap + * @throws ClassCastException in case of incompatible type in the array + * @throws IllegalArgumentException in case of invalid array length (odd) + */ + @SuppressWarnings("unchecked") + public Map parse(Object... args) throws ClassCastException, IllegalArgumentException + { + LinkedHashMap attrs = new LinkedHashMap(); + + if (args.length % 2 != 0) { + throw new IllegalArgumentException("Odd number of elements in varargs map!"); + } + + K key = null; + for (Object o : args) { + if (key == null) { + if (o == null) throw new RuntimeException("Key cannot be NULL in varargs map."); + key = (K) o; + } else { + attrs.put(key, (V) o); + key = null; + } + } + + return attrs; + } +} diff --git a/src/mightypork/utils/string/StringUtils.java b/src/mightypork/utils/string/StringUtils.java new file mode 100644 index 0000000..4221b64 --- /dev/null +++ b/src/mightypork/utils/string/StringUtils.java @@ -0,0 +1,181 @@ +package mightypork.utils.string; + + +/** + * General purpose string utilities + * + * @author MightyPork + */ +public class StringUtils { + + /** + * Get if string is in array + * + * @param needle checked string + * @param case_sensitive case sensitive comparision + * @param haystack array of possible values + * @return is in array + */ + public static boolean isInArray(String needle, boolean case_sensitive, String... haystack) + { + if (case_sensitive) { + for (String s : haystack) { + if (needle.equals(s)) return true; + } + return false; + } else { + for (String s : haystack) { + if (needle.equalsIgnoreCase(s)) return true; + } + return false; + } + } + + + public static String fromLastDot(String s) + { + return fromLastChar(s, '.'); + } + + + public static String toLastDot(String s) + { + return toLastChar(s, '.'); + } + + + public static String fromLastChar(String s, char c) + { + if (s == null) return null; + return s.substring(s.lastIndexOf(c) + 1, s.length()); + } + + + public static String toLastChar(String s, char c) + { + if (s == null) return null; + return s.substring(0, s.lastIndexOf(c)); + } + + + /** + * Repeat a string + * + * @param repeated string + * @param count + * @return output + */ + public static String repeat(String repeated, int count) + { + String s = ""; + for (int i = 0; i < count; i++) + s += repeated; + return s; + } + + + /** + * convert string to a same-length sequence of # marks + * + * @param password password + * @return encoded + */ + public static String passwordify(String password) + { + return passwordify(password, "*"); + } + + + /** + * convert string to a same-length sequence of chars + * + * @param password password + * @param replacing character used in output + * @return encoded + */ + public static String passwordify(String password, String replacing) + { + return repeat(replacing, password.length()); + } + + + /** + * Get ordinal version of numbers (1 = 1st, 5 = 5th etc.) + * + * @param number number + * @return ordinal, string + */ + public static String numberToOrdinal(int number) + { + if (number % 100 < 4 || number % 100 > 13) { + if (number % 10 == 1) return number + "st"; + if (number % 10 == 2) return number + "nd"; + if (number % 10 == 3) return number + "rd"; + } + return number + "th"; + } + + + /** + * Format number with thousands separated by a dot. + * + * @param number number + * @return string 12.004.225 + */ + public static String formatInt(long number) + { + String num = number + ""; + String out = ""; + String dot = "."; + int cnt = 1; + for (int i = num.length() - 1; i >= 0; i--) { + out = num.charAt(i) + out; + if (cnt % 3 == 0 && i > 0) out = dot + out; + cnt++; + } + + return out; + } + + + public static boolean isValidFilenameChar(char ch) + { + return isValidFilenameString(Character.toString(ch)); + } + + + public static boolean isValidFilenameString(String filename) + { + return filename.matches("[a-zA-Z0-9 +\\-.,_%@#!]+"); + } + + + public static boolean isValidIdentifierChar(char ch) + { + return isValidIdentifierString(Character.toString(ch)); + } + + + public static boolean isValidIdentifierString(String filename) + { + return filename.matches("[a-zA-Z0-9._]+"); + } + + + public static String ellipsisStart(String orig, int length) + { + if (orig.length() > length) { + orig = "\u2026" + orig.substring(length, orig.length()); + } + return orig; + } + + + public static String ellipsisEnd(String orig, int length) + { + if (orig.length() > length) { + orig = orig.substring(0, length - 1) + "\u2026"; + } + return orig; + } +} diff --git a/src/mightypork/utils/string/validation/CharValidator.java b/src/mightypork/utils/string/validation/CharValidator.java new file mode 100644 index 0000000..83fe898 --- /dev/null +++ b/src/mightypork/utils/string/validation/CharValidator.java @@ -0,0 +1,7 @@ +package mightypork.utils.string.validation; + + +public interface CharValidator { + + public boolean isValid(char c); +} diff --git a/src/mightypork/utils/string/validation/CharValidatorRegex.java b/src/mightypork/utils/string/validation/CharValidatorRegex.java new file mode 100644 index 0000000..27b128f --- /dev/null +++ b/src/mightypork/utils/string/validation/CharValidatorRegex.java @@ -0,0 +1,20 @@ +package mightypork.utils.string.validation; + + +public class CharValidatorRegex implements CharValidator { + + private String formula; + + + public CharValidatorRegex(String regex) { + this.formula = regex; + } + + + @Override + public boolean isValid(char c) + { + return Character.toString(c).matches(formula); + } + +} diff --git a/src/mightypork/utils/string/validation/CharValidatorWhitelist.java b/src/mightypork/utils/string/validation/CharValidatorWhitelist.java new file mode 100644 index 0000000..d270479 --- /dev/null +++ b/src/mightypork/utils/string/validation/CharValidatorWhitelist.java @@ -0,0 +1,20 @@ +package mightypork.utils.string.validation; + + +public class CharValidatorWhitelist implements CharValidator { + + private String whitelist; + + + public CharValidatorWhitelist(String allowed) { + this.whitelist = allowed; + } + + + @Override + public boolean isValid(char c) + { + return whitelist.contains(Character.toString(c)); + } + +} diff --git a/src/mightypork/utils/string/validation/FileSuffixFilter.java b/src/mightypork/utils/string/validation/FileSuffixFilter.java new file mode 100644 index 0000000..ccb4713 --- /dev/null +++ b/src/mightypork/utils/string/validation/FileSuffixFilter.java @@ -0,0 +1,45 @@ +package mightypork.utils.string.validation; + + +import java.io.File; +import java.io.FileFilter; + + +/** + * File filter for certain suffixes + * + * @author MightyPork + */ +public class FileSuffixFilter implements FileFilter { + + /** Array of allowed suffixes */ + private String[] suffixes = null; + + + /** + * Suffix filter + * + * @param suffixes var-args allowed suffixes, case insensitive + */ + public FileSuffixFilter(String... suffixes) { + this.suffixes = suffixes; + } + + + @Override + public boolean accept(File pathname) + { + if (!pathname.isFile()) return false; + + String fname = pathname.getName().toLowerCase().trim(); + + for (String suffix : suffixes) { + if (fname.endsWith(suffix.toLowerCase().trim())) { + return true; + } + } + + return false; + } + +} diff --git a/src/mightypork/utils/string/validation/FilenameCharValidator.java b/src/mightypork/utils/string/validation/FilenameCharValidator.java new file mode 100644 index 0000000..6907264 --- /dev/null +++ b/src/mightypork/utils/string/validation/FilenameCharValidator.java @@ -0,0 +1,10 @@ +package mightypork.utils.string.validation; + + +public class FilenameCharValidator extends CharValidatorRegex { + + public FilenameCharValidator() { + super("[a-zA-Z0-9 +\\-.,_%@#$!'\"]"); + } + +} diff --git a/src/mightypork/utils/string/validation/StringFilter.java b/src/mightypork/utils/string/validation/StringFilter.java new file mode 100644 index 0000000..e06e3f4 --- /dev/null +++ b/src/mightypork/utils/string/validation/StringFilter.java @@ -0,0 +1,12 @@ +package mightypork.utils.string.validation; + + +/** + * Utility interface for string filters (accepting filepaths and similar) + * + * @author MightyPork + */ +public interface StringFilter { + + public boolean accept(String entry); +} diff --git a/src/mightypork/utils/time/AnimDouble.java b/src/mightypork/utils/time/AnimDouble.java new file mode 100644 index 0000000..48d9648 --- /dev/null +++ b/src/mightypork/utils/time/AnimDouble.java @@ -0,0 +1,263 @@ +package mightypork.utils.time; + + +import mightypork.utils.math.Calc; + + +/** + * Double which supports delta timing + * + * @author MightyPork + */ +public class AnimDouble implements Updateable, Pauseable { + + /** target double */ + protected double endValue = 0; + + /** last tick double */ + protected double startValue = 0; + + /** how long the transition should last */ + protected double duration = 0; + + /** current anim time */ + protected double elapsedTime = 0; + + /** True if this animator is paused */ + protected boolean paused = false; + + + /** + * @param value value + */ + public AnimDouble(double value) { + setTo(value); + } + + + public AnimDouble(AnimDouble other) { + setTo(other); + } + + + /** + * Get start value + * + * @return number + */ + public double getStartValue() + { + return startValue; + } + + + /** + * Get value at delta time + * + * @return the value + */ + public double getCurrentValue() + { + if (duration == 0) return endValue; + return Calc.interpolate(startValue, endValue, elapsedTime / duration); + } + + + /** + * Get end value + * + * @return number + */ + public double getEndValue() + { + return endValue; + } + + + /** + * Get how much of the animation is already finished + * + * @return completion ratio (0 to 1) + */ + public double getProgress() + { + if (duration == 0) return 1; + return elapsedTime / duration; + } + + + @Override + public void update(double delta) + { + elapsedTime = Calc.clampd(elapsedTime + delta, 0, duration); + if (isFinished()) { + duration = 0; + elapsedTime = 0; + startValue = endValue; + } + } + + + /** + * Get if animation is finished + * + * @return is finished + */ + public boolean isFinished() + { + return duration == 0 || elapsedTime >= duration; + } + + + /** + * Set to a value (without animation) + * + * @param value + */ + public void setTo(double value) + { + startValue = endValue = value; + elapsedTime = 0; + duration = 0; + } + + + /** + * Copy other + * + * @param other + */ + public void setTo(AnimDouble other) + { + this.startValue = other.startValue; + this.endValue = other.endValue; + this.duration = other.duration; + this.elapsedTime = other.elapsedTime; + this.paused = other.paused; + } + + + /** + * Animate between two states, discard current state + * + * @param from initial state + * @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; + } + + + /** + * Animate between two states, start from current value (if it's in between) + * + * @param from start value + * @param to target state + * @param time animation time (secs) + */ + public void fadeTo(double from, 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; + } + } + + + /** + * Animate 0 to 1 + * + * @param time animation time (secs) + */ + public void fadeIn(double time) + { + fadeTo(0, 1, time); + } + + + /** + * Animate 1 to 0 + * + * @param time animation time (secs) + */ + public void fadeOut(double time) + { + fadeTo(1, 0, time); + } + + + /** + * Make a copy + * + * @return copy + */ + public Pauseable getCopy() + { + return new AnimDouble(this); + } + + + @Override + public String toString() + { + return "Animation(" + startValue + " -> " + endValue + ", t=" + duration + "s, elapsed=" + elapsedTime + "s)"; + } + + + /** + * Set to zero and stop animation + */ + public void clear() + { + startValue = endValue = 0; + elapsedTime = 0; + duration = 0; + paused = false; + } + + + /** + * Stop animation, keep current value + */ + public void stop() + { + startValue = endValue = getCurrentValue(); + elapsedTime = 0; + duration = 0; + } + + + @Override + public void pause() + { + paused = true; + } + + + @Override + public void resume() + { + paused = false; + } + + + @Override + public boolean isPaused() + { + return paused; + } +} diff --git a/src/mightypork/utils/time/AnimDoubleDeg.java b/src/mightypork/utils/time/AnimDoubleDeg.java new file mode 100644 index 0000000..455fb3e --- /dev/null +++ b/src/mightypork/utils/time/AnimDoubleDeg.java @@ -0,0 +1,41 @@ +package mightypork.utils.time; + + +import mightypork.utils.math.Calc; + + +/** + * Double which supports delta timing + * + * @author MightyPork + */ +public class AnimDoubleDeg extends AnimDouble { + + /** + * new AnimDoubleDeg + * + * @param d value + */ + public AnimDoubleDeg(double d) { + super(d); + } + + + /** + * Get value at delta time + * + * @return the value + */ + @Override + public double getCurrentValue() + { + return Calc.interpolateDeg(startValue, endValue, elapsedTime / duration); + } + + + @Override + public void fadeTo(double from, double to, double time) + { + throw new UnsupportedOperationException("Cannot fadeTo in AnimDoubleDeg. Use animate() instead."); + } +} diff --git a/src/mightypork/utils/time/FpsMeter.java b/src/mightypork/utils/time/FpsMeter.java new file mode 100644 index 0000000..90d0431 --- /dev/null +++ b/src/mightypork/utils/time/FpsMeter.java @@ -0,0 +1,62 @@ +package mightypork.utils.time; + + +/** + * Class for counting FPS in games.
+ * This class can be used also as a simple frequency meter - output is in Hz. + * + * @author MightyPork + */ +public class FpsMeter { + + private long frames = 0; + private long drops = 0; + private long lastTimeMillis = System.currentTimeMillis(); + private long lastSecFPS = 0; + private long lastSecDrop = 0; + + + /** + * @return current second's FPS + */ + public long getFPS() + { + return lastSecFPS; + } + + + /** + * Notification that frame was rendered + */ + public void frame() + { + if (System.currentTimeMillis() - lastTimeMillis > 1000) { + lastSecFPS = frames; + lastSecDrop = drops; + frames = 0; + drops = 0; + lastTimeMillis = System.currentTimeMillis(); + } + frames++; + } + + + /** + * Notification that some frames have been dropped + * + * @param dropped dropped frames + */ + public void drop(int dropped) + { + drops += dropped; + } + + + /** + * @return current second's dropped frames + */ + public long getDropped() + { + return lastSecDrop; + } +} diff --git a/src/mightypork/utils/time/Pauseable.java b/src/mightypork/utils/time/Pauseable.java new file mode 100644 index 0000000..99d3101 --- /dev/null +++ b/src/mightypork/utils/time/Pauseable.java @@ -0,0 +1,23 @@ +package mightypork.utils.time; + + +public interface Pauseable { + + /** + * Pause operation + */ + public void pause(); + + + /** + * Resume operation + */ + public void resume(); + + + /** + * @return paused state + */ + public boolean isPaused(); + +} diff --git a/src/mightypork/utils/time/TimerDelta.java b/src/mightypork/utils/time/TimerDelta.java new file mode 100644 index 0000000..862dca9 --- /dev/null +++ b/src/mightypork/utils/time/TimerDelta.java @@ -0,0 +1,47 @@ +package mightypork.utils.time; + + +/** + * Timer for delta timing + * + * @author MightyPork + */ +public class TimerDelta { + + private long lastFrame; + + private static final long SECOND = 1000000000; // a million nanoseconds + + + /** + * New delta timer + */ + public TimerDelta() { + lastFrame = System.nanoTime(); + } + + + /** + * Get current time in NS + * + * @return current time NS + */ + public long getTime() + { + return System.nanoTime(); + } + + + /** + * Get time since the last "getDelta()" call. + * + * @return delta time (seconds) + */ + public double getDelta() + { + long time = getTime(); + double delta = (time - lastFrame) / (double) SECOND; + lastFrame = time; + return delta; + } +} diff --git a/src/mightypork/utils/time/TimerInterpolating.java b/src/mightypork/utils/time/TimerInterpolating.java new file mode 100644 index 0000000..3cfee74 --- /dev/null +++ b/src/mightypork/utils/time/TimerInterpolating.java @@ -0,0 +1,105 @@ +package mightypork.utils.time; + + +/** + * Timer for interpolated timing + * + * @author MightyPork + */ +public class TimerInterpolating { + + private long lastFrame = 0; + private long nextFrame = 0; + private long skipped = 0; + private long lastSkipped = 0; + + private static final long SECOND = 1000000000; // a million nanoseconds + private long FRAME; // a time of one frame in nanoseconds + + + /** + * New interpolated timer + * + * @param fps target FPS + */ + public TimerInterpolating(long fps) { + FRAME = Math.round(SECOND / fps); + + lastFrame = System.nanoTime(); + nextFrame = System.nanoTime() + FRAME; + } + + + /** + * Sync and calculate dropped frames etc. + */ + public void sync() + { + long time = getTime(); + if (time >= nextFrame) { + long skippedNow = (long) Math.floor((time - nextFrame) / (double) FRAME) + 1; + //System.out.println("Skipping: "+skippedNow); + skipped += skippedNow; + lastFrame = nextFrame + (1 - skippedNow) * FRAME; + nextFrame += skippedNow * FRAME; + } + } + + + /** + * Get nanotime + * + * @return nanotime + */ + public long getTime() + { + return System.nanoTime(); + } + + + /** + * Get fraction of next frame + * + * @return fraction + */ + public double getFraction() + { + if (getSkipped() >= 1) { + return 1; + } + + long time = getTime(); + + if (time <= nextFrame) { + return (double) (time - lastFrame) / (double) FRAME; + } + + return 1; + } + + + /** + * Get number of elapsed ticks + * + * @return ticks + */ + public int getSkipped() + { + long change = skipped - lastSkipped; + lastSkipped = skipped; + return (int) change; + } + + + /** + * Clear timer and start counting new tick. + */ + public void startNewFrame() + { + //System.out.println("! start new frame !"); + long time = getTime(); + lastFrame = time; + nextFrame = time + FRAME; + lastSkipped = skipped; + } +} diff --git a/src/mightypork/utils/time/Updateable.java b/src/mightypork/utils/time/Updateable.java new file mode 100644 index 0000000..6512069 --- /dev/null +++ b/src/mightypork/utils/time/Updateable.java @@ -0,0 +1,17 @@ +package mightypork.utils.time; + + +/** + * object supporting delta timing + * + * @author MightyPork + */ +public interface Updateable { + + /** + * Update item state based on elapsed time + * + * @param delta time elapsed since last update, in seconds + */ + public void update(double delta); +} diff --git a/src/net/mightypork/rogue/Game.java b/src/net/mightypork/rogue/Game.java deleted file mode 100644 index 50de2dc..0000000 --- a/src/net/mightypork/rogue/Game.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.mightypork.rogue; - -import org.mini2Dx.core.game.BasicGame; -import org.mini2Dx.core.game.Mini2DxGame; -import org.mini2Dx.core.graphics.Graphics; - -import com.badlogic.gdx.backends.lwjgl.LwjglApplication; -import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; - - -public class Game extends BasicGame { - - @Override - public void initialise() { - - // TODO Auto-generated method stub - - } - - @Override - public void interpolate(float arg0) { - - // TODO Auto-generated method stub - - } - - @Override - public void render(Graphics arg0) { - - // TODO Auto-generated method stub - - } - - @Override - public void update(float arg0) { - - // TODO Auto-generated method stub - - } - - public static void main(String [] args) { - LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration(); - cfg.title = "A basic game"; - cfg.useGL20 = true; - cfg.width = 800; - cfg.height = 600; - cfg.useCPUSynch = false; - cfg.vSyncEnabled = true; - new LwjglApplication(new Mini2DxGame(new Game()), cfg); - } - -}