net.algart.math.IRectangularArea Maven / Gradle / Ivy
Show all versions of algart Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.algart.math;
import java.util.*;
/**
* Rectangular integer area, i.e.
* hyperparallelepiped in multidimensional space with integer coordinates of vertices.
* All edges of the hyperparallelepiped are parallel to coordinate axes.
* In 1-dimensional case it is an equivalent of {@link IRange} class,
* in 2-dimensional case it is an analog of the standard java.awt.Rectangle
class.
*
* More precisely, the region, specified by this class, is defined by two n-dimensional points
* with integer coordinates ({@link IPoint}),
* named the minimal vertex min and maximal vertex max,
* and consists of all such points
* (x0, x1, ..., xn−1), that:
*
*
* min.{@link IPoint#coord(int) coord(0)} ≤ x0 ≤
* max.{@link IPoint#coord(int) coord(0)},
* min.{@link IPoint#coord(int) coord(1)} ≤ x1 ≤
* max.{@link IPoint#coord(int) coord(1)},
* ...,
* min.{@link IPoint#coord(int) coord(n-1)} ≤ xn−1 ≤
* max.{@link IPoint#coord(int) coord(n-1)}.
*
*
* The min and max points are specified while creating an instance of this class
* and can be retrieved by {@link #min()} and {@link #max()} methods.
*
* The coordinates of the minimal vertex
* min.{@link IPoint#coord(int) coord(i)}
* are never greater than the corresponding coordinates of the maximal vertex
* max.{@link IPoint#coord(int) coord(i)},
* the coordinates of the minimal and maximal vertices are always in range
* -Long.MAX_VALUE+1..Long.MAX_VALUE-1
,
* and their difference is always less than Long.MAX_VALUE
.
* In other words,
* "max.{@link IPoint#coord(int)
* coord(i)}-min.{@link IPoint#coord(int) coord(i)}+1
" expression,
* returned by {@link #size(int)} method, and also
* "min.{@link IPoint#coord(int) coord(i)}-1
",
* "min.{@link IPoint#coord(int) coord(i)}-2
" and
* "max.{@link IPoint#coord(int) coord(i)}+1
" expressions
* are always calculated without overflow,
* and the {@link #range(int)} method is always possible to return an allowed range.
*
*
This class is immutable and thread-safe:
* there are no ways to modify settings of the created instance.
*
* @author Daniel Alievsky
* @see RectangularArea
*/
public class IRectangularArea {
final IPoint min;
final IPoint max;
private IRectangularArea(IPoint min, IPoint max) {
this.min = min;
this.max = max;
}
/**
* Returns an instance of this class with the given minimal vertex min and
* maximal vertex max.
* See the {@link IRectangularArea comments to this class} for more details.
*
* @param min the minimal vertex, inclusive.
* @param max the maximal vertex, inclusive.
* @return the new rectangular area "between" these vertices.
* @throws NullPointerException if one of arguments is {@code null}.
* @throws IllegalArgumentException if the {@link #coordCount() numbers of dimensions} in min
* and max
points are different,
* or if, for some i,
* min.{@link IPoint#coord(int) coord}(i)
* > max.{@link IPoint#coord(int) coord}(i)
,
* or if max.{@link IPoint#coord(int)
* coord}(i)-min.{@link IPoint#coord(int)
* coord}(i)+1
* > Long.MAX_VALUE
* (more precisely, if this Java expression is nonpositive
* due to integer overflow),
* or if min.{@link IPoint#coord(int)
* coord}(i) <= -Long.MAX_VALUE
,
* or if max.{@link IPoint#coord(int)
* coord}(i) == Long.MAX_VALUE
.
*/
public static IRectangularArea valueOf(IPoint min, IPoint max) {
return valueOf(min, max, false);
}
/**
* Returns the Cartesian product of the specified coordinate ranges.
* More precisely, return an n-dimensional {@link IRectangularArea rectangular area}
* with the minimal vertex min and maximal vertex max, where
* n=coordRanges.length
,
* min.{@link IPoint#coord(int)
* coord(i)}=coordRanges[i].{@link IRange#min() min()}
,
* max.{@link IPoint#coord(int)
* coord(i)}=coordRanges[i].{@link IRange#max() max()}
.
* See the {@link IRectangularArea comments to this class} for more details.
*
* @param coordRanges the coordinate ranges.
* @return the Cartesian product of the specified coordinate ranges.
* @throws NullPointerException if the argument is {@code null}
* or if one of specified coordRanges
is {@code null}.
* @throws IllegalArgumentException if the passed array is empty (no ranges are passed).
*/
public static IRectangularArea valueOf(IRange... coordRanges) {
Objects.requireNonNull(coordRanges, "Null coordRanges argument");
int n = coordRanges.length;
if (n == 0) {
throw new IllegalArgumentException("Empty coordRanges array");
}
coordRanges = coordRanges.clone();
// cloning before checking guarantees correct check while multithreading
for (int k = 0; k < n; k++) {
Objects.requireNonNull(coordRanges[k], "Null coordRanges[" + k + "]");
}
long[] min = new long[n];
long[] max = new long[n];
for (int k = 0; k < n; k++) {
min[k] = coordRanges[k].min;
max[k] = coordRanges[k].max;
}
return new IRectangularArea(new IPoint(min), new IPoint(max));
}
/**
* Returns a 1-dimensional rectangular area (range) with the given minimal and maximal vertex.
* Equivalent to
*
* {@link #valueOf(IPoint, IPoint) valueOf}(
* {@link IPoint#valueOf(long) IPoint.valueOf}(minX),
* {@link IPoint#valueOf(long) IPoint.valueOf}(maxX));
*
*
* @param minX the minimal x-coordinate, inclusive.
* @param maxX the maximal x-coordinate, inclusive.
* @return the new 1-dimensional rectangular area.
* @throws IllegalArgumentException in the same situations as {@link #valueOf(IPoint, IPoint)} method.
*/
public static IRectangularArea valueOf(long minX, long maxX) {
return valueOf(
IPoint.valueOf(minX),
IPoint.valueOf(maxX));
}
/**
* Returns a 2-dimensional rectangle with the given minimal and maximal vertex.
* Equivalent to
*
* {@link #valueOf(IPoint, IPoint) valueOf}(
* {@link IPoint#valueOf(long...) IPoint.valueOf}(minX, minY),
* {@link IPoint#valueOf(long...) IPoint.valueOf}(maxX, maxY));
*
*
* @param minX the minimal x-coordinate, inclusive.
* @param minY the minimal y-coordinate, inclusive.
* @param maxX the maximal x-coordinate, inclusive.
* @param maxY the maximal y-coordinate, inclusive.
* @return the new 2-dimensional rectangle.
* @throws IllegalArgumentException in the same situations as {@link #valueOf(IPoint, IPoint)} method.
*/
public static IRectangularArea valueOf(long minX, long minY, long maxX, long maxY) {
return valueOf(
IPoint.valueOf(minX, minY),
IPoint.valueOf(maxX, maxY));
}
/**
* Returns a 3-dimensional parallelepiped with the given minimal and maximal vertex.
* Equivalent to
*
* {@link #valueOf(IPoint, IPoint) valueOf}(
* {@link IPoint#valueOf(long...) IPoint.valueOf}(minX, minY, minZ),
* {@link IPoint#valueOf(long...) IPoint.valueOf}(maxX, maxY, maxZ));
*
*
* @param minX the minimal x-coordinate, inclusive.
* @param minY the minimal y-coordinate, inclusive.
* @param minZ the minimal z-coordinate, inclusive.
* @param maxX the maximal x-coordinate, inclusive.
* @param maxY the maximal y-coordinate, inclusive.
* @param maxZ the maximal z-coordinate, inclusive.
* @return the new 3-dimensional parallelepiped.
* @throws IllegalArgumentException in the same situations as {@link #valueOf(IPoint, IPoint)} method.
*/
public static IRectangularArea valueOf(long minX, long minY, long minZ, long maxX, long maxY, long maxZ) {
return valueOf(
IPoint.valueOf(minX, minY, minZ),
IPoint.valueOf(maxX, maxY, maxZ));
}
/**
* Returns a new rectangular area with the same coordinates as the given area.
* All double
coordinates of the passed area are converted
* to long
coordinates of the returned area by standard
* Java typecast (long)doubleValue
.
* Equivalent to {@link #valueOf(IPoint, IPoint) valueOf}({@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #min() min()}), {@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #max() max()}))
.
*
* @param area the real rectangular area.
* @return the integer rectangular area with same (cast) coordinates.
* @throws NullPointerException if the passed area is {@code null}.
* @throws IllegalArgumentException if the points {@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #min() min()})
* and {@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #max() max()})
* do not match requirements of {@link #valueOf(IPoint, IPoint)} method.
*/
public static IRectangularArea valueOf(RectangularArea area) {
Objects.requireNonNull(area, "Null area argument");
return valueOf(IPoint.valueOf(area.min), IPoint.valueOf(area.max));
}
/**
* Returns a new rectangular area with the same coordinates as the given area.
* All double
coordinates of the passed area are converted
* to long
coordinates of the returned area by StrictMath.round
method.
* Java typecast (long)doubleValue
.
* Equivalent to {@link #valueOf(IPoint, IPoint) valueOf}({@link IPoint#roundOf(Point)
* IPoint.roundOf}(area.{@link #min() min()}), {@link IPoint#roundOf(Point)
* IPoint.roundOf}(area.{@link #max() max()}))
.
*
* @param area the real rectangular area.
* @return the integer rectangular area with same (rounded) coordinates.
* @throws NullPointerException if the passed area is {@code null}.
* @throws IllegalArgumentException if the points {@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #min() min()})
* and {@link IPoint#valueOf(Point)
* IPoint.valueOf}(area.{@link #max() max()})
* do not match requirements of {@link #valueOf(IPoint, IPoint)} method.
*/
public static IRectangularArea roundOf(RectangularArea area) {
Objects.requireNonNull(area, "Null area argument");
return valueOf(IPoint.roundOf(area.min), IPoint.roundOf(area.max));
}
/**
* Returns the number of dimensions of this rectangular area.
* Equivalent to {@link #min()}.{@link IPoint#coordCount() coordCount()}
* or {@link #max()}.{@link IPoint#coordCount() coordCount()}
, but works faster.
*
* The result of this method is always positive (>0).
*
* @return the number of dimensions of this rectangular area.
*/
public int coordCount() {
return min.coordinates.length;
}
/**
* Returns the minimal vertex of this rectangular area:
* the point with minimal coordinates, belonging to this area.
* See the {@link IRectangularArea comments to this class} for more details.
*
* @return the minimal vertex of this rectangular area.
*/
public IPoint min() {
return min;
}
/**
* Returns the maximal vertex of this rectangular area:
* the point with maximal coordinates, belonging to this area.
* See the {@link IRectangularArea comments to this class} for more details.
*
* @return the maximal vertex of this rectangular area.
*/
public IPoint max() {
return max;
}
/**
* Returns all sizes of this rectangular area in a form of {@link IPoint}.
* Equivalent to {@link IPoint#valueOf(long...) IPoint.valueOf}({@link #sizes()})
.
* The coordinates of the returned point are greater by 1 than coordinates of
* {@link #max()}.{@link IPoint#subtract(IPoint) subtract}({@link #min()})
.
*
* @return all sizes of this rectangular area in a form of {@link IPoint}.
*/
public IPoint size() {
return new IPoint(sizes());
}
/**
* Returns all sizes of this rectangular area, decreased by 1, in a form of {@link IPoint}.
* Equivalent to {@link IPoint#valueOf(long...) IPoint.valueOf}({@link #widths()})
.
* The coordinates of the returned point are equal to coordinates of
* {@link #max()}.{@link IPoint#subtract(IPoint) subtract}({@link #min()})
.
*
* @return all sizes of this rectangular area, decreased by 1, in a form of {@link IPoint}.
*/
public IPoint width() {
return new IPoint(widths());
}
/**
* Returns {@link #min()}.{@link IPoint#coord(int) coord}(coordIndex)
.
*
* @param coordIndex the index of the coordinate.
* @return {@link #min()}.{@link IPoint#coord(int) coord}(coordIndex)
.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #coordCount()}
.
*/
public long min(int coordIndex) {
return min.coordinates[coordIndex];
}
/**
* Returns {@link #max()}.{@link IPoint#coord(int) coord}(coordIndex)
.
*
* @param coordIndex the index of the coordinate.
* @return {@link #max()}.{@link IPoint#coord(int) coord}(coordIndex)
.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #coordCount()}
.
*/
public long max(int coordIndex) {
return max.coordinates[coordIndex];
}
/**
* Returns {@link #max(int) max}(coordIndex) - {@link #min(int) min}(coordIndex) + 1
.
*
* @param coordIndex the index of the coordinate.
* @return {@link #max(int) max}(coordIndex) - {@link #min(int) min}(coordIndex) + 1
.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #coordCount()}
.
*/
public long size(int coordIndex) {
return max.coordinates[coordIndex] - min.coordinates[coordIndex] + 1;
}
/**
* Returns {@link #max(int) max}(coordIndex) - {@link #min(int) min}(coordIndex)
.
*
* @param coordIndex the index of the coordinate.
* @return {@link #max(int) max}(coordIndex) - {@link #min(int) min}(coordIndex)
.
* @throws IndexOutOfBoundsException if coordIndex<0
or
* coordIndex>={@link #coordCount()}
.
*/
public long width(int coordIndex) {
return max.coordinates[coordIndex] - min.coordinates[coordIndex];
}
/**
* Returns {@link #min()}.{@link IPoint#x() x()}
.
*
* @return {@link #min()}.{@link IPoint#x() x()}
.
*/
public long minX() {
return min.coordinates[0];
}
/**
* Returns {@link #max()}.{@link IPoint#x() x()}
.
*
* @return {@link #max()}.{@link IPoint#x() x()}
.
*/
public long maxX() {
return max.coordinates[0];
}
/**
* Returns {@link #maxX() maxX()} - {@link #minX() minX()} + 1
.
*
* @return {@link #maxX() maxX()} - {@link #minX() minX()} + 1
.
*/
public long sizeX() {
return max.coordinates[0] - min.coordinates[0] + 1;
}
/**
* Returns {@link #maxX() maxX()} - {@link #minX() minX()}
.
*
* @return {@link #maxX() maxX()} - {@link #minX() minX()}
.
*/
public long widthX() {
return max.coordinates[0] - min.coordinates[0];
}
/**
* Returns {@link #min()}.{@link IPoint#y() y()}
.
*
* @return {@link #min()}.{@link IPoint#y() y()}
.
* @throws IllegalStateException if {@link #coordCount()}<2
.
*/
public long minY() {
if (min.coordinates.length < 2) {
throw new IllegalStateException("Cannot get y-coordinates of " + coordCount() + "-dimensional area");
}
return min.coordinates[1];
}
/**
* Returns {@link #max()}.{@link IPoint#y() y()}
.
*
* @return {@link #max()}.{@link IPoint#y() y()}
.
* @throws IllegalStateException if {@link #coordCount()}<2
.
*/
public long maxY() {
if (min.coordinates.length < 2) {
throw new IllegalStateException("Cannot get y-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[1];
}
/**
* Returns {@link #maxY() maxY()} - {@link #minY() minY()} + 1
.
*
* @return {@link #maxY() maxY()} - {@link #minY() minY()} + 1
.
* @throws IllegalStateException if {@link #coordCount()}<2
.
*/
public long sizeY() {
if (min.coordinates.length < 2) {
throw new IllegalStateException("Cannot get y-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[1] - min.coordinates[1] + 1;
}
/**
* Returns {@link #maxY() maxY()} - {@link #minY() minY()}
.
*
* @return {@link #maxY() maxY()} - {@link #minY() minY()}
.
* @throws IllegalStateException if {@link #coordCount()}<2
.
*/
public long widthY() {
if (min.coordinates.length < 2) {
throw new IllegalStateException("Cannot get y-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[1] - min.coordinates[1];
}
/**
* Returns {@link #min()}.{@link IPoint#z() z()}
.
*
* @return {@link #min()}.{@link IPoint#z() z()}
.
* @throws IllegalStateException if {@link #coordCount()}<3
.
*/
public long minZ() {
if (min.coordinates.length < 3) {
throw new IllegalStateException("Cannot get z-coordinates of " + coordCount() + "-dimensional area");
}
return min.coordinates[2];
}
/**
* Returns {@link #max()}.{@link IPoint#z() z()}
.
*
* @return {@link #max()}.{@link IPoint#z() z()}
.
* @throws IllegalStateException if {@link #coordCount()}<3
.
*/
public long maxZ() {
if (min.coordinates.length < 3) {
throw new IllegalStateException("Cannot get z-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[2];
}
/**
* Returns {@link #maxZ() maxZ()} - {@link #minZ() minZ()} + 1
.
*
* @return {@link #maxZ() maxZ()} - {@link #minZ() minZ()} + 1
.
* @throws IllegalStateException if {@link #coordCount()}<3
.
*/
public long sizeZ() {
if (min.coordinates.length < 3) {
throw new IllegalStateException("Cannot get z-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[2] - min.coordinates[2] + 1;
}
/**
* Returns {@link #maxZ() maxZ()} - {@link #minZ() minZ()}
.
*
* @return {@link #maxZ() maxZ()} - {@link #minZ() minZ()}
.
* @throws IllegalStateException if {@link #coordCount()}<3
.
*/
public long widthZ() {
if (min.coordinates.length < 3) {
throw new IllegalStateException("Cannot get z-coordinates of " + coordCount() + "-dimensional area");
}
return max.coordinates[2] - min.coordinates[2];
}
/**
* Returns the sizes of this rectangular area along all dimensions.
* The returned array consists of {@link #coordCount()} elements,
* and the element #k
contains {@link #size(int) size}(k)
.
*
* @return the sizes of this rectangular area along all dimensions.
*/
public long[] sizes() {
long[] sizes = new long[min.coordinates.length];
for (int k = 0; k < sizes.length; k++) {
sizes[k] = max.coordinates[k] - min.coordinates[k] + 1;
}
return sizes;
}
/**
* Returns the sizes of this rectangular area along all dimensions, decreased by 1.
* The returned array consists of {@link #coordCount()} elements,
* and the element #k
contains {@link #width(int) width}(k)
.
*
* @return the sizes of this rectangular area along all dimensions, decreased by 1.
*/
public long[] widths() {
long[] widths = new long[min.coordinates.length];
for (int k = 0; k < widths.length; k++) {
widths[k] = max.coordinates[k] - min.coordinates[k];
}
return widths;
}
/**
* Returns the volume of this rectangular area: the product of all sizes
* returned by {@link #sizes()} method. This area is calculated in double
values.
*
* @return the multidimensional volume of this rectangular area (usual area in 2-dimensional case).
*/
public double volume() {
double result = max.coordinates[0] - min.coordinates[0] + 1;
for (int k = 1; k < min.coordinates.length; k++) {
result *= max.coordinates[k] - min.coordinates[k] + 1;
}
return result;
}
/**
* Returns {@link IRange}.{@link IRange#valueOf(long, long)
* valueOf}({@link #min(int) min}(coordIndex), {@link #max(int) max}(coordIndex))
.
*
* @param coordIndex the index of the coordinate.
* @return {@link IRange}.{@link IRange#valueOf(long, long)
* valueOf}({@link #min(int) min}(coordIndex), {@link #max(int) max}(coordIndex))
.
*/
public IRange range(int coordIndex) {
return new IRange(min.coordinates[coordIndex], max.coordinates[coordIndex]);
}
/**
* Returns the projections of this rectangular area to all axes.
* The returned array consists of {@link #coordCount()} elements,
* and the element #k
contains {@link #range(int) range}(k)
.
*
* @return the projections of this rectangular area to all axes.
*/
public IRange[] ranges() {
IRange[] ranges = new IRange[min.coordinates.length];
for (int k = 0; k < ranges.length; k++) {
ranges[k] = IRange.valueOf(min.coordinates[k], max.coordinates[k]);
}
return ranges;
}
/**
* Returns true
if and only if
* {@link #min(int) min}(k)<=point.{@link IPoint#coord(int) coord}(k)<={@link #max(int) max}(k)
* for all k.
*
* @param point the checked point.
* @return true
if this rectangular area contains the given point.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if point.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public boolean contains(IPoint point) {
Objects.requireNonNull(point, "Null point argument");
int n = min.coordinates.length;
if (point.coordinates.length != n) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ point.coordinates.length + " instead of " + n);
}
for (int k = 0; k < n; k++) {
if (point.coordinates[k] < min.coordinates[k] || point.coordinates[k] > max.coordinates[k]) {
return false;
}
}
return true;
}
/**
* Returns true
if at least one of the specified areas
contains
* the passed point
* (see {@link #contains(IPoint)} method).
*
* @param areas list of checked rectangular areas.
* @param point the checked point.
* @return true
if one of the passed areas contains the given point.
* @throws NullPointerException if one of the arguments or one of the areas is {@code null}.
* @throws IllegalArgumentException if point.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of one of areas.
*/
public static boolean contains(Collection areas, IPoint point) {
Objects.requireNonNull(areas, "Null areas argument");
Objects.requireNonNull(point, "Null point argument");
for (IRectangularArea a : areas) {
if (a.contains(point)) {
return true;
}
}
return false;
}
/**
* Returns true
if and only if
* {@link #min(int) min}(k)<=area.{@link #min(int) min}(k)
* and area.{@link #max(int) max}(k)<={@link #max(int) max}(k)
* for all k.
*
* @param area the checked rectangular area.
* @return true
if the checked rectangular area is a subset of this area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public boolean contains(IRectangularArea area) {
Objects.requireNonNull(area, "Null area argument");
int n = min.coordinates.length;
if (area.min.coordinates.length != n) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ area.min.coordinates.length + " instead of " + n);
}
for (int k = 0; k < n; k++) {
if (area.min.coordinates[k] < min.coordinates[k] || area.max.coordinates[k] > max.coordinates[k]) {
return false;
}
}
return true;
}
/**
* Returns true
if at least one of the specified areas
* contains the passed area
* (see {@link #contains(IRectangularArea)} method).
*
* @param areas list of checked rectangular areas.
* @param area the checked area.
* @return true
if one of the passed areas (1st argument) contains the given area (2nd argument).
* @throws NullPointerException if one of the arguments or one of the areas is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of one of areas
* in the 1st argument.
*/
public static boolean contains(Collection areas, IRectangularArea area) {
Objects.requireNonNull(areas, "Null areas argument");
Objects.requireNonNull(area, "Null area argument");
for (IRectangularArea a : areas) {
if (a.contains(area)) {
return true;
}
}
return false;
}
/**
* Returns true
if and only if
* {@link #min(int) min}(k)<=area.{@link #max(int) max}(k)
* and area.{@link #min(int) min}(k)<={@link #max(int) max}(k)
* for all k.
*
* @param area the checked rectangular area.
* @return true
if the checked rectangular area overlaps with this area,
* maybe in boundary points only.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public boolean intersects(IRectangularArea area) {
Objects.requireNonNull(area, "Null area argument");
int n = min.coordinates.length;
if (area.min.coordinates.length != n) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ area.min.coordinates.length + " instead of " + n);
}
for (int k = 0; k < n; k++) {
if (area.max.coordinates[k] < min.coordinates[k] || area.min.coordinates[k] > max.coordinates[k]) {
return false;
}
}
return true;
}
/*Repeat.SectionStart operationsAndParallelDistance*/
/**
* Returns the set-theoretical intersection A ∩ B of this (A) and
* the passed rectangular area (B) or {@code null} if they
* do not {@link #intersects(IRectangularArea) intersect}
* (A ∩ B = ∅).
* Equivalent to
* thisInstance.{@link #intersects(IRectangularArea) intersects}(area) ? {@link #valueOf(IPoint, IPoint)
* IRectangularArea.valueOf}(
* thisInstance.{@link #min()}.{@link IPoint#max(IPoint) max}(area.{@link #min()}),
* thisInstance.{@link #max()}.{@link IPoint#min(IPoint) min}(area.{@link #max()})) :
* null
.
*
* @param area the second rectangular area.
* @return intersection of this and the second rectangular area or {@code null} if they do not intersect.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public IRectangularArea intersection(IRectangularArea area) {
Objects.requireNonNull(area, "Null area argument");
int n = min.coordinates.length;
if (area.min.coordinates.length != n) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ area.min.coordinates.length + " instead of " + n);
}
long[] newMin = new long[n];
long[] newMax = new long[n];
for (int k = 0; k < n; k++) {
newMin[k] = Math.max(min.coordinates[k], area.min.coordinates[k]);
newMax[k] = Math.min(max.coordinates[k], area.max.coordinates[k]);
if (newMin[k] > newMax[k]) {
return null;
}
}
return new IRectangularArea(new IPoint(newMin), new IPoint(newMax));
}
/**
* Returns a list of set-theoretical intersections A ∩ Bi
* of this rectangular area (A) and all rectangular areas (Bi), specified
* by areas
argument.
* If the passed collection doesn't contain areas, intersecting this area, the result will be an empty list.
* Equivalent to the following loop:
*
* final List<IRectangularArea>gt; result = ... (some empty list);
* for (IRectangularArea area : areas) {
* IRectangularArea intersection = {@link #intersection(IRectangularArea) intersection}(area);
* if (intersection != null) {
* result.add(intersection);
* }
* }
*
.
*
* @param areas collection of areas (we find intersection with each from them).
* @return intersection of this and the second rectangular area or {@code null} if they do not intersect.
* @throws NullPointerException if the argument is {@code null} or one of its elements is {@code null}.
* @throws IllegalArgumentException if this rectangular area or some of the elements of the passed collection
* have different {@link #coordCount()}.
*/
public List intersection(Collection areas) {
Objects.requireNonNull(areas, "Null areas argument");
final List result = new ArrayList<>();
for (IRectangularArea area : areas) {
IRectangularArea intersection = intersection(area);
if (intersection != null) {
result.add(intersection);
}
}
return result;
}
/**
* Calculates the set-theoretical difference A \ B of this (A) and
* the passed rectangular area (B)
* in a form of N rectangular areas
* R1,R2,...,RN,
* the set-theoretical union of which is equal to this difference
* (R1∪R2∪...∪RN =
* A \ B).
* The resulting areas R1,R2,...,RN
* are added into the collection results
by Collection.add(...)
method.
* So, the collection results
must be not-null and support adding elements
* (usually it is List
or Queue
).
*
* It is possible that the difference is empty (A \ B = ∅),
* i.e. this area A is a subset of the passed one B. In this case, this method does nothing.
*
*
It is possible that the difference is equal to this area
* (A \ B = A),
* i.e. this area A does not intersect the passed one B.
* In this case, this method is equivalent to results.add(thisInstance)
call.
*
*
In other cases, there is more than 1 way to represent the resulting difference
* in a form of union of several rectangular areas
* R1,R2,...,RN.
* The precise way, how this method forms this set of rectangular areas Ri,
* is not documented, but this method tries to minimize the number N of such areas.
* In any case, there is a guarantee that N≤2*{@link #coordCount()}.
*
* @param results the collection to store results (new areas will be added to this collection).
* @param area the area B, subtracted from this area A.
* @return a reference to the results
argument.
* @throws NullPointerException if result
or area
argument is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
* @see #subtractCollection(java.util.Queue, java.util.Collection)
*/
public Collection difference(Collection results, IRectangularArea area) {
Objects.requireNonNull(results, "Null results argument");
if (!intersects(area)) { // also checks number of dimensions
results.add(this);
return results;
}
long[] min = this.min.coordinates.clone();
long[] max = this.max.coordinates.clone();
for (int k = min.length - 1; k >= 0; k--) {
assert area.max.coordinates[k] >= min[k] && area.min.coordinates[k] <= max[k]; // because they intersect
if (area.min.coordinates[k] > this.min.coordinates[k]) {
min[k] = this.min.coordinates[k];
max[k] = area.min.coordinates[k] - 1;
results.add(new IRectangularArea(IPoint.valueOf(min), IPoint.valueOf(max)));
}
if (area.max.coordinates[k] < this.max.coordinates[k]) {
min[k] = area.max.coordinates[k] + 1;
max[k] = this.max.coordinates[k];
results.add(new IRectangularArea(IPoint.valueOf(min), IPoint.valueOf(max)));
}
min[k] = Math.max(area.min.coordinates[k], this.min.coordinates[k]);
max[k] = Math.min(area.max.coordinates[k], this.max.coordinates[k]);
// - intersection of two ranges area.min-max[k] and this.min-max[k]
}
return results;
}
/**
* Calculates the set-theoretical difference A \ B of
* the set-theoretical union A of all elements of the collection fromWhatToSubtract
* and the set-theoretical union B of all elements of the collection whatToSubtract
,
* in a form of a union of N rectangular areas, and replaces
* the old content of fromWhatToSubtract
with the resulting N areas.
*
* More precisely, this method is equivalent to the following loop:
*
*
* for (IRectangularArea area : whatToSubtract) {
* for (int i = 0, n = fromWhatToSubtract.size(); i < n; i++) {
* IRectangularArea minuend = fromWhatToSubtract.poll();
* minuend.{@link #difference(Collection, IRectangularArea) difference}(fromWhatToSubtract, area);
* }
* if (fromWhatToSubtract.isEmpty()) {
* break;
* }
* }
*
*
* Note: if some exception occurs while execution of the listed loop (for example,
* some elements of the collections are {@code null} or have different number of dimensions),
* the fromWhatToSubtract
stays partially modified.
* In other words, this method is non-atomic regarding failures.
*
* @param fromWhatToSubtract the minuend A, which will be replaced with A \ B.
* @param whatToSubtract the subtrahend B.
* @return a reference to fromWhatToSubtract
argument, which will contain
* the difference A \ B.
* @throws NullPointerException if fromWhatToSubtract
or whatToSubtract
argument
* is {@code null} or if one of their elements it {@code null}.
* @throws IllegalArgumentException if some of the elements of the passed collections
* have different {@link #coordCount()}.
* @see #subtractCollection(java.util.Queue, IRectangularArea...)
*/
public static Queue subtractCollection(
Queue fromWhatToSubtract,
Collection whatToSubtract) {
Objects.requireNonNull(fromWhatToSubtract, "Null fromWhatToSubtract");
Objects.requireNonNull(whatToSubtract, "Null whatToSubtract");
for (IRectangularArea area : whatToSubtract) {
for (int i = 0, n = fromWhatToSubtract.size(); i < n; i++) {
IRectangularArea minuend = fromWhatToSubtract.poll();
minuend.difference(fromWhatToSubtract, area);
}
if (fromWhatToSubtract.isEmpty()) {
break;
}
}
return fromWhatToSubtract;
}
/**
* Equivalent to {@link #subtractCollection(Queue, Collection)
* subtractCollection}(fromWhatToSubtract, java.util.Arrays.asList(whatToSubtract))
.
*
* @param fromWhatToSubtract the minuend A, which will be replaced with A \ B.
* @param whatToSubtract the subtrahend B.
* @return a reference to fromWhatToSubtract
argument, which will contain
* the difference A \ B.
* @throws NullPointerException if fromWhatToSubtract
or whatToSubtract
argument
* is {@code null} or if one of their elements it {@code null}.
* @throws IllegalArgumentException if some of the elements of the passed collection and array
* have different {@link #coordCount()}.
*/
public static Queue subtractCollection(
Queue fromWhatToSubtract,
IRectangularArea... whatToSubtract) {
return subtractCollection(fromWhatToSubtract, java.util.Arrays.asList(whatToSubtract));
}
/**
* Equivalent to {@link #subtractCollection(Queue, Collection)
* subtractCollection}(fromWhatToSubtract, whatToSubtract
,
* where fromWhatToSubtract
contains this object as the only element.
*
* @param whatToSubtract the subtrahend B.
* @return new collection, containing the difference A \ B
* (A = this object, B = union of all whatToSubtract
).
* @throws NullPointerException if whatToSubtract
argument
* is {@code null} or if one of their elements it {@code null}.
* @throws IllegalArgumentException if this rectangular area or some of the elements of the passed collection
* have different {@link #coordCount()}.
*/
public Queue subtract(Collection whatToSubtract) {
Objects.requireNonNull(whatToSubtract, "Null whatToSubtract");
Queue difference = new ArrayDeque<>();
difference.add(this);
IRectangularArea.subtractCollection(difference, whatToSubtract);
return difference;
}
/**
* Equivalent to {@link #subtract(Collection)
* subtract}(java.util.Arrays.asList(whatToSubtract))
.
*
* @param whatToSubtract the subtrahend B.
* @return new collection, containing the difference A \ B
* (A = this object, B = union of all whatToSubtract
).
* @throws NullPointerException if whatToSubtract
argument
* is {@code null} or if one of their elements it {@code null}.
* @throws IllegalArgumentException if this rectangular area or some of the elements of the passed array
* have different {@link #coordCount()}.
*/
public Queue subtract(IRectangularArea... whatToSubtract) {
return subtract(java.util.Arrays.asList(whatToSubtract));
}
/**
* Returns the minimal rectangular area, containing this area and the given point.
* In the returned area, the {@link #min() minimal vertex} is equal to
* thisInstance.{@link #min()}.{@link IPoint#min(IPoint) min}(point)
and
* the {@link #max() maximal vertex} is equal to
* thisInstance.{@link #max()}.{@link IPoint#max(IPoint) max}(point)
.
*
* @param point some point that should be included to the new rectangular area.
* @return the expanded rectangular area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if point.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance,
* or if the points
* thisInstance.{@link #min()}.{@link IPoint#min(IPoint) min}(point)
* and
* thisInstance.{@link #max()}.{@link IPoint#max(IPoint) max}(point)
* do not match requirements of {@link #valueOf(IPoint, IPoint)} method.
*/
public IRectangularArea expand(IPoint point) {
if (contains(point)) {
// - also checks number of dimensions
return this;
}
long[] newMin = new long[min.coordinates.length];
long[] newMax = new long[min.coordinates.length];
for (int k = 0; k < min.coordinates.length; k++) {
newMin[k] = min.coordinates[k] <= point.coordinates[k] ? min.coordinates[k] : point.coordinates[k];
newMax[k] = max.coordinates[k] >= point.coordinates[k] ? max.coordinates[k] : point.coordinates[k];
}
return valueOf(new IPoint(newMin), new IPoint(newMax));
}
/**
* Returns the minimal rectangular area, containing this and the passed area.
* Equivalent to
* {@link #valueOf(IPoint, IPoint) IRectangularArea.valueOf}(
* thisInstance.{@link #min()}.{@link IPoint#min(IPoint) min}(area.{@link #min()}),
* thisInstance.{@link #max()}.{@link IPoint#max(IPoint) max}(area.{@link #max()}))
.
*
* @param area the second rectangular area.
* @return the minimal rectangular area, containing this and the passed area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if area.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public IRectangularArea expand(IRectangularArea area) {
if (contains(area)) {
// - also checks number of dimensions
return this;
}
long[] newMin = new long[min.coordinates.length];
long[] newMax = new long[min.coordinates.length];
for (int k = 0; k < min.coordinates.length; k++) {
newMin[k] = min.coordinates[k] <= area.min.coordinates[k] ? min.coordinates[k] : area.min.coordinates[k];
newMax[k] = max.coordinates[k] >= area.max.coordinates[k] ? max.coordinates[k] : area.max.coordinates[k];
}
return new IRectangularArea(new IPoint(newMin), new IPoint(newMax));
}
/**
* Returns the minimal rectangular area, containing all passed areas.
* Equivalent to the loop of {@link #expand(IRectangularArea)} methods, called for each element
* of the passed collection, but works faster.
*
* If the passed collection is empty, returns {@code null}.
*
* @param areas some collection of rectangular areas.
* @return the minimal rectangular area, containing all them, or {@code null} for empty collection.
* @throws NullPointerException if the argument or one of the passed areas is {@code null}.
* @throws IllegalArgumentException if {@link #coordCount() coordCount()}
is not equal for all areas.
*/
public static IRectangularArea minimalContainingArea(Collection areas) {
Objects.requireNonNull(areas, "Null areas");
if (areas.isEmpty()) {
return null;
}
final int coordCount = areas.iterator().next().coordCount();
final long[] min = new long[coordCount];
final long[] max = new long[coordCount];
java.util.Arrays.fill(min, Long.MAX_VALUE);
java.util.Arrays.fill(max, Long.MIN_VALUE);
for (IRectangularArea area : areas) {
if (area.coordCount() != coordCount) {
throw new IllegalArgumentException("Some areas have different number of dimension: "
+ area.coordCount() + " and " + coordCount);
}
for (int k = 0; k < coordCount; k++) {
min[k] = Math.min(min[k], area.min(k));
max[k] = Math.max(max[k], area.max(k));
}
}
return IRectangularArea.valueOf(new IPoint(min), new IPoint(max));
}
/**
* Returns the parallel distance from the given point to this rectangular area.
* The parallel distance is a usual distance, with plus or minus sign,
* from the point to some of hyperplanes, containing the hyperfacets of this hyperparallelepiped,
* chosen so that:
*
*
* - the parallel distance is zero at the hyperfacets, negative inside the rectangular area and
* positive outside it;
* - for any constant c,
* the set of all such points, that the parallel distance from them to this rectangular area ≤c,
* is also hyperparallelepiped (rectangular area) wich hyperfacets,
* parallel to the the coordinate hyperplanes,
* or an empty set if c<c0, where c0 is the (negative)
* parallel distance from the geometrical center of this hyperparallelepiped.
*
*
* Formally, let p is any point with coordinates
* p0, p1, ..., pn−1,
* li = {@link #min(int) min}(i),
* ri = {@link #max(int) max}(i),
* di = max(li−pi,
* pi−ri).
* Note that di is positive if pi<li
* or pi>ri and negative if pi
* is inside li..ri range.
* The parallel distance from the point p to this rectangular area
* is defined as maximal value from all di:
* max(d0, d1, ..., dn−1).
*
* @param point some point.
* @return the parallel distance from this point to this rectangular area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if point.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public long parallelDistance(IPoint point) {
Objects.requireNonNull(point, "Null point argument");
return parallelDistance(point.coordinates);
}
/**
* Equivalent to {@link #parallelDistance(IPoint) parallelDistance}({@link IPoint#valueOf(long...)
* IPoint.valueOf}(coordinates)), but works faster because does not require to create an instance
* of {@link IPoint} class.
*
* @param coordinates coordinates of some point.
* @return the parallel distance from this point to this rectangular area.
* @throws NullPointerException if coordinates
argument is {@code null}.
* @throws IllegalArgumentException if coordinates.length
is not equal to
* the {@link #coordCount() number of dimensions} of this instance.
*/
public long parallelDistance(long... coordinates) {
Objects.requireNonNull(coordinates, "Null coordinates argument");
int n = this.min.coordinates.length;
if (coordinates.length != n) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ coordinates.length + " instead of " + n);
}
long min = this.min.coordinates[0];
long max = this.max.coordinates[0];
long x = coordinates[0];
long maxD = min - x >= x - max ? min - x : x - max;
for (int k = 1; k < n; k++) {
min = this.min.coordinates[k];
max = this.max.coordinates[k];
long xk = coordinates[k];
long d = min - xk >= xk - max ? min - xk : xk - max;
if (d > maxD) {
maxD = d;
}
}
return maxD;
}
/**
* Equivalent to {@link #parallelDistance(IPoint) parallelDistance}({@link IPoint#valueOf(long...)
* IPoint.valueOf}(x, y)), but works faster because does not require to allocate any objects.
* Works only for 2-dimensional rectangular areas, in other cases throws
* IllegalArgumentException
.
*
* @param x the 1st coordinate of some point.
* @param y the 2nd coordinate of some point.
* @return the parallel distance from this point to this rectangular area.
* @throws IllegalArgumentException if coordinates.length!=2
.
*/
public long parallelDistance(long x, long y) {
int n = min.coordinates.length;
if (n != 2) {
throw new IllegalArgumentException("Dimensions count mismatch: 2 instead of " + n);
}
long min = this.min.coordinates[0];
long max = this.max.coordinates[0];
long maxD = min - x >= x - max ? min - x : x - max;
min = this.min.coordinates[1];
max = this.max.coordinates[1];
long d = min - y >= y - max ? min - y : y - max;
if (d > maxD) {
maxD = d;
}
return maxD;
}
/**
* Equivalent to {@link #parallelDistance(IPoint) parallelDistance}({@link IPoint#valueOf(long...)
* IPoint.valueOf}(x, y, z)), but works faster because does not require to allocate any objects.
* Works only for 3-dimensional rectangular areas, in other cases throws
* IllegalArgumentException
.
*
* @param x the 1st coordinate of some point.
* @param y the 2nd coordinate of some point.
* @param z the 3rd coordinate of some point.
* @return the parallel distance from this point to this rectangular area.
* @throws IllegalArgumentException if coordinates.length!=2
.
*/
public long parallelDistance(long x, long y, long z) {
int n = min.coordinates.length;
if (n != 3) {
throw new IllegalArgumentException("Dimensions count mismatch: 3 instead of " + n);
}
long min = this.min.coordinates[0];
long max = this.max.coordinates[0];
long maxD = min - x >= x - max ? min - x : x - max;
min = this.min.coordinates[1];
max = this.max.coordinates[1];
long d = min - y >= y - max ? min - y : y - max;
if (d > maxD) {
maxD = d;
}
min = this.min.coordinates[2];
max = this.max.coordinates[2];
d = min - z >= z - max ? min - z : z - max;
if (d > maxD) {
maxD = d;
}
return maxD;
}
/*Repeat.SectionEnd operationsAndParallelDistance*/
/**
* Shifts this rectangular area by the specified vector and returns the shifted area.
* Equivalent to
*
{@link #valueOf(IPoint, IPoint)
* valueOf}(thisInstance.{@link #min()}.{@link IPoint#addExact(IPoint)
* addExact}(vector), thisInstance.{@link #max()}.{@link IPoint#addExact(IPoint) addExact}(vector))
*
* Note: the coordinates of new areas are calculated with overflow control.
*
* @param vector the vector which is added to all vertices of this area.
* @return the shifted area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if vector.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance,
* or if the result is illegal due to the integer overflow.
* @throws ArithmeticException in a case of long
overflow.
*/
public IRectangularArea shift(IPoint vector) {
Objects.requireNonNull(vector, "Null vector argument");
if (vector.coordinates.length != min.coordinates.length) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ vector.coordinates.length + " instead of " + min.coordinates.length);
}
if (vector.isOrigin()) {
return this;
}
return IRectangularArea.valueOf(min.addExact(vector), max.addExact(vector));
}
/**
* Shifts this rectangular area by vector.{@link IPoint#symmetric() symmetric()}
* and returns the shifted area.
* Equivalent to
*
{@link #valueOf(IPoint, IPoint)
* valueOf}(thisInstance.{@link #min()}.{@link IPoint#subtractExact(IPoint)
* subtractExact}(vector), thisInstance.{@link #max()}.{@link IPoint#subtractExact(IPoint) subtractExact}(vector))
*
* Note: the coordinates of new areas are calculated with overflow control.
*
* @param vector the vector which is subtracted from all vertices of this area.
* @return the shifted area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if vector.{@link IPoint#coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance,
* or if the result is illegal due to the integer overflow.
* @throws ArithmeticException in a case of long
overflow.
*/
public IRectangularArea shiftBack(IPoint vector) {
Objects.requireNonNull(vector, "Null vector argument");
if (vector.coordinates.length != min.coordinates.length) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ vector.coordinates.length + " instead of " + min.coordinates.length);
}
if (vector.isOrigin()) {
return this;
}
return IRectangularArea.valueOf(min.subtractExact(vector), max.subtractExact(vector));
}
/**
* Returns this rectangular area, dilated (expanded) according the argument. More precisely,
* returns
*
IRectangularArea.valueOf(
* thisInstance.{@link #min() min()}.{@link IPoint#subtractExact(IPoint) subtractExact}(expansion),
* thisInstance.{@link #max() max()}.{@link IPoint#addExact(IPoint) addExact}(expansion))
* (but if expansion.{@link IPoint#isOrigin() isOrigin()}
, return this object without changes).
*
* @param expansion how to dilate this area.
* @return dilated area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if expansion.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance,
* or if the result area will be incorrect (see comments to
* {@link #valueOf(IPoint, IPoint)} method).
* @throws ArithmeticException in a case of long
overflow.
*/
public IRectangularArea dilate(IPoint expansion) {
Objects.requireNonNull(expansion, "Null expansion");
if (expansion.coordCount() != coordCount()) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ expansion.coordCount() + " instead of " + coordCount());
}
if (expansion.isOrigin()) {
return this;
}
return IRectangularArea.valueOf(min().subtractExact(expansion), max().addExact(expansion));
}
/**
* Equivalent to 3{@link #dilate(IPoint) dilate}(IPoint.valueOfEqualCoordinates(thisObjects.{@link
* #coordCount() coordCount()}, expansion)
.
*
* @param expansion how to dilate this area.
* @return dilated area.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if the result area will be incorrect (see comments to
* {@link #valueOf(IPoint, IPoint)} method).
* @throws ArithmeticException in a case of long
overflow.
*/
public IRectangularArea dilate(long expansion) {
return dilate(IPoint.valueOfEqualCoordinates(coordCount(), expansion));
}
/**
* Returns this area, dilated according the argument only along coordinate axes,
* without full hypercube areas near vertices (like in {@link #dilate(IPoint)} method).
*
* More precisely, the result is a list, consisting of this area and (usually) 2*{@link #coordCount()}
* rectangular areas, lying along facets of this area, like in the following picture:
*
* aaaaaaaaaaaa
* bbRRRRRRRRRRRRcc
* bbRRRRRRRRRRRRcc
* bbRRRRRRRRRRRRcc
* ddddddddddd
*
* This figure shows dilation of some 2-dimensional rectangle R
by
* expansion=IPoint.valueOf(2,1)
:
* the results consists of the original rectangle and 4 rectangles a
, b
(height 1) and
* c
, d
(width 2).
*
* Note: all coordinates of expansion
argument must be non-negative
* (unlike {@link #dilate(IPoint)} method).
*
*
Note: the coordinates of new areas are calculated with overflow control:
* if the result cannot be exactly represented by 64-bit long
integers,
* this method throws ArithmeticException
.
*
*
If some of coordinates of the point expansion
are zero, new areas along the corresponding
* facets are not added (recanglar area cannot be empty).
* In particular, if expansion.{@link IPoint#isOrigin() isOrigin()}
,
* the result will contain this area as the only element.
*
* @param results the list to store results (new areas will be added to the end of this list).
* @param expansion how to dilate this area.
* @return a reference to the results
argument.
* @throws NullPointerException if one of the arguments is {@code null}.
* @throws IllegalArgumentException if expansion.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of this instance,
* or if one of coordinates of expansion
is negative,
* or if the result area will be incorrect (see comments to
* {@link #valueOf(IPoint, IPoint)} method).
* @throws ArithmeticException in a case of long
overflow.
*/
public List dilateStraightOnly(List results, IPoint expansion) {
Objects.requireNonNull(results, "Null results");
Objects.requireNonNull(expansion, "Null expansion");
results.add(this);
final int coordCount = coordCount();
if (expansion.coordCount() != coordCount) {
throw new IllegalArgumentException("Dimensions count mismatch: "
+ expansion.coordCount() + " instead of " + coordCount);
}
final long[] min = this.min.coordinates();
final long[] max = this.max.coordinates();
for (int k = 0; k < coordCount; k++) {
final long delta = expansion.coordinates[k];
if (delta == 0) {
continue;
}
if (delta < 0) {
throw new IllegalArgumentException("Negative expansion is impossible: " + expansion);
}
final long saveMin = min[k];
final long saveMax = max[k];
min[k] = IPoint.subtractExact(saveMin, delta);
max[k] = IPoint.subtractExact(saveMin, 1);
results.add(IRectangularArea.valueOf(IPoint.valueOf(min), IPoint.valueOf(max)));
min[k] = IPoint.addExact(saveMax, 1);
max[k] = IPoint.addExact(saveMax, delta);
results.add(IRectangularArea.valueOf(IPoint.valueOf(min), IPoint.valueOf(max)));
min[k] = saveMin;
max[k] = saveMax;
}
return results;
}
/**
* Equivalent to {@link #dilateStraightOnly(List, IPoint)
* dilateStraightOnly}(results, IPoint.valueOfEqualCoordinates(thisObjects.{@link
* #coordCount() coordCount()}, expansion)
.
*
* @param results the list to store results (new areas will be added to the end of this list).
* @param expansion how to dilate this area.
* @return a reference to the results
argument.
* @throws NullPointerException if the argument is {@code null}.
* @throws IllegalArgumentException if expansion < 0
* or if the result area will be incorrect (see comments to
* {@link #valueOf(IPoint, IPoint)} method).
* @throws ArithmeticException in a case of long
overflow.
*/
public List dilateStraightOnly(List results, long expansion) {
return dilateStraightOnly(results, IPoint.valueOfEqualCoordinates(coordCount(), expansion));
}
/**
* Dilates all areas, specified by the argument, by {@link #dilate(IPoint) dilate} or
* {@link #dilateStraightOnly(List, IPoint) dilateStraightOnly} method,
* and returns the list of dilated areas.
* If straightOnly
argument is false
, this method is equivalent to the following code:
*
* final List<IRectangularArea> result = new ArrayList<IRectangularArea>();
* for (IRectangularArea area : areas) {
* result.add(area.{@link #dilate(IPoint) dilate}(expansion));
* }
* If straightOnly
argument is true
, this method is equivalent to the following code:
*
* final List<IRectangularArea> result = new ArrayList<IRectangularArea>();
* for (IRectangularArea area : areas) {
* area.{@link #dilateStraightOnly(List, IPoint) dilateStraightOnly}(result, expansion);
* }
* Note that in the second case the resulting list will usually contain more elements than
* the source areas
collection.
*
* @param areas areas to be dilated.
* @param expansion how to dilate these areas.
* @param straightOnly dilation mode.
* @return list of dilated areas.
* @throws NullPointerException if one of the arguments is {@code null} or one of areas is {@code null}.
* @throws IllegalArgumentException if expansion.{@link #coordCount() coordCount()}
is not equal to
* the {@link #coordCount() number of dimensions} of one of areas,
* or if straightOnly
amd one of coordinates
* of expansion
* is negative (and collection of areas is not empty),
* or if one of the result areas will be incorrect (see comments to
* {@link #valueOf(IPoint, IPoint)} method).
* @throws ArithmeticException in a case of long
overflow.
*/
public static List dilate(
Collection areas,
IPoint expansion,
boolean straightOnly) {
Objects.requireNonNull(areas, "Null areas");
final List result = new ArrayList<>();
for (IRectangularArea area : areas) {
if (straightOnly) {
area.dilateStraightOnly(result, expansion);
} else {
result.add(area.dilate(expansion));
}
}
return result;
}
/**
* Equivalent to
* {@link RectangularArea#valueOf(IRectangularArea) RectangularArea.valueOf}(thisInstance)
.
*
* @return the rectangular area with same real coordinates as this one.
*/
public RectangularArea toRectangularArea() {
return RectangularArea.valueOf(this);
}
/**
* Returns a brief string description of this object.
*
* The result of this method may depend on implementation and usually contains
* information about all coordinates ranges between the minimum and maximum vertices of this area.
*
* @return a brief string description of this object.
*/
public String toString() {
StringBuilder sb = new StringBuilder("[" + range(0) + "]");
for (int k = 1; k < min.coordinates.length; k++) {
sb.append("x").append("[").append(range(k)).append("]");
}
return sb.toString();
}
/**
* Returns the hash code of this rectangular area.
*
* @return the hash code of this rectangular area.
*/
public int hashCode() {
return min.hashCode() * 37 + max.hashCode();
}
/**
* Indicates whether some other rectangular area is equal to this instance.
* Returns true
if and only if obj instanceof IRectangularArea
,
* ((IRectangularArea)obj).min().equals(this.min())
and
* ((IRectangularArea)obj).max().equals(this.max())
.
*
* @param obj the object to be compared for equality with this instance.
* @return true
if the specified object is a rectangular area equal to this one.
*/
public boolean equals(Object obj) {
return obj instanceof IRectangularArea
&& ((IRectangularArea) obj).min.equals(this.min) && ((IRectangularArea) obj).max.equals(this.max);
}
static IRectangularArea valueOf(IPoint min, IPoint max, boolean ise) {
Objects.requireNonNull(min, "Null min vertex");
Objects.requireNonNull(max, "Null max vertex");
int n = min.coordinates.length;
if (n != max.coordinates.length) {
throw new IllegalArgumentException("min.coordCount() = " + n
+ " does not match max.coordCount() = " + max.coordinates.length);
}
for (int k = 0; k < n; k++) {
if (min.coordinates[k] > max.coordinates[k]) {
throw IRange.invalidBoundsException("min.coord(" + k + ") > max.coord(" + k + ")"
+ " (min = " + min + ", max = " + max + ")", ise);
}
if (max.coordinates[k] == Long.MAX_VALUE) {
throw IRange.invalidBoundsException("max.coord(" + k + ") == Long.MAX_VALUE", ise);
}
if (min.coordinates[k] <= -Long.MAX_VALUE) {
throw IRange.invalidBoundsException("min.coord(" + k + ") == Long.MAX_VALUE or Long.MIN_VALUE+1", ise);
}
if (max.coordinates[k] - min.coordinates[k] + 1L <= 0L) {
throw IRange.invalidBoundsException("max.coord(" + k + ") - min.coord(" + k + ")"
+ " >= Long.MAX_VALUE (min = " + min + ", max = " + max + ")", ise);
}
}
return new IRectangularArea(min, max);
}
}