Jarrah Technology logo

March 20, 2013

Category: News

Author: Charles

Testing for Partial Visibility Twitter Facebook Share on Google+

As Concealed Intent is a 3D game with objects free to move in any direction, development often throws up interesting 3D geometry problems (see old problems here and here). The latest problem was testing to see if an object is partially visible, or rather, if something is slightly blocking the view. Imagine that you have a spaceship and you want to know not only if it is visible on the screen, but also if a part of it is behind a planet or another ship.

There are well-known good solutions to finding if an object is on the screen or if there is something inbetween the camera and a single point in space (using Physics.Linecast). However, I wanted to know if part of the object was hidden and part visible. That is, if the object’s edge projected onto the plane with the line from the object’s center to the camera as its normal, is anything intersecting it when viewed from the camera. Luckily for me the objects I’m interested in are spheres and so become circles when projected onto a plane. Furthermore, I only tested 4 diametrically opposed points on these circles (plus the center). The problem then is to derive these points.

Firstly we know the position of the camera position C, center of the object O and its radius r. Thus the plane passing through the point O with the normal CO is the plane the object needs to be projected onto. Since the object is a sphere, all that is required is to get orthogonal axes on this plane passing through O and the move along them r distance in each direction (giving 4 opposite points on the projected circle). To get one such axis cross the plane normal with any vector other than the normal itself. To get the next axis cross the derived axis with the normal. After normalising the axis vectors, multiply each by the radius and the 4 opposite points are O plus/minus these two vectors. In Unity3D use Physics.Linecast to test if there is anything inbetween the camera and these points.

Below is the C# code I used to perform these tests. The result is an array of five booleans corresponding to whether the view of the object center and 4 opposite bounding points is obscured by an intervening object. What is done after this is up to the programmer. I used the code to ensure that when focusing on an object it wasn’t partially blocked. If it was blocked than the camera was moved in the direction opposite to the blocking object and the view tested again. For reference, I found the radius with the code ((SphereCollider)focusEntity.collider).radius * focusEntity.transform.lossyScale.x .

bool[] IsObscured(Vector3 cameraPos, Vector3 focus, float radius) {
    Vector3 cameraNormal = (cameraPos - focus).normalized;
    Vector3 planeAxis1 = Vector3.Cross(cameraNormal, cameraNormal != Vector3.forward ? Vector3.forward : Vector3.right).normalized;
    Vector3 planeAxis2 = Vector3.Cross(planeAxis1, cameraNormal).normalized
    return Obscured(cameraPos, focusEntity.transform, planeAxis1, planeAxis2, radius);
}

bool[] Obscured(Vector3 camera, Transform center, Vector3 axis1, Vector3 axis2, float radius) {
    bool[] result = new bool[5];
    result[0] = Intercepted(camera, center.position, center);
    result[1] = Intercepted(camera, center.position + (radius * axis1), center);
    result[2] = Intercepted(camera, center.position - (radius * axis1), center);
    result[3] = Intercepted(camera, center.position + (radius * axis2), center);
    result[4] = Intercepted(camera, center.position - (radius * axis2), center);
    return result;
}

// Returns true iff linecast from<->to does not hit anything other than targetTrans
bool Intercepted(Vector3 from, Vector3 to, Transform targetTrans) {
    RaycastHit hit;
    return Physics.Linecast(from, to, out hit) && hit.transform != targetTrans;
}
Tags: Unity and Technical
comments powered by Disqus