Last active
December 28, 2025 00:35
-
-
Save s5bug/53d416aa7c35f8687b1780e5be667138 to your computer and use it in GitHub Desktop.
Movement around a grid wrapped on cube faces using quaternions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //> using scala 3.7.4 | |
| //> using dep org.typelevel::spire:0.18.0 | |
| import spire.math.{Algebraic, Quaternion} | |
| enum Face extends java.lang.Enum[Face] { | |
| case Front | |
| case Back | |
| case Left | |
| case Right | |
| case Top | |
| case Bottom | |
| } | |
| final case class Qube(size: Int, orientation: Quaternion[Algebraic], x: Int, y: Int) { | |
| def moveUp: Qube = | |
| if this.y <= 0 then | |
| this.copy(orientation = this.orientation * Qube.upWrap, y = this.size - 1) | |
| else | |
| this.copy(y = this.y - 1) | |
| def moveLeft: Qube = | |
| if this.x <= 0 then | |
| this.copy(orientation = this.orientation * Qube.leftWrap, x = this.size - 1) | |
| else | |
| this.copy(x = this.x - 1) | |
| def moveDown: Qube = | |
| if this.y >= this.size - 1 then | |
| this.copy(orientation = this.orientation * Qube.downWrap, y = 0) | |
| else | |
| this.copy(y = this.y + 1) | |
| def moveRight: Qube = | |
| if this.x >= this.size - 1 then | |
| this.copy(orientation = this.orientation * Qube.rightWrap, x = 0) | |
| else | |
| this.copy(x = this.x + 1) | |
| def face: Face = { | |
| val pointer = this.orientation * Qube.LeftVector * this.orientation.reciprocal | |
| pointer match { | |
| case Qube.LeftVector => Face.Left | |
| case Qube.RightVector => Face.Right | |
| case Qube.UpVector => Face.Top | |
| case Qube.DownVector => Face.Bottom | |
| case Qube.BackVector => Face.Back | |
| case Qube.FrontVector => Face.Front | |
| case notAFace => throw new IllegalStateException("unknown orientation: " + this.orientation) | |
| } | |
| } | |
| def normalizedPosition: (Int, Int) = { | |
| val r = this.orientation.r | |
| val i = this.orientation.i | |
| if r != Algebraic.Zero && i == Algebraic.Zero then { | |
| // 0deg case | |
| (x, y) | |
| } else if r == i then { | |
| // 90deg case | |
| ((this.size - 1) - y, x) | |
| } else if r == Algebraic.Zero && i != Algebraic.Zero then { | |
| // 180deg case | |
| ((this.size - 1) - x, (this.size - 1) - y) | |
| } else if r == -i then { | |
| // 270deg case | |
| (y, (this.size - 1) - x) | |
| } else { | |
| throw new IllegalStateException("unknown orientation: " + this.orientation) | |
| } | |
| } | |
| } | |
| object Qube { | |
| private final val oneOverSqrtTwo = Algebraic(1) / Algebraic(2).sqrt | |
| private final val LeftVector: Quaternion[Algebraic] = | |
| Quaternion.i[Algebraic] | |
| private final val UpVector: Quaternion[Algebraic] = | |
| Quaternion.j[Algebraic] | |
| private final val FrontVector: Quaternion[Algebraic] = | |
| Quaternion.k[Algebraic] | |
| private final val RightVector: Quaternion[Algebraic] = | |
| -LeftVector | |
| private final val DownVector: Quaternion[Algebraic] = | |
| -UpVector | |
| private final val BackVector: Quaternion[Algebraic] = | |
| -FrontVector | |
| private final val leftWrap: Quaternion[Algebraic] = | |
| Quaternion(oneOverSqrtTwo, Algebraic.Zero, oneOverSqrtTwo, Algebraic.Zero) | |
| private final val upWrap: Quaternion[Algebraic] = | |
| Quaternion(oneOverSqrtTwo, Algebraic.Zero, Algebraic.Zero, oneOverSqrtTwo) | |
| private final val rightWrap: Quaternion[Algebraic] = | |
| Quaternion(oneOverSqrtTwo, Algebraic.Zero, -oneOverSqrtTwo, Algebraic.Zero) | |
| private final val downWrap: Quaternion[Algebraic] = | |
| Quaternion(oneOverSqrtTwo, Algebraic.Zero, Algebraic.Zero, -oneOverSqrtTwo) | |
| def leftBackTop(size: Int): Qube = | |
| Qube(size, Quaternion.one, 0, 0) | |
| } | |
| val q0 = Qube.leftBackTop(2) | |
| q0 | |
| q0.face // => Left | |
| q0.normalizedPosition // => (0, 0) | |
| val q1 = q0.moveRight | |
| q1 | |
| q1.face // => Left | |
| q1.normalizedPosition // => (1, 0) | |
| val q2 = q1.moveRight | |
| q2 | |
| q2.face // => Front | |
| q2.normalizedPosition // => (0, 0) | |
| val q3 = q2.moveUp | |
| q3 | |
| q3.face // => Top | |
| q3.normalizedPosition // => (1, 1) | |
| val q4 = q3.moveUp | |
| q4 | |
| q4.face // => Top | |
| q4.normalizedPosition // => (0, 1) | |
| val q5 = q4.moveUp | |
| q5 | |
| q5.face // => Back | |
| q5.normalizedPosition // => (1, 0) | |
| val q6 = q5.moveLeft | |
| q6 | |
| q6.face // => Left | |
| q6.normalizedPosition // => (0, 0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment