/******************************************************************************* * This file is part of Goko. * * Goko is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Goko 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Goko. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package org.goko.tools.viewer.jogl.camera; import java.util.ArrayList; import java.util.List; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.fixedfunc.GLMatrixFunc; import javax.media.opengl.glu.GLU; import javax.vecmath.Point2i; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3f; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.goko.core.common.exception.GkException; import org.goko.core.math.BoundingTuple6b; import org.goko.core.math.Tuple6b; import org.goko.tools.viewer.jogl.preferences.JoglViewerPreference; import org.goko.tools.viewer.jogl.service.JoglUtils; import com.jogamp.opengl.swt.GLCanvas; import com.jogamp.opengl.util.PMVMatrix; public class PerspectiveCamera extends AbstractCamera implements MouseMoveListener,MouseListener,Listener,FocusListener, IPropertyChangeListener { public static final String ID = "org.goko.tools.viewer.jogl.camera.PerspectiveCamera"; protected Point2i last; protected Point3f eye; protected Point3f at; protected Point3f target; protected Vector3f up; private double angleVertical ; private double angleHorizontal; private double maxAngleLimit = Math.PI/300; private double distance; private GLCanvas glCanvas; private GLU glu; private float fAspect; public Point2i screenMin; public Point2i screenMax; public Point2i screenCenter; private int orbitX; private int orbitY; private float orbitSensitivity; private int panX; private int panY; private float panSensitivity; private int zoomFactor; private float zoomSensitivity; /** * Constructor * @param canvas the canvas */ public PerspectiveCamera(final GLCanvas canvas) { super(); this.glCanvas = canvas; fAspect = 0; glCanvas.addMouseListener(this); glCanvas.addMouseMoveListener(this); glCanvas.addFocusListener(this); glCanvas.addListener(SWT.MouseWheel, this); pmvMatrix = new PMVMatrix(); glu = new GLU(); last = new Point2i(); eye = new Point3f(0,0,15); at = new Point3f(0,0,0); target = new Point3f(); up = new Vector3f(0,0,1); angleHorizontal = 0; angleVertical = Math.PI/4; angleHorizontal = -Math.PI/4; distance = 120; addPreferenceListener(); // Force init of the values this.propertyChange(null); update(); } /** * */ private void addPreferenceListener() { JoglViewerPreference.getInstance().addPropertyChangeListener(this); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#setup() */ @Override public void setup(){ fAspect = 0; if(height != 0){ fAspect = width / height; } glCanvas.getGL().getGL2().glMatrixMode(GL2.GL_MODELVIEW); // choose projection matrix glCanvas.getGL().getGL2().glLoadIdentity(); // reset projection matrix glu.gluPerspective(45.0f, fAspect, 0.5f, 10000.0f); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); pmvMatrix.glLoadIdentity(); float fov = 45.0f; float aspect = fAspect; float zNear = 0.5f; float zFar = 10000.0f; pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); pmvMatrix.glLoadIdentity(); pmvMatrix.gluPerspective(fov, aspect, zNear, zFar); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.update(); // http://stackoverflow.com/questions/13462516/camera-rotation } /** * Update camera position in spherical coordinate system */ protected void update() { Vector3f direction = new Vector3f( (float)(Math.cos(angleHorizontal) * Math.sin(angleVertical)), (float)(Math.sin(angleHorizontal) * Math.sin(angleVertical)), (float)(Math.cos(angleVertical)) ); eye.x = (float) (target.x + direction.x * distance);///Math.cos(angleHorizontal) * Math.sin(angleVertical) * distance); eye.y = (float) (target.y + direction.y * distance);//Math.sin(angleHorizontal) * Math.sin(angleVertical) * distance); eye.z = (float) (target.z + direction.z * distance);//Math.cos(angleVertical) * distance); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#updatePosition() */ @Override public void updatePosition(){ glu.gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, up.x, up.y, up.z); updatePMVMatrix(); } private void updatePMVMatrix(){ pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, up.x, up.y, up.z); pmvMatrix.update(); } /** (inheritDoc) * @see org.eclipse.swt.events.MouseMoveListener#mouseMove(org.eclipse.swt.events.MouseEvent) */ @Override public void mouseMove(MouseEvent e) { if(glCanvas.isFocusControl() && isActivated()){ CameraMotionMode mode = null; if((e.stateMask & SWT.BUTTON1) != 0 ){ if((e.stateMask & SWT.ALT) != 0 ){ mode = CameraMotionMode.ZOOM; }else{ mode = CameraMotionMode.PAN; } }else if((e.stateMask & SWT.BUTTON3) != 0 ){ mode = CameraMotionMode.ORBIT; } if(mode != null){ if(last == null){ last = new Point2i(e.x,e.y); } switch(mode){ case PAN:panMouse(e); break; case ORBIT:orbitMouse(e); break; case ZOOM:zoomMouse(e); } last.x = e.x; last.y = e.y; update(); } } } protected void zoomMouse(MouseEvent e){ distance += (e.y - last.y)/20.0; distance = Math.min(1000, Math.max(1, distance)); } protected void panMouse(MouseEvent e){ Vector3f yCameraRelative = new Vector3f(target.x - eye.x, target.y - eye.y, 0); yCameraRelative.normalize(); Vector3f xCameraRelative = new Vector3f(); xCameraRelative.cross(yCameraRelative, up); float factor = (float) (distance / 600); xCameraRelative.scale( -panX*(e.x-last.x) * factor * panSensitivity); yCameraRelative.z = 0; yCameraRelative.scale(panY*(e.y-last.y)* factor * panSensitivity); xCameraRelative.add(yCameraRelative); target.add(xCameraRelative); } protected void orbitMouse(MouseEvent e){ angleHorizontal += (float) orbitSensitivity * (orbitX * (e.x-last.x) / 100.0)%Math.PI; angleVertical += (float) orbitSensitivity * (orbitY *(e.y-last.y) / 100.0)%Math.PI; angleVertical = Math.min(Math.PI-maxAngleLimit, Math.max(+maxAngleLimit, angleVertical)); } /** (inheritDoc) * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(org.eclipse.swt.events.MouseEvent) */ @Override public void mouseDoubleClick(MouseEvent e) { } /** (inheritDoc) * @see org.eclipse.swt.events.MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent) */ @Override public void mouseDown(MouseEvent e) { last = new Point2i(e.x, e.y); //glCanvas.forceFocus(); } /** (inheritDoc) * @see org.eclipse.swt.events.MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent) */ @Override public void mouseUp(MouseEvent e) { } /** (inheritDoc) * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ @Override public void handleEvent(Event event) { // Zoom on scroll if(glCanvas.isFocusControl() && isActivated()){ distance = distance * (1 - (zoomFactor * zoomSensitivity *event.count)/20.0); distance = Math.min(1000, Math.max(1, distance)); update(); } } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#zoomToFit(org.goko.core.math.BoundingTuple6b) */ @Override public void zoomToFit(BoundingTuple6b bounds) throws GkException { double boundCenterX = (bounds.getMax().getX().doubleValue(JoglUtils.JOGL_UNIT) + bounds.getMin().getX().doubleValue(JoglUtils.JOGL_UNIT) ) /2; double boundCenterY = (bounds.getMax().getY().doubleValue(JoglUtils.JOGL_UNIT) + bounds.getMin().getY().doubleValue(JoglUtils.JOGL_UNIT) ) /2; target.x = (float) boundCenterX; target.y = (float) boundCenterY; update(); updatePMVMatrix(); BoundingTuple6b projectedBound = getProjectedBound(bounds); for(int i =0; i < 2; i++){ double[] screenCenter = new double[]{ (projectedBound.getMax().getX().doubleValue(JoglUtils.JOGL_UNIT) + projectedBound.getMin().getX().doubleValue(JoglUtils.JOGL_UNIT)) / 2, (projectedBound.getMax().getY().doubleValue(JoglUtils.JOGL_UNIT) + projectedBound.getMin().getY().doubleValue(JoglUtils.JOGL_UNIT)) / 2, (projectedBound.getMax().getZ().doubleValue(JoglUtils.JOGL_UNIT) + projectedBound.getMin().getZ().doubleValue(JoglUtils.JOGL_UNIT)) / 2}; float[] worldCenter = new float[4]; getPmvMatrix().gluUnProject((float)screenCenter[0], (float)screenCenter[1], (float)screenCenter[2], new int[]{x,y,width,height}, 0, worldCenter, 0); update(); updatePMVMatrix(); } //Let's zoom now int i = 0; while(!isBoundInScreen(bounds) && i < 100){ distance *= 1.1; update(); updatePMVMatrix(); i++; } i = 0; while(isBoundInScreen(bounds) && i < 1000){ distance *= 0.99; update(); updatePMVMatrix(); i++; } distance *= 1.1; update(); updatePMVMatrix(); projectedBound = getProjectedBound(bounds); this.screenMin = new Point2i(projectedBound.getMin().getX().value(JoglUtils.JOGL_UNIT).intValue(), projectedBound.getMin().getY().value(JoglUtils.JOGL_UNIT).intValue()); this.screenMax = new Point2i(projectedBound.getMax().getX().value(JoglUtils.JOGL_UNIT).intValue(), projectedBound.getMax().getY().value(JoglUtils.JOGL_UNIT).intValue()); } protected BoundingTuple6b getProjectedBound(BoundingTuple6b bounds){ float xMx = (float) bounds.getMax().getX().doubleValue(JoglUtils.JOGL_UNIT); float yMx = (float) bounds.getMax().getY().doubleValue(JoglUtils.JOGL_UNIT); float zMx = (float) bounds.getMax().getZ().doubleValue(JoglUtils.JOGL_UNIT); float xMn = (float) bounds.getMin().getX().doubleValue(JoglUtils.JOGL_UNIT); float yMn = (float) bounds.getMin().getY().doubleValue(JoglUtils.JOGL_UNIT); float zMn = (float) bounds.getMin().getZ().doubleValue(JoglUtils.JOGL_UNIT); float[] p1Low = new float[]{xMn, yMn, zMn}; float[] p2Low = new float[]{xMx, yMn, zMn}; float[] p3Low = new float[]{xMx, yMx, zMn}; float[] p4Low = new float[]{xMn, yMx, zMn}; float[] p1High = new float[]{xMn, yMn, zMx}; float[] p2High = new float[]{xMx, yMn, zMx}; float[] p3High = new float[]{xMx, yMx, zMx}; float[] p4High = new float[]{xMn, yMx, zMx}; float[][] pts = new float[][]{ p1Low, p2Low, p3Low, p4Low, p1High, p2High, p3High, p4High}; float[] screenMin = new float[]{Float.MAX_VALUE,Float.MAX_VALUE,Float.MAX_VALUE,Float.MAX_VALUE}; float[] screenMax = new float[]{-Float.MAX_VALUE,-Float.MAX_VALUE,-Float.MAX_VALUE,-Float.MAX_VALUE}; float[] screenP = new float[4]; for(int i = 0; i < pts.length; i++){ float[] pt = pts[i]; boolean result = getPmvMatrix().gluProject( pt[0],pt[1],pt[2], new int[]{0,0,width,height}, 0, screenP, 0); if(result){ for(int j = 0; j < 4; j++){ if(screenP[j] < screenMin[j]){ screenMin[j] = screenP[j]; } if(screenP[j] > screenMax[j]){ screenMax[j] = screenP[j]; } } } } return new BoundingTuple6b(new Tuple6b(screenMin[0], screenMin[1], screenMin[2], JoglUtils.JOGL_UNIT), new Tuple6b(screenMax[0], screenMax[1], screenMax[2], JoglUtils.JOGL_UNIT)); } protected boolean isBoundInScreen(BoundingTuple6b bounds){ float xMx = (float) bounds.getMax().getX().doubleValue(JoglUtils.JOGL_UNIT); float yMx = (float) bounds.getMax().getY().doubleValue(JoglUtils.JOGL_UNIT); float xMn = (float) bounds.getMin().getX().doubleValue(JoglUtils.JOGL_UNIT); float yMn = (float) bounds.getMin().getY().doubleValue(JoglUtils.JOGL_UNIT); float zMn = (float) bounds.getMin().getZ().doubleValue(JoglUtils.JOGL_UNIT); List<Point3f> lstAabbPoints = new ArrayList<Point3f>(); lstAabbPoints.add(new Point3f(xMn, yMn, zMn)); lstAabbPoints.add(new Point3f(xMx, yMn, zMn)); lstAabbPoints.add(new Point3f(xMx, yMx, zMn)); lstAabbPoints.add(new Point3f(xMn, yMx, zMn)); Point3f screen = new Point3f(); for (Point3f point3f : lstAabbPoints) { getProjectedPoint(point3f, screen); if(screen.x < 0 || screen.x > width || screen.y < 0 || screen.y > height){ return false; } } return true; } protected void getProjectedPoint(Point3f obj, Point3f screen){ float[] screenCoord = new float[4]; getPmvMatrix().gluProject( obj.x, obj.y, obj.z, new int[]{0,0,width,height}, 0, screenCoord, 0); screen.x = screenCoord[0]; screen.y = screenCoord[1]; screen.z = screenCoord[2]; } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#getLabel() */ @Override public String getLabel() { return "Perspective"; } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#lookAt(javax.vecmath.Point3d) */ @Override public void lookAt(Point3d lookat) { target.x = (float) lookat.x; target.y = (float) lookat.y; target.z = (float) lookat.z; } /** (inheritDoc) * @see javax.media.opengl.GLEventListener#display(javax.media.opengl.GLAutoDrawable) */ @Override public void display(GLAutoDrawable arg0) { } /** (inheritDoc) * @see javax.media.opengl.GLEventListener#dispose(javax.media.opengl.GLAutoDrawable) */ @Override public void dispose(GLAutoDrawable arg0) { } /** (inheritDoc) * @see javax.media.opengl.GLEventListener#init(javax.media.opengl.GLAutoDrawable) */ @Override public void init(GLAutoDrawable arg0) { setup(); } /** (inheritDoc) * @see javax.media.opengl.GLEventListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int) */ @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { super.reshape(drawable, x, y, width, height); GL2 gl = drawable.getGL().getGL2(); if (height == 0) { height = 1; // prevent divide by zero } // Set the view port (display area) to cover the entire window gl.glViewport(0, 0, width, height); float fov = 45.0f; float aspect = (float) width / height; float zNear = 0.5f; float zFar = 10000.0f; pmvMatrix.glMatrixMode(GLMatrixFunc.GL_PROJECTION); pmvMatrix.glLoadIdentity(); pmvMatrix.gluPerspective(fov, aspect, zNear, zFar); pmvMatrix.glMatrixMode(GLMatrixFunc.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); pmvMatrix.update(); } /** (inheritDoc) * @see org.goko.tools.viewer.jogl.camera.AbstractCamera#getId() */ @Override public String getId() { return ID; } /** * @return the pmvMatrix */ @Override public PMVMatrix getPmvMatrix() { return pmvMatrix; } /** * @param pmvMatrix the pmvMatrix to set */ @Override public void setPmvMatrix(PMVMatrix pmvMatrix) { this.pmvMatrix = pmvMatrix; } /** (inheritDoc) * @see org.eclipse.swt.events.FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ @Override public void focusGained(FocusEvent e) { last = null; } /** (inheritDoc) * @see org.eclipse.swt.events.FocusListener#focusLost(org.eclipse.swt.events.FocusEvent) */ @Override public void focusLost(FocusEvent e) { last = null; } /** (inheritDoc) * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) */ @Override public void propertyChange(PropertyChangeEvent event) { this.orbitX = JoglViewerPreference.getInstance().isCameraOrbitInvertXAxis() ? -1 : 1; this.orbitY = JoglViewerPreference.getInstance().isCameraOrbitInvertYAxis() ? -1 : 1; float orbitSensitivityPref = JoglViewerPreference.getInstance().getCameraOrbitSensitivity().floatValue(); this.orbitSensitivity = (float) (1 + (orbitSensitivityPref - 50) / 100.0); this.panX = JoglViewerPreference.getInstance().isCameraPanInvertXAxis() ? -1 : 1; this.panY = JoglViewerPreference.getInstance().isCameraPanInvertYAxis() ? -1 : 1; float panSensitivityPref = JoglViewerPreference.getInstance().getCameraPanSensitivity().floatValue(); this.panSensitivity = (float) (1 + (panSensitivityPref - 50) / 100.0); this.zoomFactor = JoglViewerPreference.getInstance().isCameraZoomInvertAxis() ? -1 : 1; float zoomSensitivityPref = JoglViewerPreference.getInstance().getCameraZoomSensitivity().floatValue(); this.zoomSensitivity = (float) (1 + (zoomSensitivityPref - 50) / 100.0); } }