Files
TiedUp-/src/main/java/com/tiedup/remake/client/gltf/GlbParserUtils.java
NotEvil a71093ba9c Remove internal phase comments and format code
Strip all Phase references, TODO/FUTURE roadmap notes, and internal
planning comments from the codebase. Run Prettier for consistent
formatting across all Java files.
2026-04-12 01:25:55 +02:00

299 lines
10 KiB
Java

package com.tiedup.remake.client.gltf;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.nio.ByteBuffer;
import java.util.List;
import org.joml.Quaternionf;
import org.joml.Vector3f;
/**
* Shared stateless utilities for parsing binary glTF (.glb) files.
*
* <p>These methods are used by both {@link GlbParser} (single-armature bondage meshes)
* and {@link com.tiedup.remake.v2.furniture.client.FurnitureGlbParser FurnitureGlbParser}
* (multi-armature furniture meshes). Extracted to eliminate ~160 lines of verbatim
* duplication between the two parsers.</p>
*
* <p>All methods are pure functions (no state, no side effects).</p>
*/
public final class GlbParserUtils {
// glTF component type constants
public static final int BYTE = 5120;
public static final int UNSIGNED_BYTE = 5121;
public static final int SHORT = 5122;
public static final int UNSIGNED_SHORT = 5123;
public static final int UNSIGNED_INT = 5125;
public static final int FLOAT = 5126;
private GlbParserUtils() {}
// ---- Material name parsing ----
/**
* Parse the root "materials" array and extract each material's "name" field.
* Returns an empty array if no materials are present.
*/
public static String[] parseMaterialNames(JsonObject root) {
if (!root.has("materials") || !root.get("materials").isJsonArray()) {
return new String[0];
}
JsonArray materials = root.getAsJsonArray("materials");
String[] names = new String[materials.size()];
for (int i = 0; i < materials.size(); i++) {
JsonObject mat = materials.get(i).getAsJsonObject();
names[i] = mat.has("name") ? mat.get("name").getAsString() : null;
}
return names;
}
// ---- Array flattening utilities ----
public static float[] flattenFloats(List<float[]> arrays) {
int total = 0;
for (float[] a : arrays) total += a.length;
float[] result = new float[total];
int offset = 0;
for (float[] a : arrays) {
System.arraycopy(a, 0, result, offset, a.length);
offset += a.length;
}
return result;
}
public static int[] flattenInts(List<int[]> arrays) {
int total = 0;
for (int[] a : arrays) total += a.length;
int[] result = new int[total];
int offset = 0;
for (int[] a : arrays) {
System.arraycopy(a, 0, result, offset, a.length);
offset += a.length;
}
return result;
}
// ---- Accessor reading utilities ----
public static float[] readFloatAccessor(
JsonArray accessors,
JsonArray bufferViews,
ByteBuffer binData,
int accessorIdx
) {
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
int count = accessor.get("count").getAsInt();
int componentType = accessor.get("componentType").getAsInt();
String type = accessor.get("type").getAsString();
int components = typeComponents(type);
int bvIdx = accessor.get("bufferView").getAsInt();
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
int byteOffset =
(bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
(accessor.has("byteOffset")
? accessor.get("byteOffset").getAsInt()
: 0);
int byteStride = bv.has("byteStride")
? bv.get("byteStride").getAsInt()
: 0;
int totalElements = count * components;
float[] result = new float[totalElements];
int componentSize = componentByteSize(componentType);
int stride = byteStride > 0 ? byteStride : components * componentSize;
for (int i = 0; i < count; i++) {
int pos = byteOffset + i * stride;
for (int c = 0; c < components; c++) {
binData.position(pos + c * componentSize);
result[i * components + c] = readComponentAsFloat(
binData,
componentType
);
}
}
return result;
}
public static int[] readIntAccessor(
JsonArray accessors,
JsonArray bufferViews,
ByteBuffer binData,
int accessorIdx
) {
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
int count = accessor.get("count").getAsInt();
int componentType = accessor.get("componentType").getAsInt();
String type = accessor.get("type").getAsString();
int components = typeComponents(type);
int bvIdx = accessor.get("bufferView").getAsInt();
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
int byteOffset =
(bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
(accessor.has("byteOffset")
? accessor.get("byteOffset").getAsInt()
: 0);
int byteStride = bv.has("byteStride")
? bv.get("byteStride").getAsInt()
: 0;
int totalElements = count * components;
int[] result = new int[totalElements];
int componentSize = componentByteSize(componentType);
int stride = byteStride > 0 ? byteStride : components * componentSize;
for (int i = 0; i < count; i++) {
int pos = byteOffset + i * stride;
for (int c = 0; c < components; c++) {
binData.position(pos + c * componentSize);
result[i * components + c] = readComponentAsInt(
binData,
componentType
);
}
}
return result;
}
public static float readComponentAsFloat(
ByteBuffer buf,
int componentType
) {
return switch (componentType) {
case FLOAT -> buf.getFloat();
case BYTE -> buf.get() / 127.0f;
case UNSIGNED_BYTE -> (buf.get() & 0xFF) / 255.0f;
case SHORT -> buf.getShort() / 32767.0f;
case UNSIGNED_SHORT -> (buf.getShort() & 0xFFFF) / 65535.0f;
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) /
(float) 0xFFFFFFFFL;
default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
};
}
public static int readComponentAsInt(ByteBuffer buf, int componentType) {
return switch (componentType) {
case BYTE -> buf.get();
case UNSIGNED_BYTE -> buf.get() & 0xFF;
case SHORT -> buf.getShort();
case UNSIGNED_SHORT -> buf.getShort() & 0xFFFF;
case UNSIGNED_INT -> buf.getInt();
case FLOAT -> (int) buf.getFloat();
default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
};
}
public static int typeComponents(String type) {
return switch (type) {
case "SCALAR" -> 1;
case "VEC2" -> 2;
case "VEC3" -> 3;
case "VEC4" -> 4;
case "MAT4" -> 16;
default -> throw new IllegalArgumentException(
"Unknown accessor type: " + type
);
};
}
public static int componentByteSize(int componentType) {
return switch (componentType) {
case BYTE, UNSIGNED_BYTE -> 1;
case SHORT, UNSIGNED_SHORT -> 2;
case UNSIGNED_INT, FLOAT -> 4;
default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
};
}
// ---- Deep-copy utility ----
/**
* Deep-copy an AnimationClip (preserves original data before MC conversion).
*/
public static GltfData.AnimationClip deepCopyClip(
GltfData.AnimationClip clip
) {
Quaternionf[][] rawRotations =
new Quaternionf[clip.rotations().length][];
for (int j = 0; j < clip.rotations().length; j++) {
if (clip.rotations()[j] != null) {
rawRotations[j] = new Quaternionf[clip.rotations()[j].length];
for (int f = 0; f < clip.rotations()[j].length; f++) {
rawRotations[j][f] = new Quaternionf(
clip.rotations()[j][f]
);
}
}
}
Vector3f[][] rawTranslations = null;
if (clip.translations() != null) {
rawTranslations = new Vector3f[clip.translations().length][];
for (int j = 0; j < clip.translations().length; j++) {
if (clip.translations()[j] != null) {
rawTranslations[j] =
new Vector3f[clip.translations()[j].length];
for (int f = 0; f < clip.translations()[j].length; f++) {
rawTranslations[j][f] = new Vector3f(
clip.translations()[j][f]
);
}
}
}
}
return new GltfData.AnimationClip(
clip.timestamps().clone(),
rawRotations,
rawTranslations,
clip.frameCount()
);
}
// ---- Coordinate system conversion ----
/**
* Convert an animation clip's rotations and translations to MC space.
* Negate qx/qy for rotations and negate tx/ty for translations.
*/
public static void convertAnimationToMinecraftSpace(
GltfData.AnimationClip clip,
int jointCount
) {
if (clip == null) return;
Quaternionf[][] rotations = clip.rotations();
for (int j = 0; j < jointCount; j++) {
if (j < rotations.length && rotations[j] != null) {
for (Quaternionf q : rotations[j]) {
q.x = -q.x;
q.y = -q.y;
}
}
}
Vector3f[][] translations = clip.translations();
if (translations != null) {
for (int j = 0; j < jointCount; j++) {
if (j < translations.length && translations[j] != null) {
for (Vector3f t : translations[j]) {
t.x = -t.x;
t.y = -t.y;
}
}
}
}
}
}