/*
* 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.terasology.logic.world.WorldProvider;
import org.terasology.teraspout.TeraBlock;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import java.util.ArrayList;
import java.util.Collections;
/**
* Provides support for ray-box intersection tests.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
*/
public class RayBlockIntersection {
/**
* Represents an intersection of a ray with the face of a block.
*
* @author Benjamin Glatzel <benjamin.glatzel@me.com>
*/
public static class Intersection implements Comparable<Intersection> {
private final double _t;
private final Vector3d _surfaceNormal;
private final BlockPosition _blockPosition;
public Intersection(BlockPosition blockPosition, Vector3d normal, double d, double t, Vector3d rayOrigin, Vector3d rayDirection, Vector3d intersectionPoint) {
this._t = t;
this._surfaceNormal = normal;
this._blockPosition = blockPosition;
}
public int compareTo(Intersection o) {
if (o == null) {
return 0;
}
double distance = _t;
double distance2 = o._t;
if (distance == distance2)
return 0;
return distance2 > distance ? -1 : 1;
}
public boolean equals(Object o) {
if (o.getClass() != Intersection.class)
return false;
Intersection i = (Intersection) o;
return this._blockPosition.equals(i.getBlockPosition());
}
public Vector3f getSurfaceNormal() {
return new Vector3f(_surfaceNormal);
}
public BlockPosition calcAdjacentBlockPos() {
Vector3d pos = getBlockPosition().toVector3d();
pos.add(_surfaceNormal);
return new BlockPosition(pos);
}
public BlockPosition getBlockPosition() {
return _blockPosition;
}
@Override
public String toString() {
return String.format("x: %.2d y: %.2d z: %.2d", _blockPosition.x, _blockPosition.y, _blockPosition.z);
}
}
/**
* Calculates the intersection of a given ray originating from a specified point with
* a block. Returns a list of intersections ordered by the distance to the player.
*
* @param w The world provider
* @param x The block's position on the x-axis
* @param y The block's position on the y-axis
* @param z The block's position on the z-axis
* @param rayOrigin The origin of the ray
* @param rayDirection The direction of the ray
* @return The list of intersections
*/
public static ArrayList<Intersection> executeIntersection(WorldProvider w, int x, int y, int z, Vector3d rayOrigin, Vector3d rayDirection) {
TeraBlock block = w.getBlock(x, y, z);
/*
* Ignore invisible blocks.
*/
if (block.isInvisible()) {
return null;
}
ArrayList<Intersection> result = new ArrayList<Intersection>();
for (AABB blockAABB : block.getColliders(x, y, z)) {
/*
* Fetch all vertices of the specified block.
*/
Vector3d[] vertices = blockAABB.getVertices();
BlockPosition blockPos = new BlockPosition(x, y, z);
/*
* Generate a new intersection for each side of the block.
*/
// Front
Intersection is = executeBlockFaceIntersection(blockPos, vertices[0], vertices[1], vertices[3], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
// Back
is = executeBlockFaceIntersection(blockPos, vertices[4], vertices[7], vertices[5], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
// Left
is = executeBlockFaceIntersection(blockPos, vertices[4], vertices[0], vertices[7], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
// Right
is = executeBlockFaceIntersection(blockPos, vertices[5], vertices[6], vertices[1], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
// Top
is = executeBlockFaceIntersection(blockPos, vertices[7], vertices[3], vertices[6], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
// Bottom
is = executeBlockFaceIntersection(blockPos, vertices[4], vertices[5], vertices[0], rayOrigin, rayDirection);
if (is != null) {
result.add(is);
}
}
// Sort the intersections by distance to the player
Collections.sort(result);
return result;
}
/**
* Calculates an intersection with the face of a block defined by three points.
*
* @param blockPos The position of the block to intersect with
* @param v0 Point 1
* @param v1 Point 2
* @param v2 Point 3
* @param origin Origin of the intersection ray
* @param ray Direction of the intersection ray
* @return Ray-face-intersection
*/
private static Intersection executeBlockFaceIntersection(BlockPosition blockPos, Vector3d v0, Vector3d v1, Vector3d v2, Vector3d origin, Vector3d ray) {
// Calculate the plane to intersect with
Vector3d a = new Vector3d();
a.sub(v1, v0);
Vector3d b = new Vector3d();
b.sub(v2, v0);
Vector3d norm = new Vector3d();
norm.cross(a, b);
double d = -(norm.x * v0.x + norm.y * v0.y + norm.z * v0.z);
/**
* Calculate the distance on the ray, where the intersection occurs.
*/
double t = -(norm.x * origin.x + norm.y * origin.y + norm.z * origin.z + d) / ray.dot(norm);
// No intersection possible
if (t < 0)
return null;
/**
* Calc. the point of intersection.
*/
Vector3d intersectPoint = new Vector3d(ray.x * t, ray.y * t, ray.z * t);
intersectPoint.add(intersectPoint, origin);
/**
* Check if the point lies on block's face.
*/
if (intersectPoint.x >= v0.x && intersectPoint.x <= Math.max(v1.x, v2.x) && intersectPoint.y >= v0.y && intersectPoint.y <= Math.max(v1.y, v2.y) && intersectPoint.z >= v0.z && intersectPoint.z <= Math.max(v1.z, v2.z)) {
return new Intersection(blockPos, norm, d, t, origin, ray, intersectPoint);
}
// Point of intersection was NOT lying on the block's face
return null;
}
}