/*
* Project Info: http://jcae.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* (C) Copyright 2007, by EADS France
*/
package org.jcae.viewer3d;
import java.awt.Rectangle;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BoundingPolytope;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.IndexedQuadArray;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.vecmath.Point2d;
import javax.vecmath.Point3d;
import javax.vecmath.Tuple3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector4d;
/**
* A Bound object representing the visible part of the space.
* It's computed by cutting the infinite pyramid made by the eye and
* the screen, by the back and front clipping planes.
*/
public class ViewPyramid extends BoundingPolytope
{
private Canvas3D canvas;
/**
* Point where start rays when doing picking
*/
private Point3d startPoint=new Point3d();
private Point3d eye;
private Rectangle rectangle;
private BoundingSphere bounds;
public ViewPyramid(Canvas3D canvas)
{
this(canvas, canvas.getBounds());
}
public ViewPyramid(Canvas3D canvas, Rectangle rectangle)
{
this(canvas, rectangle, null);
}
public ViewPyramid(Canvas3D canvas, Rectangle rectangle, BoundingSphere bounds)
{
this.canvas=canvas;
this.rectangle=rectangle;
this.bounds=bounds;
setPlanes(computeRectangleProjection(rectangle));
}
/**
* returns the plane equation defined by tow vectors a point going throw
* and a point pointed by the plane normal
*/
private Vector4d computePlane(Vector3d n1,Vector3d n2,Point3d goesThrow,Point3d pointed){
Vector3d n=new Vector3d();
//Compute normal
n.cross(n1,n2);
n.normalize();
Point3d p=new Point3d();
p.sub(pointed,goesThrow);
if(n.dot(new Vector3d(p))<0)
n.scale(-1);
//Compute origin
double[] plane=new double[4];
n.get(plane);
plane[3]=-1*n.dot(new Vector3d(goesThrow));
return new Vector4d(plane);
}
private Vector4d[] computeRectangleProjection(Rectangle rectangle)
{
Point3d[] p=getPrismVertices(rectangle);
//Define an array of Vector4d for all the planes of the pickBounds
Vector4d[] planeFunc = new Vector4d[6];
planeFunc[0] = getPlaneFunc(p[0], p[1], p[2]);
planeFunc[1] = getPlaneFunc(p[4], p[6], p[5]);
planeFunc[2] = getPlaneFunc(p[1], p[0], p[4]);
planeFunc[3] = getPlaneFunc(p[1], p[6], p[2]);
planeFunc[4] = getPlaneFunc(p[2], p[6], p[3]);
planeFunc[5] = getPlaneFunc(p[0], p[3], p[4]);
//Define the BoundingPolytope bounds object for picking
return planeFunc;
}
//Fix a bug in PickResult line 1819
@Override
public void getPlanes(Vector4d[] planes)
{
for(int i=0;i<planes.length;i++)
{
planes[i]=new Vector4d();
}
super.getPlanes(planes);
}
/**
* Compute the plane function, return it
* @param p1 The 1rst point
* @param p2 The 2nd point
* @param p3 The 3rd point
* @return An object of Vector4d, which represents a plane function
*/
private static Vector4d getPlaneFunc(Point3d p1, Point3d p2, Point3d p3)
{
Point3d pt = new Point3d();
pt.sub(p1, p2);
Vector3d v1 = new Vector3d(pt);
pt.sub(p3, p2);
Vector3d v2 = new Vector3d(pt);
Vector3d planeVec = new Vector3d();
planeVec.cross(v2, v1);
planeVec.normalize();
//double d0 = -planeVec.dot(new Vector3d(p1));
double d1 = -planeVec.dot(new Vector3d(p2));
return new Vector4d(planeVec.x, planeVec.y, planeVec.z, d1);
}
/**
* returns the { {minX,minY},{minX,maxY},{maxX,maxY},{maxX,minY},{midle},{eye} }
* positions in the Vworld coordinates corresponding to the rectangle parameter
* on the canvas3d. This is the pyramid between the eye and the screen.
* @param rectangle
* @return
*/
private Point3d[] getPyramVertex(Rectangle rectangle){
if(rectangle==null) return null;
Point3d[] pyramVertex = new Point3d[6];
Point2d[] rectPoint = new Point2d[5];
for (int ii = 0; ii < pyramVertex.length; ii++)
pyramVertex[ii] = new Point3d();
rectPoint[0] = new Point2d(rectangle.getMinX(), rectangle.getMinY());
rectPoint[1] = new Point2d(rectangle.getMinX(), rectangle.getMaxY());
rectPoint[2] = new Point2d(rectangle.getMaxX(), rectangle.getMaxY());
rectPoint[3] = new Point2d(rectangle.getMaxX(), rectangle.getMinY());
rectPoint[4] = new Point2d(
(rectangle.getMaxX()+rectangle.getMinX()) / 2.0,
(rectangle.getMaxY()+rectangle.getMinY()) / 2.0);
for (int ii = 0; ii < rectPoint.length; ii++)
canvas.getPixelLocationInImagePlate(rectPoint[ii], pyramVertex[ii]);
canvas.getCenterEyeInImagePlate(pyramVertex[5]);
Transform3D trans = new Transform3D();
canvas.getImagePlateToVworld(trans);
for (int ii = 0; ii < pyramVertex.length; ii++)
trans.transform(pyramVertex[ii], pyramVertex[ii]);
return pyramVertex;
}
/**
* Return
* {{minX,minY,frontZ},{minX,maxY,frontZ},{maxX,maxY,fontZ},{maxX,minY,frontZ},
* {minX,minY,backZ},{minX,maxY,backZ},{maxX,maxY,backZ},{maxX,minY,backZ}}
* @param rectangle
* @return
*/
private Point3d[] getPrismVertices(Rectangle rectangle)
{
Point3d[] pyramid = getPyramVertex(rectangle);
eye = pyramid[5];
startPoint = pyramid[4];
double frontClip;
double farClip;
if(bounds == null)
{
farClip = canvas.getView().getBackClipDistance();
frontClip = canvas.getView().getFrontClipDistance();
}
else
{
double osDistance = objectScreenDistance();
frontClip = osDistance - bounds.getRadius();
farClip = osDistance + bounds.getRadius();
}
double frontFactor = computeClipFactor(eye, startPoint, frontClip);
double backFactor = computeClipFactor(eye, startPoint, farClip);
if(frontFactor<0)
frontFactor = 0;
if(backFactor<0)
backFactor = 0;
Point3d[] toReturn = new Point3d[8];
for(int i=0; i<4; i++)
{
toReturn[i] = computePrismPoint(frontFactor, eye, pyramid[i]);
toReturn[i+4] = computePrismPoint(backFactor, eye, pyramid[i]);
}
return toReturn;
}
/** Compute the distance between the bouding sphere and the screen */
private double objectScreenDistance()
{
Vector3d eyeScreen = new Vector3d(eye);
eyeScreen.sub(startPoint);
Vector3d eyeCenter = new Vector3d(eye);
Point3d center = new Point3d();
bounds.getCenter(center);
eyeCenter.sub(center);
double eyeScreenNorm = eyeScreen.length();
return eyeCenter.dot(eyeScreen)/eyeScreenNorm-eyeScreenNorm;
}
private Point3d computePrismPoint(double factor, Point3d pyramidTop,
Point3d pyramidBasePoint)
{
Point3d p3d=new Point3d(pyramidTop);
p3d.scale(1-factor);
Point3d toReturn=new Point3d(pyramidBasePoint);
toReturn.scaleAdd(factor, p3d);
return toReturn;
}
/** Compute ||v1-v2||+d / ||v1-v2|| **/
private double computeClipFactor(Tuple3d v1, Tuple3d v2, double d)
{
Vector3d p=new Vector3d(v1);
p.sub(v2);
double l=p.length();
return (l+d)/l;
}
/**
* Return a point from where to start when picking
* @todo Probably useless, must be checked
*/
public Point3d getStartPoint()
{
return startPoint;
}
public void getSidePlanes(Vector4d[] planes)
{
Vector4d[] vs=new Vector4d[getNumPlanes()];
getPlanes(vs);
planes[0]=vs[2];
planes[1]=vs[3];
planes[2]=vs[4];
planes[3]=vs[5];
}
/**
* Return the position of the eye in the VWorld
* @todo Probably useless, must be checked
*/
public Point3d getEye()
{
return eye;
}
/**
* Return a shape3D representing the current pyramid.
* This method aims at being used for debugging.
*/
public Shape3D getShape3D()
{
IndexedQuadArray geom=new IndexedQuadArray(8,GeometryArray.COORDINATES,24);
geom.setCoordinates(0, getPrismVertices(rectangle));
geom.setCoordinateIndices(0, new int[]{
0,1,2,3,
4,5,6,7,
1,2,6,5,
0,1,5,4,
2,3,7,6,
0,3,7,4});
Shape3D s3d = new Shape3D(geom);
Appearance app=new Appearance();
PolygonAttributes pa=new PolygonAttributes();
pa.setCullFace(PolygonAttributes.CULL_NONE);
pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
app.setPolygonAttributes(pa);
s3d.setAppearance(app);
System.out.println(new BoundingBox(s3d.getBounds()));
System.out.println(new BoundingBox(this));
return s3d;
}
}