You are here:Home » Java 2D » Moving objects in Java 2D

Moving objects in Java 2D

Hit testing, Moving objects

In this part of the Java 2D programming tutorial, we will first talk about hit testing. We will show, how to determine, if we have clicked inside a shape on a panel. In the second example, we will create two shapes, that we can move with a mouse on the panel and resize them with a mouse wheel. In the last example, we will be resizing a rectangle with two controlling points.

Hit testing

Hit testing is determining if we have clicked inside a Shape with a mouse pointer. Each Shape has a contains() method. The method tests if a specified Point2D is inside the boundary of a Shape.
HitTesting.java
package com.zetcode;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class HitTesting extends JPanel {

private Rectangle2D rect;
private Ellipse2D ellipse;

private float alpha_rectangle;
private float alpha_ellipse;


public HitTesting() {

this.addMouseListener(new HitTestAdapter());

rect = new Rectangle2D.Float(20f, 20f, 80f, 50f);
ellipse = new Ellipse2D.Float(120f, 30f, 60f, 60f);

alpha_rectangle = 1f;
alpha_ellipse = 1f;
}


public void paint(Graphics g) {
super.paint(g);

Graphics2D g2d = (Graphics2D)g;

g2d.setColor(new Color(50, 50, 50));

RenderingHints rh =
new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

rh.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);

g2d.setRenderingHints(rh);

g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
alpha_rectangle));
g2d.fill(rect);

g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
alpha_ellipse));
g2d.fill(ellipse);
}


public static void main(String[] args) {

JFrame frame = new JFrame("Hit testing");
frame.add(new HitTesting());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250, 150);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}


