Skip to content

Commit

Permalink
First draft of a 3d polygon, called PolygonMesh
Browse files Browse the repository at this point in the history
  • Loading branch information
craigtaverner committed Apr 18, 2018
1 parent d74e1ed commit 1866637
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 0 deletions.
8 changes: 8 additions & 0 deletions core/src/main/java/org/amanzi/spatial/core/Point.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,12 @@ public double[] getCoordinate() {
public String toString() {
return format("Point%s", Arrays.toString(coordinate));
}

public Point withShift(double... shifts) {
double[] shifted = Arrays.copyOf(this.coordinate, this.coordinate.length);
for (int i = 0; i < shifted.length; i++) {
shifted[i] += shifts[i];
}
return new Point(shifted);
}
}
191 changes: 191 additions & 0 deletions core/src/main/java/org/amanzi/spatial/core/PolygonMesh.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package org.amanzi.spatial.core;

import java.lang.reflect.Array;
import java.util.*;

import static java.lang.String.format;

public class PolygonMesh implements Polygon {
private Point[] vertices;
private Edge[] edges;
private Face[] faces;

private PolygonMesh(Point[] vertices, Edge[] edges, Face[] faces) {
this.vertices = vertices;
this.edges = edges;
this.faces = faces;
}

@Override
public int dimension() {
return 3;
}

@Override
public Point[] getPoints() {
return vertices;
}

public Edge[] getEdges() {
return edges;
}

public Face[] getFaces() {
return faces;
}

@Override
public boolean isSimple() {
return false;
}

@Override
public SimplePolygon[] getShells() {
return new SimplePolygon[0];
}

@Override
public SimplePolygon[] getHoles() {
return new SimplePolygon[0];
}

@Override
public MultiPolygon withShell(Polygon shell) {
return null;
}

@Override
public MultiPolygon withHole(Polygon hole) {
return null;
}

public static PolygonMeshBuilder start() {
return new PolygonMeshBuilder();
}

public static class Edge {
int startVertex;
int endVertex;

Edge(int startVertex, int endVertex) {
if (startVertex < endVertex) {
this.startVertex = startVertex;
this.endVertex = endVertex;
} else {
this.startVertex = endVertex;
this.endVertex = startVertex;
}
}

@Override
public int hashCode() {
return 31 * startVertex + endVertex;
}

public boolean equals(Edge other) {
return this.startVertex == other.startVertex && this.endVertex == other.endVertex;
}

@Override
public boolean equals(Object other) {
return other instanceof Edge && equals((Edge) other);
}

@Override
public String toString() {
return format("Edge[%d,%d]", startVertex, endVertex);
}
}

public static class Face {
int[] edges;

private Face(int[] edges) {
this.edges = edges;
Arrays.sort(this.edges);
}

@Override
public int hashCode() {
return Arrays.hashCode(edges);
}

public boolean equals(Face other) {
return Arrays.equals(this.edges, other.edges);
}

@Override
public boolean equals(Object other) {
return other instanceof Face && equals((Face) other);
}

@Override
public String toString() {
return "Face" + Arrays.toString(edges);
}
}

public static class PolygonMeshBuilder {
LinkedHashMap<Point, Integer> vertices = new LinkedHashMap<>();
LinkedHashMap<Edge, Integer> edges = new LinkedHashMap<>();
LinkedHashMap<Face, Integer> faces = new LinkedHashMap<>();

public PolygonMeshBuilder addFace(SimplePolygon simple) {
Point previous = null;
Integer previousId = 0;
Point[] points = simple.getPoints();
int[] faceEdges = new int[points.length - 1];
int edgeIndex = 0;
for (Point vertex : simple.getPoints()) {
Integer vertexId = vertices.get(vertex);
if (vertexId == null) {
vertexId = vertices.size();
vertices.put(vertex, vertexId);
}
if (previous != null) {
Edge edge = new Edge(previousId, vertexId);
Integer edgeId = edges.get(edge);
if (edgeId == null) {
edgeId = edges.size();
edges.put(edge, edgeId);
}
faceEdges[edgeIndex] = edgeId;
edgeIndex += 1;
}
previous = vertex;
previousId = vertexId;
}
Face face = new Face(faceEdges);
Integer faceId = faces.get(face);
if (faceId == null) {
faceId = faces.size();
faces.put(face, faceId);
}
return this;
}

public PolygonMesh build() {
Point[] v = new MakeArray<>(Point.class).fromMap(vertices);
Edge[] e = new MakeArray<>(Edge.class).fromMap(edges);
Face[] f = new MakeArray<>(Face.class).fromMap(faces);
return new PolygonMesh(v, e, f);
}

private static class MakeArray<T> {
private Class<T> c;

private MakeArray(Class<T> c) {
this.c = c;
}

private T[] fromMap(HashMap<T, Integer> map) {
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(c, map.size());
for (Map.Entry<T, Integer> entry : map.entrySet()) {
array[entry.getValue()] = entry.getKey();
}
return array;
}
}
}
}
97 changes: 97 additions & 0 deletions core/src/test/java/org/amanzi/spatial/core/PolygonMeshTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.amanzi.spatial.core;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;

