Java tutorial
/* * Copyright (C) 2016 UMR AMAP (botAnique et Modlisation de l'Architecture des Plantes et des vgtations. * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package fr.amap.viewer3d.object.camera; import fr.amap.commons.math.matrix.Mat4F; import fr.amap.commons.math.vector.Vec3F; import fr.amap.commons.math.util.SphericalCoordinates; import fr.amap.viewer3d.object.scene.MousePicker; import fr.amap.viewer3d.object.scene.SceneObject; import javax.swing.event.EventListenerList; import javax.vecmath.Point3d; import org.apache.commons.math3.util.MathUtils; /** * * @author Julien Heurtebize (julienhtbe@gmail.com) */ public class TrackballCamera extends Camera { private Vec3F forwardVec; private Vec3F rightVec; private Vec3F upVec; private float viewportWidth; private float viewportHeight; private float width; private float height; private boolean inverseY = false; private MousePicker mousePicker; private SceneObject pivot; public float getAngleX() { return angleX; } public float getAngleY() { return angleY; } public float getAngleZ() { return angleZ; } public TrackballCamera() { listeners = new EventListenerList(); viewMatrix = Mat4F.identity(); location = new Vec3F(); target = new Vec3F(); up = new Vec3F(0, 0, 1); mousePicker = new MousePicker(this); } @Override public void init(Vec3F location, Vec3F target, Vec3F up) { /* this.location =location; this.target = target; */ this.up = up; /* viewMatrix = Mat4F.identity(); viewMatrix = Mat4F.lookAt(location, target, up); orientation = Vec3F.substract(target, location); orientation = Vec3F.normalize(orientation); */ //updateViewMatrix(); } public void initOrtho(float left, float right, float top, float bottom, float near, float far) { this.left = left; this.right = right; this.top = top; this.bottom = bottom; this.nearOrtho = near; this.farOrtho = far; } @Override public void setPerspective(float fovy, float aspect, float near, float far) { this.fovy = fovy; this.aspect = aspect; this.nearPersp = near; this.farPersp = far; this.perspective = true; updateProjMatrix(); } @Override public void setOrthographic(float left, float right, float top, float bottom, float near, float far) { this.left = left; this.right = right; this.top = top; this.bottom = bottom; this.nearOrtho = near; this.farOrtho = far; this.perspective = false; updateProjMatrix(); } @Override public void rotateAroundPoint(Vec3F axis, Vec3F pivot, float angle) { if (axis.x != 0) { //this.angleX = angle; this.angleX = this.angleX + angle; float r = (float) Math.sqrt(Math.pow(pivot.z - location.z, 2) + Math.pow(pivot.y - location.y, 2)); float z = (float) (pivot.z + r * Math.cos(this.angleX)); float y = (float) (pivot.y + r * Math.sin(this.angleX)); location.z = z; location.y = y; } else if (axis.y != 0) { //this.angleY = angle; this.angleY = this.angleY + angle; /* if(Math.toDegrees(angleY)>360){ angleY = (float) Math.toRadians(Math.toDegrees(angleY) - 360); }else if(Math.toDegrees(angleY)<0){ angleY = (float) Math.toRadians(360 - Math.toDegrees(-angleY)); } */ float r = (float) Math.sqrt(Math.pow(pivot.x - location.x, 2) + Math.pow(pivot.z - location.z, 2)); float x = (float) (pivot.x + r * Math.cos(this.angleY)); float z = (float) (pivot.z + r * Math.sin(this.angleY)); location.x = x; location.z = z; } else if (axis.z != 0) { //this.angleZ = angle; this.angleZ = this.angleZ + angle; float r = (float) Math.sqrt(Math.pow(pivot.x - location.x, 2) + Math.pow(pivot.y - location.y, 2)); float x = (float) (pivot.x + r * Math.cos(this.angleZ)); float y = (float) (pivot.y + r * Math.sin(this.angleZ)); location.x = x; location.y = y; } target = pivot; updateViewMatrix(); } public void setPivot(SceneObject sceneObject) { this.target = new Vec3F(sceneObject.getGravityCenter().x, sceneObject.getGravityCenter().y, sceneObject.getGravityCenter().z); this.pivot = sceneObject; } public void setPivot(Vec3F pivot) { this.target = pivot; } private float normalizeTheta(float theta) { return (float) MathUtils.normalizeAngle(theta, 0); //normalize between 0 and 2pi /*while(theta < 0){ theta += Math.PI * 2; } while(theta > (Math.PI * 2)){ theta -= (Math.PI * 2); } return theta;*/ } private float normalizePhi(float phi) { //lock between 0 excluded and pi exluded while (phi < 0) { phi += Math.PI * 2; } while (phi > (Math.PI * 2)) { phi -= (Math.PI * 2); } return phi; } public void rotateFromOrientationV2(float offsetX, float offsetY) { //get current theta and phi SphericalCoordinates sc1 = new SphericalCoordinates(); sc1.toSpherical(new Point3d(location.x - target.x, location.y - target.y, location.z - target.z)); float oldTheta = (float) sc1.getAzimut(); float oldPhi = (float) sc1.getZenith(); if (location.x == target.x && location.y == target.y) { SphericalCoordinates sc2 = new SphericalCoordinates(); sc2.toSpherical(upVec.x, upVec.y, upVec.z); double azimut1 = sc2.getAzimut(); oldTheta = (float) -azimut1; if (upVec.y == -1) { offsetY = 0; } } //theta doit tre compris entre 0 et 2pi //phi doit tre compris entre entre ]0 et pi[ float theta = 0, phi = 0; forwardVec = getForwardVector(); float radius = Vec3F.length(forwardVec); float thetaStep = (float) Math.toRadians(Math.abs(offsetX)); float phiStep = (float) Math.toRadians(Math.abs(offsetY / 2.0f)); //float thetaStep = (float) ((Math.PI * 2)/360.0f); //1 step //float phiStep = (float) ((Math.PI * 2)/360.0f); //1 step theta += oldTheta; phi += oldPhi; if (offsetX > 0) { theta -= thetaStep; } else if (offsetX < 0) { theta += thetaStep; } //theta = normalizeTheta(theta); if (offsetY > 0) { phi -= phiStep; if (phi < 0) { phi += phiStep; } } else if (offsetY < 0) { if (phi + phiStep < Math.PI) { phi += phiStep; } /*if(phi > Math.PI){ phi -= phiStep + 0.000001; }*/ } //phi = normalizePhi(phi); SphericalCoordinates sc = new SphericalCoordinates(theta, phi, radius); Point3d cartesian = sc.toCartesian(); location.x = target.x + (float) cartesian.getX(); location.y = target.y + (float) cartesian.getY(); location.z = target.z + (float) cartesian.getZ(); updateViewMatrix(); } public void rotateFromOrientation(Vec3F axis, Vec3F center, float angle) { forwardVec = getForwardVector(); float radius = Vec3F.length(forwardVec); forwardVec = Vec3F.normalize(forwardVec); rightVec = Vec3F.cross(forwardVec, up); if (Vec3F.length(rightVec) == 0) { rightVec.y = 1; } upVec = getUpVector(); //forwardVec = Vec3F.normalize(forwardVec); rightVec = Vec3F.normalize(rightVec); upVec = Vec3F.normalize(upVec); if (axis.x != 0) { //quation du cercle: //M = C + A* r * cos(t) + B * r *sin(t) avec M = position sur le cercle, C = centre du cercle, A = vecteur du cercle, B = vecteur du cercle orthogonal A, r = rayon du cercle, t = angle //position = Vec3.add(pivot, Vec3.add(Vec3.multiply(viewXAxis, r* (float)Math.cos(angleX)), Vec3.multiply(viewYAxis, r* (float)Math.sin(angleX)))); //pondration de l'angle par l'inclinaison float n = Vec3F.dot(forwardVec, up); float d = Vec3F.length(forwardVec) * Vec3F.length(up); float tilt = (float) (Math.acos(Math.abs(n / d))); /* if(tilt == 0){ tilt = 0.18f; }*/ angle *= (tilt / (Math.PI / 2.0d)); angle = -angle; float angleSinus = (float) Math.sin(angle); float angleCosinus = (float) Math.cos(angle); location.x = target.x + (-forwardVec.x * radius * angleCosinus) + (rightVec.x * radius * angleSinus); location.y = target.y + (-forwardVec.y * radius * angleCosinus) + (rightVec.y * radius * angleSinus); location.z = target.z + (-forwardVec.z * radius * angleCosinus) + (rightVec.z * radius * angleSinus); } if (axis.y != 0) { float angleSinus = (float) Math.sin(angle); float angleCosinus = (float) Math.cos(angle); //copy Vec3F oldLocation = new Vec3F(location.x, location.y, location.z); location.x = target.x + (-forwardVec.x * radius * angleCosinus) + (upVec.x * radius * angleSinus); location.y = target.y + (-forwardVec.y * radius * angleCosinus) + (upVec.y * radius * angleSinus); location.z = target.z + (-forwardVec.z * radius * angleCosinus) + (upVec.z * radius * angleSinus); Vec3F newForwardVec = getForwardVector(); newForwardVec = Vec3F.normalize(newForwardVec); Vec3F newRightVec = Vec3F.cross(newForwardVec, up); if ((newRightVec.y < 0 && rightVec.y > 0) || (newRightVec.y > 0 && rightVec.y < 0)) { location = oldLocation; } } //target = pivot; updateViewMatrix(); } public void rotateX(Vec3F center, float angle) { rotateAroundPoint(new Vec3F(1.0f, 0.0f, 0.0f), center, angle); } public void rotateY(Vec3F center, float angle) { rotateAroundPoint(new Vec3F(0.0f, 1.0f, 0.0f), center, angle); } public void rotateZ(Vec3F center, float angle) { rotateAroundPoint(new Vec3F(0.0f, 0.0f, 1.0f), center, angle); } /** * * @param translation translation vector of camera */ @Override public void translate(Vec3F translation) { float copyDistanceToTarget = getDistanceToTarget(); forwardVec = getForwardVector(); forwardVec = Vec3F.normalize(forwardVec); if (translation.x != 0.0f) { Vec3F sideShift = Vec3F.normalize(Vec3F.cross(up, forwardVec)); location = Vec3F.add(location, Vec3F.multiply(sideShift, translation.x)); //target = Vec3F.add(target, Vec3F.multiply(sideShift, translation.x)); } if (translation.y != 0.0f) { Vec3F verticalShift = up; location = Vec3F.add(location, Vec3F.multiply(verticalShift, translation.y)); //target = Vec3F.add(target, Vec3F.multiply(verticalShift, translation.y)); } if (translation.z != 0.0f) { if (perspective) { //target = Vec3F.add(target, Vec3F.multiply(orientation, translation.z)); //use for not reaching the target //setPerspective(70.0f, (1.0f*640)/480, near-translation.z, far-translation.z); } else { if ((left < -1.0f && right > 1.0f) || translation.z < 0) { float widthCoeff = viewportWidth / 100.0f; float heightCoeff = viewportHeight / 100.0f; float leftCopy = left; float rightCopy = right; float topCopy = top; float bottomCopy = bottom; left = left + translation.z * (widthCoeff); right = right - translation.z * (widthCoeff); bottom = bottom + translation.z * (heightCoeff); top = top - translation.z * (heightCoeff); if (left > -1 || right < 1 || bottom > -1 || top < 1) { left = leftCopy; right = rightCopy; bottom = bottomCopy; top = topCopy; } } //updateProjMatrix(); } //copy old location Vec3F oldForwardVector = getForwardVector(); Vec3F oldLocation = location; //test translation effect location = Vec3F.add(location, Vec3F.multiply(forwardVec, translation.z)); Vec3F newForwardVector = getForwardVector(); //if translation is not good, get back to the original location (equivalent to not move) if ((newForwardVector.z < 0 && oldForwardVector.z > 0) || (newForwardVector.z > 0 && oldForwardVector.z < 0)) { location = oldLocation; } } updateViewMatrix(); float distanceToTarget = getDistanceToTarget(); nearPersp = distanceToTarget - 500.0f; nearPersp = Float.max(nearPersp, 0.01f); farPersp = distanceToTarget + 500.0f; //nearOrtho = nearPersp; //farOrtho = farPersp; updateProjMatrix(); } public void translateV2(Vec3F translation) { forwardVec = getForwardVector(); //slow down relatively from the length of the forward vector if (perspective) { translation = Vec3F.multiply(translation, (Vec3F.length(forwardVec) / (float) Math.tan(fovy)) * 0.001f); } else { translation = Vec3F.multiply(translation, Vec3F.length(forwardVec) * 0.0025f); } forwardVec = Vec3F.normalize(forwardVec); rightVec = getRightVector(); upVec = getUpVector(); Vec3F xTranslation = Vec3F.multiply(rightVec, -translation.x); Vec3F yTranslation = Vec3F.multiply(upVec, translation.y); Vec3F translationVec = Vec3F.add(xTranslation, yTranslation); location = Vec3F.add(translationVec, location); target = Vec3F.add(translationVec, target); updateViewMatrix(); } @Override public void updateViewMatrix() { Mat4F oldValue = viewMatrix; forwardVec = getForwardVector(); viewMatrix = Mat4F.lookAt(location, target, up); notifyViewMatrixChanged(); props.firePropertyChange("viewMatrix", oldValue, viewMatrix); } public void notifyViewMatrixChanged() { fireLocationChanged(location); fireTargetChanged(target); fireViewMatrixChanged(viewMatrix); } @Override public void addCameraListener(CameraListener listener) { listeners.add(CameraListener.class, listener); } @Override public void fireLocationChanged(Vec3F location) { for (CameraListener listener : listeners.getListeners(CameraListener.class)) { listener.locationChanged(location); } } @Override public void fireTargetChanged(Vec3F target) { for (CameraListener listener : listeners.getListeners(CameraListener.class)) { listener.targetChanged(target); } } @Override public void setTarget(Vec3F target) { this.target = target; } @Override public void setLocation(Vec3F location) { this.location = location; } @Override public void fireViewMatrixChanged(Mat4F viewMatrix) { for (CameraListener listener : listeners.getListeners(CameraListener.class)) { listener.viewMatrixChanged(viewMatrix); } } @Override public void fireProjectionMatrixChanged(Mat4F projMatrix) { for (CameraListener listener : listeners.getListeners(CameraListener.class)) { try { listener.projMatrixChanged(projMatrix); } catch (RuntimeException e) { listeners.remove(CameraListener.class, listener); } } } @Override public boolean isPerspective() { return perspective; } public void setOrthographic(float near, float far) { this.nearOrtho = near; this.farOrtho = far; this.perspective = false; updateProjMatrix(); } private float getTargetDistance() { Vec3F center = new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z); return Vec3F.length(Vec3F.substract(location, center)); } public void setViewToBack() { project(new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y + getTargetDistance(), getPivot().getGravityCenter().z), new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); updateViewMatrix(); } public void setViewToFront() { project(new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y - getTargetDistance(), getPivot().getGravityCenter().z), new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); updateViewMatrix(); } public void setViewToLeft() { setLocation(new Vec3F(getPivot().getGravityCenter().x - getTargetDistance(), getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); setTarget(new Vec3F(getPivot().getGravityCenter().x, getLocation().y, getLocation().z)); updateViewMatrix(); } public void setViewToRight() { setLocation(new Vec3F(getPivot().getGravityCenter().x + getTargetDistance(), getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); setTarget(new Vec3F(getPivot().getGravityCenter().x, getLocation().y, getLocation().z)); updateViewMatrix(); } public void setViewToBottom() { project(new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z - getTargetDistance()), new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); updateViewMatrix(); upVec = new Vec3F(0, -1, 0); rightVec = new Vec3F(1, 0, 0); } public void setViewToTop() { project(new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z + getTargetDistance()), new Vec3F(getPivot().getGravityCenter().x, getPivot().getGravityCenter().y, getPivot().getGravityCenter().z)); //rightVec = new Vec3F(0, 0, 1); upVec = new Vec3F(0, 1, 0); rightVec = new Vec3F(1, 0, 0); updateViewMatrix(); } public void setViewToOrthographic() { /*float objectDepth = Vec3F.dot( Vec3F.substract( new Vec3F(voxelSpace.getCenterX(), voxelSpace.getCenterY(), voxelSpace.getCenterZ()), camera.getLocation()), camera.getForwardVector()); float cameraWidth = (2.0f / camera.getProjectionMatrix().mat[0]) * objectDepth; float cameraHeight = (2.0f / camera.getProjectionMatrix().mat[5]) * objectDepth; float ymax = (float) Math.tan(camera.getFovy() * Math.PI / 360.0f); float xmax = ymax * camera.getAspect(); cameraWidth = objectDepth * xmax; cameraHeight = objectDepth * ymax; camera.setWidth(width); camera.setHeight(height);*/ setOrthographic(getLeft(), getRight(), getTop(), getBottom(), getNearOrtho(), getFarOrtho()); } public void setViewToOrthographic(float left, float right, float top, float bottom, float near, float far) { setOrthographic(left, right, top, bottom, near, far); } public void setViewToPerspective() { setPerspective(getFovy(), getAspect(), getNearPersp(), getFarPersp()); } public void setViewToPerspective(float fov, float near, float far) { setPerspective(fov, getAspect(), near, far); } public void switchPerspective() { if (isPerspective()) { setViewToOrthographic(); } else { setViewToPerspective(); } } public Vec3F getForwardVector() { return Vec3F.substract(target, location); } public float getDistanceToTarget() { return Vec3F.length(Vec3F.substract(target, location)); } public Vec3F getUpVector() { return Vec3F.cross(rightVec, forwardVec); } public Vec3F getRightVector() { Vec3F result = Vec3F.cross(forwardVec, up); if (Vec3F.length(result) == 0) { result.x = 1; } rightVec = result; return result; } @Override public Vec3F getTarget() { return target; } @Override public Vec3F getLocation() { return location; } public void setViewportWidth(float viewportWidth) { this.viewportWidth = viewportWidth; } public void setViewportHeight(float viewportHeight) { this.viewportHeight = viewportHeight; } public float getViewportWidth() { return viewportWidth; } public float getViewportHeight() { return viewportHeight; } public float getWidth() { return width; } public void setWidth(float width) { this.width = width; } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } public SceneObject getPivot() { return pivot; } }