Skip to content

Instantly share code, notes, and snippets.

@waterskier2007
Created January 14, 2021 04:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save waterskier2007/4584bdd4c0b2f3f90e765b9a50747e48 to your computer and use it in GitHub Desktop.
Save waterskier2007/4584bdd4c0b2f3f90e765b9a50747e48 to your computer and use it in GitHub Desktop.
A swift implementation of Penrose tiling
//MARK: - Playground code
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = PenroseView(frame: CGRect(x: 0, y: 0, width: 1000, height: 800))
view.backgroundColor = .white
self.view = view
}
}
class PenroseView: UIView {
let penrose: PenroseTiling
override init(frame: CGRect) {
self.penrose = PenroseTiling(width: frame.width, height: frame.height)
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
guard let graphics = UIGraphicsGetCurrentContext() else {
return
}
graphics.setStrokeColor(UIColor.darkGray.cgColor)
let dist: [[CGFloat]] = [
[PenroseTiling.goldenRatio, PenroseTiling.goldenRatio, PenroseTiling.goldenRatio],
[-1.0 * PenroseTiling.goldenRatio, -1, -1.0 * PenroseTiling.goldenRatio]
]
for tile in penrose.tiles {
var angle = tile.angle - PenroseTiling.theta
let path = CGMutablePath()
path.move(to: CGPoint(x: tile.x, y: tile.y))
let ord = tile.type.rawValue
for i in stride(from: 0, to: 3, by: 1) {
let x = tile.x + dist[ord][i] * tile.size * cos(angle)
let y = tile.y - dist[ord][i] * tile.size * sin(angle)
path.addLine(to: CGPoint(x: x, y: y))
angle += PenroseTiling.theta
}
path.closeSubpath()
graphics.addPath(path)
graphics.setFillColor(ord == 0 ? UIColor.orange.cgColor : UIColor.yellow.cgColor)
graphics.drawPath(using: .fillStroke)
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
//MARK: - Penrose code
import Foundation
import CoreGraphics
public struct PenroseTiling {
public static let goldenRatio: CGFloat = (1.0 + sqrt(5.0)) / 2.0
public static let theta: CGFloat = 36.degreesToRadians
public let tiles: [Tile]
public init(width: CGFloat, height: CGFloat) {
tiles = PenroseTiling.deflateTiles(tiles: PenroseTiling.setupPrototiles(width: width, height: height), generation: 5)
}
private static func setupPrototiles(width: CGFloat, height: CGFloat) -> [Tile] {
var proto = [Tile]()
for i in stride(from: CGFloat.pi / 2.0 + theta, to: CGFloat.pi * 3.0, by: 2.0 * theta) {
proto.append(Tile(t: .kite, x: width / 2.0, y: height / 2.0, a: i, s: width / 2.5))
}
return proto
}
private static func deflateTiles(tiles: [Tile], generation: Int) -> [Tile] {
guard generation > 0 else {
return tiles
}
var next = [Tile]()
for tile in tiles {
let x = tile.x
let y = tile.y
let a = tile.angle
let size = tile.size / goldenRatio
switch tile.type {
case .dart:
next.append(Tile(t: .kite, x: x, y: y, a: a + 5.0 * theta, s: size))
var sign: CGFloat = 1.0
for _ in stride(from: 0, to: 2, by: 1) {
let nx = x + cos(a - 4.0 * theta * sign) * goldenRatio * tile.size
let ny = y - sin(a - 4.0 * theta * sign) * goldenRatio * tile.size
next.append(Tile(t: .dart, x: nx, y: ny, a: a - 4 * theta * sign, s: size))
sign *= -1
}
case .kite:
var sign: CGFloat = 1.0
for _ in stride(from: 0, to: 2, by: 1) {
next.append(Tile(t: .dart, x: x, y: y, a: a - 4.0 * theta * sign, s: size))
let nx = x + cos(a - theta * sign) * goldenRatio * tile.size
let ny = y - sin(a - theta * sign) * goldenRatio * tile.size
next.append(Tile(t: .kite, x: nx, y: ny, a: a + 3 * theta * sign, s: size))
sign *= -1
}
}
}
//remove duplicates
let dedup = next.distinct()
return deflateTiles(tiles: dedup, generation: generation - 1)
}
}
public struct Tile: Equatable {
public enum TileType: Int {
case kite
case dart
}
public let x, y, angle, size: CGFloat
public let type: TileType
init(t: TileType, x: CGFloat, y: CGFloat, a: CGFloat, s: CGFloat) {
self.type = t
self.x = x
self.y = y
self.angle = a
self.size = s
}
public static func ==(lhs: Tile, rhs: Tile) -> Bool {
return lhs.type == rhs.type
&& lhs.x == rhs.x
&& lhs.y == rhs.y
&& lhs.angle == rhs.angle
}
}
extension BinaryInteger {
var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { self * .pi / 180 }
var radiansToDegrees: Self { self * 180 / .pi }
}
extension Array where Element: Equatable {
func distinct() -> Self {
var unique = [Element]()
for item in self
{
if !unique.contains(item)
{
unique.append(item)
}
}
return unique
}
}
@sabiland
Copy link

Cool comments @laclouis5. Waterskier, will you try to improve performance?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment