Skip to content
Permalink
Browse files
GEOMETRY-115: adding initial IO modules
  • Loading branch information
darkma773r committed Mar 28, 2021
1 parent 8c82ce9 commit dcc5d43a33ffddc546088b644162d7f51b548192
Showing 156 changed files with 23,617 additions and 3,042 deletions.
@@ -111,46 +111,6 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<!--
Needed for command-line access, e.g mvn apache-rat:rat and mvn apache-rat:check
Below should agree with config in <reporting> section, so the site
gets consistent output.
-->
<excludes combine.children="append">
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/issue-1211.bsp</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-bad-orientation.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-hole.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-out-of-plane.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-too-close.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N.ply</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

<reporting>
<plugins>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<!-- Should agree with apache-rat-plugin config under <build> -->
<excludes combine.children="append">
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/issue-1211.bsp</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-bad-orientation.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-hole.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-out-of-plane.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-too-close.ply</exclude>
<exclude>src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N.ply</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</reporting>

</project>
@@ -20,9 +20,11 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
import org.apache.commons.geometry.core.partitioning.Split;
@@ -398,6 +400,98 @@ public static List<PlaneConvexSubset> extrude(final RegionBSPTree2D region, fina
return new PlaneRegionExtruder(plane, extrusionVector, precision).extrude(region);
}

/** Convert a convex polygon defined by a list of vertices into a triangle fan. The vertex forming the largest
* interior angle in the polygon is selected as the base of the triangle fan. Callers are responsible for
* ensuring that the given list of vertices define a geometrically valid convex polygon; no validation (except
* for a check on the minimum number of vertices) is performed.
* @param <T> triangle result type
* @param vertices vertices defining a convex polygon
* @param fn function accepting the vertices of each triangle as a list and returning the object used
* to represent that triangle in the result; each argument to this function is guaranteed to
* contain 3 vertices
* @return a list containing the return results of the function when passed the vertices for each
* triangle in order
* @throws IllegalArgumentException if fewer than 3 vertices are given
*/
public static <T> List<T> convexPolygonToTriangleFan(final List<Vector3D> vertices,
final Function<List<Vector3D>, T> fn) {
final int size = vertices.size();
if (size < 3) {
throw new IllegalArgumentException("Cannot create triangle fan: 3 or more vertices are required " +
"but found only " + vertices.size());
} else if (size == 3) {
return Collections.singletonList(fn.apply(vertices));
}

final List<T> triangles = new ArrayList<>(size - 2);

final int fanIdx = findBestTriangleFanIndex(vertices);
int vertexIdx = (fanIdx + 1) % size;

final Vector3D fanBase = vertices.get(fanIdx);
Vector3D vertexA = vertices.get(vertexIdx);
Vector3D vertexB;

vertexIdx = (vertexIdx + 1) % size;
while (vertexIdx != fanIdx) {
vertexB = vertices.get(vertexIdx);

triangles.add(fn.apply(Arrays.asList(fanBase, vertexA, vertexB)));

vertexA = vertexB;
vertexIdx = (vertexIdx + 1) % size;
}

return triangles;
}

/** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
* defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
* polygon since a split at that point will help prevent the creation of very thin triangles.
* @param vertices vertices defining the convex polygon; must not be empty; no validation is performed
* to ensure that the vertices actually define a convex polygon
* @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
*/
private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
final Iterator<Vector3D> it = vertices.iterator();

Vector3D curPt = it.next();
Vector3D nextPt;

final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
Vector3D incomingVec = lastVec;
Vector3D outgoingVec;

int bestIdx = 0;
double bestDot = -1.0;

int idx = 0;
double dot;
while (it.hasNext()) {
nextPt = it.next();
outgoingVec = curPt.directionTo(nextPt);

dot = incomingVec.dot(outgoingVec);
if (dot > bestDot) {
bestIdx = idx;
bestDot = dot;
}

curPt = nextPt;
incomingVec = outgoingVec;

++idx;
}

// handle the last vertex on its own
dot = incomingVec.dot(lastVec);
if (dot > bestDot) {
bestIdx = idx;
}

return bestIdx;
}

/** Get the unique intersection of the plane subset with the given line. Null is
* returned if no unique intersection point exists (ie, the line and plane are
* parallel or coincident) or the line does not intersect the plane subset.
@@ -528,79 +622,8 @@ static ConvexPolygon3D fromConvexPlanarVertices(final Plane plane, final List<Ve
* @throws IllegalArgumentException if fewer than 3 vertices are given
*/
static List<Triangle3D> convexPolygonToTriangleFan(final Plane plane, final List<Vector3D> vertices) {
final int size = vertices.size();
if (size < 3) {
throw new IllegalArgumentException("Cannot create triangle fan: 3 or more vertices are required " +
"but found only " + vertices.size());
}

final List<Triangle3D> triangles = new ArrayList<>(size - 2);

final int fanIdx = findBestTriangleFanIndex(vertices);
int vertexIdx = (fanIdx + 1) % size;

final Vector3D fanBase = vertices.get(fanIdx);
Vector3D vertexA = vertices.get(vertexIdx);
Vector3D vertexB;

vertexIdx = (vertexIdx + 1) % size;
while (vertexIdx != fanIdx) {
vertexB = vertices.get(vertexIdx);

// add directly as a triangle instance to avoid computation of the plane again
triangles.add(new SimpleTriangle3D(plane, fanBase, vertexA, vertexB));

vertexA = vertexB;
vertexIdx = (vertexIdx + 1) % size;
}

return triangles;
}

