add box UV bbmodel support, enable unit tests, hide Share button outside library tab
This commit is contained in:
@@ -103,6 +103,11 @@ neoForge {
|
|||||||
sourceSet(sourceSets.main)
|
sourceSet(sourceSets.main)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unitTest {
|
||||||
|
enable()
|
||||||
|
testedMod = mods."${mod_id}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up a dependency configuration called 'localRuntime'.
|
// Sets up a dependency configuration called 'localRuntime'.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.razz.dfashion.bbmodel;
|
package com.razz.dfashion.bbmodel;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -12,4 +13,10 @@ public record BbCube (
|
|||||||
float inflate,
|
float inflate,
|
||||||
Vector3f origin,
|
Vector3f origin,
|
||||||
Vector3f rotation,
|
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;
|
JsonObject res = root.has("resolution") ? root.getAsJsonObject("resolution") : null;
|
||||||
int resW = res != null && res.has("width") ? res.get("width").getAsInt() : 64;
|
int resW = res != null && res.has("width") ? res.get("width").getAsInt() : 64;
|
||||||
int resH = res != null && res.has("height") ? res.get("height").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<BbCube> cubes = new ArrayList<>();
|
||||||
List<BbLocator> locators = new ArrayList<>();
|
List<BbLocator> locators = new ArrayList<>();
|
||||||
|
|
||||||
@@ -73,7 +73,10 @@ public class BbModelParser {
|
|||||||
JsonObject obj = e.getAsJsonObject();
|
JsonObject obj = e.getAsJsonObject();
|
||||||
String type = obj.has("type") ? obj.get("type").getAsString() : "cube";
|
String type = obj.has("type") ? obj.get("type").getAsString() : "cube";
|
||||||
switch (type) {
|
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));
|
case "locator" -> locators.add(GSON.fromJson(obj, BbLocator.class));
|
||||||
// meshes, texture_meshes, null_objects, etc. silently dropped — renderer ignores them
|
// 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();
|
JsonArray outlinerArr = root.has("outliner") ? root.getAsJsonArray("outliner") : new JsonArray();
|
||||||
List<BbOutlinerNode> outliner = GSON.fromJson(outlinerArr, new TypeToken<List<BbOutlinerNode>>(){}.getType());
|
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);
|
validate(model);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
@@ -188,6 +191,44 @@ public class BbModelParser {
|
|||||||
if (!Float.isFinite(v)) throw new BadBbmodelException(label + " is not finite: " + v);
|
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> {
|
public static class Vector3fDeserializer implements JsonDeserializer<Vector3f> {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import java.util.List;
|
|||||||
public record Bbmodel(
|
public record Bbmodel(
|
||||||
int resolutionWidth,
|
int resolutionWidth,
|
||||||
int resolutionHeight,
|
int resolutionHeight,
|
||||||
|
boolean metaBoxUV,
|
||||||
List<BbCube> elements,
|
List<BbCube> elements,
|
||||||
List<BbLocator> locators,
|
List<BbLocator> locators,
|
||||||
List<BbGroup> groups,
|
List<BbGroup> groups,
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ public class ClosetScreen extends Screen {
|
|||||||
private final @Nullable ClosetBlockEntity closet;
|
private final @Nullable ClosetBlockEntity closet;
|
||||||
private CameraType previousCamera;
|
private CameraType previousCamera;
|
||||||
private @Nullable Button leftArrow, rightArrow;
|
private @Nullable Button leftArrow, rightArrow;
|
||||||
|
private @Nullable Button shareButton;
|
||||||
// Body/head yaw applied every tick. yRot (camera-facing yaw) is left untouched so
|
// 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.
|
// rotating doesn't pan the camera — only the player's visible body/head turn.
|
||||||
private float displayYaw = 0f;
|
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
|
// Open the shared-cosmetic upload form. All validation + parsing happens in the
|
||||||
// uploader's pipeline (SafePngReader + BbModelParser + BbmodelCodec); this button
|
// uploader's pipeline (SafePngReader + BbModelParser + BbmodelCodec); this button
|
||||||
// just swaps the closet UI into the form's widgets.
|
// just swaps the closet UI into the form's widgets.
|
||||||
addRenderableWidget(Button.builder(
|
shareButton = Button.builder(
|
||||||
Component.literal("Share"),
|
Component.literal("Share"),
|
||||||
b -> openShareForm()
|
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) {
|
private void selectCategory(String cat) {
|
||||||
selectedCategory = cat;
|
selectedCategory = cat;
|
||||||
cosmeticScroll = 0;
|
cosmeticScroll = 0;
|
||||||
lastKnownSkinCount = -1; // force the next skin-tab tick to re-evaluate the grid
|
lastKnownSkinCount = -1; // force the next skin-tab tick to re-evaluate the grid
|
||||||
|
if (shareButton != null) shareButton.visible = SHARED_TAB.equals(cat);
|
||||||
rebuildCosmeticRow();
|
rebuildCosmeticRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,11 +74,12 @@ public final class BbmodelCodec {
|
|||||||
private static Bbmodel readBbmodel(ByteBuf buf) {
|
private static Bbmodel readBbmodel(ByteBuf buf) {
|
||||||
int w = VarInt.read(buf);
|
int w = VarInt.read(buf);
|
||||||
int h = VarInt.read(buf);
|
int h = VarInt.read(buf);
|
||||||
|
boolean metaBoxUv = false;
|
||||||
List<BbCube> elements = readList(buf, BbmodelCodec::readCube);
|
List<BbCube> elements = readList(buf, BbmodelCodec::readCube);
|
||||||
List<BbLocator> locators = readList(buf, BbmodelCodec::readLocator);
|
List<BbLocator> locators = readList(buf, BbmodelCodec::readLocator);
|
||||||
List<BbGroup> groups = readList(buf, BbmodelCodec::readGroup);
|
List<BbGroup> groups = readList(buf, BbmodelCodec::readGroup);
|
||||||
List<BbOutlinerNode> outliner = readList(buf, BbmodelCodec::readOutlinerNode);
|
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 ----
|
// ---- BbCube ----
|
||||||
@@ -123,7 +124,7 @@ public final class BbmodelCodec {
|
|||||||
String key = readString(buf);
|
String key = readString(buf);
|
||||||
faces.put(key, readFace(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 ----
|
// ---- BbFace ----
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class BbmodelCodecSecurityTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void roundTripEmptyModel() {
|
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);
|
Bbmodel decoded = roundTrip(original);
|
||||||
assertEquals(64, decoded.resolutionWidth());
|
assertEquals(64, decoded.resolutionWidth());
|
||||||
assertEquals(64, decoded.resolutionHeight());
|
assertEquals(64, decoded.resolutionHeight());
|
||||||
@@ -47,9 +47,10 @@ class BbmodelCodecSecurityTest {
|
|||||||
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
||||||
0f,
|
0f,
|
||||||
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0),
|
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(cube), List.of(), List.of(),
|
||||||
List.of(new BbOutlinerNode.ElementRef("uuid-1")));
|
List.of(new BbOutlinerNode.ElementRef("uuid-1")));
|
||||||
Bbmodel decoded = roundTrip(original);
|
Bbmodel decoded = roundTrip(original);
|
||||||
@@ -63,12 +64,13 @@ class BbmodelCodecSecurityTest {
|
|||||||
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
||||||
0f,
|
0f,
|
||||||
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0),
|
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0),
|
||||||
Map.of());
|
Map.of(),
|
||||||
|
null, false, null);
|
||||||
BbLocator loc = new BbLocator("l", "loc",
|
BbLocator loc = new BbLocator("l", "loc",
|
||||||
new Vector3f(1, 2, 3), new Vector3f(0, 0, 0));
|
new Vector3f(1, 2, 3), new Vector3f(0, 0, 0));
|
||||||
BbGroup grp = new BbGroup("g", "group",
|
BbGroup grp = new BbGroup("g", "group",
|
||||||
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0));
|
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(cube), List.of(loc), List.of(grp),
|
||||||
List.of(new BbOutlinerNode.GroupRef("g", List.of(
|
List.of(new BbOutlinerNode.GroupRef("g", List.of(
|
||||||
new BbOutlinerNode.ElementRef("c"),
|
new BbOutlinerNode.ElementRef("c"),
|
||||||
@@ -121,7 +123,7 @@ class BbmodelCodecSecurityTest {
|
|||||||
// A valid decode is followed by extra bytes — callers MUST verify
|
// A valid decode is followed by extra bytes — callers MUST verify
|
||||||
// readableBytes() == 0 after decode (we do so in SharedCosmeticCache/uploader).
|
// readableBytes() == 0 after decode (we do so in SharedCosmeticCache/uploader).
|
||||||
// Codec itself doesn't enforce that; this test documents the contract.
|
// 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();
|
ByteBuf buf = Unpooled.buffer();
|
||||||
try {
|
try {
|
||||||
BbmodelCodec.CODEC.encode(buf, original);
|
BbmodelCodec.CODEC.encode(buf, original);
|
||||||
@@ -149,14 +151,16 @@ class BbmodelCodecSecurityTest {
|
|||||||
BbCube cubeA = new BbCube("u", "n",
|
BbCube cubeA = new BbCube("u", "n",
|
||||||
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
||||||
0f,
|
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",
|
BbCube cubeB = new BbCube("u", "n",
|
||||||
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
new Vector3f(0, 0, 0), new Vector3f(1, 1, 1),
|
||||||
0f,
|
0f,
|
||||||
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), b);
|
new Vector3f(0, 0, 0), new Vector3f(0, 0, 0), b,
|
||||||
Bbmodel modelA = new Bbmodel(64, 64, List.of(cubeA), List.of(), List.of(),
|
null, false, null);
|
||||||
|
Bbmodel modelA = new Bbmodel(64, 64, false, List.of(cubeA), List.of(), List.of(),
|
||||||
List.of(new BbOutlinerNode.ElementRef("u")));
|
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")));
|
List.of(new BbOutlinerNode.ElementRef("u")));
|
||||||
|
|
||||||
assertArrayEquals(encode(modelA), encode(modelB),
|
assertArrayEquals(encode(modelA), encode(modelB),
|
||||||
|
|||||||
Reference in New Issue
Block a user