____tbvns____ 6 kuukautta sitten
commit
05490fba3a
34 muutettua tiedostoa jossa 2104 lisäystä ja 0 poistoa
  1. 38 0
      .gitignore
  2. BIN
      GD3D.ico
  3. 153 0
      pom.xml
  4. 16 0
      src/main/java/module-info.java
  5. 51 0
      src/main/java/xyz/tbvns/Constant.java
  6. 277 0
      src/main/java/xyz/tbvns/Generate/Generate3D.java
  7. 34 0
      src/main/java/xyz/tbvns/Generate/ReadMTL.java
  8. 136 0
      src/main/java/xyz/tbvns/Generate/ReadOBJ.java
  9. 92 0
      src/main/java/xyz/tbvns/GeometryDash/GDObject.java
  10. 67 0
      src/main/java/xyz/tbvns/JavaFX/FXAbout.java
  11. 88 0
      src/main/java/xyz/tbvns/JavaFX/FXBPMtoFPS.java
  12. 93 0
      src/main/java/xyz/tbvns/JavaFX/FXWarning.java
  13. 595 0
      src/main/java/xyz/tbvns/JavaFX/FXui.java
  14. 16 0
      src/main/java/xyz/tbvns/Main.java
  15. 27 0
      src/main/java/xyz/tbvns/Maths/BPM.java
  16. 23 0
      src/main/java/xyz/tbvns/Object/Color.java
  17. 8 0
      src/main/java/xyz/tbvns/Object/CompletedFace.java
  18. 11 0
      src/main/java/xyz/tbvns/Object/Face.java
  19. 9 0
      src/main/java/xyz/tbvns/Object/Frame.java
  20. 15 0
      src/main/java/xyz/tbvns/Object/Vector3.java
  21. 12 0
      src/main/java/xyz/tbvns/Other/Colors.java
  22. 12 0
      src/main/java/xyz/tbvns/Other/ZAxisComparator.java
  23. 179 0
      src/main/java/xyz/tbvns/Utils.java
  24. 38 0
      src/main/java/xyz/tbvns/WebSocket/CheckGDConnection.java
  25. 46 0
      src/main/java/xyz/tbvns/WebSocket/WSClient.java
  26. 38 0
      src/main/java/xyz/tbvns/WebSocket/WSGDChecker.java
  27. 15 0
      src/main/resources/About.html
  28. 0 0
      src/main/resources/Css/Pane.css
  29. BIN
      src/main/resources/GD3D.png
  30. BIN
      src/main/resources/GD3Dbanner.png
  31. 4 0
      src/main/resources/META-INF/MANIFEST.MF
  32. 11 0
      src/main/resources/Random.txt
  33. BIN
      src/main/resources/WarnFX/error.png
  34. BIN
      src/main/resources/WarnFX/info.png

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store

BIN
GD3D.ico


+ 153 - 0
pom.xml

@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>xyz.tbvns</groupId>
+    <artifactId>GD3D-rewrite</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <organization>
+        <name>Tbvns</name>
+        <url>https://github.com/tbvns</url>
+    </organization>
+
+    <inceptionYear>2024</inceptionYear>
+    <description>A 3d importer for GD</description>
+
+    <name>GD3D</name>
+
+    <properties>
+        <maven.compiler.source>21</maven.compiler.source>
+        <maven.compiler.target>21</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <repositories>
+        <repository>
+            <id>JCenter</id>
+            <url>https://jcenter.bintray.com/</url>
+        </repository>
+        <repository>
+            <id>jitpack.io</id>
+            <url>https://jitpack.io</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-controls</artifactId>
+            <version>21</version>
+        </dependency>
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-fxml</artifactId>
+            <version>21</version>
+        </dependency>
+        <dependency>
+            <groupId>org.java-websocket</groupId>
+            <artifactId>Java-WebSocket</artifactId>
+            <version>1.5.6</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/javax.vecmath/vecmath -->
+        <dependency>
+            <groupId>org.joml</groupId>
+            <artifactId>joml</artifactId>
+            <version>1.10.5</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.locationtech.jts.io/jts-io-common -->
+        <dependency>
+            <groupId>org.locationtech.jts.io</groupId>
+            <artifactId>jts-io-common</artifactId>
+            <version>1.19.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
+        <dependency>
+            <groupId>org.locationtech.jts</groupId>
+            <artifactId>jts-core</artifactId>
+            <version>1.19.0</version>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts -->
+        <dependency>
+            <groupId>org.locationtech.jts</groupId>
+            <artifactId>jts</artifactId>
+            <version>1.19.0</version>
+            <type>pom</type>
+        </dependency>
+        <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-web -->
+        <dependency>
+            <groupId>org.openjfx</groupId>
+            <artifactId>javafx-web</artifactId>
+            <version>23-ea+18</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <archive>
+                        <manifest>
+                            <mainClass>xyz.tbvns.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.akathist.maven.plugins.launch4j</groupId>
+                <artifactId>launch4j-maven-plugin</artifactId>
+                <version>2.5.0</version>
+                <executions>
+                    <execution>
+                        <id>l4j-gui</id>
+                        <phase>package</phase>
+                        <goals><goal>launch4j</goal></goals>
+                        <configuration>
+                            <icon>GD3D.ico</icon>
+                            <classPath>
+                                <mainClass>xyz.tbvns.Main</mainClass>
+                            </classPath>
+                            <headerType>gui</headerType>
+                            <outfile>target/GD3D.exe</outfile>
+                            <jar>./target/GD3D-rewrite-1.0-SNAPSHOT-jar-with-dependencies.jar</jar>
+                            <errTitle>GD3D - Error:</errTitle>
+
+                            <jre>
+                                <minVersion>21</minVersion>
+                            </jre>
+
+                            <versionInfo>
+                                <fileVersion>1.2.3.4</fileVersion>
+                                <txtFileVersion>txt file version?</txtFileVersion>
+                                <fileDescription>a description</fileDescription>
+                                <copyright>my copyright</copyright>
+                                <productVersion>4.3.2.1</productVersion>
+                                <txtProductVersion>txt product version</txtProductVersion>
+                                <productName>GD3D</productName>
+                                <internalName>GD3D</internalName>
+                                <originalFilename>GD3D.exe</originalFilename>
+                            </versionInfo>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 16 - 0
src/main/java/module-info.java

@@ -0,0 +1,16 @@
+module xyz.tbvns.gd3dfx {
+    requires javafx.controls;
+    requires javafx.fxml;
+    requires org.locationtech.jts;
+    requires org.locationtech.jts.io;
+    requires org.joml;
+    requires org.java_websocket;
+    requires javafx.web;
+
+    opens xyz.tbvns to javafx.fxml;
+    exports xyz.tbvns;
+    exports xyz.tbvns.JavaFX;
+    opens xyz.tbvns.JavaFX to javafx.fxml;
+    exports xyz.tbvns.Other;
+    opens xyz.tbvns.Other to javafx.fxml;
+}

+ 51 - 0
src/main/java/xyz/tbvns/Constant.java

@@ -0,0 +1,51 @@
+package xyz.tbvns;
+
+import org.joml.Vector4f;
+import xyz.tbvns.Object.Face;
+import xyz.tbvns.Object.Vector3;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Constant {
+    public static boolean useAnimation = true;
+    public static boolean hasOBJ = false;
+    public static boolean hasMTL = false;
+    public static boolean hasGD = false;
+    public static boolean isReady = true;
+    public static File obj = null;
+    public static File mtl = null;
+    public static boolean followPlayer = true;
+    public static boolean useColor = true;
+    public static boolean useKeyframe = true;
+    public static List<Face> faces = new ArrayList<>();
+    public static List<Vector3> points = new ArrayList<>();
+    public static List<Vector4f> points3d = new ArrayList<>();
+    public static List<Vector4f> sortedPoints3d = new ArrayList<>();
+
+    public static int xScreenCenter = 160;
+    public static int yScreenCenter = 120;
+    public static float screenPositionX = 0;
+    public static float screenPositionY = 0;
+    public static float screenPositionZ = 20;
+    public static float viewAngleX = 0;
+    public static float viewAngleY = 0;
+    public static float viewAngleZ = -90;
+    public static float modelScale = 20;
+    public static int skipFrame = 5;
+    public static float fps = 24;
+    public static int speed = 1;
+    public static float gameSpeed = 10.41667F;
+    public static int startingGroup = 1;
+    public static int placeDelay = 5;
+    public static boolean optimizeColor = false;
+    public static boolean optimizeGradient = false;
+
+
+
+
+    //Values
+    public static String version = "B0.1.0";
+    public static boolean shouldClose = false;
+}

+ 277 - 0
src/main/java/xyz/tbvns/Generate/Generate3D.java

