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) { } } }