Skip to content

Commit 9bc1a05

Browse files
fix issue where probuilder with quad topology instead of triangle would trigger out of bounds exception when being exported (#619)
* fix issue where probuilder with quad topology instead of triangle would trigger out of bounds exception when being exported * added changelog * fix following PR comments
1 parent c6ed2be commit 9bc1a05

File tree

3 files changed

+65
-39
lines changed

3 files changed

+65
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1212
- [PBLD-242] Fixed a bug where edges were being incorrectly selected when one or both vertices were behind the camera's near plane, causing flipped lines and inconsistent selection behavior.
1313
- [PBLD-226] Fixed a bug where ProBuilder faces could not selected when obscured by another GameObject
1414
- [PBLD-164] Fixed a bug with UV autostitching where the position offset would not take into account the face rotation center offset.
15+
- [PBLD-251] Fixed a bug which would cause out of bounds exceptions when exporting meshes with quad topology
1516
- [PBLD-253] Removes call to internal API that is being removed.
1617

1718
## [6.0.6] - 2025-07-01

Runtime/Core/ProBuilderMeshFunction.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -409,27 +409,37 @@ public void ToMesh(MeshTopology preferredTopology = MeshTopology.Triangles)
409409
// remove any indices that contain degenerate triangles
410410
int numBadIndices = 0;
411411
int[] indexes = submeshes[i].m_Indexes;
412-
for (var tri = 0; tri < indexes.Length; tri += 3)
412+
413+
if (submeshes[i].m_Topology == MeshTopology.Triangles && indexes.Length % 3 == 0)
413414
{
414-
Vector3 ab = positions[indexes[tri + 1]] - positions[indexes[tri]];
415-
Vector3 ac = positions[indexes[tri + 2]] - positions[indexes[tri]];
416-
if (Vector3.Cross(ab, ac).sqrMagnitude < Mathf.Epsilon)
417-
{
418-
numBadIndices += 3;
419-
}
420-
else
415+
for (var tri = 0; tri < indexes.Length; tri += 3)
421416
{
422-
submeshes[i].m_Indexes[tri - numBadIndices] = submeshes[i].m_Indexes[tri];
423-
submeshes[i].m_Indexes[tri - numBadIndices + 1] = submeshes[i].m_Indexes[tri + 1];
424-
submeshes[i].m_Indexes[tri - numBadIndices + 2] = submeshes[i].m_Indexes[tri + 2];
417+
if (tri + 2 >= indexes.Length ||
418+
indexes[tri] >= positions.Count ||
419+
indexes[tri + 1] >= positions.Count ||
420+
indexes[tri + 2] >= positions.Count)
421+
continue;
422+
423+
Vector3 ab = positions[indexes[tri + 1]] - positions[indexes[tri]];
424+
Vector3 ac = positions[indexes[tri + 2]] - positions[indexes[tri]];
425+
if (Vector3.Cross(ab, ac).sqrMagnitude < Mathf.Epsilon)
426+
{
427+
numBadIndices += 3;
428+
}
429+
else
430+
{
431+
indexes[tri - numBadIndices] = indexes[tri];
432+
indexes[tri - numBadIndices + 1] = indexes[tri + 1];
433+
indexes[tri - numBadIndices + 2] = indexes[tri + 2];
434+
}
425435
}
426436
}
427437

428438
int[] fixedIndices;
429439
if (numBadIndices > 0)
430440
{
431-
fixedIndices = new int[submeshes[i].m_Indexes.Length - numBadIndices];
432-
submeshes[i].m_Indexes.AsSpan(0, fixedIndices.Length).CopyTo(fixedIndices);
441+
fixedIndices = new int[indexes.Length - numBadIndices];
442+
Array.Copy(indexes, 0, fixedIndices, 0, fixedIndices.Length);
433443
}
434444
else
435445
{

Tests/Editor/Geometry/ProbuilderMeshDegenerateTriangleTest.cs

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,57 @@
77

88
public class ProbuilderMeshDegenerateTriangleTest
99
{
10-
private ProBuilderMesh m_Mesh;
11-
12-
[SetUp]
13-
public void SetUp()
10+
[Test]
11+
public void TestNormalsWithDegenerateTriangles()
1412
{
1513
// Create a Probuilder cube
16-
m_Mesh = ShapeFactory.Instantiate(typeof(Cube));
17-
}
14+
var mesh = ShapeFactory.Instantiate(typeof(Cube));
15+
try
16+
{
17+
// Collapse two of the shared vertices together.
18+
var positions = new List<Vector3>(mesh.positions);
19+
Vector3 svPosition = positions[mesh.sharedVertices[0][0]];
20+
for (int v = 0; v < mesh.sharedVertices[1].Count; ++v)
21+
{
22+
positions[mesh.sharedVertices[1][v]] = svPosition;
23+
}
1824

19-
[TearDown]
20-
public void TearDown()
21-
{
22-
// Clean up the created GameObject
23-
GameObject.DestroyImmediate(m_Mesh.gameObject);
25+
mesh.positions = positions.ToList();
26+
mesh.Rebuild();
27+
28+
// Check all the normals in use by faces to ensure none have invalid values.
29+
foreach (int index in mesh.mesh.triangles)
30+
{
31+
var normal = mesh.normals[index];
32+
Assert.IsFalse(float.IsNaN(normal.x) || float.IsNaN(normal.y) || float.IsNaN(normal.z), "Normals should not contain NaN values.");
33+
Assert.IsFalse(float.IsInfinity(normal.x) || float.IsInfinity(normal.y) || float.IsInfinity(normal.z), "Normals should not contain Infinite values.");
34+
Assert.IsFalse(normal == Vector3.zero, "Normals should not be zero vectors.");
35+
}
36+
}
37+
finally
38+
{
39+
// Clean up the created GameObject
40+
GameObject.DestroyImmediate(mesh.gameObject);
41+
}
2442
}
2543

2644
[Test]
27-
public void TestNormalsWithDegenerateTriangles()
45+
[Description("PBLD-251 : IndexOutOfRangeException appears in the Console when exporting certain ProBuilder meshes")]
46+
public void TestOriginalDoorBugScenario()
2847
{
29-
// Collapse two of the shared vertices together.
30-
var positions = new List<Vector3>(m_Mesh.positions);
31-
Vector3 svPosition = positions[m_Mesh.sharedVertices[0][0]];
32-
for (int v = 0; v < m_Mesh.sharedVertices[1].Count; ++v)
48+
// Recreate the specific Door shape bug scenario
49+
var door = ShapeFactory.Instantiate(typeof(Door));
50+
51+
try
3352
{
34-
positions[m_Mesh.sharedVertices[1][v]] = svPosition;
35-
}
36-
m_Mesh.positions = positions.ToList();
37-
m_Mesh.Rebuild();
53+
Assert.DoesNotThrow(() => door.ToMesh(MeshTopology.Quads));
3854

39-
// Check all the normals in use by faces to ensure none have invalid values.
40-
foreach (int index in m_Mesh.mesh.triangles)
55+
Assert.IsNotNull(door.mesh);
56+
Assert.Greater(door.mesh.vertexCount, 0);
57+
}
58+
finally
4159
{
42-
var normal = m_Mesh.normals[index];
43-
Assert.IsFalse(float.IsNaN(normal.x) || float.IsNaN(normal.y) || float.IsNaN(normal.z), "Normals should not contain NaN values.");
44-
Assert.IsFalse(float.IsInfinity(normal.x) || float.IsInfinity(normal.y) || float.IsInfinity(normal.z), "Normals should not contain Infinite values.");
45-
Assert.IsFalse(normal == Vector3.zero, "Normals should not be zero vectors.");
60+
GameObject.DestroyImmediate(door.gameObject);
4661
}
4762
}
4863
}

0 commit comments

Comments
 (0)