Related
Can I get a clarification on how to figure out the point in which 2 line segments intersect?
I have this code in which I am creating a pong game but I am having trouble with figuring out where the ball intersects with the paddle and if so basically bounce it back. I know some basic algebra is needed but for the sake of my code what is needed?
I have the current position of the ball, the old position of the ball, and the paddles current position (ballX,Y , oldBallX,Y , etc) What would I have to do to get the (x, y) intersection point for the collision of the ball on the paddle?
CODE
`
package com.example.pong;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.Region;
import android.media.MediaPlayer;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
public class PongView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private static final String TAG = "";
private final GameActivity gameActivity;
private Thread _thread;
private int screenWidth; // screen width
private int screenHeight; // screen height
Context _context;
private SurfaceHolder _surfaceHolder;
private boolean _run = false;
// All pong variables go in here
// Right Paddle
private float rPaddle; // variable set by touch event
// Left Paddle
private float lPaddle; // variable set by touch event
// Paddle variables
private int halfPaddle = 50;
private final static int MAX_FPS = 30; //desired fps
private final static int FRAME_PERIOD = 1000 / MAX_FPS; // the frame period
private Paint background = new Paint();
private Paint dark = new Paint();
private float ballX = 500.0f; // ball location
private float ballY = 250.0f;
/// pongSpeed variables
public float pongSpeed = 1.0f;
private long mLastTime = Long.MAX_VALUE;
private float timeElapsed; // Time since last frame in seconds
// The speed in pixels per second
private float ballSpeedX = 300.0f;
private float ballSpeedY = 300.0f;
// Variables for current pong position
private float oldBallX = 0.0f;
private float oldBallY = 0.0f;
private float xPos = 0.0f;
private float yPos = 0.0f;
private float xDir = 1.0f;
private float yDir = 1.0f;
//
public PongView(Context context) {
super(context);
_surfaceHolder = getHolder();
getHolder().addCallback(this);
this.gameActivity = (GameActivity) context;
_context = context;
setFocusable(true);
setFocusableInTouchMode(true);
}
//
public void setRPaddle(float rp) {
synchronized (_surfaceHolder) {
rPaddle = rp;
}
}
public void setLPaddle(float lp) {
synchronized (_surfaceHolder) {
lPaddle = lp;
}
}
#Override
public void run() {
float avg_sleep = 0.0f;
float fcount = 0.0f;
long fps = System.currentTimeMillis();
Canvas c;
while (_run) {
c = null;
long started = System.currentTimeMillis();
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
// Update game state
update();
}
// draw image
drawImage(c);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
float deltaTime = (System.currentTimeMillis() - started);
int sleepTime = (int) (FRAME_PERIOD - deltaTime);
if (sleepTime > 0) {
try {
_thread.sleep(sleepTime);
}
catch (InterruptedException e) {
}
}
}
}
public void pause() {
_run = false;
boolean retry = true;
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
public void initialize(int w, int h) {
screenWidth = w;
screenHeight = h;
// create paints, rectangles, init time, etc
background.setColor(0xff200040); // should really get this from resource file
dark.setColor(0xffdddddd);
}
protected void update() {
// game update goes here
int width = getWidth();
int height = getHeight();
MediaPlayer pip = MediaPlayer.create(_context.getApplicationContext(), R.raw.lil_pip);
long now = System.currentTimeMillis(); // Current time
if (now > mLastTime)
{
timeElapsed = (now - mLastTime) / 1000.0f;
}
mLastTime = now;
ballX += ballSpeedX * timeElapsed;
ballY += ballSpeedY * timeElapsed;
oldBallX = ballX;
oldBallY = ballY;
collisionCheck();
//Treat paddle as line segment. Treat ball positions as points.
//Change ball direction if the two line segments intersect.
}
protected void collisionCheck()
{
// Bounce right side
if ((ballX > screenWidth) && (ballSpeedX > 0.0f))
{
ballSpeedX *= -1.0f;
pip.start();
}
// Bounce left side
// Not sure if using "80 *" is the best
if ((80 * ballX < screenHeight) && (ballSpeedX < 0.0f))
{
ballSpeedX *= -1.0f;
pip.start();
}
// Bounce bottom side
if ((ballY > screenHeight) && (ballSpeedY > 0.0f))
{
ballSpeedY *= -1.0f;
pip.start();
}
// Bounce top side
// Not sure if using "80 *" is the best
if ((80 * ballY < screenHeight) && (ballSpeedY < 0.0f))
{
ballSpeedY *= -1.0f;
pip.start();
}
Log.d("TAG", "Ball is moving");
}
private void rightPaddle (Canvas canvas)
{
canvas.drawRect(7 * screenWidth / 8, rPaddle * screenHeight + halfPaddle,
7 * screenWidth / 8 + 15, rPaddle * screenHeight - halfPaddle, dark);
}
private void leftPaddle (Canvas canvas)
{
canvas.drawRect( screenWidth / 8, lPaddle * screenHeight + halfPaddle,
screenWidth / 8 + 15, lPaddle * screenHeight - halfPaddle, dark);
}
private void pongBall (Canvas canvas)
{
canvas.drawRect(ballX, ballY, ballX + 10, ballY + 10, dark);
}
protected void drawImage(Canvas canvas) {
if (canvas == null) return;
// Draw commands go here
// Draw commands go here
canvas.drawRect(0, 0, getWidth(), getHeight(), background);
pongBall(canvas);
rightPaddle(canvas);
leftPaddle(canvas);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
screenWidth = w;
screenHeight = h;
super.onSizeChanged(w, h, oldw, oldh);
initialize(w, h);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
_run = true;
_thread = new Thread(this);
_thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// simply copied from sample application LunarLander:
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
_run = false;
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// we will try it again and again...
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int numPointers = event.getPointerCount();
int ptrIdx = 0;
while (ptrIdx < numPointers) {
int id = event.getPointerId(ptrIdx);
float xp = event.getX(ptrIdx) / screenWidth;
if (xp > 0.6) {
setRPaddle(event.getY(ptrIdx) / screenHeight);
} else if (xp < 0.4) {
setLPaddle(event.getY(ptrIdx) / screenHeight);
}
if ((ptrIdx > 0) || (id > 0)) {
Log.i(TAG, " Feel the power..." + ptrIdx + " " + id);
}
ptrIdx++;
}
return true;
}
}
`
I've tried making the paddles into Path's but my confusion is what exactly am I using for x1, x2, y1, y2 to find out the point of intersection.
You have to know some basic algebra knowledge. A line can be written in the form of Ax + By + C = 0 in a plain. (See Wikipedia-Line)
To calculate the intersect, you may first calculate the equation of the two lines.
A line passing from (x1, y1) and (x2, y2) can be written in the form of (y1 - y2) * x + (x2 - x1) * y + (x1 * y2 - x2 * y1) = 0. This way, you can calculate the equation of the two lines.
Now, you get the equation of the two lines. Say A1 * x + B1 * y + C1 = 0 and A2 * x + B2 * y + C2 = 0. Then the x-coordinate of the intersect will be (C2 * B1 - C1 * B2) / (A1 * B2 - A2 * B1), and the y-coordinate will be (C1 * A2 - C2 * A1) / (A1 * B2 - A2 * B1). Thus, you get the intersect.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 9 months ago.
Improve this question
I have a tank game where you rotate via A/D keys and move forward/backward with W/S. The turret follows the mouse cursor on the screen. I am attempting to get shooting to work when I click. I get the initial degree of the turret rotation on click, as well as the point at the end of the turret. I set isShooting() to check if I am currently shooting at the given moment, and it is set to true on click. I use shotFired() also set to true on click, to tell whether there is a shot on the screen.
I want to have the bullet moving along a straight line with a give slope every time I click, from the end of the barrel to off the screen.
Currently the bullet moves in the wrong directions and is influenced by movement of the turret. I have been working to find the issue and cannot find it.
public void update() {
playerTank.setCenterTurret(new Point2D.Double(playerTank.xPos() + 67, playerTank.yPos() + 125));
playerTank.setEndTurret(new Point2D.Double(playerTank.xPos() + ((Math.sin(Math.toRadians(playerTank.getTurretDegree())))) + 63, playerTank.yPos() + ((Math.cos(Math.toRadians(playerTank.getTurretDegree())))) - 25));
playerTank.setCenterBase(new Point2D.Double(playerTank.xPos() + (playerTank_PNG_WIDTH / 2), playerTank.yPos() + (playerTank_PNG_HEIGHT / 2)));
mouseLoc = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(mouseLoc, this);
mouseLocX = (int) mouseLoc.getX();
mouseLocY = (int) mouseLoc.getY();
mouseDistX = mouseLocX - playerTank.getCenterTurret().getX();
mouseDistY = mouseLocY - playerTank.getCenterTurret().getY();
mouseDegree = angleInRelation(mouseLoc, playerTank.getCenterTurret());
if(moveUp) {
playerTank.setLocation(playerTank.xPos() + (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() - MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(moveDown) {
playerTank.setLocation(playerTank.xPos() - (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() + MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(rotateLeft && playerTank.xPos() >= 0) {
playerTank.setBaseDegree(playerTank.getBaseDegree() - 5);
}
if(rotateRight && playerTank.xPos() + playerTank.getWidth() <= FRAME_WIDTH) {
playerTank.setBaseDegree(playerTank.getBaseDegree() + 5);
}
mouseDegree -= playerTank.getBaseDegree();
this.setBackground(Color.white);
repaint();
}
#Override
public void paint(Graphics g) {
this.setBackground(Color.white);
Graphics2D g2D = (Graphics2D) g;
g2D.setBackground(Color.white);
g2D.setColor(Color.white);
g2D.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
paintBase(g2D);
playerTank.setTurretDegree(mouseDegree);
g2D.rotate(Math.toRadians(playerTank.getTurretDegree()), playerTank.xPos() + 67, playerTank.yPos() + 125);
paintTurret(g2D);
paintBullet(g2D);
}
public void paintBullet(Graphics2D g2D) {
g2D.setColor(Color.black);
if(playerTank.isShooting()) {
playerTank.setBulletPos(playerTank.getEndTurret());
g2D.fillRect((int) playerTank.getEndTurret().getX(), (int) playerTank.getEndTurret().getY(), 8, 18);
playerTank.setShooting(false);
}
if (playerTank.shotFired()) {
double newX = (BULLET_SPEED * (Math.sin(Math.toRadians(playerTank.getInitialTurretDegree()))));
double newY = (BULLET_SPEED * (Math.cos(Math.toRadians(playerTank.getInitialTurretDegree()))));
playerTank.setBulletPos(playerTank.getBulletX() + newX, playerTank.getBulletY() - newY);
g2D.fillRect((int) playerTank.getBulletX(), (int) playerTank.getBulletY(), 8, 18);
if((playerTank.getBulletX() > FRAME_WIDTH || playerTank.getBulletY() > FRAME_HEIGHT) || (playerTank.getBulletX() < 0 || playerTank.getBulletY() < 0)) {
playerTank.setShotFired(false);
}
}
Thank you so much.
So, your problem basically boils down to a series of trigonometry problems ... yea (can you feel the sarcasm 😜).
But what do I mean? So, you want to find the end point of the barrel, based on it's current angle of rotation? Well, you will need to know the length of barrel, this will give you a radius around which the barrel can travel, from that you can simply perform a "point on a circle" calculation.
Want to know the path of the projectile, calculate the "point on a circle" based on a radius greater than the visible area and then draw a line between the barrel and this point, that's your path. Told you, trigonometry.
So, I started by creating a simple entity which would end up looking something like (with color guides)...
The turret and the body are seperate images, but when drawn on top of each other, they will appear as above, this is really important, as this saves so much effort.
The turret then has a specified radius and can rotate easily around the body
If you look really hard, the turret is actually not centered around the "natural" center of the image, but by laying it out this way, we can easily rotate around the mid point of the image - so much easier.
Okay, that all sounds fun, let's start with a basic concept of our "tank" entity...
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
The important things to note here are...
The x/y position of the tank represents it's "center" point
The body and turret can rotate independently of each other
Now, we can make the turret look at the mouse by using a MouseMotionListener and, you guessed it, some more trigonometry
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
}
g2d.dispose();
}
}
The "core" functionality is in the Timer and looks like...
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
It's important to note that the Graphics API and Math API have a different concept of 0 (yea).
Okay, but how do we find the projectile path?! Well, more trigonometry!
But first, we need some help...
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
So, all this does is provide some basic "point on circle" calculations. From this, we can calculate the "point" in the "world" we're looking for...
So, we can add...
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
to our Tank entity, this will return the "world" coordinates that represents the end of the barrel.
Let's add some "debug" graphics to our paintComponent...
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
This will add...
A guide which represents the target circle created by the mouse
A projectile line from the barrel to the mouse
A projectile line from the barrel to outside the visible area
You might be surprised that this actually now provides the answer to the question. You have the start point and end point of the projectile, now you just need some way to animate it...
When it comes to animation, I tend to prefer time based animations, but since the projectile might travel a variable distance, what we really need is a linear progression, so if it was to travel a long distance or a short distance, it would travel at the same speed.
Now, I banged by head against Google for a bit and was able to come up with...
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
This will basically calculate a x/y delta which needs to be applied to move the projectile from the current point to it's target point using a constant speed.
Now we can add a MouseListener...
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
Update the projectiles in our main loop...
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
And update our paintComponent...
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new GamePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
private List<Projectile> projectiles = new ArrayList<>(8);
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
g2d.dispose();
}
}
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
}
I want to move a sprite between two points.
first point is x=0 and y=0 and the second point is a point where the user touched the screen.
To move, I want to use an equation to go through the two points which is y=ax+b.
I have attempted this in the method move() but the sprite does not move.
Please help.
Class Display:
package com.example.name;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Display extends SurfaceView {
private Bitmap bmp;
private SurfaceHolder holder;
private GameLoopThread gameLoopThread;
private Sprite sprite;
private long lastClick;
private float x = 0.0F;
private float y = 0.0F;
public Display(Context context) {
super(context);
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.gonia);
sprite = new Sprite(this, bmp, x, y, x, y);
gameLoopThread = new GameLoopThread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
sprite.onDraw(canvas);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (System.currentTimeMillis() - lastClick > 500) {
lastClick = System.currentTimeMillis();
x = (float) sprite.ostatniaWartoscX();
y = (float) sprite.ostatniaWartoscY();
float gotox = (float) event.getX();
float gotoy = (float) event.getY();
synchronized (getHolder()) {
sprite = new Sprite(this, bmp, x, y, gotox, gotoy);
}
}
return true;
}
}
Class Sprite:
package com.example.name;
import android.graphics.Bitmap;
import android.graphics.Canvas;
public class Sprite {
private float a;
private float b;
private float x;
private float y;
private float gotox;
private float gotoy;
private int executeMove = 0;
private Display display;
private Bitmap bmp;
public Sprite(Display display, Bitmap bmp, float x, float y, float gotox,
float gotoy) {
this.display = display;
this.bmp = bmp;
this.x = x;
this.y = y;
this.gotox = gotox;
this.gotoy = gotoy;
}
void update() {
if (x < gotox) {x++;executeMove = 1;}
if (x > gotox) {x--;executeMove = 1;}
if (executeMove == 1) {move();}
executeMove = 0;
}
void move() {
float x1 = x;
float y1 = y;
float x2 = gotox;
float y2 = gotoy;
a = (y2-y1)/(x2-x1);
b = y1 - x1*a;
y = x1 * a + b;
}
public float ostatniaWartoscX() {
return x;
}
public float ostatniaWartoscY() {
return y;
}
public void onDraw(Canvas canvas) {
update();
canvas.drawBitmap(bmp, x, y, null);
}
}
Thank You!
You need Bresenham's line algorithm to move your player. You can even cut it short that you only need to calculate the next movement (not the whole line). It's a simple/easy and low-calorie-algorithm.
You must adjust it to your needs.
public static ArrayList<Point> getLine(Point start, Point target) {
ArrayList<Point> ret = new ArrayList<Point>();
int x0 = start.x;
int y0 = start.y;
int x1 = target.x;
int y1 = target.y;
int sx = 0;
int sy = 0;
int dx = Math.abs(x1-x0);
sx = x0<x1 ? 1 : -1;
int dy = -1*Math.abs(y1-y0);
sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for(;;){ /* loop */
ret.add( new Point(x0,y0) );
if (x0==x1 && y0==y1) break;
e2 = 2*err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
return ret;
}
Here is an example of code I use to move an object between two points.
Math.atan2 calculates the theta angle (-pi to +pi) which is the trajectory the object needs to travel. Delta is the time between this this update and the last update and velocity is the desired speed of the object. These all need to be multiplied together and then added to the current position to get the new position.
#Override
protected void update(float delta) {
double theta = Math.atan2(targetPos.y - pos.y, targetPos.x - pos.x);
double valX = (delta * velocity) * Math.cos(theta);
double valY = (delta * velocity) * Math.sin(theta);
pos.x += valX;
pos.y += valY;
}
I'm struggling with adding a map scale that displays current length on screen depending on current zoom level. I'm having a feeling that it might exist some predefined class to use but I have no clue...? I've searched around a lot but can't find anything.
Any help i much appreciated =)
// Alex
Alright, I got it now! Luis answer helped me a lot and also OpenStreetMap. Here's what I came up with:
<your.own.package.path>;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Picture;
import android.graphics.Rect;
import android.location.Location;
import android.util.Log;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;
import com.iqqn.uppgift5.GameMapActivity;
public class ScaleBarOverlay extends Overlay{
// ===========================================================
// Fields
// ===========================================================
// Defaults
boolean enabled = true;
float xOffset = 10;
float yOffset = 10;
float lineWidth = 2;
int textSize = 12;
boolean imperial = false;
boolean nautical = false;
boolean latitudeBar = true;
boolean longitudeBar = false;
// Internal
protected final MapView mapView;
protected final GameMapActivity master;
private Context context;
protected final Picture scaleBarPicture = new Picture();
private final Matrix scaleBarMatrix = new Matrix();
private int lastZoomLevel = -1;
float xdpi;
float ydpi;
int screenWidth;
int screenHeight;
// ===========================================================
// Constructors
// ===========================================================
public ScaleBarOverlay(Context _context, GameMapActivity master, MapView mapView) {
super();
this.master = master;
this.context = _context;
this.mapView = mapView;
xdpi = this.context.getResources().getDisplayMetrics().xdpi;
ydpi = this.context.getResources().getDisplayMetrics().ydpi;
screenWidth = this.context.getResources().getDisplayMetrics().widthPixels;
screenHeight = this.context.getResources().getDisplayMetrics().heightPixels;
}
// ===========================================================
// Getter & Setter
// ===========================================================
/**
* #return the enabled
*/
public boolean isEnabled() {
return enabled;
}
/**
* #param enabled the enabled to set
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* #return the lineWidth
*/
public float getLineWidth() {
return lineWidth;
}
/**
* #param lineWidth the lineWidth to set
*/
public void setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
}
/**
* #return the imperial
*/
public boolean isImperial() {
return imperial;
}
/**
* #param imperial the imperial to set
*/
public void setImperial() {
this.imperial = true;
this.nautical = false;
createScaleBarPicture();
}
/**
* #return the nautical
*/
public boolean isNautical() {
return nautical;
}
/**
* #param nautical the nautical to set
*/
public void setNautical() {
this.nautical = true;
this.imperial = false;
createScaleBarPicture();
}
public void setMetric() {
this.nautical = false;
this.imperial = false;
createScaleBarPicture();
}
public void drawLatitudeScale(boolean latitude) {
this.latitudeBar = latitude;
}
public void drawLongitudeScale(boolean longitude) {
this.longitudeBar = longitude;
}
#Override
public void draw(Canvas canvas, MapView localMapView, boolean shadow) {
if (this.enabled) {
// Draw the overlay
if (shadow == false) {
final int zoomLevel = localMapView.getZoomLevel();
if (zoomLevel != lastZoomLevel) {
lastZoomLevel = zoomLevel;
createScaleBarPicture();
}
this.scaleBarMatrix.setTranslate(-1 * (scaleBarPicture.getWidth() / 2 - 0.5f), -1 * (scaleBarPicture.getHeight() / 2 - 0.5f));
this.scaleBarMatrix.postTranslate(xdpi/2, ydpi/2 + canvas.getHeight()-50);
canvas.save();
canvas.setMatrix(scaleBarMatrix);
canvas.drawPicture(scaleBarPicture);
canvas.restore();
}
}
}
// ===========================================================
// Methods
// ===========================================================
public void disableScaleBar() {
this.enabled = false;
}
public boolean enableScaleBar() {
return this.enabled = true;
}
private void createScaleBarPicture() {
// We want the scale bar to be as long as the closest round-number miles/kilometers
// to 1-inch at the latitude at the current center of the screen.
Projection projection = mapView.getProjection();
if (projection == null) {
return;
}
Location locationP1 = new Location("ScaleBar location p1");
Location locationP2 = new Location("ScaleBar location p2");
// Two points, 1-inch apart in x/latitude, centered on screen
GeoPoint p1 = projection.fromPixels((int) ((screenWidth / 2) - (xdpi / 2)), screenHeight/2);
GeoPoint p2 = projection.fromPixels((int) ((screenWidth / 2) + (xdpi / 2)), screenHeight/2);
locationP1.setLatitude(p1.getLatitudeE6()/1E6);
locationP2.setLatitude(p2.getLatitudeE6()/1E6);
locationP1.setLongitude(p1.getLongitudeE6()/1E6);
locationP2.setLongitude(p2.getLongitudeE6()/1E6);
float xMetersPerInch = locationP1.distanceTo(locationP2);
p1 = projection.fromPixels(screenWidth/2, (int) ((screenHeight / 2) - (ydpi / 2)));
p2 = projection.fromPixels(screenWidth/2, (int) ((screenHeight / 2) + (ydpi / 2)));
locationP1.setLatitude(p1.getLatitudeE6()/1E6);
locationP2.setLatitude(p2.getLatitudeE6()/1E6);
locationP1.setLongitude(p1.getLongitudeE6()/1E6);
locationP2.setLongitude(p2.getLongitudeE6()/1E6);
float yMetersPerInch = locationP1.distanceTo(locationP2);
final Paint barPaint = new Paint();
barPaint.setColor(Color.BLACK);
barPaint.setAntiAlias(true);
barPaint.setStyle(Style.FILL);
barPaint.setAlpha(255);
final Paint textPaint = new Paint();
textPaint.setColor(Color.BLACK);
textPaint.setAntiAlias(true);
textPaint.setStyle(Style.FILL);
textPaint.setAlpha(255);
textPaint.setTextSize(textSize);
final Canvas canvas = scaleBarPicture.beginRecording((int)xdpi, (int)ydpi);
if (latitudeBar) {
String xMsg = scaleBarLengthText(xMetersPerInch, imperial, nautical);
Rect xTextRect = new Rect();
textPaint.getTextBounds(xMsg, 0, xMsg.length(), xTextRect);
int textSpacing = (int)(xTextRect.height() / 5.0);
canvas.drawRect(xOffset, yOffset, xOffset + xdpi, yOffset + lineWidth, barPaint);
canvas.drawRect(xOffset + xdpi, yOffset, xOffset + xdpi + lineWidth, yOffset + xTextRect.height() + lineWidth + textSpacing, barPaint);
if (!longitudeBar) {
canvas.drawRect(xOffset, yOffset, xOffset + lineWidth, yOffset + xTextRect.height() + lineWidth + textSpacing, barPaint);
}
canvas.drawText(xMsg, (xOffset + xdpi/2 - xTextRect.width()/2), (yOffset + xTextRect.height() + lineWidth + textSpacing), textPaint);
}
if (longitudeBar) {
String yMsg = scaleBarLengthText(yMetersPerInch, imperial, nautical);
Rect yTextRect = new Rect();
textPaint.getTextBounds(yMsg, 0, yMsg.length(), yTextRect);
int textSpacing = (int)(yTextRect.height() / 5.0);
canvas.drawRect(xOffset, yOffset, xOffset + lineWidth, yOffset + ydpi, barPaint);
canvas.drawRect(xOffset, yOffset + ydpi, xOffset + yTextRect.height() + lineWidth + textSpacing, yOffset + ydpi + lineWidth, barPaint);
if (! latitudeBar) {
canvas.drawRect(xOffset, yOffset, xOffset + yTextRect.height() + lineWidth + textSpacing, yOffset + lineWidth, barPaint);
}
float x = xOffset + yTextRect.height() + lineWidth + textSpacing;
float y = yOffset + ydpi/2 + yTextRect.width()/2;
canvas.rotate(-90, x, y);
canvas.drawText(yMsg, x, y + textSpacing, textPaint);
}
scaleBarPicture.endRecording();
}
private String scaleBarLengthText(float meters, boolean imperial, boolean nautical) {
if (this.imperial) {
if (meters >= 1609.344) {
return (meters / 1609.344) + "mi";
} else if (meters >= 1609.344/10) {
return ((meters / 160.9344) / 10.0) + "mi";
} else {
return (meters * 3.2808399) + "ft";
}
} else if (this.nautical) {
if (meters >= 1852) {
return ((meters / 1852)) + "nm";
} else if (meters >= 1852/10) {
return (((meters / 185.2)) / 10.0) + "nm";
} else {
return ((meters * 3.2808399)) + "ft";
}
} else {
if (meters >= 1000) {
return ((meters / 1000)) + "km";
} else if (meters > 100) {
return ((meters / 100.0) / 10.0) + "km";
} else {
return meters + "m";
}
}
}
#Override
public boolean onTap(GeoPoint point, MapView mapView) {
// Do not react to screen taps.
return false;
}
}
Use it the following way in your onCreate():
...
scaleBarOverlay = new ScaleBarOverlay(this.getBaseContext(), this, myMapView);
List<Overlay> overlays = myMapView.getOverlays();
// Add scale bar overlay
scaleBarOverlay.setMetric();
overlays.add(scaleBarOverlay);
...
Hope this will help anyone =) This will work from API level 7+. I haven't tested it on API level 14+ though, and i know that some hardware accelerated stuff "don't" work there like drawing a picture with canvas. But i think it'll work with a recording.
Thanks again Luis!
// Alexander
As far as I know there isn't a predifined class to do that.
One possibility is to create an overlay that checks current longitude span, as it changes accordingly to latitude, and then draw the scale at the correct size.
Bellow you can find an example on how to do that:
ScaleBar Overlay
public class CopyOfScaleBarOverlay extends Overlay {
private static final String STR_M = "m";
private static final String STR_KM = "km";
//Constants
private static float scaleBarProportion = 0.25f;
private float cMarginLeft=4;
private float cLineTopSize=8;
private float cMarginTop=6;
private float cMarginBottom=2;
private float cTextSize=12;
private float distanceFromBottom=100;
//instantiation
private Context context;
private Paint paintLine, paintText, paintRectangle;
private Location l0;
private Location l1;
private float ds;
private int width, height, pi;
private float marginLeft, marginTop, marginBottom, lineTopSize;
private String unit;
public CopyOfScaleBarOverlay(Context context){
super();
this.context=context;
paintText= new TextPaint();
paintText.setARGB(180, 0, 0, 0);
paintText.setAntiAlias(true);
paintText.setTextAlign(Align.CENTER);
paintRectangle = new Paint();
paintRectangle.setARGB(80,255,255,255);
paintRectangle.setAntiAlias(true);
paintLine = new Paint();
paintLine.setARGB(180, 0, 0, 0);
paintLine.setAntiAlias(true);
l0 = new Location("none");
l1 = new Location("none");
ds=this.context.getApplicationContext().getResources().getDisplayMetrics().density;
width=this.context.getApplicationContext().getResources().getDisplayMetrics().widthPixels;
height=this.context.getApplicationContext().getResources().getDisplayMetrics().heightPixels;
pi = (int) (height - distanceFromBottom *ds);
marginLeft=cMarginLeft*ds;
lineTopSize=cLineTopSize*ds;
marginTop=cMarginTop*ds;
marginBottom=cMarginBottom*ds;
}
#Override
public void draw(Canvas canvas, MapView mapview, boolean shadow) {
super.draw(canvas, mapview, shadow);
if(mapview.getZoomLevel() > 1){
//Calculate scale bar size and units
GeoPoint g0 = mapview.getProjection().fromPixels(0, height/2);
GeoPoint g1 = mapview.getProjection().fromPixels(width, height/2);
l0.setLatitude(g0.getLatitudeE6()/1E6);
l0.setLongitude(g0.getLongitudeE6()/1E6);
l1.setLatitude(g1.getLatitudeE6()/1E6);
l1.setLongitude(g1.getLongitudeE6()/1E6);
float d01=l0.distanceTo(l1);
float d02=d01*scaleBarProportion;
// multiply d02 by a unit conversion factor if needed
float cd02;
if(d02 > 1000){
unit = STR_KM;
cd02 = d02 / 1000;
} else{
unit = STR_M;
cd02 = d02;
}
int i=1;
do{
i *=10;
}while (i <= cd02);
i/=10;
float dcd02=(int)(cd02/i)*i;
float bs=dcd02*width/d01*d02/cd02;
String text=String.format("%.0f %s", dcd02, unit);
paintText.setTextSize(cTextSize * ds);
float text_x_size=paintText.measureText(text);
float x_size = bs + text_x_size/2 + 2*marginLeft;
//Draw rectangle
canvas.drawRect(0,pi,x_size,pi+marginTop+paintText.getFontSpacing()+marginBottom, paintRectangle);
//Draw line
canvas.drawLine(marginLeft, pi+marginTop, marginLeft + bs, pi+marginTop, paintLine);
//Draw line tops
canvas.drawLine(marginLeft, pi+marginTop - lineTopSize/2, marginLeft, pi+marginTop + lineTopSize/2, paintLine);
canvas.drawLine(marginLeft +bs, pi+marginTop - lineTopSize/2, marginLeft+bs, pi+marginTop + lineTopSize/2, paintLine);
//Draw line midle
canvas.drawLine(marginLeft + bs/2, pi+marginTop - lineTopSize/3, marginLeft + bs/2, pi+marginTop + lineTopSize/3, paintLine);
//Draw line quarters
canvas.drawLine(marginLeft + bs/4, pi+marginTop - lineTopSize/4, marginLeft + bs/4, pi+marginTop + lineTopSize/4, paintLine);
canvas.drawLine(marginLeft + 3*bs/4, pi+marginTop - lineTopSize/4, marginLeft + 3*bs/4, pi+marginTop + lineTopSize/4, paintLine);
//Draw text
canvas.drawText(text, marginLeft +bs, pi+marginTop+paintText.getFontSpacing(), paintText);
}
}
}
To use
On your activity that extendes MapActivity, add the following:
mapView.getOverlays().add(new CopyOfScaleBarOverlay(this));
Note
The example is using metric units. To use a different unit system, multiply d02 in the code above by a unit conversion factor and adjust strings with the unit name.
Enjoy it.
I'm developing a game, and in this game I have to move an image on a Canvas from one point to another in any direction, not just vertical or horizontal.
How can I move that image in this manner?
After getting a math lecture, it turns out that this is easy to solve. First, we need to get the angle which the target will be moving at.
float deltaX = targetX - startX;
float deltaY = targetY - startY;
float angle = Math.atan2( deltaY, deltaX );
startX/Y can be current X/Y.
Now that we have calculated the angle, we can apply it to the current coordinates:
currentX += speed * Math.cos(angle);//Using cos
currentY += speed * Math.sin(angle);//or sin
to handle the calculations of how much X and Y will be increased by.
Using speed as a custom variable if you need to have a custom speed set as well. If you don't need more speed, remove the variable.
And to move the object, apply X/Y to the object:
c.drawBitmap(bm, x, y, null);
Example:
int speed = 10;
int x, y;
int targetX = 100, targetY = 600;
int startX = 900, startY = 100;
public void render(Canvas c){
super.draw(c);
float deltaX = targetX - startX;
float deltaY = targetY - startY;
float angle = Math.atan2( deltaY, deltaX );
x += speed * Math.cos(angle);//Using cos
y += speed * Math.sin(angle);//or sin
c.drawBitmap(bm, x, y, null);
(...)
}
I couldn't understand what you actually want but here's something I've tried.
interface coordinateListener
{
public void currentPosition(float x,float y);
}
public class ImageView extends View{
int width;
int height;
RectF rect = new RectF();
float x=0f,y=0f;
float dX,dY;
float mStartX,mStartY;
float mEndX,mEndY;
Paint paint = new Paint();
Bitmap mBitmap;
TranslateAnimation anim;
View view;
coordinateListener mListener;
public ImageView(Context context) {
super(context);
init();
}
public ImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init()
{
view = this;
}
#Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
width = getMeasuredWidth();
height = getMeasuredHeight();
rect.set(0,0,width,height);
}
#Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if(mBitmap!=null) {
canvas.drawBitmap(mBitmap,0,0,paint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action)
{
case MotionEvent.ACTION_DOWN:
dX = this.getX() - event.getRawX();
dY = this.getY() - event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
y = event.getRawY();
x = event.getRawX();
y += dY;
x+=dX;
this.setY(y);
this.setX(x);
mListener = (coordinateListener)getContext();
mListener.currentPosition(x,y);
invalidate();
break;
}
return true;
}
public void startCoordinates(float x,float y)
{
mStartX = x;
mStartY = y;
}
public void endCoordinates(float x,float y)
{
mEndX = x;
mEndY = y;
}
public void startTranslation(long duration)
{
anim = new TranslateAnimation(mStartX,mEndX,mStartY,mEndY);
anim.setDuration(duration);
anim.setFillAfter(true);
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
view.setX((int)mEndX);
view.setY((int)mEndY);
animation.setFillAfter(false);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
this.startAnimation(anim);
}
}
You can either drag it from one position to another or you can use Translate to move it...like this,
view.startCoordinates(0f,0f);
view.endCoordinates(500f,0f);
view.startTranslation(3000);
Take a look into this library. It should help you with moving, and I think dragging objects like image, etc - PhysicsLayout. Here you can see moving with Two directions - X, Y. If you want to move including Z, there is only single way to implement it, you should use simple scaling.
If you want something more, there are a lot of powerful and pretty nice Frameworks, Environment.