/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed 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.arakhne.afc.math.geometry.d3.ai; import java.util.Iterator; import java.util.NoSuchElementException; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.math.MathConstants; import org.arakhne.afc.math.MathUtil; import org.arakhne.afc.math.geometry.CrossingComputationType; import org.arakhne.afc.math.geometry.PathWindingRule; import org.arakhne.afc.math.geometry.d3.Point3D; import org.arakhne.afc.math.geometry.d3.Transform3D; import org.arakhne.afc.math.geometry.d3.Vector3D; import org.arakhne.afc.math.geometry.d3.ai.Segment3ai.BresenhamLineIterator; import org.arakhne.afc.vmutil.asserts.AssertMessages; /** Fonctional interface that represented a 2D rectangle on a plane. * * @param <ST> is the type of the general implementation. * @param <IT> is the type of the implementation of this shape. * @param <IE> is the type of the path elements. * @param <P> is the type of the points. * @param <V> is the type of the vectors. * @param <B> is the type of the bounding boxes. * @author $Author: sgalland$ * @author $Author: hjaffali$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ public interface RectangularPrism3ai< ST extends Shape3ai<?, ?, IE, P, V, B>, IT extends RectangularPrism3ai<?, ?, IE, P, V, B>, IE extends PathElement3ai, P extends Point3D<? super P, ? super V>, V extends Vector3D<? super V, ? super P>, B extends RectangularPrism3ai<?, ?, IE, P, V, B>> extends Prism3ai<ST, IT, IE, P, V, B> { /** Replies if two rectangles are intersecting. * * @param x1 is the first corner of the first rectangle. * @param y1 is the first corner of the first rectangle. * @param z1 is the first corner of the first rectangle. * @param x2 is the second corner of the first rectangle. * @param y2 is the second corner of the first rectangle. * @param z2 is the second corner of the first rectangle. * @param x3 is the first corner of the second rectangle. * @param y3 is the first corner of the second rectangle. * @param z3 is the first corner of the second rectangle. * @param x4 is the second corner of the second rectangle. * @param y4 is the second corner of the second rectangle. * @param z4 is the second corner of the second rectangle. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"}) static boolean intersectsRectangleRectangle(int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int x4, int y4, int z4) { assert x1 <= x2 : AssertMessages.lowerEqualParameters(0, x1, 3, x2); assert y1 <= y2 : AssertMessages.lowerEqualParameters(1, y1, 4, y2); assert z1 <= z2 : AssertMessages.lowerEqualParameters(2, y1, 5, z2); assert x3 <= x4 : AssertMessages.lowerEqualParameters(6, x3, 9, x4); assert y3 <= y4 : AssertMessages.lowerEqualParameters(7, y3, 10, y4); assert z3 <= z4 : AssertMessages.lowerEqualParameters(8, z3, 11, z4); return x2 > x3 && x1 < x4 && y2 > y3 && y1 < y4 && z2 > z3 && z1 < z4; } /** Replies if a rectangle is intersecting a segment. * *<p>The intersection test is partly based on the Cohen-Sutherland * classification of the segment. * This classification permits to detect the base cases; * and to run a clipping-like algorithm for the intersection * detection. * * @param x1 is the first corner of the rectangle. * @param y1 is the first corner of the rectangle. * @param z1 is the first corner of the rectangle. * @param x2 is the second corner of the rectangle. * @param y2 is the second corner of the rectangle. * @param z2 is the second corner of the rectangle. * @param x3 is the first point of the segment. * @param y3 is the first point of the segment. * @param z3 is the first point of the segment. * @param x4 is the second point of the segment. * @param y4 is the second point of the segment. * @param z4 is the second point of the segment. * @return <code>true</code> if the two shapes are intersecting; otherwise * <code>false</code> */ @Pure @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:parameternumber", "checkstyle:magicnumber"}) static boolean intersectsRectangleSegment(int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3, int x4, int y4, int z4) { assert x1 <= x2 : AssertMessages.lowerEqualParameters(0, x1, 3, x2); assert y1 <= y2 : AssertMessages.lowerEqualParameters(1, y1, 4, y2); assert z1 <= z2 : AssertMessages.lowerEqualParameters(2, y1, 5, z2); int c1 = MathUtil.getCohenSutherlandCode3D(x3, y3, z3, x1, y1, z1, x2, y2, z2); final int c2 = MathUtil.getCohenSutherlandCode3D(x4, y4, z4, x1, y1, z1, x2, y2, z2); // 0x32; //COHEN_SUTHERLAND_BACK // 0x16; //COHEN_SUTHERLAND_FRONT // 0x8; //COHEN_SUTHERLAND_LEFT // 0x4; //COHEN_SUTHERLAND_RIGHT // 0x2; //COHEN_SUTHERLAND_BOTTOM // 0x1; //COHEN_SUTHERLAND_TOP if (c1 == MathConstants.COHEN_SUTHERLAND_INSIDE || c2 == MathConstants.COHEN_SUTHERLAND_INSIDE) { return true; } if ((c1 & c2) != 0) { return false; } int sx1 = x3; int sy1 = y3; final int sz1 = z3; final int sx2 = x4; final int sy2 = y4; final int sz2 = z4; // Only for internal use final Point3D<?, ?> pts = new InnerComputationPoint3ai(); final BresenhamLineIterator<InnerComputationPoint3ai, InnerComputationVector3ai> iterator = new BresenhamLineIterator<>( InnerComputationGeomFactory.SINGLETON, sx1, sy1, sz1, sx2, sy2, sz2); while (iterator.hasNext() && c1 != MathConstants.COHEN_SUTHERLAND_INSIDE && c2 != MathConstants.COHEN_SUTHERLAND_INSIDE && (c1 & c2) == 0) { if ((c1 & MathConstants.COHEN_SUTHERLAND_TOP) != 0) { do { iterator.next(pts); sy1 = pts.iy(); } while (iterator.hasNext() && sy1 != y2); if (sy1 != y2) { return false; } sx1 = pts.ix(); } else if ((c1 & MathConstants.COHEN_SUTHERLAND_BOTTOM) != 0) { do { iterator.next(pts); sy1 = pts.iy(); } while (iterator.hasNext() && sy1 != y1); if (sy1 != y1) { return false; } sx1 = pts.ix(); } else if ((c1 & MathConstants.COHEN_SUTHERLAND_RIGHT) != 0) { do { iterator.next(pts); sx1 = pts.ix(); } while (iterator.hasNext() && sx1 != x2); if (sx1 != x2) { return false; } sy1 = pts.iy(); } else { do { iterator.next(pts); sx1 = pts.ix(); } while (iterator.hasNext() && sx1 != x1); if (sx1 != x1) { return false; } sy1 = pts.iy(); } c1 = MathUtil.getCohenSutherlandCode(sx1, sy1, x1, y1, x2, y2); } return c1 == MathConstants.COHEN_SUTHERLAND_INSIDE || c2 == MathConstants.COHEN_SUTHERLAND_INSIDE; } /** Compute the closest point on the rectangle from the given point. * * @param minx is the x-coordinate of the lowest coordinate of the rectangle. * @param miny is the y-coordinate of the lowest coordinate of the rectangle. * @param minz is the z-coordinate of the lowest coordinate of the rectangle. * @param maxx is the x-coordinate of the highest coordinate of the rectangle. * @param maxy is the y-coordinate of the highest coordinate of the rectangle. * @param maxz is the z-coordinate of the highest coordinate of the rectangle. * @param px is the x-coordinate of the point. * @param py is the y-coordinate of the point. * @param pz is the z-coordinate of the point. * @param result the closest point. */ @Pure @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"}) static void computeClosestPoint(int minx, int miny, int minz, int maxx, int maxy, int maxz, int px, int py, int pz, Point3D<?, ?> result) { assert minx <= maxx : AssertMessages.lowerEqualParameters(0, minx, 3, maxx); assert miny <= maxy : AssertMessages.lowerEqualParameters(1, miny, 4, maxy); assert minz <= maxz : AssertMessages.lowerEqualParameters(2, minz, 5, maxz); assert result != null : AssertMessages.notNullParameter(9); final int x; int same = 0; if (px < minx) { x = minx; } else if (px > maxx) { x = maxx; } else { x = px; ++same; } final int y; if (py < miny) { y = miny; } else if (py > maxy) { y = maxy; } else { y = py; ++same; } final int z; if (pz < minz) { z = minz; } else if (pz > maxz) { z = maxz; } else { z = pz; ++same; } if (same == 3) { result.set(px, py, pz); } else { result.set(x, y, z); } } /** Compute the farthest point on the rectangle from the given point. * * @param minx is the x-coordinate of the lowest coordinate of the rectangle. * @param miny is the y-coordinate of the lowest coordinate of the rectangle. * @param minz is the z-coordinate of the lowest coordinate of the rectangle. * @param maxx is the x-coordinate of the highest coordinate of the rectangle. * @param maxy is the y-coordinate of the highest coordinate of the rectangle. * @param maxz is the z-coordinate of the highest coordinate of the rectangle. * @param px is the x-coordinate of the point. * @param py is the y-coordinate of the point. * @param pz is the z-coordinate of the point. * @param result the farthest point. */ @Pure @SuppressWarnings({"checkstyle:parameternumber", "checkstyle:magicnumber"}) static void computeFarthestPoint(int minx, int miny, int minz, int maxx, int maxy, int maxz, int px, int py, int pz, Point3D<?, ?> result) { assert minx <= maxx : AssertMessages.lowerEqualParameters(0, minx, 3, maxx); assert miny <= maxy : AssertMessages.lowerEqualParameters(1, miny, 4, maxy); assert minz <= maxz : AssertMessages.lowerEqualParameters(2, minz, 5, maxz); assert result != null : AssertMessages.notNullParameter(9); final int x; if (px <= ((minx + maxx) / 2)) { x = maxx; } else { x = minx; } final int y; if (py <= ((miny + maxy) / 2)) { y = maxy; } else { y = miny; } final int z; if (pz <= ((minz + maxz) / 2)) { z = maxz; } else { z = minz; } result.set(x, y, z); } /** Update the given Cohen-Sutherland code that corresponds to the given segment in order * to obtain a segment restricted to a single Cohen-Sutherland zone. * This function is at the heart of the * <a href="http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm">Cohen-Sutherland algorithm</a>. * * <p>The result of this function may be: <ul> * <li>the code for a single zone, or</li> * <li>the code that corresponds to a single column, or </li> * <li>the code that corresponds to a single row.</li> * </ul> * * @param rx1 is the first corner of the rectangle. * @param ry1 is the first corner of the rectangle. * @param rz1 is the first corner of the rectangle. * @param rx2 is the second corner of the rectangle. * @param ry2 is the second corner of the rectangle. * @param rz2 is the second corner of the rectangle. * @param sx1 is the first point of the segment. * @param sy1 is the first point of the segment. * @param sz1 is the first point of the segment. * @param sx2 is the second point of the segment. * @param sy2 is the second point of the segment. * @param sz2 is the second point of the segment. * @param codePoint1 the Cohen-Sutherland code for the first point of the segment. * @param codePoint2 the Cohen-Sutherland code for the second point of the segment. * @param newSegmentP1 is set with the new coordinates of the segment first point. If <code>null</code>, * this parameter is ignored. * @param newSegmentP2 is set with the new coordinates of the segment second point. If <code>null</code>, * this parameter is ignored. * @return the rectricted Cohen-Sutherland zone. */ @Pure @SuppressWarnings({ "checkstyle:parameternumber", "checkstyle:npathcomplexity", "checkstyle:magicnumber", "checkstyle:cyclomaticcomplexity" }) static int reduceCohenSutherlandZoneRectangularPrismSegment(int rx1, int ry1, int rz1, int rx2, int ry2, int rz2, int sx1, int sy1, int sz1, int sx2, int sy2, int sz2, int codePoint1, int codePoint2, Point3D<?, ?> newSegmentP1, Point3D<?, ?> newSegmentP2) { assert rx1 <= rx2 : AssertMessages.lowerEqualParameters(0, rx1, 3, rx2); assert ry1 <= ry2 : AssertMessages.lowerEqualParameters(1, ry1, 4, ry2); assert rz1 <= rz2 : AssertMessages.lowerEqualParameters(2, ry1, 5, ry2); assert codePoint1 == MathUtil.getCohenSutherlandCode3D(sx1, sy1, sz1, rx1, ry1, rz1, rx2, ry2, rz2) : AssertMessages .invalidValue(8); assert codePoint2 == MathUtil.getCohenSutherlandCode3D(sx2, sy2, sz2, rx1, ry1, rz1, rx2, ry2, rz2) : AssertMessages .invalidValue(9); int segmentX1 = sx1; int segmentY1 = sy1; int segmentZ1 = sz1; int segmentX2 = sx2; int segmentY2 = sy2; int segmentZ2 = sz2; int code1 = codePoint1; int code2 = codePoint2; while (true) { if ((code1 | code2) == 0) { // Bitwise OR is 0. Trivially accept and get out of loop if (newSegmentP1 != null) { newSegmentP1.set(segmentX1, segmentY1, segmentZ1); } if (newSegmentP2 != null) { newSegmentP2.set(segmentX2, segmentY2, segmentZ2); } return 0; } if ((code1 & code2) != 0) { // Bitwise AND is not 0. Trivially reject and get out of loop if (newSegmentP1 != null) { newSegmentP1.set(segmentX1, segmentY1, segmentZ1); } if (newSegmentP2 != null) { newSegmentP2.set(segmentX2, segmentY2, segmentZ2); } return code1 & code2; } // failed both tests, so calculate the line segment intersection // At least one endpoint is outside the clip rectangle; pick it. int code3 = (code1 != 0) ? code1 : code2; int x = 0; int y = 0; int z = 0; // Now find the intersection point; // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0) if ((code3 & MathConstants.COHEN_SUTHERLAND_TOP) != 0) { // point is above the clip rectangle x = segmentX1 + (segmentX2 - segmentX1) * (ry2 - segmentY1) / (segmentY2 - segmentY1); y = ry2; z = rz2; } else if ((code3 & MathConstants.COHEN_SUTHERLAND_BOTTOM) != 0) { // point is below the clip rectangle x = segmentX1 + (segmentX2 - segmentX1) * (ry1 - segmentY1) / (segmentY2 - segmentY1); y = ry1; z = rz1; } else if ((code3 & MathConstants.COHEN_SUTHERLAND_RIGHT) != 0) { // point is to the right of clip rectangle y = segmentY1 + (segmentY2 - segmentY1) * (rx2 - segmentX1) / (segmentX2 - segmentX1); x = rx2; z = rz2; } else if ((code3 & MathConstants.COHEN_SUTHERLAND_LEFT) != 0) { // point is to the left of clip rectangle y = segmentY1 + (segmentY2 - segmentY1) * (rx1 - segmentX1) / (segmentX2 - segmentX1); x = rx1; z = rz1; } else if ((code3 & MathConstants.COHEN_SUTHERLAND_FRONT) != 0) { // point is to the front of clip rectangle z = segmentZ1 + (segmentZ2 - segmentZ1) * (rz2 - segmentZ1) / (segmentZ2 - segmentZ1); x = rx2; y = ry2; } else if ((code3 & MathConstants.COHEN_SUTHERLAND_BACK) != 0) { // point is to the back of clip rectangle z = segmentZ1 + (segmentZ2 - segmentZ1) * (rz1 - segmentZ1) / (segmentZ2 - segmentZ1); x = rx1; y = ry1; } else { code3 = 0; } if (code3 != 0) { // Now we move outside point to intersection point to clip // and get ready for next pass. if (code3 == code1) { segmentX1 = x; segmentY1 = y; segmentZ1 = z; code1 = MathUtil.getCohenSutherlandCode3D(segmentX1, segmentY1, segmentZ1, rx1, ry1, rz1, rx2, ry2, rz2); } else { segmentX2 = x; segmentY2 = y; segmentZ2 = z; code2 = MathUtil.getCohenSutherlandCode3D(segmentX2, segmentY2, segmentZ2, rx1, ry1, rz1, rx2, ry2, rz2); } } } } @Pure @Override default boolean equalsToShape(IT shape) { if (shape == null) { return false; } if (shape == this) { return true; } return getMinX() == shape.getMinX() && getMinY() == shape.getMinY() && getMinZ() == shape.getMinZ() && getMaxX() == shape.getMaxX() && getMaxY() == shape.getMaxY() && getMaxZ() == shape.getMaxZ(); } @Pure @Override default boolean intersects(RectangularPrism3ai<?, ?, ?, ?, ?, ?> rectangularPrism) { assert rectangularPrism != null : AssertMessages.notNullParameter(); return intersectsRectangleRectangle( getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ(), rectangularPrism.getMinX(), rectangularPrism.getMinY(), rectangularPrism.getMinZ(), rectangularPrism.getMaxX(), rectangularPrism.getMaxY(), rectangularPrism.getMaxZ()); } @Pure @Override default boolean intersects(Sphere3ai<?, ?, ?, ?, ?, ?> sphere) { assert sphere != null : AssertMessages.notNullParameter(); return Sphere3ai.intersectsSphereRectangularPrism( sphere.getX(), sphere.getY(), sphere.getZ(), sphere.getRadius(), getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ()); } @Pure @Override default boolean intersects(Segment3ai<?, ?, ?, ?, ?, ?> segment) { assert segment != null : AssertMessages.notNullParameter(); return intersectsRectangleSegment( getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ(), segment.getX1(), segment.getY1(), segment.getZ1(), segment.getX2(), segment.getY2(), segment.getZ2()); } @Pure @Override default boolean intersects(PathIterator3ai<?> iterator) { assert iterator != null : AssertMessages.notNullParameter(); final int mask = iterator.getWindingRule() == PathWindingRule.NON_ZERO ? -1 : 2; final int crossings = Path3ai.computeCrossingsFromRect( 0, iterator, getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ(), CrossingComputationType.SIMPLE_INTERSECTION_WHEN_NOT_POLYGON); return crossings == MathConstants.SHAPE_INTERSECTS || (crossings & mask) != 0; } @Pure @Override default boolean intersects(MultiShape3ai<?, ?, ?, ?, ?, ?, ?> multishape) { assert multishape != null : AssertMessages.notNullParameter(); return multishape.intersects(this); } @Pure @Override default boolean contains(int x, int y, int z) { return x >= getMinX() && x <= this.getMaxX() && y >= getMinY() && y <= getMaxY() && z >= getMinZ() && z <= getMaxZ(); } @Pure @Override default boolean contains(RectangularPrism3ai<?, ?, ?, ?, ?, ?> box) { assert box != null : AssertMessages.notNullParameter(); return box.getMinX() >= getMinX() && box.getMaxX() <= getMaxX() && box.getMinY() >= getMinY() && box.getMaxY() <= getMaxY() && box.getMinZ() >= getMinZ() && box.getMaxZ() <= getMaxZ(); } @Override default void set(IT rectangularPrism) { assert rectangularPrism != null : AssertMessages.notNullParameter(); setFromCorners(rectangularPrism.getMinX(), rectangularPrism.getMinY(), rectangularPrism.getMinZ(), rectangularPrism.getMaxX(), rectangularPrism.getMaxY(), rectangularPrism.getMaxZ()); } @Pure @Override default P getClosestPointTo(Point3D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P point = getGeomFactory().newPoint(); computeClosestPoint(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ(), pt.ix(), pt.iy(), pt.iz(), point); return point; } @Override default P getClosestPointTo(RectangularPrism3ai<?, ?, ?, ?, ?, ?> rectangle) { throw new UnsupportedOperationException(); } @Override default P getClosestPointTo(Sphere3ai<?, ?, ?, ?, ?, ?> circle) { throw new UnsupportedOperationException(); } @Override default P getClosestPointTo(Segment3ai<?, ?, ?, ?, ?, ?> segment) { throw new UnsupportedOperationException(); } @Override default P getClosestPointTo(MultiShape3ai<?, ?, ?, ?, ?, ?, ?> multishape) { throw new UnsupportedOperationException(); } @Override default P getClosestPointTo(Path3ai<?, ?, ?, ?, ?, ?> path) { throw new UnsupportedOperationException(); } @Pure @Override default P getFarthestPointTo(Point3D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final P point = getGeomFactory().newPoint(); computeFarthestPoint(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ(), pt.ix(), pt.iy(), pt.iz(), point); return point; } @Pure @Override default double getDistanceSquared(Point3D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final int dx; if (pt.ix() < getMinX()) { dx = getMinX() - pt.ix(); } else if (pt.ix() > getMaxX()) { dx = pt.ix() - getMaxX(); } else { dx = 0; } final int dy; if (pt.iy() < getMinY()) { dy = getMinY() - pt.iy(); } else if (pt.iy() > getMaxY()) { dy = pt.iy() - getMaxY(); } else { dy = 0; } final int dz; if (pt.iz() < getMinZ()) { dz = getMinZ() - pt.iz(); } else if (pt.iz() > getMaxZ()) { dz = pt.iz() - getMaxZ(); } else { dz = 0; } return dx * dx + dy * dy + dz * dz; } @Pure @Override default double getDistanceL1(Point3D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final int dx; if (pt.ix() < getMinX()) { dx = getMinX() - pt.ix(); } else if (pt.ix() > getMaxX()) { dx = pt.ix() - getMaxX(); } else { dx = 0; } final int dy; if (pt.iy() < getMinY()) { dy = getMinY() - pt.iy(); } else if (pt.iy() > getMaxY()) { dy = pt.iy() - getMaxY(); } else { dy = 0; } final int dz; if (pt.iz() < getMinZ()) { dz = getMinZ() - pt.iz(); } else if (pt.iy() > getMaxY()) { dz = pt.iz() - getMaxZ(); } else { dz = 0; } return dx + dy + dz; } @Pure @Override default double getDistanceLinf(Point3D<?, ?> pt) { assert pt != null : AssertMessages.notNullParameter(); final int dx; if (pt.ix() < getMinX()) { dx = getMinX() - pt.ix(); } else if (pt.ix() > getMaxX()) { dx = pt.ix() - getMaxX(); } else { dx = 0; } final int dy; if (pt.iy() < getMinY()) { dy = getMinY() - pt.iy(); } else if (pt.iy() > getMaxY()) { dy = pt.iy() - getMaxY(); } else { dy = 0; } final int dz; if (pt.iz() < getMinZ()) { dz = getMinZ() - pt.iz(); } else if (pt.iz() > getMaxZ()) { dz = pt.iz() - getMaxZ(); } else { dz = 0; } return MathUtil.max(dx, dy, dz); } @Pure @Override default Iterator<P> getPointIterator() { return getPointIterator(Side.TOP); } /** Replies the points on the bounds of the rectangle. * * @param startingBorder is the first border to reply. * @return the points on the bounds of the rectangle. */ @Pure default Iterator<P> getPointIterator(Side startingBorder) { assert startingBorder != null : AssertMessages.notNullParameter(); return new RectangleSideIterator<>(this, startingBorder); } @Override default PathIterator3ai<IE> getPathIterator(Transform3D transform) { if (transform == null || transform.isIdentity()) { return new RectanglePathIterator<>(this); } return new TransformedRectanglePathIterator<>(this, transform); } /** Compute and replies the union of this rectangular prism and the given prism. * This function does not change this rectangular prism. * *<p>It is equivalent to (where <code>ur</code> is the union): * <pre><code> * RectangularPrism3d ur = new RectangularPrism3d(this); * ur.setUnion(r); * </code></pre> * * @param prism the prism * @return the union of this rectangle and the given rectangle. * @see #setUnion(Prism3ai) */ @Pure default B createUnion(Prism3ai<?, ?, ?, ?, ?, ?> prism) { assert prism != null : AssertMessages.notNullParameter(); final B rr = getGeomFactory().newBox(); rr.setFromCorners(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ()); rr.setUnion(prism); return rr; } /** Compute and replies the intersection of this rectangular prism and the given prism. * This function does not change this rectangular prism. * *<p>It is equivalent to (where <code>ir</code> is the union): * <pre><code> * RectangularPrism3d ir = new RectangularPrism3d(this); * ur.setIntersection(r); * </code></pre> * * @param prism the prism * @return the intersection of this rectangle and the given rectangle. * @see #setIntersection(Prism3ai) */ @Pure default B createIntersection(Prism3ai<?, ?, ?, ?, ?, ?> prism) { assert prism != null : AssertMessages.notNullParameter(); final B rr = getGeomFactory().newBox(); final int x1 = Math.max(getMinX(), prism.getMinX()); final int y1 = Math.max(getMinY(), prism.getMinY()); final int z1 = Math.max(getMinZ(), prism.getMinZ()); final int x2 = Math.min(getMaxX(), prism.getMaxX()); final int y2 = Math.min(getMaxY(), prism.getMaxY()); final int z2 = Math.min(getMaxZ(), prism.getMaxZ()); if (x1 <= x2 && y1 <= y2 && z1 <= z2) { rr.setFromCorners(x1, y1, z1, x2, y2, z2); } else { rr.clear(); } return rr; } /** Compute the union of this rectangular prism and the given prism. * This function changes this rectangular prism. * * @param prism the prism * @see #createUnion(Prism3ai) */ default void setUnion(Prism3ai<?, ?, ?, ?, ?, ?> prism) { assert prism != null : AssertMessages.notNullParameter(); setFromCorners( Math.min(getMinX(), prism.getMinX()), Math.min(getMinY(), prism.getMinY()), Math.min(getMinZ(), prism.getMinZ()), Math.max(getMaxX(), prism.getMaxX()), Math.max(getMaxY(), prism.getMaxY()), Math.max(getMaxZ(), prism.getMaxZ())); } /** Compute the intersection of this rectangular prism and the given prism. * This function changes this rectangular prism. * * <p>If there is no intersection, this rectangular Prism is cleared. * * @param prism the prism * @see #createIntersection(Prism3ai) * @see #clear() */ default void setIntersection(Prism3ai<?, ?, ?, ?, ?, ?> prism) { assert prism != null : AssertMessages.notNullParameter(); final int x1 = Math.max(getMinX(), prism.getMinX()); final int y1 = Math.max(getMinY(), prism.getMinY()); final int z1 = Math.max(getMinZ(), prism.getMinZ()); final int x2 = Math.min(getMaxX(), prism.getMaxX()); final int y2 = Math.min(getMaxY(), prism.getMaxY()); final int z2 = Math.min(getMaxZ(), prism.getMaxZ()); if (x1 <= x2 && y1 <= y2 && z1 <= z2) { setFromCorners(x1, y1, z1, x2, y2, z2); } else { clear(); } } /** Sides of a prism. * * @author $Author: sgalland$ * @author $Author: tpiotrow$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ enum Side { /** Top. */ TOP, /** Right. */ RIGHT, /** Bottom. */ BOTTOM, /** Left. */ LEFT, /** Front. */ FRONT, /** Back. */ BACK; } /** Iterates on points on the sides of a rectangle. * * @param <P> type of the points. * @param <V> type of the vectors. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ class RectangleSideIterator<P extends Point3D<? super P, ? super V>, V extends Vector3D<? super V, ? super P>> implements Iterator<P> { private final GeomFactory3ai<?, P, V, ?> factory; private final int x0; private final int y0; private final int z0; private final int x1; private final int y1; private final int z1; private final Side firstSide; private Side currentSide; private int index; /** * @param rectangle is the rectangle to iterate. * @param firstSide the first side to iterate on. */ public RectangleSideIterator(RectangularPrism3ai<?, ?, ?, P, V, ?> rectangle, Side firstSide) { assert rectangle != null : AssertMessages.notNullParameter(0); assert firstSide != null : AssertMessages.notNullParameter(1); this.factory = rectangle.getGeomFactory(); this.firstSide = firstSide; this.x0 = rectangle.getMinX(); this.y0 = rectangle.getMinY(); this.z0 = rectangle.getMinZ(); this.x1 = rectangle.getMaxX(); this.y1 = rectangle.getMaxY(); this.z1 = rectangle.getMaxZ(); this.currentSide = (this.x1 > this.x0 && this.y1 > this.y0 && this.z1 > this.z0) ? this.firstSide : null; this.index = 0; } @Pure @Override public boolean hasNext() { return this.currentSide != null; } // TODO : integrate z coordinate @Override @SuppressWarnings({"checkstyle:npathcomplexity", "checkstyle:cyclomaticcomplexity"}) public P next() { int x = 0; int y = 0; final int z = 0; switch (this.currentSide) { case TOP: x = this.x0 + this.index; y = this.y0; break; case RIGHT: x = this.x1; y = this.y0 + this.index + 1; break; case BOTTOM: x = this.x1 - this.index - 1; y = this.y1; break; case LEFT: x = this.x0; y = this.y1 - this.index - 1; break; case FRONT: case BACK: break; default: throw new NoSuchElementException(); } ++this.index; Side newSide = null; switch (this.currentSide) { case TOP: if (x >= this.x1) { newSide = Side.RIGHT; this.index = 0; } break; case RIGHT: if (y >= this.y1) { newSide = Side.BOTTOM; this.index = 0; } break; case BOTTOM: if (x <= this.x0) { newSide = Side.LEFT; this.index = 0; } break; case LEFT: if (y <= this.y0 + 1) { newSide = Side.TOP; this.index = 0; } break; case FRONT: case BACK: break; default: throw new NoSuchElementException(); } if (newSide != null) { this.currentSide = (this.firstSide == newSide) ? null : newSide; } return this.factory.newPoint(x, y, z); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** Iterator on the path elements of the rectangle. * * @param <E> the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") class RectanglePathIterator<E extends PathElement3ai> implements PathIterator3ai<E> { private final RectangularPrism3ai<?, ?, E, ?, ?, ?> rectangle; private int x1; private int y1; private int z1; private int x2; private int y2; private int z2; private int index; /** * @param rectangle is the rectangle to iterate. */ public RectanglePathIterator(RectangularPrism3ai<?, ?, E, ?, ?, ?> rectangle) { assert rectangle != null : AssertMessages.notNullParameter(); this.rectangle = rectangle; if (rectangle.isEmpty()) { this.index = 5; } else { this.x1 = rectangle.getMinX(); this.y1 = rectangle.getMinY(); this.z1 = rectangle.getMinZ(); this.x2 = rectangle.getMaxX(); this.y2 = rectangle.getMaxY(); this.z2 = rectangle.getMaxZ(); } } @Override public PathIterator3ai<E> restartIterations() { return new RectanglePathIterator<>(this.rectangle); } @Pure @Override public boolean hasNext() { return this.index <= 4; } @Override @SuppressWarnings("checkstyle:returncount") public E next() { final int idx = this.index; ++this.index; switch (idx) { case 0: return this.rectangle.getGeomFactory().newMovePathElement( this.x1, this.y1, this.z1); case 1: return this.rectangle.getGeomFactory().newLinePathElement( this.x1, this.y1, this.z1, this.x1, this.y1, this.z2); case 2: return this.rectangle.getGeomFactory().newLinePathElement( this.x1, this.y1, this.z2, this.x1, this.y2, this.z2); case 3: return this.rectangle.getGeomFactory().newLinePathElement( this.x1, this.y2, this.z2, this.x1, this.y2, this.z1); case 4: return this.rectangle.getGeomFactory().newLinePathElement( this.x1, this.y2, this.z1, this.x2, this.y2, this.z1); case 5: return this.rectangle.getGeomFactory().newLinePathElement( this.x2, this.y2, this.z1, this.x2, this.y2, this.z2); case 6: return this.rectangle.getGeomFactory().newLinePathElement( this.x2, this.y2, this.z2, this.x2, this.y1, this.z2); case 7: return this.rectangle.getGeomFactory().newClosePathElement( this.x2, this.y1, this.z2, this.x2, this.y1, this.z1); default: throw new NoSuchElementException(); } } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Pure @Override public boolean isPolyline() { return false; } @Override public boolean isCurved() { return false; } @Override public boolean isMultiParts() { return false; } @Override public boolean isPolygon() { return true; } @Override public GeomFactory3ai<E, ?, ?, ?> getGeomFactory() { return this.rectangle.getGeomFactory(); } } /** Iterator on the path elements of the rectangle. * * @param <E> the type of the path elements. * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ @SuppressWarnings("checkstyle:magicnumber") class TransformedRectanglePathIterator<E extends PathElement3ai> implements PathIterator3ai<E> { private final RectangularPrism3ai<?, ?, E, ?, ?, ?> rectangle; private final Transform3D transform; private int x1; private int y1; private int z1; private int x2; private int y2; private int z2; private int index; private Point3D<?, ?> move; private Point3D<?, ?> p1; private Point3D<?, ?> p2; /** * @param rectangle is the rectangle to iterate. * @param transform the transformation to apply on the rectangle. */ public TransformedRectanglePathIterator(RectangularPrism3ai<?, ?, E, ?, ?, ?> rectangle, Transform3D transform) { assert rectangle != null : AssertMessages.notNullParameter(0); assert transform != null : AssertMessages.notNullParameter(1); this.rectangle = rectangle; this.transform = transform; if (rectangle.isEmpty()) { this.index = 5; } else { this.move = new InnerComputationPoint3ai(); this.p1 = new InnerComputationPoint3ai(); this.p2 = new InnerComputationPoint3ai(); this.x1 = rectangle.getMinX(); this.y1 = rectangle.getMinY(); this.z1 = rectangle.getMinZ(); this.x2 = rectangle.getMaxX(); this.y2 = rectangle.getMaxY(); this.z2 = rectangle.getMaxZ(); } } @Override public PathIterator3ai<E> restartIterations() { return new TransformedRectanglePathIterator<>(this.rectangle, this.transform); } @Pure @Override public boolean hasNext() { return this.index <= 4; } @Override @SuppressWarnings({"checkstyle:returncount", "checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) public E next() { final int idx = this.index; ++this.index; switch (idx) { case 0: this.p2.set(this.x1, this.y1, this.z1); if (this.transform != null) { this.transform.transform(this.p2); } this.move.set(this.p2); if (this.transform != null) { this.transform.transform(this.p2); } return this.rectangle.getGeomFactory().newMovePathElement(this.p2.ix(), this.p2.iy(), this.p2.iz()); case 1: this.p1.set(this.p2); this.p2.set(this.x1, this.y1, this.z2); if (this.transform != null) { this.transform.transform(this.p2); } return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 2: this.p1.set(this.p2); this.p2.set(this.x1, this.y2, this.z2); if (this.transform != null) { this.transform.transform(this.p2); } return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 3: this.p1.set(this.p2); if (this.transform != null) { this.transform.transform(this.p2); } this.transform.transform(this.p2); return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 4: this.p1.set(this.p2); if (this.transform != null) { this.transform.transform(this.p2); } this.transform.transform(this.p2); return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 5: this.p1.set(this.p2); this.p2.set(this.x2, this.y2, this.z2); if (this.transform != null) { this.transform.transform(this.p2); } return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 6: this.p1.set(this.p2); this.p2.set(this.x2, this.y1, this.z2); if (this.transform != null) { this.transform.transform(this.p2); } return this.rectangle.getGeomFactory().newLinePathElement(this.p1.ix(), this.p1.iy(), this.p1.iz(), this.p2.ix(), this.p2.iy(), this.p2.iz()); case 7: return this.rectangle.getGeomFactory().newClosePathElement(this.p2.ix(), this.p2.iy(), this.p2.iz(), this.move.ix(), this.move.iy(), this.move.iz()); default: throw new NoSuchElementException(); } } @Override public void remove() { throw new UnsupportedOperationException(); } @Pure @Override public PathWindingRule getWindingRule() { return PathWindingRule.NON_ZERO; } @Pure @Override public boolean isPolyline() { return false; } @Override public boolean isCurved() { return false; } @Override public boolean isMultiParts() { return false; } @Override public boolean isPolygon() { return true; } @Override public GeomFactory3ai<E, ?, ?, ?> getGeomFactory() { return this.rectangle.getGeomFactory(); } } }