package util; import gl.GLCamera; /** * see {@link Vec#Vec(float, float, float)} * * @author Spobo * */ public class Vec { private static final float SMALLEST_DISTANCE = 0.0001f; private static final String LOG_TAG = "Vec"; public static final float deg2rad = 0.01745329238474369f; final public static float rad2deg = (float) (180.0f / Math.PI); /** * @param x * value on red axis (east direction=longitude) * @param y * value on green axis (north direction=latitude) * @param z * value on blue axis (sky direction=altitude=height) */ public float x, y, z = 0; private float[] myArray; /** * @param x * value on red axis (east direction=longitude) * @param y * value on green axis (north direction=latitude) * @param z * value on blue axis (sky direction=altitude=height) */ public Vec(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } /** * inits x,y,z with 0 */ public Vec() { } public Vec(Vec vecToCopy) { this.x = vecToCopy.x; this.y = vecToCopy.y; this.z = vecToCopy.z; } /** * @param vec2 * @return itself, no copy! just if you want to go on doing more like * v.add(v2).add(v3) */ public Vec add(Vec vec2) { x += vec2.x; y += vec2.y; z += vec2.z; return this; } public void add(float x, float y, float z) { this.x += x; this.y += y; this.z += z; } /** * returns the leght of a given vector * * @param a * @return */ public static float vectorLength(Vec a) { return (float) Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z); } public Vec sub(Vec vec2) { x -= vec2.x; y -= vec2.y; z -= vec2.z; return this; } public Vec normalize() { return mult(1 / vectorLength(this)); } public static float abs(float d) { if (d < 0) return -d; return d; } public Vec mult(float factor) { x = x * factor; y = y * factor; z = z * factor; return this; } /** * rotates the vector COUNTERCLOCKWISE around the z axis. Warning this is * different to {@link GLCamera#getCameraAnglesInDegree()}[0] which will * give the rotation CLOCKWISE around the Z axsis * * <br> * <br> * * EXAMPLE: vector (0,10,0) is currently pointing north. passing 90 degree * here will cause it to point to -10 0 0 (so west) * * <br> * <br> * * if you want to rotate according to the current camera rotation you have * to calculate 360-angle first * * @param angleInDegree */ public synchronized Vec rotateAroundZAxis(double angleInDegree) { /* * Rotation matrix: * * cos a -sin a 0 * * sin a cos a 0 * * 0 0 1 */ angleInDegree = Math.toRadians(angleInDegree); float cos = (float) Math.cos(angleInDegree); float sin = (float) Math.sin(angleInDegree); float x2 = cos * x - sin * y; y = sin * x + cos * y; x = x2; return this; } /** * To get the point 10 meters away 30 degree clockwise from north you just * have to pass (10, 30) as parameters * * @param distanceInMeters * @param angleInDegree * CLOCKWISE rotation angle * @return */ public static Vec rotatedVecInXYPlane(float distanceInMeters, double angleInDegree) { Vec v = new Vec(distanceInMeters, 0, 0); v.rotateAroundZAxis(angleInDegree); return v; } /** * Rotates counterclockwise around the x axis * * @param angleInDegree */ public synchronized Vec rotateAroundXAxis(double angleInDegree) { /* * Rotation matrix: * * 1 0 0 * * 0 cos a sin a * * 0 -sin a cos a */ angleInDegree = Math.toRadians(angleInDegree); float cos = (float) Math.cos(angleInDegree); float sin = (float) Math.sin(angleInDegree); float y2 = cos * y + sin * z; z = cos * z - sin * y; y = y2; return this; } public Vec div(float factor) { x = x / factor; y = y / factor; z = z / factor; return this; } public static Vec mult(float factor, Vec oldVec) { Vec v = oldVec.copy(); v.x *= factor; v.y *= factor; v.z *= factor; return v; } /** * @param a * @param b * @return a positive distance value or -1 if something was wrong */ public static float distance(Vec a, Vec b) { if (a == null || b == null) return -1; return vectorLength(new Vec(a.x - b.x, a.y - b.y, a.z - b.z)); } /** * The same method like {@link Vec#distance(Vec, Vec)} but only the XY-plane * is taken into account * * @param a * @param b * @return */ public static float XYdistance(Vec a, Vec b) { if (a == null || b == null) return -1; return vectorLength(new Vec(a.x - b.x, a.y - b.y, 0)); } // // TODO check how to set optional parameters // public static Vec crossingLines(Vec startVec1, Vec directionVec1, // Vec startVec2, Vec directionVec2) { // return crossingLines(startVec1, directionVec1, startVec2, // directionVec2, true); // } // // // TODO neu machen! siehe vorlesungfolien // public static Vec crossingLines(Vec startVec1, Vec directionVec1, // Vec startVec2, Vec directionVec2, boolean directionLengthRelevant) { // if (Vec.parallelVecs(directionVec1, directionVec2)) { // // TODO check if startVec1 lies in the second line (important for // // cubes eg) // return null; // } // // startVec1+x*directionVec1 = startVec2+ x * directionVec2 // // <=> // float y = (directionVec1.x * (startVec1.x - startVec2.x) - // directionVec1.x // * (startVec1.y - startVec2.y)) // / (directionVec2.x * directionVec1.y - directionVec1.x // * directionVec2.y); // float x = (directionVec2.x * y - startVec1.x + startVec2.x) // / directionVec1.x; // // if its not important how long the directionVecs are then just return // // the "collision-position" // if (!directionLengthRelevant) // return add(startVec1, mult(x, directionVec1)); // // now compare the length of the directionVecs with x and y // if ((abs(Vec.vlength(directionVec1)) <= abs(x)) // && (abs(vlength(directionVec2)) <= abs(y))) // return add(startVec1, mult(x, directionVec1)); // alternately y*... // // would be the same // // result // return null; // } private static boolean parallelVecs(Vec vec1, Vec vec2) { if ((vec1.copy().normalize()).equals(vec2.copy().normalize())) return true; return false; } /** * @param vec * @param factor * the same factor as in {@link Vec#round(float)} * @return */ public boolean equals(Vec vec, float factor) { this.round(factor); vec.round(factor); if ((x == vec.x) && (y == vec.y) && (z == vec.z)) return true; return false; } /** * @param factor * pass 100 if you want to cut 0.12345678 to 0.12 and 1000 to cut * it to 0.123 */ public void round(float factor) { x = Math.round(x * factor) / factor; y = Math.round(y * factor) / factor; z = Math.round(z * factor) / factor; } @Override public boolean equals(Object o) { if (o instanceof Vec) return equals((Vec) o, 1000000f); return super.equals(o); } public static Vec add(Vec a, Vec b) { float x = a.x + b.x; float y = a.y + b.y; float z = a.z + b.z; return new Vec(x, y, z); } /** * @param a * @param b * @return a-b */ public static Vec sub(Vec a, Vec b) { float x = a.x - b.x; float y = a.y - b.y; float z = a.z - b.z; return new Vec(x, y, z); } /** * returns the orthogonal vector in 2d so z=0 * * @param a * @return */ public static Vec getOrthogonalHorizontal(Vec a) { /* * => a*orthogonal=0 <=> a.x*orthogonal.x+a.y*orthogonal.y=0 * * set orthogonal.y=-1 => orthogonal.x=a.y/a.x */ if (a.x == 0) return new Vec(1, 0, 0); return new Vec(a.y / a.x, -1, 0); } // TODO you can't mirror a 3d line an another line so extend to plane // // this "mirrors" a vector (for eg the mirror line is the ground =(1,0) // and // // a is the force of a ball which hits it // public static Vec mirror(Vec mirrorLine, Vec a) { // /* // * Its a orthogonal projection: => a'=a+(a * // * mirrorLine)/(|mirrorLine|^2) * mirrorLine // */ // // // TODO not correct i think, it returns the senkrechte?? // // has to be Vec mirrored = Vec.sub(mult(2,Vec.project(a, // // mirrorLine)),a); ?? // // Vec mirrored = Vec.add(a, Vec.project(a, mirrorLine)); // return mirrored; // } /** * This is the scalar product of two vectors * * @param a * @param b * @return */ private static float multScalar(Vec a, Vec b) { return a.x * b.x + a.y * b.y + a.z * b.z; } // Read also info at Vec.mirror() /** returns the shadow of the tree on the ground TODO add explanation here! */ public static Vec orthogonalProjection(Vec tree, Vec ground) { return mult((multScalar(tree, ground) / (multScalar(ground, ground))), ground); } public Vec copy() { return new Vec(x, y, z); } /** * @param length * @return the resized vector to allow chains */ public Vec setLength(float length) { if (length == 0f) { length = 0.00000001f; } if (x == 0f && y == 0f && z == 0f) { Log.w(LOG_TAG, "Request to setLength on 0 Vector which is impossible"); return this; } return mult(length / vectorLength(this)); } public float getLength() { return Vec.vectorLength(this); } public void setToVec(Vec b) { x = b.x; y = b.y; z = b.z; } public void setToZero() { x = 0; y = 0; z = 0; } @Override public String toString() { return "Vec: (" + x + ", " + y + ", " + z + ")"; } /** * can be used for smooth scrolling or just morphing from vec a to b * * @param target * @param newPos * @param factor * should be >0 and <1 * @return */ public static void morphToNewVec(Vec target, Vec newPos, float factor) { if (factor > 1) { factor = 1; } /* * the other way would be: * * final Vec dif = Vec.sub(newPos, target); dif.mult(factor); * target.add(dif); */ target.x += factor * (newPos.x - target.x); target.y += factor * (newPos.y - target.y); target.z += factor * (newPos.z - target.z); } public static void morphToNewAngleVec(Vec target, Vec newRotation, float timeDelta) { morphToNewAngleVec(target, newRotation.x, newRotation.y, newRotation.z, timeDelta); } /** * * @param target * @param newX * @param newY * @param newZ * @param timeDelta */ public static void morphToNewAngleVec(Vec target, float newX, float newY, float newZ, float timeDelta) { if (timeDelta > 1) { timeDelta = 1; } float deltaX = (newX - target.x); if (deltaX > 180) { target.x -= (360 - deltaX) * timeDelta; } else if (deltaX < -180) { target.x += (360 + deltaX) * timeDelta; } else if (!(-SMALLEST_DISTANCE < deltaX && deltaX < SMALLEST_DISTANCE)) { target.x += (deltaX) * timeDelta; } if (target.x < 0) target.x += 360; if (target.x >= 360) target.x -= 360; float deltaY = (newY - target.y); if (deltaY > 180) { target.y -= (360 - deltaY) * timeDelta; } else if (deltaY < -180) { target.y += (360 + deltaY) * timeDelta; } else if (!(-SMALLEST_DISTANCE < deltaY && deltaY < SMALLEST_DISTANCE)) { target.y += (deltaY) * timeDelta; } if (target.y < 0) target.y += 360; if (target.y >= 360) target.y -= 360; float deltaZ = newZ - target.z; if (deltaZ > 180) { target.z -= (360 - deltaZ) * timeDelta; } else if (deltaZ < -180) { target.z += (360 + deltaZ) * timeDelta; } else if (!(-SMALLEST_DISTANCE < deltaZ && deltaZ < SMALLEST_DISTANCE)) { target.z += (deltaZ) * timeDelta; } if (target.z < 0) target.z += 360; if (target.z >= 360) target.z -= 360; } public boolean isNullVector() { return ((x == 0) && (y == 0) && (z == 0)); } /** * see {@link Vec#toAngleVec()} <br> * <br> * * After this method there are some common adjustments.<br> * The following is for object rotation: <br> * v.x -= 90;<br> * v.z *= -1;<br> * obj.setRotation(v); <br> * <br> * The other one is for camera rotation: <br> * v.x*=-1; <br> * camera.setRotation(v); <br> * <br> * The reason is that OpenGL e.g. needs negative x rotation values, or the * -90 is because the object should not be rotated when the rotation is * parallel to the ground * * * @param from * the position where you are standing * @param to * the position where you want to look at */ public void toAngleVec(Vec from, Vec to) { this.setToVec(to); sub(from); toAngleVec(); } /** * x=0 if the angle is facing to the ground, 90 if it looks to the horizon * and 180 if it looks in the sky. All the values in between are possible * too. So x will be between 0 and 180<br> * <br> * * y=always 0<br> * <br> * * z=the compass angle. 0 is north, 45 is northeast, 90 is east, */ public void toAngleVec() { if ((x == 0) && (y == 0)) { if (z > 0) { // the angle is looking directly in the sky (eg 0,0,1) x = 180; y = 0; z = 0; return; } else { // the angle is looking directly on the ground (eg 0,0,-1) x = 0; y = 0; z = 0; return; } } /* * arcCos has a value range from -180 to 180 so you have to check on * which side (east (x>=0) or west (x<0) if it would be a compass) the x * value is */ float zAngle; if (x >= 0) { zAngle = (float) Math.acos(y / Math.sqrt(x * x + y * y)) * rad2deg; } else { zAngle = 360.0f - (float) Math.acos(y / Math.sqrt(x * x + y * y)) * rad2deg; } /* * if y is 0 the x value cant be set to 0 as well, so check this first * and if y is 0 use angle between x and z and not y and z as usual */ if (y != 0) { x = 0; x = (float) Math.acos(-z / getLength()) * rad2deg; } else { x = (float) Math.acos(-z / getLength()) * rad2deg; } y = 0; // then set the compass rotation to the z value z = zAngle; } public static Vec copy(Vec valueVec) { if (valueVec == null) return null; return valueVec.copy(); } /** * @return the mirrored version on the x y and z axis */ public Vec getNegativeClone() { return new Vec(-x, -y, -z); } /** * @param center * @param minDistance * to the center * @param maxDistance * to the center * @return A random vector with the same z value */ public static Vec getNewRandomPosInXYPlane(Vec center, float minDistance, float maxDistance) { if (center == null) return null; float rndDistance = (float) (Math.random() * (maxDistance - minDistance) + minDistance); Vec rndPos = new Vec(rndDistance, 0, 0); rndPos.rotateAroundZAxis(Math.random() * 359); rndPos.x += center.x; rndPos.y += center.y; rndPos.z = 0; return rndPos; } public static Vec getNewRandomPosInXYZ(Vec center, float minDistance, float maxDistance) { if (center == null) return null; float rndDistance = (float) (Math.random() * (maxDistance - minDistance) + minDistance); Vec rndPos = new Vec(rndDistance, 0, 0); rndPos.rotateAroundXAxis(Math.random() * 359); rndPos.rotateAroundZAxis(Math.random() * 359); rndPos.x += center.x; rndPos.y += center.y; rndPos.z += center.z; return rndPos; } /** * Given 2 vectors this calculates the vector which is orthogonal to the * plane the two vectors create * * @param v1 * @param v2 * @return the orthogonal vector */ public static Vec calcNormalVec(Vec uVec, Vec vVec) { Vec ret = new Vec(); ret.x = (uVec.y * vVec.z) - (uVec.z * vVec.y); ret.y = (uVec.z * vVec.x) - (uVec.x * vVec.z); ret.z = (uVec.x * vVec.y) - (uVec.y * vVec.x); return ret; } public float[] getArrayVersion() { if (myArray == null) { myArray = new float[4]; /* * set the last of the 4 values to 1 on default. This is important * for light-positioning eg, there it is used as a flag to indicate * that the light should be a positional light source. See the * LightSource class and * http://fly.cc.fer.hr/~unreal/theredbook/chapter06.html for more * details * * TODO so is this the right place to do this? */ myArray[3] = 1; } myArray[0] = x; myArray[1] = y; myArray[2] = z; return myArray; } public float scalarMult(Vec b) { return x * b.x + y * b.y + z * b.z; } public void setTo(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public void setTo(float x, float y) { this.x = x; this.y = y; } /** * @param x * @param y * @return a value from 0 to 359 */ public static float getRotationAroundZAxis(float x, float y) { /* * Scalarproduct rule: * * cos(w)=a*b/(|a|*|b|) * * a is (1,0,0) */ float result = (float) Math.acos((x / Math.sqrt(x * x + y * y))) * rad2deg; if (y < 0) return 360 - result; return result; } }