Fixed split/side inconsistencies in BSP trees.

JIRA: MATH-1266
This commit is contained in:
Luc Maisonobe 2015-09-06 19:47:27 +02:00
parent 5fccd09a81
commit 50c5eae1a6
22 changed files with 243 additions and 224 deletions

View File

@ -51,6 +51,9 @@ If the output is not quite correct, check for invisible trailing spaces!
</properties>
<body>
<release version="3.6" date="XXXX-XX-XX" description="">
<action dev="luc" type="fix" issue="MATH-1266">
Fixed split/side inconsistencies in BSP trees.
</action>
<action dev="erans" type="add" issue="MATH-1265">
"NeuronSquareMesh2D" (package "o.a.c.m.ml.neuralnet.twod") implements "Iterable".
</action>

View File

@ -182,6 +182,7 @@ public enum LocalizedFormats implements Localizable {
NUMBER_OF_INTERPOLATION_POINTS("number of interpolation points ({0})"), /* keep */
NUMBER_OF_TRIALS("number of trials ({0})"),
NOT_CONVEX("vertices do not form a convex hull in CCW winding"),
NOT_CONVEX_HYPERPLANES("hyperplanes do not define a convex region"),
ROBUSTNESS_ITERATIONS("number of robustness iterations ({0})"),
START_POSITION("start position ({0})"), /* keep */
NON_CONVERGENT_CONTINUED_FRACTION("Continued fraction convergents failed to converge (in less than {0} iterations) for value {1}"),

View File

@ -19,7 +19,6 @@ package org.apache.commons.math3.geometry.euclidean.oned;
import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
import org.apache.commons.math3.geometry.partitioning.Hyperplane;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.Side;
/** This class represents sub-hyperplane for {@link OrientedPoint}.
* <p>An hyperplane in 1D is a simple point, its orientation being a
@ -57,20 +56,17 @@ public class SubOrientedPoint extends AbstractSubHyperplane<Euclidean1D, Euclide
return new SubOrientedPoint(hyperplane, remainingRegion);
}
/** {@inheritDoc} */
@Override
public Side side(final Hyperplane<Euclidean1D> hyperplane) {
final double global = hyperplane.getOffset(((OrientedPoint) getHyperplane()).getLocation());
return (global < -1.0e-10) ? Side.MINUS : ((global > 1.0e-10) ? Side.PLUS : Side.HYPER);
}
/** {@inheritDoc} */
@Override
public SplitSubHyperplane<Euclidean1D> split(final Hyperplane<Euclidean1D> hyperplane) {
final double global = hyperplane.getOffset(((OrientedPoint) getHyperplane()).getLocation());
return (global < -1.0e-10) ?
new SplitSubHyperplane<Euclidean1D>(null, this) :
new SplitSubHyperplane<Euclidean1D>(this, null);
if (global < -1.0e-10) {
return new SplitSubHyperplane<Euclidean1D>(null, this);
} else if (global > 1.0e-10) {
return new SplitSubHyperplane<Euclidean1D>(this, null);
} else {
return new SplitSubHyperplane<Euclidean1D>(null, null);
}
}
}

View File

