6

I just recently added frustum culling to my voxel game; it worked pretty well at first glance. However, I immediately noticed that the frustum seems to be a little off—being that some voxels near the edge of the window are prematurely cut-off, before completely falling out of visual scope. Here is an image that illustrates my endeavor:

Frustum Glitch

And Here are three classes which will likely be needed to solve this problem.

My FrustumWidget Class:

using System;
using OpenTK;

namespace GameProject.Game.Client.Widgets {

    public class FrustumWidget {
        private float[] _clipMatrix = new float[ 16 ];
        private float[ , ] _frustum = new float[ 6 , 4 ];
        const int RIGHT = 0 , LEFT = 1 , BOTTOM = 2 , TOP = 3 , BACK = 4 , FRONT = 5;

        private void NormalizePlane( float[ , ] frustum , int side ) {
            float magnitude = ( float )Math.Sqrt( ( frustum[ side , 0 ] * frustum[ side , 0 ] ) + ( frustum[ side , 1 ] * frustum[ side , 1 ] )
                                                + ( frustum[ side , 2 ] * frustum[ side , 2 ] ) );
            frustum[ side , 0 ] /= magnitude;
            frustum[ side , 1 ] /= magnitude;
            frustum[ side , 2 ] /= magnitude;
            frustum[ side , 3 ] /= magnitude;
        }

        public bool SphereInFrustum( float x , float y , float z , float radius ) {
            float d = 0;
            for( int p = 0; p < 6; p++ ) {
                d = _frustum[ p , 0 ] * x + _frustum[ p , 1 ] * y + _frustum[ p , 2 ] * z + _frustum[ p , 3 ];
                if( d <= -radius ) 
                {
                    return false;
                }
            }
            return true;
        }

        public bool VoxelWithinFrustum( float x1 , float y1 , float z1 , float x2 , float y2 , float z2 ) {
            for( int i = 0; i < 6; i++ ) {
                if( ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z1 + this._frustum[ i, 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z1 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y1 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x1 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) &&
                  ( this._frustum[ i , 0 ] * x2 + this._frustum[ i , 1 ] * y2 + this._frustum[ i , 2 ] * z2 + this._frustum[ i , 3 ] <= 0.0F ) ) {
                    return false;
                }
            }
            return true;
        }

