block bench 'support boilerplate

This commit is contained in:
= 2024-10-27 19:33:23 +03:00
parent 9d9f8aeedf
commit 715eafaf51
11 changed files with 618 additions and 3 deletions

View file

@ -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.

View file

@ -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")

View file

@ -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<String, Vector3d> offsetMappings = new HashMap<>();
public final static HashMap<String, Vector3d> diffMappings = new HashMap<>();
static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
private final static HashMap<String, HashMap<String, ItemStack>> 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<String, ItemStack> 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<String, ItemStack> 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<Vector3d> 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<MQLPoint> 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<MQLPoint> getMQLPos(JsonArray arr) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
if (arr == null) return Optional.empty();
else {
return Optional.of(new MQLPoint(arr));
}
}
}

View file

@ -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<String, JsonObject> loadedAnimations = new HashMap<>();
private static final Map<String, JsonObject> loadedModels = new HashMap<>();
// <Model> -> <Bone/Animation>
private static final Map<String, Map<String, FrameProvider>> interpolationTranslateCache = new HashMap<>();
private static final Map<String, Map<String, FrameProvider>> interpolationRotateCache = new HashMap<>();
private static final Map<String, Map<String, FrameProvider>> 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<String, FrameProvider> m = interpolationRotateCache.get(model);
if (m == null) return null;
return m.get(key);
}
public static FrameProvider getCacheTranslation(String key, String model) {
Map<String, FrameProvider> m = interpolationTranslateCache.get(model);
if (m == null) return null;
return m.get(key);
}
public static FrameProvider getCacheScale(String modelName, String s) {
Map<String, FrameProvider> m = interpolationScaleCache.get(modelName);
if (m == null) return null;
return m.get(s);
}
public static Map<String, JsonObject> parseAnimations(String animationString) {
Map<String, JsonObject> res = new LinkedHashMap<>();
JsonObject animations = GSON.fromJson(new StringReader(animationString), JsonObject.class);
for (Map.Entry<String, JsonElement> animation : animations.get("animations").getAsJsonObject().entrySet()) {
res.put(animation.getKey(), animation.getValue().getAsJsonObject());
}
return res;
}
public enum AnimationType {
ROTATION, SCALE, TRANSLATION
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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<MQLEvaluator> compiler = new MqlCompiler<>(MQLEvaluator.class);
Class<MQLEvaluator> 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 +
'}';
}
}

View file

@ -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")
}