Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies {
implementation api("org.slf4j:slf4j-api:2.0.17")
implementation api("it.unimi.dsi:fastutil:8.5.18")
implementation api("io.netty:netty-buffer:4.2.7.Final")
implementation api("org.joml:joml:1.10.8")

// Testing
testImplementation("org.junit.jupiter:junit-jupiter-api:6.0.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ private static List<List<Expression>> getEasingArgsForAxis(JsonObject entryObj,
easingArg;
}

private static List<Keyframe> addArgsForKeyframes(List<Keyframe> frames) {
public static List<Keyframe> addArgsForKeyframes(List<Keyframe> frames) {
if (frames.getFirst().startValue().getFirst() instanceof AccessExpression accessExpression
&& "disabled".equals(accessExpression.property()) && accessExpression.object() instanceof IdentifierExpression identifierExpression
&& "pal".equals(identifierExpression.name()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package com.zigythebird.playeranimcore.loading;

import com.google.gson.*;
import com.zigythebird.playeranimcore.animation.Animation;
import com.zigythebird.playeranimcore.animation.ExtraAnimationData;
import com.zigythebird.playeranimcore.animation.keyframe.BoneAnimation;
import com.zigythebird.playeranimcore.animation.keyframe.Keyframe;
import com.zigythebird.playeranimcore.animation.keyframe.KeyframeStack;
import com.zigythebird.playeranimcore.easing.EasingType;
import com.zigythebird.playeranimcore.enums.TransformType;
import com.zigythebird.playeranimcore.math.Vec3f;
import com.zigythebird.playeranimcore.molang.MolangLoader;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import team.unnamed.mocha.parser.ast.Expression;
import team.unnamed.mocha.parser.ast.FloatExpression;

import java.lang.reflect.Type;
import java.util.*;

import static com.zigythebird.playeranimcore.loading.UniversalAnimLoader.NO_KEYFRAMES;
import static java.util.Map.entry;

public class BlockyAnimLoader implements JsonDeserializer<Animation> {
public static final Gson GSON = new GsonBuilder().registerTypeAdapter(Animation.class, new BlockyAnimLoader()).create();

private static final float TIME_SCALE = 20.0f / 60.0f;

private static final Map<String, String> RENAMES = Map.of(
"head", "headhy"
);

private static final float SCALE = 24.0f / 87.0f;

private static final Map<String, Vec3f> PIVOTS = Map.ofEntries(
// Body
entry("origin", new Vec3f(0, 0, 0)),
entry("pelvis", new Vec3f(0, 51 * SCALE, 0)),
entry("belly", new Vec3f(0, 55 * SCALE, 0)),
entry("chest", new Vec3f(0, 68 * SCALE, -3 * SCALE)),

// Head
entry("headhy", new Vec3f(0, 87 * SCALE, -1 * SCALE)),

// Right Arm
entry("r-shoulder", new Vec3f(14.5f * SCALE, 86.6f * SCALE, -0.94f * SCALE)),
entry("r-arm", new Vec3f(14.5f * SCALE, 86.6f * SCALE, -0.94f * SCALE)),

// Left Arm
entry("l-shoulder", new Vec3f(-14.5f * SCALE, 86.6f * SCALE, -0.94f * SCALE)),
entry("l-arm", new Vec3f(-14.5f * SCALE, 86.6f * SCALE, -0.94f * SCALE)),

// Right Leg
entry("r-thigh", new Vec3f(7.5f * SCALE, 50 * SCALE, 1 * SCALE)),

// Left Leg
entry("l-thigh", new Vec3f(-7.5f * SCALE, 50 * SCALE, 1 * SCALE)),

// Cape
entry("back-attachment", new Vec3f(0, 79 * SCALE, -18 * SCALE))
);
private static final Map<String, String> PARENTS = Map.ofEntries(
// Body
entry("pelvis", "origin"),
entry("belly", "pelvis"),
entry("chest", "belly"),
entry("body", "pelvis"), // Minecraft

// Torso
entry("torso", "chest"), // Minecraft TODO

// Head
entry("headhy", "chest"),
entry("head", "headhy"), // Minecraft

// Right Arm
entry("r-shoulder", "chest"),
entry("r-arm", "r-shoulder"),
entry("right_arm", "r-arm"), // Minecraft

// Left Arm
entry("l-shoulder", "chest"),
entry("l-arm", "l-shoulder"),
entry("left_arm", "l-arm"), // Minecraft

// Right Leg
entry("r-thigh", "pelvis"),
entry("right_leg", "r-thigh"), // Minecraft

// Left Leg
entry("l-thigh", "pelvis"),
entry("left_leg", "l-thigh"), // Minecraft

// Cape
entry("back-attachment", "chest"),
entry("cape", "back-attachment") // Minecraft
);
/*private static final Map<String, String> BENDS = Map.of(
"r-forearm", "right_arm",
"l-forearm", "left_arm",
"r-calf", "right_leg",
"l-calf", "left_leg"
);*/

@Override
public Animation deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject root = json.getAsJsonObject();
float duration = root.has("duration") ? root.get("duration").getAsFloat() * TIME_SCALE : 0;
boolean loop = root.has("holdLastKeyframe") && root.get("holdLastKeyframe").getAsBoolean();

Map<String, BoneAnimation> animations = new HashMap<>();

if (root.has("nodeAnimations")) {
for (Map.Entry<String, JsonElement> entry : root.getAsJsonObject("nodeAnimations").entrySet()) {
String src = entry.getKey().toLowerCase(Locale.ROOT);
JsonObject data = entry.getValue().getAsJsonObject();

KeyframeStack rot = parseStack(data.getAsJsonArray("orientation"), TransformType.ROTATION);
KeyframeStack pos = parseStack(data.getAsJsonArray("position"), TransformType.POSITION);
KeyframeStack scl = parseStack(data.getAsJsonArray("shapeStretch"), TransformType.SCALE);
animations.put(RENAMES.getOrDefault(src, src), new BoneAnimation(rot, pos, scl, new ArrayList<>()));
}
}

return new Animation(new ExtraAnimationData(), duration, loop ? Animation.LoopType.HOLD_ON_LAST_FRAME : Animation.LoopType.PLAY_ONCE, animations, NO_KEYFRAMES, PIVOTS, PARENTS);
}

private KeyframeStack parseStack(JsonArray json, TransformType type) {
if (json == null || json.isEmpty()) return new KeyframeStack();

List<Keyframe> xFrames = new ObjectArrayList<>();
List<Keyframe> yFrames = new ObjectArrayList<>();
List<Keyframe> zFrames = new ObjectArrayList<>();

List<Expression> xPrev = null;
List<Expression> yPrev = null;
List<Expression> zPrev = null;
float prevTime = 0;

Expression defaultValue = type == TransformType.SCALE ? FloatExpression.ONE : FloatExpression.ZERO;

for (JsonElement element : json) {
JsonObject keyframe = element.getAsJsonObject();
JsonObject delta = keyframe.getAsJsonObject("delta");

float curTime = keyframe.get("time").getAsFloat() * TIME_SCALE;
float timeDelta = curTime - prevTime;

List<Expression> xValue, yValue, zValue;
if (type == TransformType.ROTATION) {
Vector3f vector3f = new Quaternionf(
delta.get("x").getAsFloat(), delta.get("y").getAsFloat(), delta.get("z").getAsFloat(), delta.get("w").getAsFloat()
).normalize().getEulerAnglesZYX(new Vector3f());

xValue = Collections.singletonList(FloatExpression.of(vector3f.x()));
yValue = Collections.singletonList(FloatExpression.of(-vector3f.y()));
zValue = Collections.singletonList(FloatExpression.of(-vector3f.z()));
} else {
xValue = MolangLoader.parseJson(false, delta.get("x"), defaultValue);
yValue = MolangLoader.parseJson(false, delta.get("y"), defaultValue);
zValue = MolangLoader.parseJson(false, delta.get("z"), defaultValue);
}

EasingType easingType = keyframe.has("interpolationType") && keyframe.get("interpolationType").getAsString().equals("smooth") ? EasingType.CATMULLROM : EasingType.LINEAR;
xFrames.add(new Keyframe(timeDelta, xPrev == null ? xValue : xPrev, xValue, easingType, Collections.emptyList()));
yFrames.add(new Keyframe(timeDelta, yPrev == null ? yValue : yPrev, yValue, easingType, Collections.emptyList()));
zFrames.add(new Keyframe(timeDelta, zPrev == null ? zValue : zPrev, zValue, easingType, Collections.emptyList()));

xPrev = xValue; yPrev = yValue; zPrev = zValue;
prevTime = curTime;
}

return new KeyframeStack(
AnimationLoader.addArgsForKeyframes(xFrames),
AnimationLoader.addArgsForKeyframes(yFrames),
AnimationLoader.addArgsForKeyframes(zFrames)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.zigythebird.playeranimcore.util.JsonUtil;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
Expand All @@ -31,13 +32,21 @@ public class UniversalAnimLoader implements JsonDeserializer<Map<String, Animati
private static final Pattern UNDERSCORE_PATTERN = Pattern.compile("_(.)");

public static Map<String, Animation> loadAnimations(InputStream resource) throws IOException {
return loadAnimations(resource, null);
}

public static Map<String, Animation> loadAnimations(InputStream resource, @Nullable String name) throws IOException {
try (Reader reader = new InputStreamReader(resource)) {
return loadAnimations(PlayerAnimLib.GSON.fromJson(reader, JsonObject.class));
return loadAnimations(PlayerAnimLib.GSON.fromJson(reader, JsonObject.class), name);
}
}

public static Map<@NotNull String, Animation> loadAnimations(JsonObject json) {
if (json.has("animations")) {
return loadAnimations(json, null);
}

public static Map<@NotNull String, Animation> loadAnimations(JsonObject json, @Nullable String name) {
if (json.has("animations")) { // Bedrock
Map<String, Animation> animationMap = PlayerAnimLib.GSON.fromJson(json.get("animations"), PlayerAnimLib.ANIMATIONS_MAP_TYPE);
if (json.has("parents") && json.has("model")) {
Map<String, String> parents = UniversalAnimLoader.getParents(JsonUtil.getAsJsonObject(json, "parents", new JsonObject()));
Expand All @@ -52,9 +61,15 @@ public static Map<String, Animation> loadAnimations(InputStream resource) throws
}
}
return animationMap;
} else {
} else if (json.has("nodeAnimations")) { // Hytale
Animation animation = BlockyAnimLoader.GSON.fromJson(json, Animation.class);
if (name != null && !animation.data().has(ExtraAnimationData.NAME_KEY)) animation.data().put(ExtraAnimationData.NAME_KEY, name);
return Collections.singletonMap(animation.getNameOrId(), animation);
} else if (json.has("emote")) { // PlayerAnimator
Animation animation = PlayerAnimatorLoader.GSON.fromJson(json, Animation.class);
return Collections.singletonMap(animation.getNameOrId(), animation);
} else {
throw new JsonParseException("Unsupported format!");
}
}

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true

# Mod properties
mod_version = 1.1.5
mod_version = 1.1.5+dev
maven_group = com.zigythebird.playeranim
archives_base_name = PlayerAnimationLib

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -83,10 +84,13 @@ public static boolean hasAnimation(Identifier id) {
public void onResourceManagerReload(ResourceManager manager) {
ANIMATIONS.clear();

for (var resource : manager.listResources("player_animations", resourceLocation -> resourceLocation.getPath().endsWith(".json")).entrySet()) {
for (var resource : manager.listResources("player_animations", identifier -> {
String path = identifier.getPath();
return path.endsWith(".json") || path.endsWith(".blockyanim");
}).entrySet()) {
String namespace = resource.getKey().getNamespace();
try (InputStream is = resource.getValue().open()) {
for (var entry : UniversalAnimLoader.loadAnimations(is).entrySet()) {
for (var entry : UniversalAnimLoader.loadAnimations(is, FilenameUtils.getBaseName(resource.getKey().getPath())).entrySet()) {
ANIMATIONS.put(Identifier.fromNamespaceAndPath(namespace, entry.getKey()), entry.getValue());
}
} catch (Exception e) {
Expand Down
Loading