raylib icon indicating copy to clipboard operation
raylib copied to clipboard

[rcamera] extend Camera API with some basic Camera Frustum related functions ?

Open SuperUserNameMan opened this issue 1 year ago • 7 comments

Hi @raysan5 ,

I read here #3136 that frustum culling is up to users because Raylib does not contain scene-graph.

But what about some basic Camera-Frustum functions ? (see code below)

Would it be welcome as a PR into rcamera.h ? (edit : or maybe a rfrustum.h ?)

SuperUserNameMan avatar Jun 27 '24 18:06 SuperUserNameMan

i think a example would be better.

MrScautHD avatar Jun 28 '24 10:06 MrScautHD

i think a example would be better.

The problem is that the addition to the API will be relatively large. If published as an example, this would make the example extremely large.

Here is the WIP current version of the API :

// BoundingBoxCornersFlag 
// NOTE: bitwise flags to select corners
typedef enum 
{
	BOX_NO_CORNER          = 0,

	BOX_FRONT_BOTTOM_LEFT  = 1,
	BOX_FRONT_BOTTOM_RIGHT = 2,
	BOX_FRONT_TOP_LEFT     = 4,
	BOX_FRONT_TOP_RIGHT    = 8,

	BOX_BACK_BOTTOM_LEFT  = 16,
	BOX_BACK_BOTTOM_RIGHT = 32,
	BOX_BACK_TOP_LEFT     = 64,
	BOX_BACK_TOP_RIGHT    = 128,

	BOX_ALL_CORNERS       = 255
} BoundingBoxCornersFlag;

typedef struct Frustum {
	Vector4 up;
	Vector4 down;
	Vector4 left;
	Vector4 right;
	Vector4 near;
	Vector4 far;
} Frustum;

float PlaneDistanceToPoint( Vector4 plane , Vector3 point );

bool CheckCollisionPlanePoint( Vector4 plane , Vector3 point );
bool CheckCollisionPlaneSphere( Vector4 plane , Vector3 center , float radius );
bool CheckCollisionPlaneBox( Vector4 plane , BoundingBox box );
int  CheckCollisionPlaneBoxEx( Vector4 plane , BoundingBox box ); // Return a BoundingBoxCornersFlag bitfield

Frustum CameraGetFrustum( Camera *camera , float aspect );

bool FrustumContainsPoint( Frustum frustum , Vector3 point );
bool FrustumContainsSphere( Frustum frustum , Vector3 center , float radius );
bool FrustumContainsBox( Frustum frustum , BoundingBox box );


// Return the frustum of the camera.
// NOTE : The returned frustum is in World Space coordinates.
Frustum CameraGetFrustum( Camera *camera , float aspect )
{
	Frustum frustum ;

	Matrix view = GetCameraViewMatrix( camera );
	Matrix proj = GetCameraProjectionMatrix( camera , aspect );

	Matrix clip = MatrixMultiply( view , proj ); // The frustum is calculated in World Space

	if (1) // TODO : Perspective mode condition
	{
		frustum.left  = Vector4Normalize( (Vector4){ clip.m3 + clip.m0 , clip.m7 + clip.m4 , clip.m11 + clip.m8 , clip.m15 + clip.m12 } );
		frustum.right = Vector4Normalize( (Vector4){ clip.m3 - clip.m0 , clip.m7 - clip.m4 , clip.m11 - clip.m8 , clip.m15 - clip.m12 } );

		frustum.down  = Vector4Normalize( (Vector4){ clip.m3 + clip.m1 , clip.m7 + clip.m5 , clip.m11 + clip.m9 , clip.m15 + clip.m13 } );
		frustum.up    = Vector4Normalize( (Vector4){ clip.m3 - clip.m1 , clip.m7 - clip.m5 , clip.m11 - clip.m9 , clip.m15 - clip.m13  } );

		frustum.near  = Vector4Normalize( (Vector4){ clip.m3 + clip.m2 , clip.m7 + clip.m6 , clip.m11 + clip.m10 , clip.m15 + clip.m14 } );
		frustum.far   = Vector4Normalize( (Vector4){ clip.m3 - clip.m2 , clip.m7 - clip.m6 , clip.m11 - clip.m10 , clip.m15 - clip.m14 } );
	}
	else
	{
		// TODO : Orthogonal mode
	}

	return frustum;
}

// Return the closest (orthogonal) signed distance between a point and a plane.
// NOTE : A negative distance means the point is under the plane.
float PlaneDistanceToPoint( Vector4 plane , Vector3 point )
{
	float d = point.x * plane.x + point.y * plane.y + point.z * plane.z + plane.w ;
	float e = sqrt( plane.x * plane.x + plane.y * plane.y + plane.z * plane.z );
	float distance = d/e ;
	return distance ;
}

// Check if the point is touching or is under the plane.
bool CheckCollisionPlanePoint( Vector4 plane , Vector3 point )
{
	return PlaneDistanceToPoint( plane , point ) <= 0.0f ;
}

// Check if the sphere is touching or is under the plane.
bool CheckCollisionPlaneSphere( Vector4 plane , Vector3 center , float radius )
{
	return PlaneDistanceToPoint( plane , center ) <= radius ;
}

