1. ScenceKit による3D表示
↓ こちらの動画で説明されている ScenceKit
による3D表示のコードは、非常にシンプルで、かつ、キューブの視点変更操作がノーコードで実現できる点は非常に魅力的です。
しかし、自分は次の点で使いこなすことができませんでした。
・ エッジキューブの2面やコーナーキューブの3面の色を 動的に変更する方法が分からない
・ルービックキューブ の面の回転をアニメーションで表現したいが、方法が分からない
(単に自分がScenceKit
を知らないだけだと思います)
2. Javascript による3D表示
Javascript による ルービックキューブ の 3D表示ライブラリはいくつか公開されており、次のサイトで紹介されています。
この中のRoofpig
を WKWebView
に表示させる方法を試みたが、次の点で 使いこなすことができませんでした。
・視点をリセットする方法が分からない
・キューブの色を動的に指定する方法が分からない
3. Javascript による3D表示 (その2)
次のサイトで紹介されている Javascript による ルービックキューブ の 3D表示は、外部の 3Dライブラリを使わず(CSSも使わず)、自身で3Dを2Dに変換する関数を定義して 2D Canvasに描画する方法で実現しています。表示のみならず、マウス操作による「面の回転」と「3D視点変更」(GUI)も実現しています。
このコードであれば、全行を解読し Swift への書き換えも可能であろうと考え、こちらを参考にさせていただくことにしました。
結果、
次のように動作するSwiftコードを書くことができました。
上記の Javascript によるコードとは、次の点で変更を加えています。
・マウス左ボタンドラッグによる ルービックキューブの面の回転を削除
・マウス右ボタンドラッグによる 視点変更を マウス左ボタンドラッグに変更
コードを表示する(8ファイル)
import SwiftUI
#if os(macOS)
typealias UIColor = NSColor
typealias UIView = NSView
typealias UITouch = NSEvent
typealias UIBezierPath = NSBezierPath
typealias UIViewRepresentable = NSViewRepresentable
#endif
extension UIColor {
static let rcWhite = UIColor(r: 255, g: 255, b: 255) //U
static let rcOrange = UIColor(r: 247, g: 147, b: 30) //L
static let rcGreen = UIColor(r: 0, g: 168, b: 0) //F
static let rcRed = UIColor(r: 255, g: 42, b: 0) //R
static let rcBlue = UIColor(r: 0, g: 101, b: 255) //B
static let rcYellow = UIColor(r: 255, g: 246, b: 26) //D
//specifies an RGB values 0 ~ 255
convenience init(r: Int, g: Int, b: Int) {
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: 1)
}
}
private class MouseTrackpadView: UIView {
let cubeManager: CubeManager
private var isDragging = false
// change color position 1 2 3 4 5 6
let ColorIndex = [UIColor.black, .rcRed, .rcGreen, .rcOrange, .rcBlue, .rcYellow, .rcWhite]
var doMouseDown: ((MouseEvent)->Void)? = nil
var doMouseDrag: ((MouseEvent)->Void)? = nil
var doMouseUp: ((MouseEvent)->Void)? = nil
init(cubeManager: CubeManager) {
self.cubeManager = cubeManager
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createMouseEvent(_ event: UITouch) -> MouseEvent {
let windowSize = self.frame.size
#if os(macOS)
var location = self.convert(event.locationInWindow, from: nil)
location.y = windowSize.height - location.y
#else
let location = event.location(in: self)
#endif
// 0: No button
// 1: 左ボタン
// 2: 中央ボタン
// 3: 右ボタン
let which = 1
let point = CGPoint(x: (location.x / windowSize.width) * 2 - 1, y: (location.y / windowSize.height) * 2 - 1)
let mouseEvent = MouseEvent(which: which, point: point, size: windowSize)
return mouseEvent
}
#if os(macOS)
override func mouseDown(with event: NSEvent) {
isDragging = true
doMouseDown?(createMouseEvent(event))
}
override func mouseDragged(with event: NSEvent) {
guard isDragging else { return }
doMouseDrag?(createMouseEvent(event))
}
override func mouseMoved(with event: NSEvent) {
doMouseDrag?(createMouseEvent(event))
}
override func mouseUp(with event: NSEvent) {
doMouseUp?(createMouseEvent(event))
isDragging = false
}
#else
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
isDragging = true
doMouseDown?(createMouseEvent(touches.first!))
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard isDragging else { return }
doMouseDrag?(createMouseEvent(touches.first!))
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
doMouseUp?(createMouseEvent(touches.first!))
isDragging = false
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
isDragging = false
}
#endif
override func draw(_ rect: CGRect) {
#if !os(macOS)
let path = UIBezierPath(rect: self.bounds)
self.backgroundColor?.set()
path.fill()
#endif
let windowSize = self.frame.size
//let scale = min(windowSize.width / 2, windowSize.height / 2)
let scale = min(windowSize.width * 0.6, windowSize.height * 0.6)
for (path, index) in cubeManager.drawInfo {
let color = ColorIndex[index]
#if os(macOS)
let transformOffset = CGAffineTransform(translationX: (windowSize.width - scale) / 2, y: (windowSize.height + scale) / 2)
let transformScale = CGAffineTransform(scaleX: scale, y: -scale)
#else
let transformOffset = CGAffineTransform(translationX: (windowSize.width - scale) / 2, y: (windowSize.height - scale) / 2)
let transformScale = CGAffineTransform(scaleX: scale, y: scale)
#endif
let transformedPath = path.applying(transformScale).applying(transformOffset)
let nsPath = UIBezierPath(cgPath: transformedPath.cgPath)
color.set()
nsPath.fill()
UIColor.black.set()
nsPath.lineWidth = 0.5
nsPath.stroke()
}
}
}
private struct MouseTrackpadViewRepresentable: UIViewRepresentable {
typealias NSViewType = MouseTrackpadView
typealias UIViewType = MouseTrackpadView
@EnvironmentObject var cubeManager: CubeManager
typealias MouseEventType = ((MouseEvent)->Void)
let mouseDown: MouseEventType?
let mouseDrag: MouseEventType?
let mouseUp: MouseEventType?
init (mouseDown: MouseEventType? = nil, mouseDrag: MouseEventType? = nil, mouseUp: MouseEventType? = nil) {
self.mouseDown = mouseDown
self.mouseDrag = mouseDrag
self.mouseUp = mouseUp
}
#if os(macOS)
func updateNSView(_ nsView: MouseTrackpadView, context: Context) {
if cubeManager.drawInfo.count > 0 {
DispatchQueue.main.async {
nsView.needsDisplay = true
}
}
}
func makeNSView(context: Context) -> MouseTrackpadView {
return makeUIView(context: context)
}
#else
func updateUIView(_ uiView: MouseTrackpadView, context: Context) {
if cubeManager.drawInfo.count > 0 {
DispatchQueue.main.async {
uiView.setNeedsDisplay()
}
}
}
#endif
func makeUIView(context: Context) -> MouseTrackpadView {
let view = MouseTrackpadView(cubeManager: cubeManager)
view.doMouseDown = { cubeManager.mousedown($0) }
view.doMouseDrag = { cubeManager.mousemove($0) }
view.doMouseUp = { cubeManager.mouseup($0) }
return view
}
}
struct CubeDrawView: View {
@EnvironmentObject var cubeManager: CubeManager
var body: some View {
VStack {
MouseTrackpadViewRepresentable()
.environmentObject(cubeManager)
HStack {
Button("Front View", action: { cubeManager.setFrontCam() })
//Button("Flat Cam", action: { cubeManager.flatCam() })
Button("Back View", action: { cubeManager.setBackCam() })
}
.padding(.bottom, 4)
}
.background(.gray.opacity(0.3))
}
}
import SwiftUI
class CubeManager: ObservableObject {
@MainActor static let shared = CubeManager()
@Published var drawInfo: [(Path, Int)] = []
let undoManager = UndoManager()
var cam = Cam(pos: Vector3d(x: 0, y: 0, z: -10), n: Vector3d(x: 1, y: 0, z: 0))
let cube_size = 0.5
let black = 0
let yellow = 1
let red = 2
let green = 3
let orange = 4
let blue = 5
let white = 6
var mouseRDown = false
var mouseLDown = false
var mousePos3d = Vector3d(x: 0, y: 0, z: 0)
var clickedMousePos3d: Vector3d? = nil
var move: String? = nil
var animating = false
var rubiks_cube: RubiksCube
private init() {
undoManager.groupsByEvent = false
rubiks_cube = init_rubiks_cube(cube_size, red, green, yellow, white, blue, orange)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam)
}
func reset() {
undoManager.removeAllActions()
rubiks_cube = init_rubiks_cube(cube_size, red, green, yellow, white, blue, orange)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam)
}
func flatCam() {
cam = Cam(pos: Vector3d(x: 0, y: 0, z: -10), n: Vector3d(x: 1, y: 0, z: 0))
clear_canvas()
render_rubiks_cube(rubiks_cube, cam)
}
func setFrontCam() {
cam = Cam(pos: Vector3d(x: -6.431036678846858, y: -4.595754987972483, z: -6.125422705891604),
n: Vector3d(x: 0.6884108826075905, y: -0.004547991351971411, z: -0.716043468761283))
clear_canvas()
render_rubiks_cube(rubiks_cube, cam)
}
func setBackCam() {
cam = Cam(pos: Vector3d(x: 6.431036678846858, y: 4.595754987972483, z: 6.125422705891604),
n: Vector3d(x: 0.6884108826075905, y: 0.004547991351971411, z: -0.726043468761283))
clear_canvas()
render_rubiks_cube(rubiks_cube, cam)
}
func contextmenu(_ e: MouseEvent) -> Bool {
return false
}
func mousedown(_ e: MouseEvent) {
let mousePos = getMousePos(e)
if (e.which == 3) {
mouseLDown = true
clickedMousePos3d = mousePosTo3d(cam, mousePos)
} else if (e.which == 1) {
mouseRDown = true
}
mousePos3d = mousePosTo3d(cam, mousePos)
}
func mouseup(_ e: MouseEvent) {
if (e.which == 3 && !animating) {
mouseLDown = false
clickedMousePos3d = nil
rubiks_cube.applyMove(move)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam, move)
} else if (e.which == 1) {
mouseRDown = false
}
}
func mousemove(_ e: MouseEvent) {
let mousePos = getMousePos(e)
if (mouseRDown) {
let prevPos = mousePos3d
let currPos = mousePosTo3d(cam, mousePos)
rotate_cam_by_mouse(prevPos, currPos)
mousePos3d = mousePosTo3d(cam, mousePos)
} else if (mouseLDown && !animating) {
let currPos = mousePosTo3d(cam, mousePos)
move = rubiks_cube.moveEvent(cam, clickedMousePos3d, currPos)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam, move)
}
mousePos3d = mousePosTo3d(cam, mousePos)
}
func static_move(_ move: String) {
rubiks_cube.applyMove(move)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam, move)
}
func animate_move(_ move: String, _ _speed: Double = 0.03) {
var speed = _speed
let ax = Vector3d(x: 1, y: 0, z: 0)
let ay = Vector3d(x: 0, y: 1, z: 0)
let az = Vector3d(x: 0, y: 0, z: 1)
var t_end = Double(0)
var axis = Vector3d(x: 0, y: 0, z: 0)
switch (move) {
case "R1", "R":
axis = ax
t_end = -.pi / 2
speed = -speed
case "R2":
axis = ax
t_end = -.pi
speed = -speed
case "R3", "R'":
axis = ax
t_end = .pi / 2
//speed = speed
break
case "L1", "L":
axis = ax
t_end = .pi / 2
//speed = speed
case "L2":
axis = ax
t_end = .pi
//speed = speed
case "L3", "L'":
axis = ax
t_end = -.pi / 2
speed = -speed
case "F1", "F":
axis = az
t_end = .pi / 2
//speed = speed
case "F2":
axis = az
t_end = .pi
//speed = speed
case "F3", "F'":
axis = az
t_end = -.pi / 2
speed = -speed
case "B1", "B":
axis = az
t_end = -.pi / 2
speed = -speed
case "B2":
axis = az
t_end = -.pi
speed = -speed
case "B3", "B'":
axis = az
t_end = .pi / 2
//speed = speed
case "U1", "U":
axis = ay
t_end = .pi / 2
//speed = speed
case "U2":
axis = ay
t_end = .pi
//speed = speed
case "U3", "U'":
axis = ay
t_end = -.pi / 2
speed = -speed
case "D1", "D":
axis = ay
t_end = -.pi / 2
speed = -speed
case "D2":
axis = ay
t_end = -.pi
speed = -speed
case "D3", "D'":
axis = ay
t_end = .pi / 2
//speed = speed
default: return
}
func loop() {
var t = Double(0)
Timer.scheduledTimer(withTimeInterval: 0.003, repeats: true) { timer in
if (speed > 0 && t < t_end || speed < 0 && t > t_end) {
self.rubiks_cube.init_pos()
self.rubiks_cube.move_slice(self.rubiks_cube.get_cubes_from_move(move), axis, t)
self.clear_canvas()
self.render_rubiks_cube(self.rubiks_cube, self.cam, move)
t += speed
} else {
self.static_move(move)
timer.invalidate()
}
}
}
loop()
}
func rotate_cam_by_mouse(_ prevPos: Vector3d, _ currPos: Vector3d) {
let nz = vec_div(cam.pos, norm(cam.pos))
let p1 = vec_add(prevPos, vec_mul(nz, norm(cam.pos)))
let p2 = vec_add(currPos, vec_mul(nz, norm(cam.pos)))
let axis = v_product(p1, p2)
if (norm(axis) > 0) {
let theta = acos(d_product(p1, p2) / (norm(p1) * norm(p2)))
cam = cam.rotate_cam(axis, -theta * 15)
clear_canvas()
render_rubiks_cube(rubiks_cube, cam, move)
}
}
private func render_rubiks_cube(_ rubiks_cube: RubiksCube, _ cam: Cam, _ move: String? = nil) {
func render_block(_ block: Cubes) {
let cube = block.sorted { a, b in
let da = distance(cam.pos, cube_center(a))
let db = distance(cam.pos, cube_center(b))
return db < da
}
for c in cube {
self.drawInfo.append(contentsOf: render_cube(c, cam))
}
}
guard let move else {
render_block(rubiks_cube.cube)
return
}
let slice_cube_ids = rubiks_cube.get_cubes_from_move(move)
let slice_cubes = rubiks_cube.cube.enumerated().filter { slice_cube_ids.contains($0.offset) }.map { $0.element }
let block_cubes = rubiks_cube.cube.enumerated().filter { !slice_cube_ids.contains($0.offset) }.map { $0.element }
let da = distance(cam.pos, block_center(slice_cubes))
//let db = distance(cam.pos, block_center(block_cubes))
if (da > 10) { // da - dbで評価したかったが、うまく描画できなかった
render_block(slice_cubes)
render_block(block_cubes)
} else {
render_block(block_cubes)
render_block(slice_cubes)
}
}
func clear_canvas() {
drawInfo.removeAll()
}
}
extension CubeManager {
var undoCount: Int { undoManager.undoCount }
var redoCount: Int { undoManager.redoCount }
var canUndo: Bool { undoManager.canUndo }
var canRedo: Bool { undoManager.canRedo }
func undo() { if canUndo { undoManager.undo() }}
func redo() { if canRedo { undoManager.redo() }}
}
import Foundation
/* 四角形の重心 */
func face_center(_ face: Face) -> Vector3d {
var center = Vector3d(x: 0, y: 0, z: 0)
center = vec_add(center, face.p1)
center = vec_add(center, face.p2)
center = vec_add(center, face.p3)
center = vec_add(center, face.p4)
return vec_div(center, 4)
}
/* 立方体の重心 */
func cube_center(_ cube: Cube) -> Vector3d {
var center = Vector3d(x: 0, y: 0, z: 0)
var cnt = 0
for c in cube.face {
center = vec_add(center, face_center(c))
cnt += 1
}
return vec_div(center, cnt)
}
/* 立方体のあつまりの重心 */
func block_center(_ block: Cubes) -> Vector3d {
var center = Vector3d(x: 0, y: 0, z: 0)
var cnt = 0
for c in block {
center = vec_add(center, cube_center(c))
cnt += 1
}
return vec_div(center, cnt)
}
import SwiftUI
struct Cam {
var pos: Vector3d
var n: Vector3d
func rotate_cam(_ axis: Vector3d, _ theta: Double) -> Cam {
Cam(pos: rotate(self.pos, axis, theta),
n: rotate(self.n, axis, theta))
}
}
struct Event {
let f: String
let mp1: Vector3d
let mp2: Vector3d
let move: String?
}
protocol Face {
var p1: Vector3d { get }
var p2: Vector3d { get }
var p3: Vector3d { get }
var p4: Vector3d { get }
}
struct CubeFace: Face {
var p1: Vector3d
var p2: Vector3d
var p3: Vector3d
var p4: Vector3d
var color: Int
}
struct Cube {
var face: [CubeFace]
}
typealias Cubes = [Cube]
struct FrameFace: Face {
let p1: Vector3d
let p2: Vector3d
let p3: Vector3d
let p4: Vector3d
let label: String
let mouseOn: (Cam, Vector3d) -> Bool
let faceEvent: (Cam, Vector3d, Vector3d) -> String?
}
struct Frame {
let face: [FrameFace]
}
struct CubePos {
let p1: Vector3d
let p2: Vector3d
let p3: Vector3d
let p4: Vector3d
}
struct MoveCubes {
let u: [Int]
let d: [Int]
let l: [Int]
let r: [Int]
let f: [Int]
let b: [Int]
}
struct RubiksCube {
var cube: Cubes
let frame: Frame
var cube_init_pos: [[CubePos]]
let move_cubes: MoveCubes
func get_cubes_from_move(_ move: String?) -> [Int] {
guard let move else { return [] }
return switch move.first! {
case "F": move_cubes.f
case "B": move_cubes.b
case "U": move_cubes.u
case "D": move_cubes.d
case "L": move_cubes.l
case "R": move_cubes.r
default: []
}
}
mutating func init_pos() {
for (i, c) in cube_init_pos.enumerated() {
for (j, f) in c.enumerated() {
cube[i].face[j].p1 = Vector3d(x: f.p1.x, y: f.p1.y, z: f.p1.z)
cube[i].face[j].p2 = Vector3d(x: f.p2.x, y: f.p2.y, z: f.p2.z)
cube[i].face[j].p3 = Vector3d(x: f.p3.x, y: f.p3.y, z: f.p3.z)
cube[i].face[j].p4 = Vector3d(x: f.p4.x, y: f.p4.y, z: f.p4.z)
}
}
}
private func mouseOnFace(_ cam: Cam, _ mousePos3d: Vector3d) -> String {
var label = ""
var min_d = Double(1000)
for e in frame.face {
if (e.mouseOn(cam, mousePos3d)) {
let d = distance(cam.pos, face_center(e))
if (d < min_d) {
min_d = d
label = e.label
}
}
}
return label
}
private func faceEvent(_ cam: Cam, _ clickedMousePos3d: Vector3d?, _ mousePos3d: Vector3d) -> Event? {
var min_d = Double(1000)
var event: Event? = nil
guard let clickedMousePos3d else {
return nil
}
for e in frame.face {
if (e.mouseOn(cam, clickedMousePos3d)) {
let d = distance(cam.pos, face_center(e))
if (d < min_d) {
min_d = d
event = Event(
f: e.label,
mp1: MousePosToFacePos(cam, e, clickedMousePos3d),
mp2: MousePosToFacePos(cam, e, mousePos3d),
move: e.faceEvent(cam, clickedMousePos3d, mousePos3d)
)
}
}
}
return event
}
@inline(__always)
private func rotate_cube(_ cube: inout Cube, _ axis: Vector3d, _ theta: Double) {
let face = cube.face.map { f in
CubeFace(p1: rotate(f.p1, axis, theta), p2: rotate(f.p2, axis, theta), p3: rotate(f.p3, axis, theta), p4: rotate(f.p4, axis, theta), color: f.color)
}
cube.face = face
}
@inline(__always)
mutating func move_slice(_ slice: [Int], _ axis: Vector3d, _ theta: Double) {
for e in slice {
rotate_cube(&self.cube[e], axis, theta)
}
}
mutating func moveEvent(_ cam: Cam, _ clickedMousePos3d: Vector3d?, _ mousePos3d: Vector3d) -> String? {
func th2m(_ T: Double) -> String {
var t = T
while (t < 0) {
t += .pi * 2
}
t += .pi * 0.25
t /= .pi * 0.5
return String(Int(floor(t)) % 4)
}
let e = self.faceEvent(cam, clickedMousePos3d, mousePos3d)
var move: String? = nil
self.init_pos()
if let e, let e_move = e.move {
let mag = 2.5
let ax = Vector3d(x: 1, y: 0, z: 0)
let ay = Vector3d(x: 0, y: 1, z: 0)
let az = Vector3d(x: 0, y: 0, z: 1)
let tx = (e.mp1.x - e.mp2.x) * mag
let ty = (e.mp1.y - e.mp2.y) * mag
let tz = (e.mp1.z - e.mp2.z) * mag
switch e.f {
case "F":
switch e_move {
case "U":
self.move_slice(self.get_cubes_from_move(e.move), ay, tx)
move = e_move + th2m(tx)
case "D":
self.move_slice(self.get_cubes_from_move(e.move), ay, tx)
move = e_move + th2m(-tx)
case "L":
self.move_slice(self.get_cubes_from_move(e.move), ax, -ty)
move = e_move + th2m(-ty)
case "R":
self.move_slice(self.get_cubes_from_move(e.move), ax, -ty)
move = e_move + th2m(ty)
default: break
}
case "B":
switch e_move {
case "U":
self.move_slice(self.get_cubes_from_move(e.move), ay, -tx)
move = e_move + th2m(-tx)
case "D":
self.move_slice(self.get_cubes_from_move(e.move), ay, -tx)
move = e_move + th2m(tx)
case "L":
self.move_slice(self.get_cubes_from_move(e.move), ax, ty)
move = e_move + th2m(ty)
case "R":
self.move_slice(self.get_cubes_from_move(e.move), ax, ty)
move = e_move + th2m(-ty)
default: break
}
case "R":
switch e_move {
case "U":
self.move_slice(self.get_cubes_from_move(e.move), ay, tz)
move = e_move + th2m(tz)
case "D":
self.move_slice(self.get_cubes_from_move(e.move), ay, tz)
move = e_move + th2m(-tz)
case "F":
self.move_slice(self.get_cubes_from_move(e.move), az, -ty)
move = e_move + th2m(-ty)
case "B":
self.move_slice(self.get_cubes_from_move(e.move), az, -ty)
move = e_move + th2m(ty)
default: break
}
case "L":
switch e_move {
case "U":
self.move_slice(self.get_cubes_from_move(e.move), ay, -tz)
move = e_move + th2m(-tz)
case "D":
self.move_slice(self.get_cubes_from_move(e.move), ay, -tz)
move = e_move + th2m(tz)
case "F":
self.move_slice(self.get_cubes_from_move(e.move), az, ty)
move = e_move + th2m(ty)
case "B":
self.move_slice(self.get_cubes_from_move(e.move), az, ty)
move = e_move + th2m(-ty)
default: break
}
case "U":
switch e_move {
case "L":
self.move_slice(self.get_cubes_from_move(e.move), ax, tz)
move = e_move + th2m(tz)
case "R":
self.move_slice(self.get_cubes_from_move(e.move), ax, tz)
move = e_move + th2m(-tz)
case "F":
self.move_slice(self.get_cubes_from_move(e.move), az, -tx)
move = e_move + th2m(-tx)
case "B":
self.move_slice(self.get_cubes_from_move(e.move), az, -tx)
move = e_move + th2m(tx)
default: break
}
case "D":
switch e_move {
case "L":
self.move_slice(self.get_cubes_from_move(e.move), ax, -tz)
move = e_move + th2m(-tz)
case "R":
self.move_slice(self.get_cubes_from_move(e.move), ax, -tz)
move = e_move + th2m(tz)
case "F":
self.move_slice(self.get_cubes_from_move(e.move), az, tx)
move = e_move + th2m(tx)
case "B":
self.move_slice(self.get_cubes_from_move(e.move), az, tx)
move = e_move + th2m(-tx)
default: break
}
default: break
}
}
return move
}
mutating func applyMove(_ move: String?) {
guard let move else { return }
self.init_pos()
func replacement(_ f1: (c: Int, f: Int), _ f2: (c: Int, f: Int), _ f3: (c: Int, f: Int), _ f4: (c: Int, f: Int)) {
let tmp = self.cube[f4.c].face[f4.f].color
self.cube[f4.c].face[f4.f].color = self.cube[f3.c].face[f3.f].color
self.cube[f3.c].face[f3.f].color = self.cube[f2.c].face[f2.f].color
self.cube[f2.c].face[f2.f].color = self.cube[f1.c].face[f1.f].color
self.cube[f1.c].face[f1.f].color = tmp
}
func replacementU1() {
replacement(( 0, 2), (17, 1), (19, 3), ( 2, 0))
replacement(( 9, 2), (18, 1), (11, 3), ( 1, 0))
replacement((17, 2), (19, 1), ( 2, 3), ( 0, 0))
replacement(( 0, 4), (17, 4), (19, 4), ( 2, 4))
replacement(( 9, 4), (18, 4), (11, 4), ( 1, 4))
}
func replacementD1() {
replacement((23, 2), ( 6, 0), ( 8, 3), (25, 1))
replacement((14, 2), ( 7, 0), (16, 3), (24, 1))
replacement(( 6, 2), ( 8, 0), (25, 3), (23, 1))
replacement((23, 5), ( 6, 5), ( 8, 5), (25, 5))
replacement((14, 5), ( 7, 5), (16, 5), (24, 5))
}
func replacementR1() {
replacement(( 2, 0), (19, 4), (25, 1), ( 8, 5))
replacement(( 5, 0), (11, 4), (22, 1), (16, 5))
replacement(( 8, 0), ( 2, 4), (19, 1), (25, 5))
replacement(( 2, 3), (19, 3), (25, 3), ( 8, 3))
replacement(( 5, 3), (11, 3), (22, 3), (16, 3))
}
func replacementL1() {
replacement(( 0, 0), ( 6, 5), (23, 1), (17, 4))
replacement(( 3, 0), (14, 5), (20, 1), ( 9, 4))
replacement(( 6, 0), (23, 5), (17, 1), ( 0, 4))
replacement(( 0, 2), ( 6, 2), (23, 2), (17, 2))
replacement(( 3, 2), (14, 2), (20, 2), ( 9, 2))
}
func replacementF1() {
replacement(( 0, 4), ( 2, 3), ( 8, 5), ( 6, 2))
replacement(( 1, 4), ( 5, 3), ( 7, 5), ( 3, 2))
replacement(( 2, 4), ( 8, 3), ( 6, 5), ( 0, 2))
replacement(( 0, 0), ( 2, 0), ( 8, 0), ( 6, 0))
replacement(( 1, 0), ( 5, 0), ( 7, 0), ( 3, 0))
}
func replacementB1() {
replacement((19, 4), (17, 2), (23, 5), (25, 3))
replacement((18, 4), (20, 2), (24, 5), (22, 3))
replacement((17, 4), (23, 2), (25, 5), (19, 3))
replacement((19, 1), (17, 1), (23, 1), (25, 1))
replacement((18, 1), (20, 1), (24, 1), (22, 1))
}
switch (move) {
case "U1", "U":
replacementU1()
case "U2":
replacementU1()
replacementU1()
case "U3", "U'":
replacementU1()
replacementU1()
replacementU1()
case "D1", "D":
replacementD1()
case "D2":
replacementD1()
replacementD1()
case "D3", "D'":
replacementD1()
replacementD1()
replacementD1()
case "R1", "R":
replacementR1()
case "R2":
replacementR1()
replacementR1()
case "R3", "R'":
replacementR1()
replacementR1()
replacementR1()
case "L1", "L":
replacementL1()
case "L2":
replacementL1()
replacementL1()
case "L3", "L'":
replacementL1()
replacementL1()
replacementL1()
case "F1", "F":
replacementF1()
case "F2":
replacementF1()
replacementF1()
case "F3", "F'":
replacementF1()
replacementF1()
replacementF1()
case "B1", "B":
replacementB1()
case "B2":
replacementB1()
replacementB1()
case "B3", "B'":
replacementB1()
replacementB1()
replacementB1()
default: break
}
}
mutating func applyMoveR(_ move: String) {
self.init_pos()
switch (move) {
case "U1", "U": self.applyMove("U3")
case "U2": self.applyMove("U2")
case "U3", "U'": self.applyMove("U1")
case "D1", "D": self.applyMove("D3")
case "D2": self.applyMove("D2")
case "D3", "D'": self.applyMove("D1")
case "R1", "R": self.applyMove("R3")
case "R2": self.applyMove("R2")
case "R3", "R'": self.applyMove("R1")
case "L1", "L": self.applyMove("L3")
case "L2": self.applyMove("L2")
case "L3", "L'": self.applyMove("L1")
case "F1", "F": self.applyMove("F3")
case "F2": self.applyMove("F2")
case "F3", "F'": self.applyMove("F1")
case "B1", "B": self.applyMove("B3")
case "B2": self.applyMove("B2")
case "B3", "B'": self.applyMove("B1")
default: break
}
}
}
func init_rubiks_cube(_ cube_size: Double, _ red: Int, _ green: Int, _ yellow: Int, _ white: Int, _ blue: Int, _ orange: Int) -> RubiksCube {
let black = 0 //Color.black
let p1 = Vector3d(x: -cube_size, y: -cube_size, z: -cube_size)
let p2 = Vector3d(x: cube_size, y: -cube_size, z: -cube_size)
let p3 = Vector3d(x: cube_size, y: cube_size, z: -cube_size)
let p4 = Vector3d(x: -cube_size, y: cube_size, z: -cube_size)
let p5 = Vector3d(x: -cube_size, y: -cube_size, z: cube_size)
let p6 = Vector3d(x: cube_size, y: -cube_size, z: cube_size)
let p7 = Vector3d(x: cube_size, y: cube_size, z: cube_size)
let p8 = Vector3d(x: -cube_size, y: cube_size, z: cube_size)
let p000 = p1
let p100 = vec_add(p1, vec_mul(vec_div(vec_sub(p2, p1), 3), 1))
let p200 = vec_add(p1, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p300 = p2
let p010 = vec_add(p1, vec_mul(vec_div(vec_sub(p4, p1), 3), 1))
let p020 = vec_add(p1, vec_mul(vec_div(vec_sub(p4, p1), 3), 2))
let p030 = p4
let p001 = vec_add(p1, vec_mul(vec_div(vec_sub(p5, p1), 3), 1))
let p002 = vec_add(p1, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p003 = p5
let p110 = vec_add(p100, vec_mul(vec_div(vec_sub(p4, p1), 3), 1))
let p120 = vec_add(p100, vec_mul(vec_div(vec_sub(p4, p1), 3), 2))
let p130 = vec_add(p100, vec_sub(p4, p1))
let p011 = vec_add(p001, vec_mul(vec_div(vec_sub(p4, p1), 3), 1))
let p111 = vec_add(p011, vec_mul(vec_div(vec_sub(p2, p1), 3), 1))
let p101 = vec_add(p100, vec_mul(vec_div(vec_sub(p5, p1), 3), 1))
let p210 = vec_add(p010, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p230 = vec_add(p030, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p211 = vec_add(p011, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p201 = vec_add(p001, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p310 = vec_add(p010, vec_sub(p2, p1))
let p311 = vec_add(p011, vec_sub(p2, p1))
let p301 = vec_add(p001, vec_sub(p2, p1))
let p021 = vec_add(p001, vec_mul(vec_div(vec_sub(p4, p1), 3), 2))
let p031 = vec_add(p001, vec_sub(p4, p1))
let p231 = vec_add(p031, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p121 = vec_add(p101, vec_mul(vec_div(vec_sub(p4, p1), 3), 2))
let p131 = vec_add(p101, vec_sub(p4, p1))
let p220 = vec_add(p020, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p221 = vec_add(p021, vec_mul(vec_div(vec_sub(p2, p1), 3), 2))
let p320 = vec_add(p020, vec_sub(p2, p1))
let p330 = vec_add(p030, vec_sub(p2, p1))
let p321 = vec_add(p021, vec_sub(p2, p1))
let p331 = vec_add(p031, vec_sub(p2, p1))
let p012 = vec_add(p010, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p112 = vec_add(p110, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p102 = vec_add(p100, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p212 = vec_add(p210, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p202 = vec_add(p200, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p312 = vec_add(p310, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p302 = vec_add(p300, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p022 = vec_add(p020, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p122 = vec_add(p120, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p222 = vec_add(p220, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p322 = vec_add(p320, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p032 = vec_add(p030, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p132 = vec_add(p130, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p232 = vec_add(p230, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p332 = vec_add(p330, vec_mul(vec_div(vec_sub(p5, p1), 3), 2))
let p013 = vec_add(p010, vec_sub(p5, p1))
let p113 = vec_add(p110, vec_sub(p5, p1))
let p103 = vec_add(p100, vec_sub(p5, p1))
let p213 = vec_add(p210, vec_sub(p5, p1))
let p203 = vec_add(p200, vec_sub(p5, p1))
let p313 = vec_add(p310, vec_sub(p5, p1))
let p303 = vec_add(p300, vec_sub(p5, p1))
let p023 = vec_add(p020, vec_sub(p5, p1))
let p123 = vec_add(p120, vec_sub(p5, p1))
let p223 = vec_add(p220, vec_sub(p5, p1))
let p323 = vec_add(p320, vec_sub(p5, p1))
let p033 = vec_add(p030, vec_sub(p5, p1))
let p133 = vec_add(p130, vec_sub(p5, p1))
let p233 = vec_add(p230, vec_sub(p5, p1))
let p333 = vec_add(p330, vec_sub(p5, p1))
let frame = Frame(face: [
FrameFace(p1: p1, p2: p2, p3: p3, p4: p4, label: "F", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
FrameFace(p1: p5, p2: p8, p3: p7, p4: p6, label: "B", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
FrameFace(p1: p1, p2: p4, p3: p8, p4: p5, label: "L", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
FrameFace(p1: p2, p2: p6, p3: p7, p4: p3, label: "R", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
FrameFace(p1: p1, p2: p5, p3: p6, p4: p2, label: "U", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
FrameFace(p1: p4, p2: p3, p3: p7, p4: p8, label: "D", mouseOn: { _,_ in return false }, faceEvent: { _,_,_ in return nil }),
])
let cube: Cubes = [
Cube(face: [//0
CubeFace(p1: p000, p2: p100, p3: p110, p4: p010, color:red),
CubeFace(p1: p001, p2: p011, p3: p111, p4: p101, color:black),
CubeFace(p1: p000, p2: p010, p3: p011, p4: p001, color:green),
CubeFace(p1: p100, p2: p101, p3: p111, p4: p110, color:black),
CubeFace(p1: p000, p2: p001, p3: p101, p4: p100, color:white),
CubeFace(p1: p010, p2: p110, p3: p111, p4: p011, color:black),
]),
Cube(face: [//1
CubeFace(p1: p100, p2: p200, p3: p210, p4: p110, color: red),
CubeFace(p1: p101, p2: p111, p3: p211, p4: p201, color: black),
CubeFace(p1: p100, p2: p110, p3: p111, p4: p101, color: black),
CubeFace(p1: p200, p2: p201, p3: p211, p4: p210, color: black),
CubeFace(p1: p100, p2: p101, p3: p201, p4: p200, color: white),
CubeFace(p1: p110, p2: p210, p3: p211, p4: p111, color: black),
]),
Cube(face: [//2
CubeFace(p1: p200, p2: p300, p3: p310, p4: p210, color: red),
CubeFace(p1: p201, p2: p211, p3: p311, p4: p301, color: black),
CubeFace(p1: p200, p2: p210, p3: p211, p4: p201, color: black),
CubeFace(p1: p300, p2: p301, p3: p311, p4: p310, color: yellow),
CubeFace(p1: p200, p2: p201, p3: p301, p4: p300, color: white),
CubeFace(p1: p210, p2: p310, p3: p311, p4: p211, color: black),
]),
Cube(face: [//3
CubeFace(p1: p010, p2: p110, p3: p120, p4: p020, color: red),
CubeFace(p1: p011, p2: p021, p3: p121, p4: p111, color: black),
CubeFace(p1: p010, p2: p020, p3: p021, p4: p011, color: green),
CubeFace(p1: p110, p2: p111, p3: p121, p4: p120, color: black),
CubeFace(p1: p010, p2: p011, p3: p111, p4: p110, color: black),
CubeFace(p1: p020, p2: p120, p3: p121, p4: p021, color: black),
]),
Cube(face: [//4
CubeFace(p1: p110, p2: p210, p3: p220, p4: p120, color: red),
CubeFace(p1: p111, p2: p121, p3: p221, p4: p211, color: black),
CubeFace(p1: p110, p2: p120, p3: p121, p4: p111, color: black),
CubeFace(p1: p210, p2: p211, p3: p221, p4: p220, color: black),
CubeFace(p1: p110, p2: p111, p3: p211, p4: p210, color: black),
CubeFace(p1: p120, p2: p220, p3: p221, p4: p121, color: black),
]),
Cube(face: [//5
CubeFace(p1: p210, p2: p310, p3: p320, p4: p220, color: red),
CubeFace(p1: p211, p2: p221, p3: p321, p4: p311, color: black),
CubeFace(p1: p210, p2: p220, p3: p221, p4: p211, color: black),
CubeFace(p1: p310, p2: p311, p3: p321, p4: p320, color: yellow),
CubeFace(p1: p210, p2: p211, p3: p311, p4: p310, color: black),
CubeFace(p1: p220, p2: p320, p3: p321, p4: p221, color: black),
]),
Cube(face: [//6
CubeFace(p1: p020, p2: p120, p3: p130, p4: p030, color: red),
CubeFace(p1: p021, p2: p031, p3: p131, p4: p121, color: black),
CubeFace(p1: p020, p2: p030, p3: p031, p4: p021, color: green),
CubeFace(p1: p120, p2: p121, p3: p131, p4: p130, color: black),
CubeFace(p1: p020, p2: p021, p3: p121, p4: p120, color: black),
CubeFace(p1: p030, p2: p130, p3: p131, p4: p031, color: blue),
]),
Cube(face: [//7
CubeFace(p1: p120, p2: p220, p3: p230, p4: p130, color: red),
CubeFace(p1: p121, p2: p131, p3: p231, p4: p221, color: black),
CubeFace(p1: p120, p2: p130, p3: p131, p4: p121, color: black),
CubeFace(p1: p220, p2: p221, p3: p231, p4: p230, color: black),
CubeFace(p1: p120, p2: p121, p3: p221, p4: p220, color: black),
CubeFace(p1: p130, p2: p230, p3: p231, p4: p131, color: blue),
]),
Cube(face: [//8
CubeFace(p1: p220, p2: p320, p3: p330, p4: p230, color: red),
CubeFace(p1: p221, p2: p231, p3: p331, p4: p321, color: black),
CubeFace(p1: p220, p2: p230, p3: p231, p4: p221, color: black),
CubeFace(p1: p320, p2: p321, p3: p331, p4: p330, color: yellow),
CubeFace(p1: p220, p2: p221, p3: p321, p4: p320, color: black),
CubeFace(p1: p230, p2: p330, p3: p331, p4: p231, color: blue),
]),
Cube(face: [//9
CubeFace(p1: p001, p2: p101, p3: p111, p4: p011, color: black),
CubeFace(p1: p002, p2: p012, p3: p112, p4: p102, color: black),
CubeFace(p1: p001, p2: p011, p3: p012, p4: p002, color: green),
CubeFace(p1: p101, p2: p102, p3: p112, p4: p111, color: black),
CubeFace(p1: p001, p2: p002, p3: p102, p4: p101, color: white),
CubeFace(p1: p011, p2: p111, p3: p112, p4: p012, color: black),
]),
Cube(face: [//10
CubeFace(p1: p101, p2: p201, p3: p211, p4: p111, color: black),
CubeFace(p1: p102, p2: p112, p3: p212, p4: p202, color: black),
CubeFace(p1: p101, p2: p111, p3: p112, p4: p102, color: black),
CubeFace(p1: p201, p2: p202, p3: p212, p4: p211, color: black),
CubeFace(p1: p101, p2: p102, p3: p202, p4: p201, color: white),
CubeFace(p1: p111, p2: p211, p3: p212, p4: p112, color: black),
]),
Cube(face: [//11
CubeFace(p1: p201, p2: p301, p3: p311, p4: p211, color: black),
CubeFace(p1: p202, p2: p212, p3: p312, p4: p302, color: black),
CubeFace(p1: p201, p2: p211, p3: p212, p4: p202, color: black),
CubeFace(p1: p301, p2: p302, p3: p312, p4: p311, color: yellow),
CubeFace(p1: p201, p2: p202, p3: p302, p4: p301, color: white),
CubeFace(p1: p211, p2: p311, p3: p312, p4: p212, color: black),
]),
Cube(face: [//12
CubeFace(p1: p011, p2: p111, p3: p121, p4: p021, color: black),
CubeFace(p1: p012, p2: p022, p3: p122, p4: p112, color: black),
CubeFace(p1: p011, p2: p021, p3: p022, p4: p012, color: green),
CubeFace(p1: p111, p2: p112, p3: p122, p4: p121, color: black),
CubeFace(p1: p011, p2: p012, p3: p112, p4: p111, color: black),
CubeFace(p1: p021, p2: p121, p3: p122, p4: p022, color: black),
]),
Cube(face: [//13
CubeFace(p1: p211, p2: p311, p3: p321, p4: p221, color: black),
CubeFace(p1: p212, p2: p222, p3: p322, p4: p312, color: black),
CubeFace(p1: p211, p2: p221, p3: p222, p4: p212, color: black),
CubeFace(p1: p311, p2: p312, p3: p322, p4: p321, color: yellow),
CubeFace(p1: p211, p2: p212, p3: p312, p4: p311, color: black),
CubeFace(p1: p221, p2: p321, p3: p322, p4: p222, color: black),
]),
Cube(face: [//14
CubeFace(p1: p021, p2: p121, p3: p131, p4: p031, color: black),
CubeFace(p1: p022, p2: p032, p3: p132, p4: p122, color: black),
CubeFace(p1: p021, p2: p031, p3: p032, p4: p022, color: green),
CubeFace(p1: p121, p2: p122, p3: p132, p4: p131, color: black),
CubeFace(p1: p021, p2: p022, p3: p122, p4: p121, color: black),
CubeFace(p1: p031, p2: p131, p3: p132, p4: p032, color: blue),
]),
Cube(face: [//15
CubeFace(p1: p121, p2: p221, p3: p231, p4: p131, color: black),
CubeFace(p1: p122, p2: p132, p3: p232, p4: p222, color: black),
CubeFace(p1: p121, p2: p131, p3: p132, p4: p122, color: black),
CubeFace(p1: p221, p2: p222, p3: p232, p4: p231, color: black),
CubeFace(p1: p121, p2: p122, p3: p222, p4: p221, color: black),
CubeFace(p1: p131, p2: p231, p3: p232, p4: p132, color: blue),
]),
Cube(face: [//16
CubeFace(p1: p221, p2: p321, p3: p331, p4: p231, color: black),
CubeFace(p1: p222, p2: p232, p3: p332, p4: p322, color: black),
CubeFace(p1: p221, p2: p231, p3: p232, p4: p222, color: black),
CubeFace(p1: p321, p2: p322, p3: p332, p4: p331, color: yellow),
CubeFace(p1: p221, p2: p222, p3: p322, p4: p321, color: black),
CubeFace(p1: p231, p2: p331, p3: p332, p4: p232, color: blue),
]),
Cube(face: [//17
CubeFace(p1: p002, p2: p102, p3: p112, p4: p012, color: black),
CubeFace(p1: p003, p2: p013, p3: p113, p4: p103, color: orange),
CubeFace(p1: p002, p2: p012, p3: p013, p4: p003, color: green),
CubeFace(p1: p102, p2: p103, p3: p113, p4: p112, color: black),
CubeFace(p1: p002, p2: p003, p3: p103, p4: p102, color: white),
CubeFace(p1: p012, p2: p112, p3: p113, p4: p013, color: black),
]),
Cube(face: [//18
CubeFace(p1: p102, p2: p202, p3: p212, p4: p112, color: black),
CubeFace(p1: p103, p2: p113, p3: p213, p4: p203, color: orange),
CubeFace(p1: p102, p2: p112, p3: p113, p4: p103, color: black),
CubeFace(p1: p202, p2: p203, p3: p213, p4: p212, color: black),
CubeFace(p1: p102, p2: p103, p3: p203, p4: p202, color: white),
CubeFace(p1: p112, p2: p212, p3: p213, p4: p113, color: black),
]),
Cube(face: [//19
CubeFace(p1: p202, p2: p302, p3: p312, p4: p212, color: black),
CubeFace(p1: p203, p2: p213, p3: p313, p4: p303, color: orange),
CubeFace(p1: p202, p2: p212, p3: p213, p4: p203, color: black),
CubeFace(p1: p302, p2: p303, p3: p313, p4: p312, color: yellow),
CubeFace(p1: p202, p2: p203, p3: p303, p4: p302, color: white),
CubeFace(p1: p212, p2: p312, p3: p313, p4: p213, color: black),
]),
Cube(face: [//20
CubeFace(p1: p012, p2: p112, p3: p122, p4: p022, color: black),
CubeFace(p1: p013, p2: p023, p3: p123, p4: p113, color: orange),
CubeFace(p1: p012, p2: p022, p3: p023, p4: p013, color: green),
CubeFace(p1: p112, p2: p113, p3: p123, p4: p122, color: black),
CubeFace(p1: p012, p2: p013, p3: p113, p4: p112, color: black),
CubeFace(p1: p022, p2: p122, p3: p123, p4: p023, color: black),
]),
Cube(face: [//21
CubeFace(p1: p112, p2: p212, p3: p222, p4: p122, color: black),
CubeFace(p1: p113, p2: p123, p3: p223, p4: p213, color: orange),
CubeFace(p1: p112, p2: p122, p3: p123, p4: p113, color: black),
CubeFace(p1: p212, p2: p213, p3: p223, p4: p222, color: black),
CubeFace(p1: p112, p2: p113, p3: p213, p4: p212, color: black),
CubeFace(p1: p122, p2: p222, p3: p223, p4: p123, color: black),
]),
Cube(face: [//22
CubeFace(p1: p212, p2: p312, p3: p322, p4: p222, color: black),
CubeFace(p1: p213, p2: p223, p3: p323, p4: p313, color: orange),
CubeFace(p1: p212, p2: p222, p3: p223, p4: p213, color: black),
CubeFace(p1: p312, p2: p313, p3: p323, p4: p322, color: yellow),
CubeFace(p1: p212, p2: p213, p3: p313, p4: p312, color: black),
CubeFace(p1: p222, p2: p322, p3: p323, p4: p223, color: black),
]),
Cube(face: [//23
CubeFace(p1: p022, p2: p122, p3: p132, p4: p032, color: black),
CubeFace(p1: p023, p2: p033, p3: p133, p4: p123, color: orange),
CubeFace(p1: p022, p2: p032, p3: p033, p4: p023, color: green),
CubeFace(p1: p122, p2: p123, p3: p133, p4: p132, color: black),
CubeFace(p1: p022, p2: p023, p3: p123, p4: p122, color: black),
CubeFace(p1: p032, p2: p132, p3: p133, p4: p033, color: blue),
]),
Cube(face: [//24
CubeFace(p1: p122, p2: p222, p3: p232, p4: p132, color: black),
CubeFace(p1: p123, p2: p133, p3: p233, p4: p223, color: orange),
CubeFace(p1: p122, p2: p132, p3: p133, p4: p123, color: black),
CubeFace(p1: p222, p2: p223, p3: p233, p4: p232, color: black),
CubeFace(p1: p122, p2: p123, p3: p223, p4: p222, color: black),
CubeFace(p1: p132, p2: p232, p3: p233, p4: p133, color: blue),
]),
Cube(face: [//25
CubeFace(p1: p222, p2: p322, p3: p332, p4: p232, color: black),
CubeFace(p1: p223, p2: p233, p3: p333, p4: p323, color: orange),
CubeFace(p1: p222, p2: p232, p3: p233, p4: p223, color: black),
CubeFace(p1: p322, p2: p323, p3: p333, p4: p332, color: yellow),
CubeFace(p1: p222, p2: p223, p3: p323, p4: p322, color: black),
CubeFace(p1: p232, p2: p332, p3: p333, p4: p233, color: blue),
])
]
let cube_init_pos = cube.map { c in
c.face.map {
CubePos(p1: Vector3d(x: $0.p1.x, y: $0.p1.y, z: $0.p1.z),
p2: Vector3d(x: $0.p2.x, y: $0.p2.y, z: $0.p2.z),
p3: Vector3d(x: $0.p3.x, y: $0.p3.y, z: $0.p3.z),
p4: Vector3d(x: $0.p4.x, y: $0.p4.y, z: $0.p4.z)
)
}
}
let move_cubes = MoveCubes(
u: [0, 1, 2, 9, 10, 11, 17, 18, 19],
d: [6, 7, 8, 14, 15, 16, 23, 24, 25],
l: [0, 3, 6, 9, 12, 14, 17, 20, 23],
r: [2, 5, 8, 11, 13, 16, 19, 22, 25],
f: [0, 1, 2, 3, 4, 5, 6, 7, 8],
b: [17, 18, 19, 20, 21, 22, 23, 24, 25]
)
let rubiks_cube = RubiksCube(
cube: cube,
frame: frame,
cube_init_pos: cube_init_pos,
move_cubes: move_cubes
)
return rubiks_cube
}
import Foundation
struct MouseEvent {
var which: Int
var point: CGPoint
let size: CGSize
}
func getMousePos(_ e: MouseEvent) -> CGPoint {
return e.point
}
func mousePosTo3d(_ cam: Cam, _ pos: CGPoint) -> Vector3d {
let nx = cam.n
var ny = v_product(neg(cam.pos), nx)
ny = vec_div(ny, norm(ny))
return vec_add(vec_mul(nx, pos.x), vec_mul(ny, pos.y))
}
func MousePosToFacePos(_ cam: Cam, _ face: Face, _ p: Vector3d) -> Vector3d {
let fv1 = vec_sub(face.p1, face.p2)
let fv2 = vec_sub(face.p3, face.p2)
var fn = v_product(fv1, fv2)
fn = vec_div(fn, norm(fn))
let fd = -d_product(face.p1, fn)
let q = cam.pos
let v = vec_sub(p, q)
let t = -(d_product(fn, q) + fd) / d_product(fn, v)
let r = vec_add(q, vec_mul(v, t))
return r
}
import SwiftUI
func R3toR2(_ cam: Cam, _ p: Vector3d) -> CGPoint {
let nx = cam.n
var ny = v_product(neg(cam.pos), nx)
ny = vec_div(ny, norm(ny))
let nz = vec_div(cam.pos, norm(cam.pos))
var pn = vec_sub(p, cam.pos)
let ratio = norm(cam.pos) / d_product(pn, neg(nz))
pn = vec_mul(pn, ratio)
let x = d_product(nx, pn)
let y = d_product(ny, pn)
let ret = CGPoint(x: x, y: y)
return ret
}
/* 四角形を描画する(2次元座標) */
func quadrangle_2d(_ fillstyle: Int, _ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint, _ p4: CGPoint) -> (Path, Int) {
let scale_px = Double(1)
let o_px_x = Double(0.5)
let o_px_y = Double(0.5)
var path = Path()
path.move(to: CGPoint(x: p1.x * scale_px + o_px_x, y: p1.y * scale_px + o_px_y))
path.addLine(to: CGPoint(x: p2.x * scale_px + o_px_x, y: p2.y * scale_px + o_px_y))
path.addLine(to: CGPoint(x: p3.x * scale_px + o_px_x, y: p3.y * scale_px + o_px_y))
path.addLine(to: CGPoint(x: p4.x * scale_px + o_px_x, y: p4.y * scale_px + o_px_y))
return (path, fillstyle)
}
/* 四角形を描画する(3次元座標) */
func quadrangle_3d(_ cam: Cam, _ fillstyle: Int, _ p1: Vector3d, _ p2: Vector3d, _ p3: Vector3d, _ p4: Vector3d) -> (Path, Int) {
let p_2d_1 = R3toR2(cam, p1)
let p_2d_2 = R3toR2(cam, p2)
let p_2d_3 = R3toR2(cam, p3)
let p_2d_4 = R3toR2(cam, p4)
return quadrangle_2d(fillstyle, p_2d_1, p_2d_2, p_2d_3, p_2d_4)
}
/* 立方体を描画する */
/* カメラから近い順に3面描画すれば辻褄が合う */
func render_cube(_ cube: Cube, _ cam: Cam) -> [(Path, Int)] {
let face = cube.face.sorted { a, b in
let da = distance(cam.pos, face_center(a))
let db = distance(cam.pos, face_center(b))
return da < db
}
var result = [(Path, Int)]()
for i in stride(from: 3, through: 0, by: -1) {
let e = face[i]
let path = quadrangle_3d(cam, e.color, e.p1, e.p2, e.p3, e.p4)
result.append(path)
}
return result
}
/* カメラから遠い順に全てのキューブをレンダリングすれば辻褄が合う */
#if false
func render_rubiks_cube(_ rubiks_cube: RubiksCube, _ cam: Cam) {
let cube = rubiks_cube.cube.sorted { a, b in
let da = distance(cam.pos, cube_center(a))
let db = distance(cam.pos, cube_center(b))
return db < da
}
for c in cube {
render_cube(c, cam)
}
}
#endif
import Foundation
public struct Vector3d {
let x: Double
let y: Double
let z: Double
}
extension Vector3d {
public static func +(lhs: Self, rhs: Self) -> Self {
Vector3d(x: lhs.x + rhs.x,
y: lhs.y + rhs.y,
z: lhs.z + rhs.z)
}
public static func -(lhs: Self, rhs: Self) -> Self {
Vector3d(x: lhs.x - rhs.x,
y: lhs.y - rhs.y,
z: lhs.z - rhs.z)
}
public static func /(lhs: Self, rhs: Double) -> Self {
Vector3d(x: lhs.x / rhs,
y: lhs.y / rhs,
z: lhs.z / rhs)
}
public static func *(lhs: Self, rhs: Double) -> Self {
Vector3d(x: lhs.x * rhs,
y: lhs.y * rhs,
z: lhs.z * rhs)
}
//prefix operator -
public static prefix func -(_ lhs: Self) -> Self {
Vector3d(x: -lhs.x,
y: -lhs.y,
z: -lhs.z)
}
public static func normalize(_ lhs: Vector3d) -> Double {
sqrt(lhs.x * lhs.x + lhs.y * lhs.y + lhs.z * lhs.z)
}
public static func distance(_ lhs: Vector3d, _ rhs: Vector3d) -> Double {
sqrt((lhs.x - rhs.x) * (lhs.x - rhs.x) + (lhs.y - rhs.y) * (lhs.y - rhs.y) + (lhs.z - rhs.z) * (lhs.z - rhs.z))
}
//内積
public static func dProduct(_ lhs: Vector3d, _ rhs: Vector3d) -> Double {
lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z
}
//ベクトル積
public static func vProduct(_ lhs: Vector3d, _ rhs: Vector3d) -> Vector3d {
Vector3d(x: lhs.y * rhs.z - lhs.z * rhs.y,
y: lhs.z * rhs.x - lhs.x * rhs.z,
z: lhs.x * rhs.y - lhs.y * rhs.x)
}
}
@inlinable @inline(__always)
func vec_add(_ a: Vector3d, _ b: Vector3d) -> Vector3d { a + b }
@inlinable @inline(__always)
func vec_sub(_ a: Vector3d, _ b: Vector3d) -> Vector3d { a - b }
@inlinable @inline(__always)
func vec_div(_ a: Vector3d, _ div: Double) -> Vector3d { a / div }
@inlinable @inline(__always)
func vec_div(_ a: Vector3d, _ div: Int) -> Vector3d { a / Double(div) }
@inlinable @inline(__always)
func vec_mul(_ a: Vector3d, _ mul: Double) -> Vector3d { a * mul }
@inlinable @inline(__always)
func neg(_ p: Vector3d) -> Vector3d { -p }
@inlinable @inline(__always)
func norm(_ p: Vector3d) -> Double { Vector3d.normalize(p) }
@inlinable @inline(__always)
func distance(_ a: Vector3d, _ b: Vector3d) -> Double { Vector3d.distance(a, b) }
@inlinable @inline(__always)
func d_product(_ a: Vector3d, _ b: Vector3d) -> Double { Vector3d.dProduct(a, b) }
@inlinable @inline(__always)
func v_product(_ a: Vector3d, _ b: Vector3d) -> Vector3d { Vector3d.vProduct(a, b) }
/* 3D rotation */
func rotate(_ p: Vector3d, _ axis: Vector3d, _ theta: Double) -> Vector3d {
let a = axis / Vector3d.normalize(axis)
let c = cos(theta)
let s = sin(theta)
let result = Vector3d(
x: (c + a.x * a.x * (1 - c)) * p.x + (a.x * a.y * (1 - c) - a.z * s) * p.y + (a.x * a.z * (1 - c) + a.y * s) * p.z,
y: (a.x * a.y * (1 - c) + a.z * s) * p.x + (c + a.y * a.y * (1 - c)) * p.y + (a.y * a.z * (1 - c) - a.x * s) * p.z,
z: (a.x * a.z * (1 - c) - a.y * s) * p.x + (a.y * a.z * (1 - c) + a.x * s) * p.y + (c + a.z * a.z * (1 - c)) * p.z
)
return result
}
import SwiftUI
struct SubView: View {
@EnvironmentObject var cubeManager: CubeManager
@State var moves = [String]()
@State var moving = false
@State var reqAnimation = true
var body: some View {
VStack {
HStack(alignment: .top) {
CubeDrawView().environmentObject(cubeManager)
}.onAppear { cubeManager.setFrontCam() }
Spacer()
HStack {
Button("U", action: { move("U") }).frame(width: 40)
Button("F", action: { move("F") }).frame(width: 40)
Button("R", action: { move("R") }).frame(width: 40)
Button("D", action: { move("D") }).frame(width: 40)
Button("B", action: { move("B") }).frame(width: 40)
Button("L", action: { move("L") }).frame(width: 40)
}
HStack {
Button("U'", action: { move("U'") }).frame(width: 40)
Button("F'", action: { move("F'") }).frame(width: 40)
Button("R'", action: { move("R'") }).frame(width: 40)
Button("D'", action: { move("D'") }).frame(width: 40)
Button("B'", action: { move("B'") }).frame(width: 40)
Button("L'", action: { move("L'") }).frame(width: 40)
}
HStack {
Button("U2", action: { move("U2") }).frame(width: 40)
Button("F2", action: { move("F2") }).frame(width: 40)
Button("R2", action: { move("R2") }).frame(width: 40)
Button("D2", action: { move("D2") }).frame(width: 40)
Button("B2", action: { move("B2") }).frame(width: 40)
Button("L2", action: { move("L2") }).frame(width: 40)
}
HStack {
Button("Undo (\(cubeManager.undoCount))", action: { cubeManager.undo() }).disabled(!cubeManager.canUndo && moving)
.keyboardShortcut(.init("z"), modifiers: [.command])
.padding(.trailing, 50)
Button("Redo (\(cubeManager.redoCount))", action: { cubeManager.redo() }).disabled(!cubeManager.canRedo && moving)
.keyboardShortcut(.init("z"), modifiers: [.shift, .command])
}
HStack {
Button("Reset", action: {
moves.removeAll()
cubeManager.reset()
cubeManager.setFrontCam()
}).keyboardShortcut(.init("r"), modifiers: [.command])
Button("Scramble", action: { scramble() })
.keyboardShortcut(.init("s"), modifiers: [.command])
}
HStack {
Toggle(isOn: $reqAnimation) {
Text("with Animation")
}.toggleStyle(.button)
}
}
.padding(10)
}
func move(_ _notation: String, undo: Bool = false) {
var notation = _notation
if undo {
let rev = ["U": "U'", "U'": "U", "U2": "U2", "F": "F'", "F'": "F", "F2": "F2", "R": "R'", "R'": "R", "R2": "R2", "D": "D'", "D'": "D", "D2": "D2", "B": "B'", "B'": "B", "B2": "B2", "L": "L'", "L'": "L", "L2": "L2"]
if let undoNotation = rev[notation] {
notation = undoNotation
} else {
assertionFailure("_notation undo failer [\(_notation)]")
}
}
cubeManager.undoManager.beginUndoGrouping()
reqAnimation ? cubeManager.animate_move(notation) : cubeManager.static_move(notation)
cubeManager.undoManager.registerUndo(withTarget: cubeManager) { _ in
self.move(notation, undo: !undo)
}
cubeManager.undoManager.endUndoGrouping()
if reqAnimation { Task { try await Task.sleep(nanoseconds: 700_000_000) } }
}
func scramble() {
var prevMove = "x", move = "y"
let MoveNotation: [String] = ["U", "U'", "U2", "F", "F'", "F2", "R", "R'", "R2", "D", "D'", "D2", "B", "B'", "B2", "L", "L'", "L2"]
moves.removeAll()
for _ in 0 ..< 30 {
repeat {
move = MoveNotation.randomElement()!
} while move.first! == prevMove.first!
moves.append(move)
prevMove = move
}
moving = true
var doubleMove = false
Timer.scheduledTimer(withTimeInterval: reqAnimation ? 0.7: 0.1, repeats: true) { timer in
if !doubleMove {
if moves.isEmpty {
timer.invalidate()
moving = false
}
else {
self.move(moves.first!)
let move = moves.removeFirst()
if move.last! == "2" && reqAnimation {
doubleMove = true
}
}
} else {
doubleMove = false
}
}
}
}
struct ContentView: View {
var body: some View {
SubView()
.environmentObject(CubeManager.shared)
}
}
(コードの説明は省略します)
Javascriptでは、アニメーション描画の部分を非同期処理で実現していますが、そのままSwiftに移植してもViewが待通りに更新されませんでした。とりあえず Timerイベントで逃げました。
このままでも、iPad の Swift Playground で 問題なく動きます(指を滑らせると自在に視点を変えられます)。
次回の記事では、展開図と3D表示を同期して回転するようにすることと、次の解法アルゴリズムを導入したいと思います。
以上