I'm attempting to determine if one four sided shape is above another one, and returning the ratio of the amount that is uncovered. My definition of "uncovered" is if there is no other shape above the one being examined on the y-axis, and if there is how much they are intersecting on the x-axis.
Example:
In the above image, the desired output should be 0.6363. Out of the 110 pixels, 70 are intersecting with the object above it, and 70/110=0.6363
Another Example:
In this example the desired output would be 0.3636.
What I have attempted so far is that I am starting with double out = 1; and then if the y-axis of shape 2 is less than shape 1, I subtract the amount the two shapes intersect on the x axis from the ratio with some variant of out-=(c.getX2()-b.getX2())/Math.abs(c.getX1()-c.getX2());
However, this doesn't seem to be working, and my attempts at correcting the code seem to just be adding more and more unnecessary complexity. I assume there is a much easier way to do what I'm trying to do, but I'm not great with geometry.
c is the current shape being examined, and b is the one it is being compared to.
if(((b.getX2() < c.getX2()) && (b.getX2()>c.getX1()))||((b.getX2()>c.getX2())&&(b.getX2()<c.getX1()))||((b.getX1()>c.getX2())&&(b.getX1()<c.getX1()))) {
if(b.getY2() < c.getY2()) {
if((b.getX2() < c.getX2()) && (b.getX2()>c.getX1())) {
out-= (c.getX2()-b.getX2())/Math.abs(c.getX1()-c.getX2());
}
if(((b.getX2()>c.getX2())&&(b.getX2()<c.getX1()))) {
out-=(b.getX2()-c.getX2())/Math.abs(c.getX1()-c.getX2());
}
if(((b.getX1()>c.getX2())&&(b.getX1()<c.getX1()))) {
out-=(c.getX2()-b.getX2())/Math.abs(c.getX1()-c.getX2());
}
}
}
I think your approach is way to complicated, and you might just have got lost in all the different possible conditions and configurations.
If I understood you correctly, then the simplest solution would be to base the computations on the minimum and maximum X-values of both shapes.
Side note:
You did not say what type your "shape" objects b and c are. From the methods that you are calling, they might be a java.awt.geom.Line2D objects, but these are not really "four sided". In any case, you can compute the minimum and maximum values as
double bMinX = Math.min(b.getX1(), b.getX2());
double bMaxX = Math.max(b.getX1(), b.getX2());
double cMinX = Math.min(c.getX1(), c.getX2());
double cMaxX = Math.max(c.getX1(), c.getX2());
In the program below, I'm using actual Shape objects and call the getBounds2D method to obtain the bounding boxes, which offer these min/max values conveniently. But you can also do this manually.
When you have these minimum/maxmimum values, you can rule out the cases where an object is either entirely left or entirely right of the other object. If this is not the case, they are overlapping. In this case, you can compute the minimum and maximum of the overlap, and then divide this by the width of the object in question.
Here is a program where a example objects are created based on the coordinates you gave in the question. You can drag around the objects with the mouse. The overlap is painted and its value is printed.
The computation takes place in the computeOverlap method.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeOverlap
{
public static void main(String[] args) throws IOException
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ShapeOverlapPanel());
f.setSize(900,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ShapeOverlapPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private final List<Point2D> points0;
private final List<Point2D> points1;
private final List<Point2D> draggedPoints;
private Point previousMousePosition;
ShapeOverlapPanel()
{
points0 = new ArrayList<Point2D>();
points0.add(new Point2D.Double(160, 200));
points0.add(new Point2D.Double(180, 200));
points0.add(new Point2D.Double(270, 260));
points0.add(new Point2D.Double(250, 260));
points1 = new ArrayList<Point2D>();
points1.add(new Point2D.Double(200, 280));
points1.add(new Point2D.Double(220, 280));
points1.add(new Point2D.Double(310, 340));
points1.add(new Point2D.Double(290, 340));
draggedPoints = new ArrayList<Point2D>();
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
Shape s0 = createShape(points0);
Shape s1 = createShape(points1);
g.setColor(Color.RED);
g.fill(s0);
g.setColor(Color.BLUE);
g.fill(s1);
g.setColor(Color.GRAY);
drawOverlap(g, s0, s1);
double overlap = computeOverlap(s0, s1);
g.drawString("Overlap of red from blue: "+overlap, 10, 20);
}
private static double computeOverlap(Shape s0, Shape s1)
{
Rectangle2D b0 = s0.getBounds2D();
Rectangle2D b1 = s1.getBounds2D();
if (b0.getMaxX() < b1.getMinX())
{
System.out.println("Shape 0 is left of shape 1");
return Double.NaN;
}
if (b0.getMinX() > b1.getMaxX())
{
System.out.println("Shape 0 is right of shape 1");
return Double.NaN;
}
double overlapMinX = Math.max(b0.getMinX(), b1.getMinX());
double overlapMaxX = Math.min(b0.getMaxX(), b1.getMaxX());
double overlapSize = overlapMaxX - overlapMinX;
double relativeOverlap = overlapSize / b0.getWidth();
return relativeOverlap;
}
private void drawOverlap(Graphics2D g, Shape s0, Shape s1)
{
Rectangle2D b0 = s0.getBounds2D();
Rectangle2D b1 = s1.getBounds2D();
if (b0.getMaxX() < b1.getMinX())
{
return;
}
if (b0.getMinX() > b1.getMaxX())
{
return;
}
double overlapMinX = Math.max(b0.getMinX(), b1.getMinX());
double overlapMaxX = Math.min(b0.getMaxX(), b1.getMaxX());
g.drawLine((int)overlapMinX, 0, (int)overlapMinX, getHeight());
g.drawLine((int)overlapMaxX, 0, (int)overlapMaxX, getHeight());
}
private static Shape createShape(Iterable<? extends Point2D> points)
{
Path2D path = new Path2D.Double();
boolean first = true;
for (Point2D p : points)
{
if (first)
{
path.moveTo(p.getX(), p.getY());
first = false;
}
else
{
path.lineTo(p.getX(), p.getY());
}
}
path.closePath();
return path;
}
#Override
public void mouseDragged(MouseEvent e)
{
int dx = e.getX() - previousMousePosition.x;
int dy = e.getY() - previousMousePosition.y;
for (Point2D p : draggedPoints)
{
p.setLocation(p.getX() + dx, p.getY() + dy);
}
repaint();
previousMousePosition = e.getPoint();
}
#Override
public void mouseMoved(MouseEvent e)
{
}
#Override
public void mouseClicked(MouseEvent e)
{
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoints.clear();
Shape s0 = createShape(points0);
Shape s1 = createShape(points1);
if (s0.contains(e.getPoint()))
{
draggedPoints.addAll(points0);
}
else if (s1.contains(e.getPoint()))
{
draggedPoints.addAll(points1);
}
previousMousePosition = e.getPoint();
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoints.clear();
}
#Override
public void mouseEntered(MouseEvent e)
{
}
#Override
public void mouseExited(MouseEvent e)
{
}
}
Related
I have been working on a top down game for about a day or two and I am trying to figure out how to get a point after I translate and rotate and object back to its original position. I have an image (a weapon) and I rotate it based off of the mouse location and player however, I want to get a certain point of the rotated image after I have rotated it so I can use this point to set the start draw location for bullets, animations, etc. Does anyone know how I can solve this? thanks!!
private Point hand, location, handle, barrelExit;
private BufferedImage image;
private double theta;
public WeaponSpriteBase(BufferedImage image, Point location, Point handle, Point barrelExit) {
super(image, location);
this.image = image;
this.location = location;
this.handle = handle;
this.barrelExit = barrelExit;
//Sets the hand location getter
hand = PlayerSprite.playerHand();
}
public void draw(Graphics2D g2, int currentMouseX, int currentMouseY) {
theta = PlayerSprite.DegreeFinder(currentMouseX, currentMouseY);
//Refreshes the hands location getter
hand = PlayerSprite.playerHand();
setLocation(hand.x - handle.x, hand.y - handle.y);
g2.translate(hand.x, hand.y);
g2.rotate(-Math.toRadians(theta));
setLocation(-handle.x, - handle.y);
if(theta > 90 && theta < 270) {
flipVert(g2);
}
else {
super.draw(g2);
}
g2.rotate(Math.toRadians(theta));
g2.translate(-hand.x, -hand.y);
g2.setColor(Color.RED);
g2.drawRect(hand.x - 1, hand.y - 1, 2, 2);
g2.setColor(Color.black);
//Want to get a certain point on the image and draw it here (with barrelExit.x , barrelExit.y) without rotating it
}
}
If you know...
The position of the weapon
It's angle of direction
The length (from it's centre point to it's end point)
Then you can simply apply a "point on circle" approach to the problem, for example...
The blue line is the weapon, the red line is the projection of the projectile path. Oddly enough, it's calculating both the start and end point the projectile should follow 😉
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
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() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
enum WeaponInput {
LEFT, RIGHT, NONE
}
public class TestPane extends JPanel {
private static final double MIN_WEAPON_ANGLE = -85;
private static final double MAX_WEAPON_ANGLE = 85;
private double angleOfWeapon = 0;
private double weaponAngleDelta = 1;
private WeaponInput weaponInput = WeaponInput.NONE;
private Timer timer;
public TestPane() {
setBackground(Color.BLACK);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "Pressed.weaponMoveLeft");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Pressed.weaponMoveRight");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "Pressed.weaponReleasedLeft");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "Pressed.weaponReleasedRight");
WeaponMovementAction.WeaponObserver weaponObserver = new WeaponMovementAction.WeaponObserver() {
#Override
public void weaponDidMove(WeaponInput weaponInput) {
TestPane.this.weaponInput = weaponInput;
}
};
am.put("Pressed.weaponMoveLeft", new WeaponMovementAction(WeaponInput.LEFT, weaponObserver));
am.put("Pressed.weaponMoveRight", new WeaponMovementAction(WeaponInput.RIGHT, weaponObserver));
am.put("Pressed.weaponReleasedLeft", new WeaponMovementAction(WeaponInput.NONE, weaponObserver));
am.put("Pressed.weaponReleasedRight", new WeaponMovementAction(WeaponInput.NONE, weaponObserver));
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (weaponInput) {
case LEFT:
angleOfWeapon -= weaponAngleDelta;
break;
case RIGHT:
angleOfWeapon += weaponAngleDelta;
break;
}
if (angleOfWeapon < MIN_WEAPON_ANGLE) {
angleOfWeapon = MIN_WEAPON_ANGLE;
} else if (angleOfWeapon > MAX_WEAPON_ANGLE) {
angleOfWeapon = MAX_WEAPON_ANGLE;
}
repaint();
}
});
timer.start();
}
protected Point2D getPointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 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);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
g2d.setColor(Color.WHITE);
g2d.drawRect(centerX - 20, centerY - 20, 40, 40);
g2d.setColor(Color.RED);
g2d.translate(centerX, centerY - 20);
// Projection of projectile
g2d.setColor(Color.BLUE);
Point2D endOfWeaponPoint = getPointOnCircle(angleOfWeapon, 20);
g2d.draw(new Line2D.Double(new Point2D.Double(0, 0), endOfWeaponPoint));
// Weapon direction
int radius = Math.max(getWidth(), getHeight());
g2d.setColor(Color.RED);
Point2D poc = getPointOnCircle(angleOfWeapon, radius);
g2d.draw(new Line2D.Double(endOfWeaponPoint, poc));
g2d.dispose();
}
}
public class WeaponMovementAction extends AbstractAction {
public interface WeaponObserver {
public void weaponDidMove(WeaponInput weaponInput);
}
private WeaponInput weaponInput;
private WeaponObserver observer;
public WeaponMovementAction(WeaponInput weaponInput, WeaponObserver observer) {
this.weaponInput = weaponInput;
this.observer = observer;
}
#Override
public void actionPerformed(ActionEvent e) {
getObserver().weaponDidMove(getWeaponInput());
}
public WeaponInput getWeaponInput() {
return weaponInput;
}
public WeaponObserver getObserver() {
return observer;
}
}
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
Step 1: Task
Okay so I'm trying to replicate this in a jframe.
https://codepen.io/allanpope/pen/LVWYYd
Problem:
I have a loading screen and over the course of 5 seconds I'm moving dots from a circle to an image. Just like the codepen. Except the problem is I'm not sure how to animate it correctly. JAVA
My Idea was to make an animate function like Move.to(Dot,Duration)
One problem I'm having is that when using decimals the dots wont be in the exact place, and some wont move at all. I'm just not sure how to make this animate function and how to end it. If anyone could help me that would be epic. My code is down below. And if someone could tell me how I could do a Bezier curve animation would also be cool
If anyone wantes to test my code and tell me whats wrong that would be epic.
So first I have a function to make the circle positions
package loadingScreen;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class LoadTree {
static BufferedImage tree;
static int radius =300;
public static List<Dot> dots = new ArrayList<Dot>();
public static void make() {
File e = new File("assets/tree.png");
try {
tree = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
for(int x = 0; x<tree.getWidth();x+=1) {
for(int y = 0; y<tree.getWidth();y+=1) {
int clr = tree.getRGB(x, y);
if(clr==0) {
}else {
int i =(y)*300+x;
int a = (i / 4) % 300;
int b = (int) Math.floor(Math.floor(i / 300) / 4);
if (( a % ((1)) == 0) && (b % ((1)) == 0)) {
double p = (double) Math.random();
int circleX = (int) (MyFrame.width/2+ radius * Math.cos(2 * Math.PI * p));
int circleY = (int) (MyFrame.height/2 + radius * Math.sin(2 * Math.PI * p));
Dot dot = new Dot(circleX, circleY, clr,
MyFrame.width/2+x-150, MyFrame.height/2+y-150,circleX,circleY);
dots.add(dot);
}
}
}
}
}
}
Here is the dot class
package loadingScreen;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
public class Dot {
public int imageY;
public int imageX;
public int color;
public double y;
public double x;
public int circleX;
public int circleY;
public Dot(int x, int y, int color, int imageX, int imageY, int circleX, int circleY){
this.x = x;
this.y = y;
this.color = color;
this.imageX = imageX;
this.imageY = imageY;
this.circleX = circleX;
this.circleY = circleY;
}
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color c = new Color(color);
g2d.setColor(c);
g2d.fillRect((int)x,(int) y, 1, 1);
}
}
Here is the Jpanel
package loadingScreen;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
import loadingScreen.animate.Move;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MyPanel extends JPanel implements ActionListener{
Image background;
Timer timer;
MyPanel(){
timer = new Timer(0,this);
timer.start();
File e = new File("assets/background.png");
try {
background = ImageIO.read(e);
} catch (IOException e1) {
e1.printStackTrace();
}
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
//g2d.drawImage(background, 0, 0, MyFrame.width,MyFrame.height,null);
LoadTree.dots.forEach(dot ->{
dot.paint(g);
});
}
#Override
public void actionPerformed(ActionEvent e) {
LoadTree.dots.forEach(dot ->{
Move.to(dot, 1000, 0);
//get slope
});
repaint();
//System.out.println(LoadTree.dots.size());
}
}
And here is the Jframe
package loadingScreen;
import javax.swing.JFrame;
public class MyFrame extends JFrame{
MyPanel panel;
public static int width = 1080;
public static int height = 720;
MyFrame(){
panel = new MyPanel();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(panel);
this.setSize(width, height);
this.setLocationRelativeTo(null);
this.setVisible(true);
}
}
You are in for a massive deep dive. Animation, good animation, animation you don't notice, is really, really hard to achieve and is a very complex subject.
You've kind of started in the right direction. You need some kind of "ticker" to tell you when the animation should update, but it kind of falls apart after that.
The first thing you want to do is move away from the concept of "linear progression". That is, on each "tick", the object is moved by a specific delta value. This doesn't produce good animation and can fall apart really quickly when you want to change the speed or duration of the animation.
A better solution is to start with a "duration based progress". This is, basically, the animation will run over a fixed period of time and on each tick of the animation, you calculate the new "state" of the object based on the amount of time which has passed and the amount of time remaining.
This has the benefit of "normalising" the timeline. That is, the animation occurs between 0-1. From this it becomes incredibly easy to calculate where a object should be along that time line. Want to make it faster or slower? Change the duration, the rest is taken care for you!
To start with, figure out how to move one dot from one point to another, if you can move one, you can move a thousand.
Duration base animation engine...
Play close attention to:
The Utilities class
The DurationAnimationEngine
The engine is backed by a Swing Timer, so it's safe to use within Swing. It's whole purpose to run (as fast as it safely can) for a specified period of time and produce "ticks" with the amount of progression has occurred (remember, normalised time)
The following is basic implementation of the animation. A lot of the "work" happens in the mouseClicked event, as it starts the engine. When the engine ticks the dots are updated. Each dot is wrapped in AnimatableDot which has a "from" and "to" point, it then, based on the normalised time, calculates it's new position and then a paint pass is executed
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
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.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class DurationTest {
public static void main(String[] args) {
new DurationTest();
}
public DurationTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private DurationAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
// Reset poisitions
for (AnimatedDot dot : dots) {
dot.move(0);
}
repaint();
return;
}
engine = new DurationAnimationEngine(duration, new DurationAnimationEngine.Tickable() {
#Override
public void animationDidTick(double progress) {
for (AnimatedDot dot : dots) {
dot.move(progress);
}
repaint();
}
});
engine.start();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class DurationAnimationEngine {
public interface Tickable {
public void animationDidTick(double progress);
}
private Duration duration;
private Instant timeStarted;
private Timer timer;
private Tickable tickable;
public DurationAnimationEngine(Duration duration, Tickable tickable) {
this.duration = duration;
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
tickable.animationDidTick(progress);
if (progress >= 1.0) {
stop();
}
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void move(double progress) {
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Okay, so when we run it, we get...
😮 ... Hmmm, I'd like to say that that was expected, but once I saw it, it was obvious what had gone wrong.
All the dots are moving at the same speed over the same time range!
So, what's the answer. Well, actually a few...
We could change the duration of each dot so that they have an individual duration. This would "randomise" the movement, but I'm not sure it would generate the exact same effect, as they'd be moving at different speeds
We could randomise the start time of the dots, so they started at different times, allowing them all to have the same duration (or even a randomised duration)
We could move only a small subset of the dots, but this would mean that you'd probably end up waiting for the current subset to finish before the next one started
A combination of 2 & 3
Individualised, randomised duration...
Okay, for simplicity (and my sanity), I'm actually going to start with 1. Each dot will have it's own, randomised duration. This means that each dot will be moving at a different speed though.
Pay close attention to LinearAnimationEngine and the AnimatedDot#move method.
This should look familiar, it's basically the same animation logic as before, just isolated for the dot itself
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
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.Point2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class RandomIndividualDuration {
public static void main(String[] args) {
new RandomIndividualDuration();
}
public RandomIndividualDuration() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Utilities {
public static Point2D pointOnCircle(double degress, double radius) {
double rads = Math.toRadians(degress - 90); // 0 becomes the top
double xPosy = Math.round((Math.cos(rads) * radius));
double yPosy = Math.round((Math.sin(rads) * radius));
return new Point2D.Double(radius + xPosy, radius + yPosy);
}
public static Point2D pointOnCircle(double xOffset, double yOffset, double degress, double radius) {
Point2D poc = pointOnCircle(degress, radius);
return new Point2D.Double(xOffset + poc.getX(), yOffset + poc.getY());
}
}
public class DurationRange {
private Duration from;
private Duration to;
public DurationRange(Duration from, Duration to) {
this.from = from;
this.to = to;
}
public Duration getFrom() {
return from;
}
public Duration getTo() {
return to;
}
public Duration getDistance() {
return Duration.ofNanos(getTo().toNanos() - getFrom().toNanos());
}
public Duration valueAt(double progress) {
Duration distance = getDistance();
long value = (long) Math.round((double) distance.toNanos() * progress);
value += getFrom().getNano();
return Duration.ofNanos(value);
}
}
public class TestPane extends JPanel {
private List<AnimatedDot> dots = new ArrayList<>(128);
private Duration duration = Duration.ofSeconds(5);
private LinearAnimationEngine engine;
private List<Color> colors = Arrays.asList(new Color[]{
Color.RED,
Color.BLUE,
Color.CYAN,
Color.GRAY,
Color.GREEN,
Color.LIGHT_GRAY,
Color.MAGENTA,
Color.PINK,
Color.WHITE,
Color.YELLOW
});
public TestPane() {
Random rnd = new Random();
setBackground(Color.BLACK);
DurationRange range = new DurationRange(Duration.ofSeconds(1), Duration.ofSeconds(5));
for (int index = 0; index < 100; index++) {
double fromAngle = 360.0 * rnd.nextDouble();
double toAngle = fromAngle + 180.0;
Collections.shuffle(colors);
Color color = colors.get(0);
Duration duration = range.valueAt(rnd.nextDouble());
dots.add(new AnimatedDot(
Utilities.pointOnCircle(fromAngle, 150),
Utilities.pointOnCircle(toAngle, 150),
color, 2, duration));
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (engine != null) {
engine.stop();
engine = null;
reset();
return;
}
System.out.println("Go");
List<AnimatedDot> avaliableDots = new ArrayList<>(120);
avaliableDots.addAll(dots);
engine = new LinearAnimationEngine(new LinearAnimationEngine.Tickable() {
#Override
public void animationDidTick() {
List<AnimatedDot> completed = new ArrayList<>(128);
// Reset poisitions
for (AnimatedDot dot : avaliableDots) {
if (!dot.move()) {
completed.add(dot);
}
}
avaliableDots.removeAll(completed);
repaint();
if (avaliableDots.isEmpty()) {
engine.stop();
engine = null;
reset();
}
}
});
engine.start();
}
});
}
protected void reset() {
for (AnimatedDot dot : dots) {
dot.reset();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
int xOffset = (getWidth() - 300) / 2;
int yOffset = (getWidth() - 300) / 2;
g2d.translate(xOffset, yOffset);
g2d.setColor(Color.DARK_GRAY);
g2d.drawOval(0, 0, 300, 300);
for (AnimatedDot dot : dots) {
dot.paint(g2d);
}
g2d.dispose();
}
}
public class LinearAnimationEngine {
public interface Tickable {
public void animationDidTick();
}
private Tickable tickable;
private Timer timer;
public LinearAnimationEngine(Tickable tickable) {
this.tickable = tickable;
}
public void start() {
// You could create the timer lazierly and restarted it as needed
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
tickable.animationDidTick();
}
});
timer.start();
}
public void stop() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
}
public class AnimatedDot {
private Dot dot;
private Point2D from;
private Point2D to;
private Duration duration;
private Instant timeStarted;
public AnimatedDot(Point2D from, Point2D to, Color color, int radius, Duration duration) {
dot = new Dot(from.getX(), from.getY(), color, radius);
this.from = from;
this.to = to;
this.duration = duration;
}
public void paint(Graphics2D g) {
dot.paint(g);
}
public void reset() {
Point2D futureFrom = to;
to = from;
from = futureFrom;
timeStarted = null;
}
public boolean move() {
if (timeStarted == null) {
timeStarted = Instant.now();
}
Duration runtime = Duration.between(timeStarted, Instant.now());
double progress = Math.min(1.0, runtime.toMillis() / (double) duration.toMillis());
Point2D pointAt = pointAt(progress);
dot.setLocation(pointAt);
return progress < 1.0;
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
protected double getFromX() {
return getFrom().getX();
}
protected double getFromY() {
return getFrom().getY();
}
public Double getXDistance() {
return getTo().getX() - getFrom().getX();
}
public Double getYDistance() {
return getTo().getY() - getFrom().getY();
}
protected Point2D pointAt(double progress) {
double xDistance = getXDistance();
double yDistance = getYDistance();
double xValue = Math.round(xDistance * progress);
double yValue = Math.round(yDistance * progress);
xValue += getFromX();
yValue += getFromY();
return new Point2D.Double(xValue, yValue);
}
}
public class Dot {
private Color color;
private double y;
private double x;
private int radius;
private Ellipse2D dot;
public Dot(double x, double y, Color color, int radius) {
this.x = x;
this.y = y;
this.color = color;
this.radius = radius;
dot = new Ellipse2D.Double(0, 0, radius * 2, radius * 2);
}
public void setLocation(Point2D point) {
setLocation(point.getX(), point.getY());
}
public void setLocation(double x, double y) {
this.x = x;
this.y = y;
}
public void paint(Graphics2D g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(color);
g2d.translate(x - radius, y - radius);
g2d.fill(dot);
g2d.dispose();
}
}
}
Now, when we run it we get...
Well, at least it's now more "randomised", and this is where I think points 2 & 3 might be a better mix.
But they're not rebounding?!
Ah, well, actually, click the second example again! The dots will move from the current position (the original to point) and back to their original from point. Soooo, conceptually, it's doable.
But they don't from a nice picture when I click it!
😐 ... So the above examples demonstrate how to animate a object from point A to point B over a specified duration, forming the picture is just changing the target destination (assuming you know what it was to start with). Based on my observations, a moving dot is first allowed to move to its current "end" position before moving to the final picture position, as trying to calculate a curving path would make me 🤯.
What's missing...
Yes, there's something missing, you probably can't see it, but it really stands out for me.
Each dot starts out slowly, speeds up and then decelerates into position. This is known as "easement" (or, in this case, ease-in/ease-out) and it's not the simplest thing in the world to implement. If you're really interested, take a look at How can I implement easing functions with a thread
Now, what is the actual answer to your question? Unless you're completely crazy (and I am), don't try to roll this kind of thing yourself, unless you have a very specific reason for doing so. Instead, make use one of the other ready made engines, for example:
universal-tween-engine
timingframework
Much of the concepts used above are taken from my animation playground source, Super Simple Swing Animation Framework. This is where I do a lot of my playing around and experimentation
This is one of those questions that has you digging deeper and trying to figure out what you could actually achieve, to that end BounceImagePixel is an accumulation of much tinkering to see "where this could go"
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 6 years ago.
Improve this question
easy question:
I draw more than one Rectangel2D (or other shapes) in one JPane with overlapping.
I want to select the one on the top by click the overlapping area and do something, e.g. change color or drag.
But I can't select the one which I want.
The order of the Shapes is not under control.
I find a method setComponentZOrder, but that is for component not for Rectangel2D.
I think this is a typical question, but I can't find the answer.
The order that a Shape such as a Rectangle2D are drawn is determined by the order that they are held in whatever collection holds them as this collection is iterated through in your JComponent's (such as a JPanel's) painting method. Thus the last items in the collection, be it an array or ArrayList or LinkedList, is drawn on top.
So if you want to get the top Shape on mouse press or click, then the key will be to iterate through the same collection backwards, getting the first Shape that contains(Point p) the mouse Point location. That's it.
For example, please look at my code here which does just his in this method which is called from the MouseListener (actually MouseAdapater):
public Shape getShapeAtPoint(Point p) {
Shape shapeAtPoint = null;
for (int i = shapeList.size() - 1; i >= 0; i--) {
if (shapeList.get(i).contains(p)) {
return shapeList.get(i);
}
}
return shapeAtPoint;
}
For example:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.*;
#SuppressWarnings("serial")
public class RandomShapes extends JPanel {
private static final Stroke STROKE = new BasicStroke(4f);
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int SHAPE_COUNT = 30;
private static final int SHAPE_WIDTH = 100;
private static final int SHAPE_HEIGHT = SHAPE_WIDTH;
private List<Path2D> paths = new ArrayList<>();
private Map<Path2D, Color> colorMap = new HashMap<>();
private Random random = new Random();
public RandomShapes() {
for (int i = 0; i < SHAPE_COUNT; i++) {
Shape shape = createRandomShape(i);
Path2D path = new Path2D.Double(shape);
paths.add(path);
colorMap.put(path, createRandomColor());
}
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
private class MyMouse extends MouseAdapter {
private Path2D selectedPath = null;
private Point p1;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
for (int i = paths.size() - 1; i >= 0; i--) {
Path2D path = paths.get(i);
if (path.contains(e.getPoint())) {
selectedPath = path;
p1 = e.getPoint();
paths.remove(selectedPath);
paths.add(selectedPath);
repaint();
break;
}
}
}
private void movePath(MouseEvent e) {
Point p2 = e.getPoint();
int tx = p2.x - p1.x;
int ty = p2.y - p1.y;
p1 = p2;
AffineTransform at = AffineTransform.getTranslateInstance(tx, ty);
selectedPath.transform(at);
repaint();
}
#Override
public void mouseDragged(MouseEvent e) {
if (selectedPath != null) {
movePath(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (selectedPath != null) {
movePath(e);
}
selectedPath = null;
}
}
private Color createRandomColor() {
float min = 0.2f;
float h = random.nextFloat();
float s = min * random.nextFloat() + (1f - min);
float b = min * random.nextFloat() + (1f - min);
return Color.getHSBColor(h, s, b);
}
private Shape createRandomShape(int i) {
Dimension size = getPreferredSize();
int x = random.nextInt(size.width - SHAPE_WIDTH);
int y = random.nextInt(size.height - SHAPE_HEIGHT);
switch (i % 3) {
case 0:
return new Ellipse2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT);
case 1:
return new Rectangle2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT);
case 2:
return new RoundRectangle2D.Double(x, y, SHAPE_WIDTH, SHAPE_HEIGHT, 15, 15);
default:
break;
}
return null;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(STROKE);
for (Path2D path : paths) {
g2.setColor(colorMap.get(path));
g2.fill(path);
g2.setColor(colorMap.get(path).darker());
g2.draw(path);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
JFrame frame = new JFrame("RandomShapes");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RandomShapes());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Note that if you want to delete a shape on right mouse click (MouseEvent.BUTTON3), this edit to the MyMouse MouseAdapter class can make this happen (see comments in code):
private class MyMouse extends MouseAdapter {
private Path2D selectedPath = null;
private Point p1;
#Override
public void mousePressed(MouseEvent e) {
int button = e.getButton();
// if the *middle* mouse button is pushed, ignore it
if (button == MouseEvent.BUTTON2) {
return;
}
// iterate through all the shapes,
// starting at the top of the list (last drawn shapes)
for (int i = paths.size() - 1; i >= 0; i--) {
Path2D path = paths.get(i);
if (path.contains(e.getPoint())) {
// if the mouse presses on one of the shapes
selectedPath = path;
p1 = e.getPoint();
// remove the shape from the collection
paths.remove(selectedPath);
if (button == MouseEvent.BUTTON1) {
// re-add it back in at the end of the collection
// only if the left mouse button has been pressed
paths.add(selectedPath);
}
repaint();
break;
}
}
}
// ....
}
So, I'm trying to dynamically draw a Polygon starting from when I click the mouse until I stop dragging and release. Instead of, for the purpose of this question, a square outline being drawn when I click, drag down, then right-across, then up, then left-across, this is what happens: http://imgur.com/t8ZN3Pp
Any suggestions?
Notes:
model.addPolygon() creates a Polygon with starting points and adds it to an ArrayList called 'polys'
model.addPolygonPoint() adds points to this created polygon that is stored in 'polys'
my paint function iterates through polys to paint
public void mousePressed(MouseEvent e) {
oldX = e.getX();
oldY = e.getY();
model.addPolygon(oldX, oldY);
}
public void mouseDragged(MouseEvent e) {
currentX = e.getX();
currentY = e.getY();
model.addPolyPoint(currentX, currentY);
repaint();
}
.
.
. then in paintComponent:
for (ListIterator<Polys> iter =
model.polys.listIterator(model.polys.size());
iter.hasPrevious();){
graphics2D.draw(iter.previous().poly);
Full paintComponent:
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (image == null) {
image = createImage(getSize().width, getSize().height);
graphics2D = (Graphics2D) image.getGraphics();
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
g.drawImage(image, 0, 0, null);
for (ListIterator<Polys> iter =
model.polys.listIterator(model.polys.size());
iter.hasPrevious();){
graphics2D.draw(iter.previous().poly);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
public class Testing {
private static int lastX;
private static int lastY;
private static int modX;
private static int modY;
private static final BasicStroke STROKE = new BasicStroke(2.0F);
private static final Point[] SHAPE = new Point[]{
new Point(10, 10),
new Point(10, 40),
new Point(60, 90),
new Point(50, 50)
};
public static void main(final String[] args) {
final JFrame frame = new JFrame("Polygon Movement");
final JPanel pane = new JPanel() {
#Override
public void paintComponent(final Graphics g1) {
final Graphics2D g = (Graphics2D) g1;
g.setColor(Color.RED);
g.translate(modX, modY);
g.setStroke(STROKE);
for (int i = 0; i < SHAPE.length; i++) {
g.drawLine(SHAPE[i].x, SHAPE[i].y, SHAPE[(i + 1) % SHAPE.length].x, SHAPE[(i + 1) % SHAPE.length].y);
}
}
};
pane.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
modX += e.getX() - lastX;
modY += e.getY() - lastY;
lastX = e.getX();
lastY = e.getY();
frame.repaint();
}
});
pane.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
lastX = e.getX();
lastY = e.getY();
}
});
pane.setPreferredSize(new Dimension(200, 200));
frame.add(pane);
frame.pack();
frame.setVisible(true);
}
}
As you can see, I make a basic shape with defined points. It is the most effective way to do this, unless you wish to change the basic shape (here it is static). In that case, you find the point the mouse it 'grabbing' and modify that one. Either way, no adding or removing of points is needed. I use the terms lastX instead of oldX just by preference.
The BasicStroke is quite optional, same with casting to a Graphics2D object.
The line:
g.drawLine(SHAPE[i].x, SHAPE[i].y, SHAPE[(i + 1) % SHAPE.length].x, SHAPE[(i + 1) % SHAPE.length].y);
Should make some sense if you've tried this thing before. It iterates through all the points, drawing a line from the current (SHAPE[i]) to the next (SHAPE[(i + 1) & SHAPE.length).
The reason behind that logic, is that say you have 4 points, as we do here. The last iteration through them, you will be given i = 3. Due to this and the array only containing 4 indexes (0-3), we must get that value back down to zero. For simplicity I use the % SHAPE.length so there wouldn't be a need for special cases.
I also opted to use adapters seeing as there were only 2 methods needed of the 7 possible ones.
If you have any questions feel free to ask me about this.
~Legend
If all you want to do is draw a polygon. You could simply use the Shape API
This will allow you to "add" points to the shape and allow the shape to paint itself.
Here I use a simple Path2D shape, as it allows me to append points over time. I keep a running list of shapes, which allows me to generate multiple polygons as required
import java.awt.BorderLayout;
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.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class DrawPolygon {
public static void main(String[] args) {
new DrawPolygon();
}
public DrawPolygon() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new PolyPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class PolyPane extends JPanel {
private MouseHandler mouseHandler;
private Path2D currentShape;
private List<Path2D> lstPloys;
private Point lastPoint;
private Point currentPoint;
public PolyPane() {
lstPloys = new ArrayList<>();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void addNotify() {
super.addNotify();
addMouseListener(getMouseHandler());
addMouseMotionListener(getMouseHandler());
}
#Override
public void removeNotify() {
removeMouseListener(getMouseHandler());
removeMouseMotionListener(getMouseHandler());
super.removeNotify();
}
public MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (lastPoint != null) {
g2d.setColor(Color.RED);
g2d.fillOval(lastPoint.x - 2, lastPoint.y - 2, 4, 4);
}
if (currentShape != null) {
g2d.setColor(Color.RED);
g2d.draw(currentShape);
if (lastPoint != null && currentPoint != null) {
System.out.println(lastPoint + " - " + currentPoint);
g2d.setColor(new Color(255, 0, 0, 64));
g2d.draw(new Line2D.Float(lastPoint, currentPoint));
}
}
g2d.setColor(Color.BLACK);
for (Shape shape : lstPloys) {
g2d.draw(shape);
}
g2d.dispose();
}
public class MouseHandler extends MouseAdapter {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
if (e.getClickCount() == 1) {
Point p = e.getPoint();
lastPoint = p;
if (currentShape == null) {
currentShape = new Path2D.Float();
currentShape.moveTo(p.x, p.y);
} else {
currentShape.lineTo(p.x, p.y);
}
repaint();
} else if (e.getClickCount() == 2) {
currentShape.closePath();
lstPloys.add(currentShape);
currentShape = null;
lastPoint = null;
repaint();
}
}
}
#Override
public void mouseMoved(MouseEvent e) {
if (currentShape != null) {
currentPoint = e.getPoint();
repaint();
} else {
currentPoint = null;
}
}
}
}
}
Take a look at Working with Geometry for more details
I'm involved in a project in which we're doing a visual editor (written in Java). Now, I'm trying to make curves that join two different objects that I'm painting in a class that extends JPanel (this class is what I'm using to paint, inside a JFrame, overriding the method paintComponent). I'm in troubles because I'm using the class QuadCurve2D to make this, but I cannot make it clickable (I'm using the method contains, but it doesn't work everytime), make it editable (for example, setting a square in its middle point to modify its curvature. The point that is used on the middle of the QuadCurve2D when the constructor is called is outside the curve) or something (method, variable, iterator, etc) that could tell me which Points are in the QuadCurve2D.
After looking for all of that some time, I have no answer, so I'm trying posting it here to find a solution. Is there anyway to make it with the QuadCurve2D class, or do I have to try with some external library?
First of all sorry for the long reply. I am now posting a complete answer to your question. I am sub classing the QuadCurve2D.Double class and with a little math now you define the curve with a start,end and a middle point instead of a control point. Also i have created a new method that checks whether a point is on the curve. The intersects method checks if the convex hull of the shape intersects with the provided shape so in the case of the concave curve this is functional but not accurate. Note that my implementation of the method to check whether a point is on the curve is rather computationally expensive and not 100% accurate since i am checking along the curve length with a specified resolution (0 is the beginning of the curve, 1 is the end. So in the example provided i am checking with a resolution of 0.01 meaning 100 checks are made along the curve). For that matter make sure that the provided step in the resolution is a divider of 0.5 (the middle point) so that you may be able to select it. If that makes no sense don't pay attention it doesn't really matter, you can use my example out of the box. Note that i also provide a check box to switch between the intersects method and my own for checking whether the mouse is on the curve. And when using my new method, i also provide a slider to specify the resolution so that you may see the effects of various values. Here are the classes.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
#SuppressWarnings("serial")
public class CurvePanel extends JPanel implements MouseListener,MouseMotionListener{
Point2D startPoint = new Point2D.Double(50, 50);
Point2D middlePoint = new Point2D.Double(100,80);
Point2D endPoint = new Point2D.Double(200, 200);
Point2D[] points = new Point2D[] {startPoint,middlePoint,endPoint};
QuadCurveWithMiddlePoint curve;
private Point2D movingPoint;
private boolean dragIt = false;
private boolean showControls = false;
JCheckBox useNewMethod;
JSlider resolution;
public CurvePanel() {
setPreferredSize(new Dimension(300,300));
addMouseListener(this);
addMouseMotionListener(this);
curve = new QuadCurveWithMiddlePoint();
useNewMethod = new JCheckBox("Use new \"contains\" method");
resolution = new JSlider(JSlider.HORIZONTAL,1,10,1);
useNewMethod.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
resolution.setEnabled(useNewMethod.isSelected());
}
});
useNewMethod.setSelected(false);
resolution.setEnabled(false);
setCurve();
}
private void setCurve() {
curve.setCurveWithMiddlePoint(startPoint, middlePoint, endPoint);
}
public static void main(String[] args) {
JFrame f = new JFrame("Test");
CurvePanel panel = new CurvePanel();
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(panel.useNewMethod,BorderLayout.NORTH);
f.getContentPane().add(panel,BorderLayout.CENTER);
f.getContentPane().add(panel.resolution,BorderLayout.SOUTH);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {
for (Point2D point : points) {
if (e.getPoint().distance(point) <= 2) {
movingPoint = point;
dragIt = true;
}
}
}
#Override
public void mouseReleased(MouseEvent e) {
dragIt = false;
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragIt) {
movingPoint.setLocation(e.getPoint());
setCurve();
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
if (useNewMethod.isSelected())
showControls = curve.pointOnCurve(e.getPoint(), 2, resolution.getValue()/100.0);
else
showControls = curve.intersects(e.getX()-2, e.getY()-2, 4, 4);
repaint();
}
#Override
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(Color.white);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(Color.black);
g2.draw(curve);
if (showControls)
for (Point2D point : points) {
g2.setPaint(Color.black);
g2.drawOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
g2.setPaint(Color.red);
g2.fillOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
}
}
}
And also :
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D.Double;
#SuppressWarnings("serial")
public class QuadCurveWithMiddlePoint extends Double {
private Point2D middlePoint = new Point2D.Double();
private final double L = 0.5;
public QuadCurveWithMiddlePoint(double x1,double y1, double xm, double ym, double x2, double y2) {
super(x1,y1,xm,ym,x2,y2);
setMiddlePoint(xm, ym);
}
public QuadCurveWithMiddlePoint() {
this(0,0,0,0,0,0);
}
public Point2D getMiddlePoint() {
calculateMiddlePoint();
return middlePoint;
}
public void setMiddlePoint(double middleX, double middleY) {
setCurve(getP1(), getControlPointByMiddle(middleX, middleY), getP2());
calculateMiddlePoint();
}
public void setMiddlePoint(Point2D middle) {
setMiddlePoint(middle.getX(),middle.getY());
}
private Point2D getControlPointByMiddle(double middleX,double middleY) {
double cpx = (middleX-(L*L-2*L+1)*x1-(L*L)*x2)/(-2*L*L+2*L);
double cpy = (middleY-(L*L-2*L+1)*y1-(L*L)*y2)/(-2*L*L+2*L);
return new Point2D.Double(cpx,cpy);
}
private Point2D calculatePoint(double position) {
if (position<0 || position>1)
return null;
double middlex = (position*position-2*position+1)*x1+(-2*position*position+2*position)*ctrlx+(position*position)*x2;
double middley = (position*position-2*position+1)*y1+(-2*position*position+2*position)*ctrly+(position*position)*y2;
return new Point2D.Double(middlex,middley);
}
public void calculateMiddlePoint() {
middlePoint.setLocation(calculatePoint(L));
}
public void setCurveWithMiddlePoint(double xx1,double yy1, double xxm, double yym, double xx2, double yy2) {
setCurve(xx1, yy1, xxm, yym, xx2, yy2);
setMiddlePoint(xxm,yym);
}
public void setCurveWithMiddlePoint(Point2D start, Point2D middle, Point2D end) {
setCurveWithMiddlePoint(start.getX(),start.getY(),middle.getX(),middle.getY(),end.getX(),end.getY());
}
public boolean pointOnCurve(Point2D point, double accuracy, double step) {
if (accuracy<=0)
return false;
if (step<=0 || step >1)
return false;
boolean oncurve = false;
double current = 0;
while (!oncurve && current <= 1) {
if (calculatePoint(current).distance(point)<accuracy)
oncurve = true;
current += step;
}
return oncurve;
}
}
If you want to know how i made the class, do a search for basic linear algebra and also search Wikipedia for Bézier curves.