diff --git a/Terrain2/Algorithms.swift b/Terrain2/Algorithms.swift index ee88760..9ad80ed 100644 --- a/Terrain2/Algorithms.swift +++ b/Terrain2/Algorithms.swift @@ -11,6 +11,7 @@ import Metal enum KernelError: Error { case badFunction + case badSize case textureCreationFailed } @@ -133,6 +134,10 @@ public class DiamondSquareAlgorithm: TerrainGenerator { public struct Size { let w: Int let h: Int + + var half: Size { + return Size(w: w / 2, h: h / 2) + } } public struct Box { @@ -206,6 +211,49 @@ public class DiamondSquareAlgorithm: TerrainGenerator { } } } + + struct Algorithm { + let grid: Box + private(set) var rng: RandomNumberGenerator + + init(grid: Box, rng: RandomNumberGenerator = SystemRandomNumberGenerator()) { + // TODO: Assert log2(w) and log2(h) are integral values. + self.grid = grid + self.rng = rng + } + + /// Run the algorithm and return the genreated height map. +// func render() -> [Float] { +// var heightMap = [Float](repeating: 0, count: grid.size.w * grid.size.h) +// return heightMap +// } + + /// Find our diamond's corners, wrapping around the grid if needed. + func diamondCorners(forPoint pt: Point, diamondSize: Size) -> [Point] { + let halfSize = diamondSize.half + let n = Point(x: pt.x, y: pt.y - halfSize.h) + let w = Point(x: pt.x - halfSize.w, y: pt.y) + let s = Point(x: pt.x, y: pt.y + halfSize.h) + let e = Point(x: pt.x + halfSize.w, y: pt.y) + return [n, w, s, e].map { (p: Point) -> Point in + if p.x < 0 { + return Point(x: p.x + grid.size.w - 1, y: p.y) + } else if p.x > grid.size.w { + return Point(x: p.x - grid.size.w + 1, y: p.y) + } else if p.y < 0 { + return Point(x: p.x, y: p.y + grid.size.h - 1) + } else if p.y > grid.size.h { + return Point(x: p.x, y: p.y - grid.size.h + 1) + } else { + return p + } + } + } + + func convert(pointToIndex pt: Point) -> Int { + return pt.y * grid.size.w + pt.x + } + } let name = "Diamond-Square" @@ -262,21 +310,8 @@ public class DiamondSquareAlgorithm: TerrainGenerator { // 2. Square. Find the midpoints of the sides of this box. These four points are the origins of the new subdivided boxes. for p in box.sideMidpoints { - // Find our diamond's corners, wrapping around the grid if needed. - let diamondCorners = [ - (x: p.x, y: p.y - halfSize.h), // North - (x: p.x - halfSize.w, y: p.y), // West - (x: p.x, y: (p.y + halfSize.h) % size.height), // South - (x: (p.x + halfSize.w) % size.width, y: p.y), // West - ].map { (p: Box.Point) -> Box.Point in - if p.x < 0 { - return (x: p.x + size.width, y: p.y) - } else if p.y < 0 { - return (x: p.x, y: p.y + size.height) - } else { - return p - } - } + // TODO. + let diamondCorners = [Point]() let idx = ptToIndex(p) let value = Float.random(in: 0...1) + 0.25 * diamondCorners.reduce(0) { (acc, pt) -> Float in @@ -311,12 +346,24 @@ extension DiamondSquareAlgorithm.Point: Equatable { } } +extension DiamondSquareAlgorithm.Point: CustomStringConvertible { + public var description: String { + return "(x: \(x), y: \(y))" + } +} + extension DiamondSquareAlgorithm.Size: Equatable { public static func == (lhs: DiamondSquareAlgorithm.Size, rhs: DiamondSquareAlgorithm.Size) -> Bool { return lhs.w == rhs.w && lhs.h == rhs.h } } +extension DiamondSquareAlgorithm.Size: CustomStringConvertible { + public var description: String { + return "(w: \(w), h: \(h))" + } +} + extension DiamondSquareAlgorithm.Box: Equatable { public static func == (lhs: DiamondSquareAlgorithm.Box, rhs: DiamondSquareAlgorithm.Box) -> Bool { return lhs.origin == rhs.origin && lhs.size == rhs.size diff --git a/Terrain2Tests/AlgorithmsTests.swift b/Terrain2Tests/AlgorithmsTests.swift index d8dad7c..f753181 100644 --- a/Terrain2Tests/AlgorithmsTests.swift +++ b/Terrain2Tests/AlgorithmsTests.swift @@ -9,9 +9,53 @@ import XCTest @testable import Terrain2 -public typealias Box = DiamondSquareAlgorithm.Box -public typealias Point = DiamondSquareAlgorithm.Box.Point -public typealias Size = DiamondSquareAlgorithm.Box.Size +fileprivate typealias Box = DiamondSquareAlgorithm.Box +fileprivate typealias Point = DiamondSquareAlgorithm.Point +fileprivate typealias Size = DiamondSquareAlgorithm.Size + +class DiamondSquareAlgorithmTests: XCTestCase { + fileprivate let grid = Box(origin: Point(x: 0, y: 0), size: Size(w: 5, h: 5)) + + lazy var alg = { + DiamondSquareAlgorithm.Algorithm(grid: grid) + }() + + func testDiamondCornersNorth() { + let corners = alg.diamondCorners(forPoint: Point(x: 2, y: 0), diamondSize: grid.size) + XCTAssertEqual(corners.count, 4) + XCTAssertEqual(corners[0], Point(x: 2, y: 2)) + XCTAssertEqual(corners[1], Point(x: 0, y: 0)) + XCTAssertEqual(corners[2], Point(x: 2, y: 2)) + XCTAssertEqual(corners[3], Point(x: 4, y: 0)) + } + + func testDiamondCornersWest() { + let corners = alg.diamondCorners(forPoint: Point(x: 0, y: 2), diamondSize: grid.size) + XCTAssertEqual(corners.count, 4) + XCTAssertEqual(corners[0], Point(x: 0, y: 0)) + XCTAssertEqual(corners[1], Point(x: 2, y: 2)) + XCTAssertEqual(corners[2], Point(x: 0, y: 4)) + XCTAssertEqual(corners[3], Point(x: 2, y: 2)) + } + + func testDiamondCornersSouth() { + let corners = alg.diamondCorners(forPoint: Point(x: 2, y: 4), diamondSize: grid.size) + XCTAssertEqual(corners.count, 4) + XCTAssertEqual(corners[0], Point(x: 2, y: 2)) + XCTAssertEqual(corners[1], Point(x: 0, y: 4)) + XCTAssertEqual(corners[2], Point(x: 2, y: 2)) + XCTAssertEqual(corners[3], Point(x: 4, y: 4)) + } + + func testDiamondCornersEast() { + let corners = alg.diamondCorners(forPoint: Point(x: 4, y: 2), diamondSize: grid.size) + XCTAssertEqual(corners.count, 4) + XCTAssertEqual(corners[0], Point(x: 4, y: 0)) + XCTAssertEqual(corners[1], Point(x: 2, y: 2)) + XCTAssertEqual(corners[2], Point(x: 4, y: 4)) + XCTAssertEqual(corners[3], Point(x: 2, y: 2)) + } +} class DiamondSquareBoxTests: XCTestCase { fileprivate let box = Box(origin: Point(x: 3, y: 4), size: Size(w: 5, h: 5))