I have a custom View, IndicatorView, which is essentially a triangle that orients itself according to a specified angle of a circle with a radius equal to the triangle's length. The angle the triangle points to is frequently updated and I would like to animate between these two positions similar to how a hand on a clock moves. Below is an illustration of my custom view (not drawn proportionally or to scale; drawn according to the Android View coordinate plane):
In the IndicatorView class, I draw the triangle using a Path object and three PointF objects:
#Override
protected void onDraw(Canvas canvas){
path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
//a, b, and c are PointF objects
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.close();
canvas.drawPath(path, paint);
}
To calculate the different points, given the angle, I use parametric equations:
public void showAngle(){
//x = centerX + radius * cos(angle)
//y = centerY + radius * sin(angle)
//TODO sloppy; tidy up / optimize once finished
//centerX, centerY, length, and bottomWidth are all values
//calculated in onSizeChanged
a = new PointF((float) (centerX + (length * Math.cos(angle))), (float) (centerY + (length * Math.sin(angle))));
//perpendicular bilateral radius
double pRadius = bottomWidth / 2;
//perpendicular angle plus or minus 90 degrees depending on point
float pAngle = angle - 90;
pAngle = (pAngle < 0) ? 360 - Math.abs(pAngle) : pAngle;
pAngle = (pAngle > 360) ? pAngle % 360 : pAngle;
b = new PointF((float) (centerX + (pRadius * Math.cos(pAngle))), (float) (centerY + (pRadius * Math.sin(pAngle))));
pAngle = angle + 90;
pAngle = (pAngle < 0) ? 360 - Math.abs(pAngle) : pAngle;
pAngle = (pAngle > 360) ? pAngle % 360 : pAngle;
c = new PointF((float) (centerX + (pRadius * Math.cos(pAngle))), (float) (centerY + pRadius * Math.sin(pAngle)));
invalidate();
}
When I have a new angle, I use an ObjectAnimator to animate between the two angles. I place an AnimatorUpdateListener on the ObjectAnimator and call my showAngle() method in my IndicatorView using the intermediate values specified from the Animator:
public void updateAngle(float newAngle){
//don't animate to an angle if the previous angle is the same
if(view.getAngle() != newAngle){
if(anim != null && anim.isRunning()){
anim.cancel();
}
anim = ObjectAnimator.ofFloat(view, "angle", view.getAngle(), newAngle);
anim.setDuration(duration);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
if(view != null){
view.showAngle();
}
}
});
}
}
However, this code produces some strange and unexpected behavior:
The width size of the triangle changes somewhat drastically. This could be due to casting between different types but it shouldn't be that dramatic.
The point of the triangle never stops at the specified angle. Instead it just keeps moving in a circle.
The angle seems to dictate the animations speed rather than where the triangle should stop.
Sometimes it seems as though there are numerous triangles on the screen. This could be due to the speed, perhaps it's moving very fast.
Obviously, somewhere along the line my calculations must be incorrect, though, I'm struggling to find out where I went wrong. Question(s): Is there a more efficient way of getting my custom view to animate rotation to a given angle? If I am approaching this correctly, where am I going wrong?
So, the solution to my problem was rather simple but simple enough to be overlooked. The angle field that was being used for the calculations was in degrees and it just had to be converted to radians in order for it to work with the sin and cos methods.
Change all PointF instantiations, for instance:
a = new PointF((float) (centerX + (length * Math.cos(angle))), (float) (centerY + (length * Math.sin(angle))));
to use the angle in radians:
a = new PointF((float) (centerX + (length * Math.cos(Math.toRadians(angle))),
(float) (centerY + (length * Math.sin(Math.toRadians(angle)))));
Also, part of the problem was due to sound constantly being analyzed and the View being updated before the previous animation had time to render a few frames. This led to the IndicatorView hardly moving when the angle was being updated often and when it was not it would quickly move to its destination. This happens because the previous animation is canceled before another animation is set (which is necessary to prevent a delay). This is a tricky problem to fix but one optimization I found was to avoid starting a new animation if the current angle and the previous angle were relatively close to each other.
Hopefully this will be useful for someone stuck with a similar problem. This was all part of a guitar tuner project I was working on and the source can be found on GitHub.
Related
I am trying to draw lines on android canvas by calculating the last point (X,Y) given a point (Xo,Yo), the distance and the angle. A diagram is illustrated below:
I am calculating the azimuth orientation angle degree using the below formula from magnetic sensor and accelerometer values
if (accelValues != null && magnetValues != null) {
float rotation[] = new float[9];
float orientation[] = new float[3];
if (SensorManager.getRotationMatrix(rotation, null, accelValues, magnetValues)) {
SensorManager.getOrientation(rotation, orientation);
float azimuthDegree = (float) (Math.toDegrees(orientation[0]) + 360) % 360;
orientationDegree = Math.round(azimuthDegree);
}
}
I am saving all lines in an array and afterwhich I am calling onDraw to repaint the canvas. Below are my codes for the onDraw and calculating the steps so it redraw the lines at each step the user is taking depending on his orientation. (Assuming the distance length is 60)
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
drawRotateImage(canvas);
canvas.drawPoint(Position.x_axis, Position.y_axis, paint);
for (Line l : listLine) {
canvas.drawLine(l.StartX, l.StartY, l.EndX, l.EndY, paint);
}
private void stepDetector () {
l = new Line();
l.setStartX(lastX);
l.setStartY(lastY);
l.setOrientationDegree(orientationDegree);
lineX = (float) (lastX + (60 * cos(orientationDegree)));
lineY = (float) (lastY + (60 * sin(orientationDegree)));
l.setEndX(lineX);
l.setEndY(lineY);
listLine.add(l);
System.out.println ("" + l.toString());
invalidate();
lastX = lineX;
lastY = lineY;
}
Problem I am facing is that the lines are not being drawn with the correct orientation. It is going in any direction irrespective of the orientation direction. I get something as shown below:
To what I have analyzed, the line are not being drawn accurately, given the orientationdegree. I believe it is something, relating to the Android Coordinate System. It would be grateful if someone can help me on this to calculate the exact orientation degree in any direction [0-360].
Is there a different equation for calculating the last point (X,Y) in each quadrant of the circle axis ?
I believe that the behaviour you are observing is due to the fact that your azimuth is in degrees whereas the trig functions expect radians. You could convert from degrees back to radians using Math.toRadians. However, I would stick to using radians everywhere and do away with calling Math.toDegrees (and stop rounding too).
I currently have a sprite following a path...all is working well from a steering perspective, however I am trying to make it so the center of the sprite tracks along the path, rather than the corner (0,0) tracing along the path. Essentially, I would like the center of the fish to follow the line.
Below is an image of what I have implemented, and what I would like to achieve.
The core implementation of this mechanic lies within my update() method; as follows;
private void update(float deltaTime) {
float angle = (float) Math.atan2(path.get(waypoint).y - getY(), path.get(waypoint).x - getX());
velocity.set((float) Math.cos(angle) * speed, (float) Math.sin(angle) * speed);
velocity_normal = new Vector2(-velocity.y, velocity.x).nor();
setPosition(
getX() + (velocity.x * deltaTime),
getY() + (velocity.y * deltaTime)
);
setRotation(angle * MathUtils.radiansToDegrees);
if(isWayPointReached()){
setPosition(path.get(waypoint).x, path.get(waypoint).y);
if(waypoint + 1 >= path.size){
waypoint = 0;
} else {
waypoint++;
}
}
}
In particular the setPosition call. My initial thoughts were to calculate the normal vector to the velocity vector, normalize, and multiply the x and y components respectively by the fish height... to my mind this would offset the fish by its height (150px). The code attempted is as follows;
velocity_normal = new Vector2(-velocity.y, velocity.x).nor();
setPosition(
getX() + (velocity.x * deltaTime) + velocity_normal.x * getHeight() * deltaTime,
getY() + (velocity.y * deltaTime) + velocity_normal.y * getHeight() * deltaTime
);
The results in some odd behavior, the fish gets progressively further from the line, it seems the vector is getting compounded and added each frame.
I have also tried to update the normal vector once each way-point has been reached, however this does not work either.
I think the above logic is correct, however have I made a fundamental error in my vector maths?
Your assistance would be greatly appreciated.
EDIT:
Added to constructor:
setPosition(
path.get(waypoint).x - 0.5f * getWidth() ,
path.get(waypoint).y - 0.5f * getHeight()
);
Amended update() method;
private void update(float deltaTime) {
float angle = (float) Math.atan2(path.get(waypoint).y - getY(), path.get(waypoint).x - getX());
velocity.set((float) Math.cos(angle) * speed, (float) Math.sin(angle) * speed);
Vector2 velocity_normal = new Vector2();
velocity_normal.set(velocity).nor().scl( speed * deltaTime ); // multiply the speed to scale the unit vector up
translate( velocity_normal.x, velocity_normal.y );
setRotation(angle * MathUtils.radiansToDegrees);
if(isWayPointReached()){
setPosition(path.get(waypoint).x, path.get(waypoint).y);
if(waypoint + 1 >= path.size){
waypoint = 0;
} else {
waypoint++;
}
}
}
Note the omission of the setPosition call, and the replacement with;
Vector2 velocity_normal = new Vector2();
velocity_normal.set(velocity).nor().scl( speed * deltaTime );
translate( velocity_normal.x, velocity_normal.y );
How would I influence the pointA / pointB as mentioned below?
Thanks.
The Sprite position is from the left bottom corner and the origin (where the sprite rotates around) is already set to the center of the sprite. So only the local offset is wrong. You have to substract half the size from the position and then the sprite can move relatively from that offset.
Where you set the sprite:
setPosition(
path.get(waypoint).x - 0.5f * getWidth(),
path.get(waypoint).y - 0.5f * getHeight()
);
In the update method. Because you are adding the velocity every frame you can translate the sprite.
void update(float deltaTime){
// directional global vector
Vector2 velocity = tmp.set(path.get(waypoint)).sub(path.get(waypoint - 1)).nor().scl(speed * deltaTime);
// reference angle is relative to the right vector(1, 0)
float angle = velocity.angle();
setRotation(angle);
translate(velocity.x, velocity.y);
if (isWayPointReached()){
setPosition(
path.get(waypoint).x - 0.5f * getWidth(),
path.get(waypoint).y - 0.5f * getHeight()
);
if(waypoint + 1 >= path.size){
waypoint = 1;
} else {
waypoint++;
}
}
}
There is setCenter() method in Sprite class:
https://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/g2d/Sprite.html#setCenter-float-float-
Try setting center like that instead of calculating it additionally.
after creating a very simple animation using libGDX i have some questions I'd like to clarify in order to make sure I understand everything prior to start with any other development with more complexity.
I have a box created like this:
public void createBaseCube(Model modelCube) {
ModelBuilder modelBuilder = new ModelBuilder();
modelCube = modelBuilder.createBox(1f, 1f, 1f,
new Material(ColorAttribute.createDiffuse(Color.GREEN)),
VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal);
this.cubeInstance.transform.translate(0.5f, 0.5f, 0.5f);
}
As it is centered in position (0,0,0) and a want its corner to be allocated in (0, 0, 0) I applied the translation in last line.
Here is how it looks like (I added a set of tiles to have some reference):
Afterwards, I create the animation. I want to overturn the box, so it will be positioned over the white tile, and rotating over its bottom-right edge
public class CubeAnimation {
...
<<definition of attributes>>
...
public CubeAnimation (ModelInstance cubeModel, float fromAngle, float toAngle, float speed, float alpha){
this.cubeInstance = cubeModel;
this.fromAngle = fromAngle; //value set to 0
this.toAngle = toAngle; //value set to 90f
this.speed = speed; //value set to 1f
this.alpha = alpha; //value set to 0
}
public void update(float delta) {
alpha += delta * speed;
if (alpha >= 1f) {
finished =true;
return;
}
float angle = alpha * (toAngle - fromAngle);
fromAngle = angle;
Vector3 t = new Vector3(0.5f, -0.5f, 0);
cubeInstance.transform.idt().translate(t).rotate(Vector3.Z, -angle).translate(t.scl(-1));
}
Everything seems to be fine, and code is quite simple, BUT (and here are the issues) when applying the animation, the box is translated to the center again (so first translate when box was created is undone), and -surprise- although I'm passing 90f as parameter to the animation, cube only rotates 45 degrees (when I set 180, it rotated as expected: 90).
Here how it looks like after the animation:
What is wrong here? Thanks in advance for your help!
You want to rotate the cube from angle fromAngle to angle toAngle
You are attempting to do so gradually by calculating the percentage completed over time, stored in your alpha variable.
alpha += delta * speed;
if (alpha >= 1f) {
finished =true;
return;
}
This part is fine for calculating the percentage as an angular-velocity multiplied by time passed. ie
angle_percentage/seconds * seconds_passed
You then get the distance between the start and stop angles in this line
float angle = alpha * (toAngle - fromAngle);
This code works for a starting angle of 0, but will fail for non zero starting points. The equation for a line is y = mx + b, so to correct this, you should include the b value:
float angle = fromAngle + alpha * (toAngle - fromAngle);
This will start the animation at fromAngle and push it over the distance required.
The extra line fromAngle = angle; changes your starting location on every iteration, so you end up with an unpredictable animation, which will be different depending on the speed you choose... I'm fairly certain the factor of two is merely a coincidence ;)
Finally this loop ends when the value is set to 100%, but never actually updates to 100%. Try this instead.
public void update(float delta) {
alpha += delta * speed;
if (alpha <= 1f) {
float angle = fromAngle + alpha * (toAngle - fromAngle);
Vector3 t = new Vector3(0.5f, -0.5f, 0);
cubeInstance.transform.idt().translate(t).rotate(Vector3.Z, -angle).translate(t.scl(-1));
} else {
finished = true;
}
}
I am trying to get my bullets to fire towards (input coords) at a constant speed.
So far I was able to get it to shoot at the direction but the farther I click (touch, android game) the faster the bullet goes. I have tried different methods by scaling but failed miserably, I have started coding just a month ago and using this as a project to increase my knowledge of how things work before I work on a full game but having too much trouble with this.
This is what I have been using to get the bullet to move towards the direction I want it to, the codes with // in front were other samples I got while browsing through the internet in hopes of getting what I wanted. I have thought of not using velocity to set the direction, but I have no clue of another method for this.
EDIT: All in short, I cannot get all the bullets to move in the same speed, farther I click, higher velocity bullet has.
Any help guys? Thanks a bunch
Player Class :
public void update(float delta) {
if (Gdx.input.isTouched()) {
if (System.currentTimeMillis() - lastShot >= FIRE_RATE) {
bullets.add(new Bullet(position.x + 6,position.y + 6,4,4,Gdx.input.getX() / 2,Gdx.input.getY() / 2));
lastShot = System.currentTimeMillis();
}
}
for (int i=0;i<bullets.size();i++) {
bullets.get(i).update(delta);
}
}
Bullet Class :
public Bullet(float x, float y, int width, int height, float targetX, float targetY) {
this.width = width;
this.height = height;
position = new Vector2( x , y );
velocity = new Vector2( 0 , 0 );
velocity.set(targetX - position.x,targetY - position.y);
//velocity.set(targetX - position.x, targetY - position.y).nor().scl(Math.min(position.dst(targetX, targetY), speedMax));
}
public void update(float deltaTime) {
//position.add(position.x + speedMax * deltaTime * ax,position.y + speedMax * deltaTime * ay);
position.add(velocity.x * deltaTime, velocity.y * deltaTime);
//velocity.scl(1 - (0.98f * deltaTime));
// Linear dampening, otherwise the ball will keep going at the original velocity forever
}
Well, normalizing vectors should be rather straightforward. Take your components, square them, and add them together (pythagorean theorem) and then divide each component by this result. I.e. vX = (targetX - position.x)/Math.sqrt(((targetX - position.x) * (targetX - position.x)) + ((targetY - position.y) *(targetY - position.y )))
Then you can multiply vX by some constant, and do the same for a vY and then set your velocity.
I have a circle being drawn at a certain position. I can move it just fine with speed set to 10f but when it starts to circle it becomes extremely fast. Its obviously not moving at (units/second) I'm not sure whats going on. I thought that the archSpeed needed to be in radians or something, that slowed it down - still not right though.
Here's the Circle Arc Equation I'm basing off of:
s = r * theta
Here are the functions I'm using:
private void moveOut(double deltaTime)
{
SetPosition(x += direction * speed * deltaTime, y, 0);
if (x - (direction * GetWidth() / 2f) >= centerX + radius + GetWidth() / 2f)
{
//onOutside = true;
}
Log.d(TAG, "moving out");
}
private void circleCenter(double deltaTime)
{
float angleSpeed = (float) (radius * (speed * Math.PI / 180) * deltaTime);
currentAngle += angleSpeed;
if (currentAngle >= 2 * Math.PI)
{
currentAngle = (float) (2 * Math.PI - currentAngle);
}
SetPosition(centerX + radius * FloatMath.cos(currentAngle), centerY + radius * FloatMath.sin(currentAngle), 0);
}
Your angleSpeed formula looks wrong.
I'd work it out first by saying What is the distance I travel in that time. The answer as you already know is speed*deltaTime. Now you have a distance you can work out the angle by using the arc forumla that says arclength = radius*angle. So angle = arclength/radius.
Put these two together to get
angle = speed*deltaTime/radius
This will be in radians of course.
Essentially this boils down to the fact you were multiplying by radius instead of dividing by it (looking at it in terms of units would have helped spot this but that is outside the scope of a programming forum).