package gl; import geo.GeoObj; import javax.microedition.khronos.opengles.GL10; import system.EventManager; import util.Calculus; import util.HasDebugInformation; import util.Log; import util.Vec; import worldData.MoveComp; import worldData.Updateable; import actions.ActionUseCameraAngles2; import android.location.Location; import android.opengl.Matrix; /** * This is the virtual camera needed to display a virtual world. The 3 important * properties you might want to change manually are its position, rotation and * offset. Do this via {@link GLCamera#setNewPosition(Vec)}, * {@link GLCamera#setNewRotation(Vec)} and {@link GLCamera#setNewOffset(Vec)} * * @author Spobo * */ public class GLCamera implements Updateable, HasDebugInformation, Renderable, HasPosition, HasRotation, GLCamRotationController { private static final String LOG_TAG = "GLCamera"; // private float mybufferValue = 1000; // private float minimumBufferValue = 20; // private float bufferCount = 20; private Vec myOffset = null; private Vec myNewOffset = new Vec(0, 0, 0); private Vec myPosition = new Vec(0, 0, 0); // TODO would be dangerous to set any of those vecs to null because there // might be references in commands to those objects so check where those are // set to null! // @Deprecated // private Vec myNewPosition = new Vec(0, 0, 0); /** * y is always the angle from floor to top (rotation around green achsis * counterclockwise) and x always the clockwise rotation (like when you lean * to the side on a motorcycle) angle of the camera. * * to move from the green to the red axis (clockwise) you would have to add * 90 degree. */ private Vec myRotationVec = new Vec(0, 0, 0); @Deprecated public Vec myNewRotationVec; /** * set to false if you want the camera not to react on sensor events */ private boolean sensorInputEnabled = true; /** * http://www.songho.ca/opengl/gl_transform.html */ private float[] rotationMatrix = Calculus.createIdentityMatrix(); /** * This lock has to be used to not override the matrix while it is displayed * by opengl */ private final Object rotMatrLock = new Object(); private int matrixOffset = 0; private final float[] invRotMatrix = Calculus.createIdentityMatrix(); /** * use a {@link ActionUseCameraAngles2} instead * * The order is z,x,y achses. * * The camera rotation angles (positive and COUNTERCLOCKWISE !!) extracted * from the rotation matrix. These values will only be calculated if an * angleUpdateListener is set or {@link GLCamera#forceAngleCalculation} is * set to true */ @Deprecated private final float[] cameraAnglesInDegree = new float[3]; float[] initDir = new float[4]; private final float[] rotDirection = new float[4]; // public boolean forceAngleCalculation = false; private final MoveComp myMover = new MoveComp(3); public GLCamera() { } public GLCamera(Vec initialCameraPosition) { setNewPosition(initialCameraPosition); } @Override public boolean update(float timeDelta, Updateable parent) { if ((myRotationVec != null) && (myNewRotationVec != null)) { Vec.morphToNewAngleVec(myRotationVec, myNewRotationVec, timeDelta); } if ((myOffset != null) && (myNewOffset != null)) { Vec.morphToNewVec(myOffset, myNewOffset, timeDelta); } if (myPosition != null) { myMover.update(timeDelta, this); } return true; } @Override public Vec getRotation() { return myRotationVec; } @Override @Deprecated public void setRotation(Vec rotation) { if (myRotationVec == null) { myRotationVec = rotation; } else { myRotationVec.setToVec(rotation); } } public void setNewPosition(Vec cameraPosition) { if (myPosition == null) { myPosition = new Vec(); } myMover.myTargetPos = cameraPosition; } /** * x positive means east of zero pos (latitude direction) <br> * y positive means north of zero pos (longitude direction) <br> * z the height of the camera * * @return the {@link Vec} (x,y,z) */ public Vec getMyNewPosition() { return myMover.myTargetPos; } public void setNewCameraOffset(Vec newCameraOffset) { if (newCameraOffset != null) { if (myNewOffset == null) { myNewOffset = new Vec(newCameraOffset); if (myOffset == null) { myOffset = new Vec(); } } else { myNewOffset.setToVec(newCameraOffset); } } } @Deprecated public void setNewRotation(Vec cameraRotation) { if (cameraRotation != null) { if (myNewRotationVec == null) { myNewRotationVec = new Vec(cameraRotation); } else { myNewRotationVec.setToVec(cameraRotation); } } } /** * @param rayPosition * the vector where the ray pos will be stored in, so pass a * vector here that can be overwritten. Normally this value will * be the same as {@link GLCamera#myPosition} but if a marker is * used to move the {@link GLCamera} the translation will be * contained in the matrix as well and therefore the rayPosition * will be this translation in relation to the marker * @param rayDirection * the vector where the ray direction will be stored in, so pass * a vector here that can be overwritten (don't pass null!) * @param x * the horizontal screen-coordinates (from 0 to screen-width) * @param y * the vertical screen-coordinates (from 0 to screen-height). * Just pass the value you get from the Android onClick event */ public void getPickingRay(Vec rayPosition, Vec rayDirection, float x, float y) { if (rayDirection == null) { Log.e(LOG_TAG, "Passed direction vector object was null"); return; } // convert to opengl screen coords: x = (x - GLRenderer.halfWidth) / GLRenderer.halfWidth; y = (GLRenderer.height - y - GLRenderer.halfHeight) / GLRenderer.halfHeight; Matrix.invertM(invRotMatrix, 0, rotationMatrix, matrixOffset); if (rayPosition != null) { float[] rayPos = new float[4]; float[] initPos = { 0.0f, 0.0f, 0.0f, 1.0f }; Matrix.multiplyMV(rayPos, 0, invRotMatrix, 0, initPos, 0); rayPosition.x = rayPos[0]; rayPosition.y = rayPos[1]; rayPosition.z = rayPos[2]; if (myPosition != null) { rayPosition.add(myPosition); } } float[] rayDir = new float[4]; float[] initDir = { x * GLRenderer.nearHeight * GLRenderer.aspectRatio, y * GLRenderer.nearHeight, -GLRenderer.minViewDistance, 0.0f }; Matrix.multiplyMV(rayDir, 0, invRotMatrix, 0, initDir, 0); rayDirection.x = rayDir[0]; rayDirection.y = rayDir[1]; rayDirection.z = rayDir[2]; } /** * not jet ready for use * * @param virtualWorldPosition * @return */ @Deprecated public float[] getScreenCoordinatesFor(Vec virtualWorldPosition) { float[] rayPos = new float[4]; float[] initPos = { virtualWorldPosition.x, virtualWorldPosition.y, virtualWorldPosition.z, 1.0f }; Matrix.multiplyMV(rayPos, 0, rotationMatrix, matrixOffset, initPos, 0); // TODO return rayPos; } public int getMatrixOffset() { return matrixOffset; } /** * "Ground" means the plane where z is 0 * * Nearly the same code as * {@link GLCamera#getPickingRay(Vec, Vec, float, float)} just a little bit * optimized * * @return the position in the virtual world in the xy plane (so z is 0) * where the camera is looking at */ public Vec getPositionOnGroundWhereTheCameraIsLookingAt() { /* * This is an optimized version of the getPickingRay method. The good * readable code would look like this: * * Vec pos = new Vec(); Vec dir = new Vec(); * * camera.getPickingRay(pos, dir, GLRenderer.halfWidth, * GLRenderer.halfHeight); * * now the calculation where the direction vec hits the ground plane. * can be reduced to intersection of two lines where only the z values * of start and direction are different * * when you break down the intersection of two lines with nearly the * same direction vectors and nearly the same start vectors then you get * this: * * dir.mult(-pos.z / dir.z); * * dir.add(pos); * * dir is the position on the ground which then can be returned */ float[] rayPos = new float[4]; float[] rayDir = new float[4]; getCameraViewDirectionRay(rayPos, rayDir); /* * then calc intersection with ground */ float f = -rayPos[2] / rayDir[2]; return new Vec(f * rayDir[0] + rayPos[0], f * rayDir[1] + rayPos[1], 0); } /** * This will return a starting-point and direction of the line which comes * out of the camera. * * @param rayPos * here the rayPos will be stored, pass a new float[4]. The * result will contain {@link GLCamera#myPosition} so you dont * need to add it manually! Can be NULL if you only need the * ray-direction * @param rayDir * here the rayDir will be stored, pass a new float[4] * @return */ public void getCameraViewDirectionRay(float[] rayPos, float[] rayDir) { Matrix.invertM(invRotMatrix, 0, rotationMatrix, matrixOffset); if (rayPos != null) { float[] initPos = { 0.0f, 0.0f, 0.0f, 1.0f }; Matrix.multiplyMV(rayPos, 0, invRotMatrix, 0, initPos, 0); /* * TODO is raypos != 0 if initPos ist the 0 vector?? is this calc. * redundant? */ rayPos[0] += myPosition.x; rayPos[1] += myPosition.y; rayPos[2] += myPosition.z; } float[] initDir = { 0, 0, -GLRenderer.minViewDistance, 0.0f }; Matrix.multiplyMV(rayDir, 0, invRotMatrix, 0, initDir, 0); } /** * This method will be called by the virtual world to load the camera * parameters like the position and the rotation * * @param gl * @param parent * @param */ @Override public synchronized void render(GL10 gl, Renderable parent) { // if the camera sould not be in the center of the rotation it has to be // moved out before rotating: glLoadPosition(gl, myOffset); synchronized (rotMatrLock) { // load rotation matrix: gl.glMultMatrixf(rotationMatrix, matrixOffset); } // rotate Camera TODO use for manual rotation: glLoadRotation(gl, myRotationVec); // set the point where to rotate around glLoadPosition(gl, myPosition); } /* * (non-Javadoc) * * @see gl.GLCamRotationController#setRotationMatrix(float[], int) */ @Override public void setRotationMatrix(float[] rotMatrix, int offset) { synchronized (rotMatrLock) { rotationMatrix = rotMatrix; matrixOffset = offset; } } /** * Use a {@link ActionUseCameraAngles2} subclass instead * * Currently only the azimuth is calculated here * * @return [0]=azimuth (0 is north and 90 is east) */ @Deprecated public float[] getCameraAnglesInDegree() { updateCameraAngles(); return cameraAnglesInDegree; } @Deprecated private void updateCameraAngles() { Calculus.invertM(invRotMatrix, 0, rotationMatrix, matrixOffset); initDir[0] = 0; initDir[1] = 0; initDir[2] = -GLRenderer.minViewDistance; initDir[3] = 0; // TODO not a good idea to use myAnglesInRadians2 here, maybe additional // helper var?: Matrix.multiplyMV(rotDirection, 0, invRotMatrix, 0, initDir, 0); cameraAnglesInDegree[0] = Vec.getRotationAroundZAxis(rotDirection[1], rotDirection[0]); } private void glLoadPosition(GL10 gl, Vec vec) { if (vec != null) { // if you want to set the center to 0 0 5 you have to move the // camera -5 units OUT of the screen gl.glTranslatef(-vec.x, -vec.y, -vec.z); } } private void glLoadRotation(GL10 gl, Vec vec) { /* * a very important point is that its something completely different * when you change the rotation order to x y z ! the order y x z is * needed to use extract the angles from the rotation matrix with: * * SensorManager.getOrientation(rotationMatrix, anglesInRadians); * * so remember this oder when doing own rotations. * * y is always the angle from floor to top and x always the clockwise * rotation (like when you lean to the side on a motorcycle) angle of * the camera. */ if (vec != null) { gl.glRotatef(vec.y, 0, 1, 0); gl.glRotatef(vec.x, 1, 0, 0); gl.glRotatef(vec.z, 0, 0, 1); } } /** * y is always the angle from floor to top (rotation around green achsis * counterclockwise) and x always the clockwise rotation (like when you lean * to the side on a motorcycle) angle of the camera. * * to rotate from the green to the red axis (clockwise) you would have to * add 90 degree. * * @param xAngle * 0 means the car drives straight forward, positive values (0 to * 90) mean that the car turns left, negative values mean that * the car turns right * @param yAngle * 0 means the camera targets the ground, 180 the camera looks * into the sky * @param zAngle * like a compass (0=north, 90 east and so on */ public void setRotation(float xAngle, float yAngle, float zAngle) { myRotationVec.x = xAngle; myRotationVec.y = yAngle; myRotationVec.z = zAngle; } @Deprecated public void setNewRotation(float xAngle, float yAngle, float zAngle) { if (myNewRotationVec == null) { myNewRotationVec = new Vec(xAngle, yAngle, zAngle); } else { myNewRotationVec.x = xAngle; myNewRotationVec.y = yAngle; myNewRotationVec.z = zAngle; } } /** * change camera position relative to the actual camera rotation around the * z axis, so the the camera is moved along the camera coordinate system and * not the world coordinate system * * @param deltaX * @param deltaY */ public synchronized void changeXYPositionBuffered(float deltaX, float deltaY) { myMover.myTargetPos.add(deltaX, deltaY, 0); } /** * * This will change the x and y position values instantly by * adding/subtracting the passed values! If you want a smooth buffered * movement to the new position, use * {@link GLCamera#setNewPosition(float, float)} * * @param deltaX * its important that this is not the absolute value. Its only * the value wich will be added/subtracted to the current one * @param deltaY * see deltaX description */ public synchronized void changePositionUnbuffered(float deltaX, float deltaY) { myPosition.x += deltaX; myPosition.y += deltaY; } /* * (non-Javadoc) * * @see gl.GLCamRotationController#resetBufferedAngle() */ @Override @Deprecated public void resetBufferedAngle() { Log.d(LOG_TAG, "Reseting camera rotation in resetBufferedAngle()!"); if ((myNewRotationVec != null) && (sensorInputEnabled)) { myNewRotationVec.setToZero(); } } /** * This will change the z value of the camera-rotation instantly without * buffering by adding/subtracting the specified deltaZ value. The buffered * version of this method is called * {@link GLCamera#changeXYPositionBuffered(float, float)} * * @param deltaZ */ public void changeZAngleUnbuffered(float deltaZ) { myRotationVec.z += deltaZ; } /* * (non-Javadoc) * * @see gl.GLCamRotationController#changeZAngleBuffered(float) */ @Override @Deprecated public void changeZAngleBuffered(float deltaZ) { if (myNewRotationVec == null) { myNewRotationVec = new Vec(); } myNewRotationVec.z += deltaZ; } /** * This will change the z value of the camera-position by adding/subtracting * the specified deltaZ value. * * @param deltaZ * eg. -10 to move the camera 10 meters down */ public void changeZPositionBuffered(float deltaZ) { myMover.myTargetPos.add(0, 0, deltaZ); } /** * @param resetZValueToo * if you just want to reset x and y set this to false */ public void resetPosition(boolean resetZValueToo) { float pz = myPosition.z; float npz = myMover.myTargetPos.z; myPosition.setToZero(); myMover.myTargetPos.setToZero(); if (!resetZValueToo) { myPosition.z = pz; myMover.myTargetPos.z = npz; } } /** * This will reset the camera postion to (0,0,0) */ public void resetPosition() { resetPosition(true); } public void changeNewPosition(float deltaX, float deltaY, float deltaZ) { myMover.myTargetPos.add(deltaX, deltaY, deltaZ); } /** * @param x * positive means east of zero pos (longitude direction) * @param y * positive means north of zero pos (latitude direction) * @param z * the height of the camera */ public void setNewPosition(float x, float y, float z) { myMover.myTargetPos.setTo(x, y, z); } /** * @param x * positive means east of zero pos (longitude direction) * @param y * positive means north of zero pos (latitude direction) */ public void setNewPosition(float x, float y) { myMover.myTargetPos.setTo(x, y); } public Vec getNewCameraOffset() { return myNewOffset; } public void setNewOffset(Vec myNewOffset) { this.myNewOffset = myNewOffset; } /** * @return the position in the virtual world. This vec could be used as the * users postion e.g. <br> * <br> * x positive means east of zero pos (latitude direction) <br> * y positive means north of zero pos (longitude direction) <br> * z the height of the camera */ @Override public Vec getPosition() { return myPosition; } @Override public void setPosition(Vec position) { if (myPosition == null) { myPosition = position; } else { myPosition.setToVec(position); } } /** * @return The position where the camera moves to. Will be NULL if new * position never set before! */ // public Vec getMyNewPosition() { // // return myNewPosition; // } /** * The resulting coordinates can differ from * {@link EventManager#getCurrentLocationObject()} if the camera was not * moved according to the GPS input (eg moved via trackball). * * @return */ public Location getGPSLocation() { Vec coords = getGPSPositionVec(); Location pos = new Location("customCreated"); pos.setLatitude(coords.y); pos.setLongitude(coords.x); pos.setAltitude(coords.z); return pos; } /** * The resulting coordinates can differ from * {@link EventManager#getCurrentLocationObject()} if the camera was not * moved according to the GPS input (eg moved via trackball). * * @return a Vector with x=Longitude, y=Latitude, z=Altitude */ public Vec getGPSPositionVec() { GeoObj zeroPos = EventManager.getInstance() .getZeroPositionLocationObject(); return GeoObj.calcGPSPosition(this.getPosition(), zeroPos.getLatitude(), zeroPos.getLongitude(), zeroPos.getAltitude()); } public void setGpsPos(GeoObj newPos) { Vec pos = newPos.getVirtualPosition(EventManager.getInstance() .getZeroPositionLocationObject()); setNewPosition(pos); } /** * The resulting coordinates can differ from * {@link EventManager#getCurrentLocationObject()} if the camera was not * moved according to the GPS input (eg moved via trackball). * * @return */ public GeoObj getGPSPositionAsGeoObj() { Vec v = getGPSPositionVec(); return new GeoObj(v.y, v.x, v.z); } public float[] getRotationMatrix() { return rotationMatrix; } @Override public void showDebugInformation() { Log.w(LOG_TAG, "Infos about GLCamera:"); Log.w(LOG_TAG, " > myPosition=" + myPosition); Log.w(LOG_TAG, " > myMover.myTargetPos=" + myMover.myTargetPos); Log.w(LOG_TAG, " > myOffset=" + myOffset); Log.w(LOG_TAG, " > myNewOffset=" + myNewOffset); Log.w(LOG_TAG, " > myRotationVec=" + myRotationVec); Log.w(LOG_TAG, " > myNewRotationVec=" + myNewRotationVec); Log.w(LOG_TAG, " > rotationMatrix=" + rotationMatrix); } public boolean isSensorInputEnabled() { return sensorInputEnabled; } /** * @param sensorInputEnabled * set false tell the camera to ignore sensor input. You can * still use the methods like * {@link GLCamera#setNewPosition(Vec)} * {@link GLCamera#setNewRotation(Vec)} to move the camera but * the AR impression will be lost. Use this for games and defined * movement through a virtual world. */ public void setSensorInputEnabled(boolean sensorInputEnabled) { this.sensorInputEnabled = sensorInputEnabled; if (!sensorInputEnabled) { // reset rotation matrix rotationMatrix = Calculus.createIdentityMatrix(); } } }