render chests from the dumped vanilla models (single, double, trapped, ender, copper)
This commit is contained in:
1 parent
9a6c8b538d
commit
5852d4b367
7 files changed
+161
-21
No files matched your search
@@ -126,6 +126,7 @@ public final class BlockModelRegistry {
|
||||
|| m == Material.CHEST
|
||||
|| m == Material.TRAPPED_CHEST
|
||||
|| m == Material.ENDER_CHEST
|
||||
|| n.endsWith("COPPER_CHEST") // copper chests incl. weathered/waxed stages
|
||||
|| m == Material.CONDUIT
|
||||
|| m == Material.DECORATED_POT
|
||||
|| m == Material.BELL;
|
||||
|
||||
@@ -16,9 +16,7 @@ public final class BlockEntityModels {
|
||||
/** The CEM ({@code .jem}) model name for a block-entity. */
|
||||
public static String cemModel(BlockEntityState s) {
|
||||
return switch (s.kind()) {
|
||||
case CHEST, TRAPPED_CHEST, ENDER_CHEST -> switch (s.chestKind() == null
|
||||
? BlockEntityState.ChestKind.SINGLE
|
||||
: s.chestKind()) {
|
||||
case CHEST, TRAPPED_CHEST, ENDER_CHEST, COPPER_CHEST -> switch (chestKind(s)) {
|
||||
case LEFT -> "chest_left";
|
||||
case RIGHT -> "chest_right";
|
||||
case SINGLE -> "chest";
|
||||
@@ -36,6 +34,40 @@ public final class BlockEntityModels {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A block-entity model from the vanilla client dump ({@code layer/<layer>#<part>}, see
|
||||
* {@code tools/VanillaModelDump.java}) plus the vanilla BlockEntityRenderer pose: {@code pose} maps
|
||||
* Java model space (already scaled to blocks) into the block cell, origin at the cell's min corner —
|
||||
* exactly the PoseStack the renderer sets up before {@code submitModel}.
|
||||
*/
|
||||
public record VanillaModel(String layer, Affine pose) {}
|
||||
|
||||
/**
|
||||
* The vanilla model + renderer pose for a block-entity, or null when the type still uses the CEM
|
||||
* template path. To migrate another type, add its case here (layer name from the dump + the pose its
|
||||
* vanilla renderer applies); the baker needs no further changes.
|
||||
*/
|
||||
public static VanillaModel vanillaModel(BlockEntityState s) {
|
||||
// ChestRenderer: rotationAround(Y, -facing.toYRot(), 0.5, 0, 0.5); no Y flip (model y is up).
|
||||
return switch (s.kind()) {
|
||||
case CHEST, TRAPPED_CHEST, ENDER_CHEST, COPPER_CHEST -> new VanillaModel(
|
||||
switch (chestKind(s)) {
|
||||
case LEFT -> "layer/double_chest_left#main";
|
||||
case RIGHT -> "layer/double_chest_right#main";
|
||||
case SINGLE -> "layer/chest#main";
|
||||
},
|
||||
rotationAroundY(-s.facingDeg(), 0.5, 0.5));
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
/** JOML {@code rotationAround}: a Y rotation (deg) about the vertical line through (cx, ·, cz). */
|
||||
private static Affine rotationAroundY(double deg, double cx, double cz) {
|
||||
return Affine.translation(cx, 0, cz)
|
||||
.mul(Affine.rotY(Math.toRadians(deg)))
|
||||
.mul(Affine.translation(-cx, 0, -cz));
|
||||
}
|
||||
|
||||
private static String headModel(String headType) {
|
||||
if (headType == null) return "head";
|
||||
return switch (headType) {
|
||||
@@ -54,6 +86,7 @@ public final class BlockEntityModels {
|
||||
case CHEST -> chestTextures(paths, s, "normal");
|
||||
case TRAPPED_CHEST -> chestTextures(paths, s, "trapped");
|
||||
case ENDER_CHEST -> paths.add("entity/chest/ender");
|
||||
case COPPER_CHEST -> chestTextures(paths, s, s.colorName() == null ? "copper" : "copper_" + s.colorName());
|
||||
case SIGN, WALL_SIGN -> {
|
||||
String wood = s.wood() == null ? "oak" : s.wood();
|
||||
paths.add("entity/signs/" + wood);
|
||||
@@ -82,9 +115,12 @@ public final class BlockEntityModels {
|
||||
return out;
|
||||
}
|
||||
|
||||
private static BlockEntityState.ChestKind chestKind(BlockEntityState s) {
|
||||
return s.chestKind() == null ? BlockEntityState.ChestKind.SINGLE : s.chestKind();
|
||||
}
|
||||
|
||||
private static void chestTextures(List<String> paths, BlockEntityState s, String base) {
|
||||
BlockEntityState.ChestKind ck = s.chestKind() == null ? BlockEntityState.ChestKind.SINGLE : s.chestKind();
|
||||
switch (ck) {
|
||||
switch (chestKind(s)) {
|
||||
case LEFT -> paths.add("entity/chest/" + base + "_left");
|
||||
case RIGHT -> paths.add("entity/chest/" + base + "_right");
|
||||
case SINGLE -> paths.add("entity/chest/" + base);
|
||||
|
||||
@@ -17,9 +17,9 @@ public record BlockEntityState(
|
||||
int by,
|
||||
int bz,
|
||||
float facingDeg,
|
||||
ChestKind chestKind, // double-chest half (CHEST/TRAPPED_CHEST/ENDER_CHEST)
|
||||
ChestKind chestKind, // double-chest half (CHEST/TRAPPED_CHEST/COPPER_CHEST)
|
||||
int baseColorArgb, // banner base tint (white texture); 0 = none
|
||||
String colorName, // bed/shulker/banner colour variant ("red", "white", …); null = default
|
||||
String colorName, // bed/shulker/banner colour ("red", …) or copper-chest stage ("exposed", …); null = default
|
||||
String wood, // sign/hanging-sign wood ("oak", "spruce", …); null = default
|
||||
BedPart bedPart, // bed half
|
||||
String headType, // "skeleton","wither_skeleton","zombie","creeper","piglin","dragon","player"
|
||||
@@ -34,6 +34,7 @@ public record BlockEntityState(
|
||||
CHEST,
|
||||
TRAPPED_CHEST,
|
||||
ENDER_CHEST,
|
||||
COPPER_CHEST,
|
||||
SIGN,
|
||||
WALL_SIGN,
|
||||
HANGING_SIGN,
|
||||
|
||||
@@ -27,6 +27,10 @@ import java.util.Set;
|
||||
* <p>The CEM block-entity models are authored centred on X/Z with their base near Y=0 (after the px→
|
||||
* block scale), so the placement is {@code T(cell centre) · rotY(yaw) · T(localOffset)} and most types
|
||||
* only need defaults. Wall-mounted and scaled types (signs, wall heads, beds) override via {@link Place}.
|
||||
*
|
||||
* <p>Types mapped in {@link BlockEntityModels#vanillaModel} (currently chests) instead use the model
|
||||
* dumped from the vanilla client, placed by the vanilla renderer's own pose (see {@code bakeVanilla});
|
||||
* the CEM template path above stays as the fallback for everything else.
|
||||
*/
|
||||
public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
|
||||
|
||||
@@ -64,6 +68,8 @@ public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
|
||||
@Override
|
||||
public RenderedEntity bake(BlockEntityState s) {
|
||||
List<Layer> layers = layers(s);
|
||||
RenderedEntity vanilla = bakeVanilla(s, layers);
|
||||
if (vanilla != null) return vanilla;
|
||||
// Head model depends on the texture aspect (skeleton 64x32 vs zombie/player 64x64), so resolve it
|
||||
// from the chosen texture rather than statically.
|
||||
CemModelLoader.CemModel model = layers.isEmpty() ? null : models.get(modelName(s, layers.getFirst().tex));
|
||||
@@ -92,6 +98,31 @@ public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
|
||||
return cubes.isEmpty() ? null : RenderedEntity.of(cubes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla path: the model dumped from the client ({@link BlockEntityModels#vanillaModel}) placed by its
|
||||
* renderer's pose, {@code T(cell min corner) · pose · S(1/16) · CEM_TO_JAVA}. Position, rotation and
|
||||
* box-UV then match vanilla by construction (no per-type calibration offsets). Returns null when the
|
||||
* type has no vanilla mapping or the dump lacks the layer, so the caller falls back to the CEM template.
|
||||
*/
|
||||
private RenderedEntity bakeVanilla(BlockEntityState s, List<Layer> layers) {
|
||||
BlockEntityModels.VanillaModel vm = BlockEntityModels.vanillaModel(s);
|
||||
if (vm == null || layers.isEmpty()) return null;
|
||||
CemModelLoader.CemModel model = models.vanilla(vm.layer());
|
||||
if (model == null) return null;
|
||||
Affine pre = Affine.translation(s.bx(), s.by(), s.bz())
|
||||
.mul(vm.pose())
|
||||
.mul(Affine.scale(1 / 16.0))
|
||||
.mul(CemGeometry.CEM_TO_JAVA);
|
||||
List<EntityCube> cubes = new ArrayList<>();
|
||||
for (Layer layer : layers) {
|
||||
for (CemGeometry.Baked b :
|
||||
CemGeometry.bakeModel(model, layer.tex, pre, layer.hidden, layer.texW, layer.texH, layer.boxUv)) {
|
||||
cubes.add(new EntityCube(b.from(), b.to(), b.faces(), b.world()));
|
||||
}
|
||||
}
|
||||
return cubes.isEmpty() ? null : RenderedEntity.of(cubes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends flat text quads in front of (and behind) the sign board, rasterized from the captured
|
||||
* {@link BlockEntityState.SignText}. Built in the board's model-px frame and transformed by the same
|
||||
|
||||
+15
-5
@@ -96,10 +96,13 @@ public final class BlockEntitySnapshotBuilder {
|
||||
String n = mat.name();
|
||||
|
||||
// --- chests ---
|
||||
if (mat == Material.CHEST || mat == Material.TRAPPED_CHEST || mat == Material.ENDER_CHEST) {
|
||||
Kind kind = mat == Material.TRAPPED_CHEST
|
||||
? Kind.TRAPPED_CHEST
|
||||
: mat == Material.ENDER_CHEST ? Kind.ENDER_CHEST : Kind.CHEST;
|
||||
boolean copper = n.endsWith("COPPER_CHEST");
|
||||
if (mat == Material.CHEST || mat == Material.TRAPPED_CHEST || mat == Material.ENDER_CHEST || copper) {
|
||||
Kind kind = copper
|
||||
? Kind.COPPER_CHEST
|
||||
: mat == Material.TRAPPED_CHEST
|
||||
? Kind.TRAPPED_CHEST
|
||||
: mat == Material.ENDER_CHEST ? Kind.ENDER_CHEST : Kind.CHEST;
|
||||
ChestKind ck = ChestKind.SINGLE;
|
||||
if (data instanceof Chest cd) {
|
||||
ck = switch (cd.getType()) {
|
||||
@@ -107,7 +110,14 @@ public final class BlockEntitySnapshotBuilder {
|
||||
case RIGHT -> ChestKind.RIGHT;
|
||||
case SINGLE -> ChestKind.SINGLE;};
|
||||
}
|
||||
return base(kind, bx, by, bz, facingYaw(data)).chestKind(ck).build();
|
||||
Builder b = base(kind, bx, by, bz, facingYaw(data)).chestKind(ck);
|
||||
// Copper stage from the name: [WAXED_][EXPOSED_|WEATHERED_|OXIDIZED_]COPPER_CHEST.
|
||||
if (copper) {
|
||||
String stage =
|
||||
n.replace("WAXED_", "").replace("COPPER_CHEST", "").replace("_", "");
|
||||
b.colorName(stage.isEmpty() ? null : stage.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return b.build();
|
||||
}
|
||||
|
||||
// --- beds ---
|
||||
|
||||
Reference in new issue
Block a user