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 aShape
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;In our example, we have two
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");
}
}
}
}
}
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;We work with a rectangle and an ellipse.
private Ellipse2D ellipse;
private float alpha_rectangle;These two variables control the transparency of the two geometrical objects.
private float alpha_ellipse;
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,Inside the
alpha_rectangle));
g2d.fill(rect);
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)) {If we press inside the ellipse a new
ellipseAnimator = new Thread(this);
ellipseAnimator.start();
}
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)) {For the rectangle, we have a separate inner class. A
rectAnimator = new RectRunnable();
}
RectRunnable
class. This class creates its own thread in the constructor. public void run() {Note that the
while (alpha_ellipse >= 0) {
repaint();
alpha_ellipse += -0.01f;
...
}
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. 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;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.
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);
}
}
private ZRectangle zrect;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.
private ZEllipse zell;
addMouseMotionListener(ma);We register three listeners. These listeners will capture mouse press, mouse drag and mouse wheel events.
addMouseListener(ma);
addMouseWheelListener(new ScaleHandler());
class ZEllipse extends Ellipse2D.Float {This code excerpt shows a
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;
}
}
...
}
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) {In the
x = e.getX();
y = e.getY();
}
mousePressed()
method, we remember the initial x, y coordinates of the object. int dx = e.getX() - x;Inside the
int dy = e.getY() - y;
mouseDragged()
method, we calculate the distance by which we have dragged the object. if (zrect.isHit(x, y)) {Here if we are inside the area of the rectangle, we update the x, y coordinates of the rectangle and repaint the panel.
zrect.addX(dx);
zrect.addY(dy);
repaint();
}
x += dx;The initial coordinates are updated.
y += dy;
The
ScaleHandler
class handles the scaling of the objects. if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {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
if (zrect.isHit(x, y)) {
float amount = e.getWheelRotation() * 5f;
zrect.addWidth(amount);
zrect.addHeight(amount);
repaint();
}
...
}
getWheelRotation()
method, which returns the amount of the wheel rotation. 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;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.
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);
}
}
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];These are the initial coordinates for a rectangle.
points[0] = new Point2D.Double(50, 50);
points[1] = new Point2D.Double(150, 100);
Rectangle2D s = new Rectangle2D.Double();Here we draw a rectangle from the points.
s.setFrameFromDiagonal(points[0], points[1]);
g2.draw(s);
for (int i = 0; i < points.length; i++) {This code draws the two small controlling rectangles.
double x = points[i].getX() - SIZE / 2;
double y = points[i].getY() - SIZE / 2;
g2.fill(new Rectangle2D.Double(x, y, SIZE, SIZE));
}
public void mousePressed(MouseEvent event) {In the
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;
}
}
}
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) {Here the rectangle is dynamically resized. During the
if (pos == -1)
return;
points[pos] = event.getPoint();
repaint();
}
mouseDragged()
event, we get the current point, update our array of points and repaint the panel. Figure: Resizing a Rectangle
In this part of the Java 2D tutorial, we covered hit testing and moving objects.
0 comments:
Post a Comment