[SWIFT] Bodenkollaps mit ARKit + SceneKit

Ich habe die Kombination von & Portal versucht, um das virtuelle Objekt zu texturieren, mit dem die Kameraerfassung durchgeführt wird. Lassen Sie uns den Boden als Thema zusammenbrechen.

Vollständiges Bild demo.pngdemo.gif Wenn das aufgenommene Bild der Kamera auf diese Weise auf das virtuelle Objekt eingefügt werden kann, ist es möglich, den Boden stufenförmig zu gestalten, die Wand zu öffnen und als Tür zu verwenden usw.

Wie man den Boden zum Einsturz bringt

(1) Bereiten Sie ein kastenförmiges 3D-Modell (im Folgenden "Gitter") vor, das in den Boden eingebettet werden soll. Eine kleine Box (im Folgenden als "Zelle" bezeichnet) ist in einer Größe von 10 x 10 oben auf dem Gitter verteilt. Stellen Sie sicher, dass die Zellen zusammenfallen und sich am unteren Rand des Rasters sammeln. (2) Führen Sie bei aktivierter Ebenenerkennung von ARKit zum Zeitpunkt des Tippens auf den Bildschirm einen Treffertest in der Mitte des Bildschirms durch. (3) Erhalten Sie die Transformation der Ebene, die im Treffertest erhalten wurde, und setzen Sie sie in das Gitter. Das Gitter passt zur Trefferposition und -haltung. ④ Nehmen Sie das Kamerabild auf ⑤ Konvertieren Sie die Eckpunkte (Weltkoordinaten der vier Ecken) jeder Zellenoberfläche in Bildschirmkoordinaten (normalisierte Gerätekoordinaten). ⑥ Da ④ als Zelltextur verwendet wird, werden die normalisierten Gerätekoordinaten von ⑤ in UV-Koordinaten konvertiert. ⑦ Stellen Sie ⑥ für die Texturkoordinaten jeder Zelle ein ⑧ Stellen Sie den SCN-Physikkörper vom Knoten der mittleren Zelle nach außen so ein, dass die 10x10-Zelle von der Mitte aus zusammenfällt.

Die Punkte werden unten beschrieben.

(1) Bereiten Sie ein kastenförmiges 3D-Modell (im Folgenden "Gitter") vor, das in den Boden eingebettet werden soll.