/** Find the index of the best vertex to use as the base for a triangle fan split of the convex polygon
* defined by the given vertices. The best vertex is the one that forms the largest interior angle in the
* polygon since a split at that point will help prevent the creation of very thin triangles.
* @param vertices vertices defining the convex polygon; must not be empty
* @return the index of the best vertex to use as the base for a triangle fan split of the convex polygon
*/
private static int findBestTriangleFanIndex(final List<Vector3D> vertices) {
final Iterator<Vector3D> it = vertices.iterator();

Vector3D curPt = it.next();
Vector3D nextPt;

final Vector3D lastVec = vertices.get(vertices.size() - 1).directionTo(curPt);
Vector3D incomingVec = lastVec;
Vector3D outgoingVec;

int bestIdx = 0;
double bestDot = -1.0;

int idx = 0;
double dot;
while (it.hasNext()) {
nextPt = it.next();
outgoingVec = curPt.directionTo(nextPt);

dot = incomingVec.dot(outgoingVec);
if (dot > bestDot) {
bestIdx = idx;
bestDot = dot;
}

curPt = nextPt;
incomingVec = outgoingVec;

++idx;
}

// handle the last vertex on its own
dot = incomingVec.dot(lastVec);
if (dot > bestDot) {
bestIdx = idx;
}

return bestIdx;
return convexPolygonToTriangleFan(vertices,
tri -> new SimpleTriangle3D(plane, tri.get(0), tri.get(1), tri.get(2)));
}

/** Internal helper class used to construct planes from sequences of points. Instances can be also be
@@ -523,6 +523,15 @@ public int getVertexCount() {
return vertices.size();
}

/** Get the vertex at the given index.
* @param index index of the vertex to retrieve
* @return vertex at the given index
* @throws IndexOutOfBoundsException if the index is out of bounds of the mesh vertex list
*/
public Vector3D getVertex(final int index) {
return vertices.get(index);
}

/** Append a face to this mesh.
* @param index1 index of the first vertex in the face
* @param index2 index of the second vertex in the face
@@ -16,7 +16,6 @@
*/
package org.apache.commons.geometry.euclidean;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -29,7 +28,6 @@
import org.apache.commons.geometry.euclidean.oned.Interval;
import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
import org.apache.commons.geometry.euclidean.oned.Vector1D;
import org.apache.commons.geometry.euclidean.testio.TestOBJWriter;
import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
import org.apache.commons.geometry.euclidean.threed.ConvexPolygon3D;
import org.apache.commons.geometry.euclidean.threed.Plane;
@@ -41,10 +39,8 @@
import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
import org.apache.commons.geometry.euclidean.threed.line.Ray3D;
import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
import org.apache.commons.geometry.euclidean.threed.shape.Parallelepiped;
import org.apache.commons.geometry.euclidean.threed.shape.Sphere;
import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
import org.apache.commons.geometry.euclidean.twod.Line;
import org.apache.commons.geometry.euclidean.twod.LinecastPoint2D;
@@ -66,35 +62,6 @@ public class DocumentationExamplesTest {

private static final double TEST_EPS = 1e-12;

@Test
public void testIndexPageExample() {
// construct a precision context to handle floating-point comparisons
final DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-6);

// create a BSP tree representing the unit cube
final RegionBSPTree3D tree = Parallelepiped.unitCube(precision).toTree();

// create a sphere centered on the origin
final Sphere sphere = Sphere.from(Vector3D.ZERO, 0.65, precision);

// subtract a BSP tree approximation of the sphere containing 512 facets
// from the cube, modifying the cube tree in place
tree.difference(sphere.toTree(3));

// compute some properties of the resulting region
final double size = tree.getSize(); // 0.11509505362599505
final Vector3D centroid = tree.getCentroid(); // (0, 0, 0)

// convert to a triangle mesh for output to other programs
final TriangleMesh mesh = tree.toTriangleMesh(precision);

// -----------
Assertions.assertEquals(0.11509505362599505, size, TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, centroid, TEST_EPS);

TestOBJWriter.write(mesh, new File("target/index-page-example.obj"));
}

@Test
public void testPrecisionContextExample() {
// create a precision context with an epsilon (aka, tolerance) value of 1e-3

0 comments on commit dcc5d43

Please sign in to comment.