Frustum culling

    Camera class must have this object

    Calculate frustum.

    
    struct slCameraFrustum
    {
    	slVec4 m_planes[6u];
    
    	bool PointInFrustum(const slVec4& point)
    	{
    		if ((
    			m_planes[0].x * point.x +
    			m_planes[0].y * point.y +
    			m_planes[0].z * point.z +
    			m_planes[0].w)
    			< 0.f)
    			return false;
    		if ((
    			m_planes[1].x * point.x +
    			m_planes[1].y * point.y +
    			m_planes[1].z * point.z +
    			m_planes[1].w)
    			< 0.f)
    			return false;
    		if ((
    			m_planes[2].x * point.x +
    			m_planes[2].y * point.y +
    			m_planes[2].z * point.z +
    			m_planes[2].w)
    			< 0.f)
    			return false;
    		if ((
    			m_planes[3].x * point.x +
    			m_planes[3].y * point.y +
    			m_planes[3].z * point.z +
    			m_planes[3].w)
    			< 0.f)
    			return false;
    		if ((
    			m_planes[4].x * point.x +
    			m_planes[4].y * point.y +
    			m_planes[4].z * point.z +
    			m_planes[4].w)
    			< 0.f)
    			return false;
    		if ((
    			m_planes[5].x * point.x +
    			m_planes[5].y * point.y +
    			m_planes[5].z * point.z +
    			m_planes[5].w)
    			< 0.f)
    			return false;
    		return true;
    	}
    	bool SphereInFrustum(real_t radius, const slVec4& position)
    	{
    		if (m_planes[0].x * position.x +
    			m_planes[0].y * position.y +
    			m_planes[0].z * position.z +
    			m_planes[0].w <= -radius)
    			return false;
    		if (m_planes[1].x * position.x +
    			m_planes[1].y * position.y +
    			m_planes[1].z * position.z +
    			m_planes[1].w <= -radius)
    			return false;
    		if (m_planes[2].x * position.x +
    			m_planes[2].y * position.y +
    			m_planes[2].z * position.z +
    			m_planes[2].w <= -radius)
    			return false;
    		if (m_planes[3].x * position.x +
    			m_planes[3].y * position.y +
    			m_planes[3].z * position.z +
    			m_planes[3].w <= -radius)
    			return false;
    		if (m_planes[4].x * position.x +
    			m_planes[4].y * position.y +
    			m_planes[4].z * position.z +
    			m_planes[4].w <= -radius)
    			return false;
    		if (m_planes[5].x * position.x +
    			m_planes[5].y * position.y +
    			m_planes[5].z * position.z +
    			m_planes[5].w <= -radius)
    			return false;
    		return true;
    	}
    
    	//https://gist.github.com/Kinwailo/d9a07f98d8511206182e50acda4fbc9b but modified
    	bool AABBInFrustum(const slAabb& aabb)
    	{
    		bool ret = true;
    		slVec4 vmin, vmax;
    
    		for (int i = 0; i < 6; ++i) {
    			// X axis 
    			if (m_planes[i].x > 0) {
    				vmin.x = aabb.m_min.x;
    				vmax.x = aabb.m_max.x;
    			}
    			else {
    				vmin.x = aabb.m_max.x;
    				vmax.x = aabb.m_min.x;
    			}
    			// Y axis 
    			if (m_planes[i].y > 0) {
    				vmin.y = aabb.m_min.y;
    				vmax.y = aabb.m_max.y;
    			}
    			else {
    				vmin.y = aabb.m_max.y;
    				vmax.y = aabb.m_min.y;
    			}
    			// Z axis 
    			if (m_planes[i].z > 0) {
    				vmin.z = aabb.m_min.z;
    				vmax.z = aabb.m_max.z;
    			}
    			else {
    				vmin.z = aabb.m_max.z;
    				vmax.z = aabb.m_min.z;
    			}
    
    			if (m_planes[i].x * vmax.x +
    				m_planes[i].y * vmax.y +
    				m_planes[i].z * vmax.z +
    				m_planes[i].w <= 0.0)
    				return false;
    		}
    		return ret;
    	}
    
    	enum FrustumSide
    	{
    		RIGHT = 0,		// The RIGHT side of the frustum
    		LEFT = 1,		// The LEFT	 side of the frustum
    		BOTTOM = 2,		// The BOTTOM side of the frustum
    		TOP = 3,		// The TOP side of the frustum
    		BACK = 4,		// The BACK	side of the frustum
    		FRONT = 5			// The FRONT side of the frustum
    	};
    
    	void NormalizePlane(slVec4& plane)
    	{
    		// Here we calculate the magnitude of the normal to the plane (point A B C)
    		// Remember that (A, B, C) is that same thing as the normal's (X, Y, Z).
    		// To calculate magnitude you use the equation:  magnitude = sqrt( x^2 + y^2 + z^2)
    		float magnitude = (float)sqrt(plane.x * plane.x +
    			plane.y * plane.y +
    			plane.z * plane.z);
    
    		// Then we divide the plane's values by it's magnitude.
    		// This makes it easier to work with.
    		plane.x /= magnitude;
    		plane.y /= magnitude;
    		plane.z /= magnitude;
    		plane.w /= magnitude;
    	}
    
    	// I don't know original author. I saw this in NeHe tutorials.
    	void CalculateFrustum(slMat4& P, slMat4& V)
    	{
    		double* proj = P.data();
    		double* modl = V.data();
    		double   clip[16]; //clipping planes
    
    		clip[0] = modl[0] * proj[0] + modl[1] * proj[4] + modl[2] * proj[8] + modl[3] * proj[12];
    		clip[1] = modl[0] * proj[1] + modl[1] * proj[5] + modl[2] * proj[9] + modl[3] * proj[13];
    		clip[2] = modl[0] * proj[2] + modl[1] * proj[6] + modl[2] * proj[10] + modl[3] * proj[14];
    		clip[3] = modl[0] * proj[3] + modl[1] * proj[7] + modl[2] * proj[11] + modl[3] * proj[15];
    
    		clip[4] = modl[4] * proj[0] + modl[5] * proj[4] + modl[6] * proj[8] + modl[7] * proj[12];
    		clip[5] = modl[4] * proj[1] + modl[5] * proj[5] + modl[6] * proj[9] + modl[7] * proj[13];
    		clip[6] = modl[4] * proj[2] + modl[5] * proj[6] + modl[6] * proj[10] + modl[7] * proj[14];
    		clip[7] = modl[4] * proj[3] + modl[5] * proj[7] + modl[6] * proj[11] + modl[7] * proj[15];
    
    		clip[8] = modl[8] * proj[0] + modl[9] * proj[4] + modl[10] * proj[8] + modl[11] * proj[12];
    		clip[9] = modl[8] * proj[1] + modl[9] * proj[5] + modl[10] * proj[9] + modl[11] * proj[13];
    		clip[10] = modl[8] * proj[2] + modl[9] * proj[6] + modl[10] * proj[10] + modl[11] * proj[14];
    		clip[11] = modl[8] * proj[3] + modl[9] * proj[7] + modl[10] * proj[11] + modl[11] * proj[15];
    
    		clip[12] = modl[12] * proj[0] + modl[13] * proj[4] + modl[14] * proj[8] + modl[15] * proj[12];
    		clip[13] = modl[12] * proj[1] + modl[13] * proj[5] + modl[14] * proj[9] + modl[15] * proj[13];
    		clip[14] = modl[12] * proj[2] + modl[13] * proj[6] + modl[14] * proj[10] + modl[15] * proj[14];
    		clip[15] = modl[12] * proj[3] + modl[13] * proj[7] + modl[14] * proj[11] + modl[15] * proj[15];
    
    
    		//RIGHT 
    		m_planes[RIGHT].x = (clip[3] - clip[0]);
    		m_planes[RIGHT].y = (clip[7] - clip[4]);
    		m_planes[RIGHT].z = (clip[11u] - clip[8]);
    		m_planes[RIGHT].w = (clip[15u] - clip[12u]);
    		NormalizePlane(m_planes[RIGHT]);
    
    		//LEFT  
    		m_planes[LEFT].x = (clip[3] + clip[0]);
    		m_planes[LEFT].y = (clip[7] + clip[4]);
    		m_planes[LEFT].z = (clip[11u] + clip[8]);
    		m_planes[LEFT].w = (clip[15u] + clip[12u]);
    		NormalizePlane(m_planes[LEFT]);
    
    		//BOTTOM  
    		m_planes[BOTTOM].x = (clip[3] + clip[1]);
    		m_planes[BOTTOM].y = (clip[7] + clip[5]);
    		m_planes[BOTTOM].z = (clip[11u] + clip[9]);
    		m_planes[BOTTOM].w = (clip[15u] + clip[13u]);
    		NormalizePlane(m_planes[BOTTOM]);
    
    		//TOP  
    		m_planes[TOP].x = (clip[3] - clip[1]);
    		m_planes[TOP].y = (clip[7] - clip[5]);
    		m_planes[TOP].z = (clip[11u] - clip[9]);
    		m_planes[TOP].w = (clip[15u] - clip[13u]);
    		NormalizePlane(m_planes[TOP]);
    
    		//FAR  
    		m_planes[BACK].x = (clip[3] - clip[2]);
    		m_planes[BACK].y = (clip[7] - clip[6]);
    		m_planes[BACK].z = (clip[11u] - clip[10u]);
    		m_planes[BACK].w = (clip[15u] - clip[14u]);
    		NormalizePlane(m_planes[BACK]);
    
    		//NEAR  
    		m_planes[FRONT].x = (clip[3] + clip[2]);
    		m_planes[FRONT].y = (clip[7] + clip[6]);
    		m_planes[FRONT].z = (clip[11u] + clip[10u]);
    		m_planes[FRONT].w = (clip[15u] + clip[14u]);
    		NormalizePlane(m_planes[FRONT]);
    	}
    };
    

    All this culling must be done in application side.

    From Demo:

    
    m_objectsInFrustum.clear();
    for (int i = 0; i < 100; ++i)
    {
    	if (m_GUICheckRotate->m_isChecked)
    	{
    		float M = float(i + 1) * 0.01;
    		m_objects[i]->RotateX(slMath::DegToRad((30.f * M) * (*m_app->m_dt)));
    		m_objects[i]->RotateY(slMath::DegToRad((25.f * M) * (*m_app->m_dt)));
    		m_objects[i]->RotateZ(slMath::DegToRad((21.f * M) * (*m_app->m_dt)));
    		m_objects[i]->RecalculateWorldMatrix();
    		m_objects[i]->UpdateBV();
    	}
    
    	if (m_GUIRadioBVMethodSphere->m_isChecked)
    	{
    		if(m_camera->m_frust.SphereInFrustum(m_objects[i]->GetBVRadius(), m_objects[i]->GetPosition()))
    			m_objectsInFrustum.push_back(m_objects[i]);
    	}
    	else
    	{
    		if (m_camera->m_frust.AABBInFrustum(m_objects[i]->GetAabbTransformed()))
    			m_objectsInFrustum.push_back(m_objects[i]);
    	}
    }
    

    m_objectsInFrustum is array slArray. After this work with objects in m_objectsInFrustum

    Object rotation

    I not implemented object rotation. Because I don't know how to do it in right way. But I done something, and this is best thing that I've ever done before.

    Add this things into Object class

    
    // only rotation matrix
    slMat4 m_matrixRotation;
    // also add scale
    slVec3 m_scale = slVec3(1.0);
    
    // Order for rotation will be X Y Z
    slQuaternion m_qX;
    slQuaternion m_qY;
    slQuaternion m_qZ;
    slQuaternion m_qOrientation;
    

    Update RecalculateWorldMatrix.

    
    virtual void RecalculateWorldMatrix()
    {
    	m_matrixWorld.identity();
    	m_matrixWorld.m_data[0].x = m_scale.x;
    	m_matrixWorld.m_data[1].y = m_scale.y;
    	m_matrixWorld.m_data[2].z = m_scale.z;
    	
    	// I don't know how to do it fast and better
    	slQuaternion Q;
    	Q = Q * m_qX;
    	Q = Q * m_qY;
    	Q = Q * m_qZ;
    
    	if(m_axisAlignedRotation)
    		m_qOrientation = m_qOrientation * Q;
    	else
    		m_qOrientation = Q * m_qOrientation; // like a airplane
    
    	m_qX.identity();
    	m_qY.identity();
    	m_qZ.identity();
    	m_matrixRotation.set_rotation(m_qOrientation);
    
    	m_matrixWorld = m_matrixWorld * m_matrixRotation;
    
    	m_matrixWorld.m_data[3].x = m_position.x;
    	m_matrixWorld.m_data[3].y = m_position.y;
    	m_matrixWorld.m_data[3].z = m_position.z;
    
    	slSceneObject* parent = dynamic_cast(m_parent);
    	if (parent)
    		m_matrixWorld = m_matrixWorld * parent->m_matrixWorld;
    }
    // I don't know how to call it.
    // if false then object will rotates like airplane
    bool m_axisAlignedRotation = false;
    

    And now I can rotate object like airplane, or not like, I call it m_axisAlignedRotation.

    Rotate object

    
    virtual void RotateY(float rad)
    {
    	slQuaternion q;
    	q.set(0.f, rad, 0.f);
    	m_qY = q * m_qY;
    }
    
    virtual void RotateX(float rad)
    {
    	slQuaternion q;
    	q.set(rad, 0.f, 0.f);
    	m_qX = q * m_qX;
    }
    
    virtual void RotateZ(float rad)
    {
    	slQuaternion q;
    	q.set(0.f, 0.f, rad);
    	m_qZ = q * m_qZ;
    }
    

    I don't know how to store angles, if you try to save angles, and then set rotation by this angles, you will(or can) get wrong result. Better to save angles by yourself.

    slSceneObject is just basic object for more complex game object.

    Also update bounding volume

    
    virtual void UpdateBV()
    {
    	m_aabbTransformed.Transform(&m_aabb, &m_matrixWorld, &m_position);
    	m_BVRadius = slMath::distance(m_aabb.m_min, m_aabb.m_max)*0.5;
    }
    

    Better to use sphere for frustum culling

    Download