[
  {
    "path": ".gitignore",
    "content": "# Windows image file caches\nThumbs.db\nehthumbs.db\n\n# Folder config file\nDesktop.ini\n\n# Recycle Bin used on file shares\n$RECYCLE.BIN/\n\n# Windows Installer files\n*.cab\n*.msi\n*.msm\n*.msp\n\n# Windows shortcuts\n*.lnk\n\n# =========================\n# Operating System Files\n# =========================\n\n# OSX\n# =========================\n\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Thumbnails\n._*\n\n# Files that might appear on external disk\n.Spotlight-V100\n.Trashes\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n/bin/\n\n.project\n.classpath\n*.classpath\n.classpath\ntest.json\n/bin1/\n/bin1/\nout/\n.idea/\n*.iml\nsrc/META-INF/MANIFEST.MF\n.metadata/\n/.gradle/\n/build/\n.settings/\n.project\nclasses/\n"
  },
  {
    "path": "LICENSE",
    "content": "﻿Copyright © 2016 MrCrayfish\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "build.gradle",
    "content": "apply plugin: \"java\"\napply plugin: \"application\"\n\nversion = \"0.7.0\"\nmainClassName = \"com.mrcrayfish.modelcreator.Start\"\nsourceCompatibility = targetCompatibility = 1.8\n\nrepositories {\n\tmavenCentral()\n}\n\ndependencies {\n\tcompile 'com.google.code.gson:gson:2.8.5'\n\tcompile 'org.slick2d:slick2d-core:1.0.2'\n\tcompile 'com.jtattoo:JTattoo:1.6.11'\n\t\n\tcompile 'org.lwjgl.lwjgl:lwjgl:2.9.3'\n\tcompile 'org.lwjgl.lwjgl:lwjgl_util:2.9.3'\n\tcompile 'org.lwjgl.lwjgl:lwjgl-platform:2.9.3'\n}\n\njar {\n\tmanifest {\n\t\tattributes \"Main-Class\": mainClassName\n\t}\n\t\n\tfrom {\n\t\tconfigurations.compile.collect { it.isDirectory() ? it : zipTree(it) }\n\t}\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.9-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Actions.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\n\n/**\n * Author: MrCrayfish\n */\npublic class Actions\n{\n    public static int optimiseModel(ElementManager manager)\n    {\n        int count = 0;\n        for(Element element : manager.getAllElements())\n        {\n            for(Face face : element.getAllFaces())\n            {\n                if(face.isEnabled() && !face.isVisible(manager))\n                {\n                    count++;\n                    face.setEnabled(false);\n                }\n            }\n        }\n        if(count > 0)\n        {\n            StateManager.pushState(manager);\n        }\n        return count;\n    }\n\n    public static void rotateModel(ElementManager manager, boolean clockwise)\n    {\n        manager.getAllElements().forEach(element -> rotateElement(element, clockwise));\n        manager.updateValues();\n        StateManager.pushState(manager);\n    }\n\n    private static void rotateElement(Element element, boolean clockwise)\n    {\n        /* Calculates and sets the new starting x and y position of the element */\n        double newX;\n        double newZ;\n        if(clockwise)\n        {\n            newX = element.getStartX() - 8 > 0 ? 16 - (element.getDepth() + element.getStartZ()) : 16 - element.getDepth() - element.getStartZ();\n            newZ = element.getStartX();\n        }\n        else\n        {\n            newX = element.getStartZ();\n            newZ = element.getStartZ() - 8 > 0 ? 16 - (element.getWidth() + element.getStartX()) : 16 - element.getWidth() - element.getStartX();\n        }\n        element.setStartX(newX);\n        element.setStartZ(newZ);\n\n        /* Swaps the width and depth of the element */\n        double width = element.getWidth();\n        element.setWidth(element.getDepth());\n        element.setDepth(width);\n\n        /* Shifts the UVs of horizontal faces to the next target face */\n        Face[] faces = element.getAllFaces();\n        Face tempFace = new Face(faces[clockwise ? 3 : 0]);\n        if(clockwise)\n        {\n            for(int i = 3; i >= 1; i--)\n            {\n                faces[i].copyProperties(faces[i - 1]);\n            }\n        }\n        else\n        {\n            for(int i = 0; i < 3; i++)\n            {\n                faces[i].copyProperties(faces[i + 1]);\n            }\n        }\n        faces[clockwise ? 0 : 3].copyProperties(tempFace);\n\n        /* Rotates the textures on the top so they match the original when rotated */\n        faces[Face.UP].setRotation(getNextFaceRotation(element, Face.UP, clockwise));\n        faces[Face.DOWN].setRotation(getNextFaceRotation(element, Face.DOWN, clockwise));\n\n        /* Rotates the rotation axis. This only applies to horizontal axis */\n        if(element.getRotationAxis() == 0)\n        {\n            element.setRotationAxis(2);\n            if(!clockwise)\n            {\n                element.setRotation(-element.getRotation());\n            }\n        }\n        else if(element.getRotationAxis() == 2)\n        {\n            element.setRotationAxis(0);\n            if(clockwise)\n            {\n                element.setRotation(-element.getRotation());\n            }\n        }\n\n        /* Rotates the origin starting x and y */\n        double newOriginX;\n        double newOriginZ;\n        if(clockwise)\n        {\n            newOriginX = element.getOriginX() - 8 > 0 ? 16 - element.getOriginZ() : 16 - element.getOriginZ();\n            newOriginZ = element.getOriginX();\n        }\n        else\n        {\n            newOriginX = element.getOriginZ();\n            newOriginZ = element.getOriginZ() - 8 > 0 ? 16 - element.getOriginX() : 16 - element.getOriginX();\n        }\n        element.setOriginX(newOriginX);\n        element.setOriginZ(newOriginZ);\n    }\n\n    private static int getNextFaceRotation(Element element, int side, boolean clockwise)\n    {\n        Face[] faces = element.getAllFaces();\n        if(clockwise)\n        {\n            return (faces[side].getRotation() + 1) % 4;\n        }\n        else if(faces[side].getRotation() - 1 >= 0)\n        {\n            return faces[side].getRotation() - 1;\n        }\n        return 3;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Animation.java",
    "content": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic class Animation\n{\n    private static int counter;\n    private static float partialTicks;\n\n    public static void tick()\n    {\n        Animation.counter++;\n    }\n\n    public static void setPartialTicks(float partialTicks)\n    {\n        Animation.partialTicks = partialTicks;\n    }\n\n    public static int getCounter()\n    {\n        return Animation.counter;\n    }\n\n    public static float getPartialTicks()\n    {\n        return partialTicks;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Camera.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport static org.lwjgl.opengl.GL11.*;\nimport static org.lwjgl.util.glu.GLU.gluPerspective;\n\npublic class Camera\n{\n    private float x;\n    private float y;\n    private float z;\n    private float rx;\n    private float ry;\n    private float rz;\n\n    private float fov;\n    private float aspect;\n    private float near;\n    private float far;\n\n    public Camera(float fov, float aspect, float near, float far)\n    {\n        x = 0;\n        y = 0;\n        z = -20;\n        rx = 30F;\n        ry = 0F;\n        rz = 0;\n\n        this.fov = fov;\n        this.aspect = aspect;\n        this.near = near;\n        this.far = far;\n        initProjection();\n    }\n\n    private void initProjection()\n    {\n        glMatrixMode(GL_PROJECTION);\n        glLoadIdentity();\n        gluPerspective(fov, aspect, near, far);\n        glMatrixMode(GL_MODELVIEW);\n        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n        glEnable(GL_DEPTH_TEST);\n    }\n\n    public void useView()\n    {\n        glTranslatef(x, y, z);\n        glRotatef(rx, 1, 0, 0);\n        glRotatef(ry, 0, 1, 0);\n        glRotatef(rz, 0, 0, 1);\n    }\n\n    public float getX()\n    {\n        return x;\n    }\n\n    public float getY()\n    {\n        return y;\n    }\n\n    public float getZ()\n    {\n        return z;\n    }\n\n    public void setX(float x)\n    {\n        this.x = x;\n    }\n\n    public void setY(float y)\n    {\n        this.y = y;\n    }\n\n    public void setZ(float z)\n    {\n        this.z = z;\n    }\n\n    public float getRX()\n    {\n        return rx;\n    }\n\n    public float getRY()\n    {\n        return ry;\n    }\n\n    public float getRZ()\n    {\n        return rz;\n    }\n\n    public void setRX(float rx)\n    {\n        this.rx = rx;\n    }\n\n    public void setRY(float ry)\n    {\n        this.ry = ry;\n    }\n\n    public void setRZ(float rz)\n    {\n        this.rz = rz;\n    }\n\n    public void move(float amt, float dir)\n    {\n        z += amt * Math.sin(Math.toRadians(ry + 90 * dir));\n        x += amt * Math.cos(Math.toRadians(ry + 90 * dir));\n    }\n\n    public void addX(float amt)\n    {\n        x += amt;\n    }\n\n    public void addY(float amt)\n    {\n        y += amt;\n    }\n\n    public void addZ(float amt)\n    {\n        z += amt;\n    }\n\n    public void rotateX(float amt)\n    {\n        rx = ((rx + amt) % 360);\n    }\n\n    public void rotateY(float amt)\n    {\n        ry = ((ry + amt) % 360);\n    }\n}"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Constants.java",
    "content": "package com.mrcrayfish.modelcreator;\n\npublic class Constants\n{\n    public static final String NAME = \"MrCrayfish's Model Creator\";\n    public static final String VERSION = \"0.7.0\";\n\n    public static final String URL_DONATE = \"https://www.patreon.com/mrcrayfish?ty=h\";\n    public static final String URL_TWITTER = \"https://www.twitter.com/MrCraayfish\";\n    public static final String URL_FACEBOOK = \"https://www.facebook.com/MrCrayfish\";\n    public static final String URL_GITHUB = \"https://github.com/MrCrayfish/ModelCreator\";\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Exporter.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport java.io.BufferedWriter;\nimport java.io.File;\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.text.DecimalFormat;\nimport java.text.DecimalFormatSymbols;\n\npublic abstract class Exporter\n{\n    /**\n     * decimalformatter for rounding\n     */\n    public static final DecimalFormat FORMAT = new DecimalFormat(\"#.###\");\n    private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols();\n\n    static\n    {\n        SYMBOLS.setDecimalSeparator('.');\n        FORMAT.setDecimalFormatSymbols(SYMBOLS);\n    }\n\n    // Model Variables\n    protected ElementManager manager;\n\n    public Exporter(ElementManager manager)\n    {\n        this.manager = manager;\n    }\n\n    public File export(File file)\n    {\n        File path = file.getParentFile();\n        if(path.exists() && path.isDirectory())\n        {\n            this.writeFile(file);\n        }\n        return file;\n    }\n\n    protected abstract void write(BufferedWriter writer) throws IOException;\n\n    public File writeFile(File file)\n    {\n        try(BufferedWriter writer = new BufferedWriter(new FileWriter(file)))\n        {\n            if(!file.exists())\n            {\n                file.createNewFile();\n            }\n            this.write(writer);\n            return file;\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    protected String space(int size)\n    {\n        StringBuilder builder = new StringBuilder();\n        for(int i = 0; i < size; i++)\n        {\n            //TODO add setting to export with tabs instead\n            builder.append(\"    \");\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ExporterJavaCode.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.Element;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.datatransfer.StringSelection;\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.io.StringWriter;\n\npublic class ExporterJavaCode extends Exporter\n{\n    private ModelCreator creator;\n    private Version version = Version.V_1_12;\n    private boolean includeFields, includeMethods, useBoundsHelper, generateRotatedBounds;\n\n    public ExporterJavaCode(ModelCreator creator, boolean includeFields, boolean includeMethods, boolean useBoundsHelper, boolean generateRotatedBounds)\n    {\n        super(creator.getElementManager());\n        this.creator = creator;\n        this.includeFields = includeFields;\n        this.includeMethods = includeMethods;\n        this.useBoundsHelper = useBoundsHelper;\n        this.generateRotatedBounds = useBoundsHelper && generateRotatedBounds;\n    }\n\n    public void setVersion(Version version)\n    {\n        this.version = version;\n    }\n\n    public void writeCodeToClipboard() throws IOException\n    {\n        StringWriter writerFile = new StringWriter();\n        try(BufferedWriter writer = new BufferedWriter(writerFile))\n        {\n            write(writer);\n            writer.flush();\n            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(writerFile.toString()), null);\n        }\n    }\n\n    @Override\n    protected void write(BufferedWriter writer) throws IOException\n    {\n        if(version == Version.V_1_13)\n        {\n            if(includeFields)\n            {\n                /* Generates member fields */\n                writeNewLine(writer, \"/* Member variables */\");\n                if(generateRotatedBounds)\n                {\n                    writeNewLine(writer, \"public final ImmutableMap<IBlockState, VoxelShape> SHAPES;\");\n                }\n                else\n                {\n                    writeNewLine(writer, \"public final VoxelShape SHAPE;\");\n                }\n                writer.newLine();\n\n                /* Generates logic which is to be placed into the constructor */\n                writeNewLine(writer, \"/* Place in Constructor */\");\n                if(generateRotatedBounds)\n                {\n                    writeNewLine(writer, \"SHAPES = this.generateShapes(this.getStateContainer().getValidStates());\");\n                }\n                else\n                {\n                    writeNewLine(writer, \"SHAPE = this.generateShape();\");\n                }\n                writer.newLine();\n            }\n\n            if(!includeMethods)\n            {\n                return;\n            }\n\n            writeNewLine(writer, \"/* Methods */\");\n\n            /* Creates method for generating voxel shapes for rotatable blocks */\n            if(generateRotatedBounds)\n            {\n                writeNewLine(writer, \"private ImmutableMap<IBlockState, VoxelShape> generateShapes(ImmutableList<IBlockState> states)\");\n                writeNewLine(writer, \"{\");\n                for(Element element : manager.getAllElements())\n                {\n                    if(element.getRotation() == 0)\n                    {\n                        String name = element.getName().toUpperCase().replaceAll(\" \", \"_\");\n                        double x = element.getStartX();\n                        double y = element.getStartY();\n                        double z = element.getStartZ();\n                        writer.write(\"    \");\n                        writeField(writer, null, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());\n                    }\n                    else\n                    {\n                        writer.write(String.format(\"    // Skipped '%s', as it has rotation\", element.getName()));\n                    }\n                    writer.newLine();\n                }\n\n                writer.newLine();\n                writeNewLine(writer, \"    ImmutableMap.Builder<IBlockState, VoxelShape> builder = new ImmutableMap.Builder<>();\");\n                writeNewLine(writer, \"    for(IBlockState state : states)\");\n                writeNewLine(writer, \"    {\");\n                writeNewLine(writer, \"        EnumFacing facing = state.getValue(HORIZONTAL_FACING);\");\n                writeNewLine(writer, \"        List<VoxelShape> shapes = new ArrayList<>();\");\n\n                for(Element element : manager.getAllElements())\n                {\n                    if(element.getRotation() == 0)\n                    {\n                        String name = element.getName().toUpperCase().replaceAll(\" \", \"_\");\n                        writeNewLine(writer, String.format(\"        shapes.add(%s[facing.getHorizontalIndex()]);\", name));\n                    }\n                }\n\n                writeNewLine(writer, \"        builder.put(state, VoxelShapeHelper.combineAll(shapes));\");\n                writeNewLine(writer, \"    }\");\n                writeNewLine(writer, \"    return builder.build();\");\n                writeNewLine(writer, \"}\");\n                writer.newLine();\n            }\n            else\n            {\n                writeNewLine(writer, \"private VoxelShape generateShape()\");\n                writeNewLine(writer, \"{\");\n                writeNewLine(writer, \"    List<VoxelShape> shapes = new ArrayList<>();\");\n                for(Element element : manager.getAllElements())\n                {\n                    if(element.getRotation() == 0)\n                    {\n                        String name = element.getName().toUpperCase().replaceAll(\" \", \"_\");\n                        double x = element.getStartX();\n                        double y = element.getStartY();\n                        double z = element.getStartZ();\n                        writer.write(\"    \");\n                        writeField(writer, null, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());\n                    }\n                    else\n                    {\n                        writer.write(String.format(\"    // Skipped '%s', as it has rotation\", element.getName()));\n                    }\n                    writer.newLine();\n                }\n\n                if(useBoundsHelper)\n                {\n                    writeNewLine(writer, \"    return VoxelShapeHelper.combineAll(shapes)\");\n                }\n                else\n                {\n                    writer.newLine();\n                    writeNewLine(writer, \"    VoxelShape result = ShapeUtils.empty();\");\n                    writeNewLine(writer, \"    for(VoxelShape shape : shapes)\");\n                    writeNewLine(writer, \"    {\");\n                    writeNewLine(writer, \"        result = ShapeUtils.combine(result, shape, IBooleanFunction.OR);\");\n                    writeNewLine(writer, \"    }\");\n                    writeNewLine(writer, \"    return result.simplify();\");\n                }\n                writeNewLine(writer, \"}\");\n                writer.newLine();\n            }\n\n            /* Produces the method for selection box */\n            writeNewLine(writer, \"@Override\");\n            writeNewLine(writer, \"public VoxelShape getShape(IBlockState state, IBlockReader reader, BlockPos pos)\");\n            writeNewLine(writer, \"{\");\n            if(generateRotatedBounds)\n            {\n                writeNewLine(writer, \"    return SHAPES.get(state);\");\n            }\n            else\n            {\n                writeNewLine(writer, \"    return SHAPE;\");\n            }\n            writeNewLine(writer, \"}\");\n            writer.newLine();\n\n            /* Produces the method for collisions */\n            writeNewLine(writer, \"@Override\");\n            writeNewLine(writer, \"public VoxelShape getCollisionShape(IBlockState state, IBlockReader reader, BlockPos pos)\");\n            writeNewLine(writer, \"{\");\n            if(generateRotatedBounds)\n            {\n                writeNewLine(writer, \"    return SHAPES.get(state);\");\n            }\n            else\n            {\n                writeNewLine(writer, \"    return SHAPE;\");\n            }\n            writeNewLine(writer, \"}\");\n        }\n        else if(version == Version.V_1_12)\n        {\n            if(includeFields)\n            {\n                StringBuilder boxList = new StringBuilder(\"private static final List<AxisAlignedBB>\");\n                boxList.append(generateRotatedBounds ? \"[] COLLISION_BOXES = Bounds.getRotatedBoundLists(\" : \" COLLISION_BOXES = Lists.newArrayList(\");\n                ModelBounds bounds = useBoundsHelper ? null : new ModelBounds();\n                String name = null;\n                double x, y, z;\n                for(Element element : manager.getAllElements())\n                {\n                    if(element.getRotation() != 0)\n                    {\n                        writer.write(String.format(\"// Skipped '%s', as it has roatation\", element.getName()));\n                    }\n                    else\n                    {\n                        if(name != null)\n                        {\n                            boxList.append(\", \");\n                        }\n\n                        x = element.getStartX();\n                        y = element.getStartY();\n                        z = element.getStartZ();\n                        name = element.getName();\n                        name = name.toUpperCase().replaceAll(\" \", \"_\");\n                        boxList.append(name);\n                        writeField(writer, bounds, name, x, y, z, x + element.getWidth(), y + element.getHeight(), z + element.getDepth());\n                    }\n                    writer.newLine();\n                }\n\n                if(name == null)\n                {\n                    JOptionPane.showMessageDialog(creator, \"No non-rotated elements were found.\", \"None Found\", JOptionPane.INFORMATION_MESSAGE);\n                    return;\n                }\n\n                if(useBoundsHelper)\n                {\n                    writer.newLine();\n                }\n                else\n                {\n                    writeNewLine(writer, \"/**\");\n                    writeNewLine(writer, String.format(\"* %s generated using MrCrayfish's Model Creator <a href=\\\"https://mrcrayfish.com/tools?id=mc\\\">https://mrcrayfish.com/tools?id=mc</a>\", includeMethods ? \"AxisAlignedBBs and methods getBoundingBox, collisionRayTrace, and collisionRayTrace\" : \"AxisAlignedBBs\"));\n                    writeNewLine(writer, \"*/\");\n                }\n\n                writer.write(boxList.append(\");\").toString());\n                writer.newLine();\n\n                if(bounds != null)\n                {\n                    bounds.write(writer);\n                }\n                else if(generateRotatedBounds)\n                {\n                    writer.write(\"private static final AxisAlignedBB[] BOUNDING_BOX = Bounds.getBoundingBoxes(COLLISION_BOXES);\");\n                }\n                else\n                {\n                    writer.write(\"private static final AxisAlignedBB BOUNDING_BOX = Bounds.getBoundingBox(COLLISION_BOXES);\");\n                }\n            }\n\n            if(!includeMethods)\n            {\n                return;\n            }\n\n            if(includeFields)\n            {\n                writer.newLine();\n                writer.newLine();\n            }\n\n            writeNewLine(writer, \"@Override\");\n            writeNewLine(writer, \"public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos)\");\n            writeNewLine(writer, \"{\");\n            writeNewLine(writer, \"    return BOUNDING_BOX%s\", generateRotatedBounds ? \"[state.getValue(FACING).getHorizontalIndex()];\" : \";\");\n            writeNewLine(writer, \"}\");\n\n            if(useBoundsHelper)\n            {\n                writer.newLine();\n                writeNewLine(writer, \"@Override\");\n                writeNewLine(writer, \"protected List<AxisAlignedBB> getCollisionBoxes(IBlockState state, World world, BlockPos pos, @Nullable Entity entity, boolean isActualState)\");\n                writeNewLine(writer, \"{\");\n                writeNewLine(writer, \"    return COLLISION_BOXES%s\", generateRotatedBounds ? \"[state.getValue(FACING).getHorizontalIndex()];\" : \";\");\n                writer.write(\"}\");\n                return;\n            }\n\n            writer.newLine();\n            writeNewLine(writer, \"@Override\");\n            writeNewLine(writer, \"public void addCollisionBoxToList(IBlockState state, World world, BlockPos pos, AxisAlignedBB entityBox, List<AxisAlignedBB> collidingBoxes, @Nullable Entity entity, boolean isActualState)\");\n            writeNewLine(writer, \"{\");\n            writeNewLine(writer, \"    entityBox = entityBox.offset(-pos.getX(), -pos.getY(), -pos.getZ());\");\n            writeNewLine(writer, \"    for (AxisAlignedBB box : COLLISION_BOXES)\");\n            writeNewLine(writer, \"    {\");\n            writeNewLine(writer, \"        if (entityBox.intersects(box))\");\n            writeNewLine(writer, \"            collidingBoxes.add(box.offset(pos));\");\n            writeNewLine(writer, \"    }\");\n            writeNewLine(writer, \"}\");\n            writer.newLine();\n            writeNewLine(writer, \"@Override\");\n            writeNewLine(writer, \"@Nullable\");\n            writeNewLine(writer, \"public RayTraceResult collisionRayTrace(IBlockState state, World world, BlockPos pos, Vec3d start, Vec3d end)\");\n            writeNewLine(writer, \"{\");\n            writeNewLine(writer, \"    double distanceSq;\");\n            writeNewLine(writer, \"    double distanceSqShortest = Double.POSITIVE_INFINITY;\");\n            writeNewLine(writer, \"    RayTraceResult resultClosest = null;\");\n            writeNewLine(writer, \"    RayTraceResult result;\");\n            writeNewLine(writer, \"    start = start.subtract(pos.getX(), pos.getY(), pos.getZ());\");\n            writeNewLine(writer, \"    end = end.subtract(pos.getX(), pos.getY(), pos.getZ());\");\n            writeNewLine(writer, \"    for (AxisAlignedBB box : COLLISION_BOXES)\");\n            writeNewLine(writer, \"    {\");\n            writeNewLine(writer, \"        result = box.calculateIntercept(start, end);\");\n            writeNewLine(writer, \"        if (result == null)\");\n            writeNewLine(writer, \"            continue;\");\n            writer.newLine();\n            writeNewLine(writer, \"        distanceSq = result.hitVec.squareDistanceTo(start);\");\n            writeNewLine(writer, \"        if (distanceSq < distanceSqShortest)\");\n            writeNewLine(writer, \"        {\");\n            writeNewLine(writer, \"            distanceSqShortest = distanceSq;\");\n            writeNewLine(writer, \"            resultClosest = result;\");\n            writeNewLine(writer, \"        }\");\n            writeNewLine(writer, \"    }\");\n            writeNewLine(writer, \"    return resultClosest == null ? null : new RayTraceResult(RayTraceResult.Type.BLOCK, resultClosest.hitVec.addVector(pos.getX(), pos.getY(), pos.getZ()), resultClosest.sideHit, pos);\");\n            writer.write(\"}\");\n        }\n    }\n\n    private String format(double value)\n    {\n        return FORMAT.format(useBoundsHelper ? value : value * 0.0625);\n    }\n\n    private void writeField(BufferedWriter writer, ModelBounds bounds, String name, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) throws IOException\n    {\n        if(version == Version.V_1_13)\n        {\n            if(generateRotatedBounds)\n            {\n                writer.write(String.format(\"final VoxelShape[] %s = VoxelShapeHelper.getRotatedVoxelShapes(Block.makeCuboidShape(%s, %s, %s, %s, %s, %s));\", name, format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ)));\n            }\n            else\n            {\n                writer.write(String.format(\"shapes.add(Block.makeCuboidShape(%s, %s, %s, %s, %s, %s)); // %s\", format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ), name));\n            }\n        }\n        else if(version == Version.V_1_12)\n        {\n            StringBuilder builder = new StringBuilder(\"private static final AxisAlignedBB\");\n\n            if(generateRotatedBounds)\n            {\n                builder.append(\"[]\");\n            }\n\n            builder.append(\" %s = new \").append(useBoundsHelper ? \"Bounds\" : \"AxisAlignedBB\").append(\"(%s, %s, %s, %s, %s, %s)\");\n\n            if(useBoundsHelper)\n            {\n                builder.append(generateRotatedBounds ? \".getRotatedBounds()\" : \".toAABB()\");\n            }\n\n            writer.write(String.format(builder.append(\";\").toString(), name, format(minX), format(minY), format(minZ), format(maxX), format(maxY), format(maxZ)));\n        }\n\n        if(bounds != null)\n        {\n            bounds.union(minX, minY, minZ, maxX, maxY, maxZ);\n        }\n    }\n\n    private void writeNewLine(BufferedWriter writer, String line, Object... args) throws IOException\n    {\n        writer.write(String.format(line, args));\n        writer.newLine();\n    }\n\n    private class ModelBounds\n    {\n        private double minX, minY, minZ, maxX, maxY, maxZ;\n\n        private ModelBounds()\n        {\n            minX = minY = minZ = Double.MAX_VALUE;\n            maxX = maxY = maxZ = Double.MIN_VALUE;\n        }\n\n        public void write(BufferedWriter writer) throws IOException\n        {\n            writeField(writer, this, \"BOUNDING_BOX\", minX, minY, minZ, maxX, maxY, maxZ);\n        }\n\n        private void union(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)\n        {\n            this.minX = Math.min(this.minX, minX);\n            this.minY = Math.min(this.minY, minY);\n            this.minZ = Math.min(this.minZ, minZ);\n            this.maxX = Math.max(this.maxX, maxX);\n            this.maxY = Math.max(this.maxY, maxY);\n            this.maxZ = Math.max(this.maxZ, maxZ);\n        }\n    }\n\n    public enum Version\n    {\n        V_1_12(\"1.12\"), V_1_13(\"1.13\");\n\n        private String label;\n\n        Version(String label)\n        {\n            this.label = label;\n        }\n\n        @Override\n        public String toString()\n        {\n            return label;\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ExporterModel.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport java.io.BufferedWriter;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ExporterModel extends Exporter\n{\n    private static final String[] DISPLAY_PROPERTY_ORDER = {\"gui\", \"ground\", \"fixed\", \"head\", \"firstperson_righthand\", \"thirdperson_righthand\"};\n\n    private Map<String, String> textureMap = new HashMap<>();\n    private boolean optimize = true;\n    private boolean includeNames = true;\n    private boolean displayProps = true;\n    private boolean includeNonTexturedFaces = false;\n\n    public ExporterModel(ElementManager manager)\n    {\n        super(manager);\n        compileTextureList();\n    }\n\n    public void setOptimize(boolean optimize)\n    {\n        this.optimize = optimize;\n    }\n\n    public void setIncludeNames(boolean includeNames)\n    {\n        this.includeNames = includeNames;\n    }\n\n    public void setDisplayProps(boolean displayProps)\n    {\n        this.displayProps = displayProps;\n    }\n\n    public void setIncludeNonTexturedFaces(boolean includeNonTexturedFaces)\n    {\n        this.includeNonTexturedFaces = includeNonTexturedFaces;\n    }\n\n    private void compileTextureList()\n    {\n        for(Element cuboid : manager.getAllElements())\n        {\n            for(Face face : cuboid.getAllFaces())\n            {\n                if(face.getTexture() != null && face.isEnabled() && (!optimize || face.isVisible(manager)))\n                {\n                    TextureEntry entry = face.getTexture();\n                    textureMap.put(entry.getKey(), entry.getTexturePath().toString());\n                }\n            }\n        }\n    }\n\n    @Override\n    protected void write(BufferedWriter writer) throws IOException\n    {\n        writer.write(\"{\");\n        writer.newLine();\n\n        writer.write(space(1) + \"\\\"__comment\\\": \\\"Model generated using MrCrayfish's Model Creator (https://mrcrayfish.com/tools?id=mc)\\\",\");\n        writer.newLine();\n\n        if(!manager.getAmbientOcc())\n        {\n            writer.write(\"\\\"ambientocclusion\\\": \" + manager.getAmbientOcc() + \",\");\n            writer.newLine();\n        }\n\n        writeTextures(writer);\n        writer.newLine();\n\n        if(displayProps)\n        {\n            writeDisplayProperties(writer);\n            writer.newLine();\n        }\n\n        writer.write(space(1) + \"\\\"elements\\\": [\");\n\n        for(int i = 0; i < manager.getElementCount() - 1; i++)\n        {\n            Element element = manager.getElement(i);\n            if(canWriteElement(element))\n            {\n                writeElement(writer, manager.getElement(i));\n                writer.write(\",\");\n            }\n        }\n        if(manager.getElementCount() > 0)\n        {\n            Element element = manager.getElement(manager.getElementCount() - 1);\n            if(canWriteElement(element))\n            {\n                writeElement(writer, manager.getElement(manager.getElementCount() - 1));\n            }\n        }\n\n        writer.newLine();\n        writer.write(space(1) + \"]\");\n        writer.newLine();\n        writer.write(\"}\");\n    }\n\n    private void writeTextures(BufferedWriter writer) throws IOException\n    {\n        writer.write(space(1) + \"\\\"textures\\\": {\");\n        writer.newLine();\n        if(manager.getParticle() != null)\n        {\n            TextureEntry entry = manager.getParticle();\n            writer.write(space(2) + \"\\\"particle\\\": \\\"\" + entry.getModId() + \":\");\n            if(!entry.getDirectory().isEmpty())\n            {\n                writer.write(entry.getDirectory() + \"/\");\n            }\n            writer.write(entry.getName() + \"\\\"\");\n            if(textureMap.size() > 0)\n            {\n                writer.write(\",\");\n            }\n            writer.newLine();\n        }\n\n        List<String> ids = new ArrayList<>(textureMap.keySet());\n        for(int i = 0; i < ids.size() - 1; i++)\n        {\n            String id = ids.get(i);\n            String texture = textureMap.get(id);\n            writer.write(space(2) + \"\\\"\" + id + \"\\\": \\\"\" + texture + \"\\\"\");\n            writer.write(\",\");\n            writer.newLine();\n        }\n        if(ids.size() > 0)\n        {\n            String id = ids.get(ids.size() - 1);\n            String texture = textureMap.get(id);\n            writer.write(space(2) + \"\\\"\" + id + \"\\\": \\\"\" + texture + \"\\\"\");\n            writer.newLine();\n        }\n\n        writer.write(space(1) + \"},\");\n    }\n\n    private void writeElement(BufferedWriter writer, Element cuboid) throws IOException\n    {\n        writer.newLine();\n        writer.write(space(2) + \"{\");\n        writer.newLine();\n        if(includeNames)\n        {\n            writer.write(space(3) + \"\\\"name\\\": \\\"\" + cuboid.getName() + \"\\\",\");\n            writer.newLine();\n        }\n        writeBounds(writer, cuboid);\n        writer.newLine();\n        if(!cuboid.isShaded())\n        {\n            writeShade(writer, cuboid);\n            writer.newLine();\n        }\n        if(cuboid.getRotation() != 0)\n        {\n            writeRotation(writer, cuboid);\n            writer.newLine();\n        }\n        writeFaces(writer, cuboid);\n        writer.newLine();\n        writer.write(space(2) + \"}\");\n    }\n\n    private void writeBounds(BufferedWriter writer, Element cuboid) throws IOException\n    {\n        writer.write(space(3) + \"\\\"from\\\": [ \" + FORMAT.format(cuboid.getStartX()) + \", \" + FORMAT.format(cuboid.getStartY()) + \", \" + FORMAT.format(cuboid.getStartZ()) + \" ], \");\n        writer.newLine();\n        writer.write(space(3) + \"\\\"to\\\": [ \" + FORMAT.format(cuboid.getStartX() + cuboid.getWidth()) + \", \" + FORMAT.format(cuboid.getStartY() + cuboid.getHeight()) + \", \" + FORMAT.format(cuboid.getStartZ() + cuboid.getDepth()) + \" ], \");\n    }\n\n    private void writeShade(BufferedWriter writer, Element cuboid) throws IOException\n    {\n        writer.write(space(3) + \"\\\"shade\\\": \" + cuboid.isShaded() + \",\");\n    }\n\n    private void writeRotation(BufferedWriter writer, Element cuboid) throws IOException\n    {\n        writer.write(space(3) + \"\\\"rotation\\\": { \");\n        writer.write(\"\\\"origin\\\": [ \" + FORMAT.format(cuboid.getOriginX()) + \", \" + FORMAT.format(cuboid.getOriginY()) + \", \" + FORMAT.format(cuboid.getOriginZ()) + \" ], \");\n        writer.write(\"\\\"axis\\\": \\\"\" + Element.parseAxis(cuboid.getRotationAxis()) + \"\\\", \");\n        writer.write(\"\\\"angle\\\": \" + cuboid.getRotation());\n        if(cuboid.shouldRescale())\n        {\n            writer.write(\", \\\"rescale\\\": \" + cuboid.shouldRescale());\n        }\n        writer.write(\" },\");\n    }\n\n    private void writeFaces(BufferedWriter writer, Element cuboid) throws IOException\n    {\n        writer.write(space(3) + \"\\\"faces\\\": {\");\n        writer.newLine();\n\n        /* Creates a list of all the valid faces to export */\n        List<Face> validFaces = new ArrayList<>();\n        for(Face face : cuboid.getAllFaces())\n        {\n            if(face.isEnabled() && (includeNonTexturedFaces || face.getTexture() != null) && (!optimize || face.isVisible(manager)))\n            {\n                validFaces.add(face);\n            }\n        }\n\n        /* Writes the valid faces to the writer */\n        for(int i = 0; i < validFaces.size() - 1; i++)\n        {\n            Face face = validFaces.get(i);\n            writeFace(writer, face);\n            writer.write(\",\");\n            writer.newLine();\n        }\n        if(validFaces.size() > 0)\n        {\n            writeFace(writer, validFaces.get(validFaces.size() - 1));\n        }\n\n        writer.newLine();\n        writer.write(space(3) + \"}\");\n    }\n\n    private void writeFace(BufferedWriter writer, Face face) throws IOException\n    {\n        writer.write(space(4) + \"\\\"\" + Face.getFaceName(face.getSide()) + \"\\\": { \");\n        if(face.getTexture() != null)\n        {\n            writer.write(\"\\\"texture\\\": \\\"#\" + face.getTexture().getKey() + \"\\\"\");\n            writer.write(\", \\\"uv\\\": [ \" + FORMAT.format(face.getStartU()) + \", \" + FORMAT.format(face.getStartV()) + \", \" + FORMAT.format(face.getEndU()) + \", \" + FORMAT.format(face.getEndV()) + \" ]\");\n            if(face.getRotation() > 0)\n            {\n                writer.write(\", \\\"rotation\\\": \" + face.getRotation() * 90);\n            }\n            if(face.isCullfaced())\n            {\n                writer.write(\", \\\"cullface\\\": \\\"\" + Face.getFaceName(face.getSide()) + \"\\\"\");\n            }\n            if(face.isTintIndexEnabled() && face.getTintIndex() >= 0)\n            {\n                writer.write(\", \\\"tintindex\\\": \" + face.getTintIndex());\n            }\n        }\n        writer.write(\" }\");\n    }\n\n    private void writeDisplayProperties(BufferedWriter writer) throws IOException\n    {\n        Map<String, DisplayProperties.Entry> entries = manager.getDisplayProperties().getEntries();\n        List<String> ids = new ArrayList<>();\n        for(String id : DISPLAY_PROPERTY_ORDER)\n        {\n            DisplayProperties.Entry entry = entries.get(id);\n            if(entry != null && entry.isEnabled())\n            {\n                ids.add(id);\n            }\n        }\n\n        writer.write(space(1) + \"\\\"display\\\": {\");\n        writer.newLine();\n\n        for(int i = 0; i < ids.size() - 1; i++)\n        {\n            String key = ids.get(i);\n            writeDisplayEntry(writer, key, entries.get(key));\n            writer.write(\",\");\n            writer.newLine();\n        }\n        if(ids.size() > 0)\n        {\n            String key = ids.get(ids.size() - 1);\n            writeDisplayEntry(writer, key, entries.get(key));\n        }\n\n        writer.newLine();\n        writer.write(space(1) + \"},\");\n    }\n\n    private void writeDisplayEntry(BufferedWriter writer, String id, DisplayProperties.Entry entry) throws IOException\n    {\n        writer.write(space(2) + \"\\\"\" + id + \"\\\": {\");\n        writer.newLine();\n        writer.write(space(3) + String.format(\"\\\"rotation\\\": [ %s, %s, %s ],\", FORMAT.format(entry.getRotationX()), FORMAT.format(entry.getRotationY()), FORMAT.format(entry.getRotationZ())));\n        writer.newLine();\n        writer.write(space(3) + String.format(\"\\\"translation\\\": [ %s, %s, %s ],\", FORMAT.format(entry.getTranslationX()), FORMAT.format(entry.getTranslationY()), FORMAT.format(entry.getTranslationZ())));\n        writer.newLine();\n        writer.write(space(3) + String.format(\"\\\"scale\\\": [ %s, %s, %s ]\", FORMAT.format(entry.getScaleX()), FORMAT.format(entry.getScaleY()), FORMAT.format(entry.getScaleZ())));\n        writer.newLine();\n        writer.write(space(2) + \"}\");\n    }\n\n    private boolean canWriteElement(Element element)\n    {\n        for(Face face : element.getAllFaces())\n        {\n            if(face.isEnabled() && (includeNonTexturedFaces || face.getTexture() != null) && (!optimize || face.isVisible(manager)))\n            {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Icons.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport javax.swing.*;\n\npublic class Icons\n{\n    public static Icon bin;\n    public static Icon new_;\n    public static Icon import_;\n    public static Icon export;\n    public static Icon texture;\n    public static Icon clear_texture;\n    public static Icon copy;\n    public static Icon copy_small;\n    public static Icon clipboard;\n    public static Icon clipboard_texture;\n    public static Icon transparent;\n    public static Icon coin;\n    public static Icon load;\n    public static Icon disk;\n    public static Icon exit;\n    public static Icon settings;\n    public static Icon cube;\n    public static Icon light_off;\n    public static Icon light_on;\n    public static Icon arrow_up;\n    public static Icon arrow_down;\n    public static Icon facebook;\n    public static Icon twitter;\n    public static Icon reddit;\n    public static Icon imgur;\n    public static Icon patreon;\n    public static Icon planet_minecraft;\n    public static Icon minecraft_forum;\n    public static Icon github;\n    public static Icon model_cauldron;\n    public static Icon model_chair;\n    public static Icon extract;\n    public static Icon mojang;\n    public static Icon java;\n    public static Icon undo;\n    public static Icon redo;\n    public static Icon optimize;\n    public static Icon rotate;\n    public static Icon rotate_clockwise;\n    public static Icon rotate_counter_clockwise;\n    public static Icon refresh;\n    public static Icon gallery;\n    public static Icon bin2;\n    public static Icon edit;\n    public static Icon edit_image;\n\n    public static void init(Class<?> clazz)\n    {\n        ClassLoader loader = clazz.getClassLoader();\n        cube = new ImageIcon(loader.getResource(\"icons/cube.png\"));\n        bin = new ImageIcon(loader.getResource(\"icons/bin.png\"));\n        new_ = new ImageIcon(loader.getResource(\"icons/new.png\"));\n        import_ = new ImageIcon(loader.getResource(\"icons/import.png\"));\n        export = new ImageIcon(loader.getResource(\"icons/export.png\"));\n        texture = new ImageIcon(loader.getResource(\"icons/texture.png\"));\n        clear_texture = new ImageIcon(loader.getResource(\"icons/clear_texture.png\"));\n        copy = new ImageIcon(loader.getResource(\"icons/copy.png\"));\n        copy_small = new ImageIcon(loader.getResource(\"icons/copy_small.png\"));\n        clipboard = new ImageIcon(loader.getResource(\"icons/clipboard.png\"));\n        clipboard_texture = new ImageIcon(loader.getResource(\"icons/paste_texture.png\"));\n        transparent = new ImageIcon(loader.getResource(\"icons/transparent.png\"));\n        coin = new ImageIcon(loader.getResource(\"icons/coin.png\"));\n        load = new ImageIcon(loader.getResource(\"icons/load.png\"));\n        disk = new ImageIcon(loader.getResource(\"icons/disk.png\"));\n        exit = new ImageIcon(loader.getResource(\"icons/exit.png\"));\n        settings = new ImageIcon(loader.getResource(\"icons/settings.png\"));\n        extract = new ImageIcon(loader.getResource(\"icons/extract.png\"));\n        light_off = new ImageIcon(loader.getResource(\"icons/box_off.png\"));\n        light_on = new ImageIcon(loader.getResource(\"icons/box_on.png\"));\n        arrow_up = new ImageIcon(loader.getResource(\"icons/arrow_up.png\"));\n        arrow_down = new ImageIcon(loader.getResource(\"icons/arrow_down.png\"));\n        facebook = new ImageIcon(loader.getResource(\"icons/facebook.png\"));\n        twitter = new ImageIcon(loader.getResource(\"icons/twitter.png\"));\n        reddit = new ImageIcon(loader.getResource(\"icons/reddit.png\"));\n        imgur = new ImageIcon(loader.getResource(\"icons/imgur.png\"));\n        patreon = new ImageIcon(loader.getResource(\"icons/patreon.png\"));\n        planet_minecraft = new ImageIcon(loader.getResource(\"icons/planet_minecraft.png\"));\n        minecraft_forum = new ImageIcon(loader.getResource(\"icons/minecraft_forum.png\"));\n        github = new ImageIcon(loader.getResource(\"icons/github.png\"));\n        model_cauldron = new ImageIcon(loader.getResource(\"icons/model_cauldron.png\"));\n        model_chair = new ImageIcon(loader.getResource(\"icons/model_chair.png\"));\n        mojang = new ImageIcon(loader.getResource(\"icons/mojang.png\"));\n        java = new ImageIcon(loader.getResource(\"icons/java.png\"));\n        undo = new ImageIcon(loader.getResource(\"icons/undo.png\"));\n        redo = new ImageIcon(loader.getResource(\"icons/redo.png\"));\n        optimize = new ImageIcon(loader.getResource(\"icons/optimize.png\"));\n        rotate = new ImageIcon(loader.getResource(\"icons/rotate.png\"));\n        rotate_clockwise = new ImageIcon(loader.getResource(\"icons/rotate_clockwise.png\"));\n        rotate_counter_clockwise = new ImageIcon(loader.getResource(\"icons/rotate_anticlockwise.png\"));\n        refresh = new ImageIcon(loader.getResource(\"icons/refresh.png\"));\n        gallery = new ImageIcon(loader.getResource(\"icons/gallery.png\"));\n        bin2 = new ImageIcon(loader.getResource(\"icons/bin2.png\"));\n        edit = new ImageIcon(loader.getResource(\"icons/edit.png\"));\n        edit_image = new ImageIcon(loader.getResource(\"icons/edit_image.png\"));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Importer.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.ResourceBundle;\n\npublic class Importer\n{\n    private Map<String, String> textureMap = new HashMap<>();\n    private String[] faceNames = {\"north\", \"east\", \"south\", \"west\", \"up\", \"down\"};\n    private String[] displayNames = {\"gui\", \"ground\", \"fixed\", \"head\", \"firstperson_righthand\", \"firstperson_lefthand\", \"thirdperson_righthand\", \"thirdperson_lefthand\"};\n\n    // Input File\n    private String inputPath;\n\n    // Model Variables\n    private ElementManager manager;\n\n    public Importer(ElementManager manager, String outputPath)\n    {\n        this.manager = manager;\n        this.inputPath = outputPath;\n    }\n\n    public void importFromJSON()\n    {\n        File path = new File(inputPath);\n\n        if(path.exists() && path.isFile())\n        {\n            FileReader fr;\n            BufferedReader reader;\n            try\n            {\n                fr = new FileReader(path);\n                reader = new BufferedReader(fr);\n                readComponents(reader, manager, path.getParentFile());\n                reader.close();\n                fr.close();\n            }\n            catch(IOException e)\n            {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private void readComponents(BufferedReader reader, ElementManager manager, File dir) throws IOException\n    {\n        manager.clearElements();\n        manager.setParticle(null);\n        manager.setDisplayProperties(DisplayProperties.MODEL_CREATOR_BLOCK);\n\n        JsonParser parser = new JsonParser();\n        JsonElement read = parser.parse(reader);\n\n        if(read.isJsonObject())\n        {\n            JsonObject obj = read.getAsJsonObject();\n\n            if(obj.has(\"parent\") && obj.get(\"parent\").isJsonPrimitive())\n            {\n                String parent = obj.get(\"parent\").getAsString();\n                File file = new File(dir, parent + \".json\");\n                if(!file.exists())\n                {\n                    parent = parent.substring(parent.lastIndexOf('/') + 1, parent.length());\n                    file = new File(dir, parent + \".json\");\n                }\n\n                if(file.exists())\n                {\n                    // load textures\n                    loadTextures(dir, obj);\n\n                    // Load Parent\n                    FileReader fr = new FileReader(file);\n                    reader = new BufferedReader(fr);\n                    readComponents(reader, manager, file.getParentFile());\n                    reader.close();\n                    fr.close();\n                }\n\n                return;\n            }\n\n            // load textures\n            loadTextures(dir, obj);\n\n            // load display properties\n            if(obj.has(\"display\") && obj.get(\"display\").isJsonObject())\n            {\n                readDisplayProperties(obj.getAsJsonObject(\"display\"), manager);\n            }\n\n            // load elements\n            if(obj.has(\"elements\") && obj.get(\"elements\").isJsonArray())\n            {\n                JsonArray elements = obj.get(\"elements\").getAsJsonArray();\n\n                for(int i = 0; i < elements.size(); i++)\n                {\n                    if(elements.get(i).isJsonObject())\n                    {\n                        readElement(elements.get(i).getAsJsonObject(), manager);\n                    }\n                }\n            }\n\n            manager.setAmbientOcc(true);\n            if(obj.has(\"ambientocclusion\") && obj.get(\"ambientocclusion\").isJsonPrimitive())\n            {\n                manager.setAmbientOcc(obj.get(\"ambientocclusion\").getAsBoolean());\n            }\n        }\n    }\n\n    private void loadTextures(File file, JsonObject obj)\n    {\n        if(obj.has(\"textures\") && obj.get(\"textures\").isJsonObject())\n        {\n            JsonObject textures = obj.get(\"textures\").getAsJsonObject();\n\n            for(Entry<String, JsonElement> entry : textures.entrySet())\n            {\n                if(entry.getValue().isJsonPrimitive())\n                {\n                    String key = entry.getKey().trim().toLowerCase(Locale.ENGLISH);\n                    String value = entry.getValue().getAsString().trim().toLowerCase(Locale.ENGLISH);\n                    if(!textureMap.containsKey(key))\n                    {\n                        if(key.equals(\"particle\"))\n                        {\n                            manager.setParticle(this.loadTexture(file, key, value));\n                        }\n                        else if(!value.startsWith(\"#\"))\n                        {\n                            textureMap.put(key, value);\n                            this.loadTexture(file, key, value);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private TextureEntry loadTexture(File project, String id, String texture)\n    {\n        TexturePath texturePath = new TexturePath(texture);\n\n        /* Try loading textures as project format */\n        if(project != null)\n        {\n            File path = new File(project, \"textures\");\n            if(path.exists() && path.isDirectory())\n            {\n                File textureFile = new File(path, texturePath.getName() + \".png\");\n                if(textureFile.exists() && textureFile.isFile())\n                {\n                    return TextureManager.addImage(id, texturePath, textureFile);\n                }\n            }\n\n            /* V2 of project file format uses assets folder */\n            File assets = new File(project, \"assets\");\n            if(assets.exists() && assets.isDirectory())\n            {\n                File textureFile = new File(assets, texturePath.toRelativePath());\n                if(textureFile.exists() && textureFile.isFile())\n                {\n                    return TextureManager.addImage(id, texturePath, textureFile);\n                }\n            }\n        }\n\n        /* Try loading textures as if it was from assets */\n        File parent = project;\n        if(parent != null)\n        {\n            while((parent = parent.getParentFile()) != null)\n            {\n                if(parent.getName().equals(\"assets\"))\n                {\n                    File textureFile = new File(parent, texturePath.toRelativePath());\n                    if(textureFile.exists() && textureFile.isFile())\n                    {\n                        return TextureManager.addImage(id, texturePath, textureFile);\n                    }\n                }\n                else if(parent.getName().equals(texturePath.getModId()))\n                {\n                    File textureFile = new File(parent, \"textures\" + File.separator + texturePath.getDirectory() + File.separator + texturePath.getName() + \".png\");\n                    if(textureFile.exists() && textureFile.isFile())\n                    {\n                        return TextureManager.addImage(id, texturePath, textureFile);\n                    }\n                }\n            }\n        }\n\n        /* Try loading textures from assets directory */\n        if(Settings.getAssetsDir() != null)\n        {\n            String path = Settings.getAssetsDir() + File.separator + texturePath.toRelativePath();\n            File textureFile = new File(path);\n            if(textureFile.exists())\n            {\n                return TextureManager.addImage(id, texturePath, textureFile);\n            }\n        }\n\n        return null;\n    }\n\n    private void readDisplayProperties(JsonObject obj, ElementManager manager)\n    {\n        DisplayProperties properties = manager.getDisplayProperties();\n        properties.getEntries().forEach((s, entry) -> entry.setEnabled(false));\n        for(String displayName : displayNames)\n        {\n            if(obj.has(displayName) && obj.get(displayName).isJsonObject())\n            {\n                readEntry(obj.getAsJsonObject(displayName), displayName, properties);\n            }\n        }\n    }\n\n    private void readEntry(JsonObject obj, String id, DisplayProperties properties)\n    {\n        DisplayProperties.Entry entry = new DisplayProperties.Entry(id, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);\n        if(obj.has(\"rotation\") && obj.get(\"rotation\").isJsonArray())\n        {\n            JsonArray array = obj.get(\"rotation\").getAsJsonArray();\n            if(array.size() == 3)\n            {\n                entry.setRotationX(array.get(0).getAsDouble());\n                entry.setRotationY(array.get(1).getAsDouble());\n                entry.setRotationZ(array.get(2).getAsDouble());\n            }\n        }\n        if(obj.has(\"translation\") && obj.get(\"translation\").isJsonArray())\n        {\n            JsonArray array = obj.get(\"translation\").getAsJsonArray();\n            if(array.size() == 3)\n            {\n                entry.setTranslationX(array.get(0).getAsDouble());\n                entry.setTranslationY(array.get(1).getAsDouble());\n                entry.setTranslationZ(array.get(2).getAsDouble());\n            }\n        }\n        if(obj.has(\"scale\") && obj.get(\"scale\").isJsonArray())\n        {\n            JsonArray array = obj.get(\"scale\").getAsJsonArray();\n            if(array.size() == 3)\n            {\n                entry.setScaleX(array.get(0).getAsDouble());\n                entry.setScaleY(array.get(1).getAsDouble());\n                entry.setScaleZ(array.get(2).getAsDouble());\n            }\n        }\n        properties.getEntries().put(id, entry);\n    }\n\n    private void readElement(JsonObject obj, ElementManager manager)\n    {\n        String name = \"Element\";\n        JsonArray from = null;\n        JsonArray to = null;\n\n        if(obj.has(\"name\") && obj.get(\"name\").isJsonPrimitive())\n        {\n            name = obj.get(\"name\").getAsString();\n        }\n        else if(obj.has(\"comment\") && obj.get(\"comment\").isJsonPrimitive())\n        {\n            name = obj.get(\"comment\").getAsString();\n        }\n        else if(obj.has(\"__comment\") && obj.get(\"__comment\").isJsonPrimitive())\n        {\n            name = obj.get(\"__comment\").getAsString();\n        }\n        if(obj.has(\"from\") && obj.get(\"from\").isJsonArray())\n        {\n            from = obj.get(\"from\").getAsJsonArray();\n        }\n        if(obj.has(\"to\") && obj.get(\"to\").isJsonArray())\n        {\n            to = obj.get(\"to\").getAsJsonArray();\n        }\n\n        if(from != null && to != null)\n        {\n            double x = from.get(0).getAsDouble();\n            double y = from.get(1).getAsDouble();\n            double z = from.get(2).getAsDouble();\n\n            double w = to.get(0).getAsDouble() - x;\n            double h = to.get(1).getAsDouble() - y;\n            double d = to.get(2).getAsDouble() - z;\n\n            Element element = new Element(w, h, d);\n            element.setName(name);\n            element.setStartX(x);\n            element.setStartY(y);\n            element.setStartZ(z);\n\n            if(obj.has(\"rotation\") && obj.get(\"rotation\").isJsonObject())\n            {\n                JsonObject rot = obj.get(\"rotation\").getAsJsonObject();\n\n                if(rot.has(\"origin\") && rot.get(\"origin\").isJsonArray())\n                {\n                    JsonArray origin = rot.get(\"origin\").getAsJsonArray();\n\n                    double ox = origin.get(0).getAsDouble();\n                    double oy = origin.get(1).getAsDouble();\n                    double oz = origin.get(2).getAsDouble();\n\n                    element.setOriginX(ox);\n                    element.setOriginY(oy);\n                    element.setOriginZ(oz);\n                }\n\n                if(rot.has(\"axis\") && rot.get(\"axis\").isJsonPrimitive())\n                {\n                    element.setRotationAxis(Element.parseAxisString(rot.get(\"axis\").getAsString()));\n                }\n\n                if(rot.has(\"angle\") && rot.get(\"angle\").isJsonPrimitive())\n                {\n                    element.setRotation(rot.get(\"angle\").getAsDouble());\n                }\n\n                if(rot.has(\"rescale\") && rot.get(\"rescale\").isJsonPrimitive())\n                {\n                    element.setRescale(rot.get(\"rescale\").getAsBoolean());\n                }\n            }\n\n            element.setShade(true);\n            if(obj.has(\"shade\") && obj.get(\"shade\").isJsonPrimitive())\n            {\n                element.setShade(obj.get(\"shade\").getAsBoolean());\n            }\n\n            for(Face face : element.getAllFaces())\n            {\n                face.setEnabled(false);\n            }\n\n            if(obj.has(\"faces\") && obj.get(\"faces\").isJsonObject())\n            {\n                JsonObject faces = obj.get(\"faces\").getAsJsonObject();\n\n                for(String faceName : faceNames)\n                {\n                    if(faces.has(faceName) && faces.get(faceName).isJsonObject())\n                    {\n                        readFace(faces.get(faceName).getAsJsonObject(), faceName, element);\n                    }\n                }\n            }\n\n            manager.addElement(element);\n        }\n    }\n\n    private void readFace(JsonObject obj, String name, Element element)\n    {\n        Face face = null;\n        for(Face f : element.getAllFaces())\n        {\n            if(f.getSide() == Face.getFaceSide(name))\n            {\n                face = f;\n            }\n        }\n\n        if(face != null)\n        {\n            face.setEnabled(true);\n\n            // automatically set uv if not specified\n            face.setEndU(element.getFaceDimension(face.getSide()).getWidth());\n            face.setEndV(element.getFaceDimension(face.getSide()).getHeight());\n            face.setAutoUVEnabled(true);\n\n            if(obj.has(\"uv\") && obj.get(\"uv\").isJsonArray())\n            {\n                JsonArray uv = obj.get(\"uv\").getAsJsonArray();\n\n                double uStart = uv.get(0).getAsDouble();\n                double vStart = uv.get(1).getAsDouble();\n                double uEnd = uv.get(2).getAsDouble();\n                double vEnd = uv.get(3).getAsDouble();\n\n                face.setStartU(uStart);\n                face.setStartV(vStart);\n                face.setEndU(uEnd);\n                face.setEndV(vEnd);\n\n                if(element.getFaceDimension(face.getSide()).getWidth() != face.getEndU() - face.getStartU() || element.getFaceDimension(face.getSide()).getHeight() != face.getEndV() - face.getStartV())\n                {\n                    face.setAutoUVEnabled(false);\n                }\n            }\n\n            if(obj.has(\"texture\") && obj.get(\"texture\").isJsonPrimitive())\n            {\n                String id = obj.get(\"texture\").getAsString().replace(\"#\", \"\");\n                TextureEntry entry = TextureManager.getTexture(id);\n                if(entry != null)\n                {\n                    face.setTexture(entry);\n                }\n            }\n\n            if(obj.has(\"rotation\") && obj.get(\"rotation\").isJsonPrimitive())\n            {\n                face.setRotation((int) obj.get(\"rotation\").getAsDouble() / 90);\n            }\n\n            // TODO cullface with different direction than face,tintindex\n            if(obj.has(\"cullface\") && obj.get(\"cullface\").isJsonPrimitive())\n            {\n                String cullface = obj.get(\"cullface\").getAsString();\n\n                if(cullface.equals(Face.getFaceName(face.getSide())))\n                {\n                    face.setCullface(true);\n                }\n            }\n\n            if(obj.has(\"tintindex\") && obj.get(\"tintindex\").isJsonPrimitive())\n            {\n                int tintIndex = obj.get(\"tintindex\").getAsInt();\n                if(tintIndex >= 0)\n                {\n                    face.setTintIndexEnabled(true);\n                    face.setTintIndex(tintIndex);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ModelCreator.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish.modelcreator.dialog.WelcomeDialog;\nimport com.mrcrayfish.modelcreator.display.CanvasRenderer;\nimport com.mrcrayfish.modelcreator.display.render.StandardRenderer;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementCellEntry;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.ElementManagerState;\nimport com.mrcrayfish.modelcreator.panels.SidebarPanel;\nimport com.mrcrayfish.modelcreator.screenshot.PendingScreenshot;\nimport com.mrcrayfish.modelcreator.screenshot.Screenshot;\nimport com.mrcrayfish.modelcreator.sidebar.Sidebar;\nimport com.mrcrayfish.modelcreator.sidebar.UVSidebar;\nimport com.mrcrayfish.modelcreator.texture.TextureAtlas;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport com.mrcrayfish.modelcreator.util.FontManager;\nimport com.mrcrayfish.modelcreator.util.KeyboardUtil;\nimport org.lwjgl.LWJGLException;\nimport org.lwjgl.input.Keyboard;\nimport org.lwjgl.input.Mouse;\nimport org.lwjgl.opengl.AWTGLCanvas;\nimport org.lwjgl.opengl.Display;\nimport org.lwjgl.opengl.GL11;\nimport org.lwjgl.opengl.PixelFormat;\nimport org.lwjgl.util.glu.GLU;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.IntBuffer;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport static org.lwjgl.opengl.GL11.*;\n\npublic class ModelCreator extends JFrame\n{\n    public static final Color BACKGROUND = new Color(227, 227, 234);\n\n    // Canvas Variables\n    private final static AtomicReference<Dimension> newCanvasSize = new AtomicReference<>();\n    private Canvas canvas;\n    private int width = 990, height = 800;\n\n    // Swing Components\n    private JScrollPane scroll;\n    private Camera camera;\n    private SidebarPanel manager;\n    private Element grabbed = null;\n\n    // Texture Loading Cache\n    private PendingScreenshot screenshot = null;\n\n    private int lastMouseX, lastMouseY;\n    private boolean grabbing = false;\n    private boolean closeRequested = false;\n    private boolean performedChange = false;\n    private boolean grabbingInSidebar = false;\n\n    /* Sidebar Variables */\n    private final int SIDEBAR_WIDTH = 130;\n    private Sidebar activeSidebar = null;\n    public static Sidebar uvSidebar;\n    public static boolean isUVSidebarOpen = false;\n\n    /* Key Events */\n    private Set<Integer> keyDown = new HashSet<>();\n    private List<KeyAction> keyActions = new ArrayList<>();\n\n    private static boolean changedCanvas = false;\n    private static CanvasRenderer standardRenderer = new StandardRenderer();\n    private static CanvasRenderer canvasRenderer = standardRenderer;\n\n    private boolean debugMode = false;\n\n    public ModelCreator(String title)\n    {\n        super(title);\n\n        setPreferredSize(new Dimension(1200, 815));\n        setMinimumSize(new Dimension(1200, 500));\n        setLayout(new BorderLayout(10, 0));\n        setIconImages(getIcons());\n        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);\n\n        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(e ->\n        {\n            if(e.getID() == KeyEvent.KEY_PRESSED && !keyDown.contains(e.getKeyCode()))\n            {\n                keyDown.add(e.getKeyCode());\n                ModelCreator.this.handleKeyAction(e.getKeyCode(), e.getModifiers(), true, true);\n            }\n            else if(e.getID() == KeyEvent.KEY_RELEASED)\n            {\n                keyDown.remove(e.getKeyCode());\n                ModelCreator.this.handleKeyAction(e.getKeyCode(), e.getModifiers(), true, false);\n            }\n            return false;\n        });\n\n        try\n        {\n            canvas = new AWTGLCanvas();\n            canvas.addComponentListener(new ComponentAdapter()\n            {\n                @Override\n                public void componentResized(ComponentEvent e)\n                {\n                    newCanvasSize.set(canvas.getSize());\n                }\n            });\n            addWindowFocusListener(new WindowAdapter()\n            {\n                @Override\n                public void windowGainedFocus(WindowEvent e)\n                {\n                    canvas.requestFocusInWindow();\n                }\n            });\n        }\n        catch(LWJGLException e)\n        {\n            e.printStackTrace();\n            System.exit(1);\n        }\n\n        initComponents();\n        registerShortcuts();\n\n        uvSidebar = new UVSidebar(\"UV Editor\", manager);\n\n        addWindowListener(new WindowAdapter()\n        {\n            @Override\n            public void windowClosing(WindowEvent e)\n            {\n                closeRequested = true;\n            }\n        });\n\n        manager.updateValues();\n\n        pack();\n        setVisible(true);\n        setLocationRelativeTo(null);\n\n        SwingUtilities.invokeLater(() -> WelcomeDialog.show(ModelCreator.this));\n\n        createDisplay();\n\n        loop();\n\n        Display.destroy();\n        dispose();\n        System.exit(0);\n    }\n\n    private void initComponents()\n    {\n        Icons.init(this.getClass());\n        setupMenuBar();\n\n        canvas.setFocusable(true);\n        add(canvas, BorderLayout.CENTER);\n\n        manager = new SidebarPanel(this);\n        scroll = new JScrollPane(manager);\n        scroll.setBorder(BorderFactory.createEmptyBorder());\n        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);\n        scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);\n        add(scroll, BorderLayout.EAST);\n        StateManager.pushState(manager);\n    }\n\n    private void registerShortcuts()\n    {\n        this.keyActions.add(new KeyAction(KeyEvent.VK_E, Keyboard.KEY_E, (modifiers, pressed) ->\n        {\n            if(pressed && modifiers == InputEvent.CTRL_MASK)\n            {\n                manager.newElement();\n            }\n        }));\n        this.keyActions.add(new KeyAction(KeyEvent.VK_D, Keyboard.KEY_D, (modifiers, pressed) ->\n        {\n            if(pressed && (modifiers & InputEvent.CTRL_MASK) != 0 && (modifiers & InputEvent.SHIFT_MASK) != 0 && (modifiers & InputEvent.ALT_MASK) != 0)\n            {\n                debugMode = !debugMode;\n            }\n        }));\n        this.keyActions.add(new KeyAction(KeyEvent.VK_F, Keyboard.KEY_F, (modifiers, pressed) ->\n        {\n            if(pressed && modifiers == InputEvent.CTRL_MASK)\n            {\n                ElementCellEntry entry = manager.getSelectedElementEntry();\n                if(entry != null)\n                {\n                    entry.toggleVisibility();\n                    manager.getList().repaint();\n                }\n            }\n        }));\n        this.keyActions.add(new KeyAction(KeyEvent.VK_D, Keyboard.KEY_D, (modifiers, pressed) ->\n        {\n            if(pressed && modifiers == InputEvent.CTRL_MASK)\n            {\n                manager.deleteElement();\n            }\n        }));\n    }\n\n    private List<Image> getIcons()\n    {\n        List<Image> icons = new ArrayList<>();\n        icons.add(Toolkit.getDefaultToolkit().getImage(\"res/icons/set/icon_16x.png\"));\n        icons.add(Toolkit.getDefaultToolkit().getImage(\"res/icons/set/icon_32x.png\"));\n        icons.add(Toolkit.getDefaultToolkit().getImage(\"res/icons/set/icon_64x.png\"));\n        icons.add(Toolkit.getDefaultToolkit().getImage(\"res/icons/set/icon_128x.png\"));\n        return icons;\n    }\n\n    public int getCanvasWidth()\n    {\n        return width;\n    }\n\n    public int getCanvasHeight()\n    {\n        return height;\n    }\n\n    public int getCanvasOffset()\n    {\n        return activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;\n    }\n\n    private void setupMenuBar()\n    {\n        JPopupMenu.setDefaultLightWeightPopupEnabled(false);\n        setJMenuBar(new com.mrcrayfish.modelcreator.component.Menu(this));\n    }\n\n    private void createDisplay()\n    {\n        try\n        {\n            Display.setVSyncEnabled(true);\n            Display.setInitialBackground(0.92F, 0.92F, 0.93F);\n            Display.setParent(canvas);\n\n        }\n        catch(LWJGLException e)\n        {\n            e.printStackTrace();\n        }\n\n        try\n        {\n            Display.create((new PixelFormat(8, 0, 0, 4)).withDepthBits(24));\n            return;\n        }\n        catch(LWJGLException e)\n        {\n            e.printStackTrace();\n        }\n\n        try\n        {\n            Thread.sleep(1000L);\n        }\n        catch(InterruptedException ignored)\n        {\n        }\n\n        try\n        {\n            Display.create();\n        }\n        catch(LWJGLException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    private void loop()\n    {\n        TextureAtlas.load();\n\n        camera = new Camera(60F, (float) Display.getWidth() / (float) Display.getHeight(), 0.3F, 1000F);\n\n        long lastTime = System.nanoTime();\n        double delta = 0.0;\n        double ns = 1000000000.0 / 60.0;\n        long timer = System.currentTimeMillis();\n        int updates = 0;\n        int frames = 0;\n        while(!Display.isCloseRequested() && !getCloseRequested())\n        {\n            long now = System.nanoTime();\n            delta += (now - lastTime) / ns;\n            lastTime = now;\n            if(delta >= 1.0)\n            {\n                tick();\n                updates++;\n                delta--;\n            }\n            render(frames / 60.0F);\n            frames++;\n            if(System.currentTimeMillis() - timer > 1000)\n            {\n                timer += 1000;\n                updates = 0;\n                frames = 0;\n            }\n        }\n    }\n\n    private void tick()\n    {\n        Animation.tick();\n    }\n\n    private void render(float partialTicks)\n    {\n        Animation.setPartialTicks(partialTicks);\n        TextureManager.processPendingTextures();\n\n        Dimension newDim = newCanvasSize.getAndSet(null);\n        if (newDim != null)\n        {\n            width = newDim.width;\n            height = newDim.height;\n        }\n\n        this.handleKeyboardInput();\n\n        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);\n\n        this.draw();\n\n        Display.update();\n\n        if(screenshot != null)\n        {\n            if(screenshot.getFile() != null)\n            {\n                Screenshot.getScreenshot(width, height, screenshot.getCallback(), screenshot.getFile());\n            }\n            else\n            {\n                Screenshot.getScreenshot(width, height, screenshot.getCallback());\n            }\n            screenshot = null;\n        }\n    }\n\n    private void handleKeyboardInput()\n    {\n        while(Keyboard.next())\n        {\n            int modifiers = 0;\n            if(KeyboardUtil.isCtrlKeyDown())\n            {\n                modifiers += InputEvent.CTRL_MASK;\n            }\n            if(KeyboardUtil.isShiftKeyDown())\n            {\n                modifiers += InputEvent.SHIFT_MASK;\n            }\n            if(KeyboardUtil.isAltKeyDown())\n            {\n                modifiers += InputEvent.ALT_MASK;\n            }\n\n            int code = Keyboard.getEventKey();\n            int finalModifiers = modifiers;\n            if(Keyboard.getEventKeyState())\n            {\n                SwingUtilities.invokeLater(() -> this.handleKeyAction(code, finalModifiers, false, true));\n            }\n            else\n            {\n                SwingUtilities.invokeLater(() -> this.handleKeyAction(code, finalModifiers, false, false));\n            }\n        }\n    }\n\n    private void draw()\n    {\n        if(changedCanvas)\n        {\n            canvasRenderer.onInit(camera);\n            changedCanvas = false;\n        }\n\n        int offset = activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;\n\n        glViewport(offset, 0, width - offset, height);\n\n        this.handleInput(offset);\n\n        glMatrixMode(GL_PROJECTION);\n        glLoadIdentity();\n        GLU.gluPerspective(60F, (float) (width - offset) / (float) height, 0.3F, 1000F);\n\n        canvasRenderer.onRenderPerspective(this, manager, camera);\n\n        glViewport(0, 0, width, height);\n        glMatrixMode(GL_PROJECTION);\n        glLoadIdentity();\n        GLU.gluOrtho2D(0, width, height, 0);\n        glMatrixMode(GL_MODELVIEW);\n        glLoadIdentity();\n\n        canvasRenderer.onRenderOverlay(manager, camera, this);\n\n        glViewport(0, 0, width, height);\n        glMatrixMode(GL_PROJECTION);\n        glLoadIdentity();\n        GLU.gluOrtho2D(0, width, height, 0);\n        glMatrixMode(GL_MODELVIEW);\n        glLoadIdentity();\n\n        drawOverlay(offset);\n    }\n\n    private void drawOverlay(int offset)\n    {\n        glDisable(GL_TEXTURE_2D);\n        glDisable(GL_DEPTH_TEST);\n        glPushMatrix();\n        {\n            glColor3f(0.58F, 0.58F, 0.58F);\n            glLineWidth(2F);\n            glBegin(GL_LINES);\n            {\n                glVertex2i(offset, 0);\n                glVertex2i(width, 0);\n                glVertex2i(width, 0);\n                glVertex2i(width, height);\n                glVertex2i(offset, height);\n                glVertex2i(offset, 0);\n                glVertex2i(offset, height);\n                glVertex2i(width, height);\n            }\n            glEnd();\n        }\n        glPopMatrix();\n\n        if(debugMode)\n        {\n            glPushMatrix();\n            {\n                List<ElementManagerState> states = StateManager.getStates();\n                for(int i = 0; i < states.size(); i++)\n                {\n                    ElementManagerState managerState = states.get(i);\n                    String text = \"No Elements\";\n                    if(managerState.getElements().size() > 0)\n                    {\n                        text = managerState.getElements().toString();\n                    }\n\n                    if(StateManager.getTailIndex() == i)\n                    {\n                        text = text + \" <<<\";\n                    }\n\n                    GL11.glEnable(GL11.GL_BLEND);\n                    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);\n                    FontManager.BEBAS_NEUE_20.drawString(10, 10 + i * 20, text, new org.newdawn.slick.Color(1, 1, 1));\n                    GL11.glDisable(GL11.GL_BLEND);\n                }\n            }\n            glPopMatrix();\n        }\n\n        if(activeSidebar != null)\n        {\n            activeSidebar.draw(offset, width, height, getHeight());\n        }\n\n        if(canvasRenderer == standardRenderer)\n        {\n            glPushMatrix();\n            {\n                glTranslatef(width - 80, height - 80, 0);\n                glLineWidth(2F);\n                glRotated(-camera.getRY(), 0, 0, 1);\n\n                GL11.glEnable(GL11.GL_BLEND);\n                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);\n                FontManager.BEBAS_NEUE_20.drawString(-5, -75, \"N\", new org.newdawn.slick.Color(1, 1, 1));\n                GL11.glDisable(GL11.GL_BLEND);\n\n                glColor3d(0.6, 0.6, 0.6);\n                glBegin(GL_LINES);\n                {\n                    glVertex2i(0, -50);\n                    glVertex2i(0, 50);\n                    glVertex2i(-50, 0);\n                    glVertex2i(50, 0);\n                }\n                glEnd();\n\n                glColor3d(0.3, 0.3, 0.6);\n                glBegin(GL_TRIANGLES);\n                {\n                    glVertex2i(-5, -45);\n                    glVertex2i(0, -50);\n                    glVertex2i(5, -45);\n\n                    glVertex2i(-5, 45);\n                    glVertex2i(0, 50);\n                    glVertex2i(5, 45);\n\n                    glVertex2i(-45, -5);\n                    glVertex2i(-50, 0);\n                    glVertex2i(-45, 5);\n\n                    glVertex2i(45, -5);\n                    glVertex2i(50, 0);\n                    glVertex2i(45, 5);\n                }\n                glEnd();\n            }\n            glPopMatrix();\n        }\n\n        /*glColor3f(1.0F, 1.0F, 1.0F);\n        glBindTexture(GL_TEXTURE_2D, 6);\n        glBegin(GL_QUADS);\n        {\n            glTexCoord2d(0.0, 0.0);\n            glVertex2f(0, 0);\n            glTexCoord2d(1.0, 0.0);\n            glVertex2f(300, 0);\n            glTexCoord2d(1.0, 1.0);\n            glVertex2f(300, 300);\n            glTexCoord2d(0.0, 1.0);\n            glVertex2f(0, 300);\n        }\n        glEnd();*/\n    }\n\n    private void handleInput(int offset)\n    {\n        final float cameraMod = Math.abs(camera.getZ());\n\n        if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1))\n        {\n            if(!grabbingInSidebar)\n            {\n                if(!grabbing && Mouse.getX() < offset)\n                {\n                    grabbingInSidebar = true;\n                }\n                if(!grabbing)\n                {\n                    lastMouseX = Mouse.getX();\n                    lastMouseY = Mouse.getY();\n                    grabbing = true;\n                    uvSidebar.handleMouseInput(0, Mouse.getX(), Mouse.getY(), true);\n                }\n            }\n        }\n        else if(grabbing)\n        {\n            if(grabbed != null && performedChange)\n            {\n                StateManager.pushState(manager);\n                performedChange = false;\n            }\n            grabbing = false;\n            grabbed = null;\n        }\n        else if(grabbingInSidebar)\n        {\n            uvSidebar.handleMouseInput(0, Mouse.getX(), Mouse.getY(), false);\n            grabbingInSidebar = false;\n        }\n\n        if(grabbingInSidebar)\n        {\n            activeSidebar.handleInput(getHeight());\n        }\n        else\n        {\n            if(Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) && canvasRenderer == standardRenderer)\n            {\n                if(grabbed == null)\n                {\n                    if(Mouse.isButtonDown(0) || Mouse.isButtonDown(1))\n                    {\n                        int sel = select(Mouse.getX(), Mouse.getY());\n                        if(sel >= 0)\n                        {\n                            grabbed = manager.getAllElements().get(sel);\n                            manager.setSelectedElement(sel);\n                        }\n                        else\n                        {\n                            grabbed = null;\n                            manager.setSelectedElement(-1);\n                        }\n                    }\n                }\n                else\n                {\n                    Element element = grabbed;\n                    int state = getCameraState(camera);\n\n                    int newMouseX = Mouse.getX();\n                    int newMouseY = Mouse.getY();\n\n                    int xMovement = (newMouseX - lastMouseX) / 20;\n                    int yMovement = (newMouseY - lastMouseY) / 20;\n\n                    if(xMovement != 0 | yMovement != 0)\n                    {\n                        if(Mouse.isButtonDown(0))\n                        {\n                            switch(state)\n                            {\n                                case 0:\n                                    element.addStartX(xMovement);\n                                    element.addStartY(yMovement);\n                                    break;\n                                case 1:\n                                    element.addStartZ(xMovement);\n                                    element.addStartY(yMovement);\n                                    break;\n                                case 2:\n                                    element.addStartX(-xMovement);\n                                    element.addStartY(yMovement);\n                                    break;\n                                case 3:\n                                    element.addStartZ(-xMovement);\n                                    element.addStartY(yMovement);\n                                    break;\n                                case 4:\n                                    element.addStartX(xMovement);\n                                    element.addStartZ(-yMovement);\n                                    break;\n                                case 5:\n                                    element.addStartX(yMovement);\n                                    element.addStartZ(xMovement);\n                                    break;\n                                case 6:\n                                    element.addStartX(-xMovement);\n                                    element.addStartZ(yMovement);\n                                    break;\n                                case 7:\n                                    element.addStartX(-yMovement);\n                                    element.addStartZ(-xMovement);\n                                    break;\n                            }\n                        }\n                        else if(Mouse.isButtonDown(1))\n                        {\n                            switch(state)\n                            {\n                                case 0:\n                                    element.addHeight(yMovement);\n                                    element.addWidth(xMovement);\n                                    break;\n                                case 1:\n                                    element.addHeight(yMovement);\n                                    element.addDepth(xMovement);\n                                    break;\n                                case 2:\n                                    element.addHeight(yMovement);\n                                    element.addWidth(-xMovement);\n                                    break;\n                                case 3:\n                                    element.addHeight(yMovement);\n                                    element.addDepth(-xMovement);\n                                    break;\n                                case 4:\n                                    element.addDepth(-yMovement);\n                                    element.addWidth(xMovement);\n                                    break;\n                                case 5:\n                                    element.addDepth(xMovement);\n                                    element.addWidth(yMovement);\n                                    break;\n                                case 6:\n                                    element.addDepth(yMovement);\n                                    element.addWidth(-xMovement);\n                                    break;\n                                case 7:\n                                    element.addDepth(-xMovement);\n                                    element.addWidth(-yMovement);\n                                    break;\n                                case 8:\n                                    element.addDepth(-yMovement);\n                                    element.addWidth(xMovement);\n                                    break;\n                            }\n                        }\n\n                        if(xMovement != 0)\n                        {\n                            lastMouseX = newMouseX;\n                        }\n                        if(yMovement != 0)\n                        {\n                            lastMouseY = newMouseY;\n                        }\n\n                        manager.updateValues();\n                        element.updateEndUVs();\n\n                        performedChange = true;\n                    }\n                }\n            }\n            else\n            {\n                if(Mouse.isButtonDown(0))\n                {\n                    final float modifier = (cameraMod * 0.05f);\n                    camera.addX(Mouse.getDX() * 0.01F * modifier);\n                    camera.addY(Mouse.getDY() * 0.01F * modifier);\n                }\n                else if(Mouse.isButtonDown(1))\n                {\n                    final float modifier = applyLimit(cameraMod * 0.1f);\n                    camera.rotateX(-(Mouse.getDY() * 0.5F) * modifier);\n                    final float rxAbs = Math.abs(camera.getRX());\n                    camera.rotateY((rxAbs >= 90 && rxAbs < 270 ? -1 : 1) * Mouse.getDX() * 0.5F * modifier);\n                }\n\n                final float wheel = Mouse.getDWheel();\n                if(wheel != 0)\n                {\n                    camera.addZ(wheel * (cameraMod / 5000F));\n                }\n            }\n        }\n    }\n\n    private int select(int x, int y)\n    {\n        IntBuffer selBuffer = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder()).asIntBuffer();\n        int[] buffer = new int[256];\n\n        IntBuffer viewBuffer = ByteBuffer.allocateDirect(64).order(ByteOrder.nativeOrder()).asIntBuffer();\n        int[] viewport = new int[4];\n\n        int hits;\n        GL11.glGetInteger(GL11.GL_VIEWPORT, viewBuffer);\n        viewBuffer.get(viewport);\n\n        GL11.glSelectBuffer(selBuffer);\n        GL11.glRenderMode(GL11.GL_SELECT);\n        GL11.glInitNames();\n        GL11.glPushName(0);\n        GL11.glPushMatrix();\n        {\n            GL11.glMatrixMode(GL11.GL_PROJECTION);\n            GL11.glLoadIdentity();\n            GLU.gluPickMatrix(x, y, 1, 1, IntBuffer.wrap(viewport));\n\n            int offset = activeSidebar == null ? 0 : getHeight() < 805 ? SIDEBAR_WIDTH * 2 : SIDEBAR_WIDTH;\n            GLU.gluPerspective(60F, (float) (width - offset) / (float) height, 0.3F, 1000F);\n            canvasRenderer.onRenderPerspective(this, manager, camera);\n        }\n        GL11.glPopMatrix();\n        hits = GL11.glRenderMode(GL11.GL_RENDER);\n\n        selBuffer.get(buffer);\n        if(hits > 0)\n        {\n            int choose = buffer[3];\n            int depth = buffer[1];\n\n            for(int i = 1; i < hits; i++)\n            {\n                if((buffer[i * 4 + 1] < depth || choose == 0) && buffer[i * 4 + 3] != 0)\n                {\n                    choose = buffer[i * 4 + 3];\n                    depth = buffer[i * 4 + 1];\n                }\n            }\n\n            if(choose > 0)\n            {\n                return choose - 1;\n            }\n        }\n\n        return -1;\n    }\n\n    private float applyLimit(float value)\n    {\n        if(value > 0.4F)\n        {\n            value = 0.4F;\n        }\n        else if(value < 0.15F)\n        {\n            value = 0.15F;\n        }\n        return value;\n    }\n\n    private int getCameraState(Camera camera)\n    {\n        int cameraRotY = (int) (camera.getRY() >= 0 ? camera.getRY() : 360 + camera.getRY());\n        int state = (int) ((cameraRotY * 4.0F / 360.0F) + 0.5D) & 3;\n\n        if(camera.getRX() > 45)\n        {\n            state += 4;\n        }\n        if(camera.getRX() < -45)\n        {\n            state += 8;\n        }\n        return state;\n    }\n\n    public void startScreenshot(PendingScreenshot screenshot)\n    {\n        this.screenshot = screenshot;\n    }\n\n    public void setSidebar(Sidebar s)\n    {\n        activeSidebar = s;\n        if(s == null)\n        {\n            isUVSidebarOpen = false;\n        }\n    }\n\n    public Sidebar getActiveSidebar()\n    {\n        return activeSidebar;\n    }\n\n    public ElementManager getElementManager()\n    {\n        return manager;\n    }\n\n    public void close()\n    {\n        this.closeRequested = true;\n    }\n\n    private boolean getCloseRequested()\n    {\n        return closeRequested;\n    }\n\n    private void handleKeyAction(int code, int modifiers, boolean awt, boolean pressed)\n    {\n        if(!this.isActive())\n            return;\n\n        keyActions.forEach(keyAction ->\n        {\n            if(awt)\n            {\n                if(keyAction.awtCode == code)\n                {\n                    keyAction.handler.process(modifiers, pressed);\n                }\n            }\n            else\n            {\n                if(keyAction.keyboardCode == code)\n                {\n                    keyAction.handler.process(modifiers, pressed);\n                }\n            }\n        });\n    }\n\n    public static void setCanvasRenderer(CanvasRenderer displayRenderer)\n    {\n        canvasRenderer = displayRenderer;\n        changedCanvas = true;\n    }\n\n    public static void restoreStandardRenderer()\n    {\n        setCanvasRenderer(standardRenderer);\n    }\n\n    public void registerKeyAction(KeyAction keyAction)\n    {\n        this.keyActions.add(keyAction);\n    }\n\n    public static class KeyAction\n    {\n        private final int awtCode;\n        private final int keyboardCode;\n        private final Handler handler;\n\n        public KeyAction(int awtCode, int keyboardCode, Handler handler)\n        {\n            this.awtCode = awtCode;\n            this.keyboardCode = keyboardCode;\n            this.handler = handler;\n        }\n\n        public interface Handler\n        {\n            void process(int modifiers, boolean pressed);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Processor.java",
    "content": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic interface Processor<T>\n{\n    /**\n     * Processes the given argument.\n     *\n     * @param t the object to process\n     * @return if the object was processed successfully\n     */\n    boolean run(T t);\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/ProjectManager.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipInputStream;\nimport java.util.zip.ZipOutputStream;\n\npublic class ProjectManager\n{\n    private static final Pattern TEXTURE_FIX = Pattern.compile(\"\\\\d+$\"); //Matches numbers at the end of line\n\n    public static void loadProject(ElementManager manager, String modelFile)\n    {\n        TextureManager.clear();\n        manager.clearElements();\n        manager.setParticle(null);\n\n        File projectFolder = extractFiles(modelFile);\n        if(projectFolder != null)\n        {\n            Project project = new Project(projectFolder);\n            Importer importer = new Importer(manager, project.getModel().getPath());\n            importer.importFromJSON();\n        }\n        deleteFolder(projectFolder);\n    }\n\n    private static void deleteFolder(File file)\n    {\n        Runtime.getRuntime().addShutdownHook(new Thread(() ->\n        {\n            try\n            {\n                Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);\n            }\n            catch(IOException e)\n            {\n                e.printStackTrace();\n            }\n        }));\n    }\n\n    private static File extractFiles(String modelFile)\n    {\n        try\n        {\n            Path path = Files.createTempDirectory(\"ModelCreator\");\n            File folder = path.toFile();\n\n            ZipInputStream zis = new ZipInputStream(new FileInputStream(modelFile));\n            ZipEntry ze;\n            while((ze = zis.getNextEntry()) != null)\n            {\n                String fileName = ze.getName();\n\n                /* Fixes old project texture files extracting with numbers on the file name */\n                Matcher matcher = TEXTURE_FIX.matcher(ze.getName());\n                if(matcher.find())\n                {\n                    String numbers = matcher.group(0);\n                    fileName = fileName.replace(numbers, \"\");\n                }\n\n                File file = new File(folder, fileName);\n                file.getParentFile().mkdirs();\n                file.createNewFile();\n\n                byte[] buffer = new byte[1024];\n                FileOutputStream fos = new FileOutputStream(file);\n\n                int len;\n                while((len = zis.read(buffer)) > 0)\n                {\n                    fos.write(buffer, 0, len);\n                }\n\n                fos.flush();\n                fos.close();\n                zis.closeEntry();\n            }\n            zis.close();\n\n            return folder;\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static void saveProject(ElementManager manager, String name)\n    {\n        try\n        {\n            FileOutputStream fos = new FileOutputStream(name);\n            ZipOutputStream zos = new ZipOutputStream(fos);\n\n            File file = getSaveFile(manager);\n            addToZipFile(file, zos, \"model.json\");\n            file.delete();\n\n            for(TextureEntry entry : getAllTextures(manager))\n            {\n                File temp = File.createTempFile(entry.getName(), \"\");\n                BufferedImage image = entry.getSource();\n                ImageIO.write(image, \"PNG\", temp);\n                addToZipFile(temp, zos, \"assets/\" + entry.getModId() + \"/textures/\" + entry.getDirectory() + \"/\", entry.getName() + \".png\");\n                temp.delete();\n            }\n\n            //TODO make output animation properties\n            /*for(String metaLocation : getMetaLocations(manager))\n            {\n                if(metaLocation != null)\n                {\n                    File texture = new File(metaLocation);\n                    if(texture.exists())\n                    {\n                        addToZipFile(texture, zos, \"textures/\", texture.getName());\n                    }\n                }\n            }*/\n\n            zos.close();\n            fos.close();\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    private static Set<TextureEntry> getAllTextures(ElementManager manager)\n    {\n        Set<TextureEntry> textureEntries = new HashSet<>();\n        for(Element element : manager.getAllElements())\n        {\n            for(Face face : element.getAllFaces())\n            {\n                if(face.getTexture() != null)\n                {\n                    textureEntries.add(face.getTexture());\n                }\n            }\n        }\n        return textureEntries;\n    }\n\n    private static File getSaveFile(ElementManager manager) throws IOException\n    {\n        ExporterModel exporter = new ExporterModel(manager);\n        exporter.setOptimize(false);\n        exporter.setIncludeNonTexturedFaces(true);\n        return exporter.writeFile(File.createTempFile(\"model.json\", \"\"));\n    }\n\n    private static void addToZipFile(File file, ZipOutputStream zos, String name) throws IOException\n    {\n        addToZipFile(file, zos, \"\", name);\n    }\n\n    private static void addToZipFile(File file, ZipOutputStream zos, String folder, String name) throws IOException\n    {\n        FileInputStream fis = new FileInputStream(file);\n        ZipEntry zipEntry = new ZipEntry(folder + name);\n        zos.putNextEntry(zipEntry);\n\n        byte[] bytes = new byte[1024];\n        int length;\n        while((length = fis.read(bytes)) >= 0)\n        {\n            zos.write(bytes, 0, length);\n        }\n\n        zos.closeEntry();\n        fis.close();\n    }\n\n    private static class Project\n    {\n        public File model;\n        public File textures;\n\n        public Project(File folder)\n        {\n            File[] files = folder.listFiles();\n            if(files != null)\n            {\n                for(File file : files)\n                {\n                    String name = file.getName();\n                    if(file.isFile() && name.equals(\"model.json\"))\n                    {\n                        this.model = file;\n                    }\n                    else if(file.isDirectory() && name.equals(\"textures\"))\n                    {\n                        this.textures = file;\n                    }\n                }\n            }\n        }\n\n        public File getModel()\n        {\n            return model;\n        }\n\n        public File getTextures()\n        {\n            return textures;\n        }\n    }\n\n    private static class ProjectTexture\n    {\n        private File texture;\n        private File meta;\n\n        public ProjectTexture(File texture, File meta)\n        {\n            this.texture = texture;\n            this.meta = meta;\n        }\n\n        public File getTexture()\n        {\n            return texture;\n        }\n\n        public File getMeta()\n        {\n            return meta;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/PropertyIdentifiers.java",
    "content": "package com.mrcrayfish.modelcreator;\n\n/**\n * Author: MrCrayfish\n */\npublic class PropertyIdentifiers\n{\n    public static final int SIZE_X = 0;\n    public static final int SIZE_Y = 1;\n    public static final int SIZE_Z = 2;\n    public static final int POS_X = 3;\n    public static final int POS_Y = 4;\n    public static final int POS_Z = 5;\n    public static final int ORIGIN_X = 6;\n    public static final int ORIGIN_Y = 7;\n    public static final int ORIGIN_Z = 8;\n    public static final int START_U = 9;\n    public static final int START_V = 10;\n    public static final int END_U = 11;\n    public static final int END_V = 12;\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Settings.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport java.util.prefs.Preferences;\n\npublic class Settings\n{\n    private static final String IMAGE_IMPORT_DIR = \"image_import_dir\";\n    private static final String SCREENSHOT_DIR = \"screenshot_dir\";\n    private static final String MODEL_DIR = \"model_dir\";\n    private static final String JSON_DIR = \"json_dir\";\n    private static final String EXPORT_JSON_DIR = \"export_json_dir\";\n    private static final String UNDO_LIMIT = \"undo_limit\";\n    private static final String RENDER_CARDINAL_POINTS = \"cardinal_points\";\n    private static final String ASSESTS_DIR = \"assets_dir\";\n    private static final String FACE_COLORS = \"face_colors\";\n    private static final String IMAGE_EDITOR = \"image_editor\";\n    private static final String IMAGE_EDITOR_ARGS = \"image_editor_args\";\n\n    public static final int[] DEFAULT_FACE_COLORS = {16711680, 65280, 255, 16776960, 16711935, 65535};\n\n    public static String getImageImportDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(IMAGE_IMPORT_DIR, null);\n    }\n\n    public static void setImageImportDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(IMAGE_IMPORT_DIR, dir);\n    }\n\n    public static String getScreenshotDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(SCREENSHOT_DIR, null);\n    }\n\n    public static void setScreenshotDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(SCREENSHOT_DIR, dir);\n    }\n\n    public static String getModelDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(MODEL_DIR, null);\n    }\n\n    public static void setModelDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(MODEL_DIR, dir);\n    }\n\n    public static String getJSONDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(JSON_DIR, null);\n    }\n\n    public static void setJSONDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(JSON_DIR, dir);\n    }\n\n    public static String getExportJSONDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(EXPORT_JSON_DIR, null);\n    }\n\n    public static void setExportJSONDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(EXPORT_JSON_DIR, dir);\n    }\n\n    public static String getAssetsDir()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(ASSESTS_DIR, null);\n    }\n\n    public static void setAssetsDir(String dir)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(ASSESTS_DIR, dir);\n    }\n\n    public static int getUndoLimit()\n    {\n        Preferences prefs = getPreferences();\n        String s = prefs.get(UNDO_LIMIT, null);\n        try\n        {\n            return Math.max(1, Integer.parseInt(s));\n        }\n        catch(NumberFormatException e)\n        {\n            return 50;\n        }\n    }\n\n    public static void setUndoLimit(int limit)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(UNDO_LIMIT, Integer.toString(Math.max(1, limit)));\n    }\n\n    public static boolean getCardinalPoints()\n    {\n        Preferences prefs = getPreferences();\n        String s = prefs.get(RENDER_CARDINAL_POINTS, \"true\");\n        return Boolean.parseBoolean(s);\n    }\n\n    public static void setCardinalPoints(boolean renderCardinalPoints)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(RENDER_CARDINAL_POINTS, Boolean.toString(renderCardinalPoints));\n    }\n\n    public static int[] getFaceColors()\n    {\n        Preferences prefs = getPreferences();\n        String s = prefs.get(FACE_COLORS, \"\");\n        String[] values = s.split(\",\");\n        if(values.length == 6)\n        {\n            int[] colors = new int[6];\n            for(int i = 0; i < values.length; i++)\n            {\n                int color = Integer.parseInt(values[i]);\n                colors[i] = color;\n            }\n            return colors;\n        }\n        return DEFAULT_FACE_COLORS;\n    }\n\n    public static void setFaceColors(int[] colors)\n    {\n        StringBuilder builder = new StringBuilder();\n        for(int value : colors)\n        {\n            builder.append(value);\n            builder.append(\",\");\n        }\n        builder.setLength(builder.length() - 1);\n        Preferences prefs = getPreferences();\n        prefs.put(FACE_COLORS, builder.toString());\n    }\n\n    public static String getImageEditor()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(IMAGE_EDITOR, null);\n    }\n\n    public static void setImageEditor(String file)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(IMAGE_EDITOR, file);\n    }\n\n    public static String getImageEditorArgs()\n    {\n        Preferences prefs = getPreferences();\n        return prefs.get(IMAGE_EDITOR_ARGS, \"\\\"%s\\\"\");\n    }\n\n    public static void setImageEditorArgs(String args)\n    {\n        Preferences prefs = getPreferences();\n        prefs.put(IMAGE_EDITOR_ARGS, args);\n    }\n\n    private static Preferences getPreferences()\n    {\n        return Preferences.userNodeForPackage(Settings.class);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/Start.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.jtattoo.plaf.fast.FastLookAndFeel;\nimport com.mrcrayfish.modelcreator.util.SharedLibraryLoader;\n\nimport javax.swing.*;\nimport java.util.Properties;\n\npublic class Start\n{\n    public static void main(String[] args)\n    {\n        SharedLibraryLoader.load(false);\n\n        Double version = Double.parseDouble(System.getProperty(\"java.specification.version\"));\n        if(version < 1.8)\n        {\n            JOptionPane.showMessageDialog(null, \"You need Java 1.8 or higher to run this program.\");\n            return;\n        }\n\n        System.setProperty(\"org.lwjgl.util.Debug\", \"true\");\n\n        try\n        {\n            Properties props = new Properties();\n            props.put(\"logoString\", \"\");\n            props.put(\"centerWindowTitle\", \"on\");\n            props.put(\"buttonBackgroundColor\", \"127 132 145\");\n            props.put(\"buttonForegroundColor\", \"255 255 255\");\n            props.put(\"windowTitleBackgroundColor\", \"97 102 115\");\n            props.put(\"windowTitleForegroundColor\", \"255 255 255\");\n            props.put(\"backgroundColor\", \"221 221 228\");\n            props.put(\"menuBackgroundColor\", \"221 221 228\");\n            props.put(\"controlForegroundColor\", \"120 120 120\");\n            props.put(\"windowBorderColor\", \"97 102 110\");\n            FastLookAndFeel.setTheme(props);\n            UIManager.setLookAndFeel(\"com.jtattoo.plaf.fast.FastLookAndFeel\");\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n\n        new ModelCreator(Constants.NAME + \" v\" + Constants.VERSION);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/StateManager.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.ElementManagerState;\n\nimport javax.swing.*;\nimport java.awt.event.ActionListener;\nimport java.util.Stack;\n\n/**\n * Author: MrCrayfish\n */\npublic class StateManager\n{\n    /* Undo/Redo Stack */\n    private static Stack<ElementManagerState> states = new Stack<>();\n    private static int tailIndex = -1;\n    private static int lastId = -1;\n    private static Timer timer;\n\n    public static void pushState(ElementManager manager)\n    {\n        pushState(manager.createState());\n    }\n\n    private static void pushState(ElementManagerState state)\n    {\n        if(timer != null && timer.isRunning())\n        {\n            for(ActionListener listener : timer.getActionListeners())\n            {\n                listener.actionPerformed(null);\n            }\n            timer.stop();\n            timer = null;\n        }\n        pushManagerState(state);\n    }\n\n    private static void pushManagerState(ElementManagerState state)\n    {\n        while(tailIndex < states.size() - 1)\n        {\n            states.pop();\n        }\n\n        if(states.size() >= 50) //Make configurable\n        {\n            while(states.size() > 50)\n            {\n                states.remove(0);\n            }\n        }\n        states.push(state);\n        tailIndex = states.size() - 1;\n    }\n\n    public static void restorePreviousState(ElementManager manager)\n    {\n        if(canRestorePreviousState())\n        {\n            ElementManagerState state = states.get(tailIndex - 1);\n            manager.restoreState(state);\n            tailIndex--;\n        }\n    }\n\n    public static void restoreNextState(ElementManager manager)\n    {\n        if(canRestoreNextState())\n        {\n            ElementManagerState state = states.get(tailIndex + 1);\n            manager.restoreState(state);\n            tailIndex++;\n        }\n    }\n\n    public static boolean canRestorePreviousState()\n    {\n        return tailIndex > 0;\n    }\n\n    public static boolean canRestoreNextState()\n    {\n        return tailIndex < states.size() - 1;\n    }\n\n    public static void clear()\n    {\n        if(timer != null && timer.isRunning())\n        {\n            timer.stop();\n            timer = null;\n        }\n        states.clear();\n        tailIndex = -1;\n    }\n\n    public static Stack<ElementManagerState> getStates()\n    {\n        return states;\n    }\n\n    public static int getTailIndex()\n    {\n        return tailIndex;\n    }\n\n    public static void pushStateDelayed(ElementManager manager, int id)\n    {\n        if(lastId != id)\n        {\n            if(timer != null && timer.isRunning())\n            {\n                for(ActionListener listener : timer.getActionListeners())\n                {\n                    listener.actionPerformed(null);\n                }\n                timer.stop();\n            }\n        }\n        else\n        {\n            if(timer != null && timer.isRunning())\n            {\n                timer.stop();\n            }\n        }\n\n        ElementManagerState state = manager.createState();\n        ActionListener listener = e -> pushManagerState(state);\n        timer = new Timer(400, listener);\n        timer.setRepeats(false);\n        timer.start();\n        lastId = id;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/TexturePath.java",
    "content": "package com.mrcrayfish.modelcreator;\n\nimport com.mrcrayfish.modelcreator.util.AssetsUtil;\n\nimport java.io.File;\nimport java.util.regex.Pattern;\n\n/**\n * Author: MrCrayfish\n */\npublic class TexturePath\n{\n    public static final Pattern PATTERN = Pattern.compile(\"([a-z_0-9]+:)?([a-z_0-9]+/)*[a-z_0-9]+\");\n\n    private String modId = \"minecraft\";\n    private String directory;\n    private String name;\n\n    public TexturePath(String s)\n    {\n        String[] split = s.split(\":\");\n        if(split.length == 2)\n        {\n            this.modId = split[0];\n        }\n        String assetPath = split[split.length - 1];\n        this.directory = assetPath.substring(0, Math.max(0, assetPath.lastIndexOf(\"/\")));\n        this.name = assetPath.replace(this.directory, \"\").substring(1);\n    }\n\n    public TexturePath(File file)\n    {\n        this.modId = AssetsUtil.getModId(file);\n        this.directory = AssetsUtil.getTextureDirectory(file);\n        this.name = file.getName().substring(0, file.getName().indexOf(\".\"));\n    }\n\n    public String getModId()\n    {\n        return modId;\n    }\n\n    public String getDirectory()\n    {\n        return directory;\n    }\n\n    public String getName()\n    {\n        return name;\n    }\n\n    @Override\n    public String toString()\n    {\n        return modId + \":\" + directory + \"/\" + name;\n    }\n\n    public String toRelativePath()\n    {\n        return modId + File.separator + \"textures\" + File.separator + directory + File.separator + name + \".png\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/DisplayPropertiesDialog.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.CanvasRenderer;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.panels.DisplayEntryPanel;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class DisplayPropertiesDialog extends JDialog\n{\n    private ModelCreator creator;\n\n    private JTabbedPane tabbedPane;\n\n    public DisplayPropertiesDialog(ModelCreator creator)\n    {\n        super(creator, \"Display Properties\", Dialog.ModalityType.MODELESS);\n        this.creator = creator;\n        this.init();\n        this.pack();\n        this.setResizable(false);\n    }\n\n\n    private void init()\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(400, 490));\n        this.add(panel);\n\n        JLabel labelProperties = new JLabel(\"Presets\");\n        panel.add(labelProperties);\n\n        JComboBox<DisplayProperties> comboBoxProperties = new JComboBox<>();\n        comboBoxProperties.addItem(DisplayProperties.MODEL_CREATOR_BLOCK);\n        comboBoxProperties.addItem(DisplayProperties.DEFAULT_BLOCK);\n        comboBoxProperties.addItem(DisplayProperties.DEFAULT_ITEM);\n        comboBoxProperties.setPreferredSize(new Dimension(0, 24));\n        panel.add(comboBoxProperties);\n\n        DisplayProperties properties = creator.getElementManager().getDisplayProperties();\n\n        tabbedPane = new JTabbedPane();\n        tabbedPane.addTab(\"GUI\", new DisplayEntryPanel(properties.getEntry(\"gui\")));\n        tabbedPane.addTab(\"Ground\", new DisplayEntryPanel(properties.getEntry(\"ground\")));\n        tabbedPane.addTab(\"Fixed\", new DisplayEntryPanel(properties.getEntry(\"fixed\")));\n        tabbedPane.addTab(\"Head\", new DisplayEntryPanel(properties.getEntry(\"head\")));\n        tabbedPane.addTab(\"First Person\", new DisplayEntryPanel(properties.getEntry(\"firstperson_righthand\")));\n        tabbedPane.addTab(\"Third Person\", new DisplayEntryPanel(properties.getEntry(\"thirdperson_righthand\")));\n        tabbedPane.addTab(\"First Person (Left)\", new DisplayEntryPanel(properties.getEntry(\"firstperson_lefthand\")));\n        tabbedPane.addTab(\"Third Person (Left)\", new DisplayEntryPanel(properties.getEntry(\"thirdperson_lefthand\")));\n        tabbedPane.addChangeListener(e ->\n        {\n            Component c = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());\n            if(c instanceof DisplayEntryPanel)\n            {\n                DisplayEntryPanel entryPanel = (DisplayEntryPanel) c;\n                CanvasRenderer render = DisplayProperties.RENDER_MAP.get(entryPanel.getEntry().getId());\n                if(render != null)\n                {\n                    ModelCreator.setCanvasRenderer(render);\n                }\n                else\n                {\n                    ModelCreator.restoreStandardRenderer();\n                }\n            }\n        });\n        panel.add(tabbedPane);\n\n        JButton btnApplyProperties = new JButton(\"Apply\");\n        btnApplyProperties.setPreferredSize(new Dimension(80, 24));\n        btnApplyProperties.addActionListener(e ->\n        {\n            creator.getElementManager().setDisplayProperties((DisplayProperties) comboBoxProperties.getSelectedItem());\n            this.updateValues(creator.getElementManager().getDisplayProperties());\n        });\n        panel.add(btnApplyProperties);\n\n        JCheckBox checkBoxShowGrid = ComponentUtil.createCheckBox(\"Show Grid\", \"Determines whether the grid should render\", Menu.shouldRenderGrid);\n        checkBoxShowGrid.addActionListener(e -> Menu.shouldRenderGrid = checkBoxShowGrid.isSelected());\n        panel.add(checkBoxShowGrid);\n\n        layout.putConstraint(SpringLayout.WEST, labelProperties, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, labelProperties, 2, SpringLayout.NORTH, comboBoxProperties);\n        layout.putConstraint(SpringLayout.EAST, comboBoxProperties, -10, SpringLayout.WEST, btnApplyProperties);\n        layout.putConstraint(SpringLayout.NORTH, comboBoxProperties, 10, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, comboBoxProperties, 10, SpringLayout.EAST, labelProperties);\n        layout.putConstraint(SpringLayout.NORTH, btnApplyProperties, 10, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.EAST, btnApplyProperties, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.WEST, checkBoxShowGrid, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, checkBoxShowGrid, 5, SpringLayout.SOUTH, comboBoxProperties);\n        layout.putConstraint(SpringLayout.EAST, tabbedPane, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.NORTH, tabbedPane, 5, SpringLayout.SOUTH, checkBoxShowGrid);\n        layout.putConstraint(SpringLayout.WEST, tabbedPane, 10, SpringLayout.WEST, panel);\n    }\n\n    public void updateValues(DisplayProperties displayProperties)\n    {\n        Component[] components = tabbedPane.getComponents();\n        for(Component c : components)\n        {\n            if(c instanceof DisplayEntryPanel)\n            {\n                DisplayEntryPanel entryPanel = (DisplayEntryPanel) c;\n                DisplayProperties.Entry oldEntry = entryPanel.getEntry();\n                DisplayProperties.Entry newEntry = displayProperties.getEntry(oldEntry.getId());\n                if(newEntry != null)\n                {\n                    entryPanel.updateValues(newEntry);\n                }\n            }\n        }\n    }\n\n    public static void update(ModelCreator creator)\n    {\n        if(Menu.displayPropertiesDialog != null)\n        {\n            Menu.displayPropertiesDialog.updateValues(creator.getElementManager().getDisplayProperties());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/JElementList.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.element.ElementCellEntry;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseEvent;\n\n/**\n * Author: MrCrayfish\n */\npublic class JElementList extends JList<ElementCellEntry>\n{\n    private boolean processInput;\n\n    @Override\n    protected void processMouseEvent(MouseEvent e)\n    {\n        if(e.getID() == MouseEvent.MOUSE_FIRST)\n        {\n            return;\n        }\n\n        if(e.getID() == MouseEvent.MOUSE_RELEASED)\n        {\n            processInput = true;\n            return;\n        }\n\n        if(!processInput)\n        {\n            return;\n        }\n\n        if(e.getButton() == MouseEvent.BUTTON1)\n        {\n            int index = this.locationToIndex(e.getPoint());\n            if(index != -1)\n            {\n                Rectangle rectangle = this.getCellBounds(index, index);\n                if(rectangle.contains(e.getPoint()))\n                {\n                    Point relativePoint = new Point((int) e.getPoint().getX(), (int) (e.getPoint().getY() - rectangle.getY()));\n                    ElementCellEntry entry = this.getModel().getElementAt(index);\n                    Rectangle buttonBounds = ComponentUtil.expandRectangle(entry.getVisibility().getBounds(), 4);\n                    if(buttonBounds.contains(relativePoint))\n                    {\n                        entry.toggleVisibility();\n                        this.repaint();\n                        e.consume();\n                        processInput = false;\n                        return;\n                    }\n                }\n            }\n        }\n\n        super.processMouseEvent(e);\n    }\n\n    @Override\n    protected void processMouseMotionEvent(MouseEvent e)\n    {\n        if(processInput)\n        {\n            super.processMouseMotionEvent(e);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/Menu.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.display.CanvasRenderer;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.panels.DisplayEntryPanel;\nimport com.mrcrayfish.modelcreator.screenshot.PendingScreenshot;\nimport com.mrcrayfish.modelcreator.screenshot.Screenshot;\nimport com.mrcrayfish.modelcreator.screenshot.Uploader;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\nimport com.mrcrayfish.modelcreator.util.KeyboardUtil;\nimport com.mrcrayfish.modelcreator.util.Util;\nimport org.lwjgl.input.Keyboard;\n\nimport javax.swing.*;\nimport javax.swing.event.MenuEvent;\nimport javax.swing.filechooser.FileNameExtensionFilter;\nimport java.awt.*;\nimport java.awt.datatransfer.StringSelection;\nimport java.awt.event.*;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Set;\n\npublic class Menu extends JMenuBar\n{\n    private ModelCreator creator;\n\n    /* File */\n    private JMenu menuFile;\n    private JMenuItem itemNew;\n    private JMenuItem itemLoad;\n    private JMenuItem itemSave;\n    private JMenuItem itemImport;\n    private JMenuItem itemExport;\n    private JMenuItem itemSettings;\n    private JMenuItem itemExit;\n\n    /* Edit */\n    private JMenu menuEdit;\n    private JMenuItem itemUndo;\n    private JMenuItem itemRedo;\n\n    /* Model */\n    private JMenu menuModel;\n    private JMenuItem itemTextureManager;\n    private JMenuItem itemDisplayProps;\n    private JMenuItem itemOptimise;\n    private JMenu menuRotate;\n    private JMenuItem itemRotateClockwise;\n    private JMenuItem itemRotateCounterClockwise;\n\n    /* Share */\n    private JMenu menuScreenshot;\n    private JMenuItem itemSaveToDisk;\n    private JMenuItem itemShareFacebook;\n    private JMenuItem itemShareTwitter;\n    private JMenuItem itemShareReddit;\n    private JMenuItem itemImgurLink;\n\n    /* Extras */\n    private JMenu menuMore;\n    private JMenuItem itemExtractAssets;\n    private JMenu menuDeveloper;\n    private JMenuItem itemJavaCode;\n    private JMenu menuExamples;\n    private JMenuItem itemModelCauldron;\n    private JMenuItem itemModelChair;\n    private JMenuItem itemDonate;\n    private JMenuItem itemGitHub;\n\n    public static DisplayPropertiesDialog displayPropertiesDialog = null;\n    public static boolean shouldRenderGrid = false;\n\n    public Menu(ModelCreator creator)\n    {\n        this.creator = creator;\n        this.initMenu();\n    }\n\n    private void initMenu()\n    {\n        menuFile = new JMenu(\"File\");\n        {\n            itemNew = createMenuItem(\"New\", \"New Model\", KeyEvent.VK_N, Icons.new_, KeyEvent.VK_N, Keyboard.KEY_N, InputEvent.CTRL_MASK);\n            itemLoad = createMenuItem(\"Load Project...\", \"Load Project from File\", KeyEvent.VK_S, Icons.load, KeyEvent.VK_O, Keyboard.KEY_O, InputEvent.CTRL_MASK);\n            itemSave = createMenuItem(\"Save Project...\", \"Save Project to File\", KeyEvent.VK_S, Icons.disk, KeyEvent.VK_S, Keyboard.KEY_S, InputEvent.CTRL_MASK);\n            itemImport = createMenuItem(\"Import JSON...\", \"Import Model from JSON\", KeyEvent.VK_I, Icons.import_);\n            itemExport = createMenuItem(\"Export JSON...\", \"Export Model to JSON\", KeyEvent.VK_E, Icons.export);\n            itemSettings = createMenuItem(\"Settings\", \"Change the settings of the Model Creator\", KeyEvent.VK_S, Icons.settings, KeyEvent.VK_S, Keyboard.KEY_S, InputEvent.CTRL_MASK + InputEvent.ALT_MASK);\n            itemExit = createMenuItem(\"Exit\", \"Exit Application\", KeyEvent.VK_E, Icons.exit);\n        }\n\n        menuEdit = new JMenu(\"Edit\");\n        {\n            itemUndo = createMenuItem(\"Undo\", \"Undos the previous action\", KeyEvent.VK_U, Icons.undo, KeyEvent.VK_Z, Keyboard.KEY_Z, InputEvent.CTRL_MASK);\n            itemRedo = createMenuItem(\"Redo\", \"Redos the previous action\", KeyEvent.VK_R, Icons.redo, KeyEvent.VK_Y, Keyboard.KEY_Y, InputEvent.CTRL_MASK);\n        }\n\n        menuModel = new JMenu(\"Model\");\n        {\n            itemTextureManager = createMenuItem(\"Texture Manager\", \"Manage the textures entries for the model\", KeyEvent.VK_T, Icons.texture, KeyEvent.VK_T, Keyboard.KEY_T, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK);\n            itemDisplayProps = createMenuItem(\"Display Properties\", \"Change the display properties of the model\", KeyEvent.VK_D, Icons.gallery, KeyEvent.VK_D, Keyboard.KEY_D, InputEvent.CTRL_MASK + InputEvent.ALT_MASK);\n            itemOptimise = createMenuItem(\"Optimize\", \"Performs basic optimizion by disabling faces that aren't visible\", KeyEvent.VK_O, Icons.optimize, KeyEvent.VK_N, Keyboard.KEY_N, InputEvent.CTRL_MASK + InputEvent.SHIFT_MASK);\n            menuRotate = new JMenu(\"Rotate\");\n            menuRotate.setMnemonic(KeyEvent.VK_R);\n            menuRotate.setIcon(Icons.rotate);\n            {\n                itemRotateClockwise = createMenuItem(\"90\\u00B0 Clockwise\", \"Rotates all elements clockwise by 90\\u00B0\", KeyEvent.VK_C, Icons.rotate_clockwise, KeyEvent.VK_RIGHT, Keyboard.KEY_RIGHT, InputEvent.CTRL_MASK);\n                itemRotateCounterClockwise = createMenuItem(\"90\\u00B0 Counter Clockwise\", \"Rotates all elements counter clockwise by 90\\u00B0\", KeyEvent.VK_C, Icons.rotate_counter_clockwise, KeyEvent.VK_LEFT, Keyboard.KEY_LEFT, InputEvent.CTRL_MASK);\n            }\n        }\n\n        menuScreenshot = new JMenu(\"Screenshot\");\n        {\n            itemSaveToDisk = createMenuItem(\"Save to Disk...\", \"Save screenshot to disk.\", KeyEvent.VK_D, Icons.disk);\n            itemShareFacebook = createMenuItem(\"Share to Facebook\", \"Share a screenshot of your model Facebook.\", KeyEvent.VK_S, Icons.facebook);\n            itemShareTwitter = createMenuItem(\"Share to Twitter\", \"Share a screenshot of your model to Twitter.\", KeyEvent.VK_S, Icons.twitter);\n            itemShareReddit = createMenuItem(\"Share to Minecraft Subreddit\", \"Share a screenshot of your model to Minecraft Reddit.\", KeyEvent.VK_S, Icons.reddit);\n            itemImgurLink = createMenuItem(\"Get Imgur Link\", \"Get an Imgur link of your screenshot to share.\", KeyEvent.VK_I, Icons.imgur);\n        }\n\n        menuMore = new JMenu(\"More\");\n        {\n            itemExtractAssets = createMenuItem(\"Extract Assets...\", \"Extract Minecraft assets so you can get access to block and item textures\", KeyEvent.VK_E, Icons.extract);\n            menuDeveloper = new JMenu(\"Mod Developer\");\n            menuDeveloper.setMnemonic(KeyEvent.VK_M);\n            menuDeveloper.setIcon(Icons.mojang);\n            {\n                itemJavaCode = createMenuItem(\"Generate Java Code...\", \"Generate Java code for selection and collisions boxes\", KeyEvent.VK_J, Icons.java);\n            }\n            menuExamples = new JMenu(\"Examples\");\n            menuExamples.setMnemonic(KeyEvent.VK_E);\n            menuExamples.setIcon(Icons.new_);\n            {\n                itemModelCauldron = createMenuItem(\"Cauldron\", \"<html>Model by MrCrayfish<br><b>Private use only</b></html>\", KeyEvent.VK_C, Icons.model_cauldron);\n                itemModelChair = createMenuItem(\"Chair\", \"<html>Model by MrCrayfish<br><b>Private use only</b></html>\", KeyEvent.VK_C, Icons.model_chair);\n            }\n            itemDonate = createMenuItem(\"Donate (Patreon)\", \"Pledge to MrCrayfish\", KeyEvent.VK_D, Icons.patreon);\n            itemGitHub = createMenuItem(\"Source Code\", \"View Source Code\", KeyEvent.VK_S, Icons.github);\n        }\n\n        this.initActions();\n\n        /* Menu File */\n        menuFile.add(itemNew);\n        menuFile.addSeparator();\n        menuFile.add(itemLoad);\n        menuFile.add(itemSave);\n        menuFile.addSeparator();\n        menuFile.add(itemImport);\n        menuFile.add(itemExport);\n        menuFile.addSeparator();\n        menuFile.add(itemSettings);\n        menuFile.addSeparator();\n        menuFile.add(itemExit);\n        this.add(menuFile);\n\n        /* Menu Edit */\n        menuEdit.add(itemUndo);\n        menuEdit.add(itemRedo);\n        menuEdit.addMenuListener(new MenuAdapter()\n        {\n            @Override\n            public void menuSelected(MenuEvent e)\n            {\n                itemRedo.setEnabled(StateManager.canRestoreNextState());\n                itemUndo.setEnabled(StateManager.canRestorePreviousState());\n            }\n        });\n        this.add(menuEdit);\n\n        /* Menu Model Sub Menus */\n        menuRotate.add(itemRotateClockwise);\n        menuRotate.add(itemRotateCounterClockwise);\n\n        /* Menu Model */\n        menuModel.add(itemTextureManager);\n        menuModel.add(itemDisplayProps);\n        menuModel.add(itemOptimise);\n        menuModel.addSeparator();\n        menuModel.add(menuRotate);\n        this.add(menuModel);\n\n        /* Menu Screenshots */\n        menuScreenshot.add(itemSaveToDisk);\n        menuScreenshot.add(itemShareFacebook);\n        menuScreenshot.add(itemShareTwitter);\n        menuScreenshot.add(itemShareReddit);\n        menuScreenshot.add(itemImgurLink);\n        this.add(menuScreenshot);\n\n        /* Menu More Sub Menus */\n        menuDeveloper.add(itemJavaCode);\n        menuExamples.add(itemModelCauldron);\n        menuExamples.add(itemModelChair);\n\n        /* Menu More */\n        menuMore.add(itemExtractAssets);\n        menuMore.add(menuDeveloper);\n        menuMore.addSeparator();\n        menuMore.add(menuExamples);\n        menuMore.addSeparator();\n        menuMore.add(itemGitHub);\n        menuMore.add(itemDonate);\n        this.add(menuMore);\n    }\n\n    private void initActions()\n    {\n        itemNew.addActionListener(a -> newProject(creator));\n\n        itemLoad.addActionListener(a -> loadProject(creator));\n\n        itemSave.addActionListener(a -> saveProject(creator));\n\n        itemImport.addActionListener(a -> showImportJson(creator));\n\n        itemExport.addActionListener(a -> showExportJson(creator));\n\n        itemJavaCode.addActionListener(a -> showExportJavaCode(creator, a));\n\n        itemSettings.addActionListener(a -> showSettings(creator));\n\n        itemExit.addActionListener(a -> creator.close());\n\n        itemTextureManager.addActionListener(a ->\n        {\n            TextureManager manager = new TextureManager(creator, creator.getElementManager(), Dialog.ModalityType.APPLICATION_MODAL, false);\n            manager.setLocationRelativeTo(null);\n            manager.setVisible(true);\n        });\n\n        itemDisplayProps.addActionListener(a -> showDisplayProperties(creator));\n\n        itemOptimise.addActionListener(a -> optimizeModel(creator));\n\n        itemRotateClockwise.addActionListener(a -> Actions.rotateModel(creator.getElementManager(), true));\n\n        itemRotateCounterClockwise.addActionListener(a -> Actions.rotateModel(creator.getElementManager(), false));\n\n        itemSaveToDisk.addActionListener(a ->\n        {\n            JFileChooser chooser = new JFileChooser();\n            chooser.setDialogTitle(\"Output Directory\");\n            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n            chooser.setApproveButtonText(\"Save\");\n\n            FileNameExtensionFilter filter = new FileNameExtensionFilter(\"PNG (.png)\", \"png\");\n            chooser.setFileFilter(filter);\n\n            String dir = Settings.getScreenshotDir();\n\n            if(dir != null)\n            {\n                chooser.setCurrentDirectory(new File(dir));\n            }\n\n            int returnVal = chooser.showSaveDialog(null);\n            if(returnVal == JFileChooser.APPROVE_OPTION)\n            {\n                if(chooser.getSelectedFile().exists())\n                {\n                    returnVal = JOptionPane.showConfirmDialog(null, \"A file already exists with that name, are you sure you want to override?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n                }\n                if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)\n                {\n                    File location = chooser.getSelectedFile().getParentFile();\n                    Settings.setScreenshotDir(location.toString());\n\n                    String filePath = chooser.getSelectedFile().getAbsolutePath();\n                    if(!filePath.endsWith(\".png\"))\n                    {\n                        chooser.setSelectedFile(new File(filePath + \".png\"));\n                    }\n                    creator.setSidebar(null);\n                    creator.startScreenshot(new PendingScreenshot(chooser.getSelectedFile(), null));\n                }\n            }\n        });\n\n        itemShareFacebook.addActionListener(a ->\n        {\n            creator.setSidebar(null);\n            creator.startScreenshot(new PendingScreenshot(null, file ->\n            {\n                try\n                {\n                    String url = Uploader.upload(file);\n                    Screenshot.shareToFacebook(url);\n                }\n                catch(Exception e)\n                {\n                    e.printStackTrace();\n                }\n            }));\n        });\n\n        itemShareTwitter.addActionListener(a ->\n        {\n            creator.setSidebar(null);\n            creator.startScreenshot(new PendingScreenshot(null, file ->\n            {\n                try\n                {\n                    String url = Uploader.upload(file);\n                    Screenshot.shareToTwitter(url);\n                }\n                catch(Exception e)\n                {\n                    e.printStackTrace();\n                }\n            }));\n        });\n\n        itemShareReddit.addActionListener(a ->\n        {\n            creator.setSidebar(null);\n            creator.startScreenshot(new PendingScreenshot(null, file ->\n            {\n                try\n                {\n                    String url = Uploader.upload(file);\n                    Screenshot.shareToReddit(url);\n                }\n                catch(Exception e)\n                {\n                    e.printStackTrace();\n                }\n            }));\n        });\n\n        itemImgurLink.addActionListener(a ->\n        {\n            creator.setSidebar(null);\n            creator.startScreenshot(new PendingScreenshot(null, file -> SwingUtilities.invokeLater(() ->\n            {\n                try\n                {\n                    String url = Uploader.upload(file);\n\n                    JOptionPane message = new JOptionPane();\n                    String title;\n\n                    if(url != null && !url.equals(\"null\"))\n                    {\n                        StringSelection text = new StringSelection(url);\n                        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(text, null);\n                        title = \"Success\";\n                        message.setMessage(\"<html><b>\" + url + \"</b> has been copied to your clipboard.</html>\");\n                    }\n                    else\n                    {\n                        title = \"Error\";\n                        message.setMessage(\"Failed to upload screenshot. Check your internet connection then try again.\");\n                    }\n\n                    JDialog dialog = message.createDialog(this, title);\n                    dialog.setLocationRelativeTo(null);\n                    dialog.setModal(false);\n                    dialog.setVisible(true);\n                }\n                catch(Exception e)\n                {\n                    e.printStackTrace();\n                }\n            })));\n        });\n\n        itemGitHub.addActionListener(a -> Util.openUrl(Constants.URL_GITHUB));\n\n        itemDonate.addActionListener(a -> Util.openUrl(Constants.URL_DONATE));\n\n        itemExtractAssets.addActionListener(a -> showExtractAssets(creator));\n\n        itemModelCauldron.addActionListener(a ->\n        {\n            StateManager.clear();\n            Util.loadModelFromJar(creator.getElementManager(), getClass(), \"models/cauldron\");\n            StateManager.pushState(creator.getElementManager());\n        });\n\n        itemModelChair.addActionListener(a ->\n        {\n            StateManager.clear();\n            Util.loadModelFromJar(creator.getElementManager(), getClass(), \"models/modern_chair\");\n            StateManager.pushState(creator.getElementManager());\n        });\n\n        itemUndo.addActionListener(a -> StateManager.restorePreviousState(creator.getElementManager()));\n\n        itemRedo.addActionListener(a -> StateManager.restoreNextState(creator.getElementManager()));\n    }\n\n    private JMenuItem createMenuItem(String name, String tooltip, int mnemonic, Icon icon)\n    {\n        return createMenuItem(name, tooltip, mnemonic, icon, -1, -1, -1);\n    }\n\n    private JMenuItem createMenuItem(String name, String tooltip, int mnemonic, Icon icon, int awtCode, int keyCode, int modifiers)\n    {\n        JMenuItem item = new JMenuItem(name);\n        item.setToolTipText(tooltip);\n        item.setMnemonic(mnemonic);\n        item.setIcon(icon);\n\n        if(awtCode != -1 && keyCode != -1 && modifiers != -1)\n        {\n            KeyStroke shortcut = KeyStroke.getKeyStroke(awtCode, modifiers);\n            if(shortcut != null)\n            {\n                String shortcutText = KeyboardUtil.convertKeyStokeToString(shortcut);\n                item.setLayout(new FlowLayout(FlowLayout.RIGHT, 0, 0));\n                JLabel label = new JLabel(\"<html><p style='color:#666666;font-size:9px'>\" + shortcutText + \"<p></html>\");\n                item.add(label);\n                Dimension size = new Dimension((int) Math.ceil(item.getPreferredSize().getWidth() + label.getPreferredSize().getWidth()) + 10, 20);\n                item.setPreferredSize(size);\n            }\n\n            if(shortcut != null)\n            {\n                creator.registerKeyAction(new ModelCreator.KeyAction(awtCode, keyCode, (modifier, pressed) ->\n                {\n                    if(pressed && modifier == modifiers)\n                    {\n                        for(ActionListener listener : item.getActionListeners())\n                        {\n                            listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, item.getActionCommand(), modifier));\n                        }\n                    }\n                }));\n            }\n        }\n\n        return item;\n    }\n\n    public static void newProject(ModelCreator creator)\n    {\n        int returnVal = JOptionPane.showConfirmDialog(creator, \"You current work will be cleared, are you sure?\", \"Note\", JOptionPane.YES_NO_OPTION);\n        if(returnVal == JOptionPane.YES_OPTION)\n        {\n            TextureManager.clear();\n            StateManager.clear();\n            creator.getElementManager().reset();\n            creator.getElementManager().updateValues();\n            DisplayPropertiesDialog.update(creator);\n            StateManager.pushState(creator.getElementManager());\n        }\n    }\n\n    public static void loadProject(ModelCreator creator)\n    {\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Load Project\");\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n        chooser.setApproveButtonText(\"Load\");\n\n        FileNameExtensionFilter filter = new FileNameExtensionFilter(\"Model (.model)\", \"model\");\n        chooser.setFileFilter(filter);\n\n        String dir = Settings.getModelDir();\n\n        if(dir != null)\n        {\n            chooser.setCurrentDirectory(new File(dir));\n        }\n\n        int returnVal = chooser.showOpenDialog(null);\n        if(returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            if(creator.getElementManager().getElementCount() > 0)\n            {\n                returnVal = JOptionPane.showConfirmDialog(null, \"Your current project will be cleared, are you sure you want to continue?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n            }\n            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)\n            {\n                File location = chooser.getSelectedFile().getParentFile();\n                Settings.setModelDir(location.toString());\n\n                TextureManager.clear();\n                StateManager.clear();\n                ProjectManager.loadProject(creator.getElementManager(), chooser.getSelectedFile().getAbsolutePath());\n                DisplayPropertiesDialog.update(creator);\n                StateManager.pushState(creator.getElementManager());\n            }\n        }\n    }\n\n    public static void saveProject(ModelCreator creator)\n    {\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Save Project\");\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n        chooser.setApproveButtonText(\"Save\");\n\n        FileNameExtensionFilter filter = new FileNameExtensionFilter(\"Model (.model)\", \"model\");\n        chooser.setFileFilter(filter);\n        String dir = Settings.getModelDir();\n\n        if(dir != null)\n        {\n            chooser.setCurrentDirectory(new File(dir));\n        }\n\n        int returnVal = chooser.showSaveDialog(null);\n        if(returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            if(chooser.getSelectedFile().exists())\n            {\n                returnVal = JOptionPane.showConfirmDialog(null, \"A file already exists with that name, are you sure you want to override?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n            }\n            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)\n            {\n                File location = chooser.getSelectedFile().getParentFile();\n                Settings.setModelDir(location.toString());\n\n                String filePath = chooser.getSelectedFile().getAbsolutePath();\n                if(!filePath.endsWith(\".model\"))\n                {\n                    filePath += \".model\";\n                }\n                ProjectManager.saveProject(creator.getElementManager(), filePath);\n            }\n        }\n    }\n\n    public static void optimizeModel(ModelCreator creator)\n    {\n        int result = JOptionPane.showConfirmDialog(null, \"<html>Are you sure you want to optimize the model?<br/>It is recommended you save the project before running this<br/>action, otherwise you will have to re-enable the disabled faces.<html>\", \"Optimize Confirmation\", JOptionPane.YES_NO_OPTION);\n        if(result == JOptionPane.YES_OPTION)\n        {\n            int count = Actions.optimiseModel(creator.getElementManager());\n            JOptionPane.showMessageDialog(null, \"<html>Optimizing the model disabled <b>\" + count + \"</b> faces</html>\", \"Optimization Success\", JOptionPane.INFORMATION_MESSAGE);\n        }\n    }\n\n    public static void showImportJson(ModelCreator creator)\n    {\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Import JSON Model\");\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n        chooser.setApproveButtonText(\"Import\");\n\n        FileNameExtensionFilter filter = new FileNameExtensionFilter(\"JSON (.json)\", \"json\");\n        chooser.setFileFilter(filter);\n\n        String dir = Settings.getJSONDir();\n        if(dir != null)\n        {\n            chooser.setCurrentDirectory(new File(dir));\n        }\n\n        int returnVal = chooser.showOpenDialog(null);\n        if(returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            if(creator.getElementManager().getElementCount() > 0)\n            {\n                returnVal = JOptionPane.showConfirmDialog(null, \"Your current project will be cleared, are you sure you want to continue?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n            }\n            if(returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)\n            {\n                File location = chooser.getSelectedFile().getParentFile();\n                Settings.setJSONDir(location.toString());\n\n                TextureManager.clear();\n                StateManager.clear();\n                Importer importer = new Importer(creator.getElementManager(), chooser.getSelectedFile().getAbsolutePath());\n                importer.importFromJSON();\n                StateManager.pushState(creator.getElementManager());\n            }\n            creator.getElementManager().updateValues();\n        }\n    }\n\n    public static void showExportJson(ModelCreator creator)\n    {\n        JDialog dialog = new JDialog(creator, \"Export JSON Model\", Dialog.ModalityType.APPLICATION_MODAL);\n\n        JPanel panel = new JPanel(new BorderLayout());\n        panel.setPreferredSize(new Dimension(500, 225));\n\n        SpringLayout springLayout = new SpringLayout();\n        JPanel exportDir = new JPanel(springLayout);\n\n        JLabel labelName = new JLabel(\"Name\");\n        labelName.setHorizontalAlignment(SwingConstants.RIGHT);\n        exportDir.add(labelName);\n\n        JTextField textFieldName = new JTextField();\n        textFieldName.setPreferredSize(new Dimension(100, 24));\n        textFieldName.setCaretPosition(0);\n        exportDir.add(textFieldName);\n\n        JTextField textFieldDestination = new JTextField();\n        textFieldDestination.setPreferredSize(new Dimension(100, 24));\n\n        String exportJsonDir = Settings.getExportJSONDir();\n        if(exportJsonDir != null)\n        {\n            textFieldDestination.setText(exportJsonDir);\n        }\n        else\n        {\n            String userHome = System.getProperty(\"user.home\", \".\");\n            textFieldDestination.setText(userHome);\n        }\n\n        textFieldDestination.setEditable(false);\n        textFieldDestination.setFocusable(false);\n        textFieldDestination.setCaretPosition(0);\n        exportDir.add(textFieldDestination);\n\n        JButton btnBrowserDir = new JButton(\"Browse\");\n        btnBrowserDir.setPreferredSize(new Dimension(80, 24));\n        btnBrowserDir.setIcon(Icons.load);\n        btnBrowserDir.addActionListener(e ->\n        {\n            JFileChooser chooser = new JFileChooser();\n            chooser.setDialogTitle(\"Export Destination\");\n            chooser.setCurrentDirectory(new File(textFieldDestination.getText()));\n            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\n            chooser.setApproveButtonText(\"Select\");\n            int returnVal = chooser.showOpenDialog(dialog);\n            if(returnVal == JFileChooser.APPROVE_OPTION)\n            {\n                File file = chooser.getSelectedFile();\n                if(file != null)\n                {\n                    textFieldDestination.setText(file.getAbsolutePath());\n                }\n            }\n        });\n        exportDir.add(btnBrowserDir);\n\n        JLabel labelExportDir = new JLabel(\"Destination\");\n        exportDir.add(labelExportDir);\n\n        //JComponent optionSeparator = DefaultComponentFactory.getInstance().createSeparator(\"Export Options\");\n        JComponent optionSeparator = new JSeparator();\n        exportDir.add(optionSeparator);\n\n        JCheckBox checkBoxOptimize = ComponentUtil.createCheckBox(\"Optimize Model\", \"Removes unnecessary faces that can't been seen in the model\", true);\n        exportDir.add(checkBoxOptimize);\n\n        JCheckBox checkBoxDisplayProps = ComponentUtil.createCheckBox(\"Include Display Properties\", \"Adds the display definitions (first-person, third-person, etc) to the model file\", true);\n        exportDir.add(checkBoxDisplayProps);\n\n        JCheckBox checkBoxElementNames = ComponentUtil.createCheckBox(\"Include Element Names\", \"The name of each element will be added to it's entry in the json model elements array. Useful for identifying elements, and when importing back into Model Creator, it will use those names\", true);\n        exportDir.add(checkBoxElementNames);\n\n        JSeparator separator = new JSeparator();\n        exportDir.add(separator);\n\n\t\t/* Constraints */\n\n        springLayout.putConstraint(SpringLayout.NORTH, labelName, 3, SpringLayout.NORTH, textFieldName);\n        springLayout.putConstraint(SpringLayout.WEST, labelName, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.EAST, labelName, -5, SpringLayout.WEST, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.NORTH, textFieldName, 10, SpringLayout.NORTH, exportDir);\n        springLayout.putConstraint(SpringLayout.WEST, textFieldName, 0, SpringLayout.WEST, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.EAST, textFieldName, 0, SpringLayout.EAST, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.WEST, optionSeparator, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.EAST, optionSeparator, -10, SpringLayout.EAST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, optionSeparator, 10, SpringLayout.SOUTH, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.EAST, btnBrowserDir, -10, SpringLayout.EAST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, textFieldDestination, 10, SpringLayout.SOUTH, textFieldName);\n        springLayout.putConstraint(SpringLayout.WEST, textFieldDestination, 5, SpringLayout.EAST, labelExportDir);\n        springLayout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);\n        springLayout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);\n        springLayout.putConstraint(SpringLayout.WEST, labelExportDir, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, checkBoxOptimize, 5, SpringLayout.SOUTH, optionSeparator);\n        springLayout.putConstraint(SpringLayout.WEST, checkBoxOptimize, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, checkBoxDisplayProps, 0, SpringLayout.SOUTH, checkBoxOptimize);\n        springLayout.putConstraint(SpringLayout.WEST, checkBoxDisplayProps, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, checkBoxElementNames, 0, SpringLayout.SOUTH, checkBoxDisplayProps);\n        springLayout.putConstraint(SpringLayout.WEST, checkBoxElementNames, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.WEST, separator, 10, SpringLayout.WEST, exportDir);\n        springLayout.putConstraint(SpringLayout.EAST, separator, -10, SpringLayout.EAST, exportDir);\n        springLayout.putConstraint(SpringLayout.NORTH, separator, 5, SpringLayout.SOUTH, checkBoxElementNames);\n        springLayout.putConstraint(SpringLayout.SOUTH, separator, 5, SpringLayout.SOUTH, exportDir);\n\n        panel.setPreferredSize(panel.getPreferredSize());\n        panel.add(exportDir, BorderLayout.CENTER);\n\n        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10));\n\n        JButton btnCancel = new JButton(\"Cancel\");\n        btnCancel.addActionListener(new AbstractAction()\n        {\n            @Override\n            public void actionPerformed(ActionEvent e)\n            {\n                dialog.dispose();\n            }\n        });\n        buttons.add(btnCancel);\n\n        JButton btnExport = new JButton(\"Export\");\n        btnExport.addActionListener(e ->\n        {\n            String name = textFieldName.getText().trim();\n            if(!textFieldDestination.getText().isEmpty() && !name.isEmpty())\n            {\n                File destination = new File(textFieldDestination.getText());\n                destination.mkdirs();\n\n                File modelFile = new File(destination, textFieldName.getText() + \".json\");\n                if(modelFile.exists())\n                {\n                    int returnVal = JOptionPane.showConfirmDialog(dialog, \"A file for that name already exists in the directory. Are you sure you want to override it?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n                    if(returnVal != JOptionPane.YES_OPTION)\n                    {\n                        return;\n                    }\n                }\n\n                try\n                {\n                    modelFile.createNewFile();\n                }\n                catch(IOException e1)\n                {\n                    JOptionPane.showMessageDialog(dialog, \"Unable to create the file. Check that your destination folder is writable\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                }\n\n                dialog.dispose();\n\n                ExporterModel exporter = new ExporterModel(creator.getElementManager());\n                exporter.setOptimize(checkBoxOptimize.isSelected());\n                exporter.setDisplayProps(checkBoxDisplayProps.isSelected());\n                exporter.setIncludeNames(checkBoxElementNames.isSelected());\n                if(exporter.writeFile(modelFile) == null)\n                {\n                    modelFile.delete();\n                    JOptionPane.showMessageDialog(dialog, \"An error occured while exporting the model. Please try again\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                }\n                else\n                {\n                    Settings.setExportJSONDir(textFieldDestination.getText());\n                    int returnVal = JOptionPane.showOptionDialog(dialog, \"Model exported successfully!\", \"Success\", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, new Object[]{\"Open Folder\", \"Close\"}, \"Close\");\n                    if(returnVal == 0)\n                    {\n                        Desktop desktop = Desktop.getDesktop();\n                        try\n                        {\n                            desktop.open(destination);\n                        }\n                        catch(IOException e1)\n                        {\n                            e1.printStackTrace();\n                        }\n                    }\n                }\n            }\n        });\n        buttons.add(btnExport);\n\n        panel.add(buttons, BorderLayout.SOUTH);\n\n        dialog.add(panel);\n\n        dialog.pack();\n        dialog.setResizable(false);\n        dialog.setLocationRelativeTo(null);\n        dialog.setVisible(true);\n    }\n\n    private static void showExportJavaCode(ModelCreator creator, ActionEvent actionEvent)\n    {\n        JCheckBox includeFields = ComponentUtil.createCheckBox(\"Generate Fields\", \"Include decelerations of the AABBs fields that represent the model's elements\", true);\n        JCheckBox includeMethods = ComponentUtil.createCheckBox(\"Generate Methods\", \"Include bounds, raytracing, & collision methods\", true);\n        JCheckBox useBoundsHelper = ComponentUtil.createCheckBox(\"Use Bounds Helper\", \"Fields and methods use MrCrayfish's Bounds helper class, and target his code-base\", false);\n        JCheckBox generateRotatedBounds = ComponentUtil.createCheckBox(\"Make Rotatable\", \"Use Bounds helper class to create AABB rotation arrays for each element\", false);\n\n        useBoundsHelper.addActionListener(e -> generateRotatedBounds.setEnabled(useBoundsHelper.isSelected()));\n\n        String mcTooltip = \"Select Minecraft version\";\n        JPanel panelMC = new JPanel(new FlowLayout(FlowLayout.LEFT));\n\n        JLabel mcLabel = new JLabel(\"MC Version\");\n        mcLabel.setToolTipText(mcTooltip);\n        panelMC.add(mcLabel);\n\n        JComboBox<ExporterJavaCode.Version> mcVersion = new JComboBox<>(ExporterJavaCode.Version.values());\n        mcVersion.setToolTipText(mcTooltip);\n        mcVersion.setPreferredSize(new Dimension(60, 24));\n        mcVersion.addActionListener(e ->\n        {\n            ExporterJavaCode.Version version = (ExporterJavaCode.Version) mcVersion.getSelectedItem();\n            switch(version)\n            {\n                case V_1_12:\n                    useBoundsHelper.setText(\"Use Bounds Helper\");\n                    break;\n                default:\n                    useBoundsHelper.setText(\"Use VoxelShapeHelper\");\n                    break;\n            }\n        });\n        panelMC.add(mcVersion);\n\n        JPanel panelMain = new JPanel();\n        panelMain.setLayout(new GridLayout(1, 2));\n        panelMain.add(includeFields);\n        panelMain.add(includeMethods);\n\n        JPanel parent = new JPanel();\n        parent.setLayout(new BoxLayout(parent, BoxLayout.Y_AXIS));\n        parent.add(panelMC);\n\n        JPanel controls = new JPanel(new GridLayout(1, 1));\n        controls.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), \"<html><b>Options</b></html>\"));\n        controls.add(panelMain);\n        parent.add(controls);\n\n        if((actionEvent.getModifiers() & ActionEvent.SHIFT_MASK) > 0 && (actionEvent.getModifiers() & ActionEvent.CTRL_MASK) > 0)\n        {\n            includeMethods.setToolTipText(includeMethods.getToolTipText().replace(\", raytracing\", \"\"));\n            useBoundsHelper.setForeground(Color.BLACK);\n            useBoundsHelper.setSelected(true);\n            generateRotatedBounds.setSelected(true);\n\n            JPanel panelCray = new JPanel();\n            panelCray.setLayout(new GridLayout(1, 2));\n            panelCray.add(useBoundsHelper);\n            panelCray.add(generateRotatedBounds);\n\n            controls.setLayout(new GridLayout(2, 1));\n            controls.add(panelCray);\n        }\n\n        JLabel infoLabel = new JLabel(\"<html><body><p style='width:260px'>\" +\n                \"Use this tool to generate Java code for selection and collision boxes. \" +\n                \"This includes AxisAlignedBB fields and the required methods for the \" +\n                \"Block class to apply them. It should be noted that elements in the model \" +\n                \"that have been rotated will be ignored when generating.\" +\n                \"</p></body></html>\");\n        JLabel questionLabel = new JLabel(\"<html><body><p style='width:260px'>\" +\n                \"Would you like the code to be copied to your clipboard or saved to a text file?\" +\n                \"</p></body></html>\");\n\n        int returnValDestination = JOptionPane.showOptionDialog(creator, new Object[] {infoLabel, Box.createHorizontalStrut(20), Box.createHorizontalStrut(20), new JSeparator(),\n                        parent, new JSeparator(), Box.createHorizontalStrut(20), Box.createHorizontalStrut(20),\n                        questionLabel}, \"Generate Java Code\", JOptionPane.YES_NO_OPTION,\n                        JOptionPane.INFORMATION_MESSAGE, null, new String[] {\"Clipboard\", \"Text File\"}, \"Clipboard\");\n        if (!includeFields.isSelected() && !includeMethods.isSelected())\n        {\n            JOptionPane.showMessageDialog(creator, \"Either AxisAlignedBBs or methods must be selected.\", \"None Selected\", JOptionPane.INFORMATION_MESSAGE);\n            return;\n        }\n        ExporterJavaCode exporter = new ExporterJavaCode(creator, includeFields.isSelected(), includeMethods.isSelected(), useBoundsHelper.isSelected(), generateRotatedBounds.isSelected());\n        exporter.setVersion((ExporterJavaCode.Version) mcVersion.getSelectedItem());\n        if (returnValDestination == JOptionPane.CLOSED_OPTION)\n            return;\n\n        if (returnValDestination == JOptionPane.OK_OPTION)\n        {\n            try\n            {\n                exporter.writeCodeToClipboard();\n            }\n            catch (Exception exception)\n            {\n                JOptionPane.showMessageDialog(creator, \"An error occured while copying code to your clipboard.\", \"Error\", JOptionPane.ERROR_MESSAGE);\n            }\n            return;\n        }\n\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Output Directory\");\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n        chooser.setApproveButtonText(\"Export\");\n\n        FileNameExtensionFilter filter = new FileNameExtensionFilter(\"TXT (.txt)\", \"txt\");\n        chooser.setFileFilter(filter);\n\n        String dir = Settings.getJSONDir();\n\n        if (dir != null)\n        {\n            chooser.setCurrentDirectory(new File(dir));\n        }\n\n        int returnVal = chooser.showSaveDialog(null);\n        if (returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            if (chooser.getSelectedFile().exists())\n            {\n                returnVal = JOptionPane.showConfirmDialog(null, \"A file already exists with that name, are you sure you want to override?\", \"Warning\", JOptionPane.YES_NO_OPTION);\n            }\n            if (returnVal != JOptionPane.NO_OPTION && returnVal != JOptionPane.CLOSED_OPTION)\n            {\n                File location = chooser.getSelectedFile().getParentFile();\n                Settings.setJSONDir(location.toString());\n                String filePath = chooser.getSelectedFile().getAbsolutePath();\n                if (!filePath.endsWith(\".txt\"))\n                    chooser.setSelectedFile(new File(filePath + \".txt\"));\n\n                exporter.export(chooser.getSelectedFile());\n            }\n        }\n    }\n\n    public static void showSettings(ModelCreator creator)\n    {\n        JDialog dialog = new JDialog(creator, \"Settings\", Dialog.ModalityType.APPLICATION_MODAL);\n\n        JPanel panel = new JPanel(new BorderLayout());\n        panel.setPreferredSize(new Dimension(500, 300));\n        dialog.add(panel);\n\n        JTabbedPane tabbedPane = new JTabbedPane();\n        panel.add(tabbedPane, BorderLayout.CENTER);\n\n        SpringLayout generalSpringLayout = new SpringLayout();\n        JPanel generalPanel = new JPanel(generalSpringLayout);\n        tabbedPane.addTab(\"General\", generalPanel);\n\n        JPanel optionsPanel = new JPanel(new GridLayout(1, 2));\n        generalPanel.add(optionsPanel);\n\n        JPanel undoPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));\n        optionsPanel.add(undoPanel);\n\n        JLabel labelUndoLimit = new JLabel(\"Undo / Redo Limit\");\n        undoPanel.add(labelUndoLimit);\n\n        final Boolean[] changed = {false};\n        SpinnerNumberModel undoSpinnerNumberModel = new SpinnerNumberModel();\n        undoSpinnerNumberModel.setMinimum(1);\n        JSpinner undoLimitSpinner = new JSpinner(undoSpinnerNumberModel);\n        undoLimitSpinner.setPreferredSize(new Dimension(40, 24));\n        undoLimitSpinner.setValue(Settings.getUndoLimit());\n        undoLimitSpinner.addChangeListener(e ->\n        {\n            if(!changed[0])\n            {\n                JOptionPane.showMessageDialog(dialog, \"Increasing the undo/redo limit will increase the amount of memory the program use. Change this setting with caution.\", \"Warning\", JOptionPane.WARNING_MESSAGE);\n                changed[0] = true;\n            }\n        });\n        undoPanel.add(undoLimitSpinner);\n\n        JCheckBox checkBoxCardinalPoints = ComponentUtil.createCheckBox(\"Show Cardinal Points\", \"\", Settings.getCardinalPoints());\n        optionsPanel.add(checkBoxCardinalPoints);\n\n        JSeparator separator = new JSeparator();\n        generalPanel.add(separator);\n\n        String assetsPath = Settings.getAssetsDir() != null ? Settings.getAssetsDir() : \"\";\n        JPanel texturePathPanel = createDirectorySelector(\"Assets Path\", dialog, assetsPath);\n        generalPanel.add(texturePathPanel);\n\n        JSeparator separator2 = new JSeparator();\n        generalPanel.add(separator2);\n\n        String imageEditorPath = Settings.getImageEditor() != null ? Settings.getImageEditor() : \"\";\n        JPanel imageEditorPanel = ComponentUtil.createFileSelector(\"Image Editor\", dialog, imageEditorPath, null, null);\n        generalPanel.add(imageEditorPanel);\n\n        JLabel labelArguments = new JLabel(\"Arguments\");\n        generalPanel.add(labelArguments);\n\n        String imageEditorArgs = Settings.getImageEditorArgs() != null ? Settings.getImageEditorArgs() : \"\\\"%s\\\"\";\n        JTextField textFieldArguments = new JTextField(imageEditorArgs);\n        textFieldArguments.setPreferredSize(new Dimension(0, 24));\n        generalPanel.add(textFieldArguments);\n\n        generalSpringLayout.putConstraint(SpringLayout.WEST, optionsPanel, 5, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, optionsPanel, 5, SpringLayout.NORTH, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, optionsPanel, 5, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, separator, 0, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, separator, 0, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, separator, 5, SpringLayout.SOUTH, optionsPanel);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, texturePathPanel, -10, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, texturePathPanel, 10, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, texturePathPanel, 10, SpringLayout.SOUTH, separator);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, separator2, 0, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, separator2, 0, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, separator2, 10, SpringLayout.SOUTH, texturePathPanel);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, imageEditorPanel, -10, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, imageEditorPanel, 10, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, imageEditorPanel, 10, SpringLayout.SOUTH, separator2);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, labelArguments, 10, SpringLayout.WEST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, labelArguments, 2, SpringLayout.NORTH, textFieldArguments);\n        generalSpringLayout.putConstraint(SpringLayout.EAST, textFieldArguments, -10, SpringLayout.EAST, generalPanel);\n        generalSpringLayout.putConstraint(SpringLayout.WEST, textFieldArguments, 20, SpringLayout.EAST, labelArguments);\n        generalSpringLayout.putConstraint(SpringLayout.NORTH, textFieldArguments, 10, SpringLayout.SOUTH, imageEditorPanel);\n\n        JPanel colorGrid = new JPanel();\n        colorGrid.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));\n        JScrollPane colorScrollPane = new JScrollPane(colorGrid);\n        tabbedPane.addTab(\"Appearance\", colorScrollPane);\n\n        colorGrid.add(createColorSelector(dialog, \"North Face\", Face.getFaceColour(Face.NORTH), createFaceColorProcessor(Face.NORTH)));\n        colorGrid.add(createColorSelector(dialog, \"East Face\", Face.getFaceColour(Face.EAST), createFaceColorProcessor(Face.EAST)));\n        colorGrid.add(createColorSelector(dialog, \"South Face\", Face.getFaceColour(Face.SOUTH), createFaceColorProcessor(Face.SOUTH)));\n        colorGrid.add(createColorSelector(dialog, \"West Face\", Face.getFaceColour(Face.WEST), createFaceColorProcessor(Face.WEST)));\n        colorGrid.add(createColorSelector(dialog, \"Up Face\", Face.getFaceColour(Face.UP), createFaceColorProcessor(Face.UP)));\n        colorGrid.add(createColorSelector(dialog, \"Down Face\", Face.getFaceColour(Face.DOWN), createFaceColorProcessor(Face.DOWN)));\n\n        JButton btnReset = new JButton(\"Reset Colors\");\n        btnReset.addActionListener(a ->\n        {\n            Face.setFaceColors(Settings.DEFAULT_FACE_COLORS);\n            dialog.dispose();\n            JOptionPane.showMessageDialog(creator, \"Colors reset\");\n        });\n        colorGrid.add(btnReset);\n\n        colorGrid.setLayout(new GridLayout(colorGrid.getComponentCount(), 1, 20, 10));\n\n        dialog.addWindowListener(new WindowAdapter()\n        {\n            @Override\n            public void windowClosed(WindowEvent e)\n            {\n                Settings.setAssetsDir(getDirectoryFromSelector(texturePathPanel));\n                Settings.setUndoLimit((int) undoLimitSpinner.getValue());\n                Settings.setFaceColors(Face.getFaceColors());\n                Settings.setImageEditor(getDirectoryFromSelector(imageEditorPanel));\n                Settings.setImageEditorArgs(textFieldArguments.getText());\n                Settings.setCardinalPoints(checkBoxCardinalPoints.isSelected());\n            }\n        });\n\n        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n        dialog.requestFocus();\n        dialog.pack();\n        dialog.setResizable(false);\n        dialog.setLocationRelativeTo(null);\n        dialog.setVisible(true);\n    }\n\n    private static JPanel createDirectorySelector(String label, Component parent, String defaultDir)\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(100, 24));\n\n        JTextField textFieldDestination = new JTextField();\n        textFieldDestination.setPreferredSize(new Dimension(100, 24));\n        textFieldDestination.setText(defaultDir);\n        textFieldDestination.setEditable(false);\n        textFieldDestination.setFocusable(false);\n        textFieldDestination.setCaretPosition(0);\n        panel.add(textFieldDestination);\n\n        JButton btnBrowserDir = new JButton(\"Browse\");\n        btnBrowserDir.setPreferredSize(new Dimension(80, 24));\n        btnBrowserDir.setIcon(Icons.load);\n        btnBrowserDir.addActionListener(e ->\n        {\n            JFileChooser chooser = new JFileChooser();\n            chooser.setDialogTitle(\"Select a Folder\");\n            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\n            chooser.setApproveButtonText(\"Select\");\n            chooser.setCurrentDirectory(new File(defaultDir));\n            int returnVal = chooser.showOpenDialog(parent);\n            if(returnVal == JFileChooser.APPROVE_OPTION)\n            {\n                File file = chooser.getSelectedFile();\n                if(file != null)\n                {\n                    textFieldDestination.setText(file.getAbsolutePath());\n                }\n            }\n        });\n        panel.add(btnBrowserDir);\n\n        JLabel labelExportDir = new JLabel(label);\n        panel.add(labelExportDir);\n\n        layout.putConstraint(SpringLayout.NORTH, textFieldDestination, 0, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, textFieldDestination, 10, SpringLayout.EAST, labelExportDir);\n        layout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);\n        layout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.WEST, labelExportDir, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.EAST, btnBrowserDir, 0, SpringLayout.EAST, panel);\n\n        return panel;\n    }\n\n    public static String getDirectoryFromSelector(JPanel panel)\n    {\n        for(Component component : panel.getComponents())\n        {\n            if(component instanceof JTextField)\n            {\n                return ((JTextField) component).getText();\n            }\n        }\n        return \"\";\n    }\n\n    public static void showExtractAssets(ModelCreator creator)\n    {\n        JDialog dialog = new JDialog(creator, \"Extract Assets\", Dialog.ModalityType.APPLICATION_MODAL);\n        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(300, 150));\n        dialog.add(panel);\n\n        JLabel labelInfo = new JLabel(\"<html>This tool allows you to extract Minecraft's assets. The versions listed below are the ones you have downloaded with the Java edition of the game.</html>\");\n        panel.add(labelInfo);\n\n        JLabel labelMinecraftAssets = new JLabel(\"Minecraft Version\");\n        panel.add(labelMinecraftAssets);\n\n        JComboBox<String> comboBoxMinecraftVersions = new JComboBox<>();\n        comboBoxMinecraftVersions.setPreferredSize(new Dimension(40, 24));\n        Util.getMinecraftVersions().forEach(comboBoxMinecraftVersions::addItem);\n        panel.add(comboBoxMinecraftVersions);\n\n        JButton btnExtract = new JButton(\"Extract\");\n        btnExtract.setIcon(Icons.extract);\n        btnExtract.setPreferredSize(new Dimension(80, 24));\n        btnExtract.addActionListener(e ->\n        {\n            Util.extractMinecraftAssets((String) comboBoxMinecraftVersions.getSelectedItem(), dialog);\n            dialog.dispose();\n        });\n        panel.add(btnExtract);\n\n        layout.putConstraint(SpringLayout.NORTH, labelInfo, 10, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.EAST, labelInfo, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.WEST, labelInfo, 10, SpringLayout.WEST, panel);\n\n        layout.putConstraint(SpringLayout.NORTH, labelMinecraftAssets, 2, SpringLayout.NORTH, comboBoxMinecraftVersions);\n        layout.putConstraint(SpringLayout.WEST, labelMinecraftAssets, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, comboBoxMinecraftVersions, 15, SpringLayout.SOUTH, labelInfo);\n        layout.putConstraint(SpringLayout.WEST, comboBoxMinecraftVersions, 10, SpringLayout.EAST, labelMinecraftAssets);\n        layout.putConstraint(SpringLayout.EAST, comboBoxMinecraftVersions, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.SOUTH, btnExtract, -10, SpringLayout.SOUTH, panel);\n        layout.putConstraint(SpringLayout.EAST, btnExtract, -10, SpringLayout.EAST, panel);\n\n        dialog.pack();\n        dialog.setResizable(false);\n        dialog.setLocationRelativeTo(null);\n        dialog.setVisible(true);\n    }\n\n    public static JPanel createColorSelector(Window parent, String labelText, int startColor, Processor<Integer> processor)\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(200, 30));\n        panel.setBackground(new Color(0, 0, 0, 0));\n\n        JLabel label = new JLabel(labelText);\n        panel.add(label);\n\n        JPanel colorPanel = new JPanel();\n        colorPanel.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));\n        colorPanel.setBackground(new Color(startColor));\n        colorPanel.setPreferredSize(new Dimension(24, 24));\n        panel.add(colorPanel);\n\n        JButton button = new JButton(\"Change\");\n        button.setPreferredSize(new Dimension(80, 24));\n        button.addActionListener(e ->\n        {\n            int color = selectColor(parent, startColor);\n            if(processor.run(color))\n            {\n                colorPanel.setBackground(new Color(color));\n            }\n        });\n        panel.add(button);\n\n        layout.putConstraint(SpringLayout.WEST, label, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.VERTICAL_CENTER, label, 0, SpringLayout.VERTICAL_CENTER, panel);\n        layout.putConstraint(SpringLayout.EAST, label, 5, SpringLayout.WEST, colorPanel);\n        layout.putConstraint(SpringLayout.WEST, colorPanel, 80, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.VERTICAL_CENTER, colorPanel, 0, SpringLayout.VERTICAL_CENTER, panel);\n        layout.putConstraint(SpringLayout.EAST, colorPanel, -10, SpringLayout.WEST, button);\n        layout.putConstraint(SpringLayout.EAST, button, 0, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.VERTICAL_CENTER, button, 0, SpringLayout.VERTICAL_CENTER, panel);\n        return panel;\n    }\n\n    private static int selectColor(Window parent, int startColor)\n    {\n        JDialog dialog = new JDialog(parent, \"Select a Color\", Dialog.ModalityType.APPLICATION_MODAL);\n        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n\n        JPanel panel = new JPanel(new BorderLayout());\n        dialog.add(panel);\n\n        JColorChooser colorChooser = new JColorChooser();\n        colorChooser.setColor(startColor);\n        panel.add(colorChooser, BorderLayout.CENTER);\n\n        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));\n        panel.add(buttons, BorderLayout.SOUTH);\n\n        JButton btnExtract = new JButton(\"Select\");\n        btnExtract.setIcon(Icons.extract);\n        btnExtract.setPreferredSize(new Dimension(80, 24));\n        btnExtract.addActionListener(e -> dialog.dispose());\n        buttons.add(btnExtract);\n\n        dialog.pack();\n        dialog.setResizable(false);\n        dialog.setLocationRelativeTo(null);\n        dialog.setVisible(true);\n\n        return colorChooser.getColor().getRGB();\n    }\n\n    private static Processor<Integer> createFaceColorProcessor(int side)\n    {\n        return integer ->\n        {\n            if(Face.getFaceColour(side) != integer)\n            {\n                Face.setFaceColor(side, integer);\n                return true;\n            }\n            return false;\n        };\n    }\n\n    private static void showDisplayProperties(ModelCreator creator)\n    {\n        DisplayPropertiesDialog dialog = new DisplayPropertiesDialog(creator);\n        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n        dialog.addWindowListener(new WindowAdapter()\n        {\n            @Override\n            public void windowClosed(WindowEvent e)\n            {\n                Menu.displayPropertiesDialog = null;\n                ModelCreator.restoreStandardRenderer();\n            }\n        });\n        dialog.setLocationRelativeTo(null);\n        dialog.setLocation(dialog.getLocation().x - 500, dialog.getLocation().y);\n        dialog.setVisible(true);\n        dialog.requestFocus();\n\n        Menu.displayPropertiesDialog = dialog;\n        ModelCreator.setCanvasRenderer(DisplayProperties.RENDER_MAP.get(\"gui\"));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/MenuAdapter.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport javax.swing.event.MenuEvent;\nimport javax.swing.event.MenuListener;\n\n/**\n * Author: MrCrayfish\n */\npublic abstract class MenuAdapter implements MenuListener\n{\n    @Override\n    public void menuSelected(MenuEvent e)\n    {\n\n    }\n\n    @Override\n    public void menuDeselected(MenuEvent e)\n    {\n\n    }\n\n    @Override\n    public void menuCanceled(MenuEvent e)\n    {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/TextureEntryEditor.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcreator.TexturePath;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\nimport com.mrcrayfish.modelcreator.util.Util;\n\nimport javax.imageio.ImageIO;\nimport javax.swing.*;\nimport javax.swing.filechooser.FileNameExtensionFilter;\nimport java.awt.*;\nimport java.awt.event.FocusAdapter;\nimport java.awt.event.FocusEvent;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Locale;\n\n/**\n * Author: MrCrayfish\n */\npublic class TextureEntryEditor extends JDialog\n{\n    private TextureEntry entry;\n\n    private JLabel icon;\n    private JTextField textFieldKey;\n    private JTextField textFieldValue;\n    private boolean triedEditingValue = false;\n\n    private File texture = null;\n\n    public TextureEntryEditor(Window owner, TextureEntry entry, ModalityType type)\n    {\n        super(owner, \"Edit Texture Entry\", type);\n        this.entry = entry;\n        this.setPreferredSize(new Dimension(300, 370));\n        this.setResizable(false);\n        this.initComponents();\n        this.pack();\n    }\n\n    private void initComponents()\n    {\n        JPanel panel = new JPanel();\n        panel.setLayout(new SpringLayout());\n        this.add(panel);\n\n        JPanel image = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));\n        image.setPreferredSize(new Dimension(150, 150));\n        image.setBackground(Color.WHITE);\n        icon = new JLabel(resize(entry.getSource(), 150));\n        image.add(icon);\n        panel.add(image);\n\n        JPanel fileSelector = ComponentUtil.createFileSelector(\"File\", this, entry.getTextureFile().getAbsolutePath(), new FileNameExtensionFilter(\"PNG Image\", \"png\"), file ->\n        {\n            if(this.setTexture(file))\n            {\n                TexturePath texturePath = new TexturePath(file);\n                textFieldValue.setText(texturePath.toString());\n                return true;\n            }\n            return false;\n        });\n        panel.add(fileSelector);\n\n        JSeparator separator1 = new JSeparator();\n        panel.add(separator1);\n\n        JLabel labelKey = new JLabel(\"Key\");\n        panel.add(labelKey);\n\n        textFieldKey = new JTextField();\n        textFieldKey.setPreferredSize(new Dimension(0, 24));\n        textFieldKey.setText(entry.getKey());\n        panel.add(textFieldKey);\n\n        JLabel labelValue = new JLabel(\"Value\");\n        panel.add(labelValue);\n\n        textFieldValue = new JTextField();\n        textFieldValue.setPreferredSize(new Dimension(0, 24));\n        textFieldValue.setText(entry.getTexturePath().toString());\n        textFieldValue.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusGained(FocusEvent e)\n            {\n                if(!triedEditingValue)\n                {\n                    JOptionPane.showMessageDialog(TextureEntryEditor.this, \"Only edit this value if you are an advanced user. Changing this may cause the texture to not load in Minecraft\", \"Important\", JOptionPane.INFORMATION_MESSAGE);\n                    triedEditingValue = true;\n                }\n            }\n        });\n        panel.add(textFieldValue);\n\n        JSeparator separator2 = new JSeparator();\n        panel.add(separator2);\n\n        JButton btnRefresh = new JButton();\n        btnRefresh.setIcon(Icons.refresh);\n        btnRefresh.addActionListener(e -> this.setTexture(entry.getTextureFile()));\n        panel.add(btnRefresh);\n\n        JButton btnEdit = new JButton(\"Edit\");\n        btnEdit.setIcon(Icons.edit_image);\n        btnEdit.addActionListener(a ->\n        {\n            String program = Settings.getImageEditor();\n            if(!program.isEmpty())\n            {\n                try\n                {\n                    String command = program + \" \" + String.format(Settings.getImageEditorArgs(), entry.getTextureFile().getAbsolutePath());\n                    Runtime.getRuntime().exec(command);\n                }\n                catch(IOException e)\n                {\n                    e.printStackTrace();\n                }\n            }\n            else\n            {\n                int returnVal = JOptionPane.showConfirmDialog(this, \"Image Editor has not been configured. Do you want to open with default editor?\", \"Message\", JOptionPane.YES_NO_OPTION);\n                if(returnVal == JOptionPane.YES_OPTION)\n                {\n                    try\n                    {\n                        Desktop.getDesktop().edit(entry.getTextureFile());\n                    }\n                    catch(IOException e)\n                    {\n                        e.printStackTrace();\n                    }\n                }\n            }\n        });\n        panel.add(btnEdit);\n\n        JButton btnCancel = new JButton(\"Cancel\");\n        btnCancel.addActionListener(e -> this.dispose());\n        panel.add(btnCancel);\n\n        JButton btnSave = new JButton(\"Save\");\n        btnSave.setIcon(Icons.disk);\n        btnSave.addActionListener(e ->\n        {\n            String key = textFieldKey.getText().trim().toLowerCase(Locale.ENGLISH);\n            String value = textFieldValue.getText().trim().toLowerCase(Locale.ENGLISH);\n\n            if(!TextureEntry.KEY_PATTERN.matcher(key).matches())\n            {\n                JOptionPane.showMessageDialog(this, \"Invalid key format. It may only contain lowercase letters, numbers, and underscore.\", \"Key Error\", JOptionPane.ERROR_MESSAGE);\n                return;\n            }\n\n            TextureEntry foundEntry = TextureManager.getTexture(key);\n            if(foundEntry != null && foundEntry != entry)\n            {\n                JOptionPane.showMessageDialog(this, \"The key entered is already in use by another texture. Please choose a different key\", \"Key Error\", JOptionPane.ERROR_MESSAGE);\n                return;\n            }\n\n            if(\"particle\".equals(key))\n            {\n                JOptionPane.showMessageDialog(this, \"The key 'particle' is reserved. Please choose a different key\", \"Key Error\", JOptionPane.ERROR_MESSAGE);\n                return;\n            }\n\n            if(!TexturePath.PATTERN.matcher(value).matches())\n            {\n                JOptionPane.showMessageDialog(this, \"Invalid value format\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                return;\n            }\n\n            if(texture != null)\n            {\n                try\n                {\n                    Dimension dimension = Util.getImageDimension(texture);\n                    if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)\n                    {\n                        JOptionPane.showMessageDialog(this, \"Image size must be multiple of 16\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                        return;\n                    }\n                }\n                catch(IOException e1)\n                {\n                    JOptionPane.showMessageDialog(this, \"Unable to determine image dimensions\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                    return;\n                }\n                entry.setTextureFile(texture);\n            }\n            entry.setTexturePath(new TexturePath(value));\n            entry.setKey(key);\n            this.dispose();\n        });\n        panel.add(btnSave);\n\n        SpringLayout layout = (SpringLayout) panel.getLayout();\n        layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, image, 0, SpringLayout.HORIZONTAL_CENTER, panel);\n        layout.putConstraint(SpringLayout.NORTH, image, 10, SpringLayout.NORTH, panel);\n\n        layout.putConstraint(SpringLayout.WEST, fileSelector, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, fileSelector, 20, SpringLayout.SOUTH, image);\n        layout.putConstraint(SpringLayout.EAST, fileSelector, -10, SpringLayout.EAST, panel);\n\n        layout.putConstraint(SpringLayout.WEST, separator1, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, separator1, 10, SpringLayout.SOUTH, fileSelector);\n        layout.putConstraint(SpringLayout.EAST, separator1, 0, SpringLayout.EAST, panel);\n\n        layout.putConstraint(SpringLayout.WEST, labelKey, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, labelKey, 2, SpringLayout.NORTH, textFieldKey);\n\n        layout.putConstraint(SpringLayout.WEST, textFieldKey, 40, SpringLayout.WEST, labelKey);\n        layout.putConstraint(SpringLayout.NORTH, textFieldKey, 10, SpringLayout.SOUTH, separator1);\n        layout.putConstraint(SpringLayout.EAST, textFieldKey, -10, SpringLayout.EAST, panel);\n\n        layout.putConstraint(SpringLayout.WEST, labelValue, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, labelValue, 2, SpringLayout.NORTH, textFieldValue);\n\n        layout.putConstraint(SpringLayout.WEST, textFieldValue, 40, SpringLayout.WEST, labelValue);\n        layout.putConstraint(SpringLayout.NORTH, textFieldValue, 10, SpringLayout.SOUTH, textFieldKey);\n        layout.putConstraint(SpringLayout.EAST, textFieldValue, -10, SpringLayout.EAST, panel);\n\n        layout.putConstraint(SpringLayout.WEST, separator2, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, separator2, 10, SpringLayout.SOUTH, textFieldValue);\n        layout.putConstraint(SpringLayout.EAST, separator2, 0, SpringLayout.EAST, panel);\n\n        layout.putConstraint(SpringLayout.WEST, btnRefresh, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.SOUTH, btnRefresh, -10, SpringLayout.SOUTH, panel);\n\n        layout.putConstraint(SpringLayout.WEST, btnEdit, 10, SpringLayout.EAST, btnRefresh);\n        layout.putConstraint(SpringLayout.SOUTH, btnEdit, -10, SpringLayout.SOUTH, panel);\n\n        layout.putConstraint(SpringLayout.EAST, btnCancel, -10, SpringLayout.WEST, btnSave);\n        layout.putConstraint(SpringLayout.SOUTH, btnCancel, -10, SpringLayout.SOUTH, panel);\n\n        layout.putConstraint(SpringLayout.EAST, btnSave, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.SOUTH, btnSave, -10, SpringLayout.SOUTH, panel);\n    }\n\n    private static ImageIcon resize(BufferedImage source, int size)\n    {\n        Image scaledImage = source.getScaledInstance(size, size, java.awt.Image.SCALE_FAST);\n        return new ImageIcon(scaledImage);\n    }\n\n    private boolean setTexture(File file)\n    {\n        try\n        {\n            Dimension dimension = Util.getImageDimension(file);\n            if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)\n            {\n                JOptionPane.showMessageDialog(this, \"Image size must be multiple of 16\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                return false;\n            }\n        }\n        catch(IOException e1)\n        {\n            JOptionPane.showMessageDialog(this, \"Unable to determine image dimensions\", \"Error\", JOptionPane.ERROR_MESSAGE);\n            return false;\n        }\n        try\n        {\n            icon.setIcon(resize(ImageIO.read(file), 150));\n            texture = file;\n        }\n        catch(IOException e)\n        {\n            JOptionPane.showMessageDialog(this, \"Unable to load image\", \"Error\", JOptionPane.ERROR_MESSAGE);\n            return false;\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/component/TextureManager.java",
    "content": "package com.mrcrayfish.modelcreator.component;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcreator.TexturePath;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport com.mrcrayfish.modelcreator.util.Util;\n\nimport javax.swing.*;\nimport javax.swing.filechooser.FileNameExtensionFilter;\nimport java.awt.*;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Author: MrCrayfish\n */\npublic class TextureManager extends JDialog\n{\n    public static final Object LOCK = new Object();\n\n    public static final int CANCELLED = 1;\n    public static final int APPLIED = 1;\n\n    private static final List<TextureEntry> pendingLoad = new ArrayList<>();\n    private static final List<TextureEntry> pendingRemove = new ArrayList<>();\n    private static final List<TextureEntry> textureEntries = new ArrayList<>();\n    private static File lastLocation = null;\n\n    private ElementManager manager;\n    private JList<TextureEntry> textureEntryList;\n    private JButton btnApply;\n    private JButton btnCancel;\n    private JButton btnNew;\n    private JButton btnEdit;\n    private JButton btnRemove;\n    private int result;\n    private boolean canApply = true;\n\n    public TextureManager(Frame owner, ElementManager manager, ModalityType type, boolean canApply)\n    {\n        super(owner, \"Texture Manager\", type);\n        this.canApply = canApply;\n        this.manager = manager;\n        this.setPreferredSize(new Dimension(500, 392));\n        this.setResizable(false);\n        this.initComponents();\n        this.pack();\n    }\n\n    private void initComponents()\n    {\n        JPanel content = new JPanel();\n        content.setLayout(new SpringLayout());\n        this.add(content);\n\n        textureEntryList = new JList<>();\n        textureEntryList.setModel(new DefaultListModel<>());\n        textureEntryList.setCellRenderer(new TextureCellRenderer());\n        textureEntryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n        textureEntryList.addListSelectionListener(e ->\n        {\n            if(btnApply != null)\n            {\n                btnApply.setEnabled(true);\n            }\n            btnEdit.setEnabled(true);\n            btnRemove.setEnabled(true);\n        });\n\n        JScrollPane scrollPane = new JScrollPane(textureEntryList);\n        scrollPane.getVerticalScrollBar().setUnitIncrement(10);\n        content.add(scrollPane);\n\n        if(canApply)\n        {\n            btnApply = new JButton(\"Apply\");\n            btnApply.setPreferredSize(new Dimension(110, 26));\n            btnApply.setEnabled(false);\n            btnApply.addActionListener(e ->\n            {\n                result = APPLIED;\n                this.dispose();\n            });\n            content.add(btnApply);\n        }\n\n        btnCancel = new JButton(canApply ? \"Cancel\" : \"Close\");\n        btnCancel.setPreferredSize(new Dimension(110, 26));\n        btnCancel.addActionListener(e -> this.dispose());\n        content.add(btnCancel);\n\n        btnNew = new JButton(\"New Texture\");\n        btnNew.addActionListener(e -> showFileChooser());\n        btnNew.setPreferredSize(new Dimension(110, 26));\n        btnNew.setIcon(Icons.texture);\n        content.add(btnNew);\n\n        btnEdit = new JButton(\"Edit\");\n        btnEdit.setPreferredSize(new Dimension(110, 26));\n        btnEdit.setIcon(Icons.edit);\n        btnEdit.setEnabled(false);\n        btnEdit.addActionListener(e ->\n        {\n            TextureEntry entry = textureEntryList.getSelectedValue();\n            if(entry != null)\n            {\n                TextureEntryEditor editor = new TextureEntryEditor(this.getOwner(), entry, ModalityType.APPLICATION_MODAL);\n                editor.setLocationRelativeTo(null);\n                editor.setVisible(true);\n            }\n        });\n        content.add(btnEdit);\n\n        btnRemove = new JButton(\"Remove\");\n        btnRemove.setPreferredSize(new Dimension(110, 26));\n        btnRemove.setIcon(Icons.bin2);\n        btnRemove.setEnabled(false);\n        btnRemove.addActionListener(e ->\n        {\n            if(textureEntryList.getSelectedIndex() != -1)\n            {\n                TextureEntry entry = textureEntryList.getSelectedValue();\n                manager.getAllElements().forEach(element ->\n                {\n                    for(Face face : element.getAllFaces())\n                    {\n                        if(face.getTexture() == entry)\n                        {\n                            face.setTexture(null);\n                        }\n                    }\n                });\n                if(manager.getParticle() == entry)\n                {\n                    manager.setParticle(null);\n                }\n                textureEntries.remove(entry);\n                DefaultListModel<TextureEntry> listModel = (DefaultListModel<TextureEntry>) textureEntryList.getModel();\n                listModel.removeElement(entry);\n                TextureManager.removeTexture(entry);\n                manager.updateValues();\n            }\n            if(btnApply != null)\n            {\n                btnApply.setEnabled(false);\n            }\n            btnEdit.setEnabled(false);\n            btnRemove.setEnabled(false);\n        });\n        content.add(btnRemove);\n\n        SpringLayout layout = (SpringLayout) content.getLayout();\n        layout.putConstraint(SpringLayout.WEST, scrollPane, 10, SpringLayout.WEST, content);\n        layout.putConstraint(SpringLayout.NORTH, scrollPane, 10, SpringLayout.NORTH, content);\n        layout.putConstraint(SpringLayout.EAST, scrollPane, -10, SpringLayout.WEST, btnNew);\n        layout.putConstraint(SpringLayout.SOUTH, scrollPane, -10, SpringLayout.SOUTH, content);\n\n        if(canApply)\n        {\n            layout.putConstraint(SpringLayout.SOUTH, btnApply, -10, SpringLayout.NORTH, btnCancel);\n            layout.putConstraint(SpringLayout.EAST, btnApply, -10, SpringLayout.EAST, content);\n        }\n\n        layout.putConstraint(SpringLayout.SOUTH, btnCancel, -10, SpringLayout.SOUTH, content);\n        layout.putConstraint(SpringLayout.EAST, btnCancel, -10, SpringLayout.EAST, content);\n\n        layout.putConstraint(SpringLayout.NORTH, btnNew, 10, SpringLayout.NORTH, content);\n        layout.putConstraint(SpringLayout.EAST, btnNew, -10, SpringLayout.EAST, content);\n        layout.putConstraint(SpringLayout.NORTH, btnEdit, 10, SpringLayout.SOUTH, btnNew);\n        layout.putConstraint(SpringLayout.EAST, btnEdit, -10, SpringLayout.EAST, content);\n        layout.putConstraint(SpringLayout.NORTH, btnRemove, 10, SpringLayout.SOUTH, btnEdit);\n        layout.putConstraint(SpringLayout.EAST, btnRemove, -10, SpringLayout.EAST, content);\n\n        DefaultListModel<TextureEntry> listModel = (DefaultListModel<TextureEntry>) textureEntryList.getModel();\n        textureEntries.forEach(listModel::addElement);\n    }\n\n    public TextureEntry getSelectedTexture()\n    {\n        if(textureEntryList != null)\n        {\n            return textureEntryList.getSelectedValue();\n        }\n        return null;\n    }\n\n    public int getResult()\n    {\n        return result;\n    }\n\n    public void showFileChooser()\n    {\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Select a Texture\");\n        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n        chooser.setApproveButtonText(\"Select\");\n\n        if(lastLocation == null)\n        {\n            String dir = Settings.getImageImportDir();\n            if(dir != null)\n            {\n                lastLocation = new File(dir);\n            }\n        }\n\n        if(lastLocation != null)\n        {\n            chooser.setCurrentDirectory(lastLocation);\n        }\n        else\n        {\n            try\n            {\n                if(Settings.getAssetsDir() != null)\n                {\n                    chooser.setCurrentDirectory(new File(Settings.getAssetsDir()));\n                }\n            }\n            catch(Exception e)\n            {\n                e.printStackTrace();\n            }\n        }\n\n        FileNameExtensionFilter filter = new FileNameExtensionFilter(\"PNG Images\", \"png\");\n        chooser.setFileFilter(filter);\n        int returnVal = chooser.showOpenDialog(null);\n        if(returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            this.addImage(chooser.getSelectedFile());\n        }\n    }\n\n    private void addImage(File image)\n    {\n        try\n        {\n            String type = Files.probeContentType(image.toPath());\n            if(type.equals(\"image/png\"))\n            {\n                Dimension dimension = Util.getImageDimension(image);\n                if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)\n                {\n                    JOptionPane.showMessageDialog(this, \"Image size must be multiple of 16\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                    return;\n                }\n\n                lastLocation = image.getParentFile();\n                Settings.setImageImportDir(lastLocation.toString());\n\n                DefaultListModel<TextureEntry> listModel = (DefaultListModel<TextureEntry>) textureEntryList.getModel();\n                TextureEntry entry = new TextureEntry(image);\n                listModel.addElement(entry);\n                textureEntries.add(entry);\n                TextureManager.loadTexture(entry);\n            }\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static TextureEntry addImage(String id, TexturePath path, File image)\n    {\n        for(TextureEntry entry : textureEntries)\n        {\n            if(entry.getKey().equals(id))\n            {\n                return entry;\n            }\n        }\n        try\n        {\n            if(image.exists())\n            {\n                String type = Files.probeContentType(image.toPath());\n                if(type.equals(\"image/png\"))\n                {\n                    Dimension dimension = Util.getImageDimension(image);\n                    if(dimension.getWidth() % 16 != 0 || dimension.getHeight() % 16 != 0)\n                    {\n                        JOptionPane.showMessageDialog(null, \"Image size must be multiple of 16\", \"Error\", JOptionPane.ERROR_MESSAGE);\n                        return null;\n                    }\n                    TextureEntry entry = new TextureEntry(id, path, image);\n                    textureEntries.add(entry);\n                    TextureManager.loadTexture(entry);\n                    return entry;\n                }\n            }\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    public static TextureEntry getTexture(String id)\n    {\n        for(TextureEntry entry : textureEntries)\n        {\n            if(entry.getKey().equalsIgnoreCase(id))\n            {\n                return entry;\n            }\n        }\n        return null;\n    }\n\n    public static class TextureCellRenderer implements ListCellRenderer<TextureEntry>\n    {\n        @Override\n        public Component getListCellRendererComponent(JList<? extends TextureEntry> list, TextureEntry entry, int index, boolean isSelected, boolean cellHasFocus)\n        {\n            JPanel panel = new JPanel();\n            panel.setBackground(isSelected ? new Color(186, 193, 211) : ModelCreator.BACKGROUND);\n            panel.setPreferredSize(new Dimension(200, 85));\n            if(isSelected)\n            {\n                panel.setBorder(BorderFactory.createLineBorder(new Color(131, 138, 156), 1));\n            }\n\n            SpringLayout layout = new SpringLayout();\n            panel.setLayout(layout);\n\n            JLabel icon = new JLabel(entry.getIcon());\n            panel.add(icon);\n\n            JLabel id = new JLabel(\"<html><b>\" + entry.getKey() + \"</b></html>\");\n            panel.add(id);\n\n            JLabel name = new JLabel(\"<html><span style=\\\"color:#555555\\\">\" + entry.getTexturePath().toString() + \"</span></html>\");\n            panel.add(name);\n\n            layout.putConstraint(SpringLayout.WEST, icon, 10, SpringLayout.WEST, panel);\n            layout.putConstraint(SpringLayout.NORTH, icon, 10, SpringLayout.NORTH, panel);\n\n            layout.putConstraint(SpringLayout.WEST, id, 10, SpringLayout.EAST, icon);\n            layout.putConstraint(SpringLayout.NORTH, id, 10, SpringLayout.NORTH, panel);\n            layout.putConstraint(SpringLayout.EAST, id, -10, SpringLayout.EAST, panel);\n\n            layout.putConstraint(SpringLayout.WEST, name, 10, SpringLayout.EAST, icon);\n            layout.putConstraint(SpringLayout.NORTH, name, 5, SpringLayout.SOUTH, id);\n            layout.putConstraint(SpringLayout.EAST, name, -10, SpringLayout.EAST, panel);\n\n            return panel;\n        }\n    }\n\n    public static TextureEntry display(Frame owner, ElementManager manager, ModalityType modalityType)\n    {\n        TextureManager textureManager = new TextureManager(owner, manager, modalityType, true);\n        textureManager.setLocationRelativeTo(null);\n        textureManager.setVisible(true);\n        if(textureManager.getResult() == APPLIED)\n        {\n            return textureManager.getSelectedTexture();\n        }\n        return null;\n    }\n\n    public static void loadTexture(TextureEntry entry)\n    {\n        synchronized(LOCK)\n        {\n            pendingLoad.add(entry);\n        }\n    }\n\n    public static void removeTexture(TextureEntry entry)\n    {\n        synchronized(LOCK)\n        {\n            pendingRemove.add(entry);\n        }\n    }\n\n    public static void processPendingTextures()\n    {\n        if(pendingRemove.size() > 0)\n        {\n            synchronized(LOCK)\n            {\n                for(TextureEntry entry : pendingRemove)\n                {\n                    entry.deleteTexture();\n                }\n                pendingRemove.clear();\n            }\n        }\n\n        if(pendingLoad.size() > 0)\n        {\n            synchronized(LOCK)\n            {\n                for(TextureEntry entry : pendingLoad)\n                {\n                    entry.deleteTexture();\n                    entry.loadTexture();\n                }\n                pendingLoad.clear();\n            }\n        }\n    }\n\n    public static void clear()\n    {\n        synchronized(LOCK)\n        {\n            pendingRemove.addAll(textureEntries);\n            textureEntries.clear();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/dialog/WelcomeDialog.java",
    "content": "package com.mrcrayfish.modelcreator.dialog;\n\nimport com.mrcrayfish.modelcreator.Constants;\nimport com.mrcrayfish.modelcreator.Icons;\n\nimport javax.swing.*;\nimport javax.swing.border.EmptyBorder;\nimport java.awt.*;\nimport java.net.URL;\n\npublic class WelcomeDialog\n{\n    public static void show(JFrame parent)\n    {\n        JPanel dialogContent = getDialogContent(parent);\n        JDialog welcomeDialog = getWelcomeDialog(parent, dialogContent);\n        welcomeDialog.requestFocus();\n        welcomeDialog.setAlwaysOnTop(true);\n        showDialog(welcomeDialog);\n    }\n\n    private static JPanel getDialogContent(JFrame parent)\n    {\n        JPanel container = new JPanel(new BorderLayout(20, 10));\n        container.setBorder(new EmptyBorder(10, 10, 10, 10));\n\n        ImageIcon crayfish = new ImageIcon(parent.getClass().getClassLoader().getResource(\"sticker.png\"));\n        container.add(new JLabel(crayfish), BorderLayout.EAST);\n\n        JPanel leftPanel = getLeftPanel();\n        container.add(leftPanel, BorderLayout.CENTER);\n\n        JPanel btnGrid = getButtonGrid();\n        container.add(btnGrid, BorderLayout.SOUTH);\n        return container;\n    }\n\n    private static JPanel getLeftPanel()\n    {\n        JPanel leftPanel = new JPanel(new BorderLayout());\n\n        JLabel title = new JLabel(\"<html><div style=\\\"font-size:16px;\\\"><b>Model Creator</b> by MrCrayfish<div></html>\");\n        leftPanel.add(title, BorderLayout.NORTH);\n\n        JScrollPane message = getScrollableMessage();\n        leftPanel.add(message, BorderLayout.CENTER);\n        return leftPanel;\n    }\n\n    private static JScrollPane getScrollableMessage()\n    {\n        String message = \"Thank you for downloading my program. I hope it encourages\" + \" you to create awesome models. If you do create something awesome, I\" + \" would love to see it. You can post your screenshots to me via Twitter\" + \" or Facebook. If you are unsure how to use anything works, hover your \" + \"mouse over the component and it will tell you what it does.\" + \"\\n\\n\" + \"I've put a lot of work into this program, so if you are \" + \"feeling generous, you can donate by clicking the button below. Thank you!\" + \"\";\n        JTextArea textArea = new JTextArea(message);\n        textArea.setEditable(false);\n        textArea.setCursor(null);\n        textArea.setFocusable(false);\n        textArea.setBorder(null);\n        textArea.setOpaque(false);\n        textArea.setLineWrap(true);\n        textArea.setWrapStyleWord(true);\n\n        JScrollPane scrollPane = new JScrollPane(textArea);\n        scrollPane.setBorder(null);\n        return scrollPane;\n    }\n\n    private static JPanel getButtonGrid()\n    {\n        JPanel btnGrid = new JPanel(new GridLayout(1, 4, 5, 0));\n        JButton btnDonate = new JButton(\"Donate\");\n        btnDonate.setIcon(Icons.patreon);\n        btnDonate.addActionListener(a -> openUrl(Constants.URL_DONATE));\n        btnGrid.add(btnDonate);\n\n        JButton btnTwitter = new JButton(\"Twitter\");\n        btnTwitter.setIcon(Icons.twitter);\n        btnTwitter.addActionListener(arg0 -> openUrl(Constants.URL_TWITTER));\n        btnGrid.add(btnTwitter);\n\n        JButton btnFacebook = new JButton(\"Facebook\");\n        btnFacebook.setIcon(Icons.facebook);\n        btnFacebook.addActionListener(a -> openUrl(Constants.URL_FACEBOOK));\n        btnGrid.add(btnFacebook);\n\n        JButton btnClose = new JButton(\"Close\");\n        btnClose.addActionListener(a -> SwingUtilities.getWindowAncestor(btnClose).dispose());\n        btnGrid.add(btnClose);\n        return btnGrid;\n    }\n\n    private static void openUrl(String url)\n    {\n        Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;\n        if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE))\n        {\n            try\n            {\n                desktop.browse(new URL(url).toURI());\n            }\n            catch(Exception e)\n            {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    private static JDialog getWelcomeDialog(JFrame parent, JPanel dialogContent)\n    {\n        JDialog dialog = new JDialog(parent, \"Welcome\", Dialog.ModalityType.APPLICATION_MODAL);\n        dialog.setResizable(false);\n        dialog.setPreferredSize(new Dimension(500, 290));\n        dialog.add(dialogContent);\n        dialog.pack();\n        return dialog;\n    }\n\n    private static void showDialog(JDialog dialog)\n    {\n        dialog.setLocationRelativeTo(null);\n        dialog.setVisible(true);\n        dialog.requestFocusInWindow();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/CanvasRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Author: MrCrayfish\n */\npublic abstract class CanvasRenderer\n{\n    protected List<Element> elements = new ArrayList<>();\n\n    public void onInit(Camera camera) {}\n\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera) {}\n\n    public void onRenderOverlay(ElementManager manager, Camera camera, ModelCreator creator) {}\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/DisplayProperties.java",
    "content": "package com.mrcrayfish.modelcreator.display;\n\nimport com.mrcrayfish.modelcreator.display.render.*;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Author: MrCrayfish\n */\npublic class DisplayProperties\n{\n    public static final Map<String, CanvasRenderer> RENDER_MAP = new HashMap<>();\n    public static final DisplayProperties MODEL_CREATOR_BLOCK;\n    public static final DisplayProperties DEFAULT_BLOCK;\n    public static final DisplayProperties DEFAULT_ITEM;\n\n    static\n    {\n        MODEL_CREATOR_BLOCK = new DisplayProperties(\"Model Creator Block\", true);\n        MODEL_CREATOR_BLOCK.add(\"gui\", 30, 45, 0, 0, 0, 0, 0.625, 0.625, 0.625, true);\n        MODEL_CREATOR_BLOCK.add(\"ground\", 0, 0, 0, 0, 3, 0, 0.25, 0.25, 0.25, true);\n        MODEL_CREATOR_BLOCK.add(\"fixed\", 0, 180, 0, 0, 0, 0, 1, 1, 1, true);\n        MODEL_CREATOR_BLOCK.add(\"head\", 0, 180, 0, 0, 0, 0, 1, 1, 1, true);\n        MODEL_CREATOR_BLOCK.add(\"firstperson_righthand\", 0, 315, 0, 0, 2.5, 0, 0.4, 0.4, 0.4, true);\n        MODEL_CREATOR_BLOCK.add(\"firstperson_lefthand\", 0, 0, 45, 0, 2.5, 0, 0.4, 0.4, 0.4, false);\n        MODEL_CREATOR_BLOCK.add(\"thirdperson_righthand\", 75, 315, 0, 0, 2.5, 0, 0.375, 0.375, 0.375, true);\n        MODEL_CREATOR_BLOCK.add(\"thirdperson_lefthand\", 75, 45, 0, 0, 2.5, 0, 0.375, 0.375, 0.375, false);\n\n        DEFAULT_BLOCK = new DisplayProperties(\"Default Block\", true);\n        DEFAULT_BLOCK.add(\"gui\", 30, 225, 0, 0, 0, 0, 0.625, 0.625, 0.625, true);\n        DEFAULT_BLOCK.add(\"ground\", 0, 0, 0, 0, 3, 0, 0.25, 0.25, 0.25, true);\n        DEFAULT_BLOCK.add(\"fixed\", 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, true);\n        DEFAULT_BLOCK.add(\"head\", 0, 0, 0, 0, 0, 0, 1, 1, 1, false);\n        DEFAULT_BLOCK.add(\"firstperson_righthand\", 0, 45, 0, 0, 2.5, 0, 0.4, 0.4, 0.4, true);\n        DEFAULT_BLOCK.add(\"firstperson_lefthand\", 0, 0, 225, 0, 2.5, 0, 0.4, 0.4, 0.4, true);\n        DEFAULT_BLOCK.add(\"thirdperson_righthand\", 75, 45, 0, 0, 2.5, 0, 0.375, 0.375, 0.375, true);\n        DEFAULT_BLOCK.add(\"thirdperson_lefthand\", 75, 225, 0, 0, 2.5, 0, 0.375, 0.375, 0.375, true);\n\n        DEFAULT_ITEM = new DisplayProperties(\"Default Item\", true);\n        DEFAULT_ITEM.add(\"gui\", 0, 0, 0, 0, 0, 0, 1, 1, 1, true);\n        DEFAULT_ITEM.add(\"ground\", 0, 0, 0, 0, 2, 0, 0.5, 0.5, 0.5, true);\n        DEFAULT_ITEM.add(\"fixed\", 0, 180, 0, 0, 0, 0, 1, 1, 1, true);\n        DEFAULT_ITEM.add(\"head\", 0, 180, 0, 0, 13, 7, 1, 1, 1, true);\n        DEFAULT_ITEM.add(\"firstperson_righthand\", 0, -90, 25, 1.13, 3.2, 1.13, 0.68, 0.68, 0.68, true);\n        DEFAULT_ITEM.add(\"firstperson_lefthand\", 0, -90, 25, 1.13, 3.2, 1.13, 0.68, 0.68, 0.68, true);\n        DEFAULT_ITEM.add(\"thirdperson_righthand\", 0, 0, 0, 0, 3, 1, 0.55, 0.55, 0.55, true);\n        DEFAULT_ITEM.add(\"thirdperson_lefthand\", 0, 0, 0, 0, 3, 1, 0.55, 0.55, 0.55, true);\n\n        RENDER_MAP.put(\"head\", new HeadPropertyRenderer());\n        RENDER_MAP.put(\"gui\", new GuiPropertyRenderer());\n        RENDER_MAP.put(\"ground\", new GroundPropertyRenderer());\n        RENDER_MAP.put(\"fixed\", new FixedPropertyRenderer());\n        RENDER_MAP.put(\"firstperson_righthand\", new FirstPersonPropertyRenderer(false));\n        RENDER_MAP.put(\"thirdperson_righthand\", new ThirdPersonPropertyRenderer(false));\n        RENDER_MAP.put(\"firstperson_lefthand\", new FirstPersonPropertyRenderer(true));\n        RENDER_MAP.put(\"thirdperson_lefthand\", new ThirdPersonPropertyRenderer(true));\n    }\n\n    private Map<String, Entry> entries = new HashMap<>();\n    private String name;\n    private boolean isDefault = false;\n\n    private DisplayProperties(String name, boolean isDefault)\n    {\n        this.name = name;\n        this.isDefault = isDefault;\n    }\n\n    public DisplayProperties(DisplayProperties properties)\n    {\n        this.name = properties.name;\n        properties.entries.forEach((id, entry) -> entries.put(id, new Entry(entry)));\n    }\n\n    public DisplayProperties(String name, DisplayProperties properties)\n    {\n        this.name = name;\n        properties.entries.forEach((id, entry) -> entries.put(id, new Entry(entry)));\n    }\n\n    public void add(String id, double rotationX, double rotationY, double rotationZ, double translationX, double translationY, double translationZ, double scaleX, double scaleY, double scaleZ, boolean enabled)\n    {\n        Entry entry = new Entry(id, rotationX, rotationY, rotationZ, translationX, translationY, translationZ, scaleX, scaleY, scaleZ);\n        entry.setEnabled(enabled);\n        entries.put(id, entry);\n    }\n\n    public String getName()\n    {\n        return name;\n    }\n\n    public boolean isDefault()\n    {\n        return isDefault;\n    }\n\n    public Entry getEntry(String id)\n    {\n        return entries.get(id);\n    }\n\n    public Map<String, Entry> getEntries()\n    {\n        return entries;\n    }\n\n    @Override\n    public String toString()\n    {\n        return name;\n    }\n\n    public static class Entry\n    {\n        private boolean enabled = true;\n        private String id;\n        private double rotationX, rotationY, rotationZ;\n        private double translationX, translationY, translationZ;\n        private double scaleX, scaleY, scaleZ;\n\n        public Entry(String id, double rotationX, double rotationY, double rotationZ, double translationX, double translationY, double translationZ, double scaleX, double scaleY, double scaleZ)\n        {\n            this.id = id;\n            this.rotationX = rotationX;\n            this.rotationY = rotationY;\n            this.rotationZ = rotationZ;\n            this.translationX = translationX;\n            this.translationY = translationY;\n            this.translationZ = translationZ;\n            this.scaleX = scaleX;\n            this.scaleY = scaleY;\n            this.scaleZ = scaleZ;\n        }\n\n        public Entry(Entry entry)\n        {\n            this.enabled = entry.enabled;\n            this.id = entry.id;\n            this.rotationX = entry.rotationX;\n            this.rotationY = entry.rotationY;\n            this.rotationZ = entry.rotationZ;\n            this.translationX = entry.translationX;\n            this.translationY = entry.translationY;\n            this.translationZ = entry.translationZ;\n            this.scaleX = entry.scaleX;\n            this.scaleY = entry.scaleY;\n            this.scaleZ = entry.scaleZ;\n        }\n\n        public void setEnabled(boolean enabled)\n        {\n            this.enabled = enabled;\n        }\n\n        public boolean isEnabled()\n        {\n            return enabled;\n        }\n\n        public String getId()\n        {\n            return id;\n        }\n\n        public double getRotationX()\n        {\n            return rotationX;\n        }\n\n        public void setRotationX(double rotationX)\n        {\n            this.rotationX = rotationX;\n        }\n\n        public double getRotationY()\n        {\n            return rotationY;\n        }\n\n        public void setRotationY(double rotationY)\n        {\n            this.rotationY = rotationY;\n        }\n\n        public double getRotationZ()\n        {\n            return rotationZ;\n        }\n\n        public void setRotationZ(double rotationZ)\n        {\n            this.rotationZ = rotationZ;\n        }\n\n        public double getTranslationX()\n        {\n            return translationX;\n        }\n\n        public void setTranslationX(double translationX)\n        {\n            this.translationX = translationX;\n        }\n\n        public double getTranslationY()\n        {\n            return translationY;\n        }\n\n        public void setTranslationY(double translationY)\n        {\n            this.translationY = translationY;\n        }\n\n        public double getTranslationZ()\n        {\n            return translationZ;\n        }\n\n        public void setTranslationZ(double translationZ)\n        {\n            this.translationZ = translationZ;\n        }\n\n        public double getScaleX()\n        {\n            return scaleX;\n        }\n\n        public void setScaleX(double scaleX)\n        {\n            this.scaleX = scaleX;\n        }\n\n        public double getScaleY()\n        {\n            return scaleY;\n        }\n\n        public void setScaleY(double scaleY)\n        {\n            this.scaleY = scaleY;\n        }\n\n        public double getScaleZ()\n        {\n            return scaleZ;\n        }\n\n        public void setScaleZ(double scaleZ)\n        {\n            this.scaleZ = scaleZ;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/DisplayPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport static org.lwjgl.opengl.GL11.glTranslatef;\n\n/**\n * Author: MrCrayfish\n */\npublic abstract class DisplayPropertyRenderer extends StandardRenderer\n{\n    @Override\n    protected void drawElements(ElementManager manager)\n    {\n        glTranslatef(-8, 0, -8);\n        for(int i = 0; i < manager.getElementCount(); i++)\n        {\n            Element cube = manager.getElement(i);\n            if(cube.isVisible())\n            {\n                cube.draw();\n                cube.drawExtras(manager);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/FirstPersonPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.texture.TextureAtlas;\nimport com.mrcrayfish.modelcreator.util.AtlasRenderUtil;\nimport org.lwjgl.opengl.GL11;\nimport org.lwjgl.util.glu.GLU;\nimport org.newdawn.slick.opengl.TextureImpl;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class FirstPersonPropertyRenderer extends DisplayPropertyRenderer\n{\n    private boolean leftHanded;\n\n    public FirstPersonPropertyRenderer(boolean leftHanded)\n    {\n        this.leftHanded = leftHanded;\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera) {}\n\n    @Override\n    public void onRenderOverlay(ElementManager manager, Camera camera, ModelCreator creator)\n    {\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(!leftHanded ? \"firstperson_righthand\" : \"firstperson_lefthand\");\n        if(entry != null)\n        {\n            int canvasOffset = creator.getCanvasOffset();\n            int canvasWidth = creator.getCanvasWidth() - creator.getCanvasOffset();\n            int canvasHeight = creator.getCanvasHeight();\n\n            glViewport(canvasOffset, 0, canvasWidth, canvasHeight);\n            glMatrixMode(GL_PROJECTION);\n            glLoadIdentity();\n            GL11.glOrtho(canvasOffset, canvasWidth, canvasHeight, 0, 500.0D, -500.0D);\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n\n            glPushMatrix();\n\n            TextureAtlas.bind();\n            glEnable(GL_TEXTURE_2D);\n            glEnable(GL_BLEND);\n            glDisable(GL_CULL_FACE);\n            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n            glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n            glColor3f(1.0F, 1.0F, 1.0F);\n\n            double startX = (canvasWidth - 960) / 2;\n            double startY = (canvasHeight - 540) / 2;\n\n            glTranslated((int) startX, (int) startY, 0);\n            AtlasRenderUtil.bindTexture(TextureAtlas.FIRST_PERSON_PREVIEW);\n            AtlasRenderUtil.drawQuad(0, 0, 960, 540);\n\n            glEnable(GL_SCISSOR_TEST);\n            glScissor((int) startX, (int) startY, 960, 540);\n\n            glPopMatrix();\n\n            glPushMatrix();\n            {\n                glViewport((int) startX, (int) startY, 960, 540);\n                glMatrixMode(GL_PROJECTION);\n                glLoadIdentity();\n                GLU.gluPerspective(-70F, (float) 960 / (float) 540, 0.3F, 1000F);\n                glMatrixMode(GL_MODELVIEW);\n                glLoadIdentity();\n                glEnable(GL_DEPTH_TEST);\n\n                if(leftHanded)\n                {\n                    glScaled(-1, 1, 1);\n                }\n\n                glTranslated(-9, 8.35, -11.5);\n                glTranslated(-entry.getTranslationX(), -entry.getTranslationY(), entry.getTranslationZ());\n                glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n                glRotatef(180F, 1, 0, 0);\n                glRotatef((float) entry.getRotationX(), 1, 0, 0);\n                glRotatef((float) entry.getRotationY(), 0, 1, 0);\n                glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n                glTranslated(0, -8, 0);\n                glRotated(180F, 0, 1, 0);\n\n                glEnable(GL_TEXTURE_2D);\n                glEnable(GL_DEPTH_TEST);\n                glEnable(GL_CULL_FACE);\n\n                if(leftHanded)\n                {\n                    glScaled(-1, 1, 1);\n                    glRotatef(180F, 0, 1, 0);\n                }\n                this.drawElements(manager);\n\n                glDisable(GL_DEPTH_TEST);\n                glDisable(GL_CULL_FACE);\n                glDisable(GL_TEXTURE_2D);\n                glDisable(GL_SCISSOR_TEST);\n            }\n            glPopMatrix();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/FixedPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class FixedPropertyRenderer extends DisplayPropertyRenderer\n{\n    public FixedPropertyRenderer()\n    {\n        this.addElements();\n    }\n\n    private void addElements()\n    {\n        Element frameOne = new Element(12, 1, 1);\n        frameOne.setStartX(-6);\n        frameOne.setStartY(2);\n        frameOne.setStartZ(-1);\n        elements.add(frameOne);\n\n        Element frameTwo = new Element(12, 1, 1);\n        frameTwo.setStartX(-6);\n        frameTwo.setStartY(13);\n        frameTwo.setStartZ(-1);\n        elements.add(frameTwo);\n\n        Element frameThree = new Element(10, 10, 0.5);\n        frameThree.setStartX(-5);\n        frameThree.setStartY(3);\n        frameThree.setStartZ(-1);\n        elements.add(frameThree);\n\n        Element frameFour = new Element(1, 10, 1);\n        frameFour.setStartX(-6);\n        frameFour.setStartY(3);\n        frameFour.setStartZ(-1);\n        elements.add(frameFour);\n\n        Element frameFive = new Element(1, 10, 1);\n        frameFive.setStartX(5);\n        frameFive.setStartY(3);\n        frameFive.setStartZ(-1);\n        elements.add(frameFive);\n    }\n\n    @Override\n    public void onInit(Camera camera)\n    {\n        camera.setX(0);\n        camera.setY(-9);\n        camera.setZ(-30);\n        camera.setRX(0);\n        camera.setRY(0);\n        camera.setRZ(0);\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera)\n    {\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(\"fixed\");\n        if(entry != null)\n        {\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n            glEnable(GL_DEPTH_TEST);\n            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n            glLoadIdentity();\n            camera.useView();\n\n            glTranslated(0, -5, 0);\n            glScalef(1.75F, 1.75F, 1.75F);\n\n            glPushMatrix();\n            for(Element element : elements)\n            {\n                element.drawExtras(manager);\n                element.draw();\n            }\n            glPopMatrix();\n\n            glTranslated(0, 8, 0);\n            glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n            glRotatef(180F, 0, 1, 0);\n            glScalef(0.5F, 0.5F, 0.5F);\n            glTranslated(entry.getTranslationX(), entry.getTranslationY(), entry.getTranslationZ());\n            glRotatef((float) entry.getRotationX(), 1, 0, 0);\n            glRotatef((float) entry.getRotationY(), 0, 1, 0);\n            glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n            glTranslated(0, -8, 0);\n\n            glPushMatrix();\n            {\n                this.drawGrid(camera, false);\n                this.drawElements(manager);\n            }\n            glPopMatrix();\n\n            glDisable(GL_DEPTH_TEST);\n            glDisable(GL_CULL_FACE);\n            glDisable(GL_TEXTURE_2D);\n            glDisable(GL_LIGHTING);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/GroundPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Animation;\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class GroundPropertyRenderer extends DisplayPropertyRenderer\n{\n    public GroundPropertyRenderer()\n    {\n        this.addElements();\n    }\n\n    private void addElements()\n    {\n        Element block = new Element(16, 16, 16);\n        block.setStartX(-8);\n        block.setStartY(-16);\n        block.setStartZ(-8);\n        elements.add(block);\n    }\n\n    @Override\n    public void onInit(Camera camera)\n    {\n        camera.setX(0);\n        camera.setY(0);\n        camera.setZ(-25);\n        camera.setRX(20);\n        camera.setRY(0);\n        camera.setRZ(0);\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera)\n    {\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(\"ground\");\n        if(entry != null)\n        {\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n            glEnable(GL_DEPTH_TEST);\n            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n            glLoadIdentity();\n            camera.useView();\n\n            for(Element element : elements)\n            {\n                element.drawExtras(manager);\n                element.draw();\n            }\n\n            double yOffset = (Animation.getCounter() + Animation.getCounter()) / 60F;\n            yOffset = Math.sin(yOffset);\n            glTranslated(0, (yOffset * 1.5) * entry.getScaleY(), 0);\n            glRotated((Animation.getCounter() + Animation.getPartialTicks()), 0, 1, 0);\n\n            glTranslated(-entry.getTranslationX(), entry.getTranslationY(), -entry.getTranslationZ());\n            glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n\n            glTranslated(0, 5.5, 0);\n\n            glRotatef(180F, 0, 1, 0);\n            glRotatef((float) entry.getRotationX(), 1, 0, 0);\n            glRotatef((float) entry.getRotationY(), 0, 1, 0);\n            glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n            glTranslated(0, -8, 0);\n\n            glPushMatrix();\n            {\n                this.drawGrid(camera, false);\n                this.drawElements(manager);\n            }\n            glPopMatrix();\n\n            glDisable(GL_DEPTH_TEST);\n            glDisable(GL_CULL_FACE);\n            glDisable(GL_TEXTURE_2D);\n            glDisable(GL_LIGHTING);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/GuiPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.texture.TextureAtlas;\nimport com.mrcrayfish.modelcreator.util.AtlasRenderUtil;\nimport org.lwjgl.opengl.GL11;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class GuiPropertyRenderer extends DisplayPropertyRenderer\n{\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera) {}\n\n    @Override\n    public void onRenderOverlay(ElementManager manager, Camera camera, ModelCreator creator)\n    {\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(\"gui\");\n        if(entry != null)\n        {\n            int canvasOffset = creator.getCanvasOffset();\n            int canvasWidth = creator.getCanvasWidth() - creator.getCanvasOffset();\n            int canvasHeight = creator.getCanvasHeight();\n\n            glViewport(canvasOffset, 0, canvasWidth, canvasHeight);\n            glMatrixMode(GL_PROJECTION);\n            glLoadIdentity();\n            GL11.glOrtho(canvasOffset, canvasWidth, canvasHeight, 0, 500.0D, -500.0D);\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n\n            glPushMatrix();\n            {\n                TextureAtlas.bind();\n                glEnable(GL_TEXTURE_2D);\n                glEnable(GL_BLEND);\n                glDisable(GL_CULL_FACE);\n                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n                glColor3f(1.0F, 1.0F, 1.0F);\n\n                int scale = 30;\n                int slotSize = 16 * scale;\n                int startX = (canvasWidth - slotSize) / 2;\n                int startY = (canvasHeight - slotSize) / 2;\n\n                glTranslatef(startX, startY, 0);\n                glScalef(scale, scale, 0);\n\n                AtlasRenderUtil.bindTexture(TextureAtlas.GUI_SLOT);\n                AtlasRenderUtil.drawQuad(0, 0, 16, 16);\n            }\n            glPopMatrix();\n\n            glPushMatrix();\n            {\n                int scale = 24;\n                int startX = canvasWidth / 2;\n                int startY = canvasHeight / 2;\n                glTranslatef(startX, startY, 0);\n                glScaled(scale, scale, scale);\n                glTranslated(entry.getTranslationX(), -entry.getTranslationY(), entry.getTranslationZ());\n                glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n                glRotatef(180F, 1, 0, 0);\n                glRotatef((float) entry.getRotationX(), 1, 0, 0);\n                glRotatef((float) entry.getRotationY(), 0, 1, 0);\n                glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n                glTranslatef(0, -8, 0);\n\n                glEnable(GL_TEXTURE_2D);\n                glEnable(GL_DEPTH_TEST);\n                glEnable(GL_CULL_FACE);\n\n                this.drawElements(manager);\n\n                glDisable(GL_DEPTH_TEST);\n                glDisable(GL_CULL_FACE);\n                glDisable(GL_TEXTURE_2D);\n            }\n            glPopMatrix();\n\n\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/HeadPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class HeadPropertyRenderer extends DisplayPropertyRenderer\n{\n    public HeadPropertyRenderer()\n    {\n        this.addElements();\n    }\n\n    private void addElements()\n    {\n        Element rightArm = new Element(4, 12, 4);\n        rightArm.setStartX(-8);\n        rightArm.setStartY(-12);\n        rightArm.setStartZ(-2);\n        rightArm.setOriginX(-6);\n        rightArm.setOriginY(-2);\n        rightArm.setOriginZ(0);\n        rightArm.setRotation(15F); //TODO maybe add option to change\n        elements.add(rightArm);\n\n        Element leftArm = new Element(4, 12, 4);\n        leftArm.setStartX(4);\n        leftArm.setStartY(-12);\n        leftArm.setStartZ(-2);\n        leftArm.setOriginX(6);\n        leftArm.setOriginY(-2);\n        leftArm.setOriginZ(0);\n        leftArm.setRotation(-15F); //TODO maybe add option to change\n        elements.add(leftArm);\n\n        Element body = new Element(8, 12, 4);\n        body.setStartX(-4);\n        body.setStartY(-12);\n        body.setStartZ(-2);\n        body.setOriginX(0);\n        body.setOriginY(0);\n        body.setOriginZ(0);\n        elements.add(body);\n\n        Element head = new Element(8, 8, 8);\n        head.setStartX(-4);\n        head.setStartY(0);\n        head.setStartZ(-4);\n        head.setOriginX(0);\n        head.setOriginY(0);\n        head.setOriginZ(0);\n        elements.add(head);\n\n        Element rightLeg = new Element(4, 12, 4);\n        rightLeg.setStartX(-4);\n        rightLeg.setStartY(-24);\n        rightLeg.setStartZ(-2);\n        rightLeg.setOriginX(4);\n        rightLeg.setOriginY(-12);\n        rightLeg.setOriginZ(0);\n        rightLeg.setRotation(-15F);\n        elements.add(rightLeg);\n\n        Element leftLeg = new Element(4, 12, 4);\n        leftLeg.setStartX(0);\n        leftLeg.setStartY(-24);\n        leftLeg.setStartZ(-2);\n        leftLeg.setOriginX(8);\n        leftLeg.setOriginX(2);\n        leftLeg.setOriginY(-12);\n        leftLeg.setOriginZ(0);\n        leftLeg.setRotation(15F);\n        elements.add(leftLeg);\n    }\n\n    @Override\n    public void onInit(Camera camera)\n    {\n        camera.setX(0);\n        camera.setY(-5);\n        camera.setZ(-45);\n        camera.setRX(10);\n        camera.setRY(45);\n        camera.setRZ(0);\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera)\n    {\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(\"head\");\n        if(entry != null)\n        {\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n            glEnable(GL_DEPTH_TEST);\n            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n            glLoadIdentity();\n            camera.useView();\n\n            glScaled(2.0, 2.0, 2.0);\n\n            for(Element element : elements)\n            {\n                element.drawExtras(manager);\n                element.draw();\n            }\n\n            glTranslated(0, 4, 0);\n            glTranslated(-entry.getTranslationX(), entry.getTranslationY(), -entry.getTranslationZ());\n            glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n            glRotatef(180F, 0, 1, 0);\n            glRotatef((float) entry.getRotationX(), 1, 0, 0);\n            glRotatef((float) entry.getRotationY(), 0, 1, 0);\n            glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n            glTranslated(0, -4, 0);\n            glScalef(0.625F, 0.625F, 0.625F);\n\n            glPushMatrix();\n            {\n                this.drawGrid(camera, false);\n                this.drawElements(manager);\n            }\n            glPopMatrix();\n\n            glDisable(GL_DEPTH_TEST);\n            glDisable(GL_CULL_FACE);\n            glDisable(GL_TEXTURE_2D);\n            glDisable(GL_LIGHTING);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/StandardRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcreator.component.Menu;\nimport com.mrcrayfish.modelcreator.display.CanvasRenderer;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.util.FontManager;\nimport org.lwjgl.opengl.GL11;\nimport org.newdawn.slick.Color;\nimport org.newdawn.slick.opengl.TextureImpl;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class StandardRenderer extends CanvasRenderer\n{\n    @Override\n    public void onInit(Camera camera)\n    {\n        camera.setX(0);\n        camera.setY(-5);\n        camera.setZ(-30);\n        camera.setRX(20);\n        camera.setRY(0);\n        camera.setRZ(0);\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera)\n    {\n        glMatrixMode(GL_MODELVIEW);\n        glLoadIdentity();\n        glEnable(GL_DEPTH_TEST);\n        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n        camera.useView();\n\n        this.drawGrid(camera, Settings.getCardinalPoints()); //TODO make this an option\n        this.drawElements(manager);\n\n        glDisable(GL_DEPTH_TEST);\n        glDisable(GL_CULL_FACE);\n        glDisable(GL_TEXTURE_2D);\n        glDisable(GL_LIGHTING);\n    }\n\n    protected void drawElements(ElementManager manager)\n    {\n        glTranslatef(-8, 0, -8);\n        for(int i = 0; i < manager.getElementCount(); i++)\n        {\n            Element cube = manager.getElement(i);\n            if(cube.isVisible())\n            {\n                glLoadName(i + 1);\n                cube.draw();\n                glLoadName(0);\n                cube.drawExtras(manager);\n            }\n        }\n\n        Element selectedElement = manager.getSelectedElement();\n        if(selectedElement != null && selectedElement.isVisible())\n        {\n            selectedElement.drawOutline();\n        }\n    }\n\n    protected void drawGrid(Camera camera, boolean renderCardinalPoints)\n    {\n        if(Menu.displayPropertiesDialog != null && !Menu.shouldRenderGrid)\n        {\n            return;\n        }\n\n        glPushMatrix();\n        {\n            glColor3f(0.55F, 0.55F, 0.65F);\n            glTranslatef(-8, 0, -8);\n\n            // Bold outside lines\n            glLineWidth(1.5F);\n            glBegin(GL_LINES);\n            {\n                glVertex3i(0, 0, 0);\n                glVertex3i(0, 0, 16);\n                glVertex3i(16, 0, 0);\n                glVertex3i(16, 0, 16);\n                glVertex3i(0, 0, 16);\n                glVertex3i(16, 0, 16);\n                glVertex3i(0, 0, 0);\n                glVertex3i(16, 0, 0);\n            }\n            glEnd();\n\n            // Thin inside lines\n            glLineWidth(1F);\n            glBegin(GL_LINES);\n            {\n                for(int i = 1; i <= 16; i++)\n                {\n                    glVertex3i(i, 0, 0);\n                    glVertex3i(i, 0, 16);\n                }\n\n                for(int i = 1; i <= 16; i++)\n                {\n                    glVertex3i(0, 0, i);\n                    glVertex3i(16, 0, i);\n                }\n            }\n            glEnd();\n        }\n        glPopMatrix();\n\n        glPushMatrix();\n        {\n            TextureImpl.bindNone();\n\n            glTranslatef(-8, 0, -8);\n            glEnable(GL_TEXTURE_2D);\n            glEnable(GL_BLEND);\n            glShadeModel(GL_SMOOTH);\n            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n\n            glPushMatrix();\n            glTranslated(0, 0, 16);\n            glScaled(0.015, 0.015, 0.015);\n            glRotated(90, 1, 0, 0);\n            FontManager.BEBAS_NEUE_50.drawString(0, 0, \"MrCrayfish's Model Creator\", Color.lightGray);\n            glPopMatrix();\n\n            if(renderCardinalPoints)\n            {\n                Color color = new Color(0.5F, 0.5F, 0.55F).brighter(0.40F);\n\n                glPushMatrix();\n                glTranslated(8, 0, 17);\n                glScaled(0.025, 0.025, 0.025);\n                glRotated(-camera.getRY(), 0, 1, 0);\n                glTranslated(-(FontManager.BEBAS_NEUE_50.getWidth(\"S\") / 2), 0, -(FontManager.BEBAS_NEUE_50.getHeight() / 2));\n                glRotated(90, 1, 0, 0);\n                FontManager.BEBAS_NEUE_50.drawString(0, 0, \"S\", color);\n                glPopMatrix();\n\n                glPushMatrix();\n                glTranslated(8, 0, -1);\n                glScaled(0.025, 0.025, 0.025);\n                glRotated(-camera.getRY(), 0, 1, 0);\n                glTranslated(-(FontManager.BEBAS_NEUE_50.getWidth(\"N\") / 2), 0, -(FontManager.BEBAS_NEUE_50.getHeight() / 2));\n                glRotated(90, 1, 0, 0);\n                FontManager.BEBAS_NEUE_50.drawString(0, 0, \"N\", color);\n                glPopMatrix();\n\n                glPushMatrix();\n                glTranslated(-1, 0, 8);\n                glScaled(0.025, 0.025, 0.025);\n                glRotated(-camera.getRY(), 0, 1, 0);\n                glTranslated(-(FontManager.BEBAS_NEUE_50.getWidth(\"W\") / 2), 0, -(FontManager.BEBAS_NEUE_50.getHeight() / 2));\n                glRotated(90, 1, 0, 0);\n                FontManager.BEBAS_NEUE_50.drawString(0, 0, \"W\", color);\n                glPopMatrix();\n\n                glPushMatrix();\n                glTranslated(17, 0, 8);\n                glScaled(0.025, 0.025, 0.025);\n                glRotated(-camera.getRY(), 0, 1, 0);\n                glTranslated(-(FontManager.BEBAS_NEUE_50.getWidth(\"E\") / 2), 0, -(FontManager.BEBAS_NEUE_50.getHeight() / 2));\n                glRotated(90, 1, 0, 0);\n                FontManager.BEBAS_NEUE_50.drawString(0, 0, \"E\", color);\n                glPopMatrix();\n            }\n\n            glDisable(GL_TEXTURE_2D);\n            glDisable(GL_BLEND);\n        }\n        glPopMatrix();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/display/render/ThirdPersonPropertyRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.display.render;\n\nimport com.mrcrayfish.modelcreator.Camera;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class ThirdPersonPropertyRenderer extends DisplayPropertyRenderer\n{\n    private boolean leftHanded;\n\n    public ThirdPersonPropertyRenderer(boolean leftHanded)\n    {\n        this.leftHanded = leftHanded;\n        this.addElements();\n    }\n\n    private void addElements()\n    {\n        Element rightArm = new Element(4, 12, 4);\n        rightArm.setStartX(-2);\n        rightArm.setStartY(-12);\n        rightArm.setStartZ(-12);\n        rightArm.setOriginX(0);\n        rightArm.setOriginY(-2);\n        rightArm.setOriginZ(-10);\n        rightArm.setRotation(-90F); //TODO maybe add option to change\n        elements.add(rightArm);\n\n        Element leftArm = new Element(4, 12, 4);\n        leftArm.setStartX(10);\n        leftArm.setStartY(-12);\n        leftArm.setStartZ(-12);\n        leftArm.setOriginX(12);\n        leftArm.setOriginY(-2);\n        leftArm.setOriginZ(-10);\n        leftArm.setRotation(-15F); //TODO maybe add option to change\n        elements.add(leftArm);\n\n        Element body = new Element(8, 12, 4);\n        body.setStartX(2);\n        body.setStartY(-12);\n        body.setStartZ(-12);\n        body.setOriginX(0);\n        body.setOriginY(0);\n        body.setOriginZ(0);\n        elements.add(body);\n\n        Element head = new Element(8, 8, 8);\n        head.setStartX(2);\n        head.setStartY(0);\n        head.setStartZ(-14);\n        head.setOriginX(6);\n        head.setOriginY(0);\n        head.setOriginZ(-10);\n        elements.add(head);\n\n        Element rightLeg = new Element(4, 12, 4);\n        rightLeg.setStartX(2);\n        rightLeg.setStartY(-24);\n        rightLeg.setStartZ(-12);\n        rightLeg.setOriginX(4);\n        rightLeg.setOriginY(-12);\n        rightLeg.setOriginZ(-10);\n        rightLeg.setRotation(-15F);\n        elements.add(rightLeg);\n\n        Element leftLeg = new Element(4, 12, 4);\n        leftLeg.setStartX(6);\n        leftLeg.setStartY(-24);\n        leftLeg.setStartZ(-12);\n        leftLeg.setOriginX(8);\n        leftLeg.setOriginY(-12);\n        leftLeg.setOriginZ(-10);\n        leftLeg.setRotation(15F);\n        elements.add(leftLeg);\n    }\n\n    @Override\n    public void onInit(Camera camera)\n    {\n        camera.setX(leftHanded ? -13 : 13);\n        camera.setY(-5);\n        camera.setZ(-45);\n        camera.setRX(10);\n        camera.setRY(leftHanded ? -90 : 90);\n        camera.setRZ(0);\n    }\n\n    @Override\n    public void onRenderPerspective(ModelCreator creator, ElementManager manager, Camera camera)\n    {\n        DisplayProperties.Entry entry = creator.getElementManager().getDisplayProperties().getEntry(!leftHanded ? \"thirdperson_righthand\" : \"thirdperson_lefthand\");\n        if(entry != null)\n        {\n            glMatrixMode(GL_MODELVIEW);\n            glLoadIdentity();\n            glEnable(GL_DEPTH_TEST);\n            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n            glLoadIdentity();\n            camera.useView();\n\n            glPushMatrix();\n            glPushAttrib(0);\n            {\n                if(leftHanded)\n                {\n                    glScaled(-1, 1, 1);\n                    glEnable(GL_CULL_FACE);\n                    glCullFace(GL_FRONT);\n                }\n\n                glScaled(2.5, 2.5, 2.5);\n\n                for(Element element : elements)\n                {\n                    element.drawExtras(manager);\n                    element.draw();\n                }\n\n                if(leftHanded)\n                {\n                    glCullFace(GL_BACK);\n                    glDisable(GL_CULL_FACE);\n                }\n\n                glTranslated(-entry.getTranslationX(), entry.getTranslationY(), -entry.getTranslationZ());\n                glScaled(entry.getScaleX(), entry.getScaleY(), entry.getScaleZ());\n                glRotatef(180F, 0, 1, 0);\n                glRotatef((float) entry.getRotationX(), 1, 0, 0);\n                glRotatef((float) entry.getRotationY(), 0, 1, 0);\n                glRotatef((float) entry.getRotationZ(), 0, 0, 1);\n                glTranslatef(0, -8, 0);\n\n                if(leftHanded)\n                {\n                    glScaled(-1, 1, 1);\n                    glRotatef(180F, 0, 1, 0);\n                }\n\n                glPushMatrix();\n                {\n                    this.drawGrid(camera, false);\n                    this.drawElements(manager);\n                }\n                glPopMatrix();\n\n                glDisable(GL_DEPTH_TEST);\n                glDisable(GL_CULL_FACE);\n                glDisable(GL_TEXTURE_2D);\n                glDisable(GL_LIGHTING);\n            }\n            glPopAttrib();\n            glPopMatrix();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/Element.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.object.FaceDimension;\nimport com.mrcrayfish.modelcreator.sidebar.UVSidebar;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport org.lwjgl.input.Mouse;\nimport org.lwjgl.opengl.GL11;\nimport org.lwjgl.util.glu.Sphere;\n\nimport static org.lwjgl.opengl.GL11.*;\n\npublic class Element\n{\n    private String name = \"Cube\";\n    private boolean visible = true;\n\n    // Face Variables\n    private int selectedFace = 0;\n    private Face[] faces = new Face[6];\n\n    // Element Variables\n    private double startX = 0.0, startY = 0.0, startZ = 0.0;\n    private double width = 16.0, height = 1.0, depth = 1.0;\n\n    // Rotation Variables\n    private double originX = 8, originY = 8, originZ = 8;\n    private double rotation;\n    private int axis = 0;\n    private boolean rescale = false;\n\n    // Extra Variables\n    private boolean shade = true;\n\n    // Rotation Point Indicator\n    private Sphere sphere = new Sphere();\n\n    public Element(double width, double height, double depth)\n    {\n        this.width = width;\n        this.height = height;\n        this.depth = depth;\n        initFaces();\n        updateEndUVs();\n    }\n\n    public Element(Element cuboid)\n    {\n        this.name = cuboid.getName();\n        this.width = cuboid.getWidth();\n        this.height = cuboid.getHeight();\n        this.depth = cuboid.getDepth();\n        this.startX = cuboid.getStartX();\n        this.startY = cuboid.getStartY();\n        this.startZ = cuboid.getStartZ();\n        this.originX = cuboid.getOriginX();\n        this.originY = cuboid.getOriginY();\n        this.originZ = cuboid.getOriginZ();\n        this.rotation = cuboid.getRotation();\n        this.axis = cuboid.getRotationAxis();\n        this.rescale = cuboid.shouldRescale();\n        this.shade = cuboid.isShaded();\n        this.selectedFace = cuboid.getSelectedFaceIndex();\n        initFaces();\n        for(int i = 0; i < faces.length; i++)\n        {\n            Face oldFace = cuboid.getAllFaces()[i];\n            faces[i].fitTexture(oldFace.shouldFitTexture());\n            faces[i].setTexture(oldFace.getTexture());\n            faces[i].setStartU(oldFace.getStartU());\n            faces[i].setStartV(oldFace.getStartV());\n            faces[i].setEndU(oldFace.getEndU());\n            faces[i].setEndV(oldFace.getEndV());\n            faces[i].setRotation(oldFace.getRotation());\n            faces[i].setCullface(oldFace.isCullfaced());\n            faces[i].setEnabled(oldFace.isEnabled());\n            faces[i].setAutoUVEnabled(oldFace.isAutoUVEnabled());\n            faces[i].setTintIndexEnabled(oldFace.isTintIndexEnabled());\n            faces[i].setTintIndex(oldFace.getTintIndex());\n        }\n        updateEndUVs();\n    }\n\n    private void initFaces()\n    {\n        for(int i = 0; i < faces.length; i++)\n            faces[i] = new Face(this, i);\n    }\n\n    public void setSelectedFace(int face)\n    {\n        this.selectedFace = face;\n    }\n\n    public Face getSelectedFace()\n    {\n        return faces[selectedFace];\n    }\n\n    public int getSelectedFaceIndex()\n    {\n        return selectedFace;\n    }\n\n    public Face[] getAllFaces()\n    {\n        return faces;\n    }\n\n    public FaceDimension getFaceDimension(int side)\n    {\n        switch(side)\n        {\n            case 0:\n                return new FaceDimension(getWidth(), getHeight());\n            case 1:\n                return new FaceDimension(getDepth(), getHeight());\n            case 2:\n                return new FaceDimension(getWidth(), getHeight());\n            case 3:\n                return new FaceDimension(getDepth(), getHeight());\n            case 4:\n                return new FaceDimension(getWidth(), getDepth());\n            case 5:\n                return new FaceDimension(getWidth(), getDepth());\n        }\n        return null;\n    }\n\n    public void setAllTextures(TextureEntry entry)\n    {\n        for(Face face : faces)\n        {\n            face.setTexture(entry);\n        }\n    }\n\n    public void draw()\n    {\n        GL11.glPushMatrix();\n        {\n            GL11.glEnable(GL_BLEND);\n            GL11.glEnable(GL_CULL_FACE);\n            GL11.glTranslated(getOriginX(), getOriginY(), getOriginZ());\n            rotateAxis();\n            GL11.glTranslated(-getOriginX(), -getOriginY(), -getOriginZ());\n\n            // North\n            if(faces[0].isEnabled())\n            {\n                faces[0].renderNorth();\n            }\n\n            // East\n            if(faces[1].isEnabled())\n            {\n                faces[1].renderEast();\n            }\n\n            // South\n            if(faces[2].isEnabled())\n            {\n                faces[2].renderSouth();\n            }\n\n            // West\n            if(faces[3].isEnabled())\n            {\n                faces[3].renderWest();\n            }\n\n            // Top\n            if(faces[4].isEnabled())\n            {\n                faces[4].renderUp();\n            }\n\n            // Bottom\n            if(faces[5].isEnabled())\n            {\n                faces[5].renderDown();\n            }\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void drawExtras(ElementManager manager)\n    {\n        if(manager.getSelectedElement() == this)\n        {\n            GL11.glPushMatrix();\n            {\n                GL11.glTranslated(getOriginX(), getOriginY(), getOriginZ());\n                GL11.glColor3f(0.25F, 0.25F, 0.25F);\n                sphere.draw(0.2F, 16, 16);\n                rotateAxis();\n                GL11.glLineWidth(2F);\n                GL11.glBegin(GL_LINES);\n                {\n                    GL11.glColor3f(1, 0, 0);\n                    GL11.glVertex3i(-4, 0, 0);\n                    GL11.glVertex3i(4, 0, 0);\n                    GL11.glColor3f(0, 1, 0);\n                    GL11.glVertex3i(0, -4, 0);\n                    GL11.glVertex3i(0, 4, 0);\n                    GL11.glColor3f(0, 0, 1);\n                    GL11.glVertex3i(0, 0, -4);\n                    GL11.glVertex3i(0, 0, 4);\n                }\n                GL11.glEnd();\n            }\n            GL11.glPopMatrix();\n        }\n    }\n\n    public void drawOutline()\n    {\n        GL11.glDepthMask(false);\n        GL11.glPushMatrix();\n        {\n            GL11.glTranslated(getOriginX(), getOriginY(), getOriginZ());\n            rotateAxis();\n            GL11.glTranslated(-getOriginX(), -getOriginY(), -getOriginZ());\n            GL11.glTranslated(getStartX(), getStartY(), getStartZ());\n\n            float outlineScale = 1.01F;\n            GL11.glScalef(outlineScale, outlineScale, outlineScale);\n            GL11.glTranslated(-((getWidth() * outlineScale) - getWidth()) / 2.0, -((getHeight() * outlineScale) - getHeight()) / 2.0, -((getDepth() * outlineScale) - getDepth()) / 2.0);\n\n            GL11.glColor3f(1.0F, 1.0F, 1.0F);\n            GL11.glLineWidth(3F);\n\n            boolean grabbing = ((UVSidebar)ModelCreator.uvSidebar).isGrabbing();\n            int hoveredFace = ((UVSidebar)ModelCreator.uvSidebar).getHoveredFace();\n            if(hoveredFace == -1 && ModelCreator.isUVSidebarOpen)\n            {\n                hoveredFace = this.getSelectedFace().getSide();\n            }\n\n            /* Bottom */\n            if(hoveredFace != Face.DOWN)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(width, 0, 0);\n                    GL11.glVertex3d(width, 0, depth);\n                    GL11.glVertex3d(0, 0, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* Top */\n            if(hoveredFace != Face.UP)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, height, 0);\n                    GL11.glVertex3d(width, height, 0);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(0, height, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* North */\n            if(hoveredFace != Face.NORTH)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(0, height, 0);\n                    GL11.glVertex3d(width, height, 0);\n                    GL11.glVertex3d(width, 0, 0);\n                }\n                GL11.glEnd();\n            }\n\n            /* South */\n            if(hoveredFace != Face.SOUTH)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, depth);\n                    GL11.glVertex3d(0, height, depth);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(width, 0, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* West */\n            if(hoveredFace != Face.WEST)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(0, 0, depth);\n                    GL11.glVertex3d(0, height, depth);\n                    GL11.glVertex3d(0, height, 0);\n                }\n                GL11.glEnd();\n            }\n\n            /* EAST */\n            if(hoveredFace != Face.EAST)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(width, 0, 0);\n                    GL11.glVertex3d(width, 0, depth);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(width, height, 0);\n                }\n                GL11.glEnd();\n            }\n\n            GL11.glColor3f(1.0F, 0.0F, 0.0F);\n            GL11.glLineWidth(3F);\n\n            /* Bottom */\n            if(hoveredFace == Face.DOWN)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(width, 0, 0);\n                    GL11.glVertex3d(width, 0, depth);\n                    GL11.glVertex3d(0, 0, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* Top */\n            if(hoveredFace == Face.UP)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, height, 0);\n                    GL11.glVertex3d(width, height, 0);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(0, height, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* North */\n            if(hoveredFace == Face.NORTH)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(0, height, 0);\n                    GL11.glVertex3d(width, height, 0);\n                    GL11.glVertex3d(width, 0, 0);\n                }\n                GL11.glEnd();\n            }\n\n            /* South */\n            if(hoveredFace == Face.SOUTH)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, depth);\n                    GL11.glVertex3d(0, height, depth);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(width, 0, depth);\n                }\n                GL11.glEnd();\n            }\n\n            /* West */\n            if(hoveredFace == Face.WEST)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(0, 0, 0);\n                    GL11.glVertex3d(0, 0, depth);\n                    GL11.glVertex3d(0, height, depth);\n                    GL11.glVertex3d(0, height, 0);\n                }\n                GL11.glEnd();\n            }\n\n            /* East */\n            if(hoveredFace == Face.EAST)\n            {\n                GL11.glBegin(GL_LINE_LOOP);\n                {\n                    GL11.glVertex3d(width, 0, 0);\n                    GL11.glVertex3d(width, 0, depth);\n                    GL11.glVertex3d(width, height, depth);\n                    GL11.glVertex3d(width, height, 0);\n                }\n                GL11.glEnd();\n            }\n        }\n        GL11.glPopMatrix();\n        GL11.glDepthMask(true);\n    }\n\n    public void addStartX(double amt)\n    {\n        this.startX += amt;\n    }\n\n    public void addStartY(double amt)\n    {\n        this.startY += amt;\n    }\n\n    public void addStartZ(double amt)\n    {\n        this.startZ += amt;\n    }\n\n    public double getStartX()\n    {\n        return startX;\n    }\n\n    public double getStartY()\n    {\n        return startY;\n    }\n\n    public double getStartZ()\n    {\n        return startZ;\n    }\n\n    public void setStartX(double amt)\n    {\n        this.startX = amt;\n    }\n\n    public void setStartY(double amt)\n    {\n        this.startY = amt;\n    }\n\n    public void setStartZ(double amt)\n    {\n        this.startZ = amt;\n    }\n\n    public double getWidth()\n    {\n        return width;\n    }\n\n    public double getHeight()\n    {\n        return height;\n    }\n\n    public double getDepth()\n    {\n        return depth;\n    }\n\n    public void addWidth(double amt)\n    {\n        this.width += amt;\n    }\n\n    public void addHeight(double amt)\n    {\n        this.height += amt;\n    }\n\n    public void addDepth(double amt)\n    {\n        this.depth += amt;\n    }\n\n    public void setWidth(double width)\n    {\n        this.width = width;\n    }\n\n    public void setHeight(double height)\n    {\n        this.height = height;\n    }\n\n    public void setDepth(double depth)\n    {\n        this.depth = depth;\n    }\n\n    public double getOriginX()\n    {\n        return originX;\n    }\n\n    public double getOriginY()\n    {\n        return originY;\n    }\n\n    public double getOriginZ()\n    {\n        return originZ;\n    }\n\n    public void addOriginX(double amt)\n    {\n        this.originX += amt;\n    }\n\n    public void addOriginY(double amt)\n    {\n        this.originY += amt;\n    }\n\n    public void addOriginZ(double amt)\n    {\n        this.originZ += amt;\n    }\n\n    public void setOriginX(double amt)\n    {\n        this.originX = amt;\n    }\n\n    public void setOriginY(double amt)\n    {\n        this.originY = amt;\n    }\n\n    public void setOriginZ(double amt)\n    {\n        this.originZ = amt;\n    }\n\n    public double getRotation()\n    {\n        return rotation;\n    }\n\n    public void setRotation(double rotation)\n    {\n        this.rotation = rotation;\n    }\n\n    public int getRotationAxis()\n    {\n        return axis;\n    }\n\n    public void setRotationAxis(int axis)\n    {\n        this.axis = axis;\n    }\n\n    public void setRescale(boolean rescale)\n    {\n        this.rescale = rescale;\n    }\n\n    public boolean shouldRescale()\n    {\n        return rescale;\n    }\n\n    public boolean isShaded()\n    {\n        return shade;\n    }\n\n    public void setShade(boolean shade)\n    {\n        this.shade = shade;\n    }\n\n    public void setName(String name)\n    {\n        this.name = name;\n    }\n\n    public String getName()\n    {\n        return name;\n    }\n\n    @Override\n    public String toString()\n    {\n        return name + \"(w:\" + width + \",h:\" + height + \",d:\" + depth + \")\";\n    }\n\n    public void updateStartUVs()\n    {\n        for(Face face : faces)\n        {\n            face.updateStartUV();\n        }\n    }\n\n    public void updateEndUVs()\n    {\n        for(Face face : faces)\n        {\n            face.updateEndUV();\n        }\n    }\n\n    private void rotateAxis()\n    {\n        switch(axis)\n        {\n            case 0:\n                GL11.glRotated(getRotation(), 1, 0, 0);\n                break;\n            case 1:\n                GL11.glRotated(getRotation(), 0, 1, 0);\n                break;\n            case 2:\n                GL11.glRotated(getRotation(), 0, 0, 1);\n                break;\n        }\n    }\n\n    public static String parseAxis(int axis)\n    {\n        switch(axis)\n        {\n            case 0:\n                return \"x\";\n            case 1:\n                return \"y\";\n            case 2:\n                return \"z\";\n        }\n        return \"x\";\n    }\n\n    public static int parseAxisString(String axis)\n    {\n        switch(axis)\n        {\n            case \"x\":\n                return 0;\n            case \"y\":\n                return 1;\n            case \"z\":\n                return 2;\n        }\n        return 0;\n    }\n\n    public boolean isVisible()\n    {\n        return visible;\n    }\n\n    public void setVisible(boolean visible)\n    {\n        this.visible = visible;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementCellEntry.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.Icons;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class ElementCellEntry\n{\n    private Element element;\n    private JPanel panel;\n    private JLabel visibility;\n    private JLabel name;\n\n    public ElementCellEntry(Element element)\n    {\n        this.element = element;\n        this.createPanel();\n    }\n\n    private void createPanel()\n    {\n        panel = new JPanel();\n        panel.setLayout(new FlowLayout(FlowLayout.LEFT));\n\n        visibility = new JLabel();\n        visibility.setIcon(element.isVisible() ? Icons.light_on : Icons.light_off);\n        panel.add(visibility);\n\n        name = new JLabel(element.getName());\n        panel.add(name);\n    }\n\n    public Element getElement()\n    {\n        return element;\n    }\n\n    public JPanel getPanel()\n    {\n        return panel;\n    }\n\n    public JLabel getVisibility()\n    {\n        return visibility;\n    }\n\n    public JLabel getName()\n    {\n        return name;\n    }\n\n    public void toggleVisibility()\n    {\n        element.setVisible(!element.isVisible());\n        visibility.setIcon(element.isVisible() ? Icons.light_on : Icons.light_off);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementCellRenderer.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport javax.swing.*;\nimport java.awt.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class ElementCellRenderer extends DefaultListCellRenderer\n{\n    @Override\n    public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus)\n    {\n        ElementCellEntry entry = (ElementCellEntry) value;\n        JPanel panel = entry.getPanel();\n        panel.setBackground(isSelected ? new Color(186, 193, 211) : new Color(234, 234, 242));\n        entry.getName().setText(entry.getElement().getName());\n        return panel;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementManager.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport java.util.List;\n\npublic interface ElementManager\n{\n    Element getSelectedElement();\n\n    void setSelectedElement(int pos);\n\n    List<Element> getAllElements();\n\n    Element getElement(int index);\n\n    int getElementCount();\n\n    void clearElements();\n\n    void updateName();\n\n    void updateValues();\n\n    boolean getAmbientOcc();\n\n    void setAmbientOcc(boolean occ);\n\n    void addElement(Element e);\n\n    void setParticle(TextureEntry entry);\n\n    TextureEntry getParticle();\n\n    void reset();\n\n    default ElementManagerState createState()\n    {\n        return new ElementManagerState(this);\n    }\n\n    void restoreState(ElementManagerState state);\n\n    void setDisplayProperties(DisplayProperties properties);\n\n    DisplayProperties getDisplayProperties();\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/ElementManagerState.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Author: MrCrayfish\n */\npublic class ElementManagerState\n{\n    private final List<Element> elements;\n    private final int selectedIndex;\n    private final boolean ambientOcclusion;\n    private final TextureEntry particleTexture;\n\n    public ElementManagerState(ElementManager manager)\n    {\n        this.elements = manager.getAllElements().stream().map(Element::new).collect(Collectors.toList());\n        this.selectedIndex = manager.getAllElements().indexOf(manager.getSelectedElement());\n        this.ambientOcclusion = manager.getAmbientOcc();\n        this.particleTexture = manager.getParticle();\n    }\n\n    public List<Element> getElements()\n    {\n        return elements;\n    }\n\n    public int getSelectedIndex()\n    {\n        return selectedIndex;\n    }\n\n    public boolean isAmbientOcclusion()\n    {\n        return ambientOcclusion;\n    }\n\n    public TextureEntry getParticleTexture()\n    {\n        return particleTexture;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/element/Face.java",
    "content": "package com.mrcrayfish.modelcreator.element;\n\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport org.lwjgl.BufferUtils;\nimport org.lwjgl.opengl.GL11;\n\nimport java.nio.FloatBuffer;\n\nimport static org.lwjgl.opengl.GL11.*;\n\npublic class Face\n{\n    private static int[] colors;\n\n    static\n    {\n        colors = Settings.getFaceColors();\n    }\n\n    public static final int NORTH = 0;\n    public static final int EAST = 1;\n    public static final int SOUTH = 2;\n    public static final int WEST = 3;\n    public static final int UP = 4;\n    public static final int DOWN = 5;\n\n    private TextureEntry texture = null;\n    private double textureU = 0;\n    private double textureV = 0;\n    private double textureUEnd = 16;\n    private double textureVEnd = 16;\n    private boolean fitTexture = false;\n    private boolean binded = false;\n    private boolean cullface = false;\n    private boolean enabled = true;\n    private boolean autoUV = true;\n    private int rotation;\n    private boolean tintIndexEnabled = false;\n    private int tintIndex = -1;\n\n    private Element cuboid;\n    private int side;\n\n    public Face(Element cuboid, int side)\n    {\n        this.cuboid = cuboid;\n        this.side = side;\n    }\n\n    public Face(Face face)\n    {\n        this.copyProperties(face);\n    }\n\n    public void copyProperties(Face face)\n    {\n        this.fitTexture = face.fitTexture;\n        this.texture = face.texture;\n        this.textureU = face.textureU;\n        this.textureV = face.textureV;\n        this.textureUEnd = face.textureUEnd;\n        this.textureVEnd = face.textureVEnd;\n        this.rotation = face.rotation;\n        this.cullface = face.cullface;\n        this.enabled = face.enabled;\n        this.autoUV = face.autoUV;\n        this.tintIndex = face.tintIndex;\n    }\n\n    public void renderNorth()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderNorth(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderNorth(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n            applyShade(0.45F);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void renderEast()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderEast(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderEast(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n            applyShade(0.3F);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void renderSouth()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderSouth(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderSouth(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n            applyShade(0.15F);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void renderWest()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderWest(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderWest(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n            applyShade(0.3F);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void renderUp()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderUp(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderUp(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY() + cuboid.getHeight(), cuboid.getStartZ());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    public void renderDown()\n    {\n        int passes = texture != null ? texture.getPasses() : 1;\n        for(int i = 0; i < passes; i++)\n        {\n            renderDown(i, GL11.GL_QUADS);\n        }\n    }\n\n    private void renderDown(int pass, int mode)\n    {\n        GL11.glPushMatrix();\n        {\n            startRender(pass);\n            applyShade(0.55F);\n\n            GL11.glBegin(mode);\n            {\n                if(binded)\n                {\n                    setTexCoord(0);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(1);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ());\n\n                if(binded)\n                {\n                    setTexCoord(2);\n                }\n                GL11.glVertex3d(cuboid.getStartX() + cuboid.getWidth(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n\n                if(binded)\n                {\n                    setTexCoord(3);\n                }\n                GL11.glVertex3d(cuboid.getStartX(), cuboid.getStartY(), cuboid.getStartZ() + cuboid.getDepth());\n            }\n            GL11.glEnd();\n\n            finishRender();\n        }\n        GL11.glPopMatrix();\n    }\n\n    private void setTexCoord(int corner)\n    {\n        setTexCoord(corner, false);\n    }\n\n    private void setTexCoord(int corner, boolean forceFit)\n    {\n        int coord = corner + rotation;\n        if(coord == 0 | coord == 4)\n        {\n            GL11.glTexCoord2d(fitTexture || forceFit ? 0 : (textureU / 16), fitTexture || forceFit ? 1 : (textureVEnd / 16));\n        }\n        if(coord == 1 | coord == 5)\n        {\n            GL11.glTexCoord2d(fitTexture || forceFit ? 1 : (textureUEnd / 16), fitTexture || forceFit ? 1 : (textureVEnd / 16));\n        }\n        if(coord == 2 | coord == 6)\n        {\n            GL11.glTexCoord2d(fitTexture || forceFit ? 1 : (textureUEnd / 16), fitTexture || forceFit ? 0 : (textureV / 16));\n        }\n        if(coord == 3)\n        {\n            GL11.glTexCoord2d(fitTexture || forceFit ? 0 : (textureU / 16), fitTexture || forceFit ? 0 : (textureV / 16));\n        }\n    }\n\n    private void startRender(int pass)\n    {\n        int color = Face.colors[side];\n        float b = (float) (color & 0xFF) / 0xFF;\n        float g = (float) ((color >>> 8) & 0xFF) / 0xFF;\n        float r = (float) ((color >>> 16) & 0xFF) / 0xFF;\n        GL11.glColor3f(r, g, b);\n        GL11.glEnable(GL_TEXTURE_2D);\n        bindTexture(pass);\n    }\n\n    private void finishRender()\n    {\n        GL11.glDisable(GL_TEXTURE_2D);\n    }\n\n    private void applyShade(float reduction)\n    {\n        if(cuboid.isShaded())\n        {\n            FloatBuffer buffer = BufferUtils.createFloatBuffer(16);\n            GL11.glGetFloat(GL11.GL_CURRENT_COLOR, buffer);\n            GL11.glColor3f(buffer.get() - reduction, buffer.get() - reduction, buffer.get() - reduction);\n        }\n    }\n\n    public void setTexture(TextureEntry texture)\n    {\n        this.texture = texture;\n    }\n\n    public void bindTexture(int pass)\n    {\n        GL11.glDisable(GL_TEXTURE_2D);\n        if(texture != null)\n        {\n            if(pass == 0)\n            {\n                GL11.glEnable(GL_BLEND);\n                GL11.glEnable(GL_TEXTURE_2D);\n                GL11.glColor3f(1.0F, 1.0F, 1.0F);\n                texture.bindTexture();\n            }\n            else if(pass == 1)\n            {\n                if(texture.isAnimated())\n                {\n                    GL11.glEnable(GL_TEXTURE_2D);\n                    GL11.glEnable(GL11.GL_BLEND);\n                    GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);\n                    GL11.glDepthFunc(GL11.GL_EQUAL);\n\n                    texture.bindNextTexture();\n                    GL11.glColor4d(1.0D, 1.0D, 1.0D, texture.getAnimation().getFrameInterpolation());\n                }\n            }\n\n            binded = true;\n        }\n    }\n\n    public void moveTextureU(double amt)\n    {\n        this.textureU += amt;\n        this.textureUEnd += amt;\n    }\n\n    public void moveTextureV(double amt)\n    {\n        this.textureV += amt;\n        this.textureVEnd += amt;\n    }\n\n    private double getMinX()\n    {\n        if(side == EAST)\n        {\n            return cuboid.getStartX() + cuboid.getWidth();\n        }\n        return cuboid.getStartX();\n    }\n\n    private double getMinY()\n    {\n        if(side == UP)\n        {\n            return cuboid.getStartY() + cuboid.getHeight();\n        }\n        return cuboid.getStartY();\n    }\n\n    private double getMinZ()\n    {\n        if(side == SOUTH)\n        {\n            return cuboid.getStartZ() + cuboid.getDepth();\n        }\n        return cuboid.getStartZ();\n    }\n\n    private double getMaxX()\n    {\n        if(side == WEST)\n        {\n            return cuboid.getStartX();\n        }\n        return cuboid.getStartX() + cuboid.getWidth();\n    }\n\n    private double getMaxY()\n    {\n        if(side == DOWN)\n        {\n            return cuboid.getStartY();\n        }\n        return cuboid.getStartY() + cuboid.getHeight();\n    }\n\n    private double getMaxZ()\n    {\n        if(side == NORTH)\n        {\n            return cuboid.getStartZ();\n        }\n        return cuboid.getStartZ() + cuboid.getDepth();\n    }\n\n    public void addTextureX(double amt)\n    {\n        this.textureU += amt;\n    }\n\n    public void addTextureY(double amt)\n    {\n        this.textureV += amt;\n    }\n\n    public void addTextureXEnd(double amt)\n    {\n        this.textureUEnd += amt;\n    }\n\n    public void addTextureYEnd(double amt)\n    {\n        this.textureVEnd += amt;\n    }\n\n    public double getStartU()\n    {\n        return textureU;\n    }\n\n    public double getStartV()\n    {\n        return textureV;\n    }\n\n    public double getEndU()\n    {\n        return textureUEnd;\n    }\n\n    public double getEndV()\n    {\n        return textureVEnd;\n    }\n\n    public void setStartU(double u)\n    {\n        textureU = u;\n    }\n\n    public void setStartV(double v)\n    {\n        textureV = v;\n    }\n\n    public void setEndU(double ue)\n    {\n        textureUEnd = ue;\n    }\n\n    public void setEndV(double ve)\n    {\n        textureVEnd = ve;\n    }\n\n    public TextureEntry getTexture()\n    {\n        return texture;\n    }\n\n    public void fitTexture(boolean fitTexture)\n    {\n        this.fitTexture = fitTexture;\n    }\n\n    public boolean shouldFitTexture()\n    {\n        return fitTexture;\n    }\n\n    public int getSide()\n    {\n        return side;\n    }\n\n    public boolean isCullfaced()\n    {\n        return cullface;\n    }\n\n    public void setCullface(boolean cullface)\n    {\n        this.cullface = cullface;\n    }\n\n    public boolean isEnabled()\n    {\n        return enabled;\n    }\n\n    public void setEnabled(boolean enabled)\n    {\n        this.enabled = enabled;\n    }\n\n    public boolean isAutoUVEnabled()\n    {\n        return autoUV;\n    }\n\n    public void setAutoUVEnabled(boolean enabled)\n    {\n        this.autoUV = enabled;\n    }\n\n    public boolean isBinded()\n    {\n        return binded;\n    }\n\n    public void updateStartUV()\n    {\n        if(autoUV)\n        {\n            textureU = Math.max(0.0, textureUEnd - cuboid.getFaceDimension(side).getWidth());\n            textureV = Math.max(0.0, textureVEnd - cuboid.getFaceDimension(side).getHeight());\n        }\n    }\n\n    public void updateEndUV()\n    {\n        if(autoUV)\n        {\n            textureUEnd = Math.min(16.0, textureU + cuboid.getFaceDimension(side).getWidth());\n            textureVEnd = Math.min(16.0, textureV + cuboid.getFaceDimension(side).getHeight());\n        }\n    }\n\n    public static String getFaceName(int face)\n    {\n        switch(face)\n        {\n            case 0:\n                return \"north\";\n            case 1:\n                return \"east\";\n            case 2:\n                return \"south\";\n            case 3:\n                return \"west\";\n            case 4:\n                return \"up\";\n            case 5:\n                return \"down\";\n        }\n        return null;\n    }\n\n    public static int getFaceSide(String name)\n    {\n        switch(name)\n        {\n            case \"north\":\n                return 0;\n            case \"east\":\n                return 1;\n            case \"south\":\n                return 2;\n            case \"west\":\n                return 3;\n            case \"up\":\n                return 4;\n            case \"down\":\n                return 5;\n        }\n        return -1;\n    }\n\n    public static int getFaceColour(int side)\n    {\n        if(side >= 0 && side < colors.length)\n        {\n            return colors[side];\n        }\n        return 0;\n    }\n\n    public static int[] getFaceColors()\n    {\n        return colors;\n    }\n\n    public static void setFaceColors(int[] colors)\n    {\n        if(colors.length == 6)\n        {\n            Face.colors = colors;\n        }\n    }\n\n    public static void setFaceColor(int side, int color)\n    {\n        Face.colors[side] = color;\n    }\n\n    public int getRotation()\n    {\n        return rotation;\n    }\n\n    public void setRotation(int rotation)\n    {\n        this.rotation = rotation;\n    }\n\n    public void setTintIndexEnabled(boolean enabled)\n    {\n        this.tintIndexEnabled = enabled;\n    }\n\n    public boolean isTintIndexEnabled()\n    {\n        return tintIndexEnabled;\n    }\n\n    public int getTintIndex()\n    {\n        return tintIndex;\n    }\n\n    public void setTintIndex(int tintIndex)\n    {\n        this.tintIndex = tintIndex;\n    }\n\n    public boolean isVisible(ElementManager manager)\n    {\n        if(cuboid.getRotation() != 0.0) //TODO make it an option\n        {\n            return true;\n        }\n\n        for(Element element : manager.getAllElements())\n        {\n            if(element == cuboid || element.getRotation() != 0.0)\n            {\n                continue;\n            }\n\n            if(this.getMinX() >= element.getStartX() && this.getMinX() <= element.getStartX() + element.getWidth())\n            {\n                if(this.getMinY() >= element.getStartY() && this.getMinY() <= element.getStartY() + element.getHeight())\n                {\n                    if(this.getMinZ() >= element.getStartZ() && this.getMinZ() <= element.getStartZ() + element.getDepth())\n                    {\n                        if(this.getMaxX() >= element.getStartX() && this.getMaxX() <= element.getStartX() + element.getWidth())\n                        {\n                            if(this.getMaxY() >= element.getStartY() && this.getMaxY() <= element.getStartY() + element.getHeight())\n                            {\n                                if(this.getMaxZ() >= element.getStartZ() && this.getMaxZ() <= element.getStartZ() + element.getDepth())\n                                {\n                                    return false;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/object/FaceDimension.java",
    "content": "package com.mrcrayfish.modelcreator.object;\n\npublic class FaceDimension\n{\n    private double width, height;\n\n    public FaceDimension(double width, double height)\n    {\n        this.width = width;\n        this.height = height;\n    }\n\n    public double getWidth()\n    {\n        return width;\n    }\n\n    public double getHeight()\n    {\n        return height;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/CuboidTabbedPane.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class CuboidTabbedPane extends JTabbedPane\n{\n    private ElementManager manager;\n\n    public CuboidTabbedPane(ElementManager manager)\n    {\n        this.manager = manager;\n    }\n\n    public void updateValues()\n    {\n        for(int i = 0; i < getTabCount(); i++)\n        {\n            Component component = getComponentAt(i);\n            if(component != null)\n            {\n                if(component instanceof IElementUpdater)\n                {\n                    IElementUpdater updater = (IElementUpdater) component;\n                    updater.updateValues(manager.getSelectedElement());\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/DisplayEntryPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Exporter;\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.util.Parser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.InputEvent;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\n\n/**\n * Author: MrCrayfish\n */\npublic class DisplayEntryPanel extends JPanel\n{\n    private DisplayProperties.Entry entry; //TODO remove instance\n    private JCheckBox checkBoxEnabled;\n    private JSlider sliderRotationX;\n    private JSlider sliderRotationY;\n    private JSlider sliderRotationZ;\n    private JTextField textFieldTranslationX;\n    private JTextField textFieldTranslationY;\n    private JTextField textFieldTranslationZ;\n    private JTextField textFieldScaleX;\n    private JTextField textFieldScaleY;\n    private JTextField textFieldScaleZ;\n\n    public DisplayEntryPanel(DisplayProperties.Entry entry)\n    {\n        this.entry = entry;\n        this.setPreferredSize(new Dimension(200, 330));\n        this.setLayout(new SpringLayout());\n        this.initComponents();\n    }\n\n    public DisplayProperties.Entry getEntry()\n    {\n        return entry;\n    }\n\n    private void initComponents()\n    {\n        JPanel optionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));\n        this.add(optionsPanel);\n\n        JPanel sliderPanel = new JPanel(new GridLayout(3, 1, 0, 5));\n        sliderPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(221, 221, 228), 0), \"<html><b>Rotation</b></html>\"));\n        sliderPanel.setPreferredSize(new Dimension(0, 180));\n        this.add(sliderPanel);\n\n        sliderRotationX = createRotationSlider(\"X Axis\", sliderPanel);\n        sliderRotationX.setValue((int) entry.getRotationX());\n        sliderRotationX.addChangeListener(e -> entry.setRotationX(sliderRotationX.getValue()));\n\n        sliderRotationY = createRotationSlider(\"Y Axis\", sliderPanel);\n        sliderRotationY.setValue((int) entry.getRotationY());\n        sliderRotationY.addChangeListener(e -> entry.setRotationY(sliderRotationY.getValue()));\n\n        sliderRotationZ = createRotationSlider(\"Z Axis\", sliderPanel);\n        sliderRotationZ.setValue((int) entry.getRotationZ());\n        sliderRotationZ.addChangeListener(e -> entry.setRotationZ(sliderRotationZ.getValue()));\n\n        JPanel otherPanel = new JPanel(new GridLayout(1, 2));\n        otherPanel.setPreferredSize(new Dimension(0, 120));\n        this.add(otherPanel, BorderLayout.SOUTH);\n\n        JPanel translatePanel = new JPanel(new GridLayout(3, 3, 5, 5));\n        translatePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(221, 221, 228), 0), \"<html><b>Translation</b></html>\"));\n\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 16);\n\n        textFieldTranslationX = new JTextField();\n        textFieldTranslationX.setText(Exporter.FORMAT.format(entry.getTranslationX()));\n        textFieldTranslationX.setFont(defaultFont);\n        textFieldTranslationX.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldTranslationX.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setTranslationX(Parser.parseDouble(textFieldTranslationX.getText(), entry.getTranslationX()));\n                }\n            }\n        });\n\n        textFieldTranslationY = new JTextField();\n        textFieldTranslationY.setText(Exporter.FORMAT.format(entry.getTranslationY()));\n        textFieldTranslationY.setFont(defaultFont);\n        textFieldTranslationY.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldTranslationY.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setTranslationY(Parser.parseDouble(textFieldTranslationY.getText(), entry.getTranslationY()));\n                }\n            }\n        });\n\n        textFieldTranslationZ = new JTextField();\n        textFieldTranslationZ.setText(Exporter.FORMAT.format(entry.getTranslationZ()));\n        textFieldTranslationZ.setFont(defaultFont);\n        textFieldTranslationZ.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldTranslationZ.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setTranslationZ(Parser.parseDouble(textFieldTranslationZ.getText(), entry.getTranslationZ()));\n                }\n            }\n        });\n        \n        JButton btnTransX = new JButton(Icons.arrow_up);\n        btnTransX.addActionListener(e -> \n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationX(entry.getTranslationX() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationX(entry.getTranslationX() + 0.01);\n            }\n            else\n            {\n                entry.setTranslationX(entry.getTranslationX() + 1.0);\n            }\n            textFieldTranslationX.setText(Exporter.FORMAT.format(entry.getTranslationX()));\n        });\n        translatePanel.add(btnTransX);\n\n        JButton btnTransY = new JButton(Icons.arrow_up);\n        btnTransY.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationY(entry.getTranslationY() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationY(entry.getTranslationY() + 0.01);\n            }\n            else\n            {\n                entry.setTranslationY(entry.getTranslationY() + 1.0);\n            }\n            textFieldTranslationY.setText(Exporter.FORMAT.format(entry.getTranslationY()));\n        });\n        translatePanel.add(btnTransY);\n\n        JButton btnTransZ = new JButton(Icons.arrow_up);\n        btnTransZ.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationZ(entry.getTranslationZ() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationZ(entry.getTranslationZ() + 0.01);\n            }\n            else\n            {\n                entry.setTranslationZ(entry.getTranslationZ() + 1.0);\n            }\n            textFieldTranslationZ.setText(Exporter.FORMAT.format(entry.getTranslationZ()));\n        });\n        translatePanel.add(btnTransZ);\n\n        translatePanel.add(textFieldTranslationX);\n        translatePanel.add(textFieldTranslationY);\n        translatePanel.add(textFieldTranslationZ);\n\n        JButton btnTransXNeg = new JButton(Icons.arrow_down);\n        btnTransXNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationX(entry.getTranslationX() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationX(entry.getTranslationX() - 0.01);\n            }\n            else\n            {\n                entry.setTranslationX(entry.getTranslationX() - 1.0);\n            }\n            textFieldTranslationX.setText(Exporter.FORMAT.format(entry.getTranslationX()));\n        });\n        translatePanel.add(btnTransXNeg);\n\n        JButton btnTransYNeg = new JButton(Icons.arrow_down);\n        btnTransYNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationY(entry.getTranslationY() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationY(entry.getTranslationY() - 0.01);\n            }\n            else\n            {\n                entry.setTranslationY(entry.getTranslationY() - 1.0);\n            }\n            textFieldTranslationY.setText(Exporter.FORMAT.format(entry.getTranslationY()));\n        });\n        translatePanel.add(btnTransYNeg);\n\n        JButton btnTransZNeg = new JButton(Icons.arrow_down);\n        btnTransZNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setTranslationZ(entry.getTranslationZ() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setTranslationZ(entry.getTranslationZ() - 0.01);\n            }\n            else\n            {\n                entry.setTranslationZ(entry.getTranslationZ() - 1.0);\n            }\n            textFieldTranslationZ.setText(Exporter.FORMAT.format(entry.getTranslationZ()));\n        });\n        translatePanel.add(btnTransZNeg);\n        \n        otherPanel.add(translatePanel);\n\n        JPanel scalePanel = new JPanel(new GridLayout(3, 3, 5, 5));\n        scalePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(221, 221, 228), 0), \"<html><b>Scale</b></html>\"));\n\n        textFieldScaleX = new JTextField();\n        textFieldScaleX.setText(Exporter.FORMAT.format(entry.getScaleX()));\n        textFieldScaleX.setFont(defaultFont);\n        textFieldScaleX.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldScaleX.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setScaleX(Parser.parseDouble(textFieldScaleX.getText(), entry.getScaleX()));\n                }\n            }\n        });\n\n        textFieldScaleY = new JTextField();\n        textFieldScaleY.setText(Exporter.FORMAT.format(entry.getScaleY()));\n        textFieldScaleY.setFont(defaultFont);\n        textFieldScaleY.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldScaleY.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setScaleY(Parser.parseDouble(textFieldScaleY.getText(), entry.getScaleY()));\n                }\n            }\n        });\n\n        textFieldScaleZ = new JTextField();\n        textFieldScaleZ.setText(Exporter.FORMAT.format(entry.getScaleZ()));\n        textFieldScaleZ.setFont(defaultFont);\n        textFieldScaleZ.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldScaleZ.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    entry.setScaleZ(Parser.parseDouble(textFieldScaleZ.getText(), entry.getScaleZ()));\n                }\n            }\n        });\n\n        JButton btnScaleX = new JButton(Icons.arrow_up);\n        btnScaleX.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleX(entry.getScaleX() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleX(entry.getScaleX() + 0.01);\n            }\n            else\n            {\n                entry.setScaleX(entry.getScaleX() + 1.0);\n            }\n            textFieldScaleX.setText(Exporter.FORMAT.format(entry.getScaleX()));\n        });\n        scalePanel.add(btnScaleX);\n\n        JButton btnScaleY = new JButton(Icons.arrow_up);\n        btnScaleY.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleY(entry.getScaleY() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleY(entry.getScaleY() + 0.01);\n            }\n            else\n            {\n                entry.setScaleY(entry.getScaleY() + 1.0);\n            }\n            textFieldScaleY.setText(Exporter.FORMAT.format(entry.getScaleY()));\n        });\n        scalePanel.add(btnScaleY);\n\n        JButton btnScaleZ = new JButton(Icons.arrow_up);\n        btnScaleZ.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleZ(entry.getScaleZ() + 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleZ(entry.getScaleZ() + 0.01);\n            }\n            else\n            {\n                entry.setScaleZ(entry.getScaleZ() + 1.0);\n            }\n            textFieldScaleZ.setText(Exporter.FORMAT.format(entry.getScaleZ()));\n        });\n        scalePanel.add(btnScaleZ);\n        \n        scalePanel.add(textFieldScaleX);\n        scalePanel.add(textFieldScaleY);\n        scalePanel.add(textFieldScaleZ);\n\n        JButton btnScaleXNeg = new JButton(Icons.arrow_down);\n        btnScaleXNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleX(entry.getScaleX() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleX(entry.getScaleX() - 0.01);\n            }\n            else\n            {\n                entry.setScaleX(entry.getScaleX() - 1.0);\n            }\n            textFieldScaleX.setText(Exporter.FORMAT.format(entry.getScaleX()));\n        });\n        scalePanel.add(btnScaleXNeg);\n\n        JButton btnScaleYNeg = new JButton(Icons.arrow_down);\n        btnScaleYNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleY(entry.getScaleY() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleY(entry.getScaleY() - 0.01);\n            }\n            else\n            {\n                entry.setScaleY(entry.getScaleY() - 1.0);\n            }\n            textFieldScaleY.setText(Exporter.FORMAT.format(entry.getScaleY()));\n        });\n        scalePanel.add(btnScaleYNeg);\n\n        JButton btnScaleZNeg = new JButton(Icons.arrow_down);\n        btnScaleZNeg.addActionListener(e ->\n        {\n            if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) == 0)\n            {\n                entry.setScaleZ(entry.getScaleZ() - 0.1);\n            }\n            else if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && (e.getModifiers() & InputEvent.CTRL_MASK) > 0)\n            {\n                entry.setScaleZ(entry.getScaleZ() - 0.01);\n            }\n            else\n            {\n                entry.setScaleZ(entry.getScaleZ() - 1.0);\n            }\n            textFieldScaleZ.setText(Exporter.FORMAT.format(entry.getScaleZ()));\n        });\n        scalePanel.add(btnScaleZNeg);\n        otherPanel.add(scalePanel);\n\n        checkBoxEnabled = new JCheckBox(\"Enabled\");\n        checkBoxEnabled.setSelected(entry.isEnabled());\n        checkBoxEnabled.setIcon(Icons.light_off);\n        checkBoxEnabled.setRolloverIcon(Icons.light_off);\n        checkBoxEnabled.setSelectedIcon(Icons.light_on);\n        checkBoxEnabled.setRolloverSelectedIcon(Icons.light_on);\n        checkBoxEnabled.addActionListener(e ->\n        {\n            boolean enabled = checkBoxEnabled.isSelected();\n            entry.setEnabled(enabled);\n            sliderRotationX.setEnabled(enabled);\n            sliderRotationY.setEnabled(enabled);\n            sliderRotationZ.setEnabled(enabled);\n            btnTransX.setEnabled(enabled);\n            btnTransY.setEnabled(enabled);\n            btnTransZ.setEnabled(enabled);\n            textFieldTranslationX.setEnabled(enabled);\n            textFieldTranslationY.setEnabled(enabled);\n            textFieldTranslationZ.setEnabled(enabled);\n            btnTransXNeg.setEnabled(enabled);\n            btnTransYNeg.setEnabled(enabled);\n            btnTransZNeg.setEnabled(enabled);\n            btnScaleX.setEnabled(enabled);\n            btnScaleY.setEnabled(enabled);\n            btnScaleZ.setEnabled(enabled);\n            textFieldScaleX.setEnabled(enabled);\n            textFieldScaleY.setEnabled(enabled);\n            textFieldScaleZ.setEnabled(enabled);\n            btnScaleXNeg.setEnabled(enabled);\n            btnScaleYNeg.setEnabled(enabled);\n            btnScaleZNeg.setEnabled(enabled);\n        });\n        optionsPanel.add(checkBoxEnabled);\n\n\n        SpringLayout springLayout = (SpringLayout) this.getLayout();\n        springLayout.putConstraint(SpringLayout.NORTH, optionsPanel, 5, SpringLayout.NORTH, this);\n        springLayout.putConstraint(SpringLayout.WEST, optionsPanel, 10, SpringLayout.WEST, this);\n        springLayout.putConstraint(SpringLayout.EAST, optionsPanel, 10, SpringLayout.EAST, this);\n        springLayout.putConstraint(SpringLayout.WEST, sliderPanel, 10, SpringLayout.WEST, this);\n        springLayout.putConstraint(SpringLayout.NORTH, sliderPanel, 5, SpringLayout.SOUTH, optionsPanel);\n        springLayout.putConstraint(SpringLayout.EAST, sliderPanel, -10, SpringLayout.EAST, this);\n        springLayout.putConstraint(SpringLayout.WEST, otherPanel, 10, SpringLayout.WEST, this);\n        springLayout.putConstraint(SpringLayout.NORTH, otherPanel, 10, SpringLayout.SOUTH, sliderPanel);\n        springLayout.putConstraint(SpringLayout.EAST, otherPanel, -10, SpringLayout.EAST, this);\n    }\n\n    public void updateValues(DisplayProperties.Entry entry)\n    {\n        this.entry = entry;\n        checkBoxEnabled.setSelected(entry.isEnabled());\n        sliderRotationX.setValue((int) entry.getRotationX());\n        sliderRotationY.setValue((int) entry.getRotationY());\n        sliderRotationZ.setValue((int) entry.getRotationZ());\n        textFieldTranslationX.setText(Exporter.FORMAT.format(entry.getTranslationX()));\n        textFieldTranslationY.setText(Exporter.FORMAT.format(entry.getTranslationY()));\n        textFieldTranslationZ.setText(Exporter.FORMAT.format(entry.getTranslationZ()));\n        textFieldScaleX.setText(Exporter.FORMAT.format(entry.getScaleX()));\n        textFieldScaleY.setText(Exporter.FORMAT.format(entry.getScaleY()));\n        textFieldScaleZ.setText(Exporter.FORMAT.format(entry.getScaleZ()));\n    }\n\n    private JSlider createRotationSlider(String labelText, JComponent parent)\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n\n        JLabel labelKey = new JLabel(labelText);\n        JTextField textFieldValue = new JTextField(\"0\");\n        JSlider sliderValue = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);\n\n        labelKey.setPreferredSize(new Dimension(100, 24));\n        panel.add(labelKey);\n\n        textFieldValue.setPreferredSize(new Dimension(50, 24));\n        textFieldValue.setHorizontalAlignment(SwingConstants.CENTER);\n        textFieldValue.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    int value = Parser.parseInt(textFieldValue.getText(), sliderValue.getValue());\n                    value = Math.max(0, Math.min(360, value));\n                    textFieldValue.setText(String.valueOf(value));\n                    sliderValue.setValue(value);\n                }\n            }\n        });\n        panel.add(textFieldValue);\n\n        sliderValue.setFocusable(false);\n        sliderValue.addChangeListener(e -> {\n            textFieldValue.setText(String.valueOf(sliderValue.getValue()));\n        });\n        sliderValue.addPropertyChangeListener(\"enabled\", evt ->\n        {\n            textFieldValue.setEnabled(sliderValue.isEnabled());\n        });\n        panel.add(sliderValue);\n\n        layout.putConstraint(SpringLayout.WEST, labelKey, 5, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, labelKey, 0, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.EAST, textFieldValue, -5, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.NORTH, textFieldValue, 0, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, sliderValue, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, sliderValue, 5, SpringLayout.SOUTH, textFieldValue);\n        layout.putConstraint(SpringLayout.EAST, sliderValue, 0, SpringLayout.EAST, panel);\n\n        parent.add(panel);\n\n        return sliderValue;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/ElementExtraPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class ElementExtraPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JRadioButton btnShade;\n\n    public ElementExtraPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(1, 2));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Extras</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 50));\n        this.initComponents();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnShade = ComponentUtil.createRadioButton(\"Shade\", \"<html>Determines if shadows should be rendered<br>Default: On</html>\");\n        btnShade.setBackground(ModelCreator.BACKGROUND);\n        btnShade.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.setShade(btnShade.isSelected());\n                StateManager.pushState(manager);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        add(btnShade);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            btnShade.setEnabled(true);\n            btnShade.setSelected(cube.isShaded());\n        }\n        else\n        {\n            btnShade.setEnabled(false);\n            btnShade.setSelected(false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/FaceExtrasPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.Settings;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class FaceExtrasPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JPanel horizontalBox;\n    private JRadioButton boxCullFace;\n    private JRadioButton boxFill;\n    private JRadioButton boxEnabled;\n    private JRadioButton boxAutoUV;\n    private JCheckBox checkBoxTintIndex;\n    private JSpinner tintIndexSpinner;\n\n\n    public FaceExtrasPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new BorderLayout(0, 5));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Extras</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 120));\n        this.initComponents();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        horizontalBox = new JPanel(new GridLayout(3, 2));\n        boxCullFace = ComponentUtil.createRadioButton(\"Cullface\", \"<html>Should render face is another block is adjacent<br>Default: Off</html>\");\n        boxCullFace.setBackground(ModelCreator.BACKGROUND);\n        boxCullFace.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().setCullface(boxCullFace.isSelected());\n                StateManager.pushState(manager);\n            }\n        });\n        boxFill = ComponentUtil.createRadioButton(\"Fill\", \"<html>Makes the texture fill the face<br>Default: Off</html>\");\n        boxFill.setBackground(ModelCreator.BACKGROUND);\n        boxFill.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().fitTexture(boxFill.isSelected());\n                StateManager.pushState(manager);\n            }\n        });\n        boxEnabled = ComponentUtil.createRadioButton(\"Enable\", \"<html>Determines if face should be rendered<br>Default: On</html>\");\n        boxEnabled.setBackground(ModelCreator.BACKGROUND);\n        boxEnabled.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().setEnabled(boxEnabled.isSelected());\n                StateManager.pushState(manager);\n            }\n        });\n        boxAutoUV = ComponentUtil.createRadioButton(\"Auto UV\", \"<html>Determines if UV end coordinates should be set based on element size<br>Default: On</html>\");\n        boxAutoUV.setBackground(ModelCreator.BACKGROUND);\n        boxAutoUV.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().setAutoUVEnabled(boxAutoUV.isSelected());\n                selectedElement.getSelectedFace().updateEndUV();\n                manager.updateValues();\n                StateManager.pushState(manager);\n            }\n        });\n        horizontalBox.add(boxCullFace);\n        horizontalBox.add(boxFill);\n        horizontalBox.add(boxEnabled);\n        horizontalBox.add(boxAutoUV);\n\n        checkBoxTintIndex = ComponentUtil.createCheckBox(\"Tint Index\", \"The tint index to apply to this face. Used in gam\", false);\n        checkBoxTintIndex.setBackground(ModelCreator.BACKGROUND);\n        checkBoxTintIndex.setEnabled(false);\n        horizontalBox.add(checkBoxTintIndex);\n\n        SpinnerNumberModel numberModel = new SpinnerNumberModel();\n        numberModel.setMinimum(0);\n        tintIndexSpinner = new JSpinner(numberModel);\n        tintIndexSpinner.setBackground(ModelCreator.BACKGROUND);\n        tintIndexSpinner.setEnabled(false);\n        tintIndexSpinner.setPreferredSize(new Dimension(75, 24));\n        tintIndexSpinner.setValue(0);\n        tintIndexSpinner.addChangeListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().setTintIndex((int) tintIndexSpinner.getValue());\n                StateManager.pushState(manager);\n            }\n        });\n        horizontalBox.add(tintIndexSpinner);\n\n        checkBoxTintIndex.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                boolean selected = checkBoxTintIndex.isSelected();\n                selectedElement.getSelectedFace().setTintIndexEnabled(selected);\n                tintIndexSpinner.setEnabled(selected);\n                StateManager.pushState(manager);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        add(horizontalBox, BorderLayout.NORTH);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            boxCullFace.setEnabled(true);\n            boxCullFace.setSelected(cube.getSelectedFace().isCullfaced());\n            boxFill.setEnabled(true);\n            boxFill.setSelected(cube.getSelectedFace().shouldFitTexture());\n            boxEnabled.setEnabled(true);\n            boxEnabled.setSelected(cube.getSelectedFace().isEnabled());\n            boxAutoUV.setEnabled(true);\n            boxAutoUV.setSelected(cube.getSelectedFace().isAutoUVEnabled());\n            checkBoxTintIndex.setEnabled(true);\n            checkBoxTintIndex.setSelected(cube.getSelectedFace().isTintIndexEnabled());\n            if(cube.getSelectedFace().isTintIndexEnabled())\n            {\n                tintIndexSpinner.setEnabled(true);\n                tintIndexSpinner.setValue(cube.getSelectedFace().getTintIndex());\n            }\n            else\n            {\n                tintIndexSpinner.setEnabled(false);\n            }\n        }\n        else\n        {\n            boxCullFace.setEnabled(false);\n            boxCullFace.setSelected(false);\n            boxFill.setEnabled(false);\n            boxFill.setSelected(false);\n            boxEnabled.setEnabled(false);\n            boxEnabled.setSelected(false);\n            boxAutoUV.setEnabled(false);\n            boxAutoUV.setSelected(false);\n            checkBoxTintIndex.setEnabled(false);\n            checkBoxTintIndex.setSelected(false);\n            tintIndexSpinner.setEnabled(false);\n            tintIndexSpinner.setValue(0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/GlobalPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class GlobalPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JRadioButton ambientOcc;\n    private JButton btnParticle;\n\n    public GlobalPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(2, 1, 0, 5));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Global Properties</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 80));\n        this.initComponents();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        ambientOcc = ComponentUtil.createRadioButton(\"Ambient Occlusion\", \"Determine the light for each element\");\n        ambientOcc.setBackground(ModelCreator.BACKGROUND);\n        ambientOcc.setSelected(true);\n        ambientOcc.addActionListener(a ->\n        {\n            manager.setAmbientOcc(ambientOcc.isSelected());\n            StateManager.pushState(manager);\n        });\n\n        btnParticle = new JButton(\"Particle\");\n        btnParticle.setIcon(Icons.texture);\n        btnParticle.addActionListener(a ->\n        {\n            TextureEntry entry = TextureManager.display(((SidebarPanel) manager).getCreator(), manager, Dialog.ModalityType.APPLICATION_MODAL);\n            if(entry != null)\n            {\n                manager.setParticle(entry);\n                btnParticle.setText(\"#\" + entry.getKey());\n                StateManager.pushState(manager);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        add(ambientOcc);\n        add(btnParticle);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        ambientOcc.setSelected(manager.getAmbientOcc());\n        if(manager.getParticle() == null)\n        {\n            btnParticle.setText(\"Particle\");\n        }\n        else\n        {\n            btnParticle.setText(\"#\" + manager.getParticle().getKey());\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/IElementUpdater.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.element.Element;\n\npublic interface IElementUpdater\n{\n    void updateValues(Element cube);\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/OriginPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.util.Parser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\n\npublic class OriginPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JButton btnPlusX;\n    private JButton btnPlusY;\n    private JButton btnPlusZ;\n    private JTextField xOriginField;\n    private JTextField yOriginField;\n    private JTextField zOriginField;\n    private JButton btnNegX;\n    private JButton btnNegY;\n    private JButton btnNegZ;\n\n    public OriginPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(3, 3, 4, 4));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Origin</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 124));\n        this.initComponents();\n        this.initProperties();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnPlusX = new JButton(Icons.arrow_up);\n        btnPlusY = new JButton(Icons.arrow_up);\n        btnPlusZ = new JButton(Icons.arrow_up);\n        xOriginField = new JTextField();\n        yOriginField = new JTextField();\n        zOriginField = new JTextField();\n        btnNegX = new JButton(Icons.arrow_down);\n        btnNegY = new JButton(Icons.arrow_down);\n        btnNegZ = new JButton(Icons.arrow_down);\n    }\n\n    private void initProperties()\n    {\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 20);\n        SidebarPanel.initIncrementableField(xOriginField, defaultFont);\n        xOriginField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setOriginX((Parser.parseDouble(xOriginField.getText(), selectedElement.getOriginX())));\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        xOriginField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setOriginX((Parser.parseDouble(xOriginField.getText(), selectedElement.getOriginX())));\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(yOriginField, defaultFont);\n        yOriginField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setOriginY((Parser.parseDouble(yOriginField.getText(), selectedElement.getOriginY())));\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        yOriginField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setOriginY((Parser.parseDouble(yOriginField.getText(), selectedElement.getOriginY())));\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(zOriginField, defaultFont);\n        zOriginField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setOriginZ((Parser.parseDouble(zOriginField.getText(), selectedElement.getOriginZ())));\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        zOriginField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setOriginZ((Parser.parseDouble(zOriginField.getText(), selectedElement.getOriginZ())));\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusX, defaultFont, \"X origin\", true);\n        btnPlusX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginX(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginX(1.0);\n                }\n                xOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginX()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_X);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusY, defaultFont, \"Y origin\", true);\n        btnPlusY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginY(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginY(1.0);\n                }\n                yOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginY()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusZ, defaultFont, \"Z origin\", true);\n        btnPlusZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginZ(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginZ(1.0);\n                }\n                zOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginZ()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_Z);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegX, defaultFont, \"X origin\", false);\n        btnNegX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginX(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginX(-1.0);\n                }\n                xOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginX()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_Z);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegY, defaultFont, \"Y origin\", false);\n        btnNegY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginY(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginY(-1.0);\n                }\n                yOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginY()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegZ, defaultFont, \"Z origin\", false);\n        btnNegZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addOriginZ(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addOriginZ(-1.0);\n                }\n                zOriginField.setText(Exporter.FORMAT.format(selectedElement.getOriginZ()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.ORIGIN_Z);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        this.add(btnPlusX);\n        this.add(btnPlusY);\n        this.add(btnPlusZ);\n        this.add(xOriginField);\n        this.add(yOriginField);\n        this.add(zOriginField);\n        this.add(btnNegX);\n        this.add(btnNegY);\n        this.add(btnNegZ);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            xOriginField.setEnabled(true);\n            yOriginField.setEnabled(true);\n            zOriginField.setEnabled(true);\n            xOriginField.setText(Exporter.FORMAT.format(cube.getOriginX()));\n            yOriginField.setText(Exporter.FORMAT.format(cube.getOriginY()));\n            zOriginField.setText(Exporter.FORMAT.format(cube.getOriginZ()));\n        }\n        else\n        {\n            xOriginField.setEnabled(false);\n            yOriginField.setEnabled(false);\n            zOriginField.setEnabled(false);\n            xOriginField.setText(\"\");\n            yOriginField.setText(\"\");\n            zOriginField.setText(\"\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/PositionPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.util.Parser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\n\npublic class PositionPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JButton btnPlusX;\n    private JButton btnPlusY;\n    private JButton btnPlusZ;\n    private JTextField xPositionField;\n    private JTextField yPositionField;\n    private JTextField zPositionField;\n    private JButton btnNegX;\n    private JButton btnNegY;\n    private JButton btnNegZ;\n\n    public PositionPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(3, 3, 4, 4));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Position</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 124));\n        this.setAlignmentX(JPanel.CENTER_ALIGNMENT);\n        this.initComponents();\n        this.initProperties();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnPlusX = new JButton(Icons.arrow_up);\n        btnPlusY = new JButton(Icons.arrow_up);\n        btnPlusZ = new JButton(Icons.arrow_up);\n        xPositionField = new JTextField();\n        yPositionField = new JTextField();\n        zPositionField = new JTextField();\n        btnNegX = new JButton(Icons.arrow_down);\n        btnNegY = new JButton(Icons.arrow_down);\n        btnNegZ = new JButton(Icons.arrow_down);\n    }\n\n    private void initProperties()\n    {\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 20);\n        SidebarPanel.initIncrementableField(xPositionField, defaultFont);\n        xPositionField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setStartX(Parser.parseDouble(xPositionField.getText(), selectedElement.getStartX()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n\n                }\n            }\n        });\n        xPositionField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setStartX(Parser.parseDouble(xPositionField.getText(), selectedElement.getStartX()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(yPositionField, defaultFont);\n        yPositionField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        StateManager.pushState(manager);\n                        selectedElement.setStartY(Parser.parseDouble(yPositionField.getText(), selectedElement.getStartY()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                    }\n\n                }\n            }\n        });\n        yPositionField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    StateManager.pushState(manager);\n                    selectedElement.setStartY(Parser.parseDouble(yPositionField.getText(), selectedElement.getStartY()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(zPositionField, defaultFont);\n        zPositionField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        StateManager.pushState(manager);\n                        selectedElement.setStartZ(Parser.parseDouble(zPositionField.getText(), selectedElement.getStartZ()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                    }\n\n                }\n            }\n        });\n        zPositionField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    StateManager.pushState(manager);\n                    selectedElement.setStartZ(Parser.parseDouble(zPositionField.getText(), selectedElement.getStartZ()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusX, defaultFont, \"X position\", true);\n        btnPlusX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartX(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addStartX(1.0);\n                }\n                xPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartX()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_X);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusY, defaultFont, \"Y position\", true);\n        btnPlusY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartY(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addStartY(1.0);\n                }\n                yPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartY()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusZ, defaultFont, \"Z position\", true);\n        btnPlusZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartZ(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addStartZ(1.0);\n                }\n                zPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartZ()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_Z);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegX, defaultFont, \"X position\", false);\n        btnNegX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartX(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addStartX(-1.0);\n                }\n                xPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartX()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_X);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegY, defaultFont, \"Y position\", false);\n        btnNegY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartY(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addStartY(-1.0);\n                }\n                yPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartY()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegZ, defaultFont, \"Z position\", false);\n        btnNegZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addStartZ(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    selectedElement.addStartZ(-1.0F);\n                }\n                zPositionField.setText(Exporter.FORMAT.format(selectedElement.getStartZ()));\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.POS_Z);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        this.add(btnPlusX);\n        this.add(btnPlusY);\n        this.add(btnPlusZ);\n        this.add(xPositionField);\n        this.add(yPositionField);\n        this.add(zPositionField);\n        this.add(btnNegX);\n        this.add(btnNegY);\n        this.add(btnNegZ);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            xPositionField.setEnabled(true);\n            yPositionField.setEnabled(true);\n            zPositionField.setEnabled(true);\n            xPositionField.setText(Exporter.FORMAT.format(cube.getStartX()));\n            yPositionField.setText(Exporter.FORMAT.format(cube.getStartY()));\n            zPositionField.setText(Exporter.FORMAT.format(cube.getStartZ()));\n        }\n        else\n        {\n            xPositionField.setEnabled(false);\n            yPositionField.setEnabled(false);\n            zPositionField.setEnabled(false);\n            xPositionField.setText(\"\");\n            yPositionField.setText(\"\");\n            zPositionField.setText(\"\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/SidebarPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.component.*;\nimport com.mrcrayfish.modelcreator.component.Menu;\nimport com.mrcrayfish.modelcreator.display.DisplayProperties;\nimport com.mrcrayfish.modelcreator.element.*;\nimport com.mrcrayfish.modelcreator.panels.tabs.ElementPanel;\nimport com.mrcrayfish.modelcreator.panels.tabs.FacePanel;\nimport com.mrcrayfish.modelcreator.panels.tabs.RotationPanel;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.FocusAdapter;\nimport java.awt.event.FocusEvent;\nimport java.awt.event.KeyAdapter;\nimport java.awt.event.KeyEvent;\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class SidebarPanel extends JPanel implements ElementManager\n{\n    private ModelCreator creator;\n\n    // Swing Variables\n    private SpringLayout layout;\n    private DefaultListModel<ElementCellEntry> model = new DefaultListModel<>();\n    private JList<ElementCellEntry> list = new JElementList();\n    private JScrollPane scrollPane;\n    private JPanel btnContainer;\n    private JButton btnAdd = new JButton();\n    private JButton btnRemove = new JButton();\n    private JButton btnDuplicate = new JButton();\n    private JTextField name = new JTextField();\n    private CuboidTabbedPane tabbedPane = new CuboidTabbedPane(this);\n\n    private TextureEntry particle = null;\n    private boolean ambientOcc = true;\n\n    private DisplayProperties properties = new DisplayProperties(DisplayProperties.MODEL_CREATOR_BLOCK);\n\n    public SidebarPanel(ModelCreator creator)\n    {\n        this.creator = creator;\n        this.setLayout(layout = new SpringLayout());\n        this.setPreferredSize(new Dimension(200, 760));\n        this.initComponents();\n        this.setLayoutConstaints();\n    }\n\n    private void initComponents()\n    {\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 14);\n\n        btnContainer = new JPanel(new GridLayout(1, 3, 4, 0));\n        btnContainer.setPreferredSize(new Dimension(190, 30));\n\n        btnAdd.setIcon(Icons.cube);\n        btnAdd.setToolTipText(\"New Element\");\n        btnAdd.addActionListener(e -> this.newElement());\n        btnAdd.setPreferredSize(new Dimension(30, 30));\n        btnContainer.add(btnAdd);\n\n        btnRemove.setIcon(Icons.bin);\n        btnRemove.setToolTipText(\"Remove Element\");\n        btnRemove.addActionListener(e -> this.deleteElement());\n        btnRemove.setPreferredSize(new Dimension(30, 30));\n        btnContainer.add(btnRemove);\n\n        btnDuplicate.setIcon(Icons.copy);\n        btnDuplicate.setToolTipText(\"Duplicate Element\");\n        btnDuplicate.addActionListener(e ->\n        {\n            int selected = list.getSelectedIndex();\n            if(selected != -1)\n            {\n                model.addElement(new ElementCellEntry(new Element(model.getElementAt(selected).getElement())));\n                list.setSelectedIndex(model.getSize() - 1);\n                StateManager.pushState(creator.getElementManager());\n            }\n        });\n        btnDuplicate.setFont(defaultFont);\n        btnDuplicate.setPreferredSize(new Dimension(30, 30));\n        btnContainer.add(btnDuplicate);\n        add(btnContainer);\n\n        name.setPreferredSize(new Dimension(190, 30));\n        name.setToolTipText(\"Element Name\");\n        name.setEnabled(false);\n        name.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    updateName();\n                }\n            }\n        });\n        name.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                updateName();\n            }\n        });\n        add(name);\n\n        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);\n        list.setFixedCellHeight(26);\n        list.setModel(model);\n        list.addListSelectionListener(e ->\n        {\n            Element selectedElement = getSelectedElement();\n            if(selectedElement != null)\n            {\n                tabbedPane.updateValues();\n                name.setEnabled(true);\n                name.setText(selectedElement.getName());\n                list.ensureIndexIsVisible(list.getSelectedIndex());\n            }\n        });\n        list.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if (e.getKeyCode() == KeyEvent.VK_DELETE)\n                {\n                    deleteElement();\n                    return;\n                }\n                boolean home = e.getKeyCode() == KeyEvent.VK_HOME;\n                if (home || e.getKeyCode() == KeyEvent.VK_END)\n                    setSelectedElement(home ? 0 : model.getSize() - 1);\n            }\n        });\n        list.setCellRenderer(new ElementCellRenderer());\n\n        scrollPane = new JScrollPane(list);\n        scrollPane.setPreferredSize(new Dimension(190, 170));\n        add(scrollPane);\n\n        tabbedPane.setBackground(new Color(127, 132, 145));\n        tabbedPane.setForeground(Color.WHITE);\n        tabbedPane.add(\"Element\", new ElementPanel(this));\n        tabbedPane.add(\"Rotation\", new RotationPanel(this));\n        tabbedPane.add(\"Faces\", new FacePanel(this));\n        tabbedPane.setPreferredSize(new Dimension(190, 500));\n        tabbedPane.setTabPlacement(JTabbedPane.TOP);\n        tabbedPane.addChangeListener(c ->\n        {\n            if(tabbedPane.getSelectedIndex() == 2)\n            {\n                creator.setSidebar(ModelCreator.uvSidebar);\n                ModelCreator.isUVSidebarOpen = true;\n            }\n            else\n            {\n                creator.setSidebar(null);\n                ModelCreator.isUVSidebarOpen = false;\n            }\n        });\n        add(tabbedPane);\n    }\n\n    public static void initIncrementButton(JButton button, Font defaultFont, String subject, boolean increase)\n    {\n        button.setPreferredSize(new Dimension(62, 30));\n        button.setFont(defaultFont);\n        button.setToolTipText(String.format(\"<html>%screases the %s.<br><b>Hold shift for decimals</b></html>\", increase ? \"In\" : \"De\", subject));\n    }\n\n    public static void initIncrementableField(JTextField field, Font defaultFont)\n    {\n        field.setSize(new Dimension(62, 30));\n        field.setFont(defaultFont);\n        field.setHorizontalAlignment(JTextField.CENTER);\n    }\n\n    private void setLayoutConstaints()\n    {\n        layout.putConstraint(SpringLayout.NORTH, name, 212, SpringLayout.NORTH, this);\n        layout.putConstraint(SpringLayout.NORTH, btnContainer, 176, SpringLayout.NORTH, this);\n        layout.putConstraint(SpringLayout.NORTH, tabbedPane, 250, SpringLayout.NORTH, this);\n    }\n\n    public JList<ElementCellEntry> getList()\n    {\n        return list;\n    }\n\n    @Override\n    public Element getSelectedElement()\n    {\n        int i = list.getSelectedIndex();\n        if(model.getSize() > 0 && i >= 0 && i < model.getSize())\n        {\n            return model.getElementAt(i).getElement();\n        }\n        return null;\n    }\n\n    public ElementCellEntry getSelectedElementEntry()\n    {\n        int i = list.getSelectedIndex();\n        if(model.getSize() > 0 && i >= 0 && i < model.getSize())\n        {\n            return model.getElementAt(i);\n        }\n        return null;\n    }\n\n    @Override\n    public void setSelectedElement(int pos)\n    {\n        if(pos < model.size())\n        {\n            if(pos >= 0)\n            {\n                list.setSelectedIndex(pos);\n            }\n            else\n            {\n                list.clearSelection();\n            }\n            updateValues();\n        }\n    }\n\n    @Override\n    public List<Element> getAllElements()\n    {\n        List<Element> list = new ArrayList<>();\n        for(int i = 0; i < model.size(); i++)\n        {\n            list.add(model.getElementAt(i).getElement());\n        }\n        return list;\n    }\n\n    @Override\n    public Element getElement(int index)\n    {\n        //TODO null pointer exception\n        return model.getElementAt(index).getElement();\n    }\n\n    @Override\n    public int getElementCount()\n    {\n        return model.size();\n    }\n\n    @Override\n    public void updateName()\n    {\n        String newName = name.getText();\n        if(newName.isEmpty())\n        {\n            newName = \"Cuboid\";\n        }\n        Element selectedElement = getSelectedElement();\n        if(selectedElement != null)\n        {\n            selectedElement.setName(newName);\n            name.setText(newName);\n            list.repaint();\n            StateManager.pushState(creator.getElementManager());\n        }\n    }\n\n    @Override\n    public void updateValues()\n    {\n        tabbedPane.updateValues();\n    }\n\n    public ModelCreator getCreator()\n    {\n        return creator;\n    }\n\n    @Override\n    public boolean getAmbientOcc()\n    {\n        return ambientOcc;\n    }\n\n    @Override\n    public void setAmbientOcc(boolean occ)\n    {\n        ambientOcc = occ;\n    }\n\n    @Override\n    public void clearElements()\n    {\n        model.clear();\n    }\n\n    @Override\n    public void addElement(Element e)\n    {\n        model.addElement(new ElementCellEntry(e));\n    }\n\n    @Override\n    public void setParticle(TextureEntry particle)\n    {\n        this.particle = particle;\n    }\n\n    @Override\n    public TextureEntry getParticle()\n    {\n        return particle;\n    }\n\n    @Override\n    public void reset()\n    {\n        this.clearElements();\n        ambientOcc = true;\n        particle = null;\n    }\n\n    @Override\n    public void restoreState(ElementManagerState state)\n    {\n        this.reset();\n        for(Element element : state.getElements())\n        {\n            this.model.addElement(new ElementCellEntry(new Element(element)));\n        }\n        this.setSelectedElement(state.getSelectedIndex());\n        this.ambientOcc = state.isAmbientOcclusion();\n        this.particle = state.getParticleTexture();\n        this.updateValues();\n    }\n\n    @Override\n    public void setDisplayProperties(DisplayProperties properties)\n    {\n        this.properties = new DisplayProperties(properties);\n    }\n\n    @Override\n    public DisplayProperties getDisplayProperties()\n    {\n        return properties;\n    }\n\n    public void newElement()\n    {\n        model.addElement(new ElementCellEntry(new Element(1, 1, 1)));\n        list.setSelectedIndex(model.size() - 1);\n        StateManager.pushState(creator.getElementManager());\n    }\n\n    public void deleteElement()\n    {\n        int selected = list.getSelectedIndex();\n        if(selected != -1)\n        {\n            model.remove(selected);\n            name.setText(\"\");\n            name.setEnabled(false);\n            tabbedPane.updateValues();\n            if(selected >= list.getModel().getSize())\n            {\n                list.setSelectedIndex(list.getModel().getSize() - 1);\n            }\n            else\n            {\n                list.setSelectedIndex(selected);\n            }\n            StateManager.pushState(creator.getElementManager());\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/SizePanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.util.Parser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\n\npublic class SizePanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JButton btnPlusX;\n    private JButton btnPlusY;\n    private JButton btnPlusZ;\n    private JTextField xSizeField;\n    private JTextField ySizeField;\n    private JTextField zSizeField;\n    private JButton btnNegX;\n    private JButton btnNegY;\n    private JButton btnNegZ;\n\n    public SizePanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(3, 3, 4, 4));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Size</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 124));\n        this.initComponents();\n        this.initProperties();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnPlusX = new JButton(Icons.arrow_up);\n        btnPlusY = new JButton(Icons.arrow_up);\n        btnPlusZ = new JButton(Icons.arrow_up);\n        xSizeField = new JTextField();\n        ySizeField = new JTextField();\n        zSizeField = new JTextField();\n        btnNegX = new JButton(Icons.arrow_down);\n        btnNegY = new JButton(Icons.arrow_down);\n        btnNegZ = new JButton(Icons.arrow_down);\n    }\n\n    private void initProperties()\n    {\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 20);\n        SidebarPanel.initIncrementableField(xSizeField, defaultFont);\n        xSizeField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setWidth(Parser.parseDouble(xSizeField.getText(), selectedElement.getWidth()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n\n                }\n            }\n        });\n        xSizeField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setWidth(Parser.parseDouble(xSizeField.getText(), selectedElement.getWidth()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(ySizeField, defaultFont);\n        ySizeField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setHeight(Parser.parseDouble(ySizeField.getText(), selectedElement.getHeight()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n\n                }\n            }\n        });\n        ySizeField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setHeight(Parser.parseDouble(ySizeField.getText(), selectedElement.getHeight()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(zSizeField, defaultFont);\n        zSizeField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        selectedElement.setDepth(Parser.parseDouble(zSizeField.getText(), selectedElement.getDepth()));\n                        selectedElement.updateEndUVs();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n\n                }\n            }\n        });\n        zSizeField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setDepth(Parser.parseDouble(zSizeField.getText(), selectedElement.getDepth()));\n                    selectedElement.updateEndUVs();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusX, defaultFont, \"width\", true);\n        btnPlusX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addWidth(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addWidth(1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_X);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusY, defaultFont, \"height\", true);\n        btnPlusY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addHeight(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addHeight(1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusZ, defaultFont, \"depth\", true);\n        btnPlusZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.addDepth(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    selectedElement.addDepth(1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_Z);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegX, defaultFont, \"width\", false);\n        btnNegX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.setWidth(Math.max(0.0, selectedElement.getWidth() - (ctrl == 0 ? 0.1 : 0.01)));\n                }\n                else\n                {\n                    selectedElement.setWidth(Math.max(0.0, selectedElement.getWidth() - 1.0));\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_X);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegY, defaultFont, \"height\", false);\n        btnNegY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.setHeight(Math.max(0.0, selectedElement.getHeight() - (ctrl == 0 ? 0.1 : 0.01)));\n                }\n                else\n                {\n                    selectedElement.setHeight(Math.max(0.0, selectedElement.getHeight() - 1.0));\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_Y);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegZ, defaultFont, \"depth\", false);\n        btnNegZ.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    selectedElement.setDepth(Math.max(0.0, selectedElement.getDepth() - (ctrl == 0 ? 0.1 : 0.01)));\n                }\n                else\n                {\n                    selectedElement.setDepth(Math.max(0.0, selectedElement.getDepth() - 1.0));\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.SIZE_Z);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        this.add(btnPlusX);\n        this.add(btnPlusY);\n        this.add(btnPlusZ);\n        this.add(xSizeField);\n        this.add(ySizeField);\n        this.add(zSizeField);\n        this.add(btnNegX);\n        this.add(btnNegY);\n        this.add(btnNegZ);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            xSizeField.setEnabled(true);\n            ySizeField.setEnabled(true);\n            zSizeField.setEnabled(true);\n            xSizeField.setText(Exporter.FORMAT.format(cube.getWidth()));\n            ySizeField.setText(Exporter.FORMAT.format(cube.getHeight()));\n            zSizeField.setText(Exporter.FORMAT.format(cube.getDepth()));\n        }\n        else\n        {\n            xSizeField.setEnabled(false);\n            ySizeField.setEnabled(false);\n            zSizeField.setEnabled(false);\n            xSizeField.setText(\"\");\n            ySizeField.setText(\"\");\n            zSizeField.setText(\"\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/TexturePanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.texture.Clipboard;\nimport com.mrcrayfish.modelcreator.texture.TextureEntry;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.InputEvent;\n\npublic class TexturePanel extends JPanel\n{\n    private ElementManager manager;\n\n    private JButton btnSelect;\n    private JButton btnClear;\n    private JButton btnCopy;\n    private JButton btnPaste;\n\n    public TexturePanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(2, 2, 4, 4));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Texture</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 80));\n        this.initComponents();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnSelect = new JButton(\"Select...\");\n        btnSelect.setIcon(Icons.texture);\n        btnSelect.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                TextureEntry entry = TextureManager.display(((SidebarPanel) manager).getCreator(), manager, Dialog.ModalityType.APPLICATION_MODAL);\n                if(entry != null)\n                {\n                    selectedElement.getSelectedFace().setTexture(entry);\n                    StateManager.pushState(manager);\n                }\n            }\n        });\n        btnSelect.setToolTipText(\"Opens the Texture Manager\");\n\n        btnClear = new JButton(\"Clear\");\n        btnClear.setIcon(Icons.clear_texture);\n        btnClear.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) == 1)\n                {\n                    selectedElement.setAllTextures(null);\n                }\n                else\n                {\n                    selectedElement.getSelectedFace().setTexture(null);\n                }\n                StateManager.pushState(manager);\n            }\n        });\n        btnClear.setToolTipText(\"<html>Clears the texture from this face.<br><b>Hold shift to clear all faces</b></html>\");\n\n        btnCopy = new JButton(\"Copy\");\n        btnCopy.setIcon(Icons.copy_small);\n        btnCopy.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                Clipboard.copyTexture(face);\n            }\n        });\n        btnCopy.setToolTipText(\"Copies the texture on this face to clipboard\");\n\n        btnPaste = new JButton(\"Paste\");\n        btnPaste.setIcon(Icons.clipboard_texture);\n        btnPaste.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                TextureEntry entry = Clipboard.getTexture();\n                if(entry != null)\n                {\n                    if((e.getModifiers() & InputEvent.SHIFT_MASK) == 1)\n                    {\n                        selectedElement.setAllTextures(entry);\n                    }\n                    else\n                    {\n                        Face face = selectedElement.getSelectedFace();\n                        face.setTexture(entry);\n                    }\n                    StateManager.pushState(manager);\n                }\n            }\n        });\n        btnPaste.setToolTipText(\"<html>Pastes the clipboard texture to this face.<br><b>Hold shift to paste to all faces</b></html>\");\n    }\n\n    private void addComponents()\n    {\n        this.add(btnSelect);\n        this.add(btnClear);\n        this.add(btnCopy);\n        this.add(btnPaste);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/UVPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels;\n\nimport com.mrcrayfish.modelcreator.*;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.util.Parser;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.*;\n\npublic class UVPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n    private JButton btnPlusX;\n    private JButton btnPlusY;\n    private JTextField xStartField;\n    private JTextField yStartField;\n    private JButton btnNegX;\n    private JButton btnNegY;\n\n    private JButton btnPlusXEnd;\n    private JButton btnPlusYEnd;\n    private JTextField xEndField;\n    private JTextField yEndField;\n    private JButton btnNegXEnd;\n    private JButton btnNegYEnd;\n\n    public UVPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new GridLayout(3, 4, 4, 4));\n        this.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>UV</b></html>\"));\n        this.setMaximumSize(new Dimension(186, 124));\n        this.initComponents();\n        this.initProperties();\n        this.addComponents();\n    }\n\n    private void initComponents()\n    {\n        btnPlusX = new JButton(Icons.arrow_up);\n        btnPlusY = new JButton(Icons.arrow_up);\n        xStartField = new JTextField();\n        yStartField = new JTextField();\n        btnNegX = new JButton(Icons.arrow_down);\n        btnNegY = new JButton(Icons.arrow_down);\n\n        btnPlusXEnd = new JButton(Icons.arrow_up);\n        btnPlusYEnd = new JButton(Icons.arrow_up);\n        xEndField = new JTextField();\n        yEndField = new JTextField();\n        btnNegXEnd = new JButton(Icons.arrow_down);\n        btnNegYEnd = new JButton(Icons.arrow_down);\n    }\n\n    private void initProperties()\n    {\n        Font defaultFont = new Font(\"SansSerif\", Font.BOLD, 20);\n        SidebarPanel.initIncrementableField(xStartField, defaultFont);\n        xStartField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        Face face = selectedElement.getSelectedFace();\n                        face.setStartU(Parser.parseDouble(xStartField.getText(), face.getStartU()));\n                        face.updateEndUV();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n\n                }\n            }\n        });\n        xStartField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    Face face = selectedElement.getSelectedFace();\n                    face.setStartU(Parser.parseDouble(xStartField.getText(), face.getStartU()));\n                    face.updateEndUV();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(yStartField, defaultFont);\n        yStartField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        Face face = selectedElement.getSelectedFace();\n                        face.setStartV(Parser.parseDouble(yStartField.getText(), face.getStartV()));\n                        face.updateEndUV();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        yStartField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    Face face = selectedElement.getSelectedFace();\n                    face.setStartV(Parser.parseDouble(yStartField.getText(), face.getStartV()));\n                    face.updateEndUV();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(xEndField, defaultFont);\n        xEndField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        Face face = selectedElement.getSelectedFace();\n                        face.setEndU(Parser.parseDouble(xEndField.getText(), face.getEndU()));\n                        face.updateEndUV();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        xEndField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    Face face = selectedElement.getSelectedFace();\n                    face.setEndU(Parser.parseDouble(xEndField.getText(), face.getEndU()));\n                    face.updateEndUV();\n                    manager.updateValues();\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementableField(yEndField, defaultFont);\n        yEndField.addKeyListener(new KeyAdapter()\n        {\n            @Override\n            public void keyPressed(KeyEvent e)\n            {\n                if(e.getKeyCode() == KeyEvent.VK_ENTER)\n                {\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        Face face = selectedElement.getSelectedFace();\n                        face.setEndV(Parser.parseDouble(yEndField.getText(), face.getEndV()));\n                        face.updateEndUV();\n                        manager.updateValues();\n                        StateManager.pushState(manager);\n                    }\n                }\n            }\n        });\n        yEndField.addFocusListener(new FocusAdapter()\n        {\n            @Override\n            public void focusLost(FocusEvent e)\n            {\n\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    Face face = selectedElement.getSelectedFace();\n                    face.setEndV(Parser.parseDouble(yEndField.getText(), face.getEndV()));\n                    face.updateEndUV();\n                    manager.updateValues();\n                    StateManager.pushState(manager);\n                }\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusX, defaultFont, \"start U\", true);\n        btnPlusX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureX(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    face.addTextureX(1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.START_U);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusY, defaultFont, \"start V\", true);\n        btnPlusY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureY(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    face.addTextureY(1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.START_V);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegX, defaultFont, \"start U\", false);\n        btnNegX.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureX(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    face.addTextureX(-1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.START_U);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegY, defaultFont, \"start V\", false);\n        btnNegY.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureY(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    face.addTextureY(-1.0);\n                }\n                selectedElement.updateEndUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.START_V);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusXEnd, defaultFont, \"end U\", true);\n        btnPlusXEnd.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureXEnd(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    face.addTextureXEnd(1.0);\n                }\n                selectedElement.updateStartUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.END_U);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnPlusYEnd, defaultFont, \"end V\", true);\n        btnPlusYEnd.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureYEnd(ctrl == 0 ? 0.1 : 0.01);\n                }\n                else\n                {\n                    face.addTextureYEnd(1.0);\n                }\n                selectedElement.updateStartUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.END_V);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegXEnd, defaultFont, \"end U\", false);\n        btnNegXEnd.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureXEnd(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    face.addTextureXEnd(-1.0);\n                }\n                selectedElement.updateStartUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.END_U);\n            }\n        });\n\n        SidebarPanel.initIncrementButton(btnNegYEnd, defaultFont, \"end V\", false);\n        btnNegYEnd.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                Face face = selectedElement.getSelectedFace();\n                int ctrl = e.getModifiers() & InputEvent.CTRL_MASK;\n                if((e.getModifiers() & InputEvent.SHIFT_MASK) > 0 && ctrl >= 0)\n                {\n                    face.addTextureYEnd(ctrl == 0 ? -0.1 : -0.01);\n                }\n                else\n                {\n                    face.addTextureYEnd(-1.0);\n                }\n                selectedElement.updateStartUVs();\n                manager.updateValues();\n                StateManager.pushStateDelayed(manager, PropertyIdentifiers.END_V);\n            }\n        });\n    }\n\n    private void addComponents()\n    {\n        this.add(btnPlusX);\n        this.add(btnPlusY);\n        this.add(btnPlusXEnd);\n        this.add(btnPlusYEnd);\n        this.add(xStartField);\n        this.add(yStartField);\n        this.add(xEndField);\n        this.add(yEndField);\n        this.add(btnNegX);\n        this.add(btnNegY);\n        this.add(btnNegXEnd);\n        this.add(btnNegYEnd);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            xStartField.setEnabled(true);\n            yStartField.setEnabled(true);\n            xEndField.setEnabled(true);\n            yEndField.setEnabled(true);\n            xStartField.setText(Exporter.FORMAT.format(cube.getSelectedFace().getStartU()));\n            yStartField.setText(Exporter.FORMAT.format(cube.getSelectedFace().getStartV()));\n            xEndField.setText(Exporter.FORMAT.format(cube.getSelectedFace().getEndU()));\n            yEndField.setText(Exporter.FORMAT.format(cube.getSelectedFace().getEndV()));\n        }\n        else\n        {\n            xStartField.setEnabled(false);\n            yStartField.setEnabled(false);\n            xEndField.setEnabled(false);\n            yEndField.setEnabled(false);\n            xStartField.setText(\"\");\n            yStartField.setText(\"\");\n            xEndField.setText(\"\");\n            yEndField.setText(\"\");\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/ElementPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.panels.*;\n\nimport javax.swing.*;\nimport java.awt.*;\n\npublic class ElementPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private SizePanel panelSize;\n    private PositionPanel panelPosition;\n    private ElementExtraPanel panelExtras;\n    private GlobalPanel panelGlobal;\n\n    public ElementPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        setBackground(ModelCreator.BACKGROUND);\n        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\n        initComponents();\n        addComponents();\n    }\n\n    private void initComponents()\n    {\n        panelSize = new SizePanel(manager);\n        panelPosition = new PositionPanel(manager);\n        panelExtras = new ElementExtraPanel(manager);\n        panelGlobal = new GlobalPanel(manager);\n    }\n\n    private void addComponents()\n    {\n        add(Box.createRigidArea(new Dimension(188, 5)));\n        add(panelSize);\n        add(Box.createRigidArea(new Dimension(188, 5)));\n        add(panelPosition);\n        add(Box.createRigidArea(new Dimension(188, 5)));\n        add(panelExtras);\n        add(Box.createRigidArea(new Dimension(188, 70)));\n        add(new JSeparator(JSeparator.HORIZONTAL));\n        add(panelGlobal);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        panelSize.updateValues(cube);\n        panelPosition.updateValues(cube);\n        panelExtras.updateValues(cube);\n        panelGlobal.updateValues(cube);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/FacePanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.panels.FaceExtrasPanel;\nimport com.mrcrayfish.modelcreator.panels.IElementUpdater;\nimport com.mrcrayfish.modelcreator.panels.TexturePanel;\nimport com.mrcrayfish.modelcreator.panels.UVPanel;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.util.Hashtable;\n\npublic class FacePanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private JPanel menuPanel;\n    private JComboBox<String> menuList;\n    private UVPanel panelUV;\n    private JPanel sliderPanel;\n    private JSlider rotation;\n    private TexturePanel panelTexture;\n    private FaceExtrasPanel panelProperties;\n\n    private final int ROTATION_MIN = 0;\n    private final int ROTATION_MAX = 3;\n    private final int ROTATION_INIT = 0;\n\n    private DefaultComboBoxModel<String> model;\n\n    public FacePanel(ElementManager manager)\n    {\n        this.manager = manager;\n        setBackground(ModelCreator.BACKGROUND);\n        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\n        initMenu();\n        initComponents();\n        addComponents();\n    }\n\n    private void initMenu()\n    {\n        model = new DefaultComboBoxModel<>();\n        model.addElement(\"<html><div style='padding:5px;color:rgb(255,0,0);'><b>North</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:rgb(0,255,0);'><b>East</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:rgb(0,0,255);'><b>South</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:rgb(255,187,0);'><b>West</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:rgb(0,255,255);'><b>Up</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:rgb(255,0,255);'><b>Down</b></html>\");\n    }\n\n    private void initComponents()\n    {\n        menuPanel = new JPanel(new GridLayout(1, 1));\n        menuPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Side</b></html>\"));\n        menuPanel.setMaximumSize(new Dimension(186, 56));\n        menuPanel.setBackground(ModelCreator.BACKGROUND);\n\n        menuList = new JComboBox<>();\n        menuList.setModel(model);\n        menuList.setToolTipText(\"The face to edit.\");\n        menuList.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.setSelectedFace(menuList.getSelectedIndex());\n                updateValues(selectedElement);\n            }\n        });\n        menuPanel.add(menuList);\n\n        panelTexture = new TexturePanel(manager);\n        panelUV = new UVPanel(manager);\n        panelProperties = new FaceExtrasPanel(manager);\n\n        Hashtable<Integer, JLabel> labelTable = new Hashtable<>();\n        labelTable.put(0, new JLabel(\"0\\u00b0\"));\n        labelTable.put(1, new JLabel(\"90\\u00b0\"));\n        labelTable.put(2, new JLabel(\"180\\u00b0\"));\n        labelTable.put(3, new JLabel(\"270\\u00b0\"));\n\n        sliderPanel = new JPanel(new GridLayout(1, 1));\n        sliderPanel.setBackground(ModelCreator.BACKGROUND);\n        sliderPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Rotation</b></html>\"));\n        rotation = new JSlider(JSlider.HORIZONTAL, ROTATION_MIN, ROTATION_MAX, ROTATION_INIT);\n        rotation.setBackground(ModelCreator.BACKGROUND);\n        rotation.setMajorTickSpacing(4);\n        rotation.setPaintTicks(true);\n        rotation.setPaintLabels(true);\n        rotation.setLabelTable(labelTable);\n        rotation.addChangeListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.getSelectedFace().setRotation(rotation.getValue());\n            }\n        });\n        rotation.addMouseListener(new MouseAdapter()\n        {\n            @Override\n            public void mouseReleased(MouseEvent e)\n            {\n                StateManager.pushState(manager);\n            }\n        });\n        rotation.setToolTipText(\"<html>The rotation of the texture<br>Default: 0\\u00b0</html>\");\n        sliderPanel.setMaximumSize(new Dimension(190, 80));\n        sliderPanel.add(rotation);\n    }\n\n    private void addComponents()\n    {\n        add(Box.createRigidArea(new Dimension(192, 5)));\n        add(menuPanel);\n        add(panelTexture);\n        add(panelUV);\n        add(sliderPanel);\n        add(panelProperties);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        if(cube != null)\n        {\n            menuList.setSelectedItem(model.getElementAt(cube.getSelectedFaceIndex()));\n            menuList.repaint();\n            rotation.setEnabled(true);\n            rotation.setValue(cube.getSelectedFace().getRotation());\n        }\n        else\n        {\n            rotation.setEnabled(false);\n            rotation.setValue(0);\n        }\n        panelUV.updateValues(cube);\n        panelProperties.updateValues(cube);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/panels/tabs/RotationPanel.java",
    "content": "package com.mrcrayfish.modelcreator.panels.tabs;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.StateManager;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.panels.IElementUpdater;\nimport com.mrcrayfish.modelcreator.panels.OriginPanel;\nimport com.mrcrayfish.modelcreator.util.ComponentUtil;\n\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.ItemEvent;\nimport java.awt.event.MouseAdapter;\nimport java.awt.event.MouseEvent;\nimport java.util.Hashtable;\n\npublic class RotationPanel extends JPanel implements IElementUpdater\n{\n    private ElementManager manager;\n\n    private OriginPanel panelOrigin;\n    private JPanel axisPanel;\n    private JComboBox<String> axisList;\n    private JPanel sliderPanel;\n    private JSlider rotation;\n    private JPanel extraPanel;\n    private JRadioButton btnRescale;\n\n    private DefaultComboBoxModel<String> model;\n\n    private final int ROTATION_MIN = -2;\n    private final int ROTATION_MAX = 2;\n    private final int ROTATION_INIT = 0;\n\n    public RotationPanel(ElementManager manager)\n    {\n        this.manager = manager;\n        this.setBackground(ModelCreator.BACKGROUND);\n        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\n        this.initMenu();\n        this.initComponents();\n        this.addComponents();\n    }\n\n    private void initMenu()\n    {\n        model = new DefaultComboBoxModel<>();\n        model.addElement(\"<html><div style='padding:5px;color:red;'><b>X</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:green;'><b>Y</b></html>\");\n        model.addElement(\"<html><div style='padding:5px;color:blue;'><b>Z</b></html>\");\n    }\n\n    private void initComponents()\n    {\n        panelOrigin = new OriginPanel(manager);\n        panelOrigin.setBackground(ModelCreator.BACKGROUND);\n\n        axisPanel = new JPanel(new GridLayout(1, 1));\n        axisPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Axis</b></html>\"));\n        axisPanel.setBackground(ModelCreator.BACKGROUND);\n\n        axisList = new JComboBox<>();\n        axisList.setModel(model);\n        axisList.setToolTipText(\"The axis the element will rotate around\");\n        axisList.addItemListener(e ->\n        {\n            if(e.getStateChange() == ItemEvent.SELECTED)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null && selectedElement.getRotationAxis() != axisList.getSelectedIndex())\n                {\n                    selectedElement.setRotationAxis(axisList.getSelectedIndex());\n                    StateManager.pushState(manager);\n                }\n            }\n        });\n        axisList.setMaximumSize(new Dimension(186, 55));\n        axisPanel.setMaximumSize(new Dimension(186, 55));\n        axisPanel.add(axisList);\n\n        Hashtable<Integer, JLabel> labelTable = new Hashtable<>();\n        labelTable.put(-2, new JLabel(\"-45\\u00b0\"));\n        labelTable.put(-1, new JLabel(\"-22.5\\u00b0\"));\n        labelTable.put(0, new JLabel(\"0\\u00b0\"));\n        labelTable.put(1, new JLabel(\"22.5\\u00b0\"));\n        labelTable.put(2, new JLabel(\"45\\u00b0\"));\n\n        sliderPanel = new JPanel(new GridLayout(1, 1));\n        sliderPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Rotation</b></html>\"));\n        sliderPanel.setBackground(ModelCreator.BACKGROUND);\n\n        rotation = new JSlider(JSlider.HORIZONTAL, ROTATION_MIN, ROTATION_MAX, ROTATION_INIT);\n        rotation.setBackground(ModelCreator.BACKGROUND);\n        rotation.setMajorTickSpacing(1);\n        rotation.setPaintTicks(true);\n        rotation.setPaintLabels(true);\n        rotation.setLabelTable(labelTable);\n        rotation.addChangeListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.setRotation(rotation.getValue() * 22.5D);\n            }\n        });\n        rotation.addMouseListener(new MouseAdapter()\n        {\n            @Override\n            public void mouseReleased(MouseEvent e)\n            {\n                StateManager.pushState(manager);\n            }\n        });\n        rotation.setToolTipText(\"<html>The rotation of the element<br>Default: 0</html>\");\n        sliderPanel.setMaximumSize(new Dimension(190, 80));\n        sliderPanel.add(rotation);\n\n        extraPanel = new JPanel(new GridLayout(1, 2));\n        extraPanel.setBackground(ModelCreator.BACKGROUND);\n        extraPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(ModelCreator.BACKGROUND, 5), \"<html><b>Extras</b></html>\"));\n\n        btnRescale = ComponentUtil.createRadioButton(\"Rescale\", \"<html>Should scale faces across whole block<br>Default: Off<html>\");\n        btnRescale.setBackground(ModelCreator.BACKGROUND);\n        btnRescale.addActionListener(e ->\n        {\n            Element selectedElement = manager.getSelectedElement();\n            if(selectedElement != null)\n            {\n                selectedElement.setRescale(btnRescale.isSelected());\n                StateManager.pushState(manager);\n            }\n        });\n        extraPanel.setMaximumSize(new Dimension(186, 50));\n        extraPanel.add(btnRescale);\n    }\n\n    private void addComponents()\n    {\n        add(Box.createRigidArea(new Dimension(188, 5)));\n        add(panelOrigin);\n        add(axisPanel);\n        add(sliderPanel);\n        add(extraPanel);\n    }\n\n    @Override\n    public void updateValues(Element cube)\n    {\n        panelOrigin.updateValues(cube);\n        if(cube != null)\n        {\n            axisList.setSelectedIndex(cube.getRotationAxis());\n            rotation.setEnabled(true);\n            rotation.setValue((int) (cube.getRotation() / 22.5));\n            btnRescale.setEnabled(true);\n            btnRescale.setSelected(cube.shouldRescale());\n        }\n        else\n        {\n            rotation.setValue(0);\n            rotation.setEnabled(false);\n            btnRescale.setSelected(false);\n            btnRescale.setEnabled(false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/PendingScreenshot.java",
    "content": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport java.io.File;\n\npublic class PendingScreenshot\n{\n    private File file = null;\n    private ScreenshotCallback callback;\n\n    public PendingScreenshot(File file, ScreenshotCallback callback)\n    {\n        this.file = file;\n        this.callback = callback;\n    }\n\n    public File getFile()\n    {\n        return file;\n    }\n\n    public ScreenshotCallback getCallback()\n    {\n        return callback;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/Screenshot.java",
    "content": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport com.mrcrayfish.modelcreator.util.Util;\nimport org.lwjgl.BufferUtils;\nimport org.lwjgl.opengl.GL11;\n\nimport javax.imageio.ImageIO;\nimport javax.swing.*;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.nio.ByteBuffer;\n\npublic class Screenshot\n{\n    public static void getScreenshot(int width, int height, ScreenshotCallback callback)\n    {\n        try\n        {\n            getScreenshot(width, height, callback, File.createTempFile(\"screenshot\", \".png\"));\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static void getScreenshot(int width, int height, ScreenshotCallback callback, File file)\n    {\n        GL11.glReadBuffer(GL11.GL_FRONT);\n        int bpp = 4;\n        ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * bpp);\n        GL11.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);\n\n        try\n        {\n            String format = \"PNG\";\n            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);\n\n            for(int x = 0; x < width; x++)\n            {\n                for(int y = 0; y < height; y++)\n                {\n                    int i = (x + (width * y)) * bpp;\n                    int r = buffer.get(i) & 0xFF;\n                    int g = buffer.get(i + 1) & 0xFF;\n                    int b = buffer.get(i + 2) & 0xFF;\n                    image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);\n                }\n            }\n            ImageIO.write(image, format, file);\n\n            if(callback != null)\n            {\n                callback.callback(file);\n            }\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    private static boolean isUrl(String url)\n    {\n        if(url == null | (url != null && url.equals(\"null\")))\n        {\n            JOptionPane message = new JOptionPane();\n            message.setMessage(\"Failed to upload screenshot. Check your internet connection then try again.\");\n            JDialog dialog = message.createDialog(null, \"Error\");\n            dialog.setLocationRelativeTo(null);\n            dialog.setModal(false);\n            dialog.setVisible(true);\n            return false;\n        }\n        return true;\n    }\n\n    public static void shareToFacebook(String link)\n    {\n        try\n        {\n            if(isUrl(link))\n            {\n                String url = \"https://www.facebook.com/sharer/sharer.php?\";\n                url += \"u=\" + URLEncoder.encode(link, \"UTF-8\");\n                Util.openUrl(url);\n            }\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static void shareToTwitter(String link)\n    {\n        try\n        {\n            if(isUrl(link))\n            {\n                String url = \"https://twitter.com/intent/tweet?\";\n                url += \"text=\" + URLEncoder.encode(\"Check out this awesome model I created with @MrCraayfish's Model Creator\", \"UTF-8\");\n                url += \"&url=\" + URLEncoder.encode(link, \"UTF-8\");\n                Util.openUrl(url);\n            }\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static void shareToReddit(String link)\n    {\n        try\n        {\n            if(isUrl(link))\n            {\n                String url = \"http://www.reddit.com/r/Minecraft/submit?\";\n                url += \"title=\" + URLEncoder.encode(\"[Model] <enter name and description here> (Created using MrCrayfish's Model Creator)\", \"UTF-8\");\n                url += \"&url=\" + URLEncoder.encode(link, \"UTF-8\");\n                Util.openUrl(url);\n            }\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/ScreenshotCallback.java",
    "content": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport java.io.File;\n\npublic interface ScreenshotCallback\n{\n    void callback(File file);\n}"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/screenshot/Uploader.java",
    "content": "package com.mrcrayfish.modelcreator.screenshot;\n\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\nimport com.mrcrayfish.modelcreator.util.StreamUtils;\n\nimport java.io.*;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class Uploader\n{\n    private static final String UPLOAD_URL = \"https://api.imgur.com/3/image\";\n    private static final String CLIENT_ID = \"5cd0235db91ac6e\";\n\n    public static String upload(File file)\n    {\n        HttpURLConnection conn;\n        InputStream responseIn;\n\n        try\n        {\n            conn = (HttpURLConnection) new URL(UPLOAD_URL).openConnection();\n            conn.setDoOutput(true);\n            conn.setRequestProperty(\"Authorization\", \"Client-ID \" + CLIENT_ID);\n\n            OutputStream out = conn.getOutputStream();\n            upload(new FileInputStream(file), out);\n            out.flush();\n            out.close();\n\n            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK)\n            {\n                responseIn = conn.getInputStream();\n                return getImageLink(responseIn);\n            }\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n        return null;\n    }\n\n    private static void upload(InputStream input, OutputStream output) throws IOException\n    {\n        byte[] buffer = new byte[8192];\n        int n;\n        while((n = input.read(buffer)) != -1)\n        {\n            output.write(buffer, 0, n);\n        }\n\n    }\n\n    private static String getImageLink(InputStream input) throws IOException\n    {\n        String json = StreamUtils.convertToString(input);\n\n        JsonParser parser = new JsonParser();\n        JsonElement read = parser.parse(json);\n\n        if(read.isJsonObject())\n        {\n            JsonObject obj = read.getAsJsonObject();\n\n            if(obj.has(\"data\") && obj.get(\"data\").isJsonObject())\n            {\n                JsonObject data = obj.getAsJsonObject(\"data\");\n                return \"http://imgur.com/\" + data.get(\"id\").getAsString();\n            }\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/sidebar/Sidebar.java",
    "content": "package com.mrcrayfish.modelcreator.sidebar;\n\nimport com.mrcrayfish.modelcreator.util.FontManager;\nimport org.newdawn.slick.Color;\n\nimport static org.lwjgl.opengl.GL11.*;\n\npublic abstract class Sidebar\n{\n    private String title;\n\n    public Sidebar(String title)\n    {\n        this.title = title;\n    }\n\n    public void draw(int sidebarWidth, int canvasWidth, int canvasHeight, int frameHeight)\n    {\n        glColor3f(0.866F, 0.866F, 0.894F);\n        glBegin(GL_QUADS);\n        {\n            glVertex2i(0, 0);\n            glVertex2i(sidebarWidth, 0);\n            glVertex2i(sidebarWidth, canvasHeight);\n            glVertex2i(0, canvasHeight);\n        }\n        glEnd();\n\n        drawTitle();\n    }\n\n    private void drawTitle()\n    {\n        glEnable(GL_BLEND);\n        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n        FontManager.BEBAS_NEUE_20.drawString(10, 5, title, new Color(0.5F, 0.5F, 0.6F));\n        glDisable(GL_BLEND);\n    }\n\n    public abstract void handleMouseInput(int button, int mouseX, int mouseY, boolean pressed);\n\n    public abstract void handleInput(int canvasHeight);\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/sidebar/UVSidebar.java",
    "content": "package com.mrcrayfish.modelcreator.sidebar;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport com.mrcrayfish.modelcreator.element.Element;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\nimport com.mrcrayfish.modelcreator.element.Face;\nimport com.mrcrayfish.modelcreator.util.FontManager;\nimport org.lwjgl.input.Mouse;\nimport org.newdawn.slick.Color;\nimport org.newdawn.slick.opengl.TextureImpl;\n\nimport static org.lwjgl.opengl.GL11.*;\n\npublic class UVSidebar extends Sidebar\n{\n    private ElementManager manager;\n\n    private final int LENGTH = 110;\n\n    private final Color BLACK_ALPHA = new Color(0, 0, 0, 0.75F);\n    private final Color BLACK_ALPHA_HOVER = new Color(0, 0, 0, 0.25F);\n    public static final java.awt.Color BACKGROUND = new java.awt.Color(230, 230, 240);\n\n    private int[] startX = {0, 0, 0, 0, 0, 0};\n    private int[] startY = {0, 0, 0, 0, 0, 0};\n\n    private int hoveredFace = -1;\n    private int canvasHeight;\n\n    public UVSidebar(String title, ElementManager manager)\n    {\n        super(title);\n        this.manager = manager;\n    }\n\n    @Override\n    public void draw(int sidebarWidth, int canvasWidth, int canvasHeight, int frameHeight)\n    {\n        super.draw(sidebarWidth, canvasWidth, canvasHeight, frameHeight);\n\n        this.canvasHeight = frameHeight;\n        if(!grabbing)\n        {\n            hoveredFace = getFace(frameHeight, Mouse.getX(), Mouse.getY());\n        }\n\n        glPushMatrix();\n        {\n            glTranslatef(10, 30, 0);\n\n            int count = 0;\n\n            for(int i = 0; i < 6; i++)\n            {\n                glPushMatrix();\n                {\n                    if(30 + i * (LENGTH + 10) + (LENGTH + 10) > canvasHeight)\n                    {\n                        glTranslatef(10 + LENGTH, count * (LENGTH + 10), 0);\n                        startX[i] = 20 + LENGTH;\n                        startY[i] = count * (LENGTH + 10) + 40;\n                        count++;\n                    }\n                    else\n                    {\n                        glTranslatef(0, i * (LENGTH + 10), 0);\n                        startX[i] = 10;\n                        startY[i] = i * (LENGTH + 10) + 40;\n                    }\n\n                    Face[] faces = null;\n                    Element selectedElement = manager.getSelectedElement();\n                    if(selectedElement != null)\n                    {\n                        faces = selectedElement.getAllFaces();\n                    }\n\n                    if(faces != null)\n                    {\n                        glDisable(GL_TEXTURE_2D);\n\n                        int color = ModelCreator.BACKGROUND.getRGB();\n                        float b = (float) (color & 0xFF) / 0xFF;\n                        float g = (float) ((color >>> 8) & 0xFF) / 0xFF;\n                        float r = (float) ((color >>> 16) & 0xFF) / 0xFF;\n                        glColor3f(r * 0.85F, g * 0.85F, b * 0.85F);\n\n                        glBegin(GL_QUADS);\n                        {\n                            glVertex2i(-1, LENGTH + 1);\n                            glVertex2i(LENGTH + 1, LENGTH + 1);\n                            glVertex2i(LENGTH + 1, -1);\n                            glVertex2i(-1, -1);\n                        }\n                        glEnd();\n\n                        b = (float) (color & 0xFF) / 0xFF;\n                        g = (float) ((color >>> 8) & 0xFF) / 0xFF;\n                        r = (float) ((color >>> 16) & 0xFF) / 0xFF;\n                        glColor3f(r, g, b);\n\n                        glBegin(GL_QUADS);\n                        {\n                            glVertex2i(0, LENGTH);\n                            glVertex2i(LENGTH, LENGTH);\n                            glVertex2i(LENGTH, 0);\n                            glVertex2i(0, 0);\n                        }\n                        glEnd();\n\n                        faces[i].bindTexture(0);\n\n                        glBegin(GL_QUADS);\n                        {\n                            if(faces[i].isBinded())\n                            {\n                                glTexCoord2f(0, 1);\n                            }\n                            glVertex2i(0, LENGTH);\n\n                            if(faces[i].isBinded())\n                            {\n                                glTexCoord2f(1, 1);\n                            }\n                            glVertex2i(LENGTH, LENGTH);\n\n                            if(faces[i].isBinded())\n                            {\n                                glTexCoord2f(1, 0);\n                            }\n                            glVertex2i(LENGTH, 0);\n\n                            if(faces[i].isBinded())\n                            {\n                                glTexCoord2f(0, 0);\n                            }\n                            glVertex2i(0, 0);\n                        }\n                        glEnd();\n\n                        TextureImpl.bindNone();\n\n                        glColor3f(1, 1, 1);\n                        glLineWidth(1.25F);\n\n                        glBegin(GL_LINES);\n                        {\n                            glVertex2d(faces[i].getStartU() * (LENGTH / 16D), faces[i].getStartV() * (LENGTH / 16D));\n                            glVertex2d(faces[i].getStartU() * (LENGTH / 16D), faces[i].getEndV() * (LENGTH / 16D));\n\n                            glVertex2d(faces[i].getStartU() * (LENGTH / 16D), faces[i].getEndV() * (LENGTH / 16D));\n                            glVertex2d(faces[i].getEndU() * (LENGTH / 16D), faces[i].getEndV() * (LENGTH / 16D));\n\n                            glVertex2d(faces[i].getEndU() * (LENGTH / 16D), faces[i].getEndV() * (LENGTH / 16D));\n                            glVertex2d(faces[i].getEndU() * (LENGTH / 16D), faces[i].getStartV() * (LENGTH / 16D));\n\n                            glVertex2d(faces[i].getEndU() * (LENGTH / 16D), faces[i].getStartV() * (LENGTH / 16D));\n                            glVertex2d(faces[i].getStartU() * (LENGTH / 16D), faces[i].getStartV() * (LENGTH / 16D));\n\n                        }\n                        glEnd();\n\n                        Color colorText = BLACK_ALPHA;\n                        if(hoveredFace == faces[i].getSide())\n                        {\n                            colorText = BLACK_ALPHA_HOVER;\n                        }\n                        glEnable(GL_BLEND);\n                        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);\n                        FontManager.BEBAS_NEUE_20.drawString(5, 5, Face.getFaceName(i), colorText);\n                        glDisable(GL_BLEND);\n                    }\n                }\n                glPopMatrix();\n            }\n        }\n        glPopMatrix();\n    }\n\n    private int lastMouseX, lastMouseY;\n    private int selected = -1;\n    private boolean grabbing = false;\n\n    @Override\n    public void handleMouseInput(int button, int mouseX, int mouseY, boolean pressed)\n    {\n        if(pressed && (Mouse.isButtonDown(0) || Mouse.isButtonDown(1)))\n        {\n            if(!grabbing && hoveredFace != -1)\n            {\n                this.lastMouseX = mouseX;\n                this.lastMouseY = mouseY;\n                this.grabbing = true;\n                this.hoveredFace = getFace(canvasHeight, Mouse.getX(), Mouse.getY());\n\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    selectedElement.setSelectedFace(this.hoveredFace);\n                    manager.updateValues();\n                }\n            }\n        }\n        else\n        {\n            this.grabbing = false;\n        }\n    }\n\n    @Override\n    public void handleInput(int canvasHeight)\n    {\n        int newMouseX = Mouse.getX();\n        int newMouseY = Mouse.getY();\n\n        if(grabbing)\n        {\n            if(hoveredFace != -1 || selected != -1)\n            {\n                Element selectedElement = manager.getSelectedElement();\n                if(selectedElement != null)\n                {\n                    Face face = selectedElement.getAllFaces()[(selected != -1 ? selected : hoveredFace)];\n\n                    int xMovement = (newMouseX - this.lastMouseX) / 6;\n                    int yMovement = (newMouseY - this.lastMouseY) / 6;\n\n                    if(xMovement != 0 || yMovement != 0)\n                    {\n                        if(Mouse.isButtonDown(0))\n                        {\n                            if((face.getStartU() + xMovement) >= 0.0 && (face.getEndU() + xMovement) <= 16.0)\n                            {\n                                face.moveTextureU(xMovement);\n                            }\n                            if((face.getStartV() - yMovement) >= 0.0 && (face.getEndV() - yMovement) <= 16.0)\n                            {\n                                face.moveTextureV(-yMovement);\n                            }\n                        }\n                        else\n                        {\n                            face.setAutoUVEnabled(false);\n\n                            double uMovement = (face.getEndU() + xMovement);\n                            if(uMovement >= 0 && uMovement <= 16.0)\n                            {\n                                face.addTextureXEnd(xMovement);\n                            }\n                            double vMovement = (face.getEndV() - yMovement);\n                            if(vMovement >= 0 && vMovement <= 16.0)\n                            {\n                                face.addTextureYEnd(-yMovement);\n                            }\n\n                            face.setAutoUVEnabled(false);\n                        }\n                        face.updateEndUV();\n\n                        if(xMovement != 0)\n                        {\n                            this.lastMouseX = newMouseX;\n                        }\n                        if(yMovement != 0)\n                        {\n                            this.lastMouseY = newMouseY;\n                        }\n                    }\n                    manager.updateValues();\n                }\n            }\n        }\n        else\n        {\n            selected = -1;\n        }\n    }\n\n    private int getFace(int canvasHeight, int mouseX, int mouseY)\n    {\n        for(int i = 0; i < 6; i++)\n        {\n            if(mouseX >= startX[i] && mouseX <= startX[i] + LENGTH)\n            {\n                if((canvasHeight - mouseY - 45) >= startY[i] && (canvasHeight - mouseY - 45) <= startY[i] + LENGTH)\n                {\n                    return i;\n                }\n            }\n        }\n        return -1;\n    }\n\n    public int getHoveredFace()\n    {\n        return hoveredFace;\n    }\n\n    public boolean isGrabbing()\n    {\n        return grabbing;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/Clipboard.java",
    "content": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.mrcrayfish.modelcreator.element.Face;\n\npublic class Clipboard\n{\n    // TODO make it so you can copy and paste certain properties. Eg Copy only texture and tint index, not UV.\n    private static TextureEntry entry;\n\n    public static void copyTexture(Face face)\n    {\n        entry = face.getTexture();\n    }\n\n    public static TextureEntry getTexture()\n    {\n        return entry;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureAnimation.java",
    "content": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.google.gson.JsonArray;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonObject;\nimport com.google.gson.JsonParser;\n\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileReader;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class TextureAnimation\n{\n    private int width = 16, height = 16;\n\n    private List<Integer> frames = new ArrayList<>();\n    private Map<Integer, Integer> customTimes = new HashMap<>();\n    private int frametime = 1;\n    private boolean interpolate = false;\n\n    public void setSize(int width, int height)\n    {\n        this.width = width;\n        this.height = height;\n    }\n\n    public int getWidth()\n    {\n        return width;\n    }\n\n    public int getHeight()\n    {\n        return height;\n    }\n\n    public void setFrameTime(int frametime)\n    {\n        this.frametime = frametime;\n    }\n\n    public void setFrames(List<Integer> frameList)\n    {\n        frames = new ArrayList<>();\n        frames.addAll(frameList);\n    }\n\n    public void setCustomTimes(Map<Integer, Integer> times)\n    {\n        customTimes = new HashMap<>();\n        customTimes.putAll(times);\n    }\n\n    public void setInterpolate(boolean interpolate)\n    {\n        this.interpolate = interpolate;\n    }\n\n    public boolean isInterpolated()\n    {\n        return interpolate;\n    }\n\n    public int getFrameCount()\n    {\n        return frames.size();\n    }\n\n    public long getMaxTime()\n    {\n        long maxTime = 0;\n        for(int i = 0; i < frames.size(); i++)\n        {\n            maxTime += getFrameTime(i);\n        }\n        return maxTime;\n    }\n\n    public int getCurrentAnimationFrame()\n    {\n        long maxTime = this.getMaxTime();\n        if(maxTime > 0)\n        {\n            long animTime = System.currentTimeMillis() % maxTime;\n            for(int i = 0; i < frames.size(); i++)\n            {\n                if(animTime < getFrameTime(i))\n                {\n                    return frames.get(i);\n                }\n                animTime -= getFrameTime(i);\n            }\n        }\n        return 0;\n    }\n\n    public int getNextAnimationFrame()\n    {\n        if(frames.size() < 1)\n        {\n            return 0;\n        }\n\n        long maxTime = 0;\n        for(int i = 0; i < frames.size(); i++)\n        {\n            maxTime += getFrameTime(i);\n        }\n\n        long animTime = System.currentTimeMillis() % maxTime;\n\n        for(int i = 0; i < frames.size(); i++)\n        {\n            if(animTime <= getFrameTime(i))\n            {\n                if(i < frames.size() - 1)\n                {\n                    return frames.get(i + 1);\n                }\n                else\n                {\n                    return frames.get(0);\n                }\n            }\n            animTime -= getFrameTime(i);\n        }\n\n        return 0;\n    }\n\n    public long getFrameTime(int frame)\n    {\n        if(customTimes != null && customTimes.containsKey(frame))\n        {\n            return customTimes.get(frame) * 50L;\n        }\n        return frametime * 50L;\n    }\n\n    public double getFrameInterpolation()\n    {\n        long maxTime = 0;\n        for(int i = 0; i < frames.size(); i++)\n        {\n            maxTime += getFrameTime(i);\n        }\n\n        long animTime = System.currentTimeMillis() % maxTime;\n\n        for(int i = 0; i < frames.size(); i++)\n        {\n            if(animTime <= getFrameTime(i))\n            {\n                return animTime / (double) getFrameTime(i);\n            }\n            animTime -= getFrameTime(i);\n        }\n\n        return 0;\n    }\n\n    public int getPasses()\n    {\n        if(isInterpolated() && getFrameCount() > 1)\n        {\n            return 2;\n        }\n        return 1;\n    }\n\n    public static TextureAnimation getAnimationForTexture(File file, int width, int height)\n    {\n        try\n        {\n            file = new File(file.getAbsolutePath() + \".mcmeta\");\n            if(file.exists())\n            {\n                TextureAnimation anim = null;\n                JsonObject animationObj = null;\n                JsonParser parser = new JsonParser();\n                JsonElement read = parser.parse(new FileReader(file));\n\n                if(read.isJsonObject())\n                {\n                    JsonObject mcMeta = read.getAsJsonObject();\n                    if(mcMeta.has(\"animation\") && mcMeta.get(\"animation\").isJsonObject())\n                    {\n                        animationObj = mcMeta.get(\"animation\").getAsJsonObject();\n                        anim = new TextureAnimation();\n                    }\n                }\n\n                if(anim != null && animationObj != null)\n                {\n                    int frametime = 1;\n                    if(animationObj.has(\"frametime\") && animationObj.get(\"frametime\").isJsonPrimitive())\n                    {\n                        frametime = animationObj.get(\"frametime\").getAsInt();\n                    }\n                    anim.setFrameTime(frametime);\n\n                    if(animationObj.has(\"interpolate\") && animationObj.get(\"interpolate\").isJsonPrimitive())\n                    {\n                        boolean interpolate = animationObj.get(\"interpolate\").getAsBoolean();\n                        anim.setInterpolate(interpolate);\n                    }\n\n                    if(animationObj.has(\"frames\") && animationObj.get(\"frames\").isJsonArray())\n                    {\n                        JsonArray frames = animationObj.get(\"frames\").getAsJsonArray();\n                        if(frames.size() > 0)\n                        {\n                            List<Integer> frameList = new ArrayList<>();\n                            Map<Integer, Integer> customTimes = new HashMap<>();\n\n                            for(int i = 0; i < frames.size(); i++)\n                            {\n                                JsonElement frame = frames.get(i);\n\n                                int index = 0;\n                                int time = frametime;\n                                if(frame.isJsonPrimitive())\n                                {\n                                    index = frame.getAsInt();\n                                }\n                                else if(frame.isJsonObject())\n                                {\n                                    JsonObject frameObj = frame.getAsJsonObject();\n\n                                    if(frameObj.has(\"index\") && frameObj.get(\"index\").isJsonPrimitive())\n                                    {\n                                        index = frameObj.get(\"index\").getAsInt();\n                                    }\n                                    if(frameObj.has(\"time\") && frameObj.get(\"time\").isJsonPrimitive())\n                                    {\n                                        time = frameObj.get(\"time\").getAsInt();\n                                    }\n                                }\n\n                                frameList.add(index);\n                                if(time != frametime)\n                                {\n                                    customTimes.put(frameList.size() - 1, time);\n                                }\n                            }\n\n                            anim.setFrames(frameList);\n                            anim.setCustomTimes(customTimes);\n                        }\n                    }\n                    else\n                    {\n                        int columns = width / 16;\n                        List<Integer> frameList = new ArrayList<>();\n                        for(int i = 0; i < (height / 16) * columns; i++)\n                        {\n                            frameList.add(i);\n                        }\n                        anim.setFrames(frameList);\n                    }\n                    return anim;\n                }\n            }\n        }\n        catch(FileNotFoundException e)\n        {\n            e.printStackTrace();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureAtlas.java",
    "content": "package com.mrcrayfish.modelcreator.texture;\n\nimport org.lwjgl.BufferUtils;\nimport org.lwjgl.opengl.GL11;\nimport org.lwjgl.opengl.GL12;\nimport org.newdawn.slick.opengl.Texture;\nimport org.newdawn.slick.util.BufferedImageUtil;\n\nimport javax.imageio.ImageIO;\nimport java.awt.image.BufferedImage;\nimport java.net.URL;\nimport java.nio.ByteBuffer;\n\n/**\n * Author: MrCrayfish\n */\npublic class TextureAtlas\n{\n    private static final int ATLAS_WIDTH = 1024;\n    private static final int ATLAS_HEIGHT = 1024;\n    private static int atlasTextureId;\n\n    public static final Entry GUI_SLOT;\n    public static final Entry FIRST_PERSON_PREVIEW;\n\n    static\n    {\n        GUI_SLOT = new Entry(0, 0, 20, 20);\n        FIRST_PERSON_PREVIEW = new Entry(0, 20, 512, 288);\n    }\n\n    public static void load()\n    {\n        try\n        {\n            URL url = TextureAtlas.class.getClassLoader().getResource(\"atlas.png\");\n            if(url != null)\n            {\n                BufferedImage bufferedImage = ImageIO.read(url);\n                atlasTextureId = loadTexture(bufferedImage);\n            }\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static void bind()\n    {\n        GL11.glBindTexture(GL11.GL_TEXTURE_2D, atlasTextureId);\n    }\n\n    public static class Entry\n    {\n        int u, v;\n        int width, height;\n\n        private Entry(int u, int v, int width, int height)\n        {\n            this.u = u;\n            this.v = v;\n            this.width = width;\n            this.height = height;\n        }\n\n        public double getU()\n        {\n            return u / (double) ATLAS_WIDTH;\n        }\n\n        public double getV()\n        {\n            return v / (double) ATLAS_HEIGHT;\n        }\n\n        public double getWidth()\n        {\n            return width / (double) ATLAS_WIDTH;\n        }\n\n        public double getHeight()\n        {\n            return height / (double) ATLAS_HEIGHT;\n        }\n    }\n\n    private static int loadTexture(BufferedImage image)\n    {\n        int[] pixels = new int[image.getWidth() * image.getHeight()];\n        image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());\n        ByteBuffer buffer = BufferUtils.createByteBuffer(pixels.length * 4);\n        for(int y = 0; y < image.getHeight(); y++)\n        {\n            for(int x = 0; x < image.getWidth(); x++)\n            {\n                int pixel = pixels[y * image.getWidth() + x];\n                buffer.put((byte) ((pixel >> 16) & 0xFF));\n                buffer.put((byte) ((pixel >> 8) & 0xFF));\n                buffer.put((byte) (pixel & 0xFF));\n                buffer.put((byte) ((pixel >> 24) & 0xFF));\n            }\n        }\n        buffer.flip();\n        int textureId = GL11.glGenTextures();\n        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);\n        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, image.getWidth(), image.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);\n        return textureId;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/texture/TextureEntry.java",
    "content": "package com.mrcrayfish.modelcreator.texture;\n\nimport com.mrcrayfish.modelcreator.TexturePath;\nimport com.mrcrayfish.modelcreator.component.TextureManager;\nimport org.lwjgl.BufferUtils;\nimport org.lwjgl.opengl.GL11;\nimport org.lwjgl.opengl.GL12;\n\nimport javax.imageio.ImageIO;\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.regex.Pattern;\n\npublic class TextureEntry\n{\n    public static final Pattern KEY_PATTERN = Pattern.compile(\"[a-z_0-9]+\");\n\n    private String key;\n    private TexturePath path;\n\n    private BufferedImage source;\n    private ImageIcon icon;\n    private List<Integer> textures;\n\n    private File textureFile;\n    private File metaFile;\n\n    private TextureAnimation anim;\n\n    public TextureEntry(File texture) throws IOException\n    {\n        this.path = new TexturePath(texture);\n        this.key = path.getName();\n        this.textureFile = texture;\n        this.source = ImageIO.read(texture);\n        this.anim = TextureAnimation.getAnimationForTexture(texture, this.source.getWidth(), this.source.getHeight());\n        this.icon = createIcon(this.source);\n        File metaFile = new File(texture.getAbsolutePath() + \".mcmeta\");\n        if(metaFile.exists())\n        {\n            this.metaFile = metaFile;\n        }\n    }\n\n    public TextureEntry(String key, File texture) throws IOException\n    {\n        this.key = key;\n        this.path = new TexturePath(texture);\n        this.textureFile = texture;\n        this.source = ImageIO.read(texture);\n        this.anim = TextureAnimation.getAnimationForTexture(texture, this.source.getWidth(), this.source.getHeight());\n        this.icon = createIcon(this.source);\n        File metaFile = new File(texture.getAbsolutePath() + \".mcmeta\");\n        if(metaFile.exists())\n        {\n            this.metaFile = metaFile;\n        }\n    }\n\n    public TextureEntry(String key, TexturePath path, File texture) throws IOException\n    {\n        this.key = key;\n        this.path = path;\n        this.textureFile = texture;\n        this.source = ImageIO.read(texture);\n        this.anim = TextureAnimation.getAnimationForTexture(texture, this.source.getWidth(), this.source.getHeight());\n        this.icon = createIcon(this.source);\n        File metaFile = new File(texture.getAbsolutePath() + \".mcmeta\");\n        if(metaFile.exists())\n        {\n            this.metaFile = metaFile;\n        }\n    }\n\n    public void setTexturePath(TexturePath path)\n    {\n        this.path = path;\n    }\n\n    public TexturePath getTexturePath()\n    {\n        return path;\n    }\n\n    public String getKey()\n    {\n        return key;\n    }\n\n    public void setKey(String key)\n    {\n        this.key = key;\n    }\n\n    public String getModId()\n    {\n        return path.getModId();\n    }\n\n    public String getDirectory()\n    {\n        return path.getDirectory();\n    }\n\n    public String getName()\n    {\n        return path.getName();\n    }\n\n    public void bindTexture()\n    {\n        if(textures != null)\n        {\n            GL11.glBindTexture(GL11.GL_TEXTURE_2D, textures.get(this.isAnimated() ? anim.getCurrentAnimationFrame() : 0));\n        }\n    }\n\n    public void bindNextTexture()\n    {\n        if(textures != null)\n        {\n            GL11.glBindTexture(GL11.GL_TEXTURE_2D, textures.get(this.isAnimated() ? anim.getNextAnimationFrame() : 0));\n        }\n    }\n\n    public BufferedImage getSource()\n    {\n        return source;\n    }\n\n    public ImageIcon getIcon()\n    {\n        return icon;\n    }\n\n    public TextureAnimation getAnimation()\n    {\n        return anim;\n    }\n\n    public boolean isAnimated()\n    {\n        return anim != null;\n    }\n\n    public int getPasses()\n    {\n        if(anim != null)\n        {\n            return anim.getPasses();\n        }\n        return 1;\n    }\n\n    public File getTextureFile()\n    {\n        return textureFile;\n    }\n\n    public void setTextureFile(File texture)\n    {\n        try\n        {\n            this.textureFile = texture;\n            this.source = ImageIO.read(texture);\n            this.icon = createIcon(this.source);\n            this.anim = TextureAnimation.getAnimationForTexture(texture, source.getWidth(), source.getHeight());\n            TextureManager.loadTexture(this);\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public void deleteTexture()\n    {\n        if(textures != null)\n        {\n            for(int i : textures)\n            {\n                GL11.glDeleteTextures(i);\n            }\n            textures = null;\n        }\n    }\n\n    private static ImageIcon createIcon(BufferedImage source)\n    {\n        source = source.getSubimage(0, 0, source.getWidth(), source.getWidth());\n        Image scaledImage = source.getScaledInstance(64, 64, java.awt.Image.SCALE_FAST);\n        return new ImageIcon(scaledImage);\n    }\n\n    public void loadTexture()\n    {\n        if(textures == null)\n        {\n            if(anim == null)\n            {\n                this.textures = Collections.singletonList(loadTexture(source));\n            }\n            else\n            {\n                List<Integer> textures = new ArrayList<>();\n                int width = anim.getWidth();\n                int height = anim.getHeight();\n                int x = 0;\n                while(x + width <= source.getWidth())\n                {\n                    int y = 0;\n                    while(y + height <= source.getHeight())\n                    {\n                        BufferedImage subImage = source.getSubimage(x, y, width, height);\n                        textures.add(loadTexture(subImage));\n                        y += height;\n                    }\n                    x += width;\n                }\n                this.textures = textures;\n            }\n        }\n    }\n\n    private int loadTexture(BufferedImage image)\n    {\n        int[] pixels = new int[image.getWidth() * image.getHeight()];\n        image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());\n        ByteBuffer buffer = BufferUtils.createByteBuffer(pixels.length * 4);\n        for(int y = 0; y < image.getHeight(); y++)\n        {\n            for(int x = 0; x < image.getWidth(); x++)\n            {\n                int pixel = pixels[y * image.getWidth() + x];\n                buffer.put((byte) ((pixel >> 16) & 0xFF));\n                buffer.put((byte) ((pixel >> 8) & 0xFF));\n                buffer.put((byte) (pixel & 0xFF));\n                buffer.put((byte) ((pixel >> 24) & 0xFF));\n            }\n        }\n        buffer.flip();\n        int textureId = GL11.glGenTextures();\n        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureId);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);\n        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);\n        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, image.getWidth(), image.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);\n        return textureId;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/AssetsUtil.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport java.io.File;\n\n/**\n * Author: MrCrayfish\n */\npublic class AssetsUtil\n{\n    public static String getTextureDirectory(File file)\n    {\n        StringBuilder builder = new StringBuilder();\n        File parent = file;\n        while((parent = parent.getParentFile()) != null)\n        {\n            if(!parent.getName().equals(\"textures\"))\n            {\n                builder.insert(0, parent.getName()).insert(0, \"/\");\n                continue;\n            }\n            return builder.length() > 0 ? builder.substring(1, builder.length()) : \"\";\n        }\n        return \"blocks\";\n    }\n\n    public static String getModId(File file)\n    {\n        File previous = null;\n        File parent = file;\n        while((parent = parent.getParentFile()) != null)\n        {\n            if(parent.getName().equals(\"assets\"))\n            {\n                break;\n            }\n            previous = parent;\n        }\n        return previous != null && Util.hasFolder(previous, \"textures\") ? previous.getName() : \"minecraft\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/AtlasRenderUtil.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.texture.TextureAtlas;\n\nimport static org.lwjgl.opengl.GL11.*;\n\n/**\n * Author: MrCrayfish\n */\npublic class AtlasRenderUtil\n{\n    private static TextureAtlas.Entry entry;\n\n    public static void bindTexture(TextureAtlas.Entry entry)\n    {\n        AtlasRenderUtil.entry = entry;\n    }\n\n    public static void drawQuad(int startX, int startY, int endX, int endY)\n    {\n        TextureAtlas.bind();\n        glBegin(GL_QUADS);\n        {\n            if(entry != null)\n            {\n                glTexCoord2d(entry.getU(), entry.getV());\n            }\n            glVertex2f(startX, startY);\n            if(entry != null)\n            {\n                glTexCoord2d(entry.getU() + entry.getWidth(), entry.getV());\n            }\n            glVertex2f(endX, startY);\n            if(entry != null)\n            {\n                glTexCoord2d(entry.getU() + entry.getWidth(), entry.getV() + entry.getHeight());\n            }\n            glVertex2f(endX, endY);\n            if(entry != null)\n            {\n                glTexCoord2d(entry.getU(), entry.getV() + entry.getHeight());\n            }\n            glVertex2f(startX, endY);\n        }\n        glEnd();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/ComponentUtil.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.Icons;\nimport com.mrcrayfish.modelcreator.Processor;\n\nimport javax.swing.*;\nimport javax.swing.filechooser.FileFilter;\nimport java.awt.*;\nimport java.io.File;\n\npublic class ComponentUtil\n{\n    public static JRadioButton createRadioButton(String label, String toolTip)\n    {\n        JRadioButton radioButton = new JRadioButton(label);\n        radioButton.setToolTipText(toolTip);\n        radioButton.setIcon(Icons.light_off);\n        radioButton.setRolloverIcon(Icons.light_off);\n        radioButton.setSelectedIcon(Icons.light_on);\n        radioButton.setRolloverSelectedIcon(Icons.light_on);\n        return radioButton;\n    }\n\n    public static JCheckBox createCheckBox(String text, String tooltip, boolean selected)\n    {\n        JCheckBox checkBox = new JCheckBox(text);\n        checkBox.setToolTipText(tooltip);\n        checkBox.setSelected(selected);\n        checkBox.setIcon(Icons.light_off);\n        checkBox.setRolloverIcon(Icons.light_off);\n        checkBox.setSelectedIcon(Icons.light_on);\n        checkBox.setRolloverSelectedIcon(Icons.light_on);\n        return checkBox;\n    }\n\n    public static JPanel createDirectorySelector(String label, Component parent, String defaultDir, Processor<File> processor)\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(100, 24));\n\n        JTextField textFieldDestination = new JTextField();\n        textFieldDestination.setPreferredSize(new Dimension(100, 24));\n        textFieldDestination.setText(defaultDir);\n        textFieldDestination.setEditable(false);\n        textFieldDestination.setFocusable(false);\n        textFieldDestination.setCaretPosition(0);\n        panel.add(textFieldDestination);\n\n        JButton btnBrowserDir = new JButton(\"Browse\");\n        btnBrowserDir.setPreferredSize(new Dimension(80, 24));\n        btnBrowserDir.setIcon(Icons.load);\n        btnBrowserDir.addActionListener(e ->\n        {\n            JFileChooser chooser = new JFileChooser();\n            chooser.setDialogTitle(\"Select a Folder\");\n            chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\n            chooser.setApproveButtonText(\"Select\");\n            chooser.setCurrentDirectory(new File(defaultDir));\n            int returnVal = chooser.showOpenDialog(parent);\n            if(returnVal == JFileChooser.APPROVE_OPTION)\n            {\n                File file = chooser.getSelectedFile();\n                if(file != null)\n                {\n                    if(processor != null)\n                    {\n                        if(processor.run(file))\n                        {\n                            return;\n                        }\n                    }\n                    textFieldDestination.setText(file.getAbsolutePath());\n                }\n            }\n        });\n        panel.add(btnBrowserDir);\n\n        JLabel labelExportDir = new JLabel(label);\n        panel.add(labelExportDir);\n\n        layout.putConstraint(SpringLayout.NORTH, textFieldDestination, 0, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, textFieldDestination, 10, SpringLayout.EAST, labelExportDir);\n        layout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);\n        layout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.WEST, labelExportDir, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.EAST, btnBrowserDir, 0, SpringLayout.EAST, panel);\n\n        return panel;\n    }\n\n    public static JPanel createFileSelector(String label, Component parent, String defaultDir, FileFilter filter, Processor<File> processor)\n    {\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(100, 24));\n\n        JTextField textFieldDestination = new JTextField();\n        textFieldDestination.setPreferredSize(new Dimension(100, 24));\n        textFieldDestination.setText(defaultDir);\n        textFieldDestination.setEditable(false);\n        textFieldDestination.setFocusable(false);\n        textFieldDestination.setCaretPosition(0);\n        panel.add(textFieldDestination);\n\n        JButton btnBrowserDir = new JButton(\"Browse\");\n        btnBrowserDir.setPreferredSize(new Dimension(80, 24));\n        btnBrowserDir.setIcon(Icons.load);\n        btnBrowserDir.addActionListener(e ->\n        {\n            JFileChooser chooser = new JFileChooser();\n            chooser.setDialogTitle(\"Select a File\");\n            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);\n            chooser.setApproveButtonText(\"Select\");\n            chooser.setCurrentDirectory(new File(defaultDir));\n            if(filter != null)\n            {\n                chooser.setFileFilter(filter);\n            }\n            int returnVal = chooser.showOpenDialog(parent);\n            if(returnVal == JFileChooser.APPROVE_OPTION)\n            {\n                File file = chooser.getSelectedFile();\n                if(file != null)\n                {\n                    if(processor != null)\n                    {\n                        if(processor.run(file))\n                        {\n                            return;\n                        }\n                    }\n                    textFieldDestination.setText(file.getAbsolutePath());\n                }\n            }\n        });\n        panel.add(btnBrowserDir);\n\n        JLabel labelExportDir = new JLabel(label);\n        panel.add(labelExportDir);\n\n        layout.putConstraint(SpringLayout.NORTH, textFieldDestination, 0, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, textFieldDestination, 10, SpringLayout.EAST, labelExportDir);\n        layout.putConstraint(SpringLayout.EAST, textFieldDestination, -10, SpringLayout.WEST, btnBrowserDir);\n        layout.putConstraint(SpringLayout.NORTH, labelExportDir, 3, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.WEST, labelExportDir, 0, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, btnBrowserDir, 0, SpringLayout.NORTH, textFieldDestination);\n        layout.putConstraint(SpringLayout.EAST, btnBrowserDir, 0, SpringLayout.EAST, panel);\n\n        return panel;\n    }\n\n    public static Rectangle expandRectangle(Rectangle r, int amount)\n    {\n        return new Rectangle(r.x - amount, r.y - amount, r.width + amount * 2, r.height + amount * 2);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/FontManager.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.ModelCreator;\nimport org.newdawn.slick.Color;\nimport org.newdawn.slick.TrueTypeFont;\n\nimport java.awt.*;\nimport java.io.InputStream;\n\npublic enum FontManager\n{\n    BEBAS_NEUE_20(\"bebas_neue.otf\", 20),\n    BEBAS_NEUE_50(\"bebas_neue.otf\", 50);\n\n    private TrueTypeFont font;\n\n    FontManager(String name, float size)\n    {\n        loadFont(name, size);\n    }\n\n    private void loadFont(String name, float size)\n    {\n        try\n        {\n            InputStream is = ModelCreator.class.getClassLoader().getResourceAsStream(name);\n            Font font = Font.createFont(Font.TRUETYPE_FONT, is);\n            this.font = new TrueTypeFont(font.deriveFont(size), true);\n\n        }\n        catch(Exception e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public void drawString(int x, int y, String text, Color color)\n    {\n        font.drawString(x, y, text, color);\n    }\n\n    public int getWidth(String s)\n    {\n        return font.getWidth(s);\n    }\n\n    public int getHeight()\n    {\n        return font.getHeight();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/KeyboardUtil.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport org.lwjgl.input.Keyboard;\n\nimport javax.swing.*;\nimport java.awt.event.KeyEvent;\n\n/**\n * Author: MrCrayfish\n */\npublic class KeyboardUtil\n{\n    public static String convertKeyStokeToString(KeyStroke keyStroke)\n    {\n        String shortcutText = \"\";\n        int modifiers = keyStroke.getModifiers();\n        if(modifiers > 0)\n        {\n            shortcutText = KeyEvent.getKeyModifiersText(modifiers);\n            shortcutText += \"+\";\n        }\n        shortcutText += KeyEvent.getKeyText(keyStroke.getKeyCode());\n        return shortcutText;\n    }\n\n    public static boolean isCtrlKeyDown()\n    {\n        if(OperatingSystem.get() == OperatingSystem.MAC)\n        {\n            return Keyboard.isKeyDown(Keyboard.KEY_LMETA) || Keyboard.isKeyDown(Keyboard.KEY_RMETA);\n        }\n        else\n        {\n            return Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL);\n        }\n    }\n\n    public static boolean isShiftKeyDown()\n    {\n        return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT);\n    }\n\n    public static boolean isAltKeyDown()\n    {\n        return Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/OperatingSystem.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\n/**\n * Author: MrCrayfish\n */\npublic enum OperatingSystem\n{\n    WINDOWS,\n    MAC,\n    LINUX,\n    SOLARIS,\n    UNKNOWN;\n\n    private static final OperatingSystem OS;\n\n    static\n    {\n        String name = System.getProperty(\"os.name\").toLowerCase();\n        if(name.contains(\"win\"))\n        {\n            OS = OperatingSystem.WINDOWS;\n        }\n        else if(name.contains(\"mac\"))\n        {\n            OS = OperatingSystem.MAC;\n        }\n        else if(name.contains(\"solaris\"))\n        {\n            OS = OperatingSystem.SOLARIS;\n        }\n        else if(name.contains(\"sunos\"))\n        {\n            OS = OperatingSystem.SOLARIS;\n        }\n        else if(name.contains(\"linux\"))\n        {\n            OS = OperatingSystem.LINUX;\n        }\n        else if(name.contains(\"unix\"))\n        {\n            OS = OperatingSystem.LINUX;\n        }\n        else\n        {\n            OS = OperatingSystem.UNKNOWN;\n        }\n    }\n\n    public static OperatingSystem get()\n    {\n        return OS;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/Parser.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport com.mrcrayfish.modelcreator.Exporter;\n\nimport java.text.ParseException;\n\npublic class Parser\n{\n    public static double parseDouble(String text, double def)\n    {\n        try\n        {\n            return Exporter.FORMAT.parse(text).doubleValue();\n        }\n        catch(NumberFormatException | ParseException e)\n        {\n            e.printStackTrace();\n        }\n        return def;\n    }\n\n    public static int parseInt(String text, int def)\n    {\n        try\n        {\n            return Integer.parseInt(text);\n        }\n        catch(NumberFormatException e)\n        {\n            e.printStackTrace();\n        }\n        return def;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/SharedLibraryLoader.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\n/*******************************************************************************\n * Copyright 2011 See AUTHORS file.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n *   http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n ******************************************************************************/\n\nimport java.io.*;\nimport java.lang.reflect.Method;\nimport java.util.UUID;\nimport java.util.zip.CRC32;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\n/**\n * Loads shared libraries from JAR files. Call {@link SharedLibraryLoader#load()\n * to load the required LWJGL 3 native shared libraries.\n * \n * @author mzechner\n * @author Nathan Sweet\n */\npublic class SharedLibraryLoader\n{\n\n\tpublic static final boolean isWindows = System.getProperty(\"os.name\").contains(\"Windows\");\n\tpublic static final boolean isLinux = System.getProperty(\"os.name\").contains(\"Linux\");\n\tpublic static final boolean isMac = System.getProperty(\"os.name\").contains(\"Mac\");\n\tpublic static final boolean is64Bit = System.getProperty(\"os.arch\").equals(\"amd64\")\n\t\t\t|| System.getProperty(\"os.arch\").equals(\"x86_64\");\n\n\tprivate static final boolean usingJws;\n\n\tstatic\n\t{\n\t\tif (!isWindows && !isLinux && !isMac)\n\t\t{\n\t\t\tthrow new UnsupportedOperationException(\"Unknown platform: \" + System.getProperty(\"os.name\"));\n\t\t}\n\n\t\t// Don't extract natives if using JWS.\n\t\tboolean failed = false;\n\t\ttry\n\t\t{\n\t\t\tMethod method = Class.forName(\"javax.jnlp.ServiceManager\").getDeclaredMethod(\"lookup\",\n\t\t\t\t\tnew Class[] { String.class });\n\t\t\tmethod.invoke(null, \"javax.jnlp.PersistenceService\");\n\t\t}\n\t\tcatch (Throwable ex)\n\t\t{\n\t\t\tfailed = true;\n\t\t}\n\t\tusingJws = !failed;\n\t}\n\n\t/**\n\t * Extracts the LWJGL native libraries from the classpath and sets the\n\t * \"org.lwjgl.librarypath\" system property.\n\t */\n\tpublic static synchronized void load(boolean disableOpenAL)\n\t{\n\t\tif (usingJws)\n\t\t\treturn;\n\n\t\tSharedLibraryLoader loader = new SharedLibraryLoader();\n\t\tFile nativesDir = null;\n\t\ttry\n\t\t{\n\t\t\tif (SharedLibraryLoader.isWindows)\n\t\t\t{\n\n\t\t\t\tnativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? \"lwjgl64.dll\" : \"lwjgl.dll\", null)\n\t\t\t\t\t\t.getParentFile();\n\n\t\t\t\tif (!disableOpenAL)\n\t\t\t\t\tloader.extractFile(SharedLibraryLoader.is64Bit ? \"OpenAL64.dll\" : \"OpenAL32.dll\",\n\t\t\t\t\t\t\tnativesDir.getName());\n\n\t\t\t}\n\t\t\telse if (SharedLibraryLoader.isMac)\n\t\t\t{\n\n\t\t\t\tnativesDir = loader.extractFile(\"liblwjgl.dylib\", null).getParentFile();\n\n\t\t\t\tif (!disableOpenAL)\n\t\t\t\t\tloader.extractFile(\"openal.dylib\", nativesDir.getName());\n\n\t\t\t}\n\t\t\telse if (SharedLibraryLoader.isLinux)\n\t\t\t{\n\n\t\t\t\tnativesDir = loader.extractFile(SharedLibraryLoader.is64Bit ? \"liblwjgl64.so\" : \"liblwjgl.so\", null)\n\t\t\t\t\t\t.getParentFile();\n\n\t\t\t\tif (!disableOpenAL)\n\t\t\t\t\tloader.extractFile(SharedLibraryLoader.is64Bit ? \"libopenal64.so\" : \"libopenal.so\",\n\t\t\t\t\t\t\tnativesDir.getName());\n\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tthrow new UnsupportedOperationException(\"Unknown platform: \" + System.getProperty(\"os.name\"));\n\t\t\t}\n\t\t}\n\t\tcatch (Throwable ex)\n\t\t{\n\t\t\tthrow new Error(\"Unable to extract LWJGL natives.\", ex);\n\t\t}\n\n\t\tSystem.setProperty(\"org.lwjgl.librarypath\", nativesDir.getAbsolutePath());\n\t}\n\n\tprivate String nativesJar;\n\n\tprivate SharedLibraryLoader()\n\t{\n\t}\n\n\t/** Returns a CRC of the remaining bytes in the stream. */\n\tprivate String crc(InputStream input)\n\t{\n\t\tif (input == null)\n\t\t\tthrow new IllegalArgumentException(\"input cannot be null.\");\n\t\tCRC32 crc = new CRC32();\n\t\tbyte[] buffer = new byte[4096];\n\t\ttry\n\t\t{\n\t\t\twhile (true)\n\t\t\t{\n\t\t\t\tint length = input.read(buffer);\n\t\t\t\tif (length == -1)\n\t\t\t\t\tbreak;\n\t\t\t\tcrc.update(buffer, 0, length);\n\t\t\t}\n\t\t} catch (Exception ex)\n\t\t{\n\t\t\tif (input != null)\n\t\t\t{\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tinput.close();\n\t\t\t\t}\n\t\t\t\tcatch (IOException e)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn Long.toString(crc.getValue(), 16);\n\t}\n\n\tprivate InputStream readFile(String path)\n\t{\n\t\tif (nativesJar == null)\n\t\t{\n\t\t\tInputStream input = SharedLibraryLoader.class.getResourceAsStream(\"/\" + path);\n\t\t\tif (input == null)\n\t\t\t\tthrow new RuntimeException(\"Unable to read file for extraction: \" + path);\n\t\t\treturn input;\n\t\t}\n\n\t\t// Read from JAR.\n\t\tZipFile file = null;\n\t\ttry\n\t\t{\n\t\t\tfile = new ZipFile(nativesJar);\n\t\t\tZipEntry entry = file.getEntry(path);\n\t\t\tif (entry == null)\n\t\t\t\tthrow new RuntimeException(\"Couldn't find '\" + path + \"' in JAR: \" + nativesJar);\n\t\t\treturn file.getInputStream(entry);\n\t\t}\n\t\tcatch (IOException ex)\n\t\t{\n\t\t\tthrow new RuntimeException(\"Error reading '\" + path + \"' in JAR: \" + nativesJar, ex);\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\tif (file != null)\n\t\t\t{\n\t\t\t\ttry\n\t\t\t\t{\n\t\t\t\t\tfile.close();\n\t\t\t\t}\n\t\t\t\tcatch (IOException e)\n\t\t\t\t{\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Extracts the specified file into the temp directory if it does not already\n\t * exist or the CRC does not match. If file extraction fails and the file exists\n\t * at java.library.path, that file is returned.\n\t * \n\t * @param sourcePath The file to extract from the classpath or JAR.\n\t * @param dirName    The name of the subdirectory where the file will be\n\t *                   extracted. If null, the file's CRC will be used.\n\t * @return The extracted file.\n\t */\n\tprivate File extractFile(String sourcePath, String dirName) throws IOException\n\t{\n\t\ttry\n\t\t{\n\t\t\tString sourceCrc = crc(readFile(sourcePath));\n\t\t\tif (dirName == null)\n\t\t\t\tdirName = sourceCrc;\n\n\t\t\tFile extractedFile = getExtractedFile(dirName, new File(sourcePath).getName());\n\t\t\treturn extractFile(sourcePath, sourceCrc, extractedFile);\n\t\t}\n\t\tcatch (RuntimeException ex)\n\t\t{\n\t\t\t// Fallback to file at java.library.path location, eg for applets.\n\t\t\tFile file = new File(System.getProperty(\"java.library.path\"), sourcePath);\n\t\t\tif (file.exists())\n\t\t\t\treturn file;\n\t\t\tthrow ex;\n\t\t}\n\t}\n\n\t/**\n\t * Returns a path to a file that can be written. Tries multiple locations and\n\t * verifies writing succeeds.\n\t */\n\tprivate File getExtractedFile(String dirName, String fileName)\n\t{\n\t\t// Temp directory with username in path.\n\t\tFile idealFile = new File(\n\t\t\t\tSystem.getProperty(\"java.io.tmpdir\") + \"/lwjgl\" + System.getProperty(\"user.name\") + \"/\" + dirName,\n\t\t\t\tfileName);\n\t\tif (canWrite(idealFile))\n\t\t\treturn idealFile;\n\n\t\t// System provided temp directory.\n\t\ttry\n\t\t{\n\t\t\tFile file = File.createTempFile(dirName, null);\n\t\t\tif (file.delete())\n\t\t\t{\n\t\t\t\tfile = new File(file, fileName);\n\t\t\t\tif (canWrite(file))\n\t\t\t\t\treturn file;\n\t\t\t}\n\t\t}\n\t\tcatch (IOException ignored)\n\t\t{\n\t\t}\n\n\t\t// User home.\n\t\tFile file = new File(System.getProperty(\"user.home\") + \"/.lwjgl/\" + dirName, fileName);\n\t\tif (canWrite(file))\n\t\t\treturn file;\n\n\t\t// Relative directory.\n\t\tfile = new File(\".temp/\" + dirName, fileName);\n\t\tif (canWrite(file))\n\t\t\treturn file;\n\n\t\treturn idealFile; // Will likely fail, but we did our best.\n\t}\n\n\t/**\n\t * Returns true if the parent directories of the file can be created and the\n\t * file can be written.\n\t */\n\tprivate boolean canWrite(File file)\n\t{\n\t\tFile parent = file.getParentFile();\n\t\tFile testFile;\n\t\tif (file.exists())\n\t\t{\n\t\t\tif (!file.canWrite() || !canExecute(file))\n\t\t\t\treturn false;\n\t\t\t// Don't overwrite existing file just to check if we can write to directory.\n\t\t\ttestFile = new File(parent, UUID.randomUUID().toString());\n\t\t}\n\t\telse\n\t\t{\n\t\t\tparent.mkdirs();\n\t\t\tif (!parent.isDirectory())\n\t\t\t\treturn false;\n\t\t\ttestFile = file;\n\t\t}\n\t\ttry\n\t\t{\n\t\t\tnew FileOutputStream(testFile).close();\n\t\t\tif (!canExecute(testFile))\n\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t}\n\t\tcatch (Throwable ex)\n\t\t{\n\t\t\treturn false;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\ttestFile.delete();\n\t\t}\n\t}\n\n\tprivate boolean canExecute(File file)\n\t{\n\t\ttry\n\t\t{\n\t\t\tif (file.canExecute())\n\t\t\t\treturn true;\n\n\t\t\tfile.setExecutable(true, false);\n\t\t\treturn file.canExecute();\n\t\t}\n\t\tcatch (Exception ignored)\n\t\t{\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tprivate File extractFile(String sourcePath, String sourceCrc, File extractedFile) throws IOException\n\t{\n\t\tString extractedCrc = null;\n\t\tif (extractedFile.exists())\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\textractedCrc = crc(new FileInputStream(extractedFile));\n\t\t\t}\n\t\t\tcatch (FileNotFoundException ignored)\n\t\t\t{\n\t\t\t}\n\t\t}\n\n\t\t// If file doesn't exist or the CRC doesn't match, extract it to the temp dir.\n\t\tif (extractedCrc == null || !extractedCrc.equals(sourceCrc))\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tInputStream input = readFile(sourcePath);\n\t\t\t\textractedFile.getParentFile().mkdirs();\n\t\t\t\tFileOutputStream output = new FileOutputStream(extractedFile);\n\t\t\t\tbyte[] buffer = new byte[4096];\n\t\t\t\twhile (true)\n\t\t\t\t{\n\t\t\t\t\tint length = input.read(buffer);\n\t\t\t\t\tif (length == -1)\n\t\t\t\t\t\tbreak;\n\t\t\t\t\toutput.write(buffer, 0, length);\n\t\t\t\t}\n\t\t\t\tinput.close();\n\t\t\t\toutput.close();\n\t\t\t}\n\t\t\tcatch (IOException ex)\n\t\t\t{\n\t\t\t\tthrow new RuntimeException(\n\t\t\t\t\t\t\"Error extracting file: \" + sourcePath + \"\\nTo: \" + extractedFile.getAbsolutePath(), ex);\n\t\t\t}\n\t\t}\n\n\t\treturn extractedFile;\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/StreamUtils.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport java.io.*;\n\npublic class StreamUtils\n{\n    public static String convertToString(InputStream inputStream) throws IOException\n    {\n        if(inputStream != null)\n        {\n            Writer writer = new StringWriter();\n\n            char[] buffer = new char[1024];\n            try\n            {\n                Reader reader = new BufferedReader(new InputStreamReader(inputStream, \"UTF-8\"), 1024);\n                int n;\n                while((n = reader.read(buffer)) != -1)\n                {\n                    writer.write(buffer, 0, n);\n                }\n            }\n            finally\n            {\n                inputStream.close();\n            }\n            return writer.toString();\n        }\n        else\n        {\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/mrcrayfish/modelcreator/util/Util.java",
    "content": "package com.mrcrayfish.modelcreator.util;\n\nimport com.google.gson.Gson;\nimport com.mrcrayfish.modelcreator.ProjectManager;\nimport com.mrcrayfish.modelcreator.element.ElementManager;\n\nimport javax.imageio.ImageIO;\nimport javax.imageio.ImageReader;\nimport javax.imageio.stream.FileImageInputStream;\nimport javax.imageio.stream.ImageInputStream;\nimport javax.swing.*;\nimport java.awt.*;\nimport java.awt.event.WindowAdapter;\nimport java.awt.event.WindowEvent;\nimport java.io.*;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.function.Predicate;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipInputStream;\n\npublic class Util\n{\n    private static final List<String> minecraftVersions;\n\n    static\n    {\n        List<String> versionList = new ArrayList<>();\n        File file = getMinecraftDirectory();\n        if(file != null && file.exists() && file.isDirectory())\n        {\n            File versions = null;\n            for(File folder : getSubFolders(file))\n            {\n                if(folder.getName().equals(\"versions\"))\n                {\n                    versions = folder;\n                    break;\n                }\n            }\n            if(versions != null)\n            {\n                for(File folder : getSubFolders(versions))\n                {\n                    File json = getFile(folder, folder.getName() + \".json\");\n                    if(json != null && !isLegacyAssets(json))\n                    {\n                        if(hasFile(folder, folder.getName() + \".jar\"))\n                        {\n                            versionList.add(folder.getName());\n                        }\n                    }\n                }\n            }\n        }\n        minecraftVersions = versionList;\n    }\n\n    public static List<String> getMinecraftVersions()\n    {\n        return minecraftVersions;\n    }\n\n    public static Dimension getImageDimension(File image) throws IOException\n    {\n        int pos = image.getName().lastIndexOf(\".\");\n        if(pos != -1)\n        {\n            String suffix = image.getName().substring(pos + 1);\n            Iterator<ImageReader> it = ImageIO.getImageReadersBySuffix(suffix);\n            while(it.hasNext())\n            {\n                ImageReader reader = it.next();\n                try\n                {\n                    ImageInputStream stream = new FileImageInputStream(image);\n                    reader.setInput(stream);\n                    int width = reader.getWidth(reader.getMinIndex());\n                    int height = reader.getHeight(reader.getMinIndex());\n                    stream.flush();\n                    stream.close();\n                    return new Dimension(width, height);\n                }\n                catch(IOException e)\n                {\n                    e.printStackTrace();\n                }\n                finally\n                {\n                    reader.dispose();\n                }\n            }\n        }\n        throw new IOException(\"Not a known image file: \" + image.getAbsolutePath());\n    }\n\n    public static void openUrl(String url)\n    {\n        Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;\n        if(desktop != null && desktop.isSupported(Desktop.Action.BROWSE))\n        {\n            try\n            {\n                desktop.browse(new URL(url).toURI());\n            }\n            catch(Exception e)\n            {\n                e.printStackTrace();\n            }\n        }\n    }\n\n    public static void loadModelFromJar(ElementManager manager, Class<?> clazz, String name)\n    {\n        try\n        {\n            InputStream is = clazz.getClassLoader().getResourceAsStream(name + \".model\");\n            File file = File.createTempFile(name + \".model\", \"\");\n            FileOutputStream fos = new FileOutputStream(file);\n\n            byte[] buffer = new byte[1024];\n\n            int len;\n            while((len = is.read(buffer)) > 0)\n            {\n                fos.write(buffer, 0, len);\n            }\n\n            fos.close();\n            is.close();\n\n            ProjectManager.loadProject(manager, file.getAbsolutePath());\n\n            file.delete();\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n    }\n\n    public static void extractMinecraftAssets(String version, Window window)\n    {\n        JFileChooser chooser = new JFileChooser();\n        chooser.setDialogTitle(\"Extract Destination\");\n        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);\n        chooser.setApproveButtonText(\"Select\");\n        int returnVal = chooser.showOpenDialog(window);\n        if(returnVal == JFileChooser.APPROVE_OPTION)\n        {\n            File file = chooser.getSelectedFile();\n            if(file.isDirectory())\n            {\n                File extractionFolder = new File(file, version);\n                File jar = new File(getMinecraftDirectory(), \"versions/\" + version + \"/\" + version + \".jar\");\n                extractZipFiles(jar, zipEntry -> zipEntry.getName().startsWith(\"assets/\"), window, extractionFolder);\n            }\n        }\n    }\n\n    private static void extractZipFiles(File zipFile, Predicate<ZipEntry> conditions, Window window, File extractionFolder)\n    {\n        final boolean[] cancelled = {false};\n\n        JDialog dialog = new JDialog(window, \"Extracting Assets\", Dialog.ModalityType.APPLICATION_MODAL);\n        dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n        dialog.addWindowListener(new WindowAdapter()\n        {\n            @Override\n            public void windowClosed(WindowEvent e)\n            {\n                cancelled[0] = true;\n            }\n        });\n\n        SpringLayout layout = new SpringLayout();\n        JPanel panel = new JPanel(layout);\n        panel.setPreferredSize(new Dimension(300, 60));\n        dialog.add(panel);\n\n        JLabel labelProcessing = new JLabel(\"Processing\");\n        panel.add(labelProcessing);\n\n        JLabel labelFile = new JLabel();\n        panel.add(labelFile);\n\n        JProgressBar progressBar = new JProgressBar();\n        progressBar.setForeground(new Color(129, 192, 0));\n        panel.add(progressBar);\n\n        layout.putConstraint(SpringLayout.NORTH, labelProcessing, 10, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, labelProcessing, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.NORTH, labelFile, 10, SpringLayout.NORTH, panel);\n        layout.putConstraint(SpringLayout.WEST, labelFile, 5, SpringLayout.EAST, labelProcessing);\n        layout.putConstraint(SpringLayout.EAST, labelFile, -10, SpringLayout.EAST, panel);\n        layout.putConstraint(SpringLayout.NORTH, progressBar, 10, SpringLayout.SOUTH, labelProcessing);\n        layout.putConstraint(SpringLayout.WEST, progressBar, 10, SpringLayout.WEST, panel);\n        layout.putConstraint(SpringLayout.EAST, progressBar, -10, SpringLayout.EAST, panel);\n\n        dialog.pack();\n        dialog.setResizable(false);\n        dialog.setLocationRelativeTo(null);\n\n        new Thread(() ->\n        {\n            List<ZipEntry> entries = new ArrayList<>();\n            try\n            {\n                ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile));\n                ZipEntry ze;\n                while((ze = zis.getNextEntry()) != null)\n                {\n                    if(cancelled[0])\n                    {\n                        return;\n                    }\n                    if(conditions != null && !conditions.test(ze))\n                    {\n                        continue;\n                    }\n                    entries.add(ze);\n                }\n                zis.closeEntry();\n                zis.close();\n            }\n            catch(IOException e)\n            {\n                e.printStackTrace();\n            }\n\n            if(entries.size() > 0)\n            {\n                SwingUtilities.invokeLater(() -> progressBar.setMaximum(entries.size()));\n            }\n\n            if(cancelled[0])\n            {\n                return;\n            }\n\n            try\n            {\n                ZipFile f = new ZipFile(zipFile);\n                for(int i = 0; i < entries.size(); i++)\n                {\n                    if(cancelled[0])\n                    {\n                        return;\n                    }\n\n                    ZipEntry entry = entries.get(i);\n                    SwingUtilities.invokeLater(() -> labelFile.setText(entry.getName()));\n\n                    InputStream is = f.getInputStream(entry);\n\n                    File file = new File(extractionFolder, entry.getName());\n                    file.getParentFile().mkdirs();\n                    file.createNewFile();\n\n                    byte[] buffer = new byte[8192];\n                    FileOutputStream fos = new FileOutputStream(file);\n                    int len;\n                    while((len = is.read(buffer)) > 0)\n                    {\n                        fos.write(buffer, 0, len);\n                    }\n                    fos.close();\n\n                    final int value = i;\n                    SwingUtilities.invokeLater(() -> progressBar.setValue(value + 1));\n                }\n                SwingUtilities.invokeLater(window::dispose);\n            }\n            catch(IOException e)\n            {\n                e.printStackTrace();\n            }\n        }).start();\n\n        dialog.setVisible(true);\n    }\n\n    public static File getMinecraftDirectory()\n    {\n        String userHome = System.getProperty(\"user.home\", \".\");\n        OperatingSystem os = OperatingSystem.get();\n        switch(os)\n        {\n            case WINDOWS:\n                String appDataDir = System.getenv(\"APPDATA\");\n                if(appDataDir != null)\n                {\n                    return new File(appDataDir, \".minecraft\");\n                }\n            case MAC:\n                return new File(userHome, \"Library/Application Support/minecraft\");\n            case LINUX:\n                return new File(userHome, \".minecraft\");\n            default:\n                return null;\n        }\n    }\n\n    private static File[] getSubFolders(File parent)\n    {\n        return parent.listFiles((dir, name) -> dir.isDirectory());\n    }\n\n    private static boolean hasFile(File parent, String targetName)\n    {\n        File[] files = parent.listFiles((dir, name) -> name.equals(targetName));\n        return files != null && files.length == 1;\n    }\n\n    private static File getFile(File parent, String targetName)\n    {\n        File[] files = parent.listFiles((dir, name) -> name.equals(targetName));\n        if(files != null)\n        {\n            return Arrays.stream(files).filter(file -> !file.isDirectory() && file.getName().equals(targetName)).findFirst().orElse(null);\n        }\n        return null;\n    }\n\n    public static boolean hasFolder(File parent, String targetName)\n    {\n        File[] files = parent.listFiles((dir, name) -> name.equals(targetName));\n        return files != null && Arrays.stream(files).anyMatch(File::isDirectory);\n    }\n\n    private static boolean isLegacyAssets(File file)\n    {\n        try\n        {\n            String json = new String(Files.readAllBytes(Paths.get(file.getAbsolutePath())));\n            Gson gson = new Gson();\n            VersionProperties properties = gson.fromJson(json, VersionProperties.class);\n            return properties != null && properties.assetIndex != null && \"legacy\".equals(properties.assetIndex.id);\n        }\n        catch(IOException e)\n        {\n            e.printStackTrace();\n        }\n        return false;\n    }\n\n    private static class VersionProperties\n    {\n        public AssetIndex assetIndex;\n    }\n\n    private static class AssetIndex\n    {\n        private String id;\n    }\n}\n"
  }
]