thug/Code/Gfx/NGPS/NX/geomnode.cpp

4178 lines
100 KiB
C++
Raw Normal View History

2016-02-13 21:39:12 +00:00
/*****************************************************************************
** **
** Neversoft Entertainment. **
** **
** Copyright (C) 2000 - All Rights Reserved **
** **
******************************************************************************
** **
** Project: THPS4 **
** **
** Module: geomnode **
** **
** File name: geomnode.cpp **
** **
** Created by: 00/00/00 - mrd **
** **
** Description: process-in-place geometry nodes for PS2 **
** **
*****************************************************************************/
/*****************************************************************************
** Includes **
*****************************************************************************/
#include <core/defines.h>
#include <core/math.h>
#include <core/math/geometry.h>
#include <gel/scripting/checksum.h>
//#include <sys/timer.h>
#include "render.h"
#include "group.h"
#include "geomnode.h"
#include "vu1context.h"
#include "occlude.h"
#include "dma.h"
#include "vif.h"
#include "vu1.h"
#include "gs.h"
#include "vu1code.h"
#include "light.h"
#include "line.h"
#include "dmacalls.h"
#include "scene.h"
#include "texture.h"
#include "switches.h"
// Mick - Defining this __GEOM_STATS__ will enable bunch of counters in CGeomNode::Render
//
#define __GEOM_STATS__
#define __ASSERT_ON_ZERO_RADIUS__ 0
#define OCCLUSION_CACHE 0
// Just in case we forgot to take it out, this does it automatically on final burns
#ifndef __NOPT_ASSERT__
#undef __GEOM_STATS__
#endif
#ifdef __GEOM_STATS__
#define GEOMSTATINC(x) {x++;}
#else
#define GEOMSTATINC(x) {}
#endif
//float gClipDist = 0.1f;
/*****************************************************************************
** DBG Information **
*****************************************************************************/
namespace NxPs2
{
int geom_stats_total=0;
int geom_stats_inactive =0;
int geom_stats_sky=0;
int geom_stats_transformed=0;
int geom_stats_skeletal=0;
int geom_stats_camera_sphere=0;
int geom_stats_clipcull=0;
int geom_stats_culled=0;
int geom_stats_leaf_culled=0;
int geom_stats_boxcheck=0;
int geom_stats_occludecheck=0;
int geom_stats_occluded=0;
int geom_stats_colored=0;
int geom_stats_leaf=0;
int geom_stats_wibbleUV=0;
int geom_stats_wibbleVC=0;
int geom_stats_sendcontext=0;
int geom_stats_sorted=0;
int geom_stats_shadow=0;
/*****************************************************************************
** Externals **
*****************************************************************************/
/*****************************************************************************
** Defines **
*****************************************************************************/
//#define __LOD_MULTIPASS__
#define SHADOW_CAM_FRUSTUM_HALF_WIDTH 64.0f
#define SHADOW_CAM_FRUSTUM_HALF_HEIGHT 64.0f
#define GET_FLAG(FLAG) (m_flags & (FLAG) ? true : false)
#define SET_FLAG(FLAG) m_flags = yes ? m_flags|FLAG : m_flags&~FLAG
/*****************************************************************************
** Private Types **
*****************************************************************************/
/*****************************************************************************
** Private Data **
*****************************************************************************/
/*****************************************************************************
** Public Data **
*****************************************************************************/
CGeomNode CGeomNode::sWorld;
CGeomNode CGeomNode::sDatabase;
CGeomNode *GpInstanceNode=NULL;
#ifdef __NOPT_ASSERT__
// Mick: patch variables for debugging toggle of multi-passes
uint32 gPassMask1 = 0; // 1<<6 | 1<<7 (0x40, 0x80)
uint32 gPassMask0 = 0; // 1<<6 | 1<<7 (0x40, 0x80)
#endif
/*****************************************************************************
** Private Prototypes **
*****************************************************************************/
/*****************************************************************************
** Private Functions **
*****************************************************************************/
#ifdef __PLAT_NGPS__
// This is only used in a couple of places, and it's 2% of CPU time in a release build
// so I'm making it inline for now to improve cache coherency
// TODO: would be a good candidate for VU0 optimization
// assuming the ViewVolume could be stored in VU mem or regs?
inline bool CGeomNode::SphereIsOutsideViewVolume(Mth::Vector& s)
{
return
s[3] < s[2] - render::Near ||
s[3] < render::sx * s[2] - render::cx * s[0] ||
s[3] < render::sx * s[2] + render::cx * s[0] ||
s[3] < render::sy * s[2] + render::cy * s[1] ||
s[3] < render::sy * s[2] - render::cy * s[1] ||
s[3] < render::Far - s[2];
}
// Same with this
inline bool CGeomNode::SphereIsInsideOuterVolume(Mth::Vector& s)
{
return
s[3] < render::Near - s[2] &&
s[3] < -render::Sx * s[2] + render::Cx * s[0] &&
s[3] < -render::Sx * s[2] - render::Cx * s[0] &&
s[3] < -render::Sy * s[2] - render::Cy * s[1] &&
s[3] < -render::Sy * s[2] + render::Cy * s[1] &&
s[3] < s[2] - render::Far;
}
bool CGeomNode::BoxIsOutsideViewVolume(Mth::Vector& b, Mth::Vector& s)
{
return ((render::FrustumFlagsPlus[0] && (s[0]+b[0] <= render::CameraToWorld[3][0])) ||
(render::FrustumFlagsPlus[1] && (s[1]+b[1] <= render::CameraToWorld[3][1])) ||
(render::FrustumFlagsPlus[2] && (s[2]+b[2] <= render::CameraToWorld[3][2])) ||
(render::FrustumFlagsMinus[0] && (s[0]-b[0] >= render::CameraToWorld[3][0])) ||
(render::FrustumFlagsMinus[1] && (s[1]-b[1] >= render::CameraToWorld[3][1])) ||
(render::FrustumFlagsMinus[2] && (s[2]-b[2] >= render::CameraToWorld[3][2])));
}
inline bool CGeomNode::NeedsClipping(Mth::Vector& s)
{
return true;
}
void CGeomNode::LinkDma(uint8 *p_newTail)
{
dma::Tag(dma::next, 0, 0); // will be patched up by next call to LinkDma()
vif::NOP();
vif::NOP();
((uint *)u3.mp_group->pListEnd[render::Field])[1] = (uint)p_newTail;
u3.mp_group->pListEnd[render::Field] = dma::pLoc - 16;
}
// returns the number of leaf nodes
int CGeomNode::ProcessNodeInPlace(uint8 *p_baseAddress)
{
int leaves = 0;
if (!(m_flags & NODEFLAG_LEAF))
{
leaves++;
// convert transform offset to pointer
if ((int)u2.mp_transform != -1)
{
u2.mp_transform = (Mth::Matrix *)( (int)u2.mp_transform + p_baseAddress );
}
else
{
u2.mp_transform = (Mth::Matrix *)0;
}
}
uint32 tex = GetTextureChecksum();
SetTextureChecksum(0); // clear it in case we don't find it
// convert group checksum to pointer
if ((m_flags & NODEFLAG_LEAF) && u1.mp_dma)
{
sGroup *pGroup;
for (pGroup=sGroup::pHead; pGroup; pGroup=pGroup->pNext)
if (pGroup->Checksum == (uint)u3.mp_group)
break;
Dbg_MsgAssert(pGroup, ("Couldn't group checksum #%d\n", (uint)u3.mp_group));
u3.mp_group = pGroup;
// Resolve the group+texture checksum into a texture pointer
// printf ("%x\n",GetTextureChecksum());
if (tex)
{
sGroup * p_group = GetGroup();
sScene * p_scene = p_group->pScene;
sTexture *p_textures = p_scene->pTextures;
int num_textures = p_scene->NumTextures;
for (int i=0;i<num_textures;i++)
{
// We are seraching through all the textures in the scene
// so we will find multiple instances with the same texture checksum
// so we need to check the group checksum as well
if (p_textures[i].Checksum == tex && p_textures[i].GroupChecksum == GetGroup()->Checksum)
{
//printf ("Found %dx%dx%d %x mips at 0x%x (%d)\n",p_textures[i].GetWidth(),p_textures[i].GetHeight(),p_textures[i].GetClutBitdepth(),p_textures[i].GetNumMipmaps(),*(p_textures+i),i);
SetTextureChecksum((uint32)(p_textures+i));
break;
}
}
// printf ("-> 0x%x\n",GetTextureChecksum());
}
}
if (m_flags & NODEFLAG_LEAF)
{
// convert dma offset to pointer
if ((int)u1.mp_dma != -1)
{
u1.mp_dma = (uint)u1.mp_dma + p_baseAddress;
}
else
{
u1.mp_dma = (uint8 *)0;
}
}
else
{
// convert child offset to pointer
if ((int)u1.mp_child != -1)
{
u1.mp_child = (CGeomNode *)( (int)u1.mp_child + p_baseAddress );
}
else
{
u1.mp_child = (CGeomNode *)0;
}
}
// convert sibling offset to pointer
if ((int)mp_sibling != -1)
{
mp_sibling = (CGeomNode *)( (int)mp_sibling + p_baseAddress );
}
else
{
mp_sibling = (CGeomNode *)0;
}
// convert LOD offset to pointer
if ((int)mp_next_LOD != -1)
{
mp_next_LOD = (CGeomNode *)( (int)mp_next_LOD + p_baseAddress );
}
else
{
mp_next_LOD = (CGeomNode *)0;
}
// convert uv wibble offset to pointer
if (m_flags & NODEFLAG_UVWIBBLE)
{
if (!(m_flags & NODEFLAG_ENVMAPPED) || !(m_flags & NODEFLAG_LEAF))
{
Dbg_MsgAssert ((int)mp_uv_wibble!=-1, ("Null uv wibble pointer on a uv wibbled node"));
mp_uv_wibble = (float *)( (int)mp_uv_wibble + p_baseAddress );
Dbg_MsgAssert (!(((uint32)mp_uv_wibble) & 0xf0000003),("Corrupt UV wibble offset (0x%x) in node %s\n",((int)mp_uv_wibble - (int)p_baseAddress), Script::FindChecksumName(m_checksum)));
}
else
{
mp_uv_wibble = (float *)0;
m_flags &= ~NODEFLAG_UVWIBBLE;
}
}
// convert vc wibble offset to pointer
if ((int)mp_vc_wibble != -1)
{
// first convert the wibble offset itself
mp_vc_wibble = (uint32 *)( (int)mp_vc_wibble + p_baseAddress );
// now the mesh info
uint32 *p_meshdata = mp_vc_wibble;
int seq, num_seqs, vert, num_verts;
num_seqs = *p_meshdata++;
for (seq=0; seq<num_seqs; seq++)
{
// convert sequence pointer
*(uint32 **)p_meshdata = (uint32 *) ( (int)*p_meshdata + p_baseAddress );
p_meshdata++;
// convert rgba pointers
num_verts = *p_meshdata++;
for (vert=0; vert<num_verts; vert++)
{
*(uint32 **)p_meshdata = (uint32 *) ( (uint)*p_meshdata + p_baseAddress );
p_meshdata++;
}
}
}
else
{
mp_vc_wibble = (uint32 *)0;
}
// recursively process any children
if (!(m_flags & NODEFLAG_LEAF))
{
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
leaves += p_child->ProcessNodeInPlace(p_baseAddress);
}
}
// recursively process any LODs
if (mp_next_LOD)
{
(void) mp_next_LOD->ProcessNodeInPlace(p_baseAddress);
}
return leaves;
}
void CGeomNode::BecomesChildOf(CGeomNode *p_parent)
{
if (m_flags & NODEFLAG_LEAF)
{
mp_sibling = NULL;
m_flags &= ~NODEFLAG_LEAF;
}
else
{
mp_sibling = p_parent->u1.mp_child;
}
p_parent->u1.mp_child = this;
}
bool CGeomNode::RemoveFrom(CGeomNode *p_parent)
{
if ((m_flags & NODEFLAG_LEAF) || !p_parent->u1.mp_child)
{
return false;
}
if (p_parent->u1.mp_child == this)
{
p_parent->u1.mp_child = mp_sibling;
return true;
}
else
{
for (CGeomNode *p_node=p_parent->u1.mp_child; p_node->mp_sibling; p_node=p_node->mp_sibling)
{
if (p_node->mp_sibling == this)
{
p_node->mp_sibling = mp_sibling;
return true;
}
}
}
return false;
}
bool CGeomNode::RemoveFromChildrenOf(CGeomNode *p_parent)
{
if ((m_flags & NODEFLAG_LEAF) || !p_parent->u1.mp_child)
{
return false;
}
for (CGeomNode *p_node=p_parent->u1.mp_child; p_node; p_node=p_node->mp_sibling)
{
if (RemoveFrom(p_node))
{
return true;
}
}
return false;
}
#endif // #ifdef __PLAT_NGPS__
/*****************************************************************************
** Public Functions **
*****************************************************************************/
#if OCCLUSION_CACHE
static bool set_valid_flag = false;
#endif
CGeomNode::CGeomNode()
{
m_flags = ALL_VISIBILITY_BITS;
#if OCCLUSION_CACHE
if( set_valid_flag )
{
set_valid_flag = false;
m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
}
else
{
set_valid_flag = true;
}
#endif
m_bounding_sphere[0] = 0.0f;
m_bounding_sphere[1] = 0.0f;
m_bounding_sphere[2] = 0.0f;
m_bounding_sphere[3] = 1.0e+30f;
m_bounding_box[0] = 1.0e+30f;
m_bounding_box[1] = 1.0e+30f;
m_bounding_box[2] = 1.0e+30f;
m_LOD_far_dist = MAX_LOD_DIST;
u2.mp_transform = NULL;
u1.mp_child = NULL;
mp_sibling = NULL;
mp_next_LOD = NULL;
//u1.mp_dma = NULL;
u3.mp_group = NULL;
mp_uv_wibble = NULL;
mp_vc_wibble = NULL;
m_num_bones = 0;
m_bone_index = -1;
u4.mp_bone_transforms = NULL;
m_colour = 0x80808080;
}
void CGeomNode::Init()
{
m_flags = ALL_VISIBILITY_BITS;
m_bounding_sphere[0] = 0.0f;
m_bounding_sphere[1] = 0.0f;
m_bounding_sphere[2] = 0.0f;
m_bounding_sphere[3] = 1.0e+30f;
m_bounding_box[0] = 1.0e+30f;
m_bounding_box[1] = 1.0e+30f;
m_bounding_box[2] = 1.0e+30f;
m_LOD_far_dist = MAX_LOD_DIST;
u2.mp_transform = NULL;
u1.mp_child = NULL;
mp_sibling = NULL;
mp_next_LOD = NULL;
//u1.mp_dma = NULL;
u3.mp_group = NULL;
mp_uv_wibble = NULL;
mp_vc_wibble = NULL;
m_num_bones = 0;
m_bone_index = -1;
u4.mp_bone_transforms = NULL;
m_colour = 0x80808080;
}
CGeomNode::CGeomNode(const CGeomNode &node)
{
#if 1
memcpy(this,&node,sizeof(CGeomNode));
#else
m_flags = node.m_flags;
m_bounding_sphere = node.m_bounding_sphere;
u2.mp_transform = node.u2.mp_transform;
u1.mp_child = node.u1.mp_child;
mp_sibling = node.mp_sibling;
mp_next_LOD = node.mp_next_LOD;
m_LOD_far_dist = node.m_LOD_far_dist;
//u1.mp_dma = node.u1.mp_dma;
u3.mp_group = node.u3.mp_group;
mp_uv_wibble = node.mp_uv_wibble;
mp_vc_wibble = node.mp_vc_wibble;
m_num_bones = node.m_num_bones;
m_bone_index = node.m_bone_index;
u4.mp_bone_transforms = node.u4.mp_bone_transforms;
m_colour = node.m_colour;
#endif
}
#ifdef __PLAT_NGPS__
//#define DUMP_NODE_STRUCTURE
#ifdef DUMP_NODE_STRUCTURE
static int node_level = 0;
#endif
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderWorld(CVu1Context& ctxt, uint32 renderFlags)
{
GEOMSTATINC(geom_stats_total);
// Don't render anything if it's not going to be displayed
if (!FlipCopyEnabled())
{
return;
}
// render children
CGeomNode *p_child;
for (p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderScene(ctxt, renderFlags);
}
#ifdef __NOPT_ASSERT__
m_flags |= NODEFLAG_WAS_RENDERED; // Set flag saying it was rendered
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderScene(CVu1Context& ctxt, uint32 renderFlags)
{
CVu1Context *p_ctxt;
#ifdef __NOPT_ASSERT__
m_flags &= ~NODEFLAG_WAS_RENDERED; // Clear flag saying it was rendered
#endif
GEOMSTATINC(geom_stats_total);
// cull the node if not active
if (!((m_flags & NODEFLAG_ACTIVE) && (m_flags & render::ViewportMask)))
{
GEOMSTATINC(geom_stats_inactive);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
// is it the root node of a sky?
if (m_flags & NODEFLAG_SKY)
{
GEOMSTATINC(geom_stats_sky);
// reserve space from dma list for new trans context
p_ctxt = ctxt.Localise();
p_ctxt->SetupAsSky();
RenderAsSky(*p_ctxt, renderFlags);
dma::SetList(NULL); // not quite sure why we need this, but we do
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
// is it really an object node in disguise?
if (m_flags & NODEFLAG_OBJECT)
{
RenderObject(ctxt, renderFlags);
return;
}
// get this node's transform context
if (m_flags & NODEFLAG_TRANSFORMED)
{
// copy the parent vu1 context
p_ctxt = ctxt.LocaliseExtend();
// set up the parts of the context that will depend on the new matrix
p_ctxt->StandardSetup(*u2.mp_transform);
// Propogate the 'transformed' flag when recursing.
renderFlags |= RENDERFLAG_TRANSFORMED;
}
else
{
p_ctxt = &ctxt;
}
// make a note of the root node for a skeletal model
if (m_flags & NODEFLAG_SKELETAL)
{
GpInstanceNode = this;
renderFlags |= RENDERFLAG_SKELETAL;
}
// applying a colour to a node
if (m_flags & NODEFLAG_COLOURED)
{
GEOMSTATINC(geom_stats_colored);
// if there's no local context, copy the parent's
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.Localise();
}
// copy node's colour to vu1 context
p_ctxt->SetColour(this);
renderFlags |= RENDERFLAG_COLOURED;
}
// Check for light group
if (u3.mp_light_group && u3.mp_light_group != ctxt.GetLights())
{
// if there's no local context, copy the global one
if (p_ctxt == &ctxt || !p_ctxt->IsExtended())
{
p_ctxt = ctxt.LocaliseExtend();
}
p_ctxt->SetLights(u3.mp_light_group);
}
// If we got here, then it's been rendered, so set the flag
#ifdef __NOPT_ASSERT__
m_flags |= NODEFLAG_WAS_RENDERED; // Set flag saying it was rendered
#endif
// render any children
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderObject(*p_ctxt, renderFlags);
}
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderObject(CVu1Context& ctxt, uint32 renderFlags)
{
CVu1Context *p_ctxt;
#ifdef __NOPT_ASSERT__
m_flags &= ~NODEFLAG_WAS_RENDERED; // Clear flag saying it was rendered
#endif
GEOMSTATINC(geom_stats_total);
#if __ASSERT_ON_ZERO_RADIUS__
Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
#endif
// cull the node if not active
if (!((m_flags & NODEFLAG_ACTIVE) && (m_flags & render::ViewportMask)))
{
GEOMSTATINC(geom_stats_inactive);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#if OCCLUSION_CACHE
bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
if( last_occlusion_check_valid )
{
// Clear the valid flag.
m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
// Was this object occluded last frame? If so, occlude this frame also.
if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
{
m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
#endif
// get this node's transform context
if (m_flags & NODEFLAG_TRANSFORMED)
{
// copy the parent vu1 context
p_ctxt = ctxt.LocaliseExtend();
// set up the parts of the context that will depend on the new matrix
p_ctxt->StandardSetup(*u2.mp_transform);
// Propogate the 'transformed' flag when recursing.
renderFlags |= RENDERFLAG_TRANSFORMED;
}
else
{
p_ctxt = &ctxt;
}
// check for skeletal mesh
if ((renderFlags & RENDERFLAG_SKELETAL) && (m_bone_index >= 0))
{
Dbg_MsgAssert(GpInstanceNode, ("GpInstanceNode is NULL"));
GEOMSTATINC(geom_stats_skeletal);
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.Localise();
}
p_ctxt->HierarchicalSetup(GpInstanceNode->u4.mp_bone_transforms[m_bone_index]);
}
Mth::Vector camera_sphere;
GEOMSTATINC(geom_stats_camera_sphere);
camera_sphere = m_bounding_sphere;
camera_sphere[W] = 1.0f;
camera_sphere *= *p_ctxt->GetMatrix();
camera_sphere[W] = m_bounding_sphere[W];
// view-volume bounding sphere tests, based on parent render flags
// MickNote: I experimented with only clipping the leaf nodes, but this was slightly
// slower, as the higher level culling must cull enough to justify the overhead)
if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
{
GEOMSTATINC(geom_stats_clipcull);
if (SphereIsOutsideViewVolume(camera_sphere))
{
GEOMSTATINC(geom_stats_culled);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
if (SphereIsInsideOuterVolume(camera_sphere))
{
renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
}
}
// view-volume bounding box test
if ((renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL)) &&
!((renderFlags & RENDERFLAG_TRANSFORMED) || m_flags & NODEFLAG_TRANSFORMED))
{
GEOMSTATINC(geom_stats_boxcheck);
if (BoxIsOutsideViewVolume(*(Mth::Vector *)&m_bounding_box, m_bounding_sphere))
{
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
// Occlusion test.
if (m_bounding_sphere[W] > 1.0e20f)
{
// Don't bother if sphere is stupidly large.
}
#if OCCLUSION_CACHE
else if( !last_occlusion_check_valid )
#else
else
#endif
{
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
#endif
if (m_flags & NODEFLAG_TRANSFORMED) // root of a transformed branch
{
GEOMSTATINC(geom_stats_occludecheck);
Mth::Vector world_sphere(m_bounding_sphere);
world_sphere[W] = 1.0f;
world_sphere *= *u2.mp_transform; // => use u2.mp_transform
if (TestSphereAgainstOccluders( &world_sphere, m_bounding_sphere[W] ))
{
GEOMSTATINC(geom_stats_occluded);
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
else if (renderFlags & RENDERFLAG_TRANSFORMED) // deeper within a transformed branch
{
}
else // not transformed => use world position as-is
{
GEOMSTATINC(geom_stats_occludecheck);
if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
{
GEOMSTATINC(geom_stats_occluded);
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
}
#if !STENCIL_SHADOW
// shadow test
if (renderFlags & RENDERFLAG_SHADOW)
{
if (m_flags & (NODEFLAG_TRANSFORMED))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT ) ||
( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
}
#endif
// applying a colour to a node
if (m_flags & NODEFLAG_COLOURED)
{
GEOMSTATINC(geom_stats_colored);
// if there's no local context, copy the parent's
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.Localise();
}
// copy node's colour to vu1 context
p_ctxt->SetColour(this);
renderFlags |= RENDERFLAG_COLOURED;
}
// Check for light group
if (u3.mp_light_group && u3.mp_light_group != ctxt.GetLights())
{
// if there's no local context, copy the global one
if (p_ctxt == &ctxt || !p_ctxt->IsExtended())
{
p_ctxt = ctxt.LocaliseExtend();
}
p_ctxt->SetLights(u3.mp_light_group);
}
// Check if we should use another LOD (Garrett: Logically, this should be right, but I still want to streamline it)
float camera_dist = -camera_sphere[Z];
CGeomNode *p_child = NULL;
CGeomNode *p_lod;
for (p_lod=this; p_lod; p_lod = p_lod->mp_next_LOD)
{
if (p_lod->m_LOD_far_dist > camera_dist)
{
p_child = p_lod->u1.mp_child;
break;
}
}
// If we got here, then it's been rendered, so set the flag
#ifdef __NOPT_ASSERT__
m_flags |= NODEFLAG_WAS_RENDERED; // Set flag saying it was rendered
#endif
// render any children
if (renderFlags & RENDERFLAG_TRANSFORMED)
{
for (; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderTransformedLeaf(*p_ctxt, renderFlags);
}
}
else
{
for (; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderNonTransformedLeaf(*p_ctxt, renderFlags);
}
}
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags)
{
CVu1Context *p_ctxt;
#if __ASSERT_ON_ZERO_RADIUS__
Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
#endif
#ifdef __NOPT_ASSERT__
m_flags &= ~NODEFLAG_WAS_RENDERED; // Clear flag saying it was rendered
#endif
GEOMSTATINC(geom_stats_total);
#if OCCLUSION_CACHE
bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
if( last_occlusion_check_valid )
{
// Clear the valid flag.
m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
// Was this object occluded last frame? If so, occlude this frame also.
if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
{
m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
#endif
Mth::Vector camera_sphere;
GEOMSTATINC(geom_stats_camera_sphere);
camera_sphere = m_bounding_sphere;
camera_sphere[W] = 1.0f;
camera_sphere *= *ctxt.GetMatrix();
camera_sphere[W] = m_bounding_sphere[W];
// view-volume bounding sphere tests, based on parent render flags
// MickNote: I experimented with only clipping the leaf nodes, but this was slightly
// slower, as the higher level culling must cull enough to justify the overhead)
if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
{
GEOMSTATINC(geom_stats_clipcull);
if (SphereIsOutsideViewVolume(camera_sphere))
{
GEOMSTATINC(geom_stats_culled);
GEOMSTATINC(geom_stats_leaf_culled);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
if (SphereIsInsideOuterVolume(camera_sphere))
{
renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
}
}
// Occlusion test.
#if OCCLUSION_CACHE
if( !last_occlusion_check_valid )
#endif
{
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
#endif
}
#if !STENCIL_SHADOW
// shadow test
if (renderFlags & RENDERFLAG_SHADOW)
{
if (m_flags & (NODEFLAG_BILLBOARD | NODEFLAG_ENVMAPPED))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT ) ||
( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
}
#endif
// get this node's transform context
p_ctxt = &ctxt;
// applying a colour to a node
if (m_flags & NODEFLAG_COLOURED)
{
GEOMSTATINC(geom_stats_colored);
p_ctxt = ctxt.Localise();
// copy node's colour to vu1 context
p_ctxt->SetColour(this);
renderFlags |= RENDERFLAG_COLOURED;
}
GEOMSTATINC(geom_stats_leaf);
#ifdef __NOPT_ASSERT__
// Mick: check patch variables for debugging toggle of multi-passes
if ((m_flags & gPassMask1) != gPassMask0)
{
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#endif
#ifdef __LOD_MULTIPASS__
// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere[Z] > render::sMultipassMaxDist))
{
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#endif
if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED | NODEFLAG_VCWIBBLE))
{
if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED))
{
// if there's no local context, copy the global one
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.LocaliseExtend();
}
// uv wibble
if (m_flags & NODEFLAG_UVWIBBLE)
{
GEOMSTATINC(geom_stats_wibbleUV);
p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
}
// environment mapping
if (m_flags & NODEFLAG_ENVMAPPED)
{
// reflection map vectors
p_ctxt->SetReflectionVecs((sint16)(uint)mp_uv_wibble, (sint16)(((uint)mp_uv_wibble)>>16));
}
}
// vc wibble
if (m_flags & NODEFLAG_VCWIBBLE)
{
GEOMSTATINC(geom_stats_wibbleVC);
WibbleVCs();
}
}
// set the dma context
dma::SetList(u3.mp_group);
u3.mp_group->Used[render::Field] = true;
// flag the texture as used
if (u4.mp_texture)
{
u4.mp_texture->m_render_count++;
}
Mth::Vector *p_trans = p_ctxt->GetTranslation();
// initial cnt tag, for sending row reg and z-sort key
dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere[Z]);
vif::NOP();
vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// special case for billboards
if (m_flags & NODEFLAG_BILLBOARD)
{
// compute billboard basis vecs
Mth::Vector udir,vdir;
vdir = (*p_ctxt->GetMatrix())[1];
vdir[2] = 0.0f;
vdir[3] = 0.0f;
vdir.Normalize();
udir[0] = vdir[1];
udir[1] = -vdir[0];
udir[2] = 0.0f;
udir[3] = 0.0f;
udir *= render::CameraToFrustum;
vdir *= render::CameraToFrustum;
udir[2] = 0.0f;
udir[3] = 0.0f;
vdir[2] = 0.0f;
vdir[3] = 0.0f;
// send vu1 context
dma::Tag(dma::next, 16, (uint)dma::pLoc+272);
vif::BASE(vu1::Loc);
vif::OFFSET(0);
uint vu1_loc = vu1::Loc;
vu1::Loc = 0; // must do this as a relative prim for a sortable list...
vu1::BeginPrim(REL, VU1_ADDR(L_VF20));
vu1::StoreVec(*(Vec *)&udir);
vu1::StoreVec(*(Vec *)&vdir);
vu1::StoreVec(*(Vec *)&render::IntViewportScale);
vif::StoreV4_32(0,0,0,0);
vif::StoreV4_32(0,0,0,0);
vu1::StoreVec(*(Vec *)&render::CameraToWorld[3]);
vu1::StoreVec(*(Vec *)ctxt.GetColour());
vu1::StoreVec(*(Vec *)&render::IntViewportOffset);
Mth::Matrix LocalToFrustum = (*p_ctxt->GetMatrix()) * render::CameraToFrustum;
vu1::CopyMat((float*)&LocalToFrustum);
vu1::EndPrim(0);
// gs context data, specifically the fog coefficient
float w,f;
if (renderFlags & RENDERFLAG_TRANSFORMED)
{
w = -(*p_ctxt->GetMatrix())[3][2];
}
else
{
// Context only contains WorldToCamera matrix, so convert world position to camera relative position
Mth::Vector camera_rel_pos = render::WorldToCamera.TransformAsPos(m_bounding_sphere);
w = -camera_rel_pos[2];
}
if ((w > render::FogNear) && render::EnableFog) // Garrett: We have to check for EnableFog here because the VU1 code isn't
{
f = 1.0 + render::EffectiveFogAlpha * (render::FogNear/w - 1.0f);
}
else
{
f = 1.0f;
}
gs::BeginPrim(REL,0,0);
gs::Reg1(gs::FOG, PackFOG((int)(f*255.99f)));
gs::EndPrim(0);
dma::EndTag();
((uint16 *)dma::pTag)[1] |= vu1::Loc & 0x3FF; // must write some code for doing this automatically
vu1::Loc += vu1_loc;
u3.mp_group->pVu1Context = NULL;
}
// send transform context if necessary
else if ((p_ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
{
GEOMSTATINC(geom_stats_sendcontext);
int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += qwc-1;
u3.mp_group->pVu1Context = p_ctxt;
}
// render this node by adding to the chosen dma list
uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
(renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
if (renderFlags & RENDERFLAG_COLOURED)
vu1_flags |= COLR;
if (render::EnableFog && (camera_sphere[3]-camera_sphere[2] > render::FogNear))
vu1_flags |= FOGE;
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
vif::ITOP(vu1_flags);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
// must add a 'next' tag for sorted groups
if (u3.mp_group->flags & GROUPFLAG_SORT)
{
GEOMSTATINC(geom_stats_sorted);
dma::SetList(NULL);
}
#if !STENCIL_SHADOW
// render shadow
if ((renderFlags & RENDERFLAG_SHADOW) && !(m_flags & NODEFLAG_NOSHADOW))
{
GEOMSTATINC(geom_stats_shadow);
// set the dma context
dma::SetList(sGroup::pShadow);
// set row reg
dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere[Z]);
vif::NOP();
vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// send transform context
dma::Tag(dma::ref, 18|17<<16, sGroup::pShadow->pVu1Context->GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += 17;
// render this node
vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP|SHDW :
(renderFlags & RENDERFLAG_CULL) ? CULL|SHDW : PROJ|SHDW;
if (render::EnableFog && (camera_sphere[3]-camera_sphere[2] > render::FogNear))
vu1_flags |= FOGE;
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
vif::ITOP(vu1_flags);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
}
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderNonTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags)
{
float camera_sphere_z;
#if __ASSERT_ON_ZERO_RADIUS__
Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
#endif
// kick off the frustum culling tests in VU0
asm __volatile__("
lqc2 vf08,(%0) # sphere (x,y,z,R)
lqc2 vf09,(%1) # box (bx,by,bz,?)
vcallms BothCullTests # call microsubroutine
": : "r" (&m_bounding_sphere), "r" (&m_bounding_box) );
#ifdef __NOPT_ASSERT__
m_flags &= ~NODEFLAG_WAS_RENDERED; // Clear flag saying it was rendered
#endif
GEOMSTATINC(geom_stats_total);
//Mick: Minimal leaf rendering makes no real difference
// so I've removed this call while refactoring
// check for minimal leaf node for a quicker render
if (!(m_flags & (NODEFLAG_COLOURED |
NODEFLAG_BILLBOARD |
NODEFLAG_UVWIBBLE |
NODEFLAG_VCWIBBLE |
NODEFLAG_ENVMAPPED))
&& !(renderFlags & (RENDERFLAG_COLOURED |
#if !STENCIL_SHADOW
RENDERFLAG_SHADOW |
#endif
RENDERFLAG_CLIP |
RENDERFLAG_CULL)))
{
RenderMinimalLeaf(ctxt);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#if OCCLUSION_CACHE
bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
if( last_occlusion_check_valid )
{
// Clear the valid flag.
m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
// Was this object occluded last frame? If so, occlude this frame also.
if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
{
m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
#endif
// view-volume bounding sphere tests, based on parent render flags
// MickNote: I experimented with only clipping the leaf nodes, but this was slightly
// slower, as the higher level culling must cull enough to justify the overhead)
if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
{
GEOMSTATINC(geom_stats_clipcull);
if (CullAgainstViewFrustum())
{
GEOMSTATINC(geom_stats_culled);
GEOMSTATINC(geom_stats_leaf_culled);
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
if (!CullAgainstOuterFrustum())
{
renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
}
}
// Occlusion test.
#if OCCLUSION_CACHE
if( !last_occlusion_check_valid )
#endif
{
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
#endif
GEOMSTATINC(geom_stats_occludecheck);
if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
{
GEOMSTATINC(geom_stats_occluded);
#if OCCLUSION_CACHE
m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
}
#if !STENCIL_SHADOW
// shadow test
if (renderFlags & RENDERFLAG_SHADOW)
{
if (m_flags & (NODEFLAG_BILLBOARD | NODEFLAG_ENVMAPPED))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH ) ||
( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT ) ||
( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
{
renderFlags &= ~RENDERFLAG_SHADOW;
}
}
#endif
// get this node's transform context
CVu1Context *p_ctxt = &ctxt;
// applying a colour to a node
if (m_flags & NODEFLAG_COLOURED)
{
GEOMSTATINC(geom_stats_colored);
p_ctxt = ctxt.Localise();
// copy node's colour to vu1 context
p_ctxt->SetColour(this);
renderFlags |= RENDERFLAG_COLOURED;
}
GEOMSTATINC(geom_stats_leaf);
#ifdef __NOPT_ASSERT__
// Mick: check patch variables for debugging toggle of multi-passes
if ((m_flags & gPassMask1) != gPassMask0)
{
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#endif
asm __volatile__("
qmfc2 $8,$vf05
sw $8,(%0)
": : "r" (&camera_sphere_z) : "$8" );
#ifdef __LOD_MULTIPASS__
// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere_z > render::sMultipassMaxDist))
{
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
return;
}
#endif
if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED | NODEFLAG_VCWIBBLE))
{
if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED))
{
// if there's no local context, copy the global one
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.LocaliseExtend();
}
// uv wibble
if (m_flags & NODEFLAG_UVWIBBLE)
{
GEOMSTATINC(geom_stats_wibbleUV);
p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
}
// environment mapping
if (m_flags & NODEFLAG_ENVMAPPED)
{
// reflection map vectors
p_ctxt->SetReflectionVecs((sint16)(uint)mp_uv_wibble, (sint16)(((uint)mp_uv_wibble)>>16));
}
}
// vc wibble
if (m_flags & NODEFLAG_VCWIBBLE)
{
GEOMSTATINC(geom_stats_wibbleVC);
WibbleVCs();
}
}
// set the dma context
dma::SetList(u3.mp_group);
u3.mp_group->Used[render::Field] = true;
// flag the texture as used
if (u4.mp_texture)
{
u4.mp_texture->m_render_count++;
}
Mth::Vector *p_trans = p_ctxt->GetTranslation();
// initial cnt tag, for sending row reg and z-sort key
dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
vif::NOP();
vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// special case for billboards
if (m_flags & NODEFLAG_BILLBOARD)
{
// compute billboard basis vecs
Mth::Vector udir,vdir;
vdir = (*p_ctxt->GetMatrix())[1];
vdir[2] = 0.0f;
vdir[3] = 0.0f;
vdir.Normalize();
udir[0] = vdir[1];
udir[1] = -vdir[0];
udir[2] = 0.0f;
udir[3] = 0.0f;
udir *= render::CameraToFrustum;
vdir *= render::CameraToFrustum;
udir[2] = 0.0f;
udir[3] = 0.0f;
vdir[2] = 0.0f;
vdir[3] = 0.0f;
// send vu1 context
dma::Tag(dma::next, 16, (uint)dma::pLoc+272);
vif::BASE(vu1::Loc);
vif::OFFSET(0);
uint vu1_loc = vu1::Loc;
vu1::Loc = 0; // must do this as a relative prim for a sortable list...
vu1::BeginPrim(REL, VU1_ADDR(L_VF20));
vu1::StoreVec(*(Vec *)&udir);
vu1::StoreVec(*(Vec *)&vdir);
vu1::StoreVec(*(Vec *)&render::IntViewportScale);
vif::StoreV4_32(0,0,0,0);
vif::StoreV4_32(0,0,0,0);
vu1::StoreVec(*(Vec *)&render::CameraToWorld[3]);
vu1::StoreVec(*(Vec *)ctxt.GetColour());
vu1::StoreVec(*(Vec *)&render::IntViewportOffset);
Mth::Matrix LocalToFrustum = (*p_ctxt->GetMatrix()) * render::CameraToFrustum;
vu1::CopyMat((float*)&LocalToFrustum);
vu1::EndPrim(0);
// gs context data, specifically the fog coefficient
float w,f;
if (renderFlags & RENDERFLAG_TRANSFORMED)
{
w = -(*p_ctxt->GetMatrix())[3][2];
}
else
{
// Context only contains WorldToCamera matrix, so convert world position to camera relative position
Mth::Vector camera_rel_pos = render::WorldToCamera.TransformAsPos(m_bounding_sphere);
w = -camera_rel_pos[2];
}
if ((w > render::FogNear) && render::EnableFog) // Garrett: We have to check for EnableFog here because the VU1 code isn't
{
f = 1.0 + render::EffectiveFogAlpha * (render::FogNear/w - 1.0f);
}
else
{
f = 1.0f;
}
gs::BeginPrim(REL,0,0);
gs::Reg1(gs::FOG, PackFOG((int)(f*255.99f)));
gs::EndPrim(0);
dma::EndTag();
((uint16 *)dma::pTag)[1] |= vu1::Loc & 0x3FF; // must write some code for doing this automatically
vu1::Loc += vu1_loc;
u3.mp_group->pVu1Context = NULL;
}
// send transform context if necessary
else if ((p_ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
{
GEOMSTATINC(geom_stats_sendcontext);
int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += qwc-1;
u3.mp_group->pVu1Context = p_ctxt;
}
// render this node by adding to the chosen dma list
uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
(renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
if (renderFlags & RENDERFLAG_COLOURED)
vu1_flags |= COLR;
if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
vu1_flags |= FOGE;
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
vif::ITOP(vu1_flags);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
// must add a 'next' tag for sorted groups
if (u3.mp_group->flags & GROUPFLAG_SORT)
{
GEOMSTATINC(geom_stats_sorted);
dma::SetList(NULL);
}
#if !STENCIL_SHADOW
// render shadow
if ((renderFlags & RENDERFLAG_SHADOW) && !(m_flags & NODEFLAG_NOSHADOW))
{
GEOMSTATINC(geom_stats_shadow);
// set the dma context
dma::SetList(sGroup::pShadow);
// set row reg
dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
vif::NOP();
vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// send transform context
dma::Tag(dma::ref, 18|17<<16, sGroup::pShadow->pVu1Context->GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += 17;
// render this node
vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP|SHDW :
(renderFlags & RENDERFLAG_CULL) ? CULL|SHDW : PROJ|SHDW;
if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
vu1_flags |= FOGE;
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
vif::ITOP(vu1_flags);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
}
#endif
#ifdef DUMP_NODE_STRUCTURE
node_level--;
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
// Mick: I've moved this function here to improved I-Cache usage while rendering
bool TestSphereAgainstOccluders( Mth::Vector *p_center, float radius, uint32 meshes )
{
// return false;
// GJ: This will catch bugs where a model's bounding sphere
// is uninitialized, return inconsistent values depending on
// the level you were in (this came up with the create-a-trick
// skater, which uses "ref models", which don't have valid bounding
// spheres)
#if __ASSERT_ON_ZERO_RADIUS__
Dbg_MsgAssert( radius != 0.0f, ( "Wasn't expecting a radius of 0" ) );
#endif
#if defined(OCCLUDER_USES_VU0_MACROMODE) || defined(OCCLUDER_USES_VU0_MICROMODE)
if (sOccluder::sUseVU0)
{
#ifdef OCCLUDER_USES_VU0_MACROMODE
bool result;
asm __volatile__(
"
.set noreorder
mfc1 $8, %3 # radius
qmtc2 $8, vf9
vilwr.x vi01, (vi00) # load number of occluders
lqc2 vf10, 0x0(%1) # p_center
ctc2 %2, $vi15 # meshes
vaddw.x vf31, vf00, vf00w # Load 1.0f to VF31x
# Init loop
cfc2 $10, $vi01 # move number of occluders to $10
beq $10, $0, no_occlusions
viaddi vi02, vi00, 1 # load address of first occluder
occ_loop:
viaddi vi03, vi02, 0 # and put here, too
vlqi.xyzw vf01, (vi03++) # load planes
li $9, 5 # number of planes
plane_loop:
# dot product
vmul.xyz vf11,vf01,vf10 # result = plane * center + plane_w [start]
vmulax.x ACC, vf01,vf10x
vmaddaw.x ACC, vf31,vf01w # plane_w
vmaddax.x ACC, vf31,vf09x # also add radius in
vmadday.x ACC, vf31,vf11y
vmaddz.x vf11,vf31,vf11z # result = plane * center + plane_w + radius [ready]
addi $9, $9, -1 # decrement plane counter
viaddi vi04, vi02, 5 # calc score address (since we are waiting anyway)
vilwr.x vi14, (vi04) # load score
vlqi.xyzw vf01, (vi03++) # load next planes
vnop
cfc2 $8, $vi17 # transfer if result (MAC register)
andi $8, $8, 0x80 # check result (v >= 0) not_occluded
beq $8, $0, done_occluder
nop
bne $9, $0, plane_loop
nop
# got through all planes - found occluder
found_occluder:
li %0, 1 # store true for return value
viadd vi14, vi14, vi15 # add meshes
b done
viswr.x vi14, (vi04)
done_occluder:
addi $10, $10, -1 # decrement occluder counter
bne $10, $0, occ_loop
viaddi vi02, vi02, 6 # increment occluder address
no_occlusions:
li %0, 0 # store false for return value
done:
.set reorder
": "=r" (result) : "r" (p_center), "r" (meshes), "f" (radius) : "$8", "$9", "$10");
return result;
#endif // OCCLUDER_USES_VU0_MACROMODE
#ifdef OCCLUDER_USES_VU0_MICROMODE
uint32 result, index;
Mth::Vector sphere(*p_center);
sphere[W] = radius;
// call vu0 microsubroutine
asm __volatile__("
lqc2 vf07,(%0) # load sphere (x,y,z,R) into vf07 of vu0
vcallms TestSphereAgainstOccluders # call microsubroutine
vnop # interlocking instruction, waits for vu0 completion
cfc2 $8,$vi01 # get boolean result from vu0
cfc2 $9,$vi06 # get index of first successful occluder
sw $8,(%1) # store boolean result
sw $9,(%2) # store index
": : "r" (&sphere), "r" (&result), "r" (&index) : "$8","$9");
// keeping score
if (result)
{
sOccluder::Occluders[index].score += meshes;
}
return (bool)result;
#endif // OCCLUDER_USES_VU0_MICROMODE
}
else
#endif // defined(OCCLUDER_USES_VU0_MACROMODE) || defined(OCCLUDER_USES_VU0_MICROMODE)
{
float center_x = p_center->GetX();
float center_y = p_center->GetY();
float center_z = p_center->GetZ();
sOccluder * p_occluder;
if (sOccluder::sUseScratchPad)
{
p_occluder = (sOccluder*)0x70000000;
} else {
p_occluder = &sOccluder::Occluders[0];
}
// Test against each occluder.
for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
{
Mth::Vector * p_plane = &(p_occluder->planes[0]);
// Test against each plane in the occluder.
for( uint32 p = 5; p > 0; --p )
{
float result = ( p_plane->GetX() * center_x ) +
( p_plane->GetY() * center_y ) +
( p_plane->GetZ() * center_z ) +
( p_plane->GetW() );
if( result >= -radius )
{
// Outside of this plane, therefore not occluded by this occluder.
goto NOT_OCCLUDED;
}
p_plane++;
}
// Inside all five planes, therefore occluded. Increase score for this occluder.
p_occluder->score += meshes;
return true;
NOT_OCCLUDED:
p_occluder++;
}
return false;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
inline bool CGeomNode::CullAgainstViewFrustum()
{
uint32 result;
#if 0
// test version... perform test and get results
asm __volatile__("
lqc2 vf08,(%0) # sphere (x,y,z,R)
lqc2 vf09,(%1) # box (bx,by,bz,?)
vcallms ViewCullTest # call microsubroutine
vnop # interlocking instruction, waits for vu0 completion
cfc2 $8,$vi01 # get result
sw $8,(%2) # store result
": : "r" (&m_bounding_sphere), "r" (&m_bounding_box), "r" (&result) : "$8");
#else
// faster version... pick up results from test which kicked off earlier
asm __volatile__("
vnop # interlocking instruction, waits for vu0 completion
cfc2 $8,$vi01 # get result
sw $8,(%0) # store result
": : "r" (&result) : "$8");
#endif
return (bool)result;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
inline bool CGeomNode::CullAgainstOuterFrustum()
{
uint32 result;
#if 0
// test version... perform test and get results
asm __volatile__("
lqc2 vf08,(%0) # sphere (x,y,z,R)
lqc2 vf09,(%1) # box (bx,by,bz,?)
vcallms OuterCullTest # call microsubroutine
vnop # interlocking instruction, waits for vu0 completion
cfc2 $8,$vi01 # get result
sw $8,(%2) # store result
": : "r" (&m_bounding_sphere), "r" (&m_bounding_box), "r" (&result) : "$8");
#else
// faster version... pick up results from test which kicked off earlier
asm __volatile__("
cfc2 $8,$vi11 # get result
sw $8,(%0) # store result
": : "r" (&result) : "$8");
#endif
return (bool)result;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderMinimalLeaf(CVu1Context& ctxt)
{
float camera_sphere_z;
#if __ASSERT_ON_ZERO_RADIUS__
Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
#endif
GEOMSTATINC(geom_stats_total);
GEOMSTATINC(geom_stats_occludecheck);
if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
{
GEOMSTATINC(geom_stats_occluded);
return;
}
GEOMSTATINC(geom_stats_leaf);
#ifdef __NOPT_ASSERT__
// Mick: check patch variables for debugging toggle of multi-passes
if ((m_flags & gPassMask1) != gPassMask0)
{
return;
}
#endif
// set the dma context
dma::SetList(u3.mp_group);
u3.mp_group->Used[render::Field] = true;
// flag the texture as used
if (u4.mp_texture)
{
u4.mp_texture->m_render_count++;
}
Mth::Vector *p_trans = ctxt.GetTranslation();
asm __volatile__("
qmfc2 $8,$vf05
sw $8,(%0)
": : "r" (&camera_sphere_z) : "$8" );
#ifdef __LOD_MULTIPASS__
// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
// if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere[Z] > render::sMultipassMaxDist))
if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere_z > render::sMultipassMaxDist))
return;
#endif
// initial cnt tag, for sending row reg and z-sort key
dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
vif::NOP();
vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// send transform context if necessary
if ((&ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
{
GEOMSTATINC(geom_stats_sendcontext);
//Dbg_MsgAssert(!ctxt.IsExtended(), ("oh bugger"));
int qwc = ctxt.IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
dma::Tag(dma::ref, qwc|(qwc-1)<<16, ctxt.GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += qwc-1;
u3.mp_group->pVu1Context = &ctxt;
}
// render this node by adding to the chosen dma list
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
vif::ITOP(PROJ|FOGE);
else
vif::ITOP(PROJ);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
// must add a 'next' tag for sorted groups
if (u3.mp_group->flags & GROUPFLAG_SORT)
{
GEOMSTATINC(geom_stats_sorted);
dma::SetList(NULL);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CGeomNode::RenderAsSky(CVu1Context& ctxt, uint32 renderFlags)
{
CVu1Context *p_ctxt;
// cull the node if not active
if (!(m_flags & NODEFLAG_ACTIVE))
{
return;
}
// no support for transformed nodes yet...
p_ctxt = &ctxt;
Mth::Vector sphere;
// bounding sphere tests, based on parent render flags
if (renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
{
// get and transform centre of bounding sphere
sphere = m_bounding_sphere;
sphere[W] = 1.0f;
sphere *= *p_ctxt->GetMatrix();
sphere[W] = m_bounding_sphere[W];
if (SphereIsOutsideViewVolume(sphere))
{
return;
}
if (SphereIsInsideOuterVolume(sphere))
{
renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
}
else if ((renderFlags & RENDERFLAG_CLIP) && !NeedsClipping(sphere))
{
renderFlags = renderFlags & ~RENDERFLAG_CLIP | RENDERFLAG_CULL;
}
}
// applying a colour to a node (copied from Geomnode::Render)
if (m_flags & NODEFLAG_COLOURED)
{
GEOMSTATINC(geom_stats_colored);
// if there's no local context, copy the parent's
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.Localise();
}
// copy node's colour to vu1 context
p_ctxt->SetColour(this);
renderFlags |= RENDERFLAG_COLOURED;
}
// is it a leaf node?
if (m_flags & NODEFLAG_LEAF)
{
#ifdef __NOPT_ASSERT__
// Mick: check patch variables for debugging toggle of multi-passes
if ((m_flags & gPassMask1) != gPassMask0)
{
return;
}
#endif
// uv wibble
if (m_flags & NODEFLAG_UVWIBBLE)
{
// if there's no local context, copy the global one
if (p_ctxt == &ctxt)
{
p_ctxt = ctxt.LocaliseExtend();
}
GEOMSTATINC(geom_stats_wibbleUV);
p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
}
// set the dma context
dma::SetList(u3.mp_group);
u3.mp_group->Used[render::Field] = true;
// initial cnt tag, for sending row reg and z-sort key
dma::Tag(dma::cnt, 1, 0);
vif::NOP();
vif::STROW((int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
(int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
(int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
// send transform context if necessary
if (p_ctxt != u3.mp_group->pVu1Context)
{
int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
vif::BASE(vu1::Loc);
vif::OFFSET(0);
vu1::Loc += qwc-1;
u3.mp_group->pVu1Context = p_ctxt;
}
// render this node by adding to the chosen dma list
uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
(renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
if (renderFlags & RENDERFLAG_COLOURED)
vu1_flags |= COLR;
if (render::EnableFog)
vu1_flags |= FOGE;
dma::pTag = dma::pLoc; // must set this manually if we bypass the dma tag functions
dma::Store32(u2.m_dma_tag_lo32, (uint32)u1.mp_dma);
vif::BASE(vu1::Loc);
vif::ITOP(vu1_flags);
vu1::Loc += u2.m_dma_tag_lo32>>16;
if (m_flags & NODEFLAG_BLACKFOG)
{
dma::Gosub(SET_FOGCOL,2);
dma::pLoc -= 8;
vif::FLUSHA();
vif::NOP();
}
}
else
{
// render any children
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderAsSky(*p_ctxt, renderFlags);
}
}
}
void CGeomNode::SetBoneTransforms(Mth::Matrix *pMat)
{
#if 0 // single-buffered
// copy the given matrices into the matrix buffer
for (int i = 0; i < m_num_bones; i++)
{
u4.mp_bone_transforms[i] = pMat[i];
}
#else // double-buffered
int startIndex = m_field ? m_num_bones : 0;
// copy the given matrices into the correct half of the matrix buffer
for (int i = 0; i < m_num_bones; i++)
{
u4.mp_bone_transforms[startIndex+i] = pMat[i];
}
// toggle field
m_field = !m_field;
#endif
}
#endif
bool CGeomNode::IsLeaf()
{
return GET_FLAG(NODEFLAG_LEAF);
}
bool CGeomNode::IsObject()
{
return GET_FLAG(NODEFLAG_OBJECT);
}
bool CGeomNode::IsActive()
{
return GET_FLAG(NODEFLAG_ACTIVE);
}
bool CGeomNode::IsTransformed()
{
return GET_FLAG(NODEFLAG_TRANSFORMED);
}
bool CGeomNode::IsSky()
{
return GET_FLAG(NODEFLAG_SKY);
}
bool CGeomNode::IsColored()
{
return GET_FLAG(NODEFLAG_COLOURED);
}
bool CGeomNode::IsSkeletal()
{
return GET_FLAG(NODEFLAG_SKELETAL);
}
bool CGeomNode::IsEnvMapped()
{
return GET_FLAG(NODEFLAG_ENVMAPPED);
}
bool CGeomNode::IsUVWibbled()
{
return GET_FLAG(NODEFLAG_UVWIBBLE);
}
bool CGeomNode::IsBillboard()
{
return GET_FLAG(NODEFLAG_BILLBOARD);
}
#ifdef __NOPT_ASSERT__
bool CGeomNode::WasRendered()
{
return GET_FLAG(NODEFLAG_WAS_RENDERED);
}
#endif
void CGeomNode::SetLeaf(bool yes)
{
SET_FLAG(NODEFLAG_LEAF);
}
void CGeomNode::SetObject(bool yes)
{
SET_FLAG(NODEFLAG_OBJECT);
}
void CGeomNode::SetActive(bool yes)
{
SET_FLAG(NODEFLAG_ACTIVE);
}
void CGeomNode::SetTransformed(bool yes)
{
SET_FLAG(NODEFLAG_TRANSFORMED);
}
void CGeomNode::SetSky(bool yes)
{
SET_FLAG(NODEFLAG_SKY);
}
void CGeomNode::SetZPush0(bool yes)
{
SET_FLAG(NODEFLAG_ZPUSH0);
}
void CGeomNode::SetZPush1(bool yes)
{
SET_FLAG(NODEFLAG_ZPUSH1);
}
void CGeomNode::SetZPush2(bool yes)
{
SET_FLAG(NODEFLAG_ZPUSH2);
}
void CGeomNode::SetZPush3(bool yes)
{
SET_FLAG(NODEFLAG_ZPUSH3);
}
void CGeomNode::SetNoShadow(bool yes)
{
SET_FLAG(NODEFLAG_NOSHADOW);
}
void CGeomNode::SetUVWibbled(bool yes)
{
SET_FLAG(NODEFLAG_UVWIBBLE);
}
void CGeomNode::SetVCWibbled(bool yes)
{
SET_FLAG(NODEFLAG_VCWIBBLE);
}
void CGeomNode::SetColored(bool yes)
{
SET_FLAG(NODEFLAG_COLOURED);
}
void CGeomNode::SetSkinned(bool yes)
{
SET_FLAG(NODEFLAG_SKINNED);
}
void CGeomNode::SetBillboard(bool yes)
{
SET_FLAG(NODEFLAG_BILLBOARD);
}
void CGeomNode::SetSkeletal(bool yes)
{
SET_FLAG(NODEFLAG_SKELETAL);
}
void CGeomNode::SetEnvMapped(bool yes)
{
SET_FLAG(NODEFLAG_ENVMAPPED);
}
void CGeomNode::SetBlackFog(bool yes)
{
SET_FLAG(NODEFLAG_BLACKFOG);
}
uint32 CGeomNode::GetChecksum()
{
return m_checksum;
}
void CGeomNode::SetChecksum(uint32 checksum)
{
m_checksum = checksum;
}
void CGeomNode::SetBoundingSphere(float x, float y, float z, float R)
{
m_bounding_sphere[0] = x;
m_bounding_sphere[1] = y;
m_bounding_sphere[2] = z;
m_bounding_sphere[3] = R;
}
void CGeomNode::SetBoundingBox(float x, float y, float z)
{
m_bounding_box[0] = x;
m_bounding_box[1] = y;
m_bounding_box[2] = z;
}
uint8 *CGeomNode::GetDma()
{
return (m_flags & NODEFLAG_LEAF) ? u1.mp_dma : NULL;
}
void CGeomNode::SetDma(uint8 *pDma)
{
m_flags |= NODEFLAG_LEAF;
u1.mp_dma = pDma;
}
void CGeomNode::SetDmaTag(uint8 *pDma)
{
m_flags |= NODEFLAG_LEAF;
u2.m_dma_tag_lo32 = dma::call<<28 | (((uint16 *)pDma)[1]&0x3FF)<<16 | 0;
u1.mp_dma = pDma;
}
CGeomNode *CGeomNode::GetChild()
{
return (m_flags & NODEFLAG_LEAF) ? NULL : u1.mp_child;
}
void CGeomNode::SetChild(CGeomNode *pChild)
{
m_flags &= ~NODEFLAG_LEAF;
u1.mp_child = pChild;
}
void CGeomNode::AddChild(CGeomNode *pChild)
{
if (!(m_flags & NODEFLAG_LEAF) && u1.mp_child)
{
Dbg_Assert(!pChild->mp_sibling);
CGeomNode *p_last_child = u1.mp_child;
while (p_last_child->mp_sibling)
{
p_last_child = p_last_child->mp_sibling;
}
p_last_child->mp_sibling = pChild;
} else {
u1.mp_child = pChild;
}
m_flags &= ~NODEFLAG_LEAF;
}
CGeomNode *CGeomNode::GetSibling()
{
return mp_sibling;
}
void CGeomNode::SetSibling(CGeomNode *pSibling)
{
mp_sibling = pSibling;
}
void CGeomNode::SetSlaveLOD(CGeomNode *pLOD)
{
CGeomNode *pLOD_list = this;
while (pLOD_list->mp_next_LOD) {
if (pLOD_list->mp_next_LOD->m_LOD_far_dist > pLOD->m_LOD_far_dist) {
pLOD->mp_next_LOD = pLOD_list->mp_next_LOD;
pLOD_list->mp_next_LOD = pLOD;
return;
}
pLOD_list = pLOD_list->mp_next_LOD;
}
// If we get here, then we are at the end of the list
pLOD_list->mp_next_LOD = pLOD;
}
void CGeomNode::SetLODFarDist(float LOD_far_dist)
{
m_LOD_far_dist = LOD_far_dist;
}
float CGeomNode::GetLODFarDist()
{
return m_LOD_far_dist;
}
sGroup *CGeomNode::GetGroup()
{
return u3.mp_group;
}
void CGeomNode::SetGroup(sGroup *pGroup)
{
u3.mp_group = pGroup;
}
Mth::Matrix& CGeomNode::GetMatrix()
{
Dbg_MsgAssert(IsTransformed(), ("trying to access matrix of a non-transformed CGeomNode"));
Dbg_MsgAssert(u2.mp_transform, ("null matrix pointer"));
Dbg_MsgAssert(!(m_flags&NODEFLAG_LEAF), ("Transformed leaf node\n"));
return *u2.mp_transform;
}
void CGeomNode::SetMatrix(Mth::Matrix *p_mat)
{
Dbg_MsgAssert(!(m_flags&NODEFLAG_LEAF), ("Transformed leaf node\n"));
u2.mp_transform = (Mth::Matrix *)p_mat;
SetTransformed(p_mat ? true : false);
}
void CGeomNode::SetUVWibblePointer(float *pData)
{
mp_uv_wibble = pData;
}
void CGeomNode::SetVCWibblePointer(uint32 *pData)
{
mp_vc_wibble = pData;
}
void CGeomNode::SetColor(uint32 rgba)
{
if (rgba == 0x80808080)
{
// clear the colored falg if set to the neutral color
// as things render a lot quicker that way
// also, applying the color gives a slightly different
// value from not applying any color
SetColored(false);
}
m_colour = rgba;
}
uint32 CGeomNode::GetColor()
{
if (IsColored())
{
return m_colour;
} else {
return 0x80808080;
}
}
void CGeomNode::SetLightGroup(CLightGroup *p_light_group)
{
if (!IsLeaf())
{
u3.mp_light_group = p_light_group;
}
}
CLightGroup * CGeomNode::GetLightGroup()
{
if (!IsLeaf())
{
return u3.mp_light_group;
} else {
return NULL;
}
}
void CGeomNode::SetVisibility(uint8 mask)
{
m_flags &= ~ALL_VISIBILITY_BITS; // Take out old visibility bits
m_flags |= (mask << VISIBILITY_FLAG_BIT);
}
uint8 CGeomNode::GetVisibility()
{
return (m_flags & ALL_VISIBILITY_BITS) >> VISIBILITY_FLAG_BIT;
}
void CGeomNode::SetBoneIndex(sint8 index)
{
m_bone_index = index;
}
sint8 CGeomNode::GetBoneIndex()
{
return m_bone_index;
}
void CGeomNode::CountMetrics(CGeomMetrics *p_metrics)
{
p_metrics->m_total++;
if (IsObject())
{
p_metrics->m_object++;
}
if (IsLeaf())
{
p_metrics->m_leaf++;
p_metrics->m_verts += dma::GetNumVertices(u1.mp_dma);
p_metrics->m_polys += dma::GetNumTris(u1.mp_dma);
}
else
{
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->CountMetrics(p_metrics);
}
}
}
static Mth::Vector *p_verts = NULL;
void CGeomNode::RenderWireframe(int mode)
{
#ifdef __PLAT_NGPS__
int lines = 0;
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
p_verts = new Mth::Vector[100000];
Mem::Manager::sHandle().PopContext();
RenderWireframeRecurse(mode, lines);
delete p_verts;
p_verts = NULL;
#endif
}
void CGeomNode::RenderWireframeRecurse(int mode, int &lines)
{
#ifdef __PLAT_NGPS__
int sectors = 0;
int vert_count=0;
if (IsLeaf())
{
if (lines < 95000) // clmap number of lines, otherwise we crash
{
sectors++;
//
// Do renderable geometry
int verts = GetNumVerts();
if (verts>2)
{
vert_count += verts;
#define peak 500
int n=GetNumPolys();
int r,g,b;
r=g=b=0;
if (n <= peak )
{
r = (255 * (n)) / peak; // r ramps up (black to red)
}
else if (n <= peak * 2)
{
r = 255;
b = (255 * (n - peak) / (peak)); // b&g ramps up (to white)
g = b;
}
else
{
r = g = b = 255;
}
uint32 rgb = (b<<16)|(g<<8)|r;
if (mode == 4)
{
// generate a random looking number that's basically a hash of some
// values in the node, so it stays the same
uint32 xxx = ((uint32)(this) * verts * n);
NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & xxx));
}
else
{
NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
}
GetVerts(p_verts);
Mth::Vector *p_vert = p_verts+1;
for (int i = 1; i < verts; i++)
{
NxPs2::DrawLine3D((*p_vert)[X],(*p_vert)[Y],(*p_vert)[Z],p_vert[-1][X],p_vert[-1][Y],p_vert[-1][Z]);
p_vert++;
lines++;
} // end for
NxPs2::EndLines3D();
} // end if
}
}
else
{
if (WasRendered())
{
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->RenderWireframeRecurse(mode, lines);
}
}
}
#endif
}
int CGeomNode::GetNumVerts()
{
int num_verts = 0;
if (IsLeaf())
{
num_verts = dma::GetNumVertices(u1.mp_dma);
}
else
{
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
num_verts += mp_next_LOD->GetNumVerts();
}
}
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
num_verts += p_child->GetNumVerts();
}
}
return num_verts;
}
int CGeomNode::GetNumPolys()
{
int num_polys = 0;
if (IsLeaf())
{
num_polys = dma::GetNumTris(u1.mp_dma);
}
else
{
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
num_polys += mp_next_LOD->GetNumPolys();
}
}
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
num_polys += p_child->GetNumPolys();
}
}
return num_polys;
}
int CGeomNode::GetNumBasePolys()
{
int num_base_polys = 0;
if (IsLeaf())
{
if ((m_flags & NODEFLAG_ZPUSH) == 0) // check it's the base layer
{
num_base_polys = dma::GetNumTris(u1.mp_dma);
}
}
else
{
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
num_base_polys += mp_next_LOD->GetNumBasePolys();
}
}
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
num_base_polys += p_child->GetNumBasePolys();
}
}
return num_base_polys;
}
int CGeomNode::GetNumObjects()
{
int num_objects = 0;
// Check self
if (IsObject())
{
num_objects++;
}
// Recursively check children
if (!IsLeaf())
{
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
num_objects += p_child->GetNumObjects();
}
}
return num_objects;
}
CGeomNode* CGeomNode::GetObject(int num, bool root)
{
static int found_so_far;
if (root)
{
found_so_far = -1;
//Dbg_Message("Start looking for #%d", num);
}
// Check self
if (IsObject())
{
if (++found_so_far == num)
{
//Dbg_Message("Found object #%d", num);
return this;
}
//Dbg_Message("Found object #%d; looking for #%d", found_so_far, num);
}
CGeomNode *p_found = NULL;
// Recursively check children
if (!IsLeaf())
{
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_found = p_child->GetObject(num, false);
if (p_found)
{
break;
}
}
}
if (root)
{
Dbg_Assert(p_found);
}
return p_found;
}
#ifdef __PLAT_WN32__
void CGeomNode::Preprocess(uint8 *p_baseAddress)
{
// recursively process any children
CGeomNode *p_child, *p_sibling;
if (!(m_flags & NODEFLAG_LEAF))
{
for (p_child=u1.mp_child; p_child; p_child=p_sibling)
{
p_sibling = p_child->mp_sibling;
p_child->Preprocess(p_baseAddress);
}
}
// recursively process any LODs
if (mp_next_LOD)
{
mp_next_LOD->Preprocess(p_baseAddress);
}
if (!(m_flags & NODEFLAG_LEAF))
{
// convert transform pointer to offset
if (u2.mp_transform)
{
u2.mp_transform = (Mth::Matrix *) ( (uint8 *)u2.mp_transform - p_baseAddress );
}
else
{
u2.mp_transform = (Mth::Matrix *)(-1);
}
}
if (m_flags & NODEFLAG_LEAF)
{
// convert dma pointer to offset
if (u1.mp_dma)
{
u1.mp_dma = (uint8 *)( u1.mp_dma - p_baseAddress );
}
else
{
u1.mp_dma = (uint8 *)(-1);
}
}
else
{
// convert child pointer to offset
if (u1.mp_child)
{
u1.mp_child = (CGeomNode *) ( (uint8 *)u1.mp_child - p_baseAddress );
}
else
{
u1.mp_child = (CGeomNode *)(-1);
}
}
// leave group checksum alone
// convert sibling pointer to offset
if (mp_sibling)
{
mp_sibling = (CGeomNode *) ( (uint8 *)mp_sibling - p_baseAddress );
}
else
{
mp_sibling = (CGeomNode *)(-1);
}
// convert LOD pointer to offset
if (mp_next_LOD)
{
mp_next_LOD = (CGeomNode *) ( (uint8 *)mp_next_LOD - p_baseAddress );
}
else
{
mp_next_LOD = (CGeomNode *)(-1);
}
// convert uv wibble pointer to offset
if (m_flags & NODEFLAG_UVWIBBLE)
{
if (!(m_flags & NODEFLAG_ENVMAPPED) || !(m_flags & NODEFLAG_LEAF)) // can't wibble environment-mapped leaves
{
mp_uv_wibble = (float *) ( (uint8 *)mp_uv_wibble - p_baseAddress );
}
else
{
mp_uv_wibble = (float *)(-1);
}
}
// convert vc wibble pointer to offset
if (m_flags & NODEFLAG_VCWIBBLE)
{
// first convert mesh info
uint32 *p_meshdata = mp_vc_wibble;
int seq, num_seqs, vert, num_verts;
num_seqs = *p_meshdata++;
for (seq=0; seq<num_seqs; seq++)
{
// convert sequence pointer
*(uint32 **)p_meshdata = (uint32 *) ( (uint8 *)*p_meshdata - p_baseAddress );
p_meshdata++;
// convert rgba pointers
num_verts = *p_meshdata++;
for (vert=0; vert<num_verts; vert++)
{
*(uint32 **)p_meshdata = (uint32 *) ( (uint8 *)*p_meshdata - p_baseAddress );
p_meshdata++;
}
}
// now the wibble pointer
mp_vc_wibble = (uint32 *) ( (uint8 *)mp_vc_wibble - p_baseAddress );
}
else
{
mp_vc_wibble = (uint32 *)(-1);
}
}
#endif
#ifdef __PLAT_NGPS__
static int SNL_Depth = 0;
void StripNiceLeaves(CGeomNode *p_parent)
{
SNL_Depth++;
CGeomNode *p_child = p_parent->GetChild();
// iterate over siblings without recursion
while (p_child)
{
printf ("%2d:",SNL_Depth);
for (int i=i;i<SNL_Depth;i++)
printf (" ");
printf ("0x%08x\n",p_child->GetFlags());
CGeomNode *p_next_child = p_child->GetSibling();
// if child not a leaf, then just recurse down
if (! (p_child->GetFlags() & NODEFLAG_LEAF))
{
StripNiceLeaves(p_child);
}
else
{
// it's a leaf node, so maybe strip it, and then carry on to the siblings
if (((p_child->GetFlags() & (NODEFLAG_ACTIVE |
NODEFLAG_SKY |
NODEFLAG_TRANSFORMED |
NODEFLAG_LEAF |
NODEFLAG_OBJECT |
NODEFLAG_COLOURED |
NODEFLAG_SKELETAL |
NODEFLAG_BILLBOARD |
NODEFLAG_UVWIBBLE |
NODEFLAG_VCWIBBLE |
NODEFLAG_ENVMAPPED))
== (NODEFLAG_ACTIVE |
NODEFLAG_LEAF)))
{
// strip it out (eventually add it to a quick-render list)
if (p_parent->GetChild() == p_child)
{
// just need to fix up the parent
p_parent->SetChild(p_next_child);
}
else
{
// find the elder sibling and fix up his sibling
CGeomNode *p_elder_sibling = p_parent->GetChild();
while (p_elder_sibling->GetSibling() != p_child)
{
p_elder_sibling = p_elder_sibling->GetSibling();
Dbg_Assert(p_elder_sibling); // catch infinite loop
}
p_elder_sibling->SetSibling(p_next_child);
}
// p_child is now an orphan
// TODO - add to quick render list ...
}
}
p_child = p_next_child;
}
SNL_Depth--;
}
CGeomNode* CGeomNode::sProcessInPlace(uint8* pPipData, uint32 loadFlags)
{
Dbg_MsgAssert(((int)(pPipData) & 0xf) == 0,("pPipData (0x%x) not multiple of 16 \n"
,(int)pPipData));
// Jump to beginning of CGeomNode data
pPipData += *(uint32 *)pPipData;
// from header, get address of root node
CGeomNode *p_root_node = (CGeomNode *) (pPipData + *(uint32 *)pPipData);
Dbg_MsgAssert(((int)(p_root_node) & 0xf) == 0,("p_root_node (0x%x) not multiple of 16, .SCN.PS2 file corrupt... \n"
,(int)p_root_node));
// recursively process the tree
#ifdef __NOPT_ASSERT__
// int leaves =
#endif
p_root_node->ProcessNodeInPlace(pPipData);
#ifdef __NOPT_ASSERT__
// printf (">>>>> Number of leaves in geom PIP file = %d\n",leaves);
#endif
//StripNiceLeaves(p_root_node);
// add the root node to the world or to the database
p_root_node->BecomesChildOf((loadFlags & LOADFLAG_RENDERNOW) ? &sWorld : &sDatabase);
// set the root node's sky flag if loaded as a sky
if (loadFlags & LOADFLAG_SKY)
{
p_root_node->SetSky(true);
}
// return the root node pointer
return p_root_node;
}
void * CGeomNode::sGetHierarchyArray(uint8 *pPipData, int& size)
{
Dbg_MsgAssert(((int)(pPipData) & 0xf) == 0,("pPipData (0x%x) not multiple of 16 \n"
,(int)pPipData));
uint32 *pPip32Data = (uint32 *) pPipData;
pPip32Data++; // skip CGeomNode offset
// Get array pointer and size
void *p_array = (pPipData + *(pPip32Data++));
size = *pPip32Data;
if (size)
{
return p_array;
} else {
return NULL;
}
}
void CGeomNode::AddToTree(uint32 loadFlags)
{
BecomesChildOf((loadFlags & LOADFLAG_RENDERNOW) ? &sWorld : &sDatabase);
// set the root node's sky flag if loaded as a sky
if (loadFlags & LOADFLAG_SKY)
{
SetSky(true);
}
}
void CGeomNode::Cleanup()
{
// remove from any node it belongs to
RemoveFrom(&sWorld);
RemoveFrom(&sDatabase);
}
CGeomNode* CGeomNode::CreateInstance(Mth::Matrix *pMat, CGeomNode *p_parent)
{
// copy node
CGeomNode *p_node = new CGeomNode(*this);
// flag as an instance
p_node->m_flags |= NODEFLAG_INSTANCE;
// set matrix
p_node->SetMatrix(pMat);
// add to world
if (p_parent)
{
p_node->BecomesChildOf(p_parent);
}
else
{
p_node->BecomesChildOf(&sWorld);
}
// activate!
p_node->SetActive(true);
// return new node
return p_node;
}
// skeletal version
CGeomNode* CGeomNode::CreateInstance(Mth::Matrix *pMat, int numBones, Mth::Matrix *pBoneTransforms, CGeomNode *p_parent)
{
// copy node
CGeomNode *p_node = new CGeomNode(*this);
// flag as a skeletal instance
p_node->m_flags |= NODEFLAG_SKELETAL|NODEFLAG_INSTANCE;
// set matrix and bone data
p_node->SetMatrix(pMat);
p_node->m_num_bones = numBones;
p_node->u4.mp_bone_transforms = pBoneTransforms;
p_node->m_field = 0;
// add to world
if (p_parent)
{
p_node->BecomesChildOf(p_parent);
}
else
{
p_node->BecomesChildOf(&sWorld);
}
// activate!
p_node->SetActive(true);
// return new node
return p_node;
}
void CGeomNode::DeleteInstance()
{
Dbg_MsgAssert(m_flags&NODEFLAG_INSTANCE, ("attempt to delete a non-instance"));
bool removed;
removed = RemoveFrom(&sWorld);
removed = removed || RemoveFromChildrenOf(&sWorld); // Check all cloned scenes, too
Dbg_MsgAssert(removed, ("Couldn't remove instanced CGeomNode from CGeomNode tree"));
delete this;
}
CGeomNode* CGeomNode::CreateCopy(CGeomNode *p_parent, bool root)
{
// copy node
CGeomNode *p_node = new CGeomNode(*this);
// clear some pointers
p_node->mp_sibling = NULL;
// add to world if this is the top node
if (root)
{
if (p_parent)
{
p_node->BecomesChildOf(p_parent);
}
else
{
p_node->BecomesChildOf(&sWorld);
}
}
// activate!
p_node->SetActive(true);
// Also copy data and tree underneath
if (IsLeaf())
{
// Copy the dma data
Dbg_Assert(u1.mp_dma);
p_node->u1.mp_dma = new uint8[dma::GetDmaSize(u1.mp_dma)];
Dbg_Assert(!((uint32) p_node->u1.mp_dma & 0xf))
dma::Copy(u1.mp_dma, p_node->u1.mp_dma);
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
p_node->mp_next_LOD = mp_next_LOD->CreateCopy(NULL, false);
}
}
// Recursively copy children
p_node->u1.mp_child = NULL;
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_node->AddChild(p_child->CreateCopy(NULL, false));
}
}
// return new node
return p_node;
}
void WaitForRendering();
// Recursivly deletes an object and all its children
void CGeomNode::DeleteCopy(bool root)
{
// Garrett: Kinda hacky, but gets the job done. Make sure we aren't rendering before
// we release DMA buffers
WaitForRendering();
Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to delete an instance"));
if (root)
{
bool removed;
removed = RemoveFrom(&sWorld);
removed = removed || RemoveFromChildrenOf(&sWorld); // Check all cloned scenes, too
Dbg_MsgAssert(removed, ("Couldn't remove copied CGeomNode from CGeomNode tree"));
}
// Also delete data and tree underneath
if (IsLeaf())
{
// Delete the dma data
Dbg_Assert(u1.mp_dma);
delete [] u1.mp_dma;
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->DeleteCopy(false);
}
}
// Recursively delete children
// (Mick) restructured this because DeleteCopy will delete the object it is run on
CGeomNode *p_child=u1.mp_child;
while ( p_child)
{
CGeomNode *p_next=p_child->mp_sibling;
p_child->DeleteCopy(false);
p_child = p_next;
}
}
delete this; // <<<< Warning: deletes 'this'
}
void CGeomNode::Translate(const Mth::Vector & delta_pos)
{
Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to translate an instance"));
// Update bounding sphere
m_bounding_sphere[X] += delta_pos[X];
m_bounding_sphere[Y] += delta_pos[Y];
m_bounding_sphere[Z] += delta_pos[Z];
#if 0
// Test for Park Editor
Dbg_Message("Translating by (%.8f, %.8f, %.8f)", delta_pos[X], delta_pos[Y], delta_pos[Z]);
int delta_x = (int) (delta_pos[X] * SUB_INCH_PRECISION);
int delta_y = (int) (delta_pos[Y] * SUB_INCH_PRECISION);
int delta_z = (int) (delta_pos[Z] * SUB_INCH_PRECISION);
Dbg_MsgAssert(!(delta_x & 0xF), ("Fraction in X: (%x, %x, %x)", delta_x, delta_y, delta_z));
Dbg_MsgAssert(!(delta_y & 0xF), ("Fraction in Y: (%x, %x, %x)", delta_x, delta_y, delta_z));
Dbg_MsgAssert(!(delta_z & 0xF), ("Fraction in Z: (%x, %x, %x)", delta_x, delta_y, delta_z));
#endif
// Update whole tree
if (IsLeaf())
{
// Get the dma data
Dbg_Assert(u1.mp_dma);
if (dma::GetBitLengthXYZ(u1.mp_dma) == 32)
{
// Calculate int versions
int delta_x = (int) (delta_pos[X] * SUB_INCH_PRECISION);
int delta_y = (int) (delta_pos[Y] * SUB_INCH_PRECISION);
int delta_z = (int) (delta_pos[Z] * SUB_INCH_PRECISION);
int num_verts = dma::GetNumVertices(u1.mp_dma);
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
int32 *p_verts = new int32[num_verts * 4];
dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);
for (int i = 0; i < num_verts; i++) {
p_verts[(i * 4) + 0] += delta_x;
p_verts[(i * 4) + 1] += delta_y;
p_verts[(i * 4) + 2] += delta_z;
}
dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);
delete [] p_verts;
Mem::Manager::sHandle().PopContext();
}
else
{
Dbg_MsgAssert(dma::GetBitLengthXYZ(u1.mp_dma)==16, ("oh dear, should've been either 16 or 32..."));
}
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->Translate(delta_pos);
}
}
// Recursively update children
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->Translate(delta_pos);
}
}
}
void CGeomNode::RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y, bool root)
{
Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to translate an instance"));
if (root)
{
Translate(-world_origin);
}
// Update bounding box
// Since they are relative to width and height, we just need to possibly swap X and Z
if ((int) rot_y & 1)
{
float temp = m_bounding_box[X];
m_bounding_box[X] = m_bounding_box[Z];
m_bounding_box[Z] = temp;
}
// Also rotate bounding sphere position
m_bounding_sphere.RotateY90(rot_y);
// Update whole tree
if (IsLeaf())
{
// Delete the dma data
Dbg_Assert(u1.mp_dma);
int num_verts = dma::GetNumVertices(u1.mp_dma);
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
int32 *p_verts = new int32[num_verts * 4];
dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);
for (int i = 0; i < num_verts; i++) {
Mth::RotateY90(rot_y,
p_verts[(i * 4) + 0],
p_verts[(i * 4) + 1],
p_verts[(i * 4) + 2]);
}
dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);
delete [] p_verts;
Mem::Manager::sHandle().PopContext();
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->RotateY(world_origin, rot_y, false);
}
}
// Recursively update children
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->RotateY(world_origin, rot_y, false);
}
}
if (root)
{
Translate(world_origin);
}
}
void CGeomNode::Scale(const Mth::Vector & world_origin, const Mth::Vector & scale, bool root)
{
Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to scale an instance"));
if (root)
{
Translate(-world_origin);
}
// Update bounding box
m_bounding_box[X] *= scale[X];
m_bounding_box[Y] *= scale[Y];
m_bounding_box[Z] *= scale[Z];
#if 0 // Don't do this since it is a local scale
// Also scale bounding sphere position
// Update bounding sphere
m_bounding_sphere[X] *= scale[X];
m_bounding_sphere[Y] *= scale[Y];
m_bounding_sphere[Z] *= scale[Z];
#endif
// Garrett: The radius calculation is not accurate, but so far, it works. It will always round up.
// We have to assume the largest radius
float radius_scale = sqrtf((scale[X] * scale[X]) + (scale[Y] * scale[Y]) + (scale[Z] + scale[Z]));
m_bounding_sphere[W] *= radius_scale;
// But see if the bounding box radius is smaller
float box_radius = sqrtf((m_bounding_box[X] * m_bounding_box[X]) + (m_bounding_box[Y] * m_bounding_box[Y]) + (m_bounding_box[Z] * m_bounding_box[Z]));
//Dbg_Message("Calculated sphere radius %f; Bounding box radius %f", m_bounding_sphere[W] , box_radius);
m_bounding_sphere[W] = Mth::Min(box_radius, m_bounding_sphere[W]);
// Update whole tree
if (IsLeaf())
{
// Get the dma data
Dbg_Assert(u1.mp_dma);
// Calculate int versions
int scale_x = (int) (scale[X] * SUB_INCH_PRECISION);
int scale_y = (int) (scale[Y] * SUB_INCH_PRECISION);
int scale_z = (int) (scale[Z] * SUB_INCH_PRECISION);
// Check to see that we didn't go to zero
Dbg_MsgAssert(scale_x, ("CGeomNode::Scale(): X scale went to 0 in integer conversion. Original value %f", scale[X]));
Dbg_MsgAssert(scale_y, ("CGeomNode::Scale(): Y scale went to 0 in integer conversion. Original value %f", scale[Y]));
Dbg_MsgAssert(scale_z, ("CGeomNode::Scale(): Z scale went to 0 in integer conversion. Original value %f", scale[Z]));
int num_verts = dma::GetNumVertices(u1.mp_dma);
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
int32 *p_verts = new int32[num_verts * 4];
dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);
// SUB_INCH_PRECISION must be a whole number
Dbg_Assert( ((int) (SUB_INCH_PRECISION * 1000.0f)) == (((int) SUB_INCH_PRECISION) * 1000) );
for (int i = 0; i < num_verts; i++) {
p_verts[(i * 4) + 0] = (p_verts[(i * 4) + 0] * scale_x) / ((int) SUB_INCH_PRECISION);
p_verts[(i * 4) + 1] = (p_verts[(i * 4) + 1] * scale_y) / ((int) SUB_INCH_PRECISION);
p_verts[(i * 4) + 2] = (p_verts[(i * 4) + 2] * scale_z) / ((int) SUB_INCH_PRECISION);
}
dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);
delete [] p_verts;
Mem::Manager::sHandle().PopContext();
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->Scale(world_origin, scale, false);
}
}
// Recursively update children
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->Scale(world_origin, scale, false);
}
}
if (root)
{
Translate(world_origin);
}
}
void CGeomNode::GetVerts(Mth::Vector *p_verts, bool root)
{
// Not thread safe
static int vert_index;
if (root)
{
vert_index = 0;
}
if (IsLeaf())
{
// Get the dma data
Dbg_Assert(u1.mp_dma);
int num_verts = dma::GetNumVertices(u1.mp_dma);
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
int32 *p_raw_verts = (int32 *) &p_verts[vert_index];
//int32 *p_raw_verts = new int32[num_verts * 4];
dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_raw_verts);
// Convert center to integer
// (Mick: Changed to always have a center offset, as even 32 bit coordinates have one
// previous, the code assumed that 32 bit coordinate were absolute world coords, and so
// did not add in the "center" offset
int32 center[4];
dma::ConvertFloatToXYZ(center, m_bounding_sphere);
for (int i = 0; i < num_verts; i++, vert_index++)
{
dma::ConvertXYZToFloat(p_verts[vert_index], &(p_raw_verts[i * 4]), center);
}
// delete [] p_raw_verts;
Mem::Manager::sHandle().PopContext();
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->GetVerts(p_verts, false);
}
}
// Get children verts
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->GetVerts(p_verts, false);
}
}
}
void CGeomNode::SetVerts(Mth::Vector *p_verts, bool root)
{
// Not thread safe
static int vert_index;
if (root)
{
vert_index = 0;
}
if (IsLeaf())
{
// Get the dma data
Dbg_Assert(u1.mp_dma);
int num_verts = dma::GetNumVertices(u1.mp_dma);
// Recalculate the bounding data BEFORE setting the DMA data, since it will change the
// center of the bounding sphere.
recalculate_bounding_data(&p_verts[vert_index], num_verts);
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
int32 *p_raw_verts = new int32[num_verts * 4];
// Convert center to integer
int32 center[4];
if (dma::GetBitLengthXYZ(u1.mp_dma) < 32)
{
dma::ConvertFloatToXYZ(center, m_bounding_sphere);
}
for (int i = 0; i < num_verts; i++, vert_index++) {
if (dma::GetBitLengthXYZ(u1.mp_dma) == 32)
{
dma::ConvertFloatToXYZ(&(p_raw_verts[i * 4]), p_verts[vert_index]);
} else {
dma::ConvertFloatToXYZ(&(p_raw_verts[i * 4]), p_verts[vert_index], center);
}
}
// Replaces only XYZs (not W)
dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_raw_verts, true);
delete [] p_raw_verts;
Mem::Manager::sHandle().PopContext();
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->SetVerts(p_verts, false);
}
}
// Set children verts
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->SetVerts(p_verts, false);
}
// Recalculate the bounding data at this level using all the verts (assuming this is
// going to be the object node).
Dbg_MsgAssert(IsObject(), ("Don't know how to recalculate the bounding data for this node: Not an object"));
int num_verts = GetNumVerts();
recalculate_bounding_data(p_verts, num_verts);
}
}
void CGeomNode::recalculate_bounding_data(Mth::Vector *p_verts, int num_verts)
{
Mth::Vector min, max, center, half_vec;
float radius;
int i;
// calculate bounding box for the mesh
min[0] = min[1] = min[2] = 1e30f;
max[0] = max[1] = max[2] = -1e30f;
for (i=0; i<num_verts; i++)
{
if (p_verts[i][X] < min[X])
min[X] = p_verts[i][X];
if (p_verts[i][Y] < min[Y])
min[Y] = p_verts[i][Y];
if (p_verts[i][Z] < min[Z])
min[Z] = p_verts[i][Z];
if (p_verts[i][X] > max[X])
max[X] = p_verts[i][X];
if (p_verts[i][Y] > max[Y])
max[Y] = p_verts[i][Y];
if (p_verts[i][Z] > max[Z])
max[Z] = p_verts[i][Z];
}
// calculate bounding sphere for the mesh
center = (max+min)*0.5f;
radius = 0.0f;
for (i=0; i<num_verts; i++)
{
float len = (p_verts[i] - center).Length();
if (len > radius)
radius = len;
}
// Update bounding box data
half_vec = 0.5f * (max - min);
m_bounding_box[X] = half_vec[X];
m_bounding_box[Y] = half_vec[Y];
m_bounding_box[Z] = half_vec[Z];
// Update bounding sphere data
m_bounding_sphere = center;
m_bounding_sphere[W] = radius;
}
void CGeomNode::GetColors(uint32 *p_colors, bool root)
{
// Not thread safe
static int vert_index;
if (root)
{
vert_index = 0;
}
if (IsLeaf())
{
// Get the dma data
Dbg_Assert(u1.mp_dma);
int num_verts = dma::GetNumVertices(u1.mp_dma);
dma::ExtractRGBAs(u1.mp_dma, (uint8 *) &(p_colors[vert_index]));
vert_index += num_verts;
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->GetColors(p_colors, false);
}
}
// Get children colors
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->GetColors(p_colors, false);
}
}
}
void CGeomNode::SetColors(uint32 *p_colors, bool root)
{
// Not thread safe
static int vert_index;
if (root)
{
vert_index = 0;
}
if (IsLeaf())
{
// Set the dma data
Dbg_Assert(u1.mp_dma);
int num_verts = dma::GetNumVertices(u1.mp_dma);
dma::ReplaceRGBAs(u1.mp_dma, (uint8 *) &(p_colors[vert_index]));
vert_index += num_verts;
} else {
// Check if we have LODs
if (IsObject())
{
if (mp_next_LOD)
{
mp_next_LOD->SetColors(p_colors, false);
}
}
// Set children colors
for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
{
p_child->SetColors(p_colors, false);
}
}
}
/*
vc wibble material data block format
------------------------------------
int num_seqs;
for (seq=0; seq<num_seqs; seq++)
{
int num_keys;
int phase_shift;
for (key=0; key<num_keys; key++)
{
int start_time;
float colour[4];
}
}
vc wibble mesh data block format
--------------------------------
int num_seqs;
for (seq=0; seq<num_seqs; seq++)
{
uint32 *p_sequence;
int num_wibbled_verts;
for (vert=0; vert<num_wibbled_verts; vert++)
{
uint32 *p_rgba;
}
}
*/
void CGeomNode::WibbleVCs()
{
int seq, num_seqs, key, num_keys, time, start_time, end_time, period, vert, num_verts, phase_shift;
uint32 rgba, *p_rgba;
float *p_key;
float t,r,g,b,a;
uint32 *p_meshdata, *p_matdata;
p_meshdata = mp_vc_wibble;
// get # animation sequences
num_seqs = *p_meshdata++;
for (seq=0; seq<num_seqs; seq++)
{
p_matdata = *(uint32 **)p_meshdata;
p_meshdata++;
// get # keyframes for this sequence
num_keys = *p_matdata++;
// get phase-shift
phase_shift = *p_matdata++;
// time parameters
start_time = p_matdata[0];
end_time = p_matdata[num_keys*5-5];
period = end_time - start_time;
time = start_time + (render::sTime /*(int)Tmr::GetTime()*/ + phase_shift) % period;
// keep time in range
if (time < start_time) time = start_time;
if (time > end_time) time = end_time;
// locate the keyframe
for (key=num_keys-1,p_key=(float *)p_matdata+5*num_keys-5; key>=0; key--,p_key-=5)
{
if (time >= *(int *)p_key)
{
break;
}
}
// parameter expressing how far we are between between this keyframe and the next
t = (float)(time - ((int *)p_key)[0]) / (float)(((int *)p_key)[5] - ((int *)p_key)[0]);
#if 0
// for debugging a TT bug...
for (int i=1; i<=9; i++)
Dbg_MsgAssert(p_key[i]>=0 && p_key[i]<=255, ("sTime=%d, num_keys=%d, key=%d, p_key[1]=%g, p_key[2]=%g, p_key[3]=%g, p_key[4]=%g, p_key[5]=%g, p_key[6]=%g, p_key[7]=%g, p_key[8]=%g, p_key[9]=%g, time=%d, start_time=%d, end_time=%d, period=%d, t=%g", \
render::sTime, \
num_keys, \
key, \
p_key[1], \
p_key[2], \
p_key[3], \
p_key[4], \
p_key[5], \
p_key[6], \
p_key[7], \
p_key[8], \
p_key[9], \
time, \
start_time, \
end_time, \
period, \
t));
#endif
// interpolate between colours
r = (1.0f-t) * p_key[1] + t * p_key[6];
g = (1.0f-t) * p_key[2] + t * p_key[7];
b = (1.0f-t) * p_key[3] + t * p_key[8];
a = (1.0f-t) * p_key[4] + t * p_key[9];
Dbg_MsgAssert(r>=0 && r<=255 && g>=0 && g<=255 && b>=0 && b<=255 && a>=0 && a<=255,
("wibbled colour r=%g, g=%g, b=%g, a=%g", r,g,b,a));
rgba = (uint32)(sint32)r & 0x000000FF |
(uint32)(sint32)g<<8 & 0x0000FF00 |
(uint32)(sint32)b<<16 & 0x00FF0000 |
(uint32)(sint32)a<<24;
// get # vertices wibbled by this sequence
num_verts = *p_meshdata++;
// loop over wibbled vertices, overwriting colour in dma data
for (vert=0; vert<num_verts; vert++)
{
p_rgba = *(uint32 **)p_meshdata;
p_meshdata++;
*p_rgba = rgba;
}
}
}
Mth::Vector CGeomNode::GetBoundingSphere()
{
Mth::Vector sphere;
CGeomNode *p_node = this;
// recurse till we find a sensible sphere
while (!(p_node->m_flags & NODEFLAG_LEAF) && p_node->m_bounding_sphere[3] >= 1.0e+10f)
{
p_node = p_node->u1.mp_child;
}
sphere = p_node->m_bounding_sphere;
// then account for any siblings
while (p_node->mp_sibling)
{
p_node = p_node->mp_sibling;
Mth::Vector x0, x1;
if (p_node->m_bounding_sphere[3] > sphere[3])
{
x0 = p_node->m_bounding_sphere;
x1 = sphere;
}
else
{
x0 = sphere;
x1 = p_node->m_bounding_sphere;
}
float r0=x0[3], r1=x1[3];
// (x0,r0) is the larger sphere, (x1,r1) the smaller
// d is the vector between centres
Mth::Vector d = x1 - x0;
// square of distance between centres
float D2 = DotProduct(d,d);
// (r0-r1)^2
float R2 = (r0-r1)*(r0-r1);
// m = max { (r1-r0+|d|)/2, 0 }
float m = 0.0f;
float D = sqrtf(D2);
if (R2-D2 < 0)
{
m = (r1-r0 + sqrtf(D2)) * 0.5f;
}
// normalise d
if (D>0)
d *= 1.0f/D;
sphere = x0 + m * d;
sphere[3] = r0 + m;
}
return sphere;
}
Mth::CBBox CGeomNode::GetBoundingBox()
{
CGeomNode *p_node = this;
while (!(p_node->m_flags & NODEFLAG_LEAF) && p_node->m_bounding_sphere[3] >= 1.0e+10f)
{
p_node = p_node->u1.mp_child;
}
Mth::Vector min_p(p_node->m_bounding_sphere);
Mth::Vector max_p(p_node->m_bounding_sphere);
//Dbg_Message("Bounding sphere (%.8f, %.8f, %.8f, %.8f)", p_node->m_bounding_sphere[X], p_node->m_bounding_sphere[Y], p_node->m_bounding_sphere[Z], p_node->m_bounding_sphere[W]);
//Dbg_Message("Bounding box (%.8f, %.8f, %.8f)", p_node->m_bounding_box[X], p_node->m_bounding_box[Y], p_node->m_bounding_box[Z]);
min_p[X] -= p_node->m_bounding_box[0];
min_p[Y] -= p_node->m_bounding_box[1];
min_p[Z] -= p_node->m_bounding_box[2];
min_p[W] = 1.0f;
max_p[X] += p_node->m_bounding_box[0];
max_p[Y] += p_node->m_bounding_box[1];
max_p[Z] += p_node->m_bounding_box[2];
min_p[W] = 1.0f;
return Mth::CBBox(min_p, max_p);
}
// interface to uv wibble control
void CGeomNode::SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
float v_vel, float v_amp, float v_freq, float v_phase)
{
Dbg_Assert(mp_uv_wibble);
mp_uv_wibble[0] = u_vel;
mp_uv_wibble[1] = v_vel;
mp_uv_wibble[2] = u_freq;
mp_uv_wibble[3] = v_freq;
mp_uv_wibble[4] = u_amp;
mp_uv_wibble[5] = v_amp;
mp_uv_wibble[6] = u_phase;
mp_uv_wibble[7] = v_phase;
}
void CGeomNode::UseExplicitUVWibble(bool yes)
{
SET_FLAG(NODEFLAG_EXPLICIT_UVWIBBLE);
}
void CGeomNode::SetUVWibbleOffsets(float u_offset, float v_offset)
{
Dbg_Assert(mp_uv_wibble);
mp_uv_wibble[8] = u_offset;
mp_uv_wibble[9] = v_offset;
}
void CGeomNode::SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset)
{
Dbg_MsgAssert(0, ("SetUVOffset not supported for CGeomNodes"));
}
void CGeomNode::SetUVMatrix(uint32 material_name, int pass, Mth::Matrix &mat)
{
Dbg_MsgAssert(0, ("SetUVOffset not supported for CGeomNodes"));
}
#endif
} // namespace NxPs2