        public void CalculateFrustum( Matrix4 projectionMatrix , Matrix4 modelViewMatrix ) {
            _clipMatrix[ 0 ] = ( modelViewMatrix.M11 * projectionMatrix.M11 ) + ( modelViewMatrix.M12 * projectionMatrix.M21 ) + ( modelViewMatrix.M13 * projectionMatrix.M31 ) + ( modelViewMatrix.M14 * projectionMatrix.M41 );
            _clipMatrix[ 1 ] = ( modelViewMatrix.M11 * projectionMatrix.M12 ) + ( modelViewMatrix.M12 * projectionMatrix.M22 ) + ( modelViewMatrix.M13 * projectionMatrix.M32 ) + ( modelViewMatrix.M14 * projectionMatrix.M42 );
            _clipMatrix[ 2 ] = ( modelViewMatrix.M11 * projectionMatrix.M13 ) + ( modelViewMatrix.M12 * projectionMatrix.M23 ) + ( modelViewMatrix.M13 * projectionMatrix.M33 ) + ( modelViewMatrix.M14 * projectionMatrix.M43 );
            _clipMatrix[ 3 ] = ( modelViewMatrix.M11 * projectionMatrix.M14 ) + ( modelViewMatrix.M12 * projectionMatrix.M24 ) + ( modelViewMatrix.M13 * projectionMatrix.M34 ) + ( modelViewMatrix.M14 * projectionMatrix.M44 );

            _clipMatrix[ 4 ] = ( modelViewMatrix.M21 * projectionMatrix.M11 ) + ( modelViewMatrix.M22 * projectionMatrix.M21 ) + ( modelViewMatrix.M23 * projectionMatrix.M31 ) + ( modelViewMatrix.M24 * projectionMatrix.M41 );
            _clipMatrix[ 5 ] = ( modelViewMatrix.M21 * projectionMatrix.M12 ) + ( modelViewMatrix.M22 * projectionMatrix.M22 ) + ( modelViewMatrix.M23 * projectionMatrix.M32 ) + ( modelViewMatrix.M24 * projectionMatrix.M42 );
            _clipMatrix[ 6 ] = ( modelViewMatrix.M21 * projectionMatrix.M13 ) + ( modelViewMatrix.M22 * projectionMatrix.M23 ) + ( modelViewMatrix.M23 * projectionMatrix.M33 ) + ( modelViewMatrix.M24 * projectionMatrix.M43 );
            _clipMatrix[ 7 ] = ( modelViewMatrix.M21 * projectionMatrix.M14 ) + ( modelViewMatrix.M22 * projectionMatrix.M24 ) + ( modelViewMatrix.M23 * projectionMatrix.M34 ) + ( modelViewMatrix.M24 * projectionMatrix.M44 );

            _clipMatrix[ 8 ] = ( modelViewMatrix.M31 * projectionMatrix.M11 ) + ( modelViewMatrix.M32 * projectionMatrix.M21 ) + ( modelViewMatrix.M33 * projectionMatrix.M31 ) + ( modelViewMatrix.M34 * projectionMatrix.M41 );
            _clipMatrix[ 9 ] = ( modelViewMatrix.M31 * projectionMatrix.M12 ) + ( modelViewMatrix.M32 * projectionMatrix.M22 ) + ( modelViewMatrix.M33 * projectionMatrix.M32 ) + ( modelViewMatrix.M34 * projectionMatrix.M42 );
            _clipMatrix[ 10 ] = ( modelViewMatrix.M31 * projectionMatrix.M13 ) + ( modelViewMatrix.M32 * projectionMatrix.M23 ) + ( modelViewMatrix.M33 * projectionMatrix.M33 ) + ( modelViewMatrix.M34 * projectionMatrix.M43 );
            _clipMatrix[ 11 ] = ( modelViewMatrix.M31 * projectionMatrix.M14 ) + ( modelViewMatrix.M32 * projectionMatrix.M24 ) + ( modelViewMatrix.M33 * projectionMatrix.M34 ) + ( modelViewMatrix.M34 * projectionMatrix.M44 );

            _clipMatrix[ 12 ] = ( modelViewMatrix.M41 * projectionMatrix.M11 ) + ( modelViewMatrix.M42 * projectionMatrix.M21 ) + ( modelViewMatrix.M43 * projectionMatrix.M31 ) + ( modelViewMatrix.M44 * projectionMatrix.M41 );
            _clipMatrix[ 13 ] = ( modelViewMatrix.M41 * projectionMatrix.M12 ) + ( modelViewMatrix.M42 * projectionMatrix.M22 ) + ( modelViewMatrix.M43 * projectionMatrix.M32 ) + ( modelViewMatrix.M44 * projectionMatrix.M42 );
            _clipMatrix[ 14 ] = ( modelViewMatrix.M41 * projectionMatrix.M13 ) + ( modelViewMatrix.M42 * projectionMatrix.M23 ) + ( modelViewMatrix.M43 * projectionMatrix.M33 ) + ( modelViewMatrix.M44 * projectionMatrix.M43 );
            _clipMatrix[ 15 ] = ( modelViewMatrix.M41 * projectionMatrix.M14 ) + ( modelViewMatrix.M42 * projectionMatrix.M24 ) + ( modelViewMatrix.M43 * projectionMatrix.M34 ) + ( modelViewMatrix.M44 * projectionMatrix.M44 );

            _frustum[ RIGHT , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 0 ];
            _frustum[ RIGHT , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 4 ];
            _frustum[ RIGHT , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 8 ];
            _frustum[ RIGHT , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 12 ];
            NormalizePlane( _frustum , RIGHT );

            _frustum[ LEFT , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 0 ];
            _frustum[ LEFT , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 4 ];
            _frustum[ LEFT , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 8 ];
            _frustum[ LEFT , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 12 ];
            NormalizePlane( _frustum , LEFT );

            _frustum[ BOTTOM , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 1 ];
            _frustum[ BOTTOM , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 5 ];
            _frustum[ BOTTOM , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 9 ];
            _frustum[ BOTTOM , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 13 ];
            NormalizePlane( _frustum , BOTTOM );

            _frustum[ TOP , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 1 ];
            _frustum[ TOP , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 5 ];
            _frustum[ TOP , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 9 ];
            _frustum[ TOP , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 13 ];
            NormalizePlane( _frustum , TOP );

            _frustum[ BACK , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 2 ];
            _frustum[ BACK , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 6 ];
            _frustum[ BACK , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 10 ];
            _frustum[ BACK , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 14 ];
            NormalizePlane( _frustum , BACK );

            _frustum[ FRONT , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 2 ];
            _frustum[ FRONT , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 6 ];
            _frustum[ FRONT , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 10 ];
            _frustum[ FRONT , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 14 ];
            NormalizePlane( _frustum , FRONT );
        }
    }
}

