Reputation: 3799
Let's say I have a way from Point A to Point B (doesn't have to be a straight line). For the user experience I'd like to visualize the direction of the way so that the users know where they have to go.
Because I just started to learn OpenGL I'm looking for easy solutions to handle this problem.
I could easily draw a line between all the points via GL_LINE_STRIP
. But this is without any animation. For the animation I thought about using different colors for every vertex (similar, but not the same) so that I'm able to interpolate these colors between the points. Then I could change the color of the vertices every call to have a slightly different animation.
Is this a good approach or would you do something else? Should I stick to lines or should I implement a "Line-Class" made out of triangles?
How hard would it be to visualize a moving arrow along the path (or maybe the whole path out of arrows which are moving)?
Upvotes: 0
Views: 1627
Reputation: 3799
I've created the following animation class:
public class PathAnimation {
public static final int INFINITY = -1;
private static final float epsilon = 0.1f;
private Vector3D currentPosition;
private int nextVector = 1;
private Vector3D nextPathElement;
private List<Vector3D> pointsOnPath;
private long currentTime;
private float timeToFinish;
private float pathLength;
private boolean isFinished = false;
private boolean isRunnning = false;
private int repeatCount = 1;
private int currentRepeatCount = 0;
public PathAnimation(List<Vector3D> pointsOnPath, float timeToFinish,
int repeatCount) {
this.pointsOnPath = pointsOnPath;
this.timeToFinish = timeToFinish;
this.repeatCount = repeatCount;
this.nextVector = 0;
this.currentPosition = pointsOnPath.get(nextVector++);
this.nextPathElement = pointsOnPath.get(nextVector);
this.currentTime = new Date().getTime();
calculatePathLength();
}
private void calculatePathLength() {
float distance = 0.0f;
for (int i = 0; i < pointsOnPath.size() - 1; i++) {
distance += Vector3D.getDistance(pointsOnPath.get(i),
pointsOnPath.get(i + 1));
}
this.pathLength = distance;
}
/**
* @return float[](yaw,pitch,x,y,z)
*/
public float[] animate() {
if (isRunnning) {
long timeNow = new Date().getTime();
long delta = timeNow - currentTime;
currentTime = timeNow;
if (!isFinished) {
double dX = nextPathElement.getX() - currentPosition.getX();
double dY = nextPathElement.getY() - currentPosition.getY();
double dZ = nextPathElement.getZ() - currentPosition.getZ();
float yaw = (float) Math.atan2(dY, dX);
float pitch = (float) (Math.atan2(Math.sqrt(dX * dX + dY * dY),
dZ) + Math.PI);
float pathToWalk = pathLength / timeToFinish * delta;
float distanceToNextPathElement = Vector3D.getDistance(
currentPosition, nextPathElement);
while (pathToWalk > distanceToNextPathElement - epsilon) {
currentPosition = nextPathElement;
pathToWalk -= distanceToNextPathElement;
nextVector++;
if (nextVector >= pointsOnPath.size()) {
isFinished();
return null;
}
nextPathElement = pointsOnPath.get(nextVector);
distanceToNextPathElement = Vector3D.getDistance(
currentPosition, nextPathElement);
}
float percentageToWalk = pathToWalk / distanceToNextPathElement;
float newX = (nextPathElement.getX() - currentPosition.getX())
* percentageToWalk;
float newY = (nextPathElement.getY() - currentPosition.getY())
* percentageToWalk;
float newZ = (nextPathElement.getZ() - currentPosition.getZ())
* percentageToWalk;
Vector3D difference = new Vector3D(newX, newY, newZ);
currentPosition = new Vector3D(currentPosition);
currentPosition.add(difference);
pathToWalk = 0;
return new float[] { (float) Math.toDegrees(yaw),
(float) Math.toDegrees(pitch), currentPosition.getX(),
currentPosition.getY(), currentPosition.getZ() };
}
}
return null;
}
private void isFinished() {
currentRepeatCount++;
if (repeatCount == INFINITY || currentRepeatCount < repeatCount) {
reset();
} else {
isFinished = true;
}
}
public void start() {
this.currentTime = new Date().getTime();
isRunnning = true;
}
public void pause() {
isRunnning = false;
}
public void reset() {
this.isFinished = false;
this.nextVector = 0;
this.currentPosition = pointsOnPath.get(nextVector++);
this.nextPathElement = pointsOnPath.get(nextVector);
this.currentTime = new Date().getTime();
}
}
What do you think? I can now initialize the class and call it in the arrow-class to get the newest translation and rotation :)
Edit: The Problem is that I just have one Path-Element at the same time... If I want to have more than one element this structure is probably too heavy... Or does someone know how to change this class to have animations with for example an offset (distance value for example). ?
Upvotes: 1
Reputation: 162327
Drawing a line or a path made from line segments is trivial and what you proposed would work. However instead of (varying) colors I'd use a 1D texture containing some pattern and then smoothly advance the texture coordinates along the line to give the impression of movement.
When it comes to drawing arrows you've to make the arrowheads point along the direction of the path. The direction of a line, curved or straight, is given by the partial derivatives (= gradient) of the parametric function describing it. Say your line is described by the function
l(t) = a + (b-a)·t
which describes a straight line from a to b. Applying the derivative yields
∂_x,y,z/∂_t l(t)
= ∂_x,y,z/∂_t (a + (b-a)·t)
= ∂_x,y,z/∂_t a + ∂/∂_x,y,z (b-a)·t
= ∂_x,y,z/∂_t v · t ; where v = b - a
= v_x,y,z
For curved path's it get's a little tricker, but you can use a numerical approximation. Once you have the direction you can use that to build your local frame in which to draw the arrow. In https://github.com/datenwolf/codesamples/blob/master/samples/OpenGL/frustum/frustum.c you can find a function draw_arrow
that draws a screen aligned arrow from points a to b in the space as transformed to viewspace by the current modelview matrix (yes, it's fixed function).
Upvotes: 1