@@ -0,0 +1,277 @@
+package xyz.tbvns.Generate;
+
+import org.joml.Vector3d;
+import org.joml.Vector4d;
+import org.joml.Vector4f;
+import xyz.tbvns.Constant;
+import xyz.tbvns.GeometryDash.GDObject;
+import xyz.tbvns.Object.Color;
+import xyz.tbvns.Object.Face;
+import xyz.tbvns.Object.Vector3;
+import xyz.tbvns.Other.ZAxisComparator;
+import xyz.tbvns.Utils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Generate3D {
+    public void proceed() {
+        if (Constant.isReady) {
+            try {
+                if (Constant.useKeyframe) {
+                    new Generate3D().GenerateAnimKeyframe();
+                } else {
+                    new Generate3D().GenerateAnimMove();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+    public void Generate() throws FileNotFoundException {
+        List<Vector4f> points = new ReadOBJ().getPoints(Constant.obj);
+        Constant.points3d = points;
+        for (int i = 0; i < points.size(); i++) {
+            Vector4f p = points.get(i);
+
+            Vector3d p1 = new Vector3d(new double[]{p.x, p.z, p.y});
+            Vector4d p2 = new Utils().projectPoint(p1);
+            Constant.points3d.set(i, new Vector4f((float) p2.x, (float) p2.y, (float) p2.z, (float) p2.w));
+
+            Constant.points.add(new Vector3((float) p2.x, (float) p2.y, (float) p2.z));
+
+            GDObject.addBasic(725, (float) (p2.x + 1000), (float) (p2.y + 1000), i+1);
+        }
+
+        List<Face> faces = new ReadOBJ().getFace(Constant.obj);
+
+        List<Color> colors = new ArrayList<>();
+
+        Constant.faces = faces;
+        final int[] id = {0};
+        faces.forEach(f -> {
+            if (!colors.contains(f.color)) {
+                f.colorId = colors.size() + 1;
+                colors.add(f.color);
+            }
+
+            id[0] +=1;
+            if (f.points.size() == 3) {
+                GDObject.addGradient(0f, 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(2), id[0], Math.round(Constant.points3d.get(f.points.get(0) - 1).z * 10000), f.colorId);
+            } else {
+                GDObject.addGradient(0f, 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(3), id[0], Math.round(Constant.points3d.get(f.points.get(0) - 1).z * 10000), f.colorId);
+            }
+        });
+
+
+        for (int i = 0; i < colors.size(); i++) {
+            Color c = colors.get(i);
+            GDObject.addColor(0f, 200f, Math.round(c.red * 255), Math.round(c.green * 255), Math.round(c.blue * 255), i+1);
+        }
+
+        GDObject.send();
+
+        Constant.points3d = new ArrayList<>();
+        Constant.faces = new ArrayList<>();
+        Constant.points = new ArrayList<>();
+        GDObject.request = new ArrayList<>();
+    }
+
+    public void GenerateAnimKeyframe() {
+        List<File> OBJs = new ArrayList<>();
+        List<File> MTLs = new ArrayList<>();
+
+        for (int i = 0; i < Constant.obj.listFiles().length; i++) {
+            File f = Constant.obj.listFiles()[i];
+            if (f.getAbsolutePath().endsWith(".obj")) {
+                OBJs.add(f);
+            }
+        }
+
+        for (int i = 0; i < Constant.mtl.listFiles().length; i++) {
+            File f = Constant.mtl.listFiles()[i];
+            if (f.getAbsolutePath().endsWith(".mtl")) {
+                MTLs.add(f);
+            }
+        }
+
+        if (Constant.obj.isDirectory()) {
+            if (Constant.hasMTL && !Constant.mtl.isDirectory()) {
+                return;
+            }
+            GDObject.addKeyframeTrigger();
+            int counter = 0;
+            boolean first = true;
+            for (int i = 0; i < OBJs.size(); i++) {
+                try {
+                    if (counter >= Constant.skipFrame || first) {
+                        counter = 0;
+
+                        List<Vector4f> points = new ReadOBJ().getPoints(OBJs.get(i));
+                        Constant.points3d = points;
+
+                        for (int i2 = 0; i2 < points.size(); i2++) {
+                            Vector4f p = points.get(i2);
+
+                            Vector3d p1 = new Vector3d(new double[]{p.x, p.z, p.y});
+                            Vector4d p2 = new Utils().projectPoint(p1);
+                            Constant.points3d.set(i2, new Vector4f((float) p2.x, (float) p2.y, (float) p2.z, (float) p2.w));
+
+                            Constant.points.add(new Vector3((float) p2.x, (float) p2.y, (float) p2.z));
+
+                            if (i == 0) {
+                                GDObject.addBasic(725, (float) (p2.x + 1000), (float) (p2.y + 1000), i2+1);
+                                GDObject.addKeyframe((float) (p2.x + 1000), (float) (p2.y + 1000), i2+1, 1, i, 1/Constant.fps *(Constant.skipFrame +1));
+                            } else {
+                                GDObject.addKeyframe((float) (p2.x + 1000), (float) (p2.y + 1000), i2+1, 1, i, 1/Constant.fps *(Constant.skipFrame +1));
+                            }
+                        }
+
+                        Constant.sortedPoints3d = points;
+                        Constant.sortedPoints3d.sort(new ZAxisComparator());
+                        Constant.sortedPoints3d = Constant.sortedPoints3d.reversed();
+
+                        List<Face> faces = new ReadOBJ().getFaceAnimation(OBJs.get(i), MTLs.get(i));
+                        List<Color> colors = new ArrayList<>();
+
+                        Constant.faces = faces;
+                        final int[] id = {0};
+                        AtomicInteger temp = new AtomicInteger(i);
+
+                        faces.forEach(f -> {
+                            if (!colors.contains(f.color)) {
+                                f.colorId = colors.size() + 1;
+                                colors.add(f.color);
+                            } else {
+                                for (int i1 = 0; i1 < colors.size(); i1++) {
+                                    Color color = colors.get(i1);
+                                    if (color == f.color) {
+                                        f.colorId = i1;
+                                    }
+                                }
+                            }
+
+                            id[0] +=1;
+                            if (f.points.size() == 3) {
+                                int index = Utils.getSortedIndex(Constant.points3d.get(f.points.get(0)));
+                                GDObject.addGradient((temp.get()*(1/Constant.fps * Constant.gameSpeed)*30), 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(2), id[0], index, f.colorId);
+                            } else {
+                                int index = Utils.getSortedIndex(Constant.points3d.get(f.points.get(0)));
+                                GDObject.addGradient((temp.get()*(1/Constant.fps * Constant.gameSpeed)*30), 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(3), id[0], index, f.colorId);
+                            }
+                        });
+
+
+                        for (int i2 = 0; i2 < colors.size(); i2++) {
+                            Color c = colors.get(i2);
+                            GDObject.addColor(i*10f, 200f, Math.round(c.red * 255), Math.round(c.green * 255), Math.round(c.blue * 255), i2+1);
+                        }
+
+                        Constant.points3d = new ArrayList<>();
+                        Constant.faces = new ArrayList<>();
+                        Constant.points = new ArrayList<>();
+                    } else {
+                        counter+=1;
+                    }
+                    first = false;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+
+            GDObject.send();
+
+            GDObject.request = new ArrayList<>();
+        }
+    }
+
+    public void GenerateAnimMove() throws FileNotFoundException {
+        List<File> OBJs = new ArrayList<>();
+        List<File> MTLs = new ArrayList<>();
+        List<Vector4f> oldPoint = new ArrayList<>();
+
+        for (int i = 0; i < Constant.obj.listFiles().length; i++) {
+            File f = Constant.obj.listFiles()[i];
+            if (f.getAbsolutePath().endsWith(".obj")) {
+                OBJs.add(f);
+            }
+        }
+
+        for (int i = 0; i < Constant.mtl.listFiles().length; i++) {
+            File f = Constant.mtl.listFiles()[i];
+            if (f.getAbsolutePath().endsWith(".mtl")) {
+                MTLs.add(f);
+            }
+        }
+
+        if (Constant.obj.isDirectory()) {
+            if (Constant.hasMTL && !Constant.mtl.isDirectory()) {
+                return;
+            }
+            for (int i = 0; i < OBJs.size(); i++) {
+                List<Vector4f> points = new ReadOBJ().getPoints(OBJs.get(i));
+                Constant.points3d = points;
+                for (int i2 = 0; i2 < points.size(); i2++) {
+                    Vector4f p = points.get(i2);
+
+                    Vector3d p1 = new Vector3d(p.x, p.z, p.y);
+                    Vector4d p2 = new Utils().projectPoint(p1);
+                    Constant.points3d.set(i, new Vector4f((float) p2.x, (float) p2.y, (float) p2.z, (float) p2.w));
+
+                    Constant.points.add(new Vector3((float) p2.x, (float) p2.y, (float) p2.z));
+
+                    if (i == 0) {
+                        GDObject.addBasic(725, (float) p2.x, (float) p2.y, i2+1);
+                    } else {
+                        Vector3d p1old = new Vector3d(oldPoint.get(i2).x, oldPoint.get(i2).z, oldPoint.get(i2).y);
+                        Vector4d p2old = new Utils().projectPoint(p1old);
+
+                        GDObject.addMove(i*10f, i2+1, (float) (p2old.x - p2.x), (float) (p2old.y - p2.y), 0f);
+                    }
+                }
+
+                oldPoint = points;
+
+                List<Face> faces = new ReadOBJ().getFaceAnimation(OBJs.get(i), MTLs.get(i));
+
+                List<Color> colors = new ArrayList<>();
+
+                Constant.faces = faces;
+                final int[] id = {0};
+                AtomicInteger temp = new AtomicInteger(i);
+
+                faces.forEach(f -> {
+                    if (!colors.contains(f.color)) {
+                        f.colorId = colors.size() + 1;
+                        colors.add(f.color);
+                    }
+
+                    id[0] +=1;
+                    if (f.points.size() == 3) {
+                        GDObject.addGradient(temp.get()*10f, 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(2), id[0], Math.round(Utils.getZ(f) * 10000), f.colorId);
+                    } else {
+                        GDObject.addGradient(temp.get()*10f, 100f, 0, f.points.get(0), f.points.get(1), f.points.get(2), f.points.get(3), id[0], Math.round(Utils.getZ(f) * 10000), f.colorId);
+                    }
+                });
+
+
+                for (int i2 = 0; i2 < colors.size(); i2++) {
+                    Color c = colors.get(i2);
+                    GDObject.addColor(i*10f, 200f, Math.round(c.red * 255), Math.round(c.green * 255), Math.round(c.blue * 255), i2+1);
+                }
+
+                Constant.points3d = new ArrayList<>();
+                Constant.faces = new ArrayList<>();
+                Constant.points = new ArrayList<>();
+            }
+
+            GDObject.send();
+
+            GDObject.request = new ArrayList<>();
+        }
+
+    }
+}

+ 34 - 0
src/main/java/xyz/tbvns/Generate/ReadMTL.java

@@ -0,0 +1,34 @@
+package xyz.tbvns.Generate;
+
+import xyz.tbvns.Object.Color;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ReadMTL {
+    public static HashMap<String, Color> getColors(File file) throws FileNotFoundException {
+        BufferedReader reader = new BufferedReader(new FileReader(file));
+        HashMap<String, Color> materials = new HashMap<>();
+        AtomicReference<String> name = new AtomicReference<>("");
+
+        reader.lines().forEach(l -> {
+            if (l.startsWith("newmtl")) {
+                String[] s = l.split(" ");
+                name.set(s[1]);
+            } else if (l.startsWith("Kd")) {
+                String[] s = l.split(" ");
+                float r = Float.parseFloat(s[1]);
+                float g = Float.parseFloat(s[2]);
+                float b = Float.parseFloat(s[3]);
+
+                materials.put(name.get(), new Color(r, g, b));
+            }
+        });
+
+        return materials;
+    }
+}

+ 136 - 0
src/main/java/xyz/tbvns/Generate/ReadOBJ.java

@@ -0,0 +1,136 @@
+package xyz.tbvns.Generate;
+
+import org.joml.Vector4f;
+import xyz.tbvns.Constant;
+import xyz.tbvns.Object.Color;
+import xyz.tbvns.Object.Face;
+import xyz.tbvns.Object.Vector3;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class ReadOBJ {
+    public ArrayList<Vector4f> getPoints(File OBJFile) throws FileNotFoundException {
+        BufferedReader reader = new BufferedReader(new FileReader(OBJFile));
+        ArrayList<Vector4f> Points = new ArrayList<>();
+        reader.lines().forEach(l -> {
+            if (l.startsWith("v ")) {
+                String substring = l.substring(2);
+                String[] cord = substring.split(" ");
+                Vector4f vector4f = new Vector4f();
+                vector4f.x = Float.parseFloat(cord[0]);
+                vector4f.y = Float.parseFloat(cord[1]);
+                vector4f.z = Float.parseFloat(cord[2]);
+                Points.add(vector4f);
+            }
+        });
+        return Points;
+    }
+
+    public ArrayList<Face> getFace(File OBJFile) throws FileNotFoundException {
+        BufferedReader reader = new BufferedReader(new FileReader(OBJFile));
+        ArrayList<Face> Faces = new ArrayList<>();
+        AtomicReference<HashMap<String, Color>> materials = new AtomicReference<>(new HashMap<>());
+        AtomicReference<Color> color = new AtomicReference<>(new Color(0, 0, 0));
+        reader.lines().forEach(l -> {
+            if (Constant.hasMTL) {
+                try {
+                    materials.set(ReadMTL.getColors(Constant.mtl));
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            if (Constant.hasMTL) {
+                if (l.startsWith("usemtl")) {
+                    color.set(materials.get().get(l.split(" ")[1]));
+                }
+            }
+            if (l.startsWith("f")) {
+                Face face = new Face();
+                String substring = l.substring(2);
+                String[] cord = substring.split(" ");
+                if (cord.length == 3) {
+                    for (int i = 0; i < cord.length; i++) {
+                        String id = cord[i].split("/")[0];
+                        face.points.add(Integer.valueOf(id));
+                    }
+                    if (Constant.hasMTL) {
+                        face.color = color.get();
+                    }
+                    Faces.add(face);
+                } else if (cord.length == 4) {
+                    for (int i = 0; i < cord.length; i++) {
+                        String id = cord[i].split("/")[0];
+                        face.points.add(Integer.valueOf(id));
+                    }
+                    face.Has4Point = true;
+
+                    if (Constant.hasMTL) {
+                        face.color = color.get();
+                    }
+
+                    Faces.add(face);
+                } else {
+                    System.err.println("A face had more than 4 point, ignoring it");
+                }
+            }
+        });
+        return Faces;
+    }
+
+    public ArrayList<Face> getFaceAnimation(File OBJFile, File MTLFile) throws FileNotFoundException {
+        BufferedReader reader = new BufferedReader(new FileReader(OBJFile));
+        ArrayList<Face> Faces = new ArrayList<>();
+        AtomicReference<HashMap<String, Color>> materials = new AtomicReference<>(new HashMap<>());
+        AtomicReference<Color> color = new AtomicReference<>(new Color(0, 0, 0));
+        reader.lines().forEach(l -> {
+            if (Constant.hasMTL) {
+                try {
+                    materials.set(ReadMTL.getColors(MTLFile));
+                } catch (FileNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            if (Constant.hasMTL) {
+                if (l.startsWith("usemtl")) {
+                    color.set(materials.get().get(l.split(" ")[1]));
+                }
+            }
+            if (l.startsWith("f")) {
+                Face face = new Face();
+                String substring = l.substring(2);
+                String[] cord = substring.split(" ");
+                if (cord.length == 3) {
+                    for (int i = 0; i < cord.length; i++) {
+                        String id = cord[i].split("/")[0];
+                        face.points.add(Integer.valueOf(id));
+                    }
+                    if (Constant.hasMTL) {
+                        face.color = color.get();
+                    }
+                    Faces.add(face);
+                } else if (cord.length == 4) {
+                    for (int i = 0; i < cord.length; i++) {
+                        String id = cord[i].split("/")[0];
+                        face.points.add(Integer.valueOf(id));
+                    }
+                    face.Has4Point = true;
+
+                    if (Constant.hasMTL) {
+                        face.color = color.get();
+                    }
+
+                    Faces.add(face);
+                } else {
+                    System.err.println("A face had more than 4 point, ignoring it");
+                }
+            }
+        });
+        return Faces;
+    }
+}

+ 92 - 0
src/main/java/xyz/tbvns/GeometryDash/GDObject.java

@@ -0,0 +1,92 @@
+package xyz.tbvns.GeometryDash;
+
+import xyz.tbvns.Constant;
+import xyz.tbvns.WebSocket.WSClient;
+
+import java.net.URI;
+import java.util.ArrayList;
+
+public class GDObject {
+    public static ArrayList<String> request = new ArrayList<>();
+
+    public static void addBasic(Integer objID, Float posX, Float posY) {
+        String JSON =
+                "{\n" +
+                "  \"action\": \"ADD\",\n" +
+                "  \"objects\": \"1," + objID + ",2," + posX + ",3," + posY + "\"\n" +
+                "}";
+        request.add(JSON);
+    }
+
+    public static void addBasic(Integer objID, Float posX, Float posY, Integer groupe) {
+        String JSON =
+                "{\n" +
+                "  \"action\": \"ADD\",\n" +
+                "  \"objects\": \"1," + objID + ",2," + posX + ",3," + posY + ",57," + groupe + "\"\n" +
+                "}";
+        request.add(JSON);
+    }
+
+    //public static void addGradient(Float posX, Float posY, Integer groupe) {
+    //    String JSON =
+    //            "{\n" +
+    //                    "  \"action\": \"ADD\",\n" +
+    //                    "  \"objects\": \"1," + 2903 + ",2," + posX + ",3," + posY + ",57," + groupe + "\"\n" +
+    //                    "}";
+    //    new WSClient(URI.create("ws://127.0.0.1:1313"), JSON).run();
+    //}
+
+    public static void addMove(Float time, Integer Groupe, Float moveX, Float moveY, Float moveTime) {
+        String JSON =
+                "{\n" +
+                "  \"action\": \"ADD\",\n" +
+                "  \"objects\": \"1," + 901 + ",2," + time + ",3," + -1000 + ",10," + moveTime + ",28," + moveX + ",29," + moveY +",51," + Groupe + "\"\n" +
+                "}";
+        request.add(JSON);
+    }
+
+    public static void addKeyframeTrigger() {
+        String JSON =
+                "{\n" +
+                        "  \"action\": \"ADD\",\n" +
+                        "  \"objects\": \"1," + 3033 + ",2," + 0 + ",3," + 150 + ",76," + 9999 + ",520," + 1 + ",521," + 1 + ",545," + 1 + ",522," + 1 + ",523," + 1 + ",546," + 1 + "\"\n" +
+                        "}";
+        request.add(JSON);
+    }
+
+    public static void addGradient(Float posX, Float posY, Integer groupe, int p1, int p2 ,int p3, int p4, int id, int zorder, int colorID) {
+        String JSON =
+                "{\n" +
+                        "  \"action\": \"ADD\",\n" +
+                        "  \"objects\": \"1,2903,174,0,202,1,456,1,208,0,508,0,25," + zorder + ",209," + id + ",2," + posX + ",3," + posY + ",57," + groupe + ",203, " + p1 + ",204, " + p2 + ",205, " + p4 + ",206," + p3 + ",207,1,21," + colorID + ",22," + colorID + "\"\n" +
+                        "}";
+        request.add(JSON);
+    }
+
+    public static void addColor(Float posX, Float posY, int r, int g, int b, int targetColor) {
+        String JSON =
+                "{\n" +
+                        "  \"action\": \"ADD\",\n" +
+                        "  \"objects\": \"1,899,2," + posX + ",3," + posY + ",7," + r + ",8," + g + ",9," + b + ",23," + targetColor + "\"\n" +
+                        "}";
+        request.add(JSON);
+    }
+
+    public static void addKeyframe(Float posX, Float posY, int groupeID, int curve, int order, float duration) {
+        String JSON =
+                   "{\n" +
+                   "  \"action\": \"ADD\",\n" +
+                   "  \"objects\": \"1,3032,2," + posX + ",3," + posY + ",51," + groupeID + ",373," + groupeID + ",57," + 9999 + ",378, " + curve + ",374," + order + ",10," + duration + "\"\n" +
+                   "}";
+        request.add(JSON);
+    }
+
+    public static void send() {
+        new WSClient(URI.create("ws://127.0.0.1:1313"), request).run();
+        try {
+            Thread.sleep(Constant.placeDelay);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 67 - 0
src/main/java/xyz/tbvns/JavaFX/FXAbout.java

@@ -0,0 +1,67 @@
+package xyz.tbvns.JavaFX;
+
+import javafx.fxml.FXML;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Pane;
+import javafx.scene.web.WebView;
+import javafx.stage.Stage;
+import xyz.tbvns.Main;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class FXAbout {
+    static int height = 350;
+    static int width = 350;
+    static String title = "About GD3D";
+    public static void show() {
+        try {
+            Stage stage = new Stage();
+
+            stage.setTitle(title);
+            stage.setResizable(false);
+            stage.setMaxHeight(height);
+            stage.setMinHeight(height);
+            stage.setMaxWidth(width);
+            stage.setMinWidth(width);
+
+            Pane pane = new Pane();
+            Scene scene = new Scene(pane, width, height);
+            scene.setRoot(pane);
+
+            WebView webView = new WebView();
+
+            webView.getEngine().load(Main.class.getResource("/About.html").toExternalForm());
+            webView.setMaxWidth(stage.getMaxWidth());
+            webView.setZoom(0.8);
+
+            Button button = new Button("Close");
+            button.setMinWidth(60);
+            button.setMaxWidth(60);
+            button.setLayoutY(pane.getHeight() - button.getHeight() - 30);
+            button.setLayoutX(pane.getWidth() / 2 - button.getMaxWidth() / 2);
+
+            button.setOnAction(actionEvent -> {
+                stage.close();
+            });
+
+            pane.getChildren().add(webView);
+            pane.getChildren().add(button);
+
+            stage.setScene(scene);
+            stage.show();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            FXWarning.error("Error !", "Something went wrong:\n" + e.getMessage(), 500, 100);
+        }
+    }
+}

+ 88 - 0
src/main/java/xyz/tbvns/JavaFX/FXBPMtoFPS.java

@@ -0,0 +1,88 @@
+package xyz.tbvns.JavaFX;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+import xyz.tbvns.Maths.BPM;
+import xyz.tbvns.Utils;
+
+public class FXBPMtoFPS {
+    public static void show() {
+        Stage stage = new Stage();
+
+        stage.setTitle("BPM to FPS converter");
+        stage.setResizable(false);
+        stage.setMaxHeight(160);
+        stage.setMinHeight(160);
+        stage.setMaxWidth(100);
+        stage.setMinWidth(100);
+
+        Pane pane = new Pane();
+        Scene scene = new Scene(pane, stage.getMaxWidth(), stage.getMaxHeight());
+        scene.setRoot(pane);
+
+        FlowPane flowPane = new FlowPane();
+        flowPane.setPrefSize(scene.getWidth(), scene.getHeight());
+        flowPane.alignmentProperty().set(Pos.TOP_LEFT);
+        flowPane.setPadding(new Insets(5));
+
+        flowPane.getChildren().add(new Label("BPM:"));
+        TextField bpm = new TextField("80");
+        bpm.setPrefWidth(scene.getWidth() - flowPane.getPadding().getBottom() * 2);
+        bpm.textProperty().addListener((observableValue, s, t1) -> Utils.fixToNumber(bpm, s, t1));
+        flowPane.getChildren().add(bpm);
+        flowPane.getChildren().add(new Label("Beats per bar:"));
+        TextField bpb = new TextField("4");
+        bpb.textProperty().addListener((observableValue, s, t1) -> Utils.fixToNumber(bpb, s, t1));
+        bpb.setPrefWidth(scene.getWidth() - flowPane.getPadding().getBottom() * 2);
+        flowPane.getChildren().add(bpb);
+
+        pane.getChildren().add(flowPane);
+
+        Button button = new Button("Close");
+        button.setMinWidth(60);
+        button.setMaxWidth(60);
+        button.setLayoutY(pane.getHeight() - button.getHeight() - 30);
+        button.setLayoutX(pane.getWidth() / 2 - button.getMaxWidth() / 2);
+
+        Button calc = new Button("Calculate");
+        calc.setMinWidth(80);
+        calc.setMaxWidth(80);
+        calc.setLayoutY(pane.getHeight() - calc.getHeight() - 60);
+        calc.setLayoutX(pane.getWidth() / 2 - calc.getMaxWidth() / 2);
+
+        calc.setOnAction(actionEvent -> {
+            try {
+                int BPMint = Integer.parseInt(bpm.getText());
+                int BPBint = Integer.parseInt(bpb.getText());
+
+                BPM BPM = new BPM(BPMint, BPBint);
+
+                String result =
+                        "Best fps: " + BPM.toFPS() + "\n" +
+                        "Minimal FPS: " + BPM.toMinimalFPS();
+
+                FXWarning.info("BPM converter result", result, 300, 100);
+
+            } catch (NumberFormatException e) {
+                FXWarning.error("Error !", "Oops, something went wrong: \n" + e.getMessage(), 300, 100);
+            }
+        });
+
+        button.setOnAction(actionEvent -> {
+            stage.close();
+        });
+
+        pane.getChildren().add(button);
+        pane.getChildren().add(calc);
+
+        stage.setScene(scene);
+        stage.show();
+    }
+}

+ 93 - 0
src/main/java/xyz/tbvns/JavaFX/FXWarning.java

@@ -0,0 +1,93 @@
+package xyz.tbvns.JavaFX;
+
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Pane;
+import javafx.stage.Stage;
+
+public class FXWarning {
+    public static void error(String title, String message, int width, int height) {
+        Stage stage = new Stage();
+
+        stage.setTitle(title);
+        stage.setResizable(false);
+        stage.setMaxHeight(height);
+        stage.setMinHeight(height);
+        stage.setMaxWidth(width);
+        stage.setMinWidth(width);
+
+        Pane pane = new Pane();
+        Scene scene = new Scene(pane, width, height);
+        scene.setRoot(pane);
+
+        Image icon = new Image(FXWarning.class.getResourceAsStream("/WarnFX/error.png"), 50, 100, true, false);
+        ImageView view = new ImageView(icon);
+        view.setLayoutY(20);
+        view.setLayoutX(20);
+
+        Label label = new Label(message);
+        label.setLayoutX(80);
+        label.setLayoutY(20);
+
+        Button button = new Button("Close");
+        button.setMinWidth(60);
+        button.setMaxWidth(60);
+        button.setLayoutY(pane.getHeight() - button.getHeight() - 30);
+        button.setLayoutX(pane.getWidth() / 2 - button.getMaxWidth() / 2);
+
+        button.setOnAction(actionEvent -> {
+            stage.close();
+        });
+
+        pane.getChildren().add(label);
+        pane.getChildren().add(view);
+        pane.getChildren().add(button);
+
+        stage.setScene(scene);
+        stage.show();
+    }
+
+    public static void info(String title, String message, int width, int height) {
+        Stage stage = new Stage();
+
+        stage.setTitle(title);
+        stage.setResizable(false);
+        stage.setMaxHeight(height);
+        stage.setMinHeight(height);
+        stage.setMaxWidth(width);
+        stage.setMinWidth(width);
+
+        Pane pane = new Pane();
+        Scene scene = new Scene(pane, width, height);
+        scene.setRoot(pane);
+
+        Image icon = new Image(FXWarning.class.getResourceAsStream("/WarnFX/info.png"), 70, 100, true, false);
+        ImageView view = new ImageView(icon);
+        view.setLayoutY(7);
+        view.setLayoutX(7);
+
+        Label label = new Label(message);
+        label.setLayoutX(80);
+        label.setLayoutY(20);
+
+        Button button = new Button("Close");
+        button.setMinWidth(60);
+        button.setMaxWidth(60);
+        button.setLayoutY(pane.getHeight() - button.getHeight() - 30);
+        button.setLayoutX(pane.getWidth() / 2 - button.getMaxWidth() / 2);
+
+        button.setOnAction(actionEvent -> {
+            stage.close();
+        });
+
+        pane.getChildren().add(label);
+        pane.getChildren().add(view);
+        pane.getChildren().add(button);
+
+        stage.setScene(scene);
+        stage.show();
+    }
+}

+ 595 - 0
src/main/java/xyz/tbvns/JavaFX/FXui.java

@@ -0,0 +1,595 @@
+package xyz.tbvns.JavaFX;
+
+import javafx.application.Application;
+import javafx.application.HostServices;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.*;
+import javafx.scene.layout.*;
+import javafx.stage.DirectoryChooser;
+import javafx.stage.FileChooser;
+import javafx.stage.Stage;
+import xyz.tbvns.Constant;
+import xyz.tbvns.Generate.Generate3D;
+import xyz.tbvns.Main;
+import xyz.tbvns.Utils;
+
+import java.io.File;
+
+public class FXui extends Application {
+    static Stage stage;
+    ObservableList<Node> objects;
+
+    @Override
+    public void start(Stage stage) throws Exception {
+        Pane pane = new Pane();
+        stage.setScene(new Scene(pane));
+
+        this.stage = stage;
+
+        stage.setOnCloseRequest(windowEvent -> {
+            Constant.shouldClose = true;
+        });
+
+        stage.setMinWidth(500);
+        stage.setMinHeight(500);
+        stage.setMaxWidth(500);
+        stage.setMaxHeight(500);
+        stage.setResizable(false);
+        stage.setTitle("GD3D - Open source 3d model importer for Geometry dash");
+
+        pane.setPrefSize(stage.getMaxWidth(), stage.getMaxHeight());
+
+        objects = pane.getChildren();
+        objects.add(getPane.getBar());
+        objects.add(getPane.files());
+        objects.add(getPane.settings());
+        objects.add(getPane.status());
+        objects.add(getPane.camera());
+        objects.add(getPane.frames());
+        objects.add(getPane.game());
+        objects.add(getPane.optimisation());
+        objects.add(getPane.other());
+        stage.show();
+    }
+
+    public HostServices getHostservices() {
+        return getHostServices();
+    }
+
+    public static class getPane {
+        public static Node files() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Files");
+            pane.setCollapsible(false);
+
+            VBox box = new VBox(20);
+            box.setSpacing(5);
+
+            CheckBox useAnimation = new CheckBox("Use animation");
+            useAnimation.setSelected(Constant.useAnimation);
+            Button selectOBJ = new Button("Select OBJ file");
+            Button selectMTL = new Button("Select MTL file");
+
+            if (Constant.useAnimation) {
+                selectOBJ.setText("Select OBJ folder");
+                selectMTL.setText("Select MTL folder");
+            }
+
+            useAnimation.setOnAction(actionEvent -> {
+                Constant.useAnimation = useAnimation.isSelected();
+                if (Constant.useAnimation) {
+                    selectOBJ.setText("Select OBJ folder");
+                    selectMTL.setText("Select MTL folder");
+                } else {
+                    selectOBJ.setText("Select OBJ file");
+                    selectMTL.setText("Select MTL file");
+                }
+            });
+
+            selectOBJ.setOnAction(actionEvent -> {
+                if (!Constant.useAnimation) {
+                    FileChooser fileChooser = new FileChooser();
+                    fileChooser.setTitle("GD3D - Select OBJ file");
+                    fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Wavefront OBJ (.obj)", "*.obj"));
+                    File file = fileChooser.showOpenDialog(FXui.stage);
+                    if (file != null) {
+                        Constant.obj = file;
+                        Constant.hasOBJ = true;
+                    }
+                } else {
+                    DirectoryChooser directoryChooser = new DirectoryChooser();
+                    directoryChooser.setTitle("GD3D - Select OBJ folder");
+                    File file = directoryChooser.showDialog(FXui.stage);
+                    if (file != null) {
+                        Constant.obj = file;
+                        Constant.hasOBJ = true;
+                    }
+                }
+            });
+
+            selectMTL.setOnAction(actionEvent -> {
+                if (!Constant.useAnimation) {
+                    FileChooser fileChooser = new FileChooser();
+                    fileChooser.setTitle("GD3D - Select MTL file");
+                    fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Material Library File (.mtl)", "*.mtl"));
+                    File file = fileChooser.showOpenDialog(FXui.stage);
+                    if (file != null) {
+                        Constant.mtl = file;
+                        Constant.hasMTL = true;
+                    }
+                } else {
+                    DirectoryChooser directoryChooser = new DirectoryChooser();
+                    directoryChooser.setTitle("GD3D - Select MTL folder");
+                    File file = directoryChooser.showDialog(FXui.stage);
+                    if (file != null) {
+                        Constant.mtl = file;
+                        Constant.hasMTL = true;
+                    }
+                }
+            });
+
+            box.getChildren().addAll(useAnimation, selectOBJ, selectMTL);
+
+            pane.setContent(box);
+
+            pane.setPrefSize(150, 135);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setLayoutX(15);
+            pane.setLayoutY(40);
+            return pane;
+        }
+        public static Node settings() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Settings");
+            pane.setCollapsible(false);
+
+            VBox box = new VBox(20);
+            box.setSpacing(5);
+
+            CheckBox followPlayer = new CheckBox("Follow player");
+            followPlayer.setSelected(Constant.followPlayer);
+            CheckBox useColor = new CheckBox("Use color");
+            useColor.setSelected(Constant.useColor);
+            CheckBox keyFrame = new CheckBox("Key frame");
+            keyFrame.setSelected(Constant.useKeyframe);
+
+            followPlayer.setOnAction(actionEvent -> Constant.followPlayer = followPlayer.isSelected());
+            useColor.setOnAction(actionEvent -> Constant.useColor = useColor.isSelected());
+            keyFrame.setOnAction(actionEvent -> Constant.useKeyframe = keyFrame.isSelected());
+
+            box.getChildren().addAll(followPlayer, useColor, keyFrame);
+
+            pane.setContent(box);
+
+            pane.setPrefSize(130, 135);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setLayoutX(180);
+            pane.setLayoutY(40);
+            return pane;
+        }
+        public static Label objStatus = new Label("Has obj: " + Constant.hasOBJ);
+        public static Label mtlStatus = new Label("Has mtl: " + Constant.hasMTL);
+        public static Label gdStatus = new Label("Is gd open: " + Constant.hasGD);
+        public static Label readyStatus = new Label("Is ready: " + Constant.isReady);
+
+        public static Node status() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Status");
+            pane.setCollapsible(false);
+
+            VBox box = new VBox(20);
+            box.setSpacing(5);
+
+            box.getChildren().addAll(objStatus, mtlStatus, gdStatus, readyStatus);
+
+            pane.setContent(box);
+
+            pane.setPrefSize(159, 135);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setLayoutX(325);
+            pane.setLayoutY(40);
+            return pane;
+        }
+
+        public static Node camera() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Camera");
+            pane.setCollapsible(false);
+
+            GridPane box = new GridPane();
+            box.setVgap(5);
+            box.setHgap(5);
+            box.setPrefSize(290, 140);
+
+            //Screen center
+            FlowPane screenCenter = new FlowPane();
+            screenCenter.setPrefSize(100, 40);
+            screenCenter.setMaxWidth(50);
+            Label screenCenterLabel = new Label("Screen center (x/y)");
+            TextField screenCenterX = new TextField();
+            screenCenterX.setPromptText("x");
+            screenCenterX.setPrefSize(50, 20);
+            screenCenterX.setText(String.valueOf(Constant.xScreenCenter));
+            TextField screenCenterY = new TextField();
+            screenCenterY.setPromptText("y");
+            screenCenterY.setPrefSize(50,20);
+            screenCenterY.setText(String.valueOf(Constant.yScreenCenter));
+            screenCenter.getChildren().addAll(screenCenterLabel, screenCenterX, screenCenterY);
+
+            screenCenterX.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToNumber(screenCenterX, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.xScreenCenter = Utils.safeParseInt(t1);
+            });
+
+            screenCenterY.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToNumber(screenCenterY, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.yScreenCenter = Utils.safeParseInt(t1);
+            });
+
+            //Model scale
+            FlowPane modelScale = new FlowPane();
+            modelScale.setPrefSize(100, 40);
+            modelScale.setMaxWidth(50);
+            Label modelScaleLabel = new Label("Model scale");
+            TextField modelScaleField = new TextField();
+            modelScaleField.setPromptText("Scale");
+            modelScaleField.setPrefSize(100, 20);
+            modelScaleField.setText(String.valueOf(Constant.modelScale));
+            modelScale.getChildren().addAll(modelScaleLabel, modelScaleField);
+
+            modelScaleField.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(modelScaleField, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.modelScale = Utils.safeParseFloat(t1);
+            });
+
+            //Screen position
+            FlowPane screenPosition = new FlowPane();
+            screenPosition.setPrefSize(150, 40);
+            screenPosition.setMaxWidth(150);
+            Label screenPositionLabel = new Label("Screen position (x/y/z)");
+            TextField screenPositionX = new TextField();
+            screenPositionX.setPromptText("x");
+            screenPositionX.setPrefSize(50, 20);
+            screenPositionX.setText(String.valueOf(Constant.screenPositionX));
+            TextField screenPositionY = new TextField();
+            screenPositionY.setPromptText("y");
+            screenPositionY.setPrefSize(50,20);
+            screenPositionY.setText(String.valueOf(Constant.screenPositionY));
+            TextField screenPositionZ = new TextField();
+            screenPositionZ.setPromptText("z");
+            screenPositionZ.setPrefSize(50,20);
+            screenPositionZ.setText(String.valueOf(Constant.screenPositionZ));
+            screenPosition.getChildren().addAll(screenPositionLabel, screenPositionX, screenPositionY, screenPositionZ);
+
+            screenPositionX.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(screenPositionX, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.screenPositionX = Utils.safeParseFloat(t1);
+            });
+            screenPositionY.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(screenPositionY, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.screenPositionY = Utils.safeParseFloat(t1);
+            });
+            screenPositionZ.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(screenPositionZ, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.screenPositionZ = Utils.safeParseFloat(t1);
+            });
+
+            //View angle
+            FlowPane viewAngle = new FlowPane();
+            viewAngle.setPrefSize(150, 40);
+            viewAngle.setMaxWidth(150);
+            Label viewAngleLabel = new Label("View angle (x/y/z)");
+            TextField viewAngleX = new TextField();
+            viewAngleX.setPromptText("x");
+            viewAngleX.setPrefSize(50, 20);
+            viewAngleX.setText(String.valueOf(Constant.viewAngleX));
+            TextField viewAngleY = new TextField();
+            viewAngleY.setPromptText("y");
+            viewAngleY.setPrefSize(50,20);
+            viewAngleY.setText(String.valueOf(Constant.viewAngleY));
+            TextField viewAngleZ = new TextField();
+            viewAngleZ.setPromptText("z");
+            viewAngleZ.setPrefSize(50,20);
+            viewAngleZ.setText(String.valueOf(Constant.viewAngleZ));
+            viewAngle.getChildren().addAll(viewAngleLabel, viewAngleX, viewAngleY, viewAngleZ);
+
+            viewAngleX.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(viewAngleX, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.viewAngleX = Utils.safeParseFloat(t1);
+            });
+            viewAngleY.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(viewAngleY, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.viewAngleY = Utils.safeParseFloat(t1);
+            });
+            viewAngleZ.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(viewAngleZ, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.viewAngleZ = Utils.safeParseFloat(t1);
+            });
+
+            box.add(screenCenter, 0, 0);
+            box.add(modelScale, 0, 1);
+            box.add(screenPosition, 1, 0);
+            box.add(viewAngle, 1, 1);
+
+            pane.setContent(box);
+
+            pane.setPrefSize(295, 140);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setLayoutX(15);
+            pane.setLayoutY(190);
+            return pane;
+        }
+
+        public static Node frames() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Frames");
+            pane.setPrefSize(160, 140);
+            pane.setMaxWidth(160);
+            pane.setMinWidth(160);
+            pane.setAlignment(Pos.CENTER);
+
+            GridPane box = new GridPane();
+            box.setPrefSize(140, 100);
+            box.setPadding(new Insets(10));
+            Label skipFrameLabel = new Label("Skip frame");
+            TextField skipFrame = new TextField();
+            skipFrame.setPromptText("frame");
+            skipFrame.setText(String.valueOf(Constant.skipFrame));
+            Label fpsLabel = new Label("FPS");
+            TextField FPS = new TextField();
+            FPS.setPromptText("FPS");
+            FPS.setText(String.valueOf(Constant.fps));
+
+            skipFrame.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToNumber(skipFrame, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.skipFrame = Utils.safeParseInt(t1);
+            });
+            FPS.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToFloat(FPS, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.fps = Utils.safeParseFloat(t1);
+            });
+
+            box.add(skipFrameLabel, 0, 0);
+            box.add(skipFrame, 0, 1);
+            box.add(fpsLabel, 0, 2);
+            box.add(FPS, 0, 3);
+            pane.setCollapsible(false);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setContent(box);
+            pane.setLayoutX(325);
+            pane.setLayoutY(190);
+
+            return pane;
+        }
+
+        public static Node game() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Game");
+
+            GridPane box = new GridPane();
+            Label gameSpeedLabel = new Label("Game speed");
+            String[] speed = {"Half", "Normal", "Double", "Triple", "Quadruple"};
+            ComboBox gameSpeed = new ComboBox(FXCollections.observableArrayList(speed));
+            gameSpeed.getSelectionModel().select(1);
+            Label startingGroupLabel = new Label("Starting Group");
+            TextField startingGroup = new TextField();
+            startingGroup.setPromptText("Group id");
+            startingGroup.setText(String.valueOf(Constant.startingGroup));
+
+            gameSpeed.setOnAction(actionEvent -> {
+                switch (gameSpeed.getSelectionModel().getSelectedIndex()) {
+                    case 0:
+                        Constant.gameSpeed = 8.4F;
+                        break;
+                    case 1:
+                        Constant.gameSpeed = 10.41667F;
+                        break;
+                    case 2:
+                        Constant.gameSpeed = 12.91667F;
+                        break;
+                    case 3:
+                        Constant.gameSpeed = 15.667F;
+                        break;
+                    case 4:
+                        Constant.gameSpeed = 19.2F;
+                }
+            });
+
+            startingGroup.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToNumber(startingGroup, s, t1);
+                if (t1.isBlank()) {
+                    t1 = "0";
+                }
+                Constant.fps = Utils.safeParseInt(t1);
+            });
+
+            startingGroupLabel.setMaxWidth(100);
+            gameSpeedLabel.setMaxWidth(100);
+            gameSpeed.setMaxWidth(100);
+            startingGroup.setMaxWidth(100);
+
+            box.add(gameSpeedLabel, 0, 0);
+            box.add(gameSpeed, 0, 1);
+            box.add(startingGroupLabel, 0, 2);
+            box.add(startingGroup, 0, 3);
+            pane.setCollapsible(false);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setContent(box);
+            pane.setLayoutX(15);
+            pane.setLayoutY(345);
+            pane.setPrefSize(130, 140);
+            return pane;
+        }
+
+        public static Node optimisation() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Optimisation");
+            pane.setPrefSize(100, 140);
+
+            FlowPane box = new FlowPane();
+            box.setPrefSize(100, 130);
+            Label triggersLabel = new Label("Triggers");
+            CheckBox optimizeColors = new CheckBox("Optimize colors");
+            CheckBox optimizeGradients = new CheckBox("Optimize Gradients");
+
+            optimizeColors.setOnAction(actionEvent -> {
+                Constant.optimizeColor = optimizeColors.isSelected();
+            });
+            optimizeGradients.setOnAction(actionEvent -> {
+                Constant.optimizeGradient = optimizeGradients.isSelected();
+            });
+
+            box.getChildren().addAll(triggersLabel, optimizeColors, optimizeGradients);
+            pane.setCollapsible(false);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setContent(box);
+            pane.setLayoutX(160);
+            pane.setLayoutY(345);
+
+            return pane;
+        }
+
+        public static Node other() {
+            TitledPane pane = new TitledPane();
+            pane.setText("Other");
+            pane.setPrefSize(145, 140);
+
+            GridPane box = new GridPane();
+            box.setPrefSize(100, 130);
+            Label placeDelayLabel = new Label("Place delay (ms)");
+            TextField placeDelay = new TextField();
+            placeDelay.setPromptText("Delay");
+            placeDelay.setText(String.valueOf(Constant.placeDelay));
+
+            placeDelay.textProperty().addListener((observableValue, s, t1) -> {
+                Utils.fixToNumber(placeDelay, s, t1);
+                Constant.placeDelay = Utils.safeParseInt(t1);
+            });
+
+            box.add(placeDelayLabel, 0, 0);
+            box.add(placeDelay, 0, 1);
+            pane.setCollapsible(false);
+            pane.setAlignment(Pos.TOP_CENTER);
+            pane.setContent(box);
+            pane.setLayoutX(340);
+            pane.setLayoutY(345);
+
+            return pane;
+        }
+
+        public static Node getBar() {
+            MenuBar bar = new MenuBar();
+            bar.setLayoutX(0);
+            bar.setLayoutY(0);
+            bar.setPrefSize(500, 25);
+
+            Menu actions = new Menu("Actions");
+            MenuItem actions1 = new MenuItem("Send to Geometry Dash");
+            MenuItem actions2 = new MenuItem("Save level string");
+
+            actions1.setOnAction(actionEvent -> {
+                try {
+                    if (Constant.isReady) {
+                        if (Constant.useKeyframe) {
+                            new Generate3D().GenerateAnimKeyframe();
+                        } else {
+                            new Generate3D().GenerateAnimMove();
+                        }
+                    } else {
+                        FXWarning.error("Error !", "The app is not ready !\n" +
+                                "Verify that:\n" +
+                                "- GD is opened in the editor with the WSLiveEditor mod\n" +
+                                "- You selected the OBJ file/folder\n" +
+                                "- You selected the MTL file/folder",
+                                450, 150);
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            });
+
+            actions.getItems().add(actions1);
+            actions.getItems().add(actions2);
+
+            Menu files = new Menu("Files");
+            files.getItems().add(new MenuItem("Save settings"));
+
+            Menu tools = new Menu("Tools");
+            MenuItem tools1 = new MenuItem("BPM to FPS");
+
+            tools1.setOnAction(actionEvent -> FXBPMtoFPS.show());
+
+            tools.getItems().add(tools1);
+
+            Menu help = new Menu("Help");
+            MenuItem help1 = new MenuItem("Wiki");
+            MenuItem help2 = new MenuItem("Issue");
+            MenuItem help3 = new MenuItem("Discord");
+
+            help1.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://github.com/tbvns/GD3D/wiki"));
+            help2.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://github.com/tbvns/GD3D/issues"));
+            help3.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://discord.gg/SYtmsjSMu6"));
+
+            help.getItems().addAll(help1, help2, help3);
+
+            Menu credits = new Menu("Credits");
+            MenuItem credits1 = new MenuItem("Github");
+            MenuItem credits2 = new MenuItem("Discord");
+            MenuItem credits3 = new MenuItem("Contributors");
+            MenuItem credits4 = new MenuItem("About");
+
+            credits1.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://github.com/tbvns/GD3D/"));
+            credits2.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://discord.gg/SYtmsjSMu6"));
+            credits3.setOnAction(actionEvent -> Main.ui.getHostservices().showDocument("https://github.com/tbvns/GD3D/graphs/contributors"));
+            credits4.setOnAction(actionEvent -> FXAbout.show());
+
+            credits.getItems().addAll(credits1, credits2, credits3, credits4);
+
+            bar.getMenus().addAll(actions, files, tools, help, credits);
+
+            return bar;
+        }
+    }
+
+    public void startApp() {
+        launch();
+    }
+}

