Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
253
src/main/java/com/tiedup/remake/client/gltf/GlbParserUtils.java
Normal file
253
src/main/java/com/tiedup/remake/client/gltf/GlbParserUtils.java
Normal file
@@ -0,0 +1,253 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user