"public/git@transfer.hft-stuttgart.de:max.faber/fgdi.git" did not exist on "30c396aea95fcfe8d97a3d57914fdb3183376114"
Commit 24c5c355 authored by Matthias Betz's avatar Matthias Betz
Browse files

Add OrbitCamera reproducing legacy view interaction

parent 10508066
package de.hft.stuttgart.citydoctor2.gui.gl;
/**
* Orbit camera reproducing the legacy JavaFX interaction model: world is rotated about Z then X, the
* camera sits at {@code distance} along -Z (negative distance = farther away) and pans in X/Y.
*/
public class OrbitCamera {
private static final double INITIAL_AZIMUTH = 120.0;
private static final double INITIAL_ELEVATION = 20.0;
private static final double INITIAL_DISTANCE = 100.0;
private static final double FOVY_RAD = Math.toRadians(30);
private static final float NEAR = 0.1f;
private static final float FAR = 10000f;
private double azimuthDeg = INITIAL_AZIMUTH;
private double elevationDeg = INITIAL_ELEVATION;
private double distance = INITIAL_DISTANCE;
private double panX = 0;
private double panY = 0;
private float aspect = 1.0f;
public void setAspect(float aspect) {
if (aspect > 0) {
this.aspect = aspect;
}
}
public void orbit(double deltaAzimuthDeg, double deltaElevationDeg) {
azimuthDeg += deltaAzimuthDeg;
elevationDeg += deltaElevationDeg;
}
public void pan(double dx, double dy) {
panX += dx;
panY += dy;
}
/** factor > 1 moves farther, < 1 moves closer. */
public void zoom(double factor) {
distance *= factor;
}
/** Matches legacy MainWindow.zoomOutForBoundingBox distance computation. */
public void zoomOutForBoundingBox(double diagonalLength) {
double longestSide = diagonalLength * 0.4;
distance = longestSide / Math.tan(FOVY_RAD / 2.0);
panX = 0;
panY = 0;
}
public void reset() {
azimuthDeg = INITIAL_AZIMUTH;
elevationDeg = INITIAL_ELEVATION;
distance = INITIAL_DISTANCE;
panX = 0;
panY = 0;
}
public double distance() { return distance; }
/** Combined projection * view * model-rotation, column-major, ready for a GL uniform. */
public float[] viewProjection() {
float[] proj = Mat4.perspective((float) FOVY_RAD, aspect, NEAR, FAR);
float[] view = Mat4.translation((float) -panX, (float) -panY, (float) -distance);
float[] rot = Mat4.multiply(
Mat4.rotationX((float) Math.toRadians(elevationDeg)),
Mat4.rotationZ((float) Math.toRadians(azimuthDeg)));
return Mat4.multiply(proj, Mat4.multiply(view, rot));
}
}
package de.hft.stuttgart.citydoctor2.gui.gl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class OrbitCameraTest {
private static float[] ndc(OrbitCamera cam, float x, float y, float z) {
float[] clip = Mat4.transform(cam.viewProjection(), x, y, z, 1);
return new float[]{clip[0] / clip[3], clip[1] / clip[3], clip[2] / clip[3]};
}
@Test
public void focusPointProjectsToScreenCentre() {
OrbitCamera cam = new OrbitCamera();
cam.setAspect(1.0f);
cam.zoomOutForBoundingBox(50.0);
float[] p = ndc(cam, 0, 0, 0);
assertEquals(0f, p[0], 1e-4f);
assertEquals(0f, p[1], 1e-4f);
assertTrue("origin in front of camera (within clip)", p[2] > -1f && p[2] < 1f);
}
@Test
public void zoomingKeepsFocusCentredButChangesDepth() {
OrbitCamera cam = new OrbitCamera();
cam.setAspect(1.0f);
cam.zoomOutForBoundingBox(50.0);
float depthBefore = ndc(cam, 0, 0, 0)[2];
cam.zoom(1.5);
float[] p = ndc(cam, 0, 0, 0);
assertEquals(0f, p[0], 1e-4f);
assertEquals(0f, p[1], 1e-4f);
assertTrue("depth changes when zooming", Math.abs(p[2] - depthBefore) > 1e-4f);
}
@Test
public void resetRestoresInitialDistanceAndAngles() {
OrbitCamera cam = new OrbitCamera();
cam.setAspect(1.0f);
cam.orbit(40, 25);
cam.pan(10, -5);
cam.zoom(2.0);
cam.reset();
float[] before = cam.viewProjection();
OrbitCamera fresh = new OrbitCamera();
fresh.setAspect(1.0f);
org.junit.Assert.assertArrayEquals(fresh.viewProjection(), before, 1e-5f);
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment