本篇是创建3D图形引擎(1)【OO改良版】的续篇,以创建游戏内核【OO改良版】中编写的代码为基础进行开发,细节说明请参阅创建3D图形引擎(2)。
接口:
//==============================================================================
// This class encapsulate for frustum, judge whether other object is in frustum.
//==============================================================================
typedef class FRUSTUM
{
public:
// Construct the six planes from current view and projection.
// Can override the default depth value.
BOOL create(float z_distance);
// The following functions check a single point, cube, rectangle, and sphere if
// contained in the frustum. A return value of TRUE means visible, FALSE not visible.
// When checking cubes or rectangles, you can supply a BOOL variable that determines
// if all the points are in the frustum.
BOOL check_point(float x_pos, float y_pos, float z_pos);
BOOL check_cube(float x_center, float y_center, float z_center,
float radius,
BOOL* completely_contained);
BOOL check_rectangle(float x_center, float y_center, float z_center,
float x_radius, float y_radius, float z_radius,
BOOL* completely_contained);
BOOL check_sphere(float x_center, float y_center, float z_center,
float radius);
private:
D3DXPLANE m_planes[6]; // the frustum planes
} *FRUSTUM_PTR;
实现:
/*************************************************************************
PURPOSE:
Implement for frustum.
*************************************************************************/
#include "core_common.h"
#include "core_graphics.h"
#include "frustum.h"
//----------------------------------------------------------------------------
// Construct frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::create(float z_distance)
{
D3DXMATRIX matrix, mat_view, mat_proj;
// error checking
if(g_d3d_device == NULL)
return FALSE;
// calculate FOV data
g_d3d_device->GetTransform(D3DTS_PROJECTION, &mat_proj);
if(! float_equal(z_distance, 0.0f))
{
// Calculate new projection matrix based on distance provided.
//
// projection matrix is:
//
// | xScale 0 0 0 |
// | 0 yScale 0 0 |
// | 0 0 zf/(zf-zn) 1 |
// | 0 0 -zn*zf/(zf-zn) 0 |
//
// where:
// yScale = cot(fovY/2)
// xScale = yScale / aspect ratio
float z_min = -mat_proj._43 / mat_proj._33;
float q = z_distance / (z_distance - z_min);
mat_proj._33 = q;
mat_proj._43 = -q * z_min;
}
g_d3d_device->GetTransform(D3DTS_VIEW, &mat_view);
D3DXMatrixMultiply(&matrix, &mat_view, &mat_proj);
// calculate the planes
m_planes[0].a = matrix._14 + matrix._13; // Near
m_planes[0].b = matrix._24 + matrix._23;
m_planes[0].c = matrix._34 + matrix._33;
m_planes[0].d = matrix._44 + matrix._43;
D3DXPlaneNormalize(&m_planes[0], &m_planes[0]);
m_planes[1].a = matrix._14 - matrix._13; // Far
m_planes[1].b = matrix._24 - matrix._23;
m_planes[1].c = matrix._34 - matrix._33;
m_planes[1].d = matrix._44 - matrix._43;
D3DXPlaneNormalize(&m_planes[1], &m_planes[1]);
m_planes[2].a = matrix._14 + matrix._11; // Left
m_planes[2].b = matrix._24 + matrix._21;
m_planes[2].c = matrix._34 + matrix._31;
m_planes[2].d = matrix._44 + matrix._41;
D3DXPlaneNormalize(&m_planes[2], &m_planes[2]);
m_planes[3].a = matrix._14 - matrix._11; // Right
m_planes[3].b = matrix._24 - matrix._21;
m_planes[3].c = matrix._34 - matrix._31;
m_planes[3].d = matrix._44 - matrix._41;
D3DXPlaneNormalize(&m_planes[3], &m_planes[3]);
m_planes[4].a = matrix._14 - matrix._12; // Top
m_planes[4].b = matrix._24 - matrix._22;
m_planes[4].c = matrix._34 - matrix._32;
m_planes[4].d = matrix._44 - matrix._42;
D3DXPlaneNormalize(&m_planes[4], &m_planes[4]);
m_planes[5].a = matrix._14 + matrix._12; // Bottom
m_planes[5].b = matrix._24 + matrix._22;
m_planes[5].c = matrix._34 + matrix._32;
m_planes[5].d = matrix._44 + matrix._42;
D3DXPlaneNormalize(&m_planes[5], &m_planes[5]);
return TRUE;
}
//----------------------------------------------------------------------------
// Check one point whether in frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_point(float x_pos, float y_pos, float z_pos)
{
// make sure point is in frustum
for(short i = 0; i < 6; i++)
{
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_pos, y_pos, z_pos)) < 0.0f)
return FALSE;
}
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a cube in frustum, if total cube in frustum then
// completely_contained will be set TRUE.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_cube(float x_center, float y_center, float z_center,
float radius,
BOOL* completely_contained)
{
DWORD num_points_in_frustum = 0;
// count the number of points inside the frustum
for(short i = 0; i < 6; i++)
{
DWORD count = 8;
BOOL in_all_planes = TRUE;
// test all eight points against plane
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center - radius, y_center - radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center + radius, y_center - radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center - radius, y_center + radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center + radius, y_center + radius, z_center - radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center - radius, y_center - radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center + radius, y_center - radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center - radius, y_center + radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center + radius, y_center + radius, z_center + radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
// if none contained, return FALSE.
if(count == 0)
return FALSE;
// update counter if they were all in front of plane.
if(in_all_planes)
++num_points_in_frustum;
}
// store BOOL flag if completely contained
if(completely_contained)
*completely_contained = (num_points_in_frustum == 6);
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a rectangle is in frustum, if total in then completely_contained
// will be set TRUE.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_rectangle(float x_center, float y_center, float z_center,
float x_radius, float y_radius, float z_radius,
BOOL* completely_contained)
{
DWORD num_points_in_frustum = 0;
// count the number of points inside the frustum
for(short i = 0; i < 6; i++)
{
DWORD count = 8;
BOOL in_all_planes = TRUE;
// Test all eight points against plane
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center - y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center - y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center + y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center + y_radius, z_center - z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center - y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center - y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center - x_radius, y_center + y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
if(D3DXPlaneDotCoord(&m_planes[i],
&D3DXVECTOR3(x_center + x_radius, y_center + y_radius, z_center + z_radius)) < 0.0f)
{
in_all_planes = FALSE;
count--;
}
// If none contained, return FALSE
if(count == 0)
return FALSE;
// Update counter if they were all in front of plane
if(in_all_planes)
++num_points_in_frustum;
}
// Store BOOL flag if completely contained
if(completely_contained)
*completely_contained = (num_points_in_frustum == 6);
return TRUE;
}
//----------------------------------------------------------------------------
// Check whether a sphere is in frustum.
//----------------------------------------------------------------------------
BOOL FRUSTUM::check_sphere(float x_center, float y_center, float z_center,
float radius)
{
// make sure radius is in frustum
for(short i = 0; i < 6; i++)
{
if(D3DXPlaneDotCoord(&m_planes[i], &D3DXVECTOR3(x_center, y_center, z_center)) < -radius)
return FALSE;
}
return TRUE;
}
测试代码:
/************************************************************************************
PURPOSE:
frustum test.
************************************************************************************/
#include "core_common.h"
#include "core_framework.h"
#include "core_graphics.h"
#include "core_input.h"
#include "frustum.h"
#define MAX_OBJECTS 256
class APP : public FRAMEWORK
{
public:
BOOL init()
{
if(! create_display(g_hwnd, get_client_width(g_hwnd), get_client_height(g_hwnd), 16, TRUE, TRUE))
return FALSE;
set_perspective(D3DX_PI / 4, 1.3333f, 1.0f, 10000.0f);
// create font
m_font.create("Arial", 16, TRUE, FALSE);
// initialize input and input device
m_input.create(g_hwnd, get_window_inst());
m_mouse.create(&m_input, MOUSE, TRUE);
// load mesh
if(! m_mesh.load("..\\Data\\Yodan.x", "..\\Data\\"))
return FALSE;
for(short i = 0; i < MAX_OBJECTS; i++)
{
m_objects[i].create(&m_mesh);
m_objects[i].move((float) (rand() % 4000) - 2000.0f, 0.0f, (float) (rand() % 4000) - 2000.0f);
}
return TRUE;
}
BOOL frame()
{
// read input device data
m_mouse.read();
// position camera and rotate based on mouse position
m_camera.move(0.0f, 100.0f, 0.0f);
// m_mouse.get_y_delta():
// get mouse's relative x movement coordinate.
//
// m_mouse.get_x_delta():
// get mouse's relative y movement coordinate.
m_camera.rotate_rel(m_mouse.get_y_delta() / 200.0f, m_mouse.get_x_delta() / 200.0f, 0.0f);
// set view transform matrix
g_d3d_device->SetTransform(D3DTS_VIEW, m_camera.get_view_matrix());
// render everything
clear_display(D3DCOLOR_RGBA(0, 64, 128, 255), 1.0f);
// begin render now
if(SUCCEEDED(g_d3d_device->BeginScene()))
{
FRUSTUM frustum;
frustum.create(0.0f);
long num_drawn = 0;
// render each object in frustum
for(short i = 0; i < MAX_OBJECTS; i++)\
{
float radius;
m_objects[i].get_bounds(NULL, NULL, NULL, NULL, NULL, NULL, &radius);
if(frustum.check_sphere(m_objects[i].get_x_pos(), m_objects[i].get_y_pos(), m_objects[i].get_z_pos(),
radius))
{
m_objects[i].render();
num_drawn++;
}
}
char stats[128];
// display statistics
sprintf(stats, "%lu of 256 objects drawn.", num_drawn);
m_font.draw(stats, 0, 0, 400, 100, 0xFFFFFFFF, DT_LEFT | DT_TOP);
g_d3d_device->EndScene();
}
present_display();
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
CAMERA m_camera;
FONT m_font;
INPUT m_input;
INPUT_DEVICE m_mouse;
MESH m_mesh;
OBJECT m_objects[MAX_OBJECTS];
};
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
DWORD client_width = 640;
DWORD client_height = 480;
DWORD x_pos = (get_screen_width() - client_width) / 2;
DWORD y_pos = (get_screen_height() - client_height) / 4;
if(! build_window(inst, "frustum_class", "frustum test",
WS_BORDER | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
x_pos, y_pos, client_width, client_height))
{
return -1;
}
APP app;
app.run();
return 0;
}