ios – Colours Flip Black After Combining A number of Layer Rotations in SceneKit Rubik’s Dice (Swift), However Not Individually or in Particular Mixtures

0
15
ios – Colours Flip Black After Combining A number of Layer Rotations in SceneKit Rubik’s Dice (Swift), However Not Individually or in Particular Mixtures


I’m encountering a problem the place the colours of the cubelets flip black after combining a number of layer rotations, however not after particular person rotations or the precise mixture of left layer rotation + prime layer rotation. Individually, every layer rotation (prime, backside, proper, left, entrance, again) works accurately, sustaining the colours (inexperienced for entrance, pink for proper, blue for again, orange for left, white for prime, yellow for backside). Nonetheless, once I carry out sequences like prime + proper, entrance + proper, or backside + left, the colours of some or all cubelets flip black after the second rotation, however the mixture of left + prime rotation preserves colours.

Right here is my code to date:

CubeScene:

import SwiftUI
import SceneKit

class CuboScene: SCNScene, ObservableObject {
    override init() {
        tremendous.init()
        rootNode.eulerAngles = SCNVector3(0, 0, 0)  // Guarantee rootNode is just not rotated
        rootNode.place = SCNVector3(0, 0, 0)  // Guarantee rootNode is on the origin
        rootNode.scale = SCNVector3(1, 1, 1)  // Guarantee no surprising scaling
        
        configureLights()
        createRubikCube()
    }
    
    non-public func createRubikCube() {
        let dimension: CGFloat = 0.8
        let spacing: CGFloat = 0.78
        
        for x in -1...1 {
            for y in -1...1 {
                for z in -1...1 {
                    let cubelet = SCNBox(width: dimension, top: dimension, size: dimension, chamferRadius: 0.10)
                    let cubeletNode = SCNNode(geometry: cubelet)
                    cubeletNode.place = SCNVector3(Float(x) * Float(spacing),
                                                     Float(y) * Float(spacing),
                                                     Float(z) * Float(spacing))
                    cubeletNode.eulerAngles = SCNVector3(0, 0, 0)
                    cubelet.supplies = getFaceMaterial(x: x, y: y, z: z)
                    rootNode.addChildNode(cubeletNode)
                }
            }
        }
    }
    
    non-public func getFaceMaterial(x: Int, y: Int, z: Int) -> [SCNMaterial] {
        let colours: [UIColor] = [
            (z == 1) ? .green : .black,  // Front face (+Z)
            (x == 1) ? .red : .black,    // Right face (+X)
            (z == -1) ? .blue : .black,  // Back face (-Z)
            (x == -1) ? .orange : .black, // Left face (-X)
            (y == 1) ? .white : .black,  // Top face (+Y)
            (y == -1) ? .yellow : .black // Bottom face (-Y)
        ]
        return colours.map { createMaterial(coloration: $0) }
    }
    
    non-public func createMaterial(coloration: UIColor) -> SCNMaterial {
        let materials = SCNMaterial()
        materials.diffuse.contents = coloration.withAlphaComponent(1.0)
        materials.specular.contents = UIColor.white
        materials.shininess = 100
        materials.lightingModel = .phong
        
        return materials
    }
    
    non-public func configureLights() {
        // Ambient gentle for base illumination
        let ambientLight = SCNLight()
        ambientLight.sort = .ambient
        ambientLight.depth = 500
        let ambientLightNode = SCNNode()
        ambientLightNode.gentle = ambientLight
        rootNode.addChildNode(ambientLightNode)

        // Array of positions and instructions for the 6 lights
        let positions: [(SCNVector3, SCNVector3)] = [
            (SCNVector3(0, 10, 0), SCNVector3(0, -1, 0)),  // Top
            (SCNVector3(0, -10, 0), SCNVector3(0, 1, 0)),  // Bottom
            (SCNVector3(10, 0, 0), SCNVector3(-1, 0, 0)),  // Right
            (SCNVector3(-10, 0, 0), SCNVector3(1, 0, 0)),  // Left
            (SCNVector3(0, 0, 10), SCNVector3(0, 0, -1)),  // Front
            (SCNVector3(0, 0, -10), SCNVector3(0, 0, 1))   // Back
        ]

        for (place, route) in positions {
            let gentle = SCNLight()
            gentle.sort = .directional
            gentle.depth = 1000
            gentle.coloration = UIColor.white
            let lightNode = SCNNode()
            lightNode.gentle = gentle
            lightNode.place = place
            lightNode.eulerAngles = SCNVector3(
                atan2(route.y, route.z),
                atan2(route.x, route.z),
                0)
            rootNode.addChildNode(lightNode)
        }
    }
    
