source: ps/trunk/source/maths/Brush.cpp@ 25159

Last change on this file since 25159 was 25159, checked in by Vladislav Belov, 3 years ago

Moves Frustum from graphics to maths to more related geometric primitives like bounding ones.

  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
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/*
19 * Implementation of CBrush, a class representing a convex object
20 */
21
22#include "precompiled.h"
23
24#include "Brush.h"
25
26#include "graphics/ShaderProgram.h"
27#include "maths/BoundingBoxAligned.h"
28#include "maths/Frustum.h"
29#include "lib/ogl.h"
30
31#include <float.h>
32
33
34///////////////////////////////////////////////////////////////////////////////
35// Convert the given bounds into a brush
36CBrush::CBrush(const CBoundingBoxAligned& bounds)
37{
38 m_Vertices.resize(8);
39
40 for(size_t i = 0; i < 8; ++i)
41 {
42 m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0]; // X
43 m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1]; // Y
44 m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2]; // Z
45 }
46
47 // construct cube face indices, 5 vertex indices per face (start vertex included twice)
48
49 m_Faces.resize(30);
50
51 m_Faces[0] = 0; m_Faces[1] = 1; m_Faces[2] = 3; m_Faces[3] = 2; m_Faces[4] = 0; // Z = min
52 m_Faces[5] = 4; m_Faces[6] = 5; m_Faces[7] = 7; m_Faces[8] = 6; m_Faces[9] = 4; // Z = max
53
54 m_Faces[10] = 0; m_Faces[11] = 2; m_Faces[12] = 6; m_Faces[13] = 4; m_Faces[14] = 0; // X = min
55 m_Faces[15] = 1; m_Faces[16] = 3; m_Faces[17] = 7; m_Faces[18] = 5; m_Faces[19] = 1; // X = max
56
57 m_Faces[20] = 0; m_Faces[21] = 1; m_Faces[22] = 5; m_Faces[23] = 4; m_Faces[24] = 0; // Y = min
58 m_Faces[25] = 2; m_Faces[26] = 3; m_Faces[27] = 7; m_Faces[28] = 6; m_Faces[29] = 2; // Y = max
59}
60
61
62///////////////////////////////////////////////////////////////////////////////
63// Calculate bounds of this brush
64void CBrush::Bounds(CBoundingBoxAligned& result) const
65{
66 result.SetEmpty();
67
68 for(size_t i = 0; i < m_Vertices.size(); ++i)
69 result += m_Vertices[i];
70}
71
72
73///////////////////////////////////////////////////////////////////////////////
74// Cut the brush according to a given plane
75
76/// Holds information about what happens to a single vertex in a brush during a slicing operation.
77struct SliceOpVertexInfo
78{
79 float planeDist; ///< Signed distance from this vertex to the slicing plane.
80 size_t resIdx; ///< Index of this vertex in the resulting brush (or NO_VERTEX if cut away)
81};
82
83/// Holds information about a newly introduced vertex on an edge in a brush as the result of a slicing operation.
84struct SliceOpNewVertexInfo
85{
86 /// Indices of adjacent edge vertices in original brush
87 size_t edgeIdx1, edgeIdx2;
88 /// Index of newly introduced vertex in resulting brush
89 size_t resIdx;
90
91 /**
92 * Index into SliceOpInfo.nvInfo; hold the indices of this new vertex's direct neighbours in the slicing plane face,
93 * with no consistent winding direction around the face for either field (e.g., the neighb1 of X can point back to
94 * X with either its neighb1 or neighb2).
95 */
96 size_t neighbIdx1, neighbIdx2;
97};
98
99/// Holds support information during a CBrush/CPlane slicing operation.
100struct SliceOpInfo
101{
102 CBrush* result;
103 const CBrush* original;
104
105 /**
106 * Holds information about what happens to each vertex in the original brush after the slice operation.
107 * Same size as m_Vertices of the brush getting sliced.
108 */
109 std::vector<SliceOpVertexInfo> ovInfo;
110
111 /// Holds information about newly inserted vertices during a slice operation.
112 std::vector<SliceOpNewVertexInfo> nvInfo;
113
114 /**
115 * Indices into nvInfo; during the execution of the slicing algorithm, holds the previously inserted new vertex on
116 * one of the edges of the face that's currently being evaluated for slice points, or NO_VERTEX if no such vertex
117 * exists.
118 */
119 size_t thisFaceNewVertexIdx;
120};
121
122struct CBrush::Helper
123{
124 /**
125 * Creates a new vertex between the given two vertices (indexed into the original brush).
126 * Returns the index of the new vertex in the resulting brush.
127 */
128 static size_t SliceNewVertex(SliceOpInfo& sliceInfo, size_t v1, size_t v2);
129};
130
131size_t CBrush::Helper::SliceNewVertex(SliceOpInfo& sliceOp, size_t edgeIdx1, size_t edgeIdx2)
132{
133 // check if a new vertex has already been inserted on this edge
134 size_t idx;
135 for(idx = 0; idx < sliceOp.nvInfo.size(); ++idx)
136 {
137 if ((sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx1 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx2) ||
138 (sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx2 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx1))
139 break;
140 }
141
142 if (idx >= sliceOp.nvInfo.size())
143 {
144 // no previously inserted new vertex found on this edge; insert a new one
145 SliceOpNewVertexInfo nvi;
146 CVector3D newPos;
147
148 // interpolate between the two vertices based on their distance from the plane
149 float inv = 1.0 / (sliceOp.ovInfo[edgeIdx1].planeDist - sliceOp.ovInfo[edgeIdx2].planeDist);
150 newPos = sliceOp.original->m_Vertices[edgeIdx2] * ( sliceOp.ovInfo[edgeIdx1].planeDist * inv) +
151 sliceOp.original->m_Vertices[edgeIdx1] * (-sliceOp.ovInfo[edgeIdx2].planeDist * inv);
152
153 nvi.edgeIdx1 = edgeIdx1;
154 nvi.edgeIdx2 = edgeIdx2;
155 nvi.resIdx = sliceOp.result->m_Vertices.size();
156 nvi.neighbIdx1 = NO_VERTEX;
157 nvi.neighbIdx2 = NO_VERTEX;
158
159 sliceOp.result->m_Vertices.push_back(newPos);
160 sliceOp.nvInfo.push_back(nvi);
161 }
162
163 // at this point, 'idx' is the index into nvInfo of the vertex inserted onto the edge
164
165 if (sliceOp.thisFaceNewVertexIdx != NO_VERTEX)
166 {
167 // a vertex has been previously inserted onto another edge of this face; link them together as neighbours
168 // (using whichever one of the neighbIdx1 or -2 links is still available)
169
170 if (sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 == NO_VERTEX)
171 sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 = idx;
172 else
173 sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx2 = idx;
174
175 if (sliceOp.nvInfo[idx].neighbIdx1 == NO_VERTEX)
176 sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.thisFaceNewVertexIdx;
177 else
178 sliceOp.nvInfo[idx].neighbIdx2 = sliceOp.thisFaceNewVertexIdx;
179
180 // a plane should slice a face only in two locations, so reset for the next face
181 sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
182 }
183 else
184 {
185 // store the index of the inserted vertex on this edge, so that we can retrieve it when the plane slices
186 // this face again in another edge
187 sliceOp.thisFaceNewVertexIdx = idx;
188 }
189
190 return sliceOp.nvInfo[idx].resIdx;
191}
192
193void CBrush::Slice(const CPlane& plane, CBrush& result) const
194{
195 ENSURE(&result != this);
196
197 SliceOpInfo sliceOp;
198
199 sliceOp.original = this;
200 sliceOp.result = &result;
201 sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
202 sliceOp.ovInfo.resize(m_Vertices.size());
203 sliceOp.nvInfo.reserve(m_Vertices.size() / 2);
204
205 result.m_Vertices.resize(0); // clear any left-overs
206 result.m_Faces.resize(0);
207 result.m_Vertices.reserve(m_Vertices.size() + 2);
208 result.m_Faces.reserve(m_Faces.size() + 5);
209
210 // Copy vertices that weren't sliced away by the plane to the resulting brush.
211 for(size_t i = 0; i < m_Vertices.size(); ++i)
212 {
213 const CVector3D& vtx = m_Vertices[i]; // current vertex
214 SliceOpVertexInfo& vtxInfo = sliceOp.ovInfo[i]; // slicing operation info about current vertex
215
216 vtxInfo.planeDist = plane.DistanceToPlane(vtx);
217 if (vtxInfo.planeDist >= 0.0)
218 {
219 // positive side of the plane; not sliced away
220 vtxInfo.resIdx = result.m_Vertices.size();
221 result.m_Vertices.push_back(vtx);
222 }
223 else
224 {
225 // other side of the plane; sliced away
226 vtxInfo.resIdx = NO_VERTEX;
227 }
228 }
229
230 // Transfer faces. (Recall how faces are specified; see CBrush::m_Faces). The idea is to examine each face separately,
231 // and see where its edges cross the slicing plane (meaning that exactly one of the vertices of that edge was cut away).
232 // On those edges, new vertices are introduced where the edge intersects the plane, and the resulting brush's m_Faces
233 // array is updated to refer to the newly inserted vertices instead of the original one that got cut away.
234
235 size_t currentFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the original brush
236 size_t resultFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the resulting brush
237
238 for(size_t i = 0; i < m_Faces.size(); ++i)
239 {
240 if (currentFaceStartIdx == NO_VERTEX)
241 {
242 // starting a new face
243 ENSURE(sliceOp.thisFaceNewVertexIdx == NO_VERTEX);
244
245 currentFaceStartIdx = m_Faces[i];
246 resultFaceStartIdx = result.m_Faces.size();
247 continue;
248 }
249
250 size_t prevIdx = m_Faces[i-1]; // index of previous vertex in this face list
251 size_t curIdx = m_Faces[i]; // index of current vertex in this face list
252
253 if (sliceOp.ovInfo[prevIdx].resIdx == NO_VERTEX)
254 {
255 // previous face vertex got sliced away by the plane; see if the edge (prev,current) crosses the slicing plane
256 if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
257 {
258 // re-entering the front side of the plane; insert vertex on intersection of plane and (prev,current) edge
259 result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
260 result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
261 }
262 }
263 else
264 {
265 // previous face vertex didn't get sliced away; see if the edge (prev,current) crosses the slicing plane
266 if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
267 {
268 // perfectly normal edge; doesn't cross the plane
269 result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
270 }
271 else
272 {
273 // leaving the front side of the plane; insert vertex on intersection of plane and edge (prev, current)
274 result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
275 }
276 }
277
278 // if we're back at the first vertex of the current face, then we've completed the face
279 if (curIdx == currentFaceStartIdx)
280 {
281 // close the index loop
282 if (result.m_Faces.size() > resultFaceStartIdx)
283 result.m_Faces.push_back(result.m_Faces[resultFaceStartIdx]);
284
285 currentFaceStartIdx = NO_VERTEX; // start a new face
286 }
287 }
288
289 ENSURE(currentFaceStartIdx == NO_VERTEX);
290
291 // Create the face that lies in the slicing plane. Remember, all the intersections of the slicing plane with face
292 // edges of the brush have been stored in sliceOp.nvInfo by the SliceNewVertex function, and refer to their direct
293 // neighbours in the slicing plane face using the neighbIdx1 and neighbIdx2 fields (in no consistent winding order).
294
295 if (sliceOp.nvInfo.size())
296 {
297 // push the starting vertex
298 result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
299
300 // At this point, there is no consistent winding order in the neighbX fields, so at each vertex we need to figure
301 // out whether neighb1 or neighb2 points 'onwards' along the face, according to an initially chosen winding direction.
302 // (or, equivalently, which one points back to the one we were just at). At each vertex, we then set neighb1 to be the
303 // one to point onwards, deleting any pointers which we no longer need to complete the trace.
304
305 size_t idx;
306 size_t prev = 0;
307
308 idx = sliceOp.nvInfo[0].neighbIdx2; // pick arbitrary starting direction
309 sliceOp.nvInfo[0].neighbIdx2 = NO_VERTEX;
310
311 while(idx != 0)
312 {
313 ENSURE(idx < sliceOp.nvInfo.size());
314 if (idx >= sliceOp.nvInfo.size())
315 break;
316
317 if (sliceOp.nvInfo[idx].neighbIdx1 == prev)
318 {
319 // neighb1 is pointing the wrong way; we want to normalize it to point onwards in the direction
320 // we initially chose, so swap it with neighb2 and delete neighb2 (no longer needed)
321 sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.nvInfo[idx].neighbIdx2;
322 sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
323 }
324 else
325 {
326 // neighb1 isn't pointing to the previous vertex, so neighb2 must be (otherwise a pair of vertices failed to
327 // get paired properly during face/plane slicing).
328 ENSURE(sliceOp.nvInfo[idx].neighbIdx2 == prev);
329 sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
330 }
331
332 result.m_Faces.push_back(sliceOp.nvInfo[idx].resIdx);
333
334 // move to next vertex; neighb1 has been normalized to point onward
335 prev = idx;
336 idx = sliceOp.nvInfo[idx].neighbIdx1;
337 sliceOp.nvInfo[prev].neighbIdx1 = NO_VERTEX; // no longer needed, we've moved on
338 }
339
340 // push starting vertex again to close the shape
341 result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
342 }
343}
344
345
346
347///////////////////////////////////////////////////////////////////////////////
348// Intersect with frustum by repeated slicing
349void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
350{
351 ENSURE(&result != this);
352
353 if (!frustum.GetNumPlanes())
354 {
355 result = *this;
356 return;
357 }
358
359 CBrush buf;
360 const CBrush* prev = this;
361 CBrush* next;
362
363 // Repeatedly slice this brush with each plane of the frustum, alternating between 'result' and 'buf' to
364 // save intermediate results. Set up the starting brush so that the final version always ends up in 'result'.
365
366 if (frustum.GetNumPlanes() & 1)
367 next = &result;
368 else
369 next = &buf;
370
371 for(size_t i = 0; i < frustum.GetNumPlanes(); ++i)
372 {
373 prev->Slice(frustum[i], *next);
374 prev = next;
375 if (prev == &buf)
376 next = &result;
377 else
378 next = &buf;
379 }
380
381 ENSURE(prev == &result);
382}
383std::vector<CVector3D> CBrush::GetVertices() const
384{
385 return m_Vertices;
386}
387
388void CBrush::GetFaces(std::vector<std::vector<size_t> >& out) const
389{
390 // split the back-to-back faces into separate face vectors, so that they're in a
391 // user-friendlier format than the back-to-back vertex index array
392 // i.e. split 'x--xy------yz----z' into 'x--x', 'y-------y', 'z---z'
393
394 size_t faceStartIdx = 0;
395 while (faceStartIdx < m_Faces.size())
396 {
397 // start new face
398 std::vector<size_t> singleFace;
399 singleFace.push_back(m_Faces[faceStartIdx]);
400
401 // step over all the values in the face until we hit the starting value again (which closes the face)
402 size_t j = faceStartIdx + 1;
403 while (j < m_Faces.size() && m_Faces[j] != m_Faces[faceStartIdx])
404 {
405 singleFace.push_back(m_Faces[j]);
406 j++;
407 }
408
409 // each face must be closed by the same value that started it
410 ENSURE(m_Faces[faceStartIdx] == m_Faces[j]);
411
412 singleFace.push_back(m_Faces[j]);
413 out.push_back(singleFace);
414
415 faceStartIdx = j + 1;
416 }
417}
418
419void CBrush::Render(CShaderProgramPtr& shader) const
420{
421 std::vector<float> data;
422
423 std::vector<std::vector<size_t> > faces;
424 GetFaces(faces);
425
426#define ADD_VERT(a) \
427 STMT( \
428 data.push_back(u); \
429 data.push_back(v); \
430 data.push_back(m_Vertices[faces[i][a]].X); \
431 data.push_back(m_Vertices[faces[i][a]].Y); \
432 data.push_back(m_Vertices[faces[i][a]].Z); \
433 )
434
435 for (size_t i = 0; i < faces.size(); ++i)
436 {
437 // Triangulate into (0,1,2), (0,2,3), ...
438 for (size_t j = 1; j < faces[i].size() - 2; ++j)
439 {
440 float u = 0;
441 float v = 0;
442 ADD_VERT(0);
443 ADD_VERT(j);
444 ADD_VERT(j+1);
445 }
446 }
447
448#undef ADD_VERT
449
450 shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
451 shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
452
453 shader->AssertPointersBound();
454 glDrawArrays(GL_TRIANGLES, 0, data.size() / 5);
455}
456
457void CBrush::RenderOutline(CShaderProgramPtr& shader) const
458{
459 std::vector<float> data;
460
461 std::vector<std::vector<size_t> > faces;
462 GetFaces(faces);
463
464#define ADD_VERT(a) \
465 STMT( \
466 data.push_back(u); \
467 data.push_back(v); \
468 data.push_back(m_Vertices[faces[i][a]].X); \
469 data.push_back(m_Vertices[faces[i][a]].Y); \
470 data.push_back(m_Vertices[faces[i][a]].Z); \
471 )
472
473 for (size_t i = 0; i < faces.size(); ++i)
474 {
475 for (size_t j = 0; j < faces[i].size() - 1; ++j)
476 {
477 float u = 0;
478 float v = 0;
479 ADD_VERT(j);
480 ADD_VERT(j+1);
481 }
482 }
483
484#undef ADD_VERT
485
486 shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
487 shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
488
489 shader->AssertPointersBound();
490 glDrawArrays(GL_LINES, 0, data.size() / 5);
491}
Note: See TracBrowser for help on using the repository browser.