![IMG_0006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71605/28045ace-df5a-c12c-b098-266888b1f4cc.png) Die obige Abbildung zeigt die Anordnung der Knoten. Tatsächlich ist diese Box etwas Besonderes, da der Hintergrund (Kamerabild) so gesehen werden kann, wie er ist, wenn die Box von außen betrachtet wird, und das Innere der Box richtig gesehen werden kann, wenn die Box von innen betrachtet wird (ein solcher Ausdruck ist "überall Tür"). Es scheint ein "Portal" genannt zu werden. Verwenden Sie dazu die Methode dieses Artikels ["SCNNode-Rendering-Reihenfolge von SceneKit an einem türähnlichen Ausdruck"](https://appleengine.hatenablog.com/entry/2017/11/28/175040).

"⑤ Konvertieren Sie die Eckpunkte (Weltkoordinaten der vier Ecken) jeder Zelloberfläche in Bildschirmkoordinaten (normalisierte Gerätekoordinaten)." + "Da ⑥④ als Zelltextur verwendet wird, konvertieren Sie die normalisierten Gerätekoordinaten von ⑤ in UV-Koordinaten."

Verwendung des aufgenommenen Kamerabildes als Textur. IMG_0010.png

Stellen Sie für die Textur jeder Zelle das aufgenommene Bild so ein, wie es in der obigen Abbildung gezeigt ist. Für alle Zellen wird das gleiche Bild verwendet, und die UV-Koordinaten (rote Punkte) der vier Ecken der Zelloberfläche sind für jede Zelle unterschiedlich. Die Methode zum Erhalten der Scheitelpunktkoordinaten von Zellen auf dem Bild entspricht der normalen Wiedergabe eines 3D-Modells. Weltkoordinaten jedes Scheitelpunkts der Zelle → Ansichtskonvertierung → Projektionskonvertierung.

//Modelltransformationsmatrix (Zelloberflächenknoten im Weltkoordinatensystem)
let modelTransform = cellFaceNode.simdWorldTransform
//Transformationsmatrix anzeigen. Inverse Matrix des Kamera-Ansichtspunkts (Verschieben Sie alle Weltkoordinatenscheitelpunkte an die Position mit der Kamera als Ursprung).
let viewTransform = cameraNode.simdTransform.inverse
//Projektionstransformationsmatrix
let projectionTransform = simd_float4x4(camera.projectionTransform(withViewportSize: self.scnView.bounds.size))
//MVP-Matrix
let mvpTransform = projectionTransform * viewTransform * modelTransform
 
//Projektionskoordinatenkonvertierung
var position = matrix_multiply(mvpTransform, SIMD4<Float>(vertex.x, vertex.y, vertex.z, 1.0))

Aber! Dies kann nicht so verwendet werden, wie es ist. Ich habe den folgenden Artikel gefunden (geholfen), als ich nach etwas gesucht habe, das nicht passt.

Das Ergebnis der Projektionskoordinatenkonvertierung muss in ** "normalisierte Gerätekoordinaten" ** von -1,0 bis 0,0 konvertiert werden. Teilen Sie x, y durch die w-Komponente des Ergebnisses der Projektionskonvertierung der Modellansicht wie folgt. ..

// -1.0~1.Normalisiert auf einen Wert von 0. Teilen Sie durch w, um "normalisierte Gerätekoordinaten" zu erhalten.
position = position / position.w

Nachdem wir nun ein Koordinatensystem von -1,0 bis 0,0 haben, können wir es wie folgt als Texturkoordinaten verwenden.

//In UV-Koordinaten konvertieren
let texcordX = CGFloat(position.x + 1.0) / 2.0
let texcordY = CGFloat(-position.y + 1.0) / 2.0
texcoords.append(CGPoint(x: texcordX, y: texcordY))

⑦ Stellen Sie ⑥ für die Texturkoordinaten jeder Zelle ein

Da die UV-Koordinaten jedes Scheitelpunkts des Zellknotens beliebig eingestellt werden müssen, schreiben Sie sie in Metall. .. .. Ich dachte, aber ich konnte es nur mit SceneKit leicht realisieren, indem ich auf diesen Artikel So erstellen Sie eine benutzerdefinierte Geometrie mit SceneKit + Bonus verweise. ..

//Generieren Sie die Zelloberflächengeometrie
let texcoordSource = SCNGeometrySource(textureCoordinates: texcoords)
let cellFaceGeometry = SCNGeometry(sources: [vertex, texcoordSource], elements: [element])
let cellFaceMaterial = SCNMaterial()
cellFaceMaterial.diffuse.contents = captureImage
cellFaceGeometry.materials = [cellFaceMaterial]
cellFaceNode.geometry = cellFaceGeometry    //Geometrie ersetzen

Durch Erstellen einer benutzerdefinierten Geometrie mit "SCNGeometry" können Sie die Koordinaten der Textur frei angeben. Da der Zellenknoten dieses Mal bereits beim Starten der App generiert wurde, wird die Geometrie des Zellenknotens beim Tippen auf den Bildschirm erfasst und nur die Texturkoordinaten werden ersetzt, um eine benutzerdefinierte Geometrie zu erstellen.

Tips

① Debug-Option Dieses Mal war es schwierig, die Existenz und das Trennzeichen von Knoten zu verstehen, da transparente Knoten vorhanden waren und die Zellen verteilt waren. Wenn Sie in einem solchen Fall die folgende Debug-Option festlegen, wird an der Grenze des Knotens eine weiße Linie angezeigt, die leicht zu verstehen und bequem ist.

//Debug-Option, die für SCNSceneRenderer angegeben werden kann
scnView.debugOptions = [.showBoundingBoxes]

② Wenn der Knoten klein ist, funktioniert die physikalische Beurteilung nicht. Zuerst fing ich an, die Gittergröße auf 10 cm zu verkleinern, aber als die Zelle fiel, wurde nicht festgestellt, dass sie mit dem unteren Knoten kollidierte, und ich konnte das Phänomen des Durchrutschens durch den Boden nicht vermeiden. SceneKit hat eine Eigenschaft namens "ContinuousCollisionDetectionThreshold" in "SCNPhysicsBody" als Gegenmaßnahme gegen ein solches Durchrutschen. Wenn Sie die Größe des in Konflikt stehenden Knotens angeben, wird es anscheinend so berechnet, dass es nicht durchschlüpft, aber es hat nicht gut funktioniert. Apples Dokumentation sagt " Die kontinuierliche Kollisionserkennung ist mit Leistungskosten verbunden und funktioniert nur für sphärische physikalische Formen, liefert jedoch genauere Ergebnisse. " Als Gegenmaßnahme werden der Boden und die Seiten des Gitters in eine Kastenform gebracht, um es dicker zu machen (obwohl es immer noch durchrutscht, wenn die Gittergröße verringert wird).

Ganzer Quellcode

ViewController.siwft


import ARKit
import SceneKit

class ViewController: UIViewController {

    @IBOutlet weak var scnView: ARSCNView!
    private let device = MTLCreateSystemDefaultDevice()!
    private let gridSize = 10                               //Anzahl der Gitterunterteilungen
    private let gridLength: Float = 0.8                     //Gittergröße[m]
    private let wallThickness: Float = 0.1                  //Dicke der Seite und des Bodens des Gitters
    private lazy var cellSize = gridLength / Float(gridSize) //Zellgröße
    private let gridRootNode = SCNNode()                    //Quadratischer Körper im Boden begraben
    private let gridCellParentNode = SCNNode()              //Route der Zellen auf dem Boden aufgereiht
    //Scheitelpunktkoordinaten der Zelloberfläche
    private lazy var vertices = [
        SCNVector3(-cellSize/2, 0.0, -cellSize/2), //Zurückgelassen
        SCNVector3( cellSize/2, 0.0, -cellSize/2), //Gleich zurück
        SCNVector3(-cellSize/2, 0.0, cellSize/2), //Links vorne
        SCNVector3( cellSize/2, 0.0, cellSize/2), //Rechts vorne
    ]
    //Scheitelindex der Zelloberfläche
    private let indices: [Int32] = [
        0, 2, 1,
        1, 2, 3
    ]
    private var time = 0                //Zeichenzähler
    private var isTouching = false      //Berührungserkennung
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //Aufstellen einer gitterartigen Box, um sie im Boden zu vergraben
        setupGridBox()
        //AR-Sitzung gestartet
        self.scnView.delegate = self
        let configuration = ARWorldTrackingConfiguration()
        configuration.planeDetection = [.horizontal]
        self.scnView.session.run(configuration, options: [.removeExistingAnchors, .resetTracking])
    }
}
    
