Painting an Ellipse at the end of a line
I am writing an application that includes a stickman tool. It's for yoga. I posted a few days ago but am learning how things work here a lot =)
I need to work out how to draw an ellipse that just touches the end of a line. My line is moving as the user drags it and the ellipse needs to re-orientate it's co-ordinates so that it continues to touch the tip of the line...
To explain it better, i need to give my stickman a head(thanks to crwood for much help on that) At the moment he has all the limbs he needs but i need to add a head at the point where the neck is but seeing as the limbs can be dragged, i need the head to move with the neck. Very hard to put into words.
Basically I need to know how to make an ellipse drawn so that it just touches a point no matter how you rotate that point. Hope that makes sense
I have 5 classes that make up my application so far, i'm going to post all 5 but it might be confusing although i don't want it to seem like i'm "trolling" or whatever like i have been accused of in the past. I know the word "urgent" isn't taken well around here but hasty help would be appreciated should you have the time =)
import java.awt.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
publicclass StickmanTestextends JFrame
{
JPanel buttonPanel =new JPanel ();
JButton toggleJoints =new JButton ("Toggle Joints");
JButton toggleEditable =new JButton ("Toggle Editable");
StickManTool stickmanTool =new StickManTool ();
public StickmanTest ()
{
super ();
setDefaultCloseOperation (EXIT_ON_CLOSE);
setSize (800, 600);
buttonPanel.setLayout (new FlowLayout ());
buttonPanel.add (toggleJoints);
buttonPanel.add (toggleEditable);
Container c = getContentPane ();
c.setLayout (new BorderLayout ());
c.add (stickmanTool, BorderLayout.CENTER);
c.add (buttonPanel, BorderLayout.SOUTH);
show ();
}
publicstaticvoid main (String arg [])
{
new StickmanTest ();
}
}
import java.awt.*;//imported for the use of graphic and point components as well as the Graphics2D package
import java.awt.event.*;//imported for the use of MouseAdapter, MouseEvent, MouseMotionAdapter and MouseListener classes
import javax.swing.*;//imported for the use of the JPanel and it's relevent methods
publicclass StickManToolextends JPanel
{
Limb [] limbs;// an array of Limbs that makes up the stick figure
int draggedJoint;// the number representing the limb that is being dragged by the user
boolean beingDragged =false;//a boolean indicating whether a limb is being dragged
Point centre =new Point (100, 200);
Point neck =new Point (100, 100);
Point rshoulder =new Point (120, 150);
Point relbow =new Point (135, 150);
Point rwrist =new Point (180, 180);
Point rhand =new Point (190, 190);
Point lshoulder =new Point (70, 150);
Point lelbow =new Point (85, 150);
Point lwrist =new Point (130, 180);
Point lhand =new Point (140, 190);
Point rhip =new Point (120, 200);
Point rknee =new Point (135, 230);
Point rankle =new Point (180, 270);
Point rfoot =new Point (190, 280);
Point lhip =new Point (150, 200);
Point lknee =new Point (85, 230);
Point lankle =new Point (130, 270);
Point lfoot =new Point (140, 280);
public StickManTool ()
{
limbs =new Limb [17];
limbs [0] =new Limb (centre, neck, 7);
limbs [1] =new Limb (neck, rshoulder, 4);
limbs [2] =new Limb (rshoulder, relbow, 3);
limbs [3] =new Limb (relbow, rwrist, 2);
limbs [4] =new Limb (rwrist, rhand, 1);
limbs [5] =new Limb (neck, lshoulder, 4);
limbs [6] =new Limb (lshoulder, lelbow, 3);
limbs [7] =new Limb (lelbow, lwrist, 2);
limbs [8] =new Limb (lwrist, lhand, 1);
limbs [9] =new Limb (centre, rhip, 4);
limbs [10] =new Limb (rhip, rknee, 3);
limbs [11] =new Limb (rknee, rankle, 2);
limbs [12] =new Limb (rankle, rfoot, 1);
limbs [13] =new Limb (centre, lhip, 4);
limbs [14] =new Limb (lhip, lknee, 3);
limbs [15] =new Limb (lknee, lankle, 2);
limbs [16] =new Limb (lankle, lfoot, 1);
this.setBackground (Color.white);//set the background colour of the component to white
this.addMouseListener (new PressMouseListener ());//Add mouse listener "PressMouseListener"
this.addMouseMotionListener (new DragMotionListener ());//Add mouse listener "DragMotionListener"
}
protectedvoid paintComponent (Graphics g)
{
super.paintComponent (g);//calls the paint method of the super class using the inputted Graphics component "g"
Graphics2D graphics = (Graphics2D) g;//create an object of the more advanced Graphics2D class, "graphics"
//turn antialiasing on in order to achieve better quality graphics rendering
graphics.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Head2D head =new Head2D (neck);
graphics.draw (head);
//call each limb's respective draw method which accesses the above created Graphics2D component "graphics", taking
//it in as a variable for the sake of saving having to redraw each limb here and rather drawing it in the individual
//limb's class through each limb's draw method but using the Graphics2D object of this class
for (int i = 0 ; i < limbs.length ; i++)
{
limbs [i].draw (graphics);//call each limb's draw method using the Graphics2D object "graphics" created above
}
}
//A custom mouse listener class that enables each limb to be selected and indicate whether a limb is selected so that it
//can be draged by the mouse through the use of an extended MouseMotionAdapter below
class PressMouseListenerextends MouseAdapter
{
int size = 8;//the size of the hitArea rectangle
//create a rectangle which will reflect an area that a limb must be in for that limb to begin being dragged
Rectangle hitArea =new Rectangle (size, size);
publicvoid mousePressed (MouseEvent e)
{
//create the point "mousePoint" using the co-ordinates of the mouse from the MouseEvent "e"
Point mousePoint = e.getPoint ();
//the rectangle representing the area in which a drag will start if the mouse is clicked, "hitArea" is
//resized according to the point "mousePoint" which represents the position of the mouse. It is sized
//from the centre using the "setFrameFromCenter" method using the co-ordinates of the mouse as x and y locations
//and extending half the size in each direction from the centre.
hitArea.setFrameFromCenter (mousePoint.x, mousePoint.y,
(mousePoint.x + size / 2), (mousePoint.y + size / 2));
//run through the array of limbs and check if any limb lies within the "hitArea" rectangle and if there are
//limbs that intersect with the "hitArea" rectangle (a small area around the mouse) then "draggedJoint" will
//be set to that limb's array index and the "beingDragged" variable will be set to true and a drag will begin.
//Once a limb is found to be within the "hitArea" rectangle, the for loop is ended with a "break" so that only
//one limb is dragged at a time. The rectangle "hitArea" is used so that the user doesn't have to click exactly
//on the limb in order to make a drag begin but may click close to a limb, increasing functionality.
for (int i = 0 ; i < limbs.length ; i++)
{
if (limbs [i].connectingLine.intersects (hitArea))
{
beingDragged =true;//set the "beingDragged" boolean to indicate that a drag is taking place
draggedJoint = i;//set the "draggedJoint" variable to reflect the limb which is within the "hitArea"
break;//end the for loop so that only one limb is dragged at a time
}
}
}
publicvoid mouseReleased (MouseEvent e)
{
beingDragged =false;// set the boolean "beingDragged" to false, indicating that the mouse has stopped dragging
}
}
//A custom MouseMotionAdapter that enables each limb and it's respective child limbs to be dragged by the mouse whilst
//maintaining length and orientation.
class DragMotionListenerextends MouseMotionAdapter
{
publicvoid mouseDragged (MouseEvent e)// when the component is dragging
{
if (beingDragged)//if a limb is being dragged by the mouse as determined by the custom mouse listener above
{
//create the point "mousePoint" using the co-ordinates of the mouse from the MouseEvent "e"
Point mousePoint = e.getPoint ();
//Create a temporary copy of the user-clicked limb based on the "draggedJoint" index of the limbs array
Limb limb = limbs [draggedJoint];
Point origin = limb.fixedJoint;// create a non-moving point "origin" by which the child limbs will rotate
//create a new point from the temporary limb's "movingJoint" variable that is altered below
Point movingJointBefore =new Point (limb.movingJoint);
// variables that calculate x and y values according to where the drag started and where the mouse ends
double moveY = mousePoint.y - origin.y;
double moveX = mousePoint.x - origin.x;
//Calculates the arc of the tangent to the gradient of the line, thus calculating the rotation angle of the
//limb according to the drag action of the user
double rotationAngle = Math.atan2 (moveY, moveX);
limb.rotate (rotationAngle);//rotate the selected limb by theta using the limb's "rotate" method
//update the selected limb and respective child limbs according to the original (fixed) joint of rotation
//that the user selected using the above mouse listener and also the computed point as the user drags the
//limb.
updateLimbs (movingJointBefore, limb.movingJoint);
repaint ();//repaint the component to reflect the new limb and child limbs as the drag takes place
}
}
}
privatevoid updateLimbs (Point start, Point end)
{
int moveX = end.x - start.x;//value representing difference in x values for limb before and after dragging
int moveY = end.y - start.y;//value representing difference in y values for limb before and after dragging
//run through the array of limbs, using the joint that has been dragged, "draggedJoint" as a reference for
//where in the array to start, namely "i". The amount of times to run through the for loop is determined
//by the number of child limbs that the selected limb has. The reason for this is because if the elbow, for
//instance was dragged, the wrist and hand must be dragged aswell but maintain the same distance between
//their joints and this is the reason that the move distances, namely "moveX" and "moveY" are calculated above.
//The variable "numChildLimbs" simply indicates the number of limbs which are attatched to the dragged limb and
//need to be updated so an elbow would, for example, have a numChildLimbs value of two because there are two
//limbs attatched to it, namely the arm and the hand
for (int i = (draggedJoint + 1) ; i < (draggedJoint + limbs [draggedJoint].getNumChildLimbs ()) ; i++)
{
limbs [i].move (moveX, moveY);//move the respective limbs by the above calculated move distances
}
}
}
import java.awt.*;//for use of Graphics2D package, extended classes Point and Colour and their respective methods
import java.awt.geom.*;//for use of the Line2D class and associated methods
publicclass Limb
{
Point fixedJoint;//the joint of the limb that does not move when dragged
Point movingJoint;// the joint of the limb which moves, and all attatched limbs move with, when dragged
int numChildLimbs;//the number of limbs that are attatched to this limb - used for limb dragging functionality
Line2D.Double connectingLine;// the line that connects the two joints, "fixedJoint" and "movingJoint"
double length;//length of line connecting the two joints, "fixedJoint" and "movingJoint"
//create a "Limb" object using two inputted points. A line is created connecting the points and its length calculated
//The number of limbs which are connected to this limb are denoted by the "numChildLimbsIn" variable
public Limb (Point fixedJointIn, Point movingJointIn,int numChildLimbsIn)
{
setFixedJoint (fixedJointIn);//set the fixed joint, "fixedJoint", to the inputted point, "fixedJointIn"
setMovingJoint (movingJointIn);//set the moving joint, "movingJoint", to the inputted point, "movingJointIn"
connectingLine =new Line2D.Double (fixedJointIn, movingJointIn);// line between two points of limb, "fixedJoint" and "movingJoint"
length = fixedJoint.distance (movingJoint);//distance between two points
setNumChildLimbs (numChildLimbsIn);//set the number of attatched "child limbs" to the inputted "numChildLimbsIn"
}
publicvoid setFixedJoint (Point fixedJointIn)
{
fixedJoint = fixedJointIn;
}
publicvoid setMovingJoint (Point movingJointIn)
{
movingJoint = movingJointIn;
}
//Takes in a Graphics2D object and uses it to draw the fixed joint and the line between "fixedJoint" and "movingJoint"
publicvoid draw (Graphics2D graphics)
{
graphics.setPaint (Color.black);//set the colour of the taken in Graphics2D object to black
graphics.draw (connectingLine);//draw the line between the points "fixedJoint" and "movingJoint"
graphics.setPaint (Color.blue);//set the colour of the taken in Graphics2D object to blue
graphics.draw (new Joint2D (fixedJoint));// draws circle around joint using the point "fixedJoint" as a reference for drawing co-ordinates
}
//Draws the line connecting the two points, "fixedJoint" and "movingJoint"
publicvoid drawConnectingLine ()
{
connectingLine.setLine (fixedJoint, movingJoint);//redraw the line between the points "fixedJoint" and "movingJoint"
}
//Calculates the new x and y co-ordinates after the limb has been rotated by the inputted angle "angleOfRotation"
//and redraws the connecting line to reflect the rotation
publicvoid rotate (double angleOfRotation)
{
double y = fixedJoint.y + (length * Math.sin (angleOfRotation));//calculate the y value of point after rotation
double x = fixedJoint.x + (length * Math.cos (angleOfRotation));//calculate the x value of point after rotation
movingJoint.setLocation (x, y);// move the moving joint to a new location based on calculated co-ordinates
drawConnectingLine ();//redraws line between fixedJoint and movingJoint to reflect rotation
}
//Moves the point "movingJoint" by the inputted amounts so that it's new co-ords are (x + xMove,y + yMove).
publicvoid move (int xMove,int yMove)
{
movingJoint.translate (xMove, yMove);//move movingJoint xMove along the x axis and yMove along the y axis
drawConnectingLine ();//redraws line between fixedJoint and movingJoint to reflect move
}
//return the int "numChildLimbs", the number of limbs that are attatched to this limb
publicint getNumChildLimbs ()
{
return numChildLimbs;
}
//set the int "numChildLimbs" to the inputted value
publicvoid setNumChildLimbs (int numChildLimbsIn)
{
numChildLimbs = numChildLimbsIn;
}
}
import java.awt.geom.*;
class Joint2Dextends Ellipse2D.Double
{
privatestaticfinaldouble RADIUS = 10;//radius of joint
public Joint2D (Point2D pointIn)
{
super (pointIn.getX () - (RADIUS / 2),
pointIn.getY () - (RADIUS / 2),
RADIUS, RADIUS);
}
publicdouble getRadius ()
{
return RADIUS;
}
}
import java.awt.geom.*;
import java.awt.*;
class Head2Dextends Ellipse2D.Double
{
publicstaticdouble width = 30;// width of head
publicstaticdouble height = 40;//height of head
Point2D center;// the same as movingJoint
Point2D corner;// used to create bounding box for ellipse
Point2D neck;
public Head2D (Point2D neckIn)// takes in a point representing the neck's co-ordinates
{
super ();
setNeck (neckIn);
center =new Point2D.Double (neckIn.getX (), neckIn.getY () - (height / 2));
corner =new Point2D.Double (neckIn.getX () - (width / 2), neckIn.getY ());
setFrameFromCenter (center, corner);//set bounding box
}
publicvoid setNeck (Point2D neckIn)
{
neck = neckIn;
}
public Point2D getCenter ()
{
return center;
}
public Point2D getNeck ()
{
return neck;
}
publicdouble getWidth ()
{
return width;
}
publicdouble getHeight ()
{
return height;
}
publicvoid setWidth (double widthIn)
{
width = widthIn;
}
publicvoid setHeight (double heightIn)
{
height = heightIn;
}
}

