blob: 228f3fc81c9b2f9ec382fa94a005d2082d164299 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.euclidean.threed;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.geometry.core.partitioning.BSPTree;
import org.apache.commons.geometry.core.partitioning.BSPTreeVisitor;
import org.apache.commons.geometry.core.partitioning.BoundaryAttribute;
import org.apache.commons.geometry.core.partitioning.BoundaryProjection;
import org.apache.commons.geometry.core.partitioning.Region;
import org.apache.commons.geometry.core.partitioning.RegionFactory;
import org.apache.commons.geometry.core.partitioning.SubHyperplane;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
import org.apache.commons.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.geometry.euclidean.twod.SubLine;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.Assert;
import org.junit.Test;
public class PolyhedronsSetTest {
private static final double TEST_EPS = 1e-10;
private static final DoublePrecisionContext TEST_PRECISION =
new EpsilonDoublePrecisionContext(TEST_EPS);
@Test
public void testWholeSpace() {
// act
PolyhedronsSet polySet = new PolyhedronsSet(TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
EuclideanTestUtils.assertPositiveInfinity(polySet.getSize());
Assert.assertEquals(0.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertTrue(polySet.isFull());
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
Vector3D.of(-100, -100, -100),
Vector3D.of(0, 0, 0),
Vector3D.of(100, 100, 100),
Vector3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
}
@Test
public void testEmptyRegion() {
// act
PolyhedronsSet polySet = new PolyhedronsSet(new BSPTree<Vector3D>(Boolean.FALSE), TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(0.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(0.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, polySet.getBarycenter(), TEST_EPS);
Assert.assertTrue(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
Vector3D.of(-100, -100, -100),
Vector3D.of(0, 0, 0),
Vector3D.of(100, 100, 100),
Vector3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
}
@Test
public void testHalfSpace() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.add(new SubPlane(Plane.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.PLUS_Y, TEST_PRECISION),
new PolygonsSet(TEST_PRECISION)));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
EuclideanTestUtils.assertPositiveInfinity(polySet.getSize());
EuclideanTestUtils.assertPositiveInfinity(polySet.getBoundarySize());
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
Vector3D.of(-100, -100, -100));
checkPoints(Region.Location.BOUNDARY, polySet, Vector3D.of(0, 0, 0));
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(100, 100, 100),
Vector3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
}
@Test
public void testInvertedRegion() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(Vector3D.ZERO, 1.0, TEST_EPS);
PolyhedronsSet box = new PolyhedronsSet(boundaries, TEST_PRECISION);
// act
PolyhedronsSet polySet = (PolyhedronsSet) new RegionFactory<Vector3D>().getComplement(box);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
EuclideanTestUtils.assertPositiveInfinity(polySet.getSize());
Assert.assertEquals(6, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE),
Vector3D.of(-100, -100, -100),
Vector3D.of(100, 100, 100),
Vector3D.of(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE));
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(0, 0, 0));
}
@Test
public void testCreateFromBoundaries_noBoundaries_treeRepresentsWholeSpace() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
EuclideanTestUtils.assertPositiveInfinity(polySet.getSize());
Assert.assertEquals(0.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertTrue(polySet.isFull());
}
@Test
public void testCreateFromBoundaries_unitBox() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(Vector3D.ZERO, 1.0, TEST_EPS);
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(1.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(6.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(1, 0, 0),
Vector3D.of(0, -1, 0),
Vector3D.of(0, 1, 0),
Vector3D.of(0, 0, -1),
Vector3D.of(0, 0, 1),
Vector3D.of(1, 1, 1),
Vector3D.of(1, 1, -1),
Vector3D.of(1, -1, 1),
Vector3D.of(1, -1, -1),
Vector3D.of(-1, 1, 1),
Vector3D.of(-1, 1, -1),
Vector3D.of(-1, -1, 1),
Vector3D.of(-1, -1, -1));
checkPoints(Region.Location.BOUNDARY, polySet,
Vector3D.of(0.5, 0, 0),
Vector3D.of(-0.5, 0, 0),
Vector3D.of(0, 0.5, 0),
Vector3D.of(0, -0.5, 0),
Vector3D.of(0, 0, 0.5),
Vector3D.of(0, 0, -0.5),
Vector3D.of(0.5, 0.5, 0.5),
Vector3D.of(0.5, 0.5, -0.5),
Vector3D.of(0.5, -0.5, 0.5),
Vector3D.of(0.5, -0.5, -0.5),
Vector3D.of(-0.5, 0.5, 0.5),
Vector3D.of(-0.5, 0.5, -0.5),
Vector3D.of(-0.5, -0.5, 0.5),
Vector3D.of(-0.5, -0.5, -0.5));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(0.4, 0.4, 0.4),
Vector3D.of(0.4, 0.4, -0.4),
Vector3D.of(0.4, -0.4, 0.4),
Vector3D.of(0.4, -0.4, -0.4),
Vector3D.of(-0.4, 0.4, 0.4),
Vector3D.of(-0.4, 0.4, -0.4),
Vector3D.of(-0.4, -0.4, 0.4),
Vector3D.of(-0.4, -0.4, -0.4));
}
@Test
public void testCreateFromBoundaries_twoBoxes_disjoint() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.addAll(createBoxBoundaries(Vector3D.ZERO, 1.0, TEST_EPS));
boundaries.addAll(createBoxBoundaries(Vector3D.of(2, 0, 0), 1.0, TEST_EPS));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(2.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(1, 0, 0),
Vector3D.of(3, 0, 0));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(2, 0, 0));
}
@Test
public void testCreateFromBoundaries_twoBoxes_sharedSide() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.addAll(createBoxBoundaries(Vector3D.of(0, 0, 0), 1.0, TEST_EPS));
boundaries.addAll(createBoxBoundaries(Vector3D.of(1, 0, 0), 1.0, TEST_EPS));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(2.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(10.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0, 0), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(2, 0, 0));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(1, 0, 0));
}
@Test
public void testCreateFromBoundaries_twoBoxes_separationLessThanTolerance() {
// arrange
double eps = 1e-6;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.addAll(createBoxBoundaries(Vector3D.of(0, 0, 0), 1.0, eps));
boundaries.addAll(createBoxBoundaries(Vector3D.of(1 + 1e-7, 0, 0), 1.0, eps));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, precision);
// assert
Assert.assertSame(precision, polySet.getPrecision());
Assert.assertEquals(2.0, polySet.getSize(), eps);
Assert.assertEquals(10.0, polySet.getBoundarySize(), eps);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5 + 5e-8, 0, 0), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(2, 0, 0));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(1, 0, 0));
}
@Test
public void testCreateFromBoundaries_twoBoxes_sharedEdge() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.addAll(createBoxBoundaries(Vector3D.of(0, 0, 0), 1.0, TEST_EPS));
boundaries.addAll(createBoxBoundaries(Vector3D.of(1, 1, 0), 1.0, TEST_EPS));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(2.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(1, 0, 0),
Vector3D.of(0, 1, 0),
Vector3D.of(2, 1, 0));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(1, 1, 0));
}
@Test
public void testCreateFromBoundaries_twoBoxes_sharedPoint() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
boundaries.addAll(createBoxBoundaries(Vector3D.of(0, 0, 0), 1.0, TEST_EPS));
boundaries.addAll(createBoxBoundaries(Vector3D.of(1, 1, 1), 1.0, TEST_EPS));
// act
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
// assert
Assert.assertSame(TEST_PRECISION, polySet.getPrecision());
Assert.assertEquals(2.0, polySet.getSize(), TEST_EPS);
Assert.assertEquals(12.0, polySet.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-1, 0, 0),
Vector3D.of(1, 0, 0),
Vector3D.of(0, 1, 1),
Vector3D.of(2, 1, 1));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(0, 0, 0),
Vector3D.of(1, 1, 1));
}
@Test
public void testCreateBox() {
// act
PolyhedronsSet tree = new PolyhedronsSet(0, 1, 0, 1, 0, 1, TEST_PRECISION);
// assert
Assert.assertEquals(1.0, tree.getSize(), TEST_EPS);
Assert.assertEquals(6.0, tree.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), tree.getBarycenter(), TEST_EPS);
for (double x = -0.25; x < 1.25; x += 0.1) {
boolean xOK = (x >= 0.0) && (x <= 1.0);
for (double y = -0.25; y < 1.25; y += 0.1) {
boolean yOK = (y >= 0.0) && (y <= 1.0);
for (double z = -0.25; z < 1.25; z += 0.1) {
boolean zOK = (z >= 0.0) && (z <= 1.0);
Region.Location expected =
(xOK && yOK && zOK) ? Region.Location.INSIDE : Region.Location.OUTSIDE;
Assert.assertEquals(expected, tree.checkPoint(Vector3D.of(x, y, z)));
}
}
}
checkPoints(Region.Location.BOUNDARY, tree, new Vector3D[] {
Vector3D.of(0.0, 0.5, 0.5),
Vector3D.of(1.0, 0.5, 0.5),
Vector3D.of(0.5, 0.0, 0.5),
Vector3D.of(0.5, 1.0, 0.5),
Vector3D.of(0.5, 0.5, 0.0),
Vector3D.of(0.5, 0.5, 1.0)
});
checkPoints(Region.Location.OUTSIDE, tree, new Vector3D[] {
Vector3D.of(0.0, 1.2, 1.2),
Vector3D.of(1.0, 1.2, 1.2),
Vector3D.of(1.2, 0.0, 1.2),
Vector3D.of(1.2, 1.0, 1.2),
Vector3D.of(1.2, 1.2, 0.0),
Vector3D.of(1.2, 1.2, 1.0)
});
}
@Test
public void testInvertedBox() {
// arrange
PolyhedronsSet tree = new PolyhedronsSet(0, 1, 0, 1, 0, 1, TEST_PRECISION);
// act
tree = (PolyhedronsSet) new RegionFactory<Vector3D>().getComplement(tree);
// assert
EuclideanTestUtils.assertPositiveInfinity(tree.getSize());
Assert.assertEquals(6.0, tree.getBoundarySize(), 1.0e-10);
Vector3D barycenter = tree.getBarycenter();
Assert.assertTrue(Double.isNaN(barycenter.getX()));
Assert.assertTrue(Double.isNaN(barycenter.getY()));
Assert.assertTrue(Double.isNaN(barycenter.getZ()));
for (double x = -0.25; x < 1.25; x += 0.1) {
boolean xOK = (x < 0.0) || (x > 1.0);
for (double y = -0.25; y < 1.25; y += 0.1) {
boolean yOK = (y < 0.0) || (y > 1.0);
for (double z = -0.25; z < 1.25; z += 0.1) {
boolean zOK = (z < 0.0) || (z > 1.0);
Region.Location expected =
(xOK || yOK || zOK) ? Region.Location.INSIDE : Region.Location.OUTSIDE;
Assert.assertEquals(expected, tree.checkPoint(Vector3D.of(x, y, z)));
}
}
}
checkPoints(Region.Location.BOUNDARY, tree, new Vector3D[] {
Vector3D.of(0.0, 0.5, 0.5),
Vector3D.of(1.0, 0.5, 0.5),
Vector3D.of(0.5, 0.0, 0.5),
Vector3D.of(0.5, 1.0, 0.5),
Vector3D.of(0.5, 0.5, 0.0),
Vector3D.of(0.5, 0.5, 1.0)
});
checkPoints(Region.Location.INSIDE, tree, new Vector3D[] {
Vector3D.of(0.0, 1.2, 1.2),
Vector3D.of(1.0, 1.2, 1.2),
Vector3D.of(1.2, 0.0, 1.2),
Vector3D.of(1.2, 1.0, 1.2),
Vector3D.of(1.2, 1.2, 0.0),
Vector3D.of(1.2, 1.2, 1.0)
});
}
@Test
public void testTetrahedron() {
// arrange
Vector3D vertex1 = Vector3D.of(1, 2, 3);
Vector3D vertex2 = Vector3D.of(2, 2, 4);
Vector3D vertex3 = Vector3D.of(2, 3, 3);
Vector3D vertex4 = Vector3D.of(1, 3, 4);
// act
PolyhedronsSet tree =
(PolyhedronsSet) new RegionFactory<Vector3D>().buildConvex(
Plane.fromPoints(vertex3, vertex2, vertex1, TEST_PRECISION),
Plane.fromPoints(vertex2, vertex3, vertex4, TEST_PRECISION),
Plane.fromPoints(vertex4, vertex3, vertex1, TEST_PRECISION),
Plane.fromPoints(vertex1, vertex2, vertex4, TEST_PRECISION));
// assert
Assert.assertEquals(1.0 / 3.0, tree.getSize(), TEST_EPS);
Assert.assertEquals(2.0 * Math.sqrt(3.0), tree.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1.5, 2.5, 3.5), tree.getBarycenter(), TEST_EPS);
double third = 1.0 / 3.0;
checkPoints(Region.Location.BOUNDARY, tree, new Vector3D[] {
vertex1, vertex2, vertex3, vertex4,
Vector3D.linearCombination(third, vertex1, third, vertex2, third, vertex3),
Vector3D.linearCombination(third, vertex2, third, vertex3, third, vertex4),
Vector3D.linearCombination(third, vertex3, third, vertex4, third, vertex1),
Vector3D.linearCombination(third, vertex4, third, vertex1, third, vertex2)
});
checkPoints(Region.Location.OUTSIDE, tree, new Vector3D[] {
Vector3D.of(1, 2, 4),
Vector3D.of(2, 2, 3),
Vector3D.of(2, 3, 4),
Vector3D.of(1, 3, 3)
});
}
@Test
public void testSphere() {
// arrange
// (use a high tolerance value here since the sphere is only an approximation)
double approximationTolerance = 0.2;
double radius = 1.0;
// act
PolyhedronsSet polySet = createSphere(Vector3D.of(1, 2, 3), radius, 8, 16);
// assert
Assert.assertEquals(sphereVolume(radius), polySet.getSize(), approximationTolerance);
Assert.assertEquals(sphereSurface(radius), polySet.getBoundarySize(), approximationTolerance);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), polySet.getBarycenter(), TEST_EPS);
Assert.assertFalse(polySet.isEmpty());
Assert.assertFalse(polySet.isFull());
checkPoints(Region.Location.OUTSIDE, polySet,
Vector3D.of(-0.1, 2, 3),
Vector3D.of(2.1, 2, 3),
Vector3D.of(1, 0.9, 3),
Vector3D.of(1, 3.1, 3),
Vector3D.of(1, 2, 1.9),
Vector3D.of(1, 2, 4.1),
Vector3D.of(1.6, 2.6, 3.6));
checkPoints(Region.Location.INSIDE, polySet,
Vector3D.of(1, 2, 3),
Vector3D.of(0.1, 2, 3),
Vector3D.of(1.9, 2, 3),
Vector3D.of(1, 2.1, 3),
Vector3D.of(1, 2.9, 3),
Vector3D.of(1, 2, 2.1),
Vector3D.of(1, 2, 3.9),
Vector3D.of(1.5, 2.5, 3.5));
}
@Test
public void testIsometry() {
// arrange
Vector3D vertex1 = Vector3D.of(1.1, 2.2, 3.3);
Vector3D vertex2 = Vector3D.of(2.0, 2.4, 4.2);
Vector3D vertex3 = Vector3D.of(2.8, 3.3, 3.7);
Vector3D vertex4 = Vector3D.of(1.0, 3.6, 4.5);
// act
PolyhedronsSet tree =
(PolyhedronsSet) new RegionFactory<Vector3D>().buildConvex(
Plane.fromPoints(vertex3, vertex2, vertex1, TEST_PRECISION),
Plane.fromPoints(vertex2, vertex3, vertex4, TEST_PRECISION),
Plane.fromPoints(vertex4, vertex3, vertex1, TEST_PRECISION),
Plane.fromPoints(vertex1, vertex2, vertex4, TEST_PRECISION));
// assert
Vector3D barycenter = tree.getBarycenter();
Vector3D s = Vector3D.of(10.2, 4.3, -6.7);
Vector3D c = Vector3D.of(-0.2, 2.1, -3.2);
QuaternionRotation r = QuaternionRotation.fromAxisAngle(Vector3D.of(6.2, -4.4, 2.1), 0.12);
tree = tree.rotate(c, r).translate(s);
Vector3D newB =
Vector3D.linearCombination(1.0, s,
1.0, c,
1.0, r.apply(barycenter.subtract(c)));
Assert.assertEquals(0.0,
newB.subtract(tree.getBarycenter()).norm(),
TEST_EPS);
final Vector3D[] expectedV = new Vector3D[] {
Vector3D.linearCombination(1.0, s,
1.0, c,
1.0, r.apply(vertex1.subtract(c))),
Vector3D.linearCombination(1.0, s,
1.0, c,
1.0, r.apply(vertex2.subtract(c))),
Vector3D.linearCombination(1.0, s,
1.0, c,
1.0, r.apply(vertex3.subtract(c))),
Vector3D.linearCombination(1.0, s,
1.0, c,
1.0, r.apply(vertex4.subtract(c)))
};
tree.getTree(true).visit(new BSPTreeVisitor<Vector3D>() {
@Override
public Order visitOrder(BSPTree<Vector3D> node) {
return Order.MINUS_SUB_PLUS;
}
@Override
public void visitInternalNode(BSPTree<Vector3D> node) {
@SuppressWarnings("unchecked")
BoundaryAttribute<Vector3D> attribute =
(BoundaryAttribute<Vector3D>) node.getAttribute();
if (attribute.getPlusOutside() != null) {
checkFacet((SubPlane) attribute.getPlusOutside());
}
if (attribute.getPlusInside() != null) {
checkFacet((SubPlane) attribute.getPlusInside());
}
}
@Override
public void visitLeafNode(BSPTree<Vector3D> node) {
}
private void checkFacet(SubPlane facet) {
Plane plane = (Plane) facet.getHyperplane();
Vector2D[][] vertices =
((PolygonsSet) facet.getRemainingRegion()).getVertices();
Assert.assertEquals(1, vertices.length);
for (int i = 0; i < vertices[0].length; ++i) {
Vector3D v = plane.toSpace(vertices[0][i]);
double d = Double.POSITIVE_INFINITY;
for (int k = 0; k < expectedV.length; ++k) {
d = Math.min(d, v.subtract(expectedV[k]).norm());
}
Assert.assertEquals(0, d, TEST_EPS);
}
}
});
}
@Test
public void testBuildBox() {
// arrange
double x = 1.0;
double y = 2.0;
double z = 3.0;
double w = 0.1;
double l = 1.0;
// act
PolyhedronsSet tree =
new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, TEST_PRECISION);
// assert
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(x, y, z), tree.getBarycenter(), TEST_EPS);
Assert.assertEquals(8 * l * w * w, tree.getSize(), TEST_EPS);
Assert.assertEquals(8 * w * (2 * l + w), tree.getBoundarySize(), TEST_EPS);
}
@Test
public void testCross() {
// arrange
double x = 1.0;
double y = 2.0;
double z = 3.0;
double w = 0.1;
double l = 1.0;
PolyhedronsSet xBeam =
new PolyhedronsSet(x - l, x + l, y - w, y + w, z - w, z + w, TEST_PRECISION);
PolyhedronsSet yBeam =
new PolyhedronsSet(x - w, x + w, y - l, y + l, z - w, z + w, TEST_PRECISION);
PolyhedronsSet zBeam =
new PolyhedronsSet(x - w, x + w, y - w, y + w, z - l, z + l, TEST_PRECISION);
RegionFactory<Vector3D> factory = new RegionFactory<>();
// act
PolyhedronsSet tree = (PolyhedronsSet) factory.union(xBeam, factory.union(yBeam, zBeam));
// assert
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(x, y, z), tree.getBarycenter(), TEST_EPS);
Assert.assertEquals(8 * w * w * (3 * l - 2 * w), tree.getSize(), TEST_EPS);
Assert.assertEquals(24 * w * (2 * l - w), tree.getBoundarySize(), TEST_EPS);
}
// Issue MATH-780
// See https://issues.apache.org/jira/browse/MATH-780
@Test
public void testCreateFromBoundaries_handlesSmallBoundariesCreatedDuringConstruction() {
// arrange
float[] coords = {
1.000000f, -1.000000f, -1.000000f,
1.000000f, -1.000000f, 1.000000f,
-1.000000f, -1.000000f, 1.000000f,
-1.000000f, -1.000000f, -1.000000f,
1.000000f, 1.000000f, -1f,
0.999999f, 1.000000f, 1.000000f, // 1.000000f, 1.000000f, 1.000000f,
-1.000000f, 1.000000f, 1.000000f,
-1.000000f, 1.000000f, -1.000000f};
int[] indices = {
0, 1, 2, 0, 2, 3,
4, 7, 6, 4, 6, 5,
0, 4, 5, 0, 5, 1,
1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3,
4, 0, 3, 4, 3, 7};
ArrayList<SubHyperplane<Vector3D>> subHyperplaneList = new ArrayList<>();
for (int idx = 0; idx < indices.length; idx += 3) {
int idxA = indices[idx] * 3;
int idxB = indices[idx + 1] * 3;
int idxC = indices[idx + 2] * 3;
Vector3D v_1 = Vector3D.of(coords[idxA], coords[idxA + 1], coords[idxA + 2]);
Vector3D v_2 = Vector3D.of(coords[idxB], coords[idxB + 1], coords[idxB + 2]);
Vector3D v_3 = Vector3D.of(coords[idxC], coords[idxC + 1], coords[idxC + 2]);
Vector3D[] vertices = {v_1, v_2, v_3};
Plane polyPlane = Plane.fromPoints(v_1, v_2, v_3, TEST_PRECISION);
ArrayList<SubHyperplane<Vector2D>> lines = new ArrayList<>();
Vector2D[] projPts = new Vector2D[vertices.length];
for (int ptIdx = 0; ptIdx < projPts.length; ptIdx++) {
projPts[ptIdx] = polyPlane.toSubSpace(vertices[ptIdx]);
}
SubLine lineInPlane = null;
for (int ptIdx = 0; ptIdx < projPts.length; ptIdx++) {
lineInPlane = new SubLine(projPts[ptIdx], projPts[(ptIdx + 1) % projPts.length], TEST_PRECISION);
lines.add(lineInPlane);
}
Region<Vector2D> polyRegion = new PolygonsSet(lines, TEST_PRECISION);
SubPlane polygon = new SubPlane(polyPlane, polyRegion);
subHyperplaneList.add(polygon);
}
// act
PolyhedronsSet polyhedronsSet = new PolyhedronsSet(subHyperplaneList, TEST_PRECISION);
// assert
Assert.assertEquals(8.0, polyhedronsSet.getSize(), 3.0e-6);
Assert.assertEquals(24.0, polyhedronsSet.getBoundarySize(), 5.0e-6);
}
@Test
public void testTooThinBox() {
// act
PolyhedronsSet polyhedronsSet = new PolyhedronsSet(0.0, 0.0, 0.0, 1.0, 0.0, 1.0, TEST_PRECISION);
// assert
Assert.assertEquals(0.0, polyhedronsSet.getSize(), TEST_EPS);
}
@Test
public void testWrongUsage() {
// the following is a wrong usage of the constructor.
// as explained in the javadoc, the failure is NOT detected at construction
// time but occurs later on
PolyhedronsSet ps = new PolyhedronsSet(new BSPTree<Vector3D>(), TEST_PRECISION);
Assert.assertNotNull(ps);
try {
ps.checkPoint(Vector3D.ZERO);
Assert.fail("an exception should have been thrown");
} catch (NullPointerException npe) {
// this is expected
}
}
@Test
public void testDumpParse() throws IOException, ParseException {
// arrange
double eps = 1e-8;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
Vector3D[] verts=new Vector3D[8];
double xmin=-1,xmax=1;
double ymin=-1,ymax=1;
double zmin=-1,zmax=1;
verts[0]=Vector3D.of(xmin,ymin,zmin);
verts[1]=Vector3D.of(xmax,ymin,zmin);
verts[2]=Vector3D.of(xmax,ymax,zmin);
verts[3]=Vector3D.of(xmin,ymax,zmin);
verts[4]=Vector3D.of(xmin,ymin,zmax);
verts[5]=Vector3D.of(xmax,ymin,zmax);
verts[6]=Vector3D.of(xmax,ymax,zmax);
verts[7]=Vector3D.of(xmin,ymax,zmax);
//
int[][] faces=new int[12][];
faces[0]=new int[]{3,1,0}; // bottom (-z)
faces[1]=new int[]{1,3,2}; // bottom (-z)
faces[2]=new int[]{5,7,4}; // top (+z)
faces[3]=new int[]{7,5,6}; // top (+z)
faces[4]=new int[]{2,5,1}; // right (+x)
faces[5]=new int[]{5,2,6}; // right (+x)
faces[6]=new int[]{4,3,0}; // left (-x)
faces[7]=new int[]{3,4,7}; // left (-x)
faces[8]=new int[]{4,1,5}; // front (-y)
faces[9]=new int[]{1,4,0}; // front (-y)
faces[10]=new int[]{3,6,2}; // back (+y)
faces[11]=new int[]{6,3,7}; // back (+y)
PolyhedronsSet polyset = new PolyhedronsSet(Arrays.asList(verts), Arrays.asList(faces), precision);
// act
String dump = EuclideanTestUtils.dump(polyset);
PolyhedronsSet parsed = EuclideanTestUtils.parsePolyhedronsSet(dump, precision);
// assert
Assert.assertEquals(8.0, polyset.getSize(), TEST_EPS);
Assert.assertEquals(24.0, polyset.getBoundarySize(), TEST_EPS);
Assert.assertEquals(8.0, parsed.getSize(), TEST_EPS);
Assert.assertEquals(24.0, parsed.getBoundarySize(), TEST_EPS);
Assert.assertTrue(new RegionFactory<Vector3D>().difference(polyset, parsed).isEmpty());
}
@Test
public void testCreateFromBRep_connectedFacets() throws IOException, ParseException {
InputStream stream = getClass().getResourceAsStream("pentomino-N.ply");
PLYParser parser = new PLYParser(stream);
stream.close();
PolyhedronsSet polyhedron = new PolyhedronsSet(parser.getVertices(), parser.getFaces(), TEST_PRECISION);
Assert.assertEquals( 5.0, polyhedron.getSize(), TEST_EPS);
Assert.assertEquals(22.0, polyhedron.getBoundarySize(), TEST_EPS);
}
// GEOMETRY-59
@Test
public void testCreateFromBRep_slightlyConcavePrism() {
// arrange
Vector3D vertices[] = {
Vector3D.of( 0, 0, 0 ),
Vector3D.of( 2, 1e-7, 0 ),
Vector3D.of( 4, 0, 0 ),
Vector3D.of( 2, 2, 0 ),
Vector3D.of( 0, 0, 2 ),
Vector3D.of( 2, 1e-7, 2 ),
Vector3D.of( 4, 0, 2 ),
Vector3D.of( 2, 2, 2 )
};
int facets[][] = {
{ 4, 5, 6, 7 },
{ 3, 2, 1, 0 },
{ 0, 1, 5, 4 },
{ 1, 2, 6, 5 },
{ 2, 3, 7, 6 },
{ 3, 0, 4, 7 }
};
// act
PolyhedronsSet prism = new PolyhedronsSet(
Arrays.asList(vertices),
Arrays.asList(facets),
TEST_PRECISION);
// assert
Assert.assertTrue(Double.isFinite(prism.getSize()));
checkPoints(Region.Location.INSIDE, prism, Vector3D.of(2, 1, 1));
checkPoints(Region.Location.OUTSIDE, prism,
Vector3D.of(2, 1, 3), Vector3D.of(2, 1, -3),
Vector3D.of(2, -1, 1), Vector3D.of(2, 3, 1),
Vector3D.of(-1, 1, 1), Vector3D.of(4, 1, 1));
}
@Test
public void testCreateFromBRep_verticesTooClose() throws IOException, ParseException {
checkError("pentomino-N-too-close.ply", "Vertices are too close");
}
@Test
public void testCreateFromBRep_hole() throws IOException, ParseException {
checkError("pentomino-N-hole.ply", "connected to one facet only");
}
@Test
public void testCreateFromBRep_nonPlanar() throws IOException, ParseException {
checkError("pentomino-N-out-of-plane.ply", "do not define a plane");
}
@Test
public void testCreateFromBRep_badOrientation() throws IOException, ParseException {
checkError("pentomino-N-bad-orientation.ply", "Facet orientation mismatch");
}
@Test
public void testCreateFromBRep_wrongNumberOfPoints() throws IOException, ParseException {
checkError(Arrays.asList(Vector3D.ZERO, Vector3D.of(1, 0, 0), Vector3D.of(0, 1, 0), Vector3D.of(0, 0, 1)),
Arrays.asList(new int[] { 0, 1, 2 }, new int[] {2, 3}),
"");
}
private void checkError(final String resourceName, final String expected) {
try (InputStream stream = getClass().getResourceAsStream(resourceName)) {
PLYParser parser = new PLYParser(stream);
checkError(parser.getVertices(), parser.getFaces(), expected);
} catch (IOException ioe) {
Assert.fail(ioe.getLocalizedMessage());
} catch (ParseException pe) {
Assert.fail(pe.getLocalizedMessage());
}
}
private void checkError(final List<Vector3D> vertices, final List<int[]> facets,
final String expected) {
try {
new PolyhedronsSet(vertices, facets, TEST_PRECISION);
Assert.fail("an exception should have been thrown");
} catch (RuntimeException e) {
String actual = e.getMessage();
Assert.assertTrue("Expected string to contain \"" + expected + "\" but was \"" + actual + "\"",
actual.contains(expected));
}
}
@Test
public void testFirstIntersection() {
// arrange
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(Vector3D.ZERO, 2.0, TEST_EPS);
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
Line xPlus = new Line(Vector3D.ZERO, Vector3D.of(1, 0, 0), TEST_PRECISION);
Line xMinus = new Line(Vector3D.ZERO, Vector3D.of(-1, 0, 0), TEST_PRECISION);
Line yPlus = new Line(Vector3D.ZERO, Vector3D.of(0, 1, 0), TEST_PRECISION);
Line yMinus = new Line(Vector3D.ZERO, Vector3D.of(0, -1, 0), TEST_PRECISION);
Line zPlus = new Line(Vector3D.ZERO, Vector3D.of(0, 0, 1), TEST_PRECISION);
Line zMinus = new Line(Vector3D.ZERO, Vector3D.of(0, 0, -1), TEST_PRECISION);
// act/assert
assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Vector3D.of(-1.1, 0, 0), xPlus));
assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Vector3D.of(-1, 0, 0), xPlus));
assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Vector3D.of(-0.9, 0, 0), xPlus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(1.1, 0, 0), xPlus));
assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Vector3D.of(1.1, 0, 0), xMinus));
assertSubPlaneNormal(Vector3D.of(1, 0, 0), polySet.firstIntersection(Vector3D.of(1, 0, 0), xMinus));
assertSubPlaneNormal(Vector3D.of(-1, 0, 0), polySet.firstIntersection(Vector3D.of(0.9, 0, 0), xMinus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(-1.1, 0, 0), xMinus));
assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Vector3D.of(0, -1.1, 0), yPlus));
assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Vector3D.of(0, -1, 0), yPlus));
assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Vector3D.of(0, -0.9, 0), yPlus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(0, 1.1, 0), yPlus));
assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Vector3D.of(0, 1.1, 0), yMinus));
assertSubPlaneNormal(Vector3D.of(0, 1, 0), polySet.firstIntersection(Vector3D.of(0, 1, 0), yMinus));
assertSubPlaneNormal(Vector3D.of(0, -1, 0), polySet.firstIntersection(Vector3D.of(0, 0.9, 0), yMinus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(0, -1.1, 0), yMinus));
assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Vector3D.of(0, 0, -1.1), zPlus));
assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Vector3D.of(0, 0, -1), zPlus));
assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Vector3D.of(0, 0, -0.9), zPlus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(0, 0, 1.1), zPlus));
assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Vector3D.of(0, 0, 1.1), zMinus));
assertSubPlaneNormal(Vector3D.of(0, 0, 1), polySet.firstIntersection(Vector3D.of(0, 0, 1), zMinus));
assertSubPlaneNormal(Vector3D.of(0, 0, -1), polySet.firstIntersection(Vector3D.of(0, 0, 0.9), zMinus));
Assert.assertEquals(null, polySet.firstIntersection(Vector3D.of(0, 0, -1.1), zMinus));
}
// issue GEOMETRY-38
@Test
public void testFirstIntersection_linePassesThroughVertex() {
// arrange
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
Vector3D center = lowerCorner.lerp(upperCorner, 0.5);
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(center, 1.0, TEST_EPS);
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
Line upDiagonal = new Line(lowerCorner, upperCorner, TEST_PRECISION);
Line downDiagonal = upDiagonal.revert();
// act/assert
SubPlane upFromOutsideResult = (SubPlane) polySet.firstIntersection(Vector3D.of(-1, -1, -1), upDiagonal);
Assert.assertNotNull(upFromOutsideResult);
EuclideanTestUtils.assertCoordinatesEqual(lowerCorner,
((Plane) upFromOutsideResult.getHyperplane()).intersection(upDiagonal), TEST_EPS);
SubPlane upFromCenterResult = (SubPlane) polySet.firstIntersection(center, upDiagonal);
Assert.assertNotNull(upFromCenterResult);
EuclideanTestUtils.assertCoordinatesEqual(upperCorner,
((Plane) upFromCenterResult.getHyperplane()).intersection(upDiagonal), TEST_EPS);
SubPlane downFromOutsideResult = (SubPlane) polySet.firstIntersection(Vector3D.of(2, 2, 2), downDiagonal);
Assert.assertNotNull(downFromOutsideResult);
EuclideanTestUtils.assertCoordinatesEqual(upperCorner,
((Plane) downFromOutsideResult.getHyperplane()).intersection(downDiagonal), TEST_EPS);
SubPlane downFromCenterResult = (SubPlane) polySet.firstIntersection(center, downDiagonal);
Assert.assertNotNull(downFromCenterResult);
EuclideanTestUtils.assertCoordinatesEqual(lowerCorner,
((Plane) downFromCenterResult.getHyperplane()).intersection(downDiagonal), TEST_EPS);
}
// Issue GEOMETRY-43
@Test
public void testFirstIntersection_lineParallelToFace() {
// arrange - setup box
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
Vector3D center = lowerCorner.lerp(upperCorner, 0.5);
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(center, 1.0, TEST_EPS);
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
Vector3D firstPointOnLine = Vector3D.of(0.5, -1.0, 0);
Vector3D secondPointOnLine = Vector3D.of(0.5, 2.0, 0);
Line bottomLine = new Line(firstPointOnLine, secondPointOnLine, TEST_PRECISION);
Vector3D expectedIntersection1 = Vector3D.of(0.5, 0, 0.0);
Vector3D expectedIntersection2 = Vector3D.of(0.5, 1.0, 0.0);
// act/assert
SubPlane bottom = (SubPlane) polySet.firstIntersection(firstPointOnLine, bottomLine);
Assert.assertNotNull(bottom);
EuclideanTestUtils.assertCoordinatesEqual(expectedIntersection1,
((Plane) bottom.getHyperplane()).intersection(bottomLine), TEST_EPS);
bottom = (SubPlane) polySet.firstIntersection(Vector3D.of(0.5, 0.1, 0.0), bottomLine);
Assert.assertNotNull(bottom);
Vector3D intersection = ((Plane) bottom.getHyperplane()).intersection(bottomLine);
Assert.assertNotNull(intersection);
EuclideanTestUtils.assertCoordinatesEqual(expectedIntersection2, intersection, TEST_EPS);
}
@Test
public void testFirstIntersection_rayPointOnFace() {
// arrange
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
Vector3D center = lowerCorner.lerp(upperCorner, 0.5);
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(center, 1.0, TEST_EPS);
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
Vector3D pt = Vector3D.of(0.5, 0.5, 0);
Line intoBoxLine = new Line(pt, pt.add(Vector3D.Unit.PLUS_Z), TEST_PRECISION);
Line outOfBoxLine = new Line(pt, pt.add(Vector3D.Unit.MINUS_Z), TEST_PRECISION);
// act/assert
SubPlane intoBoxResult = (SubPlane) polySet.firstIntersection(pt, intoBoxLine);
Vector3D intoBoxPt = ((Plane) intoBoxResult.getHyperplane()).intersection(intoBoxLine);
EuclideanTestUtils.assertCoordinatesEqual(pt, intoBoxPt, TEST_EPS);
SubPlane outOfBoxResult = (SubPlane) polySet.firstIntersection(pt, outOfBoxLine);
Vector3D outOfBoxPt = ((Plane) outOfBoxResult.getHyperplane()).intersection(outOfBoxLine);
EuclideanTestUtils.assertCoordinatesEqual(pt, outOfBoxPt, TEST_EPS);
}
@Test
public void testFirstIntersection_rayPointOnVertex() {
// arrange
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
Vector3D center = lowerCorner.lerp(upperCorner, 0.5);
List<SubHyperplane<Vector3D>> boundaries = createBoxBoundaries(center, 1.0, TEST_EPS);
PolyhedronsSet polySet = new PolyhedronsSet(boundaries, TEST_PRECISION);
Line intoBoxLine = new Line(lowerCorner, upperCorner, TEST_PRECISION);
Line outOfBoxLine = intoBoxLine.revert();
// act/assert
SubPlane intoBoxResult = (SubPlane) polySet.firstIntersection(lowerCorner, intoBoxLine);
Vector3D intoBoxPt = ((Plane) intoBoxResult.getHyperplane()).intersection(intoBoxLine);
EuclideanTestUtils.assertCoordinatesEqual(lowerCorner, intoBoxPt, TEST_EPS);
SubPlane outOfBoxResult = (SubPlane) polySet.firstIntersection(lowerCorner, outOfBoxLine);
Vector3D outOfBoxPt = ((Plane) outOfBoxResult.getHyperplane()).intersection(outOfBoxLine);
EuclideanTestUtils.assertCoordinatesEqual(lowerCorner, outOfBoxPt, TEST_EPS);
}
// Issue 1211
// See https://issues.apache.org/jira/browse/MATH-1211
@Test
public void testFirstIntersection_onlyReturnsPointsInDirectionOfRay() throws IOException, ParseException {
// arrange
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(1e-8);
PolyhedronsSet polyset = EuclideanTestUtils.parsePolyhedronsSet(loadTestData("issue-1211.bsp"), precision);
UniformRandomProvider random = RandomSource.create(RandomSource.WELL_1024_A, 0xb97c9d1ade21e40al);
// act/assert
int nrays = 1000;
for (int i = 0; i < nrays; i++) {
Vector3D origin = Vector3D.ZERO;
Vector3D direction = Vector3D.of(2 * random.nextDouble() - 1,
2 * random.nextDouble() - 1,
2 * random.nextDouble() - 1).normalize();
Line line = new Line(origin, origin.add(direction), polyset.getPrecision());
SubHyperplane<Vector3D> plane = polyset.firstIntersection(origin, line);
if (plane != null) {
Vector3D intersectionPoint = ((Plane)plane.getHyperplane()).intersection(line);
double dotProduct = direction.dot(intersectionPoint.subtract(origin));
Assert.assertTrue(dotProduct > 0);
}
}
}
@Test
public void testBoolean_union() throws IOException {
// arrange
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_PRECISION);
PolyhedronsSet sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().union(box, sphere);
// OBJWriter.write("union.obj", result);
// assert
Assert.assertEquals(cubeVolume(size) + (sphereVolume(radius) * 0.5),
result.getSize(), tolerance);
Assert.assertEquals(cubeSurface(size) - circleSurface(radius) + (0.5 * sphereSurface(radius)),
result.getBoundarySize(), tolerance);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, 0.5, 0.5),
Vector3D.of(1.1, 0.5, 0.5),
Vector3D.of(0.5, -0.1, 0.5),
Vector3D.of(0.5, 1.1, 0.5),
Vector3D.of(0.5, 0.5, -0.1),
Vector3D.of(0.5, 0.5, 1.6));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.5, 0.5),
Vector3D.of(0.9, 0.5, 0.5),
Vector3D.of(0.5, 0.1, 0.5),
Vector3D.of(0.5, 0.9, 0.5),
Vector3D.of(0.5, 0.5, 0.1),
Vector3D.of(0.5, 0.5, 1.4));
}
@Test
public void testUnion_self() {
// arrange
double tolerance = 0.2;
double radius = 1.0;
PolyhedronsSet sphere = createSphere(Vector3D.ZERO, radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().union(sphere, sphere.copySelf());
// assert
Assert.assertEquals(sphereVolume(radius), result.getSize(), tolerance);
Assert.assertEquals(sphereSurface(radius), result.getBoundarySize(), tolerance);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, result.getBarycenter(), TEST_EPS);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-1.1, 0, 0),
Vector3D.of(1.1, 0, 0),
Vector3D.of(0, -1.1, 0),
Vector3D.of(0, 1.1, 0),
Vector3D.of(0, 0, -1.1),
Vector3D.of(0, 0, 1.1));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(-0.9, 0, 0),
Vector3D.of(0.9, 0, 0),
Vector3D.of(0, -0.9, 0),
Vector3D.of(0, 0.9, 0),
Vector3D.of(0, 0, -0.9),
Vector3D.of(0, 0, 0.9),
Vector3D.ZERO);
}
@Test
public void testBoolean_intersection() throws IOException {
// arrange
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_PRECISION);
PolyhedronsSet sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().intersection(box, sphere);
// OBJWriter.write("intersection.obj", result);
// assert
Assert.assertEquals((sphereVolume(radius) * 0.5), result.getSize(), tolerance);
Assert.assertEquals(circleSurface(radius) + (0.5 * sphereSurface(radius)),
result.getBoundarySize(), tolerance);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, 0.5, 1.0),
Vector3D.of(1.1, 0.5, 1.0),
Vector3D.of(0.5, -0.1, 1.0),
Vector3D.of(0.5, 1.1, 1.0),
Vector3D.of(0.5, 0.5, 0.4),
Vector3D.of(0.5, 0.5, 1.1));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.5, 0.9),
Vector3D.of(0.9, 0.5, 0.9),
Vector3D.of(0.5, 0.1, 0.9),
Vector3D.of(0.5, 0.9, 0.9),
Vector3D.of(0.5, 0.5, 0.6),
Vector3D.of(0.5, 0.5, 0.9));
}
@Test
public void testIntersection_self() {
// arrange
double tolerance = 0.2;
double radius = 1.0;
PolyhedronsSet sphere = createSphere(Vector3D.ZERO, radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().intersection(sphere, sphere.copySelf());
// assert
Assert.assertEquals(sphereVolume(radius), result.getSize(), tolerance);
Assert.assertEquals(sphereSurface(radius), result.getBoundarySize(), tolerance);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, result.getBarycenter(), TEST_EPS);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-1.1, 0, 0),
Vector3D.of(1.1, 0, 0),
Vector3D.of(0, -1.1, 0),
Vector3D.of(0, 1.1, 0),
Vector3D.of(0, 0, -1.1),
Vector3D.of(0, 0, 1.1));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(-0.9, 0, 0),
Vector3D.of(0.9, 0, 0),
Vector3D.of(0, -0.9, 0),
Vector3D.of(0, 0.9, 0),
Vector3D.of(0, 0, -0.9),
Vector3D.of(0, 0, 0.9),
Vector3D.ZERO);
}
@Test
public void testBoolean_xor_twoCubes() throws IOException {
// arrange
double size = 1.0;
PolyhedronsSet box1 = new PolyhedronsSet(
0, size,
0, size,
0, size, TEST_PRECISION);
PolyhedronsSet box2 = new PolyhedronsSet(
0.5, size + 0.5,
0.5, size + 0.5,
0.5, size + 0.5, TEST_PRECISION);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().xor(box1, box2);
// OBJWriter.write("xor_twoCubes.obj", result);
Assert.assertEquals((2 * cubeVolume(size)) - (2 * cubeVolume(size * 0.5)), result.getSize(), TEST_EPS);
// assert
Assert.assertEquals(2 * cubeSurface(size), result.getBoundarySize(), TEST_EPS);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, -0.1, -0.1),
Vector3D.of(0.75, 0.75, 0.75),
Vector3D.of(1.6, 1.6, 1.6));
checkPoints(Region.Location.BOUNDARY, result,
Vector3D.of(0, 0, 0),
Vector3D.of(0.5, 0.5, 0.5),
Vector3D.of(1, 1, 1),
Vector3D.of(1.5, 1.5, 1.5));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.1, 0.1),
Vector3D.of(0.4, 0.4, 0.4),
Vector3D.of(1.1, 1.1, 1.1),
Vector3D.of(1.4, 1.4, 1.4));
}
@Test
public void testBoolean_xor_cubeAndSphere() throws IOException {
// arrange
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_PRECISION);
PolyhedronsSet sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().xor(box, sphere);
// OBJWriter.write("xor_cubeAndSphere.obj", result);
Assert.assertEquals(cubeVolume(size), result.getSize(), tolerance);
// assert
Assert.assertEquals(cubeSurface(size) + (sphereSurface(radius)),
result.getBoundarySize(), tolerance);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, 0.5, 0.5),
Vector3D.of(1.1, 0.5, 0.5),
Vector3D.of(0.5, -0.1, 0.5),
Vector3D.of(0.5, 1.1, 0.5),
Vector3D.of(0.5, 0.5, -0.1),
Vector3D.of(0.5, 0.5, 1.6),
Vector3D.of(0.5, 0.5, 0.9));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.5, 0.5),
Vector3D.of(0.9, 0.5, 0.5),
Vector3D.of(0.5, 0.1, 0.5),
Vector3D.of(0.5, 0.9, 0.5),
Vector3D.of(0.5, 0.5, 0.1),
Vector3D.of(0.5, 0.5, 1.4));
}
@Test
public void testXor_self() {
// arrange
double radius = 1.0;
PolyhedronsSet sphere = createSphere(Vector3D.ZERO, radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().xor(sphere, sphere.copySelf());
// assert
Assert.assertEquals(0.0, result.getSize(), TEST_EPS);
Assert.assertEquals(0.0, result.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, result.getBarycenter(), TEST_EPS);
Assert.assertTrue(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-1.1, 0, 0),
Vector3D.of(1.1, 0, 0),
Vector3D.of(0, -1.1, 0),
Vector3D.of(0, 1.1, 0),
Vector3D.of(0, 0, -1.1),
Vector3D.of(0, 0, 1.1),
Vector3D.of(-0.9, 0, 0),
Vector3D.of(0.9, 0, 0),
Vector3D.of(0, -0.9, 0),
Vector3D.of(0, 0.9, 0),
Vector3D.of(0, 0, -0.9),
Vector3D.of(0, 0, 0.9),
Vector3D.ZERO);
}
@Test
public void testBoolean_difference() throws IOException {
// arrange
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_PRECISION);
PolyhedronsSet sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().difference(box, sphere);
// OBJWriter.write("difference.obj", result);
// assert
Assert.assertEquals(cubeVolume(size) - (sphereVolume(radius) * 0.5), result.getSize(), tolerance);
Assert.assertEquals(cubeSurface(size) - circleSurface(radius) + (0.5 * sphereSurface(radius)),
result.getBoundarySize(), tolerance);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, 0.5, 1.0),
Vector3D.of(1.1, 0.5, 1.0),
Vector3D.of(0.5, -0.1, 1.0),
Vector3D.of(0.5, 1.1, 1.0),
Vector3D.of(0.5, 0.5, -0.1),
Vector3D.of(0.5, 0.5, 0.6));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.5, 0.4),
Vector3D.of(0.9, 0.5, 0.4),
Vector3D.of(0.5, 0.1, 0.4),
Vector3D.of(0.5, 0.9, 0.4),
Vector3D.of(0.5, 0.5, 0.1),
Vector3D.of(0.5, 0.5, 0.4));
}
@Test
public void testDifference_self() {
// arrange
double radius = 1.0;
PolyhedronsSet sphere = createSphere(Vector3D.ZERO, radius, 8, 16);
// act
PolyhedronsSet result = (PolyhedronsSet) new RegionFactory<Vector3D>().difference(sphere, sphere.copySelf());
// assert
Assert.assertEquals(0.0, result.getSize(), TEST_EPS);
Assert.assertEquals(0.0, result.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.NaN, result.getBarycenter(), TEST_EPS);
Assert.assertTrue(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-1.1, 0, 0),
Vector3D.of(1.1, 0, 0),
Vector3D.of(0, -1.1, 0),
Vector3D.of(0, 1.1, 0),
Vector3D.of(0, 0, -1.1),
Vector3D.of(0, 0, 1.1),
Vector3D.of(-0.9, 0, 0),
Vector3D.of(0.9, 0, 0),
Vector3D.of(0, -0.9, 0),
Vector3D.of(0, 0.9, 0),
Vector3D.of(0, 0, -0.9),
Vector3D.of(0, 0, 0.9),
Vector3D.ZERO);
}
@Test
public void testBoolean_multiple() throws IOException {
// arrange
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
PolyhedronsSet box = new PolyhedronsSet(0, size, 0, size, 0, size, TEST_PRECISION);
PolyhedronsSet sphereToAdd = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
PolyhedronsSet sphereToRemove1 = createSphere(Vector3D.of(size * 0.5, 0, size * 0.5), radius, 8, 16);
PolyhedronsSet sphereToRemove2 = createSphere(Vector3D.of(size * 0.5, 1, size * 0.5), radius, 8, 16);
RegionFactory<Vector3D> factory = new RegionFactory<Vector3D>();
// act
PolyhedronsSet result = (PolyhedronsSet) factory.union(box, sphereToAdd);
result = (PolyhedronsSet) factory.difference(result, sphereToRemove1);
result = (PolyhedronsSet) factory.difference(result, sphereToRemove2);
// OBJWriter.write("multiple.obj", result);
// assert
Assert.assertEquals(cubeVolume(size) - (sphereVolume(radius) * 0.5),
result.getSize(), tolerance);
Assert.assertEquals(cubeSurface(size) - (3.0 * circleSurface(radius)) + (1.5 * sphereSurface(radius)),
result.getBoundarySize(), tolerance);
Assert.assertFalse(result.isEmpty());
Assert.assertFalse(result.isFull());
checkPoints(Region.Location.OUTSIDE, result,
Vector3D.of(-0.1, 0.5, 0.5),
Vector3D.of(1.1, 0.5, 0.5),
Vector3D.of(0.5, 0.4, 0.5),
Vector3D.of(0.5, 0.6, 0.5),
Vector3D.of(0.5, 0.5, -0.1),
Vector3D.of(0.5, 0.5, 1.6));
checkPoints(Region.Location.INSIDE, result,
Vector3D.of(0.1, 0.5, 0.1),
Vector3D.of(0.9, 0.5, 0.1),
Vector3D.of(0.5, 0.4, 0.1),
Vector3D.of(0.5, 0.6, 0.1),
Vector3D.of(0.5, 0.5, 0.1),
Vector3D.of(0.5, 0.5, 1.4));
}
@Test
public void testProjectToBoundary() {
// arrange
PolyhedronsSet polySet = new PolyhedronsSet(0, 1, 0, 1, 0, 1, TEST_PRECISION);
// act/assert
checkProjectToBoundary(polySet, Vector3D.of(0.4, 0.5, 0.5),
Vector3D.of(0, 0.5, 0.5), -0.4);
checkProjectToBoundary(polySet, Vector3D.of(1.5, 0.5, 0.5),
Vector3D.of(1, 0.5, 0.5), 0.5);
checkProjectToBoundary(polySet, Vector3D.of(2, 2, 2),
Vector3D.of(1, 1, 1), Math.sqrt(3));
}
@Test
public void testProjectToBoundary_invertedRegion() {
// arrange
PolyhedronsSet polySet = new PolyhedronsSet(0, 1, 0, 1, 0, 1, TEST_PRECISION);
polySet = (PolyhedronsSet) new RegionFactory<Vector3D>().getComplement(polySet);
// act/assert
checkProjectToBoundary(polySet, Vector3D.of(0.4, 0.5, 0.5),
Vector3D.of(0, 0.5, 0.5), 0.4);
checkProjectToBoundary(polySet, Vector3D.of(1.5, 0.5, 0.5),
Vector3D.of(1, 0.5, 0.5), -0.5);
checkProjectToBoundary(polySet, Vector3D.of(2, 2, 2),
Vector3D.of(1, 1, 1), -Math.sqrt(3));
}
private void checkProjectToBoundary(PolyhedronsSet poly, Vector3D toProject,
Vector3D expectedPoint, double expectedOffset) {
BoundaryProjection<Vector3D> proj = poly.projectToBoundary(toProject);
EuclideanTestUtils.assertCoordinatesEqual(toProject, proj.getOriginal(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(expectedPoint, proj.getProjected(), TEST_EPS);
Assert.assertEquals(expectedOffset, proj.getOffset(), TEST_EPS);
}
private String loadTestData(final String resourceName)
throws IOException {
try (Reader reader = new InputStreamReader(getClass().getResourceAsStream(resourceName), "UTF-8")) {
StringBuilder builder = new StringBuilder();
for (int c = reader.read(); c >= 0; c = reader.read()) {
builder.append((char) c);
}
return builder.toString();
}
}
private void checkPoints(Region.Location expected, PolyhedronsSet poly, Vector3D ... points) {
for (int i = 0; i < points.length; ++i) {
Assert.assertEquals("Incorrect location for " + points[i], expected, poly.checkPoint(points[i]));
}
}
private List<SubHyperplane<Vector3D>> createBoxBoundaries(Vector3D center, double size, double eps) {
List<SubHyperplane<Vector3D>> boundaries = new ArrayList<>();
double offset = size * 0.5;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
Plane xMinus = Plane.fromPointAndNormal(center.add(Vector3D.of(-offset, 0, 0)), Vector3D.Unit.MINUS_X, precision);
Plane xPlus = Plane.fromPointAndNormal(center.add(Vector3D.of(offset, 0, 0)), Vector3D.Unit.PLUS_X, precision);
Plane yPlus = Plane.fromPointAndNormal(center.add(Vector3D.of(0, offset, 0)), Vector3D.Unit.PLUS_Y, precision);
Plane yMinus = Plane.fromPointAndNormal(center.add(Vector3D.of(0, -offset, 0)), Vector3D.Unit.MINUS_Y, precision);
Plane zPlus = Plane.fromPointAndNormal(center.add(Vector3D.of(0, 0, offset)), Vector3D.Unit.PLUS_Z, precision);
Plane zMinus = Plane.fromPointAndNormal(center.add(Vector3D.of(0, 0, -offset)), Vector3D.Unit.MINUS_Z, precision);
// +x
boundaries.add(createSubPlane(xPlus,
center.add(Vector3D.of(offset, offset, offset)),
center.add(Vector3D.of(offset, -offset, offset)),
center.add(Vector3D.of(offset, -offset, -offset)),
center.add(Vector3D.of(offset, offset, -offset))));
// -x
boundaries.add(createSubPlane(xMinus,
center.add(Vector3D.of(-offset, -offset, offset)),
center.add(Vector3D.of(-offset, offset, offset)),
center.add(Vector3D.of(-offset, offset, -offset)),
center.add(Vector3D.of(-offset, -offset, -offset))));
// +y
boundaries.add(createSubPlane(yPlus,
center.add(Vector3D.of(-offset, offset, offset)),
center.add(Vector3D.of(offset, offset, offset)),
center.add(Vector3D.of(offset, offset, -offset)),
center.add(Vector3D.of(-offset, offset, -offset))));
// -y
boundaries.add(createSubPlane(yMinus,
center.add(Vector3D.of(-offset, -offset, offset)),
center.add(Vector3D.of(-offset, -offset, -offset)),
center.add(Vector3D.of(offset, -offset, -offset)),
center.add(Vector3D.of(offset, -offset, offset))));
// +z
boundaries.add(createSubPlane(zPlus,
center.add(Vector3D.of(-offset, -offset, offset)),
center.add(Vector3D.of(offset, -offset, offset)),
center.add(Vector3D.of(offset, offset, offset)),
center.add(Vector3D.of(-offset, offset, offset))));
// -z
boundaries.add(createSubPlane(zMinus,
center.add(Vector3D.of(-offset, -offset, -offset)),
center.add(Vector3D.of(-offset, offset, -offset)),
center.add(Vector3D.of(offset, offset, -offset)),
center.add(Vector3D.of(offset, -offset, -offset))));
return boundaries;
}
private SubPlane createSubPlane(Plane plane, Vector3D...points) {
Vector2D[] points2d = new Vector2D[points.length];
for (int i=0; i<points.length; ++i) {
points2d[i] = plane.toSubSpace(points[i]);
}
PolygonsSet polygon = new PolygonsSet(plane.getPrecision(), points2d);
return new SubPlane(plane, polygon);
}
private PolyhedronsSet createSphere(Vector3D center, double radius, int stacks, int slices) {
List<Plane> planes = new ArrayList<>();
// add top and bottom planes (+/- z)
Vector3D topZ = Vector3D.of(center.getX(), center.getY(), center.getZ() + radius);
Vector3D bottomZ = Vector3D.of(center.getX(), center.getY(), center.getZ() - radius);
planes.add(Plane.fromPointAndNormal(topZ, Vector3D.Unit.PLUS_Z, TEST_PRECISION));
planes.add(Plane.fromPointAndNormal(bottomZ, Vector3D.Unit.MINUS_Z, TEST_PRECISION));
// add the side planes
double vDelta = Math.PI / stacks;
double hDelta = Math.PI * 2 / slices;
double adjustedRadius = (radius + (radius * Math.cos(vDelta * 0.5))) / 2.0;
double vAngle;
double hAngle;
double stackRadius;
double stackHeight;
double x, y;
Vector3D pt;
Vector3D norm;
vAngle = -0.5 * vDelta;
for (int v=0; v<stacks; ++v) {
vAngle += vDelta;
stackRadius = Math.sin(vAngle) * adjustedRadius;
stackHeight = Math.cos(vAngle) * adjustedRadius;
hAngle = -0.5 * hDelta;
for (int h=0; h<slices; ++h) {
hAngle += hDelta;
x = Math.cos(hAngle) * stackRadius;
y = Math.sin(hAngle) * stackRadius;
norm = Vector3D.of(x, y, stackHeight).normalize();
pt = center.add(norm.multiply(adjustedRadius));
planes.add(Plane.fromPointAndNormal(pt, norm, TEST_PRECISION));
}
}
return (PolyhedronsSet) new RegionFactory<Vector3D>().buildConvex(planes.toArray(new Plane[0]));
}
private void assertSubPlaneNormal(Vector3D expectedNormal, SubHyperplane<Vector3D> sub) {
Vector3D norm = ((Plane) sub.getHyperplane()).getNormal();
EuclideanTestUtils.assertCoordinatesEqual(expectedNormal, norm, TEST_EPS);
}
private double cubeVolume(double size) {
return size * size * size;
}
private double cubeSurface(double size) {
return 6.0 * size * size;
}
private double sphereVolume(double radius) {
return 4.0 * Math.PI * radius * radius * radius / 3.0;
}
private double sphereSurface(double radius) {
return 4.0 * Math.PI * radius * radius;
}
private double circleSurface(double radius) {
return Math.PI * radius * radius;
}
}