Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
80f8b21
Check config file version before attempting to load it (in future, mi…
stackotter Nov 18, 2023
dd06337
Don't overwrite invalid config until the user changes the fallback co…
stackotter Nov 18, 2023
b9dfce8
Set current Config version to 0 (will only change it to 1 once things…
stackotter Nov 18, 2023
0b9430a
Add button to reset key bindings to defaults
stackotter Nov 18, 2023
c693487
Enable order independent transparency by default
stackotter Nov 18, 2023
043b8d6
Render distance fog (still need to get it working correctly behind tr…
stackotter Nov 21, 2023
3ece840
Move fog color/distance calculation into World to make it more reusab…
stackotter Nov 21, 2023
70cc4e9
Update sky color depending on time of day, and fix light map generation
stackotter Nov 21, 2023
c27cea1
Render a sky plane when in the overworld and use exponential fog unde…
stackotter Nov 21, 2023
519a3cb
Render a void plane below the player when below the void plane visibi…
stackotter Nov 22, 2023
01965f3
Render sunsets and sunrises as an angled dish (copying vanilla behavi…
stackotter Nov 22, 2023
9867f37
Fix time of day calculations when doDaylightCycle is false (the serve…
stackotter Nov 22, 2023
34a090c
Change fog color to match the sunrise/sunset the more the player look…
stackotter Nov 23, 2023
0b05335
Fix parsing of fixed_time from dimension codec (accidentally attempte…
stackotter Nov 23, 2023
6308b20
Render sun and moon (as per vanilla)
stackotter Nov 23, 2023
582ed14
Fix sun texture rotation (forgot to fix the sun when I was fixing the…
stackotter Nov 23, 2023
50ff5ce
Add stars to the skybox (matches Vanilla's stars to the pixel)
stackotter Nov 23, 2023
31012e8
Fix star blending mode to match Vanilla (emulating shining through fr…
stackotter Nov 23, 2023
79ee9c4
Calculate fog using camera ray instead of player ray so that fog is c…
stackotter Nov 28, 2023
d84e221
Fix fog for translucent objects by moving calculation from screen eff…
stackotter Nov 28, 2023
78bcf57
Fix fog for translucent objects with OIT enabled (as best as possible)
stackotter Nov 28, 2023
0fa43c3
Render The End's textured sky box (a pixel-perfect match to vanilla :…
stackotter Dec 14, 2023
41e5990
Update Readme.md to list sky box as a completed feature (woohoo)
stackotter Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ Not every version will be perfectly supported but I will try and have the most p
- [x] Fluids (lava and water)
- [x] Chunk frustum culling
- [x] Biome blending (mostly)
- [x] [Sky box](https://github.com/stackotter/delta-client/pull/188)
- [ ] Entities
- [x] Basic entity rendering (just coloured cubes)
- [ ] Render entity models
Expand Down
17 changes: 15 additions & 2 deletions Sources/Client/Config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import OrderedCollections

/// The client's configuration. Usually stored in a JSON file.
struct Config: Codable, ClientConfiguration {
/// The current config version used by the client.
static let currentVersion: Float = 0

/// The config format version. Used to detect outdated config files (and maybe
/// in future to migrate them). Completely independent from the actual Delta
/// Client version.
var version: Float = 1
var version: Float = currentVersion

/// The random token used to identify ourselves to Mojang's API
var clientToken = UUID().uuidString
Expand All @@ -18,7 +21,7 @@ struct Config: Codable, ClientConfiguration {
/// The user's server list.
var servers: [ServerDescriptor] = []
/// Rendering related configuration.
var render = RenderConfiguration()
var render = RenderConfiguration(enableOrderIndependentTransparency: true)
/// Plugins that the user has explicitly unloaded.
var unloadedPlugins: [String] = []
/// The user's keymap.
Expand Down Expand Up @@ -50,6 +53,16 @@ struct Config: Codable, ClientConfiguration {
/// Loads a configuration from a JSON configuration file.
static func load(from file: URL) throws -> Config {
let data = try Data(contentsOf: file)
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

guard let version = json?["version"] as? Float else {
throw ConfigError.missingVersion
}

guard version == currentVersion else {
throw ConfigError.unsupportedVersion(version)
}

return try JSONDecoder().decode(Self.self, from: data)
}

Expand Down
9 changes: 9 additions & 0 deletions Sources/Client/Config/ConfigError.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import Foundation

public enum ConfigError: LocalizedError {
/// The config file is missing a version.
case missingVersion
/// The config file is using an unsupported version.
case unsupportedVersion(Float)
/// The account in question is of an invalid type.
case invalidAccountType
/// No account exists with the given id.
Expand All @@ -14,6 +18,11 @@ public enum ConfigError: LocalizedError {

public var errorDescription: String? {
switch self {
case .missingVersion:
return "Your config file is missing a 'version' field."
case let .unsupportedVersion(version):
return "Your config file version (\(version)) is unsupported. " +
"\(Config.currentVersion) is the current supported version."
case .invalidAccountType:
return "The account in question is of an invalid type."
case .invalidAccountId:
Expand Down
4 changes: 2 additions & 2 deletions Sources/Client/Views/Play/MetalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct MetalView {
mtkView.delegate = renderCoordinator
mtkView.preferredFramesPerSecond = 10000
mtkView.framebufferOnly = true
mtkView.clearColor = MTLClearColorMake(0, 0, 0, 1) // Clear with black colour
mtkView.clearColor = MTLClearColorMake(0, 0, 0, 1)
mtkView.drawableSize = mtkView.frame.size
mtkView.depthStencilPixelFormat = .depth32Float

Expand Down Expand Up @@ -60,7 +60,7 @@ final class MetalViewClass {
mtkView.delegate = renderCoordinator
mtkView.preferredFramesPerSecond = 10000
mtkView.framebufferOnly = true
mtkView.clearColor = MTLClearColorMake(0.65, 0.8, 1, 1) // Sky colour
mtkView.clearColor = MTLClearColorMake(0, 0, 0, 1)
mtkView.drawableSize = mtkView.frame.size
mtkView.depthStencilPixelFormat = .depth32Float
mtkView.clearDepth = 1.0
Expand Down
8 changes: 6 additions & 2 deletions Sources/Client/Views/Settings/ControlsSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ struct ControlsSettingsView: View {
}
.frame(width: 450)

KeymapEditorView()

HStack {
Text("Toggle sprint").frame(width: 150)
Spacer()
Expand Down Expand Up @@ -53,6 +51,12 @@ struct ControlsSettingsView: View {
Spacer()
}
.frame(width: 400)

Text("Bindings")
.font(.title)
.padding(.top, 16)

KeymapEditorView()
}
.onAppear {
sensitivity = managedConfig.mouseSensitivity
Expand Down
7 changes: 7 additions & 0 deletions Sources/Client/Views/Settings/KeymapEditorView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ struct KeymapEditorView: View {
.onKeyRelease { key in
inputCaptured = false
}

Button("Reset bindings to defaults") {
managedConfig.keymap.bindings = Keymap.default.bindings
}
.buttonStyle(SecondaryButtonStyle())
.frame(width: 250)
.padding(.top, 10)
}

static func labelColor(isUnique: Bool, isBound: Bool, isSelected: Bool) -> Color {
Expand Down
10 changes: 4 additions & 6 deletions Sources/Client/Views/Startup/LoadAndThen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,11 @@ struct LoadAndThen<Content: View>: View {
do {
config = try Config.load(from: storage.configFile)
} catch {
modal.error(RichError("Failed to load config; resetting to defaults").becauseOf(error))
modal.error(RichError("Failed to load config, using defaults").becauseOf(error))
config = Config()
do {
try config.save(to: storage.configFile)
} catch {
throw RichError("Failed to save new config").becauseOf(error)
}
// Don't save the new config (wait until the user makes changes). This means that
// the user can quit the client and attempt to fix their config without having to
// start their config all over again.
}

let managedConfig = ManagedConfig(config, backedBy: storage.configFile) { error in
Expand Down
84 changes: 53 additions & 31 deletions Sources/Core/Renderer/Camera/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import DeltaCore

/// Holds information about a camera to render from.
public struct Camera {
// MARK: Public properties

/// The vertical FOV in radians.
public private(set) var fovY: Float = 0.5 * .pi // 90deg
/// The near clipping plane.
Expand All @@ -19,12 +17,41 @@ public struct Camera {
/// The camera's position.
public private(set) var position: Vec3f = [0, 0, 0]

/// The camera's rotation around the x axis (pitch). -pi/2 radians is straight up, 0 is straight ahead, and pi/2 radians is straight up.
// TODO: Rename xRot and yRot to pitch and yaw when refactoring Camera
/// The camera's rotation around the x axis (pitch). -pi/2 radians is straight up,
/// 0 is straight ahead, and pi/2 radians is straight down.
public private(set) var xRot: Float = 0
/// The camera's rotation around the y axis measured counter-clockwise from the positive z axis when looking down from above (yaw).
/// The camera's rotation around the y axis measured counter-clockwise from the
/// positive z axis when looking down from above (yaw).
public private(set) var yRot: Float = 0

// MARK: Public computed properties
/// The ray defining the camera's position and the direction it faces.
public var ray: Ray {
Ray(from: position, pitch: xRot, yaw: yRot)
}

// TODO: Rename these matrices to translation, rotation, and projection. Could
// even replace translation and rotation with a single combined `framing`
// matrix if no code ever uses them separately.
/// A translation matrix from world-space to player-centered coordinates.
public var worldToPlayer: Mat4x4f {
MatrixUtil.translationMatrix(-position)
}

/// A rotation matrix from player-centered coordinates to camera-space.
public var playerToCamera: Mat4x4f {
MatrixUtil.rotationMatrix(y: -(Float.pi + yRot)) * MatrixUtil.rotationMatrix(x: -xRot)
}

/// The projection matrix from camera-space to clip-space.
public var cameraToClip: Mat4x4f {
MatrixUtil.projectionMatrix(
near: nearDistance,
far: farDistance,
aspect: aspect,
fieldOfViewY: fovY
)
}

/// The camera's position as an entity position.
public var entityPosition: EntityPosition {
Expand All @@ -38,41 +65,45 @@ public struct Camera {
return (unitVector * rotationMatrix).xyz
}

// MARK: Private properties

private var frustum: Frustum?

private var uniformsBuffers: [MTLBuffer] = []
private var uniformsIndex = 0
private var uniformsCount = 6

// MARK: Init

public init(_ device: MTLDevice) throws {
// TODO: Multiple-buffering should be implemented separately from the camera. I reckon the
// camera shouldn't have to know about Metal at all, or throw any errors.
for i in 0..<uniformsCount {
guard let buffer = device.makeBuffer(length: MemoryLayout<Uniforms>.stride, options: .storageModeShared) else {
guard let buffer = device.makeBuffer(
length: MemoryLayout<CameraUniforms>.stride,
options: .storageModeShared
) else {
throw RenderError.failedtoCreateWorldUniformBuffers
}
buffer.label = "dev.stackotter.Camera.uniforms-\(i)"
buffer.label = "Camera.uniforms-\(i)"
uniformsBuffers.append(buffer)
}
}

// MARK: Public methods

/// Update a buffer to contain the current world to clip uniforms.
public mutating func getUniformsBuffer() -> MTLBuffer {
let buffer = uniformsBuffers[uniformsIndex]
uniformsIndex = (uniformsIndex + 1) % uniformsCount
var uniforms = getUniforms()
buffer.contents().copyMemory(from: &uniforms, byteCount: MemoryLayout<Uniforms>.stride)
buffer.contents().copyMemory(
from: &uniforms,
byteCount: MemoryLayout<CameraUniforms>.stride
)
return buffer
}

/// Get the world to clip uniforms.
public mutating func getUniforms() -> Uniforms {
let transformation = getFrustum().worldToClip
return Uniforms(transformation: transformation)
public mutating func getUniforms() -> CameraUniforms {
return CameraUniforms(
framing: worldToPlayer * playerToCamera,
projection: cameraToClip
)
}

/// Sets this camera's vertical FOV. Horizontal FOV is calculated from vertical FOV and aspect ratio.
Expand Down Expand Up @@ -119,23 +150,14 @@ public struct Camera {
yRot = playerLook.yaw
}

/// Returns this camera's world space to clip space transformation matrix.
// TODO: Make this a computed property
/// Returns this camera's world-space to clip-space transformation matrix.
public func getWorldToClipMatrix() -> Mat4x4f {
var worldToCamera = MatrixUtil.translationMatrix(-position) // translation
worldToCamera *= MatrixUtil.rotationMatrix(y: -(Float.pi + yRot)) // y rotation
worldToCamera *= MatrixUtil.rotationMatrix(x: -xRot) // x rotation

// perspective projection
let cameraToClip = MatrixUtil.projectionMatrix(
near: nearDistance,
far: farDistance,
aspect: aspect,
fieldOfViewY: fovY
)

return worldToCamera * cameraToClip
return worldToPlayer * playerToCamera * cameraToClip
}

// TODO: This whole frustum caching thing seems weird. It can probably just be moved to the usage
// site. i.e. just call computeFrustum once to get the frustum.
/// Calculates the camera's frustum and saves it. Cached frustum can be fetched via ``getFrustum()``.
public mutating func cacheFrustum() {
self.frustum = calculateFrustum()
Expand Down
13 changes: 13 additions & 0 deletions Sources/Core/Renderer/CameraUniforms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import FirebladeMath

public struct CameraUniforms {
/// Translation and rotation (sets up the camera's framing).
public var framing: Mat4x4f
/// The projection converting camera-space coordinates to clip-space coordinates.
public var projection: Mat4x4f

public init(framing: Mat4x4f, projection: Mat4x4f) {
self.framing = framing
self.projection = projection
}
}
14 changes: 14 additions & 0 deletions Sources/Core/Renderer/CelestialBodyUniforms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import FirebladeMath

public struct CelestialBodyUniforms {
public var transformation: Mat4x4f
public var textureIndex: UInt16
public var uvPosition: Vec2f
public var uvSize: Vec2f
public var type: CelestialBodyType

public enum CelestialBodyType: UInt8 {
case sun = 0
case moon = 1
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import Foundation
import FirebladeMath

public struct Uniforms {
public struct ChunkUniforms {
/// The translation to convert chunk-space coordinates (with origin at the
/// lowest, Northmost, Eastmost block of the chunk) to world-space coordinates.
public var transformation: Mat4x4f

public init(transformation: Mat4x4f) {
Expand Down
6 changes: 6 additions & 0 deletions Sources/Core/Renderer/EndSkyUniforms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import FirebladeMath

struct EndSkyUniforms {
var transformation: Mat4x4f
var textureIndex: UInt16
}
6 changes: 6 additions & 0 deletions Sources/Core/Renderer/EndSkyVertex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import FirebladeMath

struct EndSkyVertex {
var position: Vec3f
var uv: Vec2f
}
10 changes: 5 additions & 5 deletions Sources/Core/Renderer/Entity/EntityRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public struct EntityRenderer: Renderer {
}

// Get all renderable entities
var entityUniforms: [Uniforms] = []
var entityUniforms: [EntityUniforms] = []
client.game.accessNexus { nexus in
// If the player is in first person view we don't render them
profiler.push(.getEntities)
Expand Down Expand Up @@ -124,7 +124,7 @@ public struct EntityRenderer: Renderer {

let scale: Mat4x4f = MatrixUtil.scalingMatrix(Vec3f(size))
let translation: Mat4x4f = MatrixUtil.translationMatrix(Vec3f(position))
let uniforms = Uniforms(transformation: scale * translation)
let uniforms = EntityUniforms(transformation: scale * translation)
entityUniforms.append(uniforms)
}
profiler.pop()
Expand All @@ -138,8 +138,8 @@ public struct EntityRenderer: Renderer {
// more than 64 entities too big. The maximum size limit is imposed so that the buffer isn't too
// much bigger than necessary. New buffers are always created with room for 32 more entities so
// that a new buffer isn't created each time an entity is added.
let minimumBufferSize = entityUniforms.count * MemoryLayout<Uniforms>.stride
let maximumBufferSize = minimumBufferSize + 64 * MemoryLayout<Uniforms>.stride
let minimumBufferSize = entityUniforms.count * MemoryLayout<EntityUniforms>.stride
let maximumBufferSize = minimumBufferSize + 64 * MemoryLayout<EntityUniforms>.stride
var instanceUniformsBuffer: MTLBuffer

profiler.push(.getBuffer)
Expand All @@ -150,7 +150,7 @@ public struct EntityRenderer: Renderer {
log.trace("Creating new instance uniforms buffer")
instanceUniformsBuffer = try MetalUtil.makeBuffer(
device,
length: minimumBufferSize + MemoryLayout<Uniforms>.stride * 32,
length: minimumBufferSize + MemoryLayout<EntityUniforms>.stride * 32,
options: .storageModeShared,
label: "entityInstanceUniforms"
)
Expand Down
11 changes: 11 additions & 0 deletions Sources/Core/Renderer/EntityUniforms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import FirebladeMath

public struct EntityUniforms {
/// The transformation for an instance of the generic entity hitbox. Scales and
/// translates the hitbox to the correct size and world-space position.
public var transformation: Mat4x4f

public init(transformation: Mat4x4f) {
self.transformation = transformation
}
}
10 changes: 10 additions & 0 deletions Sources/Core/Renderer/FogUniforms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import FirebladeMath

/// Uniforms used to render distance fog.
struct FogUniforms {
var fogColor: Vec3f
var fogStart: Float
var fogEnd: Float
var fogDensity: Float
var isLinear: Bool
}
Loading