+ 16 - 0
src/main/java/xyz/tbvns/Main.java

@@ -0,0 +1,16 @@
+package xyz.tbvns;
+
+import javafx.application.Platform;
+import xyz.tbvns.JavaFX.FXui;
+import xyz.tbvns.WebSocket.CheckGDConnection;
+
+public class Main {
+    public static FXui ui;
+    public static Thread update = new Thread(new CheckGDConnection());
+    public static void main(String[] args) {
+        update.start();
+
+        ui = new FXui();
+        ui.startApp();
+    }
+}

+ 27 - 0
src/main/java/xyz/tbvns/Maths/BPM.java

@@ -0,0 +1,27 @@
+package xyz.tbvns.Maths;
+
+public class BPM {
+    float BPM;
+    float BAR;
+    public BPM(float BPM, float BPMperBAR) {
+        this.BPM = BPM;
+        this.BAR = BPMperBAR;
+    }
+
+
+
+    public float toFPS() {
+        if (BAR == 0) {
+            BAR = 1;
+        }
+        return BPM/60;
+    }
+
+    public float toMinimalFPS() {
+        if (BAR == 0) {
+            BAR = 1;
+        }
+        return (BPM/60)/BAR;
+    }
+
+}

+ 23 - 0
src/main/java/xyz/tbvns/Object/Color.java

