LCOV - code coverage report
Current view: top level - source/simulation2/helpers - Render.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 308 0.0 %
Date: 2023-01-19 00:18:29 Functions: 0 17 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 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 "Render.h"
      21             : 
      22             : #include "graphics/Overlay.h"
      23             : #include "graphics/Terrain.h"
      24             : #include "maths/BoundingBoxAligned.h"
      25             : #include "maths/BoundingBoxOriented.h"
      26             : #include "maths/MathUtil.h"
      27             : #include "maths/Matrix3D.h"
      28             : #include "maths/Quaternion.h"
      29             : #include "maths/Vector2D.h"
      30             : #include "ps/Profile.h"
      31             : #include "simulation2/Simulation2.h"
      32             : #include "simulation2/components/ICmpTerrain.h"
      33             : #include "simulation2/components/ICmpWaterManager.h"
      34             : #include "simulation2/helpers/Geometry.h"
      35             : 
      36           0 : void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
      37             :         SOverlayLine& overlay, bool floating, float heightOffset)
      38             : {
      39           0 :     overlay.m_Coords.clear();
      40             : 
      41           0 :     CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY);
      42           0 :     if (!cmpTerrain)
      43           0 :         return;
      44             : 
      45           0 :     if (xz.size() < 2)
      46           0 :         return;
      47             : 
      48           0 :     float water = 0.f;
      49           0 :     if (floating)
      50             :     {
      51           0 :         CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY);
      52           0 :         if (cmpWaterManager)
      53           0 :             water = cmpWaterManager->GetExactWaterLevel(xz[0], xz[1]);
      54             :     }
      55             : 
      56           0 :     overlay.m_Coords.reserve(xz.size()/2 * 3);
      57             : 
      58           0 :     for (size_t i = 0; i < xz.size(); i += 2)
      59             :     {
      60           0 :         const float px = xz[i];
      61           0 :         const float pz = xz[i+1];
      62           0 :         const float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset;
      63           0 :         overlay.PushCoords(px, py, pz);
      64             :     }
      65             : }
      66             : 
      67           0 : static void ConstructCircleOrClosedArc(
      68             :     const CSimContext& context, float x, float z, float radius,
      69             :     bool isCircle,
      70             :     float start, float end,
      71             :     SOverlayLine& overlay, bool floating, float heightOffset)
      72             : {
      73           0 :     overlay.m_Coords.clear();
      74             : 
      75           0 :     CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY);
      76           0 :     if (!cmpTerrain)
      77           0 :         return;
      78             : 
      79           0 :     float water = 0.f;
      80           0 :     if (floating)
      81             :     {
      82           0 :         CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY);
      83           0 :         if (cmpWaterManager)
      84           0 :             water = cmpWaterManager->GetExactWaterLevel(x, z);
      85             :     }
      86             : 
      87             :     // Adapt the circle resolution to look reasonable for small and largeish radiuses
      88           0 :     size_t numPoints = Clamp<size_t>(radius * (end - start), 12, 48);
      89             : 
      90           0 :     if (!isCircle)
      91           0 :         overlay.m_Coords.reserve((numPoints + 1 + 2) * 3);
      92             :     else
      93           0 :         overlay.m_Coords.reserve((numPoints + 1) * 3);
      94             : 
      95           0 :     float cy = 0.f;
      96           0 :     if (!isCircle)
      97             :     {
      98             :         // Start at the center point
      99           0 :         cy = std::max(water, cmpTerrain->GetExactGroundLevel(x, z)) + heightOffset;
     100           0 :         overlay.PushCoords(x, cy, z);
     101             :     }
     102             : 
     103           0 :     for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop
     104             :     {
     105           0 :         float a = start + (float)i * (end - start) / (float)numPoints;
     106           0 :         float px = x + radius * cosf(a);
     107           0 :         float pz = z + radius * sinf(a);
     108           0 :         float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset;
     109           0 :         overlay.PushCoords(px, py, pz);
     110             :     }
     111             : 
     112           0 :     if (!isCircle)
     113             :     {
     114             :         // Return to the center point
     115           0 :         overlay.PushCoords(x, cy, z);
     116             :     }
     117             : }
     118             : 
     119           0 : void SimRender::ConstructCircleOnGround(
     120             :     const CSimContext& context, float x, float z, float radius,
     121             :     SOverlayLine& overlay, bool floating, float heightOffset)
     122             : {
     123           0 :     ConstructCircleOrClosedArc(context, x, z, radius, true, 0.0f, 2.0f*(float)M_PI, overlay, floating, heightOffset);
     124           0 : }
     125             : 
     126           0 : void SimRender::ConstructClosedArcOnGround(
     127             :     const CSimContext& context, float x, float z, float radius,
     128             :     float start, float end,
     129             :     SOverlayLine& overlay, bool floating, float heightOffset)
     130             : {
     131           0 :     ConstructCircleOrClosedArc(context, x, z, radius, false, start, end, overlay, floating, heightOffset);
     132           0 : }
     133             : 
     134             : // This method splits up a straight line into a number of line segments each having a length ~= TERRAIN_TILE_SIZE
     135           0 : static void SplitLine(std::vector<std::pair<float, float> >& coords, float x1, float y1, float x2, float y2)
     136             : {
     137           0 :     float length = sqrtf(SQR(x1 - x2) + SQR(y1 - y2));
     138           0 :     size_t pieces = ((int)length) / TERRAIN_TILE_SIZE;
     139           0 :     if (pieces > 0)
     140             :     {
     141           0 :         float xPieceLength = (x1 - x2) / (float)pieces;
     142           0 :         float yPieceLength = (y1 - y2) / (float)pieces;
     143           0 :         for (size_t i = 1; i <= (pieces - 1); ++i)
     144             :         {
     145           0 :             coords.emplace_back(x1 - (xPieceLength * (float)i), y1 - (yPieceLength * (float)i));
     146             :         }
     147             :     }
     148           0 :     coords.emplace_back(x2, y2);
     149           0 : }
     150             : 
     151           0 : void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a,
     152             :         SOverlayLine& overlay, bool floating, float heightOffset)
     153             : {
     154           0 :     overlay.m_Coords.clear();
     155             : 
     156           0 :     CmpPtr<ICmpTerrain> cmpTerrain(context, SYSTEM_ENTITY);
     157           0 :     if (!cmpTerrain)
     158           0 :         return;
     159             : 
     160           0 :     float water = 0.f;
     161           0 :     if (floating)
     162             :     {
     163           0 :         CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY);
     164           0 :         if (cmpWaterManager)
     165           0 :             water = cmpWaterManager->GetExactWaterLevel(x, z);
     166             :     }
     167             : 
     168           0 :     float c = cosf(a);
     169           0 :     float s = sinf(a);
     170             : 
     171           0 :     std::vector<std::pair<float, float> > coords;
     172             : 
     173             :     // Add the first vertex, since SplitLine will be adding only the second end-point of the each line to
     174             :     // the coordinates list. We don't have to worry about the other lines, since the end-point of one line
     175             :     // will be the starting point of the next
     176           0 :     coords.emplace_back(x - w/2*c + h/2*s, z + w/2*s + h/2*c);
     177             : 
     178           0 :     SplitLine(coords, x - w/2*c + h/2*s, z + w/2*s + h/2*c, x - w/2*c - h/2*s, z + w/2*s - h/2*c);
     179           0 :     SplitLine(coords, x - w/2*c - h/2*s, z + w/2*s - h/2*c, x + w/2*c - h/2*s, z - w/2*s - h/2*c);
     180           0 :     SplitLine(coords, x + w/2*c - h/2*s, z - w/2*s - h/2*c, x + w/2*c + h/2*s, z - w/2*s + h/2*c);
     181           0 :     SplitLine(coords, x + w/2*c + h/2*s, z - w/2*s + h/2*c, x - w/2*c + h/2*s, z + w/2*s + h/2*c);
     182             : 
     183           0 :     overlay.m_Coords.reserve(coords.size() * 3);
     184             : 
     185           0 :     for (size_t i = 0; i < coords.size(); ++i)
     186             :     {
     187           0 :         float px = coords[i].first;
     188           0 :         float pz = coords[i].second;
     189           0 :         float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset;
     190           0 :         overlay.PushCoords(px, py, pz);
     191             :     }
     192             : }
     193             : 
     194           0 : void SimRender::ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine)
     195             : {
     196           0 :     overlayLine.m_Coords.clear();
     197             : 
     198           0 :     if (bound.IsEmpty())
     199           0 :         return;
     200             : 
     201           0 :     const CVector3D& pMin = bound[0];
     202           0 :     const CVector3D& pMax = bound[1];
     203             : 
     204             :     // floor square
     205           0 :     overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
     206           0 :     overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z);
     207           0 :     overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z);
     208           0 :     overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z);
     209           0 :     overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
     210             :     // roof square
     211           0 :     overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
     212           0 :     overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z);
     213           0 :     overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z);
     214           0 :     overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z);
     215           0 :     overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
     216             : }
     217             : 
     218           0 : void SimRender::ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine)
     219             : {
     220           0 :     overlayLine.m_Coords.clear();
     221             : 
     222           0 :     if (box.IsEmpty())
     223           0 :         return;
     224             : 
     225           0 :     CVector3D corners[8];
     226           0 :     box.GetCorner(-1, -1, -1, corners[0]);
     227           0 :     box.GetCorner( 1, -1, -1, corners[1]);
     228           0 :     box.GetCorner( 1, -1,  1, corners[2]);
     229           0 :     box.GetCorner(-1, -1,  1, corners[3]);
     230           0 :     box.GetCorner(-1,  1, -1, corners[4]);
     231           0 :     box.GetCorner( 1,  1, -1, corners[5]);
     232           0 :     box.GetCorner( 1,  1,  1, corners[6]);
     233           0 :     box.GetCorner(-1,  1,  1, corners[7]);
     234             : 
     235           0 :     overlayLine.PushCoords(corners[0]);
     236           0 :     overlayLine.PushCoords(corners[1]);
     237           0 :     overlayLine.PushCoords(corners[2]);
     238           0 :     overlayLine.PushCoords(corners[3]);
     239           0 :     overlayLine.PushCoords(corners[0]);
     240             : 
     241           0 :     overlayLine.PushCoords(corners[4]);
     242           0 :     overlayLine.PushCoords(corners[5]);
     243           0 :     overlayLine.PushCoords(corners[6]);
     244           0 :     overlayLine.PushCoords(corners[7]);
     245           0 :     overlayLine.PushCoords(corners[4]);
     246             : }
     247             : 
     248           0 : void SimRender::ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps)
     249             : {
     250           0 :     ENSURE(numSteps > 0 && numSteps % 4 == 0); // must be a positive multiple of 4
     251             : 
     252           0 :     out.m_Coords.clear();
     253             : 
     254           0 :     size_t fullCircleSteps = numSteps;
     255           0 :     const float angleIncrement = 2.f*M_PI/fullCircleSteps;
     256             : 
     257           0 :     const CVector3D X_UNIT(1, 0, 0);
     258           0 :     const CVector3D Y_UNIT(0, 1, 0);
     259           0 :     const CVector3D Z_UNIT(0, 0, 1);
     260           0 :     CVector3D rotationVector(0, 0, radius); // directional vector based in the center that we will be rotating to get the gimbal points
     261             : 
     262             :     // first draw a quarter of XZ gimbal; then complete the XY gimbal; then continue the XZ gimbal and finally add the YZ gimbal
     263             :     // (that way we can keep a single continuous line)
     264             : 
     265             :     // -- XZ GIMBAL (PART 1/2) -----------------------------------------------
     266             : 
     267           0 :     CQuaternion xzRotation;
     268           0 :     xzRotation.FromAxisAngle(Y_UNIT, angleIncrement);
     269             : 
     270           0 :     for (size_t i = 0; i < fullCircleSteps/4; ++i) // complete only a quarter of the way
     271             :     {
     272           0 :         out.PushCoords(center + rotationVector);
     273           0 :         rotationVector = xzRotation.Rotate(rotationVector);
     274             :     }
     275             : 
     276             :     // -- XY GIMBAL ----------------------------------------------------------
     277             : 
     278             :     // now complete the XY gimbal while the XZ gimbal is interrupted
     279           0 :     CQuaternion xyRotation;
     280           0 :     xyRotation.FromAxisAngle(Z_UNIT, angleIncrement);
     281             : 
     282           0 :     for (size_t i = 0; i < fullCircleSteps; ++i) // note the <; the last point of the XY gimbal isn't added, because the XZ gimbal will add it
     283             :     {
     284           0 :         out.PushCoords(center + rotationVector);
     285           0 :         rotationVector = xyRotation.Rotate(rotationVector);
     286             :     }
     287             : 
     288             :     // -- XZ GIMBAL (PART 2/2) -----------------------------------------------
     289             : 
     290             :     // resume the XZ gimbal to completion
     291           0 :     for (size_t i = fullCircleSteps/4; i < fullCircleSteps; ++i) // exclude the last point of the circle so the YZ gimbal can add it
     292             :     {
     293           0 :         out.PushCoords(center + rotationVector);
     294           0 :         rotationVector = xzRotation.Rotate(rotationVector);
     295             :     }
     296             : 
     297             :     // -- YZ GIMBAL ----------------------------------------------------------
     298             : 
     299           0 :     CQuaternion yzRotation;
     300           0 :     yzRotation.FromAxisAngle(X_UNIT, angleIncrement);
     301             : 
     302           0 :     for (size_t i = 0; i <= fullCircleSteps; ++i)
     303             :     {
     304           0 :         out.PushCoords(center + rotationVector);
     305           0 :         rotationVector = yzRotation.Rotate(rotationVector);
     306             :     }
     307           0 : }
     308             : 
     309           0 : void SimRender::ConstructAxesMarker(const CMatrix3D& coordSystem, SOverlayLine& outX, SOverlayLine& outY, SOverlayLine& outZ)
     310             : {
     311           0 :     outX.m_Coords.clear();
     312           0 :     outY.m_Coords.clear();
     313           0 :     outZ.m_Coords.clear();
     314             : 
     315           0 :     outX.m_Color = CColor(1, 0, 0, .5f); // X axis; red
     316           0 :     outY.m_Color = CColor(0, 1, 0, .5f); // Y axis; green
     317           0 :     outZ.m_Color = CColor(0, 0, 1, .5f); // Z axis; blue
     318             : 
     319           0 :     outX.m_Thickness = 0.1f;
     320           0 :     outY.m_Thickness = 0.1f;
     321           0 :     outZ.m_Thickness = 0.1f;
     322             : 
     323           0 :     CVector3D origin = coordSystem.GetTranslation();
     324           0 :     outX.PushCoords(origin);
     325           0 :     outY.PushCoords(origin);
     326           0 :     outZ.PushCoords(origin);
     327             : 
     328           0 :     outX.PushCoords(origin + CVector3D(coordSystem(0,0), coordSystem(1,0), coordSystem(2,0)));
     329           0 :     outY.PushCoords(origin + CVector3D(coordSystem(0,1), coordSystem(1,1), coordSystem(2,1)));
     330           0 :     outZ.PushCoords(origin + CVector3D(coordSystem(0,2), coordSystem(1,2), coordSystem(2,2)));
     331           0 : }
     332             : 
     333           0 : void SimRender::SmoothPointsAverage(std::vector<CVector2D>& points, bool closed)
     334             : {
     335           0 :     PROFILE("SmoothPointsAverage");
     336             : 
     337           0 :     size_t n = points.size();
     338           0 :     if (n < 2)
     339           0 :         return; // avoid out-of-bounds array accesses, and leave the points unchanged
     340             : 
     341           0 :     std::vector<CVector2D> newPoints;
     342           0 :     newPoints.resize(points.size());
     343             : 
     344             :     // Handle the end points appropriately
     345           0 :     if (closed)
     346             :     {
     347           0 :         newPoints[0] = (points[n-1] + points[0] + points[1]) / 3.f;
     348           0 :         newPoints[n-1] = (points[n-2] + points[n-1] + points[0]) / 3.f;
     349             :     }
     350             :     else
     351             :     {
     352           0 :         newPoints[0] = points[0];
     353           0 :         newPoints[n-1] = points[n-1];
     354             :     }
     355             : 
     356             :     // Average all the intermediate points
     357           0 :     for (size_t i = 1; i < n-1; ++i)
     358           0 :         newPoints[i] = (points[i-1] + points[i] + points[i+1]) / 3.f;
     359             : 
     360           0 :     points.swap(newPoints);
     361             : }
     362             : 
     363           0 : static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a2, CVector2D a3, float offset)
     364             : {
     365             :     // Compute position on spline
     366           0 :     CVector2D p = a0*(t*t*t) + a1*(t*t) + a2*t + a3;
     367             : 
     368             :     // Compute unit-vector direction of spline
     369           0 :     CVector2D dp = (a0*(3*t*t) + a1*(2*t) + a2).Normalized();
     370             : 
     371             :     // Offset position perpendicularly
     372           0 :     return p + CVector2D(dp.Y*-offset, dp.X*offset);
     373             : }
     374             : 
     375           0 : void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples /* = 4 */)
     376             : {
     377           0 :     PROFILE("InterpolatePointsRNS");
     378           0 :     ENSURE(segmentSamples > 0);
     379             : 
     380           0 :     std::vector<CVector2D> newPoints;
     381             : 
     382             :     // (This does some redundant computations for adjacent vertices,
     383             :     // but it's fairly fast (<1ms typically) so we don't worry about it yet)
     384             : 
     385             :     // TODO: Instead of doing a fixed number of line segments between each
     386             :     // control point, it should probably be somewhat adaptive to get a nicer
     387             :     // curve with fewer points
     388             : 
     389           0 :     size_t n = points.size();
     390             : 
     391           0 :     if (closed)
     392             :     {
     393           0 :         if (n < 1)
     394           0 :             return; // we need at least a single point to not crash
     395             :     }
     396             :     else
     397             :     {
     398           0 :         if (n < 2)
     399           0 :             return; // in non-closed mode, we need at least n=2 to not crash
     400             :     }
     401             : 
     402           0 :     size_t imax = closed ? n : n-1;
     403           0 :     newPoints.reserve(imax*segmentSamples);
     404             : 
     405             :     // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point
     406           0 :     CVector2D a0;
     407           0 :     CVector2D a1;
     408           0 :     CVector2D a2;
     409           0 :     CVector2D a3;
     410             : 
     411           0 :     for (size_t i = 0; i < imax; ++i)
     412             :     {
     413             :         // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points
     414             :         // before p1 and after p2, respectively; they're needed to compute tangents and whatnot.
     415           0 :         CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below
     416           0 :         CVector2D p1 = points[i];
     417           0 :         CVector2D p2 = points[(i+1)%n];
     418           0 :         CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below
     419             : 
     420           0 :         if (!closed && (i == 0))
     421             :             // p0's point index is out of bounds, and we can't wrap around because we're in non-closed mode -- create an artificial point
     422             :             // that extends p1 -> p0 (i.e. the first segment's direction)
     423           0 :             p0 = points[0] + (points[0] - points[1]);
     424             :         else
     425             :             // standard wrap-around case
     426           0 :             p0 = points[(i-1+n)%n]; // careful; don't use (i-1)%n here, as the result is machine-dependent for negative operands (e.g. if i==0, the result could be either -1 or n-1)
     427             : 
     428             : 
     429           0 :         if (!closed && (i == n-2))
     430             :             // p3's point index is out of bounds; create an artificial point that extends p_(n-2) -> p_(n-1) (i.e. the last segment's direction)
     431             :             // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1)
     432           0 :             p3 = points[n-1] + (points[n-1] - points[n-2]);
     433             :         else
     434             :             // standard wrap-around case
     435           0 :             p3 = points[(i+2)%n];
     436             : 
     437             : 
     438             :         // Do the RNS computation (based on GPG4 "Nonuniform Splines")
     439           0 :         float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1)
     440           0 :         CVector2D s0 = (p1 - p0).Normalized(); // unit vector of spline segment (i-1)..(i)
     441           0 :         CVector2D s1 = (p2 - p1).Normalized(); // unit vector of spline segment (i)..(i+1)
     442           0 :         CVector2D s2 = (p3 - p2).Normalized(); // unit vector of spline segment (i+1)..(i+2)
     443           0 :         CVector2D v1 = (s0 + s1).Normalized() * l1; // spline velocity at i
     444           0 :         CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1
     445             : 
     446             :         // Compute standard cubic spline parameters
     447           0 :         a0 = p1*2 + p2*-2 + v1 + v2;
     448           0 :         a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
     449           0 :         a2 = v1;
     450           0 :         a3 = p1;
     451             : 
     452             :         // Interpolate at regular points across the interval
     453           0 :         for (int sample = 0; sample < segmentSamples; sample++)
     454           0 :             newPoints.push_back(EvaluateSpline(sample/((float) segmentSamples), a0, a1, a2, a3, offset));
     455             : 
     456             :     }
     457             : 
     458           0 :     if (!closed)
     459             :         // if the path is open, we should take care to include the last control point
     460             :         // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset
     461           0 :         newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset));
     462             : 
     463           0 :     points.swap(newPoints);
     464             : }
     465             : 
     466           0 : void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength)
     467             : {
     468             :     // sanity checks
     469           0 :     if (dashLength <= 0)
     470           0 :         return;
     471             : 
     472           0 :     if (blankLength <= 0)
     473           0 :         return;
     474             : 
     475           0 :     if (keyPoints.size() < 2)
     476           0 :         return;
     477             : 
     478           0 :     dashedLineOut.m_Points.clear();
     479           0 :     dashedLineOut.m_StartIndices.clear();
     480             : 
     481             :     // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the
     482             :     // required length and continue for blankLength along the line to start a new dash segment.
     483             : 
     484             :     // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length
     485             :     // so that it fits the length of the curve, but that requires knowing the length of the curve upfront which is sort of expensive to compute
     486             :     // (O(n) and lots of square roots).
     487             : 
     488           0 :     bool buildingDash = true; // true if we're building a dash, false if a blank
     489           0 :     float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes
     490           0 :     CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built.
     491             : 
     492             :     // register the first starting node of the first dash
     493           0 :     dashedLineOut.m_Points.push_back(keyPoints[0]);
     494           0 :     dashedLineOut.m_StartIndices.push_back(0);
     495             : 
     496             :     // index of the next key point on the path. Must always point to a node that is further along the path than dashLastPoint, so we can
     497             :     // properly take a direction vector along the path.
     498           0 :     size_t i = 0;
     499             : 
     500           0 :     while(i < keyPoints.size() - 1)
     501             :     {
     502             :         // get length of this segment
     503           0 :         CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode
     504           0 :         float segmentLength = segmentVector.Length();
     505             : 
     506           0 :         float targetLength = (buildingDash ? dashLength : blankLength);
     507           0 :         if (curDashLength + segmentLength > targetLength)
     508             :         {
     509             :             // segment is longer than the dash length we still have to go, so we'll need to cut it; create a cut point along the segment
     510             :             // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank.
     511           0 :             float cutLength = targetLength - curDashLength;
     512           0 :             CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength);
     513             : 
     514             :             // start a new dash or blank in the next iteration
     515           0 :             curDashLength = 0;
     516           0 :             buildingDash = !buildingDash; // flip from dash to blank and vice-versa
     517           0 :             dashLastPoint = cutPoint;
     518             : 
     519             :             // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the
     520             :             // direction vector with in the next iteration
     521             : 
     522             :             // this cut point is either the end of the current dash or the beginning of a new dash; either way, we're gonna need it
     523             :             // in the points array.
     524           0 :             dashedLineOut.m_Points.push_back(cutPoint);
     525             : 
     526           0 :             if (buildingDash)
     527             :             {
     528             :                 // if we're gonna be building a new dash, then cutPoint is now the base point of that new dash, so let's register its
     529             :                 // index as a start index of a dash.
     530           0 :                 dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1);
     531             :             }
     532             : 
     533             :         }
     534             :         else
     535             :         {
     536             :             // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1]
     537             :             // to this dash's points and continue from there
     538             : 
     539           0 :             if (buildingDash)
     540             :                 // still building the dash, add it to the output (we don't need to store the blanks)
     541           0 :                 dashedLineOut.m_Points.push_back(keyPoints[i+1]);
     542             : 
     543           0 :             curDashLength += segmentLength;
     544           0 :             dashLastPoint = keyPoints[i+1];
     545           0 :             i++;
     546             : 
     547             :         }
     548             : 
     549             :     }
     550             : 
     551             : }
     552             : 
     553             : // TODO: this serves a similar purpose to SplitLine above, but is more general. Also, SplitLine seems to be implemented more
     554             : // efficiently, might be nice to take some cues from it
     555           0 : void SimRender::SubdividePoints(std::vector<CVector2D>& points, float maxSegmentLength, bool closed)
     556             : {
     557           0 :     size_t numControlPoints = points.size();
     558           0 :     if (numControlPoints < 2)
     559           0 :         return;
     560             : 
     561           0 :     ENSURE(maxSegmentLength > 0);
     562             : 
     563           0 :     size_t endIndex = numControlPoints;
     564           0 :     if (!closed && numControlPoints > 2)
     565           0 :         endIndex--;
     566             : 
     567           0 :     std::vector<CVector2D> newPoints;
     568             : 
     569           0 :     for (size_t i = 0; i < endIndex; i++)
     570             :     {
     571           0 :         const CVector2D& curPoint = points[i];
     572           0 :         const CVector2D& nextPoint = points[(i+1) % numControlPoints];
     573           0 :         const CVector2D line(nextPoint - curPoint);
     574           0 :         CVector2D lineDirection = line.Normalized();
     575             : 
     576             :         // include control point i + a list of intermediate points between i and i + 1 (excluding i+1 itself)
     577           0 :         newPoints.push_back(curPoint);
     578             : 
     579             :         // calculate how many intermediate points are needed so that each segment is of length <= maxSegmentLength
     580           0 :         float lineLength = line.Length();
     581           0 :         size_t numSegments = (size_t) ceilf(lineLength / maxSegmentLength);
     582           0 :         float segmentLength = lineLength / numSegments;
     583             : 
     584           0 :         for (size_t s = 1; s < numSegments; ++s) // start at one, we already included curPoint
     585             :         {
     586           0 :             newPoints.push_back(curPoint + lineDirection * (s * segmentLength));
     587             :         }
     588             :     }
     589             : 
     590           0 :     points.swap(newPoints);
     591             : }
     592             : 
     593           0 : void SimRender::ConstructTexturedLineBox(SOverlayTexturedLine& overlay, const CVector2D& origin,
     594             :         const CFixedVector3D& rotation, const float sizeX, const float sizeZ)
     595             : {
     596           0 :     float s = sinf(-rotation.Y.ToFloat());
     597           0 :     float c = cosf(-rotation.Y.ToFloat());
     598             : 
     599           0 :     CVector2D unitX(c, s);
     600           0 :     CVector2D unitZ(-s, c);
     601             : 
     602             :     // Add half the line thickness to the radius so that we get an 'outside' stroke of the footprint shape
     603           0 :     const float halfSizeX = sizeX / 2.f + overlay.m_Thickness / 2.f;
     604           0 :     const float halfSizeZ = sizeZ / 2.f + overlay.m_Thickness / 2.f;
     605             : 
     606           0 :     std::vector<CVector2D> points;
     607           0 :     points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * (-halfSizeZ)));
     608           0 :     points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * (-halfSizeZ)));
     609           0 :     points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * halfSizeZ));
     610           0 :     points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * halfSizeZ));
     611             : 
     612           0 :     SimRender::SubdividePoints(points, TERRAIN_TILE_SIZE / 3.f, overlay.m_Closed);
     613           0 :     overlay.PushCoords(points);
     614           0 : }
     615             : 
     616           0 : void SimRender::ConstructTexturedLineCircle(SOverlayTexturedLine& overlay, const CVector2D& origin, const float overlay_radius)
     617             : {
     618           0 :     const float radius = overlay_radius + overlay.m_Thickness / 3.f;
     619             : 
     620           0 :     size_t numSteps = ceilf(float(2 * M_PI) * radius / (TERRAIN_TILE_SIZE / 3.f));
     621           0 :     for (size_t i = 0; i < numSteps; ++i)
     622             :     {
     623           0 :         float angle = i * float(2 * M_PI) / numSteps;
     624           0 :         float px = origin.X + radius * sinf(angle);
     625           0 :         float pz = origin.Y + radius * cosf(angle);
     626             : 
     627           0 :         overlay.PushCoords(px, pz);
     628             :     }
     629           0 : }

Generated by: LCOV version 1.13