1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.geometry.examples.io.threed.obj;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.StringReader;
22 import java.io.UncheckedIOException;
23 import java.net.URL;
24
25 import org.apache.commons.geometry.core.GeometryTestUtils;
26 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
27 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
28 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
29 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
30 import org.apache.commons.geometry.euclidean.threed.Triangle3D;
31 import org.apache.commons.geometry.euclidean.threed.Vector3D;
32 import org.apache.commons.geometry.euclidean.threed.mesh.TriangleMesh;
33 import org.junit.Assert;
34 import org.junit.Test;
35
36 public class OBJReaderTest {
37
38 private static final double TEST_EPS = 1e-10;
39
40 private static final DoublePrecisionContext TEST_PRECISION =
41 new EpsilonDoublePrecisionContext(TEST_EPS);
42
43 private static final String CUBE_MINUS_SPHERE_MODEL = "/models/cube-minus-sphere.obj";
44
45 private static final int CUBE_MINUS_SPHERE_VERTICES = 1688;
46
47 private static final int CUBE_MINUS_SPHERE_FACES = 728;
48
49 private OBJReader reader = new OBJReader();
50
51 @Test
52 public void testReadMesh_emptyInput() throws Exception {
53
54 TriangleMesh mesh = reader.readTriangleMesh(new StringReader(""), TEST_PRECISION);
55
56
57 Assert.assertEquals(0, mesh.getVertexCount());
58 Assert.assertEquals(0, mesh.getFaceCount());
59 }
60
61 @Test
62 public void testReadMesh_mixedVertexIndexTypesAndWhitespace() throws Exception {
63
64 String input =
65 "#some comments \n\r\n \n" +
66 " # some other comments\n" +
67 "v 0.0 0.0 0.0\n" +
68 "v 1e-1 0 0 \r\n" +
69 " v 0 1 0\n" +
70 "\tv\t0 0 1\r\n" +
71 "f 1 2 3\n" +
72 " f -1 -2\t-3";
73
74
75 TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
76
77
78 Assert.assertEquals(4, mesh.getVertexCount());
79 Assert.assertEquals(2, mesh.getFaceCount());
80
81 Triangle3D t0 = mesh.getFace(0).getPolygon();
82 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
83 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.1, 0, 0), t0.getPoint2(), TEST_EPS);
84 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t0.getPoint3(), TEST_EPS);
85
86 Triangle3D t1 = mesh.getFace(1).getPolygon();
87 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 0, 1), t1.getPoint1(), TEST_EPS);
88 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t1.getPoint2(), TEST_EPS);
89 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.1, 0, 0), t1.getPoint3(), TEST_EPS);
90 }
91
92 @Test
93 public void testReadMesh_multipleFaceIndices_usesTriangleFan() throws Exception {
94
95 String input =
96 "v 0 0 0\n" +
97 "v 1 0 0\n" +
98 "v 1 1 0\n" +
99 "v 0.5 1.5 0\n" +
100 "v 0 1 0\n" +
101 "f 1 2 3 -2 -1\n";
102
103
104 TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
105
106
107 Assert.assertEquals(5, mesh.getVertexCount());
108 Assert.assertEquals(3, mesh.getFaceCount());
109
110 Triangle3D t0 = mesh.getFace(0).getPolygon();
111 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
112 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), t0.getPoint2(), TEST_EPS);
113 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 0), t0.getPoint3(), TEST_EPS);
114
115 Triangle3D t1 = mesh.getFace(1).getPolygon();
116 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t1.getPoint1(), TEST_EPS);
117 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 1, 0), t1.getPoint2(), TEST_EPS);
118 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1.5, 0), t1.getPoint3(), TEST_EPS);
119
120 Triangle3D t2 = mesh.getFace(2).getPolygon();
121 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t2.getPoint1(), TEST_EPS);
122 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1.5, 0), t2.getPoint2(), TEST_EPS);
123 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t2.getPoint3(), TEST_EPS);
124 }
125
126 @Test
127 public void testReadMesh_ignoresUnsupportedContent() throws Exception {
128
129 String input =
130 "mtllib abc.mtl\n" +
131 "nope\n" +
132 "v 0 0 0\n" +
133 "v 1 0 0\n" +
134 "v 0 1 0\n" +
135 "f 1/10/20 2//40 3//\n";
136
137
138 TriangleMesh mesh = reader.readTriangleMesh(new StringReader(input), TEST_PRECISION);
139
140
141 Assert.assertEquals(3, mesh.getVertexCount());
142 Assert.assertEquals(1, mesh.getFaceCount());
143
144 Triangle3D t0 = mesh.getFace(0).getPolygon();
145 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, t0.getPoint1(), TEST_EPS);
146 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 0, 0), t0.getPoint2(), TEST_EPS);
147 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0, 1, 0), t0.getPoint3(), TEST_EPS);
148 }
149
150 @Test
151 public void testReadMesh_invalidVertexDefinition() throws Exception {
152
153 String badNumber =
154 "v abc 0 0\n" +
155 "v 1 0 0\n" +
156 "v 0 1 0\n" +
157 "f 1 2 3\n";
158
159 String notEnoughVertices =
160 "v 0 0\n" +
161 "v 1 0 0\n" +
162 "v 0 1 0\n" +
163 "f 1 2 3\n";
164
165
166 GeometryTestUtils.assertThrows(() -> {
167 try {
168 reader.readTriangleMesh(new StringReader(badNumber), TEST_PRECISION);
169 } catch (IOException exc) {
170 throw new UncheckedIOException(exc);
171 }
172 }, NumberFormatException.class);
173
174 GeometryTestUtils.assertThrows(() -> {
175 try {
176 reader.readTriangleMesh(new StringReader(notEnoughVertices), TEST_PRECISION);
177 } catch (IOException exc) {
178 throw new UncheckedIOException(exc);
179 }
180 }, IllegalArgumentException.class, "Invalid vertex definition: at least 3 fields required but found only 2");
181 }
182
183 @Test
184 public void testReadMesh_invalidFaceDefinition() throws Exception {
185
186 String badNumber =
187 "v 0 0 0\n" +
188 "v 1 0 0\n" +
189 "v 0 1 0\n" +
190 "f 1 abc 3\n";
191
192 String notEnoughIndices =
193 "v 0 0 0\n" +
194 "v 1 0 0\n" +
195 "v 0 1 0\n" +
196 "f 1 2\n";
197
198
199 GeometryTestUtils.assertThrows(() -> {
200 try {
201 reader.readTriangleMesh(new StringReader(badNumber), TEST_PRECISION);
202 } catch (IOException exc) {
203 throw new UncheckedIOException(exc);
204 }
205 }, NumberFormatException.class);
206
207 GeometryTestUtils.assertThrows(() -> {
208 try {
209 reader.readTriangleMesh(new StringReader(notEnoughIndices), TEST_PRECISION);
210 } catch (IOException exc) {
211 throw new UncheckedIOException(exc);
212 }
213 }, IllegalArgumentException.class, "Invalid face definition: at least 3 fields required but found only 2");
214 }
215
216 @Test
217 public void testReadMesh_cubeMinusSphereFile() throws Exception {
218
219 URL url = getClass().getResource(CUBE_MINUS_SPHERE_MODEL);
220 File file = new File(url.toURI());
221
222
223 TriangleMesh mesh = reader.readTriangleMesh(file, TEST_PRECISION);
224
225
226 Assert.assertEquals(CUBE_MINUS_SPHERE_VERTICES, mesh.getVertexCount());
227 Assert.assertEquals(CUBE_MINUS_SPHERE_FACES, mesh.getFaceCount());
228
229 RegionBSPTree3D tree = RegionBSPTree3D.partitionedRegionBuilder()
230 .insertAxisAlignedGrid(mesh.getBounds(), 1, TEST_PRECISION)
231 .insertBoundaries(mesh)
232 .build();
233
234 double eps = 1e-5;
235 Assert.assertEquals(0.11509505362599505, tree.getSize(), eps);
236 EuclideanTestUtils.assertCoordinatesEqual(Vector3D.ZERO, tree.getCentroid(), TEST_EPS);
237 }
238
239 @Test
240 public void testReadMesh_cubeMinusSphereUrl() throws IOException {
241
242 URL url = getClass().getResource(CUBE_MINUS_SPHERE_MODEL);
243
244
245 TriangleMesh mesh = reader.readTriangleMesh(url, TEST_PRECISION);
246
247
248 Assert.assertEquals(CUBE_MINUS_SPHERE_VERTICES, mesh.getVertexCount());
249 Assert.assertEquals(CUBE_MINUS_SPHERE_FACES, mesh.getFaceCount());
250 }
251 }