Index: ps/trunk/source/ps/Game.h =================================================================== --- ps/trunk/source/ps/Game.h (revision 911) +++ ps/trunk/source/ps/Game.h (revision 912) @@ -1,68 +1,65 @@ #ifndef _ps_Game_H #define _ps_Game_H // Kludge: Our included headers might want to subgroup the Game group, so do it // here, before including the other guys #include "Errors.h" ERROR_GROUP(Game); #include "World.h" #include "Simulation.h" #include "Player.h" #include "GameView.h" +#include "scripting/ScriptingHost.h" -#define PS_MAX_PLAYERS 6 +#include class CGameAttributes { public: inline CGameAttributes(): - m_MapFile(NULL) + m_MapFile() {} + JSBool FillFromJS(JSContext *cx, JSObject *obj); + // The VFS path of the mapfile to load or NULL for no map (and to use // default terrain) - const char *m_MapFile; + CStr m_MapFile; }; class CGame { CWorld m_World; CSimulation m_Simulation; CGameView m_GameView; - CPlayer *m_Players[PS_MAX_PLAYERS]; + std::vector m_Players; CPlayer *m_pLocalPlayer; public: - inline CGame(): - m_World(this), - m_Simulation(this), - m_GameView(this) - { - // TODO When are players created? - // TODO Probably should at least reset in here though - } + CGame(); + ~CGame(); /* Initialize all local state and members for playing a game described by the attribute class. Return: 0 on OK - a PSRETURN code otherwise */ PSRETURN Initialize(CGameAttributes *pGameAttributes); /* Perform all per-frame updates */ void Update(double deltaTime); inline CWorld *GetWorld() { return &m_World; } inline CGameView *GetView() { return &m_GameView; } }; extern CGame *g_Game; #endif Index: ps/trunk/source/ps/scripting/JSInterface_Selection.cpp =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Selection.cpp (revision 911) +++ ps/trunk/source/ps/scripting/JSInterface_Selection.cpp (revision 912) @@ -1,315 +1,317 @@ // JavaScript interface to native code selection and group objects // Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com) #include "precompiled.h" #include "JSInterface_Selection.h" #include "Interact.h" JSClass JSI_Selection::JSI_class = { "EntityCollection", JSCLASS_HAS_PRIVATE, JSI_Selection::addProperty, JSI_Selection::removeProperty, JSI_Selection::getProperty, JSI_Selection::setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JSI_Selection::finalize, NULL, NULL, NULL, NULL }; JSPropertySpec JSI_Selection::JSI_props[] = { { 0 } }; JSFunctionSpec JSI_Selection::JSI_methods[] = { { "toString", JSI_Selection::toString, 0, 0, 0 }, { 0 }, }; JSBool JSI_Selection::addProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { if( !JSVAL_IS_INT( id ) ) return( JS_TRUE ); int i = JSVAL_TO_INT( id ); std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); // Invalid index and/or value is not an object. if( !set || !JSVAL_IS_OBJECT( *vp ) || ( i < 0 ) ) return( JS_TRUE ); HEntity* e = (HEntity*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( *vp ), &JSI_Entity::JSI_class, NULL ); // Value is not an entity. if( !e ) return( JS_TRUE ); if( i >= (int)set->capacity() ) set->resize( i + 1 ); // Add to set. set->insert( set->begin() + id, *e ); return( JS_TRUE ); } JSBool JSI_Selection::removeProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { if( !JSVAL_IS_INT( id ) ) { *vp = JSVAL_TRUE; return( JS_TRUE ); } int i = JSVAL_TO_INT( id ); std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); // Invalid index and/or value is not an object. if( !set || !JSVAL_IS_OBJECT( *vp ) || ( i < 0 ) || ( i >= (int)set->size() ) ) { *vp = JSVAL_TRUE; return( JS_TRUE ); } // Remove from set. set->erase( set->begin() + id ); *vp = JSVAL_TRUE; return( JS_TRUE ); } JSBool JSI_Selection::getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { if( !JSVAL_IS_INT( id ) ) return( JS_TRUE ); int i = JSVAL_TO_INT( id ); std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); // Invalid index and/or value is not an object. if( !set || ( i < 0 ) || ( i >= (int)set->size() ) ) { *vp = JSVAL_NULL; return( JS_TRUE ); } // Retrieve from set. JSObject* e = JS_NewObject( cx, &JSI_Entity::JSI_class, NULL, NULL ); JS_SetPrivate( cx, e, new HEntity( set->at( i ) ) ); *vp = OBJECT_TO_JSVAL( e ); return( JS_TRUE ); } JSBool JSI_Selection::setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { if( !JSVAL_IS_INT( id ) ) return( JS_TRUE ); int i = JSVAL_TO_INT( id ); std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); // Invalid index and/or value is not an object. if( !set || !JSVAL_IS_OBJECT( *vp ) || ( i < 0 ) || ( i >= (int)set->size() ) ) return( JS_TRUE ); HEntity* e = (HEntity*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( *vp ), &JSI_Entity::JSI_class, NULL ); // Value is not an entity. if( !e ) { JSObject* c = JS_NewObject( cx, &JSI_Entity::JSI_class, NULL, NULL ); JS_SetPrivate( cx, c, new HEntity( set->at( i ) ) ); *vp = OBJECT_TO_JSVAL( e ); return( JS_TRUE ); } // Write into set. set->at( id ) = *e; return( JS_TRUE ); } void JSI_Selection::finalize( JSContext* cx, JSObject* obj ) { std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); if( set ) delete( set ); } void JSI_Selection::init() { g_ScriptingHost.DefineCustomObjectType( &JSI_class, NULL, 0, JSI_props, JSI_methods, NULL, NULL ); } JSBool JSI_Selection::toString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) { std::vector* set = (std::vector*)JS_GetPrivate( cx, obj ); wchar_t buffer[256]; - _snwprintf( buffer, 256, L"[object EntityCollection: %d entities]", set->size() ); + int len=swprintf( buffer, 256, L"[object EntityCollection: %d entities]", set->size() ); buffer[255] = 0; - *rval = STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, buffer ) ); + if (len < 0 || len > 255) len=255; + utf16string u16str(buffer, buffer+len); + *rval = STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, u16str.c_str() ) ); return( JS_TRUE ); } JSBool JSI_Selection::getSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) { std::vector* set = new std::vector; std::vector::iterator it; for( it = g_Selection.m_selected.begin(); it != g_Selection.m_selected.end(); it++ ) set->push_back( (*it)->me ); JSObject* selectionArray = JS_NewObject( context, &JSI_Selection::JSI_class, NULL, NULL ); JS_SetPrivate( context, selectionArray, set ); JS_DefineFunction( context, selectionArray, "isOrderTypeValid", JSI_Selection::isValidContextOrder, 1, 0 ); JS_DefineProperty( context, selectionArray, "orderType", INT_TO_JSVAL( g_Selection.m_contextOrder ), JSI_Selection::getContextOrder, JSI_Selection::setContextOrder, 0 ); *vp = OBJECT_TO_JSVAL( selectionArray ); return( JS_TRUE ); } JSBool JSI_Selection::setSelection( JSContext* context, JSObject* globalObject, jsval id, jsval* vp ) { if( !JSVAL_IS_OBJECT( *vp ) ) { JS_ReportError( context, "Not a valid EntityCollection" ); *vp = JSVAL_NULL; return( JS_TRUE ); } JSObject* selectionArray = JSVAL_TO_OBJECT( *vp ); std::vector* set = (std::vector*)JS_GetInstancePrivate( context, JSVAL_TO_OBJECT( *vp ), &JSI_Selection::JSI_class, NULL ); if( !set ) { JS_ReportError( context, "Not a valid EntityCollection" ); *vp = JSVAL_NULL; return( JS_TRUE ); } g_Selection.clearSelection(); std::vector::iterator it; for( it = set->begin(); it < set->end(); it++ ) g_Selection.addSelection( &(**it) ); /* old-style load from array, here for reference jsval entry; JS_GetElement( context, selectionArray, i, &entry ); JSObject* selection = JSVAL_TO_OBJECT( entry ); HEntity* entity = NULL; if( JSVAL_IS_OBJECT( entry ) && ( entity = (HEntity*)JS_GetInstancePrivate( context, JSVAL_TO_OBJECT( entry ), &JSI_Entity::JSI_class, NULL ) ) ) { g_Selection.addSelection( &(**entity) ); } else JS_ReportError( context, "[Entity] Invalid reference" ); */ return( JS_TRUE ); } JSBool JSI_Selection::getGroups( JSContext* context, JSObject* obj, jsval id, jsval* vp ) { JSObject* groupsArray = JS_NewArrayObject( context, 0, NULL ); for( i8 groupId = 0; groupId < MAX_GROUPS; groupId++ ) { JSObject* group = JS_NewObject( context, &JSI_Selection::JSI_class, NULL, NULL ); std::vector* set = new std::vector; std::vector::iterator it; for( it = g_Selection.m_groups[groupId].begin(); it < g_Selection.m_groups[groupId].end(); it++ ) set->push_back( (*it)->me ); JS_SetPrivate( context, group, set ); jsval v = OBJECT_TO_JSVAL( group ); JS_SetElement( context, groupsArray, groupId, &v ); } *vp = OBJECT_TO_JSVAL( groupsArray ); return( JS_TRUE ); } JSBool JSI_Selection::setGroups( JSContext* context, JSObject* obj, jsval id, jsval* vp ) { JSObject* groupsArray; if( !JSVAL_IS_OBJECT( *vp ) || !JS_IsArrayObject( context, groupsArray = JSVAL_TO_OBJECT( *vp ) ) ) { JS_ReportError( context, "Not a valid group array" ); *vp = JSVAL_NULL; return( JS_TRUE ); } for( i8 groupId = 0; groupId < MAX_GROUPS; groupId++ ) { jsval v; if( JS_GetElement( context, groupsArray, groupId, &v ) && JSVAL_IS_OBJECT( v ) ) { JSObject* group = JSVAL_TO_OBJECT( v ); std::vector* set = (std::vector*)JS_GetInstancePrivate( context, group, &JSI_Selection::JSI_class, NULL ); if( set ) { g_Selection.m_groups[groupId].clear(); std::vector::iterator it; for( it = set->begin(); it < set->end(); it++ ) g_Selection.addToGroup( groupId, &(**it) ); } } } return( JS_TRUE ); } JSBool JSI_Selection::isValidContextOrder( JSContext* context, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ) { assert( argc >= 1 ); int orderCode; try { orderCode = g_ScriptingHost.ValueToInt( argv[0] ); } catch( ... ) { JS_ReportError( context, "Invalid order type" ); *rval = BOOLEAN_TO_JSVAL( false ); return( JS_TRUE ); } *rval = BOOLEAN_TO_JSVAL( g_Selection.isContextValid( orderCode ) ); return( JS_TRUE ); } JSBool JSI_Selection::getContextOrder( JSContext* context, JSObject* obj, jsval id, jsval* vp ) { *vp = INT_TO_JSVAL( g_Selection.m_contextOrder ); return( JS_TRUE ); } JSBool JSI_Selection::setContextOrder( JSContext* context, JSObject* obj, jsval id, jsval* vp ) { int orderCode; try { orderCode = g_ScriptingHost.ValueToInt( *vp ); } catch( ... ) { JS_ReportError( context, "Invalid order type" ); return( JS_TRUE ); } if( !g_Selection.isContextValid( orderCode ) ) { JS_ReportError( context, "Order is not valid in this context: %d", orderCode ); return( JS_TRUE ); } g_Selection.setContext( orderCode ); return( JS_TRUE ); -} \ No newline at end of file +} Index: ps/trunk/source/ps/scripting/JSInterface_Selection.h =================================================================== --- ps/trunk/source/ps/scripting/JSInterface_Selection.h (revision 911) +++ ps/trunk/source/ps/scripting/JSInterface_Selection.h (revision 912) @@ -1,35 +1,35 @@ // JSInterface_Selection.h // // The JavaScript wrapper around collections of entities // (notably the selection and groups objects) #include "scripting/ScriptingHost.h" #ifndef JSI_SELECTION_INCLUDED #define JSI_SELECTION_INCLUDED namespace JSI_Selection { JSBool toString( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ); extern JSClass JSI_class; extern JSPropertySpec JSI_props[]; extern JSFunctionSpec JSI_methods[]; JSBool addProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); JSBool removeProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); JSBool getProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); JSBool setProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ); void init(); void finalize( JSContext* cx, JSObject* obj ); JSBool getSelection( JSContext* context, JSObject* obj, jsval id, jsval* vp ); JSBool setSelection( JSContext* context, JSObject* obj, jsval id, jsval* vp ); JSBool getGroups( JSContext* context, JSObject* obj, jsval id, jsval* vp ); JSBool setGroups( JSContext* context, JSObject* obj, jsval id, jsval* vp ); JSBool isValidContextOrder( JSContext* context, JSObject* obj, unsigned int argc, jsval* argv, jsval* rval ); JSBool getContextOrder( JSContext* context, JSObject* obj, jsval id, jsval* vp ); JSBool setContextOrder( JSContext* context, JSObject* obj, jsval id, jsval* vp ); }; -#endif \ No newline at end of file +#endif Index: ps/trunk/source/ps/World.cpp =================================================================== --- ps/trunk/source/ps/World.cpp (revision 911) +++ ps/trunk/source/ps/World.cpp (revision 912) @@ -1,29 +1,40 @@ #include "precompiled.h" #include "CStr.h" #include "CLogger.h" #include "Errors.h" #include "World.h" #include "MapReader.h" #include "Game.h" #include "Terrain.h" #include "LightEnv.h" #include "BaseEntityCollection.h" +#include "EntityManager.h" extern CLightEnv g_LightEnv; void CWorld::Initialize(CGameAttributes *pAttribs) { g_EntityTemplateCollection.loadTemplates(); CStr mapfilename("mods/official/maps/scenarios/"); mapfilename+=pAttribs->m_MapFile; try { CMapReader reader; reader.LoadMap(mapfilename, &m_Terrain, &m_UnitManager, &g_LightEnv); } catch (...) { LOG(ERROR, "Failed to load map %s", mapfilename.c_str()); throw PSERROR_Game_World_MapLoadFailed(); } } + +CWorld::~CWorld() +{ + // The Entity Manager should perhaps be converted into a CWorld member.. + // But for now, we'll just create and delete the global singleton instance + // following the creation and deletion of CWorld. + // The reason for not keeping the instance around is that we require a + // clean slate for each game start. + delete &m_EntityManager; +} Index: ps/trunk/source/ps/CVFSFile.h =================================================================== --- ps/trunk/source/ps/CVFSFile.h (revision 911) +++ ps/trunk/source/ps/CVFSFile.h (revision 912) @@ -1,31 +1,31 @@ // OO wrapper around VFS file Handles, to simplify common usages #include "lib/res/h_mgr.h" #include "ps/CStr.h" ERROR_GROUP(CVFSFile); ERROR_TYPE(CVFSFile, LoadFailed); ERROR_TYPE(CVFSFile, AlreadyLoaded); ERROR_TYPE(CVFSFile, InvalidBufferAccess); // Reads a file, then gives read-only access to the contents class CVFSFile { public: CVFSFile(); ~CVFSFile(); // Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed. // Dies if a file has already been successfully loaded. PSRETURN Load(const char* filename, uint flags = 0); // These die if called when no file has been successfully loaded. const void* GetBuffer() const; const size_t GetBufferSize() const; CStr GetAsString() const; private: Handle m_Handle; void* m_Buffer; size_t m_BufferSize; -}; \ No newline at end of file +}; Index: ps/trunk/source/ps/Hotkey.h =================================================================== --- ps/trunk/source/ps/Hotkey.h (revision 911) +++ ps/trunk/source/ps/Hotkey.h (revision 912) @@ -1,105 +1,105 @@ // Hotkey.h // // Constant definitions and a couple of exports for the hotkey processor // // Mark Thompson (mot20@cam.ac.uk / mark@wildfiregames.com) // // Hotkeys can be mapped onto SDL events (for use internal to the engine), // or used to trigger activation of GUI buttons. // // Adding a hotkey (SDL event type): // // - Define your constant in the enum, just below; // - Add an entry to hotkeyInfo[], in Hotkey.cpp // first column is this constant, second is the config string (minus 'hotkey.') it maps to // third and fourth are the default keys, used if the config file doesn't contain that string. // - Create an input handler for SDL_HOTKEYDOWN, SDL_HOTKEYUP, or poll the hotkeys[] array. // For SDL_HOTKEYDOWN, SDL_HOTKEYUP, the constant is passed in as ev->user.code. // - Add some bindings to the config file. #include "precompiled.h" #include "sdl.h" #include "CStr.h" const int SDL_HOTKEYDOWN = SDL_USEREVENT; const int SDL_HOTKEYUP = SDL_USEREVENT + 1; const int SDL_GUIHOTKEYPRESS = SDL_USEREVENT + 2; enum { HOTKEY_EXIT, HOTKEY_SCREENSHOT, HOTKEY_WIREFRAME, HOTKEY_CAMERA_RESET, HOTKEY_CAMERA_RESET_ORIGIN, HOTKEY_CAMERA_ZOOM_IN, HOTKEY_CAMERA_ZOOM_OUT, HOTKEY_CAMERA_ZOOM_WHEEL_IN, HOTKEY_CAMERA_ZOOM_WHEEL_OUT, HOTKEY_CAMERA_ROTATE, HOTKEY_CAMERA_ROTATE_ABOUT_TARGET, HOTKEY_CAMERA_PAN, HOTKEY_CAMERA_PAN_LEFT, HOTKEY_CAMERA_PAN_RIGHT, HOTKEY_CAMERA_PAN_FORWARD, HOTKEY_CAMERA_PAN_BACKWARD, HOTKEY_CAMERA_BOOKMARK_0, HOTKEY_CAMERA_BOOKMARK_1, HOTKEY_CAMERA_BOOKMARK_2, HOTKEY_CAMERA_BOOKMARK_3, HOTKEY_CAMERA_BOOKMARK_4, HOTKEY_CAMERA_BOOKMARK_5, HOTKEY_CAMERA_BOOKMARK_6, HOTKEY_CAMERA_BOOKMARK_7, HOTKEY_CAMERA_BOOKMARK_8, HOTKEY_CAMERA_BOOKMARK_9, HOTKEY_CAMERA_BOOKMARK_SAVE, HOTKEY_CAMERA_BOOKMARK_SNAP, HOTKEY_CONSOLE_TOGGLE, HOTKEY_CONSOLE_COPY, HOTKEY_CONSOLE_PASTE, HOTKEY_SELECTION_ADD, HOTKEY_SELECTION_REMOVE, HOTKEY_SELECTION_GROUP_0, HOTKEY_SELECTION_GROUP_1, HOTKEY_SELECTION_GROUP_2, HOTKEY_SELECTION_GROUP_3, HOTKEY_SELECTION_GROUP_4, HOTKEY_SELECTION_GROUP_5, HOTKEY_SELECTION_GROUP_6, HOTKEY_SELECTION_GROUP_7, HOTKEY_SELECTION_GROUP_8, HOTKEY_SELECTION_GROUP_9, HOTKEY_SELECTION_GROUP_10, HOTKEY_SELECTION_GROUP_11, HOTKEY_SELECTION_GROUP_12, HOTKEY_SELECTION_GROUP_13, HOTKEY_SELECTION_GROUP_14, HOTKEY_SELECTION_GROUP_15, HOTKEY_SELECTION_GROUP_16, HOTKEY_SELECTION_GROUP_17, HOTKEY_SELECTION_GROUP_18, HOTKEY_SELECTION_GROUP_19, HOTKEY_SELECTION_GROUP_ADD, HOTKEY_SELECTION_GROUP_SAVE, HOTKEY_SELECTION_GROUP_SNAP, HOTKEY_SELECTION_SNAP, HOTKEY_ORDER_QUEUE, HOTKEY_CONTEXTORDER_NEXT, HOTKEY_CONTEXTORDER_PREVIOUS, HOTKEY_HIGHLIGHTALL, HOTKEY_PLAYMUSIC, HOTKEY_LAST, }; void loadHotkeys(); int hotkeyInputHandler( const SDL_Event* ev ); void hotkeyRegisterGUIObject( const CStr& objName, const CStr& hotkeyName ); void initKeyNameMap(); CStr getKeyName( int keycode ); int getKeyCode( CStr keyname ); -extern bool hotkeys[HOTKEY_LAST]; \ No newline at end of file +extern bool hotkeys[HOTKEY_LAST]; Index: ps/trunk/source/ps/Interact.cpp =================================================================== --- ps/trunk/source/ps/Interact.cpp (revision 911) +++ ps/trunk/source/ps/Interact.cpp (revision 912) @@ -1,915 +1,919 @@ #include "precompiled.h" #include "Interact.h" #include "Renderer.h" #include "input.h" #include "CConsole.h" #include "HFTracer.h" #include "Hotkey.h" #include "timer.h" #include "Game.h" extern CGame *g_Game; extern CConsole* g_Console; extern int mouse_x, mouse_y; extern bool keys[SDLK_LAST]; +extern bool g_active; static const float SELECT_DBLCLICK_RATE = 0.5f; static const int ORDER_DELAY = 5; void CSelectedEntities::addSelection( CEntity* entity ) { m_group = -1; assert( !isSelected( entity ) ); m_selected.push_back( entity ); entity->m_selected = true; } void CSelectedEntities::removeSelection( CEntity* entity ) { m_group = -1; assert( isSelected( entity ) ); entity->m_selected = false; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); break; } } } void CSelectedEntities::renderSelectionOutlines() { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->renderSelectionOutline(); if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) (*it)->renderSelectionOutline( 0.5f ); glDisable( GL_BLEND ); } } void CSelectedEntities::renderOverlays() { CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); CCamera *pCamera=g_Game->GetView()->GetCamera(); glPushMatrix(); glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped != -1 ) { assert( (*it)->m_bounds ); glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (*it)->m_grouped ); } } if( m_group_highlight != -1 ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_groups[m_group_highlight].begin(); it < m_groups[m_group_highlight].end(); it++ ) { assert( (*it)->m_bounds ); glLoadIdentity(); float x, y; CVector3D labelpos = (*it)->m_graphics_position - pCamera->m_Orientation.GetLeft() * (*it)->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", (*it)->m_grouped ); } glDisable( GL_BLEND ); } glLoadIdentity(); glTranslatef( (float)( mouse_x + 16 ), (float)( g_Renderer.GetHeight() - mouse_y - 8 ), 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: glwprintf( L"Go to" ); break; case CEntityOrder::ORDER_PATROL: glwprintf( L"Patrol to" ); break; } glDisable( GL_TEXTURE_2D ); glPopMatrix(); } void CSelectedEntities::setSelection( CEntity* entity ) { m_group = -1; clearSelection(); m_selected.push_back( entity ); } void CSelectedEntities::clearSelection() { m_group = -1; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = false; m_selected.clear(); } void CSelectedEntities::removeAll( CEntity* entity ) { // Remove a reference to an entity from everywhere // (for use when said entity is being destroyed) std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) { m_selected.erase( it ); break; } } for( u8 group = 0; group < 10; group++ ) { for( it = m_groups[group].begin(); it < m_groups[group].end(); it++ ) { if( (*it) == entity ) { m_groups[group].erase( it ); break; } } } } CVector3D CSelectedEntities::getSelectionPosition() { CVector3D avg; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) avg += (*it)->m_graphics_position; return( avg * ( 1.0f / m_selected.size() ) ); } void CSelectedEntities::saveGroup( i8 groupid ) { std::vector::iterator it; // Clear all entities in the group... for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) (*it)->m_grouped = -1; m_groups[groupid].clear(); // Remove selected entities from each group they're in, and flag them as // members of the new group for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it)->m_grouped != -1 ) { std::vector& group = m_groups[(*it)->m_grouped]; std::vector::iterator it2; for( it2 = group.begin(); it2 < group.end(); it2++ ) { if( (*it2) == &(**it) ) { group.erase( it2 ); break; } } } (*it)->m_grouped = groupid; } // Copy the group across m_groups[groupid] = m_selected; // Set the group selection memory m_group = groupid; } void CSelectedEntities::addToGroup( i8 groupid, CEntity* entity ) { std::vector::iterator it; // Remove selected entities from each group they're in, and flag them as // members of the new group if( entity->m_grouped != -1 ) { std::vector& group = m_groups[(*it)->m_grouped]; std::vector::iterator it2; for( it2 = group.begin(); it2 < group.end(); it2++ ) { if( (*it2) == entity ) { group.erase( it2 ); break; } } } entity->m_grouped = groupid; m_groups[groupid].push_back( entity ); } void CSelectedEntities::loadGroup( i8 groupid ) { clearSelection(); m_selected = m_groups[groupid]; std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; m_group = groupid; } void CSelectedEntities::addGroup( i8 groupid ) { std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) { if( !isSelected( *it ) ) addSelection( *it ); } for( it = m_selected.begin(); it < m_selected.end(); it++ ) (*it)->m_selected = true; } void CSelectedEntities::changeGroup( CEntity* entity, i8 groupid ) { // Remove from current group u8 current = entity->m_grouped; if( current != -1 ) { std::vector::iterator it; for( it = m_groups[current].begin(); it < m_groups[current].end(); it++ ) { if( (*it) == entity ) { m_groups[current].erase( it ); break; } } } if( groupid != -1 ) m_groups[groupid].push_back( entity ); entity->m_grouped = groupid; } bool CSelectedEntities::isSelected( CEntity* entity ) { std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) { if( (*it) == entity ) return( true ); } return( false ); } void CSelectedEntities::highlightGroup( i8 groupid ) { if( m_group_highlight != -1 ) return; if( !getGroupCount( groupid ) ) return; m_group_highlight = groupid; g_Game->GetView()->PushCameraTarget( getGroupPosition( groupid ) ); } void CSelectedEntities::highlightNone() { if( m_group_highlight != -1 ) g_Game->GetView()->PopCameraTarget(); m_group_highlight = -1; } int CSelectedEntities::getGroupCount( i8 groupid ) { return( (int)m_groups[groupid].size() ); } CVector3D CSelectedEntities::getGroupPosition( i8 groupid ) { CVector3D avg; std::vector::iterator it; for( it = m_groups[groupid].begin(); it < m_groups[groupid].end(); it++ ) avg += (*it)->m_graphics_position; return( avg * ( 1.0f / m_groups[groupid].size() ) ); } void CSelectedEntities::update() { if( !isContextValid( m_contextOrder ) ) { // This order isn't valid for the current selection and/or target. for( int t = 0; t < CEntityOrder::ORDER_LAST; t++ ) if( isContextValid( t ) ) { m_contextOrder = t; return; } m_contextOrder = -1; } if( ( m_group_highlight != -1 ) && getGroupCount( m_group_highlight ) ) g_Game->GetView()->SetCameraTarget( getGroupPosition( m_group_highlight ) ); } void CSelectedEntities::setContext( int contextOrder ) { assert( isContextValid( contextOrder ) ); m_contextOrder = contextOrder; } bool CSelectedEntities::nextContext() { // No valid orders? if( m_contextOrder == -1 ) return( false ); int t = m_contextOrder + 1; while( t != m_contextOrder ) { if( t == CEntityOrder::ORDER_LAST ) t = 0; if( isContextValid( t ) ) break; t++; } if( m_contextOrder == t ) return( false ); m_contextOrder = t; return( true ); } bool CSelectedEntities::previousContext() { // No valid orders? if( m_contextOrder == -1 ) return( false ); int t = m_contextOrder - 1; while( t != m_contextOrder ) { if( isContextValid( t ) ) break; if( t == 0 ) t = CEntityOrder::ORDER_LAST; t--; } if( m_contextOrder == t ) return( false ); m_contextOrder = t; return( true ); } bool CSelectedEntities::isContextValid( int contextOrder ) { if( contextOrder == -1 ) return( false ); // Can't order anything off the map if( !g_Game->GetWorld()->GetTerrain()->isOnMap( g_Mouseover.m_worldposition ) ) return( false ); // Check to see if any member of the selection supports this order type. std::vector::iterator it; for( it = m_selected.begin(); it < m_selected.end(); it++ ) if( (*it)->acceptsOrder( contextOrder, g_Mouseover.m_target ) ) return( true ); return( false ); } void CSelectedEntities::contextOrder( bool pushQueue ) { CCamera *pCamera=g_Game->GetView()->GetCamera(); CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); std::vector::iterator it; CEntityOrder context, contextRandomized; (int&)context.m_type = m_contextOrder; switch( m_contextOrder ) { case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_PATROL: context.m_data[0].location = g_Mouseover.m_worldposition; break; default: break; } // Location randomizer, for group orders... // Having the group turn up at the destination with /some/ sort of cohesion is good // but tasking them all to the exact same point will leave them brawling for it // at the other end (it shouldn't, but the PASAP pathfinder is too simplistic) // Task them all to a point within a radius of the target, radius depends upon // the number of units in the group. float radius = 2.0f * sqrt( (float)m_selected.size() - 1 ); // A decent enough approximation float _x, _y; for( it = m_selected.begin(); it < m_selected.end(); it++ ) if( (*it)->acceptsOrder( m_contextOrder, g_Mouseover.m_target ) ) { contextRandomized = context; do { _x = (float)( rand() % 20000 ) / 10000.0f - 1.0f; _y = (float)( rand() % 20000 ) / 10000.0f - 1.0f; } while( ( _x * _x ) + ( _y * _y ) > 1.0f ); contextRandomized.m_data[0].location.x += _x * radius; contextRandomized.m_data[0].location.y += _y * radius; // Clamp it to within the map, just in case. float mapsize = (float)g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if( contextRandomized.m_data[0].location.x < 0.0f ) contextRandomized.m_data[0].location.x = 0.0f; if( contextRandomized.m_data[0].location.x >= mapsize ) contextRandomized.m_data[0].location.x = mapsize; if( contextRandomized.m_data[0].location.y < 0.0f ) contextRandomized.m_data[0].location.y = 0.0f; if( contextRandomized.m_data[0].location.y >= mapsize ) contextRandomized.m_data[0].location.y = mapsize; g_Scheduler.pushFrame( ORDER_DELAY, (*it)->me, new CMessageOrder( contextRandomized, pushQueue ) ); } } void CMouseoverEntities::update( float timestep ) { CCamera *pCamera=g_Game->GetView()->GetCamera(); CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); CVector3D origin, dir; pCamera->BuildCameraRay( origin, dir ); CUnit* hit = g_UnitMan.PickUnit( origin, dir ); m_target = NULL; m_worldposition = pCamera->GetWorldCoordinates(); if( hit ) m_target = hit->GetEntity(); if( m_viewall ) { // 'O' key. Show selection outlines for all player units on screen // (as if bandboxing them all). // These aren't selectable; clicking when 'O' is pressed won't select // all units on screen. m_mouseover.clear(); std::vector* onscreen = g_EntityManager.matches( isOnScreen ); std::vector::iterator it; for( it = onscreen->begin(); it < onscreen->end(); it++ ) m_mouseover.push_back( SMouseoverFader( &(**it), m_fademaximum, false ) ); delete( onscreen ); } else if( m_bandbox ) { m_x2 = mouse_x; m_y2 = mouse_y; // Here's the fun bit: // Get the screen-space coordinates of all onscreen entities // then find the ones falling within the box. // // Fade in the ones in the box at (in+out) speed, then fade everything // out at (out) speed. std::vector* onscreen = g_EntityManager.matches( isOnScreen ); std::vector::iterator it; // Reset active flags on everything... std::vector::iterator it2; for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) it2->isActive = false; for( it = onscreen->begin(); it < onscreen->end(); it++ ) { CVector3D worldspace = (*it)->m_graphics_position; float x, y; pCamera->GetScreenCoordinates( worldspace, x, y ); bool inBox; if( m_x1 < m_x2 ) { inBox = ( x >= m_x1 ) && ( x < m_x2 ); } else { inBox = ( x >= m_x2 ) && ( x < m_x1 ); } if( m_y1 < m_y2 ) { inBox &= ( y >= m_y1 ) && ( y < m_y2 ); } else { inBox &= ( y >= m_y2 ) && ( y < m_y1 ); } if( inBox ) { bool found = false; for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); it2++ ) if( it2->entity == &(**it) ) { found = true; it2->fade += ( m_fadeinrate + m_fadeoutrate ) * timestep; it2->isActive = true; } if( !found ) m_mouseover.push_back( SMouseoverFader( &(**it), ( m_fadeinrate + m_fadeoutrate ) * timestep ) ); } } delete( onscreen ); for( it2 = m_mouseover.begin(); it2 < m_mouseover.end(); ) { it2->fade -= m_fadeoutrate * timestep; if( it2->fade > m_fademaximum ) it2->fade = m_fademaximum; if( it2->fade < 0.0f ) { it2 = m_mouseover.erase( it2 ); } else it2++; } } else { std::vector::iterator it; bool found = false; for( it = m_mouseover.begin(); it < m_mouseover.end(); ) { if( it->entity == m_target ) { found = true; it->fade += m_fadeinrate * timestep; if( it->fade > m_fademaximum ) it->fade = m_fademaximum; it->isActive = true; it++; continue; } else { it->fade -= m_fadeoutrate * timestep; if( it->fade <= 0.0f ) { it = m_mouseover.erase( it ); continue; } it++; continue; } } if( !found && m_target ) { float initial = m_fadeinrate * timestep; if( initial > m_fademaximum ) initial = m_fademaximum; m_mouseover.push_back( SMouseoverFader( m_target, initial ) ); } } } void CMouseoverEntities::addSelection() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && !g_Selection.isSelected( it->entity ) ) g_Selection.addSelection( it->entity ); } void CMouseoverEntities::removeSelection() { std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) if( it->isActive && g_Selection.isSelected( it->entity ) ) g_Selection.removeSelection( it->entity ); } void CMouseoverEntities::setSelection() { g_Selection.clearSelection(); addSelection(); } void CMouseoverEntities::expandAcrossScreen() { std::vector* activeset = g_EntityManager.matches( isMouseoverType, isOnScreen ); m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) { m_mouseover.push_back( SMouseoverFader( &(**it) ) ); } delete( activeset ); } void CMouseoverEntities::expandAcrossWorld() { std::vector* activeset = g_EntityManager.matches( isMouseoverType ); m_mouseover.clear(); std::vector::iterator it; for( it = activeset->begin(); it < activeset->end(); it++ ) { m_mouseover.push_back( SMouseoverFader( &(**it) ) ); } delete( activeset ); } void CMouseoverEntities::renderSelectionOutlines() { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) it->entity->renderSelectionOutline( it->fade ); glDisable( GL_BLEND ); } void CMouseoverEntities::renderOverlays() { CCamera *pCamera=g_Game->GetView()->GetCamera(); CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); glLoadIdentity(); glDisable( GL_TEXTURE_2D ); if( m_bandbox ) { //glPushMatrix(); glColor3f( 1.0f, 1.0f, 1.0f ); glBegin( GL_LINE_LOOP ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y1 ); glVertex2i( m_x2, g_Renderer.GetHeight() - m_y2 ); glVertex2i( m_x1, g_Renderer.GetHeight() - m_y2 ); glEnd(); //glPopMatrix(); } glEnable( GL_TEXTURE_2D ); std::vector::iterator it; for( it = m_mouseover.begin(); it < m_mouseover.end(); it++ ) { if( it->entity->m_grouped != -1 ) { assert( it->entity->m_bounds ); glPushMatrix(); glEnable( GL_TEXTURE_2D ); glLoadIdentity(); float x, y; CVector3D labelpos = it->entity->m_graphics_position - pCamera->m_Orientation.GetLeft() * it->entity->m_bounds->m_radius; #ifdef SELECTION_TERRAIN_CONFORMANCE labelpos.Y = pTerrain->getExactGroundLevel( labelpos.X, labelpos.Z ); #endif pCamera->GetScreenCoordinates( labelpos, x, y ); glColor4f( 1.0f, 1.0f, 1.0f, it->fade ); glTranslatef( x, g_Renderer.GetHeight() - y, 0.0f ); glScalef( 1.0f, -1.0f, 1.0f ); glwprintf( L"%d", it->entity->m_grouped ); glDisable( GL_TEXTURE_2D ); glPopMatrix(); } } } void CMouseoverEntities::startBandbox( u16 x, u16 y ) { m_bandbox = true; m_x1 = x; m_y1 = y; } void CMouseoverEntities::stopBandbox() { m_bandbox = false; } int interactInputHandler( const SDL_Event* ev ) { + if (!g_active || !g_Game) + return EV_PASS; + CGameView *pView=g_Game->GetView(); CCamera *pCamera=pView->GetCamera(); CTerrain *pTerrain=g_Game->GetWorld()->GetTerrain(); static float lastclicktime = 0.0f; static CEntity* lastclickobject = NULL; static u8 clicks = 0; static u16 button_down_x, button_down_y; static bool button_down = false; switch( ev->type ) { case SDL_HOTKEYDOWN: switch( ev->user.code ) { case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = true; break; case HOTKEY_SELECTION_SNAP: if( g_Selection.m_selected.size() ) pView->SetCameraTarget( g_Selection.getSelectionPosition() ); break; case HOTKEY_CONTEXTORDER_NEXT: g_Selection.nextContext(); break; case HOTKEY_CONTEXTORDER_PREVIOUS: g_Selection.previousContext(); break; default: if( ( ev->user.code >= HOTKEY_SELECTION_GROUP_0 ) && ( ev->user.code <= HOTKEY_SELECTION_GROUP_19 ) ) { // The above test limits it to 20 groups, so don't worry about overflowing i8 id = (i8)( ev->user.code - HOTKEY_SELECTION_GROUP_0 ); if( hotkeys[HOTKEY_SELECTION_GROUP_ADD] ) { g_Selection.addGroup( id ); } else if( hotkeys[HOTKEY_SELECTION_GROUP_SAVE] ) { g_Selection.saveGroup( id ); } else if( hotkeys[HOTKEY_SELECTION_GROUP_SNAP] ) { g_Selection.highlightGroup( id ); } else { if( ( g_Selection.m_group == id ) && g_Selection.getGroupCount( id ) ) { pView->SetCameraTarget( g_Selection.getGroupPosition( id ) ); } else g_Selection.loadGroup( id ); } return( EV_HANDLED ); } return( EV_PASS ); } return( EV_HANDLED ); case SDL_HOTKEYUP: switch( ev->user.code ) { case HOTKEY_SELECTION_GROUP_SNAP: if( g_Selection.m_group_highlight != -1 ) g_Selection.highlightNone(); break; case HOTKEY_HIGHLIGHTALL: g_Mouseover.m_viewall = false; break; default: return( EV_PASS ); } return( EV_HANDLED ); case SDL_MOUSEBUTTONUP: switch( ev->button.button ) { case SDL_BUTTON_LEFT: if( g_Mouseover.m_viewall ) break; float time; time = (float)get_time(); // Reset clicks counter if too slow or if the cursor's // hovering over something else now. if( time - lastclicktime >= SELECT_DBLCLICK_RATE ) clicks = 0; if( g_Mouseover.m_target != lastclickobject ) clicks = 0; clicks++; if( clicks == 2 ) { // Double click g_Mouseover.expandAcrossScreen(); } else if( clicks == 3 ) { // Triple click g_Mouseover.expandAcrossWorld(); } lastclicktime = time; lastclickobject = g_Mouseover.m_target; button_down = false; g_Mouseover.stopBandbox(); if( hotkeys[HOTKEY_SELECTION_ADD] ) { g_Mouseover.addSelection(); } else if( hotkeys[HOTKEY_SELECTION_REMOVE] ) { g_Mouseover.removeSelection(); } else g_Mouseover.setSelection(); break; case SDL_BUTTON_RIGHT: g_Selection.contextOrder( hotkeys[HOTKEY_ORDER_QUEUE] ); break; } break; case SDL_MOUSEBUTTONDOWN: switch( ev->button.button ) { case SDL_BUTTON_LEFT: button_down = true; button_down_x = ev->button.x; button_down_y = ev->button.y; break; } break; case SDL_MOUSEMOTION: if( !g_Mouseover.isBandbox() && button_down ) { int deltax = ev->motion.x - button_down_x; int deltay = ev->motion.y - button_down_y; if( ABS( deltax ) > 2 || ABS( deltay ) > 2 ) g_Mouseover.startBandbox( button_down_x, button_down_y ); } break; } return( EV_PASS ); } bool isOnScreen( CEntity* ev ) { CCamera *pCamera=g_Game->GetView()->GetCamera(); CFrustum frustum = pCamera->GetFrustum(); return( frustum.IsBoxVisible( CVector3D(), ev->m_actor->GetModel()->GetBounds() ) ); } bool isMouseoverType( CEntity* ev ) { std::vector::iterator it; for( it = g_Mouseover.m_mouseover.begin(); it < g_Mouseover.m_mouseover.end(); it++ ) { if( it->isActive && ( (CBaseEntity*)it->entity->m_base == (CBaseEntity*)ev->m_base ) ) return( true ); } return( false ); } /* void pushCameraTarget( const CVector3D& target ) { // Save the current position cameraTargets.push_back( g_Camera.m_Orientation.GetTranslation() ); // And set the camera setCameraTarget( target ); } void setCameraTarget( const CVector3D& target ) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference beteen that position and the camera point, and restoring // that difference to our new target) cameraDelta = target - g_Camera.GetFocus(); } void popCameraTarget() { cameraDelta = cameraTargets.back() - g_Camera.m_Orientation.GetTranslation(); cameraTargets.pop_back(); } */ \ No newline at end of file Index: ps/trunk/source/ps/Game.cpp =================================================================== --- ps/trunk/source/ps/Game.cpp (revision 911) +++ ps/trunk/source/ps/Game.cpp (revision 912) @@ -1,32 +1,59 @@ #include "precompiled.h" #include "Game.h" +#include "CLogger.h" CGame *g_Game=NULL; +JSBool CGameAttributes::FillFromJS(JSContext *cx, JSObject *obj) +{ +#define GETVAL(_name) jsval _name=g_ScriptingHost.GetObjectProperty(obj, #_name) + GETVAL(mapFile); + +#define TOSTRING(_jsval) g_ScriptingHost.ValueToString(_jsval) + if (JSVAL_IS_STRING(mapFile)) + m_MapFile=TOSTRING(mapFile); + + return JS_TRUE; +} + +CGame::CGame(): + m_World(this), + m_Simulation(this), + m_GameView(this), + m_pLocalPlayer(NULL) +{ + debug_out("CGame::CGame(): Game object CREATED"); +} + +CGame::~CGame() +{ + debug_out("CGame::~CGame(): Game object DESTROYED"); +} + PSRETURN CGame::Initialize(CGameAttributes *pAttribs) { try { // RC, 040804 - GameView needs to be initialised before World, otherwise GameView initialisation // overwrites anything stored in the map file that gets loaded by CWorld::Initialize with default // values. At the minute, it's just lighting settings, but could be extended to store camera position. // Storing lighting settings in the gameview seems a little odd, but it's no big deal; maybe move it at // some point to be stored in the world object? m_GameView.Initialize(pAttribs); m_World.Initialize(pAttribs); m_Simulation.Initialize(pAttribs); } catch (PSERROR_Game e) { return e.code; } return 0; } void CGame::Update(double deltaTime) { m_Simulation.Update(deltaTime); // TODO Detect game over and bring up the summary screen or something } Index: ps/trunk/source/ps/World.h =================================================================== --- ps/trunk/source/ps/World.h (revision 911) +++ ps/trunk/source/ps/World.h (revision 912) @@ -1,41 +1,46 @@ #ifndef _ps_World_H #define _ps_World_H #include "Terrain.h" #include "UnitManager.h" +#include "EntityManager.h" class CGame; class CGameAttributes; class CWorld { CGame *m_pGame; + CTerrain m_Terrain; // These both point to the respective g_* globals - the plan is to remove // the globals and move them into CWorld members as soon as all code has // been converted - CTerrain m_Terrain; CUnitManager &m_UnitManager; + CEntityManager &m_EntityManager; public: inline CWorld(CGame *pGame): m_pGame(pGame), m_Terrain(), - m_UnitManager(g_UnitMan) + m_UnitManager(g_UnitMan), + m_EntityManager(*new CEntityManager()) {} + ~CWorld(); + /* Initialize the World - load the map and all objects */ void Initialize(CGameAttributes *pGameAttributes); inline CTerrain *GetTerrain() { return &m_Terrain; } inline CUnitManager *GetUnitManager() { return &m_UnitManager; } }; #include "Game.h" ERROR_SUBGROUP(Game, World); ERROR_TYPE(Game_World, MapLoadFailed); #endif Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 911) +++ ps/trunk/source/graphics/GameView.cpp (revision 912) @@ -1,585 +1,580 @@ #include "precompiled.h" #include "Terrain.h" #include "Renderer.h" #include "GameView.h" #include "Game.h" #include "Camera.h" #include "Matrix3D.h" #include "Renderer.h" #include "Terrain.h" #include "LightEnv.h" #include "HFTracer.h" #include "TextureManager.h" #include "ObjectManager.h" #include "Prometheus.h" #include "Hotkey.h" #include "ConfigDB.h" #include "sdl.h" #include "input.h" #include "lib.h" extern int g_xres, g_yres; extern bool g_active; -extern CLightEnv g_LightEnv; - CVector3D cameraBookmarks[10]; bool bookmarkInUse[10] = { false, false, false, false, false, false, false, false, false, false }; i8 currentBookmark = -1; CGameView::CGameView(CGame *pGame): m_pGame(pGame), m_pWorld(pGame->GetWorld()), m_Camera(), m_ViewScrollSpeed(60), m_ViewRotateSensitivity(0.002f), m_ViewRotateAboutTargetSensitivity(0.010f), m_ViewDragSensitivity(0.5f), m_ViewZoomSensitivityWheel(16.0f), m_ViewZoomSensitivity(256.0f), m_ViewZoomSmoothness(0.02f), m_ViewSnapSmoothness(0.02f), m_CameraPivot(), m_CameraDelta()//, // m_CameraZoom(10) { InitResources(); } void CGameView::Initialize(CGameAttributes *pAttribs) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m_Camera.SetViewPort(&vp); CConfigValue* cfg; #define getViewParameter( name, value ) STMT( \ cfg = g_ConfigDB.GetValue( CFG_SYSTEM, name );\ if( cfg ) cfg->GetFloat( value ); ) getViewParameter( "view.scroll.speed", m_ViewScrollSpeed ); getViewParameter( "view.rotate.speed", m_ViewRotateSensitivity ); getViewParameter( "view.rotate.abouttarget.speed", m_ViewRotateAboutTargetSensitivity ); getViewParameter( "view.drag.speed", m_ViewDragSensitivity ); getViewParameter( "view.zoom.speed", m_ViewZoomSensitivity ); getViewParameter( "view.zoom.wheel.speed", m_ViewZoomSensitivityWheel ); getViewParameter( "view.zoom.smoothness", m_ViewZoomSmoothness ); getViewParameter( "view.snap.smoothness", m_ViewSnapSmoothness ); if( ( m_ViewZoomSmoothness < 0.0f ) || ( m_ViewZoomSmoothness > 1.0f ) ) m_ViewZoomSmoothness = 0.02f; if( ( m_ViewSnapSmoothness < 0.0f ) || ( m_ViewSnapSmoothness > 1.0f ) ) m_ViewSnapSmoothness = 0.02f; #undef getViewParameter - // setup default lighting environment - g_LightEnv.m_SunColor=RGBColor(1,1,1); - g_LightEnv.m_Rotation=DEGTORAD(270); - g_LightEnv.m_Elevation=DEGTORAD(45); - g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0); - g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f); - g_Renderer.SetLightEnv(&g_LightEnv); - + // If we start storing initial camera in the Map/World, change this code to + // init from the CWorld member instead of filling in defaults m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); m_Camera.m_Orientation.Translate (100, 150, -100); + g_Renderer.SetCamera(m_Camera); } void CGameView::Render() { g_Renderer.SetCamera(m_Camera); + MICROLOG(L"render terrain"); RenderTerrain(m_pWorld->GetTerrain()); MICROLOG(L"render models"); RenderModels(m_pWorld->GetUnitManager()); } void CGameView::RenderTerrain(CTerrain *pTerrain) { CFrustum frustum=m_Camera.GetFrustum(); u32 patchesPerSide=pTerrain->GetPatchesPerSide(); for (uint j=0; jGetPatch(i,j); if (frustum.IsBoxVisible (CVector3D(0,0,0),patch->GetBounds())) { g_Renderer.Submit(patch); } } } } void CGameView::RenderModels(CUnitManager *pUnitMan) { CFrustum frustum=m_Camera.GetFrustum(); const std::vector& units=pUnitMan->GetUnits(); for (uint i=0;iGetModel()->GetBounds())) { SubmitModelRecursive(units[i]->GetModel()); } } } void CGameView::SubmitModelRecursive(CModel* model) { g_Renderer.Submit(model); const std::vector& props=model->GetProps(); for (uint i=0;iGetUnitManager(); CTerrain *pTerrain=m_pWorld->GetTerrain(); g_Renderer.SetCamera(m_Camera); uint i,j; const std::vector& units=pUnitMan->GetUnits(); for (i=0;iGetModel()); } u32 patchesPerSide=pTerrain->GetPatchesPerSide(); for (j=0; jGetPatch(i,j); g_Renderer.Submit(patch); } } } void CGameView::InitResources() { g_TexMan.LoadTerrainTextures(); g_ObjMan.LoadObjects(); const char* fns[CRenderer::NumAlphaMaps] = { "art/textures/terrain/alphamaps/special/blendcircle.png", "art/textures/terrain/alphamaps/special/blendlshape.png", "art/textures/terrain/alphamaps/special/blendedge.png", "art/textures/terrain/alphamaps/special/blendedgecorner.png", "art/textures/terrain/alphamaps/special/blendedgetwocorners.png", "art/textures/terrain/alphamaps/special/blendfourcorners.png", "art/textures/terrain/alphamaps/special/blendtwooppositecorners.png", "art/textures/terrain/alphamaps/special/blendlshapecorner.png", "art/textures/terrain/alphamaps/special/blendtwocorners.png", "art/textures/terrain/alphamaps/special/blendcorner.png", "art/textures/terrain/alphamaps/special/blendtwoedges.png", "art/textures/terrain/alphamaps/special/blendthreecorners.png", "art/textures/terrain/alphamaps/special/blendushape.png", "art/textures/terrain/alphamaps/special/blendbad.png" }; g_Renderer.LoadAlphaMaps(fns); } void CGameView::ResetCamera() { // quick hack to return camera home, for screenshots (after alt+tabbing) m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); m_Camera.m_Orientation.Translate (100, 150, -100); } void CGameView::ResetCameraOrientation() { CVector3D origin = m_Camera.m_Orientation.GetTranslation(); CVector3D dir = m_Camera.m_Orientation.GetIn(); CVector3D target = origin + dir * ( ( 50.0f - origin.Y ) / dir.Y ); target -= CVector3D( -22.474480f, 50.0f, 22.474480f ); m_Camera.SetProjection (1, 5000, DEGTORAD(20)); m_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); m_Camera.m_Orientation.RotateY(DEGTORAD(-45)); target += CVector3D( 100.0f, 150.0f, -100.0f ); m_Camera.m_Orientation.Translate( target ); } void CGameView::RotateAboutTarget() { m_CameraPivot = m_Camera.GetWorldCoordinates(); } void CGameView::Update(float DeltaTime) { if (!g_active) return; float delta = powf( m_ViewSnapSmoothness, DeltaTime ); m_Camera.m_Orientation.Translate( m_CameraDelta * ( 1.0f - delta ) ); m_CameraDelta *= delta; #define CAMERASTYLE 2 // 0 = old style, 1 = relatively new style, 2 = newest style // #if CAMERASTYLE == 2 // This could be rewritten much more reliably, so it doesn't e.g. accidentally tilt // the camera, assuming we know exactly what limits the camera should have. // Calculate mouse movement static int mouse_last_x = 0; static int mouse_last_y = 0; int mouse_dx = mouse_x - mouse_last_x; int mouse_dy = mouse_y - mouse_last_y; mouse_last_x = mouse_x; mouse_last_y = mouse_y; // Miscellaneous vectors CVector3D forwards = m_Camera.m_Orientation.GetIn(); CVector3D rightwards = m_Camera.m_Orientation.GetLeft() * -1.0f; // upwards.Cross(forwards); CVector3D upwards( 0.0f, 1.0f, 0.0f ); // rightwards.Normalize(); CVector3D forwards_horizontal = forwards; forwards_horizontal.Y = 0.0f; forwards_horizontal.Normalize(); if( hotkeys[HOTKEY_CAMERA_ROTATE] ) { // Ctrl + middle-drag or left-and-right-drag to rotate view // Untranslate the camera, so it rotates around the correct point CVector3D position = m_Camera.m_Orientation.GetTranslation(); m_Camera.m_Orientation.Translate(position*-1); // Sideways rotation m_Camera.m_Orientation.RotateY(m_ViewRotateSensitivity * (float)(mouse_dx)); // Up/down rotation CQuaternion temp; temp.FromAxisAngle(rightwards, m_ViewRotateSensitivity * (float)(mouse_dy)); m_Camera.m_Orientation.Rotate(temp); // Retranslate back to the right position m_Camera.m_Orientation.Translate(position); } else if( hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { CVector3D origin = m_Camera.m_Orientation.GetTranslation(); CVector3D delta = origin - m_CameraPivot; CQuaternion rotateH, rotateV; CMatrix3D rotateM; // Side-to-side rotation rotateH.FromAxisAngle( upwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dx ); // Up-down rotation rotateV.FromAxisAngle( rightwards, m_ViewRotateAboutTargetSensitivity * (float)mouse_dy ); rotateH *= rotateV; rotateH.ToMatrix( rotateM ); delta = rotateM.Rotate( delta ); // Lock the inclination to a rather arbitrary values (for the sake of graphical decency) float scan = sqrt( delta.X * delta.X + delta.Z * delta.Z ) / delta.Y; if( ( scan >= 0.5f ) ) { // Move the camera to the origin (in preparation for rotation ) m_Camera.m_Orientation.Translate( origin * -1.0f ); m_Camera.m_Orientation.Rotate( rotateH ); // Move the camera back to where it belongs m_Camera.m_Orientation.Translate( m_CameraPivot + delta ); } } else if( hotkeys[HOTKEY_CAMERA_PAN] ) { // Middle-drag to pan m_Camera.m_Orientation.Translate(rightwards * (m_ViewDragSensitivity * mouse_dx)); m_Camera.m_Orientation.Translate(forwards_horizontal * (-m_ViewDragSensitivity * mouse_dy)); } // Mouse movement if( !hotkeys[HOTKEY_CAMERA_ROTATE] && !hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { if (mouse_x >= g_xres-2) m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime)); else if (mouse_x <= 3) m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime)); if (mouse_y >= g_yres-2) m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); else if (mouse_y <= 3) m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); } // Keyboard movement (added to mouse movement, so you can go faster if you want) if( hotkeys[HOTKEY_CAMERA_PAN_RIGHT] ) m_Camera.m_Orientation.Translate(rightwards * (m_ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_PAN_LEFT] ) m_Camera.m_Orientation.Translate(-rightwards * (m_ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_PAN_BACKWARD] ) m_Camera.m_Orientation.Translate(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_PAN_FORWARD] ) m_Camera.m_Orientation.Translate(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); // Smoothed zooming (move a certain percentage towards the desired zoom distance every frame) static float zoom_delta = 0.0f; if( hotkeys[HOTKEY_CAMERA_ZOOM_WHEEL_IN] ) zoom_delta += m_ViewZoomSensitivityWheel; else if( hotkeys[HOTKEY_CAMERA_ZOOM_WHEEL_OUT] ) zoom_delta -= m_ViewZoomSensitivityWheel; if( hotkeys[HOTKEY_CAMERA_ZOOM_IN] ) zoom_delta += m_ViewZoomSensitivity*DeltaTime; else if( hotkeys[HOTKEY_CAMERA_ZOOM_OUT] ) zoom_delta -= m_ViewZoomSensitivity*DeltaTime; if (zoom_delta) { float zoom_proportion = powf(m_ViewZoomSmoothness, DeltaTime); m_Camera.m_Orientation.Translate(forwards * (zoom_delta * (1.0f-zoom_proportion))); zoom_delta *= zoom_proportion; } /* Just commented out to make it more obvious it's not in use. #elif CAMERASTYLE == 1 // Remember previous mouse position, to calculate changes static mouse_last_x = 0; static mouse_last_y = 0; // Miscellaneous vectors CVector3D forwards = m_Camera.m_Orientation.GetIn(); CVector3D upwards (0.0f, 1.0f, 0.0f); CVector3D rightwards = upwards.Cross(forwards); // Click and drag to look around if (mouseButtons[0]) { // Untranslate the camera, so it rotates around the correct point CVector3D position = m_Camera.m_Orientation.GetTranslation(); m_Camera.m_Orientation.Translate(position*-1); // Sideways rotation m_Camera.m_Orientation.RotateY(m_ViewRotateSpeed*(float)(mouse_x-mouse_last_x)); // Up/down rotation CQuaternion temp; temp.FromAxisAngle(rightwards, m_ViewRotateSpeed*(float)(mouse_y-mouse_last_y)); m_Camera.m_Orientation.Rotate(temp); // Retranslate back to the right position m_Camera.m_Orientation.Translate(position); } mouse_last_x = mouse_x; mouse_last_y = mouse_y; // Calculate the necessary vectors for movement rightwards.Normalize(); CVector3D forwards_horizontal = upwards.Cross(rightwards); forwards_horizontal.Normalize(); // Move when desirable if (mouse_x >= g_xres-2) m_Camera.m_Orientation.Translate(rightwards); else if (mouse_x <= 3) m_Camera.m_Orientation.Translate(-rightwards); if (mouse_y >= g_yres-2) m_Camera.m_Orientation.Translate(forwards_horizontal); else if (mouse_y <= 3) m_Camera.m_Orientation.Translate(-forwards_horizontal); // Smoothed height-changing (move a certain percentage towards the desired height every frame) static float height_delta = 0.0f; if (mouseButtons[SDL_BUTTON_WHEELUP]) height_delta -= 4.0f; else if (mouseButtons[SDL_BUTTON_WHEELDOWN]) height_delta += 4.0f; const float height_speed = 0.2f; m_Camera.m_Orientation.Translate(0.0f, height_delta*height_speed, 0.0f); height_delta *= (1.0f - height_speed); #else // CAMERASTYLE == 0 const float dx = m_ViewScrollSpeed * DeltaTime; const CVector3D Right(dx,0, dx); const CVector3D Up (dx,0,-dx); if (mouse_x >= g_xres-2) m_Camera.m_Orientation.Translate(Right); if (mouse_x <= 3) m_Camera.m_Orientation.Translate(Right*-1); if (mouse_y >= g_yres-2) m_Camera.m_Orientation.Translate(Up); if (mouse_y <= 3) m_Camera.m_Orientation.Translate(Up*-1); /* janwas: grr, plotted the zoom vector on paper twice, but it appears to be completely wrong. sticking with the FOV hack for now. if anyone sees what's wrong, or knows how to correctly implement zoom, please put this code out of its misery :) *//* // RC - added ScEd style zoom in and out (actually moving camera, rather than fudging fov) float dir=0; if (mouseButtons[SDL_BUTTON_WHEELUP]) dir=-1; else if (mouseButtons[SDL_BUTTON_WHEELDOWN]) dir=1; float factor=dir*dir; if (factor) { if (dir<0) factor=-factor; CVector3D forward=m_Camera.m_Orientation.GetIn(); // check we're not going to zoom into the terrain, or too far out into space float h=m_Camera.m_Orientation.GetTranslation().Y+forward.Y*factor*m_Camera.Zoom; float minh=65536*HEIGHT_SCALE*1.05f; if (h1500) { // yup, we will; don't move anywhere (do clamped move instead, at some point) } else { // do a full move m_Camera.Zoom-=(factor)*0.1f; if (m_Camera.Zoom<0.01f) m_Camera.Zoom=0.01f; m_Camera.m_Orientation.Translate(forward*(factor*m_Camera.Zoom)); } } #endif // CAMERASTYLE */ m_Camera.UpdateFrustum (); } void CGameView::PushCameraTarget( const CVector3D& target ) { // Save the current position m_CameraTargets.push_back( m_Camera.m_Orientation.GetTranslation() ); // And set the camera SetCameraTarget( target ); } void CGameView::SetCameraTarget( const CVector3D& target ) { // Maintain the same orientation and level of zoom, if we can // (do this by working out the point the camera is looking at, saving // the difference beteen that position and the camera point, and restoring // that difference to our new target) CVector3D CurrentTarget = m_Camera.GetFocus(); m_CameraDelta = target - CurrentTarget; } void CGameView::PopCameraTarget() { m_CameraDelta = m_CameraTargets.back() - m_Camera.m_Orientation.GetTranslation(); m_CameraTargets.pop_back(); } int game_view_handler(const SDL_Event* ev) { - CGameView *pView=g_Game->GetView(); // put any events that must be processed even if inactive here - if(!g_active) + if(!g_active || !g_Game) return EV_PASS; + CGameView *pView=g_Game->GetView(); + switch(ev->type) { case SDL_HOTKEYDOWN: switch(ev->user.code) { case HOTKEY_WIREFRAME: if (g_Renderer.GetTerrainRenderMode()==WIREFRAME) { g_Renderer.SetTerrainRenderMode(SOLID); } else { g_Renderer.SetTerrainRenderMode(WIREFRAME); } return( EV_HANDLED ); case HOTKEY_CAMERA_RESET_ORIGIN: pView->ResetCamera(); return( EV_HANDLED ); case HOTKEY_CAMERA_RESET: pView->ResetCameraOrientation(); return( EV_HANDLED ); case HOTKEY_CAMERA_ROTATE_ABOUT_TARGET: pView->RotateAboutTarget(); return( EV_HANDLED ); default: if( ( ev->user.code >= HOTKEY_CAMERA_BOOKMARK_0 ) && ( ev->user.code <= HOTKEY_CAMERA_BOOKMARK_9 ) ) { // The above test limits it to 10 bookmarks, so don't worry about overflowing i8 id = (i8)( ev->user.code - HOTKEY_CAMERA_BOOKMARK_0 ); if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SAVE] ) { // Attempt to track the ground we're looking at cameraBookmarks[id] = pView->GetCamera()->GetFocus(); bookmarkInUse[id] = true; } else if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SNAP] ) { if( bookmarkInUse[id] && ( currentBookmark == -1 ) ) { pView->PushCameraTarget( cameraBookmarks[id] ); currentBookmark = id; } } else { if( bookmarkInUse[id] ) pView->SetCameraTarget( cameraBookmarks[id] ); } return( EV_HANDLED ); } } case SDL_HOTKEYUP: switch( ev->user.code ) { case HOTKEY_CAMERA_BOOKMARK_SNAP: if( currentBookmark != -1 ) pView->PopCameraTarget(); currentBookmark = -1; break; default: return( EV_PASS ); } return( EV_HANDLED ); } return EV_PASS; } Index: ps/trunk/source/scripting/ScriptGlue.cpp =================================================================== --- ps/trunk/source/scripting/ScriptGlue.cpp (revision 911) +++ ps/trunk/source/scripting/ScriptGlue.cpp (revision 912) @@ -1,270 +1,298 @@ #include "precompiled.h" #include "ScriptGlue.h" #include "CConsole.h" #include "CLogger.h" #include "CStr.h" #include "EntityHandles.h" #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" #include "Scheduler.h" +#include "Game.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "scripting/JSInterface_Selection.h" #include "scripting/JSInterface_Camera.h" #include "scripting/JSInterface_Console.h" extern CConsole* g_Console; // Parameters for the table are: // 0: The name the function will be called as from script // 1: The function which will be called // 2: The number of arguments this function expects // 3: Flags (deprecated, always zero) // 4: Extra (reserved for future use, always zero) JSFunctionSpec ScriptFunctionTable[] = { {"writeLog", WriteLog, 1, 0, 0}, {"writeConsole", JSI_Console::writeConsole, 1, 0, 0 }, // Keep this variant available for now. {"getEntityByHandle", getEntityByHandle, 1, 0, 0 }, {"getEntityTemplate", getEntityTemplate, 1, 0, 0 }, {"setTimeout", setTimeout, 2, 0, 0 }, {"setInterval", setInterval, 2, 0, 0 }, {"cancelInterval", cancelInterval, 0, 0, 0 }, {"getGUIObjectByName", JSI_IGUIObject::getByName, 1, 0, 0 }, {"getGlobal", getGlobal, 0, 0, 0 }, {"getGUIGlobal", getGUIGlobal, 0, 0, 0 }, {"setCursor", setCursor, 0, 0, 0 }, + {"startGame", startGame, 0, 0, 0 }, + {"endGame", endGame, 0, 0, 0 }, {"exit", exitProgram, 0, 0, 0 }, {"crash", crash, 1, 0, 0 }, {0, 0, 0, 0, 0}, }; enum ScriptGlobalTinyIDs { GLOBAL_SELECTION, GLOBAL_GROUPSARRAY, GLOBAL_CAMERA, GLOBAL_CONSOLE, }; JSPropertySpec ScriptGlobalTable[] = { { "selection", GLOBAL_SELECTION, JSPROP_PERMANENT, JSI_Selection::getSelection, JSI_Selection::setSelection }, { "groups", GLOBAL_GROUPSARRAY, JSPROP_PERMANENT, JSI_Selection::getGroups, JSI_Selection::setGroups }, { "camera", GLOBAL_CAMERA, JSPROP_PERMANENT, JSI_Camera::getCamera, JSI_Camera::setCamera }, { "console", GLOBAL_CONSOLE, JSPROP_PERMANENT | JSPROP_READONLY, JSI_Console::getConsole, NULL }, { 0, 0, 0, 0, 0 }, }; // Allow scripts to output to the global log file JSBool WriteLog(JSContext* context, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* UNUSEDPARAM(rval)) { if (argc < 1) return JS_FALSE; CStr logMessage; for (int i = 0; i < (int)argc; i++) { try { CStr arg = g_ScriptingHost.ValueToString( argv[i] ); logMessage += arg; } catch( PSERROR_Scripting_ConversionFailed ) { // Do nothing. } } // We should perhaps unicodify (?) the logger at some point. LOG( NORMAL, logMessage ); return JS_TRUE; } JSBool getEntityByHandle( JSContext* context, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval ) { assert( argc >= 1 ); i32 handle; try { handle = g_ScriptingHost.ValueToInt( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; JS_ReportError( context, "Invalid handle" ); return( JS_TRUE ); } HEntity* v = g_EntityManager.getByHandle( (u16)handle ); if( !v ) { JS_ReportError( context, "No entity occupying handle: %d", handle ); *rval = JSVAL_NULL; return( JS_TRUE ); } JSObject* entity = JS_NewObject( context, &JSI_Entity::JSI_class, NULL, NULL ); JS_SetPrivate( context, entity, v ); *rval = OBJECT_TO_JSVAL( entity ); return( JS_TRUE ); } JSBool getEntityTemplate( JSContext* context, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval ) { assert( argc >= 1 ); CStrW templateName; try { templateName = g_ScriptingHost.ValueToUCString( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; JS_ReportError( context, "Invalid template identifier" ); return( JS_TRUE ); } CBaseEntity* v = g_EntityTemplateCollection.getTemplate( templateName ); if( !v ) { *rval = JSVAL_NULL; JS_ReportError( context, "No such template: %ls", (const wchar_t*)templateName ); return( JS_TRUE ); } JSObject* baseEntity = JS_NewObject( context, &JSI_BaseEntity::JSI_class, NULL, NULL ); JS_SetPrivate( context, baseEntity, v ); *rval = OBJECT_TO_JSVAL( baseEntity ); return( JS_TRUE ); } JSBool setTimeout( JSContext* context, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* UNUSEDPARAM(rval) ) { assert( argc >= 2 ); size_t delay; try { delay = g_ScriptingHost.ValueToInt( argv[1] ); } catch( ... ) { JS_ReportError( context, "Invalid timer parameters" ); return( JS_TRUE ); } switch( JS_TypeOfValue( context, argv[0] ) ) { case JSTYPE_STRING: { CStr16 fragment = g_ScriptingHost.ValueToUCString( argv[0] ); g_Scheduler.pushTime( delay, fragment, JS_GetScopeChain( context ) ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( context, argv[0] ); g_Scheduler.pushTime( delay, fn, JS_GetScopeChain( context ) ); return( JS_TRUE ); } default: JS_ReportError( context, "Invalid timer script" ); return( JS_TRUE ); } } JSBool setInterval( JSContext* context, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* UNUSEDPARAM(rval) ) { assert( argc >= 2 ); size_t first, interval; try { first = g_ScriptingHost.ValueToInt( argv[1] ); if( argc == 3 ) { // toDo, first, interval interval = g_ScriptingHost.ValueToInt( argv[2] ); } else { // toDo, interval (first = interval) interval = first; } } catch( ... ) { JS_ReportError( context, "Invalid timer parameters" ); return( JS_TRUE ); } switch( JS_TypeOfValue( context, argv[0] ) ) { case JSTYPE_STRING: { CStr16 fragment = g_ScriptingHost.ValueToUCString( argv[0] ); g_Scheduler.pushInterval( first, interval, fragment, JS_GetScopeChain( context ) ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( context, argv[0] ); g_Scheduler.pushInterval( first, interval, fn, JS_GetScopeChain( context ) ); return( JS_TRUE ); } default: JS_ReportError( context, "Invalid timer script" ); return( JS_TRUE ); } } JSBool cancelInterval( JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* UNUSEDPARAM(rval) ) { g_Scheduler.m_abortInterval = true; return( JS_TRUE ); } JSBool getGUIGlobal( JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* rval ) { *rval = OBJECT_TO_JSVAL( g_GUI.GetScriptObject() ); return( JS_TRUE ); } JSBool getGlobal( JSContext* UNUSEDPARAM(context), JSObject* globalObject, unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* rval ) { *rval = OBJECT_TO_JSVAL( globalObject ); return( JS_TRUE ); } extern CStr g_CursorName; // from main.cpp JSBool setCursor(JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* UNUSEDPARAM(rval)) { if (argc != 1) { assert(! "Invalid parameter count to setCursor"); return JS_FALSE; } g_CursorName = g_ScriptingHost.ValueToString(argv[0]); return JS_TRUE; } +// From main.cpp +extern void StartGame(); +extern CGameAttributes g_GameAttributes; + +JSBool startGame(JSContext* cx, JSObject* UNUSEDPARAM(globalObject), unsigned int argc, jsval* argv, jsval* rval) +{ + if (argc == 1) + { + JSObject *obj; + if (JS_ConvertArguments(cx, 1, argv, "o", &obj)) + { + g_GameAttributes.FillFromJS(cx, obj); + } + } + StartGame(); + *rval=BOOLEAN_TO_JSVAL(JS_TRUE); + return JS_TRUE; +} + +extern void EndGame(); +JSBool endGame(JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* UNUSEDPARAM(rval)) +{ + EndGame(); + return JS_TRUE; +} extern void kill_mainloop(); // from main.cpp JSBool exitProgram(JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* UNUSEDPARAM(rval)) { kill_mainloop(); return JS_TRUE; } JSBool crash(JSContext* UNUSEDPARAM(context), JSObject* UNUSEDPARAM(globalObject), unsigned int UNUSEDPARAM(argc), jsval* UNUSEDPARAM(argv), jsval* UNUSEDPARAM(rval)) { MICROLOG(L"Crashing at user's request."); uintptr_t ptr = 0xDEADC0DE; return *(JSBool*) ptr; } Index: ps/trunk/source/scripting/ScriptGlue.h =================================================================== --- ps/trunk/source/scripting/ScriptGlue.h (revision 911) +++ ps/trunk/source/scripting/ScriptGlue.h (revision 912) @@ -1,34 +1,37 @@ #ifndef _SCRIPTGLUE_H_ #define _SCRIPTGLUE_H_ #include "ScriptingHost.h" // Functions to be called from Javascript: JSBool WriteLog(JSContext * context, JSObject * globalObject, unsigned int argc, jsval *argv, jsval *rval); JSBool getEntityByHandle( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); JSBool getEntityTemplate( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); JSBool setTimeout( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); JSBool setInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); JSBool cancelInterval( JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval ); // Returns the sort-of-global object associated with the current GUI JSBool getGUIGlobal(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); // Returns the global object, e.g. for setting global variables. JSBool getGlobal(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); JSBool setCursor(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); +JSBool startGame(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); +JSBool endGame(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); + // Tells the main loop to stop looping JSBool exitProgram(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); // Crashes. JSBool crash(JSContext* context, JSObject* globalObject, unsigned int argc, jsval* argv, jsval* rval); extern JSFunctionSpec ScriptFunctionTable[]; extern JSPropertySpec ScriptGlobalTable[]; #endif Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 911) +++ ps/trunk/source/main.cpp (revision 912) @@ -1,1074 +1,1121 @@ #include "precompiled.h" #include #include #include #include #include #include "sdl.h" #include "ogl.h" #include "detect.h" #include "timer.h" #include "input.h" #include "lib.h" #include "res/res.h" #ifdef _M_IX86 #include "sysdep/ia32.h" // _control87 #endif #include "lib/res/cursor.h" #include "ps/CConsole.h" #include "ps/Game.h" #include "Config.h" #include "MapReader.h" #include "Terrain.h" #include "TextureManager.h" #include "ObjectManager.h" #include "SkeletonAnimManager.h" #include "Renderer.h" +#include "LightEnv.h" #include "Model.h" #include "UnitManager.h" #include "Interact.h" #include "Hotkey.h" #include "BaseEntityCollection.h" #include "Entity.h" #include "EntityHandles.h" #include "EntityManager.h" #include "PathfindEngine.h" #include "Scheduler.h" #include "scripting/ScriptingHost.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "scripting/JSInterface_Camera.h" #include "scripting/JSInterface_Selection.h" #include "scripting/JSInterface_Console.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "ConfigDB.h" #include "CLogger.h" #ifndef NO_GUI #include "gui/GUI.h" #endif #include "sound/CMusicPlayer.h" CConsole* g_Console = 0; extern int conInputHandler(const SDL_Event* ev); // Globals u32 game_ticks; bool keys[SDLK_LAST]; bool mouseButtons[5]; int mouse_x=50, mouse_y=50; int g_xres, g_yres; int g_bpp; int g_freq; bool g_active = true; // flag to disable extended GL extensions until fix found - specifically, crashes // using VBOs on laptop Radeon cards static bool g_NoGLVBO=false; // flag to switch on shadows static bool g_Shadows=false; // flag to switch off pbuffers static bool g_NoPBuffer=true; // flag to switch on fixed frame timing (RC: I'm using this for profiling purposes) static bool g_FixedFrameTiming=false; static bool g_VSync = false; +extern CLightEnv g_LightEnv; + static bool g_EntGraph = false; static float g_Gamma = 1.0f; CGameAttributes g_GameAttributes; extern int game_view_handler(const SDL_Event* ev); static Handle g_Font_Console; // for the console static Handle g_Font_Misc; // random font for miscellaneous things static CMusicPlayer MusicPlayer; CStr g_CursorName = "test"; extern int allow_reload(); extern int dir_add_watch(const char* const dir, bool watch_subdirs); extern void sle(int); extern size_t frameCount; static bool quit = false; // break out of main loop const wchar_t* HardcodedErrorString(int err) { #define E(sym) case sym: return L ## #sym; switch(err) { E(ERR_NO_MEM) E(ERR_FILE_NOT_FOUND) E(ERR_INVALID_HANDLE) E(ERR_INVALID_PARAM) E(ERR_EOF) E(ERR_PATH_NOT_FOUND) E(ERR_VFS_PATH_LENGTH) default: return 0; } } const wchar_t* ErrorString(int err) { // language file not available (yet) if(1) return HardcodedErrorString(err); // TODO: load from language file } static int write_sys_info(); ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); void Testing (void) { g_Console->InsertMessage(L"Testing Function Registration"); } void TestingUnicode (void) { // This looks really broken in my IDE's font g_Console->InsertMessage(L" Ai! laurië lantar lassi súrinen,"); g_Console->InsertMessage(L" yéni únótimë ve rámar aldaron!"); g_Console->InsertMessage(L" Yéni ve lintë yuldar avánier"); g_Console->InsertMessage(L" mi oromardi lissë-miruvóreva"); g_Console->InsertMessage(L" Andúnë pella, Vardo tellumar"); g_Console->InsertMessage(L" nu luini yassen tintilar i eleni"); g_Console->InsertMessage(L" ómaryo airetári-lírinen."); } static int write_sys_info() { get_gfx_info(); struct utsname un; uname(&un); FILE* const f = fopen("../logs/system_info.txt", "w"); if(!f) return -1; // .. OS fprintf(f, "%s %s (%s)\n", un.sysname, un.release, un.version); // .. CPU fprintf(f, "%s, %s", un.machine, cpu_type); if(cpus > 1) fprintf(f, " (x%d)", cpus); if(cpu_freq != 0.0f) { if(cpu_freq < 1e9) fprintf(f, ", %.2f MHz\n", cpu_freq*1e-6); else fprintf(f, ", %.2f GHz\n", cpu_freq*1e-9); } else fprintf(f, "\n"); // .. memory fprintf(f, "%lu MB RAM; %lu MB free\n", tot_mem/MB, avl_mem/MB); // .. graphics card fprintf(f, "%s\n", gfx_card); fprintf(f, "%s\n", gfx_drv_ver); fprintf(f, "%dx%d:%d@%d\n", g_xres, g_yres, g_bpp, g_freq); // .. network name / ips // note: can't use un.nodename because it is for an // "implementation-defined communications network". char hostname[128]; if (gethostname(hostname, sizeof(hostname)) == 0) // make sure it succeeded { fprintf(f, "%s\n", hostname); hostent* host = gethostbyname(hostname); if(host) { struct in_addr** ips = (struct in_addr**)host->h_addr_list; for(int i = 0; ips && ips[i]; i++) fprintf(f, "%s ", inet_ntoa(*ips[i])); fprintf(f, "\n"); } } // Write extensions last, because there are lots of them const char* exts = oglExtList(); if (!exts) exts = "{unknown}"; fprintf(f, "\nSupported extensions: %s\n", exts); fclose(f); return 0; } static int set_vmode(int w, int h, int bpp, bool fullscreen) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); if(!SDL_SetVideoMode(w, h, bpp, SDL_OPENGL|(fullscreen?SDL_FULLSCREEN:0))) return -1; glViewport(0, 0, w, h); #ifndef NO_GUI g_GUI.UpdateResolution(); #endif oglInit(); // required after each mode change if(SDL_SetGamma(g_Gamma, g_Gamma, g_Gamma) < 0) debug_warn("SDL_SetGamma failed"); return 0; } // use_bmp is for when you simply want high-speed output static void WriteScreenshot(bool use_bmp = false) { // determine next screenshot number. // // current approach: increment number until that file doesn't yet exist. // this is fairly slow, but it's typically only done once, since the last // number is cached. binary search shouldn't be necessary. // // known bug: after program restart, holes in the number series are // filled first. example: add 1st and 2nd; [exit] delete 1st; [restart] // add 3rd -> it gets number 1, not 3. // could fix via enumerating all files, but it's not worth it ATM. char fn[VFS_MAX_PATH]; const char* file_format; if(use_bmp) file_format = "screenshots/screenshot%04d.bmp"; else file_format = "screenshots/screenshot%04d.png"; static int next_num = 1; do sprintf(fn, file_format, next_num++); while(vfs_exists(fn)); const int w = g_xres, h = g_yres; const int bpp = 24; const size_t size = w * h * bpp; void* img = mem_alloc(size); glReadPixels(0, 0, w, h, use_bmp?GL_BGR:GL_RGB, GL_UNSIGNED_BYTE, img); if(tex_write(fn, w, h, bpp, use_bmp?TEX_BGR:0, img) < 0) debug_warn("WriteScreenshot: tex_write failed"); mem_free(img); } // HACK: Let code from other files (i.e. the scripting system) quit void kill_mainloop() { quit = true; } static int handler(const SDL_Event* ev) { int c; switch(ev->type) { case SDL_ACTIVEEVENT: g_active = ev->active.gain != 0; break; case SDL_MOUSEMOTION: mouse_x = ev->motion.x; mouse_y = ev->motion.y; break; case SDL_KEYDOWN: c = ev->key.keysym.sym; keys[c] = true; break; case SDL_HOTKEYDOWN: switch( ev->user.code ) { case HOTKEY_EXIT: quit = true; break; case HOTKEY_SCREENSHOT: WriteScreenshot(); break; case HOTKEY_PLAYMUSIC: MusicPlayer.open("audio/music/germanic peace 3.ogg"); MusicPlayer.play(); break; default: return( EV_PASS ); } return( EV_HANDLED ); case SDL_KEYUP: c = ev->key.keysym.sym; keys[c] = false; break; case SDL_MOUSEBUTTONDOWN: c = ev->button.button; if( c < 5 ) mouseButtons[c] = true; else debug_warn("SDL mouse button defs changed; fix mouseButton array def"); break; case SDL_MOUSEBUTTONUP: c = ev->button.button; if( c < 5 ) mouseButtons[c] = false; else debug_warn("SDL mouse button defs changed; fix mouseButton array def"); break; } return EV_PASS; } +extern void StartGame(); +void RenderNoCull(); +void StartGame() +{ + g_Game=new CGame(); + g_Game->Initialize(&g_GameAttributes); +} + +void EndGame() +{ + delete g_Game; + g_Game=NULL; +} + ///////////////////////////////////////////////////////////////////////////////////////////// // RenderNoCull: render absolutely everything to a blank frame to force renderer // to load required assets void RenderNoCull() { g_Renderer.BeginFrame(); - g_Game->GetView()->RenderNoCull(); + if (g_Game) + g_Game->GetView()->RenderNoCull(); g_Renderer.FlushFrame(); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); g_Renderer.EndFrame(); } static void Render() { MICROLOG(L"begin frame"); // start new frame g_Renderer.BeginFrame(); // switch on wireframe for terrain if we want it //g_Renderer.SetTerrainRenderMode( SOLID ); // (PT: If this is done here, the W key doesn't work) - g_Game->GetView()->Render(); - - MICROLOG(L"flush frame"); - g_Renderer.FlushFrame(); - - glPushAttrib( GL_ENABLE_BIT ); - glDisable( GL_LIGHTING ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_DEPTH_TEST ); - - if( g_EntGraph ) + if (g_Game) { - glColor3f( 1.0f, 0.0f, 1.0f ); + g_Game->GetView()->Render(); - MICROLOG(L"render entities"); - g_EntityManager.renderAll(); // <-- collision outlines, pathing routes - } + MICROLOG(L"flush frame"); + g_Renderer.FlushFrame(); - g_Mouseover.renderSelectionOutlines(); - g_Selection.renderSelectionOutlines(); + glPushAttrib( GL_ENABLE_BIT ); + glDisable( GL_LIGHTING ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_DEPTH_TEST ); + + if( g_EntGraph ) + { + glColor3f( 1.0f, 0.0f, 1.0f ); - glPopAttrib(); + MICROLOG(L"render entities"); + g_EntityManager.renderAll(); // <-- collision outlines, pathing routes + } + + g_Mouseover.renderSelectionOutlines(); + g_Selection.renderSelectionOutlines(); + + glPopAttrib(); + } + else + g_Renderer.FlushFrame(); MICROLOG(L"render fonts"); // overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); #ifndef NO_GUI // Temp GUI message GeeTODO glLoadIdentity(); glTranslatef(10, 60, 0); unifont_bind(g_Font_Misc); glwprintf( L"%hs", g_GUI.TEMPmessage.c_str() ); glLoadIdentity(); MICROLOG(L"render GUI"); g_GUI.Draw(); #endif // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); unifont_bind(g_Font_Misc); glColor4f(1.0f, 0.8f, 0.0f, 1.0f); glLoadIdentity(); glTranslatef(10.0f, 10.0f, 0.0f); glScalef(1.0, -1.0, 1.0); glwprintf( L"%d FPS", fps); unifont_bind(g_Font_Console); glLoadIdentity(); MICROLOG(L"render console"); g_Console->Render(); - g_Mouseover.renderOverlays(); - g_Selection.renderOverlays(); + if (g_Game) + { + g_Mouseover.renderOverlays(); + g_Selection.renderOverlays(); + } // Draw the cursor (or set the Windows cursor, on Windows) cursor_draw(g_CursorName); // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); MICROLOG(L"end frame"); g_Renderer.EndFrame(); } +static void InitDefaultGameAttributes() +{ + g_GameAttributes.m_MapFile="test01.pmp"; +} static void ParseArgs(int argc, char* argv[]) { for(int i = 1; i < argc; i++) { // this arg isn't an option; skip if(argv[i][0] != '-') continue; char* name = argv[i]+1; // no leading '-' // switch first letter of option name switch(argv[i][1]) { case 'c': if(strcmp(name, "conf") == 0) { if(argc-i >= 1) // at least one arg left { i++; char* arg = argv[i]; char* equ = strchr(arg, '='); if(equ) { *equ = 0; g_ConfigDB.CreateValue(CFG_SYSTEM, arg) ->m_String = (equ+1); } } } break; case 'e': g_EntGraph = true; break; case 'f': if(strncmp(name, "fixedframe", 10) == 0) g_FixedFrameTiming=true; break; case 'g': if(strncmp(name, "g=", 2) == 0) { g_Gamma = (float)atof(argv[i] + 3); if(g_Gamma == 0.0f) g_Gamma = 1.0f; } break; case 'm': if(strncmp(name, "m=", 2) == 0) g_GameAttributes.m_MapFile = argv[i]+3; break; case 'n': if(strncmp(name, "novbo", 5) == 0) g_ConfigDB.CreateValue(CFG_SYSTEM, "novbo")->m_String="true"; else if(strncmp(name, "nopbuffer", 9) == 0) g_NoPBuffer = true; break; case 's': if(strncmp(name, "shadows", 7) == 0) g_ConfigDB.CreateValue(CFG_SYSTEM, "shadows")->m_String="true"; break; case 'v': g_ConfigDB.CreateValue(CFG_SYSTEM, "vsync")->m_String="true"; break; case 'x': if(strncmp(name, "xres=", 6) == 0) g_ConfigDB.CreateValue(CFG_SYSTEM, "xres")->m_String=argv[i]+6; break; case 'y': if(strncmp(name, "yres=", 6) == 0) g_ConfigDB.CreateValue(CFG_SYSTEM, "yres")->m_String=argv[i]+6; break; } // switch } CConfigValue *val; if ((val=g_ConfigDB.GetValue(CFG_SYSTEM, "xres"))) val->GetInt(g_xres); if ((val=g_ConfigDB.GetValue(CFG_SYSTEM, "yres"))) val->GetInt(g_yres); if ((val=g_ConfigDB.GetValue(CFG_SYSTEM, "vsync"))) val->GetBool(g_VSync); if ((val=g_ConfigDB.GetValue(CFG_SYSTEM, "novbo"))) val->GetBool(g_NoGLVBO); if ((val=g_ConfigDB.GetValue(CFG_SYSTEM, "shadows"))) val->GetBool(g_Shadows); LOG(NORMAL, "g_x/yres is %dx%d", g_xres, g_yres); } static void InitScripting() { // Create the scripting host. This needs to be done before the GUI is created. new ScriptingHost; // It would be nice for onLoad code to be able to access the setTimeout() calls. new CScheduler; // Register the JavaScript interfaces with the runtime JSI_Entity::init(); JSI_BaseEntity::init(); JSI_IGUIObject::init(); JSI_GUITypes::init(); JSI_Vector3D::init(); JSI_Selection::init(); JSI_Camera::init(); JSI_Console::init(); } static void InitVfs(char* argv0) { // set current directory to "$game_dir/data". // this is necessary because it is otherwise unknown, // especially if run from a shortcut / symlink. // // "../data" is relative to the executable (in "$game_dir/system"). // // rationale for data/ being root: untrusted scripts must not be // allowed to overwrite critical game (or worse, OS) files. // the VFS prevents any accesses to files above this directory. int err = file_rel_chdir(argv0, "../data"); if(err < 0) throw err; // display_startup_error(L"error setting current directory.\n"\ // L"argv[0] is probably incorrect. please start the game via command-line."); vfs_mount("", "mods/official", 0); vfs_mount("screenshots", "screenshots", 0); } static void psInit() { g_Font_Console = unifont_load("console"); g_Font_Misc = unifont_load("verdana16"); // HACK: Cache some other fonts, because the resource manager doesn't unifont_load("palatino12"); g_Console->SetSize(0, g_yres-600.f, (float)g_xres, 600.f); g_Console->m_iFontHeight = unifont_linespacing(g_Font_Console); g_Console->m_iFontOffset = 9; loadHotkeys(); #ifndef NO_GUI // GUI uses VFS, so this must come after VFS init. g_GUI.Initialize(); g_GUI.LoadXMLFile("gui/test/styles.xml"); g_GUI.LoadXMLFile("gui/test/hello.xml"); g_GUI.LoadXMLFile("gui/test/sprite1.xml"); #endif oal_Init(); } static void psShutdown() { #ifndef NO_GUI g_GUI.Destroy(); delete &g_GUI; #endif delete g_Console; // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(NULL); // close down Xerces if it was loaded CXeromyces::Terminate(); MusicPlayer.release(); oal_Shutdown(); } extern u64 PREVTSC; static void Shutdown() { psShutdown(); // Must delete g_GUI before g_ScriptingHost - delete g_Game; - + if (g_Game) + delete g_Game; + delete &g_Scheduler; delete &g_Mouseover; delete &g_Selection; delete &g_ScriptingHost; delete &g_Pathfinder; - delete &g_EntityManager; + // Managed by CWorld + // delete &g_EntityManager; delete &g_EntityTemplateCollection; // destroy actor related stuff delete &g_UnitMan; delete &g_ObjMan; delete &g_SkelAnimMan; // destroy terrain related stuff delete &g_TexMan; // destroy renderer delete &g_Renderer; delete &g_ConfigDB; } static void Init(int argc, char* argv[]) { MICROLOG(L"In init"); // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(5874); #ifdef _MSC_VER u64 TSC=rdtsc(); debug_out( "----------------------------------------\n"\ "MAIN (elapsed = %f ms)\n"\ "----------------------------------------\n", (TSC-PREVTSC)/2e9*1e3); PREVTSC=TSC; #endif MICROLOG(L"init lib"); lib_init(); // set 24 bit (float) FPU precision for faster divides / sqrts #ifdef _M_IX86 _control87(_PC_24, _MCW_PC); #endif // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. MICROLOG(L"init vfs"); InitVfs(argv[0]); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in psInit()) g_Console = new CConsole(); MICROLOG(L"detect"); detect(); MICROLOG(L"init sdl"); // init SDL if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOG(ERROR, "SDL library initialization failed: %s", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); // preferred video mode = current desktop settings // (command line params may override these) get_cur_vmode(&g_xres, &g_yres, &g_bpp, &g_freq); MICROLOG(L"init scripting"); InitScripting(); // before GUI MICROLOG(L"init config"); new CConfigDB; g_ConfigDB.SetConfigFile(CFG_SYSTEM, false, "config/system.cfg"); g_ConfigDB.Reload(CFG_SYSTEM); g_ConfigDB.SetConfigFile(CFG_MOD, true, "config/mod.cfg"); // No point in reloading mod.cfg here - we haven't mounted mods yet g_ConfigDB.SetConfigFile(CFG_USER, true, "config/user.cfg"); // Same thing here; we haven't even started up yet - this will wait until // the profile dir is VFS mounted (or we will do a new SetConfigFile with // a generated profile path) + // We init the defaults here; command line options might want to override + InitDefaultGameAttributes(); ParseArgs(argc, argv); //g_xres = 800; //g_yres = 600; // GUI is notified in set_vmode, so this must come before that. #ifndef NO_GUI new CGUI; #endif CConfigValue *val=g_ConfigDB.GetValue(CFG_SYSTEM, "windowed"); bool windowed=false; if (val) val->GetBool(windowed); #ifdef _WIN32 sle(11340106); #endif MICROLOG(L"set vmode"); if(set_vmode(g_xres, g_yres, 32, !windowed) < 0) { LOG(ERROR, "Could not set %dx%d graphics mode: %s", g_xres, g_yres, SDL_GetError()); throw PSERROR_System_VmodeFailed(); } SDL_WM_SetCaption("0 A.D.", "0 A.D."); write_sys_info(); if(!oglExtAvail("GL_ARB_multitexture") || !oglExtAvail("GL_ARB_texture_env_combine")) { LOG(ERROR, "Required ARB_multitexture or ARB_texture_env_combine extension not available"); throw PSERROR_System_RequiredExtensionsMissing(); } // enable/disable VSync // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. if(oglExtAvail("WGL_EXT_swap_control")) wglSwapIntervalEXT(g_VSync? 1 : 0); #ifdef _MSC_VER u64 CURTSC=rdtsc(); debug_out( "----------------------------------------\n"\ "low-level ready (elapsed = %f ms)\n"\ "----------------------------------------\n", (CURTSC-PREVTSC)/2e9*1e3); PREVTSC=CURTSC; #endif MICROLOG(L"init ps"); psInit(); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_NOPBUFFER,g_NoPBuffer); // create terrain related stuff new CTextureManager; // create actor related stuff new CSkeletonAnimManager; new CObjectManager; new CUnitManager; MICROLOG(L"init renderer"); g_Renderer.Open(g_xres,g_yres,g_bpp); + // Setup default lighting environment. Since the Renderer accesses the + // lighting environment through a pointer, this has to be done before + // the first Frame. + g_LightEnv.m_SunColor=RGBColor(1,1,1); + g_LightEnv.m_Rotation=DEGTORAD(270); + g_LightEnv.m_Elevation=DEGTORAD(45); + g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0); + g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f); + g_Renderer.SetLightEnv(&g_LightEnv); + + // I haven't seen the camera affecting GUI rendering and such, but the + // viewport has to be updated according to the video mode + SViewPort vp; + vp.m_X=0; + vp.m_Y=0; + vp.m_Width=g_xres; + vp.m_Height=g_yres; + g_Renderer.SetViewport(vp); + // This needs to be done after the renderer has loaded all its actors... new CBaseEntityCollection; - new CEntityManager; + // CEntityManager is managed by CWorld + //new CEntityManager; new CPathfindEngine; new CSelectedEntities; new CMouseoverEntities; - // if no map name specified, load test01.pmp (for convenience during - // development. that means loading no map at all is currently impossible. - // is that a problem? - if(!g_GameAttributes.m_MapFile) - g_GameAttributes.m_MapFile = "test01.pmp"; - - MICROLOG(L"start game"); - g_Game=new CGame(); - g_Game->Initialize(&g_GameAttributes); - // Check for heap corruption after every allocation. Very, very slowly. // (And it highlights the allocation just after the one you care about, // so you need to run it again and tell it to break on the one before.) /* extern void memory_debug_extreme_turbo_plus(); memory_debug_extreme_turbo_plus(); _CrtSetBreakAlloc(36367); //*/ // Initialize entities in_add_handler(handler); in_add_handler(game_view_handler); in_add_handler(interactInputHandler); #ifndef NO_GUI in_add_handler(gui_handler); #endif in_add_handler(conInputHandler); in_add_handler(hotkeyInputHandler); // <- Leave this one until after all the others. MICROLOG(L"render blank"); // render everything to a blank frame to force renderer to load everything RenderNoCull(); if (g_FixedFrameTiming) { CCamera &g_Camera=*g_Game->GetView()->GetCamera(); #if 0 // TOPDOWN g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(90)); g_Camera.m_Orientation.SetIdentity(); g_Camera.m_Orientation.RotateX(DEGTORAD(90)); g_Camera.m_Orientation.Translate(CELL_SIZE*250*0.5, 250, CELL_SIZE*250*0.5); #else // std view g_Camera.SetProjection(1.0f,10000.0f,DEGTORAD(20)); g_Camera.m_Orientation.SetXRotation(DEGTORAD(30)); g_Camera.m_Orientation.RotateY(DEGTORAD(-45)); g_Camera.m_Orientation.Translate(350, 350, -275); #endif g_Camera.UpdateFrustum(); } g_Console->RegisterFunc(Testing, L"Testing"); { wchar_t t[] = { 'T',0xE9,'s','t','i','n','g' , 0 }; g_Console->RegisterFunc(TestingUnicode, t); } #ifdef _MSC_VER { u64 CURTSC=rdtsc(); debug_out( "----------------------------------------\n"\ "READY (elapsed = %f ms)\n"\ "----------------------------------------\n", (CURTSC-PREVTSC)/2e9*1e3); PREVTSC=CURTSC; } #endif } // Define MOVIE_RECORD to record your camera motions and run at 30fps, // or MOVIE_CREATE to play them back as fast as possible while generating // millions of screenshots. (It should be a lot faster if you make // WriteScreenshot use BMPs.) //#define MOVIE_RECORD //#define MOVIE_CREATE #if defined(MOVIE_RECORD) || defined(MOVIE_CREATE) const int camera_len = 256; struct { float f[16]; } camera_pos[camera_len]; int camera_pos_off = 0; #endif static void Frame() { MICROLOG(L"In frame"); MusicPlayer.update(); #if defined(MOVIE_RECORD) || defined(MOVIE_CREATE) // Run at precisely 30fps static double last_time; double time = get_time(); const float TimeSinceLastFrame = 1.0f/30.0f; ONCE(last_time = time); last_time += TimeSinceLastFrame; while (time-last_time < 0.0) time = get_time(); extern CCamera g_Camera; #ifdef MOVIE_RECORD memcpy(&camera_pos[camera_pos_off++], g_Camera.m_Orientation._data, 16*4); if (camera_pos_off >= camera_len) { FILE* f = fopen("c:\\0adcamera.dat", "wb"); fwrite(camera_pos, camera_len, 16*4, f); fclose(f); exit(0); } #else // #ifdef MOVIE_RECORD if (camera_pos_off == 0) { FILE* f = fopen("c:\\0adcamera.dat", "rb"); fread(camera_pos, camera_len, 16*4, f); fclose(f); } else if (camera_pos_off >= camera_len) exit(0); else WriteScreenshot(true); memcpy(g_Camera.m_Orientation._data, &camera_pos[camera_pos_off++], 16*4); #endif // #ifdef MOVIE_RECORD #else // #if defined(MOVIE_RECORD) || define(MOVIE_CREATE) // Non-movie code: static double last_time; const double time = get_time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; ONCE(return); // first call: set last_time and return assert(TimeSinceLastFrame >= 0.0f); MICROLOG(L"reload files"); res_reload_changed_files(); #endif // #if defined(MOVIE_RECORD) || define(MOVIE_CREATE) // TODO: limiter in case simulation can't keep up? // const double TICK_TIME = 30e-3; // [s] // const float SIM_UPDATE_INTERVAL = 1.0f / (float)SIM_FRAMERATE; // Simulation runs at 10 fps. MICROLOG(L"input"); in_get_events(); - g_Game->Update(TimeSinceLastFrame); - - if (!g_FixedFrameTiming) - g_Game->GetView()->Update(float(TimeSinceLastFrame)); + if (g_Game) + { + g_Game->Update(TimeSinceLastFrame); + + if (!g_FixedFrameTiming) + g_Game->GetView()->Update(float(TimeSinceLastFrame)); - // TODO Where does GameView end and other things begin? - g_Mouseover.update( TimeSinceLastFrame ); - g_Selection.update(); + // TODO Where does GameView end and other things begin? + g_Mouseover.update( TimeSinceLastFrame ); + g_Selection.update(); + } g_Console->Update(TimeSinceLastFrame); // ugly, but necessary. these are one-shot events, have to be reset. // Spoof mousebuttonup events for the hotkey system SDL_Event spoof; spoof.type = SDL_MOUSEBUTTONUP; spoof.button.button = SDL_BUTTON_WHEELUP; if( mouseButtons[SDL_BUTTON_WHEELUP] ) hotkeyInputHandler( &spoof ); spoof.button.button = SDL_BUTTON_WHEELDOWN; if( mouseButtons[SDL_BUTTON_WHEELDOWN] ) hotkeyInputHandler( &spoof ); mouseButtons[SDL_BUTTON_WHEELUP] = false; mouseButtons[SDL_BUTTON_WHEELDOWN] = false; if(g_active) { MICROLOG(L"render"); Render(); MICROLOG(L"swap buffers"); SDL_GL_SwapBuffers(); MICROLOG(L"finished render"); } // inactive; relinquish CPU for a little while // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored else SDL_Delay(10); calc_fps(); if (g_FixedFrameTiming && frameCount==100) quit=true; } // Choose when to override the standard exception handling (i.e. opening // the debugger when available, or crashing when not) with one that // generates the crash log/dump. #if defined(_WIN32) && ( defined(NDEBUG) || defined(TESTING) ) # define CUSTOM_EXCEPTION_HANDLER #endif #ifdef CUSTOM_EXCEPTION_HANDLER #include #endif int main(int argc, char* argv[]) { MICROLOG(L"In main"); #ifdef CUSTOM_EXCEPTION_HANDLER __try { #endif MICROLOG(L"Init"); Init(argc, argv); while(!quit) { MICROLOG(L"(Simulation) Frame"); Frame(); } MICROLOG(L"Shutdown"); Shutdown(); #ifdef CUSTOM_EXCEPTION_HANDLER } __except(debug_main_exception_filter(GetExceptionCode(), GetExceptionInformation())) { } #endif exit(0); } Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 911) +++ ps/trunk/source/renderer/Renderer.cpp (revision 912) @@ -1,1200 +1,1204 @@ /////////////////////////////////////////////////////////////////////////////// // // Name: Renderer.cpp // Author: Rich Cross // Contact: rich@wildfiregames.com // // Description: OpenGL renderer class; a higher level interface // on top of OpenGL to handle rendering the basic visual games // types - terrain, models, sprites, particles etc // /////////////////////////////////////////////////////////////////////////////// #include "precompiled.h" #include #include #include #include "Renderer.h" #include "TransparencyRenderer.h" #include "Terrain.h" #include "Matrix3D.h" #include "Camera.h" #include "PatchRData.h" #include "Texture.h" #include "LightEnv.h" #include "CLogger.h" #include "Model.h" #include "ModelDef.h" #include "ogl.h" #include "res/mem.h" #include "res/tex.h" struct TGAHeader { // header stuff unsigned char iif_size; unsigned char cmap_type; unsigned char image_type; unsigned char pad[5]; // origin : unused unsigned short d_x_origin; unsigned short d_y_origin; // dimensions unsigned short width; unsigned short height; // bits per pixel : 16, 24 or 32 unsigned char bpp; // image descriptor : Bits 3-0: size of alpha channel // Bit 4: must be 0 (reserved) // Bit 5: should be 0 (origin) // Bits 6-7: should be 0 (interleaving) unsigned char image_descriptor; }; static bool saveTGA(const char* filename,int width,int height,int bpp,unsigned char* data) { FILE* fp=fopen(filename,"wb"); if (!fp) return false; // fill file header TGAHeader header; header.iif_size=0; header.cmap_type=0; header.image_type=2; memset(header.pad,0,sizeof(header.pad)); header.d_x_origin=0; header.d_y_origin=0; header.width=(unsigned short)width; header.height=(unsigned short)height; header.bpp=(unsigned char)bpp; header.image_descriptor=(bpp==32) ? 8 : 0; if (fwrite(&header,sizeof(TGAHeader),1,fp)!=1) { fclose(fp); return false; } // write data if (fwrite(data,width*height*bpp/8,1,fp)!=1) { fclose(fp); return false; } // return success .. fclose(fp); return true; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::CRenderer() { m_Width=0; m_Height=0; m_Depth=0; m_FrameCounter=0; m_TerrainRenderMode=SOLID; m_ModelRenderMode=SOLID; m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0; m_ShadowMap=0; m_Options.m_NoVBO=false; m_Options.m_Shadows=true; m_Options.m_ShadowColor=RGBAColor(0.4f,0.4f,0.4f,1.0f); for (uint i=0;im_Width || height>m_Height)) { glDeleteTextures(1,(GLuint*) &m_ShadowMap); m_ShadowMap=0; } m_Width=width; m_Height=height; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO=value; break; case OPT_SHADOWS: m_Options.m_Shadows=value; break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; case OPT_SHADOWS: return m_Options.m_Shadows; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionColor: set color renderer option void CRenderer::SetOptionColor(enum Option opt,const RGBAColor& value) { switch (opt) { case OPT_SHADOWCOLOR: m_Options.m_ShadowColor=value; break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionColor: get color renderer option const RGBAColor& CRenderer::GetOptionColor(enum Option opt) const { static const RGBAColor defaultColor(1.0f,1.0f,1.0f,1.0f); switch (opt) { case OPT_SHADOWCOLOR: return m_Options.m_ShadowColor; } return defaultColor; } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { // bump frame counter m_FrameCounter++; // zero out all the per-frame stats m_Stats.Reset(); // calculate coefficients for terrain and unit lighting m_SHCoeffsUnits.Clear(); m_SHCoeffsTerrain.Clear(); if (m_LightEnv) { CVector3D dirlight; m_LightEnv->GetSunDirection(dirlight); m_SHCoeffsUnits.AddDirectionalLight(dirlight,m_LightEnv->m_SunColor); m_SHCoeffsTerrain.AddDirectionalLight(dirlight,m_LightEnv->m_SunColor); m_SHCoeffsUnits.AddAmbientLight(m_LightEnv->m_UnitsAmbientColor); m_SHCoeffsTerrain.AddAmbientLight(m_LightEnv->m_TerrainAmbientColor); } // init per frame stuff m_ShadowRendered=false; m_ShadowBound.SetEmpty(); } ////////////////////////////////////////////////////////////////////////////////////////// // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(u32 color) { m_ClearColor[0]=float(color & 0xff)/255.0f; m_ClearColor[1]=float((color>>8) & 0xff)/255.0f; m_ClearColor[2]=float((color>>16) & 0xff)/255.0f; m_ClearColor[3]=float((color>>24) & 0xff)/255.0f; } static int RoundUpToPowerOf2(int x) { if ((x & (x-1))==0) return x; int d=x; while (d & (d-1)) { d&=(d-1); } return d<<1; } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildTransformation: build transformation matrix from a position and standard basis vectors void CRenderer::BuildTransformation(const CVector3D& pos,const CVector3D& right,const CVector3D& up, const CVector3D& dir,CMatrix3D& result) { // build basis result._11=right.X; result._12=right.Y; result._13=right.Z; result._14=0; result._21=up.X; result._22=up.Y; result._23=up.Z; result._24=0; result._31=dir.X; result._32=dir.Y; result._33=dir.Z; result._34=0; result._41=0; result._42=0; result._43=0; result._44=1; CMatrix3D trans; trans.SetTranslation(-pos.X,-pos.Y,-pos.Z); result=result*trans; } /////////////////////////////////////////////////////////////////////////////////////////////////// // ConstructLightTransform: build transformation matrix for light at given position casting in // given direction void CRenderer::ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result) { CVector3D right,up; CVector3D viewdir=m_Camera.m_Orientation.GetIn(); if (fabs(dir.Y)>0.01f) { up=CVector3D(viewdir.X,(-dir.Z*viewdir.Z-dir.X*dir.X)/dir.Y,viewdir.Z); } else { up=CVector3D(0,0,1); } up.Normalize(); right=dir.Cross(up); right.Normalize(); BuildTransformation(pos,right,up,dir,result); } /////////////////////////////////////////////////////////////////////////////////////////////////// // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void CRenderer::CalcShadowMatrices() { int i; // get bounds of shadow casting objects const CBound& bounds=m_ShadowBound; // get centre of bounds CVector3D centre; bounds.GetCentre(centre); // get sunlight direction CVector3D lightdir; // ??? RC using matrix rotation to get sun direction? m_LightEnv->GetSunDirection(lightdir); // ??? RC more optimal light placement? CVector3D lightpos=centre-(lightdir*1000); // make light transformation matrix ConstructLightTransform(lightpos,lightdir,m_LightTransform); // transform shadow bounds to light space, calculate near and far bounds CVector3D vp[8]; m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]); float left=vp[0].X; float right=vp[0].X; float top=vp[0].Y; float bottom=vp[0].Y; float znear=vp[0].Z; float zfar=vp[0].Z; for (i=1;i<8;i++) { if (vp[i].Xright) right=vp[i].X; if (vp[i].Ytop) top=vp[i].Y; if (vp[i].Zzfar) zfar=vp[i].Z; } // shift near and far clip planes slightly to avoid artifacts with points // exactly on the clip planes znear=(znearright1) right1=vp[i].X; if (vp[i].Ytop1) top1=vp[i].Y; if (vp[i].Zzfar1) zfar1=vp[i].Z; } left=max(left,left1); right=min(right,right1); top=min(top,top1); bottom=max(bottom,bottom1); znear=max(znear,znear1); zfar=min(zfar,zfar1); #endif // experimental stuff, do not use .. // TODO, RC - desperately need to improve resolution here if we're using shadow maps; investigate // feasibility of PSMs // transform light space bounds to image space - TODO, RC: safe to just use 3d transform here? CVector4D vph[8]; for (i=0;i<8;i++) { CVector4D tmp(vp[i].X,vp[i].Y,vp[i].Z,1.0f); m_LightProjection.Transform(tmp,vph[i]); vph[i][0]/=vph[i][2]; vph[i][1]/=vph[i][2]; } // find the two points furthest apart int p0,p1; float maxdistsqrd=-1; for (i=0;i<8;i++) { for (int j=i+1;j<8;j++) { float dx=vph[i][0]-vph[j][0]; float dy=vph[i][1]-vph[j][1]; float distsqrd=dx*dx+dy*dy; if (distsqrd>maxdistsqrd) { p0=i; p1=j; maxdistsqrd=distsqrd; } } } // now we want to rotate the camera such that the longest axis lies the diagonal at 45 degrees - // get angle between points float angle=atan2(vph[p0][1]-vph[p1][1],vph[p0][0]-vph[p1][0]); float rotation=-angle; // build rotation matrix CQuaternion quat; quat.FromAxisAngle(lightdir,rotation); CMatrix3D m; quat.ToMatrix(m); // rotate up vector by given rotation CVector3D up(m_LightTransform._21,m_LightTransform._22,m_LightTransform._23); up=m.Rotate(up); up.Normalize(); // TODO, RC - required?? // rebuild right vector CVector3D rightvec; rightvec=lightdir.Cross(up); rightvec.Normalize(); BuildTransformation(lightpos,rightvec,up,lightdir,m_LightTransform); // retransform points m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]); m_LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]); m_LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[1].Z),vp[7]); // recalculate projection left=vp[0].X; right=vp[0].X; top=vp[0].Y; bottom=vp[0].Y; znear=vp[0].Z; zfar=vp[0].Z; for (i=1;i<8;i++) { if (vp[i].Xright) right=vp[i].X; if (vp[i].Ytop) top=vp[i].Y; if (vp[i].Zzfar) zfar=vp[i].Z; } // shift near and far clip planes slightly to avoid artifacts with points // exactly on the clip planes znear-=0.01f; zfar+=0.01f; m_LightProjection.SetZero(); m_LightProjection._11=2/(right-left); m_LightProjection._22=2/(top-bottom); m_LightProjection._33=2/(zfar-znear); m_LightProjection._14=-(right+left)/(right-left); m_LightProjection._24=-(top+bottom)/(top-bottom); m_LightProjection._34=-(zfar+znear)/(zfar-znear); m_LightProjection._44=1; #endif } void CRenderer::CreateShadowMap() { // get shadow map size as next power of two up from view width and height m_ShadowMapWidth=m_Width; m_ShadowMapWidth=RoundUpToPowerOf2(m_ShadowMapWidth); m_ShadowMapHeight=m_Height; m_ShadowMapHeight=RoundUpToPowerOf2(m_ShadowMapHeight); // create texture object - initially filled with white, so clamp to edge clamps to correct color glGenTextures(1,(GLuint*) &m_ShadowMap); BindTexture(0,(GLuint) m_ShadowMap); u32 size=m_ShadowMapWidth*m_ShadowMapHeight; u32* buf=new u32[size]; for (uint i=0;i projected light space (-1 to 1) texturematrix=texturematrix*m_LightTransform; // transform world -> light space glMatrixMode(GL_TEXTURE); glLoadMatrixf(&texturematrix._11); glMatrixMode(GL_MODELVIEW); CPatchRData::ApplyShadowMap(m_ShadowMap); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); } void CRenderer::RenderPatches() { // switch on wireframe if we need it if (m_TerrainRenderMode==WIREFRAME) { MICROLOG(L"wireframe on"); glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); } // render all the patches, including blend pass MICROLOG(L"render patch submissions"); RenderPatchSubmissions(); if (m_TerrainRenderMode==WIREFRAME) { // switch wireframe off again MICROLOG(L"wireframe off"); glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_TerrainRenderMode==EDGED_FACES) { /* // TODO, RC - fix this // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. glDepthMask(0); SetTexture(0,0); glColor4f(1,1,1,0.35f); glLineWidth(2.0f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // .. and some client states glEnableClientState(GL_VERTEX_ARRAY); uint i; // render each patch in wireframe for (i=0;iGetRenderData(); patchdata->RenderStreams(STREAM_POS); } // set color for outline glColor3f(0,0,1); glLineWidth(4.0f); // render outline of each patch for (i=0;iGetRenderData(); patchdata->RenderOutline(); } // .. and switch off the client states glDisableClientState(GL_VERTEX_ARRAY); // .. and restore the renderstates glDisable(GL_BLEND); glDepthMask(1); // restore fill mode, and we're done glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); */ } } void CRenderer::RenderModelSubmissions() { // set up texture environment for base pass - modulate texture and primary color glActiveTexture(GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR); // pass one through as alpha; transparent textures handled specially by CTransparencyRenderer glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_ONE); // janwas found BUG: INVALID ENUM glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); // setup client states glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // render models CModelRData::RenderModels(STREAM_POS|STREAM_COLOR|STREAM_UV0); // switch off client states glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); } void CRenderer::RenderModels() { // switch on wireframe if we need it if (m_ModelRenderMode==WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); } // render all the models RenderModelSubmissions(); if (m_ModelRenderMode==WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_ModelRenderMode==EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. glDepthMask(0); SetTexture(0,0); glColor4f(1,1,1,0.75f); glLineWidth(1.0f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // .. and some client states glEnableClientState(GL_VERTEX_ARRAY); // render each model CModelRData::RenderModels(STREAM_POS); // .. and switch off the client states glDisableClientState(GL_VERTEX_ARRAY); // .. and restore the renderstates glDisable(GL_BLEND); glDepthMask(1); // restore fill mode, and we're done glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // SortModelsByTexture: sorting class used for batching models with identical textures struct SortModelsByTexture { typedef CModel* SortObj; bool operator()(const SortObj& lhs,const SortObj& rhs) { return lhs->GetTexture()GetTexture() ? true : false; } }; /////////////////////////////////////////////////////////////////////////////////////////////////// // FlushFrame: force rendering of any batched objects void CRenderer::FlushFrame() { // sort all the models by texture // std::sort(m_Models.begin(),m_Models.end(),SortModelsByTexture()); // sort all the transparent stuff MICROLOG(L"sorting"); g_TransparencyRenderer.Sort(); if (!m_ShadowRendered) { if (m_Options.m_Shadows) { MICROLOG(L"render shadows"); RenderShadowMap(); } // clear buffers MICROLOG(L"clear buffer"); glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } // render submitted patches and models MICROLOG(L"render patches"); RenderPatches(); MICROLOG(L"render models"); RenderModels(); if (m_Options.m_Shadows && !m_ShadowRendered) { MICROLOG(L"apply shadows"); ApplyShadowMap(); } m_ShadowRendered=true; // call on the transparency renderer to render all the transparent stuff MICROLOG(L"render transparent"); g_TransparencyRenderer.Render(); // empty lists MICROLOG(L"empty lists"); g_TransparencyRenderer.Clear(); CPatchRData::ClearSubmissions(); CModelRData::ClearSubmissions(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end; implicitly flushes batched objects void CRenderer::EndFrame() { FlushFrame(); g_Renderer.SetTexture(0,0); static bool once=false; if (!once && glGetError()) { LOG(ERROR,"CRenderer::EndFrame: GL errors occurred"); once=true; } } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetCamera: setup projection and transform of camera and adjust viewport to current view void CRenderer::SetCamera(CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj=camera.GetProjection(); glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); - const SViewPort& vp=camera.GetViewPort(); - glViewport(vp.m_X,vp.m_Y,vp.m_Width,vp.m_Height); + SetViewport(camera.GetViewPort()); m_Camera=camera; } +void CRenderer::SetViewport(const SViewPort &vp) +{ + glViewport(vp.m_X,vp.m_Y,vp.m_Width,vp.m_Height); +} + void CRenderer::Submit(CPatch* patch) { CPatchRData::Submit(patch); } void CRenderer::Submit(CModel* model) { if (1 /*ThisModelCastsShadows*/) { m_ShadowBound+=model->GetBounds(); } CModelRData::Submit(model); } void CRenderer::Submit(CSprite* sprite) { } void CRenderer::Submit(CParticleSys* psys) { } void CRenderer::Submit(COverlay* overlay) { } void CRenderer::RenderPatchSubmissions() { // switch on required client states MICROLOG(L"enable vertex array"); glEnableClientState(GL_VERTEX_ARRAY); MICROLOG(L"enable color array"); glEnableClientState(GL_COLOR_ARRAY); MICROLOG(L"enable tex coord array"); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // render everything MICROLOG(L"render base splats"); CPatchRData::RenderBaseSplats(); MICROLOG(L"render blend splats"); CPatchRData::RenderBlendSplats(); // switch off all client states MICROLOG(L"disable tex coord array"); glDisableClientState(GL_TEXTURE_COORD_ARRAY); MICROLOG(L"disable color array"); glDisableClientState(GL_COLOR_ARRAY); MICROLOG(L"disable vertex array"); glDisableClientState(GL_VERTEX_ARRAY); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // LoadTexture: try and load the given texture; set clamp/repeat flags on texture object if necessary bool CRenderer::LoadTexture(CTexture* texture,u32 wrapflags) { Handle h=texture->GetHandle(); if (h) { // already tried to load this texture, nothing to do here - just return success according // to whether this is a valid handle or not return h==0xffffffff ? true : false; } else { h=tex_load(texture->GetName()); if (!h) { LOG(ERROR,"LoadTexture failed on \"%s\"",(const char*) texture->GetName()); texture->SetHandle(0xffffffff); return false; } else { int tw,th; tex_info(h, &tw, &th, NULL, NULL, NULL); tw&=(tw-1); th&=(th-1); if (tw || th) { texture->SetHandle(0xffffffff); LOG(ERROR,"LoadTexture failed on \"%s\" : not a power of 2 texture",(const char*) texture->GetName()); return false; } else { BindTexture(0,tex_id(h)); tex_upload(h,GL_LINEAR_MIPMAP_LINEAR); if (wrapflags) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapflags); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapflags); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } texture->SetHandle(h); return true; } } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit,GLuint tex) { #if 0 glActiveTexture(GL_TEXTURE0+unit); if (tex==m_ActiveTextures[unit]) return; if (tex) { glBindTexture(GL_TEXTURE_2D,tex); if (!m_ActiveTextures[unit]) { glEnable(GL_TEXTURE_2D); } } else if (m_ActiveTextures[unit]) { glDisable(GL_TEXTURE_2D); } m_ActiveTextures[unit]=tex; #endif glActiveTexture(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D,tex); if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } m_ActiveTextures[unit]=tex; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetTexture: set the given unit to reference the given texture; pass a null texture to disable texturing on any unit void CRenderer::SetTexture(int unit,CTexture* texture) { if (texture) { Handle h=texture->GetHandle(); BindTexture(unit,tex_id(h)); } else { BindTexture(unit,0); } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IsTextureTransparent: return true if given texture is transparent, else false - note texture must be loaded // beforehand bool CRenderer::IsTextureTransparent(CTexture* texture) { if (!texture) return false; Handle h=texture->GetHandle(); if (h<=0) return false; int fmt; int bpp; tex_info(h, NULL, NULL, &fmt, &bpp, NULL); if (bpp==24 || fmt == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) { return false; } return true; } inline void CopyTriple(unsigned char* dst,const unsigned char* src) { dst[0]=src[0]; dst[1]=src[1]; dst[2]=src[2]; } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture .. need to add // validation that all maps are the same size bool CRenderer::LoadAlphaMaps(const char* fnames[]) { Handle textures[NumAlphaMaps]; int i; for (i=0;i #include "res/res.h" #include "ogl.h" #include "Camera.h" #include "Frustum.h" #include "PatchRData.h" #include "ModelRData.h" #include "SHCoeffs.h" #include "Terrain.h" #include "Singleton.h" // necessary declarations class CCamera; class CPatch; class CSprite; class CParticleSys; class COverlay; class CMaterial; class CLightEnv; class CTexture; class CTerrain; // rendering modes enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; // stream flags #define STREAM_POS 0x01 #define STREAM_NORMAL 0x02 #define STREAM_COLOR 0x04 #define STREAM_UV0 0x08 #define STREAM_UV1 0x10 #define STREAM_UV2 0x20 #define STREAM_UV3 0x40 #define STREAM_POSTOUV0 0x80 ////////////////////////////////////////////////////////////////////////////////////////// // SVertex3D: simple 3D vertex declaration struct SVertex3D { float m_Position[3]; float m_TexCoords[2]; unsigned int m_Color; }; ////////////////////////////////////////////////////////////////////////////////////////// // SVertex2D: simple 2D vertex declaration struct SVertex2D { float m_Position[2]; float m_TexCoords[2]; unsigned int m_Color; }; // access to sole renderer object #define g_Renderer CRenderer::GetSingleton() /////////////////////////////////////////////////////////////////////////////////////////// // CRenderer: base renderer class - primary interface to the rendering engine class CRenderer : public Singleton { public: // various enumerations and renderer related constants enum { NumAlphaMaps=14 }; enum { MaxTextureUnits=16 }; enum Option { OPT_NOVBO, OPT_NOPBUFFER, OPT_SHADOWS, OPT_SHADOWCOLOR }; // stats class - per frame counts of number of draw calls, poly counts etc struct Stats { // set all stats to zero void Reset() { memset(this,0,sizeof(*this)); } // add given stats to this stats Stats& operator+=(const Stats& rhs) { m_Counter++; m_DrawCalls+=rhs.m_DrawCalls; m_TerrainTris+=rhs.m_TerrainTris; m_ModelTris+=rhs.m_ModelTris; m_BlendSplats+=rhs.m_BlendSplats; return *this; } // count of the number of stats added together u32 m_Counter; // number of draw calls per frame - total DrawElements + Begin/End immediate mode loops u32 m_DrawCalls; // number of terrain triangles drawn u32 m_TerrainTris; // number of (non-transparent) model triangles drawn u32 m_ModelTris; // number of splat passes for alphamapping u32 m_BlendSplats; }; public: // constructor, destructor CRenderer(); ~CRenderer(); // open up the renderer: performs any necessary initialisation bool Open(int width,int height,int depth); // shutdown the renderer: performs any necessary cleanup void Close(); // resize renderer view void Resize(int width,int height); // set/get boolean renderer option void SetOptionBool(enum Option opt,bool value); bool GetOptionBool(enum Option opt) const; // set/get RGBA color renderer option void SetOptionColor(enum Option opt,const RGBAColor& value); const RGBAColor& GetOptionColor(enum Option opt) const; // return view width int GetWidth() const { return m_Width; } // return view height int GetHeight() const { return m_Height; } // return view aspect ratio float GetAspect() const { return float(m_Width)/float(m_Height); } // signal frame start void BeginFrame(); // force rendering of any batched objects void FlushFrame(); // signal frame end : implicitly flushes batched objects void EndFrame(); // set color used to clear screen in BeginFrame() void SetClearColor(u32 color); // return current frame counter int GetFrameCounter() const { return m_FrameCounter; } // set camera used for subsequent rendering operations; includes viewport, projection and modelview matrices void SetCamera(CCamera& camera); + // set the viewport + void SetViewport(const SViewPort &); + // submission of objects for rendering; the passed matrix indicating the transform must be scoped such that it is valid beyond // the call to frame end, as must the object itself void Submit(CPatch* patch); void Submit(CModel* model); void Submit(CSprite* sprite); void Submit(CParticleSys* psys); void Submit(COverlay* overlay); // basic primitive rendering operations in 2 and 3D; handy for debugging stuff, but also useful in // editor tools (eg for highlighting specific terrain patches) // note: // * all 3D vertices specified in world space // * primitive operations rendered immediatedly, never batched // * primitives rendered in current material (set via SetMaterial) void RenderLine(const SVertex2D* vertices); void RenderLineLoop(int len,const SVertex2D* vertices); void RenderTri(const SVertex2D* vertices); void RenderQuad(const SVertex2D* vertices); void RenderLine(const SVertex3D* vertices); void RenderLineLoop(int len,const SVertex3D* vertices); void RenderTri(const SVertex3D* vertices); void RenderQuad(const SVertex3D* vertices); // set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer, // so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering) void SetLightEnv(CLightEnv* lightenv) { m_LightEnv=lightenv; } // set the mode to render subsequent terrain patches void SetTerrainRenderMode(ERenderMode mode) { m_TerrainRenderMode=mode; } // get the mode to render subsequent terrain patches ERenderMode GetTerrainRenderMode() const { return m_TerrainRenderMode; } // set the mode to render subsequent models void SetModelRenderMode(ERenderMode mode) { m_ModelRenderMode=mode; } // get the mode to render subsequent models ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; } // try and load the given texture bool LoadTexture(CTexture* texture,u32 wrapflags); // set the given unit to reference the given texture; pass a null texture to disable texturing on any unit; // active texture unit always set to given unit on exit void SetTexture(int unit,CTexture* texture); // bind a GL texture object to active unit void BindTexture(int unit,GLuint tex); // query transparency of given texture bool IsTextureTransparent(CTexture* texture); // load the default set of alphamaps; return false if any alphamap fails to load, true otherwise bool LoadAlphaMaps(const char* fnames[]); // return stats accumulated for current frame const Stats& GetStats() { return m_Stats; } protected: friend class CVertexBuffer; friend class CPatchRData; friend class CModelRData; friend class CTransparencyRenderer; // update renderdata of everything submitted void UpdateSubmittedObjectData(); // patch rendering stuff void RenderPatchSubmissions(); void RenderPatches(); // model rendering stuff void RenderModelSubmissions(); void RenderModels(); // shadow rendering stuff void CreateShadowMap(); void RenderShadowMap(); void ApplyShadowMap(); void BuildTransformation(const CVector3D& pos,const CVector3D& right,const CVector3D& up, const CVector3D& dir,CMatrix3D& result); void ConstructLightTransform(const CVector3D& pos,const CVector3D& lightdir,CMatrix3D& result); void CalcShadowMatrices(); void CalcShadowBounds(CBound& bounds); // RENDERER DATA: // view width int m_Width; // view height int m_Height; // view depth (bpp) int m_Depth; // frame counter int m_FrameCounter; // current terrain rendering mode ERenderMode m_TerrainRenderMode; // current model rendering mode ERenderMode m_ModelRenderMode; // current view camera CCamera m_Camera; // color used to clear screen in BeginFrame float m_ClearColor[4]; // submitted object lists for batching std::vector m_Sprites; std::vector m_ParticleSyses; std::vector m_Overlays; // current lighting setup CLightEnv* m_LightEnv; // current spherical harmonic coefficients (for unit lighting), derived from lightenv CSHCoeffs m_SHCoeffsUnits; // current spherical harmonic coefficients (for terrain lighting), derived from lightenv CSHCoeffs m_SHCoeffsTerrain; // handle of composite alpha map (all the alpha maps packed into one texture) u32 m_CompositeAlphaMap; // handle of shadow map u32 m_ShadowMap; // width, height of shadow map u32 m_ShadowMapWidth,m_ShadowMapHeight; // object space bound of shadow casting objects CBound m_ShadowBound; // per-frame flag: has the shadow map been rendered this frame? bool m_ShadowRendered; // projection matrix of shadow casting light CMatrix3D m_LightProjection; // transformation matrix of shadow casting light CMatrix3D m_LightTransform; // coordinates of each (untransformed) alpha map within the packed texture struct { float u0,u1,v0,v1; } m_AlphaMapCoords[NumAlphaMaps]; // card capabilities struct Caps { bool m_VBO; bool m_TextureBorderClamp; bool m_GenerateMipmaps; } m_Caps; // renderer options struct Options { bool m_NoVBO; bool m_Shadows; RGBAColor m_ShadowColor; } m_Options; // build card cap bits void EnumCaps(); // per-frame renderer stats Stats m_Stats; // active textures on each unit GLuint m_ActiveTextures[MaxTextureUnits]; }; #endif Index: ps/trunk/source/lib/res/h_mgr.cpp =================================================================== --- ps/trunk/source/lib/res/h_mgr.cpp (revision 911) +++ ps/trunk/source/lib/res/h_mgr.cpp (revision 912) @@ -1,630 +1,630 @@ // handle manager // // Copyright (c) 2003 Jan Wassenberg // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // Contact info: // Jan.Wassenberg@stud.uni-karlsruhe.de // http://www.stud.uni-karlsruhe.de/~urkt/ #include "precompiled.h" #include "lib.h" #include "h_mgr.h" #include "mem.h" #include #include // CHAR_BIT #include #include #include // std::bad_alloc // rationale // // why fixed size control blocks, instead of just allocating dynamically? // it is expected that resources be created and freed often. this way is // much nicer to the memory manager. defining control blocks larger than // the allotted space is caught by h_alloc (made possible by the vtbl builder // storing control block size). it is also efficient to have all CBs in an // more or less contiguous array (see below). // // why a manager, instead of a simple pool allocator? // we need a central list of resources for freeing at exit, checking if a // resource has already been loaded (for caching), and when reloading. // may as well keep them in an array, rather than add a list and index. // // handle // // 0 = invalid handle value // < 0 is an error code (we assume < 0 <==> MSB is set - // true for 1s and 2s complement and sign-magnitude systems) // fields: // (shift value = # bits between LSB and field LSB. // may be larger than the field type - only shift Handle vars!) // - tag (1-based) ensures the handle references a certain resource instance. // (field width determines maximum unambiguous resource allocs) #define TAG_BITS 32 const uint TAG_SHIFT = 0; const u32 TAG_MASK = 0xffffffff; // safer than (1 << 32) - 1 // - index (0-based) of control block in our array. // (field width determines maximum currently open handles) #define IDX_BITS 16 const uint IDX_SHIFT = 32; const u32 IDX_MASK = (1l << IDX_BITS) - 1; // make sure both fields fit within a Handle variable cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT); // return the handle's index field (always non-negative). // no error checking! static inline u32 h_idx(const Handle h) { return (u32)((h >> IDX_SHIFT) & IDX_MASK) - 1; } // return the handle's tag field. // no error checking! static inline u32 h_tag(const Handle h) { return (u32)((h >> TAG_SHIFT) & TAG_MASK); } // build a handle from index and tag. // can't fail. static inline Handle handle(const u32 _idx, const u32 tag) { const u32 idx = _idx+1; assert(idx <= IDX_MASK && tag <= TAG_MASK && "handle: idx or tag too big"); // somewhat clunky, but be careful with the shift: // *_SHIFT may be larger than its field's type. Handle h_idx = idx & IDX_MASK; h_idx <<= IDX_SHIFT; Handle h_tag = tag & TAG_MASK; h_tag <<= TAG_SHIFT; Handle h = h_idx | h_tag; assert(h > 0); return h; } // // internal per-resource-instance data // // determines maximum number of references to a resource. static const uint REF_BITS = 8; -static const u32 REF_MAX = 1ul << REF_BITS; +static const u32 REF_MAX = (1ul << REF_BITS)-1; static const uint TYPE_BITS = 8; // a handle's idx field isn't stored in its HDATA entry (not needed); // to save space, these should take its place, i.e. they should fit in IDX_BITS. // if not, ignore + comment out this assertion. cassert(REF_BITS + TYPE_BITS <= IDX_BITS); // chosen so that all current resource structs are covered, // and so sizeof(HDATA) is a power of 2 (for more efficient array access // and array page usage). static const size_t HDATA_USER_SIZE = 44+64; // 64 bytes // TODO: not anymore, fix later struct HDATA { uintptr_t key; u32 tag : TAG_BITS; // smaller bitfields combined into 1 u32 refs : REF_BITS; u32 type_idx : TYPE_BITS; u32 keep_open : 1; // regardless of refs, do not actually release the resource // (i.e. call dtor) when the handle is h_free-d. // set by h_alloc; reset on exit and by housekeeping. H_Type type; const char* fn; u8 user[HDATA_USER_SIZE]; }; // max data array entries. compared to last_in_use => signed. static const i32 hdata_cap = 1ul << IDX_BITS; // allocate entries as needed so as not to waste memory // (hdata_cap may be large). deque-style array of pages // to balance locality, fragmentation, and waste. static const size_t PAGE_SIZE = 4096; static const uint hdata_per_page = PAGE_SIZE / sizeof(HDATA); static const uint num_pages = hdata_cap / hdata_per_page; static HDATA* pages[num_pages]; // these must be signed, because there won't always be a valid // first or last element. static i32 first_free = -1; // don't want to scan array every h_alloc static i32 last_in_use = -1; // don't search unused entries // error checking strategy: // all handles passed in go through h_data(Handle, Type) // get an array entry (array is non-contiguous). // // fails (returns 0) if idx is out of bounds, or if accessing a new page // for the first time, and there's not enough memory to allocate it. // // also used by h_data, and alloc_idx to find a free entry. // // beware of conflict with h_data_any_type: // our i32 param silently converts to its Handle (= i64) param. static HDATA* h_data(const i32 idx) { // don't compare against last_in_use - this is called before allocating // new entries, and to check if the next (but possibly not yet valid) // entry is free. tag check protects against using unallocated entries. if(idx < 0 || idx >= hdata_cap) return 0; HDATA*& page = pages[idx / hdata_per_page]; if(!page) { page = (HDATA*)calloc(PAGE_SIZE, 1); if(!page) return 0; } return &page[idx % hdata_per_page]; } // get HDATA for the given handle. verifies the handle // isn't invalid or an error code, and checks the tag field. // used by the few functions callable for any handle type, e.g. h_filename. static HDATA* h_data_any_type(const Handle h) { #ifdef PARANOIA check_heap(); #endif // invalid, or an error code if(h <= 0) return 0; i32 idx = h_idx(h); // this function is only called for existing handles. // they'd also fail the tag check below, but bail out here // already to avoid needlessly allocating that entry's page. if(idx > last_in_use) return 0; HDATA* hd = h_data(idx); if(!hd) return 0; // note: tag = 0 marks unused entries => is invalid u32 tag = h_tag(h); if(tag == 0 || tag != hd->tag) return 0; return hd; } // get HDATA for the given handle, also checking handle type. // used by most functions accessing handle data. static HDATA* h_data(const Handle h, const H_Type type) { HDATA* hd = h_data_any_type(h); if(!hd) return 0; // h_alloc makes sure type isn't 0, so no need to check that here. if(hd->type != type) return 0; return hd; } void h_mgr_shutdown(void) { // close open handles for(i32 i = 0; i < last_in_use; i++) { HDATA* hd = h_data(i); if(hd) { // somewhat messy, but this only happens on cleanup. // better than an additional h_free(i32 idx) version though. Handle h = handle(i, hd->tag); // disable caching; we need to release the resource now. hd->keep_open = 0; h_free(h, hd->type); } } // free HDATA array for(uint j = 0; j < num_pages; j++) { free(pages[j]); pages[j] = 0; } } // idx and hd are undefined if we fail. // called by h_alloc only. static int alloc_idx(i32& idx, HDATA*& hd) { // we already know the first free entry if(first_free != -1) { idx = first_free; hd = h_data(idx); } // need to look for a free entry, or alloc another else { // look for an unused entry for(idx = 0; idx <= last_in_use; idx++) { hd = h_data(idx); assert(hd); // can't fail - idx is valid // found one - done if(!hd->tag) goto have_idx; } // add another if(last_in_use >= hdata_cap) { assert(!"alloc_idx: too many open handles (increase IDX_BITS)"); return -1; } idx = last_in_use+1; // just incrementing idx would start it at 1 hd = h_data(idx); if(!hd) return ERR_NO_MEM; // can't fail for any other reason - idx is checked above. { // VC6 goto fix bool is_unused = !hd->tag; assert(is_unused && "alloc_idx: invalid last_in_use"); } have_idx:; } // check if next entry is free HDATA* hd2 = h_data(idx+1); if(hd2 && hd2->tag == 0) first_free = idx+1; else first_free = -1; if(idx > last_in_use) last_in_use = idx; return 0; } static int free_idx(i32 idx) { if(first_free == -1 || idx < first_free) first_free = idx; return 0; } int h_free(Handle& h, H_Type type) { HDATA* hd = h_data(h, type); if(!hd) return ERR_INVALID_HANDLE; // only decrement if refcount not already 0. if(hd->refs > 0) hd->refs--; // still references open or caching requests it stays - do not release. if(hd->refs > 0 || hd->keep_open) return 0; // actually release the resource (call dtor, free control block). // h_alloc makes sure type != 0; if we get here, it still is H_VTbl* vtbl = hd->type; // call its destructor // note: H_TYPE_DEFINE currently always defines a dtor, but play it safe if(vtbl->dtor) vtbl->dtor(hd->user); free((void*)hd->fn); memset(hd, 0, sizeof(HDATA)); i32 idx = h_idx(h); free_idx(idx); return 0; } static int type_validate(H_Type type) { int err = ERR_INVALID_PARAM; if(!type) { debug_warn("h_alloc: type is 0"); goto fail; } if(type->user_size > HDATA_USER_SIZE) { debug_warn("h_alloc: type's user data is too large for HDATA"); goto fail; } if(type->name == 0) { debug_warn("h_alloc: type's name field is 0"); goto fail; } // success err = 0; fail: return err; } // any further params are passed to type's init routine Handle h_alloc(H_Type type, const char* fn, uint flags, ...) { ONCE(atexit2(h_mgr_shutdown)); CHECK_ERR(type_validate(type)); Handle h = 0; i32 idx; HDATA* hd; const uint scope = flags & RES_SCOPE_MASK; // get key (either hash of filename, or fn param) uintptr_t key = 0; // not backed by file; fn is the key if(flags & RES_KEY) { key = (uintptr_t)fn; fn = 0; } else { if(fn) key = fnv_hash(fn); } if(key) { // object already loaded? h = h_find(type, key); if(h > 0) { hd = h_data(h, type); if(hd->refs == REF_MAX) { debug_warn("h_alloc: too many references to a handle - increase REF_BITS"); return 0; } hd->refs++; // adding reference; already in-use if(hd->refs > 1) { // debug_warn("adding reference to handle (undermines tag security check)"); return h; } // we re-activated a cached resource. will reset tag below, then bail. } } // generate next tag value. // don't want to do this before the add-reference exit, // so as not to waste tags for often allocated handles. static u32 tag; if(++tag >= TAG_MASK) { debug_warn("h_alloc: tag overflow - allocations are no longer unique."\ "may not notice stale handle reuse. increase TAG_BITS."); tag = 1; } // we are reactivating a closed but cached handle. // need to reset tag, so that copies of the previous // handle can no longer access the resource. // (we don't need to reset the tag in h_free, because // use before this fails due to refs > 0 check in h_user_data). if(h > 0) { hd->tag = tag; return h; } CHECK_ERR(alloc_idx(idx, hd)); // one-time init hd->tag = tag; hd->key = key; hd->type = type; hd->refs = 1; if(scope != RES_TEMP) hd->keep_open = 1; // .. filename is valid - store in hd // note: if the original fn param was a key, it was reset to 0 above. if(fn) { const size_t fn_len = strlen(fn); hd->fn = (const char*)malloc(fn_len+1); strcpy((char*)hd->fn, fn); } else hd->fn = 0; h = handle(idx, tag); // can't fail. H_VTbl* vtbl = type; // init va_list args; va_start(args, flags); if(vtbl->init) vtbl->init(hd->user, args); va_end(args); // reload if(vtbl->reload) { // catch exception to simplify reload funcs - let them use new() int err; try { err = vtbl->reload(hd->user, fn, h); } catch(std::bad_alloc) { err = ERR_NO_MEM; } if(err < 0) { h_free(h, type); return (Handle)err; } } return h; } void* h_user_data(const Handle h, const H_Type type) { HDATA* hd = h_data(h, type); if(!hd) return 0; if(!hd->refs) { // note: resetting the tag is not enough (user might pass in its value) debug_warn("h_user_data: no references to resource (it's cached, but someone is accessing it directly)"); return 0; } return hd->user; } const char* h_filename(const Handle h) { HDATA* hd = h_data_any_type(h); // don't require type check: should be useable for any handle, // even if the caller doesn't know its type. return hd? hd->fn : 0; } int h_reload(const char* fn) { if(!fn) { debug_warn("h_reload: fn = 0"); return ERR_INVALID_PARAM; } const u32 key = fnv_hash(fn); i32 i; // destroy (note: not free!) all handles backed by this file. // do this before reloading any of them, because we don't specify reload // order (the parent resource may be reloaded first, and load the child, // whose original data would leak). for(i = 0; i <= last_in_use; i++) { HDATA* hd = h_data(i); if(hd && hd->key == key) hd->type->dtor(hd->user); } int ret = 0; // now reload all affected handles // TODO: what if too slow to iterate through all handles? for(i = 0; i <= last_in_use; i++) { HDATA* hd = h_data(i); if(!hd || hd->key != key) continue; Handle h = handle(i, hd->tag); int err = hd->type->reload(hd->user, hd->fn, h); // don't stop if an error is encountered - try to reload them all. if(err < 0) { h_free(h, hd->type); ret = err; } } return ret; } // TODO: more efficient search; currently linear Handle h_find(H_Type type, uintptr_t key) { for(i32 i = 0; i < last_in_use; i++) { HDATA* hd = h_data(i); if(hd) if(hd->type == type && hd->key == key) return handle(i, hd->tag); } return -1; } int res_cur_scope; Index: ps/trunk/source/lib/sysdep/debug.h =================================================================== --- ps/trunk/source/lib/sysdep/debug.h (revision 911) +++ ps/trunk/source/lib/sysdep/debug.h (revision 912) @@ -1,29 +1,29 @@ // // logging // // output to the debugger (may take ~1 ms!) extern void debug_out(const char* fmt, ...); // log to memory buffer (fast) #define MICROLOG debug_microlog extern void debug_microlog(const wchar_t *fmt, ...); // // crash notification // // notify the user that an assertion failed. // displays a stack trace with local variables on Windows. // return values: 0 = continue; 1 = suppress; 2 = break // .. or exits the program if the user so chooses. extern int debug_assert_failed(const char* source_file, int line, const char* assert_expr); extern int debug_write_crashlog(const char* file, wchar_t* header, void* context); extern void debug_check_heap(); -extern void debug_break(); \ No newline at end of file +extern void debug_break();