#include "billboard.h" #include "nx_init.h" namespace NxXbox { sBillboardManager BillboardManager; /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardManager::sBillboardManager( void ) { m_num_batches = 0; } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardManager::~sBillboardManager( void ) { } /******************************************************************/ /* */ /* */ /******************************************************************/ static int sort_batches_by_draw_order( const void *p1, const void *p2 ) { sBillboardMaterialBatch *p_batch0 = *((sBillboardMaterialBatch**)p1 ); sBillboardMaterialBatch *p_batch1 = *((sBillboardMaterialBatch**)p2 ); // Deal with NULL pointers first (caused by removing batches from the list). if( p_batch0 == NULL ) { if( p_batch1 == NULL ) { return 0; } else { return 1; } } else if( p_batch1 == NULL ) { return -1; } return p_batch0->GetDrawOrder() < p_batch1->GetDrawOrder() ? -1 : p_batch0->GetDrawOrder() > p_batch1->GetDrawOrder() ? 1 : 0; } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardMaterialBatch *sBillboardManager::GetBatch( sMaterial *p_material ) { for( int b = 0; b < m_num_batches; ++b ) { if( mp_batches[b]->GetMaterial() == p_material ) { return mp_batches[b]; } } return NULL; } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardManager::AddEntry( sMesh *p_mesh ) { if( p_mesh->mp_material ) { // Make sure it is textured. if( p_mesh->mp_material->mp_tex[0] != NULL ) { sBillboardMaterialBatch *p_batch = GetBatch( p_mesh->mp_material ); if( p_batch == NULL ) { Dbg_Assert( m_num_batches < ( MAX_BILLBOARD_BATCHES - 1 )); // Need to create a new batch for this material. p_batch = new sBillboardMaterialBatch( p_mesh->mp_material ); mp_batches[m_num_batches++] = p_batch; // Resort the list of batches. SortBatches(); } // Now add the mesh to this batch. p_batch->AddEntry( p_mesh ); } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardManager::RemoveEntry( sMesh *p_mesh ) { if( p_mesh->mp_material ) { sBillboardMaterialBatch *p_batch = GetBatch( p_mesh->mp_material ); if( p_batch ) { // Removes the entry if it exists. p_batch->RemoveEntry( p_mesh ); // If the batch is now empty, remove it. if( p_batch->IsEmpty()) { for( int i = 0; i < m_num_batches; ++i ) { if( mp_batches[i] == p_batch ) { delete p_batch; mp_batches[i] = NULL; break; } } // Resort the batches (will move the NULL pointer to the end). SortBatches(); // Important not to decrement this until after the resort has been performed. --m_num_batches; } } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardManager::SortBatches( void ) { qsort( mp_batches, m_num_batches, sizeof( sBillboardMaterialBatch* ), sort_batches_by_draw_order ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardManager::SetCameraMatrix( void ) { Mth::Vector up( 0.0f, 1.0f, 0.0f ); m_at.Set( NxXbox::EngineGlobals.view_matrix.m[0][2], NxXbox::EngineGlobals.view_matrix.m[1][2], NxXbox::EngineGlobals.view_matrix.m[2][2] ); m_screen_right = Mth::CrossProduct( m_at, up ).Normalize(); m_screen_up = Mth::CrossProduct( m_screen_right, m_at ).Normalize(); m_at_xz = Mth::Vector( m_at[X], 0.0f, m_at[Z] ).Normalize(); m_screen_right_xz = Mth::Vector( m_screen_right[X], 0.0f, m_screen_right[Z] ).Normalize(); } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardManager::Render( uint32 flags ) { // Render the opaque billboards if requested. if( flags & vRENDER_OPAQUE ) { for( int b = 0; b < m_num_batches; ++b ) { if( !( mp_batches[b]->mp_material->m_flags[0] & 0x40 )) { mp_batches[b]->Render(); } } } // Render the semitransparent billboards if requested. if( flags & vRENDER_SEMITRANSPARENT ) { for( int b = 0; b < m_num_batches; ++b ) { if( mp_batches[b]->mp_material->m_flags[0] & 0x40 ) { mp_batches[b]->Render(); } } } } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardMaterialBatch::sBillboardMaterialBatch( sMaterial *p_material ) { mp_material = p_material; mp_entries[0] = new Lst::Head ; mp_entries[1] = new Lst::Head ; mp_entries[2] = new Lst::Head ; // We can calculate what the per-entry size will be based on the material properties. m_entry_size = 4 * sizeof( float ) * 3; // Four vertex positions. m_entry_size += 4 * sizeof( D3DCOLOR ); // Four vertex colors. m_entry_size += 4 * sizeof( float ) * 2 * p_material->m_passes; // Four uv pairs for each pass. } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardMaterialBatch::~sBillboardMaterialBatch( void ) { delete mp_entries[0]; delete mp_entries[1]; delete mp_entries[2]; } /******************************************************************/ /* */ /* */ /******************************************************************/ bool sBillboardMaterialBatch::IsEmpty( void ) { return ( mp_entries[0]->IsEmpty() && mp_entries[1]->IsEmpty() && mp_entries[2]->IsEmpty()); } /******************************************************************/ /* */ /* */ /******************************************************************/ float sBillboardMaterialBatch::GetDrawOrder( void ) { return mp_material ? mp_material->m_draw_order : 0.0f; } /******************************************************************/ /* */ /* */ /******************************************************************/ sMaterial *sBillboardMaterialBatch::GetMaterial( void ) { return mp_material; } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardMaterialBatch::AddEntry( sMesh *p_mesh ) { // Create a new billboard entry. sBillboardEntry *p_entry = new sBillboardEntry( p_mesh ); // And a new node. Lst::Node *node = new Lst::Node( p_entry ); mp_entries[p_entry->m_type]->AddToTail( node ); // Now process the mesh. ProcessMesh( p_mesh ); } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardMaterialBatch::ProcessMesh( sMesh *p_mesh ) { } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardMaterialBatch::RemoveEntry( sMesh *p_mesh ) { for( int e = 0; e < 3; ++e ) { Lst::Node *node, *next; node = mp_entries[e]->GetNext(); while( node ) { next = node->GetNext(); sBillboardEntry *p_entry = node->GetData(); if( p_entry->mp_mesh == p_mesh ) { // This is the entry. Delete the node and the entry. delete node; delete p_entry; return; } node = next; } } } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardMaterialBatch::Render( void ) { static float vector_upload[12] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; if( mp_material ) { mp_material->Submit(); // Set up correct vertex shader. NxXbox::set_vertex_shader( BillboardScreenAlignedVS ); // Lock out vertex shader changes. EngineGlobals.vertex_shader_override = BillboardScreenAlignedVS; // Set up correct pixel shader - this assumes that all meshes with the same material will have the same pixel shader. for( int e = 0; e < 3; ++e ) { if( mp_entries[e]->GetNext()) { sBillboardEntry *p_entry = mp_entries[e]->GetNext()->GetData(); set_pixel_shader( p_entry->mp_mesh->m_pixel_shader ); break; } } // Load up the combined world->view_projection matrix. XGMATRIX dest_matrix; XGMATRIX temp_matrix; XGMATRIX projMatrix; XGMATRIX viewMatrix; XGMATRIX worldMatrix; // Projection matrix. XGMatrixTranspose( &projMatrix, &EngineGlobals.projection_matrix ); // View matrix. XGMatrixTranspose( &viewMatrix, &EngineGlobals.view_matrix ); viewMatrix.m[3][0] = 0.0f; viewMatrix.m[3][1] = 0.0f; viewMatrix.m[3][2] = 0.0f; viewMatrix.m[3][3] = 1.0f; // World space transformation matrix. XGMatrixIdentity( &worldMatrix ); // Calculate composite world->view->projection matrix. XGMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix ); XGMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix ); // Load up the combined world, camera & projection matrix. D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 ); Lst::Node *node, *next; // First do the screen aligned billboards. vector_upload[0] = BillboardManager.m_screen_right[X]; vector_upload[1] = BillboardManager.m_screen_right[Y]; vector_upload[2] = BillboardManager.m_screen_right[Z]; vector_upload[4] = BillboardManager.m_screen_up[X]; vector_upload[5] = BillboardManager.m_screen_up[Y]; vector_upload[6] = BillboardManager.m_screen_up[Z]; vector_upload[8] = BillboardManager.m_at[X]; vector_upload[9] = BillboardManager.m_at[Y]; vector_upload[10] = BillboardManager.m_at[Z]; D3DDevice_SetVertexShaderConstantFast( 4, (void*)( &vector_upload[0] ), 3 ); for( node = mp_entries[0]->GetNext(); node; node = next ) { next = node->GetNext(); sBillboardEntry *p_entry = node->GetData(); // Only render if the mesh is active. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) { // Deal with material color override. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE ) p_entry->mp_mesh->HandleColorOverride(); else set_pixel_shader( p_entry->mp_mesh->m_pixel_shader ); // Deal with vertex color wibble if present. if( p_entry->mp_mesh->mp_vc_wibble_data ) { p_entry->mp_mesh->wibble_vc(); IDirect3DVertexBuffer8* p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer]; p_entry->mp_mesh->SwapVertexBuffers(); D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } else { D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } } } // Next do the the y axis aligned billboards. vector_upload[0] = BillboardManager.m_screen_right_xz[X]; vector_upload[1] = BillboardManager.m_screen_right_xz[Y]; vector_upload[2] = BillboardManager.m_screen_right_xz[Z]; vector_upload[4] = 0.0f; vector_upload[5] = 1.0f; vector_upload[6] = 0.0f; vector_upload[8] = BillboardManager.m_at_xz[X]; vector_upload[9] = BillboardManager.m_at_xz[Y]; vector_upload[10] = BillboardManager.m_at_xz[Z]; D3DDevice_SetVertexShaderConstantFast( 4, (void*)vector_upload, 3 ); for( node = mp_entries[1]->GetNext(); node; node = next ) { next = node->GetNext(); sBillboardEntry *p_entry = node->GetData(); // Only render if the mesh is active. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) { // Deal with material color override. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE ) p_entry->mp_mesh->HandleColorOverride(); else set_pixel_shader( p_entry->mp_mesh->m_pixel_shader ); // Deal with vertex color wibble if present. if( p_entry->mp_mesh->mp_vc_wibble_data ) { p_entry->mp_mesh->wibble_vc(); IDirect3DVertexBuffer8* p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer]; p_entry->mp_mesh->SwapVertexBuffers(); D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } else { D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } } } // Now do the arbitrary axis aligned billboards. for( node = mp_entries[2]->GetNext(); node; node = next ) { next = node->GetNext(); sBillboardEntry *p_entry = node->GetData(); sBillboardData *p_entry_data = p_entry->mp_mesh->mp_billboard_data; // Only render if the mesh is active. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) { // For this type we need to calculate the vector perpendicular to both the view vector and the axis of rotation. Mth::Vector axis_right = Mth::CrossProduct( BillboardManager.m_at, p_entry_data->m_pivot_axis ).Normalize(); Mth::Vector axis_at = Mth::CrossProduct( p_entry_data->m_pivot_axis, axis_right ).Normalize(); // Begin state block to set vertex shader constants for bone transforms. DWORD *p_push; EngineGlobals.p_Device->BeginState( 15, &p_push ); // 1 here isn't the parameter for SET_TRANSFORM_CONSTANT_LOAD; rather, it's the number of dwords written to that register. p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT_LOAD, 1 ); // Here is the actual parameter for SET_TRANSFORM_CONSTANT_LOAD. Always add 96 to the constant register. p_push[1] = 4 + 96; p_push[2] = D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT, 12 ); p_push[3] = *((DWORD*)&axis_right[X] ); p_push[4] = *((DWORD*)&axis_right[Y] ); p_push[5] = *((DWORD*)&axis_right[Z] ); p_push[7] = *((DWORD*)&p_entry_data->m_pivot_axis[X] ); p_push[8] = *((DWORD*)&p_entry_data->m_pivot_axis[Y] ); p_push[9] = *((DWORD*)&p_entry_data->m_pivot_axis[Z] ); p_push[11] = *((DWORD*)&axis_at[X] ); p_push[12] = *((DWORD*)&axis_at[Y] ); p_push[13] = *((DWORD*)&axis_at[Z] ); p_push += 15; EngineGlobals.p_Device->EndState( p_push ); // Deal with material color override. if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE ) p_entry->mp_mesh->HandleColorOverride(); else set_pixel_shader( p_entry->mp_mesh->m_pixel_shader ); // Deal with vertex color wibble if present. if( p_entry->mp_mesh->mp_vc_wibble_data ) { p_entry->mp_mesh->wibble_vc(); IDirect3DVertexBuffer8* p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer]; p_entry->mp_mesh->SwapVertexBuffers(); D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } else { D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride ); D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); } } } // Finally do the world aligned billboards. // for( node = mp_entries[3]->GetNext(); node; node = next ) // { // next = node->GetNext(); // sBillboardEntry *p_entry = node->GetData(); // Only render if the mesh is active. // if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) // { // For this type we need to calculate the vector perpendicular to both the view vector and the axis of rotation. // Mth::Vector at( p_entry->m_pivot_axis[X] - EngineGlobals.cam_position.x, // p_entry->m_pivot_axis[Y] - EngineGlobals.cam_position.y, // p_entry->m_pivot_axis[Z] - EngineGlobals.cam_position.z ); // at.Normalize(); // Mth::Vector axis_right = Mth::CrossProduct( at, BillboardManager.m_screen_up ).Normalize(); // Mth::Vector axis_at = Mth::CrossProduct( BillboardManager.m_screen_up, axis_right ).Normalize(); // vector_upload[0] = axis_right[X]; // vector_upload[1] = axis_right[Y]; // vector_upload[2] = axis_right[Z]; // vector_upload[4] = BillboardManager.m_screen_up[X]; // vector_upload[5] = BillboardManager.m_screen_up[Y]; // vector_upload[6] = BillboardManager.m_screen_up[Z]; // vector_upload[8] = axis_at[X]; // vector_upload[9] = axis_at[Y]; // vector_upload[10] = axis_at[Z]; // D3DDevice_SetVertexShaderConstant( 4, (void*)vector_upload, 3 ); // D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride ); // D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] ); // } // } // Undo vertex shader lock. EngineGlobals.vertex_shader_override = 0; } } /******************************************************************/ /* */ /* */ /******************************************************************/ void sBillboardMaterialBatch::Reset( void ) { Lst::Node *node, *next; for( int e = 0; e < 3; ++e ) { if( mp_entries[e] ) { for( node = mp_entries[e]->GetNext(); node; node = next ) { next = node->GetNext(); delete node; } } } mp_material = NULL; m_entry_size = 0; } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardEntry::sBillboardEntry( sMesh *p_mesh ) { Dbg_Assert( p_mesh->mp_billboard_data != NULL ); mp_mesh = p_mesh; m_type = p_mesh->mp_billboard_data->m_type; // For now set the pivot point as the center of the bounding sphere. // m_pivot_pos.Set( p_mesh->m_sphere_center.x, p_mesh->m_sphere_center.y, p_mesh->m_sphere_center.z ); // Frig the verts for now. // m_verts[0][0] = -12.0f; // m_verts[0][1] = -24.0f; // m_verts[0][2] = 0.0f; // m_verts[1][0] = -12.0f; // m_verts[1][1] = 24.0f; // m_verts[1][2] = 0.0f; // m_verts[2][0] = 12.0f; // m_verts[2][1] = 24.0f; // m_verts[2][2] = 0.0f; // m_verts[3][0] = 12.0f; // m_verts[3][1] = -24.0f; // m_verts[3][2] = 0.0f; // switch( rand() & 0x03 ) // { // case 0: // { // m_type = vBILLBOARD_TYPE_SCREEN_ALIGNED; // break; // } // case 1: // { // m_type = vBILLBOARD_TYPE_Y_AXIS_ALIGNED; // break; // } // case 2: // case 3: // { // m_type = vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED; // m_pivot_axis = Mth::Vector((float)( rand() - rand()), (float)( rand() - rand()), (float)( rand() - rand())).Normalize(); // break; // } // } } /******************************************************************/ /* */ /* */ /******************************************************************/ sBillboardEntry::~sBillboardEntry( void ) { } } // namespace NxXbox