1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-05

1. ScenceKit による3D表示

↓ こちらの動画で説明されている ScenceKit による3D表示のコードは、非常にシンプルで、かつ、キューブの視点変更操作がノーコードで実現できる点は非常に魅力的です。

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

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

(単に自分がScenceKitを知らないだけだと思います)

2. Javascript による3D表示

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

この中のRoofpigWKWebView に表示させる方法を試みたが、次の点で 使いこなすことができませんでした。

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

3. Javascript による3D表示 (その2)

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

このコードであれば、全行を解読し Swift への書き換えも可能であろうと考え、こちらを参考にさせていただくことにしました。

結果、

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

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

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

コードを表示する(8ファイル)
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))
    }
}
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() }}
}
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)
}
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
}
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
}
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
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
}
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)
    }
}

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

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

qube.gif
動きが鈍い場合は 画像をクリック


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




以上

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?