diff --git a/Terrain2/Algorithms.swift b/Terrain2/Algorithms.swift index 342dd2c..410ef8e 100644 --- a/Terrain2/Algorithms.swift +++ b/Terrain2/Algorithms.swift @@ -17,10 +17,12 @@ enum KernelError: Error { protocol TerrainGenerator { var name: String { get } + var needsGPU: Bool { get } var outTexture: MTLTexture { get } func updateUniforms() func encode(in encoder: MTLComputeCommandEncoder) + func render() } class Kernel { @@ -79,6 +81,8 @@ class Kernel { class ZeroAlgorithm: Kernel, TerrainGenerator { let name = "Zero" + let needsGPU: Bool = true + init?(device: MTLDevice, library: MTLLibrary) { do { try super.init(device: device, library: library, functionName: "zeroKernel") @@ -91,12 +95,16 @@ class ZeroAlgorithm: Kernel, TerrainGenerator { // MARK: Algorithm func updateUniforms() { } + + func render() { } } /// Randomly generate heights that are independent of all others. class RandomAlgorithm: Kernel, TerrainGenerator { let name = "Random" + let needsGPU: Bool = true + private var uniforms: UnsafeMutablePointer init?(device: MTLDevice, library: MTLLibrary) { @@ -121,6 +129,8 @@ class RandomAlgorithm: Kernel, TerrainGenerator { func updateUniforms() { RandomAlgorithmUniforms_refreshRandoms(uniforms) } + + func render() { } } /// Implementation of the Diamond-Squares algorithm. @@ -162,19 +172,19 @@ public class DiamondSquareGenerator: TerrainGenerator { } var north: Point { - return Point(x: origin.x + (size.w / 2 + 1), y: origin.y) + return Point(x: origin.x + size.w / 2, y: origin.y) } var west: Point { - return Point(x: origin.x, y: origin.y + (size.h / 2 + 1)) + return Point(x: origin.x, y: origin.y + size.h / 2) } var south: Point { - return Point(x: origin.x + (size.w / 2 + 1), y: origin.y + size.h) + return Point(x: origin.x + size.w / 2, y: origin.y + size.h - 1) } var east: Point { - return Point(x: origin.x + size.w, y: origin.y + (size.h / 2 + 1)) + return Point(x: origin.x + size.w - 1, y: origin.y + size.h / 2) } var northwest: Point { @@ -182,32 +192,32 @@ public class DiamondSquareGenerator: TerrainGenerator { } var southwest: Point { - return Point(x: origin.x, y: origin.y + size.h) + return Point(x: origin.x, y: origin.y + size.h - 1) } var northeast: Point { - return Point(x: origin.x + size.w, y: origin.y) + return Point(x: origin.x + size.w - 1, y: origin.y) } var southeast: Point { - return Point(x: origin.x + size.w, y: origin.y + size.h) + return Point(x: origin.x + size.w - 1, y: origin.y + size.h - 1) } var midpoint: Point { - return Point(x: origin.x + (size.w / 2 + 1), y: origin.y + (size.h / 2 + 1)) + return Point(x: origin.x + (size.w / 2), y: origin.y + (size.h / 2)) } var subdivisions: [Box] { guard size.w > 2 && size.h > 2 else { return [] } - let midp = midpoint - let newSize = Size(w: midp.x - origin.x, h: midp.y - origin.y) + let halfSize = size.half + let newSize = Size(w: halfSize.w + 1, h: halfSize.h + 1) return [ Box(origin: origin, size: newSize), - Box(origin: Point(x: origin.x + newSize.w, y: origin.y), size: newSize), - Box(origin: Point(x: origin.x, y: origin.y + newSize.h), size: newSize), - Box(origin: Point(x: origin.x + newSize.w, y: origin.y + newSize.h), size: newSize) + Box(origin: Point(x: origin.x + halfSize.w, y: origin.y), size: newSize), + Box(origin: Point(x: origin.x, y: origin.y + halfSize.h), size: newSize), + Box(origin: Point(x: origin.x + halfSize.w, y: origin.y + halfSize.h), size: newSize) ] } @@ -293,6 +303,7 @@ public class DiamondSquareGenerator: TerrainGenerator { } let name = "Diamond-Square" + let needsGPU: Bool = false class var textureSize: MTLSize { // Needs to 2n + 1 on each side. @@ -307,7 +318,6 @@ public class DiamondSquareGenerator: TerrainGenerator { let size = DiamondSquareGenerator.textureSize let desc = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .r32Float, width: size.width, height: size.height, mipmapped: false) desc.usage = [.shaderRead, .shaderWrite] - desc.resourceOptions = .storageModeShared guard let tex = device.makeTexture(descriptor: desc) else { print("Couldn't create texture for Diamond-Squares algorithm.") return nil @@ -320,7 +330,7 @@ public class DiamondSquareGenerator: TerrainGenerator { func render() { let heightMap = algorithm.render() let region = MTLRegion(origin: MTLOrigin(), size: DiamondSquareGenerator.textureSize) - texture.replace(region: region, mipmapLevel: 0, withBytes: heightMap, bytesPerRow: MemoryLayout.stride * size.width) + texture.replace(region: region, mipmapLevel: 0, withBytes: heightMap, bytesPerRow: MemoryLayout.stride * DiamondSquareGenerator.textureSize.width) } // MARK: Algorithm diff --git a/Terrain2Tests/AlgorithmsTests.swift b/Terrain2Tests/AlgorithmsTests.swift index 008235e..85623cc 100644 --- a/Terrain2Tests/AlgorithmsTests.swift +++ b/Terrain2Tests/AlgorithmsTests.swift @@ -65,51 +65,52 @@ class DiamondSquareAlgorithmTests: XCTestCase { } class DiamondSquareBoxTests: XCTestCase { - fileprivate let box = Box(origin: Point(x: 3, y: 4), size: Size(w: 5, h: 5)) + fileprivate let box = Box(origin: Point(x: 0, y: 0), size: Size(w: 5, h: 5)) func testMidpoint() { let midpoint = box.midpoint - XCTAssertEqual(midpoint.x, 6) - XCTAssertEqual(midpoint.y, 7) + XCTAssertEqual(midpoint.x, 2) + XCTAssertEqual(midpoint.y, 2) } func testSubdivision() { let subdivs = box.subdivisions XCTAssertEqual(subdivs.count, 4) - XCTAssertEqual(subdivs[0], Box(origin: Point(x: 3, y: 4), size: Size(w: 3, h: 3))) - XCTAssertEqual(subdivs[1], Box(origin: Point(x: 6, y: 4), size: Size(w: 3, h: 3))) - XCTAssertEqual(subdivs[2], Box(origin: Point(x: 3, y: 7), size: Size(w: 3, h: 3))) - XCTAssertEqual(subdivs[3], Box(origin: Point(x: 6, y: 7), size: Size(w: 3, h: 3))) + XCTAssertEqual(subdivs[0], Box(origin: Point(x: 0, y: 0), size: Size(w: 3, h: 3))) + XCTAssertEqual(subdivs[1], Box(origin: Point(x: 2, y: 0), size: Size(w: 3, h: 3))) + XCTAssertEqual(subdivs[2], Box(origin: Point(x: 0, y: 2), size: Size(w: 3, h: 3))) + XCTAssertEqual(subdivs[3], Box(origin: Point(x: 2, y: 2), size: Size(w: 3, h: 3))) } func testBFS() { var expectedBoxes: [Box] = [ box, - Box(origin: Point(x: 3, y: 4), size: Size(w: 3, h: 3)), - Box(origin: Point(x: 6, y: 4), size: Size(w: 3, h: 3)), - Box(origin: Point(x: 3, y: 7), size: Size(w: 3, h: 3)), - Box(origin: Point(x: 6, y: 7), size: Size(w: 3, h: 3)), - Box(origin: Point(x: 3, y: 4), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 5, y: 4), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 3, y: 6), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 5, y: 6), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 0, y: 0), size: Size(w: 3, h: 3)), + Box(origin: Point(x: 2, y: 0), size: Size(w: 3, h: 3)), + Box(origin: Point(x: 0, y: 2), size: Size(w: 3, h: 3)), + Box(origin: Point(x: 2, y: 2), size: Size(w: 3, h: 3)), - Box(origin: Point(x: 6, y: 4), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 8, y: 4), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 6, y: 6), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 8, y: 6), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 0, y: 0), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 1, y: 0), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 0, y: 1), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 1, y: 1), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 3, y: 7), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 5, y: 7), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 3, y: 9), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 5, y: 9), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 2, y: 0), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 3, y: 0), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 2, y: 1), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 3, y: 1), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 6, y: 7), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 8, y: 7), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 6, y: 9), size: Size(w: 2, h: 2)), - Box(origin: Point(x: 8, y: 9), size: Size(w: 2, h: 2)), - ].reversed() + Box(origin: Point(x: 0, y: 2), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 1, y: 2), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 0, y: 3), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 1, y: 3), size: Size(w: 2, h: 2)), + + Box(origin: Point(x: 2, y: 2), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 3, y: 2), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 2, y: 3), size: Size(w: 2, h: 2)), + Box(origin: Point(x: 3, y: 3), size: Size(w: 2, h: 2)), + ].reversed() box.breadthFirstSearch { (box: Box) -> (Void) in let exBox = expectedBoxes.popLast() @@ -122,52 +123,40 @@ class DiamondSquareBoxTests: XCTestCase { // MARK: Sides func testNorth() { - let pt = box.north - XCTAssertEqual(pt.x, 6) - XCTAssertEqual(pt.y, 4) + XCTAssertEqual(box.north, Point(x: 2, y: 0)) } func testWest() { - let pt = box.west - XCTAssertEqual(pt.x, 3) - XCTAssertEqual(pt.y, 7) + XCTAssertEqual(box.west, Point(x: 0, y: 2)) } func testSouth() { - let pt = box.south - XCTAssertEqual(pt.x, 6) - XCTAssertEqual(pt.y, 9) + XCTAssertEqual(box.south, Point(x: 2, y: 4)) } func testEast() { - let pt = box.east - XCTAssertEqual(pt.x, 8) - XCTAssertEqual(pt.y, 7) + XCTAssertEqual(box.east, Point(x: 4, y: 2)) } // MARK: Corners func testNorthwest() { let pt = box.northwest - XCTAssertEqual(pt.x, 3) - XCTAssertEqual(pt.y, 4) + XCTAssertEqual(pt, Point()) } func testNortheast() { let pt = box.northeast - XCTAssertEqual(pt.x, 8) - XCTAssertEqual(pt.y, 4) + XCTAssertEqual(pt, Point(x: 4, y: 0)) } func testSouthwest() { let pt = box.southwest - XCTAssertEqual(pt.x, 3) - XCTAssertEqual(pt.y, 9) + XCTAssertEqual(pt, Point(x: 0, y: 4)) } func testSoutheast() { let pt = box.southeast - XCTAssertEqual(pt.x, 8) - XCTAssertEqual(pt.y, 9) + XCTAssertEqual(pt, Point(x: 4, y: 4)) } }