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 : }
|