I am engineering a program for my bachelor thesis that displays the inner geometry of a steel wire rope depending on the z location (location in the cable).
To test I "walk through" a piece of the cable, with this piece of code (sl is a strand list, initialized already, that works fine):
Cable c = new Cable(sl);
ImageFrame ts = new ImageFrame(c);
try {
while (location <2 ) {
location = location + 0.01;
Thread.sleep(100);
c.update(location);
ts.repaint();
}
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
The cables function update just should recalculate the geometry of the cable. My class cable has a list of strands, and what this update function does, is just calling the update function for all these strands:
public void update(double z) {
for(int i=0;i<strandList.size() ;i++) {
strandList.get(i).updateStrand(z);
}
}
The object Strand looks like this:
public abstract class Strand {
Punt middenCable;
final Punt nulpoint_midden;
Punt midden;
Double angle;
Double d_to_core;
Image img;
int img_size;
BufferedImage bf;
/**
*
* #param c centre of the cable
* #param p middle of the strand
* #param ang angle at this point
* #param dtc distance to core
* #param i image
*/
public Strand(Punt c,Punt p,Image i) {
nulpoint_midden = p; //location of strand at z=0
middenCable =c;
midden = p; // for drawing
img = i;
img_size = 135; //TODO adapt
bf = new BufferedImage(img_size, img_size, BufferedImage.TRANSLUCENT );
bf.getGraphics().drawImage(img, 0, 0, null);
}
/**
* angle goed zetten tov de z
* #param z
*/
abstract public void updateStrand(Double z);
public void paint(Graphics g){
g.setColor(Color.RED); //cirkels rood
//rotate around itself
int x = (int) (this.midden.getX() - img_size/2 );
int y = (int) (this.midden.getY() - img_size/2 );
int st = (int) this.img_size;
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
AffineTransform at = new AffineTransform();
at.setToRotation(Math.toRadians(angle), midden.getX(), midden.getY());
g2d.setTransform(at);
g2d.drawImage(bf, x, y, null);
g2d.dispose();
}
In the paint-function it rotate around itself with a certain angle. But the problem is the abstract function "update strand". What this should do is change the coordinates of the punt Midden to the location it actually is at. The parameter nulpoint_midden should not change and be constant, for calculation. For the object type outer strand (extends strand) the updatestrand is like beneath:
import java.awt.Image;
public class OuterStrand extends Strand{
private double rotateconstant1 = 10; //tweaken tot perfecte laylength
private double rotateconstant2 = (2*Math.PI/34); //tweaken tot perfecte laylength
public OuterStrand(Punt c,Punt p, Image i) {
super(c, p,i);
}
#Override
public void updateStrand(Double z) {
// midden updaten
double xpoint = nulpoint_midden.getX(); //original point
double ypoint = nulpoint_midden.getY(); //original point
double cCableX = super.middenCable.getX();
double cCableY = super.middenCable.getY();
double dx = nulpoint_midden.getX() - cCableX;
double dy = ypoint - cCableY;
System.out.println(xpoint+ " " +ypoint) ;
double curangle = Math.atan2(dy, dx);
double dist = Math.sqrt(dx*dx + dy*dy);
double dangle = rotateconstant2 * z;
double x1 = cCableX + dist * Math.cos(dangle + curangle);
double y1 = cCableY + dist * Math.sin(dangle + curangle);
super.midden.setX(x1);
super.midden.setY(y1);
// rotate around itself
super.angle = z * Math.PI * rotateconstant1;
}
}
So the println gives the xpoint and ypoint, and this should be constant. But actually, it changes (so the punt nulpoint_midden changes value). I have no idea why this value changes. here is my print:
> 500.0 200.0
> 500.55439838802135 200.00051226305845
> 501.66318759079536 200.00461035702926
> 503.32632406233347 200.0184412864145
> 505.54367148773895 200.0512248625842
> 508.3149156018249 200.11525184075379
> 511.63945065512087 200.22587972200301
> 515.5162375992163 200.40152475227
> 519.9436341271526 200.66364828540742
> 524.9191967987913 201.03673531535162
> 530.439455611731 201.54826262515832
> 536.4996615512757 202.22865365075 (etcetera)
Because of this changing values, the representation is not correct. Due to the walking through the cable, it should rotate at a constant speed, but is actually accelerating.
I am sorry for the long explanation, but if someone sees my mistake and tells me what i am doing wrong that would be great. Thanks!
I think you are confusing with the meaning of final modifier.
This modifier means that assignment to this variable can be done only once. This is controlled by compiler. You simply cannot compile code that tries to change value of final variable. However if variable is mutable you can change it state by accessing its members (fields or methods).
BTW, could you please next time try to create shorter code snippets? I am sorry to say it but you are asking trivial question and posting tons of absolutely irrelevant code.
The final keyword means the reference may be assigned only once, but it doesn't mean that the object being referenced is somehow prevented from having its values changed.
There's a potential bug in your constructor:
nulpoint_midden = p;
midden = p;
The two instance variables refer to the same object: if you call a setter on midden, nulpoint_midden will appear to change too.
The final variable nulpoint_midden refers to the same object as the variable midden, so changes to the object referred to by midden are also visible via nulpoint_modden.
Related
I am currently working on making a screensaver and I want my ellipse to slowly transform to a rectangle in java. What is the easiest way of doing that?
There are some shapes that are easy to transform into one another. For instance a square is a rectangle with equal side lengths, a circle is an ellipse with equal axes. So it is easy to transform a square into a rectangle since you can just use some drawrectangle function and adjust the parameters the whole way. Ditto for circle to ellipse.
squaretorect(double width,double height)
{
//Transform a square width * width to a rectangle width * height
int n = 100;//Number of intermediate points
int i;
double currentheight;
for(i=0;i<n;i++)
{
currentheight = width + (height-width) * i/(n-1);
drawrectangle(width,currentheight);
}
}
Transforming from a rectangle to an ellipse is harder, since in between the shape is neither a rectangle nor an ellipse. It may be that there is some more general object which can be either a rectangle, an ellipse, or something in between, but I cannot think of one.
So, the easy way is out, but there is a harder way to do it. Suppose if I divide the unit circle into N pieces and write points on an ellipse Ei and a rectangle Ri. Now as the transformation happens the points Ei move into the points Ri. A simple way to do this is to use a linear combination.
Ti = (1-v) * Ei + v * Ri
So to do the transformation we slowly increment v from 0 to 1. And we draw lines(or better yet interpolate) between the points Ti.
ellipsetorectangle(double a, double b, double w, double h)
{
//(x/a)^2+(y/b)^2 = 1
//Polar r = 1/sqrt(cos(phi)^2/a^2 + sin(phi)^2/b^2)
int N = 1000;
int i;
double phi; double r;
double phirect = atan(w/h);//Helps determine which of the 4 line segments we are on
ArrayList<Point> Ei;
ArrayList<Point> Ri;
for(i=0;i<N;i++)
{
//Construct ellipse
phi = 2PI * (double)i/N;
r = 1/sqrt(cos(phi)^2/a^2 + sin(phi)^2/b^2);
Ei.add(new Point(r * cos(phi),r * sin(phi));
//Construct Rectangle (It's hard)
if(phi > 2Pi - phirect || phi < phirect)
{Ri.add(new Point(w/2,w/2 * tan(phi)));}
else if(phi > phirect)
{Ri.add(new Point(h/2 * tan(phi),h/2));}
else if(phi > PI-phirect)
{Ri.add(new Point(-w/2,-w/2 * tan(phi)));}
else if(phi > PI+phirect)
{Ri.add(new Point(-h/2,-h/2 * tan(phi)));}
}
}
Arraylist<Point> Ti;
int transitionpoints = 100;
double v;
int j;
for(j=0;j<transitionpoints;j++)
{
//This outer loop represents one instance of the object. You should probably clear the picture here. This probably belongs in a separate function but it would take awhile to write it that way.
for(i=0;i<N;i++)
{
v = (double)1 * j/(N-1);
Ti = new Point(v * Ri.get(i).getx + (1-v) * Ei.get(i).getx,
v * Ri.get(i).gety + (1-v) * Ei.get(i).gety);
if(i != 0)
drawline(Ti,Tiold);
Tiold = Ti;
}
}
Rotating Asteroids ( Polygons )
I am trying to rotate asteroids(polygons) so that they look nice. I am doing this through multiple mathematical equations. To start I give the individual asteroid a rotation velocity:
rotVel = ((Math.random()-0.5)*Math.PI/16);
Then I create the polygon shape,
this.shape = new Polygon();
Followed by generating the points,
for (j = 0; j < s; j++) {
theta = 2 * Math.PI / s * j;
r = MIN_ROCK_SIZE + (int) (Math.random() * (MAX_ROCK_SIZE - MIN_ROCK_SIZE));
x = (int) -Math.round(r * Math.sin(theta)) + asteroidData[0];
y = (int) Math.round(r * Math.cos(theta)) + asteroidData[1];
shape.addPoint(x, y);
}
Finally, in a loop a method is being called in which it attempts to move the polygon and its points down as well as rotating them. (I'm just pasting the rotating part as the other one is working)
for (int i = 0; i < shape.npoints; i++) {
// Subtract asteroid's x and y position
double x = shape.xpoints[i] - asteroidData[0];
double y = shape.ypoints[i] - asteroidData[1];
double temp_x = ((x * Math.cos(rotVel)) - (y * Math.sin(rotVel)));
double temp_y = ((x * Math.sin(rotVel)) + (y * Math.cos(rotVel)));
shape.xpoints[i] = (int) Math.round(temp_x + asteroidData[0]);
shape.ypoints[i] = (int) Math.round(temp_y + asteroidData[1]);
}
now, the problem is that when it prints to the screen the asteroids appear to 'warp' or rather the x and y positions on some of the polygon points 'float' off course.
I've noticed that when I make 'rotVel' be a whole number the problem is solved however the asteroid will rotate at mach speeds. So I've concluded that the problem has to be in the rounding but no matter what I do I can't seem to find a way to get it to work as the Polygon object requires an array of ints.
Does anyone know how to fix this?
Currently your asteroids rotate around (0 , 0) as far as i can see. Correct would be to rotate them around the center of the shape, which would be (n , m), where n is the average of all x-coordinates of the shape, and m is the average of all y-coordinates of the shape.
Your problem is definitely caused by rounding to int! The first improvement is to make all shape coordinates to be of type double. This will solve most of your unwanted 'effects'.
But even with double you might experience nasty rounding errors in case you do a lot of very small updates of the coordinates. The solution is simple: Just avoid iterative updates of the asteroid points. Every time, you update the coordinates based on the previous coordinates, the rounding error will get worse.
Instead, add a field for the rotation angle to the shape and increment it instead of the points themselves. Not until drawing the shape, you compute the final positions by applying the rotation to the points. Note that this will never change the points themselves.
You can extend this concept to other transformations (e.g. translation) too. What you get is some kind of local coordinate system for every shape/object. The points of the shape are defined in the local coordinate system. By moving and rotating this system, you can reposition the entire object anywhere in space.
public class Shape {
// rotation and position of the local coordinate system
private double rot, x, y;
// points of the shape in local coordinate system
private double[] xp, yp;
private int npoints;
// points of the shape in world coordinates
private int[][] wxp, wyp;
private boolean valid;
public void setRotation(double r) { this.rot = r; valid = false; }
public void setPosition(double x, double y) { this.x = x; this.y = y; valid = false; }
public void addPoint(double x, double y) {
// TODO: add point to xp, yp
valid = false;
}
public void draw(...) {
if (!valid) {
computeWorldCoordinates(wxp, wyp);
valid = true;
}
// TODO: draw shape at world coordaintes wxp and wyp
}
protected void computeWorldCoordinates(int[] xcoord, int[] ycoord) {
for (int i = 0; i < npoints; i++) {
double temp_x = xp[i] * Math.cos(rot) - yp[i] * Math.sin(rot);
double temp_y = xp[i] * Math.sin(rot) + yp[i] * Math.cos(rot);
xcoord[i] = (int) Math.round(x + temp_x);
ycoord[i] = (int) Math.round(y + temp_y);
}
}
}
I have a image with blackened circles.
The image is a scanned copy of an survey sheet pretty much like an OMR questionnaire sheet.
I want to detect the circles that have been blackened using the JUI(if any other api required)
I have a few examples while searching, but they dont give me accurate result.
I tried..UDAI,Moodle...etc...
Then I decided to make my own. I am able to detect the black pixels but as follows.
BufferedImage mapa = BMPDecoder.read(new File("testjui.bmp"));
final int xmin = mapa.getMinX();
final int ymin = mapa.getMinY();
final int ymax = ymin + mapa.getHeight();
final int xmax = xmin + mapa.getWidth();
for (int i = xmin;i<xmax;i++)
{
for (int j = ymin;j<ymax;j++)
{
int pixel = mapa.getRGB(i, j);
if ((pixel & 0x00FFFFFF) == 0)
{
System.out.println("("+i+","+j+")");
}
}
}
This gives me the co-ordinates of all the black pixels but i cannot make out if its a circle or not.
How can I identify if its a circle.
2] Also I want to know if the image scanned is tilted....I know that the Udai api takes care of that, but for some reason I am not able to get my survey template to run with that code.
So if I understood correctly, you have code that picks out the black pixels so now you have the coordinates of all black pixels and you want to determine all of those that fall on a circle.
The way I would approach this is in 2 steps.
1) Cluster the pixels. Create a class called Cluster, that contains a list of points and use your clustering algorithm to put all the points in the right cluster.
2) Determine which clusters are circles. To do this find the midpoint of all of the points in each cluster (just take the mean of all the points). Then find the minimum and maximum distances from the center, The difference between these should be less than the maximum thickness for a circle in your file. These will give you the radii for the innermost and outermost circles contained within the circle. Now use the equation of a circle x^2 + y^2 = radius, with the radius set to a value between the maximum and minimum found previously to find the points that your cluster should contain. If your cluster contains these it is a circle.
Of course other considerations to consider is whether the shapes you have approximate ellipses rather than circles, in which case you should use the equation of an ellipse. Furthermore, if your file contains circle-like shapes you will need to write additional code to exclude these. On the other hand if all of your circles are exactly the same size you can cut the work that needs to be done by having your algorithm search for circles of that size only.
I hope I could be of some help, good luck!
To answer your first question, I created a class that checks weather an image contains a single non black filled black outlined circle.
This class is experimental, it does not provide exact results all the time, feel free to edit it and to correct the bugs you might encounter.
The setters do not check for nulls or out of range values.
import java.awt.Point;
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;
/**
* Checks weather an image contains a single non black filled black outlined circle<br />
* This class is experimental, it does not provide exact results all the time, feel free to edit it and to correct
* the bugs you might encounter.
* #author Ahmed KRAIEM
* #version 0.9 alpha
* #since 2013-04-03
*/
public class CircleChecker {
private BufferedImage image;
/**
* Points that are equal to the calculated radius±<code>radiusesErrorMargin%</code> are not considered rogue points.<br />
* <code>radiusesErrorMargin</code> must be <code>>0 && <1</code>
*/
private double radiusesErrorMargin = 0.2;
/**
* A shape that has fewer than roguePointSensitivity% of rogue points is considered a circle.<br />
* <code>roguePointSensitivity</code> must be <code>>0 && <1</code>
*/
private double roguePointSensitivity = 0.05;
/**
* The presumed circle is divided into <code>angleCompartimentPrecision</code> parts,<br />
* each part must have <code>minPointsPerCompartiment</code> points
* <code>angleCompartimentPrecision</code> must be <code>> 0</code>
*/
private int angleCompartimentPrecision = 50;
/**
* The minimum number of points requiered to declare a part valid.<br />
* <code>minPointsPerCompartiment</code> must be <code>> 0</code>
*/
private int minPointsPerCompartiment = 20;
public CircleChecker(BufferedImage image) {
super();
this.image = image;
}
public CircleChecker(BufferedImage image, double radiusesErrorMargin,
int minPointsPerCompartiment, double roguePointSensitivity,
int angleCompartimentPrecision) {
this(image);
this.radiusesErrorMargin = radiusesErrorMargin;
this.minPointsPerCompartiment = minPointsPerCompartiment;
this.roguePointSensitivity = roguePointSensitivity;
this.angleCompartimentPrecision = angleCompartimentPrecision;
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
}
public double getRadiusesErrorMargin() {
return radiusesErrorMargin;
}
public void setRadiusesErrorMargin(double radiusesErrorMargin) {
this.radiusesErrorMargin = radiusesErrorMargin;
}
public double getMinPointsPerCompartiment() {
return minPointsPerCompartiment;
}
public void setMinPointsPerCompartiment(int minPointsPerCompartiment) {
this.minPointsPerCompartiment = minPointsPerCompartiment;
}
public double getRoguePointSensitivity() {
return roguePointSensitivity;
}
public void setRoguePointSensitivity(double roguePointSensitivity) {
this.roguePointSensitivity = roguePointSensitivity;
}
public int getAngleCompartimentPrecision() {
return angleCompartimentPrecision;
}
public void setAngleCompartimentPrecision(int angleCompartimentPrecision) {
this.angleCompartimentPrecision = angleCompartimentPrecision;
}
/**
*
* #return true if the image contains no more than <code>roguePointSensitivity%</code> rogue points
* and all the parts contain at least <code>minPointsPerCompartiment</code> points.
*/
public boolean isCircle() {
List<Point> list = new ArrayList<>();
final int xmin = image.getMinX();
final int ymin = image.getMinY();
final int ymax = ymin + image.getHeight();
final int xmax = xmin + image.getWidth();
for (int i = xmin; i < xmax; i++) {
for (int j = ymin; j < ymax; j++) {
int pixel = image.getRGB(i, j);
if ((pixel & 0x00FFFFFF) == 0) {
list.add(new Point(i, j));
}
}
}
if (list.size() == 0)
return false;
double diameter = -1;
Point p1 = list.get(0);
Point across = null;
for (Point p2 : list) {
double d = distance(p1, p2);
if (d > diameter) {
diameter = d;
across = p2;
}
}
double radius = diameter / 2;
Point center = center(p1, across);
int diffs = 0;
int diffsUntilError = (int) (list.size() * roguePointSensitivity);
double minRadius = radius - radius * radiusesErrorMargin;
double maxRadius = radius + radius * radiusesErrorMargin;
int[] compartiments = new int[angleCompartimentPrecision];
for (int i=0; i<list.size(); i++) {
Point p = list.get(i);
double calRadius = distance(p, center);
if (calRadius>maxRadius || calRadius < minRadius)
diffs++;
else{
//Angle
double angle = Math.atan2(p.y -center.y,p.x-center.x);
//angle is between -pi and pi
int index = (int) ((angle + Math.PI)/(Math.PI * 2 / angleCompartimentPrecision));
compartiments[index]++;
}
if (diffs >= diffsUntilError){
return false;
}
}
int sumCompartiments = list.size() - diffs;
for(int comp : compartiments){
if (comp < minPointsPerCompartiment){
return false;
}
}
return true;
}
private double distance(Point p1, Point p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
private Point center(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File("image.bmp"));
CircleChecker cc = new CircleChecker(image);
System.out.println(cc.isCircle());
}
}
You'll need to program in a template of what a circle would look like, and then make it scalable to suit the different circle sizes.
For example circle of radius 3 would be:
o
ooo
o
This assumes you have a finite set of circles you need to find, maybe up to 5x5 or 6x6 this would be feasible.
or you could use: Midpoint circle algorithm
This would involve finding all black pixel groups and then selecting the middle pixel for each one.
Apply this algorithm using the outer pixels as a guid to how big the circle could be.
Finding the difference between black /expected black pixels.
If the black to expected black ratio is high enough, its a black circle and you can delete / whiten it.
Is there a reason that they decided not to add the contains method (for Path) in Android?
I'm wanting to know what points I have in a Path and hoped it was easier than seen here:
How can I tell if a closed path contains a given point?
Would it be better for me to create an ArrayList and add the integers into the array? (I only check the points once in a control statement) Ie. if(myPath.contains(x,y)
So far my options are:
Using a Region
Using an ArrayList
Extending the Class
Your suggestion
I'm just looking for the most efficient way I should go about this
I came up against this same problem a little while ago, and after some searching, I found this to be the best solution.
Java has a Polygon class with a contains() method that would make things really simple. Unfortunately, the java.awt.Polygonclass is not supported in Android. However, I was able to find someone who wrote an equivalent class.
I don't think you can get the individual points that make up the path from the Android Path class, so you will have to store the data in a different way.
The class uses a Crossing Number algorithm to determine whether or not the point is inside of the given list of points.
/**
* Minimum Polygon class for Android.
*/
public class Polygon
{
// Polygon coodinates.
private int[] polyY, polyX;
// Number of sides in the polygon.
private int polySides;
/**
* Default constructor.
* #param px Polygon y coods.
* #param py Polygon x coods.
* #param ps Polygon sides count.
*/
public Polygon( int[] px, int[] py, int ps )
{
polyX = px;
polyY = py;
polySides = ps;
}
/**
* Checks if the Polygon contains a point.
* #see "http://alienryderflex.com/polygon/"
* #param x Point horizontal pos.
* #param y Point vertical pos.
* #return Point is in Poly flag.
*/
public boolean contains( int x, int y )
{
boolean oddTransitions = false;
for( int i = 0, j = polySides -1; i < polySides; j = i++ )
{
if( ( polyY[ i ] < y && polyY[ j ] >= y ) || ( polyY[ j ] < y && polyY[ i ] >= y ) )
{
if( polyX[ i ] + ( y - polyY[ i ] ) / ( polyY[ j ] - polyY[ i ] ) * ( polyX[ j ] - polyX[ i ] ) < x )
{
oddTransitions = !oddTransitions;
}
}
}
return oddTransitions;
}
}
I would just like to comment on #theisenp answer: The code has integer arrays and if you look on the algorithm description webpage it warns against using integers instead of floating point.
I copied your code above and it seemed to work fine except for some corner cases when I made lines that didnt connect to themselves very well.
By changing everything to floating point, I got rid of this bug.
Tried the other answer, but it gave an erroneous outcome for my case. Didn't bother to find the exact cause, but made my own direct translation from the algorithm on:
http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
Now the code reads:
/**
* Minimum Polygon class for Android.
*/
public class Polygon
{
// Polygon coodinates.
private int[] polyY, polyX;
// Number of sides in the polygon.
private int polySides;
/**
* Default constructor.
* #param px Polygon y coods.
* #param py Polygon x coods.
* #param ps Polygon sides count.
*/
public Polygon( int[] px, int[] py, int ps )
{
polyX = px;
polyY = py;
polySides = ps;
}
/**
* Checks if the Polygon contains a point.
* #see "http://alienryderflex.com/polygon/"
* #param x Point horizontal pos.
* #param y Point vertical pos.
* #return Point is in Poly flag.
*/
public boolean contains( int x, int y )
{
boolean c = false;
int i, j = 0;
for (i = 0, j = polySides - 1; i < polySides; j = i++) {
if (((polyY[i] > y) != (polyY[j] > y))
&& (x < (polyX[j] - polyX[i]) * (y - polyY[i]) / (polyY[j] - polyY[i]) + polyX[i]))
c = !c;
}
return c;
}
}
For completeness, I want to make a couple notes here:
As of API 19, there is an intersection operation for Paths. You could create a very small square path around your test point, intersect it with the Path, and see if the result is empty or not.
You can convert Paths to Regions and do a contains() operation. However Regions work in integer coordinates, and I think they use transformed (pixel) coordinates, so you'll have to work with that. I also suspect that the conversion process is computationally intensive.
The edge-crossing algorithm that Hans posted is good and quick, but you have to be very careful for certain corner cases such as when the ray passes directly through a vertex, or intersects a horizontal edge, or when round-off error is a problem, which it always is.
The winding number method is pretty much fool proof, but involves a lot of trig and is computationally expensive.
This paper by Dan Sunday gives a hybrid algorithm that's as accurate as the winding number but as computationally simple as the ray-casting algorithm. It blew me away how elegant it was.
My code
This is some code I wrote recently in Java which handles a path made out of both line segments and arcs. (Also circles, but those are complete paths on their own, so it's sort of a degenerate case.)
package org.efalk.util;
/**
* Utility: determine if a point is inside a path.
*/
public class PathUtil {
static final double RAD = (Math.PI/180.);
static final double DEG = (180./Math.PI);
protected static final int LINE = 0;
protected static final int ARC = 1;
protected static final int CIRCLE = 2;
/**
* Used to cache the contents of a path for pick testing. For a
* line segment, x0,y0,x1,y1 are the endpoints of the line. For
* a circle (ellipse, actually), x0,y0,x1,y1 are the bounding box
* of the circle (this is how Android and X11 like to represent
* circles). For an arc, x0,y0,x1,y1 are the bounding box, a1 is
* the start angle (degrees CCW from the +X direction) and a1 is
* the sweep angle (degrees CCW).
*/
public static class PathElement {
public int type;
public float x0,y0,x1,y1; // Endpoints or bounding box
public float a0,a1; // Arcs and circles
}
/**
* Determine if the given point is inside the given path.
*/
public static boolean inside(float x, float y, PathElement[] path) {
// Based on algorithm by Dan Sunday, but allows for arc segments too.
// http://geomalgorithms.com/a03-_inclusion.html
int wn = 0;
// loop through all edges of the polygon
// An upward crossing requires y0 <= y and y1 > y
// A downward crossing requires y0 > y and y1 <= y
for (PathElement pe : path) {
switch (pe.type) {
case LINE:
if (pe.x0 < x && pe.x1 < x) // left
break;
if (pe.y0 <= y) { // start y <= P.y
if (pe.y1 > y) { // an upward crossing
if (isLeft(pe, x, y) > 0) // P left of edge
++wn; // have a valid up intersect
}
}
else { // start y > P.y
if (pe.y1 <= y) { // a downward crossing
if (isLeft(pe, x, y) < 0) // P right of edge
--wn; // have a valid down intersect
}
}
break;
case ARC:
wn += arcCrossing(pe, x, y);
break;
case CIRCLE:
// This should be the only element in the path, so test it
// and get out.
float rx = (pe.x1-pe.x0)/2;
float ry = (pe.y1-pe.y0)/2;
float xc = (pe.x1+pe.x0)/2;
float yc = (pe.y1+pe.y0)/2;
return (x-xc)*(x-xc)/rx*rx + (y-yc)*(y-yc)/ry*ry <= 1;
}
}
return wn != 0;
}
/**
* Return >0 if p is left of line p0-p1; <0 if to the right; 0 if
* on the line.
*/
private static float
isLeft(float x0, float y0, float x1, float y1, float x, float y)
{
return (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);
}
private static float isLeft(PathElement pe, float x, float y) {
return isLeft(pe.x0,pe.y0, pe.x1,pe.y1, x,y);
}
/**
* Determine if an arc segment crosses the test ray up or down, or not
* at all.
* #return winding number increment:
* +1 upward crossing
* 0 no crossing
* -1 downward crossing
*/
private static int arcCrossing(PathElement pe, float x, float y) {
// Look for trivial reject cases first.
if (pe.x1 < x || pe.y1 < y || pe.y0 > y) return 0;
// Find the intersection of the test ray with the arc. This consists
// of finding the intersection(s) of the line with the ellipse that
// contains the arc, then determining if the intersection(s)
// are within the limits of the arc.
// Since we're mostly concerned with whether or not there *is* an
// intersection, we have several opportunities to punt.
// An upward crossing requires y0 <= y and y1 > y
// A downward crossing requires y0 > y and y1 <= y
float rx = (pe.x1-pe.x0)/2;
float ry = (pe.y1-pe.y0)/2;
float xc = (pe.x1+pe.x0)/2;
float yc = (pe.y1+pe.y0)/2;
if (rx == 0 || ry == 0) return 0;
if (rx < 0) rx = -rx;
if (ry < 0) ry = -ry;
// We start by transforming everything so the ellipse is the unit
// circle; this simplifies the math.
x -= xc;
y -= yc;
if (x > rx || y > ry || y < -ry) return 0;
x /= rx;
y /= ry;
// Now find the points of intersection. This is simplified by the
// fact that our line is horizontal. Also, by the time we get here,
// we know there *is* an intersection.
// The equation for the circle is x²+y² = 1. We have y, so solve
// for x = ±sqrt(1 - y²)
double x0 = 1 - y*y;
if (x0 <= 0) return 0;
x0 = Math.sqrt(x0);
// We only care about intersections to the right of x, so
// that's another opportunity to punt. For a CCW arc, The right
// intersection is an upward crossing and the left intersection
// is a downward crossing. The reverse is true for a CW arc.
if (x > x0) return 0;
int wn = arcXing1(x0,y, pe.a0, pe.a1);
if (x < -x0) wn -= arcXing1(-x0,y, pe.a0, pe.a1);
return wn;
}
/**
* Return the winding number of the point x,y on the unit circle
* which passes through the arc segment defined by a0,a1.
*/
private static int arcXing1(double x, float y, float a0, float a1) {
double a = Math.atan2(y,x) * DEG;
if (a < 0) a += 360;
if (a1 > 0) { // CCW
if (a < a0) a += 360;
return a0 + a1 > a ? 1 : 0;
} else { // CW
if (a0 < a) a0 += 360;
return a0 + a1 <= a ? -1 : 0;
}
}
}
Edit: by request, adding some sample code that makes use of this.
import PathUtil;
import PathUtil.PathElement;
/**
* This class represents a single geographic area defined by a
* circle or a list of line segments and arcs.
*/
public class Area {
public float lat0, lon0, lat1, lon1; // bounds
Path path = null;
PathElement[] pathList;
/**
* Return true if this point is inside the area bounds. This is
* used to confirm touch events and may be computationally expensive.
*/
public boolean pointInBounds(float lat, float lon) {
if (lat < lat0 || lat > lat1 || lon < lon0 || lon > lon1)
return false;
return PathUtil.inside(lon, lat, pathList);
}
static void loadBounds() {
int n = number_of_elements_in_input;
path = new Path();
pathList = new PathElement[n];
for (Element element : elements_in_input) {
PathElement pe = new PathElement();
pathList[i] = pe;
pe.type = element.type;
switch (element.type) {
case LINE: // Line segment
pe.x0 = element.x0;
pe.y0 = element.y0;
pe.x1 = element.x1;
pe.y1 = element.y1;
// Add to path, not shown here
break;
case ARC: // Arc segment
pe.x0 = element.xmin; // Bounds of arc ellipse
pe.y0 = element.ymin;
pe.x1 = element.xmax;
pe.y1 = element.ymax;
pe.a0 = a0; pe.a1 = a1;
break;
case CIRCLE: // Circle; hopefully the only entry here
pe.x0 = element.xmin; // Bounds of ellipse
pe.y0 = element.ymin;
pe.x1 = element.xmax;
pe.y1 = element.ymax;
// Add to path, not shown here
break;
}
}
path.close();
}
I am making a simulation of a pendulum, but it only performs one swing before sending close to random positions for the bob to be at. Essentially, it does not go backwards.
I have tried to change the direction using the goingForward boolean, but it still doesnt work.
public class AnimationPane extends JPanel {
// START CHANGEABLE VARIABLES
private double startAngle = -60.0; // degrees
private double mass = 1; // kilogrammes
private int radius = 10; // m
private double gravity = 9.80665; // m/s^2 // on earth: 9.80665
// END CHANGEABLE VARIABLEs
private BufferedImage ball;
private BufferedImage rope;
private int pointX = 180;
private int pointY = 50;
private double endAngle = Math.abs(startAngle); // absolute value of startAngle
private double angle = startAngle; // current angle
private double circum = (2 * Math.PI * radius); // m
private double distance = 0; // m
private double velocity = 0; // m/s
private double totalEnergy = ((radius) - (Math.cos(Math.toRadians(angle)) * radius)) * gravity * mass + 0.00001;
private double previousE;
private int xPos = 0; // for program
private int yPos = 0; // for program
private boolean goingForward = true;
private double height = 0;
public AnimationPane() {
try {
ball = ImageIO.read(new File("rsz_black-circle-mask-to-fill-compass-outline.png"));
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double angleRad = Math.toRadians(Math.abs(angle));
double potentialE = ((radius) - (Math.cos(angleRad) * radius)) * gravity * mass;
Double pE = new Double(potentialE);
height = (radius - (Math.cos(angleRad) * radius));
double kineticE = totalEnergy - pE;
if (kineticE <= 0 || angle >= endAngle) {
if (goingForward == true) {
goingForward = false;
}
else
{
goingForward = true;
}
kineticE = 0.1;
angle = 60;
}
velocity = Math.sqrt(2 * kineticE / mass);
double ratio = distance / circum;
if (goingForward == true) {
distance = distance + (velocity / 10);
angle = startAngle + (360 * ratio);
}
else {
distance = distance - (velocity / 10);
angle = startAngle - (360 * ratio);
}
double angles = Math.toRadians(angle);
double xDouble = Math.sin(angles) * (radius * 10);
Double x = new Double(xDouble);
xPos = x.intValue() + 150;
double yDouble = Math.cos(angles) * (radius * 10);
Double y = new Double(yDouble);
yPos = y.intValue() + 50;
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(xPos + 20, yPos + 20, pointX, pointY);
g.drawImage(ball, xPos, yPos, this);
}
}
I would really appreciate some help getting this to work.
Thank you
I could not debug your code, which was uneasy to work with and sometimes to understand (you use a lot of integer literals in your code, which hides their semantic, I have no idea what was your intention on some statements).
Therefore, I rewrote it using the solution of the differential equation for small oscillations. It works, you can take it as a clean base to implement it again the way you wanted. Note that as Andy Turner pointed it, you should not have to worry about the fact of going forward or backward. You have an equation, you solve it, it gives you the position of the ball at any time. If you want something which is accurate for large angles, I suggest you go on Wikipedia to see the movement equation in this case. Last option, you could numerically solve the differential equation although I would personally don't know how to do it at first glance.
package stackoverflow;
public class AnimationPane extends JPanel {
private static final long serialVersionUID = 1L;
private static final double GRAVITY = 9.80665;
private BufferedImage ball;
private final Point fixedCordPoint;
private final int cordLength;
private final double startAngle;
private double currentAngle;
private final double pulsation;
private final Point ballPos = new Point();
private int time = 1;
public AnimationPane(Point fixedCordPoint, int cordLength, double startAngleRadians) {
this.fixedCordPoint = new Point(fixedCordPoint);
this.cordLength = cordLength;
this.pulsation = Math.sqrt(GRAVITY / cordLength);
this.startAngle = startAngleRadians;
this.currentAngle = startAngleRadians;
this.ball = loadImage(new File("ball.jpg"));
}
private BufferedImage loadImage(File file) {
try {
return ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load file : " + file, e);
}
}
public void start() {
Timer timer = new Timer(100, event -> {
ballPos.x = fixedCordPoint.x + (int) Math.round(Math.sin(currentAngle) * cordLength);
ballPos.y = fixedCordPoint.y + (int) Math.round(Math.cos(currentAngle) * cordLength);
repaint();
currentAngle = startAngle * Math.cos(pulsation * time);
time++;
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(ballPos.x, ballPos.y, fixedCordPoint.x, fixedCordPoint.y);
g.drawImage(ball, ballPos.x - ball.getWidth() / 2, ballPos.y - ball.getHeight() / 2, this);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AnimationPane pendulumAnimationPane = new AnimationPane(new Point(160, 25), 180, - Math.PI / 10);
frame.setContentPane(pendulumAnimationPane);
frame.setSize(400,400);
frame.setVisible(true);
pendulumAnimationPane.start();
}
}
I really can't follow your code. If I had to say where I thought the problem is, I would say that it is the distance variable, and its consequent use in ratio: this seems to be defined as being zero when the pendulum starts. When you switch the direction of the pendulum, I think that it needs to be rebased to zero... Somehow.
You should also separate out your GUI code from your simulation. You can just make it print out the x and y coordinates while you are debugging. For instance, you can write the values to a CSV file, so you can visualize them in Matlab (or whatever) to ensure that your simulation looks right over time.
I would make two suggestions to help you with the simulation:
Don't model it in terms of energy, which is a scalar quantity, meaning that you have to use additional state like a "direction flag" to model which way it is going;
You are going to need an additional state variable. Storing x and y is redundant, since these can be derived directly from the angle (and R, although that is constant). You are modelling a second-order system, so you need that second state variable which models the movement of the system at an instant, as well as its position. For example, you could model the angle and angular velocity over time.
And, of course, don't mess around with degrees - stick to radians :)
(On the matter of velocity, your variable of that name is actually speed - you don't know which direction it moves in, and that information is highly relevant to the dynamics of the system).
The derivation of the method using angle and angular velocity is quite straightforward, although my maths is rusty enough to caution its use without checking carefully!
The rotational form of Newton's second law of motion is:
Torque = moment of inertia * angular acceleration
The torque on the bob is given by -mgR sin angle (m is mass, g is gravity, R is length of pendulum, angle is the angular displacement of the pendulum from vertical). The minus sign is because the force tends to return the bob to vertical;
The moment of inertia of a point mass is mR^2 - this is a reasonable approximation if the pendulum is very long compared to the radius of the bob.
Therefore the angular acceleration is -g sin theta / R. Hopefully this should look like simple harmonic motion - an acceleration proportional to the distance from an equilibrium (for small theta, sin theta is approximately theta) and directed towards it.
You can now put this together into a numerical scheme to simulate the pendulum. For example, using Euler's method:
New angle = old angle + dt * old angular velocity
New angular velocity = old angular velocity vel - dt * g * sin(angle) / R
where dt is your time step.
You can, of course, use higher-order methods like Runge-Kutta to reduce the numerical errors in the simulation, but it is (slightly) more involved.