add box UV bbmodel support, enable unit tests, hide Share button outside library tab
This commit is contained in:
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user