diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index ad668af..1489701 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable(Voxelotl MACOSX_BUNDLE Noise/CoherentNoise.swift Noise/PerlinNoiseGenerator.swift Noise/SimplexNoise.swift + Noise/LayeredNoise.swift # Resource classes Resource/NSImageLoader.swift @@ -51,11 +52,15 @@ add_executable(Voxelotl MACOSX_BUNDLE Input/GameController.swift Input/Mouse.swift + # Worldgens + Generator/WorldGenerator.swift + Generator/StandardWorldGenerator.swift + Generator/TerrorTowerGenerator.swift + # Game logic classes Chunk.swift ChunkGeneration.swift ChunkMeshGeneration.swift - WorldGenerator.swift CubeMeshBuilder.swift ChunkMeshBuilder.swift World.swift @@ -132,3 +137,4 @@ source_group("Source Files\\Noise" REGULAR_EXPRESSION "Noise/") source_group("Source Files\\Math" REGULAR_EXPRESSION "Math/") source_group("Source Files\\Input" REGULAR_EXPRESSION "Input/") source_group("Source Files\\Renderer" REGULAR_EXPRESSION "Renderer/") +source_group("Source Files\\Generator" REGULAR_EXPRESSION "Generator/") diff --git a/Sources/Voxelotl/WorldGenerator.swift b/Sources/Voxelotl/Generator/StandardWorldGenerator.swift similarity index 74% rename from Sources/Voxelotl/WorldGenerator.swift rename to Sources/Voxelotl/Generator/StandardWorldGenerator.swift index 312c8b9..ffcc03c 100644 --- a/Sources/Voxelotl/WorldGenerator.swift +++ b/Sources/Voxelotl/Generator/StandardWorldGenerator.swift @@ -1,8 +1,3 @@ -protocol WorldGenerator { - mutating func reset(seed: UInt64) - func makeChunk(id: SIMD3) -> Chunk -} - struct StandardWorldGenerator: WorldGenerator { var noise: ImprovedPerlin!, noise2: SimplexNoise! @@ -25,7 +20,7 @@ struct StandardWorldGenerator: WorldGenerator { chunk.fill(allBy: { position in let fpos = SIMD3(position) let threshold: Float = 0.6 - let value = fpos.y / Float(Chunk.size) + let value = fpos.y / 16.0 + self.noise.get(fpos * 0.05) * 1.1 + self.noise.get(fpos * 0.10) * 0.5 + self.noise.get(fpos * 0.30) * 0.23 @@ -41,11 +36,3 @@ struct StandardWorldGenerator: WorldGenerator { return chunk } } - -fileprivate extension RandomProvider where Output == UInt64, Self: RandomSeedable, SeedType == UInt64 { - static func createState(seed value: UInt64) -> (UInt64, UInt64) { - var hash = Self(seed: value) - let state = (hash.next(), hash.next()) - return state - } -} diff --git a/Sources/Voxelotl/Generator/TerrorTowerGenerator.swift b/Sources/Voxelotl/Generator/TerrorTowerGenerator.swift new file mode 100644 index 0000000..2d1fd16 --- /dev/null +++ b/Sources/Voxelotl/Generator/TerrorTowerGenerator.swift @@ -0,0 +1,33 @@ +import simd + +struct TerrorTowerGenerator: WorldGenerator { + var noise1: LayeredNoise>! + var noise2: LayeredNoise>! + + public mutating func reset(seed: UInt64) { + var random = Xoroshiro128PlusPlus(state: SplitMix64.createState(seed: seed)) + self.noise1 = LayeredNoise(random: &random, octaves: 4, frequency: 0.05, amplitude: 1.1) + self.noise2 = LayeredNoise(random: &random, octaves: 3, frequency: 0.1, amplitude: 0.5) + } + + public func makeChunk(id chunkID: SIMD3) -> Chunk { + let chunkOrigin = chunkID &<< Chunk.shift + var chunk = Chunk(position: chunkOrigin) + chunk.fill(allBy: { position in + let fpos = SIMD3(position) + let threshold: Float = 0.6 + let gradient = simd_length(fpos.xz) / 14.0 + let value = self.noise1.get(fpos) - 0.25 + return if gradient + value < threshold { + .solid(.init( + hue: ((fpos.x * 0.5 + fpos.y) / 30.0) * 360.0, + saturation: 0.2 + noise2.get(fpos) * 0.2, + value: 0.75 + noise2.get(SIMD4(fpos, 1) * 0.25) + ).linear) + } else { + .air + } + }) + return chunk + } +} diff --git a/Sources/Voxelotl/Generator/WorldGenerator.swift b/Sources/Voxelotl/Generator/WorldGenerator.swift new file mode 100644 index 0000000..69c754d --- /dev/null +++ b/Sources/Voxelotl/Generator/WorldGenerator.swift @@ -0,0 +1,12 @@ +protocol WorldGenerator { + mutating func reset(seed: UInt64) + func makeChunk(id: SIMD3) -> Chunk +} + +internal extension RandomProvider where Output == UInt64, Self: RandomSeedable, SeedType == UInt64 { + static func createState(seed value: UInt64) -> (UInt64, UInt64) { + var hash = Self(seed: value) + let state = (hash.next(), hash.next()) + return state + } +} diff --git a/Sources/Voxelotl/Noise/CoherentNoise.swift b/Sources/Voxelotl/Noise/CoherentNoise.swift index f85c95d..7121e03 100644 --- a/Sources/Voxelotl/Noise/CoherentNoise.swift +++ b/Sources/Voxelotl/Noise/CoherentNoise.swift @@ -1,17 +1,21 @@ -public protocol CoherentNoise2D { +public protocol CoherentNoise { associatedtype Scalar: FloatingPoint & SIMDScalar + init() +} + +public protocol CoherentNoiseRandomInit: CoherentNoise { + init(random: inout Random) +} + +public protocol CoherentNoise2D: CoherentNoise { func get(_ point: SIMD2) -> Scalar } -public protocol CoherentNoise3D { - associatedtype Scalar: FloatingPoint & SIMDScalar - +public protocol CoherentNoise3D: CoherentNoise { func get(_ point: SIMD3) -> Scalar } -public protocol CoherentNoise4D { - associatedtype Scalar: FloatingPoint & SIMDScalar - +public protocol CoherentNoise4D: CoherentNoise { func get(_ point: SIMD4) -> Scalar } diff --git a/Sources/Voxelotl/Noise/LayeredNoise.swift b/Sources/Voxelotl/Noise/LayeredNoise.swift new file mode 100644 index 0000000..47f56e0 --- /dev/null +++ b/Sources/Voxelotl/Noise/LayeredNoise.swift @@ -0,0 +1,53 @@ + +public struct LayeredNoise { + public typealias Scalar = Generator.Scalar + + public let octaves: Int + public let frequency: Scalar + public let amplitude: Scalar + + private let _generators: [Generator] + + init(octaves: Int, frequency: Scalar, amplitude: Scalar) { + self.octaves = octaves + self.frequency = frequency + self.amplitude = amplitude + self._generators = Array(repeating: .init(), count: octaves) + } +} + +public extension LayeredNoise where Generator: CoherentNoiseRandomInit { + init(random: inout Random, octaves: Int, frequency: Scalar, amplitude: Scalar) { + self.octaves = octaves + self.frequency = frequency + self.amplitude = amplitude + self._generators = Array(repeating: Generator(random: &random), count: octaves) + } +} + +public extension LayeredNoise where Generator: CoherentNoise2D { + func get(_ point: SIMD2) -> Scalar { + zip(self._generators, 0..) -> Scalar { + zip(self._generators, 0..) -> Scalar { + zip(self._generators, 0..: CoherentNoise2D, CoherentNoise3D { +public struct ImprovedPerlin: CoherentNoise2D, CoherentNoise3D, CoherentNoiseRandomInit { private let p: [Int16] + public init() { + self.init(permutation: defaultPermutation) + } + public init(permutation: [Int16]) { assert(permutation.count == 0x100) self.p = permutation } - public init(random: inout any RandomProvider) { + public init(random: inout Random) { self.p = (0..<0x100).map { Int16($0) }.shuffled(using: &random) } - public func get(_ point: SIMD2) -> T { + public func get(_ point: SIMD2) -> Scalar { // Find unit square let idx = SIMD2(Int(floor(point.x)), Int(floor(point.y))) & 0xFF // Find relative point in square @@ -37,7 +41,7 @@ public struct ImprovedPerlin: CoherentNoise grad(perm(bb), inner - .init(repeating: 1)))) } - public func get(_ point: SIMD3) -> T { + public func get(_ point: SIMD3) -> Scalar { // Find unit cube containg point let idx = SIMD3(Int(floor(point.x)), Int(floor(point.y)), Int(floor(point.z))) & 0xFF // Find relative point in cube @@ -74,9 +78,9 @@ public struct ImprovedPerlin: CoherentNoise @inline(__always) fileprivate func perm(_ x: Int) -> Int { Int(self.p[x & 0xFF]) } - @inline(__always) fileprivate func grad(_ hash: Int, _ point: SIMD2) -> T { grad(hash, SIMD3(point, 0)) } + @inline(__always) fileprivate func grad(_ hash: Int, _ point: SIMD2) -> Scalar { grad(hash, SIMD3(point, 0)) } - fileprivate func grad(_ hash: Int, _ point: SIMD3) -> T { + fileprivate func grad(_ hash: Int, _ point: SIMD3) -> Scalar { // Convert low 4 bits of hash code into 12 gradient directions let low4 = hash & 0xF var u = low4 < 8 ? point.x : point.y @@ -86,3 +90,19 @@ public struct ImprovedPerlin: CoherentNoise return u + v } } + +internal let defaultPermutation: [Int16] = [ + 151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142, + 8, 99,37,240,21,10,23,190, 6,148,247,120,234,75, 0,26,197,62,94,252,219,203, + 117,35, 11,32,57,177,33, 88,237,149,56,87,174,20,125,136,171,168, 68,175,74, + 165,71,134,139,48, 27,166,77,146,158,231,83,111,229,122, 60,211,133,230,220, + 105,92,41,55,46,245,40,244,102,143,54, 65,25,63,161, 1,216,80,73,209,76,132, + 187,208, 89, 18,169,200,196,135,130,116,188,159, 86,164,100,109,198,173,186, + 3,64,52,217,226,250,124,123, 5,202, 38,147,118,126,255,82,85,212,207,206,59, + 227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152, 2,44,154,163, 70, + 221,153,101,155,167, 43,172,9,129,22,39,253, 19, 98,108,110, 79,113,224,232, + 178,185, 112,104,218,246,97,228,251, 34,242,193,238,210,144, 12,191,179,162, + 241, 81,51,145,235,249,14,239,107,49,192,214, 31,181,199,106,157,184,84,204, + 176,115,121,50,45,127, 4,150,254,138,236,205,93,222,114,67,29,24,72,243,141, + 128,195,78,66,215,61,156,180 +] diff --git a/Sources/Voxelotl/Noise/SimplexNoise.swift b/Sources/Voxelotl/Noise/SimplexNoise.swift index d7665cf..111642d 100644 --- a/Sources/Voxelotl/Noise/SimplexNoise.swift +++ b/Sources/Voxelotl/Noise/SimplexNoise.swift @@ -1,14 +1,14 @@ import Foundation -public struct SimplexNoise: CoherentNoise2D, CoherentNoise3D, CoherentNoise4D { +public struct SimplexNoise: CoherentNoise2D, CoherentNoise3D, CoherentNoise4D, CoherentNoiseRandomInit { private let p: [Int16], pMod12: [Int16] - private let grad3: [SIMD3] = [ + private let grad3: [SIMD3] = [ .init(1, 1, 0), .init(-1, 1, 0), .init(1, -1, 0), .init(-1, -1, 0), .init(1, 0, 1), .init(-1, 0, 1), .init(1, 0, -1), .init(-1, 0, -1), .init(0, 1, 1), .init( 0, -1, 1), .init(0, 1, -1), .init( 0, -1, -1) ] - private let grad4: [SIMD4] = [ + private let grad4: [SIMD4] = [ .init( 0, 1, 1, 1), .init( 0, 1, 1, -1), .init( 0, 1, -1, 1), .init( 0, 1, -1, -1), .init( 0, -1, 1, 1), .init( 0, -1, 1, -1), .init( 0, -1, -1, 1), .init( 0, -1, -1, -1), .init( 1, 0, 1, 1), .init( 1, 0, 1, -1), .init( 1, 0, -1, 1), .init( 1, 0, -1, -1), @@ -19,21 +19,25 @@ public struct SimplexNoise: CoherentNoise2D .init(-1, 1, 1, 0), .init(-1, 1, -1, 0), .init(-1, -1, 1, 0), .init(-1, -1, -1, 0) ] + public init() { + self.init(permutation: defaultPermutation) + } + public init(permutation: [Int16]) { assert(permutation.count == 0x100) self.p = permutation self.pMod12 = self.p.map { $0 % 12 } } - public init(random: inout any RandomProvider) { + public init(random: inout Random) { self.p = (0..<0x100).map { Int16($0) }.shuffled(using: &random) self.pMod12 = self.p.map { $0 % 12 } } - public func get(_ point: SIMD2) -> T { + public func get(_ point: SIMD2) -> Scalar { // Skew space into rhobuses to find which simplex cell we're in - let f2 = 0.5 * (T(3).squareRoot() - 1) - let g2 = (3 - T(3).squareRoot()) / 6 + let f2 = 0.5 * (Scalar(3).squareRoot() - 1) + let g2 = (3 - Scalar(3).squareRoot()) / 6 let skewFactor = point.sum() * f2 let cellID = SIMD2(floor(point.x + skewFactor), floor(point.y + skewFactor)) let cellOrigin = cellID - (cellID.sum() * g2) @@ -42,7 +46,7 @@ public struct SimplexNoise: CoherentNoise2D // For the 2d case, the simplex shape is an equilateral triangle // Determine which side of the rhombus on to find the simplex let cornerOfs1: SIMD2 = corner0.x > corner0.y ? .init(1, 0) : .init(0, 1) - let corner1 = corner0 - SIMD2(cornerOfs1) + g2 + let corner1 = corner0 - SIMD2(cornerOfs1) + g2 let corner2 = corner0 - 1 + 2 * g2 // Compute the hashed gradient indices of the three simplex corners @@ -52,7 +56,7 @@ public struct SimplexNoise: CoherentNoise2D let gradIndex2 = permMod12(cellHash.x + 1 + perm(cellHash.y + 1)) // Calculate the contribution from the three corners - @inline(__always) func cornerContribution(_ corner: SIMD2, _ gradID: Int) -> T { + @inline(__always) func cornerContribution(_ corner: SIMD2, _ gradID: Int) -> Scalar { var t = 0.5 - corner.x * corner.x - corner.y * corner.y if t < 0 { return 0 @@ -68,9 +72,9 @@ public struct SimplexNoise: CoherentNoise2D return 70 * (noise0 + noise1 + noise2) } - public func get(_ point: SIMD3) -> T { + public func get(_ point: SIMD3) -> Scalar { // Skew space into rhombohedrons to find which simplex cell we're in - let g3 = 1 / T(6), f3 = 1 / T(3) + let g3 = 1 / Scalar(6), f3 = 1 / Scalar(3) let skewFactor = point.sum() * f3 let cellID = SIMD3(floor(point.x + skewFactor), floor(point.y + skewFactor), floor(point.z + skewFactor)) let cellOrigin = cellID - (cellID.sum() * g3) @@ -95,8 +99,8 @@ public struct SimplexNoise: CoherentNoise2D (.init(0, 1, 0), .init(1, 1, 0)) // Y X Z } } - let corner1 = corner0 - SIMD3(corner1ID) + g3 - let corner2 = corner0 - SIMD3(corner2ID) + 2 * g3 + let corner1 = corner0 - SIMD3(corner1ID) + g3 + let corner2 = corner0 - SIMD3(corner2ID) + 2 * g3 let corner3 = corner0 - 1 + 3 * g3 // Compute the hashed gradient indices of the four simplex corners @@ -119,7 +123,7 @@ public struct SimplexNoise: CoherentNoise2D cellHash.z + 1))) // Calculate the contribution from the four corners - @inline(__always) func cornerContribution(_ corner: SIMD3, _ gradID: Int) -> T { + @inline(__always) func cornerContribution(_ corner: SIMD3, _ gradID: Int) -> Scalar { var t = 0.6 - corner.x * corner.x - corner.y * corner.y - corner.z * corner.z if t < 0 { return 0 @@ -136,8 +140,8 @@ public struct SimplexNoise: CoherentNoise2D return 32 * (noise0 + noise1 + noise2 + noise3) } - public func get(_ point: SIMD4) -> T { - let g4 = (5 - T(5).squareRoot()) / 20, f4 = (T(5).squareRoot() - 1) / 4 + public func get(_ point: SIMD4) -> Scalar { + let g4 = (5 - Scalar(5).squareRoot()) / 20, f4 = (Scalar(5).squareRoot() - 1) / 4 let skewFactor = point.sum() * f4 let cellID = SIMD4(floor(point.x + skewFactor), floor(point.y + skewFactor), floor(point.z + skewFactor), floor(point.w + skewFactor)) let cellOrigin = cellID - (cellID.sum() * g4) @@ -157,9 +161,9 @@ public struct SimplexNoise: CoherentNoise2D let cornerOfs1 = SIMD4.zero.replacing(with: .one, where: rank .>= 3) let cornerOfs2 = SIMD4.zero.replacing(with: .one, where: rank .>= 2) let cornerOfs3 = SIMD4.zero.replacing(with: .one, where: rank .>= 1) - let corner1 = corner0 - SIMD4(cornerOfs1) + g4 - let corner2 = corner0 - SIMD4(cornerOfs2) + 2 * g4 - let corner3 = corner0 - SIMD4(cornerOfs3) + 3 * g4 + let corner1 = corner0 - SIMD4(cornerOfs1) + g4 + let corner2 = corner0 - SIMD4(cornerOfs2) + 2 * g4 + let corner3 = corner0 - SIMD4(cornerOfs3) + 3 * g4 let corner4 = corner0 - 1 + 4 * g4 // Compute the hashed gradient indices of the five simplex corners @@ -191,7 +195,7 @@ public struct SimplexNoise: CoherentNoise2D cellHash.w + 1))))) & 0x1F // Calculate the contribution from the five corners - @inline(__always) func cornerContribution(_ corner: SIMD4, _ gradID: Int) -> T { + @inline(__always) func cornerContribution(_ corner: SIMD4, _ gradID: Int) -> Scalar { var t = corner.indices.reduce(0.6) { accum, i in accum - corner[i] * corner[i] } if t < 0 { return 0 diff --git a/Sources/Voxelotl/World.swift b/Sources/Voxelotl/World.swift index c82b61b..cb79224 100644 --- a/Sources/Voxelotl/World.swift +++ b/Sources/Voxelotl/World.swift @@ -15,7 +15,7 @@ public class World { public init() { self._chunks = [:] self._chunkDamage = [] - self._generator = StandardWorldGenerator() + self._generator = TerrorTowerGenerator() self._chunkGeneration = ChunkGeneration(queue: .global(qos: .userInitiated)) self._chunkGeneration.world = self }