What I'm trying to do is come up with a way to generate n random points on a graph (displaying it isn't necessary). A point is randomly selected and connected to the point closest to it (or the next closest if it's already connected to the best option) in a way so that no two lines intersect. This repeats until no more connections are possible. The vertices are meant to represent regions on a map, and connections represent adjacency. The following code I have thus far is as follows, taken from http://javaingrab.blogspot.com/2012/12/m-way-graph-coloring-with-backtracking.html:
public class MWayGrColor{
/*G is graph's adjacency matrix and x is solution vector */
private int G[][],x[],n,m,soln;
public void mColoring(int k){ //backtracking function
for(int i=1;i<=n;i++){
next_color(k); //coloring kth vertex
if(x[k]==0)
return; //if unsuccessful then backtrack
if(k==n) //if all colored then show
write();
else
mColoring(k+1); /* successful but still left to color */
}
}
private void next_color(int k){
do{
int i=1;
x[k]=(x[k]+1)%(m+1);
if(x[k]==0)
return;
for(i=1;i<=n;i++)
if(G[i][k]!=0 && x[k]==x[i]) /* checking adjacency and not same color */
break;
if(i==n+1) return; //new color found
}while(true);
}
private void write(){
System.out.print("\nColoring(V C) # "+(++soln)+"-->");
for(int i=1;i<=n;i++)
System.out.print("\t("+i+" "+x[i]+")"); //solution vector
}
public void input(){
java.util.Scanner sc=new java.util.Scanner(System.in);
System.out.print("Enter no. of vertices : ");
n=sc.nextInt();
G=new int[n+1][n+1];
x=new int[n+1];
System.out.print("Enter no. of colors : ");
m=sc.nextInt();
System.out.println("Enter adjacency matrix-->");
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
G[i][j]=sc.nextInt();
}
public static void main (String[] args) {
MWayGrColor obj=new MWayGrColor();
obj.input();
obj.mColoring(1);
if(obj.soln==0)
System.out.println("\nNeed more than "+obj.m+" colors");
else
System.out.print("\nTOTAL SOLN : "+obj.soln);
}
}
As noted, the map doesn't need to be visually represented, as the current method of display is adequate. I'm aware of the Point2D.Double class and the Line2D class, and I was originally going to just start generating points and use the lines to create the adjacency matrix already shown in the code, but the methods for connecting points and avoiding repetition are extremely confusing to me in how they should be implemented. How can I accomplish this generation of an adjacency matrix?
It's still not clear what the actual question is. It sounds like "this is so complicated, I don't get it done". However, unless there are strict requirements about the approach and its running time etc., one can pragmatically write down what has to be done:
do
{
V v0 = randomVertex();
V v1 = findClosestUnconnected(v0);
if (line(v0,v1).intersectsNoOtherLine())
{
insert(line(v0,v1));
}
} while (insertedNewLine);
Of course, this implies some searching. For large graphs there may be some sophisticated data structures to accelerate this. Particularly the search for the nearest (unconnected) neighbor may be accelerated with the classical structures like KD-trees etc. But this seems to be unrelated to the original question.
The handing of the adjacency matrix can be made a bit more convenient with a wrapper that offers methods that allow a more "natural" description:
class Graph
{
private final boolean matrix[][];
void addEdge(V v0, V v1)
{
matrix[v0.index][v1.index] = true;
matrix[v1.index][v0.index] = true;
}
boolean hasEdge(V v0, V v1)
{
return matrix[v0.index][v1.index];
}
}
But in this case, this is only a minor, syntactical simplification.
An example, only as a VERY q&d sketch:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class NonIntersectingAdjacencies
{
public static void main(String[] args)
{
Random random = new Random(0);
int numVertices = 25;
List<AdjacencyVertex> vertices =
createRandomVertices(numVertices, random);
final AdjacencyGraph adjacencyGraph =
new AdjacencyGraph(vertices);
boolean createdNewLine = true;
while (createdNewLine)
{
createdNewLine = false;
List<Integer> indices =
createShuffledList(numVertices, random);
for (int i=0; i<numVertices; i++)
{
int randomIndex = indices.get(i);
AdjacencyVertex randomVertex = vertices.get(randomIndex);
AdjacencyVertex closest =
findClosestUnconnected(randomVertex, adjacencyGraph);
if (closest != null)
{
if (!intersectsOtherLine(
randomVertex, closest, adjacencyGraph))
{
adjacencyGraph.addEdge(randomVertex, closest);
createdNewLine = true;
}
}
}
}
AdjacencyGraphPanel.show(adjacencyGraph);
}
private static List<AdjacencyVertex> createRandomVertices(
int numVertices, Random random)
{
List<AdjacencyVertex> vertices = new ArrayList<AdjacencyVertex>();
for (int i=0; i<numVertices; i++)
{
AdjacencyVertex v = new AdjacencyVertex();
v.index = i;
v.x = random.nextDouble();
v.y = random.nextDouble();
vertices.add(v);
}
return vertices;
}
private static List<Integer> createShuffledList(
int maxValue, Random random)
{
List<Integer> list = new ArrayList<Integer>();
for (int i=0; i<maxValue; i++)
{
list.add(i);
}
Collections.shuffle(list, random);
return list;
}
private static boolean intersectsOtherLine(
AdjacencyVertex v0, AdjacencyVertex v1,
AdjacencyGraph adjacencyGraph)
{
Line2D newLine = new Line2D.Double(
v0.x, v0.y, v1.x, v1.y);
List<AdjacencyVertex> vertices = adjacencyGraph.getVertices();
for (int i=0; i<vertices.size(); i++)
{
for (int j=0; j<vertices.size(); j++)
{
if (i == j)
{
continue;
}
AdjacencyVertex oldV0 = vertices.get(i);
AdjacencyVertex oldV1 = vertices.get(j);
if (adjacencyGraph.hasEdge(oldV0, oldV1))
{
Line2D oldLine = new Line2D.Double(
oldV0.x, oldV0.y, oldV1.x, oldV1.y);
if (Intersection.intersect(oldLine, newLine))
{
return true;
}
}
}
}
return false;
}
private static AdjacencyVertex findClosestUnconnected(
AdjacencyVertex v,
AdjacencyGraph adjacencyGraph)
{
double minDistanceSquared = Double.MAX_VALUE;
AdjacencyVertex closest = null;
List<AdjacencyVertex> vertices = adjacencyGraph.getVertices();
for (int i=0; i<vertices.size(); i++)
{
AdjacencyVertex other = vertices.get(i);
if (other.index == v.index)
{
continue;
}
if (adjacencyGraph.hasEdge(v, other))
{
continue;
}
double dx = other.x - v.x;
double dy = other.y - v.y;
double distanceSquared = Math.hypot(dx, dy);
if (distanceSquared < minDistanceSquared)
{
minDistanceSquared = distanceSquared;
closest = other;
}
}
return closest;
}
}
class AdjacencyVertex
{
double x;
double y;
int index;
}
class AdjacencyGraph
{
private final boolean matrix[][];
private final List<AdjacencyVertex> vertices;
AdjacencyGraph(List<AdjacencyVertex> vertices)
{
this.vertices = vertices;
this.matrix = new boolean[vertices.size()][vertices.size()];
}
List<AdjacencyVertex> getVertices()
{
return vertices;
}
void addEdge(AdjacencyVertex v0, AdjacencyVertex v1)
{
matrix[v0.index][v1.index] = true;
matrix[v1.index][v0.index] = true;
}
boolean hasEdge(AdjacencyVertex v0, AdjacencyVertex v1)
{
return matrix[v0.index][v1.index];
}
}
//============================================================================
// Only helper stuff below this line...
class AdjacencyGraphPanel extends JPanel
{
public static void show(final AdjacencyGraph adjacencyGraph)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI(adjacencyGraph);
}
});
}
private static void createAndShowGUI(AdjacencyGraph adjacencyGraph)
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new AdjacencyGraphPanel(adjacencyGraph));
f.setSize(600,600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private final AdjacencyGraph adjacencyGraph;
public AdjacencyGraphPanel(AdjacencyGraph adjacencyGraph)
{
this.adjacencyGraph = adjacencyGraph;
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
int offsetX = 30;
int offsetY = 30;
int w = getWidth() - offsetX - offsetX;
int h = getHeight() - offsetY - offsetY;
g.setColor(Color.BLACK);
List<AdjacencyVertex> vertices = adjacencyGraph.getVertices();
for (int i=0; i<vertices.size(); i++)
{
for (int j=0; j<vertices.size(); j++)
{
if (i == j)
{
continue;
}
AdjacencyVertex v0 = vertices.get(i);
AdjacencyVertex v1 = vertices.get(j);
if (adjacencyGraph.hasEdge(v0, v1))
{
Line2D newLine = new Line2D.Double(
offsetX + v0.x*w,
offsetY + v0.y*h,
offsetX + v1.x*w,
offsetY + v1.y*h);
g.draw(newLine);
}
}
}
g.setColor(Color.BLUE);
for (int i=0; i<vertices.size(); i++)
{
AdjacencyVertex v = vertices.get(i);
int ix = (int)(offsetX + v.x * w);
int iy = (int)(offsetY + v.y * h);
g.fill(new Ellipse2D.Double(
ix - 5, iy - 5, 10, 10));
g.drawString(String.valueOf(i), ix, iy+16);
}
}
}
class Intersection
{
static boolean intersect(Line2D line0, Line2D line1)
{
Point2D location = new Point2D.Double();
Point2D intersection =
Intersection.computeIntersectionSegmentSegment(
line0, line1, location);
if (intersection == null)
{
return false;
}
return !isAtLineAnd(location);
}
private static boolean isAtLineAnd(Point2D location)
{
double EPSILON = 0.05;
if (Math.abs(location.getX()) < EPSILON)
{
return true;
}
if (Math.abs(location.getX()-1) < EPSILON)
{
return true;
}
if (Math.abs(location.getY()) < EPSILON)
{
return true;
}
if (Math.abs(location.getY()-1) < EPSILON)
{
return true;
}
return false;
}
/**
* Epsilon for floating point computations
*/
private static final double epsilon = 1e-6f;
/**
* Computes the intersection of the specified line segments and returns
* the intersection point, or <code>null</code> if the line segments do
* not intersect.
*
* #param line0 The first line segment
* #param line1 The second line segment
* #param location Optional location that stores the
* relative location of the intersection point on
* the given line segments
* #return The intersection point, or <code>null</code> if
* there is no intersection.
*/
public static Point2D computeIntersectionSegmentSegment(
Line2D line0, Line2D line1, Point2D location)
{
return computeIntersectionSegmentSegment(
line0.getX1(), line0.getY1(), line0.getX2(), line0.getY2(),
line1.getX1(), line1.getY1(), line1.getX2(), line1.getY2(),
location);
}
/**
* Computes the intersection of the specified line segments and returns
* the intersection point, or <code>null</code> if the line segments do
* not intersect.
*
* #param s0x0 x-coordinate of point 0 of line segment 0
* #param s0y0 y-coordinate of point 0 of line segment 0
* #param s0x1 x-coordinate of point 1 of line segment 0
* #param s0y1 y-coordinate of point 1 of line segment 0
* #param s1x0 x-coordinate of point 0 of line segment 1
* #param s1y0 y-coordinate of point 0 of line segment 1
* #param s1x1 x-coordinate of point 1 of line segment 1
* #param s1y1 y-coordinate of point 1 of line segment 1
* #param location Optional location that stores the
* relative location of the intersection point on
* the given line segments
* #return The intersection point, or <code>null</code> if
* there is no intersection.
*/
public static Point2D computeIntersectionSegmentSegment(
double s0x0, double s0y0,
double s0x1, double s0y1,
double s1x0, double s1y0,
double s1x1, double s1y1,
Point2D location)
{
if (location == null)
{
location = new Point2D.Double();
}
Point2D result = computeIntersectionLineLine(
s0x0, s0y0, s0x1, s0y1, s1x0, s1y0, s1x1, s1y1, location);
if (location.getX() >= 0 && location.getX() <= 1.0 &&
location.getY() >= 0 && location.getY() <= 1.0)
{
return result;
}
return null;
}
/**
* Computes the intersection of the specified lines and returns the
* intersection point, or <code>null</code> if the lines do not
* intersect.
*
* Ported from
* http://www.geometrictools.com/LibMathematics/Intersection/
* Wm5IntrSegment2Segment2.cpp
*
* #param s0x0 x-coordinate of point 0 of line segment 0
* #param s0y0 y-coordinate of point 0 of line segment 0
* #param s0x1 x-coordinate of point 1 of line segment 0
* #param s0y1 y-coordinate of point 1 of line segment 0
* #param s1x0 x-coordinate of point 0 of line segment 1
* #param s1y0 y-coordinate of point 0 of line segment 1
* #param s1x1 x-coordinate of point 1 of line segment 1
* #param s1y1 y-coordinate of point 1 of line segment 1
* #param location Optional location that stores the
* relative location of the intersection point on
* the given line segments
* #return The intersection point, or <code>null</code> if
* there is no intersection.
*/
public static Point2D computeIntersectionLineLine(
double s0x0, double s0y0,
double s0x1, double s0y1,
double s1x0, double s1y0,
double s1x1, double s1y1,
Point2D location)
{
double dx0 = s0x1 - s0x0;
double dy0 = s0y1 - s0y0;
double dx1 = s1x1 - s1x0;
double dy1 = s1y1 - s1y0;
double len0 = Math.sqrt(dx0*dx0+dy0*dy0);
double len1 = Math.sqrt(dx1*dx1+dy1*dy1);
double dir0x = dx0 / len0;
double dir0y = dy0 / len0;
double dir1x = dx1 / len1;
double dir1y = dy1 / len1;
double c0x = s0x0 + dx0 * 0.5;
double c0y = s0y0 + dy0 * 0.5;
double c1x = s1x0 + dx1 * 0.5;
double c1y = s1y0 + dy1 * 0.5;
double cdx = c1x - c0x;
double cdy = c1y - c0y;
double dot = dotPerp(dir0x, dir0y, dir1x, dir1y);
if (Math.abs(dot) > epsilon)
{
double dot0 = dotPerp(cdx, cdy, dir0x, dir0y);
double dot1 = dotPerp(cdx, cdy, dir1x, dir1y);
double invDot = 1.0/dot;
double s0 = dot1*invDot;
double s1 = dot0*invDot;
if (location != null)
{
double n0 = (s0 / len0) + 0.5;
double n1 = (s1 / len1) + 0.5;
location.setLocation(n0, n1);
}
double x = c0x + s0 * dir0x;
double y = c0y + s0 * dir0y;
return new Point2D.Double(x,y);
}
return null;
}
/**
* Returns the perpendicular dot product, i.e. the length
* of the vector (x0,y0,0)x(x1,y1,0).
*
* #param x0 Coordinate x0
* #param y0 Coordinate y0
* #param x1 Coordinate x1
* #param y1 Coordinate y1
* #return The length of the cross product vector
*/
private static double dotPerp(double x0, double y0, double x1, double y1)
{
return x0*y1 - y0*x1;
}
}
Related
I am attempting to simulate a sphere, and shade it realistically given an origin vector for the light, and the sphere being centered around the origin. Moreover, the light's vector is the normal vector on a larger invisible sphere at a chosen point. The sphere looks off.
https://imgur.com/a/IDIwQQF
The problem, is that it is very difficult to bug fix this kind of program. Especially considering that I know how I want it to look in my head, but when looking at the numbers in my program there is very little meaning attached to them.
Since I don't know where the issue is, I'm forced to paste all of it here.
public class SphereDrawing extends JPanel {
private static final long serialVersionUID = 1L;
private static final int ADJ = 320;
private static final double LIGHT_SPHERE_RADIUS = 5;
private static final double LIGHT_X = 3;
private static final double LIGHT_Y = 4;
private static final double LIGHT_Z = 0;
private static final double DRAWN_SPHERE_RADIUS = 1;
private static final int POINT_COUNT = 1000000;
private static Coord[] points;
private static final double SCALE = 200;
public SphereDrawing() {
setPreferredSize(new Dimension(640, 640));
setBackground(Color.white);
points = new Coord[POINT_COUNT];
initializePoints();
for (int i = 0; i < points.length; i++) {
points[i].scale();
}
new Timer(17, (ActionEvent e) -> {
repaint();
}).start();
}
public void initializePoints() { //finding the points on the surface of the sphere (hopefully somewhat equidistant)
double random = Math.random() * (double)POINT_COUNT;
double offset = 2/(double)POINT_COUNT;
double increment = Math.PI * (3 - Math.sqrt(5));
for (int i = 0; i < POINT_COUNT; i++) {
double y = ((i * offset) - 1) + (offset / 2);
double r = Math.sqrt(1 - Math.pow(y, 2));
double phi = ((i + random) % (double)POINT_COUNT) * increment;
double x = Math.cos(phi) * r;
double z = Math.sin(phi) * r;
points[i] = new Coord(x, y, z);
}
}
public void drawSphere(Graphics2D g) {
g.translate(ADJ, ADJ); //shifting from origin for drawing purposes
Arrays.sort(points); //sorting points by their z coordinates
double iHat = -2 * LIGHT_X;
double jHat = -2 * LIGHT_Y; //Light vector
double kHat = -2 * LIGHT_Z;
double angL1 = 0;
if (Math.abs(iHat) != 0.0)
angL1 = Math.atan(jHat / iHat); //converting light vector to spherical coordinates
else
angL1 = Math.PI/2;
double angL2 = Math.atan(Math.sqrt(Math.pow(iHat, 2) + Math.pow(jHat, 2))/ kHat);
double maxArcLength = LIGHT_SPHERE_RADIUS * Math.PI; // maximum arc length
for (int i = 0; i < points.length; i++) {
if(points[i].checkValid()) {
double siHat = -2 * points[i].x;
double sjHat = -2 * points[i].y; //finding normal vector for the given point on the sphere
double skHat = -2 * points[i].z;
double angSF1 = -1 * Math.abs(Math.atan(sjHat / siHat)); // converting vector to spherical coordinates
double angSF2 = Math.atan(Math.sqrt(Math.pow(siHat, 2) + Math.pow(sjHat, 2))/ skHat);
double actArcLength = LIGHT_SPHERE_RADIUS * Math.acos(Math.cos(angL1) * Math.cos(angSF1) + Math.sin(angL1) * Math.sin(angSF1) * Math.cos(angL2 - angSF2)); //calculating arc length at this point
double comp = actArcLength / maxArcLength; // comparing the maximum arc length to the calculated arc length for this vector
int col = (int)(comp * 255);
col = Math.abs(col);
g.setColor(new Color(col, col, col));
double ovalDim = (4 * Math.PI * Math.pow(DRAWN_SPHERE_RADIUS, 2))/POINT_COUNT; //using surface area to determine how large size of each point should be drawn
if (ovalDim < 1) // if it too small, make less small
ovalDim = 2;
g.fillOval((int)points[i].x, (int)points[i].y, (int)ovalDim, (int)ovalDim); //draw this oval
}
}
}
#Override
public void paintComponent(Graphics gg) {
super.paintComponent(gg);
Graphics2D g = (Graphics2D) gg;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawSphere(g);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setTitle("Sphere");
f.setResizable(false);
f.add(new SphereDrawing(), BorderLayout.CENTER);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
#SuppressWarnings("rawtypes")
private class Coord implements Comparable {
public double x;
public double y;
public double z;
public Coord(double x2, double y2, double z2) {
x = x2;
y = y2;
z = z2;
}
public void scale() {
x *= SCALE;
y *= SCALE; //drawing purposes
z *= SCALE;
}
public String toString() {
return x + " " + y + " " + z;
}
public int compareTo(Object c) {
double diff = this.z - ((Coord)c).z;
if (diff < 0)
return -1;
else if (diff > 0) //for sorting the array of points
return 1;
else
return 0;
}
public boolean checkValid() {
return (z > 0); //checks if need to draw this point
}
}
}
I was hoping to at least draw a realistic looking sphere, even if not completely accurate, and I couldn't tell you what exactly is off with mine
I am trying to get my node to travel along the path of a circle, and at the same time have THAT circle travel along the path of a rectangle. Is it possible?
This is what I have so far:
void move(GamePane aThis)
{
double speed = 10;
Rectangle rectangle = new Rectangle(100, 200, 100, 500);
Circle circle = new Circle(50);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
circle.setStrokeWidth(3);
PathTransition pt = new PathTransition();
pt.setDuration(Duration.millis(1000));
pt.setPath(circle);
pt.setNode(this);
pt.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt.setCycleCount(Timeline.INDEFINITE);
pt.setAutoReverse(false);
pt.play();
PathTransition pt2 = new PathTransition();
pt2.setDuration(Duration.millis(1000));
pt2.setPath(rectangle);
pt2.setNode(circle);
pt2.setOrientation
(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
pt2.setCycleCount(Timeline.INDEFINITE);
pt2.setAutoReverse(false);
pt2.play();
}
Theoretically it should be possible to nest one transition over the other.
But there is a problem: transitions are applied over translate properties, while the node layout is not modified. This means for your case that the circle will follow the path defined by the rectangle, but your node will keep rotating over the circle's initial position.
So we need to find a way to update the circle's position at any instant, so the node could rotate over it at that position.
Based on this answer, one possible approach is using two AnimationTimers, and a way to interpolate the path at any instant and update the position accordingly.
The first step is converting the original path into one that only use linear elements:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import javafx.geometry.Point2D;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.QuadCurveTo;
/**
*
* #author jpereda
*/
public class LinearPath {
private final Path originalPath;
public LinearPath(Path path){
this.originalPath=path;
}
public Path generateLinePath(){
/*
Generate a list of points interpolating the original path
*/
originalPath.getElements().forEach(this::getPoints);
/*
Create a path only with MoveTo,LineTo
*/
Path path = new Path(new MoveTo(list.get(0).getX(),list.get(0).getY()));
list.stream().skip(1).forEach(p->path.getElements().add(new LineTo(p.getX(),p.getY())));
path.getElements().add(new ClosePath());
return path;
}
private Point2D p0;
private List<Point2D> list;
private final int POINTS_CURVE=5;
private void getPoints(PathElement elem){
if(elem instanceof MoveTo){
list=new ArrayList<>();
p0=new Point2D(((MoveTo)elem).getX(),((MoveTo)elem).getY());
list.add(p0);
} else if(elem instanceof LineTo){
list.add(new Point2D(((LineTo)elem).getX(),((LineTo)elem).getY()));
} else if(elem instanceof CubicCurveTo){
Point2D ini = (list.size()>0?list.get(list.size()-1):p0);
IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalCubicBezier((CubicCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
} else if(elem instanceof QuadCurveTo){
Point2D ini = (list.size()>0?list.get(list.size()-1):p0);
IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalQuadBezier((QuadCurveTo)elem, ini, ((double)i)/POINTS_CURVE)));
} else if(elem instanceof ClosePath){
list.add(p0);
}
}
private Point2D evalCubicBezier(CubicCurveTo c, Point2D ini, double t){
Point2D p=new Point2D(Math.pow(1-t,3)*ini.getX()+
3*t*Math.pow(1-t,2)*c.getControlX1()+
3*(1-t)*t*t*c.getControlX2()+
Math.pow(t, 3)*c.getX(),
Math.pow(1-t,3)*ini.getY()+
3*t*Math.pow(1-t, 2)*c.getControlY1()+
3*(1-t)*t*t*c.getControlY2()+
Math.pow(t, 3)*c.getY());
return p;
}
private Point2D evalQuadBezier(QuadCurveTo c, Point2D ini, double t){
Point2D p=new Point2D(Math.pow(1-t,2)*ini.getX()+
2*(1-t)*t*c.getControlX()+
Math.pow(t, 2)*c.getX(),
Math.pow(1-t,2)*ini.getY()+
2*(1-t)*t*c.getControlY()+
Math.pow(t, 2)*c.getY());
return p;
}
}
Now, based on javafx.animation.PathTransition.Segment inner class, and removing all the private or deprecated API, this class allows public interpolator methods, with or without translation:
import java.util.ArrayList;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
/**
* Based on javafx.animation.PathTransition
*
* #author jpereda
*/
public class PathInterpolator {
private final Path originalPath;
private final Node node;
private double totalLength = 0;
private static final int SMOOTH_ZONE = 10;
private final ArrayList<Segment> segments = new ArrayList<>();
private Segment moveToSeg = Segment.getZeroSegment();
private Segment lastSeg = Segment.getZeroSegment();
public PathInterpolator(Path path, Node node){
this.originalPath=path;
this.node=node;
calculateSegments();
}
public PathInterpolator(Shape shape, Node node){
this.originalPath=(Path)Shape.subtract(shape, new Rectangle(0,0));
this.node=node;
calculateSegments();
}
private void calculateSegments() {
segments.clear();
Path linePath = new LinearPath(originalPath).generateLinePath();
linePath.getElements().forEach(elem->{
Segment newSeg = null;
if(elem instanceof MoveTo){
moveToSeg = Segment.newMoveTo(((MoveTo)elem).getX(),((MoveTo)elem).getY(), lastSeg.accumLength);
newSeg = moveToSeg;
} else if(elem instanceof LineTo){
newSeg = Segment.newLineTo(lastSeg, ((LineTo)elem).getX(),((LineTo)elem).getY());
} else if(elem instanceof ClosePath){
newSeg = Segment.newClosePath(lastSeg, moveToSeg);
if (newSeg == null) {
lastSeg.convertToClosePath(moveToSeg);
}
}
if (newSeg != null) {
segments.add(newSeg);
lastSeg = newSeg;
}
});
totalLength = lastSeg.accumLength;
}
public void interpolate(double frac) {
interpolate(frac,0,0);
}
public void interpolate(double frac, double translateX, double translateY) {
double part = totalLength * Math.min(1, Math.max(0, frac));
int segIdx = findSegment(0, segments.size() - 1, part);
Segment seg = segments.get(segIdx);
double lengthBefore = seg.accumLength - seg.length;
double partLength = part - lengthBefore;
double ratio = partLength / seg.length;
Segment prevSeg = seg.prevSeg;
double x = prevSeg.toX + (seg.toX - prevSeg.toX) * ratio;
double y = prevSeg.toY + (seg.toY - prevSeg.toY) * ratio;
double rotateAngle = seg.rotateAngle;
// provide smooth rotation on segment bounds
double z = Math.min(SMOOTH_ZONE, seg.length / 2);
if (partLength < z && !prevSeg.isMoveTo) {
//interpolate rotation to previous segment
rotateAngle = interpolateAngle(
prevSeg.rotateAngle, seg.rotateAngle,
partLength / z / 2 + 0.5F);
} else {
double dist = seg.length - partLength;
Segment nextSeg = seg.nextSeg;
if (dist < z && nextSeg != null) {
//interpolate rotation to next segment
if (!nextSeg.isMoveTo) {
rotateAngle = interpolateAngle(
seg.rotateAngle, nextSeg.rotateAngle,
(z - dist) / z / 2);
}
}
}
node.setTranslateX(x - getPivotX() + translateX);
node.setTranslateY(y - getPivotY() + translateY);
node.setRotate(rotateAngle);
}
private double getPivotX() {
final Bounds bounds = node.getLayoutBounds();
return bounds.getMinX() + bounds.getWidth()/2;
}
private double getPivotY() {
final Bounds bounds = node.getLayoutBounds();
return bounds.getMinY() + bounds.getHeight()/2;
}
/**
* Returns the index of the first segment having accumulated length
* from the path beginning, greater than {#code length}
*/
private int findSegment(int begin, int end, double length) {
// check for search termination
if (begin == end) {
// find last non-moveTo segment for given length
return segments.get(begin).isMoveTo && begin > 0
? findSegment(begin - 1, begin - 1, length)
: begin;
}
// otherwise continue binary search
int middle = begin + (end - begin) / 2;
return segments.get(middle).accumLength > length
? findSegment(begin, middle, length)
: findSegment(middle + 1, end, length);
}
/** Interpolates angle according to rate,
* with correct 0->360 and 360->0 transitions
*/
private static double interpolateAngle(double fromAngle, double toAngle, double ratio) {
double delta = toAngle - fromAngle;
if (Math.abs(delta) > 180) {
toAngle += delta > 0 ? -360 : 360;
}
return normalize(fromAngle + ratio * (toAngle - fromAngle));
}
/** Converts angle to range 0-360
*/
private static double normalize(double angle) {
while (angle > 360) {
angle -= 360;
}
while (angle < 0) {
angle += 360;
}
return angle;
}
private static class Segment {
private static final Segment zeroSegment = new Segment(true, 0, 0, 0, 0, 0);
boolean isMoveTo;
double length;
// total length from the path's beginning to the end of this segment
double accumLength;
// end point of this segment
double toX;
double toY;
// segment's rotation angle in degrees
double rotateAngle;
Segment prevSeg;
Segment nextSeg;
private Segment(boolean isMoveTo, double toX, double toY,
double length, double lengthBefore, double rotateAngle) {
this.isMoveTo = isMoveTo;
this.toX = toX;
this.toY = toY;
this.length = length;
this.accumLength = lengthBefore + length;
this.rotateAngle = rotateAngle;
}
public static Segment getZeroSegment() {
return zeroSegment;
}
public static Segment newMoveTo(double toX, double toY,
double accumLength) {
return new Segment(true, toX, toY, 0, accumLength, 0);
}
public static Segment newLineTo(Segment fromSeg, double toX, double toY) {
double deltaX = toX - fromSeg.toX;
double deltaY = toY - fromSeg.toY;
double length = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
if ((length >= 1) || fromSeg.isMoveTo) { // filtering out flattening noise
double sign = Math.signum(deltaY == 0 ? deltaX : deltaY);
double angle = (sign * Math.acos(deltaX / length));
angle = normalize(angle / Math.PI * 180);
Segment newSeg = new Segment(false, toX, toY,
length, fromSeg.accumLength, angle);
fromSeg.nextSeg = newSeg;
newSeg.prevSeg = fromSeg;
return newSeg;
}
return null;
}
public static Segment newClosePath(Segment fromSeg, Segment moveToSeg) {
Segment newSeg = newLineTo(fromSeg, moveToSeg.toX, moveToSeg.toY);
if (newSeg != null) {
newSeg.convertToClosePath(moveToSeg);
}
return newSeg;
}
public void convertToClosePath(Segment moveToSeg) {
Segment firstLineToSeg = moveToSeg.nextSeg;
nextSeg = firstLineToSeg;
firstLineToSeg.prevSeg = this;
}
}
}
Basically, once you have a linear path, for every line it generates a Segment. Now with the list of these segments you can call the interpolate method to calculate the position and rotation of the node at any fraction between 0 and 1, and in the case of the second transition, update the position of the shape accordingly.
And finally you can create two AnimationTimers in your application:
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Polygon poly = new Polygon( 0, 0, 30, 15, 0, 30);
poly.setFill(Color.YELLOW);
poly.setStroke(Color.RED);
root.getChildren().add(poly);
Rectangle rectangle = new Rectangle(200, 100, 100, 400);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setStroke(Color.BLUE);
Circle circle = new Circle(50);
circle.setFill(Color.TRANSPARENT);
circle.setStroke(Color.RED);
circle.setStrokeWidth(3);
root.getChildren().add(rectangle);
root.getChildren().add(circle);
PathInterpolator in1=new PathInterpolator(rectangle, circle);
PathInterpolator in2=new PathInterpolator(circle, poly);
AnimationTimer timer1 = new AnimationTimer() {
#Override
public void handle(long now) {
double millis=(now/1_000_000)%10000;
in1.interpolate(millis/10000);
}
};
AnimationTimer timer2 = new AnimationTimer() {
#Override
public void handle(long now) {
double millis=(now/1_000_000)%2000;
// Interpolate over the translated circle
in2.interpolate(millis/2000,
circle.getTranslateX(),
circle.getTranslateY());
}
};
timer2.start();
timer1.start();
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that you can apply different speed to the animations.
This pic takes two instants of this animation.
I have made some progress detecting a specific kind of object. Actually a card, just like any other in your wallet.
Now I'm stuck with deskewing the photo. See:
The blue (rounded) rectangle represents the detected contour.
The purple rotate rectangle represents a RotatedRect extracted from the detected contour.
The green line is just the bounding box.
Well I need neither of those rectangles. The rectangles both have 90 degree corners. Which won't get me the perspective.
My question:
How can I get as accurate as possible all quadrangle corners from a contour?
I have created a class Quadrangle which creates the quadrangle of the 4 most largest connected polygon vertices which will intersect each other at some point. This will work in nearly any case.
If you use this code, remember to adjust the width and height in Quadrangle.warp. Note that it isn't 100% complete, the first and last polygon vertices won't be connected if they may be connect for example.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
class Line {
public Point offset;
public double angle;
public Line(Point offset, double angle) {
this.offset = offset.clone();
this.angle = angle;
}
public Point get(int length) {
Point result = offset.clone();
result.x += Math.cos(angle) * length;
result.y += Math.sin(angle) * length;
return result;
}
public Point getStart() {
return get(-5000);
}
public Point getEnd() {
return get(5000);
}
public void scale(double factor) {
offset.x *= factor;
offset.y *= factor;
}
public static Point intersect(Line l1, Line l2) {
return getLineLineIntersection(l1.getStart().x, l1.getStart().y, l1.getEnd().x, l1.getEnd().y,
l2.getStart().x, l2.getStart().y, l2.getEnd().x, l2.getEnd().y
);
}
public static Point getLineLineIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) {
double det1And2 = det(x1, y1, x2, y2);
double det3And4 = det(x3, y3, x4, y4);
double x1LessX2 = x1 - x2;
double y1LessY2 = y1 - y2;
double x3LessX4 = x3 - x4;
double y3LessY4 = y3 - y4;
double det1Less2And3Less4 = det(x1LessX2, y1LessY2, x3LessX4, y3LessY4);
if (det1Less2And3Less4 == 0){
// the denominator is zero so the lines are parallel and there's either no solution (or multiple solutions if the lines overlap) so return null.
return null;
}
double x = (det(det1And2, x1LessX2,
det3And4, x3LessX4) /
det1Less2And3Less4);
double y = (det(det1And2, y1LessY2,
det3And4, y3LessY4) /
det1Less2And3Less4);
return new Point(x, y);
}
protected static double det(double a, double b, double c, double d) {
return a * d - b * c;
}
}
class LineSegment extends Line implements Comparable {
public double length;
public LineSegment(Point offset, double angle, double length) {
super(offset, angle);
this.length = length;
}
public void melt(LineSegment segment) {
Point point = new Point();
point.x += Math.cos(angle) * length;
point.y += Math.sin(angle) * length;
point.x += Math.cos(segment.angle) * segment.length;
point.y += Math.sin(segment.angle) * segment.length;
angle = Math.atan2(point.y, point.x);
offset.x = (offset.x * length + segment.offset.x * segment.length) / (length + segment.length);
offset.y = (offset.y * length + segment.offset.y * segment.length) / (length + segment.length);
length += segment.length;
}
#Override
public int compareTo(Object other) throws ClassCastException {
if (!(other instanceof LineSegment)) {
throw new ClassCastException("A LineSegment object expected.");
}
return (int) (((LineSegment) other).length - this.length);
}
}
class Quadrangle {
static int
TOP = 0,
RIGHT = 1,
BOTTOM = 2,
LEFT = 3;
public Line[] lines = new Line[4];
public Quadrangle() {
}
private static double getAngle(Point p1, Point p2) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x);
}
private static double getLength(Point p1, Point p2) {
return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
private static double roundAngle(double angle) {
return angle - (2*Math.PI) * Math.round(angle / (2 * Math.PI));
}
public static Quadrangle fromContour(MatOfPoint contour) {
List<Point> points = contour.toList();
List<LineSegment> segments = new ArrayList<>();
// Create line segments
for (int i = 0; i < points.size(); i++) {
double a = getAngle(points.get(i), points.get((i + 1) % points.size()));
double l = getLength(points.get(i), points.get((i + 1) % points.size()));
segments.add(new LineSegment(points.get(i), a, l));
}
// Connect line segments
double angleDiffMax = 2 * Math.PI / 100;
List<LineSegment> output = new ArrayList<>();
for (LineSegment segment : segments) {
if (output.isEmpty()) {
output.add(segment);
} else {
LineSegment top = output.get(output.size() - 1);
double d = roundAngle(segment.angle - top.angle);
if (Math.abs(d) < angleDiffMax) {
top.melt(segment);
} else {
output.add(segment);
}
}
}
Collections.sort(output);
Quadrangle quad = new Quadrangle();
for (int o = 0; o < 4; o += 1) {
for (int i = 0; i < 4; i++) {
if (Math.abs(roundAngle(output.get(i).angle - (2 * Math.PI * o / 4))) < Math.PI / 4) {
quad.lines[o] = output.get(i);
}
}
}
return quad;
}
public void scale(double factor) {
for (int i = 0; i < 4; i++) {
lines[i].scale(factor);
}
}
public Mat warp(Mat src) {
Mat result = src.clone();
Core.line(result, lines[TOP].get(-5000), lines[TOP].get(5000), new Scalar(200, 100, 100), 8);
Core.line(result, lines[RIGHT].get(-5000), lines[RIGHT].get(5000), new Scalar(0, 255, 0), 8);
Core.line(result, lines[BOTTOM].get(-5000), lines[BOTTOM].get(5000), new Scalar(255, 0, 0), 8);
Core.line(result, lines[LEFT].get(-5000), lines[LEFT].get(5000), new Scalar(0, 0, 255), 8);
Point p = Line.intersect(lines[TOP], lines[LEFT]);
System.out.println(p);
if (p != null) {
Core.circle(result, p, 30, new Scalar(0, 0, 255), 8);
}
double width = 1400;
double height = width / 2.15;
Point[] srcProjection = new Point[4], dstProjection = new Point[4];
srcProjection[0] = Line.intersect(lines[TOP], lines[LEFT]);
srcProjection[1] = Line.intersect(lines[TOP], lines[RIGHT]);
srcProjection[2] = Line.intersect(lines[BOTTOM], lines[LEFT]);
srcProjection[3] = Line.intersect(lines[BOTTOM], lines[RIGHT]);
dstProjection[0] = new Point(0, 0);
dstProjection[1] = new Point(width - 1, 0);
dstProjection[2] = new Point(0, height - 1);
dstProjection[3] = new Point(width - 1, height - 1);
Mat warp = Imgproc.getPerspectiveTransform(new MatOfPoint2f(srcProjection), new MatOfPoint2f(dstProjection));
Mat rotated = new Mat();
Size size = new Size(width, height);
Imgproc.warpPerspective(src, rotated, warp, size, Imgproc.INTER_LINEAR);
return rotated;
}
}
I am doing something wrong when i try get type of object.
I am trying to sum two vectors, it can be in cartesian coordinate (x,y) or polar coordinate (azimuth, length).
How i can check what type of object i have?
Here is my code:
import java.lang.Math;
/**
* Write a description of class Velocity here.
*
* #author (your name)
* #version (a version number or a date)
*/
public class Velocity
{
// instance variables - replace the example below with your own
private double x;
private double y;
private double azimuth;
private double length;
private double prod;
private PolarCoordinate pt;
private CartesianCoordinate ct;
private boolean compare;
private double s;
/**
* Constructor for objects of class Velocity
*/
public Velocity(CartesianCoordinate c)
{
x = c.getX();
y = c.getY();
}
public Velocity(Velocity z)
{
}
public Velocity(PolarCoordinate p)
{
ct = new CartesianCoordinate(x, y);
pt = new PolarCoordinate(azimuth, length);
azimuth = p.getAzimuth();
length = p.getLength();
}
private Velocity sumOfPolar(double s)
{
this.s = s;
return null;
}
private Velocity PolarCoordinate(double azimuth, double length)
{
return null;
}
private Velocity CartesianCoordinate(double x, double y)
{
return null;
}
private Velocity dotProduct(double prod)
{
return null;
}
//private boolean compare(java.lang.Object velocity,java.lang.Object ct)
//{
// if (velocity.getClass().equals(ct.getClass())) {
// return true;
// } else {
// return false;
// }
//}
/**
* This method performs a vector addition
* and returns a new object representing the
* sum of two vectors.
*
*/
public Velocity add(final Velocity velocity)
{
if(velocity.getClass().equals(ct.getClass()))
{
double sumX = x + velocity.x;
double sumY = y + velocity.y;
Velocity v = new Velocity(CartesianCoordinate(x,y));
v.x = sumX;
v.y = sumY;
System.out.println("BLYABLYA");
return v;
}
if(compare == false)
{
System.out.println("YEAAAAA");
Velocity v = new Velocity(PolarCoordinate(azimuth, length));
double xFirst = length * Math.cos(azimuth);
System.out.println("xFirst: " + xFirst);
double xSecond = velocity.length * Math.cos(velocity.azimuth);
System.out.println("xSecond: " + xSecond);
double yFirst = length * Math.sin(azimuth);
System.out.println("yFirst: " + yFirst);
double ySecond = velocity.length * Math.sin(velocity.azimuth);
System.out.println("ySecond: " + ySecond);
double sumX = xFirst + xSecond;
System.out.println("sumX: " + sumX);
double sumY = yFirst + ySecond;
System.out.println("sumY: " + sumY);
double sumXY = sumX + sumY;
System.out.println("sumXY: " + sumXY);
Velocity sum = new Velocity(sumOfPolar(sumXY));
sum.s = sumXY;
return sum;
}
return null;
}
/**
* This method performs a vector subtraction
* and returns a new object representing the
* sub of two vectors.
*
*/
public Velocity subtarct(final Velocity velocity)
{
double subX = x - velocity.x;
double subY = y - velocity.y;
Velocity v = new Velocity(CartesianCoordinate(x,y));
v.x = subX;
v.y = subY;
return v;
}
/**
* This method performs a vector dot product
* and returns a new object representing the
* product of two vectors.
*
*/
public Velocity dotProduct(final Velocity velocity)
{
double prodX = x * velocity.x;
double prodY = y * velocity.y;
double sumProdXY = prodX + prodY;
Velocity v = new Velocity(dotProduct(prod));
v.prod = sumProdXY;
return v;
}
/**
* This method performs a scaling on a vector.
*
*/
public Velocity scale(final double scaleFactor)
{
double prodX = x * scaleFactor;
double prodY = y * scaleFactor;
Velocity v = new Velocity(CartesianCoordinate(x, y));
v.x = prodX;
v.y = prodY;
return v;
}
public double getAzimuth()
{
PolarCoordinate p = new PolarCoordinate(azimuth,length);
return p.getAzimuth();
}
public double getLength()
{
PolarCoordinate p = new PolarCoordinate(azimuth,length);
return p.getLength();
}
}
You can use instanceof to determine the object type.
if (velocity instanceof PolarCoordinate) {
return true;
} else {
return false;
}
use instanceof.
String s = "TGTGGQCC";
System.out.println(s instanceof String); // true
I want to compute the moment of inertia of a (2D) concave polygon. I found this on the internet. But I'm not very sure how to interpret the formula...
Formula http://img101.imageshack.us/img101/8141/92175941c14cadeeb956d8f.gif
1) Is this formula correct?
2) If so, is my convertion to C++ correct?
float sum (0);
for (int i = 0; i < N; i++) // N = number of vertices
{
int j = (i + 1) % N;
sum += (p[j].y - p[i].y) * (p[j].x + p[i].x) * (pow(p[j].x, 2) + pow(p[i].x, 2)) - (p[j].x - p[i].x) * (p[j].y + p[i].y) * (pow(p[j].y, 2) + pow(p[i].y, 2));
}
float inertia = (1.f / 12.f * sum) * density;
Martijn
#include <math.h> //for abs
float dot (vec a, vec b) {
return (a.x*b.x + a.y*b.y);
}
float lengthcross (vec a, vec b) {
return (abs(a.x*b.y - a.y*b.x));
}
...
do stuff
...
float sum1=0;
float sum2=0;
for (int n=0;n<N;++n) { //equivalent of the Σ
sum1 += lengthcross(P[n+1],P[n])*
(dot(P[n+1],P[n+1]) + dot(P[n+1],P[n]) + dot(P[n],P[n]));
sum2 += lengthcross(P[n+1],P[n]);
}
return (m/6*sum1/sum2);
Edit: Lots of small math changes
I think you have more work to do that merely translating formulas into code. You need to understand exactly what this formula means.
When you have a 2D polygon, you have three moments of inertia you can calculate relative to a given coordinate system: moment about x, moment about y, and polar moment of inertia. There's a parallel axis theorem that allows you to translate from one coordinate system to another.
Do you know precisely which moment and coordinate system this formula applies to?
Here's some code that might help you, along with a JUnit test to prove that it works:
import java.awt.geom.Point2D;
/**
* PolygonInertiaCalculator
* User: Michael
* Date: Jul 25, 2010
* Time: 9:51:47 AM
*/
public class PolygonInertiaCalculator
{
private static final int MIN_POINTS = 2;
public static double dot(Point2D u, Point2D v)
{
return u.getX()*v.getX() + u.getY()*v.getY();
}
public static double cross(Point2D u, Point2D v)
{
return u.getX()*v.getY() - u.getY()*v.getX();
}
/**
* Calculate moment of inertia about x-axis
* #param poly of 2D points defining a closed polygon
* #return moment of inertia about x-axis
*/
public static double ix(Point2D [] poly)
{
double ix = 0.0;
if ((poly != null) && (poly.length > MIN_POINTS))
{
double sum = 0.0;
for (int n = 0; n < (poly.length-1); ++n)
{
double twiceArea = poly[n].getX()*poly[n+1].getY() - poly[n+1].getX()*poly[n].getY();
sum += (poly[n].getY()*poly[n].getY() + poly[n].getY()*poly[n+1].getY() + poly[n+1].getY()*poly[n+1].getY())*twiceArea;
}
ix = sum/12.0;
}
return ix;
}
/**
* Calculate moment of inertia about y-axis
* #param poly of 2D points defining a closed polygon
* #return moment of inertia about y-axis
* #link http://en.wikipedia.org/wiki/Second_moment_of_area
*/
public static double iy(Point2D [] poly)
{
double iy = 0.0;
if ((poly != null) && (poly.length > MIN_POINTS))
{
double sum = 0.0;
for (int n = 0; n < (poly.length-1); ++n)
{
double twiceArea = poly[n].getX()*poly[n+1].getY() - poly[n+1].getX()*poly[n].getY();
sum += (poly[n].getX()*poly[n].getX() + poly[n].getX()*poly[n+1].getX() + poly[n+1].getX()*poly[n+1].getX())*twiceArea;
}
iy = sum/12.0;
}
return iy;
}
/**
* Calculate polar moment of inertia xy
* #param poly of 2D points defining a closed polygon
* #return polar moment of inertia xy
* #link http://en.wikipedia.org/wiki/Second_moment_of_area
*/
public static double ixy(Point2D [] poly)
{
double ixy = 0.0;
if ((poly != null) && (poly.length > MIN_POINTS))
{
double sum = 0.0;
for (int n = 0; n < (poly.length-1); ++n)
{
double twiceArea = poly[n].getX()*poly[n+1].getY() - poly[n+1].getX()*poly[n].getY();
sum += (poly[n].getX()*poly[n+1].getY() + 2.0*poly[n].getX()*poly[n].getY() + 2.0*poly[n+1].getX()*poly[n+1].getY() + poly[n+1].getX()*poly[n].getY())*twiceArea;
}
ixy = sum/24.0;
}
return ixy;
}
/**
* Calculate the moment of inertia of a 2D concave polygon
* #param poly array of 2D points defining the perimeter of the polygon
* #return moment of inertia
* #link http://www.physicsforums.com/showthread.php?t=43071
* #link http://www.physicsforums.com/showthread.php?t=25293
* #link http://stackoverflow.com/questions/3329383/help-me-with-converting-latex-formula-to-code
*/
public static double inertia(Point2D[] poly)
{
double inertia = 0.0;
if ((poly != null) && (poly.length > MIN_POINTS))
{
double numer = 0.0;
double denom = 0.0;
double scale;
double mag;
for (int n = 0; n < (poly.length-1); ++n)
{
scale = dot(poly[n + 1], poly[n + 1]) + dot(poly[n + 1], poly[n]) + dot(poly[n], poly[n]);
mag = Math.sqrt(cross(poly[n], poly[n+1]));
numer += mag * scale;
denom += mag;
}
inertia = numer / denom / 6.0;
}
return inertia;
}
}
Here's the JUnit test to accompany it:
import org.junit.Test;
import java.awt.geom.Point2D;
import static org.junit.Assert.assertEquals;
/**
* PolygonInertiaCalculatorTest
* User: Michael
* Date: Jul 25, 2010
* Time: 10:16:04 AM
*/
public class PolygonInertiaCalculatorTest
{
#Test
public void testTriangle()
{
Point2D[] poly =
{
new Point2D.Double(0.0, 0.0),
new Point2D.Double(1.0, 0.0),
new Point2D.Double(0.0, 1.0)
};
// Moment of inertia about the y1 axis
// http://www.efunda.com/math/areas/triangle.cfm
double expected = 1.0/3.0;
double actual = PolygonInertiaCalculator.inertia(poly);
assertEquals(expected, actual, 1.0e-6);
}
#Test
public void testSquare()
{
Point2D[] poly =
{
new Point2D.Double(0.0, 0.0),
new Point2D.Double(1.0, 0.0),
new Point2D.Double(1.0, 1.0),
new Point2D.Double(0.0, 1.0)
};
// Polar moment of inertia about z axis
// http://www.efunda.com/math/areas/Rectangle.cfm
double expected = 2.0/3.0;
double actual = PolygonInertiaCalculator.inertia(poly);
assertEquals(expected, actual, 1.0e-6);
}
#Test
public void testRectangle()
{
// This gives the moment of inertia about the y axis for a coordinate system
// through the centroid of the rectangle
Point2D[] poly =
{
new Point2D.Double(0.0, 0.0),
new Point2D.Double(4.0, 0.0),
new Point2D.Double(4.0, 1.0),
new Point2D.Double(0.0, 1.0)
};
double expected = 5.0 + 2.0/3.0;
double actual = PolygonInertiaCalculator.inertia(poly);
assertEquals(expected, actual, 1.0e-6);
double ix = PolygonInertiaCalculator.ix(poly);
double iy = PolygonInertiaCalculator.iy(poly);
double ixy = PolygonInertiaCalculator.ixy(poly);
assertEquals(ix, (1.0 + 1.0/3.0), 1.0e-6);
assertEquals(iy, (21.0 + 1.0/3.0), 1.0e-6);
assertEquals(ixy, 4.0, 1.0e-6);
}
}
For reference, here's a mutable 2D org.gcs.kinetic.Vector implementation and a more versatile, immutable org.jscience.mathematics.vector implementation. This article on Calculating a 2D Vector’s Cross Product is helpful, too.
I did it with Tesselation. And take the MOI's all together.