// Check if the box is touching or is under the plane.
bool CheckCollisionPlaneBox( Vector4 plane , BoundingBox box )
{
/*
      F---------G
     /|        /|
    B---------C |
    | |       | |
    | E-------|-H
    |/        |/
    A---------D
*/

	// A and G corners :
	if ( CheckCollisionPlanePoint( plane , box.min ) ) return true;
	if ( CheckCollisionPlanePoint( plane , box.max ) ) return true;

	// B corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.max.y , box.min.z } ) ) return true;

	// C corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.max.y , box.min.z } ) ) return true;

	// D corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.min.y , box.min.z } ) ) return true;

	// E corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.min.y , box.max.z } ) ) return true;

	// F corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.max.y , box.max.z } ) ) return true;

	// H corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.min.y , box.max.z } ) ) return true;

	return false;
}

// Check which box corners are touching or are under the plane.
int CheckCollisionPlaneBoxEx( Vector4 plane , BoundingBox box )
{
/*
      F---------G
     /|        /|
    B---------C |
    | |       | |
    | E-------|-H
    |/        |/
    A---------D
*/
	int corners = BOX_NO_CORNER;

	// A and G corners :
	if ( CheckCollisionPlanePoint( plane , box.min ) ) corners |= BOX_FRONT_BOTTOM_LEFT;
	if ( CheckCollisionPlanePoint( plane , box.max ) ) corners |= BOX_BACK_TOP_RIGHT;

	// B corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.max.y , box.min.z } ) ) corners |= BOX_FRONT_TOP_LEFT;

	// C corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.max.y , box.min.z } ) ) corners |= BOX_FRONT_TOP_RIGHT;

	// D corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.min.y , box.min.z } ) ) corners |= BOX_FRONT_BOTTOM_RIGHT;

	// E corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.min.y , box.max.z } ) ) corners |= BOX_BACK_BOTTOM_LEFT;

	// F corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.min.x , box.max.y , box.max.z } ) ) corners |= BOX_BACK_TOP_LEFT;

	// H corner :
	if ( CheckCollisionPlanePoint( plane , (Vector3){ box.max.x , box.min.y , box.max.z } ) ) corners |= BOX_BACK_BOTTOM_RIGHT;

	return corners;
}

bool FrustumContainsSphere( Frustum frustum , Vector3 center , float radius )
{
	if ( PlaneDistanceToPoint( frustum.left , center ) <= -radius ) return false;
	if ( PlaneDistanceToPoint( frustum.right, center ) <= -radius ) return false;
	if ( PlaneDistanceToPoint( frustum.up   , center ) <= -radius ) return false;
	if ( PlaneDistanceToPoint( frustum.down , center ) <= -radius ) return false;
	if ( PlaneDistanceToPoint( frustum.far  , center ) <= -radius ) return false;
	if ( PlaneDistanceToPoint( frustum.near , center ) <= -radius ) return false;
	return true;
}


bool FrustumContainsPoint( Frustum frustum , Vector3 point )
{
	return FrustumContainsSphere(frustum , point , 0.0f);
}

bool FrustumContainsBox( Frustum frustum , BoundingBox box )
{
	// A box is outside the frustum if all its corners are outside a single plane

	if ( CheckCollisionPlaneBoxEx( frustum.up   , box ) == BOX_ALL_CORNERS ) return false ;
	if ( CheckCollisionPlaneBoxEx( frustum.down , box ) == BOX_ALL_CORNERS ) return false ;
	if ( CheckCollisionPlaneBoxEx( frustum.left , box ) == BOX_ALL_CORNERS ) return false ;
	if ( CheckCollisionPlaneBoxEx( frustum.right, box ) == BOX_ALL_CORNERS ) return false ;
	if ( CheckCollisionPlaneBoxEx( frustum.near , box ) == BOX_ALL_CORNERS ) return false ;
	if ( CheckCollisionPlaneBoxEx( frustum.far  , box ) == BOX_ALL_CORNERS ) return false ;

	return true;
}

SuperUserNameMan avatar Jun 28 '24 11:06 SuperUserNameMan

oh and you forgot about rotated boxes in your example. grafik

Idk, i would like a example more.

MrScautHD avatar Jun 28 '24 11:06 MrScautHD

@SuperUserNameMan This is indeed an interesting addition but not sure where should it belong, it seems a bit out-of-scope for rcamera module and raylib... I think @JeffM2501 already implemented similar functionality in an example and he can share his opinion.

raysan5 avatar Jun 28 '24 20:06 raysan5

I think this should be a drop in lib, not part of raylib itself to help with maintenance. It should go in the raylib-extras repo IMHO.

JeffM2501 avatar Jun 28 '24 20:06 JeffM2501

I'm currently trying to implement everything into a single header without changing anything into Raylib's source code. I'll see how far I can go and keep you updated.

SuperUserNameMan avatar Jun 29 '24 08:06 SuperUserNameMan

I think @JeffM2501 already implemented similar functionality

Here (i think) : https://github.com/JeffM2501/raylibExtras/blob/index/rlExtrasC/Frustum.h

I'm currently adding a Node3D structure in mine to embed Model, and a FrustumDrawNode() function that draws the model only if it is inside the frustum. It will be used to manage LOD too.

Do you have that already ?

SuperUserNameMan avatar Jun 29 '24 08:06 SuperUserNameMan

Implemented as separate library...

raysan5 avatar Aug 24 '24 18:08 raysan5