class RectRunnable implements Runnable {

private Thread runner;

public RectRunnable() {
runner = new Thread(this);
runner.start();
}

public void run() {

while (alpha_rectangle >= 0) {
repaint();
alpha_rectangle += -0.01f;

if (alpha_rectangle < 0) {
alpha_rectangle = 0;
}

try {
Thread.sleep(50);
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}


class HitTestAdapter extends MouseAdapter implements Runnable {

private RectRunnable rectAnimator;
private Thread ellipseAnimator;


public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();

if (rect.contains(x, y)) {

rectAnimator = new RectRunnable();
}

if (ellipse.contains(x, y)) {

ellipseAnimator = new Thread(this);
ellipseAnimator.start();
}
}

public void run() {
while (alpha_ellipse >= 0) {

repaint();
alpha_ellipse += -0.01f;

if (alpha_ellipse < 0) {
alpha_ellipse = 0;

}

try {
Thread.sleep(50);
} catch (InterruptedException e) {
System.out.println("interrupted");
}
}
}
}
}
In our example, we have two Shapes. A rectangle and a circle. By clicking on them they gradually begin to fade away. In this example, we work with Threads.
private Rectangle2D rect;
private Ellipse2D ellipse;
We work with a rectangle and an ellipse.
private float alpha_rectangle;
private float alpha_ellipse;
These two variables control the transparency of the two geometrical objects.
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
alpha_rectangle));
g2d.fill(rect);
Inside the paint() method, we set the transparency of the rectangle. The alpha_rectangle is computed inside a dedicated Thread.
The HitTestAdapter class is responsible for handling of mouse events. It does implement the Runnable interface, which means that it also creates the first thread.
if (ellipse.contains(x, y)) {

ellipseAnimator = new Thread(this);
ellipseAnimator.start();
}
If we press inside the ellipse a new Thread is created. The thread calls the run() method. In our case, it is the run() method of the class itself. (HitTestAdapter)
if (rect.contains(x, y)) {

rectAnimator = new RectRunnable();
}
For the rectangle, we have a separate inner class. A RectRunnable class. This class creates its own thread in the constructor.
public void run() {
while (alpha_ellipse >= 0) {

repaint();
alpha_ellipse += -0.01f;
...
}
Note that the run() method is only called once. To actually do something, we have to implement a while loop. The while loop repaints the panel and decrements the alpha_ellipse variable.
Hit testing
Figure: Hit testing

Moving and Scaling

In the next section we will learn how to move and scale graphical objects with a mouse on the panel. This is a very interesting piece of code. It can be used to move and scale charts, diagrams or other various objects in your application.
MovingScaling.java
package com.zetcode;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class MovingScaling extends JPanel {

private ZRectangle zrect;
private ZEllipse zell;

public MovingScaling() {

MovingAdapter ma = new MovingAdapter();

addMouseMotionListener(ma);
addMouseListener(ma);
addMouseWheelListener(new ScaleHandler());

zrect = new ZRectangle(50, 50, 50, 50);
zell = new ZEllipse(150, 70, 80, 80);

setDoubleBuffered(true);
}

public void paint(Graphics g) {
super.paint(g);

Graphics2D g2d = (Graphics2D) g;

Font font = new Font("Serif", Font.BOLD, 40);
g2d.setFont(font);

g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

g2d.setColor(new Color(0, 0, 200));
g2d.fill(zrect);
g2d.setColor(new Color(0, 200, 0));
g2d.fill(zell);
}

class ZEllipse extends Ellipse2D.Float {
public ZEllipse(float x, float y, float width, float height) {
setFrame(x, y, width, height);
}

public boolean isHit(float x, float y) {
if (getBounds2D().contains(x, y)) {
return true;
} else {
return false;
}
}

public void addX(float x) {
this.x += x;
}

public void addY(float y) {
this.y += y;
}

public void addWidth(float w) {
this.width += w;
}

public void addHeight(float h) {
this.height += h;
}
}

class ZRectangle extends Rectangle2D.Float {

public ZRectangle(float x, float y, float width, float height) {
setRect(x, y, width, height);
}

public boolean isHit(float x, float y) {
if (getBounds2D().contains(x, y)) {
return true;
} else {
return false;
}
}

public void addX(float x) {
this.x += x;
}

public void addY(float y) {
this.y += y;
}

public void addWidth(float w) {
this.width += w;
}

public void addHeight(float h) {
this.height += h;
}
}

class MovingAdapter extends MouseAdapter {

private int x;
private int y;

public void mousePressed(MouseEvent e) {
x = e.getX();
y = e.getY();
}

public void mouseDragged(MouseEvent e) {

int dx = e.getX() - x;
int dy = e.getY() - y;

if (zrect.isHit(x, y)) {
zrect.addX(dx);
zrect.addY(dy);
repaint();
}

if (zell.isHit(x, y)) {
zell.addX(dx);
zell.addY(dy);
repaint();
}

x += dx;
y += dy;
}
}


class ScaleHandler implements MouseWheelListener {
public void mouseWheelMoved(MouseWheelEvent e) {

int x = e.getX();
int y = e.getY();

if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

if (zrect.isHit(x, y)) {
float amount = e.getWheelRotation() * 5f;
zrect.addWidth(amount);
zrect.addHeight(amount);
repaint();
}

if (zell.isHit(x, y)) {
float amount = e.getWheelRotation() * 5f;
zell.addWidth(amount);
zell.addHeight(amount);
repaint();
}
}
}
}

public static void main(String[] args) {

JFrame frame = new JFrame("Moving and Scaling");
frame.add(new MovingScaling());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

In our code example, we have two graphical objects. A rectangle and a circle. You can move both by clicking on them and dragging them. You can also scale them up or down by positioning the mouse cursor over the objects and moving the mouse wheel.
private ZRectangle zrect;
private ZEllipse zell;
As we have already mentioned, we have a rectangle and an ellipse on our panel. Both classes extend the functionality of a built-in classes from Java AWT package.
addMouseMotionListener(ma);
addMouseListener(ma);
addMouseWheelListener(new ScaleHandler());
We register three listeners. These listeners will capture mouse press, mouse drag and mouse wheel events.
class ZEllipse extends Ellipse2D.Float {
public ZEllipse(float x, float y, float width, float height) {
setFrame(x, y, width, height);
}

public boolean isHit(float x, float y) {
if (getBounds2D().contains(x, y)) {
return true;
} else {
return false;
}
}

...

}
This code excerpt shows a ZEllipse class. It extends the built-in Ellipse2D.Float class. It adds functionality for scaling and moving an ellipse. For example, the isHit() method determines, if the mouse pointer is inside the area of an ellipse.
The MovingAdapter class handles the mouse press and mouse drag events.
public void mousePressed(MouseEvent e) {
x = e.getX();
y = e.getY();
}
In the mousePressed() method, we remember the initial x, y coordinates of the object.
int dx = e.getX() - x;
int dy = e.getY() - y;
Inside the mouseDragged() method, we calculate the distance by which we have dragged the object.
if (zrect.isHit(x, y)) {
zrect.addX(dx);
zrect.addY(dy);
repaint();
}
Here if we are inside the area of the rectangle, we update the x, y coordinates of the rectangle and repaint the panel.
x += dx;
y += dy;
The initial coordinates are updated.
The ScaleHandler class handles the scaling of the objects.
if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

if (zrect.isHit(x, y)) {
float amount = e.getWheelRotation() * 5f;
zrect.addWidth(amount);
zrect.addHeight(amount);
repaint();
}

...

}
Here if we move a mouse wheel and our cursor is inside the area of a rectangle, the rectangle is resized and the panel repainted. The amount of the scaling is computed from the getWheelRotation() method, which returns the amount of the wheel rotation.
Moving and scaling objects
Figure: Moving and scaling objects

Resize Rectangle

In the next example, we will show how to resize a shape. Our shape will be a rectangle. On our rectangle, we will draw two small black rectangles. By clicking on these tiny rectangles and dragging them, we can resize our "big" rectangle.
ResizeRectangle.java
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class ResizeRectangle extends JPanel {

private Point2D[] points;
private int SIZE = 8;
private int pos;


public ResizeRectangle() {

addMouseListener(new ShapeTestAdapter());
addMouseMotionListener(new ShapeTestAdapter());
pos = -1;

points = new Point2D[2];
points[0] = new Point2D.Double(50, 50);
points[1] = new Point2D.Double(150, 100);
}

public void paintComponent(Graphics g) {
super.paintComponent(g);

Graphics2D g2 = (Graphics2D) g;

for (int i = 0; i < points.length; i++) {
double x = points[i].getX() - SIZE / 2;
double y = points[i].getY() - SIZE / 2;
g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE));
}

Rectangle2D s = new Rectangle2D.Double();
s.setFrameFromDiagonal(points[0], points[1]);

g2.draw(s);
}


