/** * Copyright 2010-2024 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package com.jogamp.math.geom; import com.jogamp.math.FloatUtil; import com.jogamp.math.Matrix4f; import com.jogamp.math.Quaternion; import com.jogamp.math.Ray; import com.jogamp.math.Recti; import com.jogamp.math.Vec3f; import com.jogamp.math.geom.plane.AffineTransform; /** * Axis Aligned Bounding Box. Defined by two 3D coordinates (low and high) * The low being the the lower left corner of the box, and the high being the upper * right corner of the box. *
* A few references for collision detection, intersections: *
* http://www.realtimerendering.com/intersections.html * http://www.codercorner.com/RayAABB.cpp * http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter0.htm * http://realtimecollisiondetection.net/files/levine_swept_sat.txt ** * */ public class AABBox { private static final boolean DEBUG = FloatUtil.DEBUG; /** Low left-bottom-far (xyz) coordinate */ private final Vec3f lo = new Vec3f(); /** High right-top-near (xyz) coordinate */ private final Vec3f hi = new Vec3f(); /** Computed center of {@link #lo} and {@link #hi}. */ private final Vec3f center = new Vec3f(); /** * Create an Axis Aligned bounding box (AABBox) with the * inverse low/high, allowing the next {@link #resize(float, float, float)} command to hit. *
* The dimension, i.e. {@link #getWidth()} abd {@link #getHeight()} is {@link Float#isInfinite()} thereafter. *
* @see #reset() */ public AABBox() { reset(); } /** * Create an AABBox copying all values from the given one * @param src the box value to be used for the new instance */ public AABBox(final AABBox src) { copy(src); } /** * Create an AABBox specifying the coordinates * of the low and high * @param lx min x-coordinate * @param ly min y-coordnate * @param lz min z-coordinate * @param hx max x-coordinate * @param hy max y-coordinate * @param hz max z-coordinate */ public AABBox(final float lx, final float ly, final float lz, final float hx, final float hy, final float hz) { setSize(lx, ly, lz, hx, hy, hz); } /** * Create a AABBox defining the low and high * @param low min xyz-coordinates * @param high max xyz-coordinates */ public AABBox(final float[] low, final float[] high) { setSize(low, high); } /** * Create a AABBox defining the low and high * @param low min xyz-coordinates * @param high max xyz-coordinates */ public AABBox(final Vec3f low, final Vec3f high) { setSize(low, high); } /** * Resets this box to the inverse low/high, allowing the next {@link #resize(float, float, float)} command to hit. ** The dimension, i.e. {@link #getWidth()} abd {@link #getHeight()} is {@link Float#isInfinite()} thereafter. *
* @return this AABBox for chaining */ public final AABBox reset() { setLow(Float.MAX_VALUE,Float.MAX_VALUE,Float.MAX_VALUE); setHigh(-1*Float.MAX_VALUE,-1*Float.MAX_VALUE,-1*Float.MAX_VALUE); center.set( 0f, 0f, 0f); return this; } /** Returns the maximum right-top-near (xyz) coordinate */ public final Vec3f getHigh() { return hi; } private final void setHigh(final float hx, final float hy, final float hz) { this.hi.set(hx, hy, hz); } /** Returns the minimum left-bottom-far (xyz) coordinate */ public final Vec3f getLow() { return lo; } private final void setLow(final float lx, final float ly, final float lz) { this.lo.set(lx, ly, lz); } private final void computeCenter() { center.set(hi).add(lo).scale(1f/2f); } /** * Copy given AABBox 'src' values to this AABBox. * * @param src source AABBox * @return this AABBox for chaining */ public final AABBox copy(final AABBox src) { lo.set(src.lo); hi.set(src.hi); center.set(src.center); return this; } /** * Set size of the AABBox specifying the coordinates * of the low and high. * * @param low min xyz-coordinates * @param high max xyz-coordinates * @return this AABBox for chaining */ public final AABBox setSize(final float[] low, final float[] high) { return setSize(low[0],low[1],low[2], high[0],high[1],high[2]); } /** * Set size of the AABBox specifying the coordinates * of the low and high. * * @param lx min x-coordinate * @param ly min y-coordnate * @param lz min z-coordinate * @param hx max x-coordinate * @param hy max y-coordinate * @param hz max z-coordinate * @return this AABBox for chaining */ public final AABBox setSize(final float lx, final float ly, final float lz, final float hx, final float hy, final float hz) { this.lo.set(lx, ly, lz); this.hi.set(hx, hy, hz); computeCenter(); return this; } /** * Set size of the AABBox specifying the coordinates * of the low and high. * * @param low min xyz-coordinates * @param high max xyz-coordinates * @return this AABBox for chaining */ public final AABBox setSize(final Vec3f low, final Vec3f high) { this.lo.set(low); this.hi.set(high); computeCenter(); return this; } /** * Resize width of this AABBox with explicit left- and right delta values * @param deltaLeft positive value will expand width, otherwise shrink width * @param deltaRight positive value will expand width, otherwise shrink width * @return this AABBox for chaining */ public final AABBox resizeWidth(final float deltaLeft, final float deltaRight) { boolean mod = false; if( !FloatUtil.isZero(deltaLeft) ) { lo.setX( lo.x() - deltaLeft ); mod = true; } if( !FloatUtil.isZero(deltaRight) ) { hi.setX( hi.x() + deltaRight ); mod = true; } if( mod ) { computeCenter(); } return this; } /** * Resize height of this AABBox with explicit bottom- and top delta values * @param deltaBottom positive value will expand height, otherwise shrink height * @param deltaTop positive value will expand height, otherwise shrink height * @return this AABBox for chaining */ public final AABBox resizeHeight(final float deltaBottom, final float deltaTop) { boolean mod = false; if( !FloatUtil.isZero(deltaBottom) ) { lo.setY( lo.y() - deltaBottom ); mod = true; } if( !FloatUtil.isZero(deltaTop) ) { hi.setY( hi.y() + deltaTop ); mod = true; } if( mod ) { computeCenter(); } return this; } /** * Assign values of given AABBox to this instance. * * @param o source AABBox * @return this AABBox for chaining */ public final AABBox set(final AABBox o) { this.lo.set(o.lo); this.hi.set(o.hi); this.center.set(o.center); return this; } /** * Resize the AABBox to encapsulate another AABox * @param newBox AABBox to be encapsulated in * @return this AABBox for chaining */ public final AABBox resize(final AABBox newBox) { final Vec3f newBL = newBox.getLow(); final Vec3f newTR = newBox.getHigh(); /** test low */ if (newBL.x() < lo.x()) { lo.setX( newBL.x() ); } if (newBL.y() < lo.y()) { lo.setY( newBL.y() ); } if (newBL.z() < lo.z()) { lo.setZ( newBL.z() ); } /** test high */ if (newTR.x() > hi.x()) { hi.setX( newTR.x() ); } if (newTR.y() > hi.y()) { hi.setY( newTR.y() ); } if (newTR.z() > hi.z()) { hi.setZ( newTR.z() ); } computeCenter(); return this; } /** * Resize the AABBox to encapsulate another AABox, which will be transformed on the fly first. * @param newBox AABBox to be encapsulated in * @param t the {@link AffineTransform} applied on newBox on the fly * @param tmpV3 temporary storage * @return this AABBox for chaining */ public final AABBox resize(final AABBox newBox, final AffineTransform t, final Vec3f tmpV3) { /** test low */ { final Vec3f newBL = t.transform(newBox.getLow(), tmpV3); if (newBL.x() < lo.x()) lo.setX( newBL.x() ); if (newBL.y() < lo.y()) lo.setY( newBL.y() ); if (newBL.z() < lo.z()) lo.setZ( newBL.z() ); } /** test high */ { final Vec3f newTR = t.transform(newBox.getHigh(), tmpV3); if (newTR.x() > hi.x()) hi.setX( newTR.x() ); if (newTR.y() > hi.y()) hi.setY( newTR.y() ); if (newTR.z() > hi.z()) hi.setZ( newTR.z() ); } computeCenter(); return this; } /** * Resize the AABBox to encapsulate the passed * xyz-coordinates. * @param x x-axis coordinate value * @param y y-axis coordinate value * @param z z-axis coordinate value * @return this AABBox for chaining */ public final AABBox resize(final float x, final float y, final float z) { /** test low */ if (x < lo.x()) { lo.setX( x ); } if (y < lo.y()) { lo.setY( y ); } if (z < lo.z()) { lo.setZ( z ); } /** test high */ if (x > hi.x()) { hi.setX( x ); } if (y > hi.y()) { hi.setY( y ); } if (z > hi.z()) { hi.setZ( z ); } computeCenter(); return this; } /** * Resize the AABBox to encapsulate the passed * xyz-coordinates. * @param xyz xyz-axis coordinate values * @param offset of the array * @return this AABBox for chaining */ public final AABBox resize(final float[] xyz, final int offset) { return resize(xyz[0+offset], xyz[1+offset], xyz[2+offset]); } /** * Resize the AABBox to encapsulate the passed * xyz-coordinates. * @param xyz xyz-axis coordinate values * @return this AABBox for chaining */ public final AABBox resize(final float[] xyz) { return resize(xyz[0], xyz[1], xyz[2]); } /** * Resize the AABBox to encapsulate the passed * xyz-coordinates. * @param xyz xyz-axis coordinate values * @return this AABBox for chaining */ public final AABBox resize(final Vec3f xyz) { return resize(xyz.x(), xyz.y(), xyz.z()); } /** * Returns whether this AABBox contains given 2D point. * @param x x-axis coordinate value * @param y y-axis coordinate value */ public final boolean contains(final float x, final float y) { return !( x* Versions uses the SAT[1], testing 6 axes. * Original code for OBBs from MAGIC. * Rewritten for AABBs and reorganized for early exits[2]. *
** [1] SAT = Separating Axis Theorem * [2] http://www.codercorner.com/RayAABB.cpp ** @param ray * @return */ public final boolean intersectsRay(final Ray ray) { // diff[XYZ] -> VectorUtil.subVec3(diff, ray.orig, center); // ext[XYZ] -> extend VectorUtil.subVec3(ext, high, center); final float dirX = ray.dir.x(); final float diffX = ray.orig.x() - center.x(); final float extX = hi.x() - center.x(); if( Math.abs(diffX) > extX && diffX*dirX >= 0f ) return false; final float dirY = ray.dir.y(); final float diffY = ray.orig.y() - center.y(); final float extY = hi.y() - center.y(); if( Math.abs(diffY) > extY && diffY*dirY >= 0f ) return false; final float dirZ = ray.dir.z(); final float diffZ = ray.orig.z() - center.z(); final float extZ = hi.z() - center.z(); if( Math.abs(diffZ) > extZ && diffZ*dirZ >= 0f ) return false; final float absDirY = Math.abs(dirY); final float absDirZ = Math.abs(dirZ); float f = dirY * diffZ - dirZ * diffY; if( Math.abs(f) > extY*absDirZ + extZ*absDirY ) return false; final float absDirX = Math.abs(dirX); f = dirZ * diffX - dirX * diffZ; if( Math.abs(f) > extX*absDirZ + extZ*absDirX ) return false; f = dirX * diffY - dirY * diffX; if( Math.abs(f) > extX*absDirY + extY*absDirX ) return false; return true; } /** * Return intersection of a {@link Ray} with this bounding box, * or null if none exist. *
*
* Method is based on the requirements: *
* Report bugs: p.terdiman@codercorner.com (original author) *
** [1] http://www.codercorner.com/RayAABB.cpp * [2] http://tog.acm.org/resources/GraphicsGems/gems/RayBox.c ** @param result vec3 * @param ray * @param epsilon * @param assumeIntersection if true, method assumes an intersection, i.e. by pre-checking via {@link #intersectsRay(Ray)}. * In this case method will not validate a possible non-intersection and just computes * coordinates. * @return float[3] result of intersection coordinates, or null if none exists */ public final Vec3f getRayIntersection(final Vec3f result, final Ray ray, final float epsilon, final boolean assumeIntersection) { final float[] maxT = { -1f, -1f, -1f }; final Vec3f origin = ray.orig; final Vec3f dir = ray.dir; boolean inside = true; /** * Use unrolled version below... * * Find candidate planes. for(int i=0; i<3; i++) { final float origin_i = origin.get(i); final float dir_i = dir.get(i); final float bl_i = bl.get(i); final float tr_i = tr.get(i); if(origin_i < bl_i) { result.set(i, bl_i); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir_i) ) { maxT[i] = (bl_i - origin_i) / dir_i; } } else if(origin_i > tr_i) { result.set(i, tr_i); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir_i) ) { maxT[i] = (tr_i - origin_i) / dir_i; } } } */ // Find candidate planes, unrolled { if(origin.x() < lo.x()) { result.setX(lo.x()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.x()) ) { maxT[0] = (lo.x() - origin.x()) / dir.x(); } } else if(origin.x() > hi.x()) { result.setX(hi.x()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.x()) ) { maxT[0] = (hi.x() - origin.x()) / dir.x(); } } } { if(origin.y() < lo.y()) { result.setX(lo.y()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.y()) ) { maxT[1] = (lo.y() - origin.y()) / dir.y(); } } else if(origin.y() > hi.y()) { result.setX(hi.y()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.y()) ) { maxT[1] = (hi.y() - origin.y()) / dir.y(); } } } { if(origin.z() < lo.z()) { result.setX(lo.z()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.z()) ) { maxT[2] = (lo.z() - origin.z()) / dir.z(); } } else if(origin.z() > hi.z()) { result.setX(hi.z()); inside = false; // Calculate T distances to candidate planes if( 0 != Float.floatToIntBits(dir.z()) ) { maxT[2] = (hi.z() - origin.z()) / dir.z(); } } } // Ray origin inside bounding box if(inside) { result.set(origin); return result; } // Get largest of the maxT's for final choice of intersection int whichPlane = 0; if(maxT[1] > maxT[whichPlane]) { whichPlane = 1; } if(maxT[2] > maxT[whichPlane]) { whichPlane = 2; } if( !assumeIntersection ) { // Check final candidate actually inside box if( 0 != ( Float.floatToIntBits(maxT[whichPlane]) & 0x80000000 ) ) { return null; } /** Use unrolled version below .. for(int i=0; i<3; i++) { if( i!=whichPlane ) { result[i] = origin[i] + maxT[whichPlane] * dir[i]; if(result[i] < minB[i] - epsilon || result[i] > maxB[i] + epsilon) { return null; } // if(result[i] < minB[i] || result[i] > maxB[i] ) { return null; } } } */ switch( whichPlane ) { case 0: result.setY( origin.y() + maxT[whichPlane] * dir.y() ); if(result.y() < lo.y() - epsilon || result.y() > hi.y() + epsilon) { return null; } result.setZ( origin.z() + maxT[whichPlane] * dir.z() ); if(result.z() < lo.z() - epsilon || result.z() > hi.z() + epsilon) { return null; } break; case 1: result.setX( origin.x() + maxT[whichPlane] * dir.x() ); if(result.x() < lo.x() - epsilon || result.x() > hi.x() + epsilon) { return null; } result.setZ( origin.z() + maxT[whichPlane] * dir.z() ); if(result.z() < lo.z() - epsilon || result.z() > hi.z() + epsilon) { return null; } break; case 2: result.setX( origin.x() + maxT[whichPlane] * dir.x() ); if(result.x() < lo.x() - epsilon || result.x() > hi.x() + epsilon) { return null; } result.setY( origin.y() + maxT[whichPlane] * dir.y() ); if(result.y() < lo.y() - epsilon || result.y() > hi.y() + epsilon) { return null; } break; default: throw new InternalError("XXX"); } } else { switch( whichPlane ) { case 0: result.setY( origin.y() + maxT[whichPlane] * dir.y() ); result.setZ( origin.z() + maxT[whichPlane] * dir.z() ); break; case 1: result.setX( origin.x() + maxT[whichPlane] * dir.x() ); result.setZ( origin.z() + maxT[whichPlane] * dir.z() ); break; case 2: result.setX( origin.x() + maxT[whichPlane] * dir.x() ); result.setY( origin.y() + maxT[whichPlane] * dir.y() ); break; default: throw new InternalError("XXX"); } } return result; // ray hits box } /** * Get the size of this AABBox where the size is represented by the * length of the vector between low and high. * @return a float representing the size of the AABBox */ public final float getSize() { return lo.dist(hi); } /** Returns computed center of this AABBox of {@link #getLow()} and {@link #getHigh()}. */ public final Vec3f getCenter() { return center; } /** * Scale this AABBox by a constant around fixed center *
* high and low is recomputed by scaling its distance to fixed center. *
* @param s scale factor * @return this AABBox for chaining * @see #scale2(float, float[]) */ public final AABBox scale(final float s) { final Vec3f tmp = new Vec3f(); tmp.set(hi).sub(center).scale(s); hi.set(center).add(tmp); tmp.set(lo).sub(center).scale(s); lo.set(center).add(tmp); return this; } /** * Scale this AABBox by constants around fixed center ** high and low is recomputed by scaling its distance to fixed center. *
* @param sX horizontal scale factor * @param sY vertical scale factor * @param sZ Z-axis scale factor * @return this AABBox for chaining * @see #scale2(float, float[]) */ public final AABBox scale(final float sX, final float sY, final float sZ) { final Vec3f tmp = new Vec3f(); tmp.set(hi).sub(center).scale(sX, sY, sZ); hi.set(center).add(tmp); tmp.set(lo).sub(center).scale(sX, sY, sZ); lo.set(center).add(tmp); return this; } /** * Scale this AABBox by a constant, recomputing center ** high and low is scaled and center recomputed. *
* @param s scale factor * @return this AABBox for chaining * @see #scale(float, float[]) */ public final AABBox scale2(final float s) { hi.scale(s); lo.scale(s); computeCenter(); return this; } /** * Scale this AABBox by constants, recomputing center ** high and low is scaled and center recomputed. *
* @param sX horizontal scale factor * @param sY vertical scale factor * @param sZ Z-axis scale factor * @return this AABBox for chaining * @see #scale(float, float[]) */ public final AABBox scale2(final float sX, final float sY, final float sZ) { hi.scale(sX, sY, sZ); lo.scale(sX, sY, sZ); computeCenter(); return this; } /** * Translate this AABBox by a float[3] vector * @param dx the translation x-component * @param dy the translation y-component * @param dz the translation z-component * @param t the float[3] translation vector * @return this AABBox for chaining */ public final AABBox translate(final float dx, final float dy, final float dz) { lo.add(dx, dy, dz); hi.add(dx, dy, dz); computeCenter(); return this; } /** * Translate this AABBox by a float[3] vector * @param t the float[3] translation vector * @return this AABBox for chaining */ public final AABBox translate(final Vec3f t) { lo.add(t); hi.add(t); computeCenter(); return this; } /** * Rotate this AABBox by a float[3] vector * @param quat the {@link Quaternion} used for rotation * @return this AABBox for chaining */ public final AABBox rotate(final Quaternion quat) { quat.rotateVector(lo, lo); quat.rotateVector(hi, hi); computeCenter(); return this; } public final float getMinX() { return lo.x(); } public final float getMinY() { return lo.y(); } public final float getMinZ() { return lo.z(); } public final float getMaxX() { return hi.x(); } public final float getMaxY() { return hi.y(); } public final float getMaxZ() { return hi.z(); } public final float getWidth(){ return hi.x() - lo.x(); } public final float getHeight() { return hi.y() - lo.y(); } public final float getDepth() { return hi.z() - lo.z(); } /** Returns the volume, i.e. width * height * depth */ public final float getVolume() { return getWidth() * getHeight() * getDepth(); } /** Return true if {@link #getVolume()} is {@link FloatUtil#isZero(float)}, considering epsilon. */ public final boolean hasZeroVolume() { return FloatUtil.isZero(getVolume()); } /** Returns the assumed 2D area, i.e. width * height while assuming low and high lies on same plane. */ public final float get2DArea() { return getWidth() * getHeight(); } /** Return true if {@link #get2DArea()} is {@link FloatUtil#isZero(float)}, considering epsilon. */ public final boolean hasZero2DArea() { return FloatUtil.isZero(get2DArea()); } @Override public final boolean equals(final Object obj) { if( obj == this ) { return true; } if( null == obj || !(obj instanceof AABBox) ) { return false; } final AABBox other = (AABBox) obj; return lo.isEqual(other.lo) && hi.isEqual(other.hi); } @Override public final int hashCode() { throw new InternalError("hashCode not designed"); } /** * Transform this box using the given {@link Matrix4f} into {@code out} * @param mat transformation {@link Matrix4f} * @param out the resulting {@link AABBox} * @return the resulting {@link AABBox} for chaining */ public AABBox transform(final Matrix4f mat, final AABBox out) { final Vec3f tmp = new Vec3f(); out.reset(); out.resize( mat.mulVec3f(lo, tmp) ); out.resize( mat.mulVec3f(hi, tmp) ); out.computeCenter(); return out; } /** * Assume this bounding box as being in object space and * compute the window bounding box. *
* If useCenterZ
is true
,
* only 4 {@link FloatUtil#mapObjToWin(float, float, float, float[], int[], float[], float[], float[]) mapObjToWinCoords}
* operations are made on points [1..4] using {@link #getCenter()}'s z-value.
* Otherwise 8 {@link FloatUtil#mapObjToWin(float, float, float, float[], int[], float[], float[], float[]) mapObjToWinCoords}
* operation on all 8 points are performed.
*
* .z() ------ [4] * | | * | | * .y() ------ [3] ** @param mat4PMv [projection] x [modelview] matrix, i.e. P x Mv * @param viewport viewport rectangle * @param useCenterZ * @param vec3Tmp0 3 component vector for temp storage * @param vec4Tmp1 4 component vector for temp storage * @param vec4Tmp2 4 component vector for temp storage * @return */ public AABBox mapToWindow(final AABBox result, final Matrix4f mat4PMv, final Recti viewport, final boolean useCenterZ) { final Vec3f tmp = new Vec3f(); final Vec3f winPos = new Vec3f(); { final float objZ = useCenterZ ? center.z() : getMinZ(); result.reset(); Matrix4f.mapObjToWin(tmp.set(getMinX(), getMinY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMinX(), getMaxY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMaxX(), getMaxY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMaxX(), getMinY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); } if( !useCenterZ ) { final float objZ = getMaxZ(); Matrix4f.mapObjToWin(tmp.set(getMinX(), getMinY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMinX(), getMaxY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMaxX(), getMaxY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); Matrix4f.mapObjToWin(tmp.set(getMaxX(), getMinY(), objZ), mat4PMv, viewport, winPos); result.resize(winPos); } if( DEBUG ) { System.err.printf("AABBox.mapToWindow: view[%s], this %s -> %s%n", viewport, toString(), result.toString()); } return result; } @Override public final String toString() { return "[dim "+getWidth()+" x "+getHeight()+" x "+getDepth()+ ", box "+lo+" .. "+hi+", ctr "+center+"]"; } }