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:
1 parent
88bae41a03
commit
6660ebe557
5 files changed
+77
-112
No files matched your search
+30
-39
@@ -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)));
|
||||
|
||||
@@ -6,7 +6,6 @@ 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 eu.mhsl.minecraft.pixelpics.render.util.MathUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -25,8 +24,7 @@ import java.util.logging.Logger;
|
||||
public class BlockEntityTestRender {
|
||||
|
||||
static final String ROOT = "/home/elias/Dokumente/mcTestServer/plugins/PixelPics";
|
||||
static final double H_FOV_HALF = Math.toRadians(35);
|
||||
static final Vector BASE = new Vector(1, 0, 0);
|
||||
static final double V_FOV_HALF = Math.toRadians(eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer.V_FOV_DEG / 2);
|
||||
static final int SSAA = 3;
|
||||
static final int TW = 220, TH = 240;
|
||||
|
||||
@@ -271,7 +269,7 @@ public class BlockEntityTestRender {
|
||||
double cx = (min[0] + max[0]) / 2, cy = (min[1] + max[1]) / 2, cz = (min[2] + max[2]) / 2;
|
||||
double ext = 0.5;
|
||||
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
|
||||
double dist = ext / (2 * Math.tan(H_FOV_HALF)) * 1.4 + 0.6;
|
||||
double dist = ext / (2 * Math.tan(V_FOV_HALF)) * 1.4 + 0.6;
|
||||
|
||||
Vector center = new Vector(cx, cy, cz);
|
||||
Vector cam = new Vector(cx - dist, cy + dist * 0.45, cz - dist * 0.35);
|
||||
@@ -300,7 +298,7 @@ public class BlockEntityTestRender {
|
||||
double cx = (min[0] + max[0]) / 2, cy = (min[1] + max[1]) / 2, cz = (min[2] + max[2]) / 2;
|
||||
double ext = 0.5;
|
||||
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
|
||||
double dist = ext / (2 * Math.tan(H_FOV_HALF)) * 1.4 + 0.6;
|
||||
double dist = ext / (2 * Math.tan(V_FOV_HALF)) * 1.4 + 0.6;
|
||||
// View from the front side (along +facing) so the picture is visible, not the back.
|
||||
DecorationState.Facing f = states.get(0).facing();
|
||||
double fx = f.axis() == 0 ? f.sign() : 0, fy = f.axis() == 1 ? f.sign() : 0, fz = f.axis() == 2 ? f.sign() : 0;
|
||||
@@ -328,24 +326,6 @@ public class BlockEntityTestRender {
|
||||
}
|
||||
|
||||
static List<Vector> buildRayMap(Location eye, int width, int height) {
|
||||
Vector dir = eye.getDirection();
|
||||
double angleYaw = Math.atan2(dir.getZ(), dir.getX());
|
||||
double anglePitch = Math.atan2(dir.getY(), Math.sqrt(dir.getX() * dir.getX() + dir.getZ() * dir.getZ()));
|
||||
double yawHalf = H_FOV_HALF;
|
||||
double pitchHalf = Math.atan(Math.tan(yawHalf) * ((double) height / width));
|
||||
Vector ll = MathUtil.doubleYawPitchRotation(BASE, -yawHalf, -pitchHalf, angleYaw, anglePitch);
|
||||
Vector ul = MathUtil.doubleYawPitchRotation(BASE, -yawHalf, pitchHalf, angleYaw, anglePitch);
|
||||
Vector lr = MathUtil.doubleYawPitchRotation(BASE, yawHalf, -pitchHalf, angleYaw, anglePitch);
|
||||
Vector ur = MathUtil.doubleYawPitchRotation(BASE, yawHalf, pitchHalf, angleYaw, anglePitch);
|
||||
List<Vector> rayMap = new ArrayList<>(width * height);
|
||||
Vector leftFrac = ul.clone().subtract(ll).multiply(1.0 / (height - 1));
|
||||
Vector rightFrac = ur.clone().subtract(lr).multiply(1.0 / (height - 1));
|
||||
for (int pitch = 0; pitch < height; pitch++) {
|
||||
Vector leftPitch = ul.clone().subtract(leftFrac.clone().multiply(pitch));
|
||||
Vector rightPitch = ur.clone().subtract(rightFrac.clone().multiply(pitch));
|
||||
Vector yawFrac = rightPitch.clone().subtract(leftPitch).multiply(1.0 / (width - 1));
|
||||
for (int yaw = 0; yaw < width; yaw++) rayMap.add(leftPitch.clone().add(yawFrac.clone().multiply(yaw)).normalize());
|
||||
}
|
||||
return rayMap;
|
||||
return eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer.buildRayMap(eye, width, height);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ 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 eu.mhsl.minecraft.pixelpics.render.util.MathUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -23,8 +22,7 @@ import java.util.logging.Logger;
|
||||
public class EntityTestRender {
|
||||
|
||||
static final String ROOT = "/home/elias/Dokumente/mcTestServer/plugins/PixelPics";
|
||||
static final double H_FOV_HALF = Math.toRadians(35);
|
||||
static final Vector BASE = new Vector(1, 0, 0);
|
||||
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
|
||||
|
||||
@@ -232,7 +230,7 @@ public class EntityTestRender {
|
||||
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(H_FOV_HALF)) * 1.25 + 0.4;
|
||||
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);
|
||||
@@ -293,28 +291,7 @@ public class EntityTestRender {
|
||||
return cell;
|
||||
}
|
||||
|
||||
// Replicated from DefaultScreenRenderer.buildRayMap.
|
||||
static List<Vector> buildRayMap(Location eye, int width, int height) {
|
||||
Vector dir = eye.getDirection();
|
||||
double angleYaw = Math.atan2(dir.getZ(), dir.getX());
|
||||
double anglePitch = Math.atan2(dir.getY(), Math.sqrt(dir.getX() * dir.getX() + dir.getZ() * dir.getZ()));
|
||||
double yawHalf = H_FOV_HALF;
|
||||
double pitchHalf = Math.atan(Math.tan(yawHalf) * ((double) height / width));
|
||||
Vector ll = MathUtil.doubleYawPitchRotation(BASE, -yawHalf, -pitchHalf, angleYaw, anglePitch);
|
||||
Vector ul = MathUtil.doubleYawPitchRotation(BASE, -yawHalf, pitchHalf, angleYaw, anglePitch);
|
||||
Vector lr = MathUtil.doubleYawPitchRotation(BASE, yawHalf, -pitchHalf, angleYaw, anglePitch);
|
||||
Vector ur = MathUtil.doubleYawPitchRotation(BASE, yawHalf, pitchHalf, angleYaw, anglePitch);
|
||||
List<Vector> rayMap = new ArrayList<>(width * height);
|
||||
Vector leftFrac = ul.clone().subtract(ll).multiply(1.0 / (height - 1));
|
||||
Vector rightFrac = ur.clone().subtract(lr).multiply(1.0 / (height - 1));
|
||||
for (int pitch = 0; pitch < height; pitch++) {
|
||||
Vector leftPitch = ul.clone().subtract(leftFrac.clone().multiply(pitch));
|
||||
Vector rightPitch = ur.clone().subtract(rightFrac.clone().multiply(pitch));
|
||||
Vector yawFrac = rightPitch.clone().subtract(leftPitch).multiply(1.0 / (width - 1));
|
||||
for (int yaw = 0; yaw < width; yaw++) {
|
||||
rayMap.add(leftPitch.clone().add(yawFrac.clone().multiply(yaw)).normalize());
|
||||
}
|
||||
}
|
||||
return rayMap;
|
||||
return eu.mhsl.minecraft.pixelpics.render.render.DefaultScreenRenderer.buildRayMap(eye, width, height);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +136,15 @@ public class PPTest extends JavaPlugin {
|
||||
scenes.put("frames_side", new Scene("frames_side", this::frames, -1.5, 3.2, -1.0, 4.5, 2.6, 3.6));
|
||||
scenes.put("frames_dirs_ne", new Scene("frames_dirs_ne", this::framesDirs, 16, 7.5, -4, 8, 4, 3.5));
|
||||
scenes.put("frames_dirs_sw", new Scene("frames_dirs_sw", this::framesDirs, 0, 1.2, 10, 8, 4, 3.5));
|
||||
// Camera projection (pinhole, vertical FOV 70): frontal checkerboard wall / item-frame map (squares must be
|
||||
// square, same size everywhere), ceiling straight up, floor straight down at yaw 30, oblique wall with
|
||||
// stripes (edges must stay straight, the vertical stripe through the view centre vertical). *_sq = 384x384.
|
||||
scenes.put("cam_checker_sq", new Scene("cam_checker_sq", this::camChecker, 5.5, 5.5, 1, 5.5, 5.5, 6));
|
||||
scenes.put("cam_checker", new Scene("cam_checker", this::camChecker, 5.5, 5.5, 1, 5.5, 5.5, 6));
|
||||
scenes.put("cam_frame_sq", new Scene("cam_frame_sq", this::camFrame, 5.5, 5.5, 3, 5.5, 5.5, 6));
|
||||
scenes.put("cam_up_sq", new Scene("cam_up_sq", this::camChecker, 5.5, 6, 0.5, 5.5, 11, 0.5));
|
||||
scenes.put("cam_down_sq", new Scene("cam_down_sq", this::camChecker, 5.5, 4, 0.5, 5.4995, 0, 0.500866));
|
||||
scenes.put("cam_oblique", new Scene("cam_oblique", this::camStripes, 2, 11, 1.5, 5.5, 5.5, 6));
|
||||
// Performance (entity BVH): a 15x18 animal farm (270 mobs, randomizeData=false, fixed body yaw) and 300
|
||||
// item displays from a seeded RNG -> deterministic, pixel-diffable; the render time is logged.
|
||||
scenes.put("perf_farm", new Scene("perf_farm", this::perfFarm, -6, 9, -13, 4, 0, 0));
|
||||
@@ -274,7 +283,7 @@ public class PPTest extends JavaPlugin {
|
||||
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());
|
||||
eye.setDirection(look);
|
||||
RenderJob job = r.prepare(eye, sc.name.startsWith("proj_") || sc.name.startsWith("rf_") || 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("frames") ? new Resolution(960, 640) : new Resolution(480, 320), null);
|
||||
if (sc.name.equals("dbg")) {
|
||||
try {
|
||||
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
|
||||
@@ -1253,6 +1262,36 @@ public class PPTest extends JavaPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/** Black/white concrete checkerboard: wall z=6 (x -6..17, y 0..11), ceiling y=11 and floor y=-1. */
|
||||
private void camChecker(Location o) {
|
||||
for (int a = -6; a < 18; a++)
|
||||
for (int b = 0; b < 12; b++) {
|
||||
Material m = ((a + b) & 1) == 0 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE;
|
||||
w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6).setType(m, false);
|
||||
}
|
||||
for (int a = -6; a < 18; a++)
|
||||
for (int c = -12; c < 6; c++) {
|
||||
Material m = ((a + c) & 1) == 0 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE;
|
||||
w.getBlockAt(o.getBlockX() + a, o.getBlockY() + 11, o.getBlockZ() + c).setType(m, false);
|
||||
w.getBlockAt(o.getBlockX() + a, o.getBlockY() - 1, o.getBlockZ() + c).setType(m, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Black wall z=6 with one item frame (synthetic map, 1x1 block) facing north at cell (5,5,5). */
|
||||
private void camFrame(Location o) {
|
||||
for (int a = -6; a < 18; a++)
|
||||
for (int b = 0; b < 12; b++) w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6).setType(Material.BLACK_CONCRETE, false);
|
||||
frame(o, 5, 5, 5, BlockFace.NORTH, false, synthMap(), 0, true);
|
||||
}
|
||||
|
||||
/** Black wall z=6 with a white vertical stripe (x=5) and a white horizontal stripe (y=5). */
|
||||
private void camStripes(Location o) {
|
||||
for (int a = -6; a < 18; a++)
|
||||
for (int b = 0; b < 12; b++)
|
||||
w.getBlockAt(o.getBlockX() + a, o.getBlockY() + b, o.getBlockZ() + 6)
|
||||
.setType(a == 5 || b == 5 ? Material.WHITE_CONCRETE : Material.BLACK_CONCRETE, false);
|
||||
}
|
||||
|
||||
private ItemFrame frame(Location o, int x, int y, int z, BlockFace face, boolean glow, ItemStack item, int rot, boolean visible) {
|
||||
Location l = o.clone().add(x + 0.5, y + 0.5, z + 0.5);
|
||||
Class<? extends ItemFrame> c = glow ? GlowItemFrame.class : ItemFrame.class;
|
||||
|
||||
Reference in new issue
Block a user