My FirstPersonCameraWidget class:

using System;
using System.Drawing;
using OpenTK;
using GameProject.Game.Framework.OpenTKExtensions;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;

namespace GameProject.Game.Client.Widgets {
    public class FirstPersonCameraWidget {

        public Matrix4 CameraMatrix;
        public Vector3 Location;
        public FrustumWidget Frustum;
        public float Pitch;
        public float Yaw;
        public float MoveSpeed;
        public float LookSpeed;
        public Vector2 MouseSpeed;
        public bool IsLooking;
        public float NearZ;
        public float FarZ;

        public FirstPersonCameraWidget(Vector3 cameraOrigin , float pitch , float yaw , float movementSpeed , float lookSpeed ) {
            CameraMatrix = Matrix4.Identity;
            this.Location = cameraOrigin;
            this.Pitch = pitch;
            this.Yaw = yaw;
            this.LookSpeed = lookSpeed;
            this.MoveSpeed = movementSpeed;
            Frustum = new FrustumWidget();
        }

        public void Update() {
            if( IsLooking ) {
                // Possible redundant method?
            }
        }

        public void ProcessMovement( KeyboardState keyboardState , FrameEventArgs e ) {
            if( keyboardState[ Key.W ] ) {
                MoveForward( e );
            }
            if( keyboardState[ Key.S ] ) {
                MoveBackward( e );
            }
            if( keyboardState[ Key.A ] ) {
                StrafeLeft( e );
            }
            if( keyboardState[ Key.D ] ) {
                StrafeRight( e );
            }
            if( keyboardState[ Key.Space ] ) {
                FlyUp( e );
            }
            if( keyboardState[ Key.LShift ] || keyboardState[ Key.RShift ] ) {
                FlyDown( e );
            }
        }

        public void MoveForward( FrameEventArgs e ) {
            Location.X += ( float )Math.Cos( Yaw ) * MoveSpeed * ( float )e.Time;
            //CameraPosition.Y += ( float )Math.Tan( Pitch ) * MoveSpeed * ( float )e.Time;
            Location.Z += ( float )Math.Sin( Yaw ) * MoveSpeed * ( float )e.Time;
        }

        public void MoveBackward( FrameEventArgs e ) {
            Location.X -= ( float )Math.Cos( Yaw ) * MoveSpeed * ( float )e.Time;
            //CameraPosition.Y -= ( float )Math.Tan( Pitch ) * MoveSpeed * ( float )e.Time;
            Location.Z -= ( float )Math.Sin( Yaw ) * MoveSpeed * ( float )e.Time;
        }