@@ -0,0 +1,23 @@
+package xyz.tbvns.Object;
+
+public class Color {
+    public Color(float r, float g, float b) {
+        this.red = r;
+        this.green = g;
+        this.blue = b;
+    }
+    public float red = 0;
+    public float green = 0;
+    public float blue = 0;
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof  Color) {
+            Color color = (Color) obj;
+            if (this.blue == color.blue && this.red == color.red && this.green == color.green) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 8 - 0
src/main/java/xyz/tbvns/Object/CompletedFace.java

@@ -0,0 +1,8 @@
+package xyz.tbvns.Object;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompletedFace extends Face {
+    public List<Vector3> points = new ArrayList<>();
+}

+ 11 - 0
src/main/java/xyz/tbvns/Object/Face.java

@@ -0,0 +1,11 @@
+package xyz.tbvns.Object;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Face {
+    public List<Integer> points = new ArrayList<>();
+    public boolean Has4Point = false;
+    public Color color = new Color(0, 0, 0);
+    public Integer colorId = 0;
+}

+ 9 - 0
src/main/java/xyz/tbvns/Object/Frame.java

@@ -0,0 +1,9 @@
+package xyz.tbvns.Object;
+
+import java.io.File;
+import java.util.List;
+
+public class Frame {
+    public File OBJ;
+    public File MTL;
+}

+ 15 - 0
src/main/java/xyz/tbvns/Object/Vector3.java

@@ -0,0 +1,15 @@
+package xyz.tbvns.Object;
+
+public class Vector3 {
+    public Vector3() {
+
+    }
+    public Vector3(float x, float y, float z) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+    public float x;
+    public float y;
+    public float z;
+}

+ 12 - 0
src/main/java/xyz/tbvns/Other/Colors.java

@@ -0,0 +1,12 @@
+package xyz.tbvns.Other;
+
+import xyz.tbvns.Object.Color;
+
+public class Colors {
+    public static final Color darkBackGround = new Color(0, 0, 0);
+    public static final Color topBar = new Color(62, 62, 62);
+    public static final Color title = new Color(255, 255, 255);
+    public static final Color objectBackground = new Color(35, 35, 35);
+    public static final Color hoverColor = new Color(0, 105, 131);
+    public static final Color clickColor = new Color(0, 198, 218);
+}

+ 12 - 0
src/main/java/xyz/tbvns/Other/ZAxisComparator.java

@@ -0,0 +1,12 @@
+package xyz.tbvns.Other;
+
+import org.joml.Vector4f;
+
+import java.util.Comparator;
+
+public class ZAxisComparator implements Comparator<Vector4f> {
+    @Override
+    public int compare(Vector4f o1, Vector4f o2) {
+        return Float.compare(o1.w, o2.w);
+    }
+}

+ 179 - 0
src/main/java/xyz/tbvns/Utils.java

@@ -0,0 +1,179 @@
+package xyz.tbvns;
+
+
+import javafx.scene.control.TextField;
+import org.joml.*;
+import org.locationtech.jts.geom.Coordinate;
+import xyz.tbvns.Object.Face;
+import xyz.tbvns.Object.Vector3;
+
+import java.io.File;
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class Utils {
+    public static int xScreenCenter = 320/2;
+    public static int yScreenCenter = 240/2;
+    public static Vector3d screenPosition = new Vector3d( 0, 0, 20 );
+    public static Vector3d viewAngle = new Vector3d( 180, 180, 90);
+    private static final double DEG_TO_RAD = 0.017453292;
+    public static double modelScale = 20;
+
+    public Vector4d projectPoint(Vector3d input) {
+        Matrix4d matrix4d = new Matrix4d();
+        matrix4d.perspective(Math.toRadians(2), 1, 0, -30);
+
+        matrix4d.translate(new Vector3d(0, 0, 20));
+        matrix4d.rotate(45, new Vector3d(1, 1, 0));
+
+        Vector4d point = matrix4d.transform(new Vector4d(input.x, input.y, input.z, 1));
+
+        point.x = (float) (point.x / point.w);
+        point.y = (float) (point.y / point.w);
+        point.z = (float) (point.z / point.w);
+
+        return point;
+    }
+
+    private static float getFacez(Face face) {
+        float facez;
+        if (face.Has4Point) {
+            facez = (Constant.points3d.get(face.points.get(0) -1).z + Constant.points3d.get(face.points.get(1) -1).z + Constant.points3d.get(face.points.get(2) -1).z + Constant.points3d.get(face.points.get(3) -1).z)/4;
+        } else {
+            facez = (Constant.points3d.get(face.points.get(0) -1).z + Constant.points3d.get(face.points.get(1) -1).z + Constant.points3d.get(face.points.get(2) -1).z)/3;
+        }
+        return facez;
+    }
+
+    public static float getZ(Face face) {
+        //TODO: REDO ALL OF THIS WITH MY SAVIOUR "JOML" !
+        //d = √[(x2 − x1)2 + (y2 − y1)2 + (z2 − z1)2]
+        List<Vector4f> points = new ArrayList<>();
+        face.points.forEach(p -> {
+            Vector4f point = Constant.points3d.get(p - 1);
+            points.add(point);
+        });
+
+        List<Double> dist = new ArrayList<>();
+
+        points.forEach(p -> {
+            dist.add((double) p.w);
+        });
+
+        double temp = 0;
+
+        for (Double d : dist) {
+            temp += d;
+            System.out.println(temp + " -- temp");
+        }
+
+        temp = temp / dist.size();
+        System.out.println(temp);
+
+        return (float) temp;
+    }
+
+    public static List<File> sort(List<File> files) {
+        HashMap<Integer, File> fileSorted = new HashMap<>();
+
+        files.forEach(file -> {
+            String name = file.getName();
+            name = name.replaceAll("[^0-9]+", "");
+            int id = Integer.parseInt(name);
+            fileSorted.put(id, file);
+        });
+
+        List<File> fileReturned = new ArrayList<>();
+
+        for (int i = 0; i < fileSorted.size(); i++) {
+            try {
+                if (fileSorted.get(i) != null) {
+                    fileReturned.add(fileSorted.get(i));
+                }
+            } catch (Exception e) {
+                //TODO: replace that code with some JavaFX
+
+                //JFrame frame = new JFrame("Warning:");
+                //JOptionPane.showMessageDialog(frame,
+                //        "The frame " + i + " is missing !",
+                //        "Warning !",
+                //        JOptionPane.WARNING_MESSAGE);
+            }
+        }
+
+        return fileReturned;
+    }
+
+    public static String fixToNumber(TextField textField, String s, String t1) {
+        textField.setText(t1.replaceAll("(?![-+]?[0-9]*)", ""));
+        if (t1.equalsIgnoreCase("-")) {
+            return t1 + "0";
+        }
+        try {
+            if (!t1.isBlank()) {
+                Integer.parseInt(t1);
+            }
+        } catch (Exception e) {
+            textField.setText(s);
+            if (t1.equalsIgnoreCase("")) {
+                textField.setText("");
+            }
+        }
+
+        return textField.getText();
+    }
+
+    public static String fixToFloat(TextField textField, String s, String t1) {
+        textField.setText(t1.replaceAll("(?![+-]?([0-9]+([.][0-9]*)?|[.][0-9]+))", ""));
+        if (t1.equalsIgnoreCase("-") || t1.equalsIgnoreCase(".")) {
+            return t1 + "0";
+        }
+        if (t1.endsWith(".")) {
+            t1 = t1 + "0";
+        }
+        try {
+            if (!t1.isBlank()) {
+                Float.parseFloat(t1);
+            }
+        } catch (Exception e) {
+            textField.setText(s);
+            if (t1.equalsIgnoreCase("")) {
+                textField.setText("");
+            }
+        }
+
+        return textField.getText();
+    }
+
+    public static int safeParseInt(String str) {
+        try {
+            return Integer.parseInt(str);
+        } catch (Exception e) {
+            return 0;
+        }
+    }
+
+    public static float safeParseFloat(String str) {
+        try {
+            return Float.parseFloat(str);
+        } catch (Exception e) {
+            return 0f;
+        }
+    }
+
+    public static int getSortedIndex(Vector4f vec) {
+        for (int i = 0; i < Constant.sortedPoints3d.size(); i++) {
+            Vector4f tempVec = Constant.sortedPoints3d.get(i);
+            if (tempVec == vec) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+}

+ 38 - 0
src/main/java/xyz/tbvns/WebSocket/CheckGDConnection.java

@@ -0,0 +1,38 @@
+package xyz.tbvns.WebSocket;
+
+import javafx.application.Platform;
+import xyz.tbvns.Constant;
+import xyz.tbvns.JavaFX.FXui;
+
+import java.net.URI;
+
+public class CheckGDConnection implements Runnable {
+    public void run() {
+        while (true) {
+            try {
+                Thread.sleep(1000);
+
+                if (Constant.hasGD && Constant.hasOBJ && Constant.hasMTL) {
+                    Constant.isReady = true;
+                } else {
+                    Constant.isReady = false;
+                }
+
+                Platform.runLater(() -> {
+                    FXui.getPane.gdStatus.setText("Has obj: " + Constant.hasOBJ);
+                    FXui.getPane.mtlStatus.setText("Has mtl: " + Constant.hasMTL);
+                    FXui.getPane.objStatus.setText("Is gd open: " + Constant.hasGD);
+                    FXui.getPane.readyStatus.setText("Is ready: " + Constant.isReady);
+                });
+
+                WSGDChecker client = new WSGDChecker(URI.create("ws://127.0.0.1:1313"));
+                client.run();
+                Thread.sleep(1000);
+                client.close();
+            } catch (Exception e) {}
+            if (Constant.shouldClose) {
+                break;
+            }
+        }
+    }
+}

+ 46 - 0
src/main/java/xyz/tbvns/WebSocket/WSClient.java

@@ -0,0 +1,46 @@
+package xyz.tbvns.WebSocket;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import xyz.tbvns.Constant;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WSClient extends WebSocketClient {
+    List<String> msg;
+    public WSClient(URI serverUri, ArrayList<String> message) {
+        super(serverUri);
+        msg = message;
+    }
+
+    @Override
+    public void onOpen(ServerHandshake serverHandshake) {
+        msg.forEach(message -> {
+            send(message);
+            try {
+                Thread.sleep(Constant.placeDelay);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+        close();
+    }
+
+    @Override
+    public void onMessage(String s) {
+
+    }
+
+    @Override
+    public void onClose(int i, String s, boolean b) {
+
+    }
+
+    @Override
+    public void onError(Exception e) {
+
+    }
+}

+ 38 - 0
src/main/java/xyz/tbvns/WebSocket/WSGDChecker.java

@@ -0,0 +1,38 @@
+package xyz.tbvns.WebSocket;
+
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.handshake.ServerHandshake;
+import xyz.tbvns.Constant;
+
+import java.net.URI;
+
+public class WSGDChecker extends WebSocketClient {
+    public WSGDChecker(URI serverUri) {
+        super(serverUri);
+    }
+
+    @Override
+    public void onOpen(ServerHandshake serverHandshake) {
+        if (!Constant.hasGD) {
+            Constant.hasGD = true;
+        }
+        close();
+    }
+
+    @Override
+    public void onMessage(String s) {
+
+    }
+
+    @Override
+    public void onClose(int i, String s, boolean b) {
+
+    }
+
+    @Override
+    public void onError(Exception e) {
+        if (Constant.hasGD) {
+            Constant.hasGD = false;
+        }
+    }
+}

+ 15 - 0
src/main/resources/About.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>About</title>
+</head>
+<body>
+<img src="GD3Dbanner.png" width="100%">
+<p style="text-align: center;">GD3D is a free and open source 3d model importer for geometry dash, it even supports animation !</p>
+<p style="text-align: center;">If you have any issue, please report them on GitHub</p>
+<p style="text-align: center;">If you need help, you can join the Discord (in the Credits tab), or open an issue on GitHub</p>
+<p style="text-align: center;">This project is in really early development,</p>
+<p style="text-align: center; text-decoration: underline; font-weight: bold; color: RED;">IT IS NOT READY FOR PRODUCTION YET. <br> IT WILL DESTROY YOU LEVEL !</p>
+</body>
+</html>

+ 0 - 0
src/main/resources/Css/Pane.css


BIN
src/main/resources/GD3D.png


BIN
src/main/resources/GD3Dbanner.png


+ 4 - 0
src/main/resources/META-INF/MANIFEST.MF

@@ -0,0 +1,4 @@
+Manifest-Version: 1.0
+Build-Jdk-Spec: 21
+Main-Class: xyz.tbvns.Main
+

+ 11 - 0
src/main/resources/Random.txt

@@ -0,0 +1,11 @@
+Player speed in blocks per second.
+Half speed: 8.4
+Normal speed: 10.41667   ->   0.43402791666 /frame 24 fps
+Double speed: 12.91667
+Triple speed: 15.667
+Quadruple speed: 19.2
+
+TODO: Add the code for the color optimisation feature
+TODO: Add the code for the gradient optimisation feature
+TODO: Add Loop option for loop animation
+TODO: fixe faces not being placed correctly

BIN
src/main/resources/WarnFX/error.png


BIN
src/main/resources/WarnFX/info.png