    func rotateLayer(cubelets: [SCNNode], axis: SCNVector3, angle: CGFloat) {
        let tempNode = SCNNode()
        
        var originalMaterials: [SCNNode: [SCNMaterial]] = [:]
        var originalPositions: [SCNNode: SCNVector3] = [:]
        var originalEulerAngles: [SCNNode: SCNVector3] = [:]
        
        for cubelet in cubelets {
            if let geometry = cubelet.geometry as? SCNBox {
                originalMaterials[cubelet] = geometry.supplies
            }
            originalPositions[cubelet] = cubelet.place
            originalEulerAngles[cubelet] = cubelet.eulerAngles
            
            cubelet.removeFromParentNode()
            tempNode.addChildNode(cubelet)
        }
        rootNode.addChildNode(tempNode)
        
        let rotationAction = SCNAction.rotate(by: angle, round: axis, period: 0.2)
        tempNode.runAction(rotationAction) {
            for cubelet in cubelets {
                let worldPosition = cubelet.worldPosition
                tempNode.removeFromParentNode()
                
                let localPosition = self.rootNode.convertPosition(worldPosition, from: nil)
                cubelet.place = localPosition
                
                if eje.x != 0 {
                    cubelet.eulerAngles = SCNVector3(cubelet.eulerAngles.x + Float(angle), cubelet.eulerAngles.y, cubelet.eulerAngles.z)
                } else if eje.y != 0 {
                    cubelet.eulerAngles = SCNVector3(cubelet.eulerAngles.x, cubelet.eulerAngles.y + Float(angle), cubelet.eulerAngles.z)
                } else if eje.z != 0 {
                    cubelet.eulerAngles = SCNVector3(cubelet.eulerAngles.x, cubelet.eulerAngles.y, cubelet.eulerAngles.z + Float(angle))
                }
                if let geometry = cubelet.geometry as? SCNBox {
                    geometry.supplies = originalMaterials[cubelet] ?? geometry.supplies
                }
                self.rootNode.addChildNode(cubelet)
            }
            tempNode.removeFromParentNode()
        }
    }
    
    func rotateTopLayer() {
        let axis = SCNVector3(x: 0, y: 1, z: 0)
        let angle: CGFloat = .pi / 2
        
        let topCubelets = rootNode.childNodes.filter { node in
            abs(node.place.y - 0.78) < 0.01
        }
        rotateLayer(cubelets: topCubelets, axis: axis, angle: angle)
    }
    
    func rotateBottomLayer() {
        let axis = SCNVector3(x: 0, y: 1, z: 0)
        let angle: CGFloat = -.pi / 2
        
        let bottomCubelets = rootNode.childNodes.filter { node in
            abs(node.place.y + 0.78) < 0.01
        }
        rotateLayer(cubelets: bottomCubelets, axis: axis, angle: angle)
    }
    
    func rotateRightLayer() {
        let axis = SCNVector3(x: 1, y: 0, z: 0)
        let angle: CGFloat = .pi / 2
        
        let rightCubelets = rootNode.childNodes.filter { node in
            abs(node.place.x - 0.78) < 0.02
        }
        rotateLayer(cubelets: rightCubelets, axis: axis, angle: angle)
    }
    
    func rotateLeftLayer() {
        let axis = SCNVector3(x: 1, y: 0, z: 0)
        let angle: CGFloat = -.pi / 2
        
        let leftCubelets = rootNode.childNodes.filter { node in
            abs(node.place.x + 0.78) < 0.02
        }
        rotateLayer(cubelets: leftCubelets, axis: axis, angle: angle)
    }
    
    func rotateFrontLayer() {
        let axis = SCNVector3(x: 0, y: 0, z: 1)
        let angle: CGFloat = .pi / 2
        
        let frontCubelets = rootNode.childNodes.filter { node in
            abs(node.place.z - 0.78) < 0.01
        }
        rotateLayer(cubelets: frontCubelets, axis: axis, angle: angle)
    }
    
    func rotateBackLayer() {
        let axis = SCNVector3(x: 0, y: 0, z: 1)
        let angle: CGFloat = -.pi / 2
        
        let backCubelets = rootNode.childNodes.filter { node in
            abs(node.place.z + 0.78) < 0.01
        }
        rotateLayer(cubelets: backCubelets, axis: axis, angle: angle)
    }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been carried out")
    }
}

SceneKitView:

import SwiftUI
import SceneKit

struct SceneKitView: UIViewRepresentable {
    @ObservedObject var cubeScene: CuboScene
    
    func makeUIView(context: Context) -> SCNView {
        let view = SCNView()
        view.scene = cubeScene
        view.allowsCameraControl = true
        view.autoenablesDefaultLighting = true
        view.backgroundColor = .grey
        
        // Configure an preliminary digital camera
        let cameraNode = SCNNode()
        cameraNode.digital camera = SCNCamera()
        cameraNode.place = SCNVector3(x: 5, y: 5, z: 5)  // Diagonal place to see all faces
        cameraNode.look(at: SCNVector3(0, 0, 0))  // Level to the dice's middle
        view.pointOfView = cameraNode
        
        return view
    }
    
    func updateUIView(_ uiView: SCNView, context: Context) {
        uiView.scene = cubeScene
    }
}

#Preview {
    SceneKitView(cubeScene: CuboScene())  // Or RubikCubeScene if most popular
}

ContenView:

import SwiftUI

struct ContentView: View {
    @StateObject non-public var cubeScene = CuboScene()  // Or use RubikCubeScene if most popular
    
    var physique: some View {
        VStack {
            SceneKitView(cubeScene: cubeScene)
                .body(top: 400)
                .ignoresSafeArea()
        }
        Button("Rotate Prime Layer") {
            cubeScene.rotateTopLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
        
        Button("Rotate Proper Layer") {
            cubeScene.rotateRightLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
        
        Button("Rotate Left Layer") {
            cubeScene.rotateLeftLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
        
        Button("Rotate Backside Layer") {
            cubeScene.rotateBottomLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
        
        Button("Rotate Entrance Layer") {
            cubeScene.rotateFrontLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
        
        Button("Rotate Again Layer") {
            cubeScene.rotateBackLayer()
        }
        .padding()
        .background(Colour.blue)
        .foregroundColor(.white)
        .cornerRadius(10)
    }
}

#Preview {
    ContentView()
}

Here’s a video of the problem:

https://www.youtube.com/shorts/7q8XcdjdoCE

LEAVE A REPLY

Please enter your comment!
Please enter your name here