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:
1 parent
b242adb6d2
commit
02e052787c
11 files changed
+1449
-1686
No files matched your search
+40
-148
@@ -1,32 +1,23 @@
|
||||
import eu.mhsl.minecraft.pixelpics.assets.*;
|
||||
import eu.mhsl.minecraft.pixelpics.render.entity.*;
|
||||
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) renderer: every entity twice (yaw 45° / 225°) against empty sky -> contact sheets. */
|
||||
/**
|
||||
* Standalone (no server) renderer: every entity twice (yaw 45° / 225°) against empty sky -> contact sheets
|
||||
* ({@code page_<n>.png}) + {@code einzeln/<key>.png} in {@code -Dout} (default ~/Dokumente/PixelPics-Entity-Renders).
|
||||
* Args: case specs (default {@link #ENTITIES}), or {@code @variants} for {@link #VARIANT_CASES}. Build: see {@link Harness}.
|
||||
*/
|
||||
public class EntityTestRender {
|
||||
|
||||
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 = 200, TH = 230; // per-view tile size
|
||||
|
||||
// Entity type keys to render (current vanilla + bundled bedrock specials). variant=null (base look).
|
||||
// Default case list: every entity type in its base look ("plain": the per-type tables below apply).
|
||||
static final String[] ENTITIES = {
|
||||
"allay","armadillo","armor_stand","axolotl","bat","bee","blaze","bogged","breeze","camel",
|
||||
"cat","cave_spider","chicken","cod","copper_golem","cow","creaking","creeper","dolphin","donkey",
|
||||
@@ -42,8 +33,10 @@ public class EntityTestRender {
|
||||
"minecart","end_crystal","arrow","trident","leash_knot","evoker_fangs","wither_skull","shulker_bullet","cushion"
|
||||
};
|
||||
|
||||
// "@variants" case set (before/after pixel diffs of variant / pose-flag / armor handling). Spec syntax:
|
||||
// [baby:]type[@variant][+flag…], flags: chest, arms, tame (red collar), saddle, armor (full diamond), noarmor.
|
||||
// "@variants" case set (before/after pixel diffs of refactors). Spec: [baby:]type[@variant][+flag…], flags:
|
||||
// chest, arms, saddle, tame (red collar), armor (full diamond), elytra, red (sheep wool), sword / crossbow
|
||||
// (right hand), crossbowl (left hand), mouth (sweet berries), passenger, shoulder (red + blue parrot), stand
|
||||
// (custom armor-stand part poses), pitch:<deg>, p:<state pose> (sleeping, sitting, eyes_glowing, …).
|
||||
static final List<String> VARIANT_CASES = new ArrayList<>();
|
||||
static {
|
||||
for (String v : new String[] {"tabby", "black", "red", "siamese", "british_shorthair", "calico", "persian",
|
||||
@@ -68,13 +61,11 @@ public class EntityTestRender {
|
||||
VARIANT_CASES.add("mooshroom@red");
|
||||
VARIANT_CASES.add("mooshroom@brown");
|
||||
VARIANT_CASES.add("baby:mooshroom@brown");
|
||||
// Armor stand with/without arms, small, with armor.
|
||||
for (String a : new String[] {"armor_stand", "armor_stand+arms", "baby:armor_stand", "baby:armor_stand+arms",
|
||||
"armor_stand+arms+armor", "baby:armor_stand+arms+armor"}) VARIANT_CASES.add(a);
|
||||
// Chests.
|
||||
for (String c : new String[] {"llama@creamy", "llama@creamy+chest", "llama@brown+chest+saddle", "llama@gray+chest",
|
||||
VARIANT_CASES.addAll(List.of("armor_stand", "armor_stand+arms", "baby:armor_stand", "baby:armor_stand+arms",
|
||||
"armor_stand+arms+armor", "baby:armor_stand+arms+armor"));
|
||||
VARIANT_CASES.addAll(List.of("llama@creamy", "llama@creamy+chest", "llama@brown+chest+saddle", "llama@gray+chest",
|
||||
"trader_llama@white+chest", "baby:llama@creamy+chest", "donkey", "donkey+chest", "donkey+chest+saddle",
|
||||
"mule", "mule+chest", "mule+chest+saddle", "baby:donkey+chest"}) VARIANT_CASES.add(c);
|
||||
"mule", "mule+chest", "mule+chest+saddle", "baby:donkey+chest"));
|
||||
// Armor wearers (and non-wearers given armor: nothing must appear).
|
||||
for (String t : new String[] {"zombie", "husk", "drowned", "zombie_villager", "zombified_piglin", "skeleton",
|
||||
"stray", "wither_skeleton", "bogged", "parched", "piglin", "piglin_brute", "giant", "player",
|
||||
@@ -84,7 +75,7 @@ public class EntityTestRender {
|
||||
VARIANT_CASES.add(t + "+armor");
|
||||
// Layer / texture states: emissive eyes, sheep undercoat, angry wolves, tipped arrows, invulnerable
|
||||
// wither, partial-alpha cutout textures, held items (piglin arm poses), elytra.
|
||||
for (String c : new String[] {"spider", "cave_spider", "enderman", "phantom", "breeze", "warden",
|
||||
VARIANT_CASES.addAll(List.of("spider", "cave_spider", "enderman", "phantom", "breeze", "warden",
|
||||
"copper_golem@oxidized", "creaking", "creaking+p:eyes_glowing", "sheep@sheared", "sheep+red",
|
||||
"sheep@sheared+red", "baby:sheep+red", "sheep+p:jeb", "wolf@pale+p:angry", "baby:wolf@pale+p:angry",
|
||||
"wolf@pale+tame+p:angry", "arrow+p:tipped", "spectral_arrow", "wither+p:invulnerable",
|
||||
@@ -92,12 +83,11 @@ public class EntityTestRender {
|
||||
"piglin+sword+crossbowl", "piglin+crossbow+p:arms_crossbow_hold", "piglin+sword+p:arms_attacking_with_melee_weapon",
|
||||
"zombified_piglin+sword", "piglin_brute+sword", "pillager+crossbow",
|
||||
"vindicator+sword", "armor_stand+elytra", "zombie+elytra", "baby:armor_stand+armor",
|
||||
"baby:armor_stand+arms+elytra"})
|
||||
VARIANT_CASES.add(c);
|
||||
"baby:armor_stand+arms+elytra"));
|
||||
VARIANT_CASES.addAll(List.of(ENTITIES));
|
||||
}
|
||||
|
||||
// Representative variants (the game always supplies these; null base textures wouldn't exist otherwise).
|
||||
// Variants the game always supplies (several types have no texture without one); villager profession/level.
|
||||
static final Map<String, String> VAR = Map.ofEntries(
|
||||
Map.entry("cat", "tabby"), Map.entry("wolf", "pale"), Map.entry("axolotl", "lucy"),
|
||||
Map.entry("parrot", "red"), Map.entry("rabbit", "brown"), Map.entry("horse", "white"),
|
||||
@@ -108,7 +98,6 @@ public class EntityTestRender {
|
||||
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).
|
||||
static final Map<String, String> PROF = Map.ofEntries(
|
||||
Map.entry("villager", "librarian"), Map.entry("zombie_villager", "farmer")
|
||||
);
|
||||
@@ -116,17 +105,17 @@ public class EntityTestRender {
|
||||
Map.entry("villager", 5)
|
||||
);
|
||||
|
||||
// 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", "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
|
||||
// Plain cases: horse markings, saddles, chests, body armor / carpet / harness.
|
||||
static final Map<String, String> MARK = Map.of("horse", "blackdots");
|
||||
static final Set<String> SADDLE = Set.of("horse", "donkey", "mule", "nautilus", "pig", "strider", "camel");
|
||||
static final Set<String> CHEST = Set.of("llama", "donkey");
|
||||
static final Map<String, String> EQUIP = Map.ofEntries(
|
||||
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")
|
||||
);
|
||||
|
||||
// Humanoid armor test cases (worn equipment, trims, glint, elytra).
|
||||
// Plain cases: humanoid armor (diamond, dyed leather, trimmed iron, enchanted netherite, glint helmet + elytra).
|
||||
static EntityState.EquipPiece P(String asset) { return new EntityState.EquipPiece(asset, 0, null, null, false); }
|
||||
static EntityState.EquipPiece P(String asset, int dye, String trimMat, String trimPat, boolean glint) {
|
||||
return new EntityState.EquipPiece(asset, dye, trimMat, trimPat, glint);
|
||||
@@ -134,124 +123,50 @@ public class EntityTestRender {
|
||||
|
||||
static final Map<String, EntityState.Equipment> ARMOR = new HashMap<>();
|
||||
static {
|
||||
// Full diamond armor.
|
||||
ARMOR.put("skeleton", new EntityState.Equipment(P("diamond"), P("diamond"), P("diamond"), P("diamond")));
|
||||
// Dyed (orange) leather armor.
|
||||
EntityState.EquipPiece le = P("leather", 0xFFFF8000, null, null, false);
|
||||
ARMOR.put("zombie", new EntityState.Equipment(le, le, le, le));
|
||||
// Iron armor with a coast/diamond trim.
|
||||
EntityState.EquipPiece ir = P("iron", 0, "diamond", "coast", false);
|
||||
ARMOR.put("armor_stand", new EntityState.Equipment(ir, ir, ir, ir));
|
||||
// Enchanted netherite armor (glint).
|
||||
EntityState.EquipPiece ne = P("netherite", 0, null, null, true);
|
||||
ARMOR.put("wither_skeleton", new EntityState.Equipment(ne, ne, ne, ne));
|
||||
// Enchanted diamond helmet + elytra in the chest slot.
|
||||
ARMOR.put("player", new EntityState.Equipment(P("diamond", 0, null, null, true), P("elytra"), null, null));
|
||||
// Gold helmet.
|
||||
ARMOR.put("piglin", new EntityState.Equipment(P("gold"), null, null, null));
|
||||
}
|
||||
|
||||
// Invisible test cases: armor_stand -> only its (trimmed iron) armor floats; creeper -> nothing renders.
|
||||
static final java.util.Set<String> INVISIBLE = java.util.Set.of("armor_stand", "creeper");
|
||||
static final Set<String> INVISIBLE = Set.of("armor_stand", "creeper");
|
||||
|
||||
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);
|
||||
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("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);
|
||||
eu.mhsl.minecraft.pixelpics.assets.font.BitmapFont font =
|
||||
eu.mhsl.minecraft.pixelpics.assets.font.FontLoader.load(pack, textures, log);
|
||||
eu.mhsl.minecraft.pixelpics.render.entity.cem.BlockEntityBaker beBaker = new eu.mhsl.minecraft.pixelpics.render.entity.cem.BlockEntityBaker(geo, textures, skins, font);
|
||||
DefaultScreenRenderer renderer = new DefaultScreenRenderer(registry, tint, textures, baker, beBaker, log);
|
||||
|
||||
BlockData air = (BlockData) Proxy.newProxyInstance(EntityTestRender.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(), 0, 1, air);
|
||||
SkyContext sky = new SkyContext(6000, 0, 6000);
|
||||
|
||||
Harness h = new Harness("test");
|
||||
WorldSnapshot empty = Harness.emptyWorld(0, 1);
|
||||
String[] list = args.length == 0 ? ENTITIES
|
||||
: args[0].equals("@variants") ? VARIANT_CASES.toArray(new String[0]) : args;
|
||||
List<BufferedImage> cells = new ArrayList<>();
|
||||
for (String key : list) {
|
||||
BufferedImage v1 = renderEntity(renderer, baker, empty, sky, key, 45);
|
||||
BufferedImage v2 = renderEntity(renderer, baker, empty, sky, key, 225);
|
||||
cells.add(labelCell(key, v1, v2));
|
||||
log.info("rendered " + key);
|
||||
cells.add(Harness.labelCell(key, renderEntity(h, empty, key, 45), renderEntity(h, empty, key, 225)));
|
||||
h.log.info("rendered " + key);
|
||||
}
|
||||
|
||||
File outDir = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-Entity-Renders"));
|
||||
outDir.mkdirs();
|
||||
// One image per entity (both 45°/225° views side by side), named by entity key.
|
||||
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].replaceAll("[:@+]", "_") + ".png"));
|
||||
}
|
||||
|
||||
// Compose contact-sheet pages: 2 columns.
|
||||
int cols = 2, cellW = cells.get(0).getWidth(), cellH = cells.get(0).getHeight();
|
||||
int perPage = cols * 5;
|
||||
for (int page = 0, idx = 0; idx < cells.size(); page++) {
|
||||
int count = Math.min(perPage, cells.size() - idx);
|
||||
int rows = (count + cols - 1) / cols;
|
||||
BufferedImage sheet = new BufferedImage(cols * cellW, rows * cellH, 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++) {
|
||||
int r = i / cols, c = i % cols;
|
||||
g.drawImage(cells.get(idx + i), c * cellW, r * cellH, null);
|
||||
}
|
||||
g.dispose();
|
||||
File f = new File(outDir, String.format("page_%d.png", page));
|
||||
ImageIO.write(sheet, "png", f);
|
||||
System.out.println("WROTE " + f);
|
||||
idx += count;
|
||||
}
|
||||
Harness.writeSheets(cells, 2, 10, outDir, page -> "page_" + page + ".png");
|
||||
}
|
||||
|
||||
static BufferedImage renderEntity(DefaultScreenRenderer renderer, eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker baker, WorldSnapshot world,
|
||||
SkyContext sky, String key, float yaw) {
|
||||
static BufferedImage renderEntity(Harness h, WorldSnapshot world, String key, float yaw) {
|
||||
EntityState s = state(key, yaw);
|
||||
RenderedEntity re = baker.bake(s);
|
||||
RenderedEntity re = h.baker.bake(s);
|
||||
if (re == null || re.cubes.isEmpty()) { // invisible with no equipment -> nothing renders
|
||||
return new BufferedImage(TW, TH, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
double cx = (re.aabbMin[0] + re.aabbMax[0]) / 2;
|
||||
double cy = (re.aabbMin[1] + re.aabbMax[1]) / 2;
|
||||
double cz = (re.aabbMin[2] + re.aabbMax[2]) / 2;
|
||||
double ext = 0;
|
||||
for (int a = 0; a < 3; a++) ext = Math.max(ext, re.aabbMax[a] - re.aabbMin[a]);
|
||||
if (ext < 0.5) ext = 0.5;
|
||||
double dist = ext / (2 * Math.tan(V_FOV_HALF)) * 1.25 + 0.4;
|
||||
|
||||
Vector center = new Vector(cx, cy, cz);
|
||||
Vector cam = new Vector(cx - dist, cy + dist * 0.42, cz - dist * 0.15);
|
||||
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(s), List.of(), List.of(), List.of());
|
||||
return renderer.execute(job);
|
||||
Vector center = Harness.center(re.aabbMin, re.aabbMax);
|
||||
double dist = Harness.frameDistance(re.aabbMin, re.aabbMax, 1.25, 0.4);
|
||||
Vector cam = new Vector(center.getX() - dist, center.getY() + dist * 0.42, center.getZ() - dist * 0.15);
|
||||
return h.renderer.execute(Harness.job(world, cam, center, TW, TH, List.of(s), List.of(), List.of(), List.of()));
|
||||
}
|
||||
|
||||
/** Entity state for a case spec ({@code [baby:]type[@variant][+flag…]}, see VARIANT_CASES). */
|
||||
@@ -266,7 +181,7 @@ public class EntityTestRender {
|
||||
variant = key.substring(key.indexOf('@') + 1);
|
||||
key = key.substring(0, key.indexOf('@'));
|
||||
}
|
||||
boolean plain = flagSplit.length == 1 && variant == null; // default ENTITIES entry: legacy test setup
|
||||
boolean plain = flagSplit.length == 1 && variant == null;
|
||||
if (variant == null) variant = VAR.get(key);
|
||||
boolean isPlayer = key.equals("player");
|
||||
int tint = key.equals("tropical_fish") ? 0xFFF9801D : (key.equals("sheep") ? 0xFFF9FFFE : 0);
|
||||
@@ -283,8 +198,6 @@ public class EntityTestRender {
|
||||
String equip = plain ? EQUIP.get(key) : null;
|
||||
boolean arms = flags.contains("arms");
|
||||
int collar = flags.contains("tame") ? 0xFFB02E26 : 0;
|
||||
// Pose flags: "p:<state pose>" (sleeping, sitting, lying, …), "pitch:<deg>", "passenger", "shoulder"
|
||||
// (red + blue parrot on the shoulders), "stand" (custom armor-stand part poses), "mouth" (sweet berries).
|
||||
List<String> poses = new ArrayList<>();
|
||||
float pitch = 0;
|
||||
for (String f : flags) {
|
||||
@@ -306,11 +219,10 @@ public class EntityTestRender {
|
||||
partPoses.put("rightLegPose", new float[] {-40, 0, 0});
|
||||
}
|
||||
boolean shoulder = flags.contains("shoulder");
|
||||
ItemSpec mouth = flags.contains("mouth") ? new ItemSpec("sweet_berries", null, null, false) : null;
|
||||
// Held items: "sword" (right hand), "crossbow" (right hand), "crossbowl" (left hand).
|
||||
ItemSpec right = flags.contains("sword") ? new ItemSpec("iron_sword", null, null, false)
|
||||
: flags.contains("crossbow") ? new ItemSpec("crossbow", null, null, false) : null;
|
||||
ItemSpec left = flags.contains("crossbowl") ? new ItemSpec("crossbow", null, null, false) : null;
|
||||
ItemSpec mouth = flags.contains("mouth") ? Harness.item("sweet_berries") : null;
|
||||
ItemSpec right = flags.contains("sword") ? Harness.item("iron_sword")
|
||||
: flags.contains("crossbow") ? Harness.item("crossbow") : null;
|
||||
ItemSpec left = flags.contains("crossbowl") ? Harness.item("crossbow") : null;
|
||||
return new EntityState(key, 0, 0, 0, yaw, baby, 0.8, 1.0,
|
||||
isPlayer, null, false, variant, tint, 1.0, PROF.get(key), LVL.getOrDefault(key, 0),
|
||||
plain ? MARK.get(key) : null, saddle, chest, arms, equip, armor,
|
||||
@@ -318,24 +230,4 @@ public class EntityTestRender {
|
||||
collar, false, mouth, poses.isEmpty() ? null : String.join("+", poses), partPoses,
|
||||
shoulder ? "red_blue" : null, shoulder ? "blue" : null);
|
||||
}
|
||||
|
||||
static BufferedImage labelCell(String key, BufferedImage v1, BufferedImage v2) {
|
||||
int w = v1.getWidth() + v2.getWidth();
|
||||
int labelH = 20;
|
||||
BufferedImage cell = new BufferedImage(w, v1.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(v1, 0, labelH, null);
|
||||
g.drawImage(v2, v1.getWidth(), 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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user