        public void StrafeLeft( FrameEventArgs e ) {
            Location.X -= ( float )Math.Cos( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
            Location.Z -= ( float )Math.Sin( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
        }

        public void StrafeRight( FrameEventArgs e ) {
            Location.X += ( float )Math.Cos( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
            Location.Z += ( float )Math.Sin( Yaw + Math.PI / 2 ) * MoveSpeed * ( float )e.Time;
        }

        public void FlyUp( FrameEventArgs e ) {
            Location.Y += MoveSpeed * ( float )e.Time;
        }

        public void FlyDown( FrameEventArgs e ) {
            Location.Y -= MoveSpeed * ( float )e.Time;
        }

        public void LookThrough( GameWindow window , Point mouseLocation , FrameEventArgs e ) {
            if( IsLooking ) {
                Point windowCenter = new Point( window.Bounds.Left + window.Bounds.Width / 2 , window.Bounds.Top + window.Bounds.Height / 2 );
                Vector2 MouseDelta = new Vector2( mouseLocation.X - window.PointToClient( windowCenter ).X , mouseLocation.Y - window.PointToClient( windowCenter ).Y );
                System.Windows.Forms.Cursor.Position = windowCenter;
                MouseSpeed.X *= LookSpeed;
                MouseSpeed.Y *= LookSpeed;
                MouseSpeed.X += MouseDelta.X / 40.0f * ( float )e.Time;
                MouseSpeed.Y += MouseDelta.Y / 40.0f * ( float )e.Time;
                Yaw += MouseSpeed.X;
                Pitch -= MouseSpeed.Y;
                if( Pitch <= -( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) + 0.01f ) {
                    Pitch = -( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) + 0.01f;
                }
                if( Pitch >= ( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) - 0.01f ) {
                    Pitch = ( ( OpenTKExtensions.HalfPI * 2 ) * 0.5f ) - 0.01f;
                }
            }
            Vector3 LookAtPoint = new Vector3( ( float )Math.Cos( Yaw ) , ( float )Math.Tan( Pitch ) , ( float )Math.Sin( Yaw ) );
            CameraMatrix = Matrix4.LookAt( Location , Location + LookAtPoint , Vector3.UnitY );

            /**
             * Recalculate the Viewport Frustum
             */
            Matrix4 projection = new Matrix4();
            GL.GetFloat(GetPName.ProjectionMatrix, out projection);
            Matrix4 modelview = new Matrix4();
            GL.GetFloat(GetPName.ModelviewMatrix, out modelview);
            Frustum.CalculateFrustum(projection,modelview);
        }
    }
}

And Lastly, my GameClient class:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using GameProject.Game.Framework.DataStructure;
using GameProject.Game.Framework.Generators;
using GameProject.Game.Framework.Geometry;
using System.Diagnostics;
using GameProject.Game.Framework.OpenGLExtensions;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using GameProject.Game.Client.Widgets;
using QuickFont;

namespace GameProject.Game.Client {
    public class GameClient : GameWindow {

        /**
         * Client Variables
         */
        public const String CLIENT_VERSION = "v0.0.1";
        public const String GAME_NAME = "Voxelbyte";
        public FirstPersonCameraWidget GameCamera;
        private Point _mousePosition;
        private Point _lastMousePosition;
        private bool _centerCursorAfterRelease = false;
        /**
         * Game Variables
         */
        public VoxelWorld World;
        public List<GameSelection> SelectionMemory;
        public QFont DeveloperText;

        public GameClient()
            : base( 800 , 600 , GraphicsMode.Default , GAME_NAME ) {
            GL.Enable( EnableCap.DepthTest );
            GL.ClearColor( 0.3f , 0.3f , 0.3f , 0.0f );

        }

        protected override void OnLoad( EventArgs e ) {
            base.OnLoad( e );

            DeveloperText = new QFont( "Resources/Fonts/times.ttf" , 14 , FontStyle.Regular );
            //TestFont.Options.UseDefaultBlendFunction = false;
            //this.VSync = VSyncMode.On;
            SelectionMemory = new List<GameSelection>();
            GameCamera = new FirstPersonCameraWidget(
                new Vector3( 30.0f , 8.0f , 30.0f ) ,
                0.0f ,
                0.0f ,
                15.0f ,
                0.4f );
            //this.WindowState = WindowState.Fullscreen;
            GL.CullFace( CullFaceMode.Back );
            GL.Enable( EnableCap.CullFace );
            GL.BlendFunc( BlendingFactorSrc.SrcAlpha , BlendingFactorDest.OneMinusSrcAlpha );
            GL.Enable( EnableCap.Blend );
            // Generate Map And Log the Time
            World = BattleWorldReader.ReadFromImage( "ColoredPyramid.png" );
            Stopwatch sw = new Stopwatch();
            sw.Start();
            World.Rebuild();
            sw.Stop();
            Console.WriteLine( "World Generation+Optimization took: {0}ms" , sw.ElapsedMilliseconds );
            //GLExtensions.EnableWireframe();
        }

        protected override void OnMouseMove( MouseMoveEventArgs e ) {
            base.OnMouseMove( e );
            _mousePosition = new Point( e.X , e.Y );
            if( e.Mouse.IsButtonDown( MouseButton.Right ) ) {

            }
        }

        protected override void OnMouseDown( MouseButtonEventArgs e ) {
            base.OnMouseDown( e );
            if( e.Mouse.IsButtonDown( MouseButton.Right ) ) {
                Point nativeMouse = System.Windows.Forms.Cursor.Position;
                _lastMousePosition = nativeMouse;
                Point windowCenter = new Point( Bounds.Left + Bounds.Width / 2 , Bounds.Top + Bounds.Height / 2 );
                System.Windows.Forms.Cursor.Position = windowCenter;
                GameCamera.IsLooking = true;
                this.CursorVisible = false;
            }
        }

        protected override void OnMouseUp( MouseButtonEventArgs e ) {
            base.OnMouseUp( e );
            if( e.Button == MouseButton.Right ) {
                GameCamera.IsLooking = false;
                if( !_centerCursorAfterRelease ) {
                    System.Windows.Forms.Cursor.Position = _lastMousePosition;
                }
                this.CursorVisible = true;
            }
        }

        protected override void OnRenderFrame( FrameEventArgs e ) {
            base.OnRenderFrame( e );
            GameCamera.LookThrough( this , _mousePosition , e );
            GL.MatrixMode( MatrixMode.Modelview );
            GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit );
            GL.LoadMatrix( ref GameCamera.CameraMatrix );
            //
            //
            GL.EnableClientState( ArrayCap.VertexArray );
            GL.EnableClientState( ArrayCap.TextureCoordArray );
            GL.DisableClientState( ArrayCap.ColorArray );
            int numberOfVoxelsRendered = 0;
            foreach( Voxel voxel in World.Voxels ) {

                if( GameCamera.Frustum.VoxelWithinFrustum( voxel.Location.X, voxel.Location.Y, voxel.Location.Z, 1.0f,1.0f,1.0f ) ) {
                    voxel.Render( GameCamera );
                    numberOfVoxelsRendered++;
                }

            }
            GL.DisableClientState( ArrayCap.VertexArray );
            GL.DisableClientState( ArrayCap.TextureCoordArray );
            GL.DisableClientState( ArrayCap.ColorArray );

            // Render Dev Hud
            RenderDeveloperHud();
            //


            SwapBuffers();
            this.Title = GAME_NAME + " FPS: " + ( int )this.RenderFrequency + " Voxels: " + numberOfVoxelsRendered;
        }

        private void RenderDeveloperHud() {
            QFont.Begin();
            GL.PushMatrix();
            GL.Translate( 0.0f , 5 , 0f );
            DeveloperText.Print( "Voxelbyte" );
            GL.Translate( 0f , 30 , 0f );
            DeveloperText.Print( "X: " + ( int )GameCamera.Location.X );
            GL.Translate( 0f , 24 , 0f );
            DeveloperText.Print( "Y: " + ( int )GameCamera.Location.Y );
            GL.Translate( 0.0f , 24 , 0f );
            DeveloperText.Print( "Z: " + ( int )GameCamera.Location.Z );
            GL.PopMatrix();
            QFont.End();
        }

        private void CheckKeyboardInput( FrameEventArgs eventArgs ) {
            KeyboardState keyboardState = OpenTK.Input.Keyboard.GetState();
            GameCamera.ProcessMovement( keyboardState , eventArgs );
            if( keyboardState[ Key.Escape ] ) {
                Exit();
            }
        }

        protected override void OnUpdateFrame( FrameEventArgs e ) {
            CheckKeyboardInput( e );
        }

        protected override void OnResize( EventArgs e ) {
            base.OnResize( e );
            GL.Viewport( ClientRectangle.X , ClientRectangle.Y , ClientRectangle.Width , ClientRectangle.Height );
            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView( ( float )Math.PI / 4 , Width / ( float )Height , 0.1f , 1000.0f );
            GL.MatrixMode( MatrixMode.Projection );
            GL.LoadMatrix( ref projection );
        }
    }
}

Anyone know what the deal is here? Because I am stumped.

EDIT:

I just noticed that if I use a different detecting method (SphereInFrustum) everything seems to work perfectly; nothing is clipped prematurely. Here is my new renderloop:

    foreach( Voxel voxel in World.Voxels ) {
        if( voxel.IsVisible() ) {
            if( GameCamera.Frustum.SphereInFrustum(
                voxel.Location.X , voxel.Location.Y , voxel.Location.Z , 1.5f ) ) {
                voxel.Render( GameCamera );
                numberOfVoxelsRendered++;
            }
        }

    }

As you can see I am passing in a sphere radius of 1.5f, and basically pretending all my Voxels are spheres. It works, and there is no premature clipping, however I want to use my VoxelWithinFrustum method. Can anyone spot if I did something wrong with that method? Or if I am using the parameters incorrectly?

2
  • I noticed that my problem is solved if I use SphereInFrustum instead of the method VoxelWithinFrustum. However, I am using Voxels and not spheres. Could there be something wrong with my VoxelWithinFrustum method?
    – Krythic
    Commented Sep 13, 2014 at 17:52
  • After further testing, I have come to the realization that the last three parameters in "VoxelWithinFrustum" are actually slightly dynamic. Allow me to explain. If I put in 2,2,2, as the last three parameters the camera-frustum will cull perfectly—until I turn my camera around to face the opposite direction, then the culling reverts back to popping them out too soon. I think this means that the coordinates are supposed to change depending on what direction I am facing.(the last three params should be -2, or something when i turn around) I just dont know how to go about this.
    – Krythic
    Commented Sep 18, 2014 at 0:31

2 Answers 2

7

I noticed that this post was one of the first google hits for opengl frustum culling, so I wanted to provide proper, working code as a new answer. Here is my updated frustum class:

public class Frustum
    {
        private readonly float[] _clipMatrix = new float[ 16 ];
        private readonly float[ , ] _frustum = new float[ 6 , 4 ];

