/// <summary>
 /// Scale a box.
 /// </summary>
 /// <param name="box">The box to scale.</param>
 /// <param name="scale">The amount to scale in each direction.</param>
 /// <returns>The scaled box.</returns>
 public static Box3F Scale(ref Box3F box, ref Vector3 scale)
 {
     Vector3 center = 0.5f * (box.Max + box.Min);
     Vector3 extent = 0.5f * (box.Max - box.Min);
     extent.X *= scale.X;
     extent.Y *= scale.Y;
     extent.Z *= scale.Z;
     Box3F outBox = new Box3F();
     outBox.Min = center - extent;
     outBox.Max = center + extent;
     return outBox;
 }
 /// <summary>
 /// Copies a render instance's fields into this render instance.
 /// </summary>
 /// <param name="src">The render instance to copy.</param>
 public void Copy(RenderInstance src)
 {
     VertexBuffer = src.VertexBuffer;
     IndexBuffer = src.IndexBuffer;
     VertexDeclaration = src.VertexDeclaration;
     Material = src.Material;
     ObjectTransform = src.ObjectTransform;
     WorldBox = src.WorldBox;
     UTextureAddressMode = src.UTextureAddressMode;
     VTextureAddressMode = src.VTextureAddressMode;
     VertexSize = src.VertexSize;
     PrimitiveType = src.PrimitiveType;
     BaseVertex = src.BaseVertex;
     VertexCount = src.VertexCount;
     StartIndex = src.StartIndex;
     PrimitiveCount = src.PrimitiveCount;
     Type = src.Type;
     SortPoint = src.SortPoint;
     IsSortPointSet = src.IsSortPointSet;
     Opacity = src.Opacity;
 }
        public bool IsObjectObscured(Vector3 camPos, Box3F bounds)
        {
            // do a fast radius check to see if any part of the object could be
            // within the fog far distance
            _objBoundsMin = bounds.Min;
            _objBoundsMax = bounds.Max;

            float radius = Math.Max(_objBoundsMax.X - _objBoundsMin.X,
                                Math.Max(_objBoundsMax.Y - _objBoundsMin.Y,
                                        _objBoundsMax.Z - _objBoundsMin.Z));

            if (Vector3.Distance(camPos, bounds.Center) - radius < _fogFarDistance)
                return false;

            return true;
        }
        public bool IsObjectFogged(Vector3 camPos, Box3F bounds)
        {
            // do a fast radius check to see if any part of the object could be completely
            // inside the unfogged area close to the camera
            _objBoundsMin = bounds.Min;
            _objBoundsMax = bounds.Max;

            float radius = Math.Max(_objBoundsMax.X - _objBoundsMin.X,
                                Math.Max(_objBoundsMax.Y - _objBoundsMin.Y,
                                        _objBoundsMax.Z - _objBoundsMin.Z));

            if (Vector3.Distance(camPos, bounds.Center) + radius < _fogNearDistance)
                return false;

            return true;
        }
        /// <summary>
        /// Transforms a box with a specified transform.
        /// </summary>
        /// <param name="inBox">The box to transform.</param>
        /// <param name="transform">The transform.</param>
        /// <returns>The transformed box.</returns>
        public static Box3F Transform(ref Box3F inBox, ref Matrix transform)
        {
            Vector3 center = 0.5f * (inBox.Min + inBox.Max);
            Vector3 extent = inBox.Max - center;

            Vector3 newExtent = new Vector3(
                    extent.X * Math.Abs(transform.M11) + extent.Y * Math.Abs(transform.M21) + extent.Z * Math.Abs(transform.M31),
                    extent.X * Math.Abs(transform.M12) + extent.Y * Math.Abs(transform.M22) + extent.Z * Math.Abs(transform.M32),
                    extent.X * Math.Abs(transform.M13) + extent.Y * Math.Abs(transform.M23) + extent.Z * Math.Abs(transform.M33));

            center = Vector3.Transform(center, transform);
            Box3F outBox = new Box3F(center - newExtent, center + newExtent);

            #if extra_strict_check
            // extra strict check
            {
                // Cycle through all the corners of the box and transform them.  Then
                // extend test box by transformed corners.  This box should match the
                // one we computed above except for precision errors.
                Box3F testBox= new Box3F(10E10f,10E10f,10E10f,-10E10f,-10E10f,-10E10f);
                center = 0.5f * (inBox.Min + inBox.Max);
                for (int i = 0; i < 8; i++)
                {
                    Vector3 corner = center;
                    corner.X += ((i & 1) == 0) ? extent.X : -extent.X;
                    corner.Y += ((i & 2) == 0) ? extent.Y : -extent.Y;
                    corner.Z += ((i & 4) == 0) ? extent.Z : -extent.Z;
                    corner = Vector3.Transform(corner, transform);
                    testBox.Extend(corner);
                }

                Assert.Fatal(Math.Abs(testBox.Min.X - outBox.Min.X) < 0.001f && Math.Abs(testBox.Min.Y - outBox.Min.Y) < 0.001f && Math.Abs(testBox.Min.Z - outBox.Min.Z) < 0.001f, "doh");
                Assert.Fatal(Math.Abs(testBox.Max.X - outBox.Max.X) < 0.001f && Math.Abs(testBox.Max.Y - outBox.Max.Y) < 0.001f && Math.Abs(testBox.Max.Z - outBox.Max.Z) < 0.001f, "doh");
            }
            #endif

            return outBox;
        }
        /// <summary>
        /// Check to see if another box overlaps this box.
        /// </summary>
        /// <param name="inBox">The other box to check.</param>
        /// <returns>Returns true if the specified box overlaps this box.</returns>
        public bool Overlaps(ref Box3F inBox)
        {
            if (inBox.Min.X > Max.X ||
                inBox.Min.Y > Max.Y ||
                inBox.Min.Z > Max.Z)
                return false;

            if (inBox.Max.X < Min.X ||
                inBox.Max.Y < Min.Y ||
                inBox.Max.Z < Min.Z)
                return false;

            return true;
        }
        /// <summary>
        /// Returns whether or not a box intersects with the frustum.
        /// </summary>
        /// <param name="box">The box to test.</param>
        /// <param name="mat">The object to world space matrix. Use identity if the box is already in world space.</param>
        /// <param name="radius">The radius of the box.</param>
        /// <returns>True if the box intersects the frustum.</returns>
        public bool Intersects(Box3F box, Matrix mat, float radius)
        {
            // Note: use the long form of vector operations because they get inlined whereas operator version does not

            #if EXTRA_DIAGNOSTICS
            _stats.totalChecks++;
            #endif

            // Vector3 objPos = 0.5f * (box.Max + box.Min) - _cameraPos;
            Vector3 objPos;
            Vector3.Add(ref box.Max, ref box.Min, out objPos);
            Vector3.Multiply(ref objPos, 0.5f, out objPos);
            Vector3.Subtract(ref objPos, ref _cameraPos, out objPos);

            float dist;
            Vector3.Dot(ref objPos, ref _cameraY, out dist);
            if (dist < -radius || dist > _farDist + radius)
            {
                // sphere behind near plane or beyond far plane
            #if EXTRA_DIAGNOSTICS
                _stats.sphereFarNearReject++;
            #endif
                return false;
            }

            // Short form of code which doesn't get inlined:
            // Vector3 conePos = -radius * _rejectInvSin * _camearY;
            // Vector3 offset = objPos - conePos;
            // float dy = Vector3.Dot(offset,_cameraY);
            // offset -= dy * _cameraY;
            // float dxSq = offset.LengthSquared();
            // Long form of code which does get inlined:
            float dy, dxSq;
            Vector3 conePos, offset, offsetY;
            Vector3.Multiply(ref _cameraY, -radius * _rejectInvSin, out conePos);
            Vector3.Subtract(ref objPos, ref conePos, out offset);
            Vector3.Dot(ref offset, ref _cameraY, out dy);
            Vector3.Multiply(ref _cameraY, dy, out offsetY);
            Vector3.Subtract(ref offset, ref offsetY, out offset);
            dxSq = offset.X * offset.X + offset.Y * offset.Y + offset.Z * offset.Z;
            if (dy * dy < _rejectCos * _rejectCos * (dxSq + dy * dy))
            {
                // early rejection, outside outer cone...
            #if EXTRA_DIAGNOSTICS
                _stats.sphereConeReject++;
            #endif
                return false;
            }

            Vector3.Multiply(ref _cameraY, -radius * _acceptInvSin, out conePos);
            Vector3.Subtract(ref objPos, ref conePos, out offset);
            Vector3.Dot(ref offset, ref _cameraY, out dy);
            Vector3.Multiply(ref _cameraY, dy, out offsetY);
            Vector3.Subtract(ref offset, ref offsetY, out offset);
            dxSq = offset.X * offset.X + offset.Y * offset.Y + offset.Z * offset.Z;
            if (dy * dy > _acceptCos * _acceptCos * (dxSq + dy * dy))
            {
                // early accept, inside inner cone...check far plane or near plane against box?
            #if EXTRA_DIAGNOSTICS
                _stats.sphereConeAccept++;
            #endif
                return true;
            }

            // Compare bounds to each of the frustum planes in turn.
            float boxDistance;
            #if EXTRA_DIAGNOSTICS
            _stats.checkLeft++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, _leftPlaneNormal, _leftPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideLeft++;
            #endif
                return false;
            }

            #if EXTRA_DIAGNOSTICS
            _stats.checkRight++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, _rightPlaneNormal, _rightPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideRight++;
            #endif
                return false;
            }

            #if EXTRA_DIAGNOSTICS
            _stats.checkTop++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, _topPlaneNormal, _topPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideTop++;
            #endif
                return false;
            }

            #if EXTRA_DIAGNOSTICS
            _stats.checkBottom++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, _bottomPlaneNormal, _bottomPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideBottom++;
            #endif
                return false;
            }

            #if EXTRA_DIAGNOSTICS
            _stats.checkNear++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, -_cameraY, _nearPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideNear++;
            #endif
                return false;
            }

            #if EXTRA_DIAGNOSTICS
            _stats.checkFar++;
            #endif
            if (!MathUtil.Collision.TestOBBPlane(box, mat, _cameraY, _farPlaneDist, out boxDistance) && boxDistance > 0.0f)
            {
                // fully outside this plane
            #if EXTRA_DIAGNOSTICS
                _stats.outsideFar++;
            #endif
                return false;
            }

            // passed all the tests, so must be inside frustum
            #if EXTRA_DIAGNOSTICS
            _stats.passAllChecks++;
            #endif
            return true;
        }
        /// <summary>
        /// Generate the frustum.
        /// </summary>
        /// <param name="nearDist">The distance to the near plane.</param>
        /// <param name="farDist">The distance to the far plane.</param>
        /// <param name="fov">The field of view of the frustum.</param>
        /// <param name="aspectRatio">The aspect ratio.</param>
        /// <param name="camToWorld">The camera transform.</param>
        public void SetFrustum(float nearDist, float farDist, float fov, float aspectRatio, Matrix camToWorld)
        {
            _nearDist = nearDist;
            _farDist = farDist;
            float left = -farDist * (float)Math.Tan(fov * 0.5f) * aspectRatio;
            float right = -left;
            float bottom = left / aspectRatio;
            float top = -bottom;

            Vector3 farPosLeftUp = new Vector3(left, farDist, top);
            Vector3 farPosLeftDown = new Vector3(left, farDist, bottom);
            Vector3 farPosRightUp = new Vector3(right, farDist, top);
            Vector3 farPosRightDown = new Vector3(right, farDist, bottom);
            _cameraPos = camToWorld.Translation;
            _cameraY = MatrixUtil.MatrixGetRow(1, ref camToWorld);

            farPosLeftUp = MatrixUtil.MatMulP(ref farPosLeftUp, ref camToWorld);
            farPosLeftDown = MatrixUtil.MatMulP(ref farPosLeftDown, ref camToWorld);
            farPosRightUp = MatrixUtil.MatMulP(ref farPosRightUp, ref camToWorld);
            farPosRightDown = MatrixUtil.MatMulP(ref farPosRightDown, ref camToWorld);

            _bounds = new Box3F(_cameraPos, _cameraPos);
            _bounds.Extend(farPosLeftUp);
            _bounds.Extend(farPosLeftDown);
            _bounds.Extend(farPosRightUp);
            _bounds.Extend(farPosRightDown);

            _leftPlaneNormal = Vector3.Cross(farPosLeftUp - _cameraPos, farPosLeftDown - _cameraPos);
            _rightPlaneNormal = Vector3.Cross(farPosRightDown - _cameraPos, farPosRightUp - _cameraPos);
            _topPlaneNormal = Vector3.Cross(farPosRightUp - _cameraPos, farPosLeftUp - _cameraPos);
            _bottomPlaneNormal = Vector3.Cross(farPosLeftDown - _cameraPos, farPosRightDown - _cameraPos);
            _leftPlaneNormal.Normalize();
            _rightPlaneNormal.Normalize();
            _topPlaneNormal.Normalize();
            _bottomPlaneNormal.Normalize();
            _leftPlaneDist = Vector3.Dot(_leftPlaneNormal, _cameraPos);
            _rightPlaneDist = Vector3.Dot(_rightPlaneNormal, _cameraPos);
            _topPlaneDist = Vector3.Dot(_topPlaneNormal, _cameraPos);
            _bottomPlaneDist = Vector3.Dot(_bottomPlaneNormal, _cameraPos);
            _nearPlaneDist = -nearDist - Vector3.Dot(_cameraY, _cameraPos);
            _farPlaneDist = farDist + Vector3.Dot(_cameraY, _cameraPos);

            // precompute sin for quick object accept
            float minr = Math.Min(right, top);
            Assert.Fatal(minr > 0.0f, "doh!");
            _acceptCos = _farDist / (float)Math.Sqrt(_farDist * _farDist + minr * minr);
            _acceptInvSin = 1.0f / (float)Math.Sqrt(1.0f - _acceptCos * _acceptCos);

            // precompute sin for object culling
            float maxr = (float)Math.Sqrt(right * right + bottom * bottom); // anything outside this cone rejected
            _rejectCos = _farDist / (float)Math.Sqrt(_farDist * _farDist + maxr * maxr);
            _rejectInvSin = 1.0f / (float)Math.Sqrt(1.0f - _rejectCos * _rejectCos);
        }
        /// <summary>
        /// Sets the material information for the draw primitive.
        /// </summary>
        /// <param name="material">The material to use.</param>
        /// <param name="bounds">The bounding box of the mesh being rendered.</param>
        /// <param name="srs">The scene render state.</param>
        public static void SetMaterial(ref Material material, ref Box3F bounds, SceneRenderState srs)
        {
            // perform name mapping to replace materials with custom materials
            if (material.Name != null && material._renderMaterial == null)
            {
                String baseName = Shape.CurrentFilePath + @"\" + material.Name;
                material._renderMaterial = MaterialManager.Lookup(baseName);
                if (material._renderMaterial == null)
                {
                    // if no material mapped, then use a default material
                    if (baseName != null)
                    {
                        LightingMaterial effect = new LightingMaterial();
                        effect.LightingMode = LightingMode.PerPixel;
                        effect.TextureFilename = baseName;
                        effect.IsTranslucent = (material.Flags & MaterialFlags.Translucent) != 0;

                        material._renderMaterial = MaterialManager.Add(baseName, effect);
                    }

                    // if we didn't find it this time, don't look it up again
                    if (material._renderMaterial == null)
                        material.Name = null;
                }
            }

            // material info
            _curRenderInst.Material = material._renderMaterial;

            bool translucent = (material.Flags & MaterialFlags.Translucent) != 0;
            _curRenderInst.Type = translucent ? RenderInstance.RenderInstanceType.Translucent2D : RenderInstance.RenderInstanceType.Mesh3D;

            bool sWrap = (material.Flags & MaterialFlags.S_Wrap) != 0;
            _curRenderInst.UTextureAddressMode = sWrap ? TextureAddressMode.Wrap : TextureAddressMode.Clamp;

            bool tWrap = (material.Flags & MaterialFlags.T_Wrap) != 0;
            _curRenderInst.VTextureAddressMode = tWrap ? TextureAddressMode.Wrap : TextureAddressMode.Clamp;

            // transform info
            Matrix transform = srs.World.Top;
            _curRenderInst.WorldBox = Box3F.Transform(ref bounds, ref transform);
            _curRenderInst.ObjectTransform = transform;
        }
        /// <summary>
        /// Sets the material information for the draw primitive. This will do nothing if the same material
        /// index was used with the previous call.
        /// </summary>
        /// <param name="matIndex">The index of the material to use.</param>
        /// <param name="matList">The material list to look up the material in.</param>
        /// <param name="bounds">The bounding box of the mesh being rendered.</param>
        /// <param name="srs">The scene render state.</param>
        public static void SetMaterial(uint matIndex, Material[] matList, ref Box3F bounds, SceneRenderState srs)
        {
            if (matIndex == _matIndex)
                return;

            _matIndex = matIndex;

            SetMaterial(ref matList[matIndex], ref bounds, srs);
        }
        // Test OBB against plane.  Return 0.0f if collide, + dist if outside plane, - dist if inside plane
        public static bool TestOBBPlane(Box3F b, Matrix mat, Vector3 planeNormal, float planeDist, out float boxDistance)
        {
            // Compute OBB center and extents
            Vector3 c0 = new Vector3(0.5f * (b.Max.X + b.Min.X), 0.5f * (b.Max.Y + b.Min.Y), 0.5f * (b.Max.Z + b.Min.Z));
            Vector3 c;
            Vector3.Transform(ref c0, ref mat, out c);
            Vector3 e = new Vector3(0.5f * (b.Max.X - b.Min.X), 0.5f * (b.Max.Y - b.Min.Y), 0.5f * (b.Max.Z - b.Min.Z));

            // Project box extents onto planeNormal
            Vector3 x, y, z;
            MatrixUtil.GetX(ref mat, out x);
            MatrixUtil.GetY(ref mat, out y);
            MatrixUtil.GetZ(ref mat, out z);
            float xdot = planeNormal.X * x.X + planeNormal.Y * x.Y + planeNormal.Z * x.Z;
            float ydot = planeNormal.X * y.X + planeNormal.Y * y.Y + planeNormal.Z * y.Z;
            float zdot = planeNormal.X * z.X + planeNormal.Y * z.Y + planeNormal.Z * z.Z;
            float cdot = planeNormal.X * c.X + planeNormal.Y * c.Y + planeNormal.Z * c.Z;
            float rad = e.X * Math.Abs(xdot) + e.Y * Math.Abs(ydot) + e.Z * Math.Abs(zdot);
            boxDistance = cdot - planeDist;
            if (Math.Abs(boxDistance) <= rad)
            {
                boxDistance = 0.0f;
                return true;
            }
            if (boxDistance < 0.0f)
                boxDistance += rad;
            else
                boxDistance -= rad;
            return false;
        }
        // From the book "Real Time Collision Detection" by Christer Ericson, pp. 164.
        public static bool TestAABBPlane(Box3F b, Vector3 planeNormal, float planeDist, out float boxDistance)
        {
            // These two lines not necessary with a (center, extents) AABB representation
            Vector3 c = (b.Max + b.Min) * 0.5f; // Compute AABB center
            Vector3 e = b.Max - c; // Compute positive extents

            // Compute the projection interval radius of b onto L(t) = b.c + t * p.n
            float r = e.X * Math.Abs(planeNormal.X) + e.Y * Math.Abs(planeNormal.Y) + e.Z * Math.Abs(planeNormal.Z);
            // Compute distance of box center from plane
            boxDistance = Vector3.Dot(planeNormal, c) - planeDist;
            // Intersection occurs when distance s falls within [-r,+r] interval
            if (Math.Abs(boxDistance) <= r)
            {
                boxDistance = 0.0f;
                return true;
            }
            if (boxDistance < 0.0f)
                boxDistance += r;
            else
                boxDistance -= r;
            return false;
        }
        /// <summary>
        /// Gets the list of lights in the scene that would have the most affect on an object
        /// inside of a box, up to a maximum number.
        /// </summary>
        /// <param name="worldBox">The box to get lights for.</param>
        /// <param name="maxLights">The maximum number of lights to return.</param>
        /// <returns></returns>
        public List<Light> GetLights(Box3F worldBox, int maxLights)
        {
            _objectLights.Clear();

            _lightComparer.WorldBox = worldBox;
            _lightList.Sort(_lightComparer);

            int count = _lightList.Count;
            for (int i = 0; i < count && i < maxLights; i++)
            {
                Light light = _lightList[i];

                if (light.Owner != null)
                    _objectLights.Add(light);
            }

            return _objectLights;
        }