Files
Client/Library/dxx8/samples/Multimedia/Direct3D/Cull/cull.cpp
LGram16 e067522598 Initial commit: ROW Client source code
Game client codebase including:
- CharacterActionControl: Character and creature management
- GlobalScript: Network, items, skills, quests, utilities
- RYLClient: Main client application with GUI and event handlers
- Engine: 3D rendering engine (RYLGL)
- MemoryManager: Custom memory allocation
- Library: Third-party dependencies (DirectX, boost, etc.)
- Tools: Development utilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 16:24:34 +09:00

1173 lines
44 KiB
C++

//-----------------------------------------------------------------------------
// File: Cull.cpp
//
// Desc: Shows a technique for culling objects whose bounding boxes are
// outside the view frustum. This technique is described at:
// http://www.cs.unc.edu/~hoff/research/vfculler/viewcull.html
//
// Note: This code uses the D3D Framework helper library.
//
// Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include <math.h>
#include <D3DX8.h>
#include "D3DApp.h"
#include "D3DFont.h"
#include "D3DUtil.h"
#include "DXUtil.h"
//-----------------------------------------------------------------------------
// Name: enum CULLSTATE
// Desc: Represents the result of the culling calculation on an object.
//-----------------------------------------------------------------------------
enum CULLSTATE
{
CS_UNKNOWN, // cull state not yet computed
CS_INSIDE, // object bounding box is at least partly inside the frustum
CS_OUTSIDE, // object bounding box is outside the frustum
CS_INSIDE_SLOW, // OBB is inside frustum, but it took extensive testing to determine this
CS_OUTSIDE_SLOW, // OBB is outside frustum, but it took extensive testing to determine this
};
//-----------------------------------------------------------------------------
// Name: struct CULLINFO
// Desc: Stores information that will be used when culling objects. It needs
// to be recomputed whenever the view matrix or projection matrix changes.
//-----------------------------------------------------------------------------
struct CULLINFO
{
D3DXVECTOR3 vecFrustum[8]; // corners of the view frustum
D3DXPLANE planeFrustum[6]; // planes of the view frustum
};
// Prototypes for the culling functions
VOID UpdateCullInfo( CULLINFO* pCullInfo, D3DXMATRIX* pMatView, D3DXMATRIX* pMatProj );
CULLSTATE CullObject( CULLINFO* pCullInfo, D3DXVECTOR3* pVecBounds, D3DXPLANE* pPlaneBounds );
BOOL EdgeIntersectsFace( D3DXVECTOR3* pEdges, D3DXVECTOR3* pFaces, D3DXPLANE* pPlane );
//-----------------------------------------------------------------------------
// Name: struct PLANEVERTEX
// Desc: Custom vertex type used for drawing the frustum planes
//-----------------------------------------------------------------------------
struct PLANEVERTEX
{
D3DXVECTOR3 p;
DWORD color;
};
#define D3DFVF_PLANEVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
//-----------------------------------------------------------------------------
// Name: class CCullableThing
// Desc: A cullable object
//-----------------------------------------------------------------------------
class CCullableThing
{
public:
D3DXVECTOR3 m_pos; // origin of object
FLOAT m_fRotX; // rotation of object around X axis
FLOAT m_fRotY; // rotation of object around Y axis
D3DXMATRIX m_mat; // object's local-to-world transformation
D3DXVECTOR3 m_vecBoundsLocal[8]; // bounding box coordinates (in local coord space)
D3DXVECTOR3 m_vecBoundsWorld[8]; // bounding box coordinates (in world coord space)
D3DXPLANE m_planeBoundsWorld[6]; // bounding box planes (in world coord space)
CULLSTATE m_cullstate; // whether object is in the view frustum
public:
VOID Init(VOID)
{
// Pick a random position and orientation
m_pos = D3DXVECTOR3( (FLOAT)(rand() % 50 - 25), // X is in (-25.0, 25.0)
(FLOAT)(rand() % 50 - 25), // Y is in (-25.0, 25.0)
(FLOAT)(rand() % 25) ); // Z is in ( 0.0, 25.0)
m_fRotX = D3DXToRadian(rand() % 360);
m_fRotY = D3DXToRadian(rand() % 360);
UpdateMatrix();
m_cullstate = CS_UNKNOWN;
}
VOID UpdateMatrix(VOID)
{
// Recompute m_mat, m_vecBoundsWorld, and m_planeBoundsWorld
// when the thing's position, orientation, or bounding box has changed
D3DXMATRIX matRotX, matRotY, matTrans;
D3DXMatrixRotationX( &matRotX, m_fRotX );
D3DXMatrixRotationY( &matRotY, m_fRotY );
D3DXMatrixTranslation( &matTrans, m_pos.x, m_pos.y, m_pos.z );
m_mat = matRotX * matRotY * matTrans;
// Transform bounding box coords from local space to world space
for( int i = 0; i < 8; i++ )
D3DXVec3TransformCoord( &m_vecBoundsWorld[i], &m_vecBoundsLocal[i], &m_mat );
// Determine planes of the bounding box
D3DXPlaneFromPoints( &m_planeBoundsWorld[0], &m_vecBoundsWorld[0],
&m_vecBoundsWorld[1], &m_vecBoundsWorld[2] ); // Near
D3DXPlaneFromPoints( &m_planeBoundsWorld[1], &m_vecBoundsWorld[6],
&m_vecBoundsWorld[7], &m_vecBoundsWorld[5] ); // Far
D3DXPlaneFromPoints( &m_planeBoundsWorld[2], &m_vecBoundsWorld[2],
&m_vecBoundsWorld[6], &m_vecBoundsWorld[4] ); // Left
D3DXPlaneFromPoints( &m_planeBoundsWorld[3], &m_vecBoundsWorld[7],
&m_vecBoundsWorld[3], &m_vecBoundsWorld[5] ); // Right
D3DXPlaneFromPoints( &m_planeBoundsWorld[4], &m_vecBoundsWorld[2],
&m_vecBoundsWorld[3], &m_vecBoundsWorld[6] ); // Top
D3DXPlaneFromPoints( &m_planeBoundsWorld[5], &m_vecBoundsWorld[1],
&m_vecBoundsWorld[0], &m_vecBoundsWorld[4] ); // Bottom
}
};
//-----------------------------------------------------------------------------
// Name: struct Camera
// Desc:
//-----------------------------------------------------------------------------
struct Camera
{
D3DXVECTOR3 m_vPosition;
D3DXVECTOR3 m_vVelocity;
FLOAT m_fYaw;
FLOAT m_fYawVelocity;
FLOAT m_fPitch;
FLOAT m_fPitchVelocity;
D3DXMATRIX m_matView;
D3DXMATRIX m_matOrientation;
};
//-----------------------------------------------------------------------------
// Name: class CMyD3DApplication
// Desc: Application class. The base class (CD3DApplication) provides the
// generic functionality needed in all Direct3D samples. CMyD3DApplication
// adds functionality specific to this sample program.
//-----------------------------------------------------------------------------
class CMyD3DApplication : public CD3DApplication
{
CD3DFont* m_pFont; // Font for drawing text
CD3DFont* m_pFontSmall;
LPD3DXMESH m_pMeshTeapot; // Mesh of thing to be cull-tested
LPD3DXMESH m_pMeshBox; // Mesh to visualize bounding box
D3DXMATRIX m_matBox; // Matrix that places bounding box correctly on teapot
LPDIRECT3DVERTEXBUFFER8 m_pPlaneVB[6]; // VBs to visualize the view frustum
// The things to render and cull
CCullableThing m_CullableThingArray[50];
DWORD m_dwNumCullableThings;
// Variables for determining the view
BYTE m_bKey[256];
BOOL m_bLeftActive; // left vs right view currently active
Camera m_CameraLeft;
Camera m_CameraRight;
D3DXMATRIX m_matProjLeft;
D3DXMATRIX m_matProjRight;
D3DMATERIAL8 m_mtrlOutside;
D3DMATERIAL8 m_mtrlInside;
D3DMATERIAL8 m_mtrlOutsideSlow;
D3DMATERIAL8 m_mtrlInsideSlow;
D3DMATERIAL8 m_mtrlWhite;
CULLINFO m_cullinfo;
BOOL m_bShowHelp;
protected:
HRESULT OneTimeSceneInit();
HRESULT InitDeviceObjects();
HRESULT RestoreDeviceObjects();
HRESULT InvalidateDeviceObjects();
HRESULT DeleteDeviceObjects();
HRESULT Render();
HRESULT RenderScene( BOOL bRenderPlanes );
HRESULT FrameMove();
HRESULT FinalCleanup();
VOID UpdateCamera(Camera* pCamera);
HRESULT UpdatePlaneVBs(VOID);
VOID CullObjects(VOID);
public:
CMyD3DApplication();
LRESULT MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
};
//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Entry point to the program. Initializes everything, and goes into a
// message-processing loop. Idle time is used to render the scene.
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
CMyD3DApplication d3dApp;
if( FAILED( d3dApp.Create( hInst ) ) )
return 0;
return d3dApp.Run();
}
//-----------------------------------------------------------------------------
// Name: CMyD3DApplication()
// Desc: Application constructor. Sets attributes for the app.
//-----------------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
{
m_strWindowTitle = _T("Cull: Culling nonvisible objects");
m_bUseDepthBuffer = TRUE;
m_dwCreationWidth = 600;
m_dwCreationHeight = 300;
m_bShowCursorWhenFullscreen = TRUE;
m_pFont = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD );
m_pFontSmall = new CD3DFont( _T("Arial"), 9, D3DFONT_BOLD );
m_pMeshTeapot = NULL;
m_pMeshBox = NULL;
m_dwNumCullableThings = 0;
ZeroMemory( m_pPlaneVB, sizeof(m_pPlaneVB) );
ZeroMemory( m_bKey, 256 );
ZeroMemory( &m_CameraLeft, sizeof(m_CameraLeft) );
ZeroMemory( &m_CameraRight, sizeof(m_CameraRight) );
m_bLeftActive = TRUE;
m_bShowHelp = FALSE;
}
//-----------------------------------------------------------------------------
// Name: OneTimeSceneInit()
// Desc: Called during initial app startup, this function performs all the
// permanent initialization.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::OneTimeSceneInit()
{
// Initialize cullable things
for( int i = 0; i < 50; i++ )
{
m_CullableThingArray[m_dwNumCullableThings].Init();
m_dwNumCullableThings++;
}
UpdateCamera( &m_CameraLeft );
UpdateCamera( &m_CameraRight );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: FrameMove()
// Desc: Called once per frame, the call is the entry point for animating
// the scene.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FrameMove()
{
BOOL bNeedToCull = FALSE;
// Handle object rotations
if( m_bKey['Y'] || m_bKey['U'] || m_bKey['H'] || m_bKey['J'])
{
CCullableThing* pCullableThing;
for( DWORD iThing = 0; iThing < m_dwNumCullableThings; iThing++ )
{
pCullableThing = &m_CullableThingArray[iThing];
if( m_bKey['Y'] )
pCullableThing->m_fRotY += m_fElapsedTime;
else if( m_bKey['U'] )
pCullableThing->m_fRotY -= m_fElapsedTime;
if( m_bKey['H'] )
pCullableThing->m_fRotX += m_fElapsedTime;
else if( m_bKey['J'] )
pCullableThing->m_fRotX -= m_fElapsedTime;
pCullableThing->UpdateMatrix();
}
bNeedToCull = TRUE;
}
// Handle camera motion
UpdateCamera(m_bLeftActive ? &m_CameraLeft : &m_CameraRight);
if( !m_bLeftActive )
{
UpdateCullInfo( &m_cullinfo, &m_CameraRight.m_matView, &m_matProjRight );
bNeedToCull = TRUE;
UpdatePlaneVBs();
}
// Re-determine cull state of all objects if necessary
if( bNeedToCull )
CullObjects();
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Called once per frame, the call is the entry point for 3d
// rendering. This function sets up render states, clears the
// viewport, and renders the scene.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::Render()
{
// The "hot" clear color is used to indicate the viewport
// whose camera is currently being controlled by the keyboard
const DWORD dwHotClearColor = 0xff0000ff;
const DWORD dwColdClearColor = 0xff000080;
//
// Draw left viewport
//
D3DVIEWPORT8 vp;
vp.X = 0;
vp.Y = 0;
vp.Width = m_d3dsdBackBuffer.Width / 2;
vp.Height = m_d3dsdBackBuffer.Height;
vp.MinZ = 0.0f;
vp.MaxZ = 1.0f;
m_pd3dDevice->SetViewport( &vp );
m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
m_bLeftActive ? dwHotClearColor : dwColdClearColor, 1.0f, 0L );
m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_CameraLeft.m_matView );
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProjLeft );
// Draw contents of left viewport
RenderScene( TRUE ); // TRUE means render frustum planes
//
// Draw right viewport
//
vp.X = m_d3dsdBackBuffer.Width / 2;
vp.Width = m_d3dsdBackBuffer.Width / 2;
m_pd3dDevice->SetViewport( &vp );
m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
m_bLeftActive ? dwColdClearColor : dwHotClearColor, 1.0f, 0L );
m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_CameraRight.m_matView );
m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_matProjRight );
// Draw contents of right viewport
RenderScene( FALSE ); // FALSE means don't render frustum planes
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: RenderScene()
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::RenderScene( BOOL bRenderPlanes )
{
// Begin the scene
if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
{
// Render each object
CCullableThing* pCullableThing;
for( DWORD iThing = 0; iThing < m_dwNumCullableThings; iThing++ )
{
pCullableThing = &m_CullableThingArray[iThing];
// Normally, if the cullstate is CS_OUTSIDE or CS_OUTSIDE_SLOW,
// there is no need to ask D3D to render the object since
// it's outside the view frustum. Since this app is meant to
// visualize the culling process, all objects are passed to D3D,
// and different colors are used to show their cullstates.
switch( pCullableThing->m_cullstate )
{
case CS_UNKNOWN:
m_pd3dDevice->SetMaterial( &m_mtrlWhite );
break;
case CS_OUTSIDE:
m_pd3dDevice->SetMaterial( &m_mtrlOutside );
break;
case CS_INSIDE:
m_pd3dDevice->SetMaterial( &m_mtrlInside );
break;
case CS_OUTSIDE_SLOW:
m_pd3dDevice->SetMaterial( &m_mtrlOutsideSlow );
break;
case CS_INSIDE_SLOW:
m_pd3dDevice->SetMaterial( &m_mtrlInsideSlow );
break;
}
m_pd3dDevice->SetTransform( D3DTS_WORLD, &pCullableThing->m_mat );
m_pMeshTeapot->DrawSubset( 0 );
D3DXMATRIX mat = m_matBox * pCullableThing->m_mat;
m_pd3dDevice->SetTransform( D3DTS_WORLD, &mat );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
m_pMeshBox->DrawSubset( 0 );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
}
if( bRenderPlanes )
{
// Render frustum planes
m_pd3dDevice->SetMaterial( &m_mtrlWhite );
D3DXMATRIX mat;
D3DXMatrixIdentity( &mat );
m_pd3dDevice->SetTransform( D3DTS_WORLD, &mat );
m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
m_pd3dDevice->SetVertexShader( D3DFVF_PLANEVERTEX );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
for( int iPlane = 0; iPlane < 6; iPlane++ )
{
m_pd3dDevice->SetStreamSource( 0, m_pPlaneVB[iPlane], sizeof(PLANEVERTEX) );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );
}
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
}
// Output statistics
m_pFont->DrawText( 2, 0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats );
m_pFont->DrawText( 2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats );
// Show help
if( m_bShowHelp )
{
m_pFontSmall->DrawText( 2, 40, D3DCOLOR_ARGB(255,200,100,100),
_T("Press F1 to hide help\n")
_T("The right viewport is the frustum being culled against.\n")
_T("The left viewport has its own camera that lets you view\n")
_T("the scene and frustum from different angles.\n")
_T("Click the viewport whose camera you want to control.\n")
_T("Keyboard controls:") );
m_pFontSmall->DrawText( 20, 140, D3DCOLOR_ARGB(255,200,100,100),
_T("Move\n")
_T("Turn\n")
_T("Spin objects\n")
_T("Snap left view to viewport\n")
_T("Snap right view to origin\n") );
m_pFontSmall->DrawText( 210, 140, D3DCOLOR_ARGB(255,200,100,100),
_T("W, S, Arrow keys\n")
_T("Q, E, A, Z\n")
_T("Y, U, H, J\n")
_T("N\n")
_T("M") );
}
else
{
m_pFontSmall->DrawText( 2, 40, D3DCOLOR_ARGB(255,200,100,100),
_T("Press F1 for help") );
}
// End the scene.
m_pd3dDevice->EndScene();
}
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: UpdateCamera()
// Desc:
//-----------------------------------------------------------------------------
VOID CMyD3DApplication::UpdateCamera(Camera* pCamera)
{
FLOAT fElapsedTime;
if( m_fElapsedTime > 0.0f )
fElapsedTime = m_fElapsedTime;
else
fElapsedTime = 0.05f;
FLOAT fSpeed = 5.0f*fElapsedTime;
FLOAT fAngularSpeed = 2.0f*fElapsedTime;
// De-accelerate the camera movement (for smooth motion)
pCamera->m_vVelocity *= 0.75f;
pCamera->m_fYawVelocity *= 0.75f;
pCamera->m_fPitchVelocity *= 0.75f;
// Process keyboard input
if( m_bKey[VK_RIGHT] ) pCamera->m_vVelocity.x += fSpeed; // Slide Right
if( m_bKey[VK_LEFT] ) pCamera->m_vVelocity.x -= fSpeed; // Slide Left
if( m_bKey[VK_UP] ) pCamera->m_vVelocity.y += fSpeed; // Slide Up
if( m_bKey[VK_DOWN] ) pCamera->m_vVelocity.y -= fSpeed; // Slide Down
if( m_bKey['W'] ) pCamera->m_vVelocity.z += fSpeed; // Move Forward
if( m_bKey['S'] ) pCamera->m_vVelocity.z -= fSpeed; // Move Backward
if( m_bKey['E'] ) pCamera->m_fYawVelocity += fSpeed; // Turn Right
if( m_bKey['Q'] ) pCamera->m_fYawVelocity -= fSpeed; // Turn Left
if( m_bKey['Z'] ) pCamera->m_fPitchVelocity += fSpeed; // Turn Down
if( m_bKey['A'] ) pCamera->m_fPitchVelocity -= fSpeed; // Turn Up
// Update the position vector
D3DXVECTOR3 vT = pCamera->m_vVelocity * fSpeed;
D3DXVec3TransformNormal( &vT, &vT, &pCamera->m_matOrientation );
pCamera->m_vPosition += vT;
// Update the yaw-pitch-rotation vector
pCamera->m_fYaw += fAngularSpeed * pCamera->m_fYawVelocity;
pCamera->m_fPitch += fAngularSpeed * pCamera->m_fPitchVelocity;
if( pCamera->m_fPitch < -D3DX_PI/2 )
pCamera->m_fPitch = -D3DX_PI/2;
if( pCamera->m_fPitch > D3DX_PI/2 )
pCamera->m_fPitch = D3DX_PI/2;
// Set the view matrix
D3DXQUATERNION qR;
D3DXQuaternionRotationYawPitchRoll( &qR, pCamera->m_fYaw, pCamera->m_fPitch, 0.0f );
D3DXMatrixAffineTransformation( &pCamera->m_matOrientation, 1.25f, NULL, &qR, &pCamera->m_vPosition );
D3DXMatrixInverse( &pCamera->m_matView, NULL, &pCamera->m_matOrientation );
}
//-----------------------------------------------------------------------------
// Name: UpdatePlaneVBs()
// Desc: Update the vertex buffers to match the view frustum. Slightly
// different colors are used for each plane to make them easier to
// distinguish from each other.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::UpdatePlaneVBs()
{
HRESULT hr;
for( int iPlane = 0; iPlane < 6; iPlane++ )
{
PLANEVERTEX* v;
if( FAILED(hr = m_pPlaneVB[iPlane]->Lock( 0, 0, (BYTE**)&v, 0 ) ) )
return hr;
switch( iPlane )
{
case 0: // near
v[0].p = m_cullinfo.vecFrustum[0];
v[1].p = m_cullinfo.vecFrustum[1];
v[2].p = m_cullinfo.vecFrustum[2];
v[3].p = m_cullinfo.vecFrustum[3];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80505050;
break;
case 1: // far
v[0].p = m_cullinfo.vecFrustum[4];
v[1].p = m_cullinfo.vecFrustum[6];
v[2].p = m_cullinfo.vecFrustum[5];
v[3].p = m_cullinfo.vecFrustum[7];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80404040;
break;
case 2: // left
v[0].p = m_cullinfo.vecFrustum[0];
v[1].p = m_cullinfo.vecFrustum[2];
v[2].p = m_cullinfo.vecFrustum[4];
v[3].p = m_cullinfo.vecFrustum[6];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80404040;
break;
case 3:
v[0].p = m_cullinfo.vecFrustum[1];
v[1].p = m_cullinfo.vecFrustum[3];
v[2].p = m_cullinfo.vecFrustum[5];
v[3].p = m_cullinfo.vecFrustum[7];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80606060;
break;
case 4:
v[0].p = m_cullinfo.vecFrustum[2];
v[1].p = m_cullinfo.vecFrustum[3];
v[2].p = m_cullinfo.vecFrustum[6];
v[3].p = m_cullinfo.vecFrustum[7];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80505050;
break;
case 5:
v[0].p = m_cullinfo.vecFrustum[0];
v[1].p = m_cullinfo.vecFrustum[1];
v[2].p = m_cullinfo.vecFrustum[4];
v[3].p = m_cullinfo.vecFrustum[5];
v[0].color = v[1].color = v[2].color = v[3].color = 0x80505050;
break;
}
m_pPlaneVB[iPlane]->Unlock();
}
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: InitDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InitDeviceObjects()
{
HRESULT hr;
// Initialize the font's internal textures
if( FAILED( hr = m_pFont->InitDeviceObjects( m_pd3dDevice ) ) )
return hr;
if( FAILED( hr = m_pFontSmall->InitDeviceObjects( m_pd3dDevice ) ) )
return hr;
// Create a teapot mesh
if( FAILED( D3DXCreateTeapot( m_pd3dDevice, &m_pMeshTeapot, NULL ) ) )
return hr;
D3DXComputeNormals( m_pMeshTeapot, NULL );
// Determine bounding box of the teapot
D3DXVECTOR3 vecMin;
D3DXVECTOR3 vecMax;
BYTE* pVertices;
if( FAILED( hr = m_pMeshTeapot->LockVertexBuffer(D3DLOCK_READONLY, &pVertices) ) )
return hr;
hr = D3DXComputeBoundingBox( pVertices, m_pMeshTeapot->GetNumVertices(),
m_pMeshTeapot->GetFVF(), &vecMin, &vecMax );
m_pMeshTeapot->UnlockVertexBuffer();
// Note that the m_vecBoundsLocal are identical for every CullableThing
// because the cullable things are all the same teapot object. In a real
// app, of course, they would all be potentially different.
CCullableThing* pCullableThing;
for( DWORD iThing = 0; iThing < m_dwNumCullableThings; iThing++ )
{
pCullableThing = &m_CullableThingArray[iThing];
pCullableThing->m_vecBoundsLocal[0] = D3DXVECTOR3( vecMin.x, vecMin.y, vecMin.z ); // xyz
pCullableThing->m_vecBoundsLocal[1] = D3DXVECTOR3( vecMax.x, vecMin.y, vecMin.z ); // Xyz
pCullableThing->m_vecBoundsLocal[2] = D3DXVECTOR3( vecMin.x, vecMax.y, vecMin.z ); // xYz
pCullableThing->m_vecBoundsLocal[3] = D3DXVECTOR3( vecMax.x, vecMax.y, vecMin.z ); // XYz
pCullableThing->m_vecBoundsLocal[4] = D3DXVECTOR3( vecMin.x, vecMin.y, vecMax.z ); // xyZ
pCullableThing->m_vecBoundsLocal[5] = D3DXVECTOR3( vecMax.x, vecMin.y, vecMax.z ); // XyZ
pCullableThing->m_vecBoundsLocal[6] = D3DXVECTOR3( vecMin.x, vecMax.y, vecMax.z ); // xYZ
pCullableThing->m_vecBoundsLocal[7] = D3DXVECTOR3( vecMax.x, vecMax.y, vecMax.z ); // XYZ
pCullableThing->UpdateMatrix();
}
// Set up m_matBox to be able to render the bounding box properly
D3DXMATRIX matTrans;
D3DXMATRIX matScale;
D3DXMatrixTranslation( &matTrans, (vecMin.x + vecMax.x) / 2,
(vecMin.y + vecMax.y) / 2, (vecMin.z + vecMax.z) / 2 );
D3DXMatrixScaling( &matScale, vecMax.x - vecMin.x, vecMax.y - vecMin.y, vecMax.z - vecMin.z );
m_matBox = matScale * matTrans;
if( FAILED( hr = D3DXCreateBox( m_pd3dDevice, 1.0f, 1.0f, 1.0f, &m_pMeshBox, NULL ) ) )
return hr;
D3DXComputeNormals( m_pMeshBox, NULL );
// Create VBs for frustum planes
for( int iPlane = 0; iPlane < 6; iPlane++ )
{
if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( 4*sizeof(PLANEVERTEX),
D3DUSAGE_WRITEONLY, D3DFVF_PLANEVERTEX, D3DPOOL_MANAGED, &m_pPlaneVB[iPlane] ) ) )
{
return hr;
}
}
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: RestoreDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
m_pFont->RestoreDeviceObjects();
m_pFontSmall->RestoreDeviceObjects();
// Set miscellaneous render states
m_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
// Set up the matrices
FLOAT fAspect = m_d3dsdBackBuffer.Width / (FLOAT)m_d3dsdBackBuffer.Height;
D3DXMatrixPerspectiveFovLH( &m_matProjRight, D3DX_PI/4, fAspect, 0.2f, 10.0f );
D3DXMatrixPerspectiveFovLH( &m_matProjLeft, D3DX_PI/4, fAspect, 0.2f, 200.0f );
// Set up a light
if( ( m_d3dCaps.VertexProcessingCaps & D3DVTXPCAPS_DIRECTIONALLIGHTS ) ||
!( m_dwCreateFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING ) )
{
D3DLIGHT8 light;
D3DUtil_InitLight( light, D3DLIGHT_DIRECTIONAL, 0.2f, -0.4f, 0.2f );
m_pd3dDevice->SetLight( 0, &light );
m_pd3dDevice->LightEnable( 0, TRUE );
}
m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0xff555555 );
// Set up materials
D3DUtil_InitMaterial(m_mtrlInside, 0.2f, 0.6f, 0.2f, 0.5f); // dark green
D3DUtil_InitMaterial(m_mtrlOutside, 0.6f, 0.2f, 0.2f, 0.5f); // dark red
D3DUtil_InitMaterial(m_mtrlInsideSlow, 0.7f, 1.0f, 0.7f, 0.5f); // pastel green
D3DUtil_InitMaterial(m_mtrlOutsideSlow, 1.0f, 0.7f, 0.7f, 0.5f); // pastel red
D3DUtil_InitMaterial(m_mtrlWhite, 1.0f, 1.0f, 1.0f, 0.5f); // white
UpdateCullInfo( &m_cullinfo, &m_CameraRight.m_matView, &m_matProjRight );
CullObjects();
UpdatePlaneVBs();
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: InvalidateDeviceObjects()
// Desc:
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InvalidateDeviceObjects()
{
m_pFont->InvalidateDeviceObjects();
m_pFontSmall->InvalidateDeviceObjects();
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: DeleteDeviceObjects()
// Desc: Called when the app is exiting, or the device is being changed,
// this function deletes any device dependent objects.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::DeleteDeviceObjects()
{
m_pFont->DeleteDeviceObjects();
m_pFontSmall->DeleteDeviceObjects();
SAFE_RELEASE( m_pMeshTeapot );
SAFE_RELEASE( m_pMeshBox );
for( int iPlane = 0; iPlane < 6; iPlane++ )
SAFE_RELEASE( m_pPlaneVB[iPlane] );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: FinalCleanup()
// Desc: Called before the app exits, this function gives the app the chance
// to cleanup after itself.
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FinalCleanup()
{
SAFE_DELETE( m_pFont );
SAFE_DELETE( m_pFontSmall );
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: MsgProc()
// Desc: Message proc function to handle key and menu input
//-----------------------------------------------------------------------------
LRESULT CMyD3DApplication::MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam )
{
// Record key presses
if( WM_KEYDOWN == uMsg )
{
m_bKey[wParam] = 1;
}
// Perform commands when keys are released
if( WM_KEYUP == uMsg )
{
m_bKey[wParam] = 0;
switch( wParam )
{
case 'N': // Set left camera to match right camera
m_CameraLeft = m_CameraRight;
break;
case 'M': // Reset right camera to original orientation
ZeroMemory( &m_CameraRight, sizeof(m_CameraRight) );
UpdateCamera( &m_CameraRight );
UpdateCullInfo( &m_cullinfo, &m_CameraRight.m_matView, &m_matProjRight );
CullObjects();
UpdatePlaneVBs();
break;
case VK_F1:
m_bShowHelp = !m_bShowHelp;
break;
}
}
if( WM_LBUTTONDOWN == uMsg )
{
INT x = LOWORD(lParam);
if( x < (INT)m_d3dsdBackBuffer.Width / 2 )
m_bLeftActive = TRUE;
else
m_bLeftActive = FALSE;
}
return CD3DApplication::MsgProc( hWnd, uMsg, wParam, lParam );
}
//-----------------------------------------------------------------------------
// Name: CullObjects()
// Desc: Cull each object in the CCullableThing array
//-----------------------------------------------------------------------------
VOID CMyD3DApplication::CullObjects(VOID)
{
CCullableThing* pCullableThing;
for( DWORD iThing = 0; iThing < m_dwNumCullableThings; iThing++ )
{
pCullableThing = &m_CullableThingArray[iThing];
pCullableThing->m_cullstate = CullObject( &m_cullinfo,
pCullableThing->m_vecBoundsWorld, pCullableThing->m_planeBoundsWorld );
}
}
//-----------------------------------------------------------------------------
// Name: UpdateCullInfo()
// Desc: Sets up the frustum planes, endpoints, and center for the frustum
// defined by a given view matrix and projection matrix. This info will
// be used when culling each object in CullObject().
//-----------------------------------------------------------------------------
VOID UpdateCullInfo( CULLINFO* pCullInfo, D3DXMATRIX* pMatView, D3DXMATRIX* pMatProj )
{
D3DXMATRIX mat;
D3DXMatrixMultiply( &mat, pMatView, pMatProj );
D3DXMatrixInverse( &mat, NULL, &mat );
pCullInfo->vecFrustum[0] = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // xyz
pCullInfo->vecFrustum[1] = D3DXVECTOR3( 1.0f, -1.0f, 0.0f); // Xyz
pCullInfo->vecFrustum[2] = D3DXVECTOR3(-1.0f, 1.0f, 0.0f); // xYz
pCullInfo->vecFrustum[3] = D3DXVECTOR3( 1.0f, 1.0f, 0.0f); // XYz
pCullInfo->vecFrustum[4] = D3DXVECTOR3(-1.0f, -1.0f, 1.0f); // xyZ
pCullInfo->vecFrustum[5] = D3DXVECTOR3( 1.0f, -1.0f, 1.0f); // XyZ
pCullInfo->vecFrustum[6] = D3DXVECTOR3(-1.0f, 1.0f, 1.0f); // xYZ
pCullInfo->vecFrustum[7] = D3DXVECTOR3( 1.0f, 1.0f, 1.0f); // XYZ
for( INT i = 0; i < 8; i++ )
D3DXVec3TransformCoord( &pCullInfo->vecFrustum[i], &pCullInfo->vecFrustum[i], &mat );
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[0], &pCullInfo->vecFrustum[0],
&pCullInfo->vecFrustum[1], &pCullInfo->vecFrustum[2] ); // Near
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[1], &pCullInfo->vecFrustum[6],
&pCullInfo->vecFrustum[7], &pCullInfo->vecFrustum[5] ); // Far
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[2], &pCullInfo->vecFrustum[2],
&pCullInfo->vecFrustum[6], &pCullInfo->vecFrustum[4] ); // Left
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[3], &pCullInfo->vecFrustum[7],
&pCullInfo->vecFrustum[3], &pCullInfo->vecFrustum[5] ); // Right
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[4], &pCullInfo->vecFrustum[2],
&pCullInfo->vecFrustum[3], &pCullInfo->vecFrustum[6] ); // Top
D3DXPlaneFromPoints( &pCullInfo->planeFrustum[5], &pCullInfo->vecFrustum[1],
&pCullInfo->vecFrustum[0], &pCullInfo->vecFrustum[4] ); // Bottom
}
//-----------------------------------------------------------------------------
// Name: CullObject()
// Desc: Determine the cullstate for an object bounding box (OBB).
// The algorithm is:
// 1) If any OBB corner pt is inside the frustum, return CS_INSIDE
// 2) Else if all OBB corner pts are outside a single frustum plane,
// return CS_OUTSIDE
// 3) Else if any frustum edge penetrates a face of the OBB, return
// CS_INSIDE_SLOW
// 4) Else if any OBB edge penetrates a face of the frustum, return
// CS_INSIDE_SLOW
// 5) Else if any point in the frustum is outside any plane of the
// OBB, return CS_OUTSIDE_SLOW
// 6) Else return CS_INSIDE_SLOW
//-----------------------------------------------------------------------------
CULLSTATE CullObject( CULLINFO* pCullInfo, D3DXVECTOR3* pVecBounds,
D3DXPLANE* pPlaneBounds )
{
BYTE bOutside[8];
ZeroMemory( &bOutside, sizeof(bOutside) );
// Check boundary vertices against all 6 frustum planes,
// and store result (1 if outside) in a bitfield
for( int iPoint = 0; iPoint < 8; iPoint++ )
{
for( int iPlane = 0; iPlane < 6; iPlane++ )
{
if( pCullInfo->planeFrustum[iPlane].a * pVecBounds[iPoint].x +
pCullInfo->planeFrustum[iPlane].b * pVecBounds[iPoint].y +
pCullInfo->planeFrustum[iPlane].c * pVecBounds[iPoint].z +
pCullInfo->planeFrustum[iPlane].d < 0)
{
bOutside[iPoint] |= (1 << iPlane);
}
}
// If any point is inside all 6 frustum planes, it is inside
// the frustum, so the object must be rendered.
if( bOutside[iPoint] == 0 )
return CS_INSIDE;
}
// If all points are outside any single frustum plane, the object is
// outside the frustum
if( (bOutside[0] & bOutside[1] & bOutside[2] & bOutside[3] &
bOutside[4] & bOutside[5] & bOutside[6] & bOutside[7]) != 0 )
{
return CS_OUTSIDE;
}
// Now see if any of the frustum edges penetrate any of the faces of
// the bounding box
D3DXVECTOR3 edge[12][2] =
{
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[1], // front bottom
pCullInfo->vecFrustum[2], pCullInfo->vecFrustum[3], // front top
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[2], // front left
pCullInfo->vecFrustum[1], pCullInfo->vecFrustum[3], // front right
pCullInfo->vecFrustum[4], pCullInfo->vecFrustum[5], // back bottom
pCullInfo->vecFrustum[6], pCullInfo->vecFrustum[7], // back top
pCullInfo->vecFrustum[4], pCullInfo->vecFrustum[6], // back left
pCullInfo->vecFrustum[5], pCullInfo->vecFrustum[7], // back right
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[4], // left bottom
pCullInfo->vecFrustum[2], pCullInfo->vecFrustum[6], // left top
pCullInfo->vecFrustum[1], pCullInfo->vecFrustum[5], // right bottom
pCullInfo->vecFrustum[3], pCullInfo->vecFrustum[7], // right top
};
D3DXVECTOR3 face[6][4] =
{
pVecBounds[0], pVecBounds[2], pVecBounds[3], pVecBounds[1], // front
pVecBounds[4], pVecBounds[5], pVecBounds[7], pVecBounds[6], // back
pVecBounds[0], pVecBounds[4], pVecBounds[6], pVecBounds[2], // left
pVecBounds[1], pVecBounds[3], pVecBounds[7], pVecBounds[5], // right
pVecBounds[2], pVecBounds[6], pVecBounds[7], pVecBounds[3], // top
pVecBounds[0], pVecBounds[4], pVecBounds[5], pVecBounds[1], // bottom
};
D3DXVECTOR3* pEdge;
D3DXVECTOR3* pFace;
pEdge = &edge[0][0];
for( INT iEdge = 0; iEdge < 12; iEdge++ )
{
pFace = &face[0][0];
for( INT iFace = 0; iFace < 6; iFace++ )
{
if( EdgeIntersectsFace( pEdge, pFace, &pPlaneBounds[iFace] ) )
{
return CS_INSIDE_SLOW;
}
pFace += 4;
}
pEdge += 2;
}
// Now see if any of the bounding box edges penetrate any of the faces of
// the frustum
D3DXVECTOR3 edge2[12][2] =
{
pVecBounds[0], pVecBounds[1], // front bottom
pVecBounds[2], pVecBounds[3], // front top
pVecBounds[0], pVecBounds[2], // front left
pVecBounds[1], pVecBounds[3], // front right
pVecBounds[4], pVecBounds[5], // back bottom
pVecBounds[6], pVecBounds[7], // back top
pVecBounds[4], pVecBounds[6], // back left
pVecBounds[5], pVecBounds[7], // back right
pVecBounds[0], pVecBounds[4], // left bottom
pVecBounds[2], pVecBounds[6], // left top
pVecBounds[1], pVecBounds[5], // right bottom
pVecBounds[3], pVecBounds[7], // right top
};
D3DXVECTOR3 face2[6][4] =
{
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[2], pCullInfo->vecFrustum[3], pCullInfo->vecFrustum[1], // front
pCullInfo->vecFrustum[4], pCullInfo->vecFrustum[5], pCullInfo->vecFrustum[7], pCullInfo->vecFrustum[6], // back
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[4], pCullInfo->vecFrustum[6], pCullInfo->vecFrustum[2], // left
pCullInfo->vecFrustum[1], pCullInfo->vecFrustum[3], pCullInfo->vecFrustum[7], pCullInfo->vecFrustum[5], // right
pCullInfo->vecFrustum[2], pCullInfo->vecFrustum[6], pCullInfo->vecFrustum[7], pCullInfo->vecFrustum[3], // top
pCullInfo->vecFrustum[0], pCullInfo->vecFrustum[4], pCullInfo->vecFrustum[5], pCullInfo->vecFrustum[1], // bottom
};
pEdge = &edge2[0][0];
for( iEdge = 0; iEdge < 12; iEdge++ )
{
pFace = &face2[0][0];
for( INT iFace = 0; iFace < 6; iFace++ )
{
if( EdgeIntersectsFace( pEdge, pFace, &pCullInfo->planeFrustum[iFace] ) )
{
return CS_INSIDE_SLOW;
}
pFace += 4;
}
pEdge += 2;
}
// Now see if frustum is contained in bounding box
// If any frustum corner point is outside any plane of the bounding box,
// the frustum is not contained in the bounding box, so the object
// is outside the frustum
for( INT iPlane = 0; iPlane < 6; iPlane++ )
{
if( pPlaneBounds[iPlane].a * pCullInfo->vecFrustum[0].x +
pPlaneBounds[iPlane].b * pCullInfo->vecFrustum[0].y +
pPlaneBounds[iPlane].c * pCullInfo->vecFrustum[0].z +
pPlaneBounds[iPlane].d < 0 )
{
return CS_OUTSIDE_SLOW;
}
}
// Bounding box must contain the frustum, so render the object
return CS_INSIDE_SLOW;
}
//-----------------------------------------------------------------------------
// Name: EdgeIntersectsFace()
// Desc: Determine if the edge bounded by the two vectors in pEdges intersects
// the quadrilateral described by the four vectors in pFacePoints.
// Note: pPlane could be derived from pFacePoints using
// D3DXPlaneFromPoints, but it is precomputed in advance for greater
// speed.
//-----------------------------------------------------------------------------
BOOL EdgeIntersectsFace( D3DXVECTOR3* pEdges, D3DXVECTOR3* pFacePoints,
D3DXPLANE* pPlane )
{
// If both edge points are on the same side of the plane, the edge does
// not intersect the face
FLOAT fDist1;
FLOAT fDist2;
fDist1 = pPlane->a * pEdges[0].x + pPlane->b * pEdges[0].y +
pPlane->c * pEdges[0].z + pPlane->d;
fDist2 = pPlane->a * pEdges[1].x + pPlane->b * pEdges[1].y +
pPlane->c * pEdges[1].z + pPlane->d;
if( fDist1 > 0 && fDist2 > 0 ||
fDist1 < 0 && fDist2 < 0 )
{
return FALSE;
}
// Find point of intersection between edge and face plane (if they're
// parallel, edge does not intersect face and D3DXPlaneIntersectLine
// returns NULL)
D3DXVECTOR3 ptIntersection;
if( NULL == D3DXPlaneIntersectLine( &ptIntersection, pPlane, &pEdges[0], &pEdges[1] ) )
return FALSE;
// Project onto a 2D plane to make the pt-in-poly test easier
FLOAT fAbsA = (pPlane->a > 0 ? pPlane->a : -pPlane->a);
FLOAT fAbsB = (pPlane->b > 0 ? pPlane->b : -pPlane->b);
FLOAT fAbsC = (pPlane->c > 0 ? pPlane->c : -pPlane->c);
D3DXVECTOR2 facePoints[4];
D3DXVECTOR2 point;
if( fAbsA > fAbsB && fAbsA > fAbsC )
{
// Plane is mainly pointing along X axis, so use Y and Z
for( INT i = 0; i < 4; i++)
{
facePoints[i].x = pFacePoints[i].y;
facePoints[i].y = pFacePoints[i].z;
}
point.x = ptIntersection.y;
point.y = ptIntersection.z;
}
else if( fAbsB > fAbsA && fAbsB > fAbsC )
{
// Plane is mainly pointing along Y axis, so use X and Z
for( INT i = 0; i < 4; i++)
{
facePoints[i].x = pFacePoints[i].x;
facePoints[i].y = pFacePoints[i].z;
}
point.x = ptIntersection.x;
point.y = ptIntersection.z;
}
else
{
// Plane is mainly pointing along Z axis, so use X and Y
for( INT i = 0; i < 4; i++)
{
facePoints[i].x = pFacePoints[i].x;
facePoints[i].y = pFacePoints[i].y;
}
point.x = ptIntersection.x;
point.y = ptIntersection.y;
}
// If point is on the outside of any of the face edges, it is
// outside the face.
// We can do this by taking the determinant of the following matrix:
// | x0 y0 1 |
// | x1 y1 1 |
// | x2 y2 1 |
// where (x0,y0) and (x1,y1) are points on the face edge and (x2,y2)
// is our test point. If this value is positive, the test point is
// "to the left" of the line. To determine whether a point needs to
// be "to the right" or "to the left" of the four lines to qualify as
// inside the face, we need to see if the faces are specified in
// clockwise or counter-clockwise order (it could be either, since the
// edge could be penetrating from either side). To determine this, we
// do the same test to see if the third point is "to the right" or
// "to the left" of the line formed by the first two points.
// See http://forum.swarthmore.edu/dr.math/problems/scott5.31.96.html
FLOAT x0, x1, x2, y0, y1, y2;
x0 = facePoints[0].x;
y0 = facePoints[0].y;
x1 = facePoints[1].x;
y1 = facePoints[1].y;
x2 = facePoints[2].x;
y2 = facePoints[2].y;
BOOL bClockwise = FALSE;
if( x1*y2 - y1*x2 - x0*y2 + y0*x2 + x0*y1 - y0*x1 < 0 )
bClockwise = TRUE;
x2 = point.x;
y2 = point.y;
for( INT i = 0; i < 4; i++ )
{
x0 = facePoints[i].x;
y0 = facePoints[i].y;
if( i < 3 )
{
x1 = facePoints[i+1].x;
y1 = facePoints[i+1].y;
}
else
{
x1 = facePoints[0].x;
y1 = facePoints[0].y;
}
if( ( x1*y2 - y1*x2 - x0*y2 + y0*x2 + x0*y1 - y0*x1 > 0 ) == bClockwise )
return FALSE;
}
// If we get here, the point is inside all four face edges,
// so it's inside the face.
return TRUE;
}