I have a planar graph which I am creating myself. I want to find the faces of this graph but I can't find a working algorithm for doing so. What I've done so far is using an algorithm to find all the cycles in the graph but this gives me all possible cycles and I've tried but not found a way to only sort the faces out. One of my ideas was to use Path2Ds contains method to see if another shape was overlapping but since the faces share nodes, that doesn't work. The picture below demonstrates what I want and the code after shows my reproductionable example.
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class PolygonFinder {
// Graph modeled as list of edges
static int[][] graph
= {
{1, 2}, {1, 6}, {1, 5}, {2, 6},
{2, 3}, {3, 7}, {7, 4}, {3, 4},
{5, 4}, {6, 5}
};
static List<int[]> cycles = new ArrayList<>();
/**
* #param args
*/
public static void main(String[] args) {
for (int[] graph1 : graph) {
for (int j = 0; j < graph1.length; j++) {
findNewCycles(new int[]{graph1[j]});
}
}
cycles.stream().map(cy -> {
String s = "" + cy[0];
for (int i = 1; i < cy.length; i++) {
s += "," + cy[i];
}
return s;
}).forEachOrdered(s -> {
System.out.println(s);
});
}
static void findNewCycles(int[] path) {
int n = path[0];
int x;
int[] sub = new int[path.length + 1];
for (int[] graph1 : graph) {
for (int y = 0; y <= 1; y++) {
if (graph1[y] == n) {
x = graph1[(y + 1) % 2];
if (!visited(x, path)) // neighbor node not on path yet
{
sub[0] = x;
System.arraycopy(path, 0, sub, 1, path.length);
// explore extended path
findNewCycles(sub);
} else if ((path.length > 2) && (x == path[path.length - 1])) // cycle found
{
int[] p = normalize(path);
int[] inv = invert(p);
if (isNew(p) && isNew(inv)) {
cycles.add(p);
}
}
}
}
}
}
// check of both arrays have same lengths and contents
static Boolean equals(int[] a, int[] b) {
Boolean ret = (a[0] == b[0]) && (a.length == b.length);
for (int i = 1; ret && (i < a.length); i++) {
if (a[i] != b[i]) {
ret = false;
}
}
return ret;
}
// create a path array with reversed order
static int[] invert(int[] path) {
int[] p = new int[path.length];
for (int i = 0; i < path.length; i++) {
p[i] = path[path.length - 1 - i];
}
return normalize(p);
}
// rotate cycle path such that it begins with the smallest node
static int[] normalize(int[] path) {
int[] p = new int[path.length];
int x = smallest(path);
int n;
System.arraycopy(path, 0, p, 0, path.length);
while (p[0] != x) {
n = p[0];
System.arraycopy(p, 1, p, 0, p.length - 1);
p[p.length - 1] = n;
}
return p;
}
// compare path against known cycles
// return true, iff path is not a known cycle
static Boolean isNew(int[] path) {
Boolean ret = true;
for (int[] p : cycles) {
if (equals(p, path)) {
ret = false;
break;
}
}
return ret;
}
// return the int of the array which is the smallest
static int smallest(int[] path) {
int min = path[0];
for (int p : path) {
if (p < min) {
min = p;
}
}
return min;
}
// check if vertex n is contained in path
static Boolean visited(int n, int[] path) {
Boolean ret = false;
for (int p : path) {
if (p == n) {
ret = true;
break;
}
}
return ret;
}
}
The result after running the above code is:
1,6,2
1,5,6,2
1,5,4,7,3,2
1,6,5,4,7,3,2
1,5,4,3,2
1,6,5,4,3,2
1,5,4,7,3,2,6
1,5,4,3,2,6
1,5,6
2,3,7,4,5,6
2,3,4,5,6
3,4,7
One of my best attempts at solving this is with the following code. The coordinates comes from the picture at the top.
List<Polygon> polys = new LinkedList<>();
Polygon p1 = new Polygon();
p1.addPoint(new Point2D.Double(-4, 4));
p1.addPoint(new Point2D.Double(-1, 3));
p1.addPoint(new Point2D.Double(-1, 5));
Polygon p2 = new Polygon();
p2.addPoint(new Point2D.Double(-4, 4));
p2.addPoint(new Point2D.Double(0, -2));
p2.addPoint(new Point2D.Double(-1, 3));
p2.addPoint(new Point2D.Double(-1, 5));
Polygon p3 = new Polygon();
p3.addPoint(new Point2D.Double(-4, 4));
p3.addPoint(new Point2D.Double(0, -2));
p3.addPoint(new Point2D.Double(4, 1));
p3.addPoint(new Point2D.Double(2, 2));
p3.addPoint(new Point2D.Double(3, 4));
p3.addPoint(new Point2D.Double(-1, 5));
Polygon p4 = new Polygon();
p4.addPoint(new Point2D.Double(-4, 4));
p4.addPoint(new Point2D.Double(-1, 3));
p4.addPoint(new Point2D.Double(0, -2));
p4.addPoint(new Point2D.Double(4, 1));
p4.addPoint(new Point2D.Double(2, 2));
p4.addPoint(new Point2D.Double(3, 4));
p4.addPoint(new Point2D.Double(-1, 5));
Polygon p5 = new Polygon();
p5.addPoint(new Point2D.Double(-4, 4));
p5.addPoint(new Point2D.Double(0, -2));
p5.addPoint(new Point2D.Double(4, 1));
p5.addPoint(new Point2D.Double(3, 4));
p5.addPoint(new Point2D.Double(-1, 5));
Polygon p6 = new Polygon();
p6.addPoint(new Point2D.Double(-4, 4));
p6.addPoint(new Point2D.Double(-1, 3));
p6.addPoint(new Point2D.Double(0, -2));
p6.addPoint(new Point2D.Double(4, 1));
p6.addPoint(new Point2D.Double(3, 4));
p6.addPoint(new Point2D.Double(-1, 5));
Polygon p7 = new Polygon();
p7.addPoint(new Point2D.Double(-4, 4));
p7.addPoint(new Point2D.Double(0, -2));
p7.addPoint(new Point2D.Double(4, 1));
p7.addPoint(new Point2D.Double(2, 2));
p7.addPoint(new Point2D.Double(3, 4));
p7.addPoint(new Point2D.Double(-1, 5));
p7.addPoint(new Point2D.Double(-1, 3));
Polygon p8 = new Polygon();
p8.addPoint(new Point2D.Double(-4, 4));
p8.addPoint(new Point2D.Double(0, -2));
p8.addPoint(new Point2D.Double(4, 1));
p8.addPoint(new Point2D.Double(3, 4));
p8.addPoint(new Point2D.Double(-1, 5));
p8.addPoint(new Point2D.Double(-1, 3));
Polygon p9 = new Polygon();
p9.addPoint(new Point2D.Double(-4, 4));
p9.addPoint(new Point2D.Double(0, -2));
p9.addPoint(new Point2D.Double(-1, 3));
Polygon p10 = new Polygon();
p10.addPoint(new Point2D.Double(-1, 5));
p10.addPoint(new Point2D.Double(3, 4));
p10.addPoint(new Point2D.Double(2, 2));
p10.addPoint(new Point2D.Double(4, 1));
p10.addPoint(new Point2D.Double(0, -2));
p10.addPoint(new Point2D.Double(-1, 3));
Polygon p11 = new Polygon();
p11.addPoint(new Point2D.Double(-1, 5));
p11.addPoint(new Point2D.Double(3, 4));
p11.addPoint(new Point2D.Double(4, 1));
p11.addPoint(new Point2D.Double(0, -2));
p11.addPoint(new Point2D.Double(-1, 3));
Polygon p12 = new Polygon();
p12.addPoint(new Point2D.Double(3, 4));
p12.addPoint(new Point2D.Double(4, 1));
p12.addPoint(new Point2D.Double(2, 2));
polys.add(p1);
polys.add(p2);
polys.add(p3);
polys.add(p4);
polys.add(p5);
polys.add(p6);
polys.add(p7);
polys.add(p8);
polys.add(p9);
polys.add(p10);
polys.add(p11);
polys.add(p12);
Set<Integer> toRemove = new HashSet<>();
for (Polygon polyI : polys) {
for (Polygon polyJ : polys) {
if (polyI.equals(polyJ)) {
continue;
}
if (polyI.contains(polyJ)) {
toRemove.add(polys.indexOf(polyI));
}
}
}
List<Integer> list = new LinkedList<>(toRemove);
Collections.sort(list);
Collections.reverse(list);
list.forEach((t) -> {
polys.remove(t.intValue());
});
System.out.println("");
polys.forEach((t) -> {
System.out.println(t.getPoints());
});
Polygons methods used is listed here.
#Override
public boolean contains(Point2D point) {
return getPath().contains(point);
}
#Override
public boolean contains(IPolygon polygon) {
List<Point2D> p2Points = polygon.getPoints();
for (Point2D point : p2Points) {
if (getPath().contains(point)) {
if (!points.contains(point)) {
return true;
}
}
}
return false;
}
private Path2D getPath() {
Path2D path = new Path2D.Double();
path.moveTo(points.get(0).getX(), points.get(0).getY());
for (int i = 1; i < points.size(); i++) {
path.lineTo(points.get(i).getX(), points.get(i).getY());
}
path.closePath();
return path;
}
This code gives me the result below and the 2nd-4th is not wanted.
[Point2D.Double[-4.0, 4.0], Point2D.Double[-1.0, 3.0], Point2D.Double[-1.0, 5.0]]
[Point2D.Double[-4.0, 4.0], Point2D.Double[0.0, -2.0], Point2D.Double[-1.0, 3.0], Point2D.Double[-1.0, 5.0]]
[Point2D.Double[-4.0, 4.0], Point2D.Double[-1.0, 3.0], Point2D.Double[0.0, -2.0], Point2D.Double[4.0, 1.0], Point2D.Double[2.0, 2.0], Point2D.Double[3.0, 4.0], Point2D.Double[-1.0, 5.0]]
[Point2D.Double[-4.0, 4.0], Point2D.Double[0.0, -2.0], Point2D.Double[4.0, 1.0], Point2D.Double[2.0, 2.0], Point2D.Double[3.0, 4.0], Point2D.Double[-1.0, 5.0], Point2D.Double[-1.0, 3.0]]
[Point2D.Double[-4.0, 4.0], Point2D.Double[0.0, -2.0], Point2D.Double[-1.0, 3.0]]
[Point2D.Double[-1.0, 5.0], Point2D.Double[3.0, 4.0], Point2D.Double[2.0, 2.0], Point2D.Double[4.0, 1.0], Point2D.Double[0.0, -2.0], Point2D.Double[-1.0, 3.0]]
[Point2D.Double[3.0, 4.0], Point2D.Double[4.0, 1.0], Point2D.Double[2.0, 2.0]]
Here's an option for identifying the faces that's based on the idea of half-edges. At a high level, the approach looks like this:
Replace each edge linking two points u and v with the directed edges (u, v) and (v, u). These are called half-edges.
Chain the half-edges together so that a single chain of half edges perfectly traces out one of the faces of the plane graph.
Walk those chains to identify all of the faces of the plane graph.
Visually, that will look something like this. We'll begin with the graph looking like this:
and end with the graph looking like this:
Once we have that second graph, walking the colored chains will identify all the faces.
The question, then, is how exactly to determine how to chain the half-edges together. The basic idea is the following: we want to chain the edges together so that
all internal faces have the half-edges winding around counterclockwise (or anticlockwise, or widdershins, depending on which side of the pond you're from),
the external face has its half-edges winding around clockwise.
Provided we can come up with a convenient strategy that will chain things like this, we can easily glue the half-edges together to get our desired property. There are many ways to do this, but the one I'd like to focus on works by looking locally at each node.
Imagine you have some node X whose neighbors are A, B, C, and D, as shown below.
Here, I've marked half-edges leaving X in solid blue, and half-edges entering X in dotted orange.
Now, focus on the outgoing half-edge (X, A) in this diagram. When we've wired everything together, some other half-edge (_, X) needs to chain into (X, A). Which edge is it? From the picture, we can see it's the half-edge (B, X), forming the partial chain (B, X), (X, A).
Similarly, focus on the half-edge (X, B) in this diagram. As before, when we've wired all the half-edges into chains, we'd need some way of determining which half-edge (_, X) should come before it. And by inspection, we can see that it would be (C, X).
More generally, notice that
The half-edge before (X, A) is (B, X).
The half-edge before (X, B) is (C, X).
The half-edge before (X, C) is (D, X).
The half-edge before (X, D) is (A, X).
See the pattern? If we order the neighbors around this node counterclockwise (anticlockwise), then the half-edge that comes before an edge (X, Y) can be found as follows: assuming Z is the next neighbor counterclockwise around the node, then the half-edge that comes before (X, Y) is the half-edge (Z, X).
This gives us a very nice strategy for wiring the edges into chains while meeting our above requirements. Here's some pseudocode:
For each node v:
Get v's neighbors sorted anticlockwise as u_1, u_2, u_3, ..., u_n
For each half-edge (v, u_i):
Update half-edge (u_{i+1 mod n}, v) to chain to (v, u_i)
At this point, we've wired everything into chains, and we're done!
There are a few technical details here I've glossed over that would need to be resolved before you code this up. For example:
How do you sort the neighbors of a node v counterclockwise? That can be done by computing the angle each neighbor of v makes with v using Math.atan2(dy, dx) and sorting based on those values.
How do you keep track of what chains into what? If all you're doing is identifying the faces, you could make a Map<HalfEdge, HalfEdge> associating each half-edge with the next half-edge after it. If you're planning on keeping the chains around for the future, you might want to make each HalfEdge part of a linked list that has a reference to the next half-edge in the sequence.
How do you map from a pair of nodes to a half-edge running between them? This could be done with something like a Map from pairs of nodes to half-edges. You could also construct the half-edges and have each half-edge store a pointer to the half-edge running in the other direction.
Attribution: I first learned this algorithm from this related question on the Computer Science Stack Exchange that asks how to build a doubly-connected edge list (DECL) from a collection of line segments. My contributions are in simplifying the algorithm to only give back the chains needed to identify the faces and in adding some visuals to better motivate the concepts.
For each edge, take the co-ordinates within your embedding of the edge's vertices and use them to calculate the angle of the edge using trigonometry.
For example, the angle from (x1, y1) to (x2, y2) measured anti-clockwise from the positive x-axis is given by Math.atan2(y2-y1,x2-x1).
For each vertex, create a cyclic edge ordering by sorting the edges by their angle. This could be stored as an array or you could use a cyclic list data structure.
Pick an edge, follow it to an adjacent vertex and then follow the next adjacent clockwise edge and repeat following edges to the next vertex and then the next clockwise edge until you get back to the starting edge; then you have found a face of the graph.
Repeat step 3 picking an unvisited edge or a visited edge in the opposite direction to previous and follow it in that same clockwise direction to find the next face. Repeat this until all the edges have been visited twice (once in each direction) and then you have found all the faces.
In Java, that would be:
import java.awt.geom.Point2D;
import java.awt.Polygon;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.stream.Collectors;
import java.text.MessageFormat;
public class GraphFaces
{
static class Vertex
{
final int index;
final Point2D point;
final ArrayList<Edge> outboundEdges = new ArrayList<>();
public Vertex( final int index, final Point2D point )
{
this.index = index;
this.point = point;
}
public void addEdge( final Edge edge )
{
this.outboundEdges.add( edge );
}
public void sortEdges()
{
this.outboundEdges.sort((e1,e2)->Double.compare(e1.angle,e2.angle));
Edge prev = this.outboundEdges.get(this.outboundEdges.size() - 1);
for ( final Edge edge: this.outboundEdges )
{
edge.setNextEdge( prev );
prev = edge;
}
}
#Override
public String toString()
{
return Integer.toString(this.index);
// return MessageFormat.format("({0},{1})",this.point.getX(),this.point.getY());
}
}
static class Edge
{
final Vertex from;
final Vertex to;
final double angle;
boolean visited = false;
Edge next = null;
Edge reverse = null;
public Edge( final Vertex from, final Vertex to )
{
this.from = from;
this.to = to;
this.angle = Math.atan2(to.point.getY() - from.point.getY(), to.point.getX() - from.point.getX());
from.addEdge( this );
}
public Vertex getFrom()
{
return this.from;
}
public Vertex getTo()
{
return this.to;
}
public void setNextEdge( final Edge edge )
{
this.next = edge;
}
public void setReverseEdge( final Edge edge )
{
this.reverse = edge;
}
#Override
public String toString()
{
return MessageFormat.format("{0} -> {1}", this.from, this.to);
}
}
public static void main(final String[] args)
{
final Vertex[] vertices = {
new Vertex( 1, new Point2D.Double(-4,+4) ),
new Vertex( 2, new Point2D.Double(-1,+5) ),
new Vertex( 3, new Point2D.Double(+3,+4) ),
new Vertex( 4, new Point2D.Double(+4,+1) ),
new Vertex( 5, new Point2D.Double(+0,-2) ),
new Vertex( 6, new Point2D.Double(-1,+3) ),
new Vertex( 7, new Point2D.Double(+2,+2) )
};
final int[][] graph = {
{1, 2}, {1, 6}, {1, 5}, {2, 6}, {2, 3}, {3, 7}, {7, 4}, {3, 4}, {5, 4}, {6, 5}
};
final Edge[] edges = new Edge[2 * graph.length];
for ( int i = 0; i < graph.length; i++ )
{
final Vertex from = vertices[graph[i][0]-1];
final Vertex to = vertices[graph[i][1]-1];
edges[2*i] = new Edge( from, to );
edges[2*i+1] = new Edge( to, from );
edges[2*i].setReverseEdge(edges[2*i+1]);
edges[2*i+1].setReverseEdge(edges[2*i]);
}
for ( final Vertex vertex: vertices )
{
vertex.sortEdges();
}
final ArrayList<ArrayList<Edge>> faces = new ArrayList<>();
for ( final Edge edge: edges )
{
if ( edge.visited )
{
continue;
}
final ArrayList<Edge> face = new ArrayList<>();
faces.add( face );
Edge e = edge;
do
{
face.add(e);
e.visited = true;
e = e.reverse.next;
}
while (e != edge);
System.out.println( face.stream().map(Edge::getFrom).collect(Collectors.toList()) );
}
}
}
Which outputs:
[1, 2, 3, 4, 5]
[2, 1, 6]
[6, 1, 5]
[2, 6, 5, 4, 7, 3]
[3, 7, 4]
Note: this includes the exterior face of the graph.
Alternatively, if you want to: test your graph for planarity; generate all possible embeddings of a (biconnected) graph; and generate a cyclic edge ordering for one (or more) of those embeddings then you can use the PhD thesis Planarity Testing by Path Addition, which includes complete Java source code in the appendices.
In a planar embedding consisting only of straight lines, the edges of a face meeting in a vertex must be adjacent among all edges of that node.
Therefore, if we are given such an embedding, and sort the edges of each vertex according to their direction, we can easily walk the perimeter of a face by leaving each vertex on the edge immediately to the right of the edge we arrived through.
As a data structure, I'd probably choose something like this:
class Vertex {
Edge edges;
}
class Edge {
Vertex source;
Vertex target;
Edge reverse; // the same edge, seen from the other end
Edge next; // forms a circular linked list, sorted in order of direction
}
Then we can iterate the perimeter of a face like this:
Edge startingEdge = ...;
Edge currentEdge = startingEdge;
do {
currentEdge = currentEdge.reverse.next;
} while (currentEdge != startingEdge);
To sort edges by direction, we can use the fact that a x b is negative if a is to the left of b (as seen from the origin of the coordinate system).
boolean left(Point2D.Double a, Point2D.Double b) {
return a.x * b.y - a.y * b.x < 0;
}
We can the use a simple insertion sort to sort edges by direction (which will be fast enough since planar graphs have a bounded average node degree, so the edge lists will be short).
Related
I made a 3D-renderer that parses .obj files (ASCII) and projects them on to a 2d plane.
At first glance the projection model seems to be fine except one thing.
I noticed that the projection model looks a bit odd:
[1]: https://i.stack.imgur.com/iaLOu.png
All polygons are being drawn including the ones in the back of the model, which I
should definitely not be able to see.
I made a quick recherche in Wikipedia to see what this is about and I think I found something called "Sichtbarkeitsproblem" (Hidden-surface determination).
(DE): https://de.wikipedia.org/wiki/Sichtbarkeitsproblem
(EN):
https://en.wikipedia.org/wiki/Hidden-surface_determination
The article mentions that this is a common thing in computer graphics and that there are many different ways to perform a "Verdeckungsberechnung" (cover up calculation).
It mentions things like using a z-Buffer and Raytracing.
Now I don't really know a lot about Raytracing but It seems to be quite applicable as I later want to add a light source.
I am not sure how Raytracing works but If I just send out rays in an angle that matches the slope from the camera to every pixel on screen and check which polygon hits it first I would only end up having some polygons completely missing only due to one vertex being potentially covered.
How do other Raytracers work? Do they remove the entire polygon when not getting a hit? Remove only one or more vertecies? (which I belief would cause massive distortion in shape) or do they just render all the Polygons and arrange them in a way that they are sorted by the minimum distance to the camera? (I guess this would made it very bad at performance)
Please help me implement this into my code or give me a hint, it would mean a lot to me.
My code is as followed, and the link for the projection model (see Image no. 1) I put here:
https://drive.google.com/file/d/10dpjcL2d2QB15qqTSu5p6kQ534hNOzCz/view?usp=sharing
(Note that the 3d-model and code must be in same folder in order to work)
// 12.11.2022
// Siehe Rotation Matrix in Wikipedia
// View Space: The world space vertex positions relative to the view of the camera
/* Die Verdeckungsberechnung ist zum korrekten Rendern einer 3D-Szene notwendig, weil Oberflächen,
die für den Betrachter nicht sichtbar sind, auch nicht dargestellt werden sollten
*/
// -> https://de.wikipedia.org/wiki/Sichtbarkeitsproblem
// TODO: Raytracing/Verdeckungsberechnung
// TODO: Texture Mapping
import java.util.Arrays;
import java.awt.Robot;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
byte b[];
int amount = 0;
String lines[];
PVector[][] vertices;
int[] faces;
float a = 0;
PVector cam, cam_angle, cam_move, cam_speed;
float angle = 0.0;
void setup() {
size(800,600);
frameRate(60);
noCursor();
cam = new PVector(0, 100, -500);
cam_angle = new PVector(0, 0, 0);
cam_move = new PVector(0, 0, 0);
cam_speed = new PVector(50, 50, 50);
lines = loadStrings("UM2_SkullPile28mm.obj");
println("File loaded. Now scanning contents...");
println();
Pattern numbers = Pattern.compile("(-?\\d+)");
ArrayList<PVector> vertices_ = new ArrayList<PVector>();
ArrayList<ArrayList> faces_ = new ArrayList<ArrayList>();
int parsed_lines = 0;
for(String i:lines) {
switch(i.charAt(0)) {
// Find faces
case 'f':
ArrayList<Integer> values = new ArrayList<Integer>();
for(Matcher m = numbers.matcher(i); m.find(); values.add(Integer.parseInt(m.group())));
faces_.add(values);
break;
// Find Vectors
case 'v':
String s[] = i.trim().split("\\s+");
vertices_.add(new PVector(Float.parseFloat(s[1])*20, Float.parseFloat(s[2])*20, Float.parseFloat(s[3])*20));
break;
};
if(++parsed_lines % (lines.length/6) == 0 || parsed_lines == lines.length) println((int)(map(parsed_lines, 0, lines.length, 0, 100)), "%");
}
println();
println("Done. Found", vertices_.size(), "Vertices and", faces_.size(), "faces");
int i=0;
vertices = new PVector[faces_.size()][];
for(ArrayList<Integer> f_:faces_) {
vertices[i] = new PVector[f_.size()];
int j = 0;
for(int f: f_) {
PVector v = vertices_.get(f-1);
vertices[i][j] = Rotate3d_x(v, -90);
j++;
}
i++;
}
}
PVector Rotate2d(PVector p, float a) {
// a = angle
float[][] m2 = {
{cos(a), -sin(a)},
{sin(a), cos(a)}
};
float[][] rotated = matmul(m2, new float[][] {
{ p.x },
{ p.y }
});
return new PVector(rotated[0][0], rotated[1][0]);
}
PVector Rotate3d(PVector p, float[][] m2) {
float[][] rotated = matmul(m2, new float[][] {
{ p.x },
{ p.y },
{ p.z }
});
return new PVector(rotated[0][0], rotated[1][0], rotated[2][0]);
}
PVector Rotate3d_x(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{1, 0, 0},
{0, cos(a), -sin(a)},
{0, sin(a), cos(a)}
});
};
PVector Rotate3d_y(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{cos(a), 0, sin(a)},
{0, 1, 0},
{-sin(a), 0, cos(a)}
});
}
PVector Rotate3d_z(PVector p, float a) {
return Rotate3d(p,
new float[][] {
{cos(a), -sin(a), 0},
{sin(a), cos(a), 0},
{0, 0, 1}
});
}
PVector Rotate3d(PVector p, PVector a) {
return Rotate3d_z( Rotate3d_y(Rotate3d_x(p, a.x), a.y), a.z );
}
// Matrixmultiplikation
float[][] matmul(float[][] m1, float[][] m2) {
int cols_m1 = m1.length,
rows_m1 = m1[0].length;
int cols_m2 = m2.length,
rows_m2 = m2[0].length;
try {
if (rows_m1 != cols_m2) throw new Exception("Rows of m1 must match Columns of m2!");
}
catch(Exception e) {
println(e);
}
float[][] res = new float[cols_m2][rows_m2];
for (int c=0; c < cols_m1; c++) {
for (int r2=0; r2 < rows_m2; r2++) {
float sum = 0;
float[] buf = new float[rows_m1];
// Multiply rows of m1 with columns of m2 and store in buf
for (int r=0; r < rows_m1; r++) {
buf[r] = m1[c][r]* m2[r][r2];
}
// Add up all entries into sum
for (float entry : buf) {
sum += entry;
}
res[c][r2] = sum;
}
}
return res;
}
PVector applyPerspective(PVector p) {
PVector d = applyViewTransform(p);
return applyPerspectiveTransform(d);
}
PVector applyViewTransform(PVector p) {
// c = camera position
// co = camera orientation / camera rotation
PVector c = cam;
PVector co = cam_angle;
// dx, dy, dz https://en.wikipedia.org/wiki/3D_projection : Mathematical Formula
float[][] dxyz = matmul(
matmul(new float[][]{
{1, 0, 0},
{0, cos(co.x), sin(co.x)},
{0, -sin(co.x), cos(co.x)}
}, new float[][]{
{cos(co.y), 0, -sin(co.y)},
{0, 1, 0},
{sin(co.y), 0, cos(co.y)}
}),
matmul(new float[][]{
{cos(co.z), sin(co.z), 0},
{-sin(co.z), cos(co.z), 0},
{0, 0, 1}
}, new float[][]{
{p.x - c.x},
{p.y - c.y},
{p.z - c.z},
}));
PVector d = new PVector(dxyz[0][0], dxyz[1][0], dxyz[2][0]);
return d;
}
PVector applyPerspectiveTransform(PVector d) {
// e = displays surface pos relative to camera pinhole c
PVector e = new PVector(0, 0, 300);
return new PVector((e.z / d.z) * d.x + e.x, (e.z / d.z) * d.y + e.y);
}
void draw() {
background(255);
translate(width/2, height/2);
scale(1,-1);
noStroke();
fill(0, 100, 0, 50);
PVector[][] points_view = new PVector[vertices.length][];
for(int i=0; i < vertices.length; i++) {
points_view[i] = new PVector[vertices[i].length];
for(int j=0; j < vertices[i].length; j++)
points_view[i][j] = applyViewTransform(Rotate3d_y(vertices[i][j], angle));
}
// The following snippet I got from: https://stackoverflow.com/questions/74443149/3d-projection-axis-inversion-problem-java-processing?noredirect=1#comment131433616_74443149
float nearPlane = 1.0;
for (int c = 0; c < points_view.length; c++) {
beginShape();
for (int r = 0; r < points_view[c].length-1; r++) {
// Alle Punkte verbinden
//if (i == a) continue;
PVector p0 = points_view[c][r];
PVector p1 = points_view[c][r+1];
if(p0.z < nearPlane && p1.z < nearPlane){ continue; };
if(p0.z >= nearPlane && p1.z < nearPlane)
p1 = PVector.lerp(p0, p1, (p0.z - nearPlane) / (p0.z - p1.z));
if(p0.z < nearPlane && p1.z >= nearPlane)
p0 = PVector.lerp(p1, p0, (p1.z - nearPlane) / (p1.z - p0.z));
// project
p0 = applyPerspectiveTransform(p0);
p1 = applyPerspectiveTransform(p1);
vertex(p0.x, p0.y);
vertex(p1.x, p1.y);
}
endShape();
}
}
Ray tracing doesn't determine whether or not a polygon is visible. It determines what point (if any) on what polygon is visible in a given direction.
As a simplification: rasterisation works by taking a set of geometry and for each one determining what pixels it affects. Ray tracing works by taking a set of pixels and, for each one determining what geometry is visible along that direction.
With rasterisation, there are many ways of making sure that polygons don't draw in the wrong order. One approach is to sort them by distance to the camera, but that doesn't work with polygons that overlap. The usual approach is to use a z-buffer: when a polygon is rasterised, calculate the distance to the camera in each pixel, and only update the buffer if the new value is nearer to the camera than the old value.
With ray tracing, each ray returns the nearest hit location along a direction, along with what it hit. Since each pixel will only be visited once, you don't need to worry about triangles drawing on top of each other.
If you just want to project a piece of 3D geometry onto a plane, rasterisation will likely be much, much faster. At a very high level, do this:
create an RGBA buffer of size X*Y
create a z buffer of size X*Y and fill it with 'inf'
for each triangle:
project the triangle onto the projection plane
for each pixel the triangle might affect:
calculate distance from camera to the corresponding position on the triangle
if the distance is lower than the current value in the z buffer:
replace the value in the RGBA and z buffers with the new values
I have a simple 2D polygon with 4 points.
int[] x = {38, 100, 80, 18};
int[] y = {50, 50, 100, 100};
Polygon poly = new Polygon(x, y, 4);
The above polygon is just an example. The polygon above could really be anything as long as the polygon is always convex, always has 4 points, and is a parallelogram. I need to split it into any number of even parts, all proportional to the bigger polygon, as long as the number is a square number. Is there any simple way I can do this? I am using Graphics on a Jframe if that's important at all.
The code below works for any convex 4-sided polygon. When the initial polygon is a parallelogram, the resultant sub-polygons are by nature all parallelograms too, all with the same size, i.e. they are even-sized.
Since the desired number of parts must be a square number, it means we can simply split the 4-sided polygon horizontally and vertically into partsPerSide = sqrt(parts).
When we split a 4-sided polygon into multiple parts, we may end up with coordinates that are not exact integers. We can simply round the value to an integer, but then the pieces wouldn't be exactly even in size. Whether that is acceptable is a matter of choice. Visually, the rounding can be noticed, since the lines won't be 100% straight.
In the code below, we assume that rounding is not acceptable, i.e. we want exact even sizes. If rounding is ok, simply comment out the if (rounded != delta) throw new ArithmeticException() code at the end, then call splitFourSided() with the desired number of partsPerSide.
Enough talk, here is the code:
private static Polygon[][] splitFourSided(Polygon poly, int partsPerSide) {
if (poly.npoints != 4)
throw new IllegalArgumentException("Polygon must be 4-sided");
if (partsPerSide <= 0)
throw new IllegalArgumentException("There must be a positive number of parts per side");
int[][] x = splitFourSided(poly.xpoints, partsPerSide);
int[][] y = splitFourSided(poly.ypoints, partsPerSide);
Polygon[][] pieces = new Polygon[partsPerSide][partsPerSide];
for (int row = 0; row < partsPerSide; row++) {
for (int col = 0; col < partsPerSide; col++) {
pieces[row][col] = new Polygon(
new int[] { x[row][col], x[row][col+1], x[row+1][col+1], x[row+1][col] },
new int[] { y[row][col], y[row][col+1], y[row+1][col+1], y[row+1][col] },
4);
}
}
return pieces;
}
private static int[][] splitFourSided(int[] xy, int parts) {
// To visualize, assume values are [topLeft, topRight, bottomRight, bottomLeft].
// The 'xy' array is either the x-coordinates or the y-coordinates.
// First we split left and right sides, e.g. for 3 parts:
// From: ┌ To: ┐
// ├ ┤
// ├ ┤
// └ ┘
// Then we split between those:
// ┌─┬─┬─┐
// ├─┼─┼─┤
// ├─┼─┼─┤
// └─┴─┴─┘
int[] from = splitRange(xy[0], xy[3], parts);
int[] to = splitRange(xy[1], xy[2], parts);
int[][] grid = new int[parts + 1][];
for (int i = 0; i <= parts; i++)
grid[i] = splitRange(from[i], to[i], parts);
return grid;
}
private static int[] splitRange(int from, int to, int parts) {
int[] prorated = new int[parts + 1];
for (int i = 0; i <= parts; i++)
prorated[i] = prorate(from, to, i, parts);
return prorated;
}
private static int prorate(int from, int to, int index, int parts) {
if (index == 0)
return from;
if (index == parts)
return to;
double delta = (to - (double) from) * index / parts;
int rounded = (int) Math.round(delta);
if (rounded != delta)
throw new ArithmeticException("Cannot prorate to integer value");
return from + rounded;
}
Test
int[] x = {38, 100, 80, 18};
int[] y = {50, 50, 100, 100};
Polygon poly = new Polygon(x, y, 4);
splitAndDrawFourSided(g, poly, 2);
private static void splitAndDrawFourSided(Graphics g, Polygon poly, int partsPerSide) {
Polygon[][] pieces = splitFourSided(poly, partsPerSide);
for (int row = 0; row < partsPerSide; row++)
for (int col = 0; col < partsPerSide; col++)
g.drawPolygon(pieces[row][col]);
Graphics gMain = g.create();
try {
gMain.setColor(Color.RED);
gMain.drawPolygon(poly);
} finally {
gMain.dispose();
}
}
Result
To search for a valid number of parts, we can add a search loop, and change the coordinates so they are only divisible by 7.
int[] x = {37, 100, 79, 16};
int[] y = {50, 50, 99, 99};
Polygon poly = new Polygon(x, y, 4);
for (int partsPerSide : new int[] { 2, 3, 5, 7, 11, 13, 17, 19 }) {
try {
splitAndDrawFourSided(g, poly, partsPerSide);
break; // stop when successful
} catch (#SuppressWarnings("unused") ArithmeticException ignored) {
continue; // try next number of parts
}
}
Result
If we remove the rounding check, that code will of course always just split by 2 parts per side, i.e. into 4 parts. This shows the effect of rounding, e.g. in this case the center row coordinates ended up a bit to the right, causing the black and red lines to not match up. Even without the red line depicting the input parallelogram, the rounding can be noticed. Anti-aliasing helps, but it can still be noticed that the vertical lines aren't 100% straight.
I have a HashSet filled with custom rectangle object("name",center_X,center_Y, width, height). i wrote a method to find if two rectangles are connected(touch/intersect). But i want to get all the rectangles that are a group. For example: if rectA is connected with rectB, RectC is connected with rectB but not rectA directly they all are connected because rectB is common.
I can find the shapes that have direct connection. But I want to get the group where shapes with secondary connection is also included. I'd assume recursion is useful in this case but can't solve it yet. Any solution/suggestion?
public static void canItbeGroup(HashSet<RECTANGLE> ipRectangles)
{
Deque<RECTANGLE> ipDeque = new ArrayDeque<>(ipRectangles);
for (RECTANGLE currRectangle : ipDeque)
{
Set<String> tempGrpMbrShapeID = new HashSet<>();
RECTANGLE tempRect = ipDeque.pop();
for (RECTANGLE r : ipDeque)
{
if (tempRect.areShapesFriend(r))
{
tempGrpMbrShapeID.add(r.shapeID);
tempGrpMbrShapeID.add(tempRect.shapeID);
}
}
if (tempGrpMbrShapeID.size() > 1)
{
System.out.println(tempGrpMbrShapeID);
}
}
}
public static void main(String[] args)
{
HashSet<RECTANGLE> rectHS = new HashSet<>();
RECTANGLE aRect = new RECTANGLE("a", 3, 2, 2, 2);
RECTANGLE aInnerRect = new RECTANGLE("aIn", 3, 2, 1, 1);
RECTANGLE bRect = new RECTANGLE("b", 5, 3, 2, 2);
RECTANGLE cRect = new RECTANGLE("c", 7, 3, 2, 2);
RECTANGLE dRect = new RECTANGLE("d", 4, 5, 4, 2);
RECTANGLE eRect = new RECTANGLE("e", 11, 3, 2, 2);
RECTANGLE fRect = new RECTANGLE("f", 11, 6, 2, 2);
RECTANGLE gRect = new RECTANGLE("g", 13, 3, 2, 2);
RECTANGLE hRect = new RECTANGLE("h", 4, 8, 2, 2);
RECTANGLE iRect = new RECTANGLE("i", 14, 1, 2, 2);
RECTANGLE jRect = new RECTANGLE("j", 16, 7, 2, 2);
RECTANGLE kRect = new RECTANGLE("k", 15, 6, 2, 2);
RECTANGLE lRect = new RECTANGLE("l", 8, 8, 2, 2);
RECTANGLE mRect = new RECTANGLE("m", 5, 10, 2, 2);
rectHS.add(aRect);
rectHS.add(bRect);
rectHS.add(cRect);
rectHS.add(eRect);
rectHS.add(dRect);
rectHS.add(fRect);
rectHS.add(gRect);
rectHS.add(aInnerRect);
rectHS.add(hRect);
rectHS.add(iRect);
rectHS.add(jRect);
rectHS.add(kRect);
rectHS.add(lRect);
rectDQ.add(aRect);
rectDQ.add(bRect);
rectDQ.add(cRect);
rectDQ.add(dRect);
rectDQ.add(eRect);
canItbeGroup(rectHS);
}
output i get:
[a, b, aIn],[b, c, d],[g, i],[e, g],[j, k],[c, d]
I'll need the group as
[a,b,aIn,c,d], [e,g,i], [j,k]
I won't solve the problem for you, but I will give you a general approach.
Generate an initial Collection<Set<String>>. This is effectively what you've already done, but you'll need to store them so that you can merge the sets, rather than printing the group as soon as your find them
Iterate over all groups (i), and compare each group to every other group (j)
If the i contains any item of j then merge the sets (put all items of j into i, and remove all items from j)
Go back to 2 and repeat until you perform 1 full iteration where no merges occur.
Iterate again and remove any empty sets.
This question already has answers here:
What causes a java.lang.ArrayIndexOutOfBoundsException and how do I prevent it?
(26 answers)
Closed 5 years ago.
Here is the code:
// Java program for implementation of Ford Fulkerson algorithm
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.LinkedList;
class MaxFlow
{
static final int V = 8; //Number of vertices in graph
/* Returns true if there is a path from source 's' to sink
't' in residual graph. Also fills parent[] to store the
path */
boolean bfs(int rGraph[][], int s, int t, int parent[])
{
// Create a visited array and mark all vertices as not
// visited
boolean visited[] = new boolean[V];
for(int i=0; i<V; ++i)
visited[i]=false;
// Create a queue, enqueue source vertex and mark
// source vertex as visited
LinkedList<Integer> queue = new LinkedList<Integer>();
queue.add(s);
visited[s] = true;
parent[s]=-1;
// Standard BFS Loop
while (queue.size()!=0)
{
int u = queue.poll();
for (int v=0; v<V; v++)
{
if (visited[v]==false && rGraph[u][v] > 0)
{
queue.add(v);
parent[v] = u;
visited[v] = true;
}
}
}
// If we reached sink in BFS starting from source, then
// return true, else false
return (visited[t] == true);
}
// Returns tne maximum flow from s to t in the given graph
int fordFulkerson(int graph[][], int s, int t)
{
int u, v;
// Create a residual graph and fill the residual graph
// with given capacities in the original graph as
// residual capacities in residual graph
// Residual graph where rGraph[i][j] indicates
// residual capacity of edge from i to j (if there
// is an edge. If rGraph[i][j] is 0, then there is
// not)
int rGraph[][] = new int[V][V];
for (u = 0; u < V; u++)
for (v = 0; v < V; v++)
rGraph[u][v] = graph[u][v];
// This array is filled by BFS and to store path
int parent[] = new int[V];
int max_flow = 0; // There is no flow initially
// Augment the flow while tere is path from source
// to sink
while (bfs(rGraph, s, t, parent))
{
// Find minimum residual capacity of the edhes
// along the path filled by BFS. Or we can say
// find the maximum flow through the path found.
int path_flow = Integer.MAX_VALUE;
for (v=t; v!=s; v=parent[v])
{
u = parent[v];
path_flow = Math.min(path_flow, rGraph[u][v]);
}
// update residual capacities of the edges and
// reverse edges along the path
for (v=t; v != s; v=parent[v])
{
u = parent[v];
rGraph[u][v] -= path_flow;
rGraph[v][u] += path_flow;
}
// Add path flow to overall flow
max_flow += path_flow;
}
// Return the overall flow
return max_flow;
}
// Driver program to test above functions
public static void main (String[] args) throws java.lang.Exception
{
int graph[][] =new int[][] { {0, 14, 0, 10, 0, 18, 0, 0},
{0, 0, 18, 0, 14, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 10},
{0, 0, 10, 0, 8, 0, 0, 0},
{0, 0, 14, 0, 8, 0, 20},
{0, 0, 0, 6, 0, 0, 16},
{0, 0, 16, 0, 0, 0, 0, 6},
{0,0,0,0,0,0,0,0} };
MaxFlow m = new MaxFlow();
System.out.println("The maximum possible flow is " +
m.fordFulkerson(graph, 0, 7));
}
}
I'd like to modify it to output the path and the weight of each edge but I'm not sure how. I would like to know the path taken so i can see whats going on graphically
edit: The error as someone has pointed out was that I was missing two elements when i created my matrix. Still unsure how to output the path used.
ArrayIndexOutOfBoundsException is thrown when an array is accessed at an invalid index.
for (u = 0; u < V; u++)
for (v = 0; v < V; v++)
rGraph[u][v] = graph[u][v];
tries to access 8 indexes in 8 one-dimensional arrays in graph and rgraph. But in line# 113, {0, 0, 14, 0, 8, 0, 20}, has 7 elements, which is the 6th one-dimensional array in graph. So accessing graph[5][7] is causing the out of bound error.
I am doing a Java project and have constructed a Polygon like this:
DPolygons[NumberOf3DPolygons] = new DPolygon(new double[]{0, 2, 2, 0}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3}, Color.red);
whereas the class DPolygon has the following constructor:
public DPolygon(double[] x, double[] y, double[] z, Color c)
{
Screen.NumberOf3DPolygons++;
this.x = x;
this.y = y;
this.z = z;
this.c = c;
createPolygon();
}
What I want to do is to calculate the sum of its z coordinates (which in this case will be = 6)
Here is what I had in mind:
sum = DPolygons[NumberOf3DPolygons].z[0]+DPolygons[NumberOf3DPolygons].z[1]+
DPolygons[NumberOf3DPolygons].z[2]+DPolygons[NumberOf3DPolygons].z[3];
But it gives a NullPointerException because it doesn't recognize DPolygons[NumberOf3DPolygons].z[0] as the first z value of the polygon and so on.
Can someone give me a clue what would be the right syntax for accessing each of these z elements? (Or how can I otherwise get that sum?)
What I want to do is to calculate the sum of its z coordinates (which in this case will be = 6)
If I were you, I would create a method for the sum of z (or even the sum for x and y):
class DPolygon
{
private double[] z;
//Your other attributes here..
//Your constructors here..
public double getSumOfZ(){
double sum = 0.0;
if(z != null && z.length > 0)
for(int i=0; i<z.length; i++)
sum += z[i];
return sum;
}
}
An example, if you have an array of 3D Polygons:
//Outside the class (for e.g. from the main method):
DPolygon poly1 = new DPolygon(new double[]{0, 2, 2, 0}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3}, Color.red)};
DPolygon poly2 = new DPolygon(new double[]{0, 2, 2, 0}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3}, Color.red)};
DPolygon poly3 = new DPolygon(new double[]{0, 2, 2, 0}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3}, Color.red)};
DPolygons[NumberOf3DPolygons] polygons3D = {poly1, poly2, poly3};
To access the sum of z from a particular 3D polygon:
polygons3D[0].getSumOfZ(); //Get sum of z from first polygon
But it gives a NullPointerException because it doesn't recognize DPolygons[NumberOf3DPolygons].z[0] as the first z value of the polygon and so on.
There are 2 possibilities for the NullPointerException to occur in your case:
You didn't initialize the array of z in DPolygon class.
The DPolygon element you are using from the array is null.
Ensure you initialize the z array from DPolygon class:
class DPolygon
{
//Your other attributes here..
private double[] x,y,z;
public DPolygon(){
initCoordinates(); //Init all arrays in the constructor
}
//Initialize all x,y,z arrays.
private void initCoordinates(){
x = new double[]{0.0, 0.0, 0.0, 0.0};
y = new double[]{0.0, 0.0, 0.0, 0.0};
z = new double[]{0.0, 0.0, 0.0, 0.0};
}
}
for(int i=0; i<DPolygons.length; i++){
//If you have some condition, you can put over here.
if(condition) {
sum = DPolygons[i].z + sum;
}
}
Is the global variable z declare public?
public double z;
However, I would recomend creating a public method within Dpolygon class to retrieve global z value and using that getter instead of calling the property directly.
Inside the class:
public Double [ ] getZ (){return new Double (z[1],z [2],z [3]);}
Design the class as such...
public class DPolygon
{
private double[] x, y, z;
private Color color;
public DPolygon(double[] x, double[] y, double[] z, Color color)
{
// Number of polygons best found in containing class.
this.x = x;
this.y = y;
this.z = z;
this.color = Color;
}
public double[] getZ()
{
return z;
}
//...Other getters and setters.
}
Use an ArrayList with nested foreach loops to get all z values of all polygons...
ArrayList<DPolygon> dPolygons = new ArrayList<DPolygon>();
dPolygons.add(new DPolygon(new double[]{0, 2, 2, 3}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3},Color.Red));
double sum=0;
for(DPolygon polygon : dPolygons)
{
for (double zValue : polygon.getZ())
{
sum += zValue;
}
}
For a particular polygon...
double sum2 = 0;
//Change index number to whatever specific polygon you want to sum.
int specificPolygon=0;
// Then to sum.
for(double zValue : dPolygons.get(specificPolygon).getZ())
{
sum2 += zValue;
}
But if you're married to an array...
DPolygons[] dPolygons = new DPolygons[numberOfPolygons];
dPolygons[0] = new DPolygon(new double[]{0, 2, 2, 3}, new double[]{0, 0, 0, 0}, new double[]{0, 0, 3, 3},Color.Red)
//...Adding other polygons
// For single polygon
double sum3 = 0;
// As before, set specificPolygon equal to the index of the polygon you want.
int specificPolygon = 0;
for(double zValue : dPolygons[specificPolygon].getZ())
{
sum3 += zValue;
}
The problem with the last approach is that you need to know the amount of polygons on the screen when the array is initialized. You can't dynamically add more at runtime.
There are two more neat concepts to introduce on the topic: Collections and, new in Java 1.8, Reduction of Streams.
Using a List will do away with NullPointerExceptions caused by trying to manage dynamic arrays using global variables.
In your case, incrementing Screen.NumberOf3DPolygons in the constructor of DPolygon is not the right place for it. What happens if you construct another DPolygon and not add it to that array? You would want to make that variable consistent with how many elements are added to the list, using an add method in a object that manages the array. Luckily this is already done for us in the Java Collections classes.
Here's a working reproduction of the relevant code demonstrating using Collections, and another goodie: a one-liner to calculate the sum of the z coordinates.
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main1
{
public static class Screen
{
List<DPolygon> polygons = new ArrayList<>();
}
public static class DPolygon
{
private double[] x, y, z;
private Color c;
public DPolygon( double[] x, double[] y, double[] z, Color c )
{
this.x = x;
this.y = y;
this.z = z;
this.c = c;
}
public double sumZ()
{
return Arrays.stream( z ).sum();
}
}
public static void main( String[] args )
{
Screen screen = new Screen();
screen.polygons.add(
new DPolygon(
new double[] { 0, 2, 2, 0 },
new double[] { 0, 0, 0, 0 },
new double[] { 0, 0, 3, 3 },
Color.red ) );
System.out.println( screen.polygons.get( 0 ).sumZ() );
}
}
It is still possible that you get an IndexOutOfBoundsException when you try to use at least two of x, y and z for a particular index: we're not sure every point has 3 coordinates, since some array may be smaller than another. To make sure all points have 3 coordinates, you'd group them like this:
screen.polygons.add(
new Polygon(
Color.RED,
new double[] { 0, 0, 0 },
new double[] { 2, 0, 0 },
new double[] { 2, 0, 3 },
new double[] { 0, 0, 3 } ) );
using this DPolygon class:
public static class DPolygon
{
List<double[]> points = new ArrayList<>();
Color color;
public DPolygon( Color c, double[]... coords )
{
this.points = Arrays.asList( coords );
this.color = c;
}
public double sumZ()
{
return points.stream().mapToDouble( ( p ) -> p[2] ).sum();
}
}
You could go even further and abstract the point to a
public static class Point3D
{
double x, y, z;
public Point3D( double x, double y, double z )
{
this.x = x;
this.y = y;
this.z = z;
}
}
which is easily supported by changing double[] to Point3D, and new double[]{ ... } to new Point3D( ... ), and ( p ) -> p[2] to ( p ) -> p.z.
One more thing: the Java Code Conventions recommend that identifiers start with a capital letter only if they are classes, interfaces, enums, and such - they should not be used for parameters, local variables, or fields. Almost everyone will assume that Screen.NumberOf.... is a static field in class Screen (after dismissing it being a nested class by seeing it used as an integer).
The idea is that if I type com.Foo.Bar.baz.hmm everyone knows that com is a package name, Foo is a class, Bar is the nested class Foo$Bar, baz is a static field in class Bar, and hmm is a field of object baz.