1 /* Copyright (C) 2017 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "TexturedLineRData.h"
22 #include "graphics/Terrain.h"
23 #include "maths/MathUtil.h"
24 #include "maths/Quaternion.h"
25 #include "renderer/OverlayRenderer.h"
26 #include "renderer/Renderer.h"
27 #include "simulation2/Simulation2.h"
28 #include "simulation2/system/SimContext.h"
29 #include "simulation2/components/ICmpWaterManager.h"
31 /* Note: this implementation uses g_VBMan directly rather than access it through the nicer VertexArray interface,
32 * because it allows you to work with variable amounts of vertices and indices more easily. New code should prefer
33 * to use VertexArray where possible, though. */
35 void CTexturedLineRData::Render(const SOverlayTexturedLine
& line
, const CShaderProgramPtr
& shader
)
37 if (!m_VB
|| !m_VBIndices
)
38 return; // might have failed to allocate
40 // -- render main line quad strip ----------------------
42 const int streamFlags
= shader
->GetStreamFlags();
44 shader
->BindTexture(str_baseTex
, line
.m_TextureBase
->GetHandle());
45 shader
->BindTexture(str_maskTex
, line
.m_TextureMask
->GetHandle());
46 shader
->Uniform(str_objectColor
, line
.m_Color
);
48 GLsizei stride
= sizeof(CTexturedLineRData::SVertex
);
49 CTexturedLineRData::SVertex
* vertexBase
= reinterpret_cast<CTexturedLineRData::SVertex
*>(m_VB
->m_Owner
->Bind());
51 if (streamFlags
& STREAM_POS
)
52 shader
->VertexPointer(3, GL_FLOAT
, stride
, &vertexBase
->m_Position
[0]);
54 if (streamFlags
& STREAM_UV0
)
55 shader
->TexCoordPointer(GL_TEXTURE0
, 2, GL_FLOAT
, stride
, &vertexBase
->m_UVs
[0]);
57 if (streamFlags
& STREAM_UV1
)
58 shader
->TexCoordPointer(GL_TEXTURE1
, 2, GL_FLOAT
, stride
, &vertexBase
->m_UVs
[0]);
60 u8
* indexBase
= m_VBIndices
->m_Owner
->Bind();
62 shader
->AssertPointersBound();
63 glDrawElements(GL_TRIANGLES
, m_VBIndices
->m_Count
, GL_UNSIGNED_SHORT
, indexBase
+ sizeof(u16
)*m_VBIndices
->m_Index
);
65 g_Renderer
.GetStats().m_DrawCalls
++;
66 g_Renderer
.GetStats().m_OverlayTris
+= m_VBIndices
->m_Count
/3;
69 void CTexturedLineRData::Update(const SOverlayTexturedLine
& line
)
73 g_VBMan
.Release(m_VB
);
78 g_VBMan
.Release(m_VBIndices
);
82 if (!line
.m_SimContext
)
84 debug_warn(L
"[TexturedLineRData] No SimContext set for textured overlay line, cannot render (no terrain data)");
89 std::vector
<SVertex
> vertices
;
90 std::vector
<u16
> indices
;
92 size_t n
= line
.m_Coords
.size() / 2; // number of line points
93 bool closed
= line
.m_Closed
;
95 ENSURE(n
>= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point)
97 // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
98 // To avoid slightly expensive terrain computations we cycle these around and
99 // recompute p2 at the end of each iteration.
102 CVector3D
p1(line
.m_Coords
[0], 0, line
.m_Coords
[1]);
103 CVector3D
p2(line
.m_Coords
[2], 0, line
.m_Coords
[3]);
106 // grab the ending point so as to close the loop
107 p0
= CVector3D(line
.m_Coords
[(n
-1)*2], 0, line
.m_Coords
[(n
-1)*2+1]);
109 // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
110 // extends the p2 -> p1 direction, and use that point instead
113 bool p1floating
= false;
114 bool p2floating
= false;
116 // Compute terrain heights, clamped to the water height (and remember whether
117 // each point was floating on water, for normal computation later)
119 // TODO: if we ever support more than one water level per map, recompute this per point
120 CmpPtr
<ICmpWaterManager
> cmpWaterManager(*line
.m_SimContext
, SYSTEM_ENTITY
);
121 float w
= cmpWaterManager
? cmpWaterManager
->GetExactWaterLevel(p0
.X
, p0
.Z
) : 0.f
;
123 const CTerrain
& terrain
= line
.m_SimContext
->GetTerrain();
125 p0
.Y
= terrain
.GetExactGroundLevel(p0
.X
, p0
.Z
);
129 p1
.Y
= terrain
.GetExactGroundLevel(p1
.X
, p1
.Z
);
136 p2
.Y
= terrain
.GetExactGroundLevel(p2
.X
, p2
.Z
);
143 for (size_t i
= 0; i
< n
; ++i
)
145 // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1)
146 // perpendicular to terrain normal
148 // Normal is vertical if on water, else computed from terrain
151 norm
= CVector3D(0, 1, 0);
153 norm
= terrain
.CalcExactNormal(p1
.X
, p1
.Z
);
155 CVector3D b
= ((p1
- p0
).Normalized() + (p2
- p1
).Normalized()).Cross(norm
);
157 // Adjust bisector length to match the line thickness, along the line's width
158 float l
= b
.Dot((p2
- p1
).Normalized().Cross(norm
));
159 if (fabs(l
) > 0.000001f
) // avoid unlikely divide-by-zero
160 b
*= line
.m_Thickness
/ l
;
162 // Push vertices and indices for each quad in GL_TRIANGLES order. The two triangles of each quad are indexed using
163 // the winding orders (BR, BL, TR) and (TR, BL, TL) (where BR is bottom-right of this iteration's quad, TR top-right etc).
164 SVertex
vertex1(p1
+ b
+ norm
*OverlayRenderer::OVERLAY_VOFFSET
, 0.f
, v
);
165 SVertex
vertex2(p1
- b
+ norm
*OverlayRenderer::OVERLAY_VOFFSET
, 1.f
, v
);
166 vertices
.push_back(vertex1
);
167 vertices
.push_back(vertex2
);
169 u16 index1
= vertices
.size() - 2; // index of vertex1 in this iteration (TR of this quad)
170 u16 index2
= vertices
.size() - 1; // index of the vertex2 in this iteration (TL of this quad)
174 // initial two vertices to continue building triangles from (n must be >= 2 for this to work)
175 indices
.push_back(index1
);
176 indices
.push_back(index2
);
180 u16 index1Prev
= vertices
.size() - 4; // index of the vertex1 in the previous iteration (BR of this quad)
181 u16 index2Prev
= vertices
.size() - 3; // index of the vertex2 in the previous iteration (BL of this quad)
182 ENSURE(index1Prev
< vertices
.size());
183 ENSURE(index2Prev
< vertices
.size());
184 // Add two corner points from last iteration and join with one of our own corners to create triangle 1
185 // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied)
188 indices
.push_back(index1Prev
);
189 indices
.push_back(index2Prev
);
191 indices
.push_back(index1
); // complete triangle 1
193 // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1
194 indices
.push_back(index1
);
195 indices
.push_back(index2Prev
);
196 indices
.push_back(index2
);
199 // alternate V coordinate for debugging
202 // cycle the p's and compute the new p2
205 p1floating
= p2floating
;
207 // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
208 if (!closed
&& i
== n
-2)
209 // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
212 p2
= CVector3D(line
.m_Coords
[((i
+2) % n
)*2], 0, line
.m_Coords
[((i
+2) % n
)*2+1]);
214 p2
.Y
= terrain
.GetExactGroundLevel(p2
.X
, p2
.Z
);
229 indices
.push_back(vertices
.size()-2);
230 indices
.push_back(vertices
.size()-1);
231 indices
.push_back(0);
233 indices
.push_back(0);
234 indices
.push_back(vertices
.size()-1);
235 indices
.push_back(1);
239 // add two vertices to have the good UVs for the last quad
240 SVertex
vertex1(vertices
[0].m_Position
, 0.f
, 1.f
);
241 SVertex
vertex2(vertices
[1].m_Position
, 1.f
, 1.f
);
242 vertices
.push_back(vertex1
);
243 vertices
.push_back(vertex2
);
245 indices
.push_back(vertices
.size()-4);
246 indices
.push_back(vertices
.size()-3);
247 indices
.push_back(vertices
.size()-2);
249 indices
.push_back(vertices
.size()-2);
250 indices
.push_back(vertices
.size()-3);
251 indices
.push_back(vertices
.size()-1);
256 // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of
257 // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector
258 // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector.
259 std::vector
<u16
> capIndices
;
260 std::vector
<SVertex
> capVertices
;
265 // the order of these vertices is important here, swapping them produces caps at the wrong side
266 vertices
[vertices
.size()-2].m_Position
, // top-right vertex of last quad
267 vertices
[vertices
.size()-1].m_Position
, // top-left vertex of last quad
268 // directional vector between centroids of last vertex pair and second-to-last vertex pair
269 (Centroid(vertices
[vertices
.size()-2], vertices
[vertices
.size()-1]) - Centroid(vertices
[vertices
.size()-4], vertices
[vertices
.size()-3])).Normalized(),
275 for (unsigned i
= 0; i
< capIndices
.size(); i
++)
276 capIndices
[i
] += vertices
.size();
278 vertices
.insert(vertices
.end(), capVertices
.begin(), capVertices
.end());
279 indices
.insert(indices
.end(), capIndices
.begin(), capIndices
.end());
287 // the order of these vertices is important here, swapping them produces caps at the wrong side
288 vertices
[1].m_Position
,
289 vertices
[0].m_Position
,
290 // directional vector between centroids of first vertex pair and second vertex pair
291 (Centroid(vertices
[1], vertices
[0]) - Centroid(vertices
[3], vertices
[2])).Normalized(),
297 for (unsigned i
= 0; i
< capIndices
.size(); i
++)
298 capIndices
[i
] += vertices
.size();
300 vertices
.insert(vertices
.end(), capVertices
.begin(), capVertices
.end());
301 indices
.insert(indices
.end(), capIndices
.begin(), capIndices
.end());
304 ENSURE(indices
.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3
306 m_VB
= g_VBMan
.Allocate(sizeof(SVertex
), vertices
.size(), GL_STATIC_DRAW
, GL_ARRAY_BUFFER
);
307 if (m_VB
) // allocation might fail (e.g. due to too many vertices)
309 m_VB
->m_Owner
->UpdateChunkVertices(m_VB
, &vertices
[0]); // copy data into VBO
311 for (size_t k
= 0; k
< indices
.size(); ++k
)
312 indices
[k
] += m_VB
->m_Index
;
314 m_VBIndices
= g_VBMan
.Allocate(sizeof(u16
), indices
.size(), GL_STATIC_DRAW
, GL_ELEMENT_ARRAY_BUFFER
);
316 m_VBIndices
->m_Owner
->UpdateChunkVertices(m_VBIndices
, &indices
[0]);
321 void CTexturedLineRData::CreateLineCap(const SOverlayTexturedLine
& line
, const CVector3D
& corner1
, const CVector3D
& corner2
,
322 const CVector3D
& lineDirectionNormal
, SOverlayTexturedLine::LineCapType endCapType
, std::vector
<SVertex
>& verticesOut
,
323 std::vector
<u16
>& indicesOut
)
325 if (endCapType
== SOverlayTexturedLine::LINECAP_FLAT
)
326 return; // no action needed, this is the default
328 // When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
329 // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
330 // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
331 // That is to say, when viewed from the top, we will have something like
333 // this: and not like this: /|
340 int roundCapPoints
= 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
341 float radius
= line
.m_Thickness
;
343 CVector3D centerPoint
= (corner1
+ corner2
) * 0.5f
;
344 SVertex
centerVertex(centerPoint
, 0.5f
, 0.5f
);
345 u16 indexOffset
= verticesOut
.size(); // index offset in verticesOut from where we start adding our vertices
349 case SOverlayTexturedLine::LINECAP_SHARP
:
351 roundCapPoints
= 3; // creates only one point directly ahead
352 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)
353 centerVertex
.m_UVs
[0] = 0.480f
; // slight visual correction to make the texture match up better at the corner points
356 case SOverlayTexturedLine::LINECAP_ROUND
:
358 // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
359 // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
360 // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then
361 // of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in
362 // the line's plane, producing the desired rounded cap.
364 // To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from
365 // the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use
366 // the negated angle.
367 float stepAngle
= -(float)(M_PI
/(roundCapPoints
-1));
369 // Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards)
370 // Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector.
371 // This is because we want to support an overly large radius to make the sharp line ending look sharper.
372 verticesOut
.push_back(centerVertex
);
373 verticesOut
.push_back(SVertex(corner2
, 0.f
, 0.f
));
375 // Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points.
376 // Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom
377 // radii to support tuning the 'sharpness' of sharp end caps (see above)
378 CVector3D rotationBaseVector
= (corner2
- centerPoint
).Normalized() * radius
;
379 // Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that
380 // is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line.
381 // Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water,
382 // then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky).
383 CVector3D capPlaneNormal
= lineDirectionNormal
.Cross(rotationBaseVector
).Normalized();
385 for (int i
= 1; i
< roundCapPoints
- 1; ++i
)
387 // Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point.
388 CQuaternion quatRotation
;
389 quatRotation
.FromAxisAngle(capPlaneNormal
, i
* stepAngle
);
390 CVector3D worldPos3D
= centerPoint
+ quatRotation
.Rotate(rotationBaseVector
);
392 // 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
393 // of the texture around the edge of the semicircle)
395 float v
= clamp((i
/(float)(roundCapPoints
-1)), 0.f
, 1.f
); // pos, u, v
396 verticesOut
.push_back(SVertex(worldPos3D
, u
, v
));
399 // connect back to the other butt-end corner point to complete the semicircle
400 verticesOut
.push_back(SVertex(corner1
, 0.f
, 1.f
));
402 // now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the
403 // first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So:
404 for (int i
=1; i
< roundCapPoints
; ++i
)
406 indicesOut
.push_back(indexOffset
); // center vertex
407 indicesOut
.push_back(indexOffset
+ i
);
408 indicesOut
.push_back(indexOffset
+ i
+ 1);
413 case SOverlayTexturedLine::LINECAP_SQUARE
:
415 // Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of
416 // three triangles (sort of like a triangle fan)
417 // NOTE: The order in which the vertices are pushed out determines the visibility, as they
418 // are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom.
419 verticesOut
.push_back(centerVertex
);
420 verticesOut
.push_back(SVertex(corner2
, 0.f
, 0.f
));
421 verticesOut
.push_back(SVertex(corner2
+ (lineDirectionNormal
* (line
.m_Thickness
)), 0.f
, 0.33333f
)); // extend butt corner point 2 along the normal vector
422 verticesOut
.push_back(SVertex(corner1
+ (lineDirectionNormal
* (line
.m_Thickness
)), 0.f
, 0.66666f
)); // extend butt corner point 1 along the normal vector
423 verticesOut
.push_back(SVertex(corner1
, 0.f
, 1.0f
)); // push butt corner point 1
425 for (int i
=1; i
< 4; ++i
)
427 indicesOut
.push_back(indexOffset
); // center point
428 indicesOut
.push_back(indexOffset
+ i
);
429 indicesOut
.push_back(indexOffset
+ i
+ 1);