extension ViewController: ARSCNViewDelegate {
    //
    //Anker hinzugefügt
    //
    func renderer(_: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

        //Knoten für planare Geometrie hinzugefügt
        guard let geometory = ARSCNPlaneGeometry(device: self.device) else { return }
        geometory.update(from: planeAnchor.geometry)
        let planeNode = SCNNode(geometry: geometory)
        planeNode.isHidden = true
        DispatchQueue.main.async {
            node.addChildNode(planeNode)
        }
    }
    //
    //Anker aktualisiert
    //
    func renderer(_: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
        
        DispatchQueue.main.async {
            for childNode in node.childNodes {
                //Ebenengeometrie aktualisieren
                guard let planeGeometry = childNode.geometry as? ARSCNPlaneGeometry else { continue }
                planeGeometry.update(from: planeAnchor.geometry)
                break
            }
        }
    }
    //
    //Bild für Bild aufgerufen
    //
    func renderer(_ renderer: SCNSceneRenderer, updateAtTime _: TimeInterval) {
        
        if isTouching {
            //Der Bildschirm wurde berührt
            isTouching = false
            DispatchQueue.main.async {
                //Überspringen, wenn ein Raster angezeigt wird
                guard self.gridRootNode.isHidden else { return }
                //Nehmen Sie das Kamerabild auf und wenden Sie es hier als Textur auf die Zelloberfläche an
                self.setupCellFaceTexture()
                //Rasteranzeige
                self.gridRootNode.isHidden = false
            }
        }
        
        DispatchQueue.main.async {
            //Tun Sie nichts, wenn das Raster ausgeblendet ist
            guard !self.gridRootNode.isHidden else { return }
            //Bodeneinsturz
            self.hourakuAnimation()
        }
    }
}

