I’m creating a 3D Rubik’s Dice utilizing SceneKit in Swift, and I’m encountering a difficulty the place the colours of the dice’s faces are usually not rendering as strong 3×3 faces as anticipated. As an alternative of every face of the 3x3x3 dice (e.g., the entrance face in inexperienced, the left face in orange, and many others.) being uniformly coloured throughout all 9 cublets, the colours seem fragmented or misplaced, usually displaying up in rows or columns quite than overlaying all the face. For instance, I see the inexperienced shade (meant for the entrance face, +Z) appropriately on the highest row, but it surely shifts to the underside row or adjoining faces as an alternative of forming a strong inexperienced 3×3 face.
Right here’s an summary of my setup and the difficulty:
-
Goal: I need every face of the Rubik’s Dice (entrance, again, left, proper, prime, backside) to be a strong shade for the 9 cublets that make up that face (e.g., all cublets with z = 1 ought to have their entrance face painted inexperienced).
-
Present Conduct: Colours are utilized partially or in fragmented patterns (e.g., a row of inexperienced on the prime, then a row of inexperienced under, however not a strong 3×3 inexperienced face). This occurs for all faces (crimson for +X, orange for -X, white for +Y, yellow for -Y, inexperienced for +Z, blue for -Z).
-
Suspected Concern: I believe the cublets (SCNBox) may be rotated, inflicting the supplies to use to the incorrect faces or within the incorrect orientation, however I’ve been unable to verify or repair this.
Right here is my code:
CubeScene:
import SwiftUI
import SceneKit
class CuboScene: SCNScene {
override init() {
tremendous.init()
rootNode.eulerAngles = SCNVector3(0, 0, 0) // Guarantee rootNode shouldn't be rotated
rootNode.place = SCNVector3(0, 0, 0) // Guarantee rootNode is on the origin
rootNode.scale = SCNVector3(1, 1, 1) // Guarantee no sudden scaling
// let mild = SCNLight()
// mild.kind = .omni
// mild.depth = 2000
// let lightNode = SCNNode()
// lightNode.mild = mild
// lightNode.place = SCNVector3(x: 5, y: 5, z: 5)
// rootNode.addChildNode(lightNode)
configureLights()
createRubikCube()
}
personal func createRubikCube() {
let measurement: CGFloat = 0.8
let spacing: CGFloat = 1.05
for x in -1...1 {
for y in -1...1 {
for z in -1...1 {
let cubelet = SCNBox(width: measurement, peak: measurement, size: measurement, chamferRadius: 0.0) // No rounded edges
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) // Guarantee no rotation
print("Cubelet at place: ((Float(x) * Float(spacing)), (Float(y) * Float(spacing)), (Float(z) * Float(spacing)) with supplies: (getFaceMaterial(x: x, y: y, z: z).map { $0.diffuse.contents as? UIColor ?? .grey }) and eulerAngles: (cubeletNode.eulerAngles), rotation: (cubeletNode.rotation)")
cubelet.supplies = getFaceMaterial(x: x, y: y, z: z)
rootNode.addChildNode(cubeletNode)
}
}
}
}
personal func getFaceMaterial(x: Int, y: Int, z: Int) -> [SCNMaterial] {
let colours: [UIColor] = [
// Right face (+X): red if x = 1, gray for x = 0 or -1
(x == 1) ? .red : .gray,
// Left face (-X): orange if x = -1, gray for x = 0 or 1
(x == -1) ? .orange : .gray,
// Top face (+Y): white if y = 1, gray for y = 0 or -1
(y == 1) ? .white : .gray,
// Bottom face (-Y): yellow if y = -1, gray for y = 0 or 1
(y == -1) ? .yellow : .gray,
// Front face (+Z): green if z = 1, gray for z = 0 or -1
(z == 1) ? .green : .gray,
// Back face (-Z): blue if z = -1, gray for z = 0 or 1
(z == -1) ? .blue : .gray
]
// print("Place ((x), (y), (z)): Colours = (colours)") // Debugging
return colours.map { createMaterial(shade: $0) }
}
personal func createMaterial(shade: UIColor) -> SCNMaterial {
let materials = SCNMaterial()
print("Making use of materials with shade: (shade)")
materials.diffuse.contents = shade.withAlphaComponent(1.0)
materials.specular.contents = UIColor.white
materials.shininess = 100
materials.lightingModel = .phong
return materials
}
personal func configureLights() {
// Ambient mild for base illumination
let ambientLight = SCNLight()
ambientLight.kind = .ambient
ambientLight.depth = 500
let ambientLightNode = SCNNode()
ambientLightNode.mild = 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, path) in positions {
let mild = SCNLight()
mild.kind = .directional
mild.depth = 1000
mild.shade = UIColor.white
let lightNode = SCNNode()
lightNode.mild = mild
lightNode.place = place
lightNode.eulerAngles = SCNVector3(
atan2(path.y, path.z),
atan2(path.x, path.z),
0
) // Orient towards the middle
rootNode.addChildNode(lightNode)
}
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been carried out")
}
}
SceneKitView:
import SwiftUI
import SceneKit
struct SceneKitView: UIViewRepresentable {
func makeUIView(context: Context) -> SCNView {
let view = SCNView()
view.scene = CuboScene() // Use the created scene
view.allowsCameraControl = true // Permit digicam motion with contact
view.autoenablesDefaultLighting = false // Disable default lighting
view.backgroundColor = .clear
// Configure an preliminary digicam
let cameraNode = SCNNode()
cameraNode.digicam = 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 cubes heart
view.pointOfView = cameraNode
return view
}
func updateUIView(_ uiView: SCNView, context: Context) {}
}
#Preview {
SceneKitView()
}
Right here is how the Rubik’s dice colours are producing:
I’ve tried a whole lot of various things, however I can’t make it proper.