Offset sprite along a path vector in LibGDX - java

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.

Related

Moving entity to location with pixels - libgdx - vector math

So I am making my bullets go to a point and travel further. Only its acting really really weird. Its like its thinking that the 0,0 location is at the top left instead of the bottom left.
This is the code:
float speed = 100;
Vector2 direction;
Vector2 thisPos = new Vector2(getX(), getY());
Vector2 mousePos;
public Bullet(){
super();
setSprite(sprite);
setX(0); setY(0);
float dx = Gdx.input.getX() - getX();
float dy = Gdx.input.getY() - getY();
mousePos = new Vector2(Gdx.input.getX(), Gdx.input.getY());
direction = new Vector2(dx, dy);
//sprite.setRotation(direction.angle(thisPos));
direction.nor();
}
public void update(){
Vector2 dirAd = new Vector2(direction);
thisPos.x += dirAd.x * speed * Gdx.graphics.getDeltaTime();
thisPos.y += dirAd.y * speed * Gdx.graphics.getDeltaTime();
setPosition(thisPos.x, thisPos.y);
super.update();
}
I hope someone can help me what I did wrong with this.
Gdx.input.getX() and getY() by definition do treat the top left as 0,0. From the getX() method:
"The screen origin is the top left corner."
You may need to look into the camera's unproject method, which takes the screen input coordinates and translates them to "world" space.

Rotation animation with custom view

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.

Java libgdx Shooting bullet at direction with constant speed

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.

Why isn't my method to rotate a point around another point working?

I have a method in my android app that looks like this:
//get point after rotation
public static PointF getRotatedPoint(PointF pt,PointF center, float degrees)
{
double angleInRadians = degrees * (Math.PI / 180);
pt.x = (float) (Math.cos(angleInRadians) * (pt.x-center.x) - Math.sin(angleInRadians) * (pt.y-center.y) + center.x);
pt.y = (float) (Math.sin(angleInRadians) * (pt.x-center.x) + Math.cos(angleInRadians) * (pt.y-center.y) + center.y);
return pt;
}
I have a rectangle that I rotate by 45 degrees. I can touch any point on the rotated rectangle and it gives me the touched point I want to get the coordinates of the point if the rectangle wasn't rotated. So I pass in -45 in the degrees argument. Here is how I call it:
getRotatedPoint(touchedPoint, centerOfRectangle,-45);
When I draw the point on the rectangle before it gets rotated, it gives me a result close to the position I touched on the rotated rectangle but off by a pretty big difference.
Here is a picture to explain my problem:
I think this might be a problem with my math so any answers are greatly appreciated.
You are mixing initial and final values in the calculations. You re-assign pt.x:
pt.x = (float) (Math.cos(angleInRadians) * (pt.x-center.x) - Math.sin(angleInRadians) * (pt.y-center.y) + center.x);
which doesn't immediately pose any problems. But the calculation for pt.y relies on the original value of pt.x, not the rotated value:
pt.y = (float) (Math.sin(angleInRadians) * (pt.x-center.x) + Math.cos(angleInRadians) * (pt.y-center.y) + center.y);
Thus just use some temporary variables to hold the initial values.
public static PointF getRotatedPoint(PointF pt,PointF center, float degrees)
{
double x0 = pt.x;
double y0 = pt.y;
double angleInRadians = degrees * (Math.PI / 180);
pt.x = (float) (Math.cos(angleInRadians) * (x0-center.x) - Math.sin(angleInRadians) * (y0-center.y) + center.x);
pt.y = (float) (Math.sin(angleInRadians) * (x0-center.x) + Math.cos(angleInRadians) * (y0-center.y) + center.y);
return pt;
}

Circle Arc Equation - Understanding speed?

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).

Categories