@ -20,13 +20,12 @@ import org.apache.commons.math3.geometry.Point;
import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
import org.apache.commons.math3.geometry.partitioning.BSPTree;
import org.apache.commons.math3.geometry.partitioning.Hyperplane;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.Side;
import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
/** This class represents a sub-hyperplane for {@link Plane}.
@ -50,45 +49,6 @@ public class SubPlane extends AbstractSubHyperplane<Euclidean3D, Euclidean2D> {
return new SubPlane(hyperplane, remainingRegion);
}
/** {@inheritDoc} */
@Override
public Side side(Hyperplane<Euclidean3D> hyperplane) {
final Plane otherPlane = (Plane) hyperplane;
final Plane thisPlane = (Plane) getHyperplane();
final Line inter = otherPlane.intersection(thisPlane);
final double tolerance = thisPlane.getTolerance();
if (inter == null) {
// the hyperplanes are parallel,
// any point can be used to check their relative position
final double global = otherPlane.getOffset(thisPlane);
return (global < -1.0e-10) ? Side.MINUS : ((global > 1.0e-10) ? Side.PLUS : Side.HYPER);
}
// create a 2D line in the otherPlane canonical 2D frame such that:
// - the line is the crossing line of the two planes in 3D
// - the line splits the otherPlane in two half planes with an
// orientation consistent with the orientation of the instance
// (i.e. the 3D half space on the plus side (resp. minus side)
// of the instance contains the 2D half plane on the plus side
// (resp. minus side) of the 2D line
Vector2D p = thisPlane.toSubSpace((Point<Euclidean3D>) inter.toSpace((Point<Euclidean1D>) Vector1D.ZERO));
Vector2D q = thisPlane.toSubSpace((Point<Euclidean3D>) inter.toSpace((Point<Euclidean1D>) Vector1D.ONE));
Vector3D crossP = Vector3D.crossProduct(inter.getDirection(), thisPlane.getNormal());
if (crossP.dotProduct(otherPlane.getNormal()) < 0) {
final Vector2D tmp = p;
p = q;
q = tmp;
}
final org.apache.commons.math3.geometry.euclidean.twod.Line line2D =
new org.apache.commons.math3.geometry.euclidean.twod.Line(p, q, tolerance);
// check the side on the 2D plane
return getRemainingRegion().side(line2D);
}
/** Split the instance in two parts by an hyperplane.
* @param hyperplane splitting hyperplane
* @return an object containing both the part of the instance
@ -106,9 +66,13 @@ public class SubPlane extends AbstractSubHyperplane<Euclidean3D, Euclidean2D> {
if (inter == null) {
// the hyperplanes are parallel
final double global = otherPlane.getOffset(thisPlane);
return (global < -1.0e-10) ?
new SplitSubHyperplane<Euclidean3D>(null, this) :
new SplitSubHyperplane<Euclidean3D>(this, null);
if (global < -tolerance) {
return new SplitSubHyperplane<Euclidean3D>(null, this);
} else if (global > tolerance) {
return new SplitSubHyperplane<Euclidean3D>(this, null);
} else {
return new SplitSubHyperplane<Euclidean3D>(null, null);
}
}
// the hyperplanes do intersect

View File

@ -30,7 +30,6 @@ import org.apache.commons.math3.geometry.partitioning.BSPTree;
import org.apache.commons.math3.geometry.partitioning.Hyperplane;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.Region.Location;
import org.apache.commons.math3.geometry.partitioning.Side;
import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
import org.apache.commons.math3.util.FastMath;
@ -169,27 +168,6 @@ public class SubLine extends AbstractSubHyperplane<Euclidean2D, Euclidean1D> {
return new SubLine(hyperplane, remainingRegion);
}
/** {@inheritDoc} */
@Override
public Side side(final Hyperplane<Euclidean2D> hyperplane) {
final Line thisLine = (Line) getHyperplane();
final Line otherLine = (Line) hyperplane;
final Vector2D crossing = thisLine.intersection(otherLine);
if (crossing == null) {
// the lines are parallel,
final double global = otherLine.getOffset(thisLine);
return (global < -1.0e-10) ? Side.MINUS : ((global > 1.0e-10) ? Side.PLUS : Side.HYPER);
}
// the lines do intersect
final boolean direct = FastMath.sin(thisLine.getAngle() - otherLine.getAngle()) < 0;
final Vector1D x = thisLine.toSubSpace((Point<Euclidean2D>) crossing);
return getRemainingRegion().side(new OrientedPoint(x, direct, thisLine.getTolerance()));
}
/** {@inheritDoc} */
@Override
public SplitSubHyperplane<Euclidean2D> split(final Hyperplane<Euclidean2D> hyperplane) {
@ -202,9 +180,13 @@ public class SubLine extends AbstractSubHyperplane<Euclidean2D, Euclidean1D> {
if (crossing == null) {
// the lines are parallel
final double global = otherLine.getOffset(thisLine);
return (global < -1.0e-10) ?
new SplitSubHyperplane<Euclidean2D>(null, this) :
new SplitSubHyperplane<Euclidean2D>(this, null);
if (global < -tolerance) {
return new SplitSubHyperplane<Euclidean2D>(null, this);
} else if (global > tolerance) {
return new SplitSubHyperplane<Euclidean2D>(this, null);
} else {
return new SplitSubHyperplane<Euclidean2D>(null, null);
}
}
// the lines do intersect
@ -224,7 +206,6 @@ public class SubLine extends AbstractSubHyperplane<Euclidean2D, Euclidean1D> {
new BSPTree<Euclidean1D>(Boolean.FALSE) :
new BSPTree<Euclidean1D>(subMinus, new BSPTree<Euclidean1D>(Boolean.FALSE),
splitTree.getMinus(), null);
return new SplitSubHyperplane<Euclidean2D>(new SubLine(thisLine.copySelf(), new IntervalsSet(plusTree, tolerance)),
new SubLine(thisLine.copySelf(), new IntervalsSet(minusTree, tolerance)));

View File

@ -216,7 +216,8 @@ public abstract class AbstractRegion<S extends Space, T extends Space> implement
final ArrayList<SubHyperplane<S>> minusList = new ArrayList<SubHyperplane<S>>();
while (iterator.hasNext()) {
final SubHyperplane<S> other = iterator.next();
switch (other.side(inserted)) {
final SubHyperplane.SplitSubHyperplane<S> split = other.split(inserted);
switch (split.getSide()) {
case PLUS:
plusList.add(other);
break;
@ -224,7 +225,6 @@ public abstract class AbstractRegion<S extends Space, T extends Space> implement
minusList.add(other);
break;
case BOTH:
final SubHyperplane.SplitSubHyperplane<S> split = other.split(inserted);
plusList.add(split.getPlus());
minusList.add(split.getMinus());
break;
@ -408,6 +408,7 @@ public abstract class AbstractRegion<S extends Space, T extends Space> implement
protected abstract void computeGeometricalProperties();
/** {@inheritDoc} */
@Deprecated
public Side side(final Hyperplane<S> hyperplane) {
final InsideFinder<S> finder = new InsideFinder<S>(this);
finder.recurseSides(tree, hyperplane.wholeHyperplane());
@ -434,23 +435,28 @@ public abstract class AbstractRegion<S extends Space, T extends Space> implement
}
final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
case PLUS :
return recurseIntersection(node.getPlus(), sub);
case MINUS :
return recurseIntersection(node.getMinus(), sub);
case BOTH :
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
final SubHyperplane<S> plus = recurseIntersection(node.getPlus(), split.getPlus());
final SubHyperplane<S> minus = recurseIntersection(node.getMinus(), split.getMinus());
if (plus == null) {
return minus;
} else if (minus == null) {
return plus;
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
if (split.getPlus() != null) {
if (split.getMinus() != null) {
// both sides
final SubHyperplane<S> plus = recurseIntersection(node.getPlus(), split.getPlus());
final SubHyperplane<S> minus = recurseIntersection(node.getMinus(), split.getMinus());
if (plus == null) {
return minus;
} else if (minus == null) {
return plus;
} else {
return plus.reunite(minus);
}
} else {
return plus.reunite(minus);
// only on plus side
return recurseIntersection(node.getPlus(), sub);
}
default :
} else if (split.getMinus() != null) {
// only on minus side
return recurseIntersection(node.getMinus(), sub);
} else {
// on hyperplane
return recurseIntersection(node.getPlus(),
recurseIntersection(node.getMinus(), sub));
}

View File

@ -175,7 +175,10 @@ public abstract class AbstractSubHyperplane<S extends Space, T extends Space>
}
/** {@inheritDoc} */
public abstract Side side(Hyperplane<S> hyper);
@Deprecated
public Side side(Hyperplane<S> hyper) {
return split(hyper).getSide();
}
/** {@inheritDoc} */
public abstract SplitSubHyperplane<S> split(Hyperplane<S> hyper);

View File

@ -574,11 +574,12 @@ public class BSPTree<S extends Space> {
final Hyperplane<S> cHyperplane = cut.getHyperplane();
final Hyperplane<S> sHyperplane = sub.getHyperplane();
switch (sub.side(cHyperplane)) {
final SubHyperplane.SplitSubHyperplane<S> subParts = sub.split(cHyperplane);
switch (subParts.getSide()) {
case PLUS :
{ // the partitioning sub-hyperplane is entirely in the plus sub-tree
final BSPTree<S> split = plus.split(sub);
if (cut.side(sHyperplane) == Side.PLUS) {
if (cut.split(sHyperplane).getSide() == Side.PLUS) {
split.plus =
new BSPTree<S>(cut.copySelf(), split.plus, minus.copySelf(), attribute);
split.plus.condense();
@ -594,7 +595,7 @@ public class BSPTree<S extends Space> {
case MINUS :
{ // the partitioning sub-hyperplane is entirely in the minus sub-tree
final BSPTree<S> split = minus.split(sub);
if (cut.side(sHyperplane) == Side.PLUS) {
if (cut.split(sHyperplane).getSide() == Side.PLUS) {
split.plus =
new BSPTree<S>(cut.copySelf(), plus.copySelf(), split.plus, attribute);
split.plus.condense();
@ -610,7 +611,6 @@ public class BSPTree<S extends Space> {
case BOTH :
{
final SubHyperplane.SplitSubHyperplane<S> cutParts = cut.split(sHyperplane);
final SubHyperplane.SplitSubHyperplane<S> subParts = sub.split(cHyperplane);
final BSPTree<S> split =
new BSPTree<S>(sub, plus.split(subParts.getPlus()), minus.split(subParts.getMinus()),
null);

View File

@ -86,7 +86,8 @@ class Characterization<S extends Space> {
}
} else {
final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
switch (split.getSide()) {
case PLUS:
characterize(node.getPlus(), sub, splitters);
break;
@ -94,7 +95,6 @@ class Characterization<S extends Space> {
characterize(node.getMinus(), sub, splitters);
break;
case BOTH:
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
splitters.add(node);
characterize(node.getPlus(), split.getPlus(), splitters);
characterize(node.getMinus(), split.getMinus(), splitters);

View File

@ -69,10 +69,11 @@ class InsideFinder<S extends Space> {
}
final Hyperplane<S> hyperplane = node.getCut().getHyperplane();
switch (sub.side(hyperplane)) {
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
switch (split.getSide()) {
case PLUS :
// the sub-hyperplane is entirely in the plus sub-tree
if (node.getCut().side(sub.getHyperplane()) == Side.PLUS) {
if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
if (!region.isEmpty(node.getMinus())) {
plusFound = true;
}
@ -87,7 +88,7 @@ class InsideFinder<S extends Space> {
break;
case MINUS :
// the sub-hyperplane is entirely in the minus sub-tree
if (node.getCut().side(sub.getHyperplane()) == Side.PLUS) {
if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
if (!region.isEmpty(node.getPlus())) {
plusFound = true;
}
@ -102,7 +103,6 @@ class InsideFinder<S extends Space> {
break;
case BOTH :
// the sub-hyperplane extends in both sub-trees
final SubHyperplane.SplitSubHyperplane<S> split = sub.split(hyperplane);
// explore first the plus sub-tree
recurseSides(node.getPlus(), split.getPlus());

View File

@ -204,7 +204,10 @@ public interface Region<S extends Space> {
* Side.MINUS}, {@link Side#BOTH Side.BOTH} or {@link Side#HYPER
* Side.HYPER} (the latter result can occur only if the tree
* contains only one cut hyperplane)
* @deprecated as of 3.6, this method which was only intended for
* internal use is not used anymore
*/
@Deprecated
Side side(final Hyperplane<S> hyperplane);
/** Get the parts of a sub-hyperplane that are contained in the region.

View File

@ -19,10 +19,13 @@ package org.apache.commons.math3.geometry.partitioning;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
import org.apache.commons.math3.exception.util.LocalizedFormats;
import org.apache.commons.math3.geometry.Point;
import org.apache.commons.math3.geometry.Space;
import org.apache.commons.math3.geometry.partitioning.BSPTree.VanishingCutHandler;
import org.apache.commons.math3.geometry.partitioning.Region.Location;
import org.apache.commons.math3.geometry.partitioning.SubHyperplane.SplitSubHyperplane;
/** This class is a factory for {@link Region}.
@ -45,7 +48,7 @@ public class RegionFactory<S extends Space> {
* @param hyperplanes collection of bounding hyperplanes
* @return a new convex region, or null if the collection is empty
*/
public Region<S> buildConvex(final Hyperplane<S> ... hyperplanes) {
public Region<S> buildConvex(@SuppressWarnings("unchecked") final Hyperplane<S> ... hyperplanes) {
if ((hyperplanes == null) || (hyperplanes.length == 0)) {
return null;
}
@ -62,6 +65,32 @@ public class RegionFactory<S extends Space> {
node.getPlus().setAttribute(Boolean.FALSE);
node = node.getMinus();
node.setAttribute(Boolean.TRUE);
} else {
// the hyperplane could not be inserted in the current leaf node
// either it is completely outside (which means the input hyperplanes
// are wrong), or it is parallel to a previous hyperplane
SubHyperplane<S> s = hyperplane.wholeHyperplane();
for (BSPTree<S> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
final Hyperplane<S> other = tree.getParent().getCut().getHyperplane();
final SplitSubHyperplane<S> split = s.split(other);
switch (split.getSide()) {
case HYPER :
// the hyperplane is parallel to a previous hyperplane
if (!hyperplane.sameOrientationAs(other)) {
// this hyperplane is opposite to the other one,
// the region is thinner than the tolerance, we consider it empty
return getComplement(hyperplanes[0].wholeSpace());
}
// the hyperplane is an extension of an already known hyperplane, we just ignore it
break;
case PLUS :
// the hyperplane is outside of the current convex zone,
// the input hyperplanes are inconsistent
throw new MathIllegalArgumentException(LocalizedFormats.NOT_CONVEX_HYPERPLANES);
default :
s = split.getMinus();
}
}
}
}

View File

@ -71,7 +71,9 @@ public interface SubHyperplane<S extends Space> {
* @param hyperplane hyperplane to check instance against
* @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH},
* {@link Side#HYPER}
* @deprecated as of 3.6, replaced with {@link #split(Hyperplane)}.{@link SplitSubHyperplane#getSide()}
*/
@Deprecated
Side side(Hyperplane<S> hyperplane);
/** Split the instance in two parts by an hyperplane.
@ -126,6 +128,28 @@ public interface SubHyperplane<S extends Space> {
return minus;
}
/** Get the side of the split sub-hyperplane with respect to its splitter.
* @return {@link Side#PLUS} if only {@link #getPlus()} is neither null nor empty,
* {@link Side#MINUS} if only {@link #getMinus()} is neither null nor empty,
* {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
* are neither null nor empty or {@link Side#HYPER} if both {@link #getPlus()} and
* {@link #getMinus()} are either null or empty
* @since 3.6
*/
public Side getSide() {
if (plus != null && !plus.isEmpty()) {
if (minus != null && !minus.isEmpty()) {
return Side.BOTH;
} else {
return Side.PLUS;
}
} else if (minus != null && !minus.isEmpty()) {
return Side.MINUS;
} else {
return Side.HYPER;
}
}
}
}

View File

@ -705,40 +705,11 @@ public class ArcsSet extends AbstractRegion<Sphere1D, Sphere1D> implements Itera
* @param arc arc to check instance against
* @return one of {@link Side#PLUS}, {@link Side#MINUS}, {@link Side#BOTH}
* or {@link Side#HYPER}
* @deprecated as of 3.6, replaced with {@link #split(Arc)}.{@link Split#getSide()}
*/
@Deprecated
public Side side(final Arc arc) {
final double reference = FastMath.PI + arc.getInf();
final double arcLength = arc.getSup() - arc.getInf();
boolean inMinus = false;
boolean inPlus = false;
for (final double[] a : this) {
final double syncedStart = MathUtils.normalizeAngle(a[0], reference) - arc.getInf();
final double arcOffset = a[0] - syncedStart;
final double syncedEnd = a[1] - arcOffset;
if (syncedStart <= arcLength - getTolerance() || syncedEnd >= MathUtils.TWO_PI + getTolerance()) {
inMinus = true;
}
if (syncedEnd >= arcLength + getTolerance()) {
inPlus = true;
}
}
if (inMinus) {
if (inPlus) {
return Side.BOTH;
} else {
return Side.MINUS;
}
} else {
if (inPlus) {
return Side.PLUS;
} else {
return Side.HYPER;
}
}
return split(arc).getSide();
}
/** Split the instance in two parts by an arc.
@ -937,6 +908,28 @@ public class ArcsSet extends AbstractRegion<Sphere1D, Sphere1D> implements Itera
return minus;
}
/** Get the side of the split arc with respect to its splitter.
* @return {@link Side#PLUS} if only {@link #getPlus()} returns non-null,
* {@link Side#MINUS} if only {@link #getMinus()} returns non-null,
* {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
* return non-null or {@link Side#HYPER} if both {@link #getPlus()} and
* {@link #getMinus()} return null
* @since 3.6
*/
public Side getSide() {
if (plus != null) {
if (minus != null) {
return Side.BOTH;
} else {
return Side.PLUS;
}
} else if (minus != null) {
return Side.MINUS;
} else {
return Side.HYPER;
}
}
}
/** Specialized exception for inconsistent BSP tree state inconsistency.

View File

@ -19,7 +19,6 @@ package org.apache.commons.math3.geometry.spherical.oned;
import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
import org.apache.commons.math3.geometry.partitioning.Hyperplane;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.Side;
/** This class represents sub-hyperplane for {@link LimitAngle}.
* <p>Instances of this class are guaranteed to be immutable.</p>
@ -55,13 +54,6 @@ public class SubLimitAngle extends AbstractSubHyperplane<Sphere1D, Sphere1D> {
return new SubLimitAngle(hyperplane, remainingRegion);
}
/** {@inheritDoc} */
@Override
public Side side(final Hyperplane<Sphere1D> hyperplane) {
final double global = hyperplane.getOffset(((LimitAngle) getHyperplane()).getLocation());
return (global < -1.0e-10) ? Side.MINUS : ((global > 1.0e-10) ? Side.PLUS : Side.HYPER);
}
/** {@inheritDoc} */
@Override
public SplitSubHyperplane<Sphere1D> split(final Hyperplane<Sphere1D> hyperplane) {

View File

@ -20,7 +20,6 @@ import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane;
import org.apache.commons.math3.geometry.partitioning.Hyperplane;
import org.apache.commons.math3.geometry.partitioning.Region;
import org.apache.commons.math3.geometry.partitioning.Side;
import org.apache.commons.math3.geometry.spherical.oned.Arc;
import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
@ -47,24 +46,6 @@ public class SubCircle extends AbstractSubHyperplane<Sphere2D, Sphere1D> {
return new SubCircle(hyperplane, remainingRegion);
}
/** {@inheritDoc} */
@Override
public Side side(final Hyperplane<Sphere2D> hyperplane) {
final Circle thisCircle = (Circle) getHyperplane();
final Circle otherCircle = (Circle) hyperplane;
final double angle = Vector3D.angle(thisCircle.getPole(), otherCircle.getPole());
if (angle < thisCircle.getTolerance() || angle > FastMath.PI - thisCircle.getTolerance()) {
// the two circles are aligned or opposite
return Side.HYPER;
} else {
// the two circles intersect each other
return ((ArcsSet) getRemainingRegion()).side(thisCircle.getInsideArc(otherCircle));
}
}
/** {@inheritDoc} */
@Override
public SplitSubHyperplane<Sphere2D> split(final Hyperplane<Sphere2D> hyperplane) {
@ -73,12 +54,9 @@ public class SubCircle extends AbstractSubHyperplane<Sphere2D, Sphere1D> {
final Circle otherCircle = (Circle) hyperplane;
final double angle = Vector3D.angle(thisCircle.getPole(), otherCircle.getPole());
if (angle < thisCircle.getTolerance()) {
// the two circles are aligned
return new SplitSubHyperplane<Sphere2D>(null, this);
} else if (angle > FastMath.PI - thisCircle.getTolerance()) {
// the two circles are opposite
return new SplitSubHyperplane<Sphere2D>(this, null);
if (angle < thisCircle.getTolerance() || angle > FastMath.PI - thisCircle.getTolerance()) {
// the two circles are aligned or opposite
return new SplitSubHyperplane<Sphere2D>(null, null);
} else {
// the two circles intersect each other
final Arc arc = thisCircle.getInsideArc(otherCircle);

View File

@ -168,6 +168,7 @@ NORMALIZE_INFINITE = impossible de normaliser vers une valeur infinie
NORMALIZE_NAN = impossible de normaliser vers NaN
NOT_ADDITION_COMPATIBLE_MATRICES = les dimensions {0}x{1} et {2}x{3} sont incompatibles pour l''addition matricielle
NOT_CONVEX = les points ne constituent pas une enveloppe convexe
NOT_CONVEX_HYPERPLANES = les hyperplans ne d\u00e9finissent pas une r\u00e9gion convexe
NOT_DECREASING_NUMBER_OF_POINTS = les points {0} et {1} ne sont pas d\u00e9croissants ({2} < {3})
NOT_DECREASING_SEQUENCE = les points {3} et {2} ne sont pas d\u00e9croissants ({1} < {0})
NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS = pas assez de donn\u00e9es ({0} lignes) pour {1} pr\u00e9dicteurs

View File

@ -29,7 +29,7 @@ public class LocalizedFormatsTest {
@Test
public void testMessageNumber() {
Assert.assertEquals(326, LocalizedFormats.values().length);
Assert.assertEquals(327, LocalizedFormats.values().length);
}
@Test

View File

@ -19,6 +19,7 @@ package org.apache.commons.math3.geometry.euclidean.twod;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.exception.MathIllegalArgumentException;
import org.apache.commons.math3.geometry.euclidean.oned.Interval;
import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
@ -1148,12 +1149,19 @@ public class PolygonsSetTest {
new Line(pD, pA, 1.0 / 16)
};
Region<Euclidean2D> degeneratedPolygon = factory.buildConvex(h2);
Assert.assertEquals(1.0 / 64.0, degeneratedPolygon.getSize(), 1.0e-10);
Assert.assertTrue(Double.isInfinite(new RegionFactory<Euclidean2D>().getComplement(degeneratedPolygon).getSize()));
Assert.assertEquals(2 * (1.0 + 1.0 / 64.0), degeneratedPolygon.getBoundarySize(), 1.0e-10);
Assert.assertEquals(0.0, degeneratedPolygon.getSize(), 1.0e-10);
Assert.assertTrue(degeneratedPolygon.isEmpty());
}
@SuppressWarnings("unchecked")
@Test(expected=MathIllegalArgumentException.class)
public void testInconsistentHyperplanes() {
double tolerance = 1.0e-10;
new RegionFactory<Euclidean2D>().buildConvex(new Line(new Vector2D(0, 0), new Vector2D(0, 1), tolerance),
new Line(new Vector2D(1, 1), new Vector2D(1, 0), tolerance));
}
@Test
public void testBoundarySimplification() {

View File

@ -383,27 +383,27 @@ public class ArcsSetTest {
ArcsSet set = (ArcsSet) new RegionFactory<Sphere1D>().difference(new ArcsSet(1.0, 6.0, 1.0e-10),
new ArcsSet(3.0, 5.0, 1.0e-10));
for (int k = -2; k < 3; ++k) {
Assert.assertEquals(Side.MINUS, set.side(new Arc(0.5 + k * MathUtils.TWO_PI,
6.1 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.PLUS, set.side(new Arc(0.5 + k * MathUtils.TWO_PI,
0.8 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.PLUS, set.side(new Arc(6.2 + k * MathUtils.TWO_PI,
6.3 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.PLUS, set.side(new Arc(3.5 + k * MathUtils.TWO_PI,
4.5 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.BOTH, set.side(new Arc(2.9 + k * MathUtils.TWO_PI,
4.5 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.BOTH, set.side(new Arc(0.5 + k * MathUtils.TWO_PI,
1.2 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.BOTH, set.side(new Arc(0.5 + k * MathUtils.TWO_PI,
5.9 + k * MathUtils.TWO_PI,
set.getTolerance())));
Assert.assertEquals(Side.MINUS, set.split(new Arc(0.5 + k * MathUtils.TWO_PI,
6.1 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.PLUS, set.split(new Arc(0.5 + k * MathUtils.TWO_PI,
0.8 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.PLUS, set.split(new Arc(6.2 + k * MathUtils.TWO_PI,
6.3 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.PLUS, set.split(new Arc(3.5 + k * MathUtils.TWO_PI,
4.5 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.BOTH, set.split(new Arc(2.9 + k * MathUtils.TWO_PI,
4.5 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.BOTH, set.split(new Arc(0.5 + k * MathUtils.TWO_PI,
1.2 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
Assert.assertEquals(Side.BOTH, set.split(new Arc(0.5 + k * MathUtils.TWO_PI,
5.9 + k * MathUtils.TWO_PI,
set.getTolerance())).getSide());
}
}
@ -413,10 +413,10 @@ public class ArcsSetTest {
ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10);
ArcsSet s16 = new ArcsSet(1.0, 6.0, 1.0e-10);
Assert.assertEquals(Side.BOTH, s16.side(new Arc(3.0, 5.0, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s16.side(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)));
Assert.assertEquals(Side.MINUS, s35.side(new Arc(1.0, 6.0, 1.0e-10)));
Assert.assertEquals(Side.PLUS, s35.side(new Arc(6.0, 1.0 + MathUtils.TWO_PI, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s16.split(new Arc(3.0, 5.0, 1.0e-10)).getSide());
Assert.assertEquals(Side.BOTH, s16.split(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)).getSide());
Assert.assertEquals(Side.MINUS, s35.split(new Arc(1.0, 6.0, 1.0e-10)).getSide());
Assert.assertEquals(Side.PLUS, s35.split(new Arc(6.0, 1.0 + MathUtils.TWO_PI, 1.0e-10)).getSide());
}
@ -425,17 +425,17 @@ public class ArcsSetTest {
ArcsSet s35 = new ArcsSet(3.0, 5.0, 1.0e-10);
ArcsSet s46 = new ArcsSet(4.0, 6.0, 1.0e-10);
Assert.assertEquals(Side.BOTH, s46.side(new Arc(3.0, 5.0, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s46.side(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s35.side(new Arc(4.0, 6.0, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s35.side(new Arc(6.0, 4.0 + MathUtils.TWO_PI, 1.0e-10)));
Assert.assertEquals(Side.BOTH, s46.split(new Arc(3.0, 5.0, 1.0e-10)).getSide());
Assert.assertEquals(Side.BOTH, s46.split(new Arc(5.0, 3.0 + MathUtils.TWO_PI, 1.0e-10)).getSide());
Assert.assertEquals(Side.BOTH, s35.split(new Arc(4.0, 6.0, 1.0e-10)).getSide());
Assert.assertEquals(Side.BOTH, s35.split(new Arc(6.0, 4.0 + MathUtils.TWO_PI, 1.0e-10)).getSide());
}
@Test
public void testSideHyper() {
ArcsSet sub = (ArcsSet) new RegionFactory<Sphere1D>().getComplement(new ArcsSet(1.0e-10));
Assert.assertTrue(sub.isEmpty());
Assert.assertEquals(Side.HYPER, sub.side(new Arc(2.0, 3.0, 1.0e-10)));
Assert.assertEquals(Side.HYPER, sub.split(new Arc(2.0, 3.0, 1.0e-10)).getSide());
}
@Test
@ -577,4 +577,16 @@ public class ArcsSetTest {
Assert.assertNull(split.getMinus());
}
@Test
public void testSideSplitConsistency() {
double epsilon = 1.0e-6;
double a = 4.725;
ArcsSet set = new ArcsSet(a, a + 0.5, epsilon);
Arc arc = new Arc(a + 0.5 * epsilon, a + 1, epsilon);
ArcsSet.Split split = set.split(arc);
Assert.assertNotNull(split.getMinus());
Assert.assertNull(split.getPlus());
Assert.assertEquals(Side.MINUS, set.split(arc).getSide());
}
}

View File

@ -221,17 +221,23 @@ public class SphericalPolygonsSetTest {
boolean zVFound = false;
Vertex first = loops.get(0);
int count = 0;
double sumPoleX = 0;
double sumPoleY = 0;
double sumPoleZ = 0;
for (Vertex v = first; count == 0 || v != first; v = v.getOutgoing().getEnd()) {
++count;
Edge e = v.getIncoming();
Assert.assertTrue(v == e.getStart().getOutgoing().getEnd());
xPFound = xPFound || e.getCircle().getPole().distance(Vector3D.MINUS_I) < 1.0e-10;
yPFound = yPFound || e.getCircle().getPole().distance(Vector3D.MINUS_J) < 1.0e-10;
zPFound = zPFound || e.getCircle().getPole().distance(Vector3D.PLUS_K) < 1.0e-10;
if (Vector3D.PLUS_K.distance(e.getCircle().getPole()) < 1.0e-10) {
Assert.assertEquals(1.5 * FastMath.PI, e.getLength(), 1.0e-10);
if (e.getCircle().getPole().distance(Vector3D.MINUS_I) < 1.0e-10) {
xPFound = true;
sumPoleX += e.getLength();
} else if (e.getCircle().getPole().distance(Vector3D.MINUS_J) < 1.0e-10) {
yPFound = true;
sumPoleY += e.getLength();
} else {
Assert.assertEquals(0.5 * FastMath.PI, e.getLength(), 1.0e-10);
Assert.assertEquals(0.0, e.getCircle().getPole().distance(Vector3D.PLUS_K), 1.0e-10);
zPFound = true;
sumPoleZ += e.getLength();
}
xVFound = xVFound || v.getLocation().getVector().distance(Vector3D.PLUS_I) < 1.0e-10;
yVFound = yVFound || v.getLocation().getVector().distance(Vector3D.PLUS_J) < 1.0e-10;
@ -243,7 +249,9 @@ public class SphericalPolygonsSetTest {
Assert.assertTrue(xVFound);
Assert.assertTrue(yVFound);
Assert.assertTrue(zVFound);
Assert.assertEquals(3, count);
Assert.assertEquals(0.5 * FastMath.PI, sumPoleX, 1.0e-10);
Assert.assertEquals(0.5 * FastMath.PI, sumPoleY, 1.0e-10);
Assert.assertEquals(1.5 * FastMath.PI, sumPoleZ, 1.0e-10);
Assert.assertEquals(1.5 * FastMath.PI, threeOctants.getSize(), 1.0e-10);

View File

@ -44,19 +44,19 @@ public class SubCircleTest {
Circle xzPlane = new Circle(Vector3D.PLUS_J, 1.0e-10);
SubCircle sc1 = create(Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_J, 1.0e-10, 1.0, 3.0, 5.0, 6.0);
Assert.assertEquals(Side.BOTH, sc1.side(xzPlane));
Assert.assertEquals(Side.BOTH, sc1.split(xzPlane).getSide());
SubCircle sc2 = create(Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_J, 1.0e-10, 1.0, 3.0);
Assert.assertEquals(Side.MINUS, sc2.side(xzPlane));
Assert.assertEquals(Side.MINUS, sc2.split(xzPlane).getSide());
SubCircle sc3 = create(Vector3D.PLUS_K, Vector3D.PLUS_I, Vector3D.PLUS_J, 1.0e-10, 5.0, 6.0);
Assert.assertEquals(Side.PLUS, sc3.side(xzPlane));
Assert.assertEquals(Side.PLUS, sc3.split(xzPlane).getSide());
SubCircle sc4 = create(Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_I, 1.0e-10, 5.0, 6.0);
Assert.assertEquals(Side.HYPER, sc4.side(xzPlane));
Assert.assertEquals(Side.HYPER, sc4.split(xzPlane).getSide());
SubCircle sc5 = create(Vector3D.MINUS_J, Vector3D.PLUS_I, Vector3D.PLUS_K, 1.0e-10, 5.0, 6.0);
Assert.assertEquals(Side.HYPER, sc5.side(xzPlane));
Assert.assertEquals(Side.HYPER, sc5.split(xzPlane).getSide());
}
@ -94,17 +94,34 @@ public class SubCircleTest {
SubCircle sc4 = create(Vector3D.PLUS_J, Vector3D.PLUS_K, Vector3D.PLUS_I, 1.0e-10, 5.0, 6.0);
SplitSubHyperplane<Sphere2D> split4 = sc4.split(xzPlane);
Assert.assertEquals(Side.HYPER, sc4.side(xzPlane));
Assert.assertEquals(Side.HYPER, sc4.split(xzPlane).getSide());
Assert.assertNull(split4.getPlus());
Assert.assertTrue(split4.getMinus() == sc4);
Assert.assertNull(split4.getMinus());
SubCircle sc5 = create(Vector3D.MINUS_J, Vector3D.PLUS_I, Vector3D.PLUS_K, 1.0e-10, 5.0, 6.0);
SplitSubHyperplane<Sphere2D> split5 = sc5.split(xzPlane);
Assert.assertTrue(split5.getPlus() == sc5);
Assert.assertEquals(Side.HYPER, sc5.split(xzPlane).getSide());
Assert.assertNull(split5.getPlus());
Assert.assertNull(split5.getMinus());
}
@Test
public void testSideSplitConsistency() {
double tolerance = 1.0e-6;
Circle hyperplane = new Circle(new Vector3D(9.738804529764676E-5, -0.6772824575010357, -0.7357230887208355),
tolerance);
SubCircle sub = new SubCircle(new Circle(new Vector3D(2.1793884139073498E-4, 0.9790647032675541, -0.20354915700704285),
tolerance),
new ArcsSet(4.7121441684170700, 4.7125386635004760, tolerance));
SplitSubHyperplane<Sphere2D> split = sub.split(hyperplane);
Assert.assertNotNull(split.getMinus());
Assert.assertNull(split.getPlus());
Assert.assertEquals(Side.MINUS, sub.split(hyperplane).getSide());
}
private SubCircle create(Vector3D pole, Vector3D x, Vector3D y,
double tolerance, double ... limits) {
RegionFactory<Sphere1D> factory = new RegionFactory<Sphere1D>();