Index: ps/trunk/source/graphics/CinemaTrack.h =================================================================== --- ps/trunk/source/graphics/CinemaTrack.h (revision 3331) +++ ps/trunk/source/graphics/CinemaTrack.h (revision 3332) @@ -1,152 +1,153 @@ #ifndef H_CinemaTracks_H #define H_CinemaTracks_H #define EM_IN 0 #define EM_OUT 1 #define EM_INOUT 2 #define EM_OUTIN 3 #define ES_DEFAULT 0 #define ES_GROWTH 1 #define ES_EXPO 2 #define ES_CIRCLE 3 #define ES_SINE 4 #include #include + +#include "CStr.h" #include "NUSpline.h" /* Andrew (aka pyrolink) Contact: ajdecker1022@msn.com desc: contains various functions used for cinematic camera tracks Note: There are some differences between the common 'things' of this and the Atlas version. The Play functions for both are essentially the same, but Atlas does not support queued tracks. The difference between the Track Manager and Cinema Manager is the track manager is for adding and deleting tracks to the list, in the editor. The Cinema Manager is for taking care of the queued up Tracks, i.e. when they are to be destroyed/added. */ class CVector3D; class CVector4D; class CCamera; -class CStr; //For loading data class CCinemaData { public: CCinemaData() {} ~CCinemaData() {} float m_TotalDuration; //X=x rotation in degrees...etc CVector3D m_TotalRotation; //Distortion variables float m_GrowthCount; float m_Growth; float m_Switch; int m_mode; int m_style; }; class CCinemaPath : public CCinemaData { public: CCinemaPath(CCinemaData data); CCinemaPath() { DistStylePtr = NULL; DistModePtr = NULL; m_Spline.Init(); } ~CCinemaPath() { DistStylePtr = NULL; DistModePtr = NULL; } float m_TimeElapsed; void UpdateSplineEq(); //Resets Rotation-Must call before MoveToPointAt()!!! void ResetRotation(float t); //sets camera position to calculated point on spline void MoveToPointAt(float t); //Distortion mode functions-change how ratio is passed to distortion style functions float EaseIn(float t); float EaseOut(float t); float EaseInOut(float t); float EaseOutIn(float t); //Distortion style functions float EaseDefault(float t); float EaseGrowth(float t); float EaseExpo(float t); float EaseCircle(float t); float EaseSine(float t); float (CCinemaPath::*DistStylePtr)(float ratio); float (CCinemaPath::*DistModePtr)(float ratio); public: //Used when time and position are already set (usually from loaded file) void AddNode(const CVector3D &pos, float timePeriod) { m_Spline.AddNode(pos, timePeriod); } void PushNode() { m_Spline.Node.push_back( SplineData() ); } void InsertNode(const int index, const CVector3D &pos, float timePeriod) { m_Spline.InsertNode(index, pos, timePeriod); } void RemoveNode(const int index) { m_Spline.RemoveNode(index); } void UpdateNodeTime(const int index, float time) { m_Spline.Node[index].Distance = time; } void UpdateNodePosition(const int index, const CVector3D &pos) { m_Spline.Node[index].Position = pos; } void DrawSpline(CVector4D RGBA, int smoothness); int GetNodeCount() { return m_Spline.NodeCount; } CVector3D GetNodePosition(const int index) { return m_Spline.Node[index].Position; } float GetDuration() { return m_Spline.MaxDistance; } //Called when nodes have been added void UpdateSpline() { m_Spline.BuildSpline(); } void SetSpline( TNSpline spline ) { m_Spline = spline; } private: TNSpline m_Spline; }; class CCinemaTrack { public: CCinemaTrack() {} ~CCinemaTrack() {} std::vector m_Paths; std::vector::iterator m_CPA; //current path selected CVector3D m_StartRotation; void AddPath(CCinemaData path, TNSpline spline); bool Validate(); //DOES NOT set CPA to Paths.begin(). Returns-false indicates it's finished, //true means it's still playing. bool Play(float DeltaTime); }; //Class for in game playing of cinematics class CCinemaManager { public: CCinemaManager() {} ~CCinemaManager() {} std::list m_TrackQueue; std::map m_Tracks; int LoadTracks(); //Loads tracks from file //Adds track to list of being played. (Called by triggers?) void AddTrack(bool queue, CStr Track); bool Update(float DeltaTime); }; #endif Index: ps/trunk/source/graphics/GameView.cpp =================================================================== --- ps/trunk/source/graphics/GameView.cpp (revision 3331) +++ ps/trunk/source/graphics/GameView.cpp (revision 3332) @@ -1,805 +1,806 @@ #include "precompiled.h" #include "Terrain.h" #include "Renderer.h" #include "GameView.h" #include "Game.h" #include "Camera.h" #include "Interact.h" #include "Matrix3D.h" #include "MathUtil.h" #include "Renderer.h" #include "Terrain.h" #include "LightEnv.h" #include "HFTracer.h" #include "TextureManager.h" #include "ObjectManager.h" #include "LOSManager.h" #include "EntityOrders.h" #include "Bound.h" #include "Pyrogenesis.h" #include "Hotkey.h" #include "ConfigDB.h" #include "Loader.h" #include "Profile.h" #include "ps/LoaderThunks.h" #include "ps/Globals.h" +#include "renderer/WaterManager.h" #include "Quaternion.h" #include "Unit.h" #include "Model.h" #include "Projectile.h" #include "input.h" #include "lib.h" #include "timer.h" float g_MaxZoomHeight=350.0f; //note: Max terrain height is this minus YMinOffset float g_YMinOffset=15.0f; extern int g_xres, g_yres; static CVector3D cameraBookmarks[10]; static bool bookmarkInUse[10] = { false, false, false, false, false, false, false, false, false, false }; static i8 currentBookmark = -1; CGameView::CGameView(CGame *pGame): m_pGame(pGame), m_pWorld(pGame->GetWorld()), m_Camera(), m_ViewScrollSpeed(60), m_ViewRotateSensitivity(0.002f), m_ViewRotateSensitivityKeyboard(1.0f), m_ViewRotateAboutTargetSensitivity(0.010f), m_ViewRotateAboutTargetSensitivityKeyboard(2.0f), m_ViewDragSensitivity(0.5f), m_ViewZoomSensitivityWheel(16.0f), m_ViewZoomSensitivity(256.0f), m_ViewZoomSmoothness(0.02f), m_ViewSnapSmoothness(0.02f), m_CameraDelta(), m_CameraPivot(), m_ZoomDelta(0) { SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; m_Camera.SetViewPort(&vp); 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); m_UnitView=NULL; m_UnitAttach=NULL; - + ONCE( ScriptingInit(); ); } CGameView::~CGameView() { UnloadResources(); } void CGameView::ScriptingInit() { AddMethod("startCustomSelection", 0); AddMethod("endCustomSelection", 0); CJSObject::ScriptingInit("GameView"); } int CGameView::Initialize(CGameAttributes* UNUSED(pAttribs)) { CFG_GET_SYS_VAL( "view.scroll.speed", Float, m_ViewScrollSpeed ); CFG_GET_SYS_VAL( "view.rotate.speed", Float, m_ViewRotateSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.speed", Float, m_ViewRotateSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.rotate.abouttarget.speed", Float, m_ViewRotateAboutTargetSensitivity ); CFG_GET_SYS_VAL( "view.rotate.keyboard.abouttarget.speed", Float, m_ViewRotateAboutTargetSensitivityKeyboard ); CFG_GET_SYS_VAL( "view.drag.speed", Float, m_ViewDragSensitivity ); CFG_GET_SYS_VAL( "view.zoom.speed", Float, m_ViewZoomSensitivity ); CFG_GET_SYS_VAL( "view.zoom.wheel.speed", Float, m_ViewZoomSensitivityWheel ); CFG_GET_SYS_VAL( "view.zoom.smoothness", Float, m_ViewZoomSmoothness ); CFG_GET_SYS_VAL( "view.snap.smoothness", Float, 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; return 0; } void CGameView::RegisterInit(CGameAttributes *pAttribs) { // CGameView init RegMemFun1(this, &CGameView::Initialize, pAttribs, L"CGameView init", 1); // previously done by CGameView::InitResources RegMemFun(g_TexMan.GetSingletonPtr(), &CTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 15); RegMemFun(g_ObjMan.GetSingletonPtr(), &CObjectManager::LoadObjects, L"LoadObjects", 1); RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 45); - RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadWaterTextures, L"LoadWaterTextures", 600); + RegMemFun(g_Renderer.GetSingletonPtr()->GetWaterManager(), &WaterManager::LoadWaterTextures, L"LoadWaterTextures", 600); } void CGameView::Render() { g_Renderer.SetCamera(m_Camera); MICROLOG(L"render terrain"); PROFILE_START( "render terrain" ); RenderTerrain(m_pWorld->GetTerrain()); PROFILE_END( "render terrain" ); MICROLOG(L"render models"); PROFILE_START( "render models" ); RenderModels(m_pWorld->GetUnitManager(), m_pWorld->GetProjectileManager()); PROFILE_END( "render models" ); } 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, CProjectileManager *pProjectileMan) { CFrustum frustum=m_Camera.GetFrustum(); CLOSManager* losMgr = m_pWorld->GetLOSManager(); const std::vector& units=pUnitMan->GetUnits(); - for (uint i=0;iGetUnitStatus(units[i], g_Game->GetLocalPlayer()); CModel* model = units[i]->GetModel(); - + model->ValidatePosition(); - + if (frustum.IsBoxVisible(CVector3D(0,0,0), model->GetBounds()) - && status != UNIT_HIDDEN) + && status != UNIT_HIDDEN) { if(units[i] != g_BuildingPlacer.m_actor) { CColor color; if(status == UNIT_VISIBLE) { color = CColor(1.0f, 1.0f, 1.0f, 1.0f); } else // status == UNIT_REMEMBERED { color = CColor(0.7f, 0.7f, 0.7f, 1.0f); } model->SetShadingColor(color); } PROFILE( "submit models" ); SubmitModelRecursive(model); } } const std::vector& projectiles=pProjectileMan->GetProjectiles(); - for (uint i=0;iGetModel(); - + model->ValidatePosition(); - + const CBound& bound = model->GetBounds(); CVector3D centre; bound.GetCentre(centre); - + if (frustum.IsBoxVisible(CVector3D(0,0,0), bound) - && losMgr->GetStatus(centre.X, centre.Z, g_Game->GetLocalPlayer()) & LOS_VISIBLE) + && losMgr->GetStatus(centre.X, centre.Z, g_Game->GetLocalPlayer()) & LOS_VISIBLE) { PROFILE( "submit projectiles" ); SubmitModelRecursive(projectiles[i]->GetModel()); } } } - + //locks the camera in place void CGameView::CameraLock(CVector3D Trans, bool smooth) { m_Terrain=g_Game->GetWorld()->GetTerrain(); float height=m_Terrain->getExactGroundLevel( - m_Camera.m_Orientation._14 + Trans.X, m_Camera.m_Orientation._34 + Trans.Z) + + m_Camera.m_Orientation._14 + Trans.X, m_Camera.m_Orientation._34 + Trans.Z) + g_YMinOffset; //is requested position within limits? if (m_Camera.m_Orientation._24 + Trans.Y <= g_MaxZoomHeight) { if( m_Camera.m_Orientation._24 + Trans.Y >= height) { m_Camera.m_Orientation.Translate(Trans); } else if (m_Camera.m_Orientation._24 + Trans.Y < height && smooth == true) { m_Camera.m_Orientation.Translate(Trans); m_Camera.m_Orientation._24=height; } - } + } } void CGameView::CameraLock(float x, float y, float z, bool smooth) { m_Terrain=g_Game->GetWorld()->GetTerrain(); float height=m_Terrain->getExactGroundLevel( - m_Camera.m_Orientation._14 + x, m_Camera.m_Orientation._34 + z) + + m_Camera.m_Orientation._14 + x, m_Camera.m_Orientation._34 + z) + g_YMinOffset; //is requested position within limits? if (m_Camera.m_Orientation._24 + y <= g_MaxZoomHeight) { if( m_Camera.m_Orientation._24 + y >= height) { m_Camera.m_Orientation.Translate(x, y, z); } else if (m_Camera.m_Orientation._24 + y < height && smooth == true) { m_Camera.m_Orientation.Translate(x, y, z); m_Camera.m_Orientation._24=height; } } } - - + + 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::UnloadResources() { g_ObjMan.UnloadObjects(); g_TexMan.UnloadTerrainTextures(); g_Renderer.UnloadAlphaMaps(); - g_Renderer.UnloadWaterTextures(); + g_Renderer.GetWaterManager()->UnloadWaterTextures(); } 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_app_has_focus) return; - + if (m_UnitView) { // CQuaternion ToRotate = m_UnitViewProp->m_Rotation - m_Camera.m_Orientation.GetRotation(); // ToRotate.m_V.Y += m_UnitView->m_orientation; // m_Camera.m_Orientation.Rotate(ToRotate); // CVector3D CamTrans = m_Camera.m_Orientation.GetTranslation(); // CVector3D ToMove = m_UnitView->m_position + m_UnitViewProp->m_Position - CamTrans; // //Used to fix incorrect positioning // //ToMove.Y += 3.5f; // m_Camera.m_Orientation.Translate(ToMove); m_Camera.m_Orientation.SetYRotation(m_UnitView->m_orientation); m_Camera.m_Orientation.Translate(m_UnitViewProp->GetTransform().GetTranslation()); /*if( !m_UnitView->m_orderQueue.empty()) { CEntityOrder Order = m_UnitView->m_orderQueue.front(); - - if (Order.m_type == CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING || + + if (Order.m_type == CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING || Order.m_type == CEntityOrder::ORDER_GATHER_NOPATHING) { CVector3D Focus = m_Camera.GetFocus(); CVector3D Target; Target.X = (float) Order.m_data[0].location.x; - Target.Y = (float) m_Terrain->getExactGroundLevel( + Target.Y = (float) m_Terrain->getExactGroundLevel( Order.m_data[0].location.x, Order.m_data[0].location.y ); Target.Z = (float) Order.m_data[0].location.y; - + CVector3D Distance = Target - Focus; float length = Distance.GetLength(); - + //We're looking too far out. Correct by moving up or down toward the true position. if (length > 1.0f) { Distance.Normalize(); CVector3D Down(0.0f, -1.0f, 0.0f); //Find opposite side's length float Angle = tan( acosf(Distance.Dot(Down)) ); - float opp = length * Angle; - float hyp = 1 / sinf(Angle) * opp; - + float opp = length * Angle; + float hyp = 1 / sinf(Angle) * opp; + CVector3D CamFace = m_Camera.m_Orientation.GetIn(); CVector3D ToFace = Target - m_Camera.m_Orientation.GetTranslation(); ToFace.Normalize(); CamFace.Normalize(); //Find out if we need to move up or down. Dot product returns cosine. //Larger cos means smaller angle. Smaller angle for target means move down. if ( ToFace.Dot(Down) > CamFace.Dot(Down) ) m_Camera.m_Orientation.Translate(0.0f, -hyp, 0.0f); else m_Camera.m_Orientation.Translate(0.0f, hyp, 0.0f); } } //Check order type } //Is order queue empty? */ - + m_Camera.UpdateFrustum(); return; } - + if (m_UnitAttach) { CVector3D ToMove = m_UnitAttach->m_position - m_Camera.GetFocus(); m_Camera.m_Orientation._14 += ToMove.X; m_Camera.m_Orientation._34 += ToMove.Z; m_Camera.UpdateFrustum(); return; } - + if (!m_TrackManager.m_TrackQueue.empty()) { if(!m_TrackManager.Update(DeltaTime)) { ResetCamera(); } m_Camera.UpdateFrustum(); return; } - + float delta = powf( m_ViewSnapSmoothness, DeltaTime ); m_Camera.m_Orientation.Translate( m_CameraDelta * ( 1.0f - delta ) ); m_CameraDelta *= delta; // 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 = g_mouse_x - mouse_last_x; int mouse_dy = g_mouse_y - mouse_last_y; mouse_last_x = g_mouse_x; mouse_last_y = g_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] || hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { // 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 - + float rightways = 0.0f; if( hotkeys[HOTKEY_CAMERA_ROTATE] ) rightways = (float)mouse_dx * m_ViewRotateSensitivity; if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_LEFT] ) rightways -= m_ViewRotateSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_RIGHT] ) rightways += m_ViewRotateSensitivityKeyboard * DeltaTime; } m_Camera.m_Orientation.RotateY( rightways ); // Up/down rotation float upways = 0.0f; if( hotkeys[HOTKEY_CAMERA_ROTATE] ) upways = (float)mouse_dy * m_ViewRotateSensitivity; if( hotkeys[HOTKEY_CAMERA_ROTATE_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_UP] ) upways -= m_ViewRotateSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_DOWN] ) upways += m_ViewRotateSensitivityKeyboard * DeltaTime; } CQuaternion temp; temp.FromAxisAngle(rightwards, upways); - + 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; // Sideways rotation - + float rightways = (float)mouse_dx * m_ViewRotateAboutTargetSensitivity; - + rotateH.FromAxisAngle( upwards, rightways ); // Up/down rotation float upways = (float)mouse_dy * m_ViewRotateAboutTargetSensitivity; rotateV.FromAxisAngle( rightwards, upways ); 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 ) ) + 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_ROTATE_ABOUT_TARGET_KEYBOARD] ) { // Split up because the keyboard controls use the centre of the screen, not the mouse position. CVector3D origin = m_Camera.m_Orientation.GetTranslation(); CVector3D pivot = m_Camera.GetFocus(); CVector3D delta = origin - pivot; - + CQuaternion rotateH, rotateV; CMatrix3D rotateM; // Sideways rotation - + float rightways = 0.0f; if( hotkeys[HOTKEY_CAMERA_LEFT] ) rightways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_RIGHT] ) rightways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; - + rotateH.FromAxisAngle( upwards, rightways ); // Up/down rotation float upways = 0.0f; if( hotkeys[HOTKEY_CAMERA_UP] ) upways -= m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; if( hotkeys[HOTKEY_CAMERA_DOWN] ) upways += m_ViewRotateAboutTargetSensitivityKeyboard * DeltaTime; - + rotateV.FromAxisAngle( rightwards, upways ); 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 ) ) + 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( pivot + delta ); } - + } else if( hotkeys[HOTKEY_CAMERA_PAN] ) { // Middle-drag to pan //keep camera in bounds CameraLock(rightwards * (m_ViewDragSensitivity * mouse_dx)); CameraLock(forwards_horizontal * (-m_ViewDragSensitivity * mouse_dy)); } - + // Mouse movement if( !hotkeys[HOTKEY_CAMERA_ROTATE] && !hotkeys[HOTKEY_CAMERA_ROTATE_ABOUT_TARGET] ) { if (g_mouse_x >= g_xres-2 && g_mouse_x < g_xres) - CameraLock(rightwards * (m_ViewScrollSpeed * DeltaTime)); + CameraLock(rightwards * (m_ViewScrollSpeed * DeltaTime)); else if (g_mouse_x <= 3 && g_mouse_x >= 0) CameraLock(-rightwards * (m_ViewScrollSpeed * DeltaTime)); - + if (g_mouse_y >= g_yres-2 && g_mouse_y < g_yres) CameraLock(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); else if (g_mouse_y <= 3 && g_mouse_y >= 0) CameraLock(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); } // Keyboard movement (added to mouse movement, so you can go faster if you want) if( hotkeys[HOTKEY_CAMERA_PAN_KEYBOARD] ) { if( hotkeys[HOTKEY_CAMERA_RIGHT] ) CameraLock(rightwards * (m_ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_LEFT] ) CameraLock(-rightwards * (m_ViewScrollSpeed * DeltaTime)); - + if( hotkeys[HOTKEY_CAMERA_DOWN] ) CameraLock(-forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); if( hotkeys[HOTKEY_CAMERA_UP] ) CameraLock(forwards_horizontal * (m_ViewScrollSpeed * DeltaTime)); } // Smoothed zooming (move a certain percentage towards the desired zoom distance every frame) // Note that scroll wheel zooming is event-based and handled in game_view_handler if( hotkeys[HOTKEY_CAMERA_ZOOM_IN] ) m_ZoomDelta += m_ViewZoomSensitivity*DeltaTime; else if( hotkeys[HOTKEY_CAMERA_ZOOM_OUT] ) m_ZoomDelta -= m_ViewZoomSensitivity*DeltaTime; if (fabsf(m_ZoomDelta) > 0.1f) // use a fairly high limit to avoid nasty flickering when zooming { float zoom_proportion = powf(m_ViewZoomSmoothness, DeltaTime); CameraLock(forwards * (m_ZoomDelta * (1.0f-zoom_proportion)), false); m_ZoomDelta *= zoom_proportion; } m_Camera.UpdateFrustum (); } -void CGameView::ToUnitView(CEntity* target, CModel* prop) -{ +void CGameView::ToUnitView(CEntity* target, CModel* prop) +{ if( !target ) { //prevent previous zooming m_ZoomDelta = 0.0f; ResetCamera(); SetCameraTarget( m_UnitView->m_position ); } - m_UnitView = target; + m_UnitView = target; m_UnitViewProp = prop; } 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 between 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(); } InReaction game_view_handler(const SDL_Event* ev) { // put any events that must be processed even if inactive here if(!g_app_has_focus || !g_Game) return IN_PASS; CGameView *pView=g_Game->GetView(); return pView->HandleEvent(ev); } InReaction CGameView::HandleEvent(const SDL_Event* ev) { switch(ev->type) { case SDL_HOTKEYDOWN: switch(ev->user.code) { case HOTKEY_WIREFRAME: if (g_Renderer.GetModelRenderMode() == SOLID) { g_Renderer.SetTerrainRenderMode(EDGED_FACES); g_Renderer.SetModelRenderMode(EDGED_FACES); } else if (g_Renderer.GetModelRenderMode() == EDGED_FACES) { g_Renderer.SetTerrainRenderMode(WIREFRAME); g_Renderer.SetModelRenderMode(WIREFRAME); } else { g_Renderer.SetTerrainRenderMode(SOLID); g_Renderer.SetModelRenderMode(SOLID); } return( IN_HANDLED ); case HOTKEY_CAMERA_RESET_ORIGIN: ResetCamera(); return( IN_HANDLED ); case HOTKEY_CAMERA_RESET: ResetCameraOrientation(); return( IN_HANDLED ); case HOTKEY_CAMERA_ROTATE_ABOUT_TARGET: RotateAboutTarget(); return( IN_HANDLED ); // Mouse wheel must be treated using events instead of polling, // because SDL auto-generates a sequence of mousedown/mouseup events // and we never get to see the "down" state inside Update(). case HOTKEY_CAMERA_ZOOM_WHEEL_IN: m_ZoomDelta += m_ViewZoomSensitivityWheel; return( IN_HANDLED ); - + case HOTKEY_CAMERA_ZOOM_WHEEL_OUT: m_ZoomDelta -= m_ViewZoomSensitivityWheel; return( IN_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] = GetCamera()->GetFocus(); bookmarkInUse[id] = true; } else if( hotkeys[HOTKEY_CAMERA_BOOKMARK_SNAP] ) { if( bookmarkInUse[id] && ( currentBookmark == -1 ) ) { PushCameraTarget( cameraBookmarks[id] ); currentBookmark = id; } } else { if( bookmarkInUse[id] ) SetCameraTarget( cameraBookmarks[id] ); } return( IN_HANDLED ); } } case SDL_HOTKEYUP: switch( ev->user.code ) { case HOTKEY_CAMERA_BOOKMARK_SNAP: if( currentBookmark != -1 ) PopCameraTarget(); currentBookmark = -1; break; default: return( IN_PASS ); } return( IN_HANDLED ); } return IN_PASS; } bool CGameView::JSI_StartCustomSelection( JSContext* UNUSED(context), uint UNUSED(argc), jsval* UNUSED(argv)) { - StartCustomSelection(); + StartCustomSelection(); return true; } bool CGameView::JSI_EndCustomSelection( JSContext* UNUSED(context), uint UNUSED(argc), jsval* UNUSED(argv)) { ResetInteraction(); return true; } Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 3331) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 3332) @@ -1,1027 +1,1028 @@ #include "precompiled.h" #include "lib.h" #include "lib/sdl.h" #include "lib/ogl.h" #include "lib/detect.h" #include "lib/timer.h" #include "lib/input.h" #if CPU_IA32 # include "lib/sysdep/ia32.h" #endif #include "lib/res/res.h" #include "lib/res/sound/snd.h" #include "lib/res/graphics/tex.h" #include "lib/res/graphics/cursor.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/Loader.h" #include "ps/Font.h" #include "ps/CConsole.h" #include "ps/Game.h" #include "ps/Interact.h" #include "ps/Hotkey.h" #include "ps/ConfigDB.h" #include "ps/CLogger.h" #include "ps/i18n.h" #include "ps/Overlay.h" #include "ps/StringConvert.h" #include "ps/Globals.h" #include "ps/Util.h" #include "graphics/MapReader.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "graphics/ObjectManager.h" #include "graphics/SkeletonAnimManager.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/UnitManager.h" #include "graphics/MaterialManager.h" #include "graphics/MeshManager.h" #include "renderer/Renderer.h" +#include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation/BaseEntityCollection.h" #include "simulation/Entity.h" #include "simulation/EntityHandles.h" #include "simulation/EntityManager.h" #include "simulation/PathfindEngine.h" #include "simulation/Scheduler.h" #include "simulation/Projectile.h" #include "scripting/ScriptingHost.h" #include "scripting/GameEvents.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 "scripting/JSCollection.h" #include "scripting/DOMEvent.h" #ifndef NO_GUI # include "gui/scripting/JSInterface_IGUIObject.h" # include "gui/scripting/JSInterface_GUITypes.h" # include "gui/GUI.h" #endif #include "sound/CMusicPlayer.h" #include "sound/JSI_Sound.h" #include "Network/SessionManager.h" #include "Network/Server.h" #include "Network/Client.h" #include "GameSetup/Config.h" #include "Atlas.h" #include "GameSetup.h" ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); #define LOG_CATEGORY "gamesetup" static int SetVideoMode(int w, int h, int bpp, bool fullscreen) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); ulong flags = SDL_OPENGL; if(fullscreen) flags |= SDL_FULLSCREEN; if(!SDL_SetVideoMode(w, h, bpp, flags)) return -1; // Work around a bug in the proprietary Linux ATI driver (at least versions 8.16.20 and 8.14.13). // The driver appears to register its own atexit hook on context creation. // If this atexit hook is called before SDL_Quit destroys the OpenGL context, // some kind of double-free problem causes a crash and lockup in the driver. // Calling SDL_Quit twice appears to be harmless, though, and avoids the problem // by destroying the context *before* the driver's atexit hook is called. // (Note that atexit hooks are guarantueed to be called in reverse order of their registration.) atexit(SDL_Quit); // End work around. 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; } static const uint SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(uint quality) { uint q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn("SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- void GUI_Init() { #ifndef NO_GUI {TIMER("ps_gui_init"); g_GUI.Initialize();} {TIMER("ps_gui_setup_xml"); g_GUI.LoadXMLFile("gui/test/setup.xml");} {TIMER("ps_gui_styles_xml"); g_GUI.LoadXMLFile("gui/test/styles.xml");} {TIMER("ps_gui_sprite1_xml"); g_GUI.LoadXMLFile("gui/test/sprite1.xml");} // Atlas is running, we won't need these GUI pages (for now! // what if Atlas switches to in-game mode?!) // TODO: temporary hack until revised GUI structure is completed. // if(ATLAS_IsRunning()) // return; {TIMER("ps_gui_1"); g_GUI.LoadXMLFile("gui/test/1_init.xml");} {TIMER("ps_gui_2"); g_GUI.LoadXMLFile("gui/test/2_mainmenu.xml");} {TIMER("ps_gui_3"); g_GUI.LoadXMLFile("gui/test/3_loading.xml");} {TIMER("ps_gui_4"); g_GUI.LoadXMLFile("gui/test/4_session.xml");} {TIMER("ps_gui_6"); g_GUI.LoadXMLFile("gui/test/6_subwindows.xml");} {TIMER("ps_gui_6_1"); g_GUI.LoadXMLFile("gui/test/6_1_manual.xml");} {TIMER("ps_gui_6_2"); g_GUI.LoadXMLFile("gui/test/6_2_jukebox.xml");} {TIMER("ps_gui_7"); g_GUI.LoadXMLFile("gui/test/7_atlas.xml");} {TIMER("ps_gui_9"); g_GUI.LoadXMLFile("gui/test/9_global.xml");} #endif } void GUI_Shutdown() { #ifndef NO_GUI g_GUI.Destroy(); delete &g_GUI; #endif } void GUI_ShowMainMenu() { } // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { #ifndef NO_GUI CStrW i18n_description = I18n::translate(pending_task); JSString* js_desc = StringConvert::wstring_to_jsstring(g_ScriptingHost.getContext(), i18n_description); g_ScriptingHost.SetGlobal("g_Progress", INT_TO_JSVAL(percent)); g_ScriptingHost.SetGlobal("g_LoadDescription", STRING_TO_JSVAL(js_desc)); g_GUI.SendEventToAll("progress"); #endif } ///////////////////////////////////////////////////////////////////////////////////////////// // RenderNoCull: render absolutely everything to a blank frame to force renderer // to load required assets static void RenderNoCull() { g_Renderer.BeginFrame(); if (g_Game) g_Game->GetView()->RenderNoCull(); g_Renderer.FlushFrame(); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); g_Renderer.EndFrame(); } void Render() { MICROLOG(L"begin frame"); oglCheck(); #ifndef NO_GUI // HACK: because colour-parsing requires the GUI CStr skystring = "61 193 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring, skycol); g_Renderer.SetClearColor(skycol.Int()); #endif // start new frame g_Renderer.BeginFrame(); oglCheck(); if (g_Game && g_Game->IsGameStarted()) { g_Game->GetView()->Render(); oglCheck(); MICROLOG(L"flush frame"); PROFILE_START( "flush frame" ); g_Renderer.FlushFrame(); PROFILE_END( "flush frame" ); glPushAttrib( GL_ENABLE_BIT ); glDisable( GL_LIGHTING ); glDisable( GL_TEXTURE_2D ); glDisable( GL_DEPTH_TEST ); if( g_EntGraph ) { PROFILE( "render entity overlays" ); glColor3f( 1.0f, 0.0f, 1.0f ); MICROLOG(L"render entities"); g_EntityManager.renderAll(); // <-- collision outlines, pathing routes } PROFILE_START( "render selection" ); glEnable( GL_DEPTH_TEST ); g_Mouseover.renderSelectionOutlines(); g_Selection.renderSelectionOutlines(); glDisable( GL_DEPTH_TEST ); PROFILE_END( "render selection" ); PROFILE_START( "render building placement cursor" ); if( g_BuildingPlacer.m_active ) { //glEnable( GL_DEPTH_TEST ); g_BuildingPlacer.render(); //glDisable( GL_DEPTH_TEST ); } PROFILE_END( "render building placement cursor" ); PROFILE_START( "render health bars" ); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); g_Mouseover.renderHealthBars(); g_Selection.renderHealthBars(); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); PROFILE_END( "render health bars" ); glPopAttrib(); } else { PROFILE_START( "flush frame" ); g_Renderer.FlushFrame(); PROFILE_END( "flush frame" ); } oglCheck(); PROFILE_START( "render fonts" ); 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(); PROFILE_END( "render fonts" ); oglCheck(); #ifndef NO_GUI // Temp GUI message GeeTODO glLoadIdentity(); MICROLOG(L"render GUI"); PROFILE_START( "render gui" ); g_GUI.Draw(); PROFILE_END( "render gui" ); #endif oglCheck(); // 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); // Added -- glEnable(GL_TEXTURE_2D); // -- GL oglCheck(); { PROFILE( "render console" ); glLoadIdentity(); MICROLOG(L"render console"); CFont font("console"); font.Bind(); g_Console->Render(); } oglCheck(); // Profile information PROFILE_START( "render profiling" ); g_ProfileViewer.RenderProfile(); PROFILE_END( "render profiling" ); oglCheck(); if (g_Game && g_Game->IsGameStarted()) { PROFILE( "render selection overlays" ); g_Mouseover.renderOverlays(); g_Selection.renderOverlays(); } oglCheck(); // Draw the cursor (or set the Windows cursor, on Windows) CStr8 cursorName = g_BuildingPlacer.m_active ? "action-build" : g_CursorName; cursor_draw(cursorName, g_mouse_x, g_mouse_y); // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); MICROLOG(L"end frame"); g_Renderer.EndFrame(); oglCheck(); } static void InitScripting() { TIMER("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 CEntity::ScriptingInit(); CBaseEntity::ScriptingInit(); JSI_Sound::ScriptingInit(); CProfileNode::ScriptingInit(); #ifndef NO_GUI JSI_IGUIObject::init(); JSI_GUITypes::init(); #endif JSI_Vector3D::init(); EntityCollection::Init( "EntityCollection" ); SColour::ScriptingInit(); CPlayer::ScriptingInit(); PlayerCollection::Init( "PlayerCollection" ); //CDamageType::ScriptingInit(); CJSComplexPropertyAccessor::ScriptingInit(); // <-- Doesn't really matter which we use, but we know CJSPropertyAccessor is already being compiled for T = CEntity. CScriptEvent::ScriptingInit(); CJSProgressTimer::ScriptingInit(); CProjectile::ScriptingInit(); g_ScriptingHost.DefineConstant( "ORDER_NONE", -1 ); g_ScriptingHost.DefineConstant( "ORDER_GOTO", CEntityOrder::ORDER_GOTO ); g_ScriptingHost.DefineConstant( "ORDER_PATROL", CEntityOrder::ORDER_PATROL ); g_ScriptingHost.DefineConstant( "ORDER_ATTACK", CEntityOrder::ORDER_ATTACK_MELEE ); g_ScriptingHost.DefineConstant( "ORDER_GATHER", CEntityOrder::ORDER_GATHER ); g_ScriptingHost.DefineConstant( "ORDER_HEAL", CEntityOrder::ORDER_HEAL ); g_ScriptingHost.DefineConstant( "ORDER_GENERIC", CEntityOrder::ORDER_GENERIC ); #define REG_JS_CONSTANT(_name) g_ScriptingHost.DefineConstant(#_name, _name) REG_JS_CONSTANT(SDL_BUTTON_LEFT); REG_JS_CONSTANT(SDL_BUTTON_MIDDLE); REG_JS_CONSTANT(SDL_BUTTON_RIGHT); REG_JS_CONSTANT(SDL_BUTTON_WHEELUP); REG_JS_CONSTANT(SDL_BUTTON_WHEELDOWN); #undef REG_JS_CONSTANT CNetMessage::ScriptingInit(); JSI_Camera::init(); JSI_Console::init(); new CGameEvents; } static void InitVfs(const char* argv0) { TIMER("InitVfs"); // set root directory to "$game_dir/data". all relative file paths // passed to file.cpp will be based from this dir. // (we don't set current directory because other libraries may // hijack it). // // "../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. WARN_ERR(file_set_root_dir(argv0, "../data")); vfs_init(); vfs_mount("", "mods/official", VFS_MOUNT_RECURSIVE|VFS_MOUNT_ARCHIVES|VFS_MOUNT_WATCH); vfs_mount("screenshots/", "screenshots"); vfs_mount("profiles/", "profiles", VFS_MOUNT_RECURSIVE); // don't try vfs_display yet: SDL_Init hasn't yet redirected stdout } static void InitPs(bool setup_gui) { // console { TIMER("ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font("console"); g_Console->m_iFontHeight = font.GetLineSpacing(); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 9; } // language and hotkeys { TIMER("ps_lang_hotkeys"); std::string lang = "english"; CFG_GET_SYS_VAL("language", String, lang); I18n::LoadLanguage(lang.c_str()); loadHotkeys(); } // GUI uses VFS, so this must come after VFS init. if (setup_gui) GUI_Init(); } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(interactInputHandler); in_add_handler(conInputHandler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(hotkeyInputHandler); in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { GUI_Shutdown(); delete g_Console; // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(0, g_mouse_x, g_mouse_y); // close down Xerces if it was loaded CXeromyces::Terminate(); // Unload the real language (since it depends on the scripting engine, // which is going to be killed later) and use the English fallback messages I18n::LoadLanguage(NULL); } static void InitRenderer() { TIMER("InitRenderer"); // 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); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTextureManager; // create the material manager new CMaterialManager; new CMeshManager; // 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.SetRotation(DEGTORAD(270)); g_LightEnv.SetElevation(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); } static void InitSDL() { MICROLOG(L"init sdl"); if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOG(ERROR, LOG_CATEGORY, "SDL library initialization failed: %s", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } void EndGame() { if (g_NetServer) { delete g_NetServer; g_NetServer=NULL; } else if (g_NetClient) { delete g_NetClient; g_NetClient=NULL; } delete g_Game; g_Game=NULL; } void Shutdown() { MICROLOG(L"Shutdown"); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost if (g_Game) EndGame(); TIMER_BEGIN("shutdown Scheduler"); delete &g_Scheduler; TIMER_END("shutdown Scheduler"); TIMER_BEGIN("shutdown SessionManager"); delete &g_SessionManager; TIMER_END("shutdown SessionManager"); TIMER_BEGIN("shutdown mouse stuff"); delete &g_Mouseover; delete &g_Selection; delete &g_BuildingPlacer; TIMER_END("shutdown mouse stuff"); TIMER_BEGIN("shutdown Pathfinder"); delete &g_Pathfinder; TIMER_END("shutdown Pathfinder"); // Managed by CWorld // delete &g_EntityManager; TIMER_BEGIN("shutdown game scripting stuff"); delete &g_GameAttributes; delete &g_JSGameEvents; delete &g_EntityTemplateCollection; TIMER_END("shutdown game scripting stuff"); // destroy actor related stuff TIMER_BEGIN("shutdown actor stuff"); delete &g_UnitMan; delete &g_ObjMan; delete &g_SkelAnimMan; delete &g_MaterialManager; delete &g_MeshManager; TIMER_END("shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN("shutdown TexMan"); delete &g_TexMan; TIMER_END("shutdown TexMan"); // destroy renderer TIMER_BEGIN("shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END("shutdown Renderer"); TIMER_BEGIN("shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END("shutdown ScriptingHost"); TIMER_BEGIN("shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END("shutdown ConfigDB"); // Shut down the network loop TIMER_BEGIN("shutdown CSocketBase"); CSocketBase::Shutdown(); TIMER_END("shutdown CSocketBase"); // Really shut down the i18n system. Any future calls // to translate() will crash. TIMER_BEGIN("shutdown I18N"); I18n::Shutdown(); TIMER_END("shutdown I18N"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN("resource modules"); snd_shutdown(); vfs_shutdown(); // must come before h_mgr_shutdown - it frees IO buffers, // which we don't want showing up as leaks. file_shutdown(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); // must come after h_mgr_shutdown - it causes memory // to be freed, which requires this module to still be active. mem_shutdown(); TIMER_END("resource modules"); TIMER_BEGIN("shutdown misc"); timer_display_client_totals(); // should be last, since the above use them debug_shutdown(); delete &g_Logger; delete &g_Profiler; delete &g_ProfileViewer; TIMER_END("shutdown misc"); } // workaround for VC7 EBP-trashing bug, which confuses the stack trace code. #if MSC_VERSION # pragma optimize("", off) #endif void Init(int argc, char* argv[], uint flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; debug_printf("INIT &argc=%p &argv=%p\n", &argc, &argv); MICROLOG(L"Init"); debug_set_thread_name("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add("TIMER"); // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(187); // Query CPU capabilities, possibly set some CPU-dependent flags cpu_init(); // 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"); const char* argv0 = argc? argv[0] : NULL; // ScEd doesn't have a main(argc, argv), and so it has no argv. In that // case, just pass NULL to InitVfs, which will work out the current // directory for itself. InitVfs(argv0); // This must come after VFS init, which sets the current directory // (required for finding our output log files). new CLogger; // Call LoadLanguage(NULL) to initialise the I18n system, but // without loading an actual language file - translate() will // just show the English key text, which is better than crashing // from a null pointer when attempting to translate e.g. error messages. // Real languages can only be loaded when the scripting system has // been initialised. // // this uses LOG and must therefore come after CLogger init. MICROLOG(L"init i18n"); I18n::LoadLanguage(NULL); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); if(setup_vmode) InitSDL(); // preferred video mode = current desktop settings // (command line params may override these) get_cur_vmode(&g_xres, &g_yres, &g_bpp, &g_freq); new CProfileViewer; new CProfileManager; // before any script code MICROLOG(L"init scripting"); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(argc, argv); // setup_gui must be set after CONFIG_Init, so command-line parameters can disable it const bool setup_gui = ((flags & INIT_NO_GUI) == 0 && g_AutostartMap.Length() == 0); // GUI is notified in SetVideoMode, so this must come before that. #ifndef NO_GUI new CGUI; #endif bool windowed = false; CFG_GET_SYS_VAL("windowed", Bool, windowed); if(setup_vmode) { MICROLOG(L"SetVideoMode"); if(SetVideoMode(g_xres, g_yres, 32, !windowed) < 0) { LOG(ERROR, LOG_CATEGORY, "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."); } uint quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); // required by ogl_tex to detect broken gfx card/driver combos get_gfx_info(); oglCheck(); if(!g_Quickstart) { WriteSystemInfo(); // vfs_display(); // disabled to decrease startup time } else { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } // (must come after SetVideoMode, since it calls oglInit) const char* missing = oglHaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", "GL_ARB_texture_env_crossbar", 0); if(missing) { wchar_t buf[500]; const wchar_t* fmt = L"The %hs extension doesn't appear to be available on your computer. " L"The game may still work, though - you are welcome to try at your own risk. " L"If not or it doesn't look right, upgrade your graphics card."; swprintf(buf, ARRAY_SIZE(buf), fmt, missing); DISPLAY_ERROR(buf); // TODO: i18n } // enable/disable VSync // note: "GL_EXT_SWAP_CONTROL" is "historical" according to dox. #if OS_WIN if(oglHaveExtension("WGL_EXT_swap_control")) pwglSwapIntervalEXT(g_VSync? 1 : 0); #endif MICROLOG(L"init ps"); InitPs(setup_gui); oglCheck(); InitRenderer(); { TIMER("Init_entitiessection"); // This needs to be done after the renderer has loaded all its actors... new CBaseEntityCollection; // CEntityManager is managed by CWorld //new CEntityManager; new CSelectedEntities; new CMouseoverEntities; } { TIMER("Init_miscgamesection"); new CPathfindEngine; new CBuildingPlacer; new CSessionManager; new CGameAttributes; } { TIMER("Init_misc"); // Register a few Game/Network JS globals g_ScriptingHost.SetGlobal("g_GameAttributes", OBJECT_TO_JSVAL(g_GameAttributes.GetScript())); // 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.) //debug_heap_enable(DEBUG_HEAP_ALL); InitInput(); oglCheck(); } #ifndef NO_GUI { TIMER("Init_guiload"); g_GUI.SendEventToAll("load"); } #endif #ifndef SCED // HACK: ScEd's init order makes this break { TIMER("Init_renderblank"); MICROLOG(L"render blank"); // render everything to a blank frame to force renderer to load everything RenderNoCull(); } #endif 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(); } if (g_AutostartMap.Length()) { // Code copied mostly from atlas/GameInterface/Handlers/Map.cpp - // maybe should be refactored to avoid duplication g_GameAttributes.m_MapFile = g_AutostartMap+".pmp"; for (int i=1; i<8; ++i) g_GameAttributes.GetSlot(i)->AssignLocal(); g_GameAttributes.m_LOSSetting = 2; g_Game = new CGame(); PSRETURN ret = g_Game->StartGame(&g_GameAttributes); debug_assert(ret == PSRETURN_OK); LDR_NonprogressiveLoad(); ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); } } #if MSC_VERSION # pragma optimize("", on) // restore; see above. #endif Index: ps/trunk/source/scripting/ScriptGlue.cpp =================================================================== --- ps/trunk/source/scripting/ScriptGlue.cpp (revision 3331) +++ ps/trunk/source/scripting/ScriptGlue.cpp (revision 3332) @@ -1,1162 +1,1163 @@ // This module defines the table of all functions callable from JS. // it's required by the interpreter; we make use of the opportunity to // document them all in one spot. we thus obviate having to dig through // all the other headers. most of the functions are implemented here; // as for the rest, we only link to their docs (duplication is bad). #include "precompiled.h" #include "ScriptGlue.h" #include "CLogger.h" #include "CConsole.h" #include "CStr.h" #include "EntityHandles.h" #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" #include "Scheduler.h" #include "timer.h" #include "LightEnv.h" #include "MapWriter.h" #include "GameEvents.h" #include "Interact.h" #include "Renderer.h" #include "Game.h" #include "LOSManager.h" #include "Network/Server.h" #include "Network/Client.h" #include "gui/CGUI.h" #include "ps/i18n.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSCollection.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" #include "scripting/JSInterface_Selection.h" #include "scripting/JSInterface_Camera.h" #include "scripting/JSInterface_Console.h" #include "scripting/JSInterface_VFS.h" #include "scripting/JSConversions.h" +#include "renderer/WaterManager.h" #ifndef NO_GUI # include "gui/scripting/JSInterface_IGUIObject.h" #endif extern CConsole* g_Console; extern bool g_TerrainModified; // rationale: the function table is now at the end of the source file to // avoid the need for forward declarations for every function. // all normal function wrappers have the following signature: // JSBool func(JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval); // all property accessors have the following signature: // JSBool accessor(JSContext* cx, JSObject* globalObject, jsval id, jsval* vp); // consistent argc checking for normal function wrappers: reports an // error via JS and returns if number of parameters is incorrect. // .. require exact number (most common case) #define REQUIRE_PARAMS(exact_number, func_name)\ if(argc != exact_number)\ {\ JS_ReportError(cx, #func_name ": number of parameters passed doesn't match expected count");\ return JS_FALSE;\ } // .. require 0 params (avoids L4 warning "unused argv param") #define REQUIRE_NO_PARAMS(func_name)\ UNUSED2(argv);\ if(argc != 0)\ {\ JS_ReportError(cx, #func_name ": number of parameters passed doesn't match expected count");\ return JS_FALSE;\ } // .. accept at most N params (e.g. buildTime) #define REQUIRE_MAX_PARAMS(max_number, func_name)\ if(argc > max_number)\ {\ JS_ReportError(cx, #func_name ": too many parameters passed");\ return JS_FALSE;\ } // .. accept at least N params (e.g. issueCommand) #define REQUIRE_MIN_PARAMS(min_number, func_name)\ if(argc < min_number)\ {\ JS_ReportError(cx, #func_name ": too few parameters passed");\ return JS_FALSE;\ } //----------------------------------------------------------------------------- // Output //----------------------------------------------------------------------------- // Write values to the log file. // params: any number of any type. // returns: // notes: // - Each argument is converted to a string and then written to the log. // - Output is in NORMAL style (see LOG). JSBool WriteLog(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_MIN_PARAMS(1, WriteLog); 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, "script", logMessage ); return JS_TRUE; } //----------------------------------------------------------------------------- // Entity //----------------------------------------------------------------------------- // Retrieve the entity currently occupying the specified handle. // params: handle [int] // returns: entity JSBool getEntityByHandle( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, getEntityByHandle); *rval = JSVAL_NULL; i32 handle; try { handle = ToPrimitive( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid handle" ); return( JS_TRUE ); } HEntity* v = g_EntityManager.getByHandle( (u16)handle ); if( !v ) { JS_ReportError( cx, "No entity occupying handle: %d", handle ); return( JS_TRUE ); } JSObject* entity = (*v)->GetScript(); *rval = OBJECT_TO_JSVAL( entity ); return( JS_TRUE ); } // Look up an EntityTemplate by name. // params: template name [wstring] // returns: entity template object JSBool getEntityTemplate( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, getEntityTemplate); *rval = JSVAL_NULL; CStrW templateName; try { templateName = g_ScriptingHost.ValueToUCString( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid template identifier" ); return( JS_TRUE ); } CBaseEntity* v = g_EntityTemplateCollection.getTemplate( templateName ); if( !v ) { JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() ); return( JS_TRUE ); } - + *rval = OBJECT_TO_JSVAL( v->GetScript() ); return( JS_TRUE ); } // Issue a command (network message) to an entity or collection. // params: either an entity- or entity collection object, message ID [int], // any further params needed by CNetMessage::CommandFromJSArgs // returns: command in serialized form [string] JSBool issueCommand( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MIN_PARAMS(2, issueCommand); debug_assert(JSVAL_IS_OBJECT(argv[0])); *rval = JSVAL_NULL; CEntityList entities; if (JS_GetClass(JSVAL_TO_OBJECT(argv[0])) == &CEntity::JSI_class) entities.push_back( (ToNative(argv[0])) ->me); - else + else entities = *EntityCollection::RetrieveSet(cx, JSVAL_TO_OBJECT(argv[0])); CNetMessage *msg = CNetMessage::CommandFromJSArgs(entities, cx, argc-1, argv+1); if (msg) { g_Console->InsertMessage(L"issueCommand: %hs", msg->GetString().c_str()); g_Game->GetSimulation()->QueueLocalCommand(msg); *rval = g_ScriptingHost.UCStringToValue(msg->GetString()); } return JS_TRUE; } //----------------------------------------------------------------------------- // Events //----------------------------------------------------------------------------- // Register a global handler for the specified DOM event. // params: event type name [wstring], handler [fragment or function] // returns: whether it was actually newly registered [bool] JSBool AddGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, AddGlobalHandler); *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.AddHandlerJS( cx, argc, argv ) ); return( JS_TRUE ); } // Remove a previously registered global handler for the specified DOM event. // params: event type name [wstring], handler [fragment or function] // returns: whether it was successfully removed [bool] JSBool RemoveGlobalHandler( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, RemoveGlobalHandler); *rval = BOOLEAN_TO_JSVAL( g_JSGameEvents.RemoveHandlerJS( cx, argc, argv ) ); return( JS_TRUE ); } //----------------------------------------------------------------------------- // Timer //----------------------------------------------------------------------------- // Request a callback be executed after the specified delay. // params: callback [fragment or function], delay in milliseconds [int] // returns: // notes: // - Scripts and functions registered this way are called on the first // simulation frame after the specified period has elapsed. If this causes // multiple segments of code to be executed in the same frame, // relative timing is maintained. Delays of 0 milliseconds cause code to be // executed on the following simulation frame. If more than one script or // function is scheduled to execute in the same millisecond, the order of // execution is undefined. Code is scheduled in simulation time, and is // therefore suspended while the game is paused or frozen. Granularity of // timing is also limited to 1/(Simulation frame rate); currently 100ms. // The called function or script executes in the same scope as the // code that called setTimeout (amongst other things, the // 'this' reference is usually maintained) JSBool setTimeout( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MIN_PARAMS(2, setTimeout); REQUIRE_MAX_PARAMS(3, setTimeout); size_t delay; try { delay = ToPrimitive( argv[1] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid timer parameters" ); return( JS_TRUE ); } JSObject* scope; if( argc == 3 ) { if( !JSVAL_IS_OBJECT( argv[2] ) ) { JS_ReportError( cx, "Invalid timer parameters" ); return( JS_TRUE ); } scope = JSVAL_TO_OBJECT( argv[2] ); } else { scope = JS_GetScopeChain( cx ); } switch( JS_TypeOfValue( cx, argv[0] ) ) { case JSTYPE_STRING: { CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] ); int id = g_Scheduler.pushTime( delay, fragment, scope ); *rval = INT_TO_JSVAL( id ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( cx, argv[0] ); int id = g_Scheduler.pushTime( delay, fn, scope ); *rval = INT_TO_JSVAL( id ); return( JS_TRUE ); } default: JS_ReportError( cx, "Invalid timer script" ); return( JS_TRUE ); } } // Request a callback be executed periodically. // params: callback [fragment or function], initial delay in ms [int], period in ms [int] // OR callback [fragment or function], period in ms [int] (initial delay = period) // returns: // notes: // - setTimeout's notes apply here as well. JSBool setInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MIN_PARAMS(2, setInterval); REQUIRE_MAX_PARAMS(3, setInterval); size_t first, interval; try { first = ToPrimitive( argv[1] ); if( argc == 3 ) { // toDo, first, interval interval = ToPrimitive( argv[2] ); } else { // toDo, interval (first = interval) interval = first; } } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid timer parameters" ); return( JS_TRUE ); } switch( JS_TypeOfValue( cx, argv[0] ) ) { case JSTYPE_STRING: { CStrW fragment = g_ScriptingHost.ValueToUCString( argv[0] ); int id = g_Scheduler.pushInterval( first, interval, fragment, JS_GetScopeChain( cx ) ); *rval = INT_TO_JSVAL( id ); return( JS_TRUE ); } case JSTYPE_FUNCTION: { JSFunction* fn = JS_ValueToFunction( cx, argv[0] ); int id = g_Scheduler.pushInterval( first, interval, fn, JS_GetScopeChain( cx ) ); *rval = INT_TO_JSVAL( id ); return( JS_TRUE ); } default: JS_ReportError( cx, "Invalid timer script" ); return( JS_TRUE ); } } // Cause all periodic functions registered via setInterval to // no longer be called. // params: // returns: // notes: // - Execution continues until the end of the triggered function or // script fragment, but is not triggered again. JSBool cancelInterval( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(cancelInterval); g_Scheduler.m_abortInterval = true; return( JS_TRUE ); } -// Cause the scheduled task (timeout or interval) with the given ID to +// Cause the scheduled task (timeout or interval) with the given ID to // no longer be called. // params: // returns: // notes: // - Execution continues until the end of the triggered function or // script fragment, but is not triggered again. JSBool cancelTimer( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_MIN_PARAMS(1, cancelTimer); REQUIRE_MAX_PARAMS(1, cancelTimer); try { int id = ToPrimitive( argv[0] ); g_Scheduler.cancelTask( id ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid ID parameter" ); return( JS_TRUE ); } return( JS_TRUE ); } //----------------------------------------------------------------------------- // Game Setup //----------------------------------------------------------------------------- // Create a new network server object. // params: // returns: net server object JSBool createServer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(createServer); if( !g_Game ) g_Game = new CGame(); if( !g_NetServer ) g_NetServer = new CNetServer(g_Game, &g_GameAttributes); - + *rval = OBJECT_TO_JSVAL(g_NetServer->GetScript()); return( JS_TRUE ); } // Create a new network client object. // params: // returns: net client object JSBool createClient(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(createClient); if( !g_Game ) g_Game = new CGame(); if( !g_NetClient ) g_NetClient = new CNetClient(g_Game, &g_GameAttributes); *rval = OBJECT_TO_JSVAL(g_NetClient->GetScript()); return( JS_TRUE ); } // Begin the process of starting a game. // params: // returns: success [bool] // notes: // - Performs necessary initialization while calling back into the // main loop, so the game remains responsive to display+user input. // - When complete, the engine calls the reallyStartGame JS function. // TODO: Replace startGame with create(Game|Server|Client)/game.start() - // after merging CGame and CGameAttributes JSBool startGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(startGame); *rval = BOOLEAN_TO_JSVAL(JS_TRUE); // Hosted MP Game if (g_NetServer) *rval = BOOLEAN_TO_JSVAL(g_NetServer->StartGame() == 0); // Joined MP Game: startGame is invalid - do nothing else if (g_NetClient) { } // Start an SP Game Session else if (!g_Game) { g_Game = new CGame(); PSRETURN ret = g_Game->StartGame(&g_GameAttributes); if (ret != PSRETURN_OK) { // Failed to start the game - destroy it, and return false delete g_Game; g_Game = NULL; *rval = BOOLEAN_TO_JSVAL(JS_FALSE); return( JS_TRUE ); } } return( JS_TRUE ); } // Immediately ends the current game (if any). // params: // returns: JSBool endGame(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_NO_PARAMS(endGame); EndGame(); return JS_TRUE; } //----------------------------------------------------------------------------- // Internationalization //----------------------------------------------------------------------------- // these remain here instead of in the i18n tree because they are // really related to the engine's use of them, as opposed to i18n itself. // contrariwise, translate() cannot be moved here because that would // make i18n dependent on this code and therefore harder to reuse. // Replaces the current language (locale) with a new one. // params: language id [string] as in I18n::LoadLanguage // returns: JSBool loadLanguage(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_PARAMS(1, loadLanguage); CStr lang = g_ScriptingHost.ValueToString(argv[0]); I18n::LoadLanguage(lang); return JS_TRUE; } // Return identifier of the current language (locale) in use. // params: // returns: language id [string] as in I18n::LoadLanguage JSBool getLanguageID(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval) { REQUIRE_NO_PARAMS(getLanguageID); *rval = JSVAL_NULL; JSString* s = JS_NewStringCopyZ(cx, I18n::CurrentLanguageName()); if (!s) { JS_ReportError(cx, "Error creating string"); return JS_FALSE; } *rval = STRING_TO_JSVAL(s); return JS_TRUE; } //----------------------------------------------------------------------------- // Debug //----------------------------------------------------------------------------- // Deliberately cause the game to crash. // params: // returns: // notes: // - currently implemented via access violation (read of address 0) // - useful for testing the crashlog/stack trace code. JSBool crash(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval)) { REQUIRE_NO_PARAMS(crash); MICROLOG(L"Crashing at user's request."); return *(JSBool*)0; } // Force a JS GC (garbage collection) cycle to take place immediately. // params: // returns: true [bool] // notes: // - writes an indication of how long this took to the console. JSBool forceGC( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(forceGC); double time = get_time(); JS_GC( cx ); time = get_time() - time; g_Console->InsertMessage( L"Garbage collection completed in: %f", time ); *rval = JSVAL_TRUE; return( JS_TRUE ); } //----------------------------------------------------------------------------- // GUI //----------------------------------------------------------------------------- // Returns the sort-of-global object associated with the current GUI. // params: // returns: global object // notes: // - Useful for accessing an object from another scope. JSBool getGUIGlobal( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getGUIGlobal); *rval = OBJECT_TO_JSVAL( g_GUI.GetScriptObject() ); return( JS_TRUE ); } //----------------------------------------------------------------------------- // Misc. Engine Interface //----------------------------------------------------------------------------- // Return the global frames-per-second value. // params: // returns: FPS [int] // notes: // - This value is recalculated once a frame. We take special care to // filter it, so it is both accurate and free of jitter. JSBool getFPS( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getFPS); *rval = INT_TO_JSVAL(fps); return JS_TRUE; } // Cause the game to exit gracefully. // params: // returns: // notes: // - Exit happens after the current main loop iteration ends // (since this only sets a flag telling it to end) JSBool exitProgram( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(exitProgram); kill_mainloop(); return JS_TRUE; } // Write an indication of total/available video RAM to console. // params: // returns: // notes: // - Not supported on all platforms. // - Only a rough approximation; do not base low-level decisions // ("should I allocate one more texture?") on this. JSBool vmem( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(vmem); #if OS_WIN int left, total; if (GetVRAMInfo(left, total)) g_Console->InsertMessage(L"VRAM: used %d, total %d, free %d", total-left, total, left); else g_Console->InsertMessage(L"VRAM: failed to detect"); #else g_Console->InsertMessage(L"VRAM: [not available on non-Windows]"); #endif return JS_TRUE; } // Change the mouse cursor. // params: cursor name [string] (i.e. basename of definition file and texture) // returns: // notes: // - Cursors are stored in "art\textures\cursors" JSBool setCursor( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS(1, setCursor); g_CursorName = g_ScriptingHost.ValueToString(argv[0]); return JS_TRUE; } // Trigger a rewrite of all maps. // params: // returns: // notes: // - Usefulness is unclear. If you need it, consider renaming this and updating the docs. JSBool _rewriteMaps( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_NO_PARAMS(_rewriteMaps); g_Game->GetWorld()->RewriteMap(); return JS_TRUE; } // Change the LOD bias. // params: LOD bias [float] // returns: // notes: // - value is as required by GL_TEXTURE_LOD_BIAS. // - useful for adjusting image "sharpness" (since it affects which mipmap level is chosen) JSBool _lodbias( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS(1, _lodbias); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, ToPrimitive(argv[0])); return JS_TRUE; } // Focus the game camera on a given position. // params: target position vector [CVector3D] // returns: success [bool] JSBool setCameraTarget( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(1, setCameraTarget); *rval = JSVAL_NULL; CVector3D* target = ToNative( argv[0] ); if(!target) { JS_ReportError( cx, "Invalid camera target" ); return( JS_TRUE ); } g_Game->GetView()->SetCameraTarget( *target ); *rval = JSVAL_TRUE; return( JS_TRUE ); } //----------------------------------------------------------------------------- // Miscellany //----------------------------------------------------------------------------- // Return the date/time at which the current executable was compiled. // params: none (-> "date time") OR // what to display [int]: 0 (-> "date"); 1 (-> "time") // returns: date and/or time [string] // notes: // - Displayed on main menu screen; tells non-programmers which auto-build // they are running. Could also be determined via .EXE file properties, // but that's a bit more trouble. // - To be exact, the date/time returned is when scriptglue.cpp was // last compiled; since the auto-build does full rebuilds, that is moot. JSBool buildTime( JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval ) { REQUIRE_MAX_PARAMS(1, buildTime); // buildTime( ) = "date time" // buildTime(0) = "date" // buildTime(1) = "time" JSString* s = JS_NewStringCopyZ(cx, argc && argv[0]==JSVAL_ONE ? __TIME__ : argc ? __DATE__ : __DATE__" "__TIME__ ); *rval = STRING_TO_JSVAL(s); return JS_TRUE; } // Return distance between 2 points. // params: 2 position vectors [CVector3D] // returns: Euclidean distance [float] JSBool v3dist( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS(2, v3dist); CVector3D* a = ToNative( argv[0] ); CVector3D* b = ToNative( argv[1] ); float dist = ( *a - *b ).GetLength(); *rval = ToJSVal( dist ); return( JS_TRUE ); } // Returns the global object. // params: // returns: global object // notes: // - Useful for accessing an object from another scope. JSBool getGlobal( JSContext* cx, JSObject* globalObject, uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS(getGlobal); *rval = OBJECT_TO_JSVAL( globalObject ); return( JS_TRUE ); } // Activates the building placement cursor for placing a building. // params: templateName - the name of the entity to place. // returns: true if cursor was activated, false if cursor was already active. JSBool startPlacing( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { CStrW name; if(argc == 0) { name = L"hele_ho"; // save some typing during testing } else { if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], name )) { JS_ReportError( cx, "Invalid template name argument" ); *rval = JSVAL_NULL; return( JS_FALSE ); } } *rval = g_BuildingPlacer.activate(name) ? JS_TRUE : JS_FALSE; return( JS_TRUE ); } // Toggles drawing the water plane JSBool toggleWater( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS( toggleWater ); debug_printf("Toggling water!\n"); - g_Renderer.m_RenderWater = !g_Renderer.m_RenderWater; + g_Renderer.GetWaterManager()->m_RenderWater = !g_Renderer.GetWaterManager()->m_RenderWater; *rval = JSVAL_VOID; return( JS_TRUE ); } // Sets the water plane height JSBool setWaterHeight( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS( 1, setWaterHeight ); float newHeight; if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], newHeight )) { JS_ReportError( cx, "Invalid water height argument" ); *rval = JSVAL_VOID; return( JS_FALSE ); } - g_Renderer.m_WaterHeight = newHeight; + g_Renderer.GetWaterManager()->m_WaterHeight = newHeight; g_TerrainModified = true; *rval = JSVAL_VOID; return( JS_TRUE ); } // Gets the water plane height JSBool getWaterHeight( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS( getWaterHeight ); - *rval = ToJSVal(g_Renderer.m_WaterHeight); + *rval = ToJSVal(g_Renderer.GetWaterManager()->m_WaterHeight); return( JS_TRUE ); } // Sets the water color JSBool setWaterColor( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS( 3, setWaterColor ); float r,g,b; if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], r ) || !ToPrimitive( g_ScriptingHost.GetContext(), argv[1], g ) || !ToPrimitive( g_ScriptingHost.GetContext(), argv[2], b )) { JS_ReportError( cx, "Invalid arguments" ); *rval = JSVAL_VOID; return( JS_FALSE ); } - g_Renderer.m_WaterColor = CColor(r, g, b, 1.0f); + g_Renderer.GetWaterManager()->m_WaterColor = CColor(r, g, b, 1.0f); *rval = JSVAL_VOID; return( JS_TRUE ); } // Sets the max water alpha (achieved when it is at WaterFullDepth or deeper) JSBool setWaterMaxAlpha( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS( 1, setWaterMaxAlpha ); float val; if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val )) { JS_ReportError( cx, "Invalid argument" ); *rval = JSVAL_VOID; return( JS_FALSE ); } - g_Renderer.m_WaterMaxAlpha = val; + g_Renderer.GetWaterManager()->m_WaterMaxAlpha = val; *rval = JSVAL_VOID; return( JS_TRUE ); } // Sets the water full depth (when it is colored WaterMaxAlpha) JSBool setWaterFullDepth( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS( 1, setWaterFullDepth ); float val; if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val )) { JS_ReportError( cx, "Invalid argument" ); *rval = JSVAL_VOID; return( JS_FALSE ); } - g_Renderer.m_WaterFullDepth = val; + g_Renderer.GetWaterManager()->m_WaterFullDepth = val; *rval = JSVAL_VOID; return( JS_TRUE ); } // Sets the water alpha offset (added to tweak water alpha near the shore) JSBool setWaterAlphaOffset( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_PARAMS( 1, setWaterAlphaOffset ); float val; if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], val )) { JS_ReportError( cx, "Invalid argument" ); *rval = JSVAL_VOID; return( JS_FALSE ); } - g_Renderer.m_WaterAlphaOffset = val; + g_Renderer.GetWaterManager()->m_WaterAlphaOffset = val; *rval = JSVAL_VOID; return( JS_TRUE ); } // Is the game paused? JSBool isPaused( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_NO_PARAMS( isPaused ); *rval = g_Game->m_Paused ? JSVAL_TRUE : JSVAL_FALSE; return( JS_TRUE ); } // Pause/unpause the game JSBool setPaused( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* UNUSED(rval) ) { REQUIRE_PARAMS( 1, setPaused ); try { g_Game->m_Paused = ToPrimitive( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid parameter to setPaused" ); } return( JS_TRUE ); } // Reveal map JSBool revealMap( JSContext* cx, JSObject* UNUSED(globalObject), uint argc, jsval* argv, jsval* rval ) { REQUIRE_MIN_PARAMS( 0, revealMap ); REQUIRE_MAX_PARAMS( 1, revealMap ); uint newValue; if(argc == 0) { newValue = 2; } else if(!ToPrimitive( g_ScriptingHost.GetContext(), argv[0], newValue ) || newValue > 2 || newValue < 0) { JS_ReportError( cx, "Invalid argument (should be 0, 1 or 2)" ); *rval = JSVAL_VOID; return( JS_FALSE ); } g_Game->GetWorld()->GetLOSManager()->m_LOSSetting = newValue; *rval = JSVAL_VOID; return( JS_TRUE ); } //----------------------------------------------------------------------------- // function table //----------------------------------------------------------------------------- // the JS interpreter expects the table to contain 5-tuples as follows: // - name the function will be called as from script; // - function which will be called; // - number of arguments this function expects // - Flags (deprecated, always zero) // - Extra (reserved for future use, always zero) // // we simplify this a bit with a macro: #define JS_FUNC(script_name, cpp_function, min_params) { #script_name, cpp_function, min_params, 0, 0 }, -JSFunctionSpec ScriptFunctionTable[] = +JSFunctionSpec ScriptFunctionTable[] = { // Console JS_FUNC(writeConsole, JSI_Console::writeConsole, 1) // external // Entity JS_FUNC(getEntityByHandle, getEntityByHandle, 1) JS_FUNC(getEntityTemplate, getEntityTemplate, 1) JS_FUNC(issueCommand, issueCommand, 2) JS_FUNC(startPlacing, startPlacing, 1) // Camera JS_FUNC(setCameraTarget, setCameraTarget, 1) // Water JS_FUNC(toggleWater, toggleWater, 0) JS_FUNC(setWaterHeight, setWaterHeight, 1) JS_FUNC(getWaterHeight, getWaterHeight, 0) JS_FUNC(setWaterColor, setWaterColor, 0) JS_FUNC(setWaterMaxAlpha, setWaterMaxAlpha, 0) JS_FUNC(setWaterFullDepth, setWaterFullDepth, 0) JS_FUNC(setWaterAlphaOffset, setWaterAlphaOffset, 0) - // GUI + // GUI #ifndef NO_GUI JS_FUNC(getGUIObjectByName, JSI_IGUIObject::getByName, 1) // external JS_FUNC(getGUIGlobal, getGUIGlobal, 0) #endif // Events JS_FUNC(addGlobalHandler, AddGlobalHandler, 2) JS_FUNC(removeGlobalHandler, RemoveGlobalHandler, 2) // Timer JS_FUNC(setTimeout, setTimeout, 2) JS_FUNC(setInterval, setInterval, 2) JS_FUNC(cancelInterval, cancelInterval, 0) JS_FUNC(cancelTimer, cancelTimer, 0) // Game Setup JS_FUNC(startGame, startGame, 0) JS_FUNC(endGame, endGame, 0) JS_FUNC(createClient, createClient, 0) JS_FUNC(createServer, createServer, 0) // VFS (external) JS_FUNC(buildFileList, JSI_VFS::BuildFileList, 1) JS_FUNC(getFileMTime, JSI_VFS::GetFileMTime, 1) JS_FUNC(getFileSize, JSI_VFS::GetFileSize, 1) JS_FUNC(readFile, JSI_VFS::ReadFile, 1) JS_FUNC(readFileLines, JSI_VFS::ReadFileLines, 1) // Internationalization JS_FUNC(loadLanguage, loadLanguage, 1) JS_FUNC(getLanguageID, getLanguageID, 0) // note: i18n/ScriptInterface.cpp registers translate() itself. // rationale: see implementation section above. // Debug JS_FUNC(crash, crash, 0) JS_FUNC(forceGC, forceGC, 0) JS_FUNC(revealMap, revealMap, 1) // Misc. Engine Interface JS_FUNC(writeLog, WriteLog, 1) JS_FUNC(exit, exitProgram, 0) JS_FUNC(isPaused, isPaused, 0) JS_FUNC(setPaused, setPaused, 1) JS_FUNC(vmem, vmem, 0) JS_FUNC(_rewriteMaps, _rewriteMaps, 0) JS_FUNC(_lodbias, _lodbias, 0) JS_FUNC(setCursor, setCursor, 1) JS_FUNC(getFPS, getFPS, 0) // Miscellany JS_FUNC(v3dist, v3dist, 2) JS_FUNC(buildTime, buildTime, 0) JS_FUNC(getGlobal, getGlobal, 0) // end of table marker {0, 0, 0, 0, 0} }; #undef JS_FUNC //----------------------------------------------------------------------------- // property accessors //----------------------------------------------------------------------------- JSBool GetEntitySet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(argv), jsval* vp ) { std::vector* extant = g_EntityManager.getExtant(); *vp = OBJECT_TO_JSVAL( EntityCollection::Create( *extant ) ); delete( extant ); return( JS_TRUE ); } JSBool GetPlayerSet( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { std::vector* players = g_Game->GetPlayers(); *vp = OBJECT_TO_JSVAL( PlayerCollection::Create( *players ) ); return( JS_TRUE ); } JSBool GetLocalPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { *vp = OBJECT_TO_JSVAL( g_Game->GetLocalPlayer()->GetScript() ); return( JS_TRUE ); } JSBool GetGaiaPlayer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { *vp = OBJECT_TO_JSVAL( g_Game->GetPlayer( 0 )->GetScript() ); return( JS_TRUE ); } JSBool SetLocalPlayer( JSContext* cx, JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { CPlayer* newLocalPlayer = ToNative( *vp ); if( !newLocalPlayer ) { JS_ReportError( cx, "Not a valid Player." ); return( JS_TRUE ); } g_Game->SetLocalPlayer( newLocalPlayer ); return( JS_TRUE ); } JSBool GetGameView( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { if (g_Game) *vp = OBJECT_TO_JSVAL( g_Game->GetView()->GetScript() ); else *vp = JSVAL_NULL; return( JS_TRUE ); } JSBool GetRenderer( JSContext* UNUSED(cx), JSObject* UNUSED(obj), jsval UNUSED(id), jsval* vp ) { if (CRenderer::IsInitialised()) *vp = OBJECT_TO_JSVAL( g_Renderer.GetScript() ); else *vp = JSVAL_NULL; return( JS_TRUE ); } enum ScriptGlobalTinyIDs { GLOBAL_SELECTION, GLOBAL_GROUPSARRAY, GLOBAL_CAMERA, GLOBAL_CONSOLE, }; // shorthand #define PERM JSPROP_PERMANENT #define CONST JSPROP_READONLY JSPropertySpec ScriptGlobalTable[] = { { "selection" , GLOBAL_SELECTION, PERM, JSI_Selection::getSelection, JSI_Selection::setSelection }, { "groups" , GLOBAL_GROUPSARRAY, PERM, JSI_Selection::getGroups, JSI_Selection::setGroups }, { "camera" , GLOBAL_CAMERA, PERM, JSI_Camera::getCamera, JSI_Camera::setCamera }, { "console" , GLOBAL_CONSOLE, PERM | CONST, JSI_Console::getConsole, 0 }, { "entities" , 0, PERM | CONST, GetEntitySet, 0 }, { "players" , 0, PERM | CONST, GetPlayerSet, 0 }, { "localPlayer", 0, PERM , GetLocalPlayer, SetLocalPlayer }, { "gaiaPlayer" , 0, PERM | CONST, GetGaiaPlayer, 0 }, { "gameView" , 0, PERM | CONST, GetGameView, 0 }, { "renderer" , 0, PERM | CONST, GetRenderer, 0 }, // end of table marker { 0, 0, 0, 0, 0 }, }; Index: ps/trunk/source/gui/MiniMap.cpp =================================================================== --- ps/trunk/source/gui/MiniMap.cpp (revision 3331) +++ ps/trunk/source/gui/MiniMap.cpp (revision 3332) @@ -1,424 +1,425 @@ #include "precompiled.h" #include "gui/MiniMap.h" #include "ps/Game.h" #include #include "ogl.h" #include "renderer/Renderer.h" #include "graphics/TextureEntry.h" #include "graphics/TextureManager.h" #include "graphics/Unit.h" #include "UnitManager.h" #include "Entity.h" #include "Bound.h" #include "Model.h" #include "Terrain.h" #include "Profile.h" #include "LOSManager.h" #include "graphics/GameView.h" - +#include "renderer/WaterManager.h" bool g_TerrainModified = false; // used by GetMapSpaceCoords (precalculated as an optimization). // this was formerly access via inline asm, which required it to be // static data instead of a class member. that is no longer the case, // but we leave it because this is slightly more efficient. static float m_scaleX, m_scaleY; static unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = uint(float(color & 0xff) * x); unsigned int g = uint(float((color>>8) & 0xff) * x); unsigned int b = uint(float((color>>16) & 0xff) * x); return (0xff000000 | r | g<<8 | b<<16); } CMiniMap::CMiniMap() : m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_LOSTexture(0), m_LOSData(0), m_UnitManager(0) { AddSetting(GUIST_CColor, "fov_wedge_color"); AddSetting(GUIST_CStr, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::HandleMessage(const SGUIMessage &Message) { switch(Message.type) { case GUIM_MOUSE_PRESS_LEFT: { CTerrain *MMTerrain=g_Game->GetWorld()->GetTerrain(); CVector3D CamOrient=m_Camera->m_Orientation.GetTranslation(); //get center point of screen int x = (int)g_Renderer.GetWidth()/2.f, y = (int)g_Renderer.GetHeight()/2.f; CVector3D ScreenMiddle=m_Camera->GetWorldCoordinates(x,y); //Get Vector required to go from camera position to ScreenMiddle CVector3D TransVector; TransVector.X=CamOrient.X-ScreenMiddle.X; TransVector.Z=CamOrient.Z-ScreenMiddle.Z; //world position of where mouse clicked CVector3D Destination; //X and Z according to proportion of mouse position and minimap Destination.X=(CELL_SIZE*m_MapSize)* ((g_mouse_x-m_CachedActualSize.left)/m_CachedActualSize.GetWidth()); Destination.Z=(CELL_SIZE*m_MapSize)*((m_CachedActualSize.bottom-g_mouse_y) /m_CachedActualSize.GetHeight()); m_Camera->m_Orientation._14=Destination.X; m_Camera->m_Orientation._34=Destination.Z; m_Camera->m_Orientation._14+=TransVector.X; m_Camera->m_Orientation._34+=TransVector.Z; - //Lock Y coord. No risk of zoom exceeding limit-Y does not increase + //Lock Y coord. No risk of zoom exceeding limit-Y does not increase float Height=MMTerrain->getExactGroundLevel( m_Camera->m_Orientation._14, m_Camera->m_Orientation._34) + g_YMinOffset; - if (m_Camera->m_Orientation._24 < Height) + if (m_Camera->m_Orientation._24 < Height) { m_Camera->m_Orientation._24=Height; } m_Camera->UpdateFrustum(); } break; default: break; } // switch } // render view rect : John M. Mena // This sets up and draws the rectangle on the mini-map // which represents the view of the camera in the world. void CMiniMap::DrawViewRect() { - // Get correct world coordinates based off corner of screen start + // Get correct world coordinates based off corner of screen start // at Bottom Left and going CW CVector3D hitPt[4]; hitPt[0]=m_Camera->GetWorldCoordinates(0,g_Renderer.GetHeight()); hitPt[1]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),g_Renderer.GetHeight()); hitPt[2]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),0); hitPt[3]=m_Camera->GetWorldCoordinates(0,0); float ViewRect[4][2]; for (int i=0;i<4;i++) { // convert to minimap space float px=hitPt[i].X; float pz=hitPt[i].Z; ViewRect[i][0]=(m_CachedActualSize.GetWidth()*px/float(CELL_SIZE*m_MapSize)); ViewRect[i][1]=(m_CachedActualSize.GetHeight()*pz/float(CELL_SIZE*m_MapSize)); } // Enable Scissoring as to restrict the rectangle // to only the mini-map below by retrieving the mini-maps // screen coords. glScissor((int)m_CachedActualSize.left, 0, (int)m_CachedActualSize.right, (int)m_CachedActualSize.GetHeight()); glEnable(GL_SCISSOR_TEST); glEnable(GL_LINE_SMOOTH); glLineWidth(2); glColor3f(1.0f, 0.3f, 0.3f); // Draw the viewing rectangle with the ScEd's conversion algorithm const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; glBegin(GL_LINE_LOOP); glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]); glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]); glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]); glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]); glEnd(); // restore state glDisable(GL_SCISSOR_TEST); glDisable(GL_POINT_SMOOTH); glLineWidth(1.0f); } void CMiniMap::Draw() { // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if(!(GetGUI() && g_Game && g_Game->IsGameStarted())) return; - + // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_UnitManager = g_Game->GetWorld()->GetUnitManager(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = round_up_to_pow2(m_MapSize); m_scaleX = float(m_Width) / float(m_MapSize - 1); m_scaleY = float(m_Height) / float(m_MapSize - 1); if(!m_TerrainTexture) CreateTextures(); // do not limit this as with LOS updates below - we must update // immediately after changes are reported because this flag will be // reset at the end of the frame. if(g_TerrainModified) RebuildTerrainTexture(); // only update 10x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow, ~20ms) static double last_time; const double cur_time = get_time(); if(cur_time - last_time > 100e-3) // 10 updates/sec { last_time = cur_time; CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); if(losMgr->m_LOSSetting != CLOSManager::ALL_VISIBLE) RebuildLOSTexture(); } const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize); const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); // Draw the main textured quad g_Renderer.BindTexture(0, m_TerrainTexture); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z); glTexCoord2f(texCoordMax, 0.0f); glVertex3f(x2, y, z); glTexCoord2f(texCoordMax, texCoordMax); glVertex3f(x2, y2, z); glTexCoord2f(0.0f, texCoordMax); glVertex3f(x, y2, z); glEnd(); // Draw the LOS quad in black, using alpha values from the LOS texture g_Renderer.BindTexture(0, m_LOSTexture); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBegin(GL_QUADS); glColor3f(0.0f, 0.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, z); glTexCoord2f(texCoordMax, 0.0f); glVertex3f(x2, y, z); glTexCoord2f(texCoordMax, texCoordMax); glVertex3f(x2, y2, z); glTexCoord2f(0.0f, texCoordMax); glVertex3f(x, y2, z); glEnd(); glDisable(GL_BLEND); // Draw unit points const std::vector &units = m_UnitManager->GetUnits(); std::vector::const_iterator iter = units.begin(); CUnit *unit = 0; CVector2D pos; CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); glDisable(GL_DEPTH_TEST); glEnable(GL_POINT_SMOOTH); glDisable(GL_TEXTURE_2D); glPointSize(3.0f); // REMOVED: glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_POINTS); for(; iter != units.end(); ++iter) { unit = (CUnit *)(*iter); if(unit && unit->GetEntity() && losMgr->GetUnitStatus(unit, g_Game->GetLocalPlayer()) != UNIT_HIDDEN) { CEntity* entity = unit->GetEntity(); CStrW& type = entity->m_minimapType; if(type==L"Unit" || type==L"Structure" || type==L"Hero") { // Use the player colour const SPlayerColour& colour = unit->GetEntity()->GetPlayer()->GetColour(); glColor3f(colour.r, colour.g, colour.b); } else { glColor3f(entity->m_minimapR/255.0f, entity->m_minimapG/255.0f, entity->m_minimapB/255.0f); } pos = GetMapSpaceCoords(unit->GetEntity()->m_position); glVertex3f(x + pos.x, y - pos.y, z); } } glEnd(); DrawViewRect(); - + // Reset everything back to normal glPointSize(1.0f); glDisable(GL_LINE_SMOOTH); glEnable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); } void CMiniMap::CreateTextures() { Destroy(); // Create terrain texture glGenTextures(1, (GLuint *)&m_TerrainTexture); g_Renderer.BindTexture(0, m_TerrainTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0); m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); // Create LOS texture glGenTextures(1, (GLuint *)&m_LOSTexture); g_Renderer.BindTexture(0, m_LOSTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0); m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); // Rebuild and upload both of them RebuildTerrainTexture(); RebuildLOSTexture(); } void CMiniMap::RebuildTerrainTexture() { u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; + float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; for(u32 j = 0; j < h; j++) { u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; for(u32 i = 0; i < w; i++) { float avgHeight = ( m_Terrain->getVertexGroundLevel((int)i, (int)j) + m_Terrain->getVertexGroundLevel((int)i+1, (int)j) + m_Terrain->getVertexGroundLevel((int)i, (int)j+1) + m_Terrain->getVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; - if(avgHeight < g_Renderer.m_WaterHeight) + if(avgHeight < waterHeight) { *dataPtr++ = 0xff304080; // TODO: perhaps use the renderer's water color? } else { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0; CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j); if(mp) { CTextureEntry *tex = mp->Tex1 ? g_TexMan.FindTexture(mp->Tex1) : 0; color = tex ? tex->GetBaseColor() : 0xffffffff; } else { color = 0xffffffff; } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_TerrainData); } TIMER_ADD_CLIENT(tc_minimap_rebuildlos); void CMiniMap::RebuildLOSTexture() { PROFILE_START("rebuild minimap: los"); TIMER_ACCRUE(tc_minimap_rebuildlos); CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); CPlayer* player = g_Game->GetLocalPlayer(); u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; for(u32 j = 0; j < h; j++) { u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x; for(u32 i = 0; i < w; i++) { ELOSStatus status = losMgr->GetStatus((int) i, (int) j, player); if(status == LOS_UNEXPLORED) { *dataPtr++ = 0xff; } else if(status == LOS_EXPLORED) { *dataPtr++ = (u8) (0xff * 0.3f); } else { *dataPtr++ = 0; } } } // Upload the texture g_Renderer.BindTexture(0, m_LOSTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData); PROFILE_END("rebuild minimap: los"); } void CMiniMap::Destroy() { if(m_TerrainTexture) glDeleteTextures(1, (GLuint *)&m_TerrainTexture); if(m_LOSTexture) glDeleteTextures(1, (GLuint *)&m_LOSTexture); delete[] m_TerrainData; m_TerrainData = 0; delete[] m_LOSData; m_LOSData = 0; } CVector2D CMiniMap::GetMapSpaceCoords(CVector3D worldPos) { float x = rintf(worldPos.X / CELL_SIZE); float y = rintf(worldPos.Z / CELL_SIZE); // Entity's Z coordinate is really its longitudinal coordinate on the terrain // Calculate map space scale return CVector2D(x * m_scaleX, y * m_scaleY); } Index: ps/trunk/source/simulation/Entity.cpp =================================================================== --- ps/trunk/source/simulation/Entity.cpp (revision 3331) +++ ps/trunk/source/simulation/Entity.cpp (revision 3332) @@ -1,1502 +1,1503 @@ // Last modified: May 15 2004, Mark Thompson (mark@wildfiregames.com) #include "precompiled.h" #include "Profile.h" #include "Entity.h" #include "EntityManager.h" #include "BaseEntityCollection.h" #include "Unit.h" #include "Aura.h" #include "Renderer.h" #include "Model.h" #include "Terrain.h" #include "Interact.h" #include "Collision.h" #include "PathfindEngine.h" #include "Game.h" #include "scripting/JSInterface_Vector3D.h" #include "MathUtil.h" #include "CConsole.h" +#include "renderer/WaterManager.h" extern CConsole* g_Console; extern int g_xres, g_yres; #include using namespace std; CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation ) { m_position = position; m_orientation = orientation; m_ahead.x = sin( m_orientation ); m_ahead.y = cos( m_orientation ); AddProperty( L"actions.move.speed", &m_speed ); AddProperty( L"actions.move.run.speed", &( m_run.m_Speed ) ); AddProperty( L"actions.move.run.rangemin", &( m_run.m_MinRange ) ); AddProperty( L"actions.move.run.range", &( m_run.m_MaxRange ) ); AddProperty( L"selected", &m_selected, false, (NotifyFn)&CEntity::checkSelection ); AddProperty( L"group", &m_grouped, false, (NotifyFn)&CEntity::checkGroup ); AddProperty( L"traits.extant", &m_extant ); AddProperty( L"traits.corpse", &m_corpse ); AddProperty( L"actions.move.turningradius", &m_turningRadius ); AddProperty( L"actions.attack.range", &( m_melee.m_MaxRange ) ); AddProperty( L"actions.attack.rangemin", &( m_melee.m_MinRange ) ); AddProperty( L"actions.attack.speed", &( m_melee.m_Speed ) ); AddProperty( L"actions.gather.range", &( m_gather.m_MaxRange ) ); AddProperty( L"actions.gather.rangemin", &( m_gather.m_MinRange ) ); AddProperty( L"actions.gather.speed", &( m_gather.m_Speed ) ); AddProperty( L"actions.heal.range", &( m_heal.m_MaxRange ) ); AddProperty( L"actions.heal.rangemin", &( m_heal.m_MinRange ) ); AddProperty( L"actions.heal.speed", &( m_heal.m_Speed ) ); AddProperty( L"position", &m_graphics_position, false, (NotifyFn)&CEntity::teleport ); AddProperty( L"orientation", &m_graphics_orientation, false, (NotifyFn)&CEntity::reorient ); AddProperty( L"player", &m_player, false, (NotifyFn)&CEntity::playerChanged ); AddProperty( L"traits.health.curr", &m_healthCurr ); AddProperty( L"traits.health.max", &m_healthMax ); AddProperty( L"traits.health.bar_height", &m_healthBarHeight ); AddProperty( L"traits.minimap.type", &m_minimapType ); AddProperty( L"traits.minimap.red", &m_minimapR ); AddProperty( L"traits.minimap.green", &m_minimapG ); AddProperty( L"traits.minimap.blue", &m_minimapB ); AddProperty( L"traits.anchor.type", &m_anchorType ); AddProperty( L"traits.vision.los", &m_los ); AddProperty( L"traits.vision.permanent", &m_permanent ); for( int t = 0; t < EVENT_LAST; t++ ) { AddProperty( EventNames[t], &m_EventHandlers[t], false ); AddHandler( t, &m_EventHandlers[t] ); } m_collisionPatch = NULL; // Set our parent unit and build us an actor. m_actor = NULL; m_bounds = NULL; m_lastState = -1; m_transition = true; m_fsm_cyclepos = NOT_IN_CYCLE; m_base = base; loadBase(); if( m_bounds ) m_bounds->setPosition( m_position.X, m_position.Z ); m_position_previous = m_position; m_orientation_previous = m_orientation; m_graphics_position = m_position; m_graphics_orientation = m_orientation; m_actor_transform_valid = false; m_destroyed = false; m_selected = false; m_grouped = -1; m_player = g_Game->GetPlayer( 0 ); Initialize(); } CEntity::~CEntity() { if( m_actor ) { g_UnitMan.RemoveUnit( m_actor ); delete( m_actor ); } if( m_bounds ) { delete( m_bounds ); } for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) { delete it->second; } m_auras.clear(); } void CEntity::loadBase() { if( m_actor ) { g_UnitMan.RemoveUnit( m_actor ); delete( m_actor ); m_actor = NULL; } if( m_bounds ) { delete( m_bounds ); m_bounds = NULL; } CStr actorName ( m_base->m_actorName ); // convert CStrW->CStr8 m_actor = g_UnitMan.CreateUnit( actorName, this ); // Set up our instance data SetBase( m_base ); m_classes.SetParent( &( m_base->m_classes ) ); SetNextObject( m_base ); if( m_base->m_bound_type == CBoundingObject::BOUND_CIRCLE ) { m_bounds = new CBoundingCircle( m_position.X, m_position.Z, m_base->m_bound_circle ); } else if( m_base->m_bound_type == CBoundingObject::BOUND_OABB ) { m_bounds = new CBoundingBox( m_position.X, m_position.Z, m_ahead, m_base->m_bound_box ); } } void CEntity::kill() { g_Selection.removeAll( me ); if( m_bounds ) delete( m_bounds ); m_bounds = NULL; m_extant = false; m_destroyed = true; //Shutdown(); // PT: tentatively removed - this seems to be called by ~CJSComplex, and we don't want to do it twice if( m_actor ) { g_UnitMan.RemoveUnit( m_actor ); delete( m_actor ); m_actor = NULL; } updateCollisionPatch(); me = HEntity(); // will deallocate the entity, assuming nobody else has a reference to it } void CEntity::SetPlayer(CPlayer *pPlayer) { m_player = pPlayer; // Store the ID of the player in the associated model if( m_actor ) m_actor->GetModel()->SetPlayerID( m_player->GetPlayerID() ); } void CEntity::updateActorTransforms() { CMatrix3D m; float s = sin( m_graphics_orientation ); float c = cos( m_graphics_orientation ); m._11 = -c; m._12 = 0.0f; m._13 = -s; m._14 = m_graphics_position.X; m._21 = 0.0f; m._22 = 1.0f; m._23 = 0.0f; m._24 = m_graphics_position.Y; m._31 = s; m._32 = 0.0f; m._33 = -c; m._34 = m_graphics_position.Z; m._41 = 0.0f; m._42 = 0.0f; m._43 = 0.0f; m._44 = 1.0f; if( m_actor ) m_actor->GetModel()->SetTransform( m ); } void CEntity::snapToGround() { m_graphics_position.Y = getAnchorLevel( m_graphics_position.X, m_graphics_position.Z ); } jsval CEntity::getClassSet() { STL_HASH_SET::iterator it; it = m_classes.m_Set.begin(); CStrW result = L""; if( it != m_classes.m_Set.end() ) { result = *( it++ ); for( ; it != m_classes.m_Set.end(); it++ ) result += L" " + *it; } return( ToJSVal( result ) ); } void CEntity::setClassSet( jsval value ) { // Get the set that was passed in. CStr temp = ToPrimitive( value ); CStr entry; m_classes.m_Added.clear(); m_classes.m_Removed.clear(); while( true ) { long brk_sp = temp.Find( ' ' ); long brk_cm = temp.Find( ',' ); long brk = ( brk_sp == -1 ) ? brk_cm : ( brk_cm == -1 ) ? brk_sp : ( brk_sp < brk_cm ) ? brk_sp : brk_cm; if( brk == -1 ) { entry = temp; } else { entry = temp.GetSubstring( 0, brk ); temp = temp.GetSubstring( brk + 1, temp.Length() ); } if( brk != 0 ) { if( entry[0] == '-' ) { entry = entry.GetSubstring( 1, entry.Length() ); if( entry.Length() ) m_classes.m_Removed.push_back( entry ); } else { if( entry[0] == '+' ) entry = entry.GetSubstring( 1, entry.Length() ); if( entry.Length() ) m_classes.m_Added.push_back( entry ); } } if( brk == -1 ) break; } rebuildClassSet(); } void CEntity::rebuildClassSet() { m_classes.Rebuild(); InheritorsList::iterator it; for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ ) (*it)->rebuildClassSet(); } void CEntity::update( size_t timestep ) { m_position_previous = m_position; m_orientation_previous = m_orientation; // Note: aura processing is done before state processing because the state // processing code is filled with all kinds of returns PROFILE_START( "aura processing" ); for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) { it->second->Update( timestep ); } PROFILE_END( "aura processing" ); // The process[...] functions return 'true' if the order at the top of the stack // still needs to be (re-)evaluated; else 'false' to terminate the processing of // this entity in this timestep. PROFILE_START( "state processing" ); while( !m_orderQueue.empty() ) { CEntityOrder* current = &m_orderQueue.front(); if( current->m_type != m_lastState ) { m_transition = true; m_fsm_cyclepos = NOT_IN_CYCLE; PROFILE( "state transition / order" ); CEntity* target = NULL; if( current->m_data[0].entity ) target = &( *( current->m_data[0].entity ) ); CVector3D worldPosition = (CVector3D)current->m_data[0].location; CEventOrderTransition evt( m_lastState, current->m_type, target, worldPosition ); if( !DispatchEvent( &evt ) ) { m_orderQueue.pop_front(); continue; } else if( target ) { current->m_data[0].location = worldPosition; current->m_data[0].entity = target->me; } m_lastState = current->m_type; } else { m_transition = false; } switch( current->m_type ) { case CEntityOrder::ORDER_GOTO_NOPATHING: case CEntityOrder::ORDER_GOTO_COLLISION: case CEntityOrder::ORDER_GOTO_SMOOTHED: if( processGotoNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_ATTACK_MELEE: if( processAttackMeleeNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_ATTACK_MELEE_NOPATHING: if( processAttackMeleeNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_GATHER: if( processGather( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_GATHER_NOPATHING: if( processGatherNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_HEAL: if( processHeal( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_HEAL_NOPATHING: if( processHealNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_GENERIC: if( processGeneric( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_GENERIC_NOPATHING: if( processGenericNoPathing( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_RUN: if( processGoto( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_PATROL: if( processPatrol( current, timestep ) ) break; updateCollisionPatch(); return; case CEntityOrder::ORDER_PATH_END_MARKER: m_orderQueue.pop_front(); break; default: debug_warn("Invalid entity order" ); } } PROFILE_END( "state processing" ); if( m_actor ) { PROFILE( "animation updates" ); if( m_extant ) { if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() ) m_actor->SetRandomAnimation( "idle" ); } else if( !m_actor->GetModel()->GetAnimation() ) m_actor->SetRandomAnimation( "corpse" ); } if( m_lastState != -1 ) { PROFILE( "state transition event" ); CEntity* d0; CVector3D d1; CEventOrderTransition evt( m_lastState, -1, d0, d1 ); DispatchEvent( &evt ); m_lastState = -1; } } void CEntity::updateCollisionPatch() { vector* newPatch = g_EntityManager.getCollisionPatch( this ); if( newPatch != m_collisionPatch ) { if( m_collisionPatch ) { // remove ourselves from old patch vector& old = *m_collisionPatch; if( old.size() == 1 ) { // we were the only ones there, just pop us old.pop_back(); } else { // find our location and put in the last guy in the patch, then pop back for( size_t i=0; i < old.size(); i++ ) { if( old[i] == this ) { old[i] = old.back(); old.pop_back(); break; } } } } if( m_extant ) { // add ourselves to new patch newPatch->push_back( this ); m_collisionPatch = newPatch; } } } #if AURA_TEST void CEntity::UpdateAuras( size_t timestep_millis ) { std::vector::iterator it_a; for( it_a = m_Auras.begin(); it_a != m_Auras.end(); it_a++ ) { SAuraData& d = it_a->m_Data; std::set & inRange = GetEntitiesWithinRange( m_position, d.m_Radius ); std::vector::iterator it1 = inRange.begin(); std::vector::iterator it2 = it_a->m_Influenced.begin(); while( WORLD_IS_ROUND ) { if( it1 == inRange.end() ) { // No more in range => anything else in the influenced set must have gone // out of range. for( ; it2 != it_a->m_Influenced.end(); it2++ ) UpdateAuras_LeaveRange( *it_a, it2->GetEntity() ); break; } if( it2 == it_a->m_Influenced.end() ) { // Everything else in the in-range set has only just come into range for( ; it1 != inRange.end(); it1++ ) UpdateAuras_EnterRange( *it_a, *it ); break; } CEntity* e1 = *it1, e2 = it2->GetEntity(); if( e1 < e2 ) { // A new entity e1 has just come into range. // Check to see if it can be affected by the aura. UpdateAuras_EnterRange( *it_a, e1 ); ++it1; } else if( e1 == e2 ) { // The entity e1/e2 was previously in range, and still is. UpdateAuras_Normal( *it_a, e1 ); ++it1; ++it2; } else { // The entity e2 was previously in range, but is no longer. UpdateAuras_LeaveRange( *it_a, e2 ); ++it2; } } } } void UpdateAuras_EnterRange( SAura& aura, CEntity* e ) { if( aura.m_Recharge ) return( false ); // Check to see if the entity is eligable if( !UpdateAuras_IsEligable( aura.m_Data, e ) ) return; // Do nothing. SAuraInstance ai; ai.m_Influenced = e; ai.m_EnteredRange = ai.m_LastInRange = 0; ai.m_Applied = -1; // If there's no timer, apply the effect now. if( aura.m_Data.m_Time == 0 ) { e->ApplyAuraEffect( aura.m_Data ); ai.m_Applied = 0; aura.m_Recharge = aura.m_Data.m_Cooldown; } aura.m_Influenced.push_back( ai ); } void UpdateAuras_Normal( SAura& aura, CEntity* e ) { // Is the entity no longer eligable? if( !UpdateAuras_IsEligable( aura.m_Data, e ) ) { //} } bool UpdateAuras_IsEligable( SAuraData& aura, CEntity* e ) { if( e == this ) { if( !( aura.m_Allegiance & SAuraData::SELF ) ) return( false ); } else if( e->m_player == GetGaiaPlayer() ) { if( !( aura.m_Allegiance & SAuraData::GAIA ) ) return( false ); } else if( e->m_player == m_player ) { if( !( aura.m_Allegiance & SAuraData::PLAYER ) ) return( false ); } // TODO: Allied players else { if( !( aura.m_Allegiance & SAuraData::ENEMY ) ) return( false ); } if( e->m_hp > e->m_hp_max * aura.m_Hitpoints ) return( false ); return( true ); } #endif void CEntity::Initialize() { CEventInitialize evt; DispatchEvent( &evt ); } void CEntity::Tick() { CEventTick evt; DispatchEvent( &evt ); } /*void CEntity::Damage( CDamageType& damage, CEntity* inflictor ) { CEventDamage evt( inflictor, &damage ); DispatchEvent( &evt ); }*/ void CEntity::clearOrders() { m_orderQueue.clear(); } void CEntity::pushOrder( CEntityOrder& order ) { if( acceptsOrder( order.m_type, order.m_data[0].entity ) ) { m_orderQueue.push_back( order ); } } bool CEntity::acceptsOrder( int orderType, CEntity* orderTarget ) { CEventPrepareOrder evt( orderTarget, orderType ); return( DispatchEvent( &evt ) ); } jsval CEntity::RequestNotification( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 3 ); CEntityListener notify; notify.m_sender = this; //(Convert from int to enum) CEntity* target = ToPrimitive( argv[0] ); *( (uint*) &(notify.m_type) ) = ToPrimitive( argv[1] ); if ( ToPrimitive( argv[2] ) ) { std::deque::iterator it = target->m_listeners.begin(); for ( ; it != target->m_listeners.end(); it++) { if ( it->m_sender == this ) target->m_listeners.erase(it); } } target->m_listeners.push_back( notify ); return JSVAL_VOID; } jsval CEntity::CheckListeners( JSContext *cx, uintN argc, jsval* argv ) { debug_assert( argc >= 1); int type = ToPrimitive( argv[0] ); for (int i=0; ipushOrder( this->m_orderQueue.front() ); } return JSVAL_VOID; } void CEntity::repath() { CVector2D destination; if( m_orderQueue.empty() ) return; while( !m_orderQueue.empty() && ( ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_COLLISION ) || ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_NOPATHING ) || ( m_orderQueue.front().m_type == CEntityOrder::ORDER_GOTO_SMOOTHED ) ) ) { destination = m_orderQueue.front().m_data[0].location; m_orderQueue.pop_front(); } g_Pathfinder.requestPath( me, destination ); } void CEntity::reorient() { m_orientation = m_graphics_orientation; m_ahead.x = sin( m_orientation ); m_ahead.y = cos( m_orientation ); if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) ((CBoundingBox*)m_bounds)->setOrientation( m_ahead ); updateActorTransforms(); } void CEntity::teleport() { m_position = m_graphics_position; m_bounds->setPosition( m_position.X, m_position.Z ); updateCollisionPatch(); repath(); } void CEntity::playerChanged() { if( m_actor ) m_actor->GetModel()->SetPlayerID( m_player->GetPlayerID() ); } void CEntity::checkSelection() { if( m_selected ) { if( !g_Selection.isSelected( me ) ) g_Selection.addSelection( me ); } else { if( g_Selection.isSelected( me ) ) g_Selection.removeSelection( me ); } } void CEntity::checkGroup() { g_Selection.changeGroup( me, -1 ); // Ungroup if( ( m_grouped >= 0 ) && ( m_grouped < MAX_GROUPS ) ) g_Selection.changeGroup( me, m_grouped ); } void CEntity::interpolate( float relativeoffset ) { CVector3D old_graphics_position = m_graphics_position; float old_graphics_orientation = m_graphics_orientation; m_graphics_position = Interpolate( m_position_previous, m_position, relativeoffset ); // Avoid wraparound glitches for interpolating angles. while( m_orientation < m_orientation_previous - PI ) m_orientation_previous -= 2 * PI; while( m_orientation > m_orientation_previous + PI ) m_orientation_previous += 2 * PI; m_graphics_orientation = Interpolate( m_orientation_previous, m_orientation, relativeoffset ); // Mark the actor transform data as invalid if the entity has moved since // the last call to 'interpolate'. // position.Y is ignored because we can't determine the new value without // calling snapToGround, which is slow. TODO: This may need to be adjusted to // handle flying units or moving terrain. if( m_graphics_orientation != old_graphics_orientation || m_graphics_position.X != old_graphics_position.X || m_graphics_position.Z != old_graphics_position.Z ) m_actor_transform_valid = false; // Update the actor transform data when necessary. if( !m_actor_transform_valid ) { snapToGround(); updateActorTransforms(); m_actor_transform_valid = true; } } void CEntity::invalidateActor() { m_actor_transform_valid = false; } void CEntity::render() { if( !m_orderQueue.empty() ) { std::deque::iterator it; CBoundingObject* destinationCollisionObject; float x0, y0, x, y; x = m_orderQueue.front().m_data[0].location.x; y = m_orderQueue.front().m_data[0].location.y; for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) { if( it->m_type == CEntityOrder::ORDER_PATROL ) break; x = it->m_data[0].location.x; y = it->m_data[0].location.y; } destinationCollisionObject = getContainingObject( CVector2D( x, y ) ); glShadeModel( GL_FLAT ); glBegin( GL_LINE_STRIP ); glVertex3f( m_position.X, m_position.Y + 0.25f, m_position.Z ); x = m_position.X; y = m_position.Z; for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ ) { x0 = x; y0 = y; x = it->m_data[0].location.x; y = it->m_data[0].location.y; rayIntersectionResults r; CVector2D fwd( x - x0, y - y0 ); float l = fwd.length(); fwd = fwd.normalize(); CVector2D rgt = fwd.beta(); if( getRayIntersection( CVector2D( x0, y0 ), fwd, rgt, l, m_bounds->m_radius, destinationCollisionObject, &r ) ) { glEnd(); glBegin( GL_LINES ); glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( x0 + fwd.x * r.distance, getAnchorLevel( x0 + fwd.x * r.distance, y0 + fwd.y * r.distance ) + 0.25f, y0 + fwd.y * r.distance ); glVertex3f( r.position.x, getAnchorLevel( r.position.x, r.position.y ) + 0.25f, r.position.y ); glEnd(); glBegin( GL_LINE_STRIP ); glVertex3f( x0, getAnchorLevel( x0, y0 ), y0 ); } switch( it->m_type ) { case CEntityOrder::ORDER_GOTO: glColor3f( 1.0f, 0.0f, 0.0f ); break; case CEntityOrder::ORDER_GOTO_COLLISION: glColor3f( 1.0f, 0.5f, 0.5f ); break; case CEntityOrder::ORDER_GOTO_NOPATHING: case CEntityOrder::ORDER_GOTO_SMOOTHED: glColor3f( 0.5f, 0.5f, 0.5f ); break; case CEntityOrder::ORDER_PATROL: glColor3f( 0.0f, 1.0f, 0.0f ); break; default: continue; } glVertex3f( x, getAnchorLevel( x, y ) + 0.25f, y ); } glEnd(); glShadeModel( GL_SMOOTH ); } glColor3f( 1.0f, 1.0f, 1.0f ); if( getCollisionObject( this ) ) glColor3f( 0.5f, 0.5f, 1.0f ); m_bounds->render( getAnchorLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f ); } float CEntity::getAnchorLevel( float x, float z ) { CTerrain *pTerrain = g_Game->GetWorld()->GetTerrain(); float groundLevel = pTerrain->getExactGroundLevel( x, z ); if( m_anchorType==L"Ground" ) { return groundLevel; } else { - return max( groundLevel, g_Renderer.m_WaterHeight ); + return max( groundLevel, g_Renderer.GetWaterManager()->m_WaterHeight ); } } void CEntity::renderSelectionOutline( float alpha ) { if( !m_bounds ) return; if( getCollisionObject( this ) ) glColor4f( 1.0f, 0.5f, 0.5f, alpha ); else { const SPlayerColour& col = m_player->GetColour(); glColor3f( col.r, col.g, col.b ); } glBegin( GL_LINE_LOOP ); CVector3D pos = m_graphics_position; switch( m_bounds->m_type ) { case CBoundingObject::BOUND_CIRCLE: { float radius = ((CBoundingCircle*)m_bounds)->m_radius; for( int i = 0; i < SELECTION_CIRCLE_POINTS; i++ ) { float ang = i * 2 * PI / (float)SELECTION_CIRCLE_POINTS; float x = pos.X + radius * sin( ang ); float y = pos.Z + radius * cos( ang ); #ifdef SELECTION_TERRAIN_CONFORMANCE glVertex3f( x, getAnchorLevel( x, y ) + 0.25f, y ); #else glVertex3f( x, pos.Y + 0.25f, y ); #endif } break; } case CBoundingObject::BOUND_OABB: { CVector2D p, q; CVector2D u, v; q.x = pos.X; q.y = pos.Z; float d = ((CBoundingBox*)m_bounds)->m_d; float w = ((CBoundingBox*)m_bounds)->m_w; u.x = sin( m_graphics_orientation ); u.y = cos( m_graphics_orientation ); v.x = u.y; v.y = -u.x; #ifdef SELECTION_TERRAIN_CONFORMANCE for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) { p = q + u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i-- ) { p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) - v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) { p = q - u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS ); glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ ) { p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) + v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); } #else p = q + u * h + v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q + u * h - v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q - u * h + v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); p = q + u * h + v * w; glVertex3f( p.x, getAnchorLevel( p.x, p.y ) + 0.25f, p.y ); #endif break; } } glEnd(); } void CEntity::renderHealthBar() { if( !m_bounds ) return; if( m_healthBarHeight < 0 ) return; // negative bar height means don't display health bar CCamera &g_Camera=*g_Game->GetView()->GetCamera(); float sx, sy; CVector3D above; above.X = m_position.X; above.Z = m_position.Z; above.Y = getAnchorLevel(m_position.X, m_position.Z) + m_healthBarHeight; g_Camera.GetScreenCoordinates(above, sx, sy); float fraction = clamp(m_healthCurr / m_healthMax, 0.0f, 1.0f); const float SIZE = 20; float x1 = sx - SIZE/2; float x2 = sx + SIZE/2; float y = g_yres - sy; glBegin(GL_LINES); // green part of bar glColor3f( 0, 1, 0 ); glVertex3f( x1, y, 0 ); glColor3f( 0, 1, 0 ); glVertex3f( x1 + SIZE*fraction, y, 0 ); // red part of bar glColor3f( 1, 0, 0 ); glVertex3f( x1 + SIZE*fraction, y, 0 ); glColor3f( 1, 0, 0 ); glVertex3f( x2, y, 0 ); glEnd(); } /* Scripting interface */ // Scripting initialization void CEntity::ScriptingInit() { AddMethod( "toString", 0 ); AddMethod( "order", 1 ); AddMethod( "orderQueued", 1 ); AddMethod( "kill", 0 ); //AddMethod( "damage", 1 ); AddMethod( "isIdle", 0 ); AddMethod( "hasClass", 1 ); AddMethod( "getSpawnPoint", 1 ); AddMethod( "addAura", 3 ); AddMethod( "removeAura", 1 ); AddMethod( "setActionParams", 5 ); AddClassProperty( L"template", (CBaseEntity* CEntity::*)&CEntity::m_base, false, (NotifyFn)&CEntity::loadBase ); AddClassProperty( L"traits.id.classes", (GetFn)&CEntity::getClassSet, (SetFn)&CEntity::setClassSet ); CJSComplex::ScriptingInit( "Entity", Construct, 2 ); } // Script constructor JSBool CEntity::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsval* argv, jsval* rval ) { debug_assert( argc >= 2 ); CVector3D position; float orientation = (float)( PI * ( (double)( rand() & 0x7fff ) / (double)0x4000 ) ); JSObject* jsBaseEntity = JSVAL_TO_OBJECT( argv[0] ); CStrW templateName; CBaseEntity* baseEntity = NULL; if( JSVAL_IS_OBJECT( argv[0] ) ) // only set baseEntity if jsBaseEntity is a valid object baseEntity = ToNative( cx, jsBaseEntity ); if( !baseEntity ) { try { templateName = g_ScriptingHost.ValueToUCString( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { *rval = JSVAL_NULL; JS_ReportError( cx, "Invalid template identifier" ); return( JS_TRUE ); } baseEntity = g_EntityTemplateCollection.getTemplate( templateName ); } if( !baseEntity ) { *rval = JSVAL_NULL; JS_ReportError( cx, "No such template: %s", CStr8(templateName).c_str() ); return( JS_TRUE ); } JSI_Vector3D::Vector3D_Info* jsVector3D = NULL; if( JSVAL_IS_OBJECT( argv[1] ) ) jsVector3D = (JSI_Vector3D::Vector3D_Info*)JS_GetInstancePrivate( cx, JSVAL_TO_OBJECT( argv[1] ), &JSI_Vector3D::JSI_class, NULL ); if( jsVector3D ) { position = *( jsVector3D->vector ); } if( argc >= 3 ) { try { orientation = ToPrimitive( argv[2] ); } catch( PSERROR_Scripting_ConversionFailed ) { // TODO: Net-safe random for this parameter. orientation = 0.0f; } } HEntity handle = g_EntityManager.create( baseEntity, position, orientation ); *rval = ToJSVal( *handle ); return( JS_TRUE ); } // Script-bound methods jsval CEntity::ToString( JSContext* cx, uintN UNUSED(argc), jsval* UNUSED(argv) ) { wchar_t buffer[256]; swprintf( buffer, 256, L"[object Entity: %ls]", m_base->m_Tag.c_str() ); buffer[255] = 0; utf16string str16(buffer, buffer+wcslen(buffer)); return( STRING_TO_JSVAL( JS_NewUCStringCopyZ( cx, str16.c_str() ) ) ); } bool CEntity::Order( JSContext* cx, uintN argc, jsval* argv, bool Queued ) { // This needs to be sorted (uses Scheduler rather than network messaging) debug_assert( argc >= 1 ); int orderCode; try { orderCode = ToPrimitive( argv[0] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid order type" ); return( false ); } CEntityOrder newOrder; CEntity* target; (int&)newOrder.m_type = orderCode; switch( orderCode ) { case CEntityOrder::ORDER_GOTO: case CEntityOrder::ORDER_PATROL: if( argc < 3 ) { JS_ReportError( cx, "Too few parameters" ); return( false ); } try { newOrder.m_data[0].location.x = ToPrimitive( argv[1] ); newOrder.m_data[0].location.y = ToPrimitive( argv[2] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid location" ); return( false ); } break; case CEntityOrder::ORDER_ATTACK_MELEE: case CEntityOrder::ORDER_GATHER: case CEntityOrder::ORDER_HEAL: if( argc < 1 ) { JS_ReportError( cx, "Too few parameters" ); return( false ); } target = ToNative( argv[1] ); if( !target ) { JS_ReportError( cx, "Invalid target" ); return( false ); } newOrder.m_data[0].entity = target->me; break; case CEntityOrder::ORDER_GENERIC: if( argc < 3 ) { JS_ReportError( cx, "Too few parameters" ); return( false ); } target = ToNative( argv[1] ); if( !target ) { JS_ReportError( cx, "Invalid target" ); return( false ); } newOrder.m_data[0].entity = target->me; try { newOrder.m_data[1].data = ToPrimitive( argv[2] ); } catch( PSERROR_Scripting_ConversionFailed ) { JS_ReportError( cx, "Invalid generic order type" ); return( false ); } break; default: JS_ReportError( cx, "Invalid order type" ); return( false ); } if( !Queued ) clearOrders(); pushOrder( newOrder ); return( true ); } /*bool CEntity::Damage( JSContext* cx, uintN argc, jsval* argv ) { CEntity* inflictor = NULL; if( argc >= 4 ) inflictor = ToNative( argv[3] ); if( argc >= 3 ) { CDamageType dmgType( ToPrimitive( argv[0] ), ToPrimitive( argv[1] ), ToPrimitive( argv[2] ) ); Damage( dmgType, inflictor ); return( true ); } if( argc >= 2 ) inflictor = ToNative( argv[1] ); // If it's a DamageType, use that. Otherwise, see if it's a float, if so, use // that as the 'typeless' unblockable damage type. CDamageType* dmg = ToNative( argv[0] ); if( !dmg ) { float dmgN; if( !ToPrimitive( cx, argv[0], dmgN ) ) return( false ); CDamageType dmgType( dmgN ); Damage( dmgType, inflictor ); return( true ); } Damage( *dmg, inflictor ); return( true ); }*/ bool CEntity::Kill( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) ) { for( AuraTable::iterator it = m_auras.begin(); it != m_auras.end(); it++ ) { it->second->RemoveAll(); delete it->second; } m_auras.clear(); for( AuraSet::iterator it = m_aurasInfluencingMe.begin(); it != m_aurasInfluencingMe.end(); it++ ) { (*it)->Remove( this ); } m_aurasInfluencingMe.clear(); // Change this entity's template to the corpse entity - but note // we don't fiddle with the actors or bounding information that we // usually do when changing templates. if(m_corpse == L"null") { kill(); } CBaseEntity* corpse = g_EntityTemplateCollection.getTemplate( m_corpse ); if( corpse ) { m_base = corpse; SetBase( m_base ); } if( m_bounds ) { delete( m_bounds ); m_bounds = NULL; } if( m_extant ) { m_extant = false; } updateCollisionPatch(); g_Selection.removeAll( me ); clearOrders(); g_EntityManager.SetDeath(true); if( m_actor ) m_actor->SetRandomAnimation( "death", true ); return( true ); } jsval CEntity::GetSpawnPoint( JSContext* UNUSED(cx), uintN argc, jsval* argv ) { float spawn_clearance = 2.0f; if( argc >= 1 ) { CBaseEntity* be = ToNative( argv[0] ); if( be ) { switch( be->m_bound_type ) { case CBoundingObject::BOUND_CIRCLE: spawn_clearance = be->m_bound_circle->m_radius; break; case CBoundingObject::BOUND_OABB: spawn_clearance = be->m_bound_box->m_radius; break; default: debug_warn("No bounding information for spawned object!" ); } } else spawn_clearance = ToPrimitive( argv[0] ); } else debug_warn("No arguments to Entity::GetSpawnPoint()" ); // TODO: Make netsafe. CBoundingCircle spawn( 0.0f, 0.0f, spawn_clearance, 0.0f ); if( m_bounds->m_type == CBoundingObject::BOUND_OABB ) { CBoundingBox* oabb = (CBoundingBox*)m_bounds; // Pick a start point int edge = rand() & 3; int point; double max_w = oabb->m_w + spawn_clearance + 1.0; double max_d = oabb->m_d + spawn_clearance + 1.0; int w_count = (int)( max_w * 2 ); int d_count = (int)( max_d * 2 ); CVector2D w_step = oabb->m_v * (float)( max_w / w_count ); CVector2D d_step = oabb->m_u * (float)( max_d / d_count ); CVector2D pos( m_position ); if( edge & 1 ) { point = rand() % ( 2 * d_count ) - d_count; pos += ( oabb->m_v * (float)max_w + d_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); } else { point = rand() % ( 2 * w_count ) - w_count; pos += ( oabb->m_u * (float)max_d + w_step * (float)point ) * ( ( edge & 2 ) ? -1.0f : 1.0f ); } int start_edge = edge; int start_point = point; spawn.m_pos = pos; // Then step around the edge (clockwise) until a free space is found, or // we've gone all the way around. while( getCollisionObject( &spawn ) ) { switch( edge ) { case 0: point++; pos += w_step; if( point >= w_count ) { edge = 1; point = -d_count; } break; case 1: point++; pos -= d_step; if( point >= d_count ) { edge = 2; point = w_count; } break; case 2: point--; pos -= w_step; if( point <= -w_count ) { edge = 3; point = d_count; } break; case 3: point--; pos += d_step; if( point <= -d_count ) { edge = 0; point = -w_count; } break; } if( ( point == start_point ) && ( edge == start_edge ) ) return( JSVAL_NULL ); spawn.m_pos = pos; } CVector3D rval( pos.x, getAnchorLevel( pos.x, pos.y ), pos.y ); return( ToJSVal( rval ) ); } else if( m_bounds->m_type == CBoundingObject::BOUND_CIRCLE ) { float ang; ang = (float)( rand() & 0x7fff ) / (float)0x4000; /* 0...2 */ ang *= PI; float radius = m_bounds->m_radius + 1.0f + spawn_clearance; float d_ang = spawn_clearance / ( 2.0f * radius ); float ang_end = ang + 2.0f * PI; float x = 0.0f, y = 0.0f; // make sure they're initialized for( ; ang < ang_end; ang += d_ang ) { x = m_position.X + radius * cos( ang ); y = m_position.Z + radius * sin( ang ); spawn.setPosition( x, y ); if( !getCollisionObject( &spawn ) ) break; } if( ang < ang_end ) { // Found a satisfactory position... CVector3D pos( x, 0, y ); pos.Y = getAnchorLevel( x, y ); return( ToJSVal( pos ) ); } else return( JSVAL_NULL ); } return( JSVAL_NULL ); } jsval CEntity::AddAura( JSContext* cx, uintN argc, jsval* argv ) { debug_assert( argc >= 3 ); debug_assert( JSVAL_IS_OBJECT(argv[2]) ); CStrW name = ToPrimitive( argv[0] ); float radius = ToPrimitive( argv[1] ); JSObject* handler = JSVAL_TO_OBJECT( argv[2] ); if( m_auras[name] ) { delete m_auras[name]; } m_auras[name] = new CAura( cx, this, name, radius, handler ); return JSVAL_VOID; } jsval CEntity::RemoveAura( JSContext* UNUSED(cx), uintN argc, jsval* argv ) { debug_assert( argc >= 1 ); CStrW name = ToPrimitive( argv[0] ); if( m_auras[name] ) { delete m_auras[name]; m_auras.erase(name); } return JSVAL_VOID; } jsval CEntity::SetActionParams( JSContext* UNUSED(cx), uintN argc, jsval* argv ) { debug_assert( argc == 5 ); int id = ToPrimitive( argv[0] ); float minRange = ToPrimitive( argv[1] ); float maxRange = ToPrimitive( argv[2] ); uint speed = ToPrimitive( argv[3] ); CStr8 animation = ToPrimitive( argv[4] ); m_actions[id] = SEntityAction( minRange, maxRange, speed, animation ); return JSVAL_VOID; } Index: ps/trunk/source/lib/ogl.cpp =================================================================== --- ps/trunk/source/lib/ogl.cpp (revision 3331) +++ ps/trunk/source/lib/ogl.cpp (revision 3332) @@ -1,380 +1,373 @@ // OpenGL helpers // // Copyright (c) 2002-2005 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 #include #include #include "lib.h" #include "sdl.h" #include "ogl.h" #include "detect.h" #include "debug.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/tex.h" #if MSC_VERSION #pragma comment(lib, "opengl32.lib") #pragma comment(lib, "glu32.lib") // glu32 is required - it is apparently pulled in by opengl32, // even though depends.exe marks it as demand-loaded. #endif //---------------------------------------------------------------------------- // extensions //---------------------------------------------------------------------------- // define extension function pointers extern "C" { #define FUNC(ret, name, params) ret (CALL_CONV *p##name) params; #define FUNC2(ret, nameARB, nameCore, version, params) ret (CALL_CONV *p##nameARB) params; #include "glext_funcs.h" #undef FUNC2 #undef FUNC } static const char* exts = NULL; static bool have_20, have_14, have_13, have_12; // return a C string of unspecified length containing a space-separated // list of all extensions the OpenGL implementation advertises. // (useful for crash logs). const char* oglExtList() { debug_assert(exts && "call oglInit before using this function"); return exts; } // paranoia: newer drivers may forget to advertise an extension // indicating support for something that has been folded into the core. // we therefore check for all extensions known to be offered by the // GL implementation present on the user's system; oglHaveExtension will // take this into account. // the app can therefore just ask for extensions and not worry about this. static bool isImplementedInCore(const char* ext) { #define MATCH(known_ext)\ if(!strcmp(ext, #known_ext))\ return true; if(have_20) { MATCH(GL_ARB_shader_objects); MATCH(GL_ARB_vertex_shader); MATCH(GL_ARB_fragment_shader); MATCH(GL_ARB_draw_buffers); MATCH(GL_ARB_texture_non_power_of_two); MATCH(GL_ARB_point_sprite); } if(have_14) { MATCH(GL_SGIS_generate_mipmap); MATCH(GL_NV_blend_square); MATCH(GL_ARB_depth_texture); MATCH(GL_ARB_shadow); MATCH(GL_EXT_fog_coord); MATCH(GL_EXT_multi_draw_arrays); MATCH(GL_ARB_point_parameters); MATCH(GL_EXT_secondary_color); MATCH(GL_EXT_blend_func_separate); MATCH(GL_EXT_stencil_wrap); MATCH(GL_ARB_texture_env_crossbar); MATCH(GL_EXT_texture_lod_bias); MATCH(GL_ARB_texture_mirrored_repeat); MATCH(GL_ARB_window_pos); } if(have_13) { MATCH(GL_ARB_texture_compression); MATCH(GL_ARB_texture_cube_map); MATCH(GL_ARB_multisample); MATCH(GL_ARB_multitexture); MATCH(GL_ARB_transpose_matrix); MATCH(GL_ARB_texture_env_add); MATCH(GL_ARB_texture_env_combine); MATCH(GL_ARB_texture_env_dot3); MATCH(GL_ARB_texture_border_clamp); } if(have_12) { MATCH(GL_EXT_texture3d); MATCH(GL_EXT_bgra); MATCH(GL_EXT_packed_pixels); MATCH(GL_EXT_rescale_normal); MATCH(GL_EXT_separate_specular_color); MATCH(GL_SGIS_texture_edge_clamp); MATCH(GL_SGIS_texture_lod); MATCH(GL_EXT_draw_range_elements); } #undef MATCH return false; } // check if the extension is supported by the OpenGL implementation. // takes subsequently added core support for some extensions into account. bool oglHaveExtension(const char* ext) { debug_assert(exts && "call oglInit before using this function"); if(isImplementedInCore(ext)) return true; const char *p = exts, *end; // make sure ext is valid & doesn't contain spaces if(!ext || ext == '\0' || strchr(ext, ' ')) return false; for(;;) { p = strstr(p, ext); if(!p) return false; // string not found - extension not supported end = p + strlen(ext); // end of current substring // make sure the substring found is an entire extension string, // i.e. it starts and ends with ' ' if((p == exts || p[-1] == ' ') && // valid start AND (*end == ' ' || *end == '\0')) // valid end return true; p = end; } } // check if the OpenGL implementation is at least at . // (format: "%d.%d" major minor) bool oglHaveVersion(const char* desired_version) { int desired_major, desired_minor; if(sscanf(desired_version, "%d.%d", &desired_major, &desired_minor) != 2) { debug_warn("invalid version string"); return false; } int major, minor; const char* version = (const char*)glGetString(GL_VERSION); if(!version || sscanf(version, "%d.%d", &major, &minor) != 2) { debug_warn("GL_VERSION invalid"); return false; } return (major > desired_major) || (major == desired_major && minor >= desired_minor); } // check if all given extension strings (passed as const char* parameters, // terminated by a 0 pointer) are supported by the OpenGL implementation, // as determined by oglHaveExtension. // returns 0 if all are present; otherwise, the first extension in the // list that's not supported (useful for reporting errors). // // note: dummy parameter is necessary to access parameter va_list. // // // rationale: this interface is more convenient than individual // oglHaveExtension calls and allows reporting which extension is missing. // // one disadvantage is that there is no way to indicate that either one // of 2 extensions would be acceptable, e.g. (ARB|EXT)_texture_env_dot3. // this is isn't so bad, since they wouldn't be named differently // if there weren't non-trivial changes between them. for that reason, // we refrain from equivalence checks (which would boil down to // string-matching known extensions to their equivalents). const char* oglHaveExtensions(int dummy, ...) { const char* ext; va_list args; va_start(args, dummy); for(;;) { ext = va_arg(args, const char*); // end of list reached; all were present => return 0. if(!ext) break; // not found => return name of missing extension. if(!oglHaveExtension(ext)) break; } va_end(args); return ext; } static void importExtensionFunctions() { // It should be safe to load the ARB function pointers even if the // extension isn't advertised, since we won't actually use them without // checking for the extension. #define FUNC(ret, name, params) *(void**)&p##name = SDL_GL_GetProcAddress(#name); #define FUNC2(ret, nameARB, nameCore, version, params) \ p##nameARB = NULL; \ if(oglHaveVersion(version)) \ *(void**)&p##nameARB = SDL_GL_GetProcAddress(#nameCore); \ if(!p##nameARB) /* use the ARB name if the driver lied about what version it supports */ \ *(void**)&p##nameARB = SDL_GL_GetProcAddress(#nameARB); #include "glext_funcs.h" #undef FUNC2 #undef FUNC } //---------------------------------------------------------------------------- static void dump_gl_error(GLenum err) { debug_printf("OGL| "); #define E(e) case e: debug_printf("%s\n", #e); break; switch (err) { E(GL_INVALID_ENUM) E(GL_INVALID_VALUE) E(GL_INVALID_OPERATION) E(GL_STACK_OVERFLOW) E(GL_STACK_UNDERFLOW) E(GL_OUT_OF_MEMORY) default:; } #undef E } #ifndef oglCheck // don't include this function if it has been defined (in ogl.h) as a no-op void oglCheck() { // glGetError may return multiple errors, so we poll it in a loop. // the debug_warn should only happen once (if this is set), though. bool error_enountered = false; for(;;) { GLenum err = glGetError(); if(err == GL_NO_ERROR) break; error_enountered = true; dump_gl_error(err); } if(error_enountered) debug_warn("OpenGL error(s) occurred"); } #endif // ignore and reset the specified error (as returned by glGetError). // any other errors that have occurred are reported as oglCheck would. // // this is useful for suppressing annoying error messages, e.g. // "invalid enum" for GL_CLAMP_TO_EDGE even though we've already // warned the user that their OpenGL implementation is too old. void oglSquelchError(GLenum err_to_ignore) { // glGetError may return multiple errors, so we poll it in a loop. // the debug_warn should only happen once (if this is set), though. bool error_enountered = false; for(;;) { GLenum err = glGetError(); if(err == GL_NO_ERROR) break; if(err == err_to_ignore) continue; error_enountered = true; dump_gl_error(err); } if(error_enountered) debug_warn("OpenGL error(s) occurred"); } //---------------------------------------------------------------------------- // feature and limit detect //---------------------------------------------------------------------------- int ogl_max_tex_size = -1; // [pixels] int ogl_max_tex_units = -1; // limit on GL_TEXTUREn // gfx_card and gfx_drv_ver are unchanged on failure. LibError ogl_get_gfx_info() { const char* vendor = (const char*)glGetString(GL_VENDOR); const char* renderer = (const char*)glGetString(GL_RENDERER); const char* version = (const char*)glGetString(GL_VERSION); // can fail if OpenGL not yet initialized, // or if called between glBegin and glEnd. if(!vendor || !renderer || !version) return ERR_AGAIN; snprintf(gfx_card, ARRAY_SIZE(gfx_card), "%s %s", vendor, renderer); // add "OpenGL" to differentiate this from the real driver version // (returned by platform-specific detect routines). snprintf(gfx_drv_ver, ARRAY_SIZE(gfx_drv_ver), "OpenGL %s", version); return ERR_OK; } -static void __stdcall emu_glDrawRangeElementsEXT(GLenum, GLuint, GLuint, GLsizei, GLenum, GLvoid*) -{ -} - // call after each video mode change, since thereafter extension functions // may have changed [address]. void oglInit() { // cache extension list and versions for oglHave*. // note: this is less about performance (since the above are not // time-critical) than centralizing the 'OpenGL is ready' check. exts = (const char*)glGetString(GL_EXTENSIONS); if(!exts) debug_warn("called before OpenGL is ready for use"); have_12 = oglHaveVersion("1.2"); have_13 = oglHaveVersion("1.3"); have_14 = oglHaveVersion("1.4"); have_20 = oglHaveVersion("2.0"); importExtensionFunctions(); -if(!pglDrawRangeElementsEXT) -pglDrawRangeElementsEXT = emu_glDrawRangeElementsEXT; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &ogl_max_tex_size); glGetIntegerv(GL_MAX_TEXTURE_UNITS, &ogl_max_tex_units); } Index: ps/trunk/source/renderer/TransparencyRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TransparencyRenderer.cpp (revision 3331) +++ ps/trunk/source/renderer/TransparencyRenderer.cpp (revision 3332) @@ -1,658 +1,658 @@ /** * ========================================================================= * File : TransparencyRenderer.h * Project : Pyrogenesis - * Description : ModelRenderer implementation that sorts models and/or - * : polygons based on distance from viewer, for transparency + * Description : ModelRenderer implementation that sorts models and/or + * : polygons based on distance from viewer, for transparency * : rendering. * * @author Rich Cross * @author Nicolai Hähnle * ========================================================================= */ #include "precompiled.h" #include #include #include "ogl.h" #include "MathUtil.h" #include "Vector3D.h" #include "Vector4D.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include "renderer/TransparencyRenderer.h" #include "renderer/VertexArray.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // PolygonSortModelRenderer implementation /** * Struct PSModelDef: Per-CModelDef data for the polygon sort vertex renderer */ struct PSModelDef : public CModelDefRPrivate { PSModelDef(CModelDefPtr mdef); - + /// Static vertex array VertexArray m_Array; - + /// UV is static VertexArray::Attribute m_UV; }; PSModelDef::PSModelDef(CModelDefPtr mdef) : m_Array(false) { m_UV.type = GL_FLOAT; m_UV.elems = 2; m_Array.AddAttribute(&m_UV); - + m_Array.SetNumVertices(mdef->GetNumVertices()); m_Array.Layout(); VertexArrayIterator UVit = m_UV.GetIterator(); - + ModelRenderer::BuildUV(mdef, UVit); - + m_Array.Upload(); m_Array.FreeBackingStore(); } /** * Struct PSModel: Per-CModel data for the polygon sorting renderer */ struct PSModel { PSModel(CModel* model); ~PSModel(); - + /** * BackToFrontIndexSort: Sort polygons by distance to camera for * transparency rendering and fill the indices array appropriately. * * @param worldToCam World to camera coordinate space transform * * @return Square of the estimated distance to the nearest triangle. */ float BackToFrontIndexSort(const CMatrix3D& objToCam); - + /// Back-link to the model CModel* m_Model; - + /// Dynamic per-CModel vertex array VertexArray m_Array; - + /// Position and lighting are recalculated on CPU every frame VertexArray::Attribute m_Position; VertexArray::Attribute m_Color; /// Indices array (sorted on CPU based on distance to camera) u16* m_Indices; }; PSModel::PSModel(CModel* model) : m_Model(model), m_Array(true) { CModelDefPtr mdef = m_Model->GetModelDef(); - + m_Position.type = GL_FLOAT; m_Position.elems = 3; m_Array.AddAttribute(&m_Position); m_Color.type = GL_UNSIGNED_BYTE; m_Color.elems = 4; m_Array.AddAttribute(&m_Color); - + m_Array.SetNumVertices(mdef->GetNumVertices()); m_Array.Layout(); - + m_Indices = new u16[mdef->GetNumFaces()*3]; } PSModel::~PSModel() { delete[] m_Indices; } typedef std::pair IntFloatPair; struct SortFacesByDist { bool operator()(const IntFloatPair& lhs,const IntFloatPair& rhs) { return lhs.second>rhs.second ? true : false; } }; float PSModel::BackToFrontIndexSort(const CMatrix3D& worldToCam) { static std::vector IndexSorter; CModelDefPtr mdef = m_Model->GetModelDef(); size_t numFaces = mdef->GetNumFaces(); const SModelFace* faces = mdef->GetFaces(); - + if (IndexSorter.size() < numFaces) IndexSorter.resize(numFaces); VertexArrayIterator Position = m_Position.GetIterator(); CVector3D tmpvtx; for(size_t i = 0; i < numFaces; ++i) { tmpvtx = Position[faces[i].m_Verts[0]]; tmpvtx += Position[faces[i].m_Verts[1]]; tmpvtx += Position[faces[i].m_Verts[2]]; tmpvtx *= 1.0f/3.0f; tmpvtx = worldToCam.Transform(tmpvtx); float distsqrd = SQR(tmpvtx.X)+SQR(tmpvtx.Y)+SQR(tmpvtx.Z); IndexSorter[i].first = (int)i; IndexSorter[i].second = distsqrd; } std::sort(IndexSorter.begin(),IndexSorter.begin()+numFaces,SortFacesByDist()); - + // now build index list u32 idxidx = 0; for (size_t i = 0; i < numFaces; ++i) { const SModelFace& face = faces[IndexSorter[i].first]; m_Indices[idxidx++] = (u16)(face.m_Verts[0]); m_Indices[idxidx++] = (u16)(face.m_Verts[1]); m_Indices[idxidx++] = (u16)(face.m_Verts[2]); } return IndexSorter[0].second; } /** * Struct PolygonSortModelRendererInternals: Internal data structure of * PolygonSortModelRenderer */ struct PolygonSortModelRendererInternals { /// Scratch space for normal vector calculation std::vector normals; }; // Construction / Destruction PolygonSortModelRenderer::PolygonSortModelRenderer() { m = new PolygonSortModelRendererInternals; } PolygonSortModelRenderer::~PolygonSortModelRenderer() { delete m; } // Create per-CModel data for the model (and per-CModelDef data if necessary) void* PolygonSortModelRenderer::CreateModelData(CModel* model) { CModelDefPtr mdef = model->GetModelDef(); PSModelDef* psmdef = (PSModelDef*)mdef->GetRenderData(m); - + if (!psmdef) { psmdef = new PSModelDef(mdef); mdef->SetRenderData(m, psmdef); } - + return new PSModel(model); } // Updated transforms void PolygonSortModelRenderer::UpdateModelData(CModel* model, void* data, u32 updateflags) { PSModel* psmdl = (PSModel*)data; if (updateflags & RENDERDATA_UPDATE_VERTICES) { CModelDefPtr mdef = model->GetModelDef(); size_t numVertices = mdef->GetNumVertices(); // build vertices if (m->normals.size() < numVertices) m->normals.resize(numVertices); VertexArrayIterator Position = psmdl->m_Position.GetIterator(); VertexArrayIterator Normal = VertexArrayIterator((char*)&m->normals[0], sizeof(CVector3D)); ModelRenderer::BuildPositionAndNormals(model, Position, Normal); VertexArrayIterator Color = psmdl->m_Color.GetIterator(); ModelRenderer::BuildColor4ub(model, Normal, Color); // upload everything to vertex buffer psmdl->m_Array.Upload(); } - + // resort model indices from back to front, according to camera position - and store // the returned sqrd distance to the centre of the nearest triangle PROFILE_START( "sorting transparent" ); - + CMatrix3D worldToCam; g_Renderer.GetCamera().m_Orientation.GetInverse(worldToCam); - + psmdl->BackToFrontIndexSort(worldToCam); PROFILE_END( "sorting transparent" ); } // Cleanup per-CModel data void PolygonSortModelRenderer::DestroyModelData(CModel* UNUSED(model), void* data) { PSModel* psmdl = (PSModel*)data; - + delete psmdl; } // Prepare for one rendering pass void PolygonSortModelRenderer::BeginPass(uint streamflags) { glEnableClientState(GL_VERTEX_ARRAY); if (streamflags & STREAM_UV0) glEnableClientState(GL_TEXTURE_COORD_ARRAY); if (streamflags & STREAM_COLOR) glEnableClientState(GL_COLOR_ARRAY); } // Cleanup rendering void PolygonSortModelRenderer::EndPass(uint streamflags) { if (streamflags & STREAM_UV0) glDisableClientState(GL_TEXTURE_COORD_ARRAY); if (streamflags & STREAM_COLOR) glDisableClientState(GL_COLOR_ARRAY); - + glDisableClientState(GL_VERTEX_ARRAY); } // Prepare for rendering models using this CModelDef void PolygonSortModelRenderer::PrepareModelDef(uint streamflags, CModelDefPtr def) { if (streamflags & STREAM_UV0) { PSModelDef* psmdef = (PSModelDef*)def->GetRenderData(m); - + debug_assert(psmdef); - + u8* base = psmdef->m_Array.Bind(); GLsizei stride = (GLsizei)psmdef->m_Array.GetStride(); - + glTexCoordPointer(2, GL_FLOAT, stride, base + psmdef->m_UV.offset); } } // Render one model void PolygonSortModelRenderer::RenderModel(uint streamflags, CModel* model, void* data) { CModelDefPtr mdef = model->GetModelDef(); PSModel* psmdl = (PSModel*)data; - + // Setup per-CModel arrays u8* base = psmdl->m_Array.Bind(); GLsizei stride = (GLsizei)psmdl->m_Array.GetStride(); - + glVertexPointer(3, GL_FLOAT, stride, base + psmdl->m_Position.offset); if (streamflags & STREAM_COLOR) - glColorPointer(3, psmdl->m_Color.type, stride, base + psmdl->m_Color.offset); + glColorPointer(3, psmdl->m_Color.type, stride, base + psmdl->m_Color.offset); // render the lot size_t numFaces = mdef->GetNumFaces(); pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdef->GetNumVertices(), (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, psmdl->m_Indices); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numFaces; } /////////////////////////////////////////////////////////////////////////////////////////////////// // SortModelRenderer implementation /** * Struct SModel: Per-CModel data for the model-sorting renderer */ struct SModel : public CModelRData { SModel(SortModelRendererInternals* tri, CModel* model); ~SModel(); - + // Back-link to the Model renderer SortModelRendererInternals* m_SMRI; - + // Private data of the ModelVertexRenderer void* m_Data; - + // Distance to camera (for sorting) float m_Distance; }; /** * Struct SortModelRendererInternals: Internal data structure of SortModelRenderer */ struct SortModelRendererInternals { /// Vertex renderer used for transform and lighting ModelVertexRendererPtr vertexRenderer; - + /// List of submitted models. std::vector models; }; SModel::SModel(SortModelRendererInternals* smri, CModel* model) : CModelRData(smri, model), m_SMRI(smri) { m_Data = m_SMRI->vertexRenderer->CreateModelData(model); m_Distance = 0; } SModel::~SModel() { m_SMRI->vertexRenderer->DestroyModelData(GetModel(), m_Data); } // Construction / Destruction SortModelRenderer::SortModelRenderer(ModelVertexRendererPtr vertexRenderer) { m = new SortModelRendererInternals; m->vertexRenderer = vertexRenderer; } SortModelRenderer::~SortModelRenderer() { delete m; } // Submit a model: Create, but don't fill in, our own Model and ModelDef structures void SortModelRenderer::Submit(CModel* model) { CModelRData* rdata = (CModelRData*)model->GetRenderData(); SModel* smdl; - + if (rdata && rdata->GetKey() == m) { smdl = (SModel*)rdata; } else { smdl = new SModel(m, model); rdata = smdl; model->SetRenderData(rdata); model->SetDirty(~0u); g_Renderer.LoadTexture(model->GetTexture(), GL_CLAMP_TO_EDGE); } - + m->models.push_back(smdl); } // Transform and sort all models struct SortModelsByDist { bool operator()(SModel* lhs, SModel* rhs) { return lhs->m_Distance > rhs->m_Distance ? true : false; } }; void SortModelRenderer::PrepareModels() { CMatrix3D worldToCam; - + if (m->models.size() == 0) return; - + g_Renderer.m_Camera.m_Orientation.GetInverse(worldToCam); - + for(std::vector::iterator it = m->models.begin(); it != m->models.end(); ++it) { SModel* smdl = *it; CModel* model = smdl->GetModel(); - + debug_assert(model->GetRenderData() == smdl); - + m->vertexRenderer->UpdateModelData(model, smdl->m_Data, smdl->m_UpdateFlags); smdl->m_UpdateFlags = 0; - + CVector3D modelpos = model->GetTransform().GetTranslation(); - + modelpos = worldToCam.Transform(modelpos); - + smdl->m_Distance = modelpos.Z; } PROFILE_START( "sorting transparent" ); std::sort(m->models.begin(), m->models.end(), SortModelsByDist()); PROFILE_END( "sorting transparent" ); } // Cleanup per-frame model list void SortModelRenderer::EndFrame() { m->models.clear(); } // Return whether models have been submitted this frame bool SortModelRenderer::HaveSubmissions() { return m->models.size() != 0; } // Render submitted models (filtered by flags) using the given modifier void SortModelRenderer::Render(RenderModifierPtr modifier, u32 flags) { uint pass = 0; - + if (m->models.size() == 0) return; - + do { u32 streamflags = modifier->BeginPass(pass); CModelDefPtr lastmdef; CTexture* lasttex = 0; - + m->vertexRenderer->BeginPass(streamflags); - + for(std::vector::iterator it = m->models.begin(); it != m->models.end(); ++it) { SModel* smdl = *it; CModel* mdl = smdl->GetModel(); - + if (flags & !(mdl->GetFlags() & flags)) continue; - + debug_assert(smdl->GetKey() == m); CModelDefPtr mdef = mdl->GetModelDef(); CTexture* tex = mdl->GetTexture(); - + // Prepare per-CModelDef data if changed if (mdef != lastmdef) { m->vertexRenderer->PrepareModelDef(streamflags, mdef); lastmdef = mdef; } - + // Prepare necessary RenderModifier stuff if (tex != lasttex) { modifier->PrepareTexture(pass, tex); lasttex = tex; } - + modifier->PrepareModel(pass, mdl); - + // Render the model m->vertexRenderer->RenderModel(streamflags, mdl, smdl->m_Data); } - + m->vertexRenderer->EndPass(streamflags); } while(!modifier->EndPass(pass++)); } /////////////////////////////////////////////////////////////////////////////////////////////////// // TransparentRenderModifier implementation TransparentRenderModifier::TransparentRenderModifier() { } TransparentRenderModifier::~TransparentRenderModifier() { } u32 TransparentRenderModifier::BeginPass(uint pass) { if (pass == 0) { // First pass: Put down Z for opaque parts of the model, // don't touch the color buffer. glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); // just pass through texture's alpha glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); // Set the proper LOD bias glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); - + glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER,0.975f); // render everything with color writes off to setup depth buffer correctly glColorMask(0,0,0,0); - + return STREAM_POS|STREAM_UV0; } else { // Second pass: Put down color, disable Z write glColorMask(1,1,1,1); glDepthMask(0); // setup texture environment to modulate diffuse color with texture color 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); glAlphaFunc(GL_GREATER,0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - + // Set the proper LOD bias glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); - + return STREAM_POS|STREAM_COLOR|STREAM_UV0; } } bool TransparentRenderModifier::EndPass(uint pass) { if (pass == 0) return false; // multi-pass - + glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); glDepthMask(1); - + return true; } void TransparentRenderModifier::PrepareTexture(uint UNUSED(pass), CTexture* texture) { g_Renderer.SetTexture(0, texture); } void TransparentRenderModifier::PrepareModel(uint UNUSED(pass), CModel* UNUSED(model)) { // No per-model setup necessary } /////////////////////////////////////////////////////////////////////////////////////////////////// // TransparentShadowRenderModifier implementation TransparentShadowRenderModifier::TransparentShadowRenderModifier() { } TransparentShadowRenderModifier::~TransparentShadowRenderModifier() { } u32 TransparentShadowRenderModifier::BeginPass(uint pass) { debug_assert(pass == 0); - + glDepthMask(0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); // Set the proper LOD bias glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); return STREAM_POS|STREAM_UV0; } bool TransparentShadowRenderModifier::EndPass(uint UNUSED(pass)) { glDepthMask(1); glDisable(GL_BLEND); - + return true; } void TransparentShadowRenderModifier::PrepareTexture(uint UNUSED(pass), CTexture* texture) { g_Renderer.SetTexture(0, texture); } void TransparentShadowRenderModifier::PrepareModel(uint UNUSED(pass), CModel* UNUSED(model)) { // No per-model setup necessary } Index: ps/trunk/source/renderer/Renderer.h =================================================================== --- ps/trunk/source/renderer/Renderer.h (revision 3331) +++ ps/trunk/source/renderer/Renderer.h (revision 3332) @@ -1,449 +1,416 @@ /////////////////////////////////////////////////////////////////////////////// // // Name: Renderer.h // 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 +// on top of OpenGL to handle rendering the basic visual games // types - terrain, models, sprites, particles etc // /////////////////////////////////////////////////////////////////////////////// #ifndef RENDERER_H #define RENDERER_H #include #include "ogl.h" #include "Camera.h" #include "Frustum.h" -#include "PatchRData.h" #include "SHCoeffs.h" #include "Terrain.h" #include "Singleton.h" #include "Overlay.h" #include "scripting/ScriptableObject.h" #include "renderer/ModelRenderer.h" // necessary declarations class CCamera; class CPatch; class CSprite; class CParticleSys; class COverlay; class CMaterial; class CModel; class CLightEnv; class CTexture; class CTerrain; class RenderPathVertexShader; +class WaterManager; // 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 struct CRendererInternals; class CRenderer : public Singleton, public CJSObject { -private: - std::vector m_VisiblePatches; - -public: - Handle m_WaterTexture[60]; - int m_WaterCurrentTex; - CColor m_WaterColor; - bool m_RenderWater; - bool m_WaterScroll; - float m_WaterHeight; - float m_WaterMaxAlpha; - float m_WaterFullDepth; - float m_WaterAlphaOffset; - - float m_SWaterSpeed; - float m_TWaterSpeed; - float m_SWaterTrans; - float m_TWaterTrans; - float m_SWaterScrollCounter; - float m_TWaterScrollCounter; - float m_WaterTexTimer; - public: // various enumerations and renderer related constants enum { NumAlphaMaps=14 }; enum { MaxTextureUnits=16 }; enum Option { OPT_NOVBO, OPT_NOPBUFFER, OPT_SHADOWS, OPT_SHADOWCOLOR, OPT_LODBIAS }; - + enum RenderPath { // If no rendering path is configured explicitly, the renderer // will choose the path when Open() is called. RP_DEFAULT, - + // Classic fixed function. RP_FIXED, - + // Use (GL 2.0) vertex shaders for T&L when possible. RP_VERTEXSHADER }; - + // 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 + // count of the number of stats added together size_t m_Counter; // number of draw calls per frame - total DrawElements + Begin/End immediate mode loops size_t m_DrawCalls; // number of terrain triangles drawn size_t m_TerrainTris; // number of (non-transparent) model triangles drawn size_t m_ModelTris; // number of splat passes for alphamapping size_t m_BlendSplats; }; - // renderer options + // renderer options struct Options { bool m_NoVBO; bool m_Shadows; RGBAColor m_ShadowColor; float m_LodBias; RenderPath m_RenderPath; } m_Options; public: // constructor, destructor CRenderer(); ~CRenderer(); // open up the renderer: performs any necessary initialisation bool Open(int width,int height,int depth); // resize renderer view void Resize(int width,int height); - - // set/get boolean renderer option + + // set/get boolean renderer option void SetOptionBool(enum Option opt,bool value); bool GetOptionBool(enum Option opt) const; - // set/get RGBA color renderer option + // set/get RGBA color renderer option void SetOptionColor(enum Option opt,const RGBAColor& value); void SetOptionFloat(enum Option opt, float val); const RGBAColor& GetOptionColor(enum Option opt) const; void SetRenderPath(RenderPath rp); RenderPath GetRenderPath() const { return m_Options.m_RenderPath; } static CStr GetRenderPathName(RenderPath rp); static RenderPath GetRenderPathByName(CStr name); - + // 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 + // 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: + // 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 a negative error code if anything along the way fails. // called via delay-load mechanism. int LoadAlphaMaps(); void UnloadAlphaMaps(); - // load textures for the active water type. - // return a negative error code if anything along the way fails. - // called via delay-load mechanism. - int LoadWaterTextures(); - void UnloadWaterTextures(); - // return stats accumulated for current frame const Stats& GetStats() { return m_Stats; } // return the current light environment const CLightEnv &GetLightEnv() { return *m_LightEnv; } - + // return the current camera const CCamera& GetCamera() const { return m_Camera; } - + + /** + * GetWaterManager: Return the renderer's water manager. + * + * @return the WaterManager object used by the renderer + */ + WaterManager* GetWaterManager() { return m_WaterManager; } + /** * SetFastPlayerColor: Tell the renderer which path to take for * player colored models. Both paths should provide the same visual * quality, however the slow path runs on older hardware using multi-pass. * * @param fast true if the fast path should be used from now on. If fast * is true but the OpenGL implementation does not support it, a warning * is printed and the slow path is used instead. */ void SetFastPlayerColor(bool fast); - + protected: friend struct CRendererInternals; friend class CVertexBuffer; friend class CPatchRData; friend class FixedFunctionModelRenderer; friend class ModelRenderer; friend class PolygonSortModelRenderer; friend class SortModelRenderer; friend class RenderPathVertexShader; friend class HWLightingModelRenderer; friend class InstancingModelRenderer; + friend class TerrainRenderer; // scripting jsval JSI_GetFastPlayerColor(JSContext*); void JSI_SetFastPlayerColor(JSContext* ctx, jsval newval); jsval JSI_GetRenderPath(JSContext*); void JSI_SetRenderPath(JSContext* ctx, jsval newval); static void ScriptingInit(); - + // patch rendering stuff - void RenderPatchSubmissions(); void RenderPatches(); - void RenderWater(); // model rendering stuff void RenderModels(); void RenderTransparentModels(); // 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: /// Private data that is not needed by inline functions CRendererInternals* m; // 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; // ogl_tex handle of composite alpha map (all the alpha maps packed into one texture) Handle m_hCompositeAlphaMap; - // 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; + // object space bound of shadow casting objects + CBound m_ShadowBound; // 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; bool m_VertexShader; } m_Caps; // build card cap bits void EnumCaps(); // per-frame renderer stats Stats m_Stats; // active textures on each unit GLuint m_ActiveTextures[MaxTextureUnits]; - + // Additional state that is only available when the vertex shader // render path is used (according to m_Options.m_RenderPath) RenderPathVertexShader* m_VertexShader; /// If false, use a multipass fallback for player colors. bool m_FastPlayerColor; - + + /** + * m_WaterManager: the WaterManager object used for water textures and settings + * (e.g. water color, water height) + */ + WaterManager* m_WaterManager; + /** * m_SortAllTransparent: If true, all transparent models are * rendered using the TransparencyRenderer which performs sorting. * * Otherwise, transparent models are rendered using the faster * batching renderer when possible. */ bool m_SortAllTransparent; - - + + /** * m_FastNormals: Use faster normal transformation in the * software transform by multiplying with the bone matrix itself * instead of the transpose of the inverse. */ bool m_FastNormals; - // State used by LoadWaterTextures with progressive loading - uint cur_loading_water_tex; - // Various model renderers struct Models { ModelRenderer* NormalFF; ModelRenderer* PlayerFF; ModelRenderer* NormalHWLit; ModelRenderer* PlayerHWLit; ModelRenderer* NormalInstancing; ModelRenderer* PlayerInstancing; - + ModelRenderer* TranspFF; ModelRenderer* TranspHWLit; ModelRenderer* TranspSortAll; - + ModelVertexRendererPtr VertexFF; ModelVertexRendererPtr VertexHWLit; ModelVertexRendererPtr VertexInstancing; ModelVertexRendererPtr VertexPolygonSort; RenderModifierPtr ModWireframe; RenderModifierPtr ModPlain; RenderModifierPtr ModPlayer; RenderModifierPtr ModSolidColor; RenderModifierPtr ModTransparent; RenderModifierPtr ModTransparentShadow; } m_Models; }; #endif Index: ps/trunk/source/renderer/PatchRData.h =================================================================== --- ps/trunk/source/renderer/PatchRData.h (revision 3331) +++ ps/trunk/source/renderer/PatchRData.h (revision 3332) @@ -1,138 +1,114 @@ #ifndef _PATCHRDATA_H #define _PATCHRDATA_H #include #include "Color.h" #include "Vector3D.h" #include "RenderableObject.h" #include "VertexBufferManager.h" class CPatch; ////////////////////////////////////////////////////////////////////////////////////////////////// // CPatchRData: class encapsulating logic for rendering terrain patches; holds per // patch data, plus some supporting static functions for batching, etc class CPatchRData : public CRenderData { public: - CPatchRData(CPatch* patch); - ~CPatchRData(); + CPatchRData(CPatch* patch); + ~CPatchRData(); void Update(); void RenderBase(); void RenderBlends(); void RenderOutline(); void RenderStreams(u32 streamflags); - // submit a patch to render this frame - static void Submit(CPatch* patch); - // clear per frame patch list - static void ClearSubmissions() { m_Patches.clear(); } - - // render the base pass of all patches - static void RenderBaseSplats(); - // render the blend pass of all patches - static void RenderBlendSplats(); - // render the outlines of all patches - static void RenderOutlines(); - // render given streams of all patches; don't fiddle with renderstate - static void RenderStreamsAll(u32 streamflags); - // apply given shadow map to all terrain patches - static void ApplyShadowMap(GLuint handle); - - // submit base batches for this patch to the vertex buffer - void SubmitBaseBatches(); - // submit next set of blend batches for this patch to the vertex buffer; - void SubmitBlendBatches(); - private: struct SSplat { SSplat() : m_Texture(0), m_IndexCount(0) {} // handle of texture to apply during splat Handle m_Texture; // offset into the index array for this patch where splat starts u32 m_IndexStart; // number of indices used by splat u32 m_IndexCount; }; struct SBaseVertex { // vertex position CVector3D m_Position; // vertex color SColor4ub m_Color; // vertex uvs for base texture float m_UVs[2]; }; struct SBlendVertex { // vertex position CVector3D m_Position; // vertex color SColor4ub m_Color; // vertex uvs for base texture float m_UVs[2]; // vertex uvs for alpha texture float m_AlphaUVs[2]; }; struct STex { bool operator==(const STex& rhs) const { return m_Handle==rhs.m_Handle; } bool operator<(const STex& rhs) const { return m_Priority m_Indices; // indices into base vertices for the shadow map pass std::vector m_ShadowMapIndices; // list of base splats to apply to this patch std::vector m_Splats; // vertices to use for blending transition texture passes std::vector m_BlendVertices; // remembers the index in the m_Vertices array of each blend vertex, so that we can // properly update its color for fog of war and shroud of darkness std::vector m_BlendVertexIndices; // indices into blend vertices for the blend splats std::vector m_BlendIndices; // splats used in blend pass std::vector m_BlendSplats; - - // list of all submitted patches - static std::vector m_Patches; }; #endif Index: ps/trunk/source/renderer/WaterManager.h =================================================================== --- ps/trunk/source/renderer/WaterManager.h (nonexistent) +++ ps/trunk/source/renderer/WaterManager.h (revision 3332) @@ -0,0 +1,68 @@ +/** + * ========================================================================= + * File : WaterManager.h + * Project : Pyrogenesis + * Description : Water settings (speed, height) and texture management + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#ifndef WATERMANAGER_H +#define WATERMANAGER_H + +#include "ps/Overlay.h" + +/** + * Class WaterManager: Maintain water settings and textures. + * + * This could be extended to provide more advanced water rendering effects + * (refractive/reflective water) in the future. + */ +class WaterManager +{ +public: + Handle m_WaterTexture[60]; + int m_WaterCurrentTex; + CColor m_WaterColor; + bool m_RenderWater; + bool m_WaterScroll; + float m_WaterHeight; + float m_WaterMaxAlpha; + float m_WaterFullDepth; + float m_WaterAlphaOffset; + + float m_SWaterSpeed; + float m_TWaterSpeed; + float m_SWaterTrans; + float m_TWaterTrans; + float m_SWaterScrollCounter; + float m_TWaterScrollCounter; + float m_WaterTexTimer; + +public: + WaterManager(); + ~WaterManager(); + + /** + * LoadWaterTextures: Load water textures from within the + * progressive load framework. + * + * @return 0 if loading has completed, a value from 1 to 100 (in percent of completion) + * if more textures need to be loaded and a negative error value on failure. + */ + int LoadWaterTextures(); + + /** + * UnloadWaterTextures: Free any loaded water textures and reset the internal state + * so that another call to LoadWaterTextures will begin progressive loading. + */ + void UnloadWaterTextures(); + +private: + /// State of progressive loading (in # of loaded textures) + uint cur_loading_water_tex; +}; + + +#endif // WATERMANAGER_H Property changes on: ps/trunk/source/renderer/WaterManager.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/ShadowMap.h =================================================================== --- ps/trunk/source/renderer/ShadowMap.h (nonexistent) +++ ps/trunk/source/renderer/ShadowMap.h (revision 3332) @@ -0,0 +1,77 @@ +/** + * ========================================================================= + * File : ShadowMap.h + * Project : Pyrogenesis + * Description : Shadow mapping related texture and matrix management + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#ifndef SHADOWMAP_H +#define SHADOWMAP_H + +#include "ogl.h" + +class CBound; +class CMatrix3D; + +struct ShadowMapInternals; + +/** + * Class ShadowMap: Maintain the shadow map texture and perform necessary OpenGL setup, + * including matrix calculations. + * + * The class will automatically generate a texture the first time the shadow map is rendered into. + * The texture will not be resized afterwards. + * + * @todo use depth textures for self-shadowing + */ +class ShadowMap +{ +public: + ShadowMap(); + ~ShadowMap(); + + /** + * SetupFrame: Setup shadow map texture and matrices for this frame. + * + * @param visibleBounds bound around objects that are visible on the screen + */ + void SetupFrame(const CBound& visibleBounds); + + /** + * BeginRender: Set OpenGL state for rendering into the shadow map texture. + * + * @todo this depends in non-obvious ways on the behaviour of the call-site + */ + void BeginRender(); + + /** + * EndRender: Finish rendering into the shadow map. + * + * @todo this depends in non-obvious ways on the behaviour of the call-site + */ + void EndRender(); + + /** + * GetTexture: Retrieve the OpenGL texture object name that contains the shadow map. + * + * @return the texture name of the shadow map texture + */ + GLuint GetTexture(); + + /** + * GetTextureMatrix: Retrieve the world-space to shadow map texture coordinates + * transformation matrix. + * + * @return the matrix that transforms world-space coordinates into homogenous + * shadow map texture coordinates + */ + const CMatrix3D& GetTextureMatrix(); + +private: + ShadowMapInternals* m; +}; + +#endif // SHADOWMAP_H Property changes on: ps/trunk/source/renderer/ShadowMap.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/TerrainRenderer.h =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.h (nonexistent) +++ ps/trunk/source/renderer/TerrainRenderer.h (revision 3332) @@ -0,0 +1,112 @@ +/** + * ========================================================================= + * File : TerrainRenderer.h + * Project : Pyrogenesis + * Description : Terrain rendering (everything related to patches and + * : water) is encapsulated in TerrainRenderer + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#ifndef TERRAINRENDERER_H +#define TERRAINRENDERER_H + +class CPatch; +class WaterManager; + +struct TerrainRendererInternals; + +/** + * Class TerrainRenderer: Render everything related to the terrain, + * especially patches and water. + */ +class TerrainRenderer +{ +public: + TerrainRenderer(); + ~TerrainRenderer(); + + /** + * Submit: Add a patch for rendering in this frame. + * + * preconditions : PrepareForRendering must not have been called + * for this frame yet. + * The patch must not have been submitted in this frame yet (i.e. you + * can only submit a frame once). + * + * @param patch the patch + */ + void Submit(CPatch* patch); + + /** + * PrepareForRendering: Prepare internal data structures like vertex + * buffers for rendering. + * + * All patches must have been submitted before the call to + * PrepareForRendering. + * PrepareForRendering must be called before any rendering calls. + */ + void PrepareForRendering(); + + /** + * EndFrame: Remove all patches from the list of submitted patches. + */ + void EndFrame(); + + /** + * HaveSubmissions: Query whether any patches have been submitted + * for this frame. + * + * @return @c true if a patch has been submitted for this frame, + * @c false otherwise. + */ + bool HaveSubmissions(); + + /** + * RenderTerrain: Render textured terrain (including blends between + * different terrain types). + * + * preconditions : PrepareForRendering must have been called this + * frame before calling RenderTerrain. + */ + void RenderTerrain(); + + /** + * RenderPatches: Render all patches un-textured as polygons. + * + * preconditions : PrepareForRendering must have been called this + * frame before calling RenderPatches. + */ + void RenderPatches(); + + /** + * RenderOutlines: Render the outline of patches as lines. + * + * preconditions : PrepareForRendering must have been called this + * frame before calling RenderOutline. + */ + void RenderOutlines(); + + /** + * RenderWater: Render water for all patches that have been submitted + * this frame. + * + * preconditions : PrepareForRendering must have been called this + * frame before calling RenderOutline. + */ + void RenderWater(); + + /** + * ApplyShadowMap: transition measure during refactoring + * + * @todo fix this + * @deprecated + */ + void ApplyShadowMap(GLuint handle); + +private: + TerrainRendererInternals* m; +}; + +#endif // TERRAINRENDERER_H Property changes on: ps/trunk/source/renderer/TerrainRenderer.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 3331) +++ ps/trunk/source/renderer/Renderer.cpp (revision 3332) @@ -1,1820 +1,1251 @@ /////////////////////////////////////////////////////////////////////////////// // // 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 "Terrain.h" #include "Matrix3D.h" #include "MathUtil.h" #include "Camera.h" -#include "PatchRData.h" #include "Texture.h" #include "LightEnv.h" #include "Terrain.h" #include "CLogger.h" #include "ps/Game.h" #include "Profile.h" #include "Game.h" #include "World.h" #include "Player.h" #include "LOSManager.h" #include "Model.h" #include "ModelDef.h" #include "ogl.h" #include "lib/res/res.h" #include "lib/res/file/file.h" #include "lib/res/graphics/tex.h" #include "lib/res/graphics/ogl_tex.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "renderer/FixedFunctionModelRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/PlayerRenderer.h" #include "renderer/RenderModifiers.h" #include "renderer/RenderPathVertexShader.h" +#include "renderer/ShadowMap.h" +#include "renderer/TerrainRenderer.h" #include "renderer/TransparencyRenderer.h" +#include "renderer/WaterManager.h" #define LOG_CATEGORY "graphics" /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { public: CRendererStatsTable(const CRenderer::Stats& st); - + // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); uint GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(uint row, uint col); AbstractProfileTable* GetChild(uint row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; - + /// Column descriptions std::vector columnDescriptions; - + enum { Row_Counter = 0, Row_DrawCalls, Row_TerrainTris, Row_ModelTris, Row_BlendSplats, - + // Must be last to count number of rows NumberRows }; // no copy ctor because some members are const CRendererStatsTable& operator=(const CRendererStatsTable&); }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } uint CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(uint row, uint col) { char buf[256]; - + switch(row) { case Row_Counter: if (col == 0) return "counter"; snprintf(buf, sizeof(buf), "%d", Stats.m_Counter); return buf; - + case Row_DrawCalls: if (col == 0) return "# draw calls"; snprintf(buf, sizeof(buf), "%d", Stats.m_DrawCalls); return buf; - + case Row_TerrainTris: if (col == 0) return "# terrain tris"; snprintf(buf, sizeof(buf), "%d", Stats.m_TerrainTris); return buf; - + case Row_ModelTris: if (col == 0) return "# model tris"; snprintf(buf, sizeof(buf), "%d", Stats.m_ModelTris); return buf; - + case Row_BlendSplats: if (col == 0) return "# blend splats"; snprintf(buf, sizeof(buf), "%d", Stats.m_BlendSplats); return buf; - + default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(uint UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; + + /// Water manager + WaterManager waterManager; + + /// Terrain renderer + TerrainRenderer* terrainRenderer; + + /// Shadow map + ShadowMap* shadow; CRendererInternals() : profileTable(g_Renderer.m_Stats) { + terrainRenderer = new TerrainRenderer(); + shadow = new ShadowMap(); + } + + ~CRendererInternals() + { + delete shadow; + delete terrainRenderer; } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::CRenderer() { m = new CRendererInternals; + m_WaterManager = &m->waterManager; + g_ProfileViewer.AddRootTable(&m->profileTable); - + 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_SortAllTransparent = false; m_FastNormals = true; - + m_VertexShader = 0; - + m_Options.m_NoVBO=false; m_Options.m_Shadows=true; m_Options.m_ShadowColor=RGBAColor(0.4f,0.4f,0.4f,1.0f); m_Options.m_RenderPath = RP_DEFAULT; for (uint i=0;iInit()) { delete m_VertexShader; m_VertexShader = 0; } // model rendering m_Models.VertexFF = ModelVertexRendererPtr(new FixedFunctionModelRenderer); if (HWLightingModelRenderer::IsAvailable()) m_Models.VertexHWLit = ModelVertexRendererPtr(new HWLightingModelRenderer); if (InstancingModelRenderer::IsAvailable()) m_Models.VertexInstancing = ModelVertexRendererPtr(new InstancingModelRenderer); m_Models.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer); m_Models.NormalFF = new BatchModelRenderer(m_Models.VertexFF); m_Models.PlayerFF = new BatchModelRenderer(m_Models.VertexFF); m_Models.TranspFF = new SortModelRenderer(m_Models.VertexFF); if (m_Models.VertexHWLit) { m_Models.NormalHWLit = new BatchModelRenderer(m_Models.VertexHWLit); m_Models.PlayerHWLit = new BatchModelRenderer(m_Models.VertexHWLit); m_Models.TranspHWLit = new SortModelRenderer(m_Models.VertexHWLit); } else { m_Models.NormalHWLit = NULL; m_Models.PlayerHWLit = NULL; m_Models.TranspHWLit = NULL; } if (m_Models.VertexInstancing) { m_Models.NormalInstancing = new BatchModelRenderer(m_Models.VertexInstancing); m_Models.PlayerInstancing = new BatchModelRenderer(m_Models.VertexInstancing); } else { m_Models.NormalInstancing = NULL; m_Models.PlayerInstancing = NULL; } m_Models.TranspSortAll = new SortModelRenderer(m_Models.VertexPolygonSort); m_Models.ModWireframe = RenderModifierPtr(new WireframeRenderModifier); m_Models.ModPlain = RenderModifierPtr(new PlainRenderModifier); SetFastPlayerColor(true); m_Models.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier); m_Models.ModTransparent = RenderModifierPtr(new TransparentRenderModifier); m_Models.ModTransparentShadow = RenderModifierPtr(new TransparentShadowRenderModifier); - // water - m_RenderWater = true; - m_WaterHeight = 5.0f; - m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f); - m_WaterFullDepth = 4.0f; - m_WaterMaxAlpha = 0.85f; - m_WaterAlphaOffset = -0.05f; - m_SWaterTrans=0; - m_TWaterTrans=0; - m_SWaterSpeed=0.0015f; - m_TWaterSpeed=0.0015f; - m_SWaterScrollCounter=0; - m_TWaterScrollCounter=0; - m_WaterCurrentTex=0; - - cur_loading_water_tex = 0; - ONCE( ScriptingInit(); ); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { // model rendering delete m_Models.NormalFF; delete m_Models.PlayerFF; delete m_Models.TranspFF; delete m_Models.NormalHWLit; delete m_Models.PlayerHWLit; delete m_Models.TranspHWLit; delete m_Models.NormalInstancing; delete m_Models.PlayerInstancing; delete m_Models.TranspSortAll; // general delete m_VertexShader; m_VertexShader = 0; // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO=false; m_Caps.m_TextureBorderClamp=false; m_Caps.m_GenerateMipmaps=false; m_Caps.m_VertexShader=false; // now start querying extensions if (!m_Options.m_NoVBO) { if (oglHaveExtension("GL_ARB_vertex_buffer_object")) { m_Caps.m_VBO=true; } } if (oglHaveExtension("GL_ARB_texture_border_clamp")) { m_Caps.m_TextureBorderClamp=true; } if (oglHaveExtension("GL_SGIS_generate_mipmap")) { m_Caps.m_GenerateMipmaps=true; } if (0 == oglHaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", 0)) { if (oglHaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader=true; } } bool CRenderer::Open(int width, int height, int depth) { m_Width = width; m_Height = height; m_Depth = depth; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOG(NORMAL, LOG_CATEGORY, "CRenderer::Open: alpha bits %d",bits); if (m_Options.m_RenderPath == RP_DEFAULT) SetRenderPath(m_Options.m_RenderPath); - + return true; } // resize renderer view void CRenderer::Resize(int width,int height) { - if (m_ShadowMap && (width>m_Width || height>m_Height)) { - glDeleteTextures(1,(GLuint*) &m_ShadowMap); - m_ShadowMap=0; - } - m_Width=width; - m_Height=height; + // need to recreate the shadow map object to resize the shadow texture + delete m->shadow; + m->shadow = new ShadowMap; + + 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; case OPT_NOPBUFFER: // NOT IMPLEMENTED break; default: debug_warn("CRenderer::SetOptionBool: unknown option"); 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; default: debug_warn("CRenderer::GetOptionBool: unknown option"); break; } 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; default: debug_warn("CRenderer::SetOptionColor: unknown option"); break; } } void CRenderer::SetOptionFloat(enum Option opt, float val) { switch(opt) { case OPT_LODBIAS: m_Options.m_LodBias = val; break; default: debug_warn("CRenderer::SetOptionFloat: unknown option"); 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; default: debug_warn("CRenderer::GetOptionColor: unknown option"); break; } return defaultColor; } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (rp == RP_DEFAULT) { if (m_Models.NormalHWLit && m_Models.PlayerHWLit) rp = RP_VERTEXSHADER; else rp = RP_FIXED; } - + if (rp == RP_VERTEXSHADER) { if (!m_Models.NormalHWLit || !m_Models.PlayerHWLit) { LOG(WARNING, LOG_CATEGORY, "Falling back to fixed function\n"); rp = RP_FIXED; } } - + m_Options.m_RenderPath = rp; } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_VERTEXSHADER: return "vertexshader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(CStr name) { if (name == "fixed") return RP_FIXED; if (name == "vertexshader") return RP_VERTEXSHADER; if (name == "default") return RP_DEFAULT; - + LOG(WARNING, LOG_CATEGORY, "Unknown render path name '%hs', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // SetFastPlayerColor void CRenderer::SetFastPlayerColor(bool fast) { m_FastPlayerColor = fast; - + if (m_FastPlayerColor) { if (!FastPlayerColorRender::IsAvailable()) { LOG(WARNING, LOG_CATEGORY, "Falling back to slower player color rendering."); m_FastPlayerColor = false; } } - + if (m_FastPlayerColor) m_Models.ModPlayer = RenderModifierPtr(new FastPlayerColorRender); else m_Models.ModPlayer = RenderModifierPtr(new SlowPlayerColorRender); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { #ifndef SCED if(!g_Game || !g_Game->IsGameStarted()) return; #endif // bump frame counter m_FrameCounter++; if (m_VertexShader) m_VertexShader->BeginFrame(); - + // 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) { m_SHCoeffsUnits.AddDirectionalLight(m_LightEnv->m_SunDir, m_LightEnv->m_SunColor); m_SHCoeffsTerrain.AddDirectionalLight(m_LightEnv->m_SunDir, 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 - // ??? RC more optimal light placement? - CVector3D lightpos=centre-(m_LightEnv->m_SunDir * 1000); - - // make light transformation matrix - ConstructLightTransform(lightpos,m_LightEnv->m_SunDir,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;ishadow->SetupFrame(m_ShadowBound); + m->shadow->BeginRender(); + + // TODO HACK fold this into ShadowMap glColor4fv(m_Options.m_ShadowColor); glDisable(GL_CULL_FACE); m_Models.NormalFF->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); m_Models.PlayerFF->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m_Models.NormalHWLit) m_Models.NormalHWLit->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m_Models.PlayerHWLit) m_Models.PlayerHWLit->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m_Models.NormalInstancing) m_Models.NormalInstancing->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m_Models.PlayerInstancing) m_Models.PlayerInstancing->Render(m_Models.ModSolidColor, MODELFLAG_CASTSHADOWS); m_Models.TranspFF->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS); if (m_Models.TranspHWLit) m_Models.TranspHWLit->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS); m_Models.TranspSortAll->Render(m_Models.ModTransparentShadow, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); glColor3f(1.0f,1.0f,1.0f); - glDisable(GL_SCISSOR_TEST); - - // copy result into shadow map texture - BindTexture(0,m_ShadowMap); - glCopyTexSubImage2D(GL_TEXTURE_2D,0,0,0,0,0,m_Width,m_Height); - - // restore matrix stack - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - -#if 0 - // debug aid - dump generated shadow map to file; helps verify shadow map - // space being well used (not that it is at the minute .. (TODO, RC)) - unsigned char* data=new unsigned char[m_ShadowMapWidth*m_ShadowMapHeight*3]; - glGetTexImage(GL_TEXTURE_2D,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,data); - saveTGA("d:\\test4.tga",m_ShadowMapWidth,m_ShadowMapHeight,24,data); - delete[] data; -#endif // 0 -#if 0 - unsigned char* data=new unsigned char[m_Width*m_Height*4]; - glReadBuffer(GL_BACK); - glReadPixels(0,0,m_Width,m_Height,GL_BGRA_EXT,GL_UNSIGNED_BYTE,data); - saveTGA("d:\\test3.tga",m_Width,m_Height,32,data); - delete[] data; -#endif // 0 + m->shadow->EndRender(); } +//TODO: Fold into TerrainRenderer void CRenderer::ApplyShadowMap() { PROFILE( "applying shadows" ); - CMatrix3D tmp2; - CMatrix3D texturematrix; - - float dx=0.5f*float(m_Width)/float(m_ShadowMapWidth); - float dy=0.5f*float(m_Height)/float(m_ShadowMapHeight); - texturematrix.SetTranslation(dx,dy,0); // transform (-0.5, 0.5) to (0,1) - texture space - tmp2.SetScaling(dx,dy,0); // scale (-1,1) to (-0.5,0.5) - texturematrix=texturematrix*tmp2; - - texturematrix=texturematrix*m_LightProjection; // transform light -> projected light space (-1 to 1) - texturematrix=texturematrix*m_LightTransform; // transform world -> light space + const CMatrix3D& texturematrix = m->shadow->GetTextureMatrix(); + GLuint shadowmap = m->shadow->GetTexture(); glMatrixMode(GL_TEXTURE); glLoadMatrixf(&texturematrix._11); glMatrixMode(GL_MODELVIEW); - CPatchRData::ApplyShadowMap(m_ShadowMap); + m->terrainRenderer->ApplyShadowMap(shadowmap); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); } void CRenderer::RenderPatches() { PROFILE(" render patches "); // 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(); + m->terrainRenderer->RenderTerrain(); 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) { // 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); - CPatchRData::RenderStreamsAll(STREAM_POS); + // render tiles edges + m->terrainRenderer->RenderPatches(); // set color for outline glColor3f(0,0,1); glLineWidth(4.0f); // render outline of each patch - CPatchRData::RenderOutlines(); - - // .. and switch off the client states - glDisableClientState(GL_VERTEX_ARRAY); + m->terrainRenderer->RenderOutlines(); // .. 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::RenderWater() -{ - PROFILE(" render water "); - - if(!m_RenderWater) - { - return; - } - - //Fresnel effect - CCamera* Camera=g_Game->GetView()->GetCamera(); - CVector3D CamFace=Camera->m_Orientation.GetIn(); - CamFace.Normalize(); - float FresnelScalar = CamFace.Dot( CVector3D(0.0f, -1.0f, 0.0f) ); - //Invert and set boundaries - FresnelScalar = (1 - FresnelScalar) * 0.4f + 0.6f; - - const int DX[] = {1,1,0,0}; - const int DZ[] = {0,1,1,0}; - - CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - int mapSize = terrain->GetVerticesPerSide(); - CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - glDepthMask(GL_FALSE); - - double time = get_time(); - - double period = 1.6; - int curTex = (int)(time*60/period) % 60; - ogl_tex_bind(m_WaterTexture[curTex], 0); - - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - float tx = -fmod(time, 20.0)/20.0; - float ty = fmod(time, 35.0)/35.0; - glTranslatef(tx, ty, 0); - - 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_ARB); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); - - // Set the proper LOD bias - glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, m_Options.m_LodBias); - - glBegin(GL_QUADS); - - for(size_t i=0; im_X*PATCH_SIZE + dx); - int z = (patch->m_Z*PATCH_SIZE + dz); - - // is any corner of the tile below the water height? if not, no point rendering it - bool shouldRender = false; - for(int j=0; j<4; j++) - { - float terrainHeight = terrain->getVertexGroundLevel(x + DX[j], z + DZ[j]); - if(terrainHeight < m_WaterHeight) - { - shouldRender = true; - break; - } - } - if(!shouldRender) - { - continue; - } - - for(int j=0; j<4; j++) - { - int ix = x + DX[j]; - int iz = z + DZ[j]; - - float vertX = ix * CELL_SIZE; - float vertZ = iz * CELL_SIZE; - - float terrainHeight = terrain->getVertexGroundLevel(ix, iz); - - float alpha = clamp((m_WaterHeight - terrainHeight) / m_WaterFullDepth + m_WaterAlphaOffset, - -100.0f, m_WaterMaxAlpha); - - float losMod = 1.0f; - for(int k=0; k<4; k++) - { - int tx = ix - DX[k]; - int tz = iz - DZ[k]; - - if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) - { - ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); - if(s == LOS_EXPLORED && losMod > 0.7f) - losMod = 0.7f; - else if(s==LOS_UNEXPLORED && losMod > 0.0f) - losMod = 0.0f; - } - } - - glColor4f(m_WaterColor.r*losMod, m_WaterColor.g*losMod, m_WaterColor.b*losMod, alpha * FresnelScalar); - pglMultiTexCoord2fARB(GL_TEXTURE0, vertX/16.0f, vertZ/16.0f); - glVertex3f(vertX, m_WaterHeight, vertZ); - } - } //end of x loop - } //end of z loop - } - - glEnd(); - - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - - glDepthMask(GL_TRUE); - glDisable(GL_BLEND); - glDisable(GL_TEXTURE_2D); -} - void CRenderer::RenderModels() { PROFILE( "render models "); // switch on wireframe if we need it if (m_ModelRenderMode==WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); } m_Models.NormalFF->Render(m_Models.ModPlain, 0); m_Models.PlayerFF->Render(m_Models.ModPlayer, 0); if (m_Models.NormalHWLit) m_Models.NormalHWLit->Render(m_Models.ModPlain, 0); if (m_Models.PlayerHWLit) m_Models.PlayerHWLit->Render(m_Models.ModPlayer, 0); if (m_Models.NormalInstancing) m_Models.NormalInstancing->Render(m_Models.ModPlain, 0); if (m_Models.PlayerInstancing) m_Models.PlayerInstancing->Render(m_Models.ModPlayer, 0); if (m_ModelRenderMode==WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_ModelRenderMode==EDGED_FACES) { m_Models.NormalFF->Render(m_Models.ModWireframe, 0); m_Models.PlayerFF->Render(m_Models.ModWireframe, 0); if (m_Models.NormalHWLit) m_Models.NormalHWLit->Render(m_Models.ModWireframe, 0); if (m_Models.PlayerHWLit) m_Models.PlayerHWLit->Render(m_Models.ModWireframe, 0); if (m_Models.NormalInstancing) m_Models.NormalInstancing->Render(m_Models.ModWireframe, 0); if (m_Models.PlayerInstancing) m_Models.PlayerInstancing->Render(m_Models.ModWireframe, 0); } } void CRenderer::RenderTransparentModels() { PROFILE( "render transparent models "); // switch on wireframe if we need it if (m_ModelRenderMode==WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); } m_Models.TranspFF->Render(m_Models.ModTransparent, 0); if (m_Models.TranspHWLit) m_Models.TranspHWLit->Render(m_Models.ModTransparent, 0); m_Models.TranspSortAll->Render(m_Models.ModTransparent, 0); if (m_ModelRenderMode==WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_ModelRenderMode==EDGED_FACES) { m_Models.TranspFF->Render(m_Models.ModWireframe, 0); if (m_Models.TranspHWLit) m_Models.TranspHWLit->Render(m_Models.ModWireframe, 0); m_Models.TranspSortAll->Render(m_Models.ModWireframe, 0); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // FlushFrame: force rendering of any batched objects void CRenderer::FlushFrame() { #ifndef SCED if(!g_Game || !g_Game->IsGameStarted()) return; #endif oglCheck(); // Prepare model renderers PROFILE_START("prepare models"); m_Models.NormalFF->PrepareModels(); m_Models.PlayerFF->PrepareModels(); m_Models.TranspFF->PrepareModels(); if (m_Models.NormalHWLit) m_Models.NormalHWLit->PrepareModels(); if (m_Models.PlayerHWLit) m_Models.PlayerHWLit->PrepareModels(); if (m_Models.TranspHWLit) m_Models.TranspHWLit->PrepareModels(); if (m_Models.NormalInstancing) m_Models.NormalInstancing->PrepareModels(); if (m_Models.PlayerInstancing) m_Models.PlayerInstancing->PrepareModels(); m_Models.TranspSortAll->PrepareModels(); PROFILE_END("prepare models"); + PROFILE_START("prepare terrain"); + m->terrainRenderer->PrepareForRendering(); + PROFILE_END("prepare terrain"); + if (!m_ShadowRendered) { if (m_Options.m_Shadows) { MICROLOG(L"render shadows"); RenderShadowMap(); } // clear buffers 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); } oglCheck(); // render submitted patches and models MICROLOG(L"render patches"); RenderPatches(); oglCheck(); MICROLOG(L"render models"); RenderModels(); oglCheck(); if (m_Options.m_Shadows && !m_ShadowRendered) { MICROLOG(L"apply shadows"); ApplyShadowMap(); oglCheck(); } m_ShadowRendered=true; // call on the transparency renderer to render all the transparent stuff MICROLOG(L"render transparent"); RenderTransparentModels(); oglCheck(); // render water (note: we're assuming there's no transparent stuff over water... // we could also do this above render transparent if we assume there's no transparent // stuff underwater) - MICROLOG(L"render water"); - RenderWater(); - oglCheck(); + if (m_WaterManager->m_RenderWater) + { + MICROLOG(L"render water"); + m->terrainRenderer->RenderWater(); + oglCheck(); + } // empty lists MICROLOG(L"empty lists"); - CPatchRData::ClearSubmissions(); - m_VisiblePatches.clear(); - + m->terrainRenderer->EndFrame(); + // Finish model renderers m_Models.NormalFF->EndFrame(); m_Models.PlayerFF->EndFrame(); m_Models.TranspFF->EndFrame(); if (m_Models.NormalHWLit) m_Models.NormalHWLit->EndFrame(); if (m_Models.PlayerHWLit) m_Models.PlayerHWLit->EndFrame(); if (m_Models.TranspHWLit) m_Models.TranspHWLit->EndFrame(); if (m_Models.NormalInstancing) m_Models.NormalInstancing->EndFrame(); if (m_Models.PlayerInstancing) m_Models.PlayerInstancing->EndFrame(); m_Models.TranspSortAll->EndFrame(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end; implicitly flushes batched objects void CRenderer::EndFrame() { #ifndef SCED if(!g_Game || !g_Game->IsGameStarted()) return; #endif FlushFrame(); g_Renderer.SetTexture(0,0); static bool once=false; if (!once && glGetError()) { LOG(ERROR, LOG_CATEGORY, "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); 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); - m_VisiblePatches.push_back(patch); + m->terrainRenderer->Submit(patch); } void CRenderer::Submit(CModel* model) { if (model->GetFlags() & MODELFLAG_CASTSHADOWS) { PROFILE( "updating shadow bounds" ); m_ShadowBound += model->GetBounds(); } // Tricky: The call to GetBounds() above can invalidate the position model->ValidatePosition(); - + bool canUseInstancing = false; - + if (model->GetModelDef()->GetNumBones() == 0) canUseInstancing = true; if (model->GetMaterial().IsPlayer()) { if (m_Options.m_RenderPath == RP_VERTEXSHADER) { if (canUseInstancing && m_Models.PlayerInstancing) m_Models.PlayerInstancing->Submit(model); else m_Models.PlayerHWLit->Submit(model); } else m_Models.PlayerFF->Submit(model); } else if (model->GetMaterial().UsesAlpha()) { if (m_SortAllTransparent) m_Models.TranspSortAll->Submit(model); else if (m_Options.m_RenderPath == RP_VERTEXSHADER) m_Models.TranspHWLit->Submit(model); else m_Models.TranspFF->Submit(model); } else { if (m_Options.m_RenderPath == RP_VERTEXSHADER) { if (canUseInstancing && m_Models.NormalInstancing) m_Models.NormalInstancing->Submit(model); else m_Models.NormalHWLit->Submit(model); } else m_Models.NormalFF->Submit(model); } } void CRenderer::Submit(CSprite* UNUSED(sprite)) { } void CRenderer::Submit(CParticleSys* UNUSED(psys)) { } void CRenderer::Submit(COverlay* UNUSED(overlay)) { } -void CRenderer::RenderPatchSubmissions() -{ - // switch on required client states - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - // render everything - CPatchRData::RenderBaseSplats(); - CPatchRData::RenderBlendSplats(); - - // switch off all client states - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_COLOR_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) { const Handle errorhandle = -1; Handle h=texture->GetHandle(); // already tried to load this texture if (h) { // nothing to do here - just return success according to // whether this is a valid handle or not return h==errorhandle ? true : false; } h=ogl_tex_load(texture->GetName()); if (h <= 0) { LOG(ERROR, LOG_CATEGORY, "LoadTexture failed on \"%s\"",(const char*) texture->GetName()); texture->SetHandle(errorhandle); return false; } if(!wrapflags) wrapflags = GL_CLAMP_TO_EDGE; (void)ogl_tex_set_wrap(h, wrapflags); (void)ogl_tex_set_filter(h, GL_LINEAR_MIPMAP_LINEAR); // (this also verifies that the texture is a power-of-two) if(ogl_tex_upload(h) < 0) { LOG(ERROR, LOG_CATEGORY, "LoadTexture failed on \"%s\" : upload failed",(const char*) texture->GetName()); ogl_tex_free(h); texture->SetHandle(errorhandle); return false; } texture->SetHandle(h); return true; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit,GLuint tex) { pglActiveTextureARB(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) { Handle h = texture? texture->GetHandle() : 0; ogl_tex_bind(h, unit); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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(); uint flags = 0; // assume no alpha on failure (void)ogl_tex_get_format(h, &flags, 0); return (flags & TEX_ALPHA) != 0; } static 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 int CRenderer::LoadAlphaMaps() { const char* const key = "(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; PathPackage pp; (void)pp_set_dir(&pp, "art/textures/terrain/alphamaps/special"); const char* fnames[NumAlphaMaps] = { "blendcircle.dds", "blendlshape.dds", "blendedge.dds", "blendedgecorner.dds", "blendedgetwocorners.dds", "blendfourcorners.dds", "blendtwooppositecorners.dds", "blendlshapecorner.dds", "blendtwocorners.dds", "blendcorner.dds", "blendtwoedges.dds", "blendthreecorners.dds", "blendushape.dds", "blendbad.dds" }; uint base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) uint bpp = 0; for(uint i=0;i::ScriptingInit("Renderer"); } Index: ps/trunk/source/renderer/PatchRData.cpp =================================================================== --- ps/trunk/source/renderer/PatchRData.cpp (revision 3331) +++ ps/trunk/source/renderer/PatchRData.cpp (revision 3332) @@ -1,890 +1,589 @@ #include "precompiled.h" #include #include #include "Pyrogenesis.h" #include "lib/res/graphics/ogl_tex.h" #include "Renderer.h" -#include "PatchRData.h" +#include "renderer/PatchRData.h" #include "AlphaMapCalculator.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "MathUtil.h" #include "LOSManager.h" -/////////////////////////////////////////////////////////////////// -// shared list of all submitted patches this frame -std::vector CPatchRData::m_Patches; - const int BlendOffsets[8][2] = { { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, { 1, 0 }, { 1, -1 } }; /////////////////////////////////////////////////////////////////// // CPatchRData constructor CPatchRData::CPatchRData(CPatch* patch) : m_Patch(patch), m_VBBase(0), m_VBBlends(0), m_Vertices(0), m_LightingColors(0) { debug_assert(patch); Build(); } /////////////////////////////////////////////////////////////////// // CPatchRData destructor CPatchRData::~CPatchRData() { // delete copy of vertex data delete[] m_Vertices; delete[] m_LightingColors; // release vertex buffer chunks if (m_VBBase) g_VBMan.Release(m_VBBase); if (m_VBBlends) g_VBMan.Release(m_VBBlends); } static Handle GetTerrainTileTexture(CTerrain* terrain,int gx,int gz) { CMiniPatch* mp=terrain->GetTile(gx,gz); return mp ? mp->Tex1 : 0; } bool QueryAdjacency(int x,int y,Handle h,Handle* texgrid) { for (int j=y-1;j<=y+1;j++) { for (int i=x-1;i<=x+1;i++) { if (i<0 || i>PATCH_SIZE+1 || j<0 || j>PATCH_SIZE+1) { continue; } if (texgrid[j*(PATCH_SIZE+2)+i]==h) { return true; } } } return false; } struct STmpSplat { Handle m_Texture; u16 m_Indices[4]; }; void CPatchRData::BuildBlends() { m_BlendIndices.clear(); m_BlendSplats.clear(); m_BlendVertices.clear(); m_BlendVertexIndices.clear(); // get index of this patch (unused) //int px=m_Patch->m_X; //int pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; // temporary list of splats std::vector splats; // set of textures used for splats std::set splatTextures; // for each tile in patch .. for (int j=0;jm_MiniPatches[j][i]; mp->GetTileIndex(gx,gz); // build list of textures of higher priority than current tile that are used by neighbouring tiles std::vector neighbourTextures; for (int m=-1;m<=1;m++) { for (int k=-1;k<=1;k++) { CMiniPatch* nmp=terrain->GetTile(gx+k,gz+m); if (nmp && nmp->Tex1 != mp->Tex1) { if (nmp->Tex1Priority>mp->Tex1Priority || (nmp->Tex1Priority==mp->Tex1Priority && nmp->Tex1>mp->Tex1)) { STex tex; tex.m_Handle=nmp->Tex1; tex.m_Priority=nmp->Tex1Priority; if (std::find(neighbourTextures.begin(),neighbourTextures.end(),tex)==neighbourTextures.end()) { neighbourTextures.push_back(tex); } } } } } if (neighbourTextures.size()>0) { // sort textures from lowest to highest priority std::sort(neighbourTextures.begin(),neighbourTextures.end()); // for each of the neighbouring textures .. uint count=(uint)neighbourTextures.size(); for (uint k=0;km_Owner->UpdateChunkVertices(m_VBBlends,&m_BlendVertices[0]); // now build outgoing splats m_BlendSplats.resize(splatTextures.size()); int splatCount=0; debug_assert(m_VBBlends->m_Index < 65536); unsigned short base = (unsigned short)m_VBBlends->m_Index; std::set::iterator iter=splatTextures.begin(); for (;iter!=splatTextures.end();++iter) { Handle tex=*iter; SSplat& splat=m_BlendSplats[splatCount]; splat.m_IndexStart=(u32)m_BlendIndices.size(); splat.m_Texture=tex; for (uint k=0;k<(uint)splats.size();k++) { if (splats[k].m_Texture==tex) { m_BlendIndices.push_back(splats[k].m_Indices[0]+base); m_BlendIndices.push_back(splats[k].m_Indices[1]+base); m_BlendIndices.push_back(splats[k].m_Indices[2]+base); m_BlendIndices.push_back(splats[k].m_Indices[3]+base); splat.m_IndexCount+=4; } } splatCount++; } } } void CPatchRData::BuildIndices() { // must have allocated some vertices before trying to build corresponding indices debug_assert(m_VBBase); // number of vertices in each direction in each patch int vsize=PATCH_SIZE+1; // release existing indices and bins m_Indices.clear(); m_ShadowMapIndices.clear(); m_Splats.clear(); // build grid of textures on this patch and boundaries of adjacent patches std::vector textures; Handle texgrid[PATCH_SIZE][PATCH_SIZE]; for (int j=0;jm_MiniPatches[j][i].Tex1; texgrid[j][i]=h; if (std::find(textures.begin(),textures.end(),h)==textures.end()) { textures.push_back(h); } } } // now build base splats from interior textures m_Splats.resize(textures.size()); // build indices for base splats u32 base=(u32)m_VBBase->m_Index; for (uint i=0;i<(uint)m_Splats.size();i++) { Handle h=textures[i]; SSplat& splat=m_Splats[i]; splat.m_Texture=h; splat.m_IndexStart=(u32)m_Indices.size(); for (int j=0;jm_X; u32 pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; // build vertices for (int j=0;jCalcPosition(ix,iz,vertices[v].m_Position); *(uint32_t*)&vertices[v].m_Color = 0; // will be set to the proper value in Update() vertices[v].m_UVs[0]=i*0.125f; vertices[v].m_UVs[1]=j*0.125f; // calculate lighting into the separate m_LightingColors array, which will // be used to set the vertex colors in Update() terrain->CalcNormal(ix,iz,normal); g_Renderer.m_SHCoeffsTerrain.Evaluate(normal, m_LightingColors[v]); } } // upload to vertex buffer if (!m_VBBase) { m_VBBase=g_VBMan.Allocate(sizeof(SBaseVertex),vsize*vsize,true); - } + } m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase,m_Vertices); } void CPatchRData::Build() { BuildVertices(); BuildIndices(); BuildBlends(); } void CPatchRData::Update() { if (m_UpdateFlags!=0) { // TODO,RC 11/04/04 - need to only rebuild necessary bits of renderdata rather // than everything; it's complicated slightly because the blends are dependent // on both vertex and index data BuildVertices(); BuildIndices(); BuildBlends(); m_UpdateFlags=0; } // Update vertex colors, which are affected by LOS u32 px=m_Patch->m_X; u32 pz=m_Patch->m_Z; CTerrain* terrain=m_Patch->m_Parent; int mapSize=terrain->GetVerticesPerSide(); CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); int vsize=PATCH_SIZE+1; // this is very similar to BuildVertices(), but just for color for (int j=0;j= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) { ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); if(s==LOS_EXPLORED && losMod > 0.7f) losMod = 0.7f; else if(s==LOS_UNEXPLORED && losMod > 0.0f) losMod = 0.0f; } } RGBColor c = m_LightingColors[v]; c *= losMod; *(uint32_t*)&m_Vertices[v].m_Color = ConvertRGBColorTo4ub(c); } } // upload base vertices into their vertex buffer m_VBBase->m_Owner->UpdateChunkVertices(m_VBBase,m_Vertices); // update blend colors by copying them from vertex colors for(uint i=0; im_Owner->UpdateChunkVertices(m_VBBlends,&m_BlendVertices[0]); } } void CPatchRData::RenderBase() { debug_assert(m_UpdateFlags==0); u8* base=m_VBBase->m_Owner->Bind(); // setup data pointers u32 stride=sizeof(SBaseVertex); glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_Position)); glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBaseVertex,m_Color)); glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_UVs[0])); // render each splat for (uint i=0;i<(uint)m_Splats.size();i++) { SSplat& splat=m_Splats[i]; ogl_tex_bind(splat.m_Texture); glDrawElements(GL_QUADS,splat.m_IndexCount,GL_UNSIGNED_SHORT,&m_Indices[splat.m_IndexStart]); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris+=splat.m_IndexCount/2; } } void CPatchRData::RenderStreams(u32 streamflags) { debug_assert(m_UpdateFlags==0); u8* base=m_VBBase->m_Owner->Bind(); // setup data pointers glVertexPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position)); if (streamflags & STREAM_UV0) { glTexCoordPointer(2,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_UVs)); } else if (streamflags & STREAM_POSTOUV0) { glTexCoordPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position)); } // render all base splats at once glDrawElements(GL_QUADS,(GLsizei)m_Indices.size(),GL_UNSIGNED_SHORT,&m_Indices[0]); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_TerrainTris+=(u32)m_Indices.size()/2; } void CPatchRData::RenderBlends() { debug_assert(m_UpdateFlags==0); if (m_BlendVertices.size()==0) return; u8* base=m_VBBlends->m_Owner->Bind(); // setup data pointers u32 stride=sizeof(SBlendVertex); glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_Position)); glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBlendVertex,m_Color)); pglClientActiveTextureARB(GL_TEXTURE0); glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_UVs[0])); pglClientActiveTextureARB(GL_TEXTURE1); glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_AlphaUVs[0])); for (uint i=0;i<(uint)m_BlendSplats.size();i++) { SSplat& splat=m_BlendSplats[i]; ogl_tex_bind(splat.m_Texture); glDrawElements(GL_QUADS,splat.m_IndexCount,GL_UNSIGNED_SHORT,&m_BlendIndices[splat.m_IndexStart]); // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_BlendSplats++; g_Renderer.m_Stats.m_TerrainTris+=splat.m_IndexCount/2; } } -void CPatchRData::RenderOutlines() -{ - for (uint i=0;iGetRenderData(); - patchdata->RenderOutline(); - } -} - -void CPatchRData::RenderStreamsAll(u32 streamflags) -{ - for (uint i=0;iGetRenderData(); - patchdata->RenderStreams(streamflags); - } -} - void CPatchRData::RenderOutline() { int i; uint vsize=PATCH_SIZE+1; u8* base=m_VBBase->m_Owner->Bind(); //TODO: this makes no sense, get rid of it UNUSED2(base); glBegin(GL_LINES); for (i=0;im_Owner->AppendBatch(m_VBBase,splat.m_Texture,splat.m_IndexCount,&m_Indices[splat.m_IndexStart]); - } -} -///////////////////////////////////////////////////////////////////////////////////////////////////// -// SubmitBlendBatches: submit next set of blend batches for this patch to the vertex buffer; -void CPatchRData::SubmitBlendBatches() -{ - for (uint i=0;im_Owner->AppendBatch(m_VBBlends, splat.m_Texture, splat.m_IndexCount, &m_BlendIndices[splat.m_IndexStart]); - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -// RenderBaseSplats: render all base passes of all patches; assumes vertex, texture and color -// client states are enabled -void CPatchRData::RenderBaseSplats() -{ - uint i; - - // set up texture environment for base pass - MICROLOG(L"base splat textures"); - pglActiveTextureARB(GL_TEXTURE0); - pglClientActiveTextureARB(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); - - // Set alpha to 1.0 - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); - float one[4] = { 1.f, 1.f, 1.f, 1.f }; - glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, one); - -#if 1 - // submit base batches for each patch to the vertex buffer - for (i=0;iGetRenderData(); - patchdata->SubmitBaseBatches(); - } - - // render base passes for each patch - const std::list& buffers=g_VBMan.GetBufferList(); - std::list::const_iterator iter; - for (iter=buffers.begin();iter!=buffers.end();++iter) { - CVertexBuffer* buffer=*iter; - - // any batches in this VB? - const std::vector& batches=buffer->GetBatches(); - if (batches.size()>0) { - u8* base=buffer->Bind(); - - // setup data pointers - u32 stride=sizeof(SBaseVertex); - - glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_Position)); - glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBaseVertex,m_Color)); - glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_UVs[0])); - - // render each batch - for (i=0;im_IndexData.size()>0) { - ogl_tex_bind(batch->m_Texture); - for (uint j=0;jm_IndexData.size();j++) { - glDrawElements(GL_QUADS, (GLsizei)batch->m_IndexData[j].first, GL_UNSIGNED_SHORT, batch->m_IndexData[j].second); - g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_TerrainTris+=(u32)batch->m_IndexData[j].first/2; - } - } - } - } - } - // everything rendered; empty out batch lists - MICROLOG(L"clear"); - g_VBMan.ClearBatchIndices(); -#else - for (i=0;iGetRenderData(); - patchdata->RenderBase(); - } -#endif -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -// RenderBlendSplats: render all blend passes of all patches; assumes vertex, texture and color -// client states are enabled -void CPatchRData::RenderBlendSplats() -{ - uint i; - - // switch on the composite alpha map texture - (void)ogl_tex_bind(g_Renderer.m_hCompositeAlphaMap, 1); - - // switch on second uv set - pglClientActiveTextureARB(GL_TEXTURE1); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - // setup additional texenv required by blend pass - pglActiveTextureARB(GL_TEXTURE1); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA); - - // switch on blending - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - - // no need to write to the depth buffer a second time - glDepthMask(0); - -#if 1 - // submit blend batches for each patch to the vertex buffer - for (i=0;iGetRenderData(); - patchdata->SubmitBlendBatches(); - } - - // render blend passes for each patch - const std::list& buffers=g_VBMan.GetBufferList(); - std::list::const_iterator iter; - for (iter=buffers.begin();iter!=buffers.end();++iter) { - CVertexBuffer* buffer=*iter; - - // any batches in this VB? - const std::vector& batches=buffer->GetBatches(); - if (batches.size()>0) { - u8* base=buffer->Bind(); - - // setup data pointers - u32 stride=sizeof(SBlendVertex); - glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_Position)); - glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBlendVertex,m_Color)); - - pglClientActiveTextureARB(GL_TEXTURE1); - glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_AlphaUVs[0])); - - pglClientActiveTextureARB(GL_TEXTURE0); - glTexCoordPointer(2,GL_FLOAT,stride,base+offsetof(SBlendVertex,m_UVs[0])); - - // render each batch - for (i=0;im_IndexData.size()>0) { - ogl_tex_bind(batch->m_Texture); - for (uint j = 0; j < batch->m_IndexData.size(); j++) { - glDrawElements(GL_QUADS, (GLsizei)batch->m_IndexData[j].first, GL_UNSIGNED_SHORT, batch->m_IndexData[j].second); - g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_TerrainTris+=(u32)batch->m_IndexData[j].first/2; - } - } - } - } - } - // everything rendered; empty out batch lists - g_VBMan.ClearBatchIndices(); -#else - // render blend passes for each patch - for (i=0;iGetRenderData(); - patchdata->RenderBlends(); - } -#endif - - // restore depth writes - glDepthMask(1); - - // restore default state: switch off blending - glDisable(GL_BLEND); - - // switch off texture unit 1 and second uv set - pglClientActiveTextureARB(GL_TEXTURE1); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - g_Renderer.BindTexture(1,0); - - // switch back to texture unit 0 - pglClientActiveTextureARB(GL_TEXTURE0); - pglActiveTextureARB(GL_TEXTURE0); -} - -///////////////////////////////////////////////////////////////////////////////////////////// -// Submit: submit a patch to render this frame -void CPatchRData::Submit(CPatch* patch) -{ - CPatchRData* data=(CPatchRData*) patch->GetRenderData(); - if (data==0) { - // no renderdata for patch, create it now - data=new CPatchRData(patch); - patch->SetRenderData(data); - } - data->Update(); - - m_Patches.push_back(patch); -} - -///////////////////////////////////////////////////////////////////////////////////////////// -// ApplyShadowMap: apply given shadow map to all terrain patches; assume the texture matrix -// has been correctly setup on unit 1 to handle the projection -void CPatchRData::ApplyShadowMap(GLuint shadowmaphandle) -{ - uint i; - - g_Renderer.BindTexture(0,shadowmaphandle); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); - - glColor3f(1,1,1); - - glEnable(GL_BLEND); - glBlendFunc(GL_DST_COLOR,GL_ZERO); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - -#if 1 - // submit base batches for each patch to the vertex buffer - for (i=0;iGetRenderData(); - patchdata->m_VBBase->m_Owner->AppendBatch(patchdata->m_VBBase,0,patchdata->m_ShadowMapIndices.size(), - &patchdata->m_ShadowMapIndices[0]); - } - - // render base passes for each patch - const std::list& buffers=g_VBMan.GetBufferList(); - std::list::const_iterator iter; - for (iter=buffers.begin();iter!=buffers.end();++iter) { - CVertexBuffer* buffer=*iter; - - // any batches in this VB? - const std::vector& batches=buffer->GetBatches(); - if (batches.size()>0) { - u8* base=buffer->Bind(); - - // setup data pointers - u32 stride=sizeof(SBaseVertex); - glVertexPointer(3,GL_FLOAT,stride,base+offsetof(SBaseVertex,m_Position)); - glColorPointer(4,GL_UNSIGNED_BYTE,stride,base+offsetof(SBaseVertex,m_Color)); - glTexCoordPointer(3,GL_FLOAT,sizeof(SBaseVertex),base+offsetof(SBaseVertex,m_Position)); - - // render batch (can only be one per buffer, since all batches are flagged as using a null texture) - const CVertexBuffer::Batch* batch=batches[0]; - for (uint j=0;jm_IndexData.size();j++) { - glDrawElements(GL_QUADS,(GLsizei)batch->m_IndexData[j].first,GL_UNSIGNED_SHORT,batch->m_IndexData[j].second); - g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_TerrainTris+=(u32)batch->m_IndexData[j].first/2; - } - } - } - // everything rendered; empty out batch lists - g_VBMan.ClearBatchIndices(); -#else - for (uint i=0;iGetRenderData();; - patchdata->RenderStreams(STREAM_POS|STREAM_POSTOUV0); - } -#endif - - glDisable(GL_BLEND); - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); -} Index: ps/trunk/source/renderer/WaterManager.cpp =================================================================== --- ps/trunk/source/renderer/WaterManager.cpp (nonexistent) +++ ps/trunk/source/renderer/WaterManager.cpp (revision 3332) @@ -0,0 +1,106 @@ +/** + * ========================================================================= + * File : WaterManager.cpp + * Project : Pyrogenesis + * Description : Water settings (speed, height) and texture management + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#include "precompiled.h" + +#include "lib/timer.h" +#include "lib/res/file/vfs.h" +#include "lib/res/graphics/tex.h" +#include "lib/res/graphics/ogl_tex.h" + +#include "ps/CLogger.h" +#include "ps/Loader.h" + +#include "renderer/WaterManager.h" + +#define LOG_CATEGORY "graphics" + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// WaterManager implementation + + +/////////////////////////////////////////////////////////////////// +// Construction/Destruction +WaterManager::WaterManager() +{ + // water + m_RenderWater = true; + m_WaterHeight = 5.0f; + m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f); + m_WaterFullDepth = 4.0f; + m_WaterMaxAlpha = 0.85f; + m_WaterAlphaOffset = -0.05f; + m_SWaterTrans=0; + m_TWaterTrans=0; + m_SWaterSpeed=0.0015f; + m_TWaterSpeed=0.0015f; + m_SWaterScrollCounter=0; + m_TWaterScrollCounter=0; + m_WaterCurrentTex=0; + + for (uint i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) + m_WaterTexture[i] = 0; + + cur_loading_water_tex = 0; +} + +WaterManager::~WaterManager() +{ + // Cleanup if the caller messed up + UnloadWaterTextures(); +} + + +/////////////////////////////////////////////////////////////////// +// Progressive load of water textures +int WaterManager::LoadWaterTextures() +{ + const uint num_textures = ARRAY_SIZE(m_WaterTexture); + + // yield after this time is reached. balances increased progress bar + // smoothness vs. slowing down loading. + const double end_time = get_time() + 100e-3; + + while (cur_loading_water_tex < num_textures) + { + char waterName[VFS_MAX_PATH]; + // TODO: add a member variable and setter for this. (can't make this + // a parameter because this function is called via delay-load code) + static const char* const water_type = "animation2"; + snprintf(waterName, ARRAY_SIZE(waterName), "art/textures/terrain/types/water/%s/water%02d.dds", water_type, cur_loading_water_tex+1); + Handle ht = ogl_tex_load(waterName); + if (ht <= 0) + { + LOG(ERROR, LOG_CATEGORY, "LoadWaterTextures failed on \"%s\"", waterName); + return ht; + } + m_WaterTexture[cur_loading_water_tex] = ht; + RETURN_ERR(ogl_tex_upload(ht)); + + cur_loading_water_tex++; + LDR_CHECK_TIMEOUT(cur_loading_water_tex, num_textures); + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////// +// Unload water textures +void WaterManager::UnloadWaterTextures() +{ + for(uint i = 0; i < ARRAY_SIZE(m_WaterTexture); i++) + { + ogl_tex_free(m_WaterTexture[i]); + m_WaterTexture[i] = 0; + } + cur_loading_water_tex = 0; // so they will be reloaded if LoadWaterTextures is called again +} Property changes on: ps/trunk/source/renderer/WaterManager.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/ShadowMap.cpp =================================================================== --- ps/trunk/source/renderer/ShadowMap.cpp (nonexistent) +++ ps/trunk/source/renderer/ShadowMap.cpp (revision 3332) @@ -0,0 +1,430 @@ +/** + * ========================================================================= + * File : ShadowMap.cpp + * Project : Pyrogenesis + * Description : Shadow mapping related texture and matrix management + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#include "precompiled.h" + +#include "ogl.h" + +#include "graphics/LightEnv.h" + +#include "maths/Bound.h" +#include "maths/MathUtil.h" +#include "maths/Matrix3D.h" + +#include "renderer/Renderer.h" +#include "renderer/ShadowMap.h" + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// ShadowMap implementation + +/** + * Struct ShadowMapInternals: Internal data for the ShadowMap implementation + */ +struct ShadowMapInternals +{ + // handle of shadow map + GLuint Texture; + // width, height of shadow map + u32 Width, Height; + // object space bound of shadow casting objects + CBound m_ShadowBound; + // project light space into projected light space + CMatrix3D LightProjection; + // transform world space into light space + CMatrix3D LightTransform; + // transform world space into texture space + CMatrix3D TextureMatrix; + + // Helper functions + void BuildTransformation( + const CVector3D& pos,const CVector3D& right,const CVector3D& up, + const CVector3D& dir,CMatrix3D& result); + void ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result); + void CalcShadowMatrices(const CBound& bound); +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Construction/Destruction +ShadowMap::ShadowMap() +{ + m = new ShadowMapInternals; + m->Texture = 0; +} + + +ShadowMap::~ShadowMap() +{ + if (m->Texture) + glDeleteTextures(1, &m->Texture); + + delete m; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////// +// BuildTransformation: build transformation matrix from a position and standard basis vectors +// TODO: Shouldn't this be part of CMatrix3D? +void ShadowMapInternals::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 ShadowMapInternals::ConstructLightTransform(const CVector3D& pos,const CVector3D& dir,CMatrix3D& result) +{ + CVector3D right,up; + + CVector3D viewdir = g_Renderer.GetCamera().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 ShadowMapInternals::CalcShadowMatrices(const CBound& bounds) +{ + const CLightEnv& lightenv = g_Renderer.GetLightEnv(); + const CCamera& camera = g_Renderer.GetCamera(); + int i; + + // get centre of bounds + CVector3D centre; + bounds.GetCentre(centre); + + // get sunlight direction + // ??? RC more optimal light placement? + CVector3D lightpos=centre-(lightenv.m_SunDir * 1000); + + // make light transformation matrix + ConstructLightTransform(lightpos, lightenv.m_SunDir, LightTransform); + + // transform shadow bounds to light space, calculate near and far bounds + CVector3D vp[8]; + LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[0].Z),vp[0]); + LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[0].Z),vp[1]); + LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[0].Z),vp[2]); + LightTransform.Transform(CVector3D(bounds[1].X,bounds[1].Y,bounds[0].Z),vp[3]); + LightTransform.Transform(CVector3D(bounds[0].X,bounds[0].Y,bounds[1].Z),vp[4]); + LightTransform.Transform(CVector3D(bounds[1].X,bounds[0].Y,bounds[1].Z),vp[5]); + LightTransform.Transform(CVector3D(bounds[0].X,bounds[1].Y,bounds[1].Z),vp[6]); + 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=(znear projected light space (-1 to 1) + TextureMatrix = TextureMatrix * LightTransform; // transform world -> light space + +#if 0 + +#if 0 + // TODO, RC - trim against frustum? + // get points of view frustum in world space + CVector3D frustumPts[8]; + m_Camera.GetFrustumPoints(frustumPts); + + // transform to light space + for (i=0;i<8;i++) { + m_LightTransform.Transform(frustumPts[i],vp[i]); + } + + float left1=vp[0].X; + float right1=vp[0].X; + float top1=vp[0].Y; + float bottom1=vp[0].Y; + float znear1=vp[0].Z; + float zfar1=vp[0].Z; + + for (int i=1;i<8;i++) { + if (vp[i].Xright1) 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 +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Prepare for the next frame: Matrix calculations and texture creation if necessary +void ShadowMap::SetupFrame(const CBound& visibleBounds) +{ + m->CalcShadowMatrices(visibleBounds); + + if (!m->Texture) + { + // get shadow map size as next power of two up from view width and height + m->Width = g_Renderer.GetWidth(); + m->Width = RoundUpToPowerOf2(m->Width); + m->Height = g_Renderer.GetHeight(); + m->Height = RoundUpToPowerOf2(m->Height); + + // create texture object - initially filled with white, so clamp to edge clamps to correct color + glGenTextures(1, &m->Texture); + g_Renderer.BindTexture(0, m->Texture); + + u32 size = m->Width*m->Height; + u32* buf=new u32[size]; + for (uint i=0;iWidth,m->Height,0,GL_RGBA,GL_UNSIGNED_BYTE,buf); + delete[] buf; + + // set texture parameters + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Set up to render into shadow map texture +void ShadowMap::BeginRender() +{ + // HACK HACK: this depends in non-obvious ways on the behaviour of the caller + + CRenderer& renderer = g_Renderer; + int renderWidth = renderer.GetWidth(); + int renderHeight = renderer.GetHeight(); + + // clear buffers + glClearColor(1,1,1,0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // setup viewport + glViewport(0, 0, renderWidth, renderHeight); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadMatrixf(&m->LightProjection._11); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadMatrixf(&m->LightTransform._11); + + glEnable(GL_SCISSOR_TEST); + glScissor(1,1, renderWidth-2, renderHeight-2); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Finish rendering into shadow map texture +void ShadowMap::EndRender() +{ + glDisable(GL_SCISSOR_TEST); + + // copy result into shadow map texture + g_Renderer.BindTexture(0, m->Texture); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight()); + + // restore matrix stack + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Retrieve the texture handle and texture matrix for shadowing +GLuint ShadowMap::GetTexture() +{ + return m->Texture; +} + +const CMatrix3D& ShadowMap::GetTextureMatrix() +{ + return m->TextureMatrix; +} Property changes on: ps/trunk/source/renderer/ShadowMap.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/renderer/TerrainRenderer.cpp =================================================================== --- ps/trunk/source/renderer/TerrainRenderer.cpp (nonexistent) +++ ps/trunk/source/renderer/TerrainRenderer.cpp (revision 3332) @@ -0,0 +1,402 @@ +/** + * ========================================================================= + * File : TerrainRenderer.cpp + * Project : Pyrogenesis + * Description : Terrain rendering (everything related to patches and + * : water) is encapsulated in TerrainRenderer + * + * @author Nicolai Hähnle + * ========================================================================= + */ + +#include "precompiled.h" + +#include "graphics/Camera.h" +#include "graphics/Patch.h" +#include "graphics/Terrain.h" + +#include "maths/MathUtil.h" + +#include "ps/Game.h" +#include "ps/Profile.h" + +#include "simulation/LOSManager.h" + +#include "renderer/PatchRData.h" +#include "renderer/Renderer.h" +#include "renderer/TerrainRenderer.h" +#include "renderer/WaterManager.h" + + +/////////////////////////////////////////////////////////////////////////////////////////////// +// TerrainRenderer implementation + + +/** + * TerrainRenderer keeps track of which phase it is in, to detect + * when Submit, PrepareForRendering etc. are called in the wrong order. + */ +enum Phase { + Phase_Submit, + Phase_Render +}; + + +/** + * Struct TerrainRendererInternals: Internal variables used by the TerrainRenderer class. + */ +struct TerrainRendererInternals +{ + /// Which phase (submitting or rendering patches) are we in right now? + Phase phase; + + /** + * VisiblePatches: Patches that were submitted for this frame + * + * @todo Merge this list with CPatchRData list + */ + std::vector visiblePatches; +}; + + + +/////////////////////////////////////////////////////////////////// +// Construction/Destruction +TerrainRenderer::TerrainRenderer() +{ + m = new TerrainRendererInternals(); + m->phase = Phase_Submit; +} + +TerrainRenderer::~TerrainRenderer() +{ + delete m; +} + + +/////////////////////////////////////////////////////////////////// +// Submit a patch for rendering +void TerrainRenderer::Submit(CPatch* patch) +{ + debug_assert(m->phase == Phase_Submit); + + CPatchRData* data=(CPatchRData*) patch->GetRenderData(); + if (data == 0) + { + // no renderdata for patch, create it now + data = new CPatchRData(patch); + patch->SetRenderData(data); + } + data->Update(); + + m->visiblePatches.push_back(patch); +} + + +/////////////////////////////////////////////////////////////////// +// Prepare for rendering +void TerrainRenderer::PrepareForRendering() +{ + debug_assert(m->phase == Phase_Submit); + + m->phase = Phase_Render; +} + +/////////////////////////////////////////////////////////////////// +// Clear submissions lists +void TerrainRenderer::EndFrame() +{ + debug_assert(m->phase == Phase_Render); + + m->visiblePatches.clear(); + + m->phase = Phase_Submit; +} + + +/////////////////////////////////////////////////////////////////// +// Query if patches have been submitted this frame +bool TerrainRenderer::HaveSubmissions() +{ + return m->visiblePatches.size() > 0; +} + + +/////////////////////////////////////////////////////////////////// +// Full-featured terrain rendering with blending and everything +void TerrainRenderer::RenderTerrain() +{ + debug_assert(m->phase == Phase_Render); + + // switch on required client states + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // render everything + // set up texture environment for base pass + MICROLOG(L"base splat textures"); + pglActiveTextureARB(GL_TEXTURE0); + pglClientActiveTextureARB(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); + + // Set alpha to 1.0 + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + float one[4] = { 1.f, 1.f, 1.f, 1.f }; + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, one); + + for(uint i = 0; i < m->visiblePatches.size(); ++i) + { + CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData(); + patchdata->RenderBase(); + } + + // render blends + // switch on the composite alpha map texture + (void)ogl_tex_bind(g_Renderer.m_hCompositeAlphaMap, 1); + + // switch on second uv set + pglClientActiveTextureARB(GL_TEXTURE1); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + // setup additional texenv required by blend pass + pglActiveTextureARB(GL_TEXTURE1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_ONE_MINUS_SRC_ALPHA); + + // switch on blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + // no need to write to the depth buffer a second time + glDepthMask(0); + + // render blend passes for each patch + for(uint i = 0; i < m->visiblePatches.size(); ++i) + { + CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData(); + patchdata->RenderBlends(); + } + + // restore OpenGL state + glDepthMask(1); + glDisable(GL_BLEND); + + pglClientActiveTextureARB(GL_TEXTURE1); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + g_Renderer.BindTexture(1,0); + + pglClientActiveTextureARB(GL_TEXTURE0); + pglActiveTextureARB(GL_TEXTURE0); + + // switch off all client states + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); +} + + +/////////////////////////////////////////////////////////////////// +// Render un-textured patches as polygons +void TerrainRenderer::RenderPatches() +{ + debug_assert(m->phase == Phase_Render); + + glEnableClientState(GL_VERTEX_ARRAY); + for(uint i = 0; i < m->visiblePatches.size(); ++i) + { + CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData(); + patchdata->RenderStreams(STREAM_POS); + } + glDisableClientState(GL_VERTEX_ARRAY); +} + + +/////////////////////////////////////////////////////////////////// +// Render outlines of submitted patches as lines +void TerrainRenderer::RenderOutlines() +{ + glEnableClientState(GL_VERTEX_ARRAY); + for(uint i = 0; i < m->visiblePatches.size(); ++i) + { + CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData(); + patchdata->RenderOutline(); + } + glDisableClientState(GL_VERTEX_ARRAY); +} + + +/////////////////////////////////////////////////////////////////// +// Render water that is part of the terrain +void TerrainRenderer::RenderWater() +{ + PROFILE(" render water "); + + //Fresnel effect + CCamera* Camera=g_Game->GetView()->GetCamera(); + CVector3D CamFace=Camera->m_Orientation.GetIn(); + CamFace.Normalize(); + float FresnelScalar = CamFace.Dot( CVector3D(0.0f, -1.0f, 0.0f) ); + //Invert and set boundaries + FresnelScalar = (1 - FresnelScalar) * 0.4f + 0.6f; + + const int DX[] = {1,1,0,0}; + const int DZ[] = {0,1,1,0}; + + CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + int mapSize = terrain->GetVerticesPerSide(); + CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager(); + WaterManager* WaterMgr = g_Renderer.GetWaterManager(); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); + + double time = get_time(); + + double period = 1.6; + int curTex = (int)(time*60/period) % 60; + ogl_tex_bind(WaterMgr->m_WaterTexture[curTex], 0); + + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + float tx = -fmod(time, 20.0)/20.0; + float ty = fmod(time, 35.0)/35.0; + glTranslatef(tx, ty, 0); + + 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_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PRIMARY_COLOR_ARB); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + + // Set the proper LOD bias + glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias); + + glBegin(GL_QUADS); + + for(size_t i=0; ivisiblePatches.size(); i++) + { + CPatch* patch = m->visiblePatches[i]; + + for(int dx=0; dxm_X*PATCH_SIZE + dx); + int z = (patch->m_Z*PATCH_SIZE + dz); + + // is any corner of the tile below the water height? if not, no point rendering it + bool shouldRender = false; + for(int j=0; j<4; j++) + { + float terrainHeight = terrain->getVertexGroundLevel(x + DX[j], z + DZ[j]); + if(terrainHeight < WaterMgr->m_WaterHeight) + { + shouldRender = true; + break; + } + } + if(!shouldRender) + { + continue; + } + + for(int j=0; j<4; j++) + { + int ix = x + DX[j]; + int iz = z + DZ[j]; + + float vertX = ix * CELL_SIZE; + float vertZ = iz * CELL_SIZE; + + float terrainHeight = terrain->getVertexGroundLevel(ix, iz); + + float alpha = clamp( + (WaterMgr->m_WaterHeight - terrainHeight) / WaterMgr->m_WaterFullDepth + WaterMgr->m_WaterAlphaOffset, + -100.0f, WaterMgr->m_WaterMaxAlpha); + + float losMod = 1.0f; + for(int k=0; k<4; k++) + { + int tx = ix - DX[k]; + int tz = iz - DZ[k]; + + if(tx >= 0 && tz >= 0 && tx <= mapSize-2 && tz <= mapSize-2) + { + ELOSStatus s = losMgr->GetStatus(tx, tz, g_Game->GetLocalPlayer()); + if(s == LOS_EXPLORED && losMod > 0.7f) + losMod = 0.7f; + else if(s==LOS_UNEXPLORED && losMod > 0.0f) + losMod = 0.0f; + } + } + + glColor4f(WaterMgr->m_WaterColor.r*losMod, WaterMgr->m_WaterColor.g*losMod, WaterMgr->m_WaterColor.b*losMod, alpha * FresnelScalar); + pglMultiTexCoord2fARB(GL_TEXTURE0, vertX/16.0f, vertZ/16.0f); + glVertex3f(vertX, WaterMgr->m_WaterHeight, vertZ); + } + } //end of x loop + } //end of z loop + } + + glEnd(); + + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + + +/////////////////////////////////////////////////////////////////// +// +void TerrainRenderer::ApplyShadowMap(GLuint shadowmaphandle) +{ + debug_assert(m->phase == Phase_Render); + + g_Renderer.BindTexture(0, shadowmaphandle); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); + + glColor3f(1,1,1); + + glEnable(GL_BLEND); + glBlendFunc(GL_DST_COLOR,GL_ZERO); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + for (uint i = 0; i < m->visiblePatches.size(); ++i) + { + CPatchRData* patchdata = (CPatchRData*)m->visiblePatches[i]->GetRenderData(); + patchdata->RenderStreams(STREAM_POS|STREAM_POSTOUV0); + } + + glDisable(GL_BLEND); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +} Property changes on: ps/trunk/source/renderer/TerrainRenderer.cpp ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/source/maths/NUSpline.h =================================================================== --- ps/trunk/source/maths/NUSpline.h (revision 3331) +++ ps/trunk/source/maths/NUSpline.h (revision 3332) @@ -1,50 +1,56 @@ //Desc: Contains classes for smooth splines //Borrowed from Game Programming Gems4. (Slightly changed to better suit our purposes //(and compatability). Any references to external material can be found there + +#ifndef NUSPLINE_H +#define NUSPLINE_H + #define MAX_SPLINE_NODES 30 #include #include "Vector3D.h" struct SplineData { CVector3D Position; CVector3D Velocity; float Distance; }; class RNSpline { public: void Init(){ NodeCount = 0; } void AddNode(const CVector3D &pos); void BuildSpline(); CVector3D GetPosition(float time); std::vector Node; float MaxDistance; int NodeCount; protected: CVector3D GetStartVelocity(int index); CVector3D GetEndVelocity(int index); }; class SNSpline : public RNSpline { public: void BuildSpline(){ RNSpline::BuildSpline(); Smooth(); Smooth(); Smooth(); } void Smooth(); }; class TNSpline : public SNSpline { public: void AddNode(const CVector3D &pos, float timePeriod); void PushNode() { Node.push_back( SplineData() ); } void InsertNode(const int index, const CVector3D &pos, float timePeriod); void RemoveNode(const int index); void BuildSpline(){ RNSpline::BuildSpline(); Smooth(); Smooth(); Smooth(); } void Smooth(){ SNSpline::Smooth(); Constrain(); } void Constrain(); -}; \ No newline at end of file +}; + +#endif // NUSPLINE_H Index: ps/trunk/source/maths/MathUtil.h =================================================================== --- ps/trunk/source/maths/MathUtil.h (revision 3331) +++ ps/trunk/source/maths/MathUtil.h (revision 3332) @@ -1,232 +1,243 @@ #ifndef MATH_UTIL_H #define MATH_UTIL_H #ifndef PI #define PI 3.14159265358979323846f #endif #define DEGTORAD(a) ((a) * (PI/180.0f)) #define RADTODEG(a) ((a) * (180.0f/PI)) #define SQR(x) ((x) * (x)) #define MAX3(a,b,c) ( MAX (MAX(a,b), c) ) #define ABS(a) ((a > 0) ? (a) : (-a)) template T Interpolate( T& a, T& b, float l ) { return( a + ( b - a ) * l ); } template inline T clamp(T value, T min, T max) { if (value<=min) return min; else if (value>=max) return max; else return value; } +static inline int RoundUpToPowerOf2(int x) +{ + if ((x & (x-1))==0) return x; + int d=x; + while (d & (d-1)) { + d&=(d-1); + } + return d<<1; +} + + #if 0 /* Math utility functions by Michael Reiland recondite_phreak@yahool.com --Overview-- Contains common math functions like Abs, Sign, Max, Min, etc. --More info-- TODO: actually write corresponding documentation http://wildfiregames.com/0ad/codepit/TDD/math_utils.html */ //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "Pyrogenesis.h" // Standard Engine Include #include // Needed for fmod() //-------------------------------------------------------- // Error declarations //-------------------------------------------------------- // MathUtil Errors DECLARE_ERROR(ERRONEOUS_BOUND_ERROR); //-------------------------------------------------------- // Declarations //-------------------------------------------------------- namespace MathUtil { const double PI = 3.14159265358932384; const double FL_FP_TOLERANCE = .000000001; //-------------------------------------------------------- // Template functions //-------------------------------------------------------- //-------------------------------------------------------- // Declarations //-------------------------------------------------------- ////////////////////////////////////////////////////////// // NAME: Abs // PURPOSE: Calculates the Absolute value // template T Abs(const T &num) { if( num < 0) return -1*num; return num; } ////////////////////////////////////////////////////////// // NAME: Clamp // PURPOSE: Forces num to be between lowerBound and upperBound // template T Clamp(T &num, const int &lowerBound,const int &upperBound) { if(num <= lowerBound) num = static_cast(lowerBound); else if( num >= upperBound) num = static_cast(upperBound); } ////////////////////////////////////////////////////////// // NAME: Max // PURPOSE: Returns the largest number. // template T Max(const T &num1, const T &num2) { if( num1 > num2) return num1; else return num2; } ////////////////////////////////////////////////////////// // NAME: Min // PURPOSE: Returns the smallest number. // template T Min(const T &num1, const T &num2) { if( num1 < num2) return num1; else return num2; } ////////////////////////////////////////////////////////// // NAME: Sign // PURPOSE: Returns 1 if the number is > 0, -1 if it's < 0, // otherwise returns 0. // template int Sign(const T &num) { if( num > 0 ) return 1; else if( num < 0 ) return -1; else return 0; } ////////////////////////////////////////////////////////// // NAME: Square // PURPOSE: Returns the square of a number // NOTES: Num should be less than the square root of the // maximum representable number for the data type. // template inline double Square(const T &num) { return num*num; } ////////////////////////////////////////////////////////// // NAME: Swap // PURPOSE: Swaps two numbers // template void Swap(T *num1, T *num2) { T temp = num1; num1 = num2; num2 = temp; } ////////////////////////////////////////////////////////// // NAME: Wrap // PURPOSE: Wraps num between lowerBound and upperBound. // template int Wrap(T *num,const T &lowerBound, const T &upperBound) { if(lowerBound >= upperBound) return -1; else { // translate to range 0 to n-1, find the modulus, then // translate back to range lowerBound to upperBound. num -= lowerBound; num = SignedModulus( num, Abs(upperBound - lowerBound) ); num += lowerBound; } return 0; } //-------------------------------------------------------- // Non-template functions //-------------------------------------------------------- int Ceiling(const float &num); int Ceiling(const double &num); bool CompareFloat(const double &, const double &); int Floor(const float &num); int Floor(const double &num); inline double RadiansToDegrees(const double &num); inline double DegreesToRadians(const double &num); float Random(const float &, const float &); int Random(const int &,const int &); int Round(const float &num); int Round(const double &num); int SignedModulus(const int &num, const int &n); long SignedModulus(const long &num, const long &n); float SignedModulus(const float &num, const float &n); double SignedModulus(const double &num, const double &n); } #endif #endif Index: ps/trunk/source/maths/NUSpline.cpp =================================================================== --- ps/trunk/source/maths/NUSpline.cpp (revision 3331) +++ ps/trunk/source/maths/NUSpline.cpp (revision 3332) @@ -1,175 +1,175 @@ -#include "Precompiled.h" +#include "precompiled.h" #include "NUSpline.h" #include "Matrix3D.h" CMatrix3D HermiteSpline( 2.f, -3.f, 0.f, 1.f, -2.f, 3.f, 0.f, 0.f, 1.f, -2.f, 1.f, 0.f, 1.f, -1.f, 0.f, 0.f ); // CMatrix3D H in article // cubic curve defined by 2 positions and 2 velocities CVector3D GetPositionOnCubic(const CVector3D &startPos, const CVector3D &startVel, const CVector3D &endPos, const CVector3D &endVel, float time) { CMatrix3D m( startPos.X, endPos.X, startVel.X, endVel.X, startPos.Y, endPos.Y, startVel.Y, endVel.Y, startPos.Z, endPos.Z, startVel.Z, endVel.Z, 0.0f, 0.0f, 0.0f, 1.0f ); m.Concatenate(HermiteSpline); // multiply by the mixer CVector3D TimeVector(time*time*time, time*time, time); CVector3D Result; m.Transform(TimeVector, Result); return Result; } /*********************************** R N S **************************************************/ // adds node and updates segment length void RNSpline::AddNode(const CVector3D &pos) { if ( NodeCount >= MAX_SPLINE_NODES ) return; if (NodeCount == 0) MaxDistance = 0.f; else { Node[NodeCount-1].Distance = (Node[NodeCount-1].Position - pos).GetLength(); MaxDistance += Node[NodeCount-1].Distance; } SplineData temp; temp.Position = pos; Node.push_back(temp); NodeCount++; } // called after all nodes added. This function calculates the node velocities void RNSpline::BuildSpline() { for (int i = 1; i 1 CVector3D RNSpline::GetPosition(float time) { float Distance = time * MaxDistance; float CurrentDistance = 0.f; int i = 0; while (CurrentDistance + Node[i].Distance < Distance && i < 100) { CurrentDistance += Node[i].Distance; i++; } float t = Distance - CurrentDistance; t /= Node[i].Distance; // scale t in range 0 - 1 CVector3D startVel = Node[i].Velocity * Node[i].Distance; CVector3D endVel = Node[i+1].Velocity * Node[i].Distance; return GetPositionOnCubic(Node[i].Position, startVel, Node[i+1].Position, endVel, t); } // internal. Based on Equation 14 CVector3D RNSpline::GetStartVelocity(int index) { CVector3D temp = (Node[index+1].Position - Node[index].Position) * ( 1 / Node[index].Distance) * 3.f; return (temp - Node[index+1].Velocity)*0.5f; } // internal. Based on Equation 15 CVector3D RNSpline::GetEndVelocity(int index) { CVector3D temp = (Node[index].Position - Node[index-1].Position) * (1 / Node[index-1].Distance) * 3.f; return (temp - Node[index-1].Velocity) * 0.5f; } /*********************************** S N S **************************************************/ // smoothing filter. void SNSpline::Smooth() { CVector3D newVel; CVector3D oldVel = GetStartVelocity(0); for (int i = 1; i= MAX_SPLINE_NODES ) return; if (NodeCount == 0) MaxDistance = 0.f; else { Node[NodeCount-1].Distance = timePeriod; MaxDistance += Node[NodeCount-1].Distance; } SplineData temp; temp.Position = pos; Node.push_back(temp); NodeCount++; } //Inserts node before position void TNSpline::InsertNode(const int index, const CVector3D &pos, float timePeriod) { if ( NodeCount >= MAX_SPLINE_NODES ) return; if (NodeCount == 0) MaxDistance = 0.f; else { Node[NodeCount-1].Distance = timePeriod; MaxDistance += Node[NodeCount-1].Distance; } SplineData temp; temp.Position = pos; Node.insert(Node.begin() + index, temp); NodeCount++; } //Removes node at index void TNSpline::RemoveNode(const int index) { if (NodeCount == 0) return; else Node.erase( Node.begin() + index, Node.begin() + index + 1 ); NodeCount--; } void TNSpline::Constrain() { for (int i = 1; i