public class PolygonMeshTest {
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void shouldHaveCorrectHashCode() {
assertThat("Should have same hashcode", new PolygonMesh.Edge(1, 2).hashCode(), equalTo(new PolygonMesh.Edge(2, 1).hashCode()));
}

@Test
public void shouldWorkWithBox() {
PolygonMesh box = makeBox(new Point(0, 0, 0), new double[]{10, 10, 10});
debugPolygonMesh("Box", box);
assertThat("Box should have six faces", box.getFaces().length, equalTo(6));
assertThat("Box should have eight vertices", box.getPoints().length, equalTo(8));
assertThat("Box should have twelve edges", box.getEdges().length, equalTo(12));
}

@Test
public void shouldWorkWithIcosohedron() {
PolygonMesh icosohedron = makeIcosohedron(new Point(0, 0, 0), 10);
debugPolygonMesh("Icosohedron", icosohedron);
assertThat("Icosohedron should have eight faces", icosohedron.getFaces().length, equalTo(8));
assertThat("Icosohedron should have six vertices", icosohedron.getPoints().length, equalTo(6));
assertThat("Icosohedron should have twelve edges", icosohedron.getEdges().length, equalTo(12));
}

private void debugPolygonMesh(String name, PolygonMesh mesh) {
System.out.println(name + " has " + mesh.getPoints().length + " vertices");
int index = 0;
Point[] vertices = mesh.getPoints();
for (Point p : vertices) {
System.out.println("\t" + index + ":\t" + p);
index++;
}
System.out.println(name + " has " + mesh.getEdges().length + " edges");
index = 0;
for (PolygonMesh.Edge e : mesh.getEdges()) {
System.out.println(format("\t%d:\t%s\t[%s, %s]", index, e, vertices[e.startVertex], vertices[e.endVertex]));
index++;
}
System.out.println(name + " has " + mesh.getFaces().length + " faces");
index = 0;
for (PolygonMesh.Face f : mesh.getFaces()) {
System.out.println(format("\t%d:\t%s", index, f));
index++;
}

}

private PolygonMesh makeBox(Point p000, double[] width) {
Point p100 = p000.withShift(width[0], 0, 0);
Point p110 = p000.withShift(width[0], width[1], 0);
Point p010 = p000.withShift(0, width[1], 0);
Point p001 = p000.withShift(0, 0, width[2]);
Point p101 = p100.withShift(0, 0, width[2]);
Point p111 = p110.withShift(0, 0, width[2]);
Point p011 = p010.withShift(0, 0, width[2]);
return PolygonMesh.start()
.addFace(Polygon.simple(p000, p100, p110, p010)) // back face
.addFace(Polygon.simple(p001, p101, p111, p011)) // front face
.addFace(Polygon.simple(p000, p001, p011, p010)) // left side
.addFace(Polygon.simple(p100, p101, p111, p110)) // right side
.addFace(Polygon.simple(p010, p011, p111, p110)) // top
.addFace(Polygon.simple(p000, p001, p101, p100)) // bottom
.build();
}

private PolygonMesh makeIcosohedron(Point p000, double radius) {
Point pp00 = p000.withShift(radius, 0, 0);
Point p0p0 = p000.withShift(0, radius, 0);
Point pn00 = p000.withShift(-radius, 0, 0);
Point p0n0 = p000.withShift(0, -radius, 0);
Point p00p = p000.withShift(0, 0, radius);
Point p00n = p000.withShift(0, 0, -radius);
return PolygonMesh.start()
.addFace(Polygon.simple(p00p, pp00, p0p0)) // upper +x+y
.addFace(Polygon.simple(p00p, pn00, p0p0)) // upper -x+y
.addFace(Polygon.simple(p00p, pn00, p0n0)) // upper -x-y
.addFace(Polygon.simple(p00p, pp00, p0n0)) // upper +x-y
.addFace(Polygon.simple(p00n, pp00, p0p0)) // lower +x+y
.addFace(Polygon.simple(p00n, pn00, p0p0)) // lower -x+y
.addFace(Polygon.simple(p00n, pn00, p0n0)) // lower -x-y
.addFace(Polygon.simple(p00n, pp00, p0n0)) // lower +x-y
.build();
}
}

0 comments on commit 1866637

Please sign in to comment.