File: test_Brush.h

package info (click to toggle)
0ad 0.0.26-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 130,460 kB
  • sloc: cpp: 261,824; ansic: 198,392; javascript: 19,067; python: 14,557; sh: 7,629; perl: 4,072; xml: 849; makefile: 741; java: 533; ruby: 229; php: 190; pascal: 30; sql: 21; tcl: 4
file content (183 lines) | stat: -rw-r--r-- 6,836 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/* Copyright (C) 2021 Wildfire Games.
 * This file is part of 0 A.D.
 *
 * 0 A.D. is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * 0 A.D. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "lib/self_test.h"

#include "maths/Brush.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Frustum.h"

class TestBrush : public CxxTest::TestSuite
{
public:
	void setUp()
	{
		CxxTest::setAbortTestOnFail(true);
	}

	void tearDown()
	{

	}

	void test_slice_empty_brush()
	{
		// verifies that the result of slicing an empty bound with a plane yields an empty bound
		CBrush brush;
		CPlane plane(CVector4D(0, 0, -1, 0.5f)); // can be anything, really

		CBrush result;
		brush.Slice(plane, result);
		TS_ASSERT(brush.IsEmpty());
	}

	void test_slice_plane_simple()
	{
		// slice a 1x1x1 cube vertically down the middle at z = 0.5, with the plane normal pointing towards the negative
		// end of the Z axis (i.e., anything with Z lower than 0.5 is considered 'in front of' the plane and is kept)
		CPlane plane(CVector4D(0, 0, -1, 0.5f));
		CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));

		CBrush result;
		brush.Slice(plane, result);

		// verify that the resulting brush consists of exactly our 8 expected, unique vertices
		TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
		size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
		size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
		size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 0.5f)); // right-bottom-back
		size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 0.5f)); // etc.
		size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
		size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
		size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 0.5f));
		size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 0.5f));

		// verify that the brush contains the six expected planes (one of which is the slicing plane)
		VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
		VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
		VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
		VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
		VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
		VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
	}

	void test_slice_plane_behind_brush()
	{
		// slice the (0,0,0) to (1,1,1) cube by the plane z = 1.5, with the plane normal pointing towards the negative
		// end of the Z axis (i.e. the entire cube is 'in front of' the plane and should be kept)
		CPlane plane(CVector4D(0, 0, -1, 1.5f));
		CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));

		CBrush result;
		brush.Slice(plane, result);

		// verify that the resulting brush consists of exactly our 8 expected, unique vertices
		TS_ASSERT_EQUALS((size_t)8, result.GetVertices().size());
		size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
		size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
		size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 1)); // right-bottom-back
		size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 1)); // etc.
		size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
		size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
		size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 1));
		size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 1));

		// verify that the brush contains the six expected planes (one of which is the slicing plane)
		VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
		VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
		VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
		VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
		VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
		VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
	}

	void test_slice_plane_in_front_of_brush()
	{
		// slices the (0,0,0) to (1,1,1) cube by the plane z = -0.5, with the plane normal pointing towards the negative
		// end of the Z axis (i.e. the entire cube is 'behind' the plane and should be cut away)
		CPlane plane(CVector4D(0, 0, -1, -0.5f));
		CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));

		CBrush result;
		brush.Slice(plane, result);

		TS_ASSERT_EQUALS((size_t)0, result.GetVertices().size());

		std::vector<std::vector<size_t> > faces;
		result.GetFaces(faces);

		TS_ASSERT_EQUALS((size_t)0, faces.size());
	}

private:
	size_t GetUniqueVertexIndex(const CBrush& brush, const CVector3D& vertex, float eps = 1e-6f)
	{
		std::vector<CVector3D> vertices = brush.GetVertices();

		for (size_t i = 0; i < vertices.size(); ++i)
		{
			const CVector3D& v = vertices[i];
			if (fabs(v.X - vertex.X) < eps
				&& fabs(v.Y - vertex.Y) < eps
				&& fabs(v.Z - vertex.Z) < eps)
				return i;
		}

		TS_FAIL("Vertex not found in brush");
		return ~0u;
	}

	void VerifyFacePresent(const CBrush& brush, int count, ...)
	{
		std::vector<size_t> face;

		va_list args;
		va_start(args, count);
		for (int x = 0; x < count; ++x)
			face.push_back(va_arg(args, size_t));
		va_end(args);

		if (face.size() == 0)
			return;

		std::vector<std::vector<size_t> > faces;
		brush.GetFaces(faces);

		// the brush is free to use any starting vertex along the face, and to use any winding order, so have 'face'
		// cycle through various starting values and see if any of them (or their reverse) matches one found in the brush.

		for (size_t c = 0; c < face.size() - 1; ++c)
		{
			std::vector<std::vector<size_t> >::iterator it1 = std::find(faces.begin(), faces.end(), face);
			if (it1 != faces.end())
				return;

			// no match, try the reverse
			std::vector<size_t> faceReverse = face;
			std::reverse(faceReverse.begin(), faceReverse.end());
			std::vector<std::vector<size_t> >::iterator it2 = std::find(faces.begin(), faces.end(), faceReverse);
			if (it2 != faces.end())
				return;

			// no match, cycle it
			face.erase(face.begin());
			face.push_back(face[0]);
		}

		TS_FAIL("Face not found in brush");
	}
};