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;

}

}

[28865 byte] By [trust_nowuna] at [2007-11-27 10:38:40]
# 1

When you see this

C:\jexp\stickman>javac stickmantest.java

Note: stickmantest.java uses or overrides a deprecated API.

Note: Recompile with -Xlint:deprecation for details.

Recompile like this:

C:\jexp\stickman>javac -Xlint:deprecation stickmantest.java

stickmantest.java:29: warning: [deprecation] show() in java.awt.Window has been

deprecated

show ();

^

1 warning

Look up the deprecated method in the Window api and replace it with the new/recommended one.

For the head, try these changes:

class StickManTool ...

{

Limb [] limbs;

Head2D head;

...

public StickManTool ()

{

head = new Head2D (neck);

limbs = new Limb [17];

...

protected void paintComponent (Graphics g)

{

...

graphics.draw(head);

...

}

private void updateLimbs(Point start, Point end)

{

...

head.setNeck(limbs[0].movingJoint.getLocation());

}

}

class Head2D ...

{

...

public Head2D(Point2D neckIn)

{

super ();

setNeck(neckIn);

}

public void setNeck(Point2D neckIn)

{

neck = 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

}

...

}

crwooda at 2007-7-28 18:55:56 > top of Java-index,Security,Cryptography...
# 2

Thank you mister crwood once again...

I've tried it though and theres an issue. I've drawn two diagrams, can i email them to you because i don't know how to post them here...

i'll try explain in words but the diagrams make things much clearer...

Basically, i want it to look like a lollipop =)

Your suggested code works fine when the neck point is at the starting position, it gets the corner and center point and draws the ellipse from that but i want the ellipse to be like a lollipop on the end of a stick where the stick is the spine (with the neck being where the stick joins the lillipop part) and the lollipop is the head. What i mean by this is that where the ellipse joins the stick must not move. Very hard to explain =)

It would be okay even to make the head a cirlce if that would be easier but it needs to remain at the same orientation to the line that forms the back in other words when you drag the line of the back, the circle must adjust so that when the stickman looks like he is bending over, the ellipse that forms the head must be below the shoulder line.

It's tricky to explain because none of the other limbs adjust in rotation when you drag another limb but when you drag the back i want the head to rotate around by the same amount whilst still being drawn from the point of the neck.

- there, i think that best explains it.

I still have the images if you want me to email them.

It took me all day yesterday working on this but i didn't figure it out and it's frustrating the hell out of me. I'm thinking the easiest way to do it would be to make it draw from the point of the neck but rotate the same amount as the neck has. i can't seem to get it right. thanks again mister.

i really appreciate it

trust_nowuna at 2007-7-28 18:55:56 > top of Java-index,Security,Cryptography...
# 3

Changes for spine–aligned head:

public class StickManTool extends JPanel

{

...

public StickManTool ()

{

...

limbs [16] = new Limb (lankle, lfoot, 1);

head = new Head2D (limbs[0]);

...

}

protected void paintComponent (Graphics g)

{

...

head.draw(graphics);

}

private void updateLimbs (Point start, Point end)

{

...

head.setPosition();

}

}

class Head2D extends Ellipse2D.Double

{

public static double width = 30;// width of head

public static double height = 40; //height of head

Point atlas;

Point pelvis;

private AffineTransform xform = new AffineTransform();

public Head2D(Limb spine)

{

super ();

atlas = spine.movingJoint;

pelvis = spine.fixedJoint;

setPosition();

}

public void setPosition()

{

// Find angle of spine.

double dy = atlas.y - pelvis.y;

double dx = atlas.x - pelvis.x;

double theta = Math.atan2(dy, dx);

//System.out.printf("theta = %.1f%n", Math.toDegrees(theta));

// Find center of head as extension along spine from atlas.

double cx = atlas.x + (height/2)*Math.cos(theta);

double cy = atlas.y + (height/2)*Math.sin(theta);

// Move to origin of head.

xform.setToTranslation(cx-width/2, cy-height/2);

// Rotate head about its center.

xform.rotate(theta+Math.PI/2, width/2, height/2);

}

public void draw(Graphics2D g2)

{

g2.draw(xform.createTransformedShape(this));

}

public double getWidth () { return width; }

public double getHeight () { return height; }

public void setWidth (double widthIn) { width = widthIn; }

public void setHeight (double heightIn) { height = heightIn; }

}

crwooda at 2007-7-28 18:55:56 > top of Java-index,Security,Cryptography...
# 4

Thanks again mr crwood.

I was toying with that kind of method all of sunday only i didn't manage to get the aTan() working properly. I was using an approach of using half the height of the head as an extention of the line but it kept on drawing it horizontally instead of transforming it according to angle and when i was adding the co-ordinates, it was doing so only in horizontal and verticle plans and it looked very deformed =)

Thanks once again for your input and insight, i am learning much from you.

have an amazing day

trust_nowuna at 2007-7-28 18:55:56 > top of Java-index,Security,Cryptography...