Skip to content

Instantly share code, notes, and snippets.

@aabewhite
Created September 22, 2018 16:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aabewhite/9839999483c5fe079351cde6b1fef824 to your computer and use it in GitHub Desktop.
Save aabewhite/9839999483c5fe079351cde6b1fef824 to your computer and use it in GitHub Desktop.
Sequence that generates points on a plane expanding from the center in a spiral pattern.
//
// SpiralPointSequence.swift
// ARPaint
//
// Created by Abe White on 9/20/18.
// Copyright © 2018 Hedonic Software. All rights reserved.
//
import Foundation
/**
Generates points in a plane expanding from the center in a spiral pattern.
*/
public struct SpiralPointSequence: Sequence, IteratorProtocol {
private let _width: Int
private let _height: Int
private let _directionSteps: [(x: Int, y: Int)]
private var _nextPoint: (x: Int, y: Int)?
private var _xRange = (min: 0, max: 0)
private var _yRange = (min: 0, max: 0)
private var _direction = 0
public init(width: Int, height: Int, step: Int = 1) {
_width = width
_height = height
_directionSteps = [(x: 0, y: step), (x: step, y: 0), (x: 0, y: -step), (x: -step, y: 0)] // down, right, up, left
if (width == 0 || height == 0) {
_nextPoint = nil
} else {
_nextPoint = (width / 2, height / 2)
_xRange = (min: _nextPoint!.x, max: _nextPoint!.x)
_yRange = (min: _nextPoint!.y, max: _nextPoint!.y)
}
}
public mutating func next() -> (x: Int, y: Int)? {
defer { _updateNextPoint() }
return _nextPoint
}
private mutating func _updateNextPoint() {
guard var exploreFromPoint = _nextPoint else {
return
}
for _ in 0..<_directionSteps.count {
let directionStep = _directionSteps[_direction]
let nextX = Swift.max(0, Swift.min(_width - 1, exploreFromPoint.x + directionStep.x))
let nextY = Swift.max(0, Swift.min(_height - 1, exploreFromPoint.y + directionStep.y))
let nextPoint = (nextX, nextY)
if nextPoint == exploreFromPoint {
// We've reached an edge we've explored before. Change
// direction and move to the previous max/min in the new
// direction and try to continue from there
_changeDirection()
exploreFromPoint = _maximumPointInDirection(from: exploreFromPoint)
} else {
if _updateRanges(for: nextPoint) {
// When we reach a new max/min in any direction, change
// direction for the next step
_changeDirection()
}
_nextPoint = nextPoint
return
}
}
_nextPoint = nil
}
private func _maximumPointInDirection(from point: (x: Int, y: Int)) -> (x: Int, y: Int) {
let directionStep = _directionSteps[_direction]
if directionStep.x > 0 {
return (_xRange.max, point.y)
} else if directionStep.x < 0 {
return (_xRange.min, point.y)
} else if directionStep.y > 0 {
return (point.x, _yRange.max)
} else if directionStep.y < 0 {
return (point.x, _yRange.min)
}
return point
}
private mutating func _changeDirection() {
_direction += 1
if _direction >= _directionSteps.count {
_direction = 0
}
}
private mutating func _updateRanges(for nextPoint: (x: Int, y: Int)) -> Bool {
if nextPoint.x > _xRange.max {
_xRange.max = nextPoint.x
return true
} else if nextPoint.x < _xRange.min {
_xRange.min = nextPoint.x
return true
} else if nextPoint.y > _yRange.max {
_yRange.max = nextPoint.y
return true
} else if nextPoint.y < _yRange.min {
_yRange.min = nextPoint.y
return true
} else {
return false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment