nicklockwood / Euclid

A Swift library for creating and manipulating 3D geometry
MIT License
639 stars 53 forks source link

Incorrect subtraction leading to additional fragments/paths #130

Open TemperMichael opened 1 day ago

TemperMichael commented 1 day ago

Hello, I am working on a game where you can cut holes into cubes where I add an additional mesh as a border as some kind of indicator.

If two holes overlap, I want to subtract the overlapping borders from each other, to create one big looking hole. This works in like 90% fine as you can see here:

IMG_0046

The problem is, sometimes there appear some strange fragments - they are only visible from one side, on the back they are invisible:

IMG_0045

Here is how I subtract them both from each other - I use a cube for the subtraction as I cutout three dimensional cubes, so when one hole is on the edge of the box, the border should be subtracted in three dimensions as well:

lastBorderMesh = lastBorderMesh
    .scaled(by: lastBorderScaleTransform)
    .rotated(by: lastBorderRotationTransform)
    .scaled(by: lastBorderParentScaleTransform)
    .translated(by: lastBorderTranslationTransform)
    .subtracting(
        Mesh.cube(size: 0.2, faces: .front)
            .scaled(by: scaleTransform)
            .rotated(by: rotationTransform)
            .scaled(by: secondScaleTransform)
            .translated(by: translationTransform)
    )
    .withMaterial(MaterialWrapper(borderMaterial))
    .translated(by: -lastBorderTranslationTransform)
    .scaled(by: Vector(1 / lastBorderParentScaleTransform.x, 1 / lastBorderParentScaleTransform.y, 1 /
lastBorderParentScaleTransform.z))
    .rotated(by: -lastBorderRotationTransform)
    .scaled(by: Vector(1 / lastBorderScaleTransform.x, 1 / lastBorderScaleTransform.y, 1 /
lastBorderScaleTransform.z))

currentBorderMesh = currentBorderMesh
    .scaled(by: scaleTransform)
    .rotated(by: rotationTransform)
    .scaled(by: secondScaleTransform)
    .translated(by: translationTransform)
    .subtracting(
        Mesh.cube(size: 0.16)
            .scaled(by: lastBorderScaleTransform)
            .rotated(by: lastBorderRotationTransform)
            .scaled(by: lastBorderParentScaleTransform)
            .translated(by: lastBorderTranslationTransform)
    )
    .translated(by: -translationTransform)
    .scaled(by: Vector(1 / secondScaleTransform.x, 1 / secondScaleTransform.y, 1 / secondScaleTransform.z))
    .rotated(by: -rotationTransform)
    .scaled(by: Vector(1 / scaleTransform.x, 1 / scaleTransform.y, 1 / scaleTransform.z))

Here is how I create the borders: My attempt is to place a cube with the size of the hole at the position where I want to cut out the hole, scale the original box by 1.1 and use the intersection of it with the hole sized cube to cut the outer part of the border, then doing the same with a 0.9 scaled size box to cut out the inner part of the border and lastly subtracting a 0.8 hole size cube to cut out the actual hole of the border (maybe there exists a better solution to that?)

    func createBorderMesh(
        for box: Mesh,
        translation: Vector,
        scale: Vector,
        rotation: Rotation,
        boxScale: Vector,
        material: RealityKit.Material
    ) -> Mesh {
        let outerHull = Mesh.cube(center: box.bounds.center, size: box.bounds.size.scaled(by: 1.1))
            .translated(by: -translation)
            .scaled(by: Vector(1 / boxScale.x, 1 / boxScale.y, 1 / boxScale.z))
            .rotated(by: -rotation)
            .scaled(by: Vector(1 / scale.x, 1 / scale.y, 1 / scale.z))

        let innerHull = Mesh.cube(center: box.bounds.center, size: box.bounds.size.scaled(by: 0.98))
            .translated(by: -translation)
            .scaled(by: Vector(1 / boxScale.x, 1 / boxScale.y, 1 / boxScale.z))
            .rotated(by: -rotation)
            .scaled(by: Vector(1 / scale.x, 1 / scale.y, 1 / scale.z))
        let innerBox = Mesh.cube(size: 0.16)
        let outerBox = Mesh.cube(size: 0.2)

        let final = outerHull
            .intersection(outerBox)
            .subtracting(innerBox)
            .subtracting(innerHull)
            .withMaterial(Euclid.MaterialWrapper(material))

        return final
    }

I already tried various attempts to find a solution on my side, but none of them worked, so I just wanted to make sure, that the problem is for sure on my side or if there is eventually a problem in the package?

The problem only occurs on device (Apple Vision Pro, visionOS 2.0) - I can also provide a TestFlight if this helps for debugging the issue.