3. ルービックキューブ 3D 表示の考察


## 1. ScenceKit による3D表示

↓ こちらの動画で説明されている [`ScenceKit`](https://developer.apple.com/documentation/scenekit/) による3D表示のコードは、非常にシンプルで、かつ、キューブの視点変更操作がノーコードで実現できる点は非常に魅力的です。

<iframe width="632" height="360" src="https://www.youtube.com/embed/M8q18D6WCBI" title="SwiftUI: Create an Incredible 3D Rubik&#39;s Cube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>



しかし、自分は次の点で使いこなすことができませんでした。

・ エッジキューブの2面やコーナーキューブの3面の色を 動的に変更する方法が分からない
・ルービックキューブ の面の回転をアニメーションで表現したいが、方法が分からない

（単に自分が`ScenceKit`を知らないだけだと思います）


## 2. Javascript による3D表示

 Javascript による ルービックキューブ の 3D表示ライブラリはいくつか公開されており、次のサイトで紹介されています。

 https://www.terabo.net/blog/cube-animation-for-web/

 この中の[`Roofpig`](http://jsfiddle.net/Lar5/86L4C/)を [`WKWebView`](https://developer.apple.com/documentation/webkit/wkwebview) に表示させる方法を試みたが、次の点で 使いこなすことができませんでした。

 ・視点をリセットする方法が分からない
 ・キューブの色を動的に指定する方法が分からない



 ## 3. Javascript による3D表示 (その2)
 
 次のサイトで紹介されている Javascript による ルービックキューブ の 3D表示は、外部の 3Dライブラリを使わず（CSSも使わず）、自身で3Dを2Dに変換する関数を定義して 2D Canvasに描画する方法で実現しています。表示のみならず、マウス操作による「面の回転」と「3D視点変更」（GUI）も実現しています。
 
 https://note.com/dimage/n/nac1f6c236f06

 https://qiita.com/ob_nullpo/items/3c37dc1ded8e30191bca

 このコードであれば、全行を解読し Swift への書き換えも可能であろうと考え、[こちら](https://github.com/HirokiHayakawa/javascript3d)を参考にさせていただくことにしました。


## 結果、

次のように動作するSwiftコードを書くことができました。

<iframe width="553" height="315" src="https://www.youtube.com/embed/gG7sf8PgPhk" title="Swift言語による ルービックキューブ の 3D表示" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

上記の Javascript によるコードとは、次の点で変更を加えています。

・マウス左ボタンドラッグによる ルービックキューブの面の回転を削除
・マウス右ボタンドラッグによる 視点変更を マウス左ボタンドラッグに変更
<br>

<details><summary><b><i>コードを表示する（8ファイル）</i></b></summary>


```swift:CubeDrawView.swift
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))
    }
}
```


```swift:CubeManager.swift
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() }}
}
```


```swift:CubeMathUtil.swift
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)
}
```


```swift:CubeUtil.swift
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
}
```


```swift:MousePosUtil.swift
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
}
```


```swift:RenderUtil.swift
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
```


```swift:Vector3d.swift
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
}
```


```swift:ContentView.swift
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)
    }
}
```

</details>


（コードの説明は省略します）
_Javascriptでは、アニメーション描画の部分を非同期処理で実現していますが、そのままSwiftに移植してもViewが待通りに更新されませんでした。とりあえず Timerイベントで逃げました。_<br>　



このままでも、iPad の Swift Playground で 問題なく動きます（指を滑らせると自在に視点を変えられます）。

![qube.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/b9d9a470-48c2-4032-8fcc-b350666b2d03.gif) 
（ _動きが鈍い場合は 画像をクリック_ ）




---

次回の記事では、展開図と3D表示を同期して回転するようにすることと、次の解法アルゴリズムを導入したいと思います。

https://github.com/muodov/kociemba

<br><br><br>**以上**