rescale rotated block elements like vanilla (cross plants, amethyst, campfire, fire, raised rails), euler x/y/z element rotations (hanging sign chains), inverted inner-face elements (spawner, vault), vanilla-like water: single surface blend with depth sky light instead of path-length density, water faces against glass/ice/leaves, block geometry and water test scenes
This commit is contained in:
1 parent
2c2e9e2e32
commit
318a309580
7 files changed
+510
-38
No files matched your search
@@ -58,17 +58,26 @@ public final class ModelBaker {
|
|||||||
double[] rotOrigin = null;
|
double[] rotOrigin = null;
|
||||||
int rotAxis = -1;
|
int rotAxis = -1;
|
||||||
double rotAngle = 0;
|
double rotAngle = 0;
|
||||||
|
double[] rotMatrix = null;
|
||||||
boolean rescale = false;
|
boolean rescale = false;
|
||||||
if (dto.rotation != null && dto.rotation.angle != 0 && dto.rotation.origin != null) {
|
ModelFileDto.RotationDto rot = dto.rotation;
|
||||||
rotOrigin = new double[] {
|
if (rot != null && rot.origin != null) {
|
||||||
dto.rotation.origin[0] / 16.0, dto.rotation.origin[1] / 16.0, dto.rotation.origin[2] / 16.0
|
if (rot.axis == null && (rot.x != null || rot.y != null || rot.z != null)) {
|
||||||
};
|
rotMatrix = eulerXyz(rot.x, rot.y, rot.z);
|
||||||
rotAxis = axisIndex(dto.rotation.axis);
|
if (rotMatrix != null) rotAxis = Element.AXIS_MATRIX;
|
||||||
rotAngle = Math.toRadians(dto.rotation.angle);
|
} else if (rot.angle != 0) {
|
||||||
rescale = dto.rotation.rescale;
|
rotAxis = axisIndex(rot.axis);
|
||||||
|
rotAngle = Math.toRadians(rot.angle);
|
||||||
|
}
|
||||||
|
if (rotAxis >= 0) {
|
||||||
|
rotOrigin = new double[] {rot.origin[0] / 16.0, rot.origin[1] / 16.0, rot.origin[2] / 16.0};
|
||||||
|
rescale = rot.rescale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Element modelElement = new Element(from, to, faces, rotOrigin, rotAxis, rotAngle, rescale);
|
Element modelElement = rotMatrix != null
|
||||||
|
? new Element(from, to, faces, rotOrigin, rotMatrix, rescale, null, 0, 0)
|
||||||
|
: new Element(from, to, faces, rotOrigin, rotAxis, rotAngle, rescale);
|
||||||
|
|
||||||
// Apply blockstate variant rotation (90-degree steps) to box + faces + element rotation.
|
// Apply blockstate variant rotation (90-degree steps) to box + faces + element rotation.
|
||||||
for (int i = 0; i < xSteps; i++) {
|
for (int i = 0; i < xSteps; i++) {
|
||||||
@@ -76,8 +85,8 @@ public final class ModelBaker {
|
|||||||
from = r[0];
|
from = r[0];
|
||||||
to = r[1];
|
to = r[1];
|
||||||
faces = rotateFacesX(faces);
|
faces = rotateFacesX(faces);
|
||||||
if (rotAxis >= 0) {
|
if (rotAxis >= 0) rotOrigin = rotatePointX(rotOrigin);
|
||||||
rotOrigin = rotatePointX(rotOrigin);
|
if (rotAxis >= 0 && rotAxis < Element.AXIS_MATRIX) {
|
||||||
AxisRotation na = rotateAxisX(rotAxis);
|
AxisRotation na = rotateAxisX(rotAxis);
|
||||||
rotAxis = na.axis();
|
rotAxis = na.axis();
|
||||||
if (na.flip()) rotAngle = -rotAngle;
|
if (na.flip()) rotAngle = -rotAngle;
|
||||||
@@ -88,8 +97,8 @@ public final class ModelBaker {
|
|||||||
from = r[0];
|
from = r[0];
|
||||||
to = r[1];
|
to = r[1];
|
||||||
faces = rotateFacesY(faces);
|
faces = rotateFacesY(faces);
|
||||||
if (rotAxis >= 0) {
|
if (rotAxis >= 0) rotOrigin = rotatePointY(rotOrigin);
|
||||||
rotOrigin = rotatePointY(rotOrigin);
|
if (rotAxis >= 0 && rotAxis < Element.AXIS_MATRIX) {
|
||||||
AxisRotation na = rotateAxisY(rotAxis);
|
AxisRotation na = rotateAxisY(rotAxis);
|
||||||
rotAxis = na.axis();
|
rotAxis = na.axis();
|
||||||
if (na.flip()) rotAngle = -rotAngle;
|
if (na.flip()) rotAngle = -rotAngle;
|
||||||
@@ -103,7 +112,20 @@ public final class ModelBaker {
|
|||||||
if (xSteps != 0 || ySteps != 0) {
|
if (xSteps != 0 || ySteps != 0) {
|
||||||
unrotated = variant.uvlock() ? uvLocked(modelElement, xSteps, ySteps) : modelElement;
|
unrotated = variant.uvlock() ? uvLocked(modelElement, xSteps, ySteps) : modelElement;
|
||||||
}
|
}
|
||||||
baked.add(new Element(from, to, faces, rotOrigin, rotAxis, rotAngle, rescale, unrotated, xSteps, ySteps));
|
baked.add(
|
||||||
|
rotMatrix != null
|
||||||
|
? new Element(
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
faces,
|
||||||
|
rotOrigin,
|
||||||
|
conjugate(rotMatrix, xSteps, ySteps),
|
||||||
|
rescale,
|
||||||
|
unrotated,
|
||||||
|
xSteps,
|
||||||
|
ySteps)
|
||||||
|
: new Element(
|
||||||
|
from, to, faces, rotOrigin, rotAxis, rotAngle, rescale, unrotated, xSteps, ySteps));
|
||||||
|
|
||||||
// Accumulate average color from the element's face textures.
|
// Accumulate average color from the element's face textures.
|
||||||
for (Face f : faces) {
|
for (Face f : faces) {
|
||||||
@@ -153,6 +175,48 @@ public final class ModelBaker {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vanilla {@code EulerXYZRotation}: {@code Matrix4f.rotationZYX(z, y, x)} = Rz·Ry·Rx (degrees), row-major;
|
||||||
|
* null when it is the identity (vanilla then skips the rotation and its rescale).
|
||||||
|
*/
|
||||||
|
static double[] eulerXyz(Double xDeg, Double yDeg, Double zDeg) {
|
||||||
|
double x = Math.toRadians(xDeg == null ? 0 : xDeg);
|
||||||
|
double y = Math.toRadians(yDeg == null ? 0 : yDeg);
|
||||||
|
double z = Math.toRadians(zDeg == null ? 0 : zDeg);
|
||||||
|
if (x == 0 && y == 0 && z == 0) return null;
|
||||||
|
double cx = Math.cos(x),
|
||||||
|
sx = Math.sin(x),
|
||||||
|
cy = Math.cos(y),
|
||||||
|
sy = Math.sin(y),
|
||||||
|
cz = Math.cos(z),
|
||||||
|
sz = Math.sin(z);
|
||||||
|
double[] rz = {cz, -sz, 0, sz, cz, 0, 0, 0, 1};
|
||||||
|
double[] ry = {cy, 0, sy, 0, 1, 0, -sy, 0, cy};
|
||||||
|
double[] rx = {1, 0, 0, 0, cx, -sx, 0, sx, cx};
|
||||||
|
return mul(mul(rz, ry), rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The element rotation as seen after the blockstate rotation: V·M·Vᵀ (V = Element#rotate of the axes). */
|
||||||
|
private static double[] conjugate(double[] m, int xSteps, int ySteps) {
|
||||||
|
double[] v = new double[9];
|
||||||
|
for (int a = 0; a < 3; a++) {
|
||||||
|
double[] col = Element.rotate(a == 0 ? 1 : 0, a == 1 ? 1 : 0, a == 2 ? 1 : 0, xSteps, ySteps, false);
|
||||||
|
v[a] = col[0];
|
||||||
|
v[3 + a] = col[1];
|
||||||
|
v[6 + a] = col[2];
|
||||||
|
}
|
||||||
|
double[] vt = {v[0], v[3], v[6], v[1], v[4], v[7], v[2], v[5], v[8]};
|
||||||
|
return mul(mul(v, m), vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double[] mul(double[] a, double[] b) {
|
||||||
|
double[] r = new double[9];
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
r[3 * i + j] = a[3 * i] * b[j] + a[3 * i + 1] * b[3 + j] + a[3 * i + 2] * b[6 + j];
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/** The model-space element with every face's UVs locked to the world under the given rotation. */
|
/** The model-space element with every face's UVs locked to the world under the given rotation. */
|
||||||
private static Element uvLocked(Element e, int xSteps, int ySteps) {
|
private static Element uvLocked(Element e, int xSteps, int ySteps) {
|
||||||
Face[] locked = new Face[6];
|
Face[] locked = new Face[6];
|
||||||
@@ -160,6 +224,8 @@ public final class ModelBaker {
|
|||||||
Face f = e.faces[d.ordinal()];
|
Face f = e.faces[d.ordinal()];
|
||||||
if (f != null) locked[d.ordinal()] = f.withUvLock(uvLockTransform(d, xSteps, ySteps));
|
if (f != null) locked[d.ordinal()] = f.withUvLock(uvLockTransform(d, xSteps, ySteps));
|
||||||
}
|
}
|
||||||
|
if (e.rotMatrix != null)
|
||||||
|
return new Element(e.from, e.to, locked, e.rotOrigin, e.rotMatrix, e.rescale, null, 0, 0);
|
||||||
return new Element(e.from, e.to, locked, e.rotOrigin, e.rotAxis, e.rotAngleRad, e.rescale);
|
return new Element(e.from, e.to, locked, e.rotOrigin, e.rotAxis, e.rotAngleRad, e.rescale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ public class ModelFileDto {
|
|||||||
public static class RotationDto {
|
public static class RotationDto {
|
||||||
public double[] origin; // 0..16
|
public double[] origin; // 0..16
|
||||||
public String axis; // "x" | "y" | "z"
|
public String axis; // "x" | "y" | "z"
|
||||||
public double angle; // -45..45 in 22.5 steps
|
public double angle; // degrees (vanilla models: 22.5 steps)
|
||||||
|
// Since 26.x alternatively Euler angles in degrees instead of axis/angle (CuboidRotation.EulerXYZRotation,
|
||||||
|
// matrix Rz·Ry·Rx), e.g. the chains of template_hanging_sign_rot_3.
|
||||||
|
public Double x, y, z;
|
||||||
public boolean rescale;
|
public boolean rescale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,14 +15,26 @@ public final class Element {
|
|||||||
|
|
||||||
// Element rotation (0..1 origin), null/zero when axis-aligned.
|
// Element rotation (0..1 origin), null/zero when axis-aligned.
|
||||||
public final double[] rotOrigin; // length 3, 0..1, may be null
|
public final double[] rotOrigin; // length 3, 0..1, may be null
|
||||||
public final int rotAxis; // 0=x,1=y,2=z, -1 when none
|
public final int rotAxis; // 0=x,1=y,2=z, AXIS_MATRIX (Euler x/y/z rotation in rotMatrix), -1 when none
|
||||||
public final double rotAngleRad; // radians
|
public final double rotAngleRad; // radians (single-axis rotations only)
|
||||||
public final boolean rescale;
|
public final boolean rescale;
|
||||||
|
|
||||||
|
/** {@link #rotAxis} of an element rotated by a general (Euler x/y/z) rotation held in {@link #rotMatrix}. */
|
||||||
|
public static final int AXIS_MATRIX = 3;
|
||||||
|
|
||||||
// cos/sin of the element rotation (+angle: local -> block) and of its inverse (-angle: block -> local),
|
// cos/sin of the element rotation (+angle: local -> block) and of its inverse (-angle: block -> local),
|
||||||
// precomputed for the intersector's hot path.
|
// precomputed for the intersector's hot path.
|
||||||
public final double rotCos, rotSin, invRotCos, invRotSin;
|
public final double rotCos, rotSin, invRotCos, invRotSin;
|
||||||
|
|
||||||
|
// General rotation (rotAxis == AXIS_MATRIX): row-major 3x3 local -> block and its inverse (transpose); null
|
||||||
|
// otherwise.
|
||||||
|
public final double[] rotMatrix, invRotMatrix;
|
||||||
|
|
||||||
|
// Vanilla rescale (CuboidRotation.computeRescale): the element is scaled per LOCAL axis by
|
||||||
|
// 1 / max|component of R·axis| before the rotation (1/cos 22.5° or 1/cos 45° on the axes the rotation tilts),
|
||||||
|
// so a rotated cross plane still spans the whole block. Reciprocals (block -> local); null without rescale.
|
||||||
|
public final double[] invScale;
|
||||||
|
|
||||||
// Blockstate rotation: the model-space element and the 90° steps (x first, then y) that turn it into this
|
// Blockstate rotation: the model-space element and the 90° steps (x first, then y) that turn it into this
|
||||||
// world-space one. Intersectors hit the unrotated element with the inverse-rotated ray, so textures stay
|
// world-space one. Intersectors hit the unrotated element with the inverse-rotated ray, so textures stay
|
||||||
// bound to the model (vanilla); under uvlock its faces carry a UV transform keeping them world-aligned.
|
// bound to the model (vanilla); under uvlock its faces carry a UV transform keeping them world-aligned.
|
||||||
@@ -42,6 +54,20 @@ public final class Element {
|
|||||||
this(from, to, faces, rotOrigin, rotAxis, rotAngleRad, rescale, null, 0, 0);
|
this(from, to, faces, rotOrigin, rotAxis, rotAngleRad, rescale, null, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** An element rotated by a general rotation matrix (row-major 3x3, local -> block, e.g. Euler x/y/z). */
|
||||||
|
public Element(
|
||||||
|
double[] from,
|
||||||
|
double[] to,
|
||||||
|
Face[] faces,
|
||||||
|
double[] rotOrigin,
|
||||||
|
double[] rotMatrix,
|
||||||
|
boolean rescale,
|
||||||
|
Element unrotated,
|
||||||
|
int xSteps,
|
||||||
|
int ySteps) {
|
||||||
|
this(from, to, faces, rotOrigin, AXIS_MATRIX, 0, rescale, unrotated, xSteps, ySteps, rotMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
public Element(
|
public Element(
|
||||||
double[] from,
|
double[] from,
|
||||||
double[] to,
|
double[] to,
|
||||||
@@ -53,6 +79,21 @@ public final class Element {
|
|||||||
Element unrotated,
|
Element unrotated,
|
||||||
int xSteps,
|
int xSteps,
|
||||||
int ySteps) {
|
int ySteps) {
|
||||||
|
this(from, to, faces, rotOrigin, rotAxis, rotAngleRad, rescale, unrotated, xSteps, ySteps, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Element(
|
||||||
|
double[] from,
|
||||||
|
double[] to,
|
||||||
|
Face[] faces,
|
||||||
|
double[] rotOrigin,
|
||||||
|
int rotAxis,
|
||||||
|
double rotAngleRad,
|
||||||
|
boolean rescale,
|
||||||
|
Element unrotated,
|
||||||
|
int xSteps,
|
||||||
|
int ySteps,
|
||||||
|
double[] rotMatrix) {
|
||||||
this.unrotated = unrotated;
|
this.unrotated = unrotated;
|
||||||
this.xSteps = xSteps;
|
this.xSteps = xSteps;
|
||||||
this.ySteps = ySteps;
|
this.ySteps = ySteps;
|
||||||
@@ -67,6 +108,41 @@ public final class Element {
|
|||||||
this.rotSin = Math.sin(rotAngleRad);
|
this.rotSin = Math.sin(rotAngleRad);
|
||||||
this.invRotCos = Math.cos(-rotAngleRad);
|
this.invRotCos = Math.cos(-rotAngleRad);
|
||||||
this.invRotSin = Math.sin(-rotAngleRad);
|
this.invRotSin = Math.sin(-rotAngleRad);
|
||||||
|
this.rotMatrix = rotAxis == AXIS_MATRIX ? rotMatrix : null;
|
||||||
|
this.invRotMatrix = this.rotMatrix == null ? null : transpose(this.rotMatrix);
|
||||||
|
this.invScale = rescale && !isAxisAligned() ? inverseRescale(linearRotation()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The rotation as a row-major 3x3 (local -> block), identity when axis-aligned. */
|
||||||
|
public double[] linearRotation() {
|
||||||
|
if (rotMatrix != null) return rotMatrix.clone();
|
||||||
|
double c = rotCos, s = rotSin;
|
||||||
|
return switch (isAxisAligned() ? -1 : rotAxis) {
|
||||||
|
case 0 -> new double[] {1, 0, 0, 0, c, -s, 0, s, c};
|
||||||
|
case 1 -> new double[] {c, 0, s, 0, 1, 0, -s, 0, c};
|
||||||
|
case 2 -> new double[] {c, -s, 0, s, c, 0, 0, 0, 1};
|
||||||
|
default -> new double[] {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vanilla rescale factors per local axis: 1 / max|component of R·axis| (column of R); as reciprocals. */
|
||||||
|
private static double[] inverseRescale(double[] r) {
|
||||||
|
double[] inv = new double[3];
|
||||||
|
for (int a = 0; a < 3; a++) {
|
||||||
|
double max = Math.max(Math.max(Math.abs(r[a]), Math.abs(r[3 + a])), Math.abs(r[6 + a]));
|
||||||
|
inv[a] = max; // 1 / (1 / max)
|
||||||
|
}
|
||||||
|
return inv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The rescale factors per local axis (local -> block), {1,1,1} without rescale. */
|
||||||
|
public double[] rescaleFactors() {
|
||||||
|
if (invScale == null) return new double[] {1, 1, 1};
|
||||||
|
return new double[] {1 / invScale[0], 1 / invScale[1], 1 / invScale[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double[] transpose(double[] m) {
|
||||||
|
return new double[] {m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Model space -> world: vanilla blockstate rotation (x steps, then y steps; clockwise about the centre). */
|
/** Model space -> world: vanilla blockstate rotation (x steps, then y steps; clockwise about the centre). */
|
||||||
@@ -93,6 +169,6 @@ public final class Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAxisAligned() {
|
public boolean isAxisAligned() {
|
||||||
return rotAxis < 0 || rotAngleRad == 0.0;
|
return rotAxis < 0 || (rotAxis != AXIS_MATRIX && rotAngleRad == 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,11 +460,13 @@ public final class ItemGeometry {
|
|||||||
switch (e.rotAxis) {
|
switch (e.rotAxis) {
|
||||||
case 0 -> Affine.rotX(e.rotAngleRad);
|
case 0 -> Affine.rotX(e.rotAngleRad);
|
||||||
case 1 -> Affine.rotY(e.rotAngleRad);
|
case 1 -> Affine.rotY(e.rotAngleRad);
|
||||||
default -> Affine.rotZ(e.rotAngleRad);
|
case 2 -> Affine.rotZ(e.rotAngleRad);
|
||||||
|
default -> new Affine(e.linearRotation(), new double[] {0, 0, 0});
|
||||||
};
|
};
|
||||||
if (e.rescale) {
|
if (e.invScale != null) {
|
||||||
double f = 1 / Math.cos(e.rotAngleRad);
|
// Vanilla CuboidRotation rescale: R·S, S = 1 / max|component of R·axis| per local axis.
|
||||||
r = r.mul(Affine.scale(e.rotAxis == 0 ? 1 : f, e.rotAxis == 1 ? 1 : f, e.rotAxis == 2 ? 1 : f));
|
double[] f = e.rescaleFactors();
|
||||||
|
r = r.mul(Affine.scale(f[0], f[1], f[2]));
|
||||||
}
|
}
|
||||||
return Affine.translation(o[0], o[1], o[2]).mul(r).mul(Affine.translation(-o[0], -o[1], -o[2]));
|
return Affine.translation(o[0], o[1], o[2]).mul(r).mul(Affine.translation(-o[0], -o[1], -o[2]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.bukkit.util.Vector;
|
|||||||
public final class ElementIntersector {
|
public final class ElementIntersector {
|
||||||
|
|
||||||
private static final double EPS = 1e-7;
|
private static final double EPS = 1e-7;
|
||||||
|
private static final Direction[] DIRECTIONS = Direction.values();
|
||||||
|
|
||||||
private ElementIntersector() {}
|
private ElementIntersector() {}
|
||||||
|
|
||||||
@@ -154,14 +155,41 @@ public final class ElementIntersector {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rotated element: transform the ray into the element's local (unrotated) frame, then slab-test. */
|
/**
|
||||||
|
* Rotated element: transform the ray into the element's local (unrotated, unscaled) frame, then slab-test.
|
||||||
|
* Vanilla bakes {@code origin + R·S·(p - origin)} (S = rescale, see {@link Element#invScale}); the inverse is
|
||||||
|
* affine, so the ray parameter t stays the block-space one.
|
||||||
|
*/
|
||||||
private static boolean intersectObb(
|
private static boolean intersectObb(
|
||||||
Scratch sc, Element e, double ox, double oy, double oz, double dx, double dy, double dz) {
|
Scratch sc, Element e, double ox, double oy, double oz, double dx, double dy, double dz) {
|
||||||
double[] v = sc.v;
|
double[] v = sc.v;
|
||||||
rotate(v, ox - e.rotOrigin[0], oy - e.rotOrigin[1], oz - e.rotOrigin[2], e.rotAxis, e.invRotCos, e.invRotSin);
|
double[] o = e.rotOrigin;
|
||||||
double lox = v[0] + e.rotOrigin[0], loy = v[1] + e.rotOrigin[1], loz = v[2] + e.rotOrigin[2];
|
unrotateElement(v, e, ox - o[0], oy - o[1], oz - o[2]);
|
||||||
rotate(v, dx, dy, dz, e.rotAxis, e.invRotCos, e.invRotSin);
|
double lox = v[0], loy = v[1], loz = v[2];
|
||||||
return slab(sc, lox, loy, loz, v[0], v[1], v[2], e.from, e.to, e);
|
unrotateElement(v, e, dx, dy, dz);
|
||||||
|
double ldx = v[0], ldy = v[1], ldz = v[2];
|
||||||
|
double[] is = e.invScale;
|
||||||
|
if (is != null) {
|
||||||
|
lox *= is[0];
|
||||||
|
loy *= is[1];
|
||||||
|
loz *= is[2];
|
||||||
|
ldx *= is[0];
|
||||||
|
ldy *= is[1];
|
||||||
|
ldz *= is[2];
|
||||||
|
}
|
||||||
|
return slab(sc, lox + o[0], loy + o[1], loz + o[2], ldx, ldy, ldz, e.from, e.to, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inverse element rotation (block -> local) of a vector into out, without the rescale. */
|
||||||
|
private static void unrotateElement(double[] out, Element e, double x, double y, double z) {
|
||||||
|
double[] m = e.invRotMatrix;
|
||||||
|
if (m != null) {
|
||||||
|
out[0] = m[0] * x + m[1] * y + m[2] * z;
|
||||||
|
out[1] = m[3] * x + m[4] * y + m[5] * z;
|
||||||
|
out[2] = m[6] * x + m[7] * y + m[8] * z;
|
||||||
|
} else {
|
||||||
|
rotate(out, x, y, z, e.rotAxis, e.invRotCos, e.invRotSin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Slab test; on a hit the entry (or, from inside, exit) face is left in the scratch's hit fields. */
|
/** Slab test; on a hit the entry (or, from inside, exit) face is left in the scratch's hit fields. */
|
||||||
@@ -176,6 +204,9 @@ public final class ElementIntersector {
|
|||||||
double[] from,
|
double[] from,
|
||||||
double[] to,
|
double[] to,
|
||||||
Element obb) {
|
Element obb) {
|
||||||
|
if (from[0] > to[0] || from[1] > to[1] || from[2] > to[2]) {
|
||||||
|
return invertedBox(sc, ox, oy, oz, dx, dy, dz, from, to, obb);
|
||||||
|
}
|
||||||
double tmin = Double.NEGATIVE_INFINITY;
|
double tmin = Double.NEGATIVE_INFINITY;
|
||||||
double tmax = Double.POSITIVE_INFINITY;
|
double tmax = Double.POSITIVE_INFINITY;
|
||||||
int axis = -1;
|
int axis = -1;
|
||||||
@@ -228,8 +259,16 @@ public final class ElementIntersector {
|
|||||||
Direction dir = faceFor(axis, negFace);
|
Direction dir = faceFor(axis, negFace);
|
||||||
if (obb != null) {
|
if (obb != null) {
|
||||||
// rotate the normal back into block space
|
// rotate the normal back into block space
|
||||||
|
// (rescale is diagonal in the local frame, so an axis normal keeps its direction: n = R·n_local)
|
||||||
double[] v = sc.v;
|
double[] v = sc.v;
|
||||||
rotate(v, dir.nx, dir.ny, dir.nz, obb.rotAxis, obb.rotCos, obb.rotSin);
|
double[] m = obb.rotMatrix;
|
||||||
|
if (m != null) {
|
||||||
|
v[0] = m[0] * dir.nx + m[1] * dir.ny + m[2] * dir.nz;
|
||||||
|
v[1] = m[3] * dir.nx + m[4] * dir.ny + m[5] * dir.nz;
|
||||||
|
v[2] = m[6] * dir.nx + m[7] * dir.ny + m[8] * dir.nz;
|
||||||
|
} else {
|
||||||
|
rotate(v, dir.nx, dir.ny, dir.nz, obb.rotAxis, obb.rotCos, obb.rotSin);
|
||||||
|
}
|
||||||
sc.hitNx = v[0];
|
sc.hitNx = v[0];
|
||||||
sc.hitNy = v[1];
|
sc.hitNy = v[1];
|
||||||
sc.hitNz = v[2];
|
sc.hitNz = v[2];
|
||||||
@@ -250,6 +289,88 @@ public final class ElementIntersector {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element with {@code from > to} on some axis (spawner/vault/trial-spawner inner faces, snow layer 2's inner
|
||||||
|
* bottom): vanilla places the WEST/DOWN/NORTH face at {@code from} and EAST/UP/SOUTH at {@code to} of its axis,
|
||||||
|
* and every inverted OTHER axis mirrors the quad, flipping its winding. So these faces point into the box and
|
||||||
|
* are seen from inside (through the cage: the far wall). Each face is tested as its own back-face-culled quad;
|
||||||
|
* the nearest visible one wins. Texture coordinates follow the mirrored corners (signed span).
|
||||||
|
*/
|
||||||
|
private static boolean invertedBox(
|
||||||
|
Scratch sc,
|
||||||
|
double ox,
|
||||||
|
double oy,
|
||||||
|
double oz,
|
||||||
|
double dx,
|
||||||
|
double dy,
|
||||||
|
double dz,
|
||||||
|
double[] from,
|
||||||
|
double[] to,
|
||||||
|
Element obb) {
|
||||||
|
double[] o = sc.o, d = sc.d;
|
||||||
|
o[0] = ox;
|
||||||
|
o[1] = oy;
|
||||||
|
o[2] = oz;
|
||||||
|
d[0] = dx;
|
||||||
|
d[1] = dy;
|
||||||
|
d[2] = dz;
|
||||||
|
double best = Double.POSITIVE_INFINITY;
|
||||||
|
Direction bestDir = null;
|
||||||
|
double bestSign = 0;
|
||||||
|
for (Direction dir : DIRECTIONS) {
|
||||||
|
int a = dir.nx != 0 ? 0 : dir.ny != 0 ? 1 : 2;
|
||||||
|
boolean neg = dir.nx + dir.ny + dir.nz < 0;
|
||||||
|
int b = (a + 1) % 3, c = (a + 2) % 3;
|
||||||
|
int flips = (from[b] > to[b] ? 1 : 0) + (from[c] > to[c] ? 1 : 0);
|
||||||
|
double sign = (neg ? -1 : 1) * (flips % 2 == 0 ? 1 : -1);
|
||||||
|
if (sign * d[a] >= 0) continue; // back face (or parallel)
|
||||||
|
double t = ((neg ? from[a] : to[a]) - o[a]) / d[a];
|
||||||
|
if (!(t >= EPS) || t >= best) continue;
|
||||||
|
double pb = o[b] + d[b] * t, pc = o[c] + d[c] * t;
|
||||||
|
if (pb < Math.min(from[b], to[b]) - EPS || pb > Math.max(from[b], to[b]) + EPS) continue;
|
||||||
|
if (pc < Math.min(from[c], to[c]) - EPS || pc > Math.max(from[c], to[c]) + EPS) continue;
|
||||||
|
best = t;
|
||||||
|
bestDir = dir;
|
||||||
|
bestSign = sign;
|
||||||
|
}
|
||||||
|
if (bestDir == null) return false;
|
||||||
|
int a = bestDir.nx != 0 ? 0 : bestDir.ny != 0 ? 1 : 2;
|
||||||
|
double nx = a == 0 ? bestSign : 0, ny = a == 1 ? bestSign : 0, nz = a == 2 ? bestSign : 0;
|
||||||
|
if (obb != null) {
|
||||||
|
double[] v = sc.v;
|
||||||
|
double[] m = obb.rotMatrix;
|
||||||
|
if (m != null) {
|
||||||
|
v[0] = m[0] * nx + m[1] * ny + m[2] * nz;
|
||||||
|
v[1] = m[3] * nx + m[4] * ny + m[5] * nz;
|
||||||
|
v[2] = m[6] * nx + m[7] * ny + m[8] * nz;
|
||||||
|
} else {
|
||||||
|
rotate(v, nx, ny, nz, obb.rotAxis, obb.rotCos, obb.rotSin);
|
||||||
|
}
|
||||||
|
nx = v[0];
|
||||||
|
ny = v[1];
|
||||||
|
nz = v[2];
|
||||||
|
}
|
||||||
|
double fx = signedFrac(o[0] + d[0] * best, from[0], to[0]);
|
||||||
|
double fy = signedFrac(o[1] + d[1] * best, from[1], to[1]);
|
||||||
|
double fz = signedFrac(o[2] + d[2] * best, from[2], to[2]);
|
||||||
|
sc.hitT = best;
|
||||||
|
sc.hitDir = bestDir;
|
||||||
|
sc.hitNx = nx;
|
||||||
|
sc.hitNy = ny;
|
||||||
|
sc.hitNz = nz;
|
||||||
|
sc.hitS = faceS(bestDir, fx, fy, fz);
|
||||||
|
sc.hitT2 = faceT(bestDir, fy, fz);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Position between {@code from} (0) and {@code to} (1), also when {@code from > to}; clamped to 0..1. */
|
||||||
|
private static double signedFrac(double v, double from, double to) {
|
||||||
|
double span = to - from;
|
||||||
|
if (Math.abs(span) < 1e-6) return 0;
|
||||||
|
double f = (v - from) / span;
|
||||||
|
return f < 0 ? 0 : Math.min(f, 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Face-local horizontal coordinate s in 0..1 for a hit at block fractions (fx, fy, fz), matching vanilla's
|
* Face-local horizontal coordinate s in 0..1 for a hit at block fractions (fx, fy, fz), matching vanilla's
|
||||||
* {@code BlockElement.uvsByFace}: a face's UV rect {@code [u1,v1,u2,v2]} is lerped with (s, t), so north/east
|
* {@code BlockElement.uvsByFace}: a face's UV rect {@code [u1,v1,u2,v2]} is lerped with (s, t), so north/east
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider;
|
|||||||
import eu.mhsl.minecraft.pixelpics.render.tint.TintResolver;
|
import eu.mhsl.minecraft.pixelpics.render.tint.TintResolver;
|
||||||
import eu.mhsl.minecraft.pixelpics.render.util.ColorUtil;
|
import eu.mhsl.minecraft.pixelpics.render.util.ColorUtil;
|
||||||
import eu.mhsl.minecraft.pixelpics.render.util.MathUtil;
|
import eu.mhsl.minecraft.pixelpics.render.util.MathUtil;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -45,6 +46,43 @@ public final class SnapshotRaytracer {
|
|||||||
// layerAlpha marker of an additive (emissive) layer: its colour is added to what lies behind.
|
// layerAlpha marker of an additive (emissive) layer: its colour is added to what lies behind.
|
||||||
private static final double ADDITIVE = -1;
|
private static final double ADDITIVE = -1;
|
||||||
|
|
||||||
|
// Brightness of a surface under water seen from above, by sky light level (index): vanilla lightmap
|
||||||
|
// (lightmap.fsh: level / (4 - 3·level), mixed halfway with notGamma = default brightness option 0.5, overworld
|
||||||
|
// ambient 0). Sky light loses one level per water block above the face (water's light block = 1), so the floor
|
||||||
|
// gets darker with VERTICAL depth; the path length through the water plays no role in vanilla when the camera
|
||||||
|
// is above the surface (no water fog there): one translucent surface layer over the dimmed scene.
|
||||||
|
private static final double[] SKY_LIGHT = new double[16];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int level = 0; level < 16; level++) {
|
||||||
|
double l = level / 15.0;
|
||||||
|
double b = l / (4 - 3 * l);
|
||||||
|
double inv = 1 - b;
|
||||||
|
SKY_LIGHT[level] = (b + (1 - inv * inv * inv * inv)) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vanilla HalfTransparentBlock / LeavesBlock that do not occlude: water renders its faces against them with the
|
||||||
|
// water overlay texture (FluidRenderer). Blue ice is a HalfTransparentBlock too but occludes, packed ice is not.
|
||||||
|
private static final EnumSet<Material> WATER_OVERLAY_NEIGHBOURS = buildWaterOverlayNeighbours();
|
||||||
|
|
||||||
|
private static EnumSet<Material> buildWaterOverlayNeighbours() {
|
||||||
|
EnumSet<Material> set = EnumSet.noneOf(Material.class);
|
||||||
|
for (Material m : Material.values()) {
|
||||||
|
String n = m.name();
|
||||||
|
if (m.isLegacy()) continue;
|
||||||
|
if (n.equals("GLASS")
|
||||||
|
|| n.equals("TINTED_GLASS")
|
||||||
|
|| n.endsWith("_STAINED_GLASS")
|
||||||
|
|| n.equals("ICE")
|
||||||
|
|| n.equals("FROSTED_ICE")
|
||||||
|
|| n.equals("SLIME_BLOCK")
|
||||||
|
|| n.equals("HONEY_BLOCK")
|
||||||
|
|| n.endsWith("_LEAVES")) set.add(m);
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
private final BlockModelRegistry registry;
|
private final BlockModelRegistry registry;
|
||||||
private final BiomeTintProvider tintProvider;
|
private final BiomeTintProvider tintProvider;
|
||||||
private final SkyRenderer skyRenderer;
|
private final SkyRenderer skyRenderer;
|
||||||
@@ -146,7 +184,10 @@ public final class SnapshotRaytracer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compose back to front: final surface (fogged), the liquid volume where it lies, translucent layers.
|
// Compose back to front: final surface (fogged), the liquid volume where it lies, translucent layers.
|
||||||
int color = Double.isInfinite(finalT) ? finalColor : fog(finalColor, finalT, skyColor);
|
// Surfaces under water (seen from above it) are dimmed by their depth like vanilla sky light.
|
||||||
|
int color = Double.isInfinite(finalT)
|
||||||
|
? finalColor
|
||||||
|
: fog(blocks.underwaterLight(finalColor, finalT), finalT, skyColor);
|
||||||
boolean liquidPending = blocks.liquidT >= 0 && blocks.liquidT < finalT;
|
boolean liquidPending = blocks.liquidT >= 0 && blocks.liquidT < finalT;
|
||||||
for (int i = layers - 1; i >= 0; i--) {
|
for (int i = layers - 1; i >= 0; i--) {
|
||||||
if (liquidPending && layerT[i] < blocks.liquidT) {
|
if (liquidPending && layerT[i] < blocks.liquidT) {
|
||||||
@@ -157,7 +198,8 @@ public final class SnapshotRaytracer {
|
|||||||
// Added light fades with distance like the surface it would otherwise be.
|
// Added light fades with distance like the surface it would otherwise be.
|
||||||
color = ColorUtil.add(color, ColorUtil.shade(layerColor[i], 1 - fogFactor(layerT[i])));
|
color = ColorUtil.add(color, ColorUtil.shade(layerColor[i], 1 - fogFactor(layerT[i])));
|
||||||
} else {
|
} else {
|
||||||
color = ColorUtil.mix(fog(layerColor[i], layerT[i], skyColor), color, layerAlpha[i], 1 - layerAlpha[i]);
|
int layer = blocks.underwaterLight(layerColor[i], layerT[i]);
|
||||||
|
color = ColorUtil.mix(fog(layer, layerT[i], skyColor), color, layerAlpha[i], 1 - layerAlpha[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (liquidPending) color = blocks.applyLiquid(color, finalT, skyColor);
|
if (liquidPending) color = blocks.applyLiquid(color, finalT, skyColor);
|
||||||
@@ -191,6 +233,9 @@ public final class SnapshotRaytracer {
|
|||||||
private boolean done;
|
private boolean done;
|
||||||
|
|
||||||
double liquidT = -1;
|
double liquidT = -1;
|
||||||
|
// The ray starts inside a liquid (underwater photo): the liquid acts as a fog along the path instead.
|
||||||
|
private final boolean startsInLiquid;
|
||||||
|
private Material left; // material of the voxel the ray passed through last
|
||||||
private int liquidColor;
|
private int liquidColor;
|
||||||
private double liquidFactor;
|
private double liquidFactor;
|
||||||
private boolean reflected;
|
private boolean reflected;
|
||||||
@@ -211,6 +256,8 @@ public final class SnapshotRaytracer {
|
|||||||
this.dy = direction.getY();
|
this.dy = direction.getY();
|
||||||
this.dz = direction.getZ();
|
this.dz = direction.getZ();
|
||||||
this.dda = new VoxelDDA(ox, oy, oz, dx, dy, dz);
|
this.dda = new VoxelDDA(ox, oy, oz, dx, dy, dz);
|
||||||
|
BlockData start = snapshot.getBlockData((int) Math.floor(ox), (int) Math.floor(oy), (int) Math.floor(oz));
|
||||||
|
this.startsInLiquid = start.getMaterial() != Material.AIR && registry.get(start).transparency > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The next block surface with {@code t > minT}, or null when the ray leaves the render distance. */
|
/** The next block surface with {@code t > minT}, or null when the ray leaves the render distance. */
|
||||||
@@ -222,20 +269,31 @@ public final class SnapshotRaytracer {
|
|||||||
}
|
}
|
||||||
int bx = dda.x, by = dda.y, bz = dda.z;
|
int bx = dda.x, by = dda.y, bz = dda.z;
|
||||||
BlockData data = snapshot.getBlockData(bx, by, bz);
|
BlockData data = snapshot.getBlockData(bx, by, bz);
|
||||||
if (data.getMaterial() == Material.AIR) {
|
Material material = data.getMaterial();
|
||||||
advance();
|
if (material == Material.AIR) {
|
||||||
|
advance(material);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ResolvedModel model = registry.get(data);
|
ResolvedModel model = registry.get(data);
|
||||||
FaceHit hit =
|
FaceHit hit =
|
||||||
ElementIntersector.intersect(model, ox - bx, oy - by, oz - bz, dx, dy, dz, bx, by, bz, minT);
|
ElementIntersector.intersect(model, ox - bx, oy - by, oz - bz, dx, dy, dz, bx, by, bz, minT);
|
||||||
if (hit == null) {
|
if (hit == null) {
|
||||||
advance();
|
advance(material);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (model.transparency > 0) {
|
if (model.transparency > 0) {
|
||||||
|
if (liquidT >= 0 && material == Material.WATER && WATER_OVERLAY_NEIGHBOURS.contains(left)) {
|
||||||
|
// Under water, leaving glass/ice/slime/honey/leaves into water again: vanilla FluidRenderer
|
||||||
|
// draws the water's faces against such non-occluding blocks (side faces with the water
|
||||||
|
// overlay texture), facing into the block, so from inside it the far wall is a water layer.
|
||||||
|
int c = shadeAndTint(hit, data, snapshot, bx, by, bz);
|
||||||
|
int a = (int) Math.round((1 - model.transparency) * 255);
|
||||||
|
advance(material);
|
||||||
|
return new BlockHit(
|
||||||
|
ColorUtil.argb(a, ColorUtil.red(c), ColorUtil.green(c), ColorUtil.blue(c)), hit.t());
|
||||||
|
}
|
||||||
recordLiquid(model, hit, data, bx, by, bz);
|
recordLiquid(model, hit, data, bx, by, bz);
|
||||||
advance();
|
advance(material);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int color = shadeAndTint(hit, data, snapshot, bx, by, bz);
|
int color = shadeAndTint(hit, data, snapshot, bx, by, bz);
|
||||||
@@ -248,7 +306,9 @@ public final class SnapshotRaytracer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void advance() {
|
/** Steps to the next voxel; {@code material} is the one of the voxel being left. */
|
||||||
|
private void advance(Material material) {
|
||||||
|
left = material;
|
||||||
dda.advance();
|
dda.advance();
|
||||||
steps++;
|
steps++;
|
||||||
}
|
}
|
||||||
@@ -269,15 +329,50 @@ public final class SnapshotRaytracer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Liquid volume over {@code behind}: denser with depth down to the final surface, plus reflection. */
|
/**
|
||||||
|
* The liquid over {@code behind} (everything beyond its surface, already dimmed by
|
||||||
|
* {@link #underwaterLight}): seen from above, one translucent surface layer like vanilla, independent of
|
||||||
|
* the path length through the liquid (before, the surface weight grew with the path, so blocks a few
|
||||||
|
* blocks deep and anything seen at a shallow angle vanished in the water colour). From inside the liquid
|
||||||
|
* the path-length model stays (a fog). Plus reflection.
|
||||||
|
*/
|
||||||
int applyLiquid(int behind, double finalT, int skyColor) {
|
int applyLiquid(int behind, double finalT, int skyColor) {
|
||||||
double depthIn = Double.isInfinite(finalT) ? 0 : finalT - liquidT;
|
|
||||||
int surface = fog(liquidColor, liquidT, skyColor);
|
int surface = fog(liquidColor, liquidT, skyColor);
|
||||||
int c = ColorUtil.mix(behind, surface, liquidFactor, (1 - liquidFactor) * (1 + depthIn / 5.0));
|
double surfaceWeight = 1 - liquidFactor;
|
||||||
|
if (startsInLiquid) {
|
||||||
|
double depthIn = Double.isInfinite(finalT) ? 0 : finalT - liquidT;
|
||||||
|
surfaceWeight *= 1 + depthIn / 5.0;
|
||||||
|
}
|
||||||
|
int c = ColorUtil.mix(behind, surface, liquidFactor, surfaceWeight);
|
||||||
if (reflected) c = ColorUtil.mix(c, reflectionColor, 1 - reflectionFactor, reflectionFactor);
|
if (reflected) c = ColorUtil.mix(c, reflectionColor, 1 - reflectionFactor, reflectionFactor);
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code color} of a surface at ray parameter {@code t} dimmed like vanilla sky light under water, when it
|
||||||
|
* lies beyond the water surface and the ray started above it: level 15 minus the blocks from the block in
|
||||||
|
* front of the surface up to the topmost water block of that column (water, and non-occluding blocks below
|
||||||
|
* it such as glass, cost one level each). Unchanged without water on the ray.
|
||||||
|
*/
|
||||||
|
int underwaterLight(int color, double t) {
|
||||||
|
if (liquidT < 0 || startsInLiquid || !(t > liquidT) || Double.isInfinite(t)) return color;
|
||||||
|
// A point just in front of the surface (back along the ray), i.e. in the block that lights it.
|
||||||
|
double back = 1e-4 / Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||||
|
int bx = (int) Math.floor(ox + dx * (t - back));
|
||||||
|
int by = (int) Math.floor(oy + dy * (t - back));
|
||||||
|
int bz = (int) Math.floor(oz + dz * (t - back));
|
||||||
|
int water = 0;
|
||||||
|
for (int i = 0; i < 15; i++) {
|
||||||
|
Material m = snapshot.getBlockData(bx, by + i, bz).getMaterial();
|
||||||
|
if (m == Material.WATER) {
|
||||||
|
water = i + 1;
|
||||||
|
} else if (m == Material.AIR || m == Material.CAVE_AIR || m == Material.VOID_AIR || m.isOccluding()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return water == 0 ? color : ColorUtil.shade(color, SKY_LIGHT[15 - water]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vanilla skips the faces between two translucent blocks of the same kind (stained glass walls, ice,
|
* Vanilla skips the faces between two translucent blocks of the same kind (stained glass walls, ice,
|
||||||
* connected panes): true when the hit lies on the block boundary and the neighbour across it is the
|
* connected panes): true when the hit lies on the block boundary and the neighbour across it is the
|
||||||
|
|||||||
@@ -176,6 +176,19 @@ public class PPTest extends JavaPlugin {
|
|||||||
scenes.put("rf_vlayers_close", new Scene("rf_vlayers_close", this::rfVisualLayers, 3.2, 1.5, -2.4, 3.2, 0.5, 1));
|
scenes.put("rf_vlayers_close", new Scene("rf_vlayers_close", this::rfVisualLayers, 3.2, 1.5, -2.4, 3.2, 0.5, 1));
|
||||||
scenes.put("rf_vstates", new Scene("rf_vstates", this::rfVisualStates, 5.5, 3.4, -6, 5.5, 0.9, 2.5));
|
scenes.put("rf_vstates", new Scene("rf_vstates", this::rfVisualStates, 5.5, 3.4, -6, 5.5, 0.9, 2.5));
|
||||||
scenes.put("rf_vstates_close", new Scene("rf_vstates_close", this::rfVisualStates, 3.2, 1.7, -2.6, 3.2, 0.9, 0.5));
|
scenes.put("rf_vstates_close", new Scene("rf_vstates_close", this::rfVisualStates, 3.2, 1.7, -2.6, 3.2, 0.9, 0.5));
|
||||||
|
// Block geometry (vanilla FaceBakery): rescaled element rotations (cross plants, amethyst, campfire, fire,
|
||||||
|
// raised rails, tripwire hooks, coral wall fans, dripleaf, stems, sea pickles), Euler x/y/z element
|
||||||
|
// rotations (hanging sign rot 3/7/11/15 chains), inverted elements (spawner/vault inner faces), 22.5° steps.
|
||||||
|
scenes.put("geo_plants", new Scene("geo_plants", this::geoPlants, 5.5, 2.6, -3.2, 5.5, 0.4, 2.2));
|
||||||
|
scenes.put("geo_plants_close", new Scene("geo_plants_close", this::geoPlants, 2.5, 1.1, -0.9, 2.5, 0.5, 1));
|
||||||
|
scenes.put("geo_misc", new Scene("geo_misc", this::geoMisc, 6, 3.4, -4.5, 6, 0.5, 3));
|
||||||
|
scenes.put("geo_misc_close", new Scene("geo_misc_close", this::geoMisc, 3, 1.6, -1.6, 3, 0.4, 1));
|
||||||
|
scenes.put("geo_misc_signs", new Scene("geo_misc_signs", this::geoMisc, 4, 2.4, 3.5, 4, 2.2, 7));
|
||||||
|
// Water depth vs. translucency: a pool with columns 1-6 blocks deep (x pairs), each with a glass block,
|
||||||
|
// a gold block and white stained glass on the sand bottom; seen from the shore, from above and from inside.
|
||||||
|
scenes.put("geo_water", new Scene("geo_water", this::geoWater, 6, 3.5, -5, 6, -3, 2.5));
|
||||||
|
scenes.put("geo_water_top", new Scene("geo_water_top", this::geoWater, 6, 9, 2.4, 6, -4, 2.5));
|
||||||
|
scenes.put("geo_water_under", new Scene("geo_water_under", this::geoWater, 11.5, -3.3, 0.3, 4, -5, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rfVisualLayers(Location o) {
|
private void rfVisualLayers(Location o) {
|
||||||
@@ -587,7 +600,7 @@ public class PPTest extends JavaPlugin {
|
|||||||
Location eye = o.clone().add(sc.camX, sc.camY, sc.camZ);
|
Location eye = o.clone().add(sc.camX, sc.camY, sc.camZ);
|
||||||
Vector look = o.clone().add(sc.lookX, sc.lookY, sc.lookZ).toVector().subtract(eye.toVector());
|
Vector look = o.clone().add(sc.lookX, sc.lookY, sc.lookZ).toVector().subtract(eye.toVector());
|
||||||
eye.setDirection(look);
|
eye.setDirection(look);
|
||||||
RenderJob job = r.prepare(eye, sc.name.endsWith("_sq") ? new Resolution(384, 384) : sc.name.startsWith("proj_") || sc.name.startsWith("rf_") || sc.name.startsWith("pose_") || sc.name.startsWith("frames") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
RenderJob job = r.prepare(eye, sc.name.endsWith("_sq") ? new Resolution(384, 384) : sc.name.startsWith("proj_") || sc.name.startsWith("rf_") || sc.name.startsWith("pose_") || sc.name.startsWith("frames") || sc.name.startsWith("geo_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
||||||
if (sc.name.equals("dbg")) {
|
if (sc.name.equals("dbg")) {
|
||||||
try {
|
try {
|
||||||
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
|
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
|
||||||
@@ -1588,6 +1601,102 @@ public class PPTest extends JavaPlugin {
|
|||||||
frame(o, 5, 5, 5, BlockFace.NORTH, false, synthMap(), 0, true);
|
frame(o, 5, 5, 5, BlockFace.NORTH, false, synthMap(), 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Rows of cross/rescaled plants on grass (z=0..3), potted plants and stems; see the geo_* scenes. */
|
||||||
|
private void geoPlants(Location o) {
|
||||||
|
for (int x = -1; x <= 12; x++) for (int z = -1; z <= 5; z++) put(o, x, -1, z, "grass_block");
|
||||||
|
String[] row0 = {"short_grass", "fern", "dandelion", "poppy", "azure_bluet", "oxeye_daisy", "cornflower",
|
||||||
|
"oak_sapling", "birch_sapling", "cherry_sapling", "dead_bush", "brown_mushroom"};
|
||||||
|
String[] row1 = {"sweet_berry_bush[age=3]", "sugar_cane", "cobweb", "red_tulip", "wither_rose", "bush",
|
||||||
|
"short_dry_grass", "firefly_bush", "mangrove_propagule[hanging=false,stage=1]", "lily_of_the_valley",
|
||||||
|
"pumpkin_stem[age=7]", "crimson_roots"};
|
||||||
|
String[] row3 = {"potted_fern", "potted_oak_sapling", "potted_red_tulip", "potted_dead_bush",
|
||||||
|
"potted_azalea_bush", "potted_mangrove_propagule", "big_dripleaf_stem[facing=north]",
|
||||||
|
"sea_pickle[pickles=4,waterlogged=false]", "sea_pickle[pickles=1,waterlogged=false]", "azalea",
|
||||||
|
"flowering_azalea", "pointed_dripstone[thickness=tip,vertical_direction=up]"};
|
||||||
|
for (int x = 0; x < 12; x++) {
|
||||||
|
put(o, x, 0, 0, row0[x]);
|
||||||
|
put(o, x, 0, 1, row1[x]);
|
||||||
|
put(o, x, 0, 3, row3[x]);
|
||||||
|
}
|
||||||
|
for (int x = 0; x < 12; x += 3) {
|
||||||
|
put(o, x, 0, 2, "tall_grass[half=lower]");
|
||||||
|
put(o, x, 1, 2, "tall_grass[half=upper]");
|
||||||
|
put(o, x + 1, 0, 2, "sunflower[half=lower]");
|
||||||
|
put(o, x + 1, 1, 2, "sunflower[half=upper]");
|
||||||
|
}
|
||||||
|
put(o, 2, 0, 2, "big_dripleaf[facing=north,tilt=none]");
|
||||||
|
put(o, 5, 0, 2, "melon_stem[age=4]");
|
||||||
|
put(o, 8, 0, 2, "attached_pumpkin_stem[facing=east]");
|
||||||
|
put(o, 9, 0, 2, "pumpkin");
|
||||||
|
put(o, 11, 0, 2, "large_fern[half=lower]");
|
||||||
|
put(o, 11, 1, 2, "large_fern[half=upper]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row z=0: amethyst cluster/buds (up/north/east), campfires, fire; z=2: raised rails, tripwire hooks, coral wall
|
||||||
|
* fans, spawner/vault/trial spawner (inverted inner faces), snow layer 2; z=6/7: hanging signs rot 0-15 under a
|
||||||
|
* stone ceiling (rot 3/7/11/15 = Euler chain rotation).
|
||||||
|
*/
|
||||||
|
private void geoMisc(Location o) {
|
||||||
|
put(o, 0, 0, 0, "amethyst_cluster[facing=up]");
|
||||||
|
put(o, 1, 0, 0, "large_amethyst_bud[facing=up]");
|
||||||
|
put(o, 2, 0, 0, "medium_amethyst_bud[facing=up]");
|
||||||
|
put(o, 3, 0, 0, "small_amethyst_bud[facing=up]");
|
||||||
|
put(o, 4, 0, 1, "amethyst_block");
|
||||||
|
put(o, 4, 0, 0, "amethyst_cluster[facing=north]");
|
||||||
|
put(o, 5, 0, 1, "amethyst_block");
|
||||||
|
put(o, 5, 1, 1, "large_amethyst_bud[facing=up]");
|
||||||
|
put(o, 6, 0, 0, "campfire[lit=true,facing=south]");
|
||||||
|
put(o, 7, 0, 0, "campfire[lit=false,facing=east]");
|
||||||
|
put(o, 8, 0, 0, "soul_campfire[lit=true,facing=south]");
|
||||||
|
put(o, 9, 0, 0, "netherrack");
|
||||||
|
put(o, 9, 1, 0, "fire");
|
||||||
|
put(o, 10, 0, 1, "stone");
|
||||||
|
put(o, 10, 0, 0, "fire[north=true]");
|
||||||
|
put(o, 11, 0, 0, "soul_fire");
|
||||||
|
put(o, 11, -1, 0, "soul_sand");
|
||||||
|
put(o, 0, 0, 2, "rail[shape=ascending_east]");
|
||||||
|
put(o, 1, 0, 2, "stone");
|
||||||
|
put(o, 2, 0, 2, "powered_rail[shape=ascending_north,powered=true]");
|
||||||
|
put(o, 2, 0, 1, "stone");
|
||||||
|
put(o, 3, 0, 2, "detector_rail[shape=ascending_west]");
|
||||||
|
put(o, 2, 0, 3, "stone");
|
||||||
|
put(o, 4, 0, 3, "stone");
|
||||||
|
put(o, 4, 0, 2, "tripwire_hook[attached=true,facing=north,powered=false]");
|
||||||
|
put(o, 5, 0, 3, "stone");
|
||||||
|
put(o, 5, 0, 2, "tripwire_hook[attached=true,facing=north,powered=true]");
|
||||||
|
put(o, 6, 0, 3, "stone");
|
||||||
|
put(o, 6, 0, 2, "tube_coral_wall_fan[facing=north,waterlogged=false]");
|
||||||
|
put(o, 7, 0, 3, "stone");
|
||||||
|
put(o, 7, 0, 2, "fire_coral_wall_fan[facing=north,waterlogged=false]");
|
||||||
|
put(o, 8, 0, 2, "spawner");
|
||||||
|
put(o, 9, 0, 2, "vault[facing=north]");
|
||||||
|
put(o, 10, 0, 2, "trial_spawner");
|
||||||
|
put(o, 11, 0, 2, "snow[layers=2]");
|
||||||
|
for (int x = -1; x <= 9; x++) for (int z = 5; z <= 8; z++) put(o, x, 4, z, "stone");
|
||||||
|
for (int r = 0; r < 16; r++) put(o, r % 8, 3, 6 + r / 8, "oak_hanging_sign[rotation=" + r + ",attached=false]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pool: water columns 1..6 deep (two blocks wide each) on sand, with glass/gold/white stained glass at the bottom. */
|
||||||
|
private void geoWater(Location o) {
|
||||||
|
for (int x = -1; x <= 12; x++)
|
||||||
|
for (int z = -1; z <= 5; z++)
|
||||||
|
for (int y = -8; y <= -1; y++) {
|
||||||
|
boolean wall = x == -1 || x == 12 || z == -1 || z == 5;
|
||||||
|
int depth = x < 0 || x > 11 ? 0 : x / 2 + 1;
|
||||||
|
String b = wall ? "smooth_stone" : y >= -depth ? "water" : y == -depth - 1 ? "sand" : "stone";
|
||||||
|
put(o, x, y, z, b);
|
||||||
|
}
|
||||||
|
for (int x = 0; x < 12; x += 2) {
|
||||||
|
int y = -(x / 2 + 1);
|
||||||
|
put(o, x, y, 1, "glass");
|
||||||
|
put(o, x + 1, y, 1, "glass");
|
||||||
|
put(o, x, y, 2, "gold_block");
|
||||||
|
put(o, x + 1, y, 3, "white_stained_glass");
|
||||||
|
}
|
||||||
|
for (int x = -1; x <= 12; x++) put(o, x, 0, 6, "stone");
|
||||||
|
}
|
||||||
|
|
||||||
/** Black wall z=6 with a white vertical stripe (x=5) and a white horizontal stripe (y=5). */
|
/** Black wall z=6 with a white vertical stripe (x=5) and a white horizontal stripe (y=5). */
|
||||||
private void camStripes(Location o) {
|
private void camStripes(Location o) {
|
||||||
for (int a = -6; a < 18; a++)
|
for (int a = -6; a < 18; a++)
|
||||||
|
|||||||
Reference in new issue
Block a user