diff --git a/block-bench-addon/README.md b/block-bench-addon/README.md new file mode 100644 index 0000000..663d05c --- /dev/null +++ b/block-bench-addon/README.md @@ -0,0 +1,4 @@ +Block Bench addon for EntityLib + +Gives complete support of making block bench bedrock compatible models in EntityLib, with animations. +This is a direct port of the WorldSeedEntityEngine made by my good pal iam https://github.com/AtlasEngineCa/WorldSeedEntityEngine/blob/master. Permission was granted to port this repository to EntityLib by iam himself. diff --git a/model-engine-addon/build.gradle.kts b/block-bench-addon/build.gradle.kts similarity index 89% rename from model-engine-addon/build.gradle.kts rename to block-bench-addon/build.gradle.kts index a4bd5c3..1a44310 100644 --- a/model-engine-addon/build.gradle.kts +++ b/block-bench-addon/build.gradle.kts @@ -11,9 +11,9 @@ repositories { } dependencies { - // compileOnly("com.ticxo.modelengine:ModelEngine:R4.0.4") api(project(":api")) - + compileOnly(libs.packetevents.api) + compileOnly(libs.gson) implementation("commons-io:commons-io:2.11.0") implementation("org.zeroturnaround:zt-zip:1.8") diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelEngine.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelEngine.java new file mode 100644 index 0000000..793ad04 --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelEngine.java @@ -0,0 +1,113 @@ +package me.tofaa.entitylib.bb; + +import com.github.retrooper.packetevents.protocol.component.ComponentTypes; +import com.github.retrooper.packetevents.protocol.item.ItemStack; +import com.github.retrooper.packetevents.protocol.item.type.ItemType; +import com.github.retrooper.packetevents.protocol.item.type.ItemTypes; +import com.github.retrooper.packetevents.util.Vector3d; +import com.google.gson.*; +import me.tofaa.entitylib.bb.mql.MQLPoint; + +import javax.json.JsonNumber; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Optional; + +public final class ModelEngine { + + public final static HashMap offsetMappings = new HashMap<>(); + public final static HashMap diffMappings = new HashMap<>(); + static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + private final static HashMap> blockMappings = new HashMap<>(); + + private static Path modelPath; + private static ItemType modelMaterial = ItemTypes.MAGMA_CREAM; + + /** + * Loads the model from the given path + * + * @param mappingsData mappings file created by model parser + * @param modelPath path of the models + */ + public static void loadMappings(Reader mappingsData, Path modelPath) { + JsonObject map = GSON.fromJson(mappingsData, JsonObject.class); + ModelEngine.modelPath = modelPath; + + blockMappings.clear(); + offsetMappings.clear(); + diffMappings.clear(); + ModelLoader.clearCache(); + + map.entrySet().forEach(entry -> { + HashMap keys = new HashMap<>(); + + entry.getValue().getAsJsonObject() + .get("id") + .getAsJsonObject() + .entrySet() + .forEach(id -> keys.put(id.getKey(), generateBoneItem(id.getValue().getAsInt()))); + + blockMappings.put(entry.getKey(), keys); + offsetMappings.put(entry.getKey(), getPos(entry.getValue().getAsJsonObject().get("offset").getAsJsonArray()).orElse(Vector3d.zero())); + diffMappings.put(entry.getKey(), getPos(entry.getValue().getAsJsonObject().get("diff").getAsJsonArray()).orElse(Vector3d.zero())); + }); + } + + private static ItemStack generateBoneItem(int model_id) { + return ItemStack.builder() + .type(modelMaterial) + .component(ComponentTypes.CUSTOM_MODEL_DATA, model_id) + .build(); + } + + public static HashMap getItems(String model, String name) { + return blockMappings.get(model + "/" + name); + } + + public static Path getGeoPath(String id) { + return modelPath.resolve(id).resolve("model.geo.json"); + } + + public static Path getAnimationPath(String id) { + return modelPath.resolve(id).resolve("model.animation.json"); + } + + public static Optional getPos(JsonElement pivot) { + if (pivot == null) return Optional.empty(); + else { + JsonArray arr = pivot.getAsJsonArray(); + return Optional.of(new Vector3d(arr.get(0).getAsDouble(), arr.get(1).getAsDouble(), arr.get(2).getAsDouble())); + } + } + + public static Optional getMQLPos(JsonElement pivot) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + if (pivot == null) return Optional.empty(); + else if (pivot instanceof JsonObject) { + JsonObject obj = (JsonObject) pivot; + return Optional.of(new MQLPoint(obj)); + } else if (pivot instanceof JsonNumber) { + JsonNumber num = (JsonNumber) pivot; + return Optional.of(new MQLPoint(num.doubleValue(), num.doubleValue(), num.doubleValue())); + } else { + return Optional.empty(); + } + } + + public static ItemType getModelMaterial() { + return modelMaterial; + } + + public static void setModelMaterial(ItemType modelMaterial) { + ModelEngine.modelMaterial = modelMaterial; + } + + public static Optional getMQLPos(JsonArray arr) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + if (arr == null) return Optional.empty(); + else { + return Optional.of(new MQLPoint(arr)); + } + } +} + diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelLoader.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelLoader.java new file mode 100644 index 0000000..9845299 --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelLoader.java @@ -0,0 +1,123 @@ +package me.tofaa.entitylib.bb; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import me.tofaa.entitylib.bb.animation.FrameProvider; + +import java.io.*; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ModelLoader { + protected static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + private static final Map loadedAnimations = new HashMap<>(); + private static final Map loadedModels = new HashMap<>(); + + // -> + private static final Map> interpolationTranslateCache = new HashMap<>(); + private static final Map> interpolationRotateCache = new HashMap<>(); + private static final Map> interpolationScaleCache = new HashMap<>(); + + public static void clearCache() { + interpolationTranslateCache.clear(); + interpolationRotateCache.clear(); + interpolationScaleCache.clear(); + loadedAnimations.clear(); + loadedModels.clear(); + } + + public static JsonObject loadAnimations(String toLoad) { + if (loadedAnimations.containsKey(toLoad)) + return loadedAnimations.get(toLoad); + + JsonObject loadedAnimations1; + + try { + loadedAnimations1 = GSON + .fromJson( + new InputStreamReader(Files.newInputStream(ModelEngine.getAnimationPath(toLoad))), + JsonObject.class + ); + } catch (IOException e) { + e.printStackTrace(); + loadedAnimations1 = null; + } + + loadedAnimations.put(toLoad, loadedAnimations1); + return loadedAnimations1; + } + + public static JsonObject loadModel(String id) { + if (loadedModels.containsKey(id)) + return loadedModels.get(id); + + JsonObject loadedModel1; + try { + loadedModel1 = GSON.fromJson(new InputStreamReader(Files.newInputStream(ModelEngine.getGeoPath(id))), JsonObject.class); + } catch (IOException e) { + e.printStackTrace(); + loadedModel1 = null; + } + + loadedModels.put(id, loadedModel1); + return loadedModel1; + } + + public static void addToTranslationCache(String key, String model, FrameProvider val) { + if (!interpolationTranslateCache.containsKey(model)) + interpolationTranslateCache.put(model, new HashMap<>()); + + interpolationTranslateCache.get(model).put(key, val); + } + + public static void addToRotationCache(String key, String model, FrameProvider val) { + if (!interpolationRotateCache.containsKey(model)) + interpolationRotateCache.put(model, new HashMap<>()); + + interpolationRotateCache.get(model).put(key, val); + } + + public static void addToScaleCache(String key, String model, FrameProvider val) { + if (!interpolationScaleCache.containsKey(model)) + interpolationScaleCache.put(model, new HashMap<>()); + + interpolationScaleCache.get(model).put(key, val); + } + + public static FrameProvider getCacheRotation(String key, String model) { + Map m = interpolationRotateCache.get(model); + if (m == null) return null; + return m.get(key); + } + + public static FrameProvider getCacheTranslation(String key, String model) { + Map m = interpolationTranslateCache.get(model); + if (m == null) return null; + return m.get(key); + } + + public static FrameProvider getCacheScale(String modelName, String s) { + Map m = interpolationScaleCache.get(modelName); + if (m == null) return null; + return m.get(s); + } + + public static Map parseAnimations(String animationString) { + Map res = new LinkedHashMap<>(); + + JsonObject animations = GSON.fromJson(new StringReader(animationString), JsonObject.class); + for (Map.Entry animation : animations.get("animations").getAsJsonObject().entrySet()) { + res.put(animation.getKey(), animation.getValue().getAsJsonObject()); + } + + return res; + } + + public enum AnimationType { + ROTATION, SCALE, TRANSLATION + } +} diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelMath.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelMath.java new file mode 100644 index 0000000..c770e99 --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/ModelMath.java @@ -0,0 +1,79 @@ +package me.tofaa.entitylib.bb; + +import com.github.retrooper.packetevents.util.Vector3d; + +class Matrix3 { + double x1, x2, x3; + double y1, y2, y3; + double z1, z2, z3; + + Matrix3(double x1, double x2, double x3, double y1, double y2, double y3, double z1, double z2, double z3) { + this.x1 = x1; + this.x2 = x2; + this.x3 = x3; + this.y1 = y1; + this.y2 = y2; + this.y3 = y3; + this.z1 = z1; + this.z2 = z2; + this.z3 = z3; + } + + Matrix3 mul(Matrix3 matrix3) { + double x1 = this.x1 * matrix3.x1 + this.x2 * matrix3.y1 + this.x3 * matrix3.z1; + double x2 = this.x1 * matrix3.x2 + this.x2 * matrix3.y2 + this.x3 * matrix3.z2; + double x3 = this.x1 * matrix3.x3 + this.x2 * matrix3.y3 + this.x3 * matrix3.z3; + + double y1 = this.y1 * matrix3.x1 + this.y2 * matrix3.y1 + this.y3 * matrix3.z1; + double y2 = this.y1 * matrix3.x2 + this.y2 * matrix3.y2 + this.y3 * matrix3.z2; + double y3 = this.y1 * matrix3.x3 + this.y2 * matrix3.y3 + this.y3 * matrix3.z3; + + double z1 = this.z1 * matrix3.x1 + this.z2 * matrix3.y1 + this.z3 * matrix3.z1; + double z2 = this.z1 * matrix3.x2 + this.z2 * matrix3.y2 + this.z3 * matrix3.z2; + double z3 = this.z1 * matrix3.x3 + this.z2 * matrix3.y3 + this.z3 * matrix3.z3; + + return new Matrix3(x1, x2, x3, y1, y2, y3, z1, z2, z3); + } + + Vector3d mul(Vector3d vec) { + double vx = vec.getX() * x1 + vec.getY() * x2 + vec.getZ() * x3; + double vy = vec.getX() * y1 + vec.getY() * y2 + vec.getZ() * y3; + double vz = vec.getX() * z1 + vec.getY() * z2 + vec.getZ() * z3; + + return new Vector3d(vx, vy, vz); + } +} + +public class ModelMath { + private static final float DEGREE = 0.017453292519943295F; + private static final float RADIAN = 57.29577951308232F; + + static Vector3d toRadians(Vector3d vector) { + return vector.multiply(DEGREE); + } + + static Vector3d toDegrees(Vector3d vector) { + return vector.multiply(RADIAN); + } + + public static Vector3d rotate(Vector3d vector, Vector3d rotation) { + Vector3d rot = toRadians(rotation); + + double rotX = rot.getX(); + double rotY = rot.getY(); + double rotZ = rot.getZ(); + + double cosX = Math.cos(rotX); + double sinX = Math.sin(rotX); + double cosY = Math.cos(rotY); + double sinY = Math.sin(rotY); + double cosZ = Math.cos(rotZ); + double sinZ = Math.sin(rotZ); + + Matrix3 rotMatrixX = new Matrix3(1, 0, 0, 0, cosX, -sinX, 0, sinX, cosX); + Matrix3 rotMatrixY = new Matrix3(cosY, 0, sinY, 0, 1, 0, -sinY, 0, cosY); + Matrix3 rotMatrixZ = new Matrix3(cosZ, -sinZ, 0, sinZ, cosZ, 0, 0, 0, 1); + + return rotMatrixZ.mul(rotMatrixY).mul(rotMatrixX).mul(vector); + } +} \ No newline at end of file diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/Quaternion.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/Quaternion.java new file mode 100644 index 0000000..aea7377 --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/Quaternion.java @@ -0,0 +1,111 @@ +package me.tofaa.entitylib.bb; + +import com.github.retrooper.packetevents.util.Vector3d; + +public final class Quaternion { + + private final double x; + private final double y; + private final double z; + + private final double w; + + public Quaternion(double x, double y, double z, double w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public Quaternion(Vector3d p) { + p = ModelMath.toRadians(p); + + double cy = Math.cos(p.getZ() * 0.5); + double sy = Math.sin(p.getZ() * 0.5); + double cp = Math.cos(p.getY() * 0.5); + double sp = Math.sin(p.getY() * 0.5); + double cr = Math.cos(p.getX() * 0.5); + double sr = Math.sin(p.getX() * 0.5); + + w = cr * cp * cy + sr * sp * sy; + x = sr * cp * cy - cr * sp * sy; + y = cr * sp * cy + sr * cp * sy; + z = cr * cp * sy - sr * sp * cy; + } + + public double x() { + return x; + } + + public double y() { + return y; + } + + public double z() { + return z; + } + + public double w() { + return w; + } + + public Vector3d toEuler() { + double t0 = (x + z) * (x - z); // x^2-z^2 + double t1 = (w + y) * (w - y); // w^2-y^2 + double xx = 0.5 * (t0 + t1); // 1/2 x of x' + double xy = x * y + w * z; // 1/2 y of x' + double xz = w * y - x * z; // 1/2 z of x' + double t = xx * xx + xy * xy; // cos(theta)^2 + double yz = 2.0 * (y * z + w * x); // z of y' + + double vx, vy, vz; + + vz = (float) Math.atan2(xy, xx); // yaw (psi) + vy = (float) Math.atan(xz / Math.sqrt(t)); // pitch (theta) + + if (t != 0) + vx = (float) Math.atan2(yz, t1 - t0); + else + vx = (float) (2.0 * Math.atan2(x, w) - Math.signum(xz) * vz); + + return ModelMath.toDegrees(new Vector3d(vx, vy, vz)); + } + + public Quaternion multiply(Quaternion q) { + double w = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z; + double x = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y; + double y = this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x; + double z = this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w; + + return new Quaternion( + x, y, z, w + ); + } + + @Override + public String toString() { + return "me.tofaa.entitylib.bb.Quaternion{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + ", w=" + w + + '}'; + } + + Vector3d threeAxisRot(double r11, double r12, double r21, double r31, double r32) { + double x = Math.atan2(r31, r32); + double y = Math.asin(r21); + double z = Math.atan2(r11, r12); + return new Vector3d(x, z, y); + } + + public Vector3d toEulerYZX() { + Quaternion q = this; + return ModelMath.toDegrees(threeAxisRot(-2 * (q.x * q.z - q.w * q.y), + q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z, + 2 * (q.x * q.y + q.w * q.z), + -2 * (q.y * q.z - q.w * q.x), + q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z)); + } + +} diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/animation/FrameProvider.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/animation/FrameProvider.java new file mode 100644 index 0000000..55c8e9f --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/animation/FrameProvider.java @@ -0,0 +1,11 @@ +package me.tofaa.entitylib.bb.animation; + + +import com.github.retrooper.packetevents.util.Vector3d; + +public interface FrameProvider { + Vector3d RotationMul = new Vector3d(-1, -1, 1); + Vector3d TranslationMul = new Vector3d(-1, 1, 1); + + Vector3d getFrame(int tick); +} \ No newline at end of file diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLData.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLData.java new file mode 100644 index 0000000..58f3e15 --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLData.java @@ -0,0 +1,16 @@ +package me.tofaa.entitylib.bb.mql; + +import net.hollowcube.mql.foreign.Query; + +public class MQLData { + private double time; + + public void setTime(double time) { + this.time = time; + } + + @Query + public double anim_time() { + return time; + } +} \ No newline at end of file diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLEvaluator.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLEvaluator.java new file mode 100644 index 0000000..2fd8cce --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLEvaluator.java @@ -0,0 +1,7 @@ +package me.tofaa.entitylib.bb.mql; + +import net.hollowcube.mql.jit.MqlEnv; + +public interface MQLEvaluator { + double evaluate(@MqlEnv({"q", "query"}) MQLData data); +} \ No newline at end of file diff --git a/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLPoint.java b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLPoint.java new file mode 100644 index 0000000..ad4275d --- /dev/null +++ b/block-bench-addon/src/main/java/me/tofaa/entitylib/bb/mql/MQLPoint.java @@ -0,0 +1,151 @@ +package me.tofaa.entitylib.bb.mql; + +import com.github.retrooper.packetevents.util.Vector3d; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.hollowcube.mql.jit.MqlCompiler; + +import java.lang.reflect.InvocationTargetException; + +public class MQLPoint { + public static final MQLPoint ZERO = new MQLPoint(); + MQLEvaluator molangX = null; + MQLEvaluator molangY = null; + MQLEvaluator molangZ = null; + double x = 0; + double y = 0; + double z = 0; + MQLData data = new MQLData(); + + public MQLPoint() { + x = 0; + y = 0; + z = 0; + } + + public MQLPoint(double x_, double y_, double z_) { + x = x_; + y = y_; + z = z_; + } + + public MQLPoint(JsonObject json) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + JsonElement fx = json.get("x"); + if (fx != null) { + try { + x = fx.getAsDouble(); + } catch (Exception ignored) { + molangX = fromString(fx.getAsString()); + } + } + + JsonElement fy = json.get("y"); + if (fy != null) { + try { + y = fy.getAsDouble(); + } catch (Exception ignored) { + molangY = fromString(fy.getAsString()); + } + } + + JsonElement fz = json.get("z"); + if (fz != null) { + try { + z = fz.getAsDouble(); + } catch (Exception ignored) { + molangZ = fromString(fz.getAsString()); + } + } + } + + public MQLPoint(JsonArray arr) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + JsonElement fx = arr.get(0); + if (fx != null) { + try { + x = fx.getAsDouble(); + } catch (Exception ignored) { + molangX = fromString(fx.getAsString()); + } + } + + JsonElement fy = arr.get(1); + if (fy != null) { + try { + y = fy.getAsDouble(); + } catch (Exception ignored) { + molangY = fromString(fy.getAsString()); + } + } + + JsonElement fz = arr.get(2); + if (fz != null) { + try { + z = fz.getAsDouble(); + } catch (Exception ignored) { + molangZ = fromString(fz.getAsString()); + } + } + } + + static MQLEvaluator fromDouble(double value) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + return fromString(Double.toString(value)); + } + + static MQLEvaluator fromString(String s) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + if (s == null || s.isEmpty()) return fromDouble(0); + MqlCompiler compiler = new MqlCompiler<>(MQLEvaluator.class); + Class scriptClass = compiler.compile(s.trim().replace("Math", "math")); + return scriptClass.getDeclaredConstructor().newInstance(); + } + + public Vector3d evaluate(double time) { + data.setTime(time); + + double evaluatedX = x; + if (molangX != null) { + try { + evaluatedX = molangX.evaluate(data); + } catch (Exception e) { + e.printStackTrace(); + } + } + + double evaluatedY = y; + if (molangY != null) { + try { + evaluatedY = molangY.evaluate(data); + } catch (Exception e) { + e.printStackTrace(); + } + } + + double evaluatedZ = z; + if (molangZ != null) { + try { + evaluatedZ = molangZ.evaluate(data); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return new Vector3d(evaluatedX, evaluatedY, evaluatedZ); + } + + @Override + public String toString() { + if (molangX != null || molangY != null || molangZ != null) { + return "MQLPoint{" + + "x=" + molangX + + ", y=" + molangY + + ", z=" + molangZ + + '}'; + } + + return "MQLPoint{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index d823582..27a36d3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,5 +37,5 @@ include(":platforms:standalone") if (!System.getenv("JITPACK").toBoolean()) { include(":code-gen") include(":test-plugin") - include(":model-engine-addon") + include(":block-bench-addon") }