primitive chunk table in world snapshot instead of colliding long-keyed hashmap, bounded cache for tinted/composited mob and banner textures, vanilla dump with nested model json, village benchmark and perf test scenes
This commit is contained in:
1 parent
b5baffebbd
commit
88bae41a03
10 files changed
+306
-38
No files matched your search
@@ -24,7 +24,7 @@ import java.util.logging.Logger;
|
||||
* times (after {@code -Dwarmup} runs), prints the median wall time and writes one PNG per scene to
|
||||
* {@code -Dout} (default ~/Dokumente/PixelPics-Entity-Renders/bench) for before/after pixel diffs.
|
||||
* Args: scene names to run (default all): farm_mobs, farm_empty (same view, no entities), farm_mobs_close,
|
||||
* farm_items, normal. {@code -Djava.util.concurrent.ForkJoinPool.common.parallelism=N} pins the thread count.
|
||||
* farm_items, village, normal. {@code -Djava.util.concurrent.ForkJoinPool.common.parallelism=N} pins the thread count.
|
||||
*/
|
||||
public class EntityBenchmark {
|
||||
|
||||
@@ -114,6 +114,33 @@ public class EntityBenchmark {
|
||||
}
|
||||
out.add(new Scene("farm_items", new Vector(0, 7, -1), new Vector(7, 1, 7), List.of(), items));
|
||||
|
||||
// Village: 300 mobs with per-mob texture compositing (villager type/profession/level overlays, dyed
|
||||
// sheep wool, dyed leather armor, tinted wolf collars) — exercises the tinted-texture cache.
|
||||
List<EntityState> village = new ArrayList<>();
|
||||
String[] vTypes = {"plains", "desert", "jungle", "savanna", "snow", "swamp", "taiga"};
|
||||
String[] profs = {"armorer", "butcher", "cartographer", "cleric", "farmer", "fisherman", "fletcher",
|
||||
"leatherworker", "librarian", "mason", "shepherd", "toolsmith", "weaponsmith", "nitwit", "none"};
|
||||
Random rv = new Random(7);
|
||||
for (int i = 0; i < 15; i++) {
|
||||
for (int j = 0; j < 20; j++) {
|
||||
double x = i * 1.4 + rv.nextDouble() * 0.3, z = j * 1.4 + rv.nextDouble() * 0.3;
|
||||
float yaw = rv.nextFloat() * 360;
|
||||
int dye = DYES[rv.nextInt(DYES.length)];
|
||||
EntityState m = switch (rv.nextInt(4)) {
|
||||
case 0 -> villager(x, z, yaw, vTypes[rv.nextInt(vTypes.length)], profs[rv.nextInt(profs.length)],
|
||||
1 + rv.nextInt(5), rv.nextInt(6) == 0);
|
||||
case 1 -> with(mob("sheep", x, 1, z, yaw, rv.nextInt(6) == 0), dye, null, 0);
|
||||
case 2 -> {
|
||||
EntityState.EquipPiece le = new EntityState.EquipPiece("leather", dye, null, null, false);
|
||||
yield with(mob("zombie", x, 1, z, yaw, false), 0, new EntityState.Equipment(le, le, le, le), 0);
|
||||
}
|
||||
default -> with(mob("wolf", x, 1, z, yaw, rv.nextInt(6) == 0), 0, null, dye);
|
||||
};
|
||||
village.add(m);
|
||||
}
|
||||
}
|
||||
out.add(new Scene("village", new Vector(-5, 10, -5), new Vector(10, 1, 13), village, List.of()));
|
||||
|
||||
// Normal scene: a handful of mobs incl. translucent (slime) and additive (charged creeper) layers,
|
||||
// a text display with the default translucent background and two items.
|
||||
List<EntityState> few = new ArrayList<>();
|
||||
@@ -142,11 +169,32 @@ public class EntityBenchmark {
|
||||
false, 0, 0, 0, false, null, null, null, 0, false, null);
|
||||
}
|
||||
|
||||
/** Vanilla DyeColor texture colours (white … black). */
|
||||
static final int[] DYES = {0xFFF9FFFE, 0xFFF9801D, 0xFFC74EBD, 0xFF3AB3DA, 0xFFFED83D, 0xFF80C71F, 0xFFF38BAA,
|
||||
0xFF474F52, 0xFF9D9D97, 0xFF169C9C, 0xFF8932B8, 0xFF3C44AA, 0xFF835432, 0xFF5E7C16, 0xFFB02E26, 0xFF1D1D21};
|
||||
|
||||
static EntityState villager(double x, double z, float yaw, String type, String prof, int level, boolean baby) {
|
||||
return new EntityState("villager", x, 1, z, yaw, baby, 0.6, 1.95,
|
||||
false, null, false, type, 0, 1.0, prof, level,
|
||||
null, false, false, false, null, null,
|
||||
false, 0, 0, 0, false, null, null, null, 0, false, null);
|
||||
}
|
||||
|
||||
/** {@code s} with wool tint, humanoid armor and collar dye replaced. */
|
||||
static EntityState with(EntityState s, int tint, EntityState.Equipment eq, int collar) {
|
||||
return new EntityState(s.typeKey(), s.x(), s.y(), s.z(), s.bodyYaw(), s.baby(), s.width(), s.height(),
|
||||
s.player(), s.skinUrl(), s.slim(), s.variant(), tint, s.sizeScale(), s.profession(), s.villagerLevel(),
|
||||
s.markings(), s.saddle(), s.chest(), s.arms(), s.bodyEquip(), eq, s.invisible(), s.pitch(),
|
||||
s.bodyDye(), s.tint2(), s.passenger(), s.rightHand(), s.leftHand(), s.headItem(), collar, s.powered(),
|
||||
s.mouthItem());
|
||||
}
|
||||
|
||||
/** The variant the game always supplies (cow/pig/chicken have no texture without one). */
|
||||
static String variant(String type) {
|
||||
return switch (type) {
|
||||
case "villager" -> "plains";
|
||||
case "cow", "pig", "chicken" -> "temperate";
|
||||
case "wolf" -> "pale";
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -31,7 +30,8 @@ import net.minecraft.world.entity.EntityType;
|
||||
|
||||
/**
|
||||
* Dumps the vanilla Java entity models straight from an (unobfuscated, 26.x+) Minecraft CLIENT jar into
|
||||
* the CEM-template JSON layout PixelPics loads ({@code {"models": {name: {"model": "<jem json>"}}}}).
|
||||
* the CEM-template JSON layout PixelPics loads ({@code {"models": {name: {"model": {<jem>}}}}}; the jem is a
|
||||
* nested object, not the template set's embedded JSON string, so the plugin parses the file only once).
|
||||
*
|
||||
* <p>Unlike the third-party CEM template set (rest geometry only), every entity model is instantiated
|
||||
* through its real renderer and posed by its real {@code setupAnim} with a default (idle, adult, not
|
||||
@@ -152,7 +152,7 @@ public class VanillaModelDump {
|
||||
parts.add(part("root", rootDef, baked, 0, 0, 0));
|
||||
jem.add("models", parts);
|
||||
JsonObject entry = new JsonObject();
|
||||
entry.addProperty("model", new Gson().toJson(jem));
|
||||
entry.add("model", jem); // nested object, not a JSON string: parsed once at plugin start
|
||||
out.putIfAbsent(name, entry);
|
||||
} catch (Throwable t) {
|
||||
failed.add(name + " (layer: " + t + ")");
|
||||
@@ -315,7 +315,7 @@ public class VanillaModelDump {
|
||||
JsonObject jem = dump(factory.apply(set.bakeLayer(loc)), state, name, failed);
|
||||
if (jem == null) return;
|
||||
JsonObject entry = new JsonObject();
|
||||
entry.addProperty("model", new Gson().toJson(jem));
|
||||
entry.add("model", jem);
|
||||
out.put(name, entry);
|
||||
} catch (Throwable t) {
|
||||
failed.add(name + " (" + t + ")");
|
||||
@@ -368,7 +368,7 @@ public class VanillaModelDump {
|
||||
String tex = textures.get(f);
|
||||
if (tex != null) jem.addProperty("texture", tex);
|
||||
JsonObject entry = new JsonObject();
|
||||
entry.addProperty("model", new Gson().toJson(jem));
|
||||
entry.add("model", jem);
|
||||
out.put(name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,10 @@ public class PPTest extends JavaPlugin {
|
||||
scenes.put("perf_farm", new Scene("perf_farm", this::perfFarm, -6, 9, -13, 4, 0, 0));
|
||||
scenes.put("perf_farm_close", new Scene("perf_farm_close", this::perfFarm, 0.5, 3, -11.5, 5, 0.5, -4));
|
||||
scenes.put("perf_items", new Scene("perf_items", this::perfItems, 0.5, 5, -10, 5, 0, -2));
|
||||
// Per-mob texture compositing (tinted-texture cache): villager type/profession/level, sheep colours, dyed
|
||||
// leather armor, wolf collars and patterned banners, all from a seeded RNG (deterministic).
|
||||
scenes.put("perf_village", new Scene("perf_village", this::perfVillage, -6, 9, -13, 4, 0, 0));
|
||||
scenes.put("perf_village_close", new Scene("perf_village_close", this::perfVillage, 0.5, 3, -11.5, 5, 0.5, -4));
|
||||
}
|
||||
|
||||
private void perfFarm(Location o) {
|
||||
@@ -156,6 +160,71 @@ public class PPTest extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
private void perfVillage(Location o) {
|
||||
Random r = new Random(11);
|
||||
List<Villager.Type> vTypes = registry(io.papermc.paper.registry.RegistryKey.VILLAGER_TYPE);
|
||||
List<Villager.Profession> profs = registry(io.papermc.paper.registry.RegistryKey.VILLAGER_PROFESSION);
|
||||
DyeColor[] dyes = DyeColor.values();
|
||||
for (int i = 0; i < 14; i++)
|
||||
for (int j = 0; j < 16; j++) {
|
||||
double x = -5 + i * 1.2, z = -10 + j * 1.2;
|
||||
DyeColor dye = dyes[r.nextInt(dyes.length)];
|
||||
LivingEntity e;
|
||||
switch (r.nextInt(4)) {
|
||||
case 0 -> {
|
||||
Villager v = rfSpawn(o, x, 0, z, 0, Villager.class);
|
||||
v.setVillagerType(vTypes.get(r.nextInt(vTypes.size())));
|
||||
if (r.nextInt(6) == 0) v.setBaby();
|
||||
else {
|
||||
v.setProfession(profs.get(r.nextInt(profs.size())));
|
||||
v.setVillagerLevel(1 + r.nextInt(5));
|
||||
}
|
||||
e = v;
|
||||
}
|
||||
case 1 -> {
|
||||
Sheep sh = rfSpawn(o, x, 0, z, 0, Sheep.class);
|
||||
sh.setColor(dye);
|
||||
if (r.nextInt(6) == 0) sh.setBaby();
|
||||
e = sh;
|
||||
}
|
||||
case 2 -> {
|
||||
Zombie zo = rfSpawn(o, x, 0, z, 0, Zombie.class);
|
||||
Material[] pieces = {Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE,
|
||||
Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS};
|
||||
ItemStack[] armor = new ItemStack[4];
|
||||
for (int k = 0; k < 4; k++) {
|
||||
armor[k] = it(pieces[k]);
|
||||
org.bukkit.inventory.meta.LeatherArmorMeta m =
|
||||
(org.bukkit.inventory.meta.LeatherArmorMeta) armor[k].getItemMeta();
|
||||
m.setColor(dye.getColor());
|
||||
armor[k].setItemMeta(m);
|
||||
}
|
||||
zo.getEquipment().setHelmet(armor[0]);
|
||||
zo.getEquipment().setChestplate(armor[1]);
|
||||
zo.getEquipment().setLeggings(armor[2]);
|
||||
zo.getEquipment().setBoots(armor[3]);
|
||||
e = zo;
|
||||
}
|
||||
default -> {
|
||||
Wolf wo = rfSpawn(o, x, 0, z, 0, Wolf.class);
|
||||
tame(wo, dye);
|
||||
if (r.nextInt(6) == 0) wo.setBaby();
|
||||
e = wo;
|
||||
}
|
||||
}
|
||||
e.setBodyYaw(r.nextInt(8) * 45);
|
||||
}
|
||||
for (int b = 0; b < 10; b++) {
|
||||
int x = -4 + b * 2;
|
||||
put(o, x, 0, 10, dyes[r.nextInt(dyes.length)].name().toLowerCase() + "_banner[rotation=" + r.nextInt(16) + "]");
|
||||
if (o.clone().add(x, 0, 10).getBlock().getState() instanceof org.bukkit.block.Banner bn) {
|
||||
bn.addPattern(new org.bukkit.block.banner.Pattern(dyes[r.nextInt(dyes.length)], org.bukkit.block.banner.PatternType.STRIPE_TOP));
|
||||
bn.addPattern(new org.bukkit.block.banner.Pattern(dyes[r.nextInt(dyes.length)], org.bukkit.block.banner.PatternType.CREEPER));
|
||||
bn.update(true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void perfItems(Location o) {
|
||||
Random r = new Random(7);
|
||||
Material[] mats = {Material.WHEAT, Material.CARROT, Material.EGG, Material.BONE, Material.ROTTEN_FLESH,
|
||||
|
||||
Reference in new issue
Block a user