Nouveaux stubs core :
- TiedUpAnimationConfig — remplace yesman.epicfight.config.ClientConfig.
Flags animation/rendu, no-op pour combat.
- TiedUpRigRegistry — remplace gameasset.Animations.EMPTY_ANIMATION +
gameasset.Armatures.ArmatureContructor.
Fichiers forkés additionnels (dépendances transitives découvertes) :
- anim/types/DirectStaticAnimation.java (EMPTY_ANIMATION est un DirectStaticAnimation)
- event/InitAnimatorEvent.java (postInit() forge event)
- event/EntityPatchRegistryEvent.java (mod bus event pour register patches)
Strip combat :
- Meshes.java : retiré les 11 mob meshes (CreeperMesh, DragonMesh, VexMesh,
WitherMesh, etc.) + armor + particle + cape. Garde BIPED
et ALEX / BIPED_OLD_TEX / BIPED_OUTLAYER (variants joueur).
- Animator.playDeathAnimation : Animations.BIPED_DEATH (ARR asset) →
EMPTY_ANIMATION fallback.
- AnimationManager.apply : Armatures.reload() stripped (no-op, à rebrancher
Phase 2 sur TiedUpArmatures).
- ClientPlayerPatch.entityPairing : body entier strippé (combat skills
Technician/Adrenaline/Emergency Escape).
sed global : ClientConfig.* → TiedUpAnimationConfig.*
sed global : Animations.EMPTY_ANIMATION → TiedUpRigRegistry.EMPTY_ANIMATION
sed global : Armatures.ArmatureContructor → TiedUpRigRegistry.ArmatureContructor
Résidus yesman.epicfight : 86 → 74 (-12)
Reste : physics (16) + network (13) + world combat (10) + particle (3) +
collider (2) + client misc (2) + skill (2). Tous combat-entangled,
demandent strip méthode par méthode.
833 lines
32 KiB
Java
833 lines
32 KiB
Java
/*
|
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
|
* by the Epic Fight Team, licensed under GPLv3.
|
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
|
*/
|
|
|
|
package com.tiedup.remake.rig.asset;
|
|
|
|
import java.io.BufferedInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.NoSuchElementException;
|
|
import java.util.Set;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.Maps;
|
|
import com.google.common.collect.Sets;
|
|
import com.google.gson.JsonArray;
|
|
import com.google.gson.JsonElement;
|
|
import com.google.gson.JsonObject;
|
|
import com.google.gson.JsonParseException;
|
|
import com.google.gson.internal.Streams;
|
|
import com.google.gson.stream.JsonReader;
|
|
|
|
import io.netty.util.internal.StringUtil;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.packs.resources.Resource;
|
|
import net.minecraft.server.packs.resources.ResourceManager;
|
|
import net.minecraft.util.GsonHelper;
|
|
import net.minecraft.world.phys.Vec3;
|
|
import net.minecraftforge.api.distmarker.Dist;
|
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
|
import net.minecraftforge.fml.ModList;
|
|
import net.minecraftforge.fml.loading.FMLEnvironment;
|
|
import com.tiedup.remake.rig.anim.AnimationClip;
|
|
import com.tiedup.remake.rig.armature.Joint;
|
|
import com.tiedup.remake.rig.armature.JointTransform;
|
|
import com.tiedup.remake.rig.anim.Keyframe;
|
|
import com.tiedup.remake.rig.anim.TransformSheet;
|
|
import com.tiedup.remake.rig.anim.property.AnimationProperty.ActionAnimationProperty;
|
|
import com.tiedup.remake.rig.anim.types.ActionAnimation;
|
|
import com.tiedup.remake.rig.anim.types.AttackAnimation;
|
|
import com.tiedup.remake.rig.anim.types.AttackAnimation.Phase;
|
|
import com.tiedup.remake.rig.anim.types.MainFrameAnimation;
|
|
import com.tiedup.remake.rig.anim.types.StaticAnimation;
|
|
import com.tiedup.remake.rig.mesh.ClassicMesh;
|
|
import com.tiedup.remake.rig.mesh.CompositeMesh;
|
|
import com.tiedup.remake.rig.mesh.Mesh;
|
|
import com.tiedup.remake.rig.mesh.MeshPartDefinition;
|
|
import com.tiedup.remake.rig.mesh.Meshes;
|
|
import com.tiedup.remake.rig.mesh.Meshes.MeshContructor;
|
|
import com.tiedup.remake.rig.mesh.SkinnedMesh;
|
|
import com.tiedup.remake.rig.mesh.SoftBodyTranslatable;
|
|
import com.tiedup.remake.rig.mesh.StaticMesh;
|
|
import com.tiedup.remake.rig.mesh.VertexBuilder;
|
|
import com.tiedup.remake.rig.mesh.transformer.VanillaModelTransformer.VanillaMeshPartDefinition;
|
|
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject.ClothPart.ConstraintType;
|
|
import com.tiedup.remake.rig.exception.AssetLoadingException;
|
|
import com.tiedup.remake.rig.armature.Armature;
|
|
import com.tiedup.remake.rig.util.ParseUtil;
|
|
import com.tiedup.remake.rig.math.MathUtils;
|
|
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
|
import com.tiedup.remake.rig.math.Vec3f;
|
|
import com.tiedup.remake.rig.math.Vec4f;
|
|
import com.tiedup.remake.rig.TiedUpRigRegistry.ArmatureContructor;
|
|
import com.tiedup.remake.rig.TiedUpRigConstants;
|
|
|
|
public class JsonAssetLoader {
|
|
public static final OpenMatrix4f BLENDER_TO_MINECRAFT_COORD = OpenMatrix4f.createRotatorDeg(-90.0F, Vec3f.X_AXIS);
|
|
public static final OpenMatrix4f MINECRAFT_TO_BLENDER_COORD = OpenMatrix4f.invert(BLENDER_TO_MINECRAFT_COORD, null);
|
|
public static final String UNGROUPED_NAME = "noGroups";
|
|
public static final String COORD_BONE = "Coord";
|
|
public static final String ROOT_BONE = "Root";
|
|
|
|
private JsonObject rootJson;
|
|
|
|
// Used for deciding armature name, other resources are nullable
|
|
@Nullable
|
|
private ResourceLocation resourceLocation;
|
|
private String filehash;
|
|
|
|
public JsonAssetLoader(ResourceManager resourceManager, ResourceLocation resourceLocation) throws AssetLoadingException {
|
|
JsonReader jsonReader = null;
|
|
this.resourceLocation = resourceLocation;
|
|
|
|
try {
|
|
try {
|
|
if (resourceManager == null) {
|
|
throw new NoSuchElementException();
|
|
}
|
|
|
|
Resource resource = resourceManager.getResource(resourceLocation).orElseThrow();
|
|
InputStream inputStream = resource.open();
|
|
InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
|
|
|
jsonReader = new JsonReader(isr);
|
|
jsonReader.setLenient(true);
|
|
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
|
} catch (NoSuchElementException e) {
|
|
// In this case, reads the animation data from mod.jar (Especially in a server)
|
|
Class<?> modClass = ModList.get().getModObjectById(resourceLocation.getNamespace()).orElseThrow(() -> new AssetLoadingException("No modid " + resourceLocation)).getClass();
|
|
InputStream inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
|
|
|
if (inputStream == null) {
|
|
modClass = ModList.get().getModObjectById(TiedUpRigConstants.MODID).get().getClass();
|
|
inputStream = modClass.getResourceAsStream("/assets/" + resourceLocation.getNamespace() + "/" + resourceLocation.getPath());
|
|
}
|
|
|
|
//Still null, throws exception.
|
|
if (inputStream == null) {
|
|
throw new AssetLoadingException("Can't find resource file: " + resourceLocation);
|
|
}
|
|
|
|
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
|
|
InputStreamReader reader = new InputStreamReader(bufferedInputStream, StandardCharsets.UTF_8);
|
|
|
|
jsonReader = new JsonReader(reader);
|
|
jsonReader.setLenient(true);
|
|
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
|
}
|
|
} catch (IOException e) {
|
|
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + " because of " + e);
|
|
} finally {
|
|
if (jsonReader != null) {
|
|
try {
|
|
jsonReader.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.filehash = ParseUtil.getBytesSHA256Hash(this.rootJson.toString().getBytes());
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public JsonAssetLoader(InputStream inputstream, ResourceLocation resourceLocation) throws AssetLoadingException {
|
|
JsonReader jsonReader = null;
|
|
this.resourceLocation = resourceLocation;
|
|
|
|
jsonReader = new JsonReader(new InputStreamReader(inputstream, StandardCharsets.UTF_8));
|
|
jsonReader.setLenient(true);
|
|
this.rootJson = Streams.parse(jsonReader).getAsJsonObject();
|
|
|
|
try {
|
|
jsonReader.close();
|
|
} catch (IOException e) {
|
|
throw new AssetLoadingException("Can't read " + resourceLocation.toString() + ": " + e);
|
|
}
|
|
|
|
this.filehash = StringUtil.EMPTY_STRING;
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public JsonAssetLoader(JsonObject rootJson, ResourceLocation rl) {
|
|
this.rootJson = rootJson;
|
|
this.resourceLocation = rl;
|
|
this.filehash = StringUtil.EMPTY_STRING;
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public static Mesh.RenderProperties getRenderProperties(JsonObject json) {
|
|
if (!json.has("render_properties")) {
|
|
return null;
|
|
}
|
|
|
|
JsonObject properties = json.getAsJsonObject("render_properties");
|
|
Mesh.RenderProperties.Builder renderProperties = Mesh.RenderProperties.Builder.create();
|
|
|
|
if (properties.has("transparent")) {
|
|
renderProperties.transparency(properties.get("transparent").getAsBoolean());
|
|
}
|
|
|
|
if (properties.has("texture_path")) {
|
|
renderProperties.customTexturePath(properties.get("texture_path").getAsString());
|
|
}
|
|
|
|
if (properties.has("color")) {
|
|
JsonArray jsonarray = properties.getAsJsonArray("color");
|
|
renderProperties.customColor(jsonarray.get(0).getAsFloat(), jsonarray.get(1).getAsFloat(), jsonarray.get(2).getAsFloat());
|
|
}
|
|
|
|
return renderProperties.build();
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public ResourceLocation getParent() {
|
|
return this.rootJson.has("parent") ? ResourceLocation.parse(this.rootJson.get("parent").getAsString()) : null;
|
|
}
|
|
|
|
private static final float DEFAULT_PARTICLE_MASS = 0.16F;
|
|
private static final float DEFAULT_SELF_COLLISON = 0.05F;
|
|
|
|
@Nullable
|
|
@OnlyIn(Dist.CLIENT)
|
|
public Map<String, SoftBodyTranslatable.ClothSimulationInfo> loadClothInformation(Float[] positionArray) {
|
|
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
|
JsonObject clothInfoObj = obj.getAsJsonObject("cloth_info");
|
|
|
|
if (clothInfoObj == null) {
|
|
return null;
|
|
}
|
|
|
|
Map<String, SoftBodyTranslatable.ClothSimulationInfo> clothInfo = Maps.newHashMap();
|
|
|
|
for (Map.Entry<String, JsonElement> e : clothInfoObj.entrySet()) {
|
|
JsonObject clothObject = e.getValue().getAsJsonObject();
|
|
int[] particlesArray = ParseUtil.toIntArrayPrimitive(clothObject.get("particles").getAsJsonObject().get("array").getAsJsonArray());
|
|
float[] weightsArray = ParseUtil.toFloatArrayPrimitive(clothObject.get("weights").getAsJsonObject().get("array").getAsJsonArray());
|
|
float particleMass = clothObject.has("particle_mass") ? clothObject.get("particle_mass").getAsFloat() : DEFAULT_PARTICLE_MASS;
|
|
float selfCollision = clothObject.has("self_collision") ? clothObject.get("self_collision").getAsFloat() : DEFAULT_SELF_COLLISON;
|
|
|
|
JsonArray constraintsArray = clothObject.get("constraints").getAsJsonArray();
|
|
List<int[]> constraintsList = new ArrayList<> (constraintsArray.size());
|
|
float[] compliances = new float[constraintsArray.size()];
|
|
ConstraintType[] constraintType = new ConstraintType[constraintsArray.size()];
|
|
float[] rootDistances = new float[particlesArray.length / 2];
|
|
|
|
int i = 0;
|
|
|
|
for (JsonElement element : constraintsArray) {
|
|
JsonObject asJsonObject = element.getAsJsonObject();
|
|
|
|
if (asJsonObject.has("unused") && GsonHelper.getAsBoolean(asJsonObject, "unused")) {
|
|
continue;
|
|
}
|
|
|
|
constraintType[i] = ConstraintType.valueOf(GsonHelper.getAsString(asJsonObject, "type").toUpperCase(Locale.ROOT));
|
|
compliances[i] = GsonHelper.getAsFloat(asJsonObject, "compliance");
|
|
constraintsList.add(ParseUtil.toIntArrayPrimitive(asJsonObject.get("array").getAsJsonArray()));
|
|
element.getAsJsonObject().get("compliance");
|
|
i++;
|
|
}
|
|
|
|
List<Vec3> rootParticles = Lists.newArrayList();
|
|
|
|
for (int j = 0; j < particlesArray.length / 2; j++) {
|
|
int weightIndex = particlesArray[j * 2 + 1];
|
|
float weight = weightsArray[weightIndex];
|
|
|
|
if (weight == 0.0F) {
|
|
int posId = particlesArray[j * 2];
|
|
rootParticles.add(new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]));
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < particlesArray.length / 2; j++) {
|
|
int posId = particlesArray[j * 2];
|
|
Vec3 position = new Vec3(positionArray[posId * 3 + 0], positionArray[posId * 3 + 1], positionArray[posId * 3 + 2]);
|
|
Vec3 nearest = MathUtils.getNearestVector(position, rootParticles);
|
|
rootDistances[j] = (float)position.distanceTo(nearest);
|
|
}
|
|
|
|
int[] normalOffsetMappingArray = null;
|
|
|
|
if (clothObject.has("normal_offsets")) {
|
|
normalOffsetMappingArray = ParseUtil.toIntArrayPrimitive(clothObject.get("normal_offsets").getAsJsonObject().get("array").getAsJsonArray());
|
|
}
|
|
|
|
SoftBodyTranslatable.ClothSimulationInfo clothSimulInfo = new SoftBodyTranslatable.ClothSimulationInfo(particleMass, selfCollision, constraintsList, constraintType, compliances, particlesArray, weightsArray, rootDistances, normalOffsetMappingArray);
|
|
clothInfo.put(e.getKey(), clothSimulInfo);
|
|
}
|
|
|
|
return clothInfo;
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public <T extends ClassicMesh> T loadClassicMesh(MeshContructor<ClassicMesh.ClassicMeshPart, VertexBuilder, T> constructor) {
|
|
ResourceLocation parent = this.getParent();
|
|
|
|
if (parent != null) {
|
|
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadClassicMesh(constructor)).get();
|
|
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
|
} else {
|
|
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
|
JsonObject positions = obj.getAsJsonObject("positions");
|
|
JsonObject normals = obj.getAsJsonObject("normals");
|
|
JsonObject uvs = obj.getAsJsonObject("uvs");
|
|
JsonObject parts = obj.getAsJsonObject("parts");
|
|
JsonObject indices = obj.getAsJsonObject("indices");
|
|
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
|
|
|
for (int i = 0; i < positionArray.length / 3; i++) {
|
|
int k = i * 3;
|
|
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
|
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
|
positionArray[k] = posVector.x;
|
|
positionArray[k+1] = posVector.y;
|
|
positionArray[k+2] = posVector.z;
|
|
}
|
|
|
|
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
|
|
|
for (int i = 0; i < normalArray.length / 3; i++) {
|
|
int k = i * 3;
|
|
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
|
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
|
normalArray[k] = normVector.x;
|
|
normalArray[k+1] = normVector.y;
|
|
normalArray[k+2] = normVector.z;
|
|
}
|
|
|
|
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
|
|
|
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
|
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
|
|
|
arrayMap.put("positions", positionArray);
|
|
arrayMap.put("normals", normalArray);
|
|
arrayMap.put("uvs", uvArray);
|
|
|
|
if (parts != null) {
|
|
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
|
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
|
}
|
|
}
|
|
|
|
if (indices != null) {
|
|
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
|
}
|
|
|
|
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
|
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
|
|
|
return mesh;
|
|
}
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public <T extends SkinnedMesh> T loadSkinnedMesh(MeshContructor<SkinnedMesh.SkinnedMeshPart, VertexBuilder, T> constructor) {
|
|
ResourceLocation parent = this.getParent();
|
|
|
|
if (parent != null) {
|
|
T mesh = Meshes.getOrCreate(parent, (jsonLoader) -> jsonLoader.loadSkinnedMesh(constructor)).get();
|
|
return constructor.invoke(null, null, mesh, getRenderProperties(this.rootJson));
|
|
} else {
|
|
JsonObject obj = this.rootJson.getAsJsonObject("vertices");
|
|
JsonObject positions = obj.getAsJsonObject("positions");
|
|
JsonObject normals = obj.getAsJsonObject("normals");
|
|
JsonObject uvs = obj.getAsJsonObject("uvs");
|
|
JsonObject vdincies = obj.getAsJsonObject("vindices");
|
|
JsonObject weights = obj.getAsJsonObject("weights");
|
|
JsonObject vcounts = obj.getAsJsonObject("vcounts");
|
|
JsonObject parts = obj.getAsJsonObject("parts");
|
|
JsonObject indices = obj.getAsJsonObject("indices");
|
|
|
|
Float[] positionArray = ParseUtil.toFloatArray(positions.get("array").getAsJsonArray());
|
|
|
|
for (int i = 0; i < positionArray.length / 3; i++) {
|
|
int k = i * 3;
|
|
Vec4f posVector = new Vec4f(positionArray[k], positionArray[k+1], positionArray[k+2], 1.0F);
|
|
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, posVector, posVector);
|
|
positionArray[k] = posVector.x;
|
|
positionArray[k+1] = posVector.y;
|
|
positionArray[k+2] = posVector.z;
|
|
}
|
|
|
|
Float[] normalArray = ParseUtil.toFloatArray(normals.get("array").getAsJsonArray());
|
|
|
|
for (int i = 0; i < normalArray.length / 3; i++) {
|
|
int k = i * 3;
|
|
Vec4f normVector = new Vec4f(normalArray[k], normalArray[k+1], normalArray[k+2], 1.0F);
|
|
OpenMatrix4f.transform(BLENDER_TO_MINECRAFT_COORD, normVector, normVector);
|
|
normalArray[k] = normVector.x;
|
|
normalArray[k+1] = normVector.y;
|
|
normalArray[k+2] = normVector.z;
|
|
}
|
|
|
|
Float[] uvArray = ParseUtil.toFloatArray(uvs.get("array").getAsJsonArray());
|
|
Float[] weightArray = ParseUtil.toFloatArray(weights.get("array").getAsJsonArray());
|
|
Integer[] affectingJointCounts = ParseUtil.toIntArray(vcounts.get("array").getAsJsonArray());
|
|
Integer[] affectingJointIndices = ParseUtil.toIntArray(vdincies.get("array").getAsJsonArray());
|
|
|
|
Map<String, Number[]> arrayMap = Maps.newHashMap();
|
|
Map<MeshPartDefinition, List<VertexBuilder>> meshMap = Maps.newHashMap();
|
|
arrayMap.put("positions", positionArray);
|
|
arrayMap.put("normals", normalArray);
|
|
arrayMap.put("uvs", uvArray);
|
|
arrayMap.put("weights", weightArray);
|
|
arrayMap.put("vcounts", affectingJointCounts);
|
|
arrayMap.put("vindices", affectingJointIndices);
|
|
|
|
if (parts != null) {
|
|
for (Map.Entry<String, JsonElement> e : parts.entrySet()) {
|
|
meshMap.put(VanillaMeshPartDefinition.of(e.getKey(), getRenderProperties(e.getValue().getAsJsonObject())), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(e.getValue().getAsJsonObject().get("array").getAsJsonArray())));
|
|
}
|
|
}
|
|
|
|
if (indices != null) {
|
|
meshMap.put(VanillaMeshPartDefinition.of(UNGROUPED_NAME), VertexBuilder.create(ParseUtil.toIntArrayPrimitive(indices.get("array").getAsJsonArray())));
|
|
}
|
|
|
|
T mesh = constructor.invoke(arrayMap, meshMap, null, getRenderProperties(this.rootJson));
|
|
mesh.putSoftBodySimulationInfo(this.loadClothInformation(positionArray));
|
|
|
|
return mesh;
|
|
}
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public CompositeMesh loadCompositeMesh() throws AssetLoadingException {
|
|
if (!this.rootJson.has("meshes")) {
|
|
throw new AssetLoadingException("Composite mesh loading exception: lower meshes undefined");
|
|
}
|
|
|
|
JsonAssetLoader clothLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("cloth").getAsJsonObject(), null);
|
|
JsonAssetLoader staticLoader = new JsonAssetLoader(this.rootJson.get("meshes").getAsJsonObject().get("static").getAsJsonObject(), null);
|
|
SoftBodyTranslatable softBodyMesh = (SoftBodyTranslatable)clothLoader.loadMesh(false);
|
|
StaticMesh<?> staticMesh = (StaticMesh<?>)staticLoader.loadMesh(false);
|
|
|
|
if (!softBodyMesh.canStartSoftBodySimulation()) {
|
|
throw new AssetLoadingException("Composite mesh loading exception: soft mesh doesn't have cloth info");
|
|
}
|
|
|
|
return new CompositeMesh(staticMesh, softBodyMesh);
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
public Mesh loadMesh() throws AssetLoadingException {
|
|
return this.loadMesh(true);
|
|
}
|
|
|
|
@OnlyIn(Dist.CLIENT)
|
|
private Mesh loadMesh(boolean allowCompositeMesh) throws AssetLoadingException {
|
|
if (!this.rootJson.has("mesh_loader")) {
|
|
throw new AssetLoadingException("Mesh loading exception: No mesh loader provided!");
|
|
}
|
|
|
|
String loader = this.rootJson.get("mesh_loader").getAsString();
|
|
|
|
switch (loader) {
|
|
case "classic_mesh" -> {
|
|
return this.loadClassicMesh(ClassicMesh::new);
|
|
}
|
|
case "skinned_mesh" -> {
|
|
return this.loadSkinnedMesh(SkinnedMesh::new);
|
|
}
|
|
case "composite_mesh" -> {
|
|
if (!allowCompositeMesh) {
|
|
throw new AssetLoadingException("Can't have a composite mesh inside another composite mesh");
|
|
}
|
|
|
|
return this.loadCompositeMesh();
|
|
}
|
|
default -> {
|
|
throw new AssetLoadingException("Mesh loading exception: Unsupported mesh loader: " + loader);
|
|
}
|
|
}
|
|
}
|
|
|
|
public <T extends Armature> T loadArmature(ArmatureContructor<T> constructor) {
|
|
if (this.resourceLocation == null) {
|
|
throw new AssetLoadingException("Can't load armature: Resource location is null.");
|
|
}
|
|
|
|
JsonObject obj = this.rootJson.getAsJsonObject("armature");
|
|
TransformFormat transformFormat = getAsTransformFormatOrDefault(obj, "armature_format");
|
|
JsonObject hierarchy = obj.get("hierarchy").getAsJsonArray().get(0).getAsJsonObject();
|
|
JsonArray nameAsVertexGroups = obj.getAsJsonArray("joints");
|
|
Map<String, Integer> jointIds = Maps.newHashMap();
|
|
|
|
int id = 0;
|
|
|
|
for (int i = 0; i < nameAsVertexGroups.size(); i++) {
|
|
String name = nameAsVertexGroups.get(i).getAsString();
|
|
|
|
if (name.equals(COORD_BONE)) {
|
|
continue;
|
|
}
|
|
|
|
jointIds.put(name, id);
|
|
id++;
|
|
}
|
|
|
|
Map<String, Joint> jointMap = Maps.newHashMap();
|
|
Joint joint = getJoint(hierarchy, jointIds, jointMap, transformFormat, true);
|
|
joint.initOriginTransform(new OpenMatrix4f());
|
|
|
|
String armatureName = this.resourceLocation.toString().replaceAll("(animmodels/|\\.json)", "");
|
|
|
|
return constructor.invoke(armatureName, jointMap.size(), joint, jointMap);
|
|
}
|
|
|
|
private static Joint getJoint(JsonObject object, Map<String, Integer> jointIdMap, Map<String, Joint> jointMap, TransformFormat transformFormat, boolean root) {
|
|
String name = object.get("name").getAsString();
|
|
|
|
if (!jointIdMap.containsKey(name)) {
|
|
throw new AssetLoadingException("Can't load joint: joint name " + name + " doesn't exist in armature hierarchy.");
|
|
}
|
|
|
|
// Skip Coord bone
|
|
if (name.equals(COORD_BONE)) {
|
|
JsonArray coordChildren = object.get("children").getAsJsonArray();
|
|
|
|
if (coordChildren.isEmpty()) {
|
|
throw new AssetLoadingException("No children for Coord bone");
|
|
} else if (coordChildren.size() > 1) {
|
|
throw new AssetLoadingException("Coord bone can't have multiple children");
|
|
} else {
|
|
return getJoint(coordChildren.get(0).getAsJsonObject(), jointIdMap, jointMap, transformFormat, false);
|
|
}
|
|
}
|
|
|
|
JsonElement transform = GsonHelper.getNonNull(object, "transform");
|
|
|
|
// WORKAROUND: The case when transform format is wrongly specified!
|
|
if (transformFormat == TransformFormat.ATTRIBUTES && transform.isJsonArray()) {
|
|
transformFormat = TransformFormat.MATRIX;
|
|
} else if (transformFormat == TransformFormat.MATRIX && transform.isJsonObject()) {
|
|
transformFormat = TransformFormat.ATTRIBUTES;
|
|
}
|
|
|
|
OpenMatrix4f localMatrix = null;
|
|
|
|
switch (transformFormat) {
|
|
case MATRIX -> {
|
|
float[] matrixElements = ParseUtil.toFloatArrayPrimitive(GsonHelper.convertToJsonArray(transform, "transform"));
|
|
localMatrix = OpenMatrix4f.load(null, matrixElements);
|
|
localMatrix.transpose();
|
|
|
|
if (root) {
|
|
localMatrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
|
}
|
|
}
|
|
case ATTRIBUTES -> {
|
|
JsonObject transformObject = transform.getAsJsonObject();
|
|
JsonArray locArray = transformObject.get("loc").getAsJsonArray();
|
|
JsonArray rotArray = transformObject.get("rot").getAsJsonArray();
|
|
JsonArray scaArray = transformObject.get("sca").getAsJsonArray();
|
|
JointTransform jointTransform
|
|
= JointTransform.fromPrimitives(
|
|
locArray.get(0).getAsFloat()
|
|
, locArray.get(1).getAsFloat()
|
|
, locArray.get(2).getAsFloat()
|
|
, -rotArray.get(1).getAsFloat()
|
|
, -rotArray.get(2).getAsFloat()
|
|
, -rotArray.get(3).getAsFloat()
|
|
, rotArray.get(0).getAsFloat()
|
|
, scaArray.get(0).getAsFloat()
|
|
, scaArray.get(1).getAsFloat()
|
|
, scaArray.get(2).getAsFloat()
|
|
);
|
|
|
|
localMatrix = jointTransform.toMatrix();
|
|
|
|
if (root) {
|
|
localMatrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
|
}
|
|
}
|
|
}
|
|
|
|
Joint joint = new Joint(name, jointIdMap.get(name), localMatrix);
|
|
jointMap.put(name, joint);
|
|
|
|
if (object.has("children")) {
|
|
for (JsonElement children : object.get("children").getAsJsonArray()) {
|
|
joint.addSubJoints(getJoint(children.getAsJsonObject(), jointIdMap, jointMap, transformFormat, false));
|
|
}
|
|
}
|
|
|
|
return joint;
|
|
}
|
|
|
|
public AnimationClip loadClipForAnimation(StaticAnimation animation) {
|
|
if (this.rootJson == null) {
|
|
throw new AssetLoadingException("Can't find animation in path: " + animation);
|
|
}
|
|
|
|
if (animation.getArmature() == null) {
|
|
TiedUpRigConstants.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
|
}
|
|
|
|
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
|
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
|
boolean action = animation instanceof MainFrameAnimation;
|
|
boolean attack = animation instanceof AttackAnimation;
|
|
boolean noTransformData = !action && !attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER;
|
|
boolean root = true;
|
|
Armature armature = animation.getArmature().get();
|
|
Set<String> allowedJoints = Sets.newLinkedHashSet();
|
|
|
|
if (attack) {
|
|
for (Phase phase : ((AttackAnimation)animation).phases) {
|
|
for (AttackAnimation.JointColliderPair colliderInfo : phase.getColliders()) {
|
|
armature.gatherAllJointsInPathToTerminal(colliderInfo.getFirst().getName(), allowedJoints);
|
|
}
|
|
}
|
|
} else if (action) {
|
|
allowedJoints.add(ROOT_BONE);
|
|
}
|
|
|
|
AnimationClip clip = new AnimationClip();
|
|
|
|
for (JsonElement element : array) {
|
|
JsonObject jObject = element.getAsJsonObject();
|
|
String name = jObject.get("name").getAsString();
|
|
|
|
if (attack && FMLEnvironment.dist == Dist.DEDICATED_SERVER && !allowedJoints.contains(name)) {
|
|
if (name.equals(COORD_BONE)) {
|
|
root = false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
Joint joint = armature.searchJointByName(name);
|
|
|
|
if (joint == null) {
|
|
if (name.equals(COORD_BONE)) {
|
|
TransformSheet sheet = getTransformSheet(jObject, new OpenMatrix4f(), true, format);
|
|
|
|
if (action) {
|
|
((ActionAnimation)animation).addProperty(ActionAnimationProperty.COORD, sheet);
|
|
}
|
|
|
|
root = false;
|
|
continue;
|
|
} else {
|
|
TiedUpRigConstants.LOGGER.debug("[EpicFightMod] No joint named " + name + " in " + animation);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
|
|
|
if (!noTransformData) {
|
|
clip.addJointTransform(name, sheet);
|
|
}
|
|
|
|
float maxFrameTime = sheet.maxFrameTime();
|
|
|
|
if (clip.getClipTime() < maxFrameTime) {
|
|
clip.setClipTime(maxFrameTime);
|
|
}
|
|
|
|
root = false;
|
|
}
|
|
|
|
return clip;
|
|
}
|
|
|
|
public AnimationClip loadAllJointsClipForAnimation(StaticAnimation animation) {
|
|
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
|
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
|
boolean root = true;
|
|
|
|
if (animation.getArmature() == null) {
|
|
TiedUpRigConstants.LOGGER.error("Animation " + animation + " doesn't have an armature.");
|
|
}
|
|
|
|
Armature armature = animation.getArmature().get();
|
|
AnimationClip clip = new AnimationClip();
|
|
|
|
for (JsonElement element : array) {
|
|
JsonObject jObject = element.getAsJsonObject();
|
|
String name = jObject.get("name").getAsString();
|
|
Joint joint = armature.searchJointByName(name);
|
|
|
|
if (joint == null) {
|
|
if (TiedUpRigConstants.IS_DEV_ENV) {
|
|
TiedUpRigConstants.LOGGER.debug(animation.getRegistryName() + ": No joint named " + name + " in armature");
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
TransformSheet sheet = getTransformSheet(jObject, OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
|
clip.addJointTransform(name, sheet);
|
|
float maxFrameTime = sheet.maxFrameTime();
|
|
|
|
if (clip.getClipTime() < maxFrameTime) {
|
|
clip.setClipTime(maxFrameTime);
|
|
}
|
|
|
|
root = false;
|
|
}
|
|
|
|
return clip;
|
|
}
|
|
|
|
public JsonObject getRootJson() {
|
|
return this.rootJson;
|
|
}
|
|
|
|
public String getFileHash() {
|
|
return this.filehash;
|
|
}
|
|
|
|
public static TransformFormat getAsTransformFormatOrDefault(JsonObject jsonObject, String propertyName) {
|
|
return jsonObject.has(propertyName) ? ParseUtil.enumValueOfOrNull(TransformFormat.class, GsonHelper.getAsString(jsonObject, propertyName)) : TransformFormat.MATRIX;
|
|
}
|
|
|
|
public AnimationClip loadAnimationClip(Armature armature) {
|
|
TransformFormat format = getAsTransformFormatOrDefault(this.rootJson, "format");
|
|
JsonArray array = this.rootJson.get("animation").getAsJsonArray();
|
|
AnimationClip clip = new AnimationClip();
|
|
boolean root = true;
|
|
|
|
for (JsonElement element : array) {
|
|
JsonObject jObject = element.getAsJsonObject();
|
|
String name = jObject.get("name").getAsString();
|
|
Joint joint = armature.searchJointByName(name);
|
|
|
|
if (joint == null) {
|
|
continue;
|
|
}
|
|
|
|
TransformSheet sheet = getTransformSheet(element.getAsJsonObject(), OpenMatrix4f.invert(joint.getLocalTransform(), null), root, format);
|
|
clip.addJointTransform(name, sheet);
|
|
float maxFrameTime = sheet.maxFrameTime();
|
|
|
|
if (clip.getClipTime() < maxFrameTime) {
|
|
clip.setClipTime(maxFrameTime);
|
|
}
|
|
|
|
root = false;
|
|
}
|
|
|
|
return clip;
|
|
}
|
|
|
|
/**
|
|
* @param jObject
|
|
* @param invLocalTransform nullable if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
|
* @param rootCorrection no matter what the value is if transformFormat == {@link TransformFormat#ATTRIBUTES}
|
|
* @param transformFormat
|
|
* @return
|
|
*/
|
|
public static TransformSheet getTransformSheet(JsonObject jObject, @Nullable OpenMatrix4f invLocalTransform, boolean rootCorrection, TransformFormat transformFormat) throws AssetLoadingException, JsonParseException {
|
|
JsonArray timeArray = jObject.getAsJsonArray("time");
|
|
JsonArray transformArray = jObject.getAsJsonArray("transform");
|
|
|
|
if (timeArray.size() != transformArray.size()) {
|
|
throw new AssetLoadingException(
|
|
"Can't read transform sheet: the size of timestamp and transform array is different."
|
|
+ "timestamp array size: " + timeArray.size() + ", transform array size: " + transformArray.size()
|
|
);
|
|
}
|
|
|
|
int timesCount = timeArray.size();
|
|
List<Keyframe> keyframeList = Lists.newArrayList();
|
|
|
|
for (int i = 0; i < timesCount; i++) {
|
|
float timeStamp = timeArray.get(i).getAsFloat();
|
|
|
|
if (timeStamp < 0.0F) {
|
|
continue;
|
|
}
|
|
|
|
// WORKAROUND: The case when transform format is wrongly specified!
|
|
if (transformFormat == TransformFormat.ATTRIBUTES && transformArray.get(i).isJsonArray()) {
|
|
transformFormat = TransformFormat.MATRIX;
|
|
} else if (transformFormat == TransformFormat.MATRIX && transformArray.get(i).isJsonObject()) {
|
|
transformFormat = TransformFormat.ATTRIBUTES;
|
|
}
|
|
|
|
switch (transformFormat) {
|
|
case MATRIX -> {
|
|
JsonArray matrixArray = transformArray.get(i).getAsJsonArray();
|
|
float[] matrixElements = new float[16];
|
|
|
|
for (int j = 0; j < 16; j++) {
|
|
matrixElements[j] = matrixArray.get(j).getAsFloat();
|
|
}
|
|
|
|
OpenMatrix4f matrix = OpenMatrix4f.load(null, matrixElements);
|
|
matrix.transpose();
|
|
|
|
if (rootCorrection) {
|
|
matrix.mulFront(BLENDER_TO_MINECRAFT_COORD);
|
|
}
|
|
|
|
matrix.mulFront(invLocalTransform);
|
|
|
|
JointTransform transform = JointTransform.fromMatrix(matrix);
|
|
transform.rotation().normalize();
|
|
keyframeList.add(new Keyframe(timeStamp, transform));
|
|
}
|
|
case ATTRIBUTES -> {
|
|
JsonObject transformObject = transformArray.get(i).getAsJsonObject();
|
|
JsonArray locArray = transformObject.get("loc").getAsJsonArray();
|
|
JsonArray rotArray = transformObject.get("rot").getAsJsonArray();
|
|
JsonArray scaArray = transformObject.get("sca").getAsJsonArray();
|
|
JointTransform transform
|
|
= JointTransform.fromPrimitives(
|
|
locArray.get(0).getAsFloat()
|
|
, locArray.get(1).getAsFloat()
|
|
, locArray.get(2).getAsFloat()
|
|
, -rotArray.get(1).getAsFloat()
|
|
, -rotArray.get(2).getAsFloat()
|
|
, -rotArray.get(3).getAsFloat()
|
|
, rotArray.get(0).getAsFloat()
|
|
, scaArray.get(0).getAsFloat()
|
|
, scaArray.get(1).getAsFloat()
|
|
, scaArray.get(2).getAsFloat()
|
|
);
|
|
|
|
keyframeList.add(new Keyframe(timeStamp, transform));
|
|
}
|
|
}
|
|
}
|
|
|
|
TransformSheet sheet = new TransformSheet(keyframeList);
|
|
|
|
return sheet;
|
|
}
|
|
|
|
/**
|
|
* Determines how the transform is expressed in json
|
|
*
|
|
* {@link TransformFormat#MATRIX} be like,
|
|
* [0, 1, 2, ..., 15]
|
|
*
|
|
* {@link TransformFormat#ATTRIBUTES} be like,
|
|
* {
|
|
* "loc": [0, 0, 0],
|
|
* "rot": [0, 0, 0, 1],
|
|
* "sca": [1, 1, 1],
|
|
* }
|
|
*/
|
|
public enum TransformFormat {
|
|
MATRIX, ATTRIBUTES
|
|
}
|
|
}
|