private class ShapeTestAdapter extends MouseAdapter {

public void mousePressed(MouseEvent event) {

Point p = event.getPoint();

for (int i = 0; i < points.length; i++) {
double x = points[i].getX() - SIZE / 2;
double y = points[i].getY() - SIZE / 2;

Rectangle2D r = new Rectangle2D.Double(x, y, SIZE, SIZE);

if (r.contains(p)) {
pos = i;
return;
}
}
}

public void mouseReleased(MouseEvent event) {
pos = -1;
}

public void mouseDragged(MouseEvent event) {
if (pos == -1)
return;

points[pos] = event.getPoint();
repaint();
}
}


public static void main(String[] args) {

JFrame frame = new JFrame("Resize Rectangle");

frame.add(new ResizeRectangle());
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Basically, there are two ways to create a rectangle. By providing x, y coordinates plus the width and height of the rectangle. Another way is to provide the top-left and bottom-right points. In our code example, we will use both methods.
private Point2D[] points;
In this array, we will store points, that will make our rectangle.
private int SIZE = 8;
This is the size of the small black rectangles.
points = new Point2D[2];
points[0] = new Point2D.Double(50, 50);
points[1] = new Point2D.Double(150, 100);
These are the initial coordinates for a rectangle.
Rectangle2D s = new Rectangle2D.Double();
s.setFrameFromDiagonal(points[0], points[1]);

g2.draw(s);
Here we draw a rectangle from the points.
for (int i = 0; i < points.length; i++) {
double x = points[i].getX() - SIZE / 2;
double y = points[i].getY() - SIZE / 2;
g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE));
}
This code draws the two small controlling rectangles.
public void mousePressed(MouseEvent event) {

Point p = event.getPoint();

for (int i = 0; i < points.length; i++) {
double x = points[i].getX() - SIZE / 2;
double y = points[i].getY() - SIZE / 2;

Rectangle2D r = new Rectangle2D.Double(x, y, SIZE, SIZE);

if (r.contains(p)) {
pos = i;
return;
}
}
}
In the mousePressed() method, we determine, if we have clicked inside one of the two controlling points. If we hit one of them, the pos variable stores which of them it was.
public void mouseDragged(MouseEvent event) {
if (pos == -1)
return;

points[pos] = event.getPoint();
repaint();
}
Here the rectangle is dynamically resized. During the mouseDragged() event, we get the current point, update our array of points and repaint the panel.
Resize Rectangle
Figure: Resizing a Rectangle
In this part of the Java 2D tutorial, we covered hit testing and moving objects.

0 comments:

Post a Comment