Last active
November 26, 2020 08:55
-
-
Save dduan/ed77683dcc9b1a52f533d17f266aa769 to your computer and use it in GitHub Desktop.
Revisions
-
dduan revised this gist
Nov 22, 2020 . 1 changed file with 5 additions and 0 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,8 @@ /// A rotating 3-D cube in terminal /// Only works on macOS /// Run `swift cube.swift` in a terminal application to run it. /// For controlling the cube, see comments for `Key` in code. import Darwin enum RawModeError: Error { -
dduan revised this gist
Nov 22, 2020 . 1 changed file with 0 additions and 10 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -169,19 +169,9 @@ struct Vec3 { } } struct Cube { var size: Float = 1 var scanFrequency: Float = 0.1 // (texture for the surface, vector perpendicular to the surfaces, points on the surface) var surfaces: [(Character, Vec3, [Vec3])] { -
dduan revised this gist
Nov 22, 2020 . 1 changed file with 40 additions and 40 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -158,55 +158,55 @@ class Game { } struct Vec3 { let x, y, z: Float static func + (lhs: Self, rhs: Self) -> Self { .init(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z) } static func * (lhs: Self, rhs: Float) -> Self { .init(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs) } } extension Vec3: CustomStringConvertible { var description: String { "(\(x),\(y),\(z))" } } struct Cube { var size: Float = 1 var scanFrequency: Float = 0.1 var position: Vec3 = .init(x: 0, y: 0, z: 0) var maxZ: Float { sqrt(3) * size } // (texture for the surface, vector perpendicular to the surfaces, points on the surface) var surfaces: [(Character, Vec3, [Vec3])] { [(Character, Vec3, Vec3, Vec3, Vec3)]([ ("█", .init(x: 0, y: 0, z: -1), .init(x: -0.5, y: -0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 1, z: 0)), ("▓", .init(x: 0, y: 1, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 0, z: 1)), ("▒", .init(x: 0, y: 0, z: 1), .init(x: -0.5, y: 0.5, z: 0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: -1, z: 0)), ("░", .init(x: 0, y: -1, z: 0), .init(x: -0.5, y: -0.5, z: 0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 0, z: -1)), ("▦", .init(x: 1, y: 0, z: 0), .init(x: 0.5, y: -0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: 1, z: 0)), ("▧", .init(x: -1, y: 0, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: -1, z: 0)), ]) .map { texture, perp, start, d1, d2 in ( texture, perp, stride(from: 0, to: 1, by: scanFrequency) .flatMap { d2Offset in stride(from: 0, to: 1, by: scanFrequency) .map { d1Offset in (start + d1 * d1Offset + d2 * d2Offset) * size } } ) } } } final class CubeGame: Game { -
dduan revised this gist
Nov 22, 2020 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -184,7 +184,7 @@ struct Cube { } // (texture for the surface, vector perpendicular to the surfaces, points on the surface) var surfaces: [(Character, Vec3, [Vec3])] { [(Character, Vec3, Vec3, Vec3, Vec3)]([ ("█", .init(x: 0, y: 0, z: -1), .init(x: -0.5, y: -0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 1, z: 0)), ("▓", .init(x: 0, y: 1, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 0, z: 1)), @@ -267,17 +267,17 @@ final class CubeGame: Game { override func render() { graphic.clear() for (texture, perp, points) in cube.surfaces { // if an vector perpendicular to a surface has negative dot product with one such vector that points towards the view point, it is perpendicular to a hidden surface. let rotatedPerp = rotate(vec: perp) if -rotatedPerp.z <= 0 { continue } for point in points { let rotated = rotate(vec: point) // coordinates need to be shifted to the middle of the view port graphic[Int(rotated.x.rounded(.down)) + graphic.width / 2, Int(rotated.y.rounded(.down)) + graphic.height / 2 + 1] = texture } } } -
dduan revised this gist
Nov 22, 2020 . 1 changed file with 10 additions and 6 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -183,6 +183,7 @@ struct Cube { sqrt(3) * size } // (texture for the surface, vector perpendicular to the surfaces, points on the surface) var points: [(Character, Vec3, [Vec3])] { [(Character, Vec3, Vec3, Vec3, Vec3)]([ ("█", .init(x: 0, y: 0, z: -1), .init(x: -0.5, y: -0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 1, z: 0)), @@ -192,10 +193,10 @@ struct Cube { ("▦", .init(x: 1, y: 0, z: 0), .init(x: 0.5, y: -0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: 1, z: 0)), ("▧", .init(x: -1, y: 0, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: -1, z: 0)), ]) .map { texture, perp, start, d1, d2 in ( texture, perp, stride(from: 0, to: 1, by: scanFrequency) .flatMap { d2Offset in stride(from: 0, to: 1, by: scanFrequency) @@ -246,6 +247,7 @@ final class CubeGame: Game { } } // Apply standard rotation matrix func rotate(vec: Vec3) -> Vec3 { let x = vec.x let y = vec.y @@ -265,14 +267,16 @@ final class CubeGame: Game { override func render() { graphic.clear() for (c, perp, surface) in cube.points { // if an vector perpendicular to a surface has negative dot product with one such vector that points towards the view point, it is perpendicular to a hidden surface. let rotatedPerp = rotate(vec: perp) if -rotatedPerp.z <= 0 { continue } for point in surface { let rotated = rotate(vec: point) // coordinates need to be shifted to the middle of the view port graphic[Int(rotated.x.rounded(.down)) + graphic.width / 2, Int(rotated.y.rounded(.down)) + graphic.height / 2 + 1] = c } } -
dduan created this gist
Nov 22, 2020 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,282 @@ import Darwin enum RawModeError: Error { case notATerminal case failedToGetTerminalSetting case failedToSetTerminalSetting } func runInRawMode(_ task: @escaping () throws -> Void) throws { var originalTermSetting = termios() guard isatty(STDIN_FILENO) != 0 else { throw RawModeError.notATerminal } guard tcgetattr(STDIN_FILENO, &originalTermSetting) >= 0 else { throw RawModeError.failedToGetTerminalSetting } var raw = originalTermSetting raw.c_iflag &= ~(UInt(BRKINT) | UInt(ICRNL) | UInt(INPCK) | UInt(ISTRIP) | UInt(IXON)) raw.c_oflag &= ~(UInt(OPOST)) raw.c_cflag |= UInt(CS8) raw.c_lflag &= ~(UInt(ECHO) | UInt(ICANON) | UInt(IEXTEN) | UInt(ISIG)) raw.c_cc.16 = 0 raw.c_cc.17 = 1 guard tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) >= 0 else { throw RawModeError.failedToSetTerminalSetting } defer { tcsetattr(STDIN_FILENO, TCSAFLUSH, &originalTermSetting) print("\u{1b}[?25h", terminator: "") print("\u{1b}[0;0H") } print("\u{1b}[2J") print("\u{1b}[?25l") try task() } enum Key { static let q = Character("q").asciiValue! // quit static let d = Character("d").asciiValue! // show debug info static let j = Character("j").asciiValue! // increase yaw static let k = Character("k").asciiValue! // decrease yaw static let x = Character("x").asciiValue! // increase pitch static let b = Character("b").asciiValue! // decrease pitch static let m = Character("m").asciiValue! // increase roll static let w = Character("w").asciiValue! // decrease roll static let r = Character("r").asciiValue! // reset } final class Graphic { var buffer: [Character] let width: Int let height: Int var clearedBuffer: [Character] init(width: Int, height: Int) { self.width = width self.height = height clearedBuffer = .init(repeating: " ", count: width * height) buffer = clearedBuffer } subscript(x: Int, y: Int) -> Character { get { let index = y * width + x assert(index < buffer.count) return buffer[index] } set { let index = y * width + x guard index >= 0 && index < buffer.count else { return } buffer[index] = newValue } } func clear() { buffer = clearedBuffer } } extension timeval { func microseconds(since other: timeval) -> Double { Double(self.tv_usec - other.tv_usec) / 1000 + Double(self.tv_sec - other.tv_sec) * 1000 } } class Game { let graphic: Graphic let fps: Double let msPerRender: Double var lastRender = timeval() var showDebugInfo = false var loopCount = 0 var exit = false var now: timeval { var result = timeval() gettimeofday(&result, nil) return result } init(graphicWidth: Int, graphicHeight: Int, fps: Double = 60) { self.graphic = .init(width: graphicWidth, height: graphicHeight) self.fps = fps self.msPerRender = 1 / fps } func positionAt(x: Int, y: Int) { print("\u{1b}[\(y);\(x)H", terminator: "") } func run() throws { try runInRawMode { [weak self] in guard let self = self else { return } while !self.exit { var input: UInt8 = 0 read(STDIN_FILENO, &input, 1) self.handle(input: input) self.update() let now = self.now let msSince = now.microseconds(since: self.lastRender) if msSince >= self.msPerRender { self.lastRender = now self.render() self.positionAt(x: 0, y: 0) for line in 0 ..< self.graphic.height { let start = line * self.graphic.width let end = start + self.graphic.width self.positionAt(x: 0, y: line) print(String(self.graphic.buffer[start ..< end])) } if self.showDebugInfo { self.positionAt(x: 1, y: 1) let lps = Int(Double(self.loopCount) / (1 / msSince)) print("w: \(self.graphic.width) h: \(self.graphic.height) lps: \(lps)") self.loopCount = 0 } } self.loopCount += 1 } } print("\u{1b}[2J") } open func handle(input: UInt8) {} open func update() {} open func render() {} } struct Vec3 { let x, y, z: Float static func + (lhs: Self, rhs: Self) -> Self { .init(x: lhs.x + rhs.x, y: lhs.y + rhs.y, z: lhs.z + rhs.z) } static func * (lhs: Self, rhs: Float) -> Self { .init(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs) } } extension Vec3: CustomStringConvertible { var description: String { "(\(x),\(y),\(z))" } } struct Cube { var size: Float = 1 var scanFrequency: Float = 0.1 var position: Vec3 = .init(x: 0, y: 0, z: 0) var maxZ: Float { sqrt(3) * size } var points: [(Character, Vec3, [Vec3])] { [(Character, Vec3, Vec3, Vec3, Vec3)]([ ("█", .init(x: 0, y: 0, z: -1), .init(x: -0.5, y: -0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 1, z: 0)), ("▓", .init(x: 0, y: 1, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 0, z: 1)), ("▒", .init(x: 0, y: 0, z: 1), .init(x: -0.5, y: 0.5, z: 0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: -1, z: 0)), ("░", .init(x: 0, y: -1, z: 0), .init(x: -0.5, y: -0.5, z: 0.5), .init(x: 1, y: 0, z: 0), .init(x: 0, y: 0, z: -1)), ("▦", .init(x: 1, y: 0, z: 0), .init(x: 0.5, y: -0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: 1, z: 0)), ("▧", .init(x: -1, y: 0, z: 0), .init(x: -0.5, y: 0.5, z: -0.5), .init(x: 0, y: 0, z: 1), .init(x: 0, y: -1, z: 0)), ]) .map { c, orth, start, d1, d2 in ( c, orth, stride(from: 0, to: 1, by: scanFrequency) .flatMap { d2Offset in stride(from: 0, to: 1, by: scanFrequency) .map { d1Offset in (start + d1 * d1Offset + d2 * d2Offset) * size } } ) } } } final class CubeGame: Game { var cube: Cube var yaw: Float = 0 var pitch: Float = 0 var roll: Float = 0 init() { cube = .init(size: 18, scanFrequency: 0.04) super.init(graphicWidth: 50, graphicHeight: 50) } override func handle(input: UInt8) { switch input { case Key.q: self.exit = true case Key.d: self.showDebugInfo.toggle() case Key.j: self.yaw += 0.1 case Key.k: self.yaw -= 0.1 case Key.x: self.roll += 0.1 case Key.b: self.roll -= 0.1 case Key.m: self.pitch += 0.1 case Key.w: self.pitch -= 0.1 case Key.r: self.pitch = 0 self.yaw = 0 self.roll = 0 default: break } } func rotate(vec: Vec3) -> Vec3 { let x = vec.x let y = vec.y let z = vec.z let cosYaw = cos(yaw) let sinYaw = sin(yaw) let cosPitch = cos(pitch) let sinPitch = sin(pitch) let cosRoll = cos(roll) let sinRoll = sin(roll) let rotatedX = cosYaw * cosPitch * x + (cosYaw * sinPitch * sinRoll - sinYaw * cosRoll) * y + (cosYaw * sinPitch * cosRoll + sinYaw * sinRoll) * z let rotatedY = sinYaw * cosPitch * x + (sinYaw * sinPitch * sinRoll + cosYaw * cosRoll) * y + (sinYaw * sinPitch * cosRoll - cosYaw * sinRoll) * z let rotatedZ = -sinPitch * x + cosPitch * sinRoll * y + cosPitch * cosRoll * z return .init(x: rotatedX, y: rotatedY, z: rotatedZ) } override func render() { graphic.clear() for (c, orth, surface) in cube.points { let rotatedOrth = rotate(vec: orth) if -rotatedOrth.z <= 0 { continue } for point in surface { let rotated = rotate(vec: point) graphic[Int(rotated.x.rounded(.down)) + graphic.width / 2, Int(rotated.y.rounded(.down)) + graphic.height / 2 + 1] = c } } } } try CubeGame().run()