428 lines
27 KiB
Java
428 lines
27 KiB
Java
import eu.mhsl.minecraft.pixelpics.assets.*;
|
|
import eu.mhsl.minecraft.pixelpics.render.entity.*;
|
|
import eu.mhsl.minecraft.pixelpics.render.entity.BlockEntityState.*;
|
|
import eu.mhsl.minecraft.pixelpics.render.entity.cem.*;
|
|
import eu.mhsl.minecraft.pixelpics.render.render.*;
|
|
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.lang.reflect.Proxy;
|
|
import java.util.*;
|
|
import java.util.List;
|
|
import java.util.logging.Logger;
|
|
|
|
/** Standalone (no server) block-entity renderer: each scene framed and labelled into a contact sheet. */
|
|
public class BlockEntityTestRender {
|
|
|
|
static final String ROOT = "/home/elias/Dokumente/mcTestServer/plugins/PixelPics";
|
|
static final double V_FOV_HALF = Math.toRadians(eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer.V_FOV_DEG / 2);
|
|
static final int SSAA = 3;
|
|
static final int TW = 220, TH = 240;
|
|
|
|
static BlockEntityBaker beBaker;
|
|
static DecorationBaker decoBaker;
|
|
|
|
/** A block-entity state at (bx, 0, bz); unused fields default. */
|
|
static BlockEntityState st(Kind kind, int bx, int bz, float yaw, String direction, boolean active, String color,
|
|
int baseColor, String head, List<BannerPattern> pats, List<String> sherds) {
|
|
return new BlockEntityState(kind, bx, 0, bz, yaw, direction, active, ChestKind.SINGLE, baseColor, color,
|
|
head, null, pats, sherds, null, null);
|
|
}
|
|
|
|
static BlockEntityState be(Kind kind, float yaw) {
|
|
return st(kind, 0, 0, yaw, null, false, null, 0, null, List.of(), List.of());
|
|
}
|
|
|
|
static BlockEntityState sign(Kind kind, float yaw, SignText front, SignText back) {
|
|
return new BlockEntityState(kind, 0, 0, 0, yaw, null, false, null, 0, null, null, null,
|
|
List.of(), List.of(), front, back);
|
|
}
|
|
|
|
static SignText txt(org.bukkit.DyeColor dye, boolean glow, String... lines) {
|
|
return new SignText(List.of(lines),
|
|
eu.mhsl.minecraft.pixelpics.render.util.ColorUtil.signFillArgb(dye, glow),
|
|
eu.mhsl.minecraft.pixelpics.render.util.ColorUtil.signOutlineArgb(dye), glow);
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
Logger log = Logger.getLogger("test");
|
|
ResourcePack pack = ResourcePackLoader.load(new File(ROOT, "resourcepack"), log).orElseThrow();
|
|
AssetReader reader = new AssetReader(pack);
|
|
TextureCache textures = new TextureCache(pack);
|
|
BlockModelRegistry registry = new BlockModelRegistry(reader, textures);
|
|
BiomeTintProvider tint = new BiomeTintProvider(textures);
|
|
CemModelLoader geo = new CemModelLoader();
|
|
geo.load(new java.io.FileInputStream("src/main/resources/cem/cem_template_models.json"), log); // entity fallback only
|
|
geo.load(new java.io.FileInputStream("src/main/resources/cem/vanilla_entity_models.json"), log);
|
|
SkinCache skins = new SkinCache();
|
|
eu.mhsl.minecraft.pixelpics.assets.font.BitmapFont font =
|
|
eu.mhsl.minecraft.pixelpics.assets.font.FontLoader.load(pack, textures, log);
|
|
log.info("font glyphs loaded: " + (font.isEmpty() ? "NONE" : "ok"));
|
|
CemBaker baker = new CemBaker(geo, textures, skins);
|
|
beBaker = new BlockEntityBaker(geo, textures, skins, font);
|
|
decoBaker = new DecorationBaker(textures, registry,
|
|
new eu.mhsl.minecraft.pixelpics.render.entity.cem.ItemGeometry(reader, registry, textures, geo, beBaker));
|
|
DefaultScreenRenderer renderer = new DefaultScreenRenderer(registry, tint, textures, baker, beBaker, log);
|
|
|
|
BlockData air = (BlockData) Proxy.newProxyInstance(BlockEntityTestRender.class.getClassLoader(),
|
|
new Class<?>[]{BlockData.class}, (p, m, a) -> {
|
|
switch (m.getName()) {
|
|
case "getMaterial": return Material.AIR;
|
|
case "equals": return p == a[0];
|
|
case "hashCode": return System.identityHashCode(p);
|
|
case "toString": return "air";
|
|
}
|
|
Class<?> rt = m.getReturnType();
|
|
if (rt == boolean.class) return false;
|
|
if (rt.isPrimitive()) return 0;
|
|
return null;
|
|
});
|
|
WorldSnapshot empty = new WorldSnapshot(Map.of(), -64, 320, air);
|
|
SkyContext sky = new SkyContext(6000, 0, 6000);
|
|
|
|
// Scenes: label -> list of block-entity states.
|
|
LinkedHashMap<String, List<BlockEntityState>> scenes = new LinkedHashMap<>();
|
|
// Chests: single + double (all four facings), trapped/ender/copper. A double chest's LEFT half has
|
|
// its partner (RIGHT) on facing.getClockWise(): S→west, W→north, N→east, E→south.
|
|
String[] dirs = {"S", "W", "N", "E"};
|
|
int[][] partner = {{-1, 0}, {0, -1}, {1, 0}, {0, 1}};
|
|
for (int i = 0; i < 4; i++) {
|
|
float yaw = i * 90;
|
|
scenes.put("chest_" + dirs[i], List.of(chest(Kind.CHEST, 0, 0, yaw, ChestKind.SINGLE, null)));
|
|
frontCam.put("chest_" + dirs[i], yaw);
|
|
scenes.put("double_chest_" + dirs[i], List.of(
|
|
chest(Kind.CHEST, 0, 0, yaw, ChestKind.LEFT, null),
|
|
chest(Kind.CHEST, partner[i][0], partner[i][1], yaw, ChestKind.RIGHT, null)));
|
|
frontCam.put("double_chest_" + dirs[i], yaw);
|
|
}
|
|
scenes.put("double_chest_back", scenes.get("double_chest_S"));
|
|
frontCam.put("double_chest_back", 180f);
|
|
scenes.put("trapped_double", List.of(
|
|
chest(Kind.TRAPPED_CHEST, 0, 0, 0, ChestKind.LEFT, null),
|
|
chest(Kind.TRAPPED_CHEST, -1, 0, 0, ChestKind.RIGHT, null)));
|
|
frontCam.put("trapped_double", 0f);
|
|
scenes.put("ender_chest", List.of(chest(Kind.ENDER_CHEST, 0, 0, 0, ChestKind.SINGLE, null)));
|
|
frontCam.put("ender_chest", 0f);
|
|
scenes.put("copper_chest", List.of(chest(Kind.COPPER_CHEST, 0, 0, 0, ChestKind.SINGLE, null)));
|
|
frontCam.put("copper_chest", 0f);
|
|
scenes.put("copper_oxidized_double", List.of(
|
|
chest(Kind.COPPER_CHEST, 0, 0, 0, ChestKind.LEFT, "oxidized"),
|
|
chest(Kind.COPPER_CHEST, -1, 0, 0, ChestKind.RIGHT, "oxidized")));
|
|
frontCam.put("copper_oxidized_double", 0f);
|
|
// --- sign text (boards are block models, not drawn here) --- facingDeg 180 → front faces north.
|
|
org.bukkit.DyeColor B = org.bukkit.DyeColor.BLACK;
|
|
String[] D = {"S", "W", "N", "E"};
|
|
for (int i = 0; i < 4; i++) {
|
|
float yaw = i * 90;
|
|
scenes.put("sign_text_" + D[i], List.of(sign(Kind.SIGN, yaw, txt(B, false, "Hello World", "zweite Zeile", "Gruesse: AÖÜ", "ßäöü 12345"), null)));
|
|
frontCam.put("sign_text_" + D[i], yaw);
|
|
}
|
|
scenes.put("sign_text_diag", List.of(sign(Kind.SIGN, 22.5f * 3, txt(B, false, "rot 3", "67.5 deg", "", ""), null)));
|
|
frontCam.put("sign_text_diag", 22.5f * 3);
|
|
scenes.put("wall_sign_text", List.of(sign(Kind.WALL_SIGN, 180, txt(B, false, "Wall Sign", "Line 2", "Line 3", "Line 4"), null)));
|
|
frontCam.put("wall_sign_text", 180f);
|
|
scenes.put("wall_sign_text_E", List.of(sign(Kind.WALL_SIGN, 270, txt(B, false, "Wall East", "Line 2", "", ""), null)));
|
|
frontCam.put("wall_sign_text_E", 270f);
|
|
scenes.put("hanging_sign_text", List.of(sign(Kind.HANGING_SIGN, 180, txt(B, false, "Hanging", "sign text", "row 3", "row 4"), null)));
|
|
frontCam.put("hanging_sign_text", 180f);
|
|
scenes.put("sign_text_red", List.of(sign(Kind.SIGN, 180, txt(org.bukkit.DyeColor.RED, false, "RED INK", "colored", "sign text", ""), null)));
|
|
frontCam.put("sign_text_red", 180f);
|
|
scenes.put("sign_text_glow", List.of(sign(Kind.SIGN, 180, txt(org.bukkit.DyeColor.LIME, true, "GLOWING", "lime glow", "outline!", ""), null)));
|
|
frontCam.put("sign_text_glow", 180f);
|
|
// Back side viewed head-on: the camera looks at the side opposite the front.
|
|
scenes.put("sign_view_back", List.of(sign(Kind.SIGN, 180, txt(B, false, "FRONTXY", "f2", "", ""), txt(org.bukkit.DyeColor.BLUE, false, "BACKXY", "b2", "", ""))));
|
|
frontCam.put("sign_view_back", 0f);
|
|
scenes.put("sign_1line", List.of(sign(Kind.SIGN, 180, txt(B, false, "", "SHOP", "", ""), null)));
|
|
frontCam.put("sign_1line", 180f);
|
|
scenes.put("sign_long", List.of(sign(Kind.SIGN, 180, txt(B, false, "a very long line here", "", "", ""), null)));
|
|
frontCam.put("sign_long", 180f);
|
|
// --- banners: standing (rotation 0/4/6), wall (4 facings), patterned ---
|
|
List<BannerPattern> pats = List.of(new BannerPattern("stripe_bottom", 0xFFCC2020), new BannerPattern("cross", 0xFF2040CC), new BannerPattern("border", 0xFF20A020));
|
|
for (float yaw : new float[] {0, 90, 135}) {
|
|
String k = "banner_" + (int) yaw;
|
|
scenes.put(k, List.of(st(Kind.BANNER, 0, 0, yaw, null, false, null, 0xFFCC2020, null, List.of(), List.of())));
|
|
frontCam.put(k, yaw);
|
|
}
|
|
scenes.put("banner_patterned", List.of(st(Kind.BANNER, 0, 0, 0, null, false, null, 0xFFFFFFFF, null, pats, List.of())));
|
|
frontCam.put("banner_patterned", 0f);
|
|
scenes.put("banner_side", scenes.get("banner_patterned"));
|
|
frontCam.put("banner_side", 90f);
|
|
for (int i = 0; i < 4; i++) {
|
|
scenes.put("wall_banner_" + D[i], List.of(st(Kind.WALL_BANNER, 0, 0, i * 90, null, false, null, 0xFF2040CC, null, pats, List.of())));
|
|
frontCam.put("wall_banner_" + D[i], i * 90f);
|
|
}
|
|
// --- shulker boxes: 6 facings ---
|
|
for (String d : new String[] {"up", "down", "north", "south", "east", "west"}) {
|
|
scenes.put("shulker_" + d, List.of(st(Kind.SHULKER_BOX, 0, 0, 0, d, false, d.equals("up") ? null : "red", 0, null, List.of(), List.of())));
|
|
frontCam.put("shulker_" + d, 0f);
|
|
}
|
|
scenes.put("conduit", List.of(be(Kind.CONDUIT, 0)));
|
|
scenes.put("conduit_active", List.of(st(Kind.CONDUIT, 0, 0, 0, null, true, null, 0, null, List.of(), List.of())));
|
|
List<String> sherds = List.of("heart_pottery_sherd", "skull_pottery_sherd", "angler_pottery_sherd", "brick");
|
|
for (int i = 0; i < 4; i++) {
|
|
scenes.put("pot_" + D[i], List.of(st(Kind.DECORATED_POT, 0, 0, i * 90, null, false, null, 0, null, List.of(), sherds)));
|
|
frontCam.put("pot_" + D[i], i * 90f);
|
|
}
|
|
scenes.put("pot_plain", List.of(be(Kind.DECORATED_POT, 0)));
|
|
frontCam.put("pot_plain", 0f);
|
|
scenes.put("bell", List.of(be(Kind.BELL, 0)));
|
|
for (int i = 0; i < 4; i++) {
|
|
scenes.put("lectern_book_" + D[i], List.of(st(Kind.LECTERN, 0, 0, i * 90, null, true, null, 0, null, List.of(), List.of())));
|
|
frontCam.put("lectern_book_" + D[i], i * 90f);
|
|
}
|
|
// --- heads: every type facing the camera (rotation 0), plus rotations and wall mounts ---
|
|
for (String t : new String[] {"skeleton", "wither_skeleton", "zombie", "creeper", "player", "piglin", "dragon"}) {
|
|
scenes.put("head_" + t, List.of(head(Kind.HEAD, t, 0)));
|
|
frontCam.put("head_" + t, 0f);
|
|
}
|
|
scenes.put("head_dragon_side", List.of(head(Kind.HEAD, "dragon", 0)));
|
|
frontCam.put("head_dragon_side", 90f);
|
|
for (float yaw : new float[] {90, 157.5f, 270}) {
|
|
scenes.put("head_creeper_" + yaw, List.of(head(Kind.HEAD, "creeper", yaw)));
|
|
frontCam.put("head_creeper_" + yaw, yaw);
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
scenes.put("wall_head_" + D[i], List.of(head(Kind.WALL_HEAD, "skeleton", i * 90)));
|
|
frontCam.put("wall_head_" + D[i], i * 90f);
|
|
}
|
|
scenes.put("wall_dragon_N", List.of(head(Kind.WALL_HEAD, "dragon", 180)));
|
|
frontCam.put("wall_dragon_N", 180f);
|
|
scenes.put("wall_piglin_E", List.of(head(Kind.WALL_HEAD, "piglin", 270)));
|
|
frontCam.put("wall_piglin_E", 270f);
|
|
|
|
// --- block-entity contents (no block models in this harness: tables/campfires/shelves/cages absent) ---
|
|
scenes.put("enchant_book_closed", List.of(be(Kind.ENCHANTING_TABLE, 0)));
|
|
scenes.put("enchant_book_open", List.of(st(Kind.ENCHANTING_TABLE, 0, 0, 90, null, true, null, 0, null, List.of(), List.of())));
|
|
frontCam.put("enchant_book_open", 0f);
|
|
scenes.put("campfire_items", List.of(contents(Kind.CAMPFIRE, 0, items("beef", "cod", "porkchop", null), null, null, null, null)));
|
|
scenes.put("campfire_items_E", List.of(contents(Kind.CAMPFIRE, 270, items("beef", "cod", "porkchop", "salmon"), null, null, null, null)));
|
|
scenes.put("shelf_items_S", List.of(contents(Kind.SHELF, 0, items("diamond", "book", "golden_apple"), null, null, null, null)));
|
|
frontCam.put("shelf_items_S", 0f);
|
|
scenes.put("shelf_items_E", List.of(contents(Kind.SHELF, 270, items("stick", null, "emerald"), null, null, null, null)));
|
|
frontCam.put("shelf_items_E", 270f);
|
|
scenes.put("vault_item", List.of(contents(Kind.VAULT, 0, items("trial_key"), null, null, null, null)));
|
|
frontCam.put("vault_item", 0f);
|
|
String[] poses = {"standing", "running", "sitting", "star"};
|
|
String[] stages = {null, "exposed", "weathered", "oxidized"};
|
|
for (int i = 0; i < 4; i++) {
|
|
scenes.put("statue_" + poses[i], List.of(contents(Kind.COPPER_GOLEM_STATUE, i * 90, List.of(), null, null, poses[i], stages[i])));
|
|
frontCam.put("statue_" + poses[i], i * 90f);
|
|
}
|
|
for (String mob : new String[] {"zombie", "pig", "spider", "blaze"}) {
|
|
scenes.put("spawner_" + mob, List.of(contents(Kind.SPAWNER, 0, List.of(), spawnerMob(mob), null, null, null)));
|
|
frontCam.put("spawner_" + mob, 0f);
|
|
}
|
|
scenes.put("beacon_beam", List.of(contents(Kind.BEACON, 0, List.of(), null,
|
|
new Beam(List.of(new BeamSection(0xFFF9FFFE, 1), new BeamSection(0xFFB02E26, 2), new BeamSection(0xFF3C44AA, 1)), 1f), null, null)));
|
|
List<BlockEntityState> portal = new ArrayList<>();
|
|
for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) portal.add(st(Kind.END_PORTAL, x, z, 0, null, false, null, 0, null, List.of(), List.of()));
|
|
scenes.put("end_portal", portal);
|
|
scenes.put("end_gateway", List.of(st(Kind.END_GATEWAY, 0, 0, 0, "down+up+north+south+west+east", false, null, 0, null, List.of(), List.of())));
|
|
|
|
// Decoration scenes (paintings + item frames), rendered via the same scene path.
|
|
LinkedHashMap<String, List<DecorationState>> decoScenes = new LinkedHashMap<>();
|
|
// kebab is 1x1; place a thin quad facing south spanning the block at z=0..0.0625.
|
|
decoScenes.put("painting_kebab_S", List.of(new DecorationState(DecorationState.Kind.PAINTING,
|
|
0, 0, 0, 1, 1, 0.0625, DecorationState.Facing.SOUTH, "kebab", null, null, 0, false, false)));
|
|
decoScenes.put("painting_kebab_N", List.of(new DecorationState(DecorationState.Kind.PAINTING,
|
|
0, 0, 0.9375, 1, 1, 1, DecorationState.Facing.NORTH, "kebab", null, null, 0, false, false)));
|
|
decoScenes.put("painting_pool_S", List.of(new DecorationState(DecorationState.Kind.PAINTING,
|
|
0, 0, 0, 2, 1, 0.0625, DecorationState.Facing.SOUTH, "pool", null, null, 0, false, false)));
|
|
decoScenes.put("item_frame", List.of(new DecorationState(DecorationState.Kind.ITEM_FRAME,
|
|
0.0625, 0.0625, 0, 0.9375, 0.9375, 0.0625, DecorationState.Facing.SOUTH, null, new eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec("diamond", null, null, false), null, 0, false, false)));
|
|
decoScenes.put("item_frame_empty", List.of(new DecorationState(DecorationState.Kind.ITEM_FRAME,
|
|
0.0625, 0.0625, 0, 0.9375, 0.9375, 0.0625, DecorationState.Facing.SOUTH, null, null, null, 0, false, false)));
|
|
decoScenes.put("item_frame_E", List.of(new DecorationState(DecorationState.Kind.ITEM_FRAME,
|
|
0.9375, 0.0625, 0.0625, 1.0, 0.9375, 0.9375, DecorationState.Facing.EAST, null, new eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec("diamond", null, null, false), null, 0, false, false)));
|
|
|
|
// Shields (plain / red base / blue + creeper + border patterns), chests (christmas with
|
|
// -Dpixelpics.testDate=2026-12-25) and a decorated map in frames (map/decorations sprites as MapRenderer).
|
|
BlockEntityState shieldPats = new BlockEntityState(Kind.BANNER, 0, 0, 0, 0, null, false, null, 0xFF3C44AA, null, null, null,
|
|
List.of(new BannerPattern("creeper", 0xFFFED83D), new BannerPattern("border", 0xFFF9FFFE)), List.of(), null, null);
|
|
BlockEntityState shieldRed = new BlockEntityState(Kind.BANNER, 0, 0, 0, 0, null, false, null, 0xFFB02E26, null, null, null,
|
|
List.of(), List.of(), null, null);
|
|
String[] frameItems = {"shield", "shield_red", "shield_patterns", "chest", "trapped_chest", "ender_chest"};
|
|
for (String id : frameItems) {
|
|
ItemSpec spec = switch (id) {
|
|
case "shield_red" -> new ItemSpec("shield", null, shieldRed, false);
|
|
case "shield_patterns" -> new ItemSpec("shield", null, shieldPats, false);
|
|
default -> new ItemSpec(id, null, null, false);
|
|
};
|
|
decoScenes.put("item_frame_" + id, List.of(new DecorationState(DecorationState.Kind.ITEM_FRAME,
|
|
0.0625, 0.0625, 0, 0.9375, 0.9375, 0.0625, DecorationState.Facing.SOUTH, null, spec, null, 0, false, false)));
|
|
}
|
|
int[][] mapPx = new int[128][128];
|
|
for (int y = 0; y < 128; y++) for (int x = 0; x < 128; x++) mapPx[y][x] = ((x / 16 + y / 16) & 1) == 0 ? 0xFF7FB238 : 0xFF3F76E4;
|
|
List<DecorationState.MapMarker> markers = List.of(
|
|
new DecorationState.MapMarker("frame", 0, 0, 0),
|
|
new DecorationState.MapMarker("red_banner", -60, -50, 8),
|
|
new DecorationState.MapMarker("target_x", 70, 60, 0),
|
|
new DecorationState.MapMarker("frame", 40, -90, 4),
|
|
new DecorationState.MapMarker("blue_banner", -100, 90, 0));
|
|
for (int rot : new int[] {0, 1}) {
|
|
decoScenes.put("item_frame_map_markers_r" + rot, List.of(new DecorationState(DecorationState.Kind.ITEM_FRAME,
|
|
0.0625, 0.0625, 0, 0.9375, 0.9375, 0.0625, DecorationState.Facing.SOUTH, null,
|
|
new ItemSpec("filled_map", null, null, false), mapPx, rot, false, false, markers)));
|
|
}
|
|
|
|
List<BufferedImage> cells = new ArrayList<>();
|
|
List<String> names = new ArrayList<>(scenes.keySet());
|
|
for (String name : names) {
|
|
cells.add(labelCell(name, renderScene(renderer, empty, sky, scenes.get(name), frontCam.get(name))));
|
|
log.info("rendered " + name);
|
|
}
|
|
for (String name : decoScenes.keySet()) {
|
|
cells.add(labelCell(name, renderDeco(renderer, empty, sky, decoScenes.get(name))));
|
|
names.add(name);
|
|
log.info("rendered " + name);
|
|
}
|
|
|
|
File outDir = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-BlockEntity-Renders"));
|
|
outDir.mkdirs();
|
|
for (int i = 0; i < names.size(); i++) ImageIO.write(cells.get(i), "png", new File(outDir, names.get(i) + ".png"));
|
|
|
|
int cols = 4, cw = cells.get(0).getWidth(), ch = cells.get(0).getHeight();
|
|
int rows = (cells.size() + cols - 1) / cols;
|
|
BufferedImage sheet = new BufferedImage(cols * cw, rows * 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 < cells.size(); i++) g.drawImage(cells.get(i), (i % cols) * cw, (i / cols) * ch, null);
|
|
g.dispose();
|
|
File f = new File(outDir, "sheet.png");
|
|
ImageIO.write(sheet, "png", f);
|
|
System.out.println("WROTE " + f);
|
|
}
|
|
|
|
/** Scenes viewed from the front (camera along the facing yaw, raised, slightly to the side). */
|
|
static final Map<String, Float> frontCam = new HashMap<>();
|
|
|
|
static BlockEntityState chest(Kind kind, int bx, int bz, float yaw, ChestKind ck, String variant) {
|
|
return new BlockEntityState(kind, bx, 0, bz, yaw, null, false, ck, 0, variant, null, null,
|
|
List.of(), List.of(), null, null);
|
|
}
|
|
|
|
/** A contents-bearing block-entity at the origin (items / spawner mob / beacon beam / statue pose). */
|
|
static BlockEntityState contents(Kind kind, float yaw, List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> items,
|
|
EntityState mob, Beam beam, String pose, String color) {
|
|
return new BlockEntityState(kind, 0, 0, 0, yaw, null, false, null, 0, color, null, null,
|
|
List.of(), List.of(), null, null, items, mob, beam, pose);
|
|
}
|
|
|
|
static List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> items(String... ids) {
|
|
List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> out = new ArrayList<>();
|
|
for (String id : ids) out.add(id == null ? null : new eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec(id, null, null, false));
|
|
return out;
|
|
}
|
|
|
|
/** EntityTestRender's default state of a mob with its real bounding box (spawner scale depends on it). */
|
|
static EntityState spawnerMob(String type) throws Exception {
|
|
EntityState e = EntityTestRender.state(type, 0);
|
|
double[] dims = switch (type) {
|
|
case "zombie" -> new double[] {0.6, 1.95};
|
|
case "pig" -> new double[] {0.9, 0.9};
|
|
case "spider" -> new double[] {1.4, 0.9};
|
|
default -> new double[] {0.6, 1.8};
|
|
};
|
|
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();
|
|
vals[i] = switch (comps[i].getName()) {
|
|
case "width" -> dims[0];
|
|
case "height" -> dims[1];
|
|
default -> comps[i].getAccessor().invoke(e);
|
|
};
|
|
}
|
|
return EntityState.class.getDeclaredConstructor(types).newInstance(vals);
|
|
}
|
|
|
|
static BlockEntityState head(Kind kind, String type, float yaw) {
|
|
return st(kind, 0, 0, yaw, null, false, null, 0, type, List.of(), List.of());
|
|
}
|
|
|
|
static BufferedImage renderScene(DefaultScreenRenderer renderer, WorldSnapshot world, SkyContext sky,
|
|
List<BlockEntityState> states, Float frontYaw) {
|
|
double[] min = {Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE};
|
|
double[] max = {-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE};
|
|
for (BlockEntityState s : states) {
|
|
RenderedEntity re = beBaker.bake(s);
|
|
if (re == null) continue;
|
|
for (int a = 0; a < 3; a++) { min[a] = Math.min(min[a], re.aabbMin[a]); max[a] = Math.max(max[a], re.aabbMax[a]); }
|
|
}
|
|
if (min[0] > max[0]) { min = new double[]{0, 0, 0}; max = new double[]{1, 1, 1}; }
|
|
if (states.get(0).kind() == Kind.BEACON) max[1] = min[1] + 4; // frame the beam's foot, not 2048 blocks
|
|
double cx = (min[0] + max[0]) / 2, cy = (min[1] + max[1]) / 2, cz = (min[2] + max[2]) / 2;
|
|
double ext = 0.5;
|
|
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
|
|
double dist = ext / (2 * Math.tan(V_FOV_HALF)) * 1.4 + 0.6;
|
|
|
|
Vector center = new Vector(cx, cy, cz);
|
|
Vector cam = new Vector(cx - dist, cy + dist * 0.45, cz - dist * 0.35);
|
|
if (frontYaw != null) {
|
|
// Facing yaw (0 = south, 90 = west) → front direction (-sin, cos); offset 25° to the side.
|
|
double a = Math.toRadians(frontYaw + 25);
|
|
cam = new Vector(cx - Math.sin(a) * dist, cy + dist * 0.55, cz + Math.cos(a) * dist);
|
|
}
|
|
Location loc = new Location(null, cam.getX(), cam.getY(), cam.getZ());
|
|
loc.setDirection(center.clone().subtract(cam));
|
|
List<Vector> rayMap = buildRayMap(loc, TW * SSAA, TH * SSAA);
|
|
RenderJob job = new RenderJob(world, rayMap, cam, TW, TH, sky, List.of(), states, List.of(), List.of());
|
|
return renderer.execute(job);
|
|
}
|
|
|
|
static BufferedImage renderDeco(DefaultScreenRenderer renderer, WorldSnapshot world, SkyContext sky,
|
|
List<DecorationState> states) {
|
|
double[] min = {Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE};
|
|
double[] max = {-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE};
|
|
for (DecorationState s : states) {
|
|
RenderedEntity re = decoBaker.bake(s);
|
|
if (re == null) continue;
|
|
for (int a = 0; a < 3; a++) { min[a] = Math.min(min[a], re.aabbMin[a]); max[a] = Math.max(max[a], re.aabbMax[a]); }
|
|
}
|
|
if (min[0] > max[0]) { min = new double[]{0, 0, 0}; max = new double[]{1, 1, 1}; }
|
|
double cx = (min[0] + max[0]) / 2, cy = (min[1] + max[1]) / 2, cz = (min[2] + max[2]) / 2;
|
|
double ext = 0.5;
|
|
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
|
|
double dist = ext / (2 * Math.tan(V_FOV_HALF)) * 1.4 + 0.6;
|
|
// View from the front side (along +facing) so the picture is visible, not the back.
|
|
DecorationState.Facing f = states.get(0).facing();
|
|
double fx = f.axis() == 0 ? f.sign() : 0, fy = f.axis() == 1 ? f.sign() : 0, fz = f.axis() == 2 ? f.sign() : 0;
|
|
Vector center = new Vector(cx, cy, cz);
|
|
Vector cam = new Vector(cx + fx * dist + 0.1, cy + fy * dist + 0.1, cz + fz * dist + 0.1);
|
|
Location loc = new Location(null, cam.getX(), cam.getY(), cam.getZ());
|
|
loc.setDirection(center.clone().subtract(cam));
|
|
List<Vector> rayMap = buildRayMap(loc, TW * SSAA, TH * SSAA);
|
|
RenderJob job = new RenderJob(world, rayMap, cam, TW, TH, sky, List.of(), List.of(), states, List.of());
|
|
return renderer.execute(job);
|
|
}
|
|
|
|
static BufferedImage labelCell(String key, BufferedImage v) {
|
|
int labelH = 20;
|
|
BufferedImage cell = new BufferedImage(v.getWidth(), v.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());
|
|
g.drawImage(v, 0, labelH, null);
|
|
g.setColor(Color.WHITE);
|
|
g.setFont(new Font("SansSerif", Font.BOLD, 13));
|
|
g.drawString(key, 4, 15);
|
|
g.dispose();
|
|
return cell;
|
|
}
|
|
|
|
static List<Vector> buildRayMap(Location eye, int width, int height) {
|
|
return eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer.buildRayMap(eye, width, height);
|
|
}
|
|
}
|