Skip to content

Commit

Permalink
GEOMETRY-110
Browse files Browse the repository at this point in the history
Build simplex incrementially while appending points.
  • Loading branch information
Andreas Goss committed Oct 31, 2023
1 parent d952130 commit 74fbc17
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -154,6 +153,9 @@ public static class Builder {
/** Simplex for testing new points and starting the algorithm. */
private Simplex simplex;

/** The minX, maxX, minY, maxY, minZ, maxZ points. */
private final Vector3D[] box;

/**
* A map which contains all the vertices of the current hull as keys and the
* associated facets as values.
Expand All @@ -169,6 +171,8 @@ public Builder(DoubleEquivalence precision) {
candidates = EuclideanCollections.pointSet3D(precision);
this.precision = precision;
vertexToFacetMap = EuclideanCollections.pointMap3D(precision);
box = new Vector3D[6];
simplex = new Simplex(Collections.emptySet());
}

/**
Expand All @@ -178,7 +182,44 @@ public Builder(DoubleEquivalence precision) {
* @return this instance.
*/
public Builder append(Vector3D point) {
if (box[0] == null) {
box[0] = box[1] = box[2] = box[3] = box[4] = box[5] = point;
candidates.add(point);
return this;
}
boolean hasBeenModified = false;
if (box[0].getX() > point.getX()) {
box[0] = point;
hasBeenModified = true;
}
if (box[1].getX() < point.getX()) {
box[1] = point;
hasBeenModified = true;
}
if (box[2].getY() > point.getY()) {
box[2] = point;
hasBeenModified = true;
}
if (box[3].getY() < point.getY()) {
box[3] = point;
hasBeenModified = true;
}
if (box[4].getZ() > point.getZ()) {
box[4] = point;
hasBeenModified = true;
}
if (box[5].getZ() < point.getZ()) {
box[5] = point;
hasBeenModified = true;
}
candidates.add(point);
if (hasBeenModified) {
// Remove all outside Points and add all vertices again.
removeFacets(simplex.facets());
simplex.facets().stream().map(Facet::getPolygon).forEach(p -> candidates.addAll(p.getVertices()));
simplex = createSimplex(candidates);
}
distributePoints(simplex.facets());
return this;
}

Expand All @@ -189,11 +230,14 @@ public Builder append(Vector3D point) {
* @return this instance.
*/
public Builder append(Collection<Vector3D> points) {
simplex = createSimplex(points);
candidates.addAll(points);
if (!simplex.isDegenerate()) {
distributePoints(simplex);
if (simplex != null) {
// Remove all outside Points and add all vertices again.
removeFacets(simplex.facets());
simplex.facets().stream().map(Facet::getPolygon).forEach(p -> candidates.addAll(p.getVertices()));
}
candidates.addAll(points);
simplex = createSimplex(candidates);
distributePoints(simplex.facets());
return this;
}

Expand All @@ -213,8 +257,8 @@ public ConvexHull3D build() {
}

vertexToFacetMap = new HashMap<>();
simplex.forEach(this::addFacet);
distributePoints(simplex);
simplex.facets().forEach(this::addFacet);
distributePoints(simplex.facets());
while (isInconflict()) {
Facet conflictFacet = getConflictFacet();
Vector3D conflictPoint = conflictFacet.getConflictPoint();
Expand Down Expand Up @@ -373,9 +417,11 @@ private void addFacet(Facet facet) {
*
* @param facets the facets to check against.
*/
private void distributePoints(Iterable<Facet> facets) {
candidates.forEach(p -> distributePoint(p, facets));
candidates.clear();
private void distributePoints(Collection<Facet> facets) {
if (!facets.isEmpty()) {
candidates.forEach(p -> distributePoint(p, facets));
candidates.clear();
}
}

/**
Expand Down Expand Up @@ -508,8 +554,7 @@ private Vector3D[] findEdge(Facet facet, Facet neighbor) {
*/
private void removeFacets(Set<Facet> visibleFacets) {
visibleFacets.forEach(f -> candidates.addAll(f.outsideSet()));

if (vertexToFacetMap != null) {
if (!vertexToFacetMap.isEmpty()) {
removeFacetsFromVertexMap(visibleFacets);
}
}
Expand Down Expand Up @@ -618,7 +663,7 @@ public Vector3D getConflictPoint() {
/**
* This class represents a simple simplex with four facets.
*/
private static class Simplex implements Iterable<Facet> {
private static class Simplex {

/** The facets of the simplex. */
private final Set<Facet> facets;
Expand All @@ -631,14 +676,6 @@ private static class Simplex implements Iterable<Facet> {
this.facets = new HashSet<>(facets);
}

/**
* {@inheritDoc}
*/
@Override
public Iterator<Facet> iterator() {
return facets.iterator();
}

/**
* Returns {@code true} if the collection of facets is empty.
*
Expand All @@ -647,5 +684,14 @@ public Iterator<Facet> iterator() {
public boolean isDegenerate() {
return facets.isEmpty();
}

/**
* Returns the facets of the simplex as set.
*
* @return the facets of the simplex as set.
*/
public Set<Facet> facets() {
return Collections.unmodifiableSet(facets);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -157,6 +158,25 @@ void unitCube() {
assertTrue(TEST_PRECISION.eq(1.0, hull.getRegion().getSize()));
}

@Test
void unitCubeSequentially() {
List<Vector3D> vertices = Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0),
Vector3D.of(0, 0, 1), Vector3D.of(1, 1, 0), Vector3D.of(1, 0, 1), Vector3D.of(0, 1, 1),
Vector3D.of(1, 1, 1));
vertices.forEach(builder::append);
ConvexHull3D hull = builder.build();
assertNotNull(hull.getRegion());
assertTrue(hull.getRegion().contains(Vector3D.ZERO));
assertTrue(hull.getRegion().contains(Vector3D.of(1, 0, 0)));
assertTrue(hull.getRegion().contains(Vector3D.of(0, 1, 0)));
assertTrue(hull.getRegion().contains(Vector3D.of(0, 0, 1)));
assertTrue(hull.getRegion().contains(Vector3D.of(1, 1, 0)));
assertTrue(hull.getRegion().contains(Vector3D.of(1, 0, 1)));
assertTrue(hull.getRegion().contains(Vector3D.of(0, 1, 1)));
assertTrue(hull.getRegion().contains(Vector3D.of(1, 1, 1)));
assertTrue(TEST_PRECISION.eq(1.0, hull.getRegion().getSize()));
}

@Test
void multiplePoints() {
List<Vector3D> vertices = Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0),
Expand Down Expand Up @@ -184,7 +204,7 @@ void multiplePoints() {
*/
@Test
void randomUnitPoints() {
//All points in the set must be on the hull. This is a worst case scenario.
// All points in the set must be on the hull. This is a worst case scenario.
Set<Vector3D> set = createRandomPoints(1000, true);
builder.append(set);
ConvexHull3D hull = builder.build();
Expand All @@ -209,10 +229,41 @@ void randomPoints() {
}
}

@Test
void randomPointsInTwoSets() {
Set<Vector3D> set1 = createRandomPoints(50000, false);
Set<Vector3D> set2 = createRandomPoints(50000, false);
builder.append(set1);
builder.append(set2);
ConvexHull3D hull = builder.build();
ConvexVolume region = hull.getRegion();
assertNotNull(region);
for (Vector3D p : set1) {
assertTrue(region.contains(p));
}
for (Vector3D p : set2) {
assertTrue(region.contains(p));
}
}

@Test
void randomPointsSequentially() {
// Points are added sequentially
List<Vector3D> list = new ArrayList<>(createRandomPoints(100, false));
list.forEach(builder::append);
ConvexHull3D hull = builder.build();
ConvexVolume region = hull.getRegion();
assertNotNull(region);
for (int i = 0; i < 100; i++) {
Vector3D p = list.get(i);
assertTrue(region.contains(p), String.format("The Vector with position %d is different.", i));
}
}

/**
* Create a specified number of random points on the unit sphere.
*
* @param number the given number.
* @param number the given number.
* @param normalize normalize the output points.
* @return a specified number of random points on the unit sphere.
*/
Expand Down

0 comments on commit 74fbc17

Please sign in to comment.