From 32dfe511a48d924cf5d4b32336b7f46ac0da062b Mon Sep 17 00:00:00 2001
From: David Adam <david.adam.cnrs@gmail.com>
Date: Fri, 14 Nov 2008 19:56:41 +0100
Subject: ComputeSphereVisibility

---
 dlls/ddraw/device.c |  165 +++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 141 insertions(+), 24 deletions(-)

diff --git a/dlls/ddraw/device.c b/dlls/ddraw/device.c
index 7efc66f..3422d06 100644
--- a/dlls/ddraw/device.c
+++ b/dlls/ddraw/device.c
@@ -46,6 +46,7 @@
 
 #include "ddraw.h"
 #include "d3d.h"
+#include "d3drmdef.h"
 
 #include "ddraw_private.h"
 #include "wine/debug.h"
@@ -4722,6 +4723,56 @@ Thunk_IDirect3DDeviceImpl_3_DrawIndexedPrimitiveVB(IDirect3DDevice3 *iface,
  *  is singular)
  *
  *****************************************************************************/
+
+D3DVECTOR* get_point(D3DVECTOR*  point, const D3DVECTOR* normal,
+    const D3DVECTOR* center, const D3DVALUE radius)
+{
+    float norm;
+
+    norm = sqrt(normal->u1.x * normal->u1.x + normal->u2.y * normal->u2.y + normal->u3.z * normal->u3.z);
+
+    point->u1.x = radius * normal->u1.x / norm + center->u1.x;
+    point->u2.y = radius * normal->u2.y / norm + center->u2.y;
+    point->u3.z = radius * normal->u3.z / norm + center->u3.z;
+
+    return point;
+}
+
+static DWORD* in_plane(DWORD* partial_visibility, const unsigned int decay, const D3DVECTOR* normal, const D3DVALUE origin_plane, 
+    const D3DVECTOR* point1, const D3DVECTOR* point2)
+{
+    float dot_product1, dot_product2;
+
+    dot_product1 = origin_plane + normal->u1.x * point1->u1.x + normal->u2.y * point1->u2.y + normal->u3.z * point1->u3.z;
+    dot_product2 = origin_plane + normal->u1.x * point2->u1.x + normal->u2.y * point2->u2.y + normal->u3.z * point2->u3.z;
+    
+    if  ( (dot_product1 >= 0.0f) && (dot_product2 >= 0.0f) ) *partial_visibility  = 0;
+    if  ( (dot_product1 >= 0.0f) && (dot_product2 < 0.0f) ) *partial_visibility  = 1 << decay;
+    if  ( (dot_product1 < 0.0f) && (dot_product2 < 0.0f) ) *partial_visibility  = 2 << decay;
+
+    return partial_visibility;
+}
+
+static D3DVALUE determinant(const D3DMATRIX *pm)
+{
+    D3DVALUE det, t[6], tmp[4];
+
+    t[0] = pm->_33 * pm->_44 - pm->_43 * pm->_34;
+    t[1] = pm->_32 * pm->_43 - pm->_42 * pm->_34;
+    t[2] = pm->_32 * pm->_43 - pm->_42 * pm->_33;
+    t[3] = pm->_31 * pm->_44 - pm->_41 * pm->_34;
+    t[4] = pm->_31 * pm->_43 - pm->_41 * pm->_33;
+    t[5] = pm->_31 * pm->_42 - pm->_41 * pm->_32;
+ 
+    tmp[0] = pm->_22 * t[0] - pm->_23 * t[1] + pm->_24 * t[2];
+    tmp[1] = pm->_21 * t[0] - pm->_23 * t[3] + pm->_24 * t[4];
+    tmp[2] = pm->_21 * t[1] - pm->_22 * t[3] + pm->_24 * t[5];
+    tmp[3] = pm->_21 * t[2] - pm->_22 * t[4] + pm->_23 * t[5];
+ 
+    det = pm->_11 * tmp[0] - pm->_12 * tmp[1] + pm->_13 * tmp[2] - pm->_14 * tmp[3]; 
+    return det;
+}
+
 static HRESULT WINAPI
 IDirect3DDeviceImpl_7_ComputeSphereVisibility(IDirect3DDevice7 *iface,
                                               D3DVECTOR *Centers,
@@ -4730,30 +4781,96 @@ IDirect3DDeviceImpl_7_ComputeSphereVisibility(IDirect3DDevice7 *iface,
                                               DWORD Flags,
                                               DWORD *ReturnValues)
 {
-    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice7, iface);
-    FIXME("(%p)->(%p,%p,%08x,%08x,%p): stub!\n", This, Centers, Radii, NumSpheres, Flags, ReturnValues);
-
-    /* the DirectX 7 sdk says that the visibility is computed by
-     * back-transforming the viewing frustum to model space
-     * using the inverse of the combined world, view and projection
-     * matrix. If the matrix can't be reversed, D3DERR_INVALIDMATRIX
-     * is returned.
-     *
-     * Basic implementation idea:
-     * 1) Check if the center is in the viewing frustum
-     * 2) Cut the sphere with the planes of the viewing
-     *    frustum
-     *
-     * ->Center inside the frustum, no intersections:
-     *    Fully visible
-     * ->Center outside the frustum, no intersections:
-     *    Not visible
-     * ->Some intersections: Partially visible
-     *
-     * Implement this call in WineD3D. Either implement the
-     * matrix and vector stuff in WineD3D, or use some external
-     * math library.
-     */
+    D3DMATRIX m, temp;
+    D3DVECTOR point1, point2, vec;
+    DWORD partial_visibility, visibility;
+    float origin_plane;
+    int i;
+
+    TRACE("(%p)->(%p,%p,%08x,%08x,%p): stub!\n", iface, Centers, Radii, NumSpheres, Flags, ReturnValues);
+
+    if (!Centers || !Radii || !ReturnValues ) return DDERR_INVALIDPARAMS;
+
+    IDirect3DDeviceImpl_7_GetTransform(iface, D3DTRANSFORMSTATE_WORLD, &m);
+
+    IDirect3DDeviceImpl_7_GetTransform(iface, D3DTRANSFORMSTATE_VIEW, &temp);
+    multiply_matrix(&m, &m, &temp);
+
+    IDirect3DDeviceImpl_7_GetTransform(iface, D3DTRANSFORMSTATE_PROJECTION, &temp);
+    multiply_matrix(&m, &m, &temp);
+
+    if (abs(determinant(&m)) < 0.0001f) return D3DERR_INVALIDMATRIX;
+
+    for(i=0; i<NumSpheres; i++)
+    {
+        visibility = 0;
+
+/* Left plane */
+        vec.u1.x = m._14 + m._11;
+        vec.u2.y = m._24 + m._21;
+        vec.u3.z = m._34 + m._31;
+        origin_plane = m._44 + m._41;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 2, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+
+/* Right plane */
+        vec.u1.x = m._14 - m._11;
+        vec.u2.y = m._24 - m._21;
+        vec.u3.z = m._34 - m._31;
+        origin_plane = m._44 - m._41;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 4, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+      
+/* Bottom plane */
+        vec.u1.x = m._14 + m._12;
+        vec.u2.y = m._24 + m._22;
+        vec.u3.z = m._34 + m._32;
+        origin_plane = m._44 + m._42;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 8, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+     
+/* Top plane */
+        vec.u1.x = m._14 - m._12;
+        vec.u2.y = m._24 - m._22;
+        vec.u3.z = m._34 - m._32;
+        origin_plane = m._44 - m._42;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 6, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+     
+/* Near plane */
+        vec.u1.x = m._13;
+        vec.u2.y = m._23;
+        vec.u3.z = m._33;
+        origin_plane = m._43;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 10, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+
+/* Far plane*/
+        vec.u1.x = m._14 - m._13;
+        vec.u2.y = m._24 - m._23;
+        vec.u3.z = m._34 - m._33;
+        origin_plane = m._44 - m._43;
+
+        get_point(&point1, &vec, &Centers[i], Radii[i]);
+        get_point(&point2, &vec, &Centers[i], -Radii[i]);
+        if (!in_plane(&partial_visibility, 12, &vec, origin_plane, &point1, &point2))
+            visibility = visibility | partial_visibility;
+    }
 
     return D3D_OK;
 }
-- 
1.6.0.2

