render entities from vanilla client models, add items, displays and leashes

- dump entity models straight from the unobfuscated client jar
  (tools/VanillaModelDump.java, tools/update-vanilla-models.sh): models posed
  by vanilla setupAnim, baby/variant/layer models, renderer transforms,
  default textures, pose variants (sitting, holding items, illager arms)
  and all layer definitions; the old CEM template is only a fallback now
- fix adult zombies captured as babies, zero-thickness planes (wings,
  fins) and back faces in the entity intersector
- held items, head items (pumpkins, skulls), shields, elytra, saddles and
  body equipment for every mount, armor following the pose
- block/item/text display entities, dropped items, falling blocks, primed
  TNT, minecart contents, block entities inside displays, leash ropes
- item models resolved from the 26.x item definitions with display
  transforms; generated items extruded like vanilla
- bundle the vanilla font definitions as fallback so sign/text rendering
  works with packs that lack font/*.json
- one failing entity no longer aborts the whole photo
- edge-case test plugin (tools/edgecase-plugin) and offline harnesses
This commit is contained in:
MineTec committed 2026-11-04 13:11:39 +01:00
1 parent 5904814e69
commit 32afe08f03
41 files changed
+3761 -370

No files matched your search

+19 -10
View File
@@ -39,7 +39,9 @@ public class EntityTestRender {
"slime","sniffer","snow_golem","spider","squid","stray","strider","tadpole","trader_llama","tropical_fish",
"turtle","vex","villager","vindicator","wandering_trader","warden","witch","wither","wither_skeleton","wolf",
"zoglin","zombie","zombie_horse","zombie_villager","zombified_piglin","ender_dragon","player","mannequin",
"nautilus","zombie_nautilus_coral","parched","camel_husk","oak_boat"
"nautilus","zombie_nautilus","parched","camel_husk","oak_boat","sulfur_cube","oak_chest_boat","bamboo_raft",
"baby:zombie","baby:cow","baby:villager","baby:wolf","baby:sheep","baby:chicken","baby:horse","baby:pig",
"minecart","end_crystal","arrow","trident","leash_knot","evoker_fangs","wither_skull","shulker_bullet","cushion"
};
// Representative variants (the game always supplies these; null base textures wouldn't exist otherwise).
@@ -49,7 +51,8 @@ public class EntityTestRender {
Map.entry("llama", "creamy"), Map.entry("trader_llama", "creamy"), Map.entry("fox", "red"),
Map.entry("mooshroom", "red"), Map.entry("frog", "temperate"), Map.entry("panda", "normal"),
Map.entry("cow", "temperate"), Map.entry("pig", "temperate"), Map.entry("chicken", "temperate"),
Map.entry("villager", "taiga"), Map.entry("zombie_villager", "swamp")
Map.entry("villager", "taiga"), Map.entry("zombie_villager", "swamp"),
Map.entry("tropical_fish", "betty"), Map.entry("pufferfish", "big"), Map.entry("zombie_nautilus", "warm")
);
// Villager profession / level for the standalone render (biome type comes from VAR above).
@@ -62,10 +65,11 @@ public class EntityTestRender {
// Horse/llama/donkey equipment for the standalone render.
static final Map<String, String> MARK = Map.of("horse", "blackdots"); // coat markings
static final java.util.Set<String> SADDLE = java.util.Set.of("horse", "donkey", "mule", "nautilus");
static final java.util.Set<String> SADDLE = java.util.Set.of("horse", "donkey", "mule", "nautilus", "pig", "strider", "camel");
static final java.util.Set<String> CHEST = java.util.Set.of("llama", "donkey");
static final Map<String, String> EQUIP = Map.ofEntries( // armor / carpet
Map.entry("horse", "diamond"), Map.entry("llama", "red"), Map.entry("trader_llama", "trader_llama"),
Map.entry("horse", "diamond"), Map.entry("llama", "red"), Map.entry("happy_ghast", "blue_harness"),
Map.entry("wolf", "armadillo_scute"), Map.entry("trader_llama", "trader_llama"),
Map.entry("nautilus", "diamond")
);
@@ -105,7 +109,8 @@ public class EntityTestRender {
BlockModelRegistry registry = new BlockModelRegistry(reader, textures);
BiomeTintProvider tint = new BiomeTintProvider(textures);
eu.mhsl.minecraft.pixelpics.render.entity.cem.CemModelLoader geo = new eu.mhsl.minecraft.pixelpics.render.entity.cem.CemModelLoader();
int n = geo.load(new java.io.FileInputStream("/tmp/cem_models.json"), log);
int n = geo.load(new java.io.FileInputStream("src/main/resources/cem/cem_template_models.json"), log);
n = geo.load(new java.io.FileInputStream("src/main/resources/cem/vanilla_entity_models.json"), log);
log.info("Loaded " + n + " geometries");
SkinCache skins = new SkinCache();
eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker baker = new eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker(geo, textures, skins);
@@ -145,7 +150,7 @@ public class EntityTestRender {
File single = new File(outDir, "einzeln");
single.mkdirs();
for (int i = 0; i < list.length; i++) {
ImageIO.write(cells.get(i), "png", new File(single, list[i] + ".png"));
ImageIO.write(cells.get(i), "png", new File(single, list[i].replace(':', '_') + ".png"));
}
// Compose contact-sheet pages: 2 columns.
@@ -172,11 +177,15 @@ public class EntityTestRender {
static BufferedImage renderEntity(DefaultScreenRenderer renderer, eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker baker, WorldSnapshot world,
SkyContext sky, String key, float yaw) {
boolean baby = key.startsWith("baby:");
if (baby) key = key.substring(5);
boolean isPlayer = key.equals("player");
EntityState s = new EntityState(key, 0, 0, 0, yaw, false, 0.8, 1.0,
isPlayer, null, false, VAR.get(key), 0, 1.0, PROF.get(key), LVL.getOrDefault(key, 0),
MARK.get(key), SADDLE.contains(key), CHEST.contains(key), EQUIP.get(key), ARMOR.get(key),
INVISIBLE.contains(key));
int tint = key.equals("tropical_fish") ? 0xFFF9801D : (key.equals("sheep") ? 0xFFF9FFFE : 0);
int tint2 = key.equals("tropical_fish") ? 0xFF3AB3DA : 0;
EntityState s = new EntityState(key, 0, 0, 0, yaw, baby, 0.8, 1.0,
isPlayer, null, false, VAR.get(key), tint, 1.0, PROF.get(key), LVL.getOrDefault(key, 0),
MARK.get(key), SADDLE.contains(key), CHEST.contains(key), EQUIP.get(key), baby ? null : ARMOR.get(key),
!baby && INVISIBLE.contains(key), 0, 0, tint2, false, null, null, null);
RenderedEntity re = baker.bake(s);
if (re == null || re.cubes.isEmpty()) { // invisible with no equipment -> nothing renders
return new BufferedImage(TW, TH, BufferedImage.TYPE_INT_RGB);