final comment sweep: section separators, signature-only javadocs and history notes removed, stale comments corrected (sign/contents model lookup, inverted entity cubes, item-bounds use, datapack folder search, biome tint argb, water-only overlay faces, beacon first section, mushroom-cow instanceof example, legacy skin arm copy, elytra and pack-detection notes in the scripts, pptest scene descriptions), comments only

This commit is contained in:
MineTec committed 2026-11-05 03:35:48 +01:00
1 parent 02e052787c
commit a3fbe122e3
29 files changed
+51 -125

No files matched your search

@@ -39,7 +39,5 @@ public final class DirectoryResourcePack implements ResourcePack {
}
@Override
public void close() {
// nothing to release
}
public void close() {}
}
@@ -47,15 +47,15 @@ public final class SkinCache extends ImageCache<String> {
int[][] src = pixels(img, w, h);
for (int y = 0; y < h; y++) System.arraycopy(src[y], 0, out[y], 0, w);
if (h <= 32) {
// Legacy skin: copy the right arm/leg regions into the modern left arm/leg slots (mirrored).
copyMirrored(img, out, 44, 16, 36, 48); // right leg -> left leg (top/quads handled by mirror)
// Legacy skin: mirrored copies of the right-arm region (40,16) into the modern left-arm slot (32,48).
copyMirrored(img, out, 44, 16, 36, 48);
copyMirrored(img, out, 44, 16, 36, 52);
copyMirrored(img, out, 40, 16, 32, 48); // right arm -> left arm
copyMirrored(img, out, 40, 16, 32, 48);
}
return out;
}
/** Mirror-copies a 16x12 limb block (legacy) into a modern slot; coarse but visually adequate. */
/** Mirror-copies a 16x12 block (whole block flipped horizontally, not per face); coarse approximation. */
private void copyMirrored(BufferedImage img, int[][] out, int srcX, int srcY, int dstX, int dstY) {
for (int y = 0; y < 12 && srcY + y < img.getHeight(); y++) {
for (int x = 0; x < 16 && srcX + x < img.getWidth(); x++) {
@@ -26,8 +26,7 @@ import org.bukkit.plugin.Plugin;
* <li><b>Globally:</b> at most {@code maxConcurrent} photos render at once, with up to
* {@code queueSize} more waiting; further requests are rejected.</li>
* <li><b>CPU:</b> the ray tracing runs on a dedicated {@link ForkJoinPool} of {@code threads}
* low-priority worker threads ({@link Thread#MIN_PRIORITY}), so it tends to use spare CPU and
* never fans out across every core like the default common pool would.</li>
* {@link Thread#MIN_PRIORITY} workers instead of fanning out over the common pool.</li>
* </ul>
*
* <p>Reserve a slot with {@link #tryReserve(UUID)}; if accepted, run the heavy work via
@@ -87,7 +86,7 @@ public final class RenderManager {
return Outcome.ACCEPTED;
}
/** Releases a reservation made by {@link #tryReserve}. Safe to call once per accepted reserve. */
/** Releases a reservation made by {@link #tryReserve}; a no-op when {@code user} holds none. */
public void release(UUID user) {
if (activeUsers.remove(user)) {
inFlight.decrementAndGet();
@@ -48,7 +48,7 @@ public final class BlockEntityModels {
*/
public record VanillaModel(Affine pose, List<Part> parts) {}
/** The vanilla model + pose of a block-entity, or null when nothing model-based is drawn (signs). */
/** The vanilla model + pose of a block-entity, or null without an entity model (signs, contents only). */
public static VanillaModel vanillaModel(BlockEntityState s) {
double deg = s.facingDeg();
return switch (s.kind()) {
@@ -7,8 +7,8 @@ import eu.mhsl.minecraft.pixelpics.assets.model.Face;
* null = not drawn), the inverse map and the world-space AABB.
*/
public final class EntityCube {
public final double[] from; // local min (inflated)
public final double[] to; // local max
public final double[] from; // local corners; from > to on some axis = inverted (below)
public final double[] to;
public final Face[] faces;
public final Affine toWorld;
public final Affine toLocal;
@@ -269,8 +269,6 @@ public final class EntityScene {
return tmin;
}
// ---- BVH construction (binned SAH over the cube AABB centroids) ----
private double[] centroid; // 3 per original cube
private double[] srcBox; // 6 per original cube
private int[] perm; // original cube indices, partitioned in place
@@ -37,8 +37,7 @@ public record EntityState(
int collar, // ARGB dye of a tamed cat's/wolf's collar; 0 = no collar (untamed)
boolean powered, // energy-swirl layer: charged creeper, wither below half health
ItemSpec mouthItem, // item carried in the mouth (fox/dolphin main hand); null = none
String pose, // "+"-joined state pose/render flags (the dump's STATE_POSES names, e.g. "lying+head_up"); null =
// idle
String pose, // "+"-joined STATE_POSES flags of the dump (e.g. "lying+head_up"); null = idle
Map<String, float[]> partPoses, // armor-stand part rotations by render-state field ("headPose", …), x/y/z deg
String shoulderLeft, // parrot variant on a player's left shoulder (e.g. "red"); null = none
String shoulderRight // parrot variant on the right shoulder; null = none
@@ -55,8 +55,6 @@ public final class ObjectBaker implements EntityBaker<ObjectState> {
return cubes.isEmpty() ? null : RenderedEntity.of(cubes);
}
// --- leash ---------------------------------------------------------------------------------------
/**
* Vanilla LeashRenderer curve: 24 segments from the leashed entity to the holder, horizontal position
* linear, height {@code dy·(t² + t)/2} (sags below the straight line), alternating light/dark brown.
@@ -105,8 +103,6 @@ public final class ObjectBaker implements EntityBaker<ObjectState> {
if (l > 0) for (int i = 0; i < 3; i++) v[i] /= l;
}
// --- text ------------------------------------------------------------------------------------
private void addText(ObjectState s, List<EntityCube> cubes) {
if (font == null || font.isEmpty()) return;
List<String> lines = wrap(s.lines(), Math.max(1, s.lineWidth()));
@@ -2,7 +2,7 @@ package eu.mhsl.minecraft.pixelpics.render.entity;
import java.util.List;
/** A baked entity: its world-space cubes and their union AABB (framing in the test harnesses). */
/** A baked entity: its world-space cubes and their union AABB (item model bounds, harness framing). */
public final class RenderedEntity {
public final List<EntityCube> cubes;
public final double[] aabbMin;
@@ -23,7 +23,6 @@ public final class TextureOps {
}
}
/** A tinted copy of {@code src}. */
public static int[][] tinted(int[][] src, int argb) {
int[][] out = deepCopy(src);
tint(out, argb);
@@ -99,8 +99,6 @@ public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
}
}
// --- contents: items, spawner mob, beacon beam, end portal ---
private void addContents(BlockEntityState s, Affine cell, List<EntityCube> cubes) {
ItemGeometry ig = items;
switch (s.kind()) {
@@ -385,8 +383,6 @@ public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
return tex[y][x];
}
// --- textures ---
private int[][] texture(String path) {
return textures.get(ResourceLocation.parse(path)).orElse(null);
}
@@ -435,8 +431,6 @@ public final class BlockEntityBaker implements EntityBaker<BlockEntityState> {
TextureOps.overlay(out, TextureOps.tinted(mask, argb));
}
// --- sign text ---
/**
* Front and back text as quads centred in the sign renderer's text frame ({@link BlockEntityModels#signTextFrame})
* facing +Z, the reader; the +Z (SOUTH) face reads left to right ({@code s = fx}).
@@ -188,8 +188,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
return tex;
}
// --- poses & items -------------------------------------------------------------------------------
/** Pre-space transform and pose overrides shared by every vanilla model of one entity. */
private record Pose(Affine pre, Map<String, CemModelLoader.PartPose> parts) {
List<CemGeometry.Baked> bake(CemModelLoader.CemModel m, int[][] tex) {
@@ -490,8 +488,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
return info != null && info.poses().keySet().stream().anyMatch(k -> k.startsWith("arms_"));
}
// --- model choice --------------------------------------------------------------------------------
/**
* The body model: baby model for babies, variant model matched by name token (temperature variants, salmon
* size, puff state, …), else the main model; else the legacy CEM template.
@@ -576,8 +572,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
});
}
// --- vanilla render layers -----------------------------------------------------------------------
private void addVanillaLayers(
EntityState s, Choice ch, CemModelLoader.EntityInfo info, Pose pose, int[][] bodyTex, Out out) {
String base = ch.base;
@@ -657,7 +651,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
}
}
/** The layer model's own texture, or null. */
private int[][] layerTexture(CemModelLoader.CemModel lm) {
return lm.texture() == null ? null : texture(lm.texture());
}
@@ -703,8 +696,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
return out;
}
// --- energy swirl & collars ----------------------------------------------------------------------
// A photo has no time: the swirl scroll is frozen at this age (ticks), chosen so the sparse wither_armor pattern
// covers every wither part (at e.g. 42 the side heads land on empty rows).
private static final float SWIRL_AGE = 59f;
@@ -757,8 +748,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
return derived.get(DerivedTextureCache.key("tint", src, argb), () -> TextureOps.tinted(src, argb));
}
// --- emissive layers -----------------------------------------------------------------------------
/**
* The dump's emissive layers (eyes, warden bioluminescence, …): texture per variant, processed by the layer's
* render type, times its static alpha. Skipped: animated layers (a photo has no age), layers whose state flag is
@@ -798,8 +787,6 @@ public final class CemBaker implements EntityBaker<EntityState> {
});
}
// --- texture resolution --------------------------------------------------------------------------
private int[][] texture(String path) {
return textures.get(ResourceLocation.parse(path)).orElse(null);
}
@@ -849,9 +836,9 @@ public final class CemBaker implements EntityBaker<EntityState> {
return null;
}
// --- villagers: biome type + profession + level badge over the base body ---
private static final String[] LEVEL_BADGE = {"stone", "iron", "gold", "emerald", "diamond"};
/** Villager texture: biome type, profession and level badge over the base body. */
private int[][] compositeVillager(EntityState s, int[][] base, String folder, boolean babyModel) {
if (base == null) return null;
// Only the inputs the composite reads (babies: no profession).
@@ -887,10 +874,9 @@ public final class CemBaker implements EntityBaker<EntityState> {
TextureOps.overlay(dst, o);
}
// --- humanoid armor: the wearer's per-slot models <base>/HumanoidArmorLayer.modelSet.<slot>, posed like the body
private static final int GLINT_COLOR = 0xFF8040CC; // approximated enchantment-glint purple
/** The wearer's per-slot armor models {@code <base>/HumanoidArmorLayer.modelSet.<slot>}, posed like the body. */
private void addArmorLayers(EntityState s, Choice ch, Pose pose, Out out) {
EntityState.Equipment eq = s.equipment();
String base = ch.base;
@@ -241,7 +241,7 @@ public final class CemModelLoader {
try {
JsonObject entry = e.getValue().getAsJsonObject();
if (!entry.has("model")) continue;
// The dump nests the jem as an object; the CEM template set (and older dumps) embed a JSON string.
// The dump nests the jem as an object; the CEM template set embeds a JSON string.
JsonElement m = entry.get("model");
JsonObject model = m.isJsonObject()
? m.getAsJsonObject()
@@ -31,8 +31,8 @@ public final class DatapackVariants {
private DatapackVariants() {}
/**
* Scans {@code <level>/datapacks/*} for every world folder — the folder itself or the level directory
* above it (26.x keeps dimensions under {@code <level>/dimensions/<ns>/<name>}); returns the number of
* Scans {@code <level>/datapacks/*} for every world folder: the folder itself or the nearest of up to 4 parents
* that has one (26.x keeps dimensions under {@code <level>/dimensions/<ns>/<name>}); returns the number of
* variants registered.
*/
public static int load(CemModelLoader models, Iterable<File> worldFolders, Logger log) {
@@ -82,8 +82,6 @@ public final class ItemGeometry {
JsonObject specialModel, // the special model's own fields (chest texture/chest_type, …) or null
Affine specialTransform) {} // item-model transformation for special models (T·Rl·S·Rr) or null
// --- public API --------------------------------------------------------------------------------
/** An item in a display context ("thirdperson_righthand", "head", "ground", "fixed", "none", …). */
public void bake(ItemSpec item, String context, boolean leftHand, Affine toWorld, List<EntityCube> out) {
if (item == null) return;
@@ -159,8 +157,6 @@ public final class ItemGeometry {
if (state != null && blockEntities != null) blockEntities.bakeModel(state, toWorld, out);
}
// --- item definition / model resolution --------------------------------------------------------
private Info info(String id, String context) {
// Keyed by date: minecraft:local_time selects (christmas chest items) change with the day.
ZonedDateTime now = SpecialDates.now();
@@ -386,8 +382,6 @@ public final class ItemGeometry {
return o.has(k) && o.get(k).isJsonObject() ? o.getAsJsonObject(k) : null;
}
// --- geometry ------------------------------------------------------------------------------------
private void bakeSpecial(ItemSpec item, Info info, Affine m, List<EntityCube> out) {
Affine xf = info.specialTransform != null ? m.mul(info.specialTransform) : m;
switch (info.special) {
@@ -39,8 +39,9 @@ public final class ElementIntersector {
double hitT, hitS, hitT2, hitNx, hitNy, hitNz;
Direction hitDir;
// Depth; coplanar within 1e-4: later elements first. Not transitive, so the tie order depends on the sort
// algorithm: keep Arrays.sort over objects (TimSort).
// Depth; coplanar within 1e-4: later elements first, like vanilla's draw order (overlays such as the grass
// side tint). Not transitive, so the tie order depends on the sort algorithm: keep Arrays.sort over objects
// (TimSort).
final Comparator<Integer> byDepth = (a, b) -> {
if (Math.abs(t[a] - t[b]) > 1e-4) return Double.compare(t[a], t[b]);
return Integer.compare(order[b], order[a]);
@@ -131,7 +132,6 @@ public final class ElementIntersector {
if (n == 0) return null;
Integer[] idx = sc.idx;
for (int k = 0; k < n; k++) idx[k] = k; // Integer cache for k < 128
// Coplanar faces: later elements first, like vanilla's draw order (overlays such as the grass side tint).
if (n > 1) Arrays.sort(idx, 0, n, sc.byDepth);
for (int j = 0; j < n; j++) {
@@ -23,7 +23,7 @@ import org.bukkit.block.data.Waterlogged;
* under the same fluid, averaged with the neighbours like {@code calculateAverageHeight}) split into the triangles
* NW-SW-SE / NW-SE-NE; side faces against every neighbour that is not the same fluid and does not cover the face;
* a bottom face. Top and sides are double-sided (the top only if {@code shouldRenderBackwardUpFace}), except side
* faces against {@code HalfTransparentBlock}/{@code LeavesBlock}, which use the overlay texture one-sided; the
* water faces against {@code HalfTransparentBlock}/{@code LeavesBlock}, which use the overlay texture one-sided; the
* bottom is seen from below only. Waterlogged blocks and seagrass/kelp/bubble columns hold a water source.
*/
public final class FluidRenderer {
@@ -74,13 +74,13 @@ public final class FluidRenderer {
try {
set.add(Material.valueOf(n));
} catch (IllegalArgumentException ignored) {
/* older/newer server */
// Older/newer server
}
}
return set;
}
// --- fluid state: type (bits 0-1), amount 1..8 (bits 2-5), falling (bit 6), source (bit 7) -------------
// Packed fluid state: type (bits 0-1), amount 1..8 (bits 2-5), falling (bit 6), source (bit 7).
private static final int WATER_SOURCE = WATER | 8 << 2 | 1 << 7;
@@ -144,8 +144,6 @@ public final class FluidRenderer {
}
}
// --- sky light under water ----------------------------------------------------------------------------
/**
* Vanilla sky light level of the cell (x, y, z) under water: 15 minus one per block from this cell up to the
* topmost water block of the column (water and non-occluding blocks below the surface cost one level each).
@@ -165,8 +163,6 @@ public final class FluidRenderer {
return 15 - water;
}
// --- textures ------------------------------------------------------------------------------------------
private final BlockModelRegistry registry;
private final int[][] waterStill, waterFlow, waterOverlay, lavaStill, lavaFlow;
// Bit per Direction: the model covers that whole cell face with opaque texels (approximates vanilla's face
@@ -196,8 +192,6 @@ public final class FluidRenderer {
return tex[y][x];
}
// --- face occlusion ------------------------------------------------------------------------------------
private int fullFaces(BlockData data) {
if (data.getMaterial().isAir()) return 0;
return fullFaces(registry.get(data));
@@ -254,8 +248,6 @@ public final class FluidRenderer {
return (fullFaces & 1 << face.ordinal()) != 0;
}
// --- heights -------------------------------------------------------------------------------------------
/** FluidRenderer.getHeight: 1 under the same fluid, own height in it, 0 for non-solid blocks, -1 for solids. */
private static float height(BlockLookup level, int type, int x, int y, int z, BlockData data, int state) {
if (type(state) == type) {
@@ -287,8 +279,6 @@ public final class FluidRenderer {
return h >= 0.8F ? 10.0F : h >= 0.0F ? 1.0F : 0.0F;
}
// --- flow (top texture direction) ---------------------------------------------------------------------
private static final int[][] HORIZONTAL = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
/**
@@ -346,8 +336,6 @@ public final class FluidRenderer {
return false;
}
// --- ray crossing --------------------------------------------------------------------------------------
/** A drawn fluid polygon crossed by a ray. */
static final class Crossing {
double t;
@@ -2,7 +2,6 @@ package eu.mhsl.minecraft.pixelpics.render.render;
import com.google.common.base.Preconditions;
/** Output size of a photo in pixels. */
public record Resolution(int width, int height) {
public Resolution {
@@ -383,7 +383,7 @@ public final class BlockEntitySnapshotBuilder {
/**
* BeaconBlockEntity.tick's beam scan from the beacon up to the WORLD_SURFACE height: the beacon starts a
* white section, stained glass (panes) starts a section of its dye colour (the first one over the beacon
* replaces white, later ones average with the previous colour, equal colours just extend it), other
* is not blended with white, later ones average with the previous colour, equal colours just extend it), other
* blocks extend the current section, and a light-blocking block (except bedrock) stops the beam.
*/
private static BlockEntityState.Beam beam(World world, int bx, int by, int bz, Location eye) {
@@ -175,7 +175,7 @@ public final class EntitySnapshotBuilder {
boolean powered = false;
try {
if (e instanceof Slime sl) sizeScale = sl.getSize(); // incl. magma cubes
// Order matters for subtypes (MushroomCow extends Cow, Llama extends ChestedHorse extends AbstractHorse).
// Order matters for subtypes (Llama extends ChestedHorse extends AbstractHorse).
if (e instanceof Sheep sh) {
tint = isJebSheep(sh) ? jebWoolColor(sh.getTicksLived()) : sheepWoolColor(sh.getColor());
if (sh.isSheared()) variant = "sheared";
@@ -601,7 +601,7 @@ public final class EntitySnapshotBuilder {
}
}
/** One armor slot -> EquipPiece (asset, leather dye, trim, glint); null for empty / non-armor items. */
/** Null for empty / non-armor items. */
private static EntityState.EquipPiece armorPiece(ItemStack it) {
if (it == null || it.getType().isAir()) return null;
String asset = armorAsset(it.getType());
@@ -10,7 +10,7 @@ import org.bukkit.block.data.BlockData;
*
* <p>Performance: the chunks live in a primitive open-addressing table (linear probing, Fibonacci hash of the
* full {@link #chunkKey}), not a {@code HashMap<Long, …>}: {@code Long.hashCode} is {@code cx ^ cz}, so every
* diagonal collided into treeified bins and each block lookup paid for boxing and {@code TreeNode.find}.
* diagonal would collide into treeified bins and each block lookup would pay for boxing.
*/
public final class WorldSnapshot {
@@ -26,7 +26,7 @@ public final class BiomeClimate {
public static final int NONE = -1;
/**
* @param water ARGB-less RGB water color
* @param water RGB water colour (no alpha)
* @param grass explicit grass color override, or {@link #NONE}
* @param foliage explicit foliage color override, or {@link #NONE}
* @param dryFoliage explicit dry-foliage color override, or {@link #NONE}
@@ -1,8 +1,6 @@
package eu.mhsl.minecraft.pixelpics.render.tint;
/**
* The biome-dependent tint colors (RGB) for the colormap-driven channels.
*/
/** Biome tint colours (opaque ARGB) of the colormap-driven channels. */
public record BiomeTint(int grass, int foliage, int dryFoliage, int water) {
/** Plains colours: the fallback without a colormap, and the tint of blocks without biome context. */
public static final BiomeTint PLAINS = new BiomeTint(0xFF91BD59, 0xFF77AB2F, 0xFFA9A05B, 0xFF3F76E4);
@@ -104,7 +104,6 @@ public final class ColorUtil {
| clamp(blue(dst) + blue(src));
}
/** Clamps a channel value to 0..255. */
public static int clamp(int v) {
return v < 0 ? 0 : Math.min(v, 255);
}