// Copyright 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.android.stardroid.renderer;
import android.content.res.Resources;
import com.google.android.stardroid.R;
import com.google.android.stardroid.renderer.util.SearchHelper;
import com.google.android.stardroid.renderer.util.TextureManager;
import com.google.android.stardroid.renderer.util.TextureReference;
import com.google.android.stardroid.renderer.util.TexturedQuad;
import com.google.android.stardroid.units.Vector3;
import com.google.android.stardroid.util.FixedPoint;
import com.google.android.stardroid.util.MathUtil;
import com.google.android.stardroid.util.VectorUtil;
import javax.microedition.khronos.opengles.GL10;
public class SearchArrow {
// The arrow quad is 10% of the screen width or height, whichever is smaller.
private final float ARROW_SIZE = 0.1f;
// The circle quad is 40% of the screen width or height, whichever is smaller.
private final float CIRCLE_SIZE = 0.4f;
// The target position is (1, theta, phi) in spherical coordinates.
private float mTargetTheta = 0;
private float mTargetPhi = 0;
private TexturedQuad mCircleQuad = null;
private TexturedQuad mArrowQuad = null;
private float mArrowOffset = 0;
private float mCircleSizeFactor = 1;
private float mArrowSizeFactor = 1;
private float mFullCircleScaleFactor = 1;
private TextureReference mArrowTex = null;
private TextureReference mCircleTex = null;
public void reloadTextures(GL10 gl, Resources res, TextureManager textureManager) {
gl.glEnable(GL10.GL_TEXTURE_2D);
mArrowTex = textureManager.getTextureFromResource(gl, R.drawable.arrow);
mCircleTex = textureManager.getTextureFromResource(gl, R.drawable.arrowcircle);
gl.glDisable(GL10.GL_TEXTURE_2D);
}
public void resize(GL10 gl, int screenWidth, int screenHeight, float fullCircleSize) {
mArrowSizeFactor = ARROW_SIZE * Math.min(screenWidth, screenHeight);
mArrowQuad = new TexturedQuad(mArrowTex,
0, 0, 0,
0.5f, 0, 0,
0, 0.5f, 0);
mFullCircleScaleFactor = fullCircleSize;
mCircleSizeFactor = CIRCLE_SIZE * mFullCircleScaleFactor;
mCircleQuad = new TexturedQuad(mCircleTex,
0, 0, 0,
0.5f, 0, 0,
0, 0.5f, 0);
mArrowOffset = mCircleSizeFactor + mArrowSizeFactor;
}
public void draw(GL10 gl, Vector3 lookDir, Vector3 upDir, SearchHelper searchHelper,
boolean nightVisionMode) {
float lookPhi = MathUtil.acos(lookDir.y);
float lookTheta = MathUtil.atan2(lookDir.z, lookDir.x);
// Positive diffPhi means you need to look up.
float diffPhi = lookPhi - mTargetPhi;
// Positive diffTheta means you need to look right.
float diffTheta = lookTheta - mTargetTheta;
// diffTheta could potentially be in the range from (-2*Pi, 2*Pi), but we need it
// in the range (-Pi, Pi).
if (diffTheta > MathUtil.PI) {
diffTheta -= MathUtil.TWO_PI;
} else if (diffTheta < -MathUtil.PI) {
diffTheta += MathUtil.TWO_PI;
}
// The image I'm using is an arrow pointing right, so an angle of 0 corresponds to that.
// This is why we're taking arctan(diffPhi / diffTheta), because diffTheta corresponds to
// the amount we need to rotate in the xz plane and diffPhi in the up direction.
float angle = MathUtil.atan2(diffPhi, diffTheta);
// Need to add on the camera roll, which is the amount you need to rotate the vector (0, 1, 0)
// about the look direction in order to get it in the same plane as the up direction.
float roll = angleBetweenVectorsWithRespectToAxis(new Vector3(0, 1, 0), upDir, lookDir);
angle += roll;
// Distance is a normalized value of the distance.
float distance = 1.0f / (1.414f * MathUtil.PI) *
MathUtil.sqrt(diffTheta * diffTheta + diffPhi * diffPhi);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glPushMatrix();
gl.glRotatef(angle * 180.0f / MathUtil.PI, 0, 0, -1);
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_BLEND);
// 0 means the circle is not expanded at all. 1 means fully expanded.
float expandFactor = searchHelper.getTransitionFactor();
if (expandFactor == 0) {
gl.glColor4x(FixedPoint.ONE, FixedPoint.ONE, FixedPoint.ONE, FixedPoint.ONE);
float redFactor, blueFactor;
if (nightVisionMode) {
redFactor = 0.6f;
blueFactor = 0;
} else {
redFactor = 1.0f - distance;
blueFactor = distance;
}
gl.glTexEnvfv(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_COLOR,
new float[] {redFactor, 0.0f, blueFactor, 0.0f}, 0);
gl.glPushMatrix();
float circleScale = mCircleSizeFactor;
gl.glScalef(circleScale, circleScale, circleScale);
mCircleQuad.draw(gl);
gl.glPopMatrix();
gl.glPushMatrix();
float arrowScale = mArrowSizeFactor;
gl.glTranslatef(mArrowOffset * 0.5f, 0, 0);
gl.glScalef(arrowScale, arrowScale, arrowScale);
mArrowQuad.draw(gl);
gl.glPopMatrix();
} else {
gl.glColor4x(FixedPoint.ONE, FixedPoint.ONE, FixedPoint.ONE,
FixedPoint.floatToFixedPoint(0.7f));
gl.glTexEnvfv(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_COLOR,
new float[] {1, nightVisionMode ? 0 : 0.5f, 0, 0.0f}, 0);
gl.glPushMatrix();
float circleScale = mFullCircleScaleFactor * expandFactor +
mCircleSizeFactor * (1 - expandFactor);
gl.glScalef(circleScale, circleScale, circleScale);
mCircleQuad.draw(gl);
gl.glPopMatrix();
}
gl.glPopMatrix();
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE);
gl.glDisable(GL10.GL_BLEND);
}
public void setTarget(Vector3 position) {
position = VectorUtil.normalized(position);
mTargetPhi = MathUtil.acos(position.y);
mTargetTheta = MathUtil.atan2(position.z, position.x);
}
// Given vectors v1 and v2, and an axis, this function returns the angle which you must rotate v1
// by in order for it to be in the same plane as v2 and axis. Assumes that all vectors are unit
// vectors and v2 and axis are perpendicular.
private static float angleBetweenVectorsWithRespectToAxis(Vector3 v1, Vector3 v2, Vector3 axis) {
// Make v1 perpendicular to axis. We want an orthonormal basis for the plane perpendicular
// to axis. After rotating v1, the projection of v1 and v2 into this plane should be equal.
Vector3 v1proj = VectorUtil.difference(v1, VectorUtil.projectOntoUnit(v1, axis));
v1proj = VectorUtil.normalized(v1proj);
// Get the vector perpendicular to the one you're rotating and the axis. Since axis and v1proj
// are orthonormal, this one must be a unit vector perpendicular to all three.
Vector3 perp = VectorUtil.crossProduct(axis, v1proj);
// v2 is perpendicular to axis, so therefore it's already in the same plane as v1proj perp.
float cosAngle = VectorUtil.dotProduct(v1proj, v2);
float sinAngle = -VectorUtil.dotProduct(perp, v2);
return MathUtil.atan2(sinAngle, cosAngle);
}
}