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: