add box UV bbmodel support, enable unit tests, hide Share button outside library tab

main
MomokoKoigakubo 4 weeks ago
parent 1d92791f74
commit b5503545b3

@ -103,6 +103,11 @@ neoForge {
sourceSet(sourceSets.main)
}
}
unitTest {
enable()
testedMod = mods."${mod_id}"
}
}
// Sets up a dependency configuration called 'localRuntime'.

@ -1,5 +1,6 @@
package com.razz.dfashion.bbmodel;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import java.util.Map;
@ -12,4 +13,10 @@ public record BbCube (
float inflate,
Vector3f origin,
Vector3f rotation,
Map<String, BbFace> faces){}
Map<String, BbFace> faces,
@Nullable
int[] uvOffset,
boolean mirrorUv,
@Nullable
Boolean boxUv
){}

@ -64,7 +64,7 @@ public class BbModelParser {
JsonObject res = root.has("resolution") ? root.getAsJsonObject("resolution") : null;
int resW = res != null && res.has("width") ? res.get("width").getAsInt() : 64;
int resH = res != null && res.has("height") ? res.get("height").getAsInt() : 64;
boolean metaBoxUv = meta != null && meta.has("box_uv") && meta.get("box_uv").getAsBoolean();
List<BbCube> cubes = new ArrayList<>();
List<BbLocator> locators = new ArrayList<>();
@ -73,7 +73,10 @@ public class BbModelParser {
JsonObject obj = e.getAsJsonObject();
String type = obj.has("type") ? obj.get("type").getAsString() : "cube";
switch (type) {
case "cube" -> cubes.add(GSON.fromJson(obj, BbCube.class));
case "cube" -> {
BbCube raw = GSON.fromJson(obj, BbCube.class);
cubes.add(expandBoxUvIfNeeded(raw, metaBoxUv));
}
case "locator" -> locators.add(GSON.fromJson(obj, BbLocator.class));
// meshes, texture_meshes, null_objects, etc. silently dropped — renderer ignores them
}
@ -85,7 +88,7 @@ public class BbModelParser {
JsonArray outlinerArr = root.has("outliner") ? root.getAsJsonArray("outliner") : new JsonArray();
List<BbOutlinerNode> outliner = GSON.fromJson(outlinerArr, new TypeToken<List<BbOutlinerNode>>(){}.getType());
Bbmodel model = new Bbmodel(resW, resH, cubes, locators, groups, outliner);
Bbmodel model = new Bbmodel(resW, resH, metaBoxUv, cubes, locators, groups, outliner);
validate(model);
return model;
}
@ -188,6 +191,44 @@ public class BbModelParser {
if (!Float.isFinite(v)) throw new BadBbmodelException(label + " is not finite: " + v);
}
private static BbCube expandBoxUvIfNeeded(BbCube c, boolean metaBoxUv) {
boolean effectiveBoxUv = c.boxUv() != null ? c.boxUv() : metaBoxUv;
if (!effectiveBoxUv) return c;
if (c.uvOffset() == null) return c;
if (c.faces() != null && !c.faces().isEmpty()) return c;
Map<String, BbFace> synth = buildBoxUvFaces(c);
return new BbCube(
c.uuid(), c.name(), c.from(), c.to(), c.inflate(),
c.origin(), c.rotation(), synth,
c.uvOffset(), c.mirrorUv(), c.boxUv()
);
}
private static Map<String, BbFace> buildBoxUvFaces(BbCube c) {
float u = c.uvOffset()[0];
float v = c.uvOffset()[1];
float w = c.to().x - c.from().x;
float h = c.to().y - c.from().y;
float d = c.to().z - c.from().z;
Map<String, BbFace> faces = new java.util.LinkedHashMap<>();
faces.put("up", new BbFace(new float[]{u + d, v, u + d + w, v + d}, 0, 0));
faces.put("down", new BbFace(new float[]{u + d + w, v, u + d + 2*w, v + d}, 0, 0));
faces.put("east", new BbFace(new float[]{u, v + d, u + d, v + d + h}, 0, 0));
faces.put("north", new BbFace(new float[]{u + d, v + d, u + d + w, v + d + h}, 0, 0));
faces.put("west", new BbFace(new float[]{u + d + w, v + d, u + d + w + d, v + d + h}, 0, 0));
faces.put("south", new BbFace(new float[]{u + d + w + d, v + d, u + d + 2*w + d, v + d + h}, 0, 0));
if (Boolean.TRUE.equals(c.mirrorUv())) { // c.mirrorUv() is primitive boolean, just use it:
BbFace e = faces.get("east");
faces.put("east", faces.get("west"));
faces.put("west", e);
}
return faces;
}
public static class Vector3fDeserializer implements JsonDeserializer<Vector3f> {
@Override

@ -5,6 +5,7 @@ import java.util.List;
public record Bbmodel(
int resolutionWidth,
int resolutionHeight,
boolean metaBoxUV,
List<BbCube> elements,
List<BbLocator> locators,
List<BbGroup> groups,

@ -120,6 +120,7 @@ public class ClosetScreen extends Screen {
private final @Nullable ClosetBlockEntity closet;
private CameraType previousCamera;
private @Nullable Button leftArrow, rightArrow;
private @Nullable Button shareButton;
// Body/head yaw applied every tick. yRot (camera-facing yaw) is left untouched so
// rotating doesn't pan the camera — only the player's visible body/head turn.
private float displayYaw = 0f;
@ -286,16 +287,19 @@ public class ClosetScreen extends Screen {
// Open the shared-cosmetic upload form. All validation + parsing happens in the
// uploader's pipeline (SafePngReader + BbModelParser + BbmodelCodec); this button
// just swaps the closet UI into the form's widgets.
addRenderableWidget(Button.builder(
shareButton = Button.builder(
Component.literal("Share"),
b -> openShareForm()
).bounds(this.width - 140, y, 60, 20).build());
).bounds(this.width - 140, y, 60, 20).build();
shareButton.visible = SHARED_TAB.equals(selectedCategory);
addRenderableWidget(shareButton);
}
private void selectCategory(String cat) {
selectedCategory = cat;
cosmeticScroll = 0;
lastKnownSkinCount = -1; // force the next skin-tab tick to re-evaluate the grid
if (shareButton != null) shareButton.visible = SHARED_TAB.equals(cat);
rebuildCosmeticRow();
}

@ -74,11 +74,12 @@ public final class BbmodelCodec {
private static Bbmodel readBbmodel(ByteBuf buf) {
int w = VarInt.read(buf);
int h = VarInt.read(buf);
boolean metaBoxUv = false;
List<BbCube> elements = readList(buf, BbmodelCodec::readCube);
List<BbLocator> locators = readList(buf, BbmodelCodec::readLocator);
List<BbGroup> groups = readList(buf, BbmodelCodec::readGroup);
List<BbOutlinerNode> outliner = readList(buf, BbmodelCodec::readOutlinerNode);
return new Bbmodel(w, h, elements, locators, groups, outliner);
return new Bbmodel(w, h, metaBoxUv, elements, locators, groups, outliner);
}
// ---- BbCube ----
@ -123,7 +124,7 @@ public final class BbmodelCodec {
String key = readString(buf);
faces.put(key, readFace(buf));
}
return new BbCube(uuid, name, from, to, inflate, origin, rotation, faces);
return new BbCube(uuid, name, from, to, inflate, origin, rotation, faces, null, false, null);
}
// ---- BbFace ----

@ -33,7 +33,7 @@ class BbmodelCodecSecurityTest {
@Test
void roundTripEmptyModel() {
Bbmodel original = new Bbmodel(64, 64, List.of(), List.of(), List.of(), List.of());
Bbmodel original = new Bbmodel(64, 64, false, List.of(), List.of(), List.of(), List.of());
Bbmodel decoded = roundTrip(original);
assertEquals(64, decoded.resolutionWidth());
assertEquals(64, decoded.resolutionHeight());
@ -47,9 +47,10 @@ class BbmodelCodecSecurityTest {
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
0f,
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0),
Map.of()
Map.of(),
null, false, null
);
Bbmodel original = new Bbmodel(64, 64,
Bbmodel original = new Bbmodel(64, 64, false,
List.of(cube), List.of(), List.of(),
List.of(new BbOutlinerNode.ElementRef("uuid-1")));
Bbmodel decoded = roundTrip(original);
@ -63,12 +64,13 @@ class BbmodelCodecSecurityTest {
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
0f,
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0),
Map.of());
Map.of(),
null, false, null);
BbLocator loc = new BbLocator("l", "loc",
new Vector3f(1, 2, 3), new Vector3f(0, 0, 0));
BbGroup grp = new BbGroup("g", "group",
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0));
Bbmodel original = new Bbmodel(64, 64,
Bbmodel original = new Bbmodel(64, 64, false,
List.of(cube), List.of(loc), List.of(grp),
List.of(new BbOutlinerNode.GroupRef("g", List.of(
new BbOutlinerNode.ElementRef("c"),
@ -121,7 +123,7 @@ class BbmodelCodecSecurityTest {
// A valid decode is followed by extra bytes — callers MUST verify
// readableBytes() == 0 after decode (we do so in SharedCosmeticCache/uploader).
// Codec itself doesn't enforce that; this test documents the contract.
Bbmodel original = new Bbmodel(64, 64, List.of(), List.of(), List.of(), List.of());
Bbmodel original = new Bbmodel(64, 64, false, List.of(), List.of(), List.of(), List.of());
ByteBuf buf = Unpooled.buffer();
try {
BbmodelCodec.CODEC.encode(buf, original);
@ -149,14 +151,16 @@ class BbmodelCodecSecurityTest {
BbCube cubeA = new BbCube("u", "n",
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
0f,
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), a);
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), a,
null, false, null);
BbCube cubeB = new BbCube("u", "n",
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
0f,
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), b);
Bbmodel modelA = new Bbmodel(64, 64, List.of(cubeA), List.of(), List.of(),
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), b,
null, false, null);
Bbmodel modelA = new Bbmodel(64, 64, false, List.of(cubeA), List.of(), List.of(),
List.of(new BbOutlinerNode.ElementRef("u")));
Bbmodel modelB = new Bbmodel(64, 64, List.of(cubeB), List.of(), List.of(),
Bbmodel modelB = new Bbmodel(64, 64, false, List.of(cubeB), List.of(), List.of(),
List.of(new BbOutlinerNode.ElementRef("u")));
assertArrayEquals(encode(modelA), encode(modelB),

Loading…
Cancel
Save