package org.sunflow.core;
import org.sunflow.SunflowAPI;
import org.sunflow.math.Matrix4;
import org.sunflow.math.OrthoNormalBasis;
import org.sunflow.math.Point3;
import org.sunflow.math.Vector3;
import org.sunflow.system.UI;
import org.sunflow.system.UI.Module;
/**
* This class represents a camera to the renderer. It handles the mapping of
* camera space to world space, as well as the mounting of {@link CameraLens}
* objects which compute the actual projection.
*/
public class Camera implements RenderObject {
private final CameraLens lens;
private Matrix4[] c2w;
private Matrix4[] w2c;
public Camera(CameraLens lens) {
this.lens = lens;
c2w = w2c = new Matrix4[1]; // null
}
public boolean update(ParameterList pl, SunflowAPI api) {
int n = pl.getInt("transform.steps", 0);
if (n <= 0) {
// no motion blur, get regular arguments or leave unchanged
updateCameraMatrix(-1, pl);
} else {
// new motion blur settings - get transform for each step
c2w = new Matrix4[n];
for (int i = 0; i < n; i++) {
if (!updateCameraMatrix(i, pl)) {
UI.printError(Module.CAM, "Camera matrix for step %d was not specified!", i + 1);
return false;
}
}
}
w2c = new Matrix4[c2w.length];
for (int i = 0; i < c2w.length; i++) {
if (c2w[i] != null) {
w2c[i] = c2w[i].inverse();
if (w2c[i] == null) {
UI.printError(Module.CAM, "Camera matrix is not invertible");
return false;
}
} else
w2c[i] = null;
}
return lens.update(pl, api);
}
private boolean updateCameraMatrix(int index, ParameterList pl) {
String offset = index < 0 ? "" : String.format("[%d]", index);
if (index < 0)
index = 0;
Matrix4 transform = pl.getMatrix(String.format("transform%s", offset), null);
if (transform == null) {
// no transform was specified, check eye/target/up
Point3 eye = pl.getPoint(String.format("eye%s", offset), null);
Point3 target = pl.getPoint(String.format("target%s", offset), null);
Vector3 up = pl.getVector(String.format("up%s", offset), null);
if (eye != null && target != null && up != null) {
c2w[index] = Matrix4.fromBasis(OrthoNormalBasis.makeFromWV(Point3.sub(eye, target, new Vector3()), up));
c2w[index] = Matrix4.translation(eye.x, eye.y, eye.z).multiply(c2w[index]);
} else {
// the matrix for this index was not specified
// return an error, unless this is a regular update
return offset.length() == 0;
}
} else
c2w[index] = transform;
return true;
}
/**
* Generate a ray passing though the specified point on the image plane.
* Additional random variables are provided for the lens to optionally
* compute depth-of-field or motion blur effects. Note that the camera may
* return <code>null</code> for invalid arguments or for pixels which
* don't project to anything.
*
* @param x x pixel coordinate
* @param y y pixel coordinate
* @param imageWidth width of the image in pixels
* @param imageHeight height of the image in pixels
* @param lensX a random variable in [0,1) to be used for DOF sampling
* @param lensY a random variable in [0,1) to be used for DOF sampling
* @param time a random variable in [0,1) to be used for motion blur
* sampling
* @return a ray passing through the specified pixel, or <code>null</code>
*/
public Ray getRay(float x, float y, int imageWidth, int imageHeight, double lensX, double lensY, double time) {
Ray r = lens.getRay(x, y, imageWidth, imageHeight, lensX, lensY, time);
if (r != null) {
if (c2w.length == 1) {
// regular sampling
r = r.transform(c2w[0]);
} else {
// motion blur
double nt = time * (c2w.length - 1);
int idx0 = (int) nt;
int idx1 = Math.min(idx0 + 1, c2w.length - 1);
r = r.transform(Matrix4.blend(c2w[idx0], c2w[idx1], (float) (nt - idx0)));
}
// renormalize to account for scale factors embeded in the transform
r.normalize();
}
return r;
}
/**
* Generate a ray from the origin of camera space toward the specified
* point.
*
* @param p point in world space
* @return ray from the origin of camera space to the specified point
*/
Ray getRay(Point3 p) {
return new Ray(c2w == null ? new Point3(0, 0, 0) : c2w[0].transformP(new Point3(0, 0, 0)), p);
}
/**
* Returns a transformation matrix mapping camera space to world space.
*
* @return a transformation matrix
*/
Matrix4 getCameraToWorld() {
return c2w == null ? Matrix4.IDENTITY : c2w[0];
}
/**
* Returns a transformation matrix mapping world space to camera space.
*
* @return a transformation matrix
*/
Matrix4 getWorldToCamera() {
return w2c == null ? Matrix4.IDENTITY : w2c[0];
}
}