pinhole camera ray map like vanilla (vertical fov 70, horizontal from aspect, pixel centres, no roll) instead of yaw/pitch corner rotation that squashed photos vertically by cos 35, shared buildRayMap in test tools, camera projection test scenes

This commit is contained in:
MineTec committed 2026-11-04 17:07:19 +01:00
1 parent 88bae41a03
commit 6660ebe557
5 files changed
+77 -112

No files matched your search

@@ -24,7 +24,6 @@ import eu.mhsl.minecraft.pixelpics.render.snapshot.SnapshotBuilder;
import eu.mhsl.minecraft.pixelpics.render.snapshot.WorldSnapshot;
import eu.mhsl.minecraft.pixelpics.render.tint.BiomeTintProvider;
import eu.mhsl.minecraft.pixelpics.render.util.ColorUtil;
import eu.mhsl.minecraft.pixelpics.render.util.MathUtil;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;
@@ -46,10 +45,11 @@ import org.bukkit.util.Vector;
*/
public class DefaultScreenRenderer implements Renderer {
/** Horizontal half field-of-view; the vertical half is derived from the output aspect ratio. */
private static final double H_FOV_HALF_RAD = Math.toRadians(35);
private static final Vector BASE_VEC = new Vector(1, 0, 0);
/**
* Vertical field of view in degrees (vanilla's default FOV option, which is vertical too); the
* horizontal FOV follows from the image aspect ratio. Square photos: 70° both ways.
*/
public static final double V_FOV_DEG = 70;
private static final double MAX_DISTANCE = 256;
private static final int REFLECTION_DEPTH = 4;
@@ -203,45 +203,36 @@ public class DefaultScreenRenderer implements Renderer {
}
}
private List<Vector> buildRayMap(Location eyeLocation, int width, int height) {
Vector lineDirection = eyeLocation.getDirection();
/**
* Pinhole camera like vanilla's projection: the rays go through a flat image plane one unit in front
* of the eye, {@code ray = forward + u·tan(fovX/2)·right + v·tan(fovY/2)·up} with u, v ∈ [-1, 1] at
* the pixel centres, row 0 = top. The vertical FOV is fixed ({@link #V_FOV_DEG}, like vanilla's
* default 70°), the horizontal one follows from the aspect ratio ({@code tan(fovX/2) = tan(fovY/2)·w/h}),
* so one pixel covers the same angle (at the centre) both ways and squares stay square. Right/up come
* from the yaw/pitch of {@code eye} (no roll), so looking straight up or down keeps the yaw orientation.
*/
public static List<Vector> buildRayMap(Location eye, int width, int height) {
double yaw = Math.toRadians(eye.getYaw());
Vector forward = eye.getDirection();
// Player's right in Minecraft's coordinates (yaw 0 = south/+Z → right = west/−X).
Vector right = new Vector(-Math.cos(yaw), 0, -Math.sin(yaw));
Vector up = right.getCrossProduct(forward).normalize();
double x = lineDirection.getX();
double y = lineDirection.getY();
double z = lineDirection.getZ();
double angleYaw = Math.atan2(z, x);
double anglePitch = Math.atan2(y, Math.sqrt(x * x + z * z));
// Derive the vertical half-FOV from the horizontal one so square output is not distorted.
double yawHalf = H_FOV_HALF_RAD;
double pitchHalf = Math.atan(Math.tan(yawHalf) * ((double) height / width));
Vector lowerLeftCorner = MathUtil.doubleYawPitchRotation(BASE_VEC, -yawHalf, -pitchHalf, angleYaw, anglePitch);
Vector upperLeftCorner = MathUtil.doubleYawPitchRotation(BASE_VEC, -yawHalf, pitchHalf, angleYaw, anglePitch);
Vector lowerRightCorner = MathUtil.doubleYawPitchRotation(BASE_VEC, yawHalf, -pitchHalf, angleYaw, anglePitch);
Vector upperRightCorner = MathUtil.doubleYawPitchRotation(BASE_VEC, yawHalf, pitchHalf, angleYaw, anglePitch);
double tanY = Math.tan(Math.toRadians(V_FOV_DEG) / 2);
double tanX = tanY * width / height;
List<Vector> rayMap = new ArrayList<>(width * height);
Vector leftFraction = upperLeftCorner.clone().subtract(lowerLeftCorner).multiply(1.0 / (height - 1));
Vector rightFraction =
upperRightCorner.clone().subtract(lowerRightCorner).multiply(1.0 / (height - 1));
for (int pitch = 0; pitch < height; pitch++) {
Vector leftPitch =
upperLeftCorner.clone().subtract(leftFraction.clone().multiply(pitch));
Vector rightPitch =
upperRightCorner.clone().subtract(rightFraction.clone().multiply(pitch));
Vector yawFraction = rightPitch.clone().subtract(leftPitch).multiply(1.0 / (width - 1));
for (int yaw = 0; yaw < width; yaw++) {
Vector ray =
leftPitch.clone().add(yawFraction.clone().multiply(yaw)).normalize();
rayMap.add(ray);
for (int row = 0; row < height; row++) {
double v = (1 - 2 * (row + 0.5) / height) * tanY;
for (int col = 0; col < width; col++) {
double u = (2 * (col + 0.5) / width - 1) * tanX;
rayMap.add(new Vector(
forward.getX() + u * right.getX() + v * up.getX(),
forward.getY() + u * right.getY() + v * up.getY(),
forward.getZ() + u * right.getZ() + v * up.getZ())
.normalize());
}
}
return rayMap;
}
}
@@ -7,28 +7,6 @@ public class MathUtil {
private MathUtil() {}
private static Vector yawPitchRotation(Vector base, double angleYaw, double anglePitch) {
double oldX = base.getX();
double oldY = base.getY();
double oldZ = base.getZ();
double sinOne = Math.sin(angleYaw);
double sinTwo = Math.sin(anglePitch);
double cosOne = Math.cos(angleYaw);
double cosTwo = Math.cos(anglePitch);
double newX = oldX * cosOne * cosTwo - oldY * cosOne * sinTwo - oldZ * sinOne;
double newY = oldX * sinTwo + oldY * cosTwo;
double newZ = oldX * sinOne * cosTwo - oldY * sinOne * sinTwo + oldZ * cosOne;
return new Vector(newX, newY, newZ);
}
public static Vector doubleYawPitchRotation(
Vector base, double firstYaw, double firstPitch, double secondYaw, double secondPitch) {
return yawPitchRotation(yawPitchRotation(base, firstYaw, firstPitch), secondYaw, secondPitch);
}
/** Reflects {@code direction} across the plane with the given (unit) {@code normal}. */
public static Vector reflectVector(Vector direction, Vector normal) {
return direction.clone().subtract(normal.clone().multiply(2 * direction.dot(normal)));