Index: ps/trunk/source/ps/Profile.cpp =================================================================== --- ps/trunk/source/ps/Profile.cpp (revision 4804) +++ ps/trunk/source/ps/Profile.cpp (revision 4805) @@ -1,481 +1,505 @@ /** * ========================================================================= * File : Profile.cpp * Project : Pyrogeneses * Description : GPG3-style hierarchical profiler * * @author Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk) * @author Nicolai Haehnle * ========================================================================= */ #include "precompiled.h" #include "Profile.h" #include "ProfileViewer.h" - +#include "lib/timer.h" /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNodeTable /** * Class CProfileNodeTable: Implement ProfileViewer's AbstractProfileTable * interface in order to display profiling data in-game. */ class CProfileNodeTable : public AbstractProfileTable { public: CProfileNodeTable(CProfileNode* n); virtual ~CProfileNodeTable(); // Implementation of AbstractProfileTable interface virtual CStr GetName(); virtual CStr GetTitle(); virtual size_t GetNumberRows(); virtual const std::vector& GetColumns(); virtual CStr GetCellText(size_t row, size_t col); virtual AbstractProfileTable* GetChild(size_t row); virtual bool IsHighlightRow(size_t row); private: /** * struct ColumnDescription: The only purpose of this helper structure * is to provide the global constructor that sets up the column * description. */ struct ColumnDescription { std::vector columns; ColumnDescription() { columns.push_back(ProfileColumn("Name", 230)); columns.push_back(ProfileColumn("calls/frame", 100)); columns.push_back(ProfileColumn("msec/frame", 100)); columns.push_back(ProfileColumn("%/frame", 100)); columns.push_back(ProfileColumn("%/parent", 100)); + columns.push_back(ProfileColumn("mem allocs", 100)); } }; /// The node represented by this table CProfileNode* node; /// Columns description (shared by all instances) static ColumnDescription columnDescription; }; CProfileNodeTable::ColumnDescription CProfileNodeTable::columnDescription; // Constructor/Destructor CProfileNodeTable::CProfileNodeTable(CProfileNode* n) { node = n; } CProfileNodeTable::~CProfileNodeTable() { } // Short name (= name of profile node) CStr CProfileNodeTable::GetName() { return node->GetName(); } // Title (= explanatory text plus time totals) CStr CProfileNodeTable::GetTitle() { char buf[512]; snprintf(buf, sizeof(buf), "Profiling Information for: %s (Time in node: %.3f msec/frame)", node->GetName(), node->GetFrameTime() * 1000.0f ); buf[sizeof(buf)-1] = '\0'; return buf; } // Total number of children size_t CProfileNodeTable::GetNumberRows() { return node->GetChildren()->size() + node->GetScriptChildren()->size() + 1; } // Column description const std::vector& CProfileNodeTable::GetColumns() { return columnDescription.columns; } // Retrieve cell text CStr CProfileNodeTable::GetCellText(size_t row, size_t col) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); - char buf[256]; + char buf[256] = "?"; if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else if (row > nrchildren + nrscriptchildren) return "!bad row!"; else { // "unlogged" row if (col == 0) return "unlogged"; else if (col == 1) return ""; float unlogged = node->GetFrameTime(); + long unlogged_mallocs = node->GetFrameMallocs(); CProfileNode::const_profile_iterator it; - for(it = node->GetChildren()->begin(); it != node->GetChildren()->end(); ++it) + for (it = node->GetChildren()->begin(); it != node->GetChildren()->end(); ++it) + { unlogged -= (*it)->GetFrameTime(); - for(it = node->GetScriptChildren()->begin(); it != node->GetScriptChildren()->end(); ++it) + unlogged_mallocs -= (*it)->GetFrameMallocs(); + } + for (it = node->GetScriptChildren()->begin(); it != node->GetScriptChildren()->end(); ++it) + { unlogged -= (*it)->GetFrameTime(); + unlogged_mallocs -= (*it)->GetFrameMallocs(); + } if (col == 2) snprintf(buf, sizeof(buf), "%.3f", unlogged * 1000.0f); else if (col == 3) snprintf(buf, sizeof(buf), "%.1f", unlogged / g_Profiler.GetRoot()->GetFrameTime()); - else + else if (col == 4) snprintf(buf, sizeof(buf), "%.1f", unlogged * 100.0f / g_Profiler.GetRoot()->GetFrameTime()); + else if (col == 5) + snprintf(buf, sizeof(buf), "%d", unlogged_mallocs); buf[sizeof(buf)-1] = '\0'; return CStr(buf); } switch(col) { default: case 0: return child->GetName(); case 1: #ifdef PROFILE_AMORTIZE snprintf(buf, sizeof(buf), "%.3f", child->GetFrameCalls()); #else snprintf(buf, sizeof(buf), "%d", child->GetFrameCalls()); #endif break; case 2: snprintf(buf, sizeof(buf), "%.3f", child->GetFrameTime() * 1000.0f); break; case 3: snprintf(buf, sizeof(buf), "%.1f", child->GetFrameTime() * 100.0 / g_Profiler.GetRoot()->GetFrameTime()); break; case 4: snprintf(buf, sizeof(buf), "%.1f", child->GetFrameTime() * 100.0 / node->GetFrameTime()); break; + case 5: + snprintf(buf, sizeof(buf), "%d", child->GetFrameMallocs()); + break; } buf[sizeof(buf)-1] = '\0'; return CStr(buf); } // Return a pointer to the child table if the child node is expandable AbstractProfileTable* CProfileNodeTable::GetChild(size_t row) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else return 0; if (child->CanExpand()) return child->display_table; return 0; } // Highlight all script nodes bool CProfileNodeTable::IsHighlightRow(size_t row) { size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); return (row >= nrchildren && row < (nrchildren + nrscriptchildren)); } /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNode implementation // Note: As with the GPG profiler, name is assumed to be a pointer to a constant string; only pointer equality is checked. CProfileNode::CProfileNode( const char* _name, CProfileNode* _parent ) { name = _name; recursion = 0; - calls_total = 0; - calls_frame_current = 0; -#ifdef PROFILE_AMORTIZE - int i; - for( i = 0; i < PROFILE_AMORTIZE_FRAMES; i++ ) - { - calls_frame_buffer[i] = 0; - time_frame_buffer[i] = 0.0; - } - calls_frame_last = calls_frame_buffer; - calls_frame_amortized = 0.0f; -#else - calls_frame_last = 0; -#endif - time_total = 0.0; - time_frame_current = 0.0; -#ifdef PROFILE_AMORTIZE - time_frame_last = time_frame_buffer; - time_frame_amortized = 0.0; -#else - time_frame_last = 0.0; -#endif + + Reset(); + parent = _parent; display_table = new CProfileNodeTable(this); Reset(); } CProfileNode::~CProfileNode() { profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) delete( *it ); for( it = script_children.begin(); it != script_children.end(); it++ ) delete( *it ); delete display_table; } const CProfileNode* CProfileNode::GetChild( const char* childName ) const { const_profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) if( (*it)->name == childName ) return( *it ); return( NULL ); } const CProfileNode* CProfileNode::GetScriptChild( const char* childName ) const { const_profile_iterator it; for( it = script_children.begin(); it != script_children.end(); it++ ) if( (*it)->name == childName ) return( *it ); return( NULL ); } CProfileNode* CProfileNode::GetChild( const char* childName ) { profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); children.push_back( newNode ); return( newNode ); } CProfileNode* CProfileNode::GetScriptChild( const char* childName ) { profile_iterator it; for( it = script_children.begin(); it != script_children.end(); it++ ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); script_children.push_back( newNode ); return( newNode ); } bool CProfileNode::CanExpand() { return( !( children.empty() && script_children.empty() ) ); } void CProfileNode::Reset() { calls_total = 0; calls_frame_current = 0; #ifdef PROFILE_AMORTIZE int i; for( i = 0; i < PROFILE_AMORTIZE_FRAMES; i++ ) { calls_frame_buffer[i] = 0; time_frame_buffer[i] = 0.0; } calls_frame_last = calls_frame_buffer; calls_frame_amortized = 0.0f; #else calls_frame_last = 0; #endif + time_total = 0.0; time_frame_current = 0.0; #ifdef PROFILE_AMORTIZE time_frame_last = time_frame_buffer; time_frame_amortized = 0.0; #else time_frame_last = 0.0; #endif - + + mallocs_total = 0; + mallocs_frame_current = 0; + mallocs_frame_last = 0; + profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) (*it)->Reset(); for( it = script_children.begin(); it != script_children.end(); it++ ) (*it)->Reset(); } void CProfileNode::Frame() { calls_total += calls_frame_current; time_total += time_frame_current; + mallocs_total += mallocs_frame_current; #ifdef PROFILE_AMORTIZE calls_frame_amortized -= *calls_frame_last; *calls_frame_last = calls_frame_current; calls_frame_amortized += calls_frame_current; time_frame_amortized -= *time_frame_last; *time_frame_last = time_frame_current; time_frame_amortized += time_frame_current; if( ++calls_frame_last == ( calls_frame_buffer + PROFILE_AMORTIZE_FRAMES ) ) calls_frame_last = calls_frame_buffer; if( ++time_frame_last == ( time_frame_buffer + PROFILE_AMORTIZE_FRAMES ) ) time_frame_last = time_frame_buffer; #else calls_frame_last = calls_frame_current; time_frame_last = time_frame_current; #endif + mallocs_frame_last = mallocs_frame_current; calls_frame_current = 0; time_frame_current = 0.0; + mallocs_frame_current = 0; profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) (*it)->Frame(); for( it = script_children.begin(); it != script_children.end(); it++ ) (*it)->Frame(); } +static long get_memory_alloc_count() +{ +#if HAVE_VC_DEBUG_ALLOC + static long bias = 0; // so we can subtract the allocations caused by this function + void* p = malloc(1); + long requestNumber = 0; + int ok = _CrtIsMemoryBlock(p, 1, &requestNumber, NULL, NULL); + UNUSED2(ok); + free(p); + ++bias; + return requestNumber - bias; +#else + return 0; +#endif +} + void CProfileNode::Call() { calls_frame_current++; if( recursion++ == 0 ) + { start = get_time(); + start_mallocs = get_memory_alloc_count(); + } } bool CProfileNode::Return() { if( !parent ) return( false ); if( ( --recursion == 0 ) && ( calls_frame_current != 0 ) ) + { time_frame_current += ( get_time() - start ); + mallocs_frame_current += ( get_memory_alloc_count() - start_mallocs ); + } return( recursion == 0 ); } void CProfileNode::ScriptingInit() { AddProperty( L"name", (IJSObject::GetFn)&CProfileNode::JS_GetName ); /* AddReadOnlyClassProperty( L"callsTotal", &CProfileNode::calls_total ); AddReadOnlyClassProperty( L"callsPerFrame", &CProfileNode::calls_frame_last ); AddReadOnlyClassProperty( L"timeTotal", &CProfileNode::time_total ); AddReadOnlyClassProperty( L"timePerFrame", &CProfileNode::time_frame_last ); */ CJSObject::ScriptingInit( "ProfilerNode" ); } CProfileManager::CProfileManager() { root = new CProfileNode( "root", NULL ); current = root; frame_start = 0.0; g_ProfileViewer.AddRootTable(root->display_table); } CProfileManager::~CProfileManager() { std::map::iterator it; for( it = m_internedStrings.begin(); it != m_internedStrings.end(); it++ ) delete[]( it->second ); delete( root ); } void CProfileManager::Start( const char* name ) { if( name != current->GetName() ) current = current->GetChild( name ); current->Call(); } void CProfileManager::StartScript( const char* name ) { if( name != current->GetName() ) current = current->GetScriptChild( name ); current->Call(); } const char* CProfileManager::InternString( CStr8 intern ) { std::map::iterator it = m_internedStrings.find( intern ); if( it != m_internedStrings.end() ) return( it->second ); size_t length = intern.length(); char* data = new char[length + 1]; strcpy( data, intern.c_str() ); data[length] = 0; m_internedStrings.insert( std::pair( intern, data ) ); return( data ); } void CProfileManager::Stop() { if( current->Return() ) current = current->GetParent(); } void CProfileManager::Reset() { root->Reset(); start = get_time(); frame_start = get_time(); } void CProfileManager::Frame() { root->time_frame_current = ( get_time() - frame_start ); root->Frame(); frame_start = get_time(); } void CProfileManager::StructuralReset() { delete( root ); root = new CProfileNode( "root", NULL ); current = root; g_ProfileViewer.AddRootTable(root->display_table); } double CProfileManager::GetTime() { return( get_time() - start ); } double CProfileManager::GetFrameTime() { return( get_time() - frame_start ); } Index: ps/trunk/source/ps/Profile.h =================================================================== --- ps/trunk/source/ps/Profile.h (revision 4804) +++ ps/trunk/source/ps/Profile.h (revision 4805) @@ -1,161 +1,168 @@ /** * ========================================================================= * File : Profile.h * Project : Pyrogenesis * Description : GPG3-style hierarchical profiler * * @author Mark Thompson (mark@wildfiregames.com / mot20@cam.ac.uk) * ========================================================================= */ #ifndef PROFILE_H_INCLUDED #define PROFILE_H_INCLUDED #include #include "Singleton.h" #include "scripting/ScriptableObject.h" -#include "lib/timer.h" #define PROFILE_AMORTIZE #define PROFILE_AMORTIZE_FRAMES 50 class CProfileManager; class CProfileNodeTable; class CProfileNode : public CJSObject { friend class CProfileManager; friend class CProfileNodeTable; const char* name; + int calls_total; int calls_frame_current; #ifdef PROFILE_AMORTIZE int calls_frame_buffer[PROFILE_AMORTIZE_FRAMES]; int* calls_frame_last; float calls_frame_amortized; #else int calls_frame_last; #endif + double time_total; double time_frame_current; #ifdef PROFILE_AMORTIZE double time_frame_buffer[PROFILE_AMORTIZE_FRAMES]; double* time_frame_last; double time_frame_amortized; #else double time_frame_last; #endif + long mallocs_total; + long mallocs_frame_current; + long mallocs_frame_last; + double start; + long start_mallocs; int recursion; CProfileNode* parent; std::vector children; std::vector script_children; CProfileNodeTable* display_table; public: typedef std::vector::iterator profile_iterator; typedef std::vector::const_iterator const_profile_iterator; CProfileNode( const char* name, CProfileNode* parent ); ~CProfileNode(); const char* GetName() const { return( name ); } int GetCalls() const { return( calls_total ); } double GetTime() const { return( time_total ); } #ifdef PROFILE_AMORTIZE float GetFrameCalls() const { return( calls_frame_amortized / PROFILE_AMORTIZE_FRAMES ); } double GetFrameTime() const { return( time_frame_amortized / PROFILE_AMORTIZE_FRAMES ); } #else int GetFrameCalls() const { return( calls_frame_last ); } double GetFrameTime() const { return( time_frame_last ); } #endif + long GetFrameMallocs() const { return( mallocs_frame_last ); } const CProfileNode* GetChild( const char* name ) const; const CProfileNode* GetScriptChild( const char* name ) const; const std::vector* GetChildren() const { return( &children ); } const std::vector* GetScriptChildren() const { return( &script_children ); } bool CanExpand(); CProfileNode* GetChild( const char* name ); CProfileNode* GetScriptChild( const char* name ); CProfileNode* GetParent() const { return( parent ); } // Resets timing information for this node and all its children void Reset(); // Resets frame timings for this node and all its children void Frame(); // Enters the node void Call(); // Leaves the node. Returns true if the node has actually been left bool Return(); // Javascript stuff... static void ScriptingInit(); jsval JS_GetName(JSContext*) { return( ToJSVal( CStrW( name ) ) ); } }; class CProfileManager : public Singleton { CProfileNode* root; CProfileNode* current; double start; double frame_start; std::map m_internedStrings; public: CProfileManager(); ~CProfileManager(); // Begins timing for a named subsection void Start( const char* name ); void StartScript( const char* name ); // Ends timing for the current subsection void Stop(); // Resets all timing information void Reset(); // Resets frame timing information void Frame(); // Resets absolutely everything void StructuralReset(); const char* InternString( CStr8 intern ); inline const CProfileNode* GetCurrent() { return( current ); } inline const CProfileNode* GetRoot() { return( root ); } double GetTime(); double GetFrameTime(); }; #define g_Profiler CProfileManager::GetSingleton() class CProfileSample { static std::map evMap; public: CProfileSample( const char* name ) { g_Profiler.Start( name ); } ~CProfileSample() { g_Profiler.Stop(); } }; // Put a PROFILE( xyz ) block at the start of all code to be profiled. // Profile blocks last until the end of the containing scope. #define PROFILE( name ) CProfileSample __profile( name ) // Cheat a bit to make things slightly easier on the user #define PROFILE_START( name ) { CProfileSample __profile( name ) #define PROFILE_END( name ) } #endif // PROFILE_H_INCLUDED Index: ps/trunk/source/simulation/EntityManager.cpp =================================================================== --- ps/trunk/source/simulation/EntityManager.cpp (revision 4804) +++ ps/trunk/source/simulation/EntityManager.cpp (revision 4805) @@ -1,375 +1,376 @@ #include "precompiled.h" #include "EntityManager.h" #include "EntityTemplateCollection.h" #include "EntityTemplate.h" #include "ps/ConfigDB.h" #include "ps/Player.h" #include "ps/Profile.h" #include "graphics/Terrain.h" #include "ps/Game.h" #include "maths/MathUtil.h" #include "Entity.h" +#include "lib/timer.h" int AURA_CIRCLE_POINTS; int SELECTION_CIRCLE_POINTS; int SELECTION_BOX_POINTS; int SELECTION_SMOOTHNESS_UNIFIED = 9; CEntityManager::CEntityManager() : m_collisionPatches(0) , m_screenshotMode(false) , m_entities() // janwas: default-initialize entire array; // CHandle ctor sets m_entity and m_refcount to 0 { m_nextalloc = 0; m_extant = true; m_death = false; m_refd.resize( MAX_HANDLES ); // Also load a couple of global entity settings CConfigValue* cfg = g_ConfigDB.GetValue( CFG_USER, "selection.outline.quality" ); if( cfg ) cfg->GetInt( SELECTION_SMOOTHNESS_UNIFIED ); if( SELECTION_SMOOTHNESS_UNIFIED < 0 ) SELECTION_SMOOTHNESS_UNIFIED = 0; SELECTION_CIRCLE_POINTS = 7 + 2 * SELECTION_SMOOTHNESS_UNIFIED; SELECTION_BOX_POINTS = 1 + SELECTION_SMOOTHNESS_UNIFIED; AURA_CIRCLE_POINTS = 7 + 3 * SELECTION_SMOOTHNESS_UNIFIED; } void CEntityManager::deleteAllHelper() { for( int i = 0; i < MAX_HANDLES; i++ ) { if( m_entities[i].m_refcount ) { delete( m_entities[i].m_entity ); m_entities[i].m_entity = 0; m_entities[i].m_refcount = 0; m_refd[i] = false; } } } CEntityManager::~CEntityManager() { m_extant = false; deleteAllHelper(); // Delete entities that were killed, but not yet reaped by a call to updateAll, // to avoid memory leak warnings upon exiting std::vector::iterator it; for( it = m_reaper.begin(); it < m_reaper.end(); it++ ) delete( *it ); m_reaper.clear(); delete[] m_collisionPatches; m_collisionPatches = NULL; } void CEntityManager::deleteAll() { m_extant = false; deleteAllHelper(); m_nextalloc = 0; delete[] m_collisionPatches; m_collisionPatches = NULL; m_extant = true; } HEntity CEntityManager::create(CEntityTemplate* base, CVector3D position, float orientation, const std::set& actorSelections, const CStrW* building) { debug_assert( base ); if( !base ) return HEntity(); while( m_entities[m_nextalloc].m_refcount ) { m_nextalloc++; if(m_nextalloc >= MAX_HANDLES) { debug_warn("Ran out of entity handles!"); return HEntity(); } } m_entities[m_nextalloc].m_entity = new CEntity( base, position, orientation, actorSelections, building ); if( m_collisionPatches) m_entities[m_nextalloc].m_entity->updateCollisionPatch(); m_entities[m_nextalloc].m_entity->me = HEntity( m_nextalloc ); return( HEntity( m_nextalloc++ ) ); } void CEntityManager::AddEntityClassData(const HEntity& handle) { //Add data for this particular entity and player size_t playerID = handle->GetPlayer()->GetPlayerID(); CStrW className, classList = handle->m_classes.getMemberList(); while ( (className = classList.BeforeFirst(L" ")) != classList ) { if ( m_entityClassData[playerID].find(className) == m_entityClassData[playerID].end() ) m_entityClassData[playerID][className] = 0; ++m_entityClassData[playerID][className]; classList = classList.AfterFirst(L" "); } ++m_entityClassData[playerID][className]; } HEntity CEntityManager::create( const CStrW& templateName, CPlayer* player, CVector3D position, float orientation, const CStrW* building ) { CEntityTemplate* base = g_EntityTemplateCollection.getTemplate( templateName, player ); debug_assert( base ); if( !base ) return HEntity(); std::set selections; HEntity ret = create( base, position, orientation, selections, building ); AddEntityClassData(ret); return ret; } HEntity CEntityManager::createFoundation( const CStrW& templateName, CPlayer* player, CVector3D position, float orientation ) { CEntityTemplate* base = g_EntityTemplateCollection.getTemplate( templateName, player ); debug_assert( base ); if( !base ) return HEntity(); std::set selections; if( base->m_foundation == L"" ) return create( base, position, orientation, selections ); // Entity has no foundation, so just create it // Else, place the foundation object, telling it to convert into the right template when built. CEntityTemplate* foundation = g_EntityTemplateCollection.getTemplate( base->m_foundation ); return create( foundation, position, orientation, selections, &templateName ); } HEntity* CEntityManager::getByHandle( u16 index ) { if( index >= MAX_HANDLES ) return( NULL ); if( !m_entities[index].m_refcount ) return( NULL ); return( new HEntity( index ) ); } CHandle *CEntityManager::getHandle( int index ) { if (!m_entities[index].m_refcount ) return NULL; return &m_entities[index]; } std::vector* CEntityManager::matches( EntityPredicate predicate, void* userdata ) { std::vector* matchlist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) if( predicate( m_entities[i].m_entity, userdata ) ) matchlist->push_back( HEntity( i ) ); return( matchlist ); } std::vector* CEntityManager::getExtant() { std::vector* activelist = new std::vector; for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) activelist->push_back( HEntity( i ) ); return( activelist ); } void CEntityManager::GetExtant( std::vector& results ) { results.clear(); for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) && m_entities[i].m_entity->m_extant ) results.push_back( m_entities[i].m_entity ); } void CEntityManager::GetInRange( float x, float z, float radius, std::vector& results ) { results.clear(); float radiusSq = radius * radius; int cx = (int) ( x / COLLISION_PATCH_SIZE ); int cz = (int) ( z / COLLISION_PATCH_SIZE ); int r = (int) ( radius / COLLISION_PATCH_SIZE + 1 ); int minX = MAX(cx-r, 0); int minZ = MAX(cz-r, 0); int maxX = MIN(cx+r, m_collisionPatchesPerSide-1); int maxZ = MIN(cz+r, m_collisionPatchesPerSide-1); for( int px = minX; px <= maxX; px++ ) { for( int pz = minZ; pz <= maxZ; pz++ ) { std::vector& vec = m_collisionPatches[ px * m_collisionPatchesPerSide + pz ]; for( size_t i=0; im_position.X; float dz = z - e->m_position.Z; if( dx*dx + dz*dz <= radiusSq ) { results.push_back( e ); } } } } } void CEntityManager::GetInLOS( CEntity* entity, std::vector& results ) { GetInRange( entity->m_position.X, entity->m_position.Z, entity->m_los*CELL_SIZE, results ); } /* void CEntityManager::dispatchAll( CMessage* msg ) { for( int i = 0; i < MAX_HANDLES; i++ ) if( m_entities[i].m_refcount && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->dispatch( msg ); } */ TIMER_ADD_CLIENT(tc_1); TIMER_ADD_CLIENT(tc_2); void CEntityManager::InitializeAll() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); int unitsPerSide = CELL_SIZE * ( terrain->GetVerticesPerSide() - 1 ); m_collisionPatchesPerSide = unitsPerSide / COLLISION_PATCH_SIZE + 1; debug_assert(! m_collisionPatches); m_collisionPatches = new std::vector[m_collisionPatchesPerSide * m_collisionPatchesPerSide]; for( int i = 0; i < MAX_HANDLES; i++ ) { if( isEntityRefd(i) ) { // [2006-06-26 2780ms total] CEntity* e = m_entities[i].m_entity; e->Initialize(); // [2006-06-26 8ms total] e->updateCollisionPatch(); } } } void CEntityManager::TickAll() { for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) && m_entities[i].m_entity->m_extant ) m_entities[i].m_entity->Tick(); } void CEntityManager::updateAll( size_t timestep ) { PROFILE_START( "reaper" ); std::vector::iterator it; for( it = m_reaper.begin(); it < m_reaper.end(); it++ ) delete( *it ); m_reaper.clear(); PROFILE_END( "reaper" ); // PT: TickAll (which sends the 'Tick' event to all entities) has been // disabled, because: // * it's very slow (particularly when there are thousands of entities, e.g. trees); // * no entity currently responds to tick events; // * nobody can think of a situation where ticks would be required in the future; // * if they ever are needed, they can be done more efficiently (e.g. by // adding a per-entity 'wants tick' flag); // * it's very slow. /* PROFILE_START( "tick all" ); TickAll(); PROFILE_END( "tick all" ); */ PROFILE_START( "update all" ); for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) m_entities[i].m_entity->update( timestep ); PROFILE_END( "update all" ); } void CEntityManager::interpolateAll( float relativeoffset ) { for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) m_entities[i].m_entity->interpolate( relativeoffset ); } void CEntityManager::renderAll() { for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) m_entities[i].m_entity->render(); } void CEntityManager::conformAll() { PROFILE_START("conform all"); for ( int i=0; i < MAX_HANDLES; i++ ) { if( isEntityRefd(i) ) { m_entities[i].m_entity->updateXZOrientation(); m_entities[i].m_entity->updateActorTransforms(); } } PROFILE_END("conform all"); } void CEntityManager::invalidateAll() { for( int i = 0; i < MAX_HANDLES; i++ ) if( isEntityRefd(i) ) m_entities[i].m_entity->invalidateActor(); } void CEntityManager::destroy( u16 handle ) { m_reaper.push_back( m_entities[handle].m_entity ); //Remove trigger-helper data size_t playerID = (size_t)m_entities[m_nextalloc].m_entity->GetPlayer()->GetPlayerID(); CStrW className, classList = m_entities[m_nextalloc].m_entity->m_classes.getMemberList(); while ( (className = classList.BeforeFirst(L" ")) != classList ) { --m_entityClassData[playerID][className]; classList = classList.AfterFirst(L" "); } --m_entityClassData[playerID][className]; m_entities[handle].m_entity->me.m_handle = INVALID_HANDLE; } bool CEntityManager::m_extant = false; std::vector* CEntityManager::getCollisionPatch( CEntity* e ) { if( !e->m_extant ) { return 0; } int ix = (int) ( e->m_position.X / COLLISION_PATCH_SIZE ); int iz = (int) ( e->m_position.Z / COLLISION_PATCH_SIZE ); return &m_collisionPatches[ ix * m_collisionPatchesPerSide + iz ]; } Index: ps/trunk/source/simulation/Aura.cpp =================================================================== --- ps/trunk/source/simulation/Aura.cpp (revision 4804) +++ ps/trunk/source/simulation/Aura.cpp (revision 4805) @@ -1,167 +1,171 @@ #include "precompiled.h" #include "Aura.h" #include "EntityManager.h" #include "Entity.h" #include +namespace +{ + // Avoid creating strings at runtime +#define ACTION(n) \ + const char n##Name[] = "on" #n; \ + utf16string n##Name16(n##Name, n##Name + ARRAY_SIZE(n##Name)-1) + ACTION(Enter); + ACTION(Exit); + ACTION(Tick); +} + CAura::CAura( JSContext* cx, CEntity* source, CStrW& name, float radius, size_t tickRate, const CVector4D& color, JSObject* handler ) : m_cx(cx), m_source(source), m_name(name), m_radius(radius), m_handler(handler), m_tickRate(tickRate), m_tickCyclePos(0), m_color(color) { JS_AddRoot( m_cx, &m_handler ); // don't GC it so we can call it later } CAura::~CAura() { JS_RemoveRoot( m_cx, &m_handler ); } void CAura::Update( size_t timestep ) { std::vector inRange; CVector3D pos = m_source->m_position; g_EntityManager.GetInRange( pos.X, pos.Z, m_radius, inRange ); std::vector prevInfluenced, curInfluenced, entered, exited; + prevInfluenced.reserve(m_influenced.size()); + curInfluenced.reserve(m_influenced.size()); + for( std::vector::iterator it = m_influenced.begin(); it != m_influenced.end(); it++ ) { CEntity* ent = *it; if( ent->m_extant ) { prevInfluenced.push_back(ent); } } m_influenced.clear(); for( std::vector::iterator it = inRange.begin(); it != inRange.end(); it++ ) { CEntity* ent = *it; if(ent != m_source) { curInfluenced.push_back(ent); m_influenced.push_back( HEntity(ent->me) ); } } sort( prevInfluenced.begin(), prevInfluenced.end() ); sort( curInfluenced.begin(), curInfluenced.end() ); jsval rval; jsval argv[1]; // Call onEnter on any new unit that has entered the aura - CStrW enterName = L"onEnter"; jsval enterFunction; - utf16string enterName16 = enterName.utf16(); - if( JS_GetUCProperty( m_cx, m_handler, enterName16.c_str(), enterName16.length(), &enterFunction ) + if( JS_GetUCProperty( m_cx, m_handler, EnterName16.c_str(), EnterName16.length(), &enterFunction ) && enterFunction != JSVAL_VOID) { std::back_insert_iterator > ins( entered ); set_difference( curInfluenced.begin(), curInfluenced.end(), prevInfluenced.begin(), prevInfluenced.end(), ins ); for( std::vector::iterator it = entered.begin(); it != entered.end(); it++ ) { argv[0] = OBJECT_TO_JSVAL( (*it)->GetScript() ); JS_CallFunctionValue( m_cx, m_handler, enterFunction, 1, argv, &rval ); (*it)->m_aurasInfluencingMe.insert( this ); } } // Call onExit on any unit that has exited the aura - CStrW exitName = L"onExit"; jsval exitFunction; - utf16string exitName16 = exitName.utf16(); - if( JS_GetUCProperty( m_cx, m_handler, exitName16.c_str(), exitName16.length(), &exitFunction ) + if( JS_GetUCProperty( m_cx, m_handler, ExitName16.c_str(), ExitName16.length(), &exitFunction ) && exitFunction != JSVAL_VOID ) { std::back_insert_iterator > ins( exited ); set_difference( prevInfluenced.begin(), prevInfluenced.end(), curInfluenced.begin(), curInfluenced.end(), ins ); for( std::vector::iterator it = exited.begin(); it != exited.end(); it++ ) { argv[0] = OBJECT_TO_JSVAL( (*it)->GetScript() ); JS_CallFunctionValue( m_cx, m_handler, exitFunction, 1, argv, &rval ); (*it)->m_aurasInfluencingMe.erase( this ); } } m_tickCyclePos += timestep; if( m_tickRate > 0 && m_tickCyclePos > m_tickRate ) { // It's time to tick; call OnTick on any unit that is in the aura - CStrW tickName = L"onTick"; jsval tickFunction; - utf16string tickName16 = tickName.utf16(); - if( JS_GetUCProperty( m_cx, m_handler, tickName16.c_str(), tickName16.length(), &tickFunction ) + if( JS_GetUCProperty( m_cx, m_handler, TickName16.c_str(), TickName16.length(), &tickFunction ) && tickFunction != JSVAL_VOID ) { for( std::vector::iterator it = curInfluenced.begin(); it != curInfluenced.end(); it++ ) { argv[0] = OBJECT_TO_JSVAL( (*it)->GetScript() ); JS_CallFunctionValue( m_cx, m_handler, tickFunction, 1, argv, &rval ); } } // Reset cycle pos m_tickCyclePos %= m_tickRate; } } void CAura::RemoveAll() { jsval rval; jsval argv[1]; - CStrW exitName = L"onExit"; jsval exitFunction; - utf16string exitName16 = exitName.utf16(); - if( JS_GetUCProperty( m_cx, m_handler, exitName16.c_str(), exitName16.length(), &exitFunction ) + if( JS_GetUCProperty( m_cx, m_handler, ExitName16.c_str(), ExitName16.length(), &exitFunction ) && exitFunction != JSVAL_VOID ) { // Call the exit function on everything in our influence for( std::vector::iterator it = m_influenced.begin(); it != m_influenced.end(); it++ ) { CEntity* ent = *it; if( ent->m_extant ) { argv[0] = OBJECT_TO_JSVAL( ent->GetScript() ); JS_CallFunctionValue( m_cx, m_handler, exitFunction, 1, argv, &rval ); (*it)->m_aurasInfluencingMe.erase( this ); } } } m_influenced.clear(); } void CAura::Remove( CEntity* ent ) { jsval rval; jsval argv[1]; - CStrW exitName = L"onExit"; jsval exitFunction; - utf16string exitName16 = exitName.utf16(); - if( JS_GetUCProperty( m_cx, m_handler, exitName16.c_str(), exitName16.length(), &exitFunction ) + if( JS_GetUCProperty( m_cx, m_handler, ExitName16.c_str(), ExitName16.length(), &exitFunction ) && exitFunction != JSVAL_VOID ) { // Call the exit function on it argv[0] = OBJECT_TO_JSVAL( ent->GetScript() ); JS_CallFunctionValue( m_cx, m_handler, exitFunction, 1, argv, &rval ); // Remove it from the m_influenced array for( size_t i=0; i < m_influenced.size(); i++ ) { if( ((CEntity*) m_influenced[i]) == ent ) { m_influenced.erase( m_influenced.begin() + i ); break; } } } } Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 4804) +++ ps/trunk/source/gui/MiniMap.cpp (revision 4805) @@ -1,594 +1,595 @@ #include "precompiled.h" #include #include "MiniMap.h" #include "graphics/GameView.h" #include "graphics/MiniPatch.h" #include "graphics/Terrain.h" #include "graphics/TextureEntry.h" #include "graphics/TextureManager.h" #include "graphics/Unit.h" #include "graphics/UnitManager.h" #include "lib/ogl.h" #include "lib/sdl.h" +#include "lib/timer.h" #include "network/NetMessage.h" #include "ps/Game.h" #include "ps/Interact.h" #include "ps/Player.h" #include "ps/Profile.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" #include "scripting/GameEvents.h" #include "simulation/Entity.h" #include "simulation/EntityTemplate.h" #include "simulation/LOSManager.h" #include "simulation/TerritoryManager.h" bool g_TerrainModified = false; bool g_GameRestarted = false; // used by GetMapSpaceCoords (precalculated as an optimization). // this was formerly access via inline asm, which required it to be // static data instead of a class member. that is no longer the case, // but we leave it because this is slightly more efficient. static float m_scaleX, m_scaleY; static unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = uint(float(color & 0xff) * x); unsigned int g = uint(float((color>>8) & 0xff) * x); unsigned int b = uint(float((color>>16) & 0xff) * x); return (0xff000000 | r | g<<8 | b<<16); } CMiniMap::CMiniMap() : m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_LOSTexture(0), m_LOSData(0), m_UnitManager(0) { AddSetting(GUIST_CColor, "fov_wedge_color"); AddSetting(GUIST_CStr, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); m_Clicking = false; } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::HandleMessage(const SGUIMessage &Message) { switch(Message.type) { case GUIM_MOUSE_PRESS_LEFT: { SetCameraPos(); m_Clicking = true; break; } case GUIM_MOUSE_RELEASE_LEFT: { if(m_Clicking) SetCameraPos(); m_Clicking = false; break; } case GUIM_MOUSE_ENTER: { g_Selection.m_mouseOverMM = true; break; } case GUIM_MOUSE_LEAVE: { g_Selection.m_mouseOverMM = false; break; } case GUIM_MOUSE_RELEASE_RIGHT: { CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; } case GUIM_MOUSE_DBLCLICK_RIGHT: { CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; } case GUIM_MOUSE_MOTION: { if (m_Clicking) SetCameraPos(); break; } default: break; } // switch } void CMiniMap::SetCameraPos() { CTerrain *MMTerrain=g_Game->GetWorld()->GetTerrain(); CVector3D CamOrient=m_Camera->m_Orientation.GetTranslation(); //get center point of screen int x = g_Renderer.GetWidth()/2; int y = g_Renderer.GetHeight()/2; CVector3D ScreenMiddle=m_Camera->GetWorldCoordinates(x,y); //Get Vector required to go from camera position to ScreenMiddle CVector3D TransVector; TransVector.X=CamOrient.X-ScreenMiddle.X; TransVector.Z=CamOrient.Z-ScreenMiddle.Z; //world position of where mouse clicked CVector3D Destination; CPos MousePos = GetMousePos(); //X and Z according to proportion of mouse position and minimap Destination.X = CELL_SIZE * m_MapSize * ( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() ); Destination.Z = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) / m_CachedActualSize.GetHeight() ); m_Camera->m_Orientation._14=Destination.X; m_Camera->m_Orientation._34=Destination.Z; m_Camera->m_Orientation._14+=TransVector.X; m_Camera->m_Orientation._34+=TransVector.Z; //Lock Y coord. No risk of zoom exceeding limit-Y does not increase float Height=MMTerrain->getExactGroundLevel( m_Camera->m_Orientation._14, m_Camera->m_Orientation._34) + g_YMinOffset; if (m_Camera->m_Orientation._24 < Height) { m_Camera->m_Orientation._24=Height; } m_Camera->UpdateFrustum(); } void CMiniMap::FireWorldClickEvent(uint button, int clicks) { //debug_printf("FireWorldClickEvent: button %d, clicks %d\n", button, clicks); CPos MousePos = GetMousePos(); CVector2D Destination; //X and Z according to proportion of mouse position and minimap Destination.x = CELL_SIZE * m_MapSize * ( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() ); Destination.y = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) / m_CachedActualSize.GetHeight() ); g_JSGameEvents.FireWorldClick( button, clicks, NMT_Goto, -1, NMT_Run, -1, NULL, (int)Destination.x, (int)Destination.y); } // render view rect : John M. Mena // This sets up and draws the rectangle on the mini-map // which represents the view of the camera in the world. void CMiniMap::DrawViewRect() { // Get correct world coordinates based off corner of screen start // at Bottom Left and going CW CVector3D hitPt[4]; hitPt[0]=m_Camera->GetWorldCoordinates(0,g_Renderer.GetHeight()); hitPt[1]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),g_Renderer.GetHeight()); hitPt[2]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),0); hitPt[3]=m_Camera->GetWorldCoordinates(0,0); float ViewRect[4][2]; for (int i=0;i<4;i++) { // convert to minimap space float px=hitPt[i].X; float pz=hitPt[i].Z; ViewRect[i][0]=(m_CachedActualSize.GetWidth()*px/float(CELL_SIZE*m_MapSize)); ViewRect[i][1]=(m_CachedActualSize.GetHeight()*pz/float(CELL_SIZE*m_MapSize)); } // Enable Scissoring as to restrict the rectangle // to only the mini-map below by retrieving the mini-maps // screen coords. glScissor((int)m_CachedActualSize.left, 0, (int)m_CachedActualSize.right, (int)m_CachedActualSize.GetHeight()); glEnable(GL_SCISSOR_TEST); glEnable(GL_LINE_SMOOTH); glLineWidth(2.0f); glColor3f(1.0f, 0.3f, 0.3f); // Draw the viewing rectangle with the ScEd's conversion algorithm const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; glBegin(GL_LINE_LOOP); glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]); glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]); glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]); glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]); glEnd(); // restore state glDisable(GL_SCISSOR_TEST); glDisable(GL_LINE_SMOOTH); glLineWidth(1.0f); } struct MinimapUnitVertex { u8 r, g, b, a; float x, y; }; void CMiniMap::Draw() { PROFILE("minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if(!(GetGUI() && g_Game && g_Game->IsGameStarted())) return; glDisable(GL_DEPTH_TEST); // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_UnitManager = &g_Game->GetWorld()->GetUnitManager(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = round_up_to_pow2(m_MapSize); m_scaleX = float(m_Width) / float(m_MapSize - 1); m_scaleY = float(m_Height) / float(m_MapSize - 1); if(!m_TerrainTexture || g_GameRestarted) CreateTextures(); // do not limit this as with LOS updates below - we must update // immediately after changes are reported because this flag will be // reset at the end of the frame. if(g_TerrainModified) RebuildTerrainTexture(); // only update 10x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow, ~20ms) static double last_time; const double cur_time = get_time(); if(cur_time - last_time > 100e-3) // 10 updates/sec { last_time = cur_time; CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); if(losMgr->m_LOSSetting != CLOSManager::ALL_VISIBLE) RebuildLOSTexture(); } const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize); const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); // Draw the main textured quad g_Renderer.BindTexture(0, m_TerrainTexture); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z); glTexCoord2f(texCoordMax, 0.0f); glVertex3f(x2, y, z); glTexCoord2f(texCoordMax, texCoordMax); glVertex3f(x2, y2, z); glTexCoord2f(0.0f, texCoordMax); glVertex3f(x, y2, z); glEnd(); // Shade territories by player CTerritoryManager* territoryMgr = g_Game->GetWorld()->GetTerritoryManager(); std::vector& territories = territoryMgr->GetTerritories(); PROFILE_START("minimap territory shade"); glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for( size_t i=0; iowner->GetPlayerID() == 0 ) continue; std::vector& boundary = territories[i]->boundary; SPlayerColour col = territories[i]->owner->GetColour(); glColor4f(col.r, col.g, col.b, 0.25f); glBegin(GL_POLYGON); for( size_t j=0; jGetTilesPerSide() * CELL_SIZE); float fy = boundary[j].y / (m_Terrain->GetTilesPerSide() * CELL_SIZE); glVertex3f( x*(1-fx) + x2*fx, y*(1-fy) + y2*fy, z ); } glEnd(); } glDisable(GL_BLEND); PROFILE_END("minimap territory shade"); // Draw territory boundaries glEnable(GL_LINE_SMOOTH); glLineWidth(1.0f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0.8f, 0.8f, 0.8f, 0.8f); for( size_t i=0; i& boundary = territories[i]->boundary; glBegin(GL_LINE_LOOP); for( size_t j=0; jGetTilesPerSide() * CELL_SIZE); float fy = boundary[j].y / (m_Terrain->GetTilesPerSide() * CELL_SIZE); glVertex3f( x*(1-fx) + x2*fx, y*(1-fy) + y2*fy, z ); } glEnd(); } glLineWidth(1.0f); glDisable(GL_LINE_SMOOTH); glDisable(GL_BLEND); // Draw the LOS quad in black, using alpha values from the LOS texture g_Renderer.BindTexture(0, m_LOSTexture); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_QUADS); glColor3f(0.0f, 0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z); glTexCoord2f(texCoordMax, 0.0f); glVertex3f(x2, y, z); glTexCoord2f(texCoordMax, texCoordMax); glVertex3f(x2, y2, z); glTexCoord2f(0.0f, texCoordMax); glVertex3f(x, y2, z); glEnd(); glDisable(GL_BLEND); PROFILE_START("minimap units"); // Draw unit points const std::vector &units = m_UnitManager->GetUnits(); std::vector::const_iterator iter = units.begin(); CUnit *unit = 0; CVector2D pos; CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); std::vector vertexArray; // TODO: don't reallocate this after every frame (but don't waste memory // after the number of units decreases substantially) // Don't enable GL_POINT_SMOOTH because it's far too slow // (~70msec/frame on a GF4 rendering a thousand points) glPointSize(3.f); for(; iter != units.end(); ++iter) { unit = (CUnit *)(*iter); if(unit && unit->GetEntity() && losMgr->GetUnitStatus(unit, g_Game->GetLocalPlayer()) != UNIT_HIDDEN) { CEntity* entity = unit->GetEntity(); CStrW& type = entity->m_base->m_minimapType; MinimapUnitVertex v; if(type==L"Unit" || type==L"Structure" || type==L"Hero") { // Use the player colour const SPlayerColour& colour = entity->GetPlayer()->GetColour(); v.r = i32_from_float(colour.r*255.f); v.g = i32_from_float(colour.g*255.f); v.b = i32_from_float(colour.b*255.f); v.a = 255; } else { CEntityTemplate* base = entity->m_base; v.r = base->m_minimapR; v.g = base->m_minimapG; v.b = base->m_minimapB; v.a = 255; } pos = GetMapSpaceCoords(entity->m_position); v.x = x + pos.x; v.y = y - pos.y; vertexArray.push_back(v); } } if (vertexArray.size()) { glPushMatrix(); glTranslatef(0, 0, z); // Unbind any vertex buffer object, if our card supports VBO's if (g_Renderer.GetCapabilities().m_VBO) { pglBindBufferARB(GL_ARRAY_BUFFER_ARB, 0); } glInterleavedArrays(GL_C4UB_V2F, sizeof(MinimapUnitVertex), &vertexArray[0]); glDrawArrays(GL_POINTS, 0, (GLsizei)vertexArray.size()); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glPopMatrix(); } PROFILE_END("minimap units"); DrawViewRect(); // Reset everything back to normal glPointSize(1.0f); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); } void CMiniMap::CreateTextures() { Destroy(); // Create terrain texture glGenTextures(1, (GLuint *)&m_TerrainTexture); g_Renderer.BindTexture(0, m_TerrainTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0); m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); // Create LOS texture glGenTextures(1, (GLuint *)&m_LOSTexture); g_Renderer.BindTexture(0, m_LOSTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); // Rebuild and upload both of them RebuildTerrainTexture(); RebuildLOSTexture(); } void CMiniMap::RebuildTerrainTexture() { u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; for(u32 j = 0; j < h; j++) { u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; for(u32 i = 0; i < w; i++) { float avgHeight = ( m_Terrain->getVertexGroundLevel((int)i, (int)j) + m_Terrain->getVertexGroundLevel((int)i+1, (int)j) + m_Terrain->getVertexGroundLevel((int)i, (int)j+1) + m_Terrain->getVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if(avgHeight < waterHeight) { *dataPtr++ = 0xff304080; // TODO: perhaps use the renderer's water color? } else { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0; CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j); if(mp) { CTextureEntry *tex = mp->Tex1 ? g_TexMan.FindTexture(mp->Tex1) : 0; color = tex ? tex->GetBaseColor() : 0xFFFFFFFF; } else { color = 0xFFFFFFFF; } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_TerrainData); } void CMiniMap::RebuildLOSTexture() { PROFILE_START("rebuild minimap: los"); CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); CPlayer* player = g_Game->GetLocalPlayer(); u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; for(u32 j = 0; j < h; j++) { u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x; for(u32 i = 0; i < w; i++) { ELOSStatus status = losMgr->GetStatus((int) i, (int) j, player); if(status == LOS_UNEXPLORED) { *dataPtr++ = 0xff; } else if(status == LOS_EXPLORED) { *dataPtr++ = (u8) (0xff * 0.3f); } else { *dataPtr++ = 0; } } } // Upload the texture g_Renderer.BindTexture(0, m_LOSTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData); PROFILE_END("rebuild minimap: los"); } void CMiniMap::Destroy() { if(m_TerrainTexture) glDeleteTextures(1, (GLuint *)&m_TerrainTexture); if(m_LOSTexture) glDeleteTextures(1, (GLuint *)&m_LOSTexture); delete[] m_TerrainData; m_TerrainData = 0; delete[] m_LOSData; m_LOSData = 0; } CVector2D CMiniMap::GetMapSpaceCoords(CVector3D worldPos) { float x = rintf(worldPos.X / CELL_SIZE); float y = rintf(worldPos.Z / CELL_SIZE); // Entity's Z coordinate is really its longitudinal coordinate on the terrain // Calculate map space scale return CVector2D(x * m_scaleX, y * m_scaleY); }