extension ViewController {
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let _ = touches.first else { return }
        isTouching = true
    }

    private func setupCellFaceTexture() {
        //Hit Test in der Mitte des Bildschirms
        let bounds = self.scnView.bounds
        let screenCenter =  CGPoint(x: bounds.midX, y: bounds.midY)
        let results = self.scnView.hitTest(screenCenter, types: [.existingPlaneUsingGeometry])
        guard let existingPlaneUsingGeometryResult = results.first(where: { $0.type == .existingPlaneUsingGeometry }),
              let _ = existingPlaneUsingGeometryResult.anchor as? ARPlaneAnchor else {
            //In der Mitte des Bildschirms befindet sich keine flache Oberfläche. Tun Sie also nichts
            return
        }
        //Kamerabild aufnehmen. Einfacher Schnappschuss, da er nicht kontinuierlich erfasst wird()verwenden
        let captureImage = self.scnView.snapshot()
        //Kameraerfassung
        guard let cameraNode = self.scnView.pointOfView,
              let camera = cameraNode.camera else { return }
        //Veröffentlichen Sie die Transformation des Trefferorts in der Transformation des Gitters
        self.gridRootNode.simdTransform = existingPlaneUsingGeometryResult.worldTransform
        for cellNode in self.gridCellParentNode.childNodes {
            guard let cellFaceNode = cellNode.childNodes.first(where: {$0.name == "face"}) else { continue }
            guard let vertex = cellFaceNode.geometry?.sources.first(where: {$0.semantic == .vertex}) else { continue }
            guard let element = cellFaceNode.geometry?.elements.first else { continue }
            
            //Modelltransformationsmatrix (Zelloberflächenknoten im Weltkoordinatensystem)
            let modelTransform = cellFaceNode.simdWorldTransform
            //Transformationsmatrix anzeigen. Inverse Matrix des Kamera-Ansichtspunkts (Verschieben Sie alle Weltkoordinatenscheitelpunkte an die Position mit der Kamera als Ursprung).
            let viewTransform = cameraNode.simdTransform.inverse
            //Projektionstransformationsmatrix
            let projectionTransform = simd_float4x4(camera.projectionTransform(withViewportSize: self.scnView.bounds.size))
            //MVP-Matrix
            let mvpTransform = projectionTransform * viewTransform * modelTransform
            //Konvertieren Sie jede Scheitelpunktkoordinate einer Zelle in Bildschirmkoordinaten und konvertieren Sie sie in UV-Koordinaten
            var texcoords: [CGPoint] = []
            for vertex in self.vertices {
                //Projektionskoordinatenkonvertierung
                var position = matrix_multiply(mvpTransform, SIMD4<Float>(vertex.x, vertex.y, vertex.z, 1.0))
                // -1.0~1.Normalisiert auf einen Wert von 0. Teilen Sie durch w, um "normalisierte Gerätekoordinaten" zu erhalten.
                position = position / position.w
                //In UV-Koordinaten konvertieren
                let texcordX = CGFloat(position.x + 1.0) / 2.0
                let texcordY = CGFloat(-position.y + 1.0) / 2.0
                texcoords.append(CGPoint(x: texcordX, y: texcordY))
            }
            //Generieren Sie die Zelloberflächengeometrie
            let texcoordSource = SCNGeometrySource(textureCoordinates: texcoords)
            let cellFaceGeometry = SCNGeometry(sources: [vertex, texcoordSource], elements: [element])
            let cellFaceMaterial = SCNMaterial()
            cellFaceMaterial.diffuse.contents = captureImage
            cellFaceGeometry.materials = [cellFaceMaterial]
            cellFaceNode.geometry = cellFaceGeometry    //Geometrie ersetzen
        }
    }
    
    private func hourakuAnimation() {
        //Stoppen Sie den Zusammenbruch in einer bestimmten Zeit
        guard self.time < 150 else { return }
        self.time += 1
        
        let time = Float(self.time)
        let gridSize = Float(self.gridSize)
        //Reduzieren Sie, um den Kreis von der Mitte aus zu verteilen
        let x = sin(Float.pi * 2 * time/30.0) * time / 150
        let y = cos(Float.pi * 2 * time/30.0) * time / 150
        let ygrid = Int((y + 1.0) / 2 * gridSize * gridSize) / self.gridSize * self.gridSize
        let xgrid = Int((x + 1.0) / 2 * gridSize) + ygrid
        guard 0 <= xgrid, xgrid < self.gridCellParentNode.childNodes.count else { return }
        let node = self.gridCellParentNode.childNodes[xgrid]
        //Knoten überspringen, für die Physikkörper bereits festgelegt wurde
        guard node.physicsBody == nil else { return }
        //Passen Sie die Größe der physischen Beurteilung an die Größe der Zelle an
        let bodyLength = CGFloat(self.cellSize) * 1.0
        let box = SCNBox(width: bodyLength, height: bodyLength, length: bodyLength, chamferRadius: 0.0)
        let boxShape = SCNPhysicsShape(geometry: box, options: nil)
        let boxBody = SCNPhysicsBody(type: .dynamic, shape: boxShape)
        boxBody.continuousCollisionDetectionThreshold = 0.001 //Ich habe es eingestellt, aber es fühlt sich nicht so an, als würde es funktionieren
        // TODO:Der Ursprung der Zelle befindet sich in der Mitte, aber der Ursprung der im PhysicsBody festgelegten Geometrie befindet sich in der Mitte, sodass die Koordinaten der Zellenhälfte deaktiviert sind.
        //Sie können entweder die PhysicsBody-Geometrie anpassen oder den Zellenursprung in die Mitte bringen, was beide mühsam sind. Beheben Sie das Problem, wenn Sie die Möglichkeit haben.
        node.physicsBody = boxBody
    }
    
    private func setupGridBox() {
        gridRootNode.isHidden = true
        //
        //Zellknoten generieren
        //
        //Stellen Sie sicher, dass Sie die Position jedes Knotens angeben. Ich bin auf ein Phänomen gestoßen, dass die Y-Koordinate die Erwartungen nur erfüllt, wenn sie initialisiert wird, selbst wenn es sich um die Mittelkoordinate handelt.
        gridCellParentNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
        gridRootNode.addChildNode(gridCellParentNode)
        gridRootNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
        self.scnView.scene.rootNode.addChildNode(gridRootNode)
        //Erstellen Sie einen Zellknoten von gridSize x gridSize
        let cellFaceGeometry = makeCellFaceGeometry()
        let cellBoxGeometry = makeCellBoxGeometry()
        let cellLeftBackPos = -(gridLength / 2) + cellSize / 2
        for y in 0 ..< gridSize {
            for x in 0 ..< gridSize {
                //Jeder Zellenknoten
                let cellNode = SCNNode()
                cellNode.simdPosition = SIMD3<Float>(x: cellLeftBackPos + (cellSize * Float(x)), y: 0, z: cellLeftBackPos + (cellSize * Float(y)))
                gridCellParentNode.addChildNode(cellNode)
                //Erzeugung von Knoten auf der Ebene der Zelloberfläche
                let cellFaceNode = SCNNode(geometry: cellFaceGeometry)
                cellFaceNode.name = "face"
                //Bestimmen Sie die Koordinaten so, dass jede Zelle verteilt ist
                cellFaceNode.simdPosition = SIMD3<Float>(x: 0.0, y: 0.0, z: 0.0)
                cellNode.addChildNode(cellFaceNode)
                //Generieren Sie einen rechteckigen Zellenknoten
                let cellBoxNode = SCNNode(geometry: cellBoxGeometry)
                cellBoxNode.simdPosition = SIMD3<Float>(x: 0.0, y: -cellSize/2*1.001, z: 0.0)
                cellNode.addChildNode(cellBoxNode)
            }
        }
        //
        //Gitterseitiger Knoten
        //
        let sideOuterBox = makeGridSideOuterGeometry()
        let sideInnerPlane = makeGridSideInnerGeometry()
        //Generierung von netzseitigen Knoten
        for i in 0..<4 {
            let x = sin(Float.pi / 2 * Float(i)) * gridLength / 2.0
            let z = cos(Float.pi / 2 * Float(i)) * gridLength / 2.0
            //Seite (außen)
            let outerNode = SCNNode(geometry: sideOuterBox)
            let nodePos = ((gridLength + wallThickness) / 2.0) / (gridLength / 2.0) * 1.001
            outerNode.simdPosition = SIMD3<Float>(x: x * nodePos, y: -gridLength/2.0, z: z * nodePos)
            outerNode.simdRotation = SIMD4<Float>(x: 0.0, y: 1.0, z: 0.0, w: -Float.pi / 2 * Float(i))
            //Machen Sie eine physische Wand an der Seite
            outerNode.physicsBody = SCNPhysicsBody.static()
            //Die Außenwand ist (fast) transparent und die Renderreihenfolge-Auf 1 setzen und vor anderen Knoten zeichnen (zuerst in den Z-Puffer schreiben).
            //Dadurch werden nicht die Knoten gezeichnet, die hinter dem (fast) transparenten Knoten gezeichnet werden sollen, was zu einem sichtbaren Hintergrund führt.
            outerNode.renderingOrder = -1
            gridRootNode.addChildNode(outerNode)
            
            //Seite (innen)
            let innerNode = SCNNode(geometry: sideInnerPlane)
            innerNode.simdPosition = SIMD3<Float>(x: x, y: -gridLength/2.0, z: z)
            innerNode.simdRotation = SIMD4<Float>(x: 0.0, y: 1.0, z: 0.0, w: -Float.pi / 2 * Float(i))
            gridRootNode.addChildNode(innerNode)
        }
        //
        //Unterer Knoten des Gitters
        //
        let bottomBox = makeGridButtomGeometry()
        let bottomNode = SCNNode(geometry: bottomBox)
        bottomNode.simdPosition = SIMD3<Float>(x: 0.0, y: -gridLength+Float(wallThickness), z: 0.0)
        bottomNode.simdRotation = SIMD4<Float>(x: 1.0, y: 0.0, z: 0.0, w: -Float.pi / 2)
        //Machen Sie eine physische Wand auf der Unterseite
        bottomNode.physicsBody = SCNPhysicsBody.static()
        gridRootNode.addChildNode(bottomNode)
    }
    
    private func makeCellFaceGeometry() -> SCNGeometry {
        //Zelloberflächengeometrie. Fügen Sie das vom Bildschirm aufgenommene Bild als Textur in diese Geometrie ein
        let cellFaceVertices = SCNGeometrySource(vertices: vertices)
        let cellFaceIndices = SCNGeometryElement(indices: indices, primitiveType: .triangles)
        let cellFaceGeometry = SCNGeometry(sources: [cellFaceVertices], elements: [cellFaceIndices])
        let cellFaceMaterial = SCNMaterial()
        cellFaceMaterial.diffuse.contents = UIColor.clear
        cellFaceGeometry.materials = [cellFaceMaterial]
        return cellFaceGeometry
    }
    
    private func makeCellBoxGeometry() -> SCNGeometry {
        //Der kleine Kastenteil der Zelle.
        let cellBox = SCNBox(width: CGFloat(cellSize), height: CGFloat(cellSize), length: CGFloat(cellSize), chamferRadius: 0.0)
        let cellBoxMaterial = SCNMaterial()
        cellBoxMaterial.diffuse.contents = UIColor.darkGray
        cellBox.materials = [cellBoxMaterial]
        return cellBox
    }
    
    private func makeGridSideOuterGeometry() -> SCNGeometry {
        //Gitterseitige (äußere) Geometrie. Machen Sie eine dicke Wand, damit die Zelle beim Fallen nicht in die Seite eindringt.
        let sideOuterBox = SCNBox(width: CGFloat(gridLength) * 1.001, height: CGFloat(gridLength), length: CGFloat(wallThickness), chamferRadius: 0)
        let sideOuterMaterial = SCNMaterial()
        sideOuterMaterial.transparency = 0.001
        sideOuterMaterial.diffuse.contents = UIColor.white
        sideOuterMaterial.isDoubleSided = true
        sideOuterBox.materials = [sideOuterMaterial]
        return sideOuterBox
    }
    
    private func makeGridSideInnerGeometry() -> SCNGeometry {
        //Geometrie der Gitterseite (innen)
        let sideInnerPlane = SCNPlane(width: CGFloat(gridLength), height: CGFloat(gridLength))
        let sideInnerMaterial = SCNMaterial()
        sideInnerMaterial.diffuse.contents = UIColor.gray
        sideInnerMaterial.isDoubleSided = true
        sideInnerPlane.materials = [sideInnerMaterial]
        return sideInnerPlane
    }
    
    private func makeGridButtomGeometry() -> SCNGeometry {
        //Die Geometrie des unteren Randes des Gitters. Machen Sie eine dicke Wand, damit die Zelle beim Fallen nicht in die Seite eindringt.
        let bottomBox = SCNBox(width: CGFloat(gridLength), height: CGFloat(gridLength), length: CGFloat(wallThickness), chamferRadius: 0)
        let bottomMaterial = SCNMaterial()
        bottomMaterial.diffuse.contents = UIColor.black
        bottomBox.materials = [bottomMaterial]
        return bottomBox
    }
}

Recommended Posts

Bodenkollaps mit ARKit + SceneKit
Surfen im Internet mit ARKit + SceneKit + Metal
Optische Tarnung mit ARKit + SceneKit + Metal ②
Cursoranzeige beim Platzieren von Objekten mit ARKit + SceneKit
So transformieren Sie ARKit- und SceneKit-Figuren mit Metal Shader
Reproduktion von "Du bist vor König Laputa" mit ARKit + SceneKit + Metal