        public const int A = 0;
        public const int B = 1;
        public const int C = 2;
        public const int D = 3;

        public enum ClippingPlane : int
        {
            Right = 0 ,
            Left = 1 ,
            Bottom = 2 ,
            Top = 3 ,
            Back = 4 ,
            Front = 5
        }

        private void NormalizePlane( float[ , ] frustum , int side )
        {
            float magnitude = ( float )Math.Sqrt( ( frustum[ side , 0 ] * frustum[ side , 0 ] ) + ( frustum[ side , 1 ] * frustum[ side , 1 ] )
                                                + ( frustum[ side , 2 ] * frustum[ side , 2 ] ) );
            frustum[ side , 0 ] /= magnitude;
            frustum[ side , 1 ] /= magnitude;
            frustum[ side , 2 ] /= magnitude;
            frustum[ side , 3 ] /= magnitude;
        }

        public bool PointVsFrustum( float x , float y , float z )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( this._frustum[ i , 0 ] * x + this._frustum[ i , 1 ] * y + this._frustum[ i , 2 ] * z + this._frustum[ i , 3 ] <= 0.0f )
                {
                    return false;
                }
            }
            return true;
        }

        public bool PointVsFrustum( Vector3 location )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( this._frustum[ i , 0 ] * location.X + this._frustum[ i , 1 ] * location.Y + this._frustum[ i , 2 ] * location.Z + this._frustum[ i , 3 ] <= 0.0f )
                {
                    return false;
                }
            }
            return true;
        }

        public bool SphereVsFrustum( float x , float y , float z , float radius )
        {
            for( int p = 0; p < 6; p++ )
            {
                float d = _frustum[ p , 0 ] * x + _frustum[ p , 1 ] * y + _frustum[ p , 2 ] * z + _frustum[ p , 3 ];
                if( d <= -radius )
                {
                    return false;
                }
            }
            return true;
        }

        public bool SphereVsFrustum( Vector3 location , float radius )
        {
            for( int p = 0; p < 6; p++ )
            {
                float d = _frustum[ p , 0 ] * location.X + _frustum[ p , 1 ] * location.Y + _frustum[ p , 2 ] * location.Z + _frustum[ p , 3 ];
                if( d <= -radius )
                {
                    return false;
                }
            }
            return true;
        }

        public bool VolumeVsFrustum( float x , float y , float z , float width , float height , float length )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y - height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + width ) + _frustum[ i , B ] * ( y + height ) + _frustum[ i , C ] * ( z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool VolumeVsFrustum( BoundingVolume volume )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z - volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y - volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X - volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( volume.X + volume.Width ) + _frustum[ i , B ] * ( volume.Y + volume.Height ) + _frustum[ i , C ] * ( volume.Z + volume.Length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool VolumeVsFrustum( Vector3 location , float width , float height , float length )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z - length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y - height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X - width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( location.X + width ) + _frustum[ i , B ] * ( location.Y + height ) + _frustum[ i , C ] * ( location.Z + length ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }

        public bool CubeVsFrustum( float x , float y , float z , float size )
        {
            for( int i = 0; i < 6; i++ )
            {
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z - size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y - size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x - size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                if( _frustum[ i , A ] * ( x + size ) + _frustum[ i , B ] * ( y + size ) + _frustum[ i , C ] * ( z + size ) + _frustum[ i , D ] > 0 )
                    continue;
                return false;
            }
            return true;
        }


        public void CalculateFrustum( Matrix4 projectionMatrix , Matrix4 modelViewMatrix )
        {
            _clipMatrix[ 0 ] = ( modelViewMatrix.M11 * projectionMatrix.M11 ) + ( modelViewMatrix.M12 * projectionMatrix.M21 ) + ( modelViewMatrix.M13 * projectionMatrix.M31 ) + ( modelViewMatrix.M14 * projectionMatrix.M41 );
            _clipMatrix[ 1 ] = ( modelViewMatrix.M11 * projectionMatrix.M12 ) + ( modelViewMatrix.M12 * projectionMatrix.M22 ) + ( modelViewMatrix.M13 * projectionMatrix.M32 ) + ( modelViewMatrix.M14 * projectionMatrix.M42 );
            _clipMatrix[ 2 ] = ( modelViewMatrix.M11 * projectionMatrix.M13 ) + ( modelViewMatrix.M12 * projectionMatrix.M23 ) + ( modelViewMatrix.M13 * projectionMatrix.M33 ) + ( modelViewMatrix.M14 * projectionMatrix.M43 );
            _clipMatrix[ 3 ] = ( modelViewMatrix.M11 * projectionMatrix.M14 ) + ( modelViewMatrix.M12 * projectionMatrix.M24 ) + ( modelViewMatrix.M13 * projectionMatrix.M34 ) + ( modelViewMatrix.M14 * projectionMatrix.M44 );

            _clipMatrix[ 4 ] = ( modelViewMatrix.M21 * projectionMatrix.M11 ) + ( modelViewMatrix.M22 * projectionMatrix.M21 ) + ( modelViewMatrix.M23 * projectionMatrix.M31 ) + ( modelViewMatrix.M24 * projectionMatrix.M41 );
            _clipMatrix[ 5 ] = ( modelViewMatrix.M21 * projectionMatrix.M12 ) + ( modelViewMatrix.M22 * projectionMatrix.M22 ) + ( modelViewMatrix.M23 * projectionMatrix.M32 ) + ( modelViewMatrix.M24 * projectionMatrix.M42 );
            _clipMatrix[ 6 ] = ( modelViewMatrix.M21 * projectionMatrix.M13 ) + ( modelViewMatrix.M22 * projectionMatrix.M23 ) + ( modelViewMatrix.M23 * projectionMatrix.M33 ) + ( modelViewMatrix.M24 * projectionMatrix.M43 );
            _clipMatrix[ 7 ] = ( modelViewMatrix.M21 * projectionMatrix.M14 ) + ( modelViewMatrix.M22 * projectionMatrix.M24 ) + ( modelViewMatrix.M23 * projectionMatrix.M34 ) + ( modelViewMatrix.M24 * projectionMatrix.M44 );

            _clipMatrix[ 8 ] = ( modelViewMatrix.M31 * projectionMatrix.M11 ) + ( modelViewMatrix.M32 * projectionMatrix.M21 ) + ( modelViewMatrix.M33 * projectionMatrix.M31 ) + ( modelViewMatrix.M34 * projectionMatrix.M41 );
            _clipMatrix[ 9 ] = ( modelViewMatrix.M31 * projectionMatrix.M12 ) + ( modelViewMatrix.M32 * projectionMatrix.M22 ) + ( modelViewMatrix.M33 * projectionMatrix.M32 ) + ( modelViewMatrix.M34 * projectionMatrix.M42 );
            _clipMatrix[ 10 ] = ( modelViewMatrix.M31 * projectionMatrix.M13 ) + ( modelViewMatrix.M32 * projectionMatrix.M23 ) + ( modelViewMatrix.M33 * projectionMatrix.M33 ) + ( modelViewMatrix.M34 * projectionMatrix.M43 );
            _clipMatrix[ 11 ] = ( modelViewMatrix.M31 * projectionMatrix.M14 ) + ( modelViewMatrix.M32 * projectionMatrix.M24 ) + ( modelViewMatrix.M33 * projectionMatrix.M34 ) + ( modelViewMatrix.M34 * projectionMatrix.M44 );

            _clipMatrix[ 12 ] = ( modelViewMatrix.M41 * projectionMatrix.M11 ) + ( modelViewMatrix.M42 * projectionMatrix.M21 ) + ( modelViewMatrix.M43 * projectionMatrix.M31 ) + ( modelViewMatrix.M44 * projectionMatrix.M41 );
            _clipMatrix[ 13 ] = ( modelViewMatrix.M41 * projectionMatrix.M12 ) + ( modelViewMatrix.M42 * projectionMatrix.M22 ) + ( modelViewMatrix.M43 * projectionMatrix.M32 ) + ( modelViewMatrix.M44 * projectionMatrix.M42 );
            _clipMatrix[ 14 ] = ( modelViewMatrix.M41 * projectionMatrix.M13 ) + ( modelViewMatrix.M42 * projectionMatrix.M23 ) + ( modelViewMatrix.M43 * projectionMatrix.M33 ) + ( modelViewMatrix.M44 * projectionMatrix.M43 );
            _clipMatrix[ 15 ] = ( modelViewMatrix.M41 * projectionMatrix.M14 ) + ( modelViewMatrix.M42 * projectionMatrix.M24 ) + ( modelViewMatrix.M43 * projectionMatrix.M34 ) + ( modelViewMatrix.M44 * projectionMatrix.M44 );

            _frustum[ ( int )ClippingPlane.Right , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 0 ];
            _frustum[ ( int )ClippingPlane.Right , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 4 ];
            _frustum[ ( int )ClippingPlane.Right , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 8 ];
            _frustum[ ( int )ClippingPlane.Right , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 12 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Right );

            _frustum[ ( int )ClippingPlane.Left , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 0 ];
            _frustum[ ( int )ClippingPlane.Left , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 4 ];
            _frustum[ ( int )ClippingPlane.Left , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 8 ];
            _frustum[ ( int )ClippingPlane.Left , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 12 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Left );

            _frustum[ ( int )ClippingPlane.Bottom , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 1 ];
            _frustum[ ( int )ClippingPlane.Bottom , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 5 ];
            _frustum[ ( int )ClippingPlane.Bottom , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 9 ];
            _frustum[ ( int )ClippingPlane.Bottom , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 13 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Bottom );

            _frustum[ ( int )ClippingPlane.Top , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 1 ];
            _frustum[ ( int )ClippingPlane.Top , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 5 ];
            _frustum[ ( int )ClippingPlane.Top , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 9 ];
            _frustum[ ( int )ClippingPlane.Top , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 13 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Top );

            _frustum[ ( int )ClippingPlane.Back , 0 ] = _clipMatrix[ 3 ] - _clipMatrix[ 2 ];
            _frustum[ ( int )ClippingPlane.Back , 1 ] = _clipMatrix[ 7 ] - _clipMatrix[ 6 ];
            _frustum[ ( int )ClippingPlane.Back , 2 ] = _clipMatrix[ 11 ] - _clipMatrix[ 10 ];
            _frustum[ ( int )ClippingPlane.Back , 3 ] = _clipMatrix[ 15 ] - _clipMatrix[ 14 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Back );

            _frustum[ ( int )ClippingPlane.Front , 0 ] = _clipMatrix[ 3 ] + _clipMatrix[ 2 ];
            _frustum[ ( int )ClippingPlane.Front , 1 ] = _clipMatrix[ 7 ] + _clipMatrix[ 6 ];
            _frustum[ ( int )ClippingPlane.Front , 2 ] = _clipMatrix[ 11 ] + _clipMatrix[ 10 ];
            _frustum[ ( int )ClippingPlane.Front , 3 ] = _clipMatrix[ 15 ] + _clipMatrix[ 14 ];
            NormalizePlane( _frustum , ( int )ClippingPlane.Front );
        }
    }

I hope someone finds it useful, instead of getting this dead page link like they were getting.

6
  • It been a long time, but can you please tell us if you still used the sphere testing method? Did this fixed class resolve your issues? I'm using the same class in my application, but in my case I have clipping issues on the side (I'm resolving them as well with sphere testing)
    – Greg
    Commented Nov 3, 2018 at 17:17
  • @GregK. No, I've witnessed no problems with the fix as it was presented here. However I am no longer using this, and instead I have copied the open source...I think it was LibGDX... implementation. That's mainly due to the fact that the library also came with other Bounding Shapes that were plug and play and well built to work with each other. I had Bounding Shapes already coded, but their implementation was superior to my own, and so I have shamelessly adopted it into my own framework. My game engine is currently using a modified LibGDX(I think) implementation.
    – Krythic
    Commented Nov 4, 2018 at 23:19
  • @GregK. My apologies, it was Monogame, actually. github.com/ManojLakshan/monogame/blob/master/MonoGame.Framework/…
    – Krythic
    Commented Nov 4, 2018 at 23:29
  • So you're using bounding volume testing using the MonoGame Framework?
    – Greg
    Commented Nov 6, 2018 at 13:53
  • @GregK. No i don't use any framework except for OpenTK for rendering. I copy and pasted the sections I needed from the Monogame framework, which is completely valid within their user agreement. I don't believe in using an entire framework for just 4 classes.
    – Krythic
    Commented Nov 7, 2018 at 23:01
1

Probably you have wrong height value set ( y ). I had the SAME problem, it was a bit stupid, I was generating Y value in glsl, for sphere check it was like (x,0.0,z) that's why.

1
  • I don't have the time to test this out, but I knew that it was something related to the parameters of that function. I will accept you as the answer.
    – Krythic
    Commented Dec 25, 2014 at 19:07

Not the answer you're looking for? Browse other questions tagged or ask your own question.