Skip to content

Commit f82ba8d

Browse files
committed
Major change to the STL import and EXPORt methods
Attempting to make the STL import and export produce valid meshes by adding points to polygons to make the CSG is manifold.
1 parent 3f8f621 commit f82ba8d

File tree

9 files changed

+949
-702
lines changed

9 files changed

+949
-702
lines changed

src/main/java/eu/mihosoft/vrl/v3d/CSG.java

Lines changed: 484 additions & 319 deletions
Large diffs are not rendered by default.

src/main/java/eu/mihosoft/vrl/v3d/Plane.java

Lines changed: 119 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class Plane {
5252
* 0.00000001;
5353
*/
5454

55-
public static final double EPSILON = 1.0e-9;
55+
public static final double EPSILON = 1.0e-11;
5656
public static final double EPSILON_Point = EPSILON;
5757
public static final double EPSILON_duplicate = 1.0e-4;
5858
/**
@@ -102,35 +102,35 @@ public static Plane createFromPoints(List<Vertex> vertices) {
102102
Vector3d n = computeNormal(vertices);
103103
return new Plane(n, n.dot(a));
104104
}
105+
105106
public static Vector3d computeNormal(List<Vertex> vertices) {
106-
if (vertices == null || vertices.size() < 3) {
107-
return new Vector3d(0, 0, 1); // Default normal for degenerate cases
108-
}
109-
110-
// First attempt: Newell's method
111-
Vector3d normal = new Vector3d(0, 0, 0);
112-
int n = vertices.size();
113-
for (int i = 0; i < n; i++) {
114-
Vector3d current = vertices.get(i).pos;
115-
Vector3d next = vertices.get((i + 1) % n).pos;
116-
117-
double d = current.z + next.z;
118-
double e = current.z - next.z;
119-
double e2 = current.y - next.y;
120-
double f = current.x + next.x;
121-
double f2 = current.x - next.x;
122-
double g = current.y + next.y;
123-
124-
normal.x += e2 * d;
125-
normal.y += e * f;
126-
normal.z += f2 * g;
127-
}
128-
Vector3d normalized = normal.normalized();
129-
130-
if (isValidNormal(normalized, EPSILON)) {
131-
return normalized;
132-
}
133-
throw new RuntimeException("Mesh has problems, can not work around it");
107+
if (vertices == null || vertices.size() < 3) {
108+
return new Vector3d(0, 0, 1); // Default normal for degenerate cases
109+
}
110+
111+
// First attempt: Newell's method
112+
Vector3d normal = new Vector3d(0, 0, 0);
113+
int n = vertices.size();
114+
Vector3d lastValid = null;
115+
for (int i = 0; i < n; i++) {
116+
Vector3d current = vertices.get(i).pos;
117+
Vector3d next = vertices.get((i + 1) % n).pos;
118+
119+
// Correct Newell's Method formulas
120+
normal.x += (current.y - next.y) * (current.z + next.z); // (y1-y2)(z1+z2)
121+
normal.y += (current.z - next.z) * (current.x + next.x); // (z1-z2)(x1+x2)
122+
normal.z += (current.x - next.x) * (current.y + next.y);
123+
if (n >= 3) {
124+
Vector3d normalized = normal.normalized();
125+
if (isValidNormal(normalized, EPSILON / 10)) {
126+
lastValid = normalized;
127+
}
128+
}
129+
}
130+
if (isValidNormal(lastValid, EPSILON / 10)) {
131+
return lastValid;
132+
}
133+
throw new RuntimeException("Mesh has problems, can not work around it");
134134
// // Second attempt: Find three non-colinear points
135135
//
136136
// normal = findNormalFromNonColinearPoints(vertices);
@@ -151,104 +151,102 @@ public static Vector3d computeNormal(List<Vertex> vertices) {
151151
}
152152

153153
private static boolean isValidNormal(Vector3d normal, double epsilon) {
154-
if(Double.isFinite(normal.x)&&Double.isFinite(normal.y)&&Double.isFinite(normal.z)) {
155-
double lengthSquared = Math.abs(normal.length());
156-
return lengthSquared >= epsilon;
154+
if (Double.isFinite(normal.x) && Double.isFinite(normal.y) && Double.isFinite(normal.z)) {
155+
double lengthSquared = Math.abs(normal.length());
156+
return lengthSquared >= epsilon;
157157
}
158158
return false;
159159
}
160160

161161
private static Vector3d findNormalFromNonColinearPoints(List<Vertex> vertices) {
162-
int n = vertices.size();
163-
Vector3d firstPoint = vertices.get(0).pos;
164-
165-
// Try to find two vectors that aren't parallel
166-
for (int i = 1; i < n; i++) {
167-
Vector3d v1 = vertices.get(i).pos.minus(firstPoint);
168-
for (int j = i + 1; j < n; j++) {
169-
Vector3d v2 = vertices.get(j).pos.minus(firstPoint);
170-
Vector3d cross = v1.cross(v2);
171-
if (isValidNormal(cross, EPSILON)) {
172-
return cross.normalized();
173-
}
174-
}
175-
}
176-
return null;
162+
int n = vertices.size();
163+
Vector3d firstPoint = vertices.get(0).pos;
164+
165+
// Try to find two vectors that aren't parallel
166+
for (int i = 1; i < n; i++) {
167+
Vector3d v1 = vertices.get(i).pos.minus(firstPoint);
168+
for (int j = i + 1; j < n; j++) {
169+
Vector3d v2 = vertices.get(j).pos.minus(firstPoint);
170+
Vector3d cross = v1.cross(v2);
171+
if (isValidNormal(cross, EPSILON)) {
172+
return cross.normalized();
173+
}
174+
}
175+
}
176+
return null;
177177
}
178178

179179
private static Vector3d findPrincipalDirection(List<Vertex> vertices) {
180-
// Find the direction with maximum spread
181-
double maxX = Double.NEGATIVE_INFINITY;
182-
double minX = Double.POSITIVE_INFINITY;
183-
double maxY = Double.NEGATIVE_INFINITY;
184-
double minY = Double.POSITIVE_INFINITY;
185-
double maxZ = Double.NEGATIVE_INFINITY;
186-
double minZ = Double.POSITIVE_INFINITY;
187-
188-
for (Vertex vertex : vertices) {
189-
Vector3d pos = vertex.pos;
190-
maxX = Math.max(maxX, pos.x);
191-
minX = Math.min(minX, pos.x);
192-
maxY = Math.max(maxY, pos.y);
193-
minY = Math.min(minY, pos.y);
194-
maxZ = Math.max(maxZ, pos.z);
195-
minZ = Math.min(minZ, pos.z);
196-
}
197-
198-
double rangeX = maxX - minX;
199-
double rangeY = maxY - minY;
200-
double rangeZ = maxZ - minZ;
201-
202-
// Use the axis with minimum spread as normal direction
203-
if (rangeX <= rangeY && rangeX <= rangeZ) {
204-
return new Vector3d(1, 0, 0);
205-
} else if (rangeY <= rangeX && rangeY <= rangeZ) {
206-
return new Vector3d(0, 1, 0);
207-
} else {
208-
return new Vector3d(0, 0, 1);
209-
}
180+
// Find the direction with maximum spread
181+
double maxX = Double.NEGATIVE_INFINITY;
182+
double minX = Double.POSITIVE_INFINITY;
183+
double maxY = Double.NEGATIVE_INFINITY;
184+
double minY = Double.POSITIVE_INFINITY;
185+
double maxZ = Double.NEGATIVE_INFINITY;
186+
double minZ = Double.POSITIVE_INFINITY;
187+
188+
for (Vertex vertex : vertices) {
189+
Vector3d pos = vertex.pos;
190+
maxX = Math.max(maxX, pos.x);
191+
minX = Math.min(minX, pos.x);
192+
maxY = Math.max(maxY, pos.y);
193+
minY = Math.min(minY, pos.y);
194+
maxZ = Math.max(maxZ, pos.z);
195+
minZ = Math.min(minZ, pos.z);
196+
}
197+
198+
double rangeX = maxX - minX;
199+
double rangeY = maxY - minY;
200+
double rangeZ = maxZ - minZ;
201+
202+
// Use the axis with minimum spread as normal direction
203+
if (rangeX <= rangeY && rangeX <= rangeZ) {
204+
return new Vector3d(1, 0, 0);
205+
} else if (rangeY <= rangeX && rangeY <= rangeZ) {
206+
return new Vector3d(0, 1, 0);
207+
} else {
208+
return new Vector3d(0, 0, 1);
209+
}
210210
}
211211

212212
private static Vector3d determineStatisticalNormal(List<Vertex> vertices) {
213-
// Calculate center of mass
214-
Vector3d center = new Vector3d(0, 0, 0);
215-
for (Vertex vertex : vertices) {
216-
center = center.plus(vertex.pos);
217-
}
218-
center = center.times(1.0 / vertices.size());
219-
220-
// Calculate covariance matrix
221-
double[][] covariance = new double[3][3];
222-
for (Vertex vertex : vertices) {
223-
Vector3d diff = vertex.pos.minus(center);
224-
covariance[0][0] += diff.x * diff.x;
225-
covariance[0][1] += diff.x * diff.y;
226-
covariance[0][2] += diff.x * diff.z;
227-
covariance[1][1] += diff.y * diff.y;
228-
covariance[1][2] += diff.y * diff.z;
229-
covariance[2][2] += diff.z * diff.z;
230-
}
231-
covariance[1][0] = covariance[0][1];
232-
covariance[2][0] = covariance[0][2];
233-
covariance[2][1] = covariance[1][2];
234-
235-
// Use the eigenvector corresponding to the smallest eigenvalue
236-
// For simplicity, we'll use power iteration to find it
237-
Vector3d normal = new Vector3d(1, 1, 1);
238-
for (int i = 0; i < 10; i++) {
239-
normal = multiplyMatrixVector(covariance, normal);
240-
normal = normal.normalized();
241-
}
242-
243-
return normal;
213+
// Calculate center of mass
214+
Vector3d center = new Vector3d(0, 0, 0);
215+
for (Vertex vertex : vertices) {
216+
center = center.plus(vertex.pos);
217+
}
218+
center = center.times(1.0 / vertices.size());
219+
220+
// Calculate covariance matrix
221+
double[][] covariance = new double[3][3];
222+
for (Vertex vertex : vertices) {
223+
Vector3d diff = vertex.pos.minus(center);
224+
covariance[0][0] += diff.x * diff.x;
225+
covariance[0][1] += diff.x * diff.y;
226+
covariance[0][2] += diff.x * diff.z;
227+
covariance[1][1] += diff.y * diff.y;
228+
covariance[1][2] += diff.y * diff.z;
229+
covariance[2][2] += diff.z * diff.z;
230+
}
231+
covariance[1][0] = covariance[0][1];
232+
covariance[2][0] = covariance[0][2];
233+
covariance[2][1] = covariance[1][2];
234+
235+
// Use the eigenvector corresponding to the smallest eigenvalue
236+
// For simplicity, we'll use power iteration to find it
237+
Vector3d normal = new Vector3d(1, 1, 1);
238+
for (int i = 0; i < 10; i++) {
239+
normal = multiplyMatrixVector(covariance, normal);
240+
normal = normal.normalized();
241+
}
242+
243+
return normal;
244244
}
245245

246246
private static Vector3d multiplyMatrixVector(double[][] matrix, Vector3d vector) {
247-
return new Vector3d(
248-
matrix[0][0] * vector.x + matrix[0][1] * vector.y + matrix[0][2] * vector.z,
249-
matrix[1][0] * vector.x + matrix[1][1] * vector.y + matrix[1][2] * vector.z,
250-
matrix[2][0] * vector.x + matrix[2][1] * vector.y + matrix[2][2] * vector.z
251-
);
247+
return new Vector3d(matrix[0][0] * vector.x + matrix[0][1] * vector.y + matrix[0][2] * vector.z,
248+
matrix[1][0] * vector.x + matrix[1][1] * vector.y + matrix[1][2] * vector.z,
249+
matrix[2][0] * vector.x + matrix[2][1] * vector.y + matrix[2][2] * vector.z);
252250
}
253251
// public static Vector3d computeNormal(List<Vertex> vertices) {
254252
// Vector3d normal = new Vector3d(0, 0, 0);
@@ -346,11 +344,13 @@ public void splitPolygon(Polygon polygon, List<Polygon> coplanarFront, List<Poly
346344
for (int i = 0; i < polygon.vertices.size(); i++) {
347345
double t = polygon.plane.getNormal().dot(polygon.vertices.get(i).pos) - polygon.plane.getDist();
348346
if (t > posEpsilon) {
349-
// com.neuronrobotics.sdk.common.Log.error("Non flat polygon, increasing positive epsilon "+t);
347+
// com.neuronrobotics.sdk.common.Log.error("Non flat polygon, increasing
348+
// positive epsilon "+t);
350349
posEpsilon = t + Plane.EPSILON;
351350
}
352351
if (t < negEpsilon) {
353-
// com.neuronrobotics.sdk.common.Log.error("Non flat polygon, decreasing negative epsilon "+t);
352+
// com.neuronrobotics.sdk.common.Log.error("Non flat polygon, decreasing
353+
// negative epsilon "+t);
354354
negEpsilon = t - Plane.EPSILON;
355355
}
356356
}
@@ -411,12 +411,12 @@ else if (somePointsInBack) {
411411
if (f.size() >= 3) {
412412
try {
413413
front.add(new Polygon(f, polygon.getStorage()).setColor(polygon.getColor()));
414-
} catch (NumberFormatException ex) {
415-
//ex.printStackTrace();
414+
} catch (Exception ex) {
415+
System.err.println("Pruning bad polygon Plane::splitPolygon");
416416
// skip adding broken polygon here
417417
}
418418
} else {
419-
//com.neuronrobotics.sdk.common.Log.error("Front Clip Fault!");
419+
// com.neuronrobotics.sdk.common.Log.error("Front Clip Fault!");
420420
}
421421
if (b.size() >= 3) {
422422
try {
@@ -426,7 +426,7 @@ else if (somePointsInBack) {
426426
System.err.println("Pruning bad polygon Plane::splitPolygon");
427427
}
428428
} else {
429-
//com.neuronrobotics.sdk.common.Log.error("Back Clip Fault!");
429+
// com.neuronrobotics.sdk.common.Log.error("Back Clip Fault!");
430430
}
431431
break;
432432
}

0 commit comments

Comments
 (0)