mirror of
https://github.com/thug1src/thug.git
synced 2025-01-22 05:43:47 +00:00
570 lines
20 KiB
C++
570 lines
20 KiB
C++
/*****************************************************************************
|
|
** **
|
|
** Neversoft Entertainment **
|
|
** **
|
|
** Copyright (C) 1999 - All Rights Reserved **
|
|
** **
|
|
******************************************************************************
|
|
** **
|
|
** Project: GFX (Graphics Library) **
|
|
** **
|
|
** Module: Graphics (GFX) **
|
|
** **
|
|
** File name: occlude.cpp **
|
|
** **
|
|
** Created: 04/02/02 - dc **
|
|
** **
|
|
** Description: Occlusion testing code **
|
|
** **
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
** Includes **
|
|
*****************************************************************************/
|
|
|
|
#include <core/math.h>
|
|
#include <gfx/debuggfx.h>
|
|
//#include <libdma.h>
|
|
#include "occlude.h"
|
|
#include "resource.h"
|
|
|
|
/*****************************************************************************
|
|
** DBG Information **
|
|
*****************************************************************************/
|
|
|
|
|
|
/*****************************************************************************
|
|
** Externals **
|
|
*****************************************************************************/
|
|
|
|
//extern D3DXMATRIX *p_bbox_transform;
|
|
|
|
namespace NxPs2
|
|
{
|
|
|
|
|
|
/*****************************************************************************
|
|
** Defines **
|
|
*****************************************************************************/
|
|
|
|
|
|
/*****************************************************************************
|
|
** Private Data **
|
|
*****************************************************************************/
|
|
|
|
static bool sCopiedOccludersOffline = false; // tells that occluders have been copied to a fast ram and the
|
|
|
|
/*****************************************************************************
|
|
** Public Data **
|
|
*****************************************************************************/
|
|
|
|
uint32 NumOcclusionPolys = 0;
|
|
uint32 NextOcclusionPolyToCheck = 0;
|
|
sOcclusionPoly OcclusionPolys[MAX_OCCLUSION_POLYS];
|
|
|
|
uint32 sOccluder::NumOccluders = 0;
|
|
sOccluder sOccluder::Occluders[MAX_OCCLUDERS];
|
|
bool sOccluder::sUseVU0 = false;
|
|
bool sOccluder::sUseScratchPad = false;
|
|
|
|
|
|
/*****************************************************************************
|
|
** Private Prototypes **
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
** Private Functions **
|
|
*****************************************************************************/
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void sOccluder::add_to_stack( sOcclusionPoly *p_poly )
|
|
{
|
|
if( NumOccluders < MAX_OCCLUDERS )
|
|
{
|
|
Occluders[NumOccluders].p_poly = p_poly;
|
|
p_poly->in_use = true;
|
|
++NumOccluders;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
static int cmp( const void *p1, const void *p2 )
|
|
{
|
|
// Zero the score for any occlusion poly that is no longer available. This will force it out of the stack.
|
|
if(((sOccluder*)p1)->p_poly->available == false )
|
|
((sOccluder*)p1)->score = 0;
|
|
|
|
if(((sOccluder*)p2)->p_poly->available == false )
|
|
((sOccluder*)p2)->score = 0;
|
|
|
|
return((sOccluder*)p1)->score < ((sOccluder*)p2)->score ? 1 : ((sOccluder*)p1)->score > ((sOccluder*)p2)->score ? -1 : 0;
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void sOccluder::sort_stack( void )
|
|
{
|
|
qsort( Occluders, NumOccluders, sizeof( sOccluder ), cmp );
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void sOccluder::tidy_stack( void )
|
|
{
|
|
if( NumOccluders > 0 )
|
|
{
|
|
// Sort in descending score order.
|
|
sort_stack();
|
|
|
|
|
|
int total = 0;
|
|
int demoted = 0;
|
|
// Count backwards so we know we get all the bad occluders.
|
|
for( int i = NumOccluders - 1; i >= 0; --i )
|
|
{
|
|
// If we have hit an occluder with zero meshes culled, cut off the stack at this point.
|
|
// also, if the stack is full, then demote the lowest scoring 4
|
|
if( Occluders[i].score == 0 || i > (int)(MAX_OCCLUDERS-MIN_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME))
|
|
{
|
|
// No longer using this poly.
|
|
Occluders[i].p_poly->in_use = false;
|
|
|
|
// One less occluder to worry about.
|
|
--NumOccluders;
|
|
demoted++;
|
|
}
|
|
else
|
|
{
|
|
total += Occluders[i].score;
|
|
// Reset the good occluders.
|
|
Occluders[i].score = 0;
|
|
}
|
|
}
|
|
// printf ("\nOccluded %d objects with %d Occluders (demoted %d)\n",total,NumOccluders,demoted);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
** Public Functions **
|
|
*****************************************************************************/
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* Used to add an occlusion poly to the list. Likely to be called */
|
|
/* as geometry is spooled in. */
|
|
/* */
|
|
/******************************************************************/
|
|
void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum )
|
|
{
|
|
// printf (" Adding occlusion polygons, currently %d\n",NumOcclusionPolys);
|
|
Dbg_MsgAssert( NumOcclusionPolys < MAX_OCCLUSION_POLYS,("Too many (%d) occlusion polygons",NumOcclusionPolys) );
|
|
|
|
OcclusionPolys[NumOcclusionPolys].in_use = false;
|
|
OcclusionPolys[NumOcclusionPolys].available = true;
|
|
OcclusionPolys[NumOcclusionPolys].checksum = checksum;
|
|
OcclusionPolys[NumOcclusionPolys].verts[0] = v0;
|
|
OcclusionPolys[NumOcclusionPolys].verts[1] = v1;
|
|
OcclusionPolys[NumOcclusionPolys].verts[2] = v2;
|
|
OcclusionPolys[NumOcclusionPolys].verts[3] = v3;
|
|
OcclusionPolys[NumOcclusionPolys].normal = Mth::CrossProduct( v1 - v0, v3 - v0 );
|
|
OcclusionPolys[NumOcclusionPolys].normal.Normalize();
|
|
|
|
++NumOcclusionPolys;
|
|
|
|
sCopiedOccludersOffline = false; // current scores now invalid
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* Used to toggle whether an occlusion poly can be used or not */
|
|
/* */
|
|
/******************************************************************/
|
|
void EnableOcclusionPoly( uint32 checksum, bool available )
|
|
{
|
|
for( uint32 i = 0; i < NumOcclusionPolys; ++i )
|
|
{
|
|
if( OcclusionPolys[i].checksum == checksum )
|
|
{
|
|
OcclusionPolys[i].available = available;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* Used to clear all occlusion polys (when a level is unloaded) */
|
|
/* */
|
|
/******************************************************************/
|
|
void RemoveAllOcclusionPolys( void )
|
|
{
|
|
Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );
|
|
|
|
sOccluder::NumOccluders = 0;
|
|
NumOcclusionPolys = 0;
|
|
NextOcclusionPolyToCheck = 0;
|
|
|
|
sCopiedOccludersOffline = false; // current scores now invalid
|
|
}
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void CheckForOptimalOccluders( Mth::Vector &cam_pos, Mth::Vector &view_direction )
|
|
{
|
|
if( NumOcclusionPolys > 0 )
|
|
{
|
|
uint32 added = 0;
|
|
uint32 checked = 0;
|
|
|
|
while( added < MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME )
|
|
{
|
|
// Given the current position of the camera, check through the unused occlusion polys to see if one scores higher
|
|
// than the lowest scoring occlusion poly in use.
|
|
sOcclusionPoly *poly_to_check = &OcclusionPolys[NextOcclusionPolyToCheck++];
|
|
if(( !poly_to_check->in_use ) && ( poly_to_check->available ))
|
|
{
|
|
sOccluder::add_to_stack( poly_to_check );
|
|
++added;
|
|
}
|
|
++checked;
|
|
|
|
// Ensure we are always checking within bounds.
|
|
if( NextOcclusionPolyToCheck >= NumOcclusionPolys )
|
|
{
|
|
NextOcclusionPolyToCheck = 0;
|
|
}
|
|
|
|
// Quit out if we have less available occluders than spaces to fill.
|
|
if( checked >= NumOcclusionPolys )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//char scratch[16384];
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
void BuildOccluders( Mth::Vector *p_cam_pos )
|
|
{
|
|
#ifdef OCCLUDER_USES_VU0_MACROMODE
|
|
if (sCopiedOccludersOffline)
|
|
{
|
|
sOccluder * p_occluder = &sOccluder::Occluders[0];
|
|
uint128 * p_vu0_mem = (uint128*)0x11004060; // Start of first score
|
|
|
|
for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
|
|
{
|
|
p_occluder->score = *((uint32*) (p_vu0_mem));
|
|
p_occluder++;
|
|
p_vu0_mem += 6;
|
|
}
|
|
|
|
sCopiedOccludersOffline = false; // we are now up-to-date
|
|
}
|
|
#endif
|
|
for( uint32 i = 0; i < NumOcclusionPolys; ++i )
|
|
{
|
|
// Gfx::AddDebugLine( OcclusionPolys[i].verts[0], OcclusionPolys[i].verts[1], 0xFFFFFFFFUL, 0, 1 );
|
|
// Gfx::AddDebugLine( OcclusionPolys[i].verts[1], OcclusionPolys[i].verts[2], 0xFFFFFFFFUL, 0, 1 );
|
|
// Gfx::AddDebugLine( OcclusionPolys[i].verts[2], OcclusionPolys[i].verts[3], 0xFFFFFFFFUL, 0, 1 );
|
|
// Gfx::AddDebugLine( OcclusionPolys[i].verts[3], OcclusionPolys[i].verts[0], 0xFFFFFFFFUL, 0, 1 );
|
|
}
|
|
|
|
// Tidy up from last frame.
|
|
sOccluder::tidy_stack();
|
|
|
|
// Cyclically add more occluders for checking.
|
|
CheckForOptimalOccluders( *p_cam_pos, *p_cam_pos );
|
|
|
|
// Build all 5 planes for each occluder.
|
|
Mth::Vector u0, u1, p;
|
|
|
|
// The order in which the verts are used to build tha planes depends upon where the camera is in relation to the occlusion
|
|
// poly. We use the default order when the viewpoint is on the side of the poly on which the default poly normal faces.
|
|
for( uint32 i = 0; i < sOccluder::NumOccluders; ++i )
|
|
{
|
|
sOcclusionPoly *p_poly = sOccluder::Occluders[i].p_poly;
|
|
if( Mth::DotProduct( *p_cam_pos - p_poly->verts[0], p_poly->normal ) >= 0.0f )
|
|
{
|
|
// Start with left (0->1->2)...
|
|
u0 = p_poly->verts[0] - *p_cam_pos;
|
|
u1 = p_poly->verts[1] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[0] = p;
|
|
|
|
// ...then right (0->3->4)...
|
|
u0 = p_poly->verts[2] - *p_cam_pos;
|
|
u1 = p_poly->verts[3] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[1] = p;
|
|
|
|
// ...then top (0->2->3)...
|
|
u0 = p_poly->verts[1] - *p_cam_pos;
|
|
u1 = p_poly->verts[2] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[2] = p;
|
|
|
|
// ...then bottom (0->4->1)...
|
|
u0 = p_poly->verts[3] - *p_cam_pos;
|
|
u1 = p_poly->verts[0] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[3] = p;
|
|
|
|
// ...then front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
|
|
// are considered occluded. (1->3->4)...
|
|
u0 = p_poly->verts[2] - p_poly->verts[0];
|
|
u1 = p_poly->verts[3] - p_poly->verts[0];
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, p_poly->verts[0] );
|
|
sOccluder::Occluders[i].planes[4] = p;
|
|
}
|
|
else
|
|
{
|
|
// Start with left (0->2->1)...
|
|
u0 = p_poly->verts[1] - *p_cam_pos;
|
|
u1 = p_poly->verts[0] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[0] = p;
|
|
|
|
// ...then right (0->4->3)...
|
|
u0 = p_poly->verts[3] - *p_cam_pos;
|
|
u1 = p_poly->verts[2] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[1] = p;
|
|
|
|
// ...then top (0->3->2)...
|
|
u0 = p_poly->verts[2] - *p_cam_pos;
|
|
u1 = p_poly->verts[1] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[2] = p;
|
|
|
|
// ...then bottom (0->1->4)...
|
|
u0 = p_poly->verts[0] - *p_cam_pos;
|
|
u1 = p_poly->verts[3] - *p_cam_pos;
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, *p_cam_pos );
|
|
sOccluder::Occluders[i].planes[3] = p;
|
|
|
|
// ...then front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
|
|
// are considered occluded. (1->4->3)...
|
|
u0 = p_poly->verts[3] - p_poly->verts[0];
|
|
u1 = p_poly->verts[2] - p_poly->verts[0];
|
|
p = Mth::CrossProduct( u0, u1 );
|
|
p.Normalize();
|
|
p[W] = -Mth::DotProduct( p, p_poly->verts[0] );
|
|
sOccluder::Occluders[i].planes[4] = p;
|
|
}
|
|
}
|
|
|
|
|
|
if (sOccluder::sUseScratchPad)
|
|
{
|
|
// Copy the occlusion polygons to the scratchpad
|
|
// as we will read them a zillion times during rendering
|
|
sOccluder * p_occluder = &sOccluder::Occluders[0];
|
|
sOccluder * p_scratch = (sOccluder*)0x70000000;
|
|
// sOccluder * p_scratch = (sOccluder*)scratch;
|
|
for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
|
|
{
|
|
*p_scratch = *p_occluder;
|
|
p_occluder++;
|
|
p_scratch++;
|
|
}
|
|
Dbg_MsgAssert((void*)p_scratch < (void*)0x70004000,("Scratchpad overflow")); // 16k scratchpad. Nice.
|
|
}
|
|
|
|
#ifdef OCCLUDER_USES_VU0_MACROMODE
|
|
sOccluder * p_occluder = &sOccluder::Occluders[0];
|
|
uint128 * p_vu0_mem = (uint128*)0x11004000; // VU0 data memory
|
|
|
|
// Copies the number of occulders into X
|
|
*((uint32*) (p_vu0_mem++)) = sOccluder::NumOccluders;
|
|
|
|
for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
|
|
{
|
|
*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[0]));
|
|
*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[1]));
|
|
*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[2]));
|
|
*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[3]));
|
|
*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[4]));
|
|
*((uint32*) (p_vu0_mem++)) = p_occluder->score;
|
|
p_occluder++;
|
|
}
|
|
|
|
FlushCache(WRITEBACK_DCACHE);
|
|
|
|
Dbg_MsgAssert((void*)p_vu0_mem < (void*)0x11005000,("VU0 memory overflow")); // 4k vu0 mem. Nice.
|
|
|
|
sCopiedOccludersOffline = true; // so we know to copy the scores back (technically, this should be in
|
|
// TestSphereAgainstOccluders(), but we only want to write this var once
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef OCCLUDER_USES_VU0_MICROMODE
|
|
|
|
// copy occlusion planes to VUMem0
|
|
sOccluder * p_occluder = &sOccluder::Occluders[0];
|
|
// Mick: removed this to fix a warning in final build.
|
|
// float * p_vumem0 = (float *)0x11004000; // VUMem0
|
|
Mth::Vector * p_plane;
|
|
asm ("viaddi vi01,vi00,0": : : "$8");
|
|
for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
|
|
{
|
|
p_plane = &(p_occluder->planes[0]);
|
|
for( uint32 p = 5; p > 0; --p )
|
|
{
|
|
#if 0
|
|
*p_vumem0++ = p_plane->GetX();
|
|
*p_vumem0++ = p_plane->GetY();
|
|
*p_vumem0++ = p_plane->GetZ();
|
|
*p_vumem0++ = p_plane->GetW();
|
|
#else
|
|
// this way we don't need the FlushCache()
|
|
asm __volatile__("
|
|
|
|
lq $8,0(%0)
|
|
qmtc2 $8,vf01
|
|
vsqi vf01,(vi01++)
|
|
|
|
": : "r" (p_plane) : "$8");
|
|
#endif
|
|
p_plane++;
|
|
}
|
|
p_occluder++;
|
|
}
|
|
// Mick - removed this assertions as it is no longer valid
|
|
// Dbg_MsgAssert((void*)p_vumem0 < (void*)0x11004FA0,("VUMem0 overflow")); // 50 occluders max
|
|
|
|
// flush the cache so VU0 can work on the data now.... not needed anymore
|
|
//FlushCache(WRITEBACK_DCACHE);
|
|
|
|
// call vu0 microsubroutine to rearrange data into a special format
|
|
asm __volatile__("
|
|
|
|
#sync # wait for write-back of the mem we just wrote to
|
|
lw $8,(%0) # t0 = num occluders
|
|
ctc2 $8,$vi01 # move it to vi01 in vu0
|
|
vcallms InitialiseOccluders # call microsubroutine to rearrange vu0 memory
|
|
vnop # interlocking instruction, waits for vu0 completion
|
|
|
|
": : "r" (&sOccluder::NumOccluders) : "$8");
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool OccludeUseVU0()
|
|
{
|
|
return sOccluder::sUseVU0 = CSystemResources::sRequestResource(CSystemResources::vVU0_MEMORY);
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void OccludeDisableVU0()
|
|
{
|
|
if (sOccluder::sUseVU0)
|
|
{
|
|
CSystemResources::sFreeResource(CSystemResources::vVU0_MEMORY);
|
|
sOccluder::sUseVU0 = false;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
bool OccludeUseScratchPad()
|
|
{
|
|
return sOccluder::sUseScratchPad = CSystemResources::sRequestResource(CSystemResources::vSCRATCHPAD_MEMORY);
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
void OccludeDisableScratchPad()
|
|
{
|
|
if (sOccluder::sUseScratchPad)
|
|
{
|
|
CSystemResources::sFreeResource(CSystemResources::vSCRATCHPAD_MEMORY);
|
|
sOccluder::sUseScratchPad = false;
|
|
}
|
|
}
|
|
|
|
/******************************************************************/
|
|
/* */
|
|
/* */
|
|
/******************************************************************/
|
|
|
|
|
|
} // namespace NxXbox
|