/*
* Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.model.structures;
import org.lwjgl.opengl.GL11;
import org.terasology.game.CoreRegistry;
import org.terasology.logic.manager.ShaderManager;
import org.terasology.rendering.world.WorldRenderer;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import java.util.ArrayList;
import java.util.Iterator;
import static org.lwjgl.opengl.GL11.*;
/**
* An axis-aligned bounding box. Provides basic support for inclusion
* and intersection tests.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
*/
public class AABB {
private final Vector3d _position = new Vector3d();
private final Vector3d _dimensions;
private Vector3d[] _vertices;
private int _displayListWire = -1;
private int _displayListSolid = -1;
/**
* Creates a new AABB at the given position with the given dimensions.
*
* @param position The position
* @param dimensions The dimensions
*/
public AABB(Vector3d position, Vector3d dimensions) {
setPosition(position);
_dimensions = dimensions;
}
public AABB(Vector3f position, Vector3f dimensions) {
setPosition(new Vector3d(position));
_dimensions = new Vector3d(dimensions);
}
/**
* Creates a new AABB that encapsulates a set of AABBs
*
* @param others
*/
public AABB(Iterable<AABB> others) {
Iterator<AABB> i = others.iterator();
if (!i.hasNext()) {
_dimensions = new Vector3d();
} else {
AABB first = i.next();
Vector3d min = new Vector3d(first.minX(), first.minY(), first.minZ());
Vector3d max = new Vector3d(first.maxX(), first.maxY(), first.maxZ());
while (i.hasNext()) {
AABB next = i.next();
if (next.minX() < min.x) {
min.x = next.minX();
}
if (next.minY() < min.y) {
min.y = next.minY();
}
if (next.minZ() < min.z) {
min.z = next.minZ();
}
if (next.maxX() > max.x) {
max.x = next.maxX();
}
if (next.maxY() > max.y) {
max.y = next.maxY();
}
if (next.maxZ() > max.z) {
max.z = next.maxZ();
}
}
_position.set(max);
_position.add(min);
_position.scale(0.5);
_dimensions = new Vector3d(max);
_dimensions.sub(min);
_dimensions.scale(0.5);
}
}
/**
* Returns true if this AABB overlaps the given AABB.
*
* @param aabb2 The AABB to check for overlapping
* @return True if overlapping
*/
public boolean overlaps(AABB aabb2) {
return !(maxX() < aabb2.minX() || minX() > aabb2.maxX()) &&
!(maxY() < aabb2.minY() || minY() > aabb2.maxY()) &&
!(maxZ() < aabb2.minZ() || minZ() > aabb2.maxZ());
}
/**
* Returns true if the AABB contains the given point.
*
* @param point The point to check for inclusion
* @return True if containing
*/
public boolean contains(Vector3d point) {
return !(maxX() < point.x || minX() > point.x) &&
!(maxY() < point.y || minY() > point.y) &&
!(maxZ() < point.z || minZ() > point.z);
}
public boolean contains(Vector3f point) {
return !(maxX() < point.x || minX() > point.x) &&
!(maxY() < point.y || minY() > point.y) &&
!(maxZ() < point.z || minZ() > point.z);
}
/**
* Returns the closest point on the AABB to a given point.
*
* @param p The point
* @return The point on the AABB closest to the given point
*/
public Vector3d closestPointOnAABBToPoint(Vector3d p) {
Vector3d r = new Vector3d(p);
if (p.x < minX()) r.x = minX();
if (p.x > maxX()) r.x = maxX();
if (p.y < minY()) r.y = minY();
if (p.y > maxY()) r.y = maxY();
if (p.z < minZ()) r.z = minZ();
if (p.z > maxZ()) r.z = maxZ();
return r;
}
public Vector3d getFirstHitPlane(Vector3d direction, Vector3d pos, Vector3d dimensions, boolean testX, boolean testY, boolean testZ) {
Vector3d hitNormal = new Vector3d();
double dist = Double.POSITIVE_INFINITY;
if (testX) {
double distX;
if (direction.x > 0) {
distX = (_position.x - pos.x - dimensions.x - _dimensions.x) / direction.x;
} else {
distX = (_position.x - pos.x + dimensions.x + _dimensions.x) / direction.x;
}
if (distX >= 0 && distX < dist) {
hitNormal.set(Math.copySign(1, direction.x), 0, 0);
}
}
if (testY) {
double distY;
if (direction.y > 0) {
distY = (_position.y - pos.y - dimensions.y - _dimensions.y) / direction.y;
} else {
distY = (_position.y - pos.y + dimensions.y + _dimensions.y) / direction.y;
}
if (distY >= 0 && distY < dist) {
hitNormal.set(0, Math.copySign(1, direction.y), 0);
}
}
if (testZ) {
double distZ;
if (direction.z > 0) {
distZ = (_position.z - pos.z - dimensions.z - _dimensions.z) / direction.z;
} else {
distZ = (_position.z - pos.z + dimensions.z + _dimensions.z) / direction.z;
}
if (distZ >= 0 && distZ < dist) {
hitNormal.set(0, 0, Math.copySign(1, direction.z));
}
}
return hitNormal;
}
/**
* Returns the normal of the plane closest to the given origin.
*
* @param pointOnAABB A point on the AABB
* @param origin The origin
* @param testX True if the x-axis should be tested
* @param testY True if the y-axis should be tested
* @param testZ True if the z-axis should be tested
* @return The normal
*/
public Vector3d normalForPlaneClosestToOrigin(Vector3d pointOnAABB, Vector3d origin, boolean testX, boolean testY, boolean testZ) {
ArrayList<Vector3d> normals = new ArrayList<Vector3d>();
if (pointOnAABB.z == minZ() && testZ) normals.add(new Vector3d(0, 0, -1));
if (pointOnAABB.z == maxZ() && testZ) normals.add(new Vector3d(0, 0, 1));
if (pointOnAABB.x == minX() && testX) normals.add(new Vector3d(-1, 0, 0));
if (pointOnAABB.x == maxX() && testX) normals.add(new Vector3d(1, 0, 0));
if (pointOnAABB.y == minY() && testY) normals.add(new Vector3d(0, -1, 0));
if (pointOnAABB.y == maxY() && testY) normals.add(new Vector3d(0, 1, 0));
double minDistance = Double.MAX_VALUE;
Vector3d closestNormal = new Vector3d();
for (int i = 0; i < normals.size(); i++) {
Vector3d n = normals.get(i);
Vector3d diff = new Vector3d(centerPointForNormal(n));
diff.sub(origin);
double distance = diff.length();
if (distance < minDistance) {
minDistance = distance;
closestNormal = n;
}
}
return closestNormal;
}
/**
* Returns the center point of one of the six planes for the given normal.
*
* @param normal The normal
* @return The center point
*/
public Vector3d centerPointForNormal(Vector3d normal) {
if (normal.x == 1 && normal.y == 0 && normal.z == 0)
return new Vector3d(getPosition().x + _dimensions.x, getPosition().y, getPosition().z);
if (normal.x == -1 && normal.y == 0 && normal.z == 0)
return new Vector3d(getPosition().x - _dimensions.x, getPosition().y, getPosition().z);
if (normal.x == 0 && normal.y == 0 && normal.z == 1)
return new Vector3d(getPosition().x, getPosition().y, getPosition().z + _dimensions.z);
if (normal.x == 0 && normal.y == 0 && normal.z == -1)
return new Vector3d(getPosition().x, getPosition().y, getPosition().z - _dimensions.z);
if (normal.x == 0 && normal.y == 1 && normal.z == 0)
return new Vector3d(getPosition().x, getPosition().y + _dimensions.y, getPosition().z);
if (normal.x == 0 && normal.y == -1 && normal.z == 0)
return new Vector3d(getPosition().x, getPosition().y - _dimensions.y, getPosition().z);
return new Vector3d();
}
/**
* Returns the vertices of this AABB.
*
* @return The vertices
*/
public Vector3d[] getVertices() {
if (_vertices == null) {
Vector3d[] vertices = new Vector3d[8];
// Front
vertices[0] = new Vector3d(minX(), minY(), maxZ());
vertices[1] = new Vector3d(maxX(), minY(), maxZ());
vertices[2] = new Vector3d(maxX(), maxY(), maxZ());
vertices[3] = new Vector3d(minX(), maxY(), maxZ());
// Back
vertices[4] = new Vector3d(minX(), minY(), minZ());
vertices[5] = new Vector3d(maxX(), minY(), minZ());
vertices[6] = new Vector3d(maxX(), maxY(), minZ());
vertices[7] = new Vector3d(minX(), maxY(), minZ());
_vertices = vertices;
}
return _vertices;
}
/**
* Renders this AABB.
* <p/>
*
* @param lineThickness The thickness of the line
*/
public void render(float lineThickness) {
ShaderManager.getInstance().enableDefault();
glPushMatrix();
Vector3d cameraPosition = CoreRegistry.get(WorldRenderer.class).getActiveCamera().getPosition();
glTranslated(getPosition().x - cameraPosition.x, -cameraPosition.y, getPosition().z - cameraPosition.z);
renderLocally(lineThickness);
glPopMatrix();
}
public void renderLocally(float lineThickness) {
ShaderManager.getInstance().enableDefault();
if (_displayListWire == -1) {
generateDisplayListWire();
}
glPushMatrix();
glTranslated(0f, getPosition().y, 0f);
glLineWidth(lineThickness);
glCallList(_displayListWire);
glPopMatrix();
}
public void renderSolidLocally() {
ShaderManager.getInstance().enableDefault();
if (_displayListSolid == -1) {
generateDisplayListSolid();
}
glPushMatrix();
glTranslated(0f, getPosition().y, 0f);
glScalef(1.5f, 1.5f, 1.5f);
glCallList(_displayListSolid);
glPopMatrix();
}
private void generateDisplayListSolid() {
_displayListSolid = glGenLists(1);
glNewList(_displayListSolid, GL11.GL_COMPILE);
glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, _dimensions.z);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, _dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(_dimensions.x, _dimensions.y, -_dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, -_dimensions.z);
GL11.glVertex3d(_dimensions.x, -_dimensions.y, _dimensions.z);
GL11.glVertex3d(-_dimensions.x, -_dimensions.y, _dimensions.z);
glEnd();
glEndList();
}
private void generateDisplayListWire() {
double offset = 0.001;
_displayListWire = glGenLists(1);
glNewList(_displayListWire, GL11.GL_COMPILE);
glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
// FRONT
glBegin(GL_LINE_LOOP);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, -_dimensions.z - offset);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, -_dimensions.z - offset);
glEnd();
// BACK
glBegin(GL_LINE_LOOP);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, +_dimensions.z + offset);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, +_dimensions.z + offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, +_dimensions.z + offset);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, +_dimensions.z + offset);
glEnd();
// TOP
glBegin(GL_LINE_LOOP);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, +_dimensions.z + offset);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, +_dimensions.z + offset);
glEnd();
// BOTTOM
glBegin(GL_LINE_LOOP);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, +_dimensions.z + offset);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, +_dimensions.z + offset);
glEnd();
// LEFT
glBegin(GL_LINE_LOOP);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(-_dimensions.x - offset, -_dimensions.y - offset, +_dimensions.z + offset);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, +_dimensions.z + offset);
glVertex3d(-_dimensions.x - offset, +_dimensions.y + offset, -_dimensions.z - offset);
glEnd();
// RIGHT
glBegin(GL_LINE_LOOP);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, -_dimensions.z - offset);
glVertex3d(+_dimensions.x + offset, -_dimensions.y - offset, +_dimensions.z + offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, +_dimensions.z + offset);
glVertex3d(+_dimensions.x + offset, +_dimensions.y + offset, -_dimensions.z - offset);
glEnd();
glEndList();
}
public double minX() {
return getPosition().x - _dimensions.x;
}
public double minY() {
return getPosition().y - _dimensions.y;
}
public double minZ() {
return getPosition().z - _dimensions.z;
}
public double maxX() {
return getPosition().x + _dimensions.x;
}
public double maxY() {
return getPosition().y + _dimensions.y;
}
public double maxZ() {
return getPosition().z + _dimensions.z;
}
public Vector3d getDimensions() {
return _dimensions;
}
public Vector3d getPosition() {
return _position;
}
public void setPosition(Vector3d position) {
_position.set(position);
}
}