diff --git a/binaries/data/mods/_test.sim/simulation/data/territorymanager.xml b/binaries/data/mods/_test.sim/simulation/data/territorymanager.xml new file mode 100644 index 0000000000..bfee4c270f --- /dev/null +++ b/binaries/data/mods/_test.sim/simulation/data/territorymanager.xml @@ -0,0 +1,6 @@ + + + 4 + 0.5 + 0.55 + diff --git a/binaries/data/mods/public/art/textures/misc/territory_border.png b/binaries/data/mods/public/art/textures/misc/territory_border.png new file mode 100644 index 0000000000..63a80862a5 --- /dev/null +++ b/binaries/data/mods/public/art/textures/misc/territory_border.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33520d117aae35cc5dac67fa53a1791970e6764976b7b1b0cc940cb75c29e051 +size 407 diff --git a/binaries/data/mods/public/art/textures/misc/territory_border_mask.png b/binaries/data/mods/public/art/textures/misc/territory_border_mask.png new file mode 100644 index 0000000000..f853aac671 --- /dev/null +++ b/binaries/data/mods/public/art/textures/misc/territory_border_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:472bb478e53b988c5c68b87c2bae50adb791de2fca371a50c4a1dd1237f3b405 +size 178 diff --git a/binaries/data/mods/public/art/textures/misc/textures.xml b/binaries/data/mods/public/art/textures/misc/textures.xml new file mode 100644 index 0000000000..3778e974ea --- /dev/null +++ b/binaries/data/mods/public/art/textures/misc/textures.xml @@ -0,0 +1,4 @@ + + + + diff --git a/binaries/data/mods/public/shaders/overlayline.fp b/binaries/data/mods/public/shaders/overlayline.fp new file mode 100644 index 0000000000..3e6ac390d1 --- /dev/null +++ b/binaries/data/mods/public/shaders/overlayline.fp @@ -0,0 +1,20 @@ +!!ARBfp1.0 +PARAM objectColor = program.local[0]; +TEMP base; +TEMP mask; +TEMP color; +TEMP los; + +// Combine base texture and color, using mask texture +TEX base, fragment.texcoord[0], texture[0], 2D; +TEX mask, fragment.texcoord[0], texture[1], 2D; +LRP color.rgb, mask, objectColor, base; + +// Multiply by LOS texture +TEX los, fragment.texcoord[1], texture[2], 2D; +MUL result.color.rgb, color, los.a; + +// Use alpha from base texture +MOV result.color.a, base.a; + +END diff --git a/binaries/data/mods/public/shaders/overlayline.vp b/binaries/data/mods/public/shaders/overlayline.vp new file mode 100644 index 0000000000..b4a9abbcba --- /dev/null +++ b/binaries/data/mods/public/shaders/overlayline.vp @@ -0,0 +1,15 @@ +!!ARBvp1.0 +PARAM losTransform = program.local[0]; +ATTRIB position = vertex.position; + +DP4 result.position.x, state.matrix.mvp.row[0], position; +DP4 result.position.y, state.matrix.mvp.row[1], position; +DP4 result.position.z, state.matrix.mvp.row[2], position; +DP4 result.position.w, state.matrix.mvp.row[3], position; + +MOV result.texcoord[0], vertex.texcoord[0]; +MAD result.texcoord[1], position.xzzz, losTransform.x, losTransform.y; + +MOV result.color, vertex.color; + +END diff --git a/binaries/data/mods/public/shaders/overlayline.xml b/binaries/data/mods/public/shaders/overlayline.xml new file mode 100644 index 0000000000..c8af23933a --- /dev/null +++ b/binaries/data/mods/public/shaders/overlayline.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/binaries/data/mods/public/shaders/terrain_base.xml b/binaries/data/mods/public/shaders/terrain_base.xml index 5b216873a6..a0124f46cd 100644 --- a/binaries/data/mods/public/shaders/terrain_base.xml +++ b/binaries/data/mods/public/shaders/terrain_base.xml @@ -5,14 +5,12 @@ - - diff --git a/binaries/data/mods/public/shaders/terrain_blend.xml b/binaries/data/mods/public/shaders/terrain_blend.xml index b7cc029a70..dc74fd3f14 100644 --- a/binaries/data/mods/public/shaders/terrain_blend.xml +++ b/binaries/data/mods/public/shaders/terrain_blend.xml @@ -7,7 +7,6 @@ - @@ -15,7 +14,6 @@ - diff --git a/binaries/data/mods/public/shaders/terrain_common.fp b/binaries/data/mods/public/shaders/terrain_common.fp index c102d40ce2..ffb67f91d1 100644 --- a/binaries/data/mods/public/shaders/terrain_common.fp +++ b/binaries/data/mods/public/shaders/terrain_common.fp @@ -81,10 +81,6 @@ TEX color, fragment.texcoord[0], texture[0], 2D; MUL color.rgb, color, temp; #endif -// Blend with the territory boundary texture -TEX tex, fragment.texcoord[4], texture[4], 2D; -LRP color.rgb, tex.a, tex, color; - // Multiply everything by the LOS texture TEX tex.a, fragment.texcoord[3], texture[3], 2D; MUL color.rgb, color, tex.a; diff --git a/binaries/data/mods/public/shaders/terrain_common.vp b/binaries/data/mods/public/shaders/terrain_common.vp index 7c8a8724c6..5117637af9 100644 --- a/binaries/data/mods/public/shaders/terrain_common.vp +++ b/binaries/data/mods/public/shaders/terrain_common.vp @@ -2,7 +2,6 @@ PARAM sunColor = program.local[0]; PARAM losTransform = program.local[1]; PARAM shadowTransform[4] = { program.local[2..5] }; -PARAM territoryTransform = program.local[6]; TEMP lighting; @@ -39,6 +38,5 @@ MOV result.texcoord[0], vertex.texcoord[0]; #endif MAD result.texcoord[3], position.xzzz, losTransform.x, losTransform.y; -MAD result.texcoord[4], position.xzzz, territoryTransform.x, territoryTransform.y; END diff --git a/binaries/data/mods/public/shaders/terrain_decal.xml b/binaries/data/mods/public/shaders/terrain_decal.xml index c0e843add4..4285406a02 100644 --- a/binaries/data/mods/public/shaders/terrain_decal.xml +++ b/binaries/data/mods/public/shaders/terrain_decal.xml @@ -7,14 +7,12 @@ - - diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml index 2dd651f01f..94f6799a3b 100644 --- a/binaries/data/mods/public/simulation/data/pathfinder.xml +++ b/binaries/data/mods/public/simulation/data/pathfinder.xml @@ -7,6 +7,7 @@ + 2 1.0 diff --git a/binaries/data/mods/public/simulation/data/territorymanager.xml b/binaries/data/mods/public/simulation/data/territorymanager.xml new file mode 100644 index 0000000000..bfee4c270f --- /dev/null +++ b/binaries/data/mods/public/simulation/data/territorymanager.xml @@ -0,0 +1,6 @@ + + + 4 + 0.5 + 0.55 + diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index 6ae03f0a49..e07b13baed 100644 --- a/source/graphics/Overlay.h +++ b/source/graphics/Overlay.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,10 +18,13 @@ #ifndef INCLUDED_GRAPHICS_OVERLAY #define INCLUDED_GRAPHICS_OVERLAY +#include "graphics/RenderableObject.h" #include "graphics/Texture.h" #include "maths/Vector3D.h" #include "ps/Overlay.h" // CColor (TODO: that file has nothing to do with overlays, it should be renamed) +class CTerrain; + /** * Line-based overlay, with world-space coordinates, rendered in the world * potentially behind other objects. Designed for selection circles and debug info. @@ -35,6 +38,24 @@ struct SOverlayLine u8 m_Thickness; // pixels }; +/** + * Textured line overlay, with world-space coordinates, rendered in the world + * onto the terrain. Designed for territory borders. + */ +struct SOverlayTexturedLine +{ + SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { } + + CTerrain* m_Terrain; + CTexturePtr m_TextureBase; + CTexturePtr m_TextureMask; + CColor m_Color; + std::vector m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed + float m_Thickness; // world-space units + + shared_ptr m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic) +}; + /** * Billboard sprite overlay, with world-space coordinates, rendered on top * of all other objects. Designed for health bars and rank icons. diff --git a/source/graphics/Terrain.cpp b/source/graphics/Terrain.cpp index 3f05de6722..9b346abd46 100644 --- a/source/graphics/Terrain.cpp +++ b/source/graphics/Terrain.cpp @@ -223,6 +223,51 @@ void CTerrain::CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) con normal.Normalize(); } +CVector3D CTerrain::CalcExactNormal(float x, float z) const +{ + // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1) + const ssize_t xi = clamp((ssize_t)floor(x/CELL_SIZE), (ssize_t)0, m_MapSize-2); + const ssize_t zi = clamp((ssize_t)floor(z/CELL_SIZE), (ssize_t)0, m_MapSize-2); + + const float xf = clamp(x/CELL_SIZE-xi, 0.0f, 1.0f); + const float zf = clamp(z/CELL_SIZE-zi, 0.0f, 1.0f); + + float h00 = m_Heightmap[zi*m_MapSize + xi]; + float h01 = m_Heightmap[(zi+1)*m_MapSize + xi]; + float h10 = m_Heightmap[zi*m_MapSize + (xi+1)]; + float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)]; + + // Determine which terrain triangle this point is on, + // then compute the normal of that triangle's plane + + if (GetTriangulationDir(xi, zi)) + { + if (xf + zf <= 1.f) + { + // Lower-left triangle (don't use h11) + return -CVector3D(CELL_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, CELL_SIZE)).Normalized(); + } + else + { + // Upper-right triangle (don't use h00) + return -CVector3D(CELL_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, CELL_SIZE)).Normalized(); + } + } + else + { + if (xf <= zf) + { + // Upper-left triangle (don't use h10) + return -CVector3D(CELL_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, CELL_SIZE)).Normalized(); + } + else + { + // Lower-right triangle (don't use h01) + return -CVector3D(CELL_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, CELL_SIZE)).Normalized(); + } + } +} + /////////////////////////////////////////////////////////////////////////////// // GetPatch: return the patch at (i,j) in patch space, or null if the patch is // out of bounds @@ -298,10 +343,36 @@ float CTerrain::GetExactGroundLevel(float x, float z) const float h01 = m_Heightmap[(zi+1)*m_MapSize + xi]; float h10 = m_Heightmap[zi*m_MapSize + (xi+1)]; float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)]; - // Linearly interpolate - return (HEIGHT_SCALE * ( - (1 - zf) * ((1 - xf) * h00 + xf * h10) - + zf * ((1 - xf) * h01 + xf * h11))); + + // Determine which terrain triangle this point is on, + // then compute the linearly-interpolated height on that triangle's plane + + if (GetTriangulationDir(xi, zi)) + { + if (xf + zf <= 1.f) + { + // Lower-left triangle (don't use h11) + return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h01-h00)*zf); + } + else + { + // Upper-right triangle (don't use h00) + return HEIGHT_SCALE * (h11 + (h01-h11)*(1-xf) + (h10-h11)*(1-zf)); + } + } + else + { + if (xf <= zf) + { + // Upper-left triangle (don't use h10) + return HEIGHT_SCALE * (h00 + (h11-h01)*xf + (h01-h00)*zf); + } + else + { + // Lower-right triangle (don't use h01) + return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h11-h10)*zf); + } + } } fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const @@ -328,6 +399,9 @@ fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const // Linearly interpolate return ((one - zf).Multiply(xf1 * h00 + xf0 * h10) + zf.Multiply(xf1 * h01 + xf0 * h11)) / (int)(HEIGHT_UNITS_PER_METRE / 2); + + // TODO: This should probably be more like GetExactGroundLevel() + // in handling triangulation properly } bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const diff --git a/source/graphics/Terrain.h b/source/graphics/Terrain.h index 98445fc72a..5a05560242 100644 --- a/source/graphics/Terrain.h +++ b/source/graphics/Terrain.h @@ -126,6 +126,8 @@ public: void CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const; void CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const; + CVector3D CalcExactNormal(float x, float z) const; + // flatten out an area of terrain (specified in world space coords); return // the average height of the flattened area float FlattenArea(float x0, float x1, float z0, float z1); diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index 2344d12787..fb30ec160e 100644 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -44,7 +44,8 @@ struct TPhash std::size_t seed = 0; boost::hash_combine(seed, a.m_Path); boost::hash_combine(seed, a.m_Filter); - boost::hash_combine(seed, a.m_Wrap); + boost::hash_combine(seed, a.m_WrapS); + boost::hash_combine(seed, a.m_WrapT); boost::hash_combine(seed, a.m_Aniso); return seed; } @@ -61,7 +62,8 @@ struct TPequal_to bool operator()(CTextureProperties const& a, CTextureProperties const& b) const { return a.m_Path == b.m_Path && a.m_Filter == b.m_Filter - && a.m_Wrap == b.m_Wrap && a.m_Aniso == b.m_Aniso; + && a.m_WrapS == b.m_WrapS && a.m_WrapT == b.m_WrapT + && a.m_Aniso == b.m_Aniso; } bool operator()(CTexturePtr const& a, CTexturePtr const& b) const { @@ -186,7 +188,7 @@ public: (void)ogl_tex_get_average_colour(h, &texture->m_BaseColour); // Set GL upload properties - (void)ogl_tex_set_wrap(h, texture->m_Properties.m_Wrap); + (void)ogl_tex_set_wrap(h, texture->m_Properties.m_WrapS, texture->m_Properties.m_WrapT); (void)ogl_tex_set_anisotropy(h, texture->m_Properties.m_Aniso); // Prevent ogl_tex automatically generating mipmaps (which is slow and unwanted), diff --git a/source/graphics/TextureManager.h b/source/graphics/TextureManager.h index 897aaf94ec..0435aa41f8 100644 --- a/source/graphics/TextureManager.h +++ b/source/graphics/TextureManager.h @@ -129,7 +129,8 @@ public: * Use the given texture name, and default GL parameters. */ explicit CTextureProperties(const VfsPath& path) : - m_Path(path), m_Filter(GL_LINEAR_MIPMAP_LINEAR), m_Wrap(GL_REPEAT), m_Aniso(1.0f) + m_Path(path), m_Filter(GL_LINEAR_MIPMAP_LINEAR), + m_WrapS(GL_REPEAT), m_WrapT(GL_REPEAT), m_Aniso(1.0f) { } @@ -141,7 +142,13 @@ public: /** * Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc). */ - void SetWrap(GLint wrap) { m_Wrap = wrap; } + void SetWrap(GLint wrap) { m_WrapS = wrap; m_WrapT = wrap; } + + /** + * Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc), + * separately for S and T. + */ + void SetWrap(GLint wrap_s, GLint wrap_t) { m_WrapS = wrap_s; m_WrapT = wrap_t; } /** * Set maximum anisotropy value. Must be >= 1.0. Should be a power of 2. @@ -168,7 +175,8 @@ public: private: VfsPath m_Path; GLint m_Filter; - GLint m_Wrap; + GLint m_WrapS; + GLint m_WrapT; float m_Aniso; }; diff --git a/source/lib/res/graphics/ogl_tex.cpp b/source/lib/res/graphics/ogl_tex.cpp index f35f24a8ff..b3ebd14666 100644 --- a/source/lib/res/graphics/ogl_tex.cpp +++ b/source/lib/res/graphics/ogl_tex.cpp @@ -275,10 +275,8 @@ struct OglTexState // mipmaps aren't called for and filter could be NEAREST anyway). GLint filter; // .. wrap mode - // note: to simplify things, we assume that apps will never want to - // set S/T modes independently. it that becomes necessary, - // it's easy to add. - GLint wrap; + GLint wrap_s; + GLint wrap_t; // .. anisotropy // note: ignored unless EXT_texture_filter_anisotropic is supported. GLfloat anisotropy; @@ -289,7 +287,8 @@ struct OglTexState static void state_set_to_defaults(OglTexState* ots) { ots->filter = default_filter; - ots->wrap = GL_REPEAT; + ots->wrap_s = GL_REPEAT; + ots->wrap_t = GL_REPEAT; ots->anisotropy = 1.0f; } @@ -305,13 +304,14 @@ static void state_latch(OglTexState* ots) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); // wrap - const GLint wrap = ots->wrap; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap); + const GLint wrap_s = ots->wrap_s; + const GLint wrap_t = ots->wrap_t; + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t); // .. only CLAMP and REPEAT are guaranteed to be available. // if we're using one of the others, we squelch the error that // may have resulted if this GL implementation is old. - if(wrap != GL_CLAMP && wrap != GL_REPEAT) + if((wrap_s != GL_CLAMP && wrap_s != GL_REPEAT) || (wrap_t != GL_CLAMP && wrap_t != GL_REPEAT)) ogl_SquelchError(GL_INVALID_ENUM); // anisotropy @@ -481,16 +481,18 @@ static Status OglTex_validate(const OglTex* ot) // texture state if(!filter_valid(ot->state.filter)) WARN_RETURN(ERR::_14); - if(!wrap_valid(ot->state.wrap)) + if(!wrap_valid(ot->state.wrap_s)) WARN_RETURN(ERR::_15); + if(!wrap_valid(ot->state.wrap_t)) + WARN_RETURN(ERR::_16); // misc if(!q_flags_valid(ot->q_flags)) - WARN_RETURN(ERR::_16); - if(ot->tmu >= 128) // unexpected that there will ever be this many WARN_RETURN(ERR::_17); - if(ot->flags > OT_ALL_FLAGS) + if(ot->tmu >= 128) // unexpected that there will ever be this many WARN_RETURN(ERR::_18); + if(ot->flags > OT_ALL_FLAGS) + WARN_RETURN(ERR::_19); // .. note: don't check ot->fmt and ot->int_fmt - they aren't set // until during ogl_tex_upload. @@ -620,19 +622,22 @@ Status ogl_tex_set_filter(Handle ht, GLint filter) // override default wrap mode (GL_REPEAT) for this texture. // must be called before uploading (raises a warning if called afterwards). -// wrap is as defined by OpenGL and applies to both S and T coordinates -// (rationale: see OglTexState). -Status ogl_tex_set_wrap(Handle ht, GLint wrap) +// wrap is as defined by OpenGL. +Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t) { H_DEREF(ht, OglTex, ot); - if(!wrap_valid(wrap)) + if(!wrap_valid(wrap_s)) WARN_RETURN(ERR::INVALID_PARAM); - if(ot->state.wrap != wrap) + if(!wrap_valid(wrap_t)) + WARN_RETURN(ERR::INVALID_PARAM); + + if(ot->state.wrap_s != wrap_s || ot->state.wrap_t != wrap_t) { warn_if_uploaded(ht, ot); - ot->state.wrap = wrap; + ot->state.wrap_s = wrap_s; + ot->state.wrap_t = wrap_t; } return INFO::OK; } diff --git a/source/lib/res/graphics/ogl_tex.h b/source/lib/res/graphics/ogl_tex.h index 84dc8906b1..9259b6daa1 100644 --- a/source/lib/res/graphics/ogl_tex.h +++ b/source/lib/res/graphics/ogl_tex.h @@ -289,13 +289,13 @@ extern Status ogl_tex_set_filter(Handle ht, GLint filter); * Override default wrap mode (GL_REPEAT) for this texture. * * @param ht Texture handle -* @param wrap OpenGL wrap mode (for both S and T coordinates) -* (rationale: see {@link OglTexState}) +* @param wrap_s OpenGL wrap mode for S coordinates +* @param wrap_t OpenGL wrap mode for T coordinates * @return Status * * Must be called before uploading (raises a warning if called afterwards). */ -extern Status ogl_tex_set_wrap(Handle ht, GLint wrap); +extern Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t); /** * Override default maximum anisotropic filtering for this texture. diff --git a/source/maths/Noise.cpp b/source/maths/Noise.cpp index 7a54723b0e..2f83c58bf6 100644 --- a/source/maths/Noise.cpp +++ b/source/maths/Noise.cpp @@ -48,14 +48,14 @@ namespace Noise2D::Noise2D(int f) { freq = f; - grads = new CVector2D_Maths*[freq]; + grads = new CVector2D*[freq]; for(int i=0; i /////////////////////////////////////////////////////////////////////////////// -// CVector2D_Maths: -class CVector2D_Maths +// CVector2D: +class CVector2D { public: - CVector2D_Maths() {} - CVector2D_Maths(float x,float y) { X=x; Y=y; } - CVector2D_Maths(const CVector2D_Maths& p) { X=p.X; Y=p.Y; } + CVector2D() {} + CVector2D(float x, float y) : X(x), Y(y) {} - operator float*() { + operator float*() + { return &X; } - operator const float*() const { + operator const float*() const + { return &X; } - CVector2D_Maths operator-() const { - return CVector2D_Maths(-X, -Y); + CVector2D operator-() const + { + return CVector2D(-X, -Y); } - CVector2D_Maths operator+(const CVector2D_Maths& t) const { - return CVector2D_Maths(X+t.X, Y+t.Y); + CVector2D operator+(const CVector2D& t) const + { + return CVector2D(X + t.X, Y + t.Y); } - CVector2D_Maths operator-(const CVector2D_Maths& t) const { - return CVector2D_Maths(X-t.X, Y-t.Y); + CVector2D operator-(const CVector2D& t) const + { + return CVector2D(X - t.X, Y - t.Y); } - CVector2D_Maths operator*(float f) const { - return CVector2D_Maths(X*f, Y*f); + CVector2D operator*(float f) const + { + return CVector2D(X * f, Y * f); } - CVector2D_Maths operator/(float f) const { - float inv=1.0f/f; - return CVector2D_Maths(X*inv, Y*inv); + CVector2D operator/(float f) const + { + float inv = 1.0f / f; + return CVector2D(X * inv, Y * inv); } - CVector2D_Maths& operator+=(const CVector2D_Maths& t) { - X+=t.X; Y+=t.Y; - return *this; + CVector2D& operator+=(const CVector2D& t) + { + X += t.X; + Y += t.Y; + return *this; } - CVector2D_Maths& operator-=(const CVector2D_Maths& t) { - X-=t.X; Y-=t.Y; - return *this; + CVector2D& operator-=(const CVector2D& t) + { + X -= t.X; + Y -= t.Y; + return *this; } - CVector2D_Maths& operator*=(float f) { - X*=f; Y*=f; - return *this; + CVector2D& operator*=(float f) + { + X *= f; + Y *= f; + return *this; } - CVector2D_Maths& operator/=(float f) { - float invf=1.0f/f; - X*=invf; Y*=invf; - return *this; + CVector2D& operator/=(float f) + { + float invf = 1.0f / f; + X *= invf; + Y *= invf; + return *this; } - float Dot(const CVector2D_Maths& a) const { - return X*a.X + Y*a.Y; + float Dot(const CVector2D& a) const + { + return X * a.X + Y * a.Y; } - float LengthSquared() const { - return Dot(*this); + float LengthSquared() const + { + return Dot(*this); } - float Length() const { - return (float) sqrt(LengthSquared()); + float Length() const + { + return (float)sqrt(LengthSquared()); } - void Normalize() { - float mag=Length(); - X/=mag; Y/=mag; + void Normalize() + { + float mag = Length(); + X /= mag; + Y /= mag; + } + + CVector2D Normalized() + { + float mag = Length(); + return CVector2D(X / mag, Y / mag); } public: diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp index 1ca83299d9..d5eb0ccd4f 100644 --- a/source/renderer/OverlayRenderer.cpp +++ b/source/renderer/OverlayRenderer.cpp @@ -19,17 +19,59 @@ #include "OverlayRenderer.h" +#include "graphics/LOSTexture.h" #include "graphics/Overlay.h" +#include "graphics/ShaderManager.h" +#include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/ogl.h" +#include "ps/Game.h" +#include "ps/Profile.h" #include "renderer/Renderer.h" +#include "renderer/VertexBuffer.h" +#include "renderer/VertexBufferManager.h" +#include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpWaterManager.h" struct OverlayRendererInternals { std::vector lines; + std::vector texlines; std::vector sprites; }; +class CTexturedLineRData : public CRenderData +{ +public: + CTexturedLineRData(SOverlayTexturedLine* line) : + m_Line(line), m_VB(NULL), m_VBIndices(NULL) + { + } + + ~CTexturedLineRData() + { + if (m_VB) + g_VBMan.Release(m_VB); + if (m_VBIndices) + g_VBMan.Release(m_VBIndices); + } + + struct SVertex + { + SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; } + CVector3D m_Position; + GLshort m_UVs[2]; + }; + cassert(sizeof(SVertex) == 16); + + void Update(); + + SOverlayTexturedLine* m_Line; + + CVertexBuffer::VBChunk* m_VB; + CVertexBuffer::VBChunk* m_VBIndices; +}; + OverlayRenderer::OverlayRenderer() { m = new OverlayRendererInternals(); @@ -40,9 +82,22 @@ OverlayRenderer::~OverlayRenderer() delete m; } -void OverlayRenderer::Submit(SOverlayLine* overlay) +void OverlayRenderer::Submit(SOverlayLine* line) { - m->lines.push_back(overlay); + ENSURE(line->m_Coords.size() % 3 == 0); + + m->lines.push_back(line); +} + +void OverlayRenderer::Submit(SOverlayTexturedLine* line) +{ + // Simplify the rest of the code by guaranteeing non-empty lines + if (line->m_Coords.empty()) + return; + + ENSURE(line->m_Coords.size() % 2 == 0); + + m->texlines.push_back(line); } void OverlayRenderer::Submit(SOverlaySprite* overlay) @@ -53,6 +108,7 @@ void OverlayRenderer::Submit(SOverlaySprite* overlay) void OverlayRenderer::EndFrame() { m->lines.clear(); + m->texlines.clear(); m->sprites.clear(); // this should leave the capacity unchanged, which is okay since it // won't be very large or very variable @@ -60,12 +116,30 @@ void OverlayRenderer::EndFrame() void OverlayRenderer::PrepareForRendering() { + PROFILE("prepare overlays"); + // This is where we should do something like sort the overlays by // colour/sprite/etc for more efficient rendering + + for (size_t i = 0; i < m->texlines.size(); ++i) + { + SOverlayTexturedLine* line = m->texlines[i]; + if (!line->m_RenderData) + { + line->m_RenderData = shared_ptr(new CTexturedLineRData(line)); + static_cast(line->m_RenderData.get())->Update(); + // We assume the overlay line will get replaced by the caller + // if terrain changes, so we don't need to detect that here and + // call Update again. Also we assume the caller won't change + // any of the parameters after first submitting the line. + } + } } -void OverlayRenderer::RenderOverlays() +void OverlayRenderer::RenderOverlaysBeforeWater() { + PROFILE("render overlays (before water)"); + glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); @@ -90,8 +164,76 @@ void OverlayRenderer::RenderOverlays() glDisable(GL_BLEND); } +void OverlayRenderer::RenderOverlaysAfterWater() +{ + PROFILE("render overlays (after water)"); + + // Only supported in shader modes + // (TODO: should support in non-shader too) + if (g_Renderer.GetRenderPath() != CRenderer::RP_SHADER) + return; + + if (!m->texlines.empty()) + { + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(0); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + CShaderManager& shaderManager = g_Renderer.GetShaderManager(); + CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram("overlayline", std::map())); + + shaderTexLine->Bind(); + + CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); + shaderTexLine->BindTexture("losTex", los.GetTexture()); + shaderTexLine->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); + + for (size_t i = 0; i < m->texlines.size(); ++i) + { + SOverlayTexturedLine* line = m->texlines[i]; + if (!line->m_RenderData) + continue; + + shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle()); + shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle()); + shaderTexLine->Uniform("objectColor", line->m_Color); + + CTexturedLineRData* rdata = static_cast(line->m_RenderData.get()); + + GLsizei stride = sizeof(CTexturedLineRData::SVertex); + CTexturedLineRData::SVertex* base = reinterpret_cast(rdata->m_VB->m_Owner->Bind()); + + glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]); + glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]); + + u8* indexBase = rdata->m_VBIndices->m_Owner->Bind(); + glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); + + g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2; + } + + shaderTexLine->Unbind(); + + // TODO: the shader should probably be responsible for unbinding its textures + g_Renderer.BindTexture(1, 0); + g_Renderer.BindTexture(0, 0); + + CVertexBuffer::Unbind(); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glDepthMask(1); + glDisable(GL_BLEND); + } +} + void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) { + PROFILE("render overlays (fg)"); + glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); @@ -132,3 +274,126 @@ void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } + +void CTexturedLineRData::Update() +{ + if (m_VB) + { + g_VBMan.Release(m_VB); + m_VB = NULL; + } + + if (m_VBIndices) + { + g_VBMan.Release(m_VBIndices); + m_VBIndices = NULL; + } + + CmpPtr cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + + std::vector vertices; + std::vector indices; + + short v = 0; + + size_t n = m_Line->m_Coords.size() / 2; + ENSURE(n >= 1); + + CTerrain* terrain = m_Line->m_Terrain; + + // TODO: this assumes paths are closed loops; probably should extend this to + // handle non-closed paths too + + // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1. + // To avoid slightly expensive terrain computations we cycle these around and + // recompute p2 at the end of each iteration. + CVector3D p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]); + CVector3D p1 = CVector3D(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]); + CVector3D p2 = CVector3D(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]); + bool p1floating = false; + bool p2floating = false; + + // Compute terrain heights, clamped to the water height (and remember whether + // each point was floating on water, for normal computation later) + + // TODO: if we ever support more than one water level per map, recompute this per point + float w = cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z); + + p0.Y = terrain->GetExactGroundLevel(p0.X, p0.Z); + if (p0.Y < w) + p0.Y = w; + + p1.Y = terrain->GetExactGroundLevel(p1.X, p1.Z); + if (p1.Y < w) + { + p1.Y = w; + p1floating = true; + } + + p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); + if (p2.Y < w) + { + p2.Y = w; + p2floating = true; + } + + for (size_t i = 0; i < n; ++i) + { + // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1) + // perpendicular to terrain normal + + // Normal is vertical if on water, else computed from terrain + CVector3D norm; + if (p1floating) + norm = CVector3D(0, 1, 0); + else + norm = m_Line->m_Terrain->CalcExactNormal(p1.X, p1.Z); + + CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm); + + // Adjust bisector length to match the line thickness, along the line's width + float l = b.Dot((p2 - p1).Normalized().Cross(norm)); + if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero + b *= m_Line->m_Thickness / l; + + // Raise off the terrain a little bit + const float raised = 0.2f; + + vertices.push_back(SVertex(p1 + b + norm*raised, 0, v)); + indices.push_back(vertices.size() - 1); + + vertices.push_back(SVertex(p1 - b + norm*raised, 1, v)); + indices.push_back(vertices.size() - 1); + + // Alternate V coordinate for debugging + v = 1 - v; + + // Cycle the p's and compute the new p2 + p0 = p1; + p1 = p2; + p1floating = p2floating; + p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]); + p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); + if (p2.Y < w) + { + p2.Y = w; + p2floating = true; + } + else + p2floating = false; + } + + // Close the path + indices.push_back(0); + indices.push_back(1); + + m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER); + m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); + + // Update the indices to include the base offset of the vertex data + for (size_t k = 0; k < indices.size(); ++k) + indices[k] += m_VB->m_Index; + + m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER); + m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]); +} diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h index 67e1559d6c..a48ae0ab07 100644 --- a/source/renderer/OverlayRenderer.h +++ b/source/renderer/OverlayRenderer.h @@ -19,6 +19,7 @@ #define INCLUDED_OVERLAYRENDERER struct SOverlayLine; +struct SOverlayTexturedLine; struct SOverlaySprite; class CCamera; @@ -39,6 +40,11 @@ public: */ void Submit(SOverlayLine* overlay); + /** + * Add a textured line overlay for rendering in this frame. + */ + void Submit(SOverlayTexturedLine* overlay); + /** * Add a sprite overlay for rendering in this frame. */ @@ -58,9 +64,17 @@ public: /** * Render all the submitted overlays that are embedded in the world - * (i.e. rendered behind other objects, underwater, etc). + * (i.e. rendered behind other objects in the normal 3D way) + * and should be drawn before water (i.e. may be visible under the water) */ - void RenderOverlays(); + void RenderOverlaysBeforeWater(); + + /** + * Render all the submitted overlays that are embedded in the world + * (i.e. rendered behind other objects in the normal 3D way) + * and should be drawn after water (i.e. may be visible on top of the water) + */ + void RenderOverlaysAfterWater(); /** * Render all the submitted overlays that should appear on top of everything diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index c59bd35182..b8308bc18f 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -106,6 +106,7 @@ private: Row_TerrainTris, Row_WaterTris, Row_ModelTris, + Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, @@ -175,6 +176,12 @@ CStr CRendererStatsTable::GetCellText(size_t row, size_t col) sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; + case Row_OverlayTris: + if (col == 0) + return "# overlay tris"; + sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); + return buf; + case Row_BlendSplats: if (col == 0) return "# blend splats"; @@ -1573,10 +1580,8 @@ void CRenderer::RenderSubmissions() TerrainOverlay::RenderOverlays(); ogl_WarnIfError(); - // render other debug-related overlays before water (so they can be displayed when underwater) - PROFILE_START("render overlays"); - m->overlayRenderer.RenderOverlays(); - PROFILE_END("render overlays"); + // render other debug-related overlays before water (so they can be seen when underwater) + m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(); @@ -1603,6 +1608,10 @@ void CRenderer::RenderSubmissions() ogl_WarnIfError(); } + // render some other overlays after water (so they can be displayed on top of water) + m->overlayRenderer.RenderOverlaysAfterWater(); + ogl_WarnIfError(); + // particles are transparent so render after water RenderParticles(); ogl_WarnIfError(); @@ -1622,9 +1631,7 @@ void CRenderer::RenderSubmissions() } // render overlays that should appear on top of all other objects - PROFILE_START("render fg overlays"); m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); - PROFILE_END("render fg overlays"); ogl_WarnIfError(); } @@ -1722,6 +1729,11 @@ void CRenderer::Submit(SOverlayLine* overlay) m->overlayRenderer.Submit(overlay); } +void CRenderer::Submit(SOverlayTexturedLine* overlay) +{ + m->overlayRenderer.Submit(overlay); +} + void CRenderer::Submit(SOverlaySprite* overlay) { m->overlayRenderer.Submit(overlay); @@ -1932,7 +1944,7 @@ int CRenderer::LoadAlphaMaps() (void)tex_wrap(total_w, total_h, 8, TEX_GREY, data, 0, &t); m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); - (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE); + (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); int ret = ogl_tex_upload(m_hCompositeAlphaMap, 0, 0, GL_INTENSITY); return ret; diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index a429564ba2..30abe5b5f7 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -116,6 +116,8 @@ public: size_t m_WaterTris; // number of (non-transparent) model triangles drawn size_t m_ModelTris; + // number of overlay triangles drawn + size_t m_OverlayTris; // number of splat passes for alphamapping size_t m_BlendSplats; // number of particles @@ -334,6 +336,7 @@ protected: //BEGIN: Implementation of SceneCollector void Submit(CPatch* patch); void Submit(SOverlayLine* overlay); + void Submit(SOverlayTexturedLine* overlay); void Submit(SOverlaySprite* overlay); void Submit(CModelDecal* decal); void Submit(CParticleEmitter* emitter); diff --git a/source/renderer/Scene.h b/source/renderer/Scene.h index e19157d88f..2018135d83 100644 --- a/source/renderer/Scene.h +++ b/source/renderer/Scene.h @@ -37,6 +37,7 @@ class CPatch; class CLOSTexture; class CTerritoryTexture; struct SOverlayLine; +struct SOverlayTexturedLine; struct SOverlaySprite; class SceneCollector; @@ -92,6 +93,11 @@ public: */ virtual void Submit(SOverlayLine* overlay) = 0; + /** + * Submit a textured line overlay. + */ + virtual void Submit(SOverlayTexturedLine* overlay) = 0; + /** * Submit a sprite overlay. */ diff --git a/source/renderer/VertexBuffer.cpp b/source/renderer/VertexBuffer.cpp index b0e255416f..204d55baa8 100644 --- a/source/renderer/VertexBuffer.cpp +++ b/source/renderer/VertexBuffer.cpp @@ -28,8 +28,6 @@ #include "VertexBufferManager.h" #include "ps/CLogger.h" -/////////////////////////////////////////////////////////////////////////////// -// CVertexBuffer constructor CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) : m_VertexSize(vertexSize), m_Handle(0), m_SysMem(0), m_Usage(usage), m_Target(target) { @@ -66,22 +64,27 @@ CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) m_FreeList.push_front(chunk); } -/////////////////////////////////////////////////////////////////////////////// -// CVertexBuffer destructor CVertexBuffer::~CVertexBuffer() { if (m_Handle) pglDeleteBuffersARB(1, &m_Handle); - if (m_SysMem) - delete[] m_SysMem; + delete[] m_SysMem; - // janwas 2004-06-14: release freelist typedef std::list::iterator Iter; for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) delete *iter; } + +bool CVertexBuffer::CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target) +{ + if (usage != m_Usage || target != m_Target || vertexSize != m_VertexSize) + return false; + + return true; +} + /////////////////////////////////////////////////////////////////////////////// // Allocate: try to allocate a buffer of given number of vertices (each of // given size), with the given type, and using the given texture - return null @@ -89,7 +92,7 @@ CVertexBuffer::~CVertexBuffer() CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target) { // check this is the right kind of buffer - if (usage != m_Usage || target != m_Target || vertexSize != m_VertexSize) + if (!CompatibleVertexType(vertexSize, usage, target)) return 0; // quick check there's enough vertices spare to allocate @@ -138,11 +141,34 @@ CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVer // Release: return given chunk to this buffer void CVertexBuffer::Release(VBChunk* chunk) { - // add to free list - // TODO, RC - need to merge available chunks where possible to avoid - // excessive fragmentation of vertex buffer space - m_FreeList.push_front(chunk); + // Update total free count before potentially modifying this chunk's count m_FreeVertices += chunk->m_Count; + + typedef std::list::iterator Iter; + + // Coalesce with any free-list items that are adjacent to this chunk; + // merge the found chunk with the new one, and remove the old one + // from the list, and repeat until no more are found + bool coalesced; + do + { + coalesced = false; + for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) + { + if ((*iter)->m_Index == chunk->m_Index + chunk->m_Count + || (*iter)->m_Index + (*iter)->m_Count == chunk->m_Index) + { + chunk->m_Index = std::min(chunk->m_Index, (*iter)->m_Index); + chunk->m_Count += (*iter)->m_Count; + m_FreeList.erase(iter); + coalesced = true; + break; + } + } + } + while (coalesced); + + m_FreeList.push_front(chunk); } /////////////////////////////////////////////////////////////////////////////// @@ -205,3 +231,17 @@ size_t CVertexBuffer::GetBytesAllocated() const { return (m_MaxVertices - m_FreeVertices) * m_VertexSize; } + +void CVertexBuffer::DumpStatus() +{ + debug_printf(L"freeverts = %d\n", m_FreeVertices); + + size_t maxSize = 0; + typedef std::list::iterator Iter; + for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) + { + debug_printf(L"free chunk %p: size=%d\n", *iter, (*iter)->m_Count); + maxSize = std::max((*iter)->m_Count, maxSize); + } + debug_printf(L"max size = %d\n", maxSize); +} diff --git a/source/renderer/VertexBuffer.h b/source/renderer/VertexBuffer.h index b884129bee..809ac65d4f 100644 --- a/source/renderer/VertexBuffer.h +++ b/source/renderer/VertexBuffer.h @@ -45,6 +45,13 @@ public: size_t m_Index; // number of vertices used by chunk size_t m_Count; + + private: + // Only CVertexBuffer can construct/delete these + // (Other people should use g_VBMan.Allocate, g_VBMan.Release) + friend class CVertexBuffer; + VBChunk() {} + ~VBChunk() {} }; public: @@ -70,6 +77,10 @@ public: size_t GetBytesReserved() const; size_t GetBytesAllocated() const; + bool CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target); + + void DumpStatus(); + protected: friend class CVertexBufferManager; // allow allocate only via CVertexBufferManager diff --git a/source/renderer/VertexBufferManager.cpp b/source/renderer/VertexBufferManager.cpp index 3279dd924c..d9ada2f5e8 100644 --- a/source/renderer/VertexBufferManager.cpp +++ b/source/renderer/VertexBufferManager.cpp @@ -26,14 +26,10 @@ #include "lib/ogl.h" #include "ps/CLogger.h" +#define DUMP_VB_STATS 0 // for debugging + CVertexBufferManager g_VBMan; -// janwas 2004-06-14: added dtor - -CVertexBufferManager::~CVertexBufferManager() -{ -} - /////////////////////////////////////////////////////////////////////////////// // Explicit shutdown of the vertex buffer subsystem. // This avoids the ordering issues that arise when using destructors of @@ -61,9 +57,22 @@ CVertexBuffer::VBChunk* CVertexBufferManager::Allocate(size_t vertexSize, size_t // TODO, RC - run some sanity checks on allocation request + typedef std::list::iterator Iter; + +#if DUMP_VB_STATS + debug_printf(L"\n============================\n# allocate vsize=%d nverts=%d\n\n", vertexSize, numVertices); + for (Iter iter = m_Buffers.begin(); iter != m_Buffers.end(); ++iter) { + CVertexBuffer* buffer = *iter; + if (buffer->CompatibleVertexType(vertexSize, usage, target)) + { + debug_printf(L"%p\n", buffer); + buffer->DumpStatus(); + } + } +#endif + // iterate through all existing buffers testing for one that'll // satisfy the allocation - typedef std::list::iterator Iter; for (Iter iter = m_Buffers.begin(); iter != m_Buffers.end(); ++iter) { CVertexBuffer* buffer = *iter; result = buffer->Allocate(vertexSize, numVertices, usage, target); @@ -89,6 +98,9 @@ CVertexBuffer::VBChunk* CVertexBufferManager::Allocate(size_t vertexSize, size_t void CVertexBufferManager::Release(CVertexBuffer::VBChunk* chunk) { ENSURE(chunk); +#if DUMP_VB_STATS + debug_printf(L"\n============================\n# release %p nverts=%d\n\n", chunk, chunk->m_Count); +#endif chunk->m_Owner->Release(chunk); } diff --git a/source/renderer/VertexBufferManager.h b/source/renderer/VertexBufferManager.h index 2cff433933..2644f4fc90 100644 --- a/source/renderer/VertexBufferManager.h +++ b/source/renderer/VertexBufferManager.h @@ -30,9 +30,6 @@ class CVertexBufferManager { public: - CVertexBufferManager() {} - ~CVertexBufferManager(); - // Explicit shutdown of the vertex buffer subsystem void Shutdown(); diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 18b0f92187..b872ed82d2 100644 --- a/source/simulation2/components/CCmpTerritoryManager.cpp +++ b/source/simulation2/components/CCmpTerritoryManager.cpp @@ -20,15 +20,22 @@ #include "simulation2/system/Component.h" #include "ICmpTerritoryManager.h" +#include "graphics/Overlay.h" #include "graphics/Terrain.h" +#include "graphics/TextureManager.h" #include "maths/MathUtil.h" +#include "maths/Vector2D.h" #include "ps/Overlay.h" +#include "renderer/Renderer.h" +#include "renderer/Scene.h" #include "renderer/TerrainOverlay.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPathfinder.h" +#include "simulation2/components/ICmpPlayer.h" +#include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpSettlement.h" #include "simulation2/components/ICmpTerrain.h" @@ -36,6 +43,7 @@ #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Grid.h" #include "simulation2/helpers/PriorityQueue.h" +#include "simulation2/helpers/Render.h" class CCmpTerritoryManager; @@ -58,6 +66,7 @@ public: componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged); componentManager.SubscribeGloballyToMessageType(MT_PositionChanged); componentManager.SubscribeToMessageType(MT_TerrainChanged); + componentManager.SubscribeToMessageType(MT_RenderSubmit); } DEFAULT_COMPONENT_ALLOCATOR(TerritoryManager) @@ -67,16 +76,30 @@ public: return ""; } + u8 m_ImpassableCost; + float m_BorderThickness; + float m_BorderSeparation; + Grid* m_Territories; TerritoryOverlay* m_DebugOverlay; + std::vector m_BoundaryLines; + bool m_BoundaryLinesDirty; virtual void Init(const CParamNode& UNUSED(paramNode)) { m_Territories = NULL; m_DebugOverlay = NULL; // m_DebugOverlay = new TerritoryOverlay(*this); + m_BoundaryLinesDirty = true; m_DirtyID = 1; + + CParamNode externalParamNode; + CParamNode::LoadXML(externalParamNode, L"simulation/data/territorymanager.xml"); + + m_ImpassableCost = externalParamNode.GetChild("TerritoryManager").GetChild("ImpassableCost").ToInt(); + m_BorderThickness = externalParamNode.GetChild("TerritoryManager").GetChild("BorderThickness").ToFixed().ToFloat(); + m_BorderSeparation = externalParamNode.GetChild("TerritoryManager").GetChild("BorderSeparation").ToFixed().ToFloat(); } virtual void Deinit() @@ -116,6 +139,12 @@ public: MakeDirty(); break; } + case MT_RenderSubmit: + { + const CMessageRenderSubmit& msgData = static_cast (msg); + RenderSubmit(msgData.collector); + break; + } } } @@ -148,6 +177,7 @@ public: { SAFE_DELETE(m_Territories); ++m_DirtyID; + m_BoundaryLinesDirty = true; } virtual bool NeedUpdate(size_t* dirtyID) @@ -169,6 +199,18 @@ public: * or 1+c if the influence have cost c (assumed between 0 and 254). */ void RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid& grid); + + struct TerritoryBoundary + { + player_id_t owner; + std::vector points; + }; + + std::vector ComputeBoundaries(); + + void UpdateBoundaryLines(); + + void RenderSubmit(SceneCollector& collector); }; REGISTER_COMPONENT_TYPE(TerritoryManager) @@ -252,15 +294,19 @@ void CCmpTerritoryManager::CalculateTerritories() Grid influenceGrid(tilesW, tilesH); CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); - ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass("default"); + ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); + ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default"); const Grid& passGrid = cmpPathfinder->GetPassabilityGrid(); for (u32 j = 0; j < tilesH; ++j) { for (u32 i = 0; i < tilesW; ++i) { + u8 g = passGrid.get(i, j); u8 cost; - if (passGrid.get(i, j) & passClass) - cost = 4; // TODO: should come from some XML file + if (g & passClassUnrestricted) + cost = 255; // off the world; use maximum cost + else if (g & passClassDefault) + cost = m_ImpassableCost; else cost = 1; influenceGrid.set(i, j, cost); @@ -425,6 +471,219 @@ void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& } } +std::vector CCmpTerritoryManager::ComputeBoundaries() +{ + PROFILE("ComputeBoundaries"); + + std::vector boundaries; + + CalculateTerritories(); + + // Copy the territories grid so we can mess with it + Grid grid (*m_Territories); + + // Some constants for the border walk + CVector2D edgeOffsets[] = { + CVector2D(0.5f, 0.0f), + CVector2D(1.0f, 0.5f), + CVector2D(0.5f, 1.0f), + CVector2D(0.0f, 0.5f) + }; + + // Try to find an assigned tile + for (int j = 0; j < grid.m_H; ++j) + { + for (int i = 0; i < grid.m_W; ++i) + { + u8 owner = grid.get(i, j); + if (owner) + { + // Found the first tile (which must be the lowest j value of any non-zero tile); + // start at the bottom edge of it and chase anticlockwise around the border until + // we reach the starting point again + + boundaries.push_back(TerritoryBoundary()); + boundaries.back().owner = owner; + std::vector& points = boundaries.back().points; + + int dir = 0; // 0 == bottom edge of tile, 1 == right, 2 == top, 3 == left + + int cdir = dir; + int ci = i, cj = j; + + while (true) + { + points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * CELL_SIZE); + + // Given that we're on an edge on a continuous boundary and aiming anticlockwise, + // we can either carry on straight or turn left or turn right, so examine each + // of the three possible cases (depending on initial direction): + switch (cdir) + { + case 0: + if (ci < grid.m_W-1 && cj > 0 && grid.get(ci+1, cj-1) == owner) + { + ++ci; + --cj; + cdir = 3; + } + else if (ci < grid.m_W-1 && grid.get(ci+1, cj) == owner) + ++ci; + else + cdir = 1; + break; + case 1: + if (ci < grid.m_W-1 && cj < grid.m_H-1 && grid.get(ci+1, cj+1) == owner) + { + ++ci; + ++cj; + cdir = 0; + } + else if (cj < grid.m_H-1 && grid.get(ci, cj+1) == owner) + ++cj; + else + cdir = 2; + break; + case 2: + if (ci > 0 && cj < grid.m_H-1 && grid.get(ci-1, cj+1) == owner) + { + --ci; + ++cj; + cdir = 1; + } + else if (ci > 0 && grid.get(ci-1, cj) == owner) + --ci; + else + cdir = 3; + break; + case 3: + if (ci > 0 && cj > 0 && grid.get(ci-1, cj-1) == owner) + { + --ci; + --cj; + cdir = 2; + } + else if (cj > 0 && grid.get(ci, cj-1) == owner) + --cj; + else + cdir = 0; + break; + } + + // Stop when we've reached the starting point again + if (ci == i && cj == j && cdir == dir) + break; + } + + // Zero out this whole territory with a simple flood fill, so we don't + // process it a second time + std::vector > tileStack; + +#define ZERO_AND_PUSH(i, j) STMT(grid.set(i, j, 0); tileStack.push_back(std::make_pair(i, j)); ) + + ZERO_AND_PUSH(i, j); + while (!tileStack.empty()) + { + int ti = tileStack.back().first; + int tj = tileStack.back().second; + tileStack.pop_back(); + + if (ti > 0 && grid.get(ti-1, tj) == owner) + ZERO_AND_PUSH(ti-1, tj); + if (ti < grid.m_W-1 && grid.get(ti+1, tj) == owner) + ZERO_AND_PUSH(ti+1, tj); + if (tj > 0 && grid.get(ti, tj-1) == owner) + ZERO_AND_PUSH(ti, tj-1); + if (tj < grid.m_H-1 && grid.get(ti, tj+1) == owner) + ZERO_AND_PUSH(ti, tj+1); + + if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner) + ZERO_AND_PUSH(ti-1, tj-1); + if (ti > 0 && tj < grid.m_H-1 && grid.get(ti-1, tj+1) == owner) + ZERO_AND_PUSH(ti-1, tj+1); + if (ti < grid.m_W-1 && tj > 0 && grid.get(ti+1, tj-1) == owner) + ZERO_AND_PUSH(ti+1, tj-1); + if (ti < grid.m_W-1 && tj < grid.m_H-1 && grid.get(ti+1, tj+1) == owner) + ZERO_AND_PUSH(ti+1, tj+1); + } + +#undef ZERO_AND_PUSH + } + } + } + + return boundaries; +} + +void CCmpTerritoryManager::UpdateBoundaryLines() +{ + PROFILE("update boundary lines"); + + m_BoundaryLines.clear(); + + std::vector boundaries = ComputeBoundaries(); + + CTextureProperties texturePropsBase("art/textures/misc/territory_border.png"); + texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsBase.SetMaxAnisotropy(2.f); + CTexturePtr textureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); + + CTextureProperties texturePropsMask("art/textures/misc/territory_border_mask.png"); + texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsMask.SetMaxAnisotropy(2.f); + CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); + + CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); + if (cmpTerrain.null()) + return; + CTerrain* terrain = cmpTerrain->GetCTerrain(); + + CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); + if (cmpPlayerManager.null()) + return; + + for (size_t i = 0; i < boundaries.size(); ++i) + { + if (boundaries[i].points.empty()) + continue; + + CColor color(1, 0, 1, 1); + CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(boundaries[i].owner)); + if (!cmpPlayer.null()) + color = cmpPlayer->GetColour(); + + m_BoundaryLines.push_back(SOverlayTexturedLine()); + m_BoundaryLines.back().m_Terrain = terrain; + m_BoundaryLines.back().m_TextureBase = textureBase; + m_BoundaryLines.back().m_TextureMask = textureMask; + m_BoundaryLines.back().m_Color = color; + m_BoundaryLines.back().m_Thickness = m_BorderThickness; + + SimRender::SmoothPointsAverage(boundaries[i].points, true); + + SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation); + + std::vector& points = m_BoundaryLines.back().m_Coords; + for (size_t j = 0; j < boundaries[i].points.size(); ++j) + { + points.push_back(boundaries[i].points[j].X); + points.push_back(boundaries[i].points[j].Y); + } + } +} + +void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) +{ + if (m_BoundaryLinesDirty) + { + UpdateBoundaryLines(); + m_BoundaryLinesDirty = false; + } + + for (size_t i = 0; i < m_BoundaryLines.size(); ++i) + collector.Submit(&m_BoundaryLines[i]); +} + void TerritoryOverlay::StartRender() { diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp index 7af59b4622..06221829ef 100644 --- a/source/simulation2/helpers/Render.cpp +++ b/source/simulation2/helpers/Render.cpp @@ -25,12 +25,14 @@ #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "maths/MathUtil.h" +#include "maths/Vector2D.h" +#include "ps/Profile.h" -static const float RENDER_HEIGHT_DELTA = 0.25f; // distance above terrain - -void SimRender::ConstructLineOnGround(const CSimContext& context, std::vector xz, - SOverlayLine& overlay, bool floating) +void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector& xz, + SOverlayLine& overlay, bool floating, float heightOffset) { + PROFILE("ConstructLineOnGround"); + overlay.m_Coords.clear(); CmpPtr cmpTerrain(context, SYSTEM_ENTITY); @@ -54,7 +56,7 @@ void SimRender::ConstructLineOnGround(const CSimContext& context, std::vectorGetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA; + float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); @@ -62,7 +64,7 @@ void SimRender::ConstructLineOnGround(const CSimContext& context, std::vectorGetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA; + float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); @@ -113,7 +115,7 @@ static void SplitLine(std::vector >& coords, float x1, f } void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, - SOverlayLine& overlay, bool floating) + SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); @@ -150,9 +152,104 @@ void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, flo { float px = coords[i].first; float pz = coords[i].second; - float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + RENDER_HEIGHT_DELTA; + float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } } + +void SimRender::SmoothPointsAverage(std::vector& points, bool closed) +{ + PROFILE("SmoothPointsAverage"); + + size_t n = points.size(); + if (n < 2) + return; // avoid out-of-bounds array accesses, and leave the points unchanged + + std::vector newPoints; + newPoints.resize(points.size()); + + // Handle the end points appropriately + if (closed) + { + newPoints[0] = (points[n-1] + points[0] + points[1]) / 3.f; + newPoints[n-1] = (points[n-2] + points[n-1] + points[0]) / 3.f; + } + else + { + newPoints[0] = points[0]; + newPoints[n-1] = points[n-1]; + } + + // Average all the intermediate points + for (size_t i = 1; i < n-1; ++i) + newPoints[i] = (points[i-1] + points[i] + points[i+1]) / 3.f; + + points.swap(newPoints); +} + +static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a2, CVector2D a3, float offset) +{ + // Compute position on spline + CVector2D p = a0*(t*t*t) + a1*(t*t) + a2*t + a3; + + // Compute unit-vector direction of spline + CVector2D dp = (a0*(3*t*t) + a1*(2*t) + a2).Normalized(); + + // Offset position perpendicularly + return p + CVector2D(dp.Y*-offset, dp.X*offset); +} + +void SimRender::InterpolatePointsRNS(std::vector& points, bool closed, float offset) +{ + PROFILE("InterpolatePointsRNS"); + + std::vector newPoints; + + // (This does some redundant computations for adjacent vertices, + // but it's fairly fast (<1ms typically) so we don't worry about it yet) + + // TODO: Instead of doing a fixed number of line segments between each + // control point, it should probably be somewhat adaptive to get a nicer + // curve with fewer points + + size_t n = points.size(); + if (n < 1) + return; // can't do anything unless we have two points + + size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths + + newPoints.reserve(imax*4); + + for (size_t i = 0; i < imax; ++i) + { + // Get the relevant points for this spline segment + CVector2D p0 = points[(i-1+n)%n]; + CVector2D p1 = points[i]; + CVector2D p2 = points[(i+1)%n]; + CVector2D p3 = points[(i+2)%n]; + + // Do the RNS computation (based on GPG4 "Nonuniform Splines") + float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1) + CVector2D s0 = (p1 - p0).Normalized(); // unit vector of spline segment (i-1)..(i) + CVector2D s1 = (p2 - p1).Normalized(); // unit vector of spline segment (i)..(i+1) + CVector2D s2 = (p3 - p2).Normalized(); // unit vector of spline segment (i+1)..(i+2) + CVector2D v1 = (s0 + s1).Normalized() * l1; // spline velocity at i + CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1 + + // Compute standard cubic spline parameters + CVector2D a0 = p1*2 + p2*-2 + v1 + v2; + CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; + CVector2D a2 = v1; + CVector2D a3 = p1; + + // Interpolate at various points + newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset)); + newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset)); + newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset)); + newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset)); + } + + points.swap(newPoints); +} diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index 06499b2bdf..0129b013a0 100644 --- a/source/simulation2/helpers/Render.h +++ b/source/simulation2/helpers/Render.h @@ -24,6 +24,7 @@ */ class CSimContext; +class CVector2D; struct SOverlayLine; namespace SimRender @@ -33,18 +34,42 @@ namespace SimRender * Updates @p overlay so that it represents the given line (a list of x, z coordinate pairs), * flattened on the terrain (or on the water if @p floating). */ -void ConstructLineOnGround(const CSimContext& context, std::vector xz, SOverlayLine& overlay, bool floating); +void ConstructLineOnGround(const CSimContext& context, const std::vector& xz, + SOverlayLine& overlay, + bool floating, float heightOffset = 0.25f); /** * Updates @p overlay so that it represents the given circle, flattened on the terrain. */ -void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, SOverlayLine& overlay, bool floating); +void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, + SOverlayLine& overlay, + bool floating, float heightOffset = 0.25f); /** * Updates @p overlay so that it represents the given square, flattened on the terrain. * @p x and @p z are position of center, @p w and @p h are size of rectangle, @p a is clockwise angle. */ -void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, SOverlayLine& overlay, bool floating); +void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, + SOverlayLine& overlay, + bool floating, float heightOffset = 0.25f); + +/** + * Updates @p points so each point is averaged with its neighbours, resulting in + * a somewhat smoother curve, assuming the points are roughly equally spaced. + * If @p closed then the points are treated as a closed path (the last is connected + * to the first). + */ +void SmoothPointsAverage(std::vector& points, bool closed); + +/** + * Updates @p points to include intermediate points interpolating between the original + * control points, using a rounded nonuniform spline. + * The points are also shifted by @p offset in a direction 90 degrees clockwise from + * the direction of the curve. + * If @p closed then the points are treated as a closed path (the last is connected + * to the first). + */ +void InterpolatePointsRNS(std::vector& points, bool closed, float offset); } // namespace