simplify tools: shared harness setup (pipeline, block proxy, render job, framing, record copy, label cells, contact sheets) for the standalone renderers and the benchmark, block-entity scenes as name/yaw records, one dump pipeline for mob and avatar renderers with a shared submit helper, transform record and jem/entry/subset helpers, posed block entities from direct layer constants, pptest scenes registered once with settle-tick and resolution tables and put() for all blocks, jdk detection and full error log in the dump script, pptest jar rebuilt instead of updated, gradle task printing the tool classpath, comments trimmed

This commit is contained in:
MineTec committed 2026-11-05 03:24:30 +01:00
1 parent b242adb6d2
commit 02e052787c
11 files changed
+1449 -1686

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
import eu.mhsl.minecraft.pixelpics.assets.*;
import eu.mhsl.minecraft.pixelpics.assets.font.FontLoader;
import eu.mhsl.minecraft.pixelpics.render.entity.*;
import eu.mhsl.minecraft.pixelpics.render.entity.cem.BlockEntityBaker;
import eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker;
import eu.mhsl.minecraft.pixelpics.render.entity.cem.CemModelLoader;
import eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer;
import eu.mhsl.minecraft.pixelpics.render.render.RenderJob;
import eu.mhsl.minecraft.pixelpics.render.sky.SkyContext;
import eu.mhsl.minecraft.pixelpics.render.snapshot.WorldSnapshot;
import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.util.Vector;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import java.util.function.IntFunction;
import java.util.logging.Logger;
/**
* Shared setup of the standalone render tools (no server: EntityTestRender, BlockEntityTestRender,
* ObjectTestRender, EntityBenchmark). Renders with the test server's resource pack and the repo's model JSONs.
*
* <p>Build/run from the repo root with a JDK 25: {@code CP=build/classes/java/main:build/resources/main:$(./gradlew -q
* printCompileClasspath)}, {@code javac -d <cls> -cp $CP $(ls tools/*.java | grep -v VanillaModelDump)},
* {@code java -cp <cls>:$CP [-Dout=<dir>] <Tool> [args]}.
*/
final class Harness {
static final String PACK_ROOT = "/home/elias/Dokumente/mcTestServer/plugins/PixelPics";
static final int SSAA = 3;
static final double V_FOV_HALF = Math.toRadians(DefaultScreenRenderer.V_FOV_DEG / 2);
static final SkyContext NOON = new SkyContext(6000, 0, 6000);
final Logger log;
final ResourcePack pack;
final AssetReader reader;
final TextureCache textures;
final BlockModelRegistry registry;
final CemModelLoader geo = new CemModelLoader();
final CemBaker baker;
final BlockEntityBaker beBaker;
final DefaultScreenRenderer renderer;
Harness(String logName) throws IOException {
log = Logger.getLogger(logName);
pack = ResourcePackLoader.load(new File(PACK_ROOT, "resourcepack"), log).orElseThrow();
reader = new AssetReader(pack);
textures = new TextureCache(pack);
registry = new BlockModelRegistry(reader, textures);
// Template set = entity fallback only; the vanilla dump is the primary model source.
for (String f : new String[] {"cem_template_models.json", "vanilla_entity_models.json"}) {
try (InputStream in = new FileInputStream("src/main/resources/cem/" + f)) {
geo.load(in, log);
}
}
SkinCache skins = new SkinCache();
baker = new CemBaker(geo, textures, skins);
beBaker = new BlockEntityBaker(geo, textures, skins, FontLoader.load(pack, textures, log));
renderer = new DefaultScreenRenderer(registry, new BiomeTintProvider(textures), textures, baker, beBaker, log);
}
/**
* Server-free BlockData stand-in: material + blockstate string are all the model registry needs. (Each proxy
* kind gets its own handler class: a shared generic one made the hot getMaterial/hashCode calls megamorphic and
* EntityBenchmark measurably slower.)
*/
static BlockData block(Material m, String state) {
String s = "minecraft:" + m.getKey().getKey() + (state == null ? "" : "[" + state + "]");
return (BlockData) Proxy.newProxyInstance(Harness.class.getClassLoader(),
new Class<?>[]{BlockData.class}, (p, meth, a) -> switch (meth.getName()) {
case "getMaterial" -> m;
case "getAsString", "toString" -> s;
case "equals" -> p == a[0];
case "hashCode" -> System.identityHashCode(p);
default -> defaultValue(meth.getReturnType());
});
}
/** false / 0 / null for an unstubbed proxy method. */
static Object defaultValue(Class<?> rt) {
if (rt == boolean.class) return false;
return rt.isPrimitive() ? 0 : null;
}
static WorldSnapshot emptyWorld(int minY, int maxY) {
return new WorldSnapshot(Map.of(), minY, maxY, block(Material.AIR, null));
}
/** Render job looking from {@code cam} at {@code look}, {@link #SSAA}x supersampled. */
static RenderJob job(WorldSnapshot world, Vector cam, Vector look, int w, int h, List<EntityState> mobs,
List<BlockEntityState> bes, List<DecorationState> decos, List<ObjectState> objects) {
Location loc = new Location(null, cam.getX(), cam.getY(), cam.getZ());
loc.setDirection(look.clone().subtract(cam));
List<Vector> rayMap = DefaultScreenRenderer.buildRayMap(loc, w * SSAA, h * SSAA);
return new RenderJob(world, rayMap, cam, w, h, NOON, mobs, bes, decos, objects);
}
/** Distance at which a box of extent {@code ext} (at least 0.5) fills the view, scaled {@code mul}, plus {@code add}. */
static double frameDistance(double[] min, double[] max, double mul, double add) {
double ext = 0.5;
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
return ext / (2 * Math.tan(V_FOV_HALF)) * mul + add;
}
static Vector center(double[] min, double[] max) {
return new Vector((min[0] + max[0]) / 2, (min[1] + max[1]) / 2, (min[2] + max[2]) / 2);
}
/** Copy of an entity state with some record components replaced (by name). */
static EntityState copy(EntityState s, Map<String, Object> overrides) throws ReflectiveOperationException {
var comps = EntityState.class.getRecordComponents();
Object[] vals = new Object[comps.length];
Class<?>[] types = new Class<?>[comps.length];
for (int i = 0; i < comps.length; i++) {
types[i] = comps[i].getType();
String n = comps[i].getName();
vals[i] = overrides.containsKey(n) ? overrides.get(n) : comps[i].getAccessor().invoke(s);
}
return EntityState.class.getDeclaredConstructor(types).newInstance(vals);
}
static ItemSpec item(String id) {
return new ItemSpec(id, null, null, false);
}
/** The views side by side under a 20 px label. */
static BufferedImage labelCell(String key, BufferedImage... views) {
int w = 0, labelH = 20;
for (BufferedImage v : views) w += v.getWidth();
BufferedImage cell = new BufferedImage(w, views[0].getHeight() + labelH, BufferedImage.TYPE_INT_RGB);
Graphics2D g = cell.createGraphics();
g.setColor(new Color(25, 25, 30));
g.fillRect(0, 0, cell.getWidth(), cell.getHeight());
for (int i = 0, x = 0; i < views.length; x += views[i].getWidth(), i++) g.drawImage(views[i], x, labelH, null);
g.setColor(Color.WHITE);
g.setFont(new Font("SansSerif", Font.BOLD, 13));
g.drawString(key, 4, 15);
g.dispose();
return cell;
}
/** Contact sheets of {@code cols} columns, {@code perPage} cells each, named {@code name(page)}. */
static void writeSheets(List<BufferedImage> cells, int cols, int perPage, File dir, IntFunction<String> name)
throws IOException {
int cw = cells.get(0).getWidth(), ch = cells.get(0).getHeight();
for (int page = 0, idx = 0; idx < cells.size(); page++, idx += perPage) {
int count = Math.min(perPage, cells.size() - idx);
BufferedImage sheet = new BufferedImage(cols * cw, (count + cols - 1) / cols * ch, BufferedImage.TYPE_INT_RGB);
Graphics2D g = sheet.createGraphics();
g.setColor(new Color(40, 40, 48));
g.fillRect(0, 0, sheet.getWidth(), sheet.getHeight());
for (int i = 0; i < count; i++) g.drawImage(cells.get(idx + i), (i % cols) * cw, (i / cols) * ch, null);
g.dispose();
File f = new File(dir, name.apply(page));
ImageIO.write(sheet, "png", f);
System.out.println("WROTE " + f);
}
}
}