LCOV - code coverage report
Current view: top level - source/renderer - TexturedLineRData.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 5 207 2.4 %
Date: 2023-01-19 00:18:29 Functions: 3 7 42.9 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "TexturedLineRData.h"
      21             : 
      22             : #include "graphics/ShaderProgram.h"
      23             : #include "graphics/Terrain.h"
      24             : #include "maths/Frustum.h"
      25             : #include "maths/MathUtil.h"
      26             : #include "maths/Quaternion.h"
      27             : #include "ps/CStrInternStatic.h"
      28             : #include "renderer/OverlayRenderer.h"
      29             : #include "renderer/Renderer.h"
      30             : #include "simulation2/Simulation2.h"
      31             : #include "simulation2/system/SimContext.h"
      32             : #include "simulation2/components/ICmpWaterManager.h"
      33             : 
      34             : /* Note: this implementation uses g_VBMan directly rather than access it through the nicer VertexArray interface,
      35             :  * because it allows you to work with variable amounts of vertices and indices more easily. New code should prefer
      36             :  * to use VertexArray where possible, though. */
      37             : 
      38             : // static
      39           6 : Renderer::Backend::IVertexInputLayout* CTexturedLineRData::GetVertexInputLayout()
      40             : {
      41           6 :     const uint32_t stride = sizeof(CTexturedLineRData::SVertex);
      42           6 :     const std::array<Renderer::Backend::SVertexAttributeFormat, 3> attributes{{
      43             :         {Renderer::Backend::VertexAttributeStream::POSITION,
      44             :             Renderer::Backend::Format::R32G32B32_SFLOAT,
      45             :             offsetof(CTexturedLineRData::SVertex, m_Position), stride,
      46             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      47             :         {Renderer::Backend::VertexAttributeStream::UV0,
      48             :             Renderer::Backend::Format::R32G32_SFLOAT,
      49             :             offsetof(CTexturedLineRData::SVertex, m_UV), stride,
      50             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      51             :         {Renderer::Backend::VertexAttributeStream::UV1,
      52             :             Renderer::Backend::Format::R32G32_SFLOAT,
      53             :             offsetof(CTexturedLineRData::SVertex, m_UV), stride,
      54             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
      55             :     }};
      56           6 :     return g_Renderer.GetVertexInputLayout(attributes);
      57             : }
      58             : 
      59           0 : void CTexturedLineRData::Render(
      60             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
      61             :     Renderer::Backend::IVertexInputLayout* vertexInputLayout,
      62             :     const SOverlayTexturedLine& line, Renderer::Backend::IShaderProgram* shader)
      63             : {
      64           0 :     if (!m_VB || !m_VBIndices)
      65           0 :         return; // might have failed to allocate
      66             : 
      67             :     // -- render main line quad strip ----------------------
      68             : 
      69           0 :     line.m_TextureBase->UploadBackendTextureIfNeeded(deviceCommandContext);
      70           0 :     line.m_TextureMask->UploadBackendTextureIfNeeded(deviceCommandContext);
      71             : 
      72           0 :     ENSURE(!m_VB->m_Owner->GetBuffer()->IsDynamic());
      73           0 :     ENSURE(!m_VBIndices->m_Owner->GetBuffer()->IsDynamic());
      74             : 
      75           0 :     deviceCommandContext->SetTexture(
      76           0 :         shader->GetBindingSlot(str_baseTex), line.m_TextureBase->GetBackendTexture());
      77           0 :     deviceCommandContext->SetTexture(
      78           0 :         shader->GetBindingSlot(str_maskTex), line.m_TextureMask->GetBackendTexture());
      79           0 :     deviceCommandContext->SetUniform(
      80           0 :         shader->GetBindingSlot(str_objectColor), line.m_Color.AsFloatArray());
      81             : 
      82           0 :     deviceCommandContext->SetVertexInputLayout(vertexInputLayout);
      83             : 
      84           0 :     deviceCommandContext->SetVertexBuffer(0, m_VB->m_Owner->GetBuffer(), 0);
      85             : 
      86           0 :     deviceCommandContext->SetIndexBuffer(m_VBIndices->m_Owner->GetBuffer());
      87           0 :     deviceCommandContext->DrawIndexed(m_VBIndices->m_Index, m_VBIndices->m_Count, 0);
      88             : 
      89           0 :     g_Renderer.GetStats().m_DrawCalls++;
      90           0 :     g_Renderer.GetStats().m_OverlayTris += m_VBIndices->m_Count/3;
      91             : }
      92             : 
      93           0 : void CTexturedLineRData::Update(const SOverlayTexturedLine& line)
      94             : {
      95           0 :     m_VBIndices.Reset();
      96           0 :     m_VB.Reset();
      97             : 
      98           0 :     if (!line.m_SimContext)
      99             :     {
     100           0 :         debug_warn(L"[TexturedLineRData] No SimContext set for textured overlay line, cannot render (no terrain data)");
     101           0 :         return;
     102             :     }
     103             : 
     104           0 :     float v = 0.f;
     105           0 :     std::vector<SVertex> vertices;
     106           0 :     std::vector<u16> indices;
     107             : 
     108           0 :     const size_t n = line.m_Coords.size(); // number of line points
     109           0 :     bool closed = line.m_Closed;
     110             : 
     111           0 :     ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point)
     112             : 
     113             :     // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
     114             :     // To avoid slightly expensive terrain computations we cycle these around and
     115             :     // recompute p2 at the end of each iteration.
     116             : 
     117           0 :     CVector3D p0;
     118           0 :     CVector3D p1(line.m_Coords[0].X, 0, line.m_Coords[0].Y);
     119           0 :     CVector3D p2(line.m_Coords[1].X, 0, line.m_Coords[1].Y);
     120             : 
     121           0 :     if (closed)
     122             :         // grab the ending point so as to close the loop
     123           0 :         p0 = CVector3D(line.m_Coords[n - 1].X, 0, line.m_Coords[n - 1].Y);
     124             :     else
     125             :         // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
     126             :         // extends the p2 -> p1 direction, and use that point instead
     127           0 :         p0 = p1 + (p1 - p2);
     128             : 
     129           0 :     bool p1floating = false;
     130           0 :     bool p2floating = false;
     131             : 
     132             :     // Compute terrain heights, clamped to the water height (and remember whether
     133             :     // each point was floating on water, for normal computation later)
     134             : 
     135             :     // TODO: if we ever support more than one water level per map, recompute this per point
     136           0 :     CmpPtr<ICmpWaterManager> cmpWaterManager(*line.m_SimContext, SYSTEM_ENTITY);
     137           0 :     float w = cmpWaterManager ? cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z) : 0.f;
     138             : 
     139           0 :     const CTerrain& terrain = line.m_SimContext->GetTerrain();
     140             : 
     141           0 :     p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z);
     142           0 :     if (p0.Y < w)
     143           0 :         p0.Y = w;
     144             : 
     145           0 :     p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z);
     146           0 :     if (p1.Y < w)
     147             :     {
     148           0 :         p1.Y = w;
     149           0 :         p1floating = true;
     150             :     }
     151             : 
     152           0 :     p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
     153           0 :     if (p2.Y < w)
     154             :     {
     155           0 :         p2.Y = w;
     156           0 :         p2floating = true;
     157             :     }
     158             : 
     159           0 :     for (size_t i = 0; i < n; ++i)
     160             :     {
     161             :         // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1)
     162             :         // perpendicular to terrain normal
     163             : 
     164             :         // Normal is vertical if on water, else computed from terrain
     165           0 :         CVector3D norm;
     166           0 :         if (p1floating)
     167           0 :             norm = CVector3D(0, 1, 0);
     168             :         else
     169           0 :             norm = terrain.CalcExactNormal(p1.X, p1.Z);
     170             : 
     171           0 :         CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm);
     172             : 
     173             :         // Adjust bisector length to match the line thickness, along the line's width
     174           0 :         float l = b.Dot((p2 - p1).Normalized().Cross(norm));
     175           0 :         if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
     176           0 :             b *= line.m_Thickness / l;
     177             : 
     178             :         // Push vertices and indices for each quad in GL_TRIANGLES order. The two triangles of each quad are indexed using
     179             :         // the winding orders (BR, BL, TR) and (TR, BL, TL) (where BR is bottom-right of this iteration's quad, TR top-right etc).
     180           0 :         SVertex vertex1(p1 + b + norm * OverlayRenderer::OVERLAY_VOFFSET, CVector2D(0.f, v));
     181           0 :         SVertex vertex2(p1 - b + norm * OverlayRenderer::OVERLAY_VOFFSET, CVector2D(1.f, v));
     182           0 :         vertices.push_back(vertex1);
     183           0 :         vertices.push_back(vertex2);
     184             : 
     185           0 :         u16 vertexCount = static_cast<u16>(vertices.size());
     186           0 :         u16 index1 = vertexCount - 2; // index of vertex1 in this iteration (TR of this quad)
     187           0 :         u16 index2 = vertexCount - 1; // index of the vertex2 in this iteration (TL of this quad)
     188             : 
     189           0 :         if (i == 0)
     190             :         {
     191             :             // initial two vertices to continue building triangles from (n must be >= 2 for this to work)
     192           0 :             indices.push_back(index1);
     193           0 :             indices.push_back(index2);
     194             :         }
     195             :         else
     196             :         {
     197           0 :             u16 index1Prev = vertexCount - 4; // index of the vertex1 in the previous iteration (BR of this quad)
     198           0 :             u16 index2Prev = vertexCount - 3; // index of the vertex2 in the previous iteration (BL of this quad)
     199           0 :             ENSURE(index1Prev < vertexCount);
     200           0 :             ENSURE(index2Prev < vertexCount);
     201             :             // Add two corner points from last iteration and join with one of our own corners to create triangle 1
     202             :             // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied)
     203           0 :             if (i > 1)
     204             :             {
     205           0 :                 indices.push_back(index1Prev);
     206           0 :                 indices.push_back(index2Prev);
     207             :             }
     208           0 :             indices.push_back(index1); // complete triangle 1
     209             : 
     210             :             // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1
     211           0 :             indices.push_back(index1);
     212           0 :             indices.push_back(index2Prev);
     213           0 :             indices.push_back(index2);
     214             :         }
     215             : 
     216             :         // alternate V coordinate for debugging
     217           0 :         v = 1 - v;
     218             : 
     219             :         // cycle the p's and compute the new p2
     220           0 :         p0 = p1;
     221           0 :         p1 = p2;
     222           0 :         p1floating = p2floating;
     223             : 
     224             :         // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
     225           0 :         if (!closed && i == n-2)
     226             :             // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
     227           0 :             p2 = p1 + (p1 - p0);
     228             :         else
     229           0 :             p2 = CVector3D(line.m_Coords[(i + 2) % n].X, 0, line.m_Coords[(i + 2) % n].Y);
     230             : 
     231           0 :         p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
     232           0 :         if (p2.Y < w)
     233             :         {
     234           0 :             p2.Y = w;
     235           0 :             p2floating = true;
     236             :         }
     237             :         else
     238           0 :             p2floating = false;
     239             :     }
     240             : 
     241           0 :     if (closed)
     242             :     {
     243             :         // close the path
     244           0 :         if (n % 2 == 0)
     245             :         {
     246           0 :             u16 vertexCount = static_cast<u16>(vertices.size());
     247           0 :             indices.push_back(vertexCount - 2);
     248           0 :             indices.push_back(vertexCount - 1);
     249           0 :             indices.push_back(0);
     250             : 
     251           0 :             indices.push_back(0);
     252           0 :             indices.push_back(vertexCount - 1);
     253           0 :             indices.push_back(1);
     254             :         }
     255             :         else
     256             :         {
     257             :             // add two vertices to have the good UVs for the last quad
     258           0 :             SVertex vertex1(vertices[0].m_Position, CVector2D(0.f, 1.f));
     259           0 :             SVertex vertex2(vertices[1].m_Position, CVector2D(1.f, 1.f));
     260           0 :             vertices.push_back(vertex1);
     261           0 :             vertices.push_back(vertex2);
     262             : 
     263           0 :             u16 vertexCount = static_cast<u16>(vertices.size());
     264           0 :             indices.push_back(vertexCount - 4);
     265           0 :             indices.push_back(vertexCount - 3);
     266           0 :             indices.push_back(vertexCount - 2);
     267             : 
     268           0 :             indices.push_back(vertexCount - 2);
     269           0 :             indices.push_back(vertexCount - 3);
     270           0 :             indices.push_back(vertexCount - 1);
     271             :         }
     272             :     }
     273             :     else
     274             :     {
     275             :         // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of
     276             :         // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector
     277             :         // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector.
     278           0 :         std::vector<u16> capIndices;
     279           0 :         std::vector<SVertex> capVertices;
     280             : 
     281             :         // create end cap
     282           0 :         CreateLineCap(
     283             :             line,
     284             :             // the order of these vertices is important here, swapping them produces caps at the wrong side
     285           0 :             vertices[vertices.size()-2].m_Position, // top-right vertex of last quad
     286           0 :             vertices[vertices.size()-1].m_Position, // top-left vertex of last quad
     287             :             // directional vector between centroids of last vertex pair and second-to-last vertex pair
     288           0 :             (Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(),
     289           0 :             line.m_EndCapType,
     290             :             capVertices,
     291             :             capIndices
     292             :         );
     293             : 
     294           0 :         for (unsigned i = 0; i < capIndices.size(); i++)
     295           0 :             capIndices[i] += static_cast<u16>(vertices.size());
     296             : 
     297           0 :         vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
     298           0 :         indices.insert(indices.end(), capIndices.begin(), capIndices.end());
     299             : 
     300           0 :         capIndices.clear();
     301           0 :         capVertices.clear();
     302             : 
     303             :         // create start cap
     304           0 :         CreateLineCap(
     305             :             line,
     306             :             // the order of these vertices is important here, swapping them produces caps at the wrong side
     307           0 :             vertices[1].m_Position,
     308           0 :             vertices[0].m_Position,
     309             :             // directional vector between centroids of first vertex pair and second vertex pair
     310           0 :             (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
     311           0 :             line.m_StartCapType,
     312             :             capVertices,
     313             :             capIndices
     314             :         );
     315             : 
     316           0 :         for (unsigned i = 0; i < capIndices.size(); i++)
     317           0 :             capIndices[i] += static_cast<u16>(vertices.size());
     318             : 
     319           0 :         vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
     320           0 :         indices.insert(indices.end(), capIndices.begin(), capIndices.end());
     321             :     }
     322             : 
     323           0 :     if (vertices.empty() || indices.empty())
     324           0 :         return;
     325             : 
     326             :     // Indices for triangles, so must be multiple of 3.
     327           0 :     ENSURE(indices.size() % 3 == 0);
     328             : 
     329           0 :     m_BoundingBox = CBoundingBoxAligned();
     330           0 :     for (const SVertex& vertex : vertices)
     331           0 :         m_BoundingBox += vertex.m_Position;
     332             : 
     333           0 :     m_VB = g_VBMan.AllocateChunk(
     334           0 :         sizeof(SVertex), vertices.size(), Renderer::Backend::IBuffer::Type::VERTEX, false);
     335             :     // Allocation might fail (e.g. due to too many vertices).
     336           0 :     if (m_VB)
     337             :     {
     338             :         // Copy data into backend buffer.
     339           0 :         m_VB->m_Owner->UpdateChunkVertices(m_VB.Get(), &vertices[0]);
     340             : 
     341           0 :         for (size_t k = 0; k < indices.size(); ++k)
     342           0 :             indices[k] += static_cast<u16>(m_VB->m_Index);
     343             : 
     344           0 :         m_VBIndices = g_VBMan.AllocateChunk(
     345           0 :             sizeof(u16), indices.size(), Renderer::Backend::IBuffer::Type::INDEX, false);
     346           0 :         if (m_VBIndices)
     347           0 :             m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices.Get(), &indices[0]);
     348             :     }
     349             : 
     350             : }
     351             : 
     352           0 : void CTexturedLineRData::CreateLineCap(const SOverlayTexturedLine& line, const CVector3D& corner1, const CVector3D& corner2,
     353             :     const CVector3D& lineDirectionNormal, SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut,
     354             :     std::vector<u16>& indicesOut)
     355             : {
     356           0 :     if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
     357           0 :         return; // no action needed, this is the default
     358             : 
     359             :     // When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
     360             :     // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
     361             :     // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
     362             :     // That is to say, when viewed from the top, we will have something like
     363             :     //                                                 .
     364             :     //  this:                     and not like this:  /|
     365             :     //         ----+                                 / |
     366             :     //             |                                /  .
     367             :     //             |                                  /
     368             :     //         ----+                                 /
     369             :     //
     370             : 
     371           0 :     int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
     372           0 :     float radius = line.m_Thickness;
     373             : 
     374           0 :     CVector3D centerPoint = (corner1 + corner2) * 0.5f;
     375           0 :     SVertex centerVertex(centerPoint, CVector2D(0.5f, 0.5f));
     376           0 :     u16 indexOffset = static_cast<u16>(verticesOut.size()); // index offset in verticesOut from where we start adding our vertices
     377             : 
     378           0 :     switch (endCapType)
     379             :     {
     380           0 :     case SOverlayTexturedLine::LINECAP_SHARP:
     381             :         {
     382           0 :             roundCapPoints = 3; // creates only one point directly ahead
     383           0 :             radius *= 1.5f; // make it a bit sharper (note that we don't use the radius for the butt-end corner points so it should be ok)
     384           0 :             centerVertex.m_UV.X = 0.480f; // slight visual correction to make the texture match up better at the corner points
     385             :         }
     386             :         FALLTHROUGH;
     387           0 :     case SOverlayTexturedLine::LINECAP_ROUND:
     388             :         {
     389             :             // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
     390             :             // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
     391             :             // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then
     392             :             // of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in
     393             :             // the line's plane, producing the desired rounded cap.
     394             : 
     395             :             // To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from
     396             :             // the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use
     397             :             // the negated angle.
     398           0 :             float stepAngle = -(float)(M_PI/(roundCapPoints-1));
     399             : 
     400             :             // Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards)
     401             :             // Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector.
     402             :             // This is because we want to support an overly large radius to make the sharp line ending look sharper.
     403           0 :             verticesOut.push_back(centerVertex);
     404           0 :             verticesOut.push_back(SVertex(corner2, CVector2D()));
     405             : 
     406             :             // Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points.
     407             :             // Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom
     408             :             // radii to support tuning the 'sharpness' of sharp end caps (see above)
     409           0 :             CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius;
     410             :             // Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that
     411             :             // is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line.
     412             :             // Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water,
     413             :             // then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky).
     414           0 :             CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized();
     415             : 
     416           0 :             for (int i = 1; i < roundCapPoints - 1; ++i)
     417             :             {
     418             :                 // Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point.
     419           0 :                 CQuaternion quatRotation;
     420           0 :                 quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle);
     421           0 :                 CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector);
     422             : 
     423             :                 // Let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge
     424             :                 // of the texture around the edge of the semicircle)
     425           0 :                 float u = 0.f;
     426           0 :                 float v = Clamp((i / static_cast<float>(roundCapPoints - 1)), 0.f, 1.f); // pos, u, v
     427           0 :                 verticesOut.push_back(SVertex(worldPos3D, CVector2D(u, v)));
     428             :             }
     429             : 
     430             :             // connect back to the other butt-end corner point to complete the semicircle
     431           0 :             verticesOut.push_back(SVertex(corner1, CVector2D(0.f, 1.f)));
     432             : 
     433             :             // now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the
     434             :             // first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So:
     435           0 :             for (int i=1; i < roundCapPoints; ++i)
     436             :             {
     437           0 :                 indicesOut.push_back(indexOffset); // center vertex
     438           0 :                 indicesOut.push_back(indexOffset + i);
     439           0 :                 indicesOut.push_back(indexOffset + i + 1);
     440           0 :             }
     441             :         }
     442           0 :         break;
     443             : 
     444           0 :     case SOverlayTexturedLine::LINECAP_SQUARE:
     445             :         {
     446             :             // Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of
     447             :             // three triangles (sort of like a triangle fan)
     448             :             // NOTE: The order in which the vertices are pushed out determines the visibility, as they
     449             :             // are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom.
     450           0 :             verticesOut.push_back(centerVertex);
     451           0 :             verticesOut.push_back(SVertex(corner2, CVector2D()));
     452           0 :             verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (line.m_Thickness)), CVector2D(0.f, 0.33333f))); // extend butt corner point 2 along the normal vector
     453           0 :             verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (line.m_Thickness)), CVector2D(0.f, 0.66666f))); // extend butt corner point 1 along the normal vector
     454           0 :             verticesOut.push_back(SVertex(corner1, CVector2D(0.f, 1.0f))); // push butt corner point 1
     455             : 
     456           0 :             for (int i=1; i < 4; ++i)
     457             :             {
     458           0 :                 indicesOut.push_back(indexOffset); // center point
     459           0 :                 indicesOut.push_back(indexOffset + i);
     460           0 :                 indicesOut.push_back(indexOffset + i + 1);
     461           0 :             }
     462             :         }
     463           0 :         break;
     464             : 
     465           0 :     default:
     466           0 :         break;
     467             :     }
     468             : 
     469             : }
     470             : 
     471           0 : bool CTexturedLineRData::IsVisibleInFrustum(const CFrustum& frustum) const
     472             : {
     473           0 :     return frustum.IsBoxVisible(m_BoundingBox);
     474           3 : }

Generated by: LCOV version 1.13