Unexpected results from multiple subtract operations on native SCNBox
tonyskansf opened this issue · 6 comments
Hi, I'm getting weird results from multiple subtract
operations between two nodes created from the SCNBox
geometry—there are always some polygons from the RHS mesh remaining in the resulting mesh after the subtraction. This, however, does not happen when Euclid's Mesh.cube
is used.
I believe the below example should explain what's happening. The example is simplified. I'm facing the same issue with custom nodes created from custom SCNGeometry
.
Example: The loop simulates a real-time node movement (position changes) and subtraction.
Unexpected results:
// LHS node
let box1 = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
box1.firstMaterial?.diffuse.contents = UIColor.orange
let node1 = SCNNode(geometry: box1)
node1.position = SCNVector3(0, 0, -0.4)
sceneView.scene.rootNode.addChildNode(node1)
// RHS node
let box2 = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
let node2 = SCNNode(geometry: box2)
for i in 0...6 {
let lhs = Mesh(node1).translated(by: Vector(node1.position))
let rhs = Mesh(node2).translated(by: Vector(0, 0.12 - 0.01 * Double(i), -0.3)) // simulate movement in y-axis
let subtracted = lhs.subtract(rhs)
node1.geometry = SCNGeometry(subtracted.translated(by: -1 * Vector(node1.position)))
}
Works fine with Euclid's Mesh.cube
:
// LHS node
let box1 = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
box1.firstMaterial?.diffuse.contents = UIColor.orange
let node1 = SCNNode(geometry: box1)
node1.position = SCNVector3(0, 0, -0.4)
sceneView.scene.rootNode.addChildNode(node1)
for i in 0...6 {
let lhs = Mesh.cube(size: Vector(0.2, 0.2, 0.2), material: UIColor.orange).translated(by: Vector(node1.position))
let rhs = Mesh.cube(size: 0.1).translated(by: Vector(0, 0.12 - 0.01 * Double(i), -0.3))
let subtracted = lhs.subtract(rhs)
node1.geometry = SCNGeometry(subtracted.translated(by: -1 * Vector(node1.position)))
}
Thanks for the detailed report - I'll look into it.
I did some digging and noticed this with the mesh created from the SCNBox
geometry:
The polygons share vertices; however, in some cases, these (visually same) vertices have different values in their x, y, or z position
coordinate. Although those values vary, they visually represent the same vertex.
-
For example: Both vertices were created from the
Mesh.init
and from whatever data was in the geometrySCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
.// A shared vertex between two polygons. let vertexA = Vector(-0.10000000149, 0.10000000149, -0.10000000149) let vertexB = Vector(-0.10000000149, 0.099999986589, -0.10000001639099999)
vertexA == vertexB
would returnfalse
.vertexA.isEqual(to: vertexB)
would returnfalse
.vertexA.isEqual(to: vertexB, withPrecision: 1e-7)
would returntrue
.
-
Also: There is a shared edge
LineSegment
in the highlighted triangles but treated as non-equal due to the difference instart
andend
.
(It seems like this is the reason why polygons.areWatertight
would return false
for this geometry even though the mesh is watertight as every edge is attached to at least two polygons.)
Not sure if it helps anything, but I thought it might have something to do with how polygons/vertices are compared during the subtraction algorithm (?).
I'd like to look into this more too, so if you have any idea where the problem might root from, I'm more than happy to help debug this.
@tonyskansf AFAICT the issue is with the actual vertices produced by SceneKit. If I reduce the precision
value in Utilities.quantize()
down to 1e-7
it solves the problem, but that's not really a good solution as it breaks other models with fine details.
I'm not really sure what to do about it. I could add a special case to produce clean data for SCNBox
, but that wouldn't solve the general problem. Maybe I need to add some code to clean up vertex data generally.
Hmm, it seems like reducing epsilon
to 1e-7
also solves the problem, and is a slightly less terrible solution. I'm still not very happy with simply tweaking magic numbers as a solution though, since there are presumably other cases which this would either break or fail to solve.
Just as you said, reducing the numbers might help in this specific case, but is breaking other cases. I still have two input meshes that are watertight but the resulting mesh after subtract
is not even though there is no reason not to be. Not sure however how to proceed further with the issue.
@tonyskansf this should be fixed in 0.5.19. I've added some additional logic to makeWatertight()
that merges vertices that are close but not exactly equal, such as those in the SCN primitives.