/////////////////////////////////////////////////////////////////////////////// // // 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 "graphics/Terrain.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "graphics/Camera.h" #include "graphics/Texture.h" #include "graphics/LightEnv.h" #include "graphics/Terrain.h" #include "ps/Pyrogenesis.h" // MICROLOG #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Game.h" #include "ps/World.h" #include "ps/Player.h" #include "simulation/LOSManager.h" #include "simulation/TerritoryManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "lib/ogl.h" #include "lib/path_util.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 "graphics/ParticleEngine.h" #include "graphics/DefaultEmitter.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/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TransparencyRenderer.h" #include "renderer/WaterManager.h" #include "renderer/SkyManager.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(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t 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(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"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t 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(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation enum { AmbientDiffuse = 0, OnlyDiffuse, NumVertexTypes }; /** * 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 { /// true if CRenderer::Open has been called bool IsOpen; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Terrain renderer TerrainRenderer* terrainRenderer; /// Shadow map ShadowMap* shadow; /// Various model renderers struct Models { // The following model renderers are aliases for the appropriate real_* // model renderers (depending on hardware availability and current settings) // and must be used for actual model submission and rendering ModelRenderer* Normal; ModelRenderer* NormalInstancing; ModelRenderer* Player; ModelRenderer* PlayerInstancing; ModelRenderer* Transp; // "Palette" of available ModelRenderers. Do not use these directly for // rendering and submission; use the aliases above instead. ModelRenderer* pal_NormalFF[NumVertexTypes]; ModelRenderer* pal_PlayerFF[NumVertexTypes]; ModelRenderer* pal_NormalHWLit[NumVertexTypes]; ModelRenderer* pal_PlayerHWLit[NumVertexTypes]; ModelRenderer* pal_NormalInstancing[NumVertexTypes]; ModelRenderer* pal_PlayerInstancing[NumVertexTypes]; ModelRenderer* pal_TranspFF[NumVertexTypes]; ModelRenderer* pal_TranspHWLit[NumVertexTypes]; ModelRenderer* pal_TranspSortAll; ModelVertexRendererPtr VertexFF[NumVertexTypes]; ModelVertexRendererPtr VertexHWLit[NumVertexTypes]; ModelVertexRendererPtr VertexInstancing[NumVertexTypes]; ModelVertexRendererPtr VertexPolygonSort; // generic RenderModifiers that are supposed to be used directly RenderModifierPtr ModWireframe; RenderModifierPtr ModSolidColor; RenderModifierPtr ModTransparentShadow; RenderModifierPtr ModTransparentDepthShadow; // RenderModifiers that are selected from the palette below RenderModifierPtr ModNormal; RenderModifierPtr ModPlayer; RenderModifierPtr ModTransparent; // Palette of available RenderModifiers RenderModifierPtr ModPlain; LitRenderModifierPtr ModPlainLit; RenderModifierPtr ModPlayerUnlit; LitRenderModifierPtr ModPlayerLit; RenderModifierPtr ModTransparentUnlit; LitRenderModifierPtr ModTransparentLit; } Model; CRendererInternals() : IsOpen(false), profileTable(g_Renderer.m_Stats) { terrainRenderer = new TerrainRenderer(); shadow = new ShadowMap(); for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType) { Model.pal_NormalFF[vertexType] = 0; Model.pal_PlayerFF[vertexType] = 0; Model.pal_TranspFF[vertexType] = 0; Model.pal_NormalHWLit[vertexType] = 0; Model.pal_PlayerHWLit[vertexType] = 0; Model.pal_TranspHWLit[vertexType] = 0; Model.pal_NormalInstancing[vertexType] = 0; Model.pal_PlayerInstancing[vertexType] = 0; } Model.pal_TranspSortAll = 0; Model.Normal = 0; Model.NormalInstancing = 0; Model.Player = 0; Model.PlayerInstancing = 0; Model.Transp = 0; } ~CRendererInternals() { delete shadow; delete terrainRenderer; } bool CanUseRenderPathVertexShader() { for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType) { if (!Model.pal_NormalHWLit[vertexType] || !Model.pal_PlayerHWLit[vertexType]) return false; } return true; } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; 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_SortAllTransparent = false; m_FastNormals = true; m_DisplayFrustum = false; m_DisableCopyShadow = false; m_FastPlayerColor = true; m_VertexShader = 0; m_Options.m_NoVBO=false; m_Options.m_NoFramebufferObject = false; m_Options.m_Shadows=true; m_Options.m_RenderPath = RP_DEFAULT; m_ShadowZBias = 0.02f; for (uint i=0;iModel.pal_NormalFF[vertexType]; delete m->Model.pal_PlayerFF[vertexType]; delete m->Model.pal_TranspFF[vertexType]; delete m->Model.pal_NormalHWLit[vertexType]; delete m->Model.pal_PlayerHWLit[vertexType]; delete m->Model.pal_TranspHWLit[vertexType]; delete m->Model.pal_NormalInstancing[vertexType]; delete m->Model.pal_PlayerInstancing[vertexType]; } delete m->Model.pal_TranspSortAll; // general delete m_VertexShader; m_VertexShader = 0; CParticleEngine::GetInstance()->cleanup(); // 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; m_Caps.m_DepthTextureShadows = false; m_Caps.m_FramebufferObject = 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; } if (0 == oglHaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", 0)) { // According to Delphi3d.net, all relevant graphics chips that support depth textures // (i.e. Geforce3+, Radeon9500+, even i915) also have >= 4 TMUs, so this restriction // isn't actually a restriction, and it helps with integrating depth texture // shadows into rendering paths. if (ogl_max_tex_units >= 4) m_Caps.m_DepthTextureShadows = true; } if (!m_Options.m_NoFramebufferObject) { if (oglHaveExtension("GL_EXT_framebuffer_object")) m_Caps.m_FramebufferObject = true; } } bool CRenderer::Open(int width, int height, int depth) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); m->shadow->SetUseDepthTexture(true); m_VertexShader = new RenderPathVertexShader; if (!m_VertexShader->Init()) { delete m_VertexShader; m_VertexShader = 0; } // model rendering m->Model.VertexFF[AmbientDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(false)); m->Model.VertexFF[OnlyDiffuse] = ModelVertexRendererPtr(new FixedFunctionModelRenderer(true)); if (HWLightingModelRenderer::IsAvailable()) { m->Model.VertexHWLit[AmbientDiffuse] = ModelVertexRendererPtr(new HWLightingModelRenderer(false)); m->Model.VertexHWLit[OnlyDiffuse] = ModelVertexRendererPtr(new HWLightingModelRenderer(true)); } if (InstancingModelRenderer::IsAvailable()) { m->Model.VertexInstancing[AmbientDiffuse] = ModelVertexRendererPtr(new InstancingModelRenderer(false)); m->Model.VertexInstancing[OnlyDiffuse] = ModelVertexRendererPtr(new InstancingModelRenderer(true)); } m->Model.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer); for(int vertexType = 0; vertexType < NumVertexTypes; ++vertexType) { m->Model.pal_NormalFF[vertexType] = new BatchModelRenderer(m->Model.VertexFF[vertexType]); m->Model.pal_PlayerFF[vertexType] = new BatchModelRenderer(m->Model.VertexFF[vertexType]); m->Model.pal_TranspFF[vertexType] = new SortModelRenderer(m->Model.VertexFF[vertexType]); if (m->Model.VertexHWLit[vertexType]) { m->Model.pal_NormalHWLit[vertexType] = new BatchModelRenderer(m->Model.VertexHWLit[vertexType]); m->Model.pal_PlayerHWLit[vertexType] = new BatchModelRenderer(m->Model.VertexHWLit[vertexType]); m->Model.pal_TranspHWLit[vertexType] = new SortModelRenderer(m->Model.VertexHWLit[vertexType]); } if (m->Model.VertexInstancing[vertexType]) { m->Model.pal_NormalInstancing[vertexType] = new BatchModelRenderer(m->Model.VertexInstancing[vertexType]); m->Model.pal_PlayerInstancing[vertexType] = new BatchModelRenderer(m->Model.VertexInstancing[vertexType]); } } m->Model.pal_TranspSortAll = new SortModelRenderer(m->Model.VertexPolygonSort); m->Model.ModWireframe = RenderModifierPtr(new WireframeRenderModifier); m->Model.ModPlain = RenderModifierPtr(new PlainRenderModifier); m->Model.ModPlainLit = LitRenderModifierPtr(new PlainLitRenderModifier); SetFastPlayerColor(true); m->Model.ModPlayerLit = LitRenderModifierPtr(new LitPlayerColorRender); m->Model.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier); m->Model.ModTransparentUnlit = RenderModifierPtr(new TransparentRenderModifier); m->Model.ModTransparentLit = LitRenderModifierPtr(new LitTransparentRenderModifier); m->Model.ModTransparentShadow = RenderModifierPtr(new TransparentShadowRenderModifier); m->Model.ModTransparentDepthShadow = RenderModifierPtr(new TransparentDepthShadowModifier); // Particle engine CParticleEngine::GetInstance()->initParticleSystem(); CEmitter *pEmitter = new CDefaultEmitter(1000, -1); CParticleEngine::GetInstance()->addEmitter(pEmitter); // Dimensions 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); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); return true; } // resize renderer view void CRenderer::Resize(int width,int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow->RecreateTexture(); 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_NOFRAMEBUFFEROBJECT: m_Options.m_NoFramebufferObject=value; break; case OPT_SHADOWS: m_Options.m_Shadows=value; break; case OPT_FANCYWATER: m_Options.m_FancyWater=value; 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_NOFRAMEBUFFEROBJECT: return m_Options.m_NoFramebufferObject; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_FANCYWATER: return m_Options.m_FancyWater; default: debug_warn("CRenderer::GetOptionBool: unknown option"); break; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionColor: set color renderer option void CRenderer::SetOptionColor(Option UNUSED(opt),const RGBAColor& UNUSED(value)) { // switch (opt) { // 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(Option UNUSED(opt)) const { static const RGBAColor defaultColor(1.0f,1.0f,1.0f,1.0f); // switch (opt) { // 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 (!m->IsOpen) { // Delay until Open() is called. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m->CanUseRenderPathVertexShader()) rp = RP_VERTEXSHADER; else rp = RP_FIXED; } if (rp == RP_VERTEXSHADER) { if (!m->CanUseRenderPathVertexShader()) { 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(const 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->Model.ModPlayerUnlit = RenderModifierPtr(new FastPlayerColorRender); else m->Model.ModPlayerUnlit = RenderModifierPtr(new SlowPlayerColorRender); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { // bump frame counter m_FrameCounter++; if (m_VertexShader) m_VertexShader->BeginFrame(); // zero out all the per-frame stats m_Stats.Reset(); // init per frame stuff m->shadow->SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); // choose model renderers for this frame int vertexType; if (m_Options.m_Shadows && m->shadow->GetUseDepthTexture()) { vertexType = OnlyDiffuse; m->Model.ModNormal = m->Model.ModPlainLit; m->Model.ModPlainLit->SetShadowMap(m->shadow); m->Model.ModPlainLit->SetLightEnv(m_LightEnv); m->Model.ModPlayer = m->Model.ModPlayerLit; m->Model.ModPlayerLit->SetShadowMap(m->shadow); m->Model.ModPlayerLit->SetLightEnv(m_LightEnv); m->Model.ModTransparent = m->Model.ModTransparentLit; m->Model.ModTransparentLit->SetShadowMap(m->shadow); m->Model.ModTransparentLit->SetLightEnv(m_LightEnv); } else { vertexType = AmbientDiffuse; m->Model.ModNormal = m->Model.ModPlain; m->Model.ModPlayer = m->Model.ModPlayerUnlit; m->Model.ModTransparent = m->Model.ModTransparentUnlit; } if (m_Options.m_RenderPath == RP_VERTEXSHADER) { debug_assert(m->Model.pal_NormalHWLit[vertexType] != 0); if (m->Model.pal_NormalInstancing) m->Model.NormalInstancing = m->Model.pal_NormalInstancing[vertexType]; else m->Model.NormalInstancing = m->Model.pal_NormalHWLit[vertexType]; m->Model.Normal = m->Model.pal_NormalHWLit[vertexType]; if (m->Model.pal_PlayerInstancing) m->Model.PlayerInstancing = m->Model.pal_PlayerInstancing[vertexType]; else m->Model.PlayerInstancing = m->Model.pal_PlayerHWLit[vertexType]; m->Model.Player = m->Model.pal_PlayerHWLit[vertexType]; } else { m->Model.NormalInstancing = m->Model.pal_NormalFF[vertexType]; m->Model.Normal = m->Model.pal_NormalFF[vertexType]; m->Model.PlayerInstancing = m->Model.pal_PlayerFF[vertexType]; m->Model.Player = m->Model.pal_PlayerFF[vertexType]; } if (m_SortAllTransparent) m->Model.Transp = m->Model.pal_TranspSortAll; else if (m_Options.m_RenderPath == RP_VERTEXSHADER) m->Model.Transp = m->Model.pal_TranspHWLit[vertexType]; else m->Model.Transp = m->Model.pal_TranspFF[vertexType]; } ////////////////////////////////////////////////////////////////////////////////////////// // 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; } void CRenderer::RenderShadowMap() { PROFILE( "render shadow map" ); m->shadow->BeginRender(); float shadowTransp = m_LightEnv->GetTerrainShadowTransparency(); glColor3f(shadowTransp, shadowTransp, shadowTransp); // Figure out transparent rendering strategy RenderModifierPtr transparentShadows = m->Model.ModTransparentShadow; if (m->shadow->GetUseDepthTexture()) transparentShadows = m->Model.ModTransparentDepthShadow; // Render all closed models (i.e. models where rendering back faces will produce // the correct result) glCullFace(GL_FRONT); if (m->shadow->GetUseDepthTexture()) m->terrainRenderer->RenderPatches(); glCullFace(GL_BACK); // Render models that aren't closed glDisable(GL_CULL_FACE); m->Model.Normal->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS); m->Model.Player->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->Render(m->Model.ModSolidColor, MODELFLAG_CASTSHADOWS); m->Model.Transp->Render(transparentShadows, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); glColor3f(1.0, 1.0, 1.0); m->shadow->EndRender(); } 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"); m->terrainRenderer->RenderTerrain(m_Options.m_Shadows ? m->shadow : 0); 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); // render tiles edges m->terrainRenderer->RenderPatches(); // set color for outline glColor3f(0,0,1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer->RenderOutlines(); // .. and restore the renderstates glDisable(GL_BLEND); glDepthMask(1); // restore fill mode, and we're done glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } } 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->Model.Normal->Render(m->Model.ModNormal, 0); m->Model.Player->Render(m->Model.ModPlayer, 0); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->Render(m->Model.ModNormal, 0); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->Render(m->Model.ModPlayer, 0); if (m_ModelRenderMode==WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_ModelRenderMode==EDGED_FACES) { m->Model.Normal->Render(m->Model.ModWireframe, 0); m->Model.Player->Render(m->Model.ModWireframe, 0); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->Render(m->Model.ModWireframe, 0); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->Render(m->Model.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->Model.Transp->Render(m->Model.ModTransparent, 0); if (m_ModelRenderMode==WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); } else if (m_ModelRenderMode==EDGED_FACES) { m->Model.Transp->Render(m->Model.ModWireframe, 0); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // GetModelViewProjectionMatrix: save the current OpenGL model-view-projection matrix CMatrix3D CRenderer::GetModelViewProjectionMatrix() { CMatrix3D proj; CMatrix3D view; glGetFloatv( GL_PROJECTION_MATRIX, &proj._11 ); glGetFloatv( GL_MODELVIEW_MATRIX, &view._11 ); return( proj * view ); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - cp is a clip plane in camera space (cp.dot(v) = 0 for any vector v on the plane) // - sign is 1 or -1, to specify the side to clip on void CRenderer::SetObliqueFrustumClipping(const CVector4D& cp, int sign) { float matrix[16]; CVector4D q; // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D viewMatrix; m_ViewCamera.m_Orientation.GetInverse(viewMatrix); CMatrix3D normalMatrix = viewMatrix; normalMatrix._14 = 0; normalMatrix._24 = 0; normalMatrix._34 = 0; normalMatrix._44 = 1; normalMatrix._41 = 0; normalMatrix._42 = 0; normalMatrix._43 = 0; // Convert the normal to camera space CVector4D planeNormal(cp.m_X, cp.m_Y, cp.m_Z, 0); planeNormal = normalMatrix.Transform(planeNormal); planeNormal.normalize(); // Find a point on the plane: we'll take the normal times -D float oldD = cp.m_W; CVector4D pointOnPlane(-oldD * cp.m_X, -oldD * cp.m_Y, -oldD * cp.m_Z, 1); pointOnPlane = viewMatrix.Transform(pointOnPlane); float newD = -pointOnPlane.dot(planeNormal); // Now create a clip plane from the new normal and new D CVector4D camPlane = planeNormal; camPlane.m_W = newD; // Grab the current projection matrix from OpenGL glGetFloatv(GL_PROJECTION_MATRIX, matrix); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix q.m_X = (sgn(camPlane.m_X) + matrix[8]) / matrix[0]; q.m_Y = (sgn(camPlane.m_Y) + matrix[9]) / matrix[5]; q.m_Z = -1.0f; q.m_W = (1.0f + matrix[10]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (sign * 2.0f / camPlane.dot(q)); // Replace the third row of the projection matrix matrix[2] = c.m_X; matrix[6] = c.m_Y; matrix[10] = c.m_Z + 1.0f; matrix[14] = c.m_W; // Load it back into OpenGL glMatrixMode(GL_PROJECTION); glLoadMatrixf(matrix); glMatrixMode(GL_MODELVIEW); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections() { MICROLOG(L"render reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. m_ViewCamera.m_Orientation.Translate(0, -wm.m_WaterHeight, 0); m_ViewCamera.m_Orientation.Scale(1, -1, 1); m_ViewCamera.m_Orientation.Translate(0, wm.m_WaterHeight, 0); SViewPort vp; vp.m_Height = wm.m_ReflectionTextureSize; vp.m_Width = wm.m_ReflectionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(&vp); m_ViewCamera.SetProjection(1, 5000, DEGTORAD(21)); // Slightly higher than view FOV of 20 CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; SetCamera(m_ViewCamera, m_CullCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane, -1); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = GetModelViewProjectionMatrix(); // Disable backface culling so trees render properly (it might also be possible to flip // the culling direction here, but this seems to lead to problems) glDisable(GL_CULL_FACE); // Make the depth buffer work backwards; there seems to be some oddness with // oblique frustum clipping and the "sign" parameter here glClearDepth(0); glClear(GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_GEQUAL); // Render sky, terrain and models m->skyManager.RenderSky(); oglCheck(); RenderPatches(); oglCheck(); RenderModels(); oglCheck(); RenderTransparentModels(); oglCheck(); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_ReflectionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, wm.m_ReflectionTextureSize, wm.m_ReflectionTextureSize); //Reset old camera and re-enable backface culling SetCamera(normalCamera, m_CullCamera); glEnable(GL_CULL_FACE); //glClearDepth(1); //glClear(GL_DEPTH_BUFFER_BIT); //glDepthFunc(GL_LEQUAL); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions() { MICROLOG(L"render refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. SViewPort vp; vp.m_Height = wm.m_RefractionTextureSize; vp.m_Width = wm.m_RefractionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(&vp); m_ViewCamera.SetProjection(1, 5000, DEGTORAD(21)); // Slightly higher than view FOV of 20 CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; SetCamera(m_ViewCamera, m_CullCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane, -1); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = GetModelViewProjectionMatrix(); // Make the depth buffer work backwards; there seems to be some oddness with // oblique frustum clipping and the "sign" parameter here glClearDepth(0); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // a neutral gray to blend in with shores glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_GEQUAL); // Render terrain and models RenderPatches(); oglCheck(); RenderModels(); oglCheck(); RenderTransparentModels(); oglCheck(); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_RefractionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, wm.m_RefractionTextureSize, wm.m_RefractionTextureSize); //Reset old camera and re-enable backface culling SetCamera(normalCamera, m_CullCamera); glEnable(GL_CULL_FACE); glClearDepth(1); glDepthFunc(GL_LEQUAL); } /////////////////////////////////////////////////////////////////////////////////////////////////// // FlushFrame: force rendering of any batched objects void CRenderer::FlushFrame() { oglCheck(); // Prepare model renderers PROFILE_START("prepare models"); m->Model.Normal->PrepareModels(); m->Model.Player->PrepareModels(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->PrepareModels(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->PrepareModels(); m->Model.Transp->PrepareModels(); PROFILE_END("prepare models"); PROFILE_START("prepare terrain"); m->terrainRenderer->PrepareForRendering(); PROFILE_END("prepare terrain"); 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(); if (m_WaterManager->m_RenderWater && m_Options.m_FancyWater) { // render reflected and refracted scenes, then re-clear the screen RenderReflections(); RenderRefractions(); glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } // render sky; this is done before everything so that // (a) we can use a box around the camera instead of placing it "infinitely far away" // (we just disable depth write so that this doesn't affect future rendering) // (b) transparent objects properly overlap the sky if (m_SkyManager->m_RenderSky) { MICROLOG(L"render sky"); m->skyManager.RenderSky(); oglCheck(); } // render submitted patches and models MICROLOG(L"render patches"); RenderPatches(); oglCheck(); g_Game->GetWorld()->GetTerritoryManager()->renderTerritories(); oglCheck(); // render debug-related terrain overlays TerrainOverlay::RenderOverlays(); oglCheck(); MICROLOG(L"render models"); RenderModels(); oglCheck(); // render transparent stuff, so it can overlap models/terrain MICROLOG(L"render transparent"); RenderTransparentModels(); oglCheck(); // render water if (m_WaterManager->m_RenderWater && g_Game) { MICROLOG(L"render water"); m->terrainRenderer->RenderWater(); oglCheck(); // render transparent stuff again, so it can overlap the water MICROLOG(L"render transparent 2"); RenderTransparentModels(); oglCheck(); // TODO: Maybe think of a better way to deal with transparent objects; // they can appear both under and above water (seaweed vs. trees), but doing // 2 renders causes (a) inefficiency and (b) darker over-water objects (e.g. // trees) than usual because the transparent bits get overwritten twice. // This doesn't look particularly bad, but it is noticeable if you try // turning the water off. On the other hand every user will have water // on all the time, so it might not be worth worrying about. } // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //// Particle Engine Rendering. MICROLOG(L"render particles"); CParticleEngine::GetInstance()->renderParticles(); oglCheck(); // render debug lines if (m_DisplayFrustum) { MICROLOG(L"display frustum"); DisplayFrustum(); m->shadow->RenderDebugDisplay(); oglCheck(); } // empty lists MICROLOG(L"empty lists"); m->terrainRenderer->EndFrame(); // Finish model renderers m->Model.Normal->EndFrame(); m->Model.Player->EndFrame(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->EndFrame(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->EndFrame(); m->Model.Transp->EndFrame(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { g_Renderer.SetTexture(0,0); static bool once=false; if (!once && glGetError()) { LOG(ERROR, LOG_CATEGORY, "CRenderer::EndFrame: GL errors occurred"); once=true; } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { glDepthMask(0); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetCamera: setup projection and transform of camera and adjust viewport to current view void CRenderer::SetCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; CMatrix3D view; m_ViewCamera.m_Orientation.GetInverse(view); const CMatrix3D& proj = m_ViewCamera.GetProjection(); glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); SetViewport(m_ViewCamera.GetViewPort()); } void CRenderer::SetViewport(const SViewPort &vp) { glViewport(vp.m_X,vp.m_Y,vp.m_Width,vp.m_Height); } void CRenderer::Submit(CPatch* patch) { m->terrainRenderer->Submit(patch); } void CRenderer::Submit(CModel* model) { if (model->GetFlags() & MODELFLAG_CASTSHADOWS) { PROFILE( "updating shadow bounds" ); m->shadow->AddShadowedBound(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 (canUseInstancing) m->Model.PlayerInstancing->Submit(model); else m->Model.Player->Submit(model); } else if (model->GetMaterial().UsesAlpha()) { m->Model.Transp->Submit(model); } else { if (canUseInstancing) m->Model.NormalInstancing->Submit(model); else m->Model.Normal->Submit(model); } } void CRenderer::Submit(CSprite* UNUSED(sprite)) { } void CRenderer::Submit(CParticleSys* UNUSED(psys)) { } void CRenderer::Submit(COverlay* UNUSED(overlay)) { } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 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; // Errored textures will give a handle of -1 if (h == -1) h = 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)path_package_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;ishadow->GetUseDepthTexture()); } void CRenderer::JSI_SetUseDepthTexture(JSContext* ctx, jsval newval) { bool depthTexture; if (!ToPrimitive(ctx, newval, depthTexture)) return; m->shadow->SetUseDepthTexture(depthTexture); } jsval CRenderer::JSI_GetDepthTextureBits(JSContext*) { return ToJSVal(m->shadow->GetDepthTextureBits()); } void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval) { int depthTextureBits; if (!ToPrimitive(ctx, newval, depthTextureBits)) return; m->shadow->SetDepthTextureBits(depthTextureBits); } jsval CRenderer::JSI_GetSky(JSContext*) { return ToJSVal(m->skyManager.GetSkySet()); } void CRenderer::JSI_SetSky(JSContext* ctx, jsval newval) { CStrW skySet; if (!ToPrimitive(ctx, newval, skySet)) return; m->skyManager.SetSkySet(skySet); } jsval CRenderer::JSI_GetHorizonHeight(JSContext*) { return ToJSVal(m->skyManager.m_HorizonHeight); } void CRenderer::JSI_SetHorizonHeight(JSContext* ctx, jsval newval) { float value; if (!ToPrimitive(ctx, newval, value)) return; m->skyManager.m_HorizonHeight = value; } jsval CRenderer::JSI_GetWaterShininess(JSContext*) { return ToJSVal(m->waterManager.m_Shininess); } void CRenderer::JSI_SetWaterShininess(JSContext* ctx, jsval newval) { float value; if (!ToPrimitive(ctx, newval, value)) return; m->waterManager.m_Shininess = value; } jsval CRenderer::JSI_GetWaterMurkiness(JSContext*) { return ToJSVal(m->waterManager.m_Murkiness); } void CRenderer::JSI_SetWaterMurkiness(JSContext* ctx, jsval newval) { float value; if (!ToPrimitive(ctx, newval, value)) return; m->waterManager.m_Murkiness = value; } jsval CRenderer::JSI_GetWaterWaviness(JSContext*) { return ToJSVal(m->waterManager.m_Waviness); } void CRenderer::JSI_SetWaterWaviness(JSContext* ctx, jsval newval) { float value; if (!ToPrimitive(ctx, newval, value)) return; m->waterManager.m_Waviness = value; } jsval CRenderer::JSI_GetWaterRepeatPeriod(JSContext*) { return ToJSVal(m->waterManager.m_RepeatPeriod); } void CRenderer::JSI_SetWaterRepeatPeriod(JSContext* ctx, jsval newval) { float value; if (!ToPrimitive(ctx, newval, value)) return; m->waterManager.m_RepeatPeriod = value; } void CRenderer::ScriptingInit() { AddProperty(L"fastPlayerColor", &CRenderer::JSI_GetFastPlayerColor, &CRenderer::JSI_SetFastPlayerColor); AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath); AddProperty(L"useDepthTexture", &CRenderer::JSI_GetUseDepthTexture, &CRenderer::JSI_SetUseDepthTexture); AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent); AddProperty(L"fastNormals", &CRenderer::m_FastNormals); AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum); AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias); AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow); AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits); AddProperty(L"skySet", &CRenderer::JSI_GetSky, &CRenderer::JSI_SetSky); AddProperty(L"horizonHeight", &CRenderer::JSI_GetHorizonHeight, &CRenderer::JSI_SetHorizonHeight); AddProperty(L"waterShininess", &CRenderer::JSI_GetWaterShininess, &CRenderer::JSI_SetWaterShininess); AddProperty(L"waterWaviness", &CRenderer::JSI_GetWaterWaviness, &CRenderer::JSI_SetWaterWaviness); AddProperty(L"waterRepeatPeriod", &CRenderer::JSI_GetWaterRepeatPeriod, &CRenderer::JSI_SetWaterRepeatPeriod); AddProperty(L"waterMurkiness", &CRenderer::JSI_GetWaterMurkiness, &CRenderer::JSI_SetWaterMurkiness); CJSObject::ScriptingInit("Renderer"); }