Parallelepiped.java
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.apache.commons.geometry.euclidean.threed.shape;
- import java.text.MessageFormat;
- import java.util.Arrays;
- import java.util.List;
- import java.util.stream.Collectors;
- import org.apache.commons.geometry.core.Transform;
- import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
- import org.apache.commons.geometry.euclidean.threed.ConvexVolume;
- import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
- import org.apache.commons.geometry.euclidean.threed.Planes;
- import org.apache.commons.geometry.euclidean.threed.Vector3D;
- import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
- import org.apache.commons.numbers.core.Precision;
- /** Class representing parallelepipeds, i.e. 3 dimensional figures formed by six
- * parallelograms. For example, cubes and rectangular prisms are parallelepipeds.
- * @see <a href="https://en.wikipedia.org/wiki/Parallelepiped">Parallelepiped</a>
- */
- public final class Parallelepiped extends ConvexVolume {
- /** Vertices defining a cube with sides of length 1 centered at the origin. */
- private static final List<Vector3D> UNIT_CUBE_VERTICES = Arrays.asList(
- Vector3D.of(-0.5, -0.5, -0.5),
- Vector3D.of(0.5, -0.5, -0.5),
- Vector3D.of(0.5, 0.5, -0.5),
- Vector3D.of(-0.5, 0.5, -0.5),
- Vector3D.of(-0.5, -0.5, 0.5),
- Vector3D.of(0.5, -0.5, 0.5),
- Vector3D.of(0.5, 0.5, 0.5),
- Vector3D.of(-0.5, 0.5, 0.5)
- );
- /** Simple constructor. Callers are responsible for ensuring that the given boundaries
- * represent a parallelepiped. No validation is performed.
- * @param boundaries the boundaries of the parallelepiped; this must be a list
- * with 6 elements
- */
- private Parallelepiped(final List<PlaneConvexSubset> boundaries) {
- super(boundaries);
- }
- /** Construct a new instance representing a unit cube centered at the origin. The vertices of this
- * cube are:
- * <pre>
- * [
- * (-0.5, -0.5, -0.5),
- * (0.5, -0.5, -0.5),
- * (0.5, 0.5, -0.5),
- * (-0.5, 0.5, -0.5),
- *
- * (-0.5, -0.5, 0.5),
- * (0.5, -0.5, 0.5),
- * (0.5, 0.5, 0.5),
- * (-0.5, 0.5, 0.5)
- * ]
- * </pre>
- * @param precision precision context used to construct boundaries
- * @return a new instance representing a unit cube centered at the origin
- */
- public static Parallelepiped unitCube(final Precision.DoubleEquivalence precision) {
- return fromTransformedUnitCube(AffineTransformMatrix3D.identity(), precision);
- }
- /** Return a new instance representing an axis-aligned parallelepiped, ie, a rectangular prism.
- * The points {@code a} and {@code b} are taken to represent opposite corner points in the prism and may be
- * specified in any order.
- * @param a first corner point in the prism (opposite of {@code b})
- * @param b second corner point in the prism (opposite of {@code a})
- * @param precision precision context used to construct boundaries
- * @return a new instance representing an axis-aligned rectangular prism
- * @throws IllegalArgumentException if the width, height, or depth of the defined prism is zero
- * as evaluated by the precision context.
- */
- public static Parallelepiped axisAligned(final Vector3D a, final Vector3D b,
- final Precision.DoubleEquivalence precision) {
- final double minX = Math.min(a.getX(), b.getX());
- final double maxX = Math.max(a.getX(), b.getX());
- final double minY = Math.min(a.getY(), b.getY());
- final double maxY = Math.max(a.getY(), b.getY());
- final double minZ = Math.min(a.getZ(), b.getZ());
- final double maxZ = Math.max(a.getZ(), b.getZ());
- final double xDelta = maxX - minX;
- final double yDelta = maxY - minY;
- final double zDelta = maxZ - minZ;
- final Vector3D scale = Vector3D.of(xDelta, yDelta, zDelta);
- final Vector3D position = Vector3D.of(
- (0.5 * xDelta) + minX,
- (0.5 * yDelta) + minY,
- (0.5 * zDelta) + minZ
- );
- return builder(precision)
- .setScale(scale)
- .setPosition(position)
- .build();
- }
- /** Construct a new instance by transforming a unit cube centered at the origin. The vertices of
- * this input cube are:
- * <pre>
- * [
- * (-0.5, -0.5, -0.5),
- * (0.5, -0.5, -0.5),
- * (0.5, 0.5, -0.5),
- * (-0.5, 0.5, -0.5),
- *
- * (-0.5, -0.5, 0.5),
- * (0.5, -0.5, 0.5),
- * (0.5, 0.5, 0.5),
- * (-0.5, 0.5, 0.5)
- * ]
- * </pre>
- * @param transform transform to apply to the vertices of the unit cube
- * @param precision precision context used to construct boundaries
- * @return a new instance created by transforming the vertices of a unit cube centered at the origin
- * @throws IllegalArgumentException if the width, height, or depth of the defined shape is zero
- * as evaluated by the precision context.
- */
- public static Parallelepiped fromTransformedUnitCube(final Transform<Vector3D> transform,
- final Precision.DoubleEquivalence precision) {
- final List<Vector3D> vertices = UNIT_CUBE_VERTICES.stream()
- .map(transform)
- .collect(Collectors.toList());
- final boolean reverse = !transform.preservesOrientation();
- // check lengths in each dimension
- ensureNonZeroSideLength(vertices.get(0), vertices.get(1), precision);
- ensureNonZeroSideLength(vertices.get(1), vertices.get(2), precision);
- ensureNonZeroSideLength(vertices.get(0), vertices.get(4), precision);
- final List<PlaneConvexSubset> boundaries = Arrays.asList(
- // planes orthogonal to x
- createFace(0, 4, 7, 3, vertices, reverse, precision),
- createFace(1, 2, 6, 5, vertices, reverse, precision),
- // planes orthogonal to y
- createFace(0, 1, 5, 4, vertices, reverse, precision),
- createFace(3, 7, 6, 2, vertices, reverse, precision),
- // planes orthogonal to z
- createFace(0, 3, 2, 1, vertices, reverse, precision),
- createFace(4, 5, 6, 7, vertices, reverse, precision)
- );
- return new Parallelepiped(boundaries);
- }
- /** Return a new {@link Builder} instance to use for constructing parallelepipeds.
- * @param precision precision context used to create boundaries
- * @return a new {@link Builder} instance
- */
- public static Builder builder(final Precision.DoubleEquivalence precision) {
- return new Builder(precision);
- }
- /** Create a single face of a parallelepiped using the indices of elements in the given vertex list.
- * @param a first vertex index
- * @param b second vertex index
- * @param c third vertex index
- * @param d fourth vertex index
- * @param vertices list of vertices for the parallelepiped
- * @param reverse if true, reverse the orientation of the face
- * @param precision precision context used to create the face
- * @return a parallelepiped face created from the indexed vertices
- */
- private static PlaneConvexSubset createFace(final int a, final int b, final int c, final int d,
- final List<? extends Vector3D> vertices, final boolean reverse,
- final Precision.DoubleEquivalence precision) {
- final Vector3D pa = vertices.get(a);
- final Vector3D pb = vertices.get(b);
- final Vector3D pc = vertices.get(c);
- final Vector3D pd = vertices.get(d);
- final List<Vector3D> loop = reverse ?
- Arrays.asList(pd, pc, pb, pa) :
- Arrays.asList(pa, pb, pc, pd);
- return Planes.convexPolygonFromVertices(loop, precision);
- }
- /** Ensure that the given points defining one side of a parallelepiped face are separated by a non-zero
- * distance, as determined by the precision context.
- * @param a first vertex
- * @param b second vertex
- * @param precision precision used to evaluate the distance between the two points
- * @throws IllegalArgumentException if the given points are equivalent according to the precision context
- */
- private static void ensureNonZeroSideLength(final Vector3D a, final Vector3D b,
- final Precision.DoubleEquivalence precision) {
- if (precision.eqZero(a.distance(b))) {
- throw new IllegalArgumentException(MessageFormat.format(
- "Parallelepiped has zero size: vertices {0} and {1} are equivalent", a, b));
- }
- }
- /** Class designed to aid construction of {@link Parallelepiped} instances. Parallelepipeds are constructed
- * by transforming the vertices of a unit cube centered at the origin with a transform built from
- * the values configured here. The transformations applied are <em>scaling</em>, <em>rotation</em>,
- * and <em>translation</em>, in that order. When applied in this order, the scale factors determine
- * the width, height, and depth of the parallelepiped; the rotation determines the orientation; and the
- * translation determines the position of the center point.
- */
- public static final class Builder {
- /** Amount to scale the parallelepiped. */
- private Vector3D scale = Vector3D.of(1, 1, 1);
- /** The rotation of the parallelepiped. */
- private QuaternionRotation rotation = QuaternionRotation.identity();
- /** Amount to translate the parallelepiped. */
- private Vector3D position = Vector3D.ZERO;
- /** Precision context used to construct boundaries. */
- private final Precision.DoubleEquivalence precision;
- /** Construct a new instance configured with the given precision context.
- * @param precision precision context used to create boundaries
- */
- private Builder(final Precision.DoubleEquivalence precision) {
- this.precision = precision;
- }
- /** Set the center position of the created parallelepiped.
- * @param pos center position of the created parallelepiped
- * @return this instance
- */
- public Builder setPosition(final Vector3D pos) {
- this.position = pos;
- return this;
- }
- /** Set the scaling for the created parallelepiped. The scale values determine
- * the lengths of the respective sides in the created parallelepiped.
- * @param scaleFactors scale factors
- * @return this instance
- */
- public Builder setScale(final Vector3D scaleFactors) {
- this.scale = scaleFactors;
- return this;
- }
- /** Set the scaling for the created parallelepiped. The scale values determine
- * the lengths of the respective sides in the created parallelepiped.
- * @param x x scale factor
- * @param y y scale factor
- * @param z z scale factor
- * @return this instance
- */
- public Builder setScale(final double x, final double y, final double z) {
- return setScale(Vector3D.of(x, y, z));
- }
- /** Set the scaling for the created parallelepiped. The given scale factor is applied
- * to the x, y, and z directions.
- * @param scaleFactor scale factor for the x, y, and z directions
- * @return this instance
- */
- public Builder setScale(final double scaleFactor) {
- return setScale(scaleFactor, scaleFactor, scaleFactor);
- }
- /** Set the rotation of the created parallelepiped.
- * @param rot the rotation of the created parallelepiped
- * @return this instance
- */
- public Builder setRotation(final QuaternionRotation rot) {
- this.rotation = rot;
- return this;
- }
- /** Build a new parallelepiped instance with the values configured in this builder.
- * @return a new parallelepiped instance
- * @throws IllegalArgumentException if the length of any side of the parallelepiped is zero,
- * as determined by the configured precision context
- * @see Parallelepiped#fromTransformedUnitCube(Transform, Precision.DoubleEquivalence)
- */
- public Parallelepiped build() {
- final AffineTransformMatrix3D transform = AffineTransformMatrix3D.createScale(scale)
- .rotate(rotation)
- .translate(position);
- return fromTransformedUnitCube(transform, precision);
- }
- }
- }