package framework.view3D;
import java.util.ArrayList;
import javax.media.j3d.BadTransformException;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.vecmath.SingularMatrixException;
import javax.vecmath.Vector3d;
import com.sun.j3d.utils.universe.ViewingPlatform;
import framework.model3D.GeometryUtility;
import framework.model3D.Object3D;
import framework.model3D.Placeable;
import framework.model3D.Position3D;
import framework.model3D.Universe;
/**
* 画角調整機能が付いたカメラ<BR>
* 視点、注視対象、視線のうち2つを指定して使う。
* @author 新田直也
*
*/
public class Camera3D {
private static final double NEAREST = 3.0;
private static final Vector3d VIEW_FORWARD = new Vector3d(0.0, 0.0, -1.0);
private static final Vector3d VIEW_DOWN = new Vector3d(0.0, -1.0, 0.0);
private Universe universe = null;
private View view = null;
private TransformGroup viewPlatformTransform = null;
protected ArrayList<Object3D> targetObjList = null;
protected ArrayList<Position3D> targetList = null;
protected Position3D viewPoint = null;
protected Object3D viewPointObj = null;
private Vector3d viewLine = null;
private Vector3d cameraBack = null;
// 以下の変数は省メモリ化のため導入
private Transform3D worldToView = new Transform3D();
private double matrix[] = new double[]{1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0};
private Transform3D cameraTransform = new Transform3D();
public Camera3D(Universe universe) {
this.universe = universe;
view = new View();
view.setPhysicalBody(new PhysicalBody());
view.setPhysicalEnvironment(new PhysicalEnvironment());
view.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY);
view.setBackClipDistance(10000.0);
ViewPlatform viewPlatform = new ViewPlatform();
view.attachViewPlatform(viewPlatform);
viewPlatformTransform = new TransformGroup();
viewPlatformTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
viewPlatformTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
viewPlatformTransform.addChild(viewPlatform);
universe.place(viewPlatformTransform);
}
public Universe getUniverse() {
return universe;
}
public View getView() {
return view;
}
/**
* カメラの注視点を追加する
*
* @param target
* 注視点
*/
public void addTarget(Position3D target) {
if (targetList == null) targetList = new ArrayList<Position3D>();
targetList.add(target);
}
/**
* カメラの注視対象を追加する
*
* @param target
* 注視対象
*/
public void addTarget(Object3D target) {
if (targetObjList == null) targetObjList = new ArrayList<Object3D>();
targetObjList.add(target);
}
/**
* カメラの注視対象を追加する
*
* @param target
* 注視対象
*/
public void addTarget(Placeable target) {
if (targetObjList == null) targetObjList = new ArrayList<Object3D>();
if (target.getBody() instanceof Object3D) {
targetObjList.add((Object3D)target.getBody());
}
}
/**
* カメラの視点を設定する
*
* @param viewPoint
* 視点
*/
public void setViewPoint(Position3D viewPoint) {
this.viewPoint = viewPoint;
}
/**
* カメラの視点を設定する
*
* @param viewPoint
* 視点となるオブジェクト
*/
public void setViewPoint(Object3D viewPointObj) {
this.viewPointObj = viewPointObj;
}
/**
* カメラの視点を設定する
*
* @param viewPoint
* 視点となる登場物
*/
public void setViewPoint(Placeable viewPointActor) {
if (viewPointActor.getBody() instanceof Object3D) {
viewPointObj = (Object3D)viewPointActor.getBody();
}
}
/**
* 手前からの視線に設定する
*/
public void setSideView() {
viewLine = VIEW_FORWARD;
}
/**
* 上から見下ろした視線に設定する
*/
public void setTopView() {
viewLine = VIEW_DOWN;
}
/**
* 視線を設定する
*/
public void setViewLine(Vector3d viewLine) {
this.viewLine = viewLine;
}
/**
* 注視対象に対してカメラを引く方向と距離を設定する
* @param cameraBack
*/
public void setCameraBack(Vector3d cameraBack) {
this.cameraBack = cameraBack;
}
public Vector3d getCameraBack() {
return cameraBack;
}
/**
* 視野角を設定する
* @param a 視野角
*/
public void setFieldOfView(double a) {
view.setFieldOfView(a);
}
/**
* 視野角を取得する
* @return 視野角
*/
public double getFieldOfView() {
return view.getFieldOfView();
}
/**
* フロントクリップ距離を設定する
* @param d
*/
public void setFrontClipDistance(double d) {
view.setFrontClipDistance(d);
}
/**
* バッククリップ距離を設定する
* @param d
*/
public void setBackClipDistance(double d) {
view.setBackClipDistance(d);
}
/**
* 注視対象や視点の移動、視線の変化に伴う画角調整
*/
public void adjust(long interval) {
// カメラ座標系(vx, vy, vz)を計算する
Vector3d vx = new Vector3d(), vy = new Vector3d(), vz = new Vector3d();
if (viewLine == null) {
// 視線が設定されていない場合
if ((viewPoint == null && viewPointObj == null)
|| ((targetObjList == null || targetObjList.size() == 0)
&& (targetList == null || targetList.size() == 0))) {
// 視点または注視対象が設定されていない場合、手前からの視線にする
vz.negate(VIEW_FORWARD);
} else {
// 注視対象の重心を注視点とする
Vector3d center = new Vector3d();
if (targetObjList != null && targetObjList.size() != 0) {
for (int i = 0; i < targetObjList.size(); i++) {
Position3D position = targetObjList.get(i).getPosition3D();
center.add(position.getVector3d());
}
center.scale(1.0 / targetObjList.size());
} else if (targetList != null && targetList.size() != 0) {
for (int i = 0; i < targetList.size(); i++) {
Position3D position = targetList.get(i);
center.add(position.getVector3d());
}
center.scale(1.0 / targetList.size());
}
if (viewPoint != null) {
center.sub(viewPoint.getVector3d());
} else {
center.sub(viewPointObj.getPosition3D().getVector3d());
}
center.normalize();
vz.negate(center);
}
} else {
// 視線が設定されている場合 vz を視線方向 と逆向きに設定する
viewLine.normalize();
vz.negate(viewLine);
}
vx.cross(vz, VIEW_DOWN);
if (vx.length() > GeometryUtility.TOLERANCE) {
vx.normalize();
} else {
vx = new Vector3d(1.0, 0.0, 0.0);
}
vy.cross(vz, vx);
// 世界座標からカメラ座標への変換を計算する
if (viewPoint != null || viewPointObj != null) {
// 視点が設定されている場合
Vector3d vp;
if (viewPoint != null) {
vp = viewPoint.getVector3d();
} else {
vp = viewPointObj.getPosition3D().getVector3d();
}
matrix[0] = vx.x; matrix[1] = vx.y; matrix[2] = vx.z; matrix[3] = 0.0;
matrix[4] = vy.x; matrix[5] = vy.y; matrix[6] = vy.z; matrix[7] = 0.0;
matrix[8] = vz.x; matrix[9] = vz.y; matrix[10] = vz.z; matrix[11] = 0.0;
matrix[12] = 0.0; matrix[13] = 0.0; matrix[14] = 0.0; matrix[15] = 1.0;
worldToView.set(matrix);
try {
worldToView.invert();
} catch (SingularMatrixException e) {
return;
}
worldToView.setTranslation(vp);
} else {
// 視点が設定されていない場合、注視対象と視線から(カメラ座標系上での)視点を逆計算する
if ((targetObjList == null || targetObjList.size() == 0)
&& (targetList == null || targetList.size() == 0)) return; // 視点も注視対象も設定されていない
double xmax = 0;
double xmin = 0;
double ymax = 0;
double ymin = 0;
// 座標の取得
Vector3d center = new Vector3d();
if (targetObjList != null && targetObjList.size() != 0) {
for (int i = 0; i < targetObjList.size(); i++) {
Position3D position = targetObjList.get(i).getPosition3D();
center.add(position.getVector3d());
double px = position.getVector3d().dot(vx);
double py = position.getVector3d().dot(vy);
if (i == 0) {
xmax = xmin = px;
ymax = ymin = py;
} else {
if (xmax < px)
xmax = px;
if (xmin > px)
xmin = px;
if (ymax < py)
ymax = py;
if (ymin > py)
ymin = py;
}
}
center.scale(1.0 / targetObjList.size());
} else if (targetList != null && targetList.size() != 0) {
for (int i = 0; i < targetList.size(); i++) {
Position3D position = targetList.get(i);
center.add(position.getVector3d());
double px = position.getVector3d().dot(vx);
double py = position.getVector3d().dot(vy);
if (i == 0) {
xmax = xmin = px;
ymax = ymin = py;
} else {
if (xmax < px)
xmax = px;
if (xmin > px)
xmin = px;
if (ymax < py)
ymax = py;
if (ymin > py)
ymin = py;
}
}
center.scale(1.0 / targetList.size());
}
double x = (xmax + xmin) / 2;
double y = (ymax + ymin) / 2;
double x_diff = Math.abs(xmax - x);
double y_diff = Math.abs(ymax - y);
if (x_diff < NEAREST)
x_diff = NEAREST;
if (y_diff < NEAREST)
y_diff = NEAREST;
double z;
if (x_diff < y_diff) {
z = y_diff / Math.tan(Math.PI / 18.0);
} else {
z = x_diff / Math.tan(Math.PI / 18.0);
}
Vector3d eye = new Vector3d(center.dot(vx), center.dot(vy), center.dot(vz));
if (cameraBack != null) {
eye.add(cameraBack);
} else {
eye.z += z;
}
matrix[0] = vx.x; matrix[1] = vx.y; matrix[2] = vx.z; matrix[3] = -eye.x;
matrix[4] = vy.x; matrix[5] = vy.y; matrix[6] = vy.z; matrix[7] = -eye.y;
matrix[8] = vz.x; matrix[9] = vz.y; matrix[10] = vz.z; matrix[11] = -eye.z;
matrix[12] = 0.0; matrix[13] = 0.0; matrix[14] = 0.0; matrix[15] = 1.0;
worldToView.set(matrix);
try {
worldToView.invert();
} catch (SingularMatrixException e) {
return;
}
}
try {
if (viewPlatformTransform != null) viewPlatformTransform.setTransform(worldToView);
} catch (BadTransformException e) {
}
}
public Position3D getViewPoint() {
if (viewPoint != null) return viewPoint;
if (viewPointObj != null) return viewPointObj.getPosition3D();
return null;
}
public Vector3d getViewLine() {
return viewLine;
}
public void setWorldToView(Position3D vp, Vector3d vl) {
// カメラパラメータへの反映
if (viewPoint != null) {
viewPoint.setVector3d(vp.getVector3d());
} else if (viewPointObj != null) {
viewPointObj.apply(vp, false);
}
if (viewLine != null) {
viewLine.set(vl);
}
// カメラ座標系(vx, vy, vz)を計算する
Vector3d vx = new Vector3d(), vy = new Vector3d(), vz = new Vector3d();
viewLine.normalize();
vz.negate(viewLine);
vx.cross(vz, VIEW_DOWN);
if (vx.length() > GeometryUtility.TOLERANCE) {
vx.normalize();
} else {
vx = new Vector3d(1.0, 0.0, 0.0);
}
vy.cross(vz, vx);
matrix[0] = vx.x; matrix[1] = vx.y; matrix[2] = vx.z; matrix[3] = 0.0;
matrix[4] = vy.x; matrix[5] = vy.y; matrix[6] = vy.z; matrix[7] = 0.0;
matrix[8] = vz.x; matrix[9] = vz.y; matrix[10] = vz.z; matrix[11] = 0.0;
matrix[12] = 0.0; matrix[13] = 0.0; matrix[14] = 0.0; matrix[15] = 1.0;
worldToView.set(matrix);
try {
worldToView.invert();
} catch (SingularMatrixException e) {
return;
}
worldToView.setTranslation(vp.getVector3d());
try {
if (viewPlatformTransform != null) viewPlatformTransform.setTransform(worldToView);
} catch (BadTransformException e) {
}
}
public Transform3D getWorldToView() {
adjust(1L);
Transform3D trans = new Transform3D();
trans.set(matrix);
trans.invert();
return trans;
}
public void transformView(Transform3D delta) {
viewPlatformTransform.getTransform(cameraTransform);
cameraTransform.mul(delta);
try {
viewPlatformTransform.setTransform(cameraTransform);
} catch (BadTransformException e) {
}
}
}