From e9a6fc5b59a264be78de856d50212b7a7843c881 Mon Sep 17 00:00:00 2001 From: Hiroaki Yamane Date: Tue, 28 Oct 2025 16:48:32 +0000 Subject: [PATCH 1/3] Added lens shift functionality to PerspectiveCamera --- Sources/Satin/Cameras/PerspectiveCamera.swift | 59 ++++++++++++++++++- Sources/SatinCore/Transforms.mm | 20 +++++++ Sources/SatinCore/include/Transforms.h | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Sources/Satin/Cameras/PerspectiveCamera.swift b/Sources/Satin/Cameras/PerspectiveCamera.swift index c3d31185..37a356a9 100644 --- a/Sources/Satin/Cameras/PerspectiveCamera.swift +++ b/Sources/Satin/Cameras/PerspectiveCamera.swift @@ -26,6 +26,24 @@ open class PerspectiveCamera: Camera { } } + @Published public var lensShiftX: Float = 0.0 { + didSet { + updateProjectionMatrix = true + } + } + + @Published public var lensShiftY: Float = 0.0 { + didSet { + updateProjectionMatrix = true + } + } + + @Published public var enableFrustumShift: Bool = false { + didSet { + updateProjectionMatrix = true + } + } + override public var scale: simd_float3 { didSet { updateViewMatrix = true @@ -50,7 +68,11 @@ open class PerspectiveCamera: Camera { override public var projectionMatrix: matrix_float4x4 { get { if updateProjectionMatrix { - _projectionMatrix = perspectiveMatrixf(fov, aspect, near, far) + if enableFrustumShift { + _projectionMatrix = frustumShiftProjectionMatrix() + } else { + _projectionMatrix = perspectiveMatrixf(fov, aspect, near, far) + } updateProjectionMatrix = false } return _projectionMatrix @@ -67,6 +89,11 @@ open class PerspectiveCamera: Camera { let sw = col3.z far = sw / sz near = sw / (1.0 + sz) + + if enableFrustumShift { + lensShiftX = col2.x + lensShiftY = col2.y + } } } @@ -103,6 +130,9 @@ open class PerspectiveCamera: Camera { let values = try decoder.container(keyedBy: CodingKeys.self) fov = try values.decode(Float.self, forKey: .fov) aspect = try values.decode(Float.self, forKey: .aspect) + lensShiftX = try values.decodeIfPresent(Float.self, forKey: .lensShiftX) ?? 0.0 + lensShiftY = try values.decodeIfPresent(Float.self, forKey: .lensShiftY) ?? 0.0 + enableFrustumShift = try values.decodeIfPresent(Bool.self, forKey: .enableFrustumShift) ?? false } override open func encode(to encoder: Encoder) throws { @@ -110,11 +140,17 @@ open class PerspectiveCamera: Camera { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(fov, forKey: .fov) try container.encode(aspect, forKey: .aspect) + try container.encode(lensShiftX, forKey: .lensShiftX) + try container.encode(lensShiftY, forKey: .lensShiftY) + try container.encode(enableFrustumShift, forKey: .enableFrustumShift) } private enum CodingKeys: String, CodingKey { case fov case aspect + case lensShiftX + case lensShiftY + case enableFrustumShift } override public func setFrom(object: Object, world: Bool = false) { @@ -122,6 +158,9 @@ open class PerspectiveCamera: Camera { if let camera = object as? PerspectiveCamera { fov = camera.fov aspect = camera.aspect + lensShiftX = camera.lensShiftX + lensShiftY = camera.lensShiftY + enableFrustumShift = camera.enableFrustumShift } } @@ -142,4 +181,22 @@ open class PerspectiveCamera: Camera { _projectionMatrix.columns.2.z = sz _projectionMatrix.columns.3.z = sw } + + // MARK: - Frustum Shift + + private func frustumShiftProjectionMatrix() -> matrix_float4x4 { + return frustumShiftPerspectiveMatrixf(fov, aspect, near, far, lensShiftX, lensShiftY) + } + + public func setLensShift(_ x: Float, _ y: Float) { + lensShiftX = x + lensShiftY = y + enableFrustumShift = x != 0.0 || y != 0.0 + } + + public func resetLensShift() { + lensShiftX = 0.0 + lensShiftY = 0.0 + enableFrustumShift = false + } } diff --git a/Sources/SatinCore/Transforms.mm b/Sources/SatinCore/Transforms.mm index d74febeb..af88dd97 100644 --- a/Sources/SatinCore/Transforms.mm +++ b/Sources/SatinCore/Transforms.mm @@ -104,6 +104,26 @@ simd_float4x4 perspectiveMatrixf(float fov, float aspect, float near, float far) return simd_matrix(col0, col1, col2, col3); } +// Frustum shift perspective projection matrix +simd_float4x4 frustumShiftPerspectiveMatrixf(float fov, float aspect, float near, float far, float lensShiftX, float lensShiftY) { + + const float halfFovRad = degToRad(0.5 * fov); + const float top = near * tanf(halfFovRad); + const float bottom = -top; + const float right = top * aspect; + const float left = -right; + + const float horizontalShift = lensShiftX * (right - left); + const float verticalShift = lensShiftY * (top - bottom); + + const float shiftedLeft = left + horizontalShift; + const float shiftedRight = right + horizontalShift; + const float shiftedBottom = bottom + verticalShift; + const float shiftedTop = top + verticalShift; + + return frustrumMatrixf(shiftedLeft, shiftedRight, shiftedBottom, shiftedTop, near, far); +} + simd_float4x4 lookAtMatrix3f(simd_float3 eye, simd_float3 at, simd_float3 up) { simd_float4x4 result = matrix_identity_float4x4; diff --git a/Sources/SatinCore/include/Transforms.h b/Sources/SatinCore/include/Transforms.h index 46f867eb..8116293d 100644 --- a/Sources/SatinCore/include/Transforms.h +++ b/Sources/SatinCore/include/Transforms.h @@ -25,6 +25,7 @@ frustrumMatrixf(float left, float right, float bottom, float top, float near, fl simd_float4x4 orthographicMatrixf(float left, float right, float bottom, float top, float near, float far); simd_float4x4 perspectiveMatrixf(float fov, float aspect, float near, float far); +simd_float4x4 frustumShiftPerspectiveMatrixf(float fov, float aspect, float near, float far, float lensShiftX, float lensShiftY); simd_float4x4 lookAtMatrix3f(simd_float3 eye, simd_float3 at, simd_float3 up); From e1bcb71120e89fc8f30c78c845c123a19c9a475d Mon Sep 17 00:00:00 2001 From: Hiroaki Yamane Date: Tue, 28 Oct 2025 16:53:04 +0000 Subject: [PATCH 2/3] Remove frustumShiftProjectionMatrix --- Sources/Satin/Cameras/PerspectiveCamera.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Sources/Satin/Cameras/PerspectiveCamera.swift b/Sources/Satin/Cameras/PerspectiveCamera.swift index 37a356a9..095419c6 100644 --- a/Sources/Satin/Cameras/PerspectiveCamera.swift +++ b/Sources/Satin/Cameras/PerspectiveCamera.swift @@ -69,7 +69,7 @@ open class PerspectiveCamera: Camera { get { if updateProjectionMatrix { if enableFrustumShift { - _projectionMatrix = frustumShiftProjectionMatrix() + _projectionMatrix = frustumShiftPerspectiveMatrixf(fov, aspect, near, far, lensShiftX, lensShiftY) } else { _projectionMatrix = perspectiveMatrixf(fov, aspect, near, far) } @@ -183,11 +183,6 @@ open class PerspectiveCamera: Camera { } // MARK: - Frustum Shift - - private func frustumShiftProjectionMatrix() -> matrix_float4x4 { - return frustumShiftPerspectiveMatrixf(fov, aspect, near, far, lensShiftX, lensShiftY) - } - public func setLensShift(_ x: Float, _ y: Float) { lensShiftX = x lensShiftY = y From e67a8eb0ca997192c1d86d76d8e120834394130d Mon Sep 17 00:00:00 2001 From: Hiroaki Yamane Date: Fri, 17 Apr 2026 08:29:14 +0100 Subject: [PATCH 3/3] Remove @Published from lens shift properties in PerspectiveCamera --- Sources/Satin/Cameras/PerspectiveCamera.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Satin/Cameras/PerspectiveCamera.swift b/Sources/Satin/Cameras/PerspectiveCamera.swift index 095419c6..b2333198 100644 --- a/Sources/Satin/Cameras/PerspectiveCamera.swift +++ b/Sources/Satin/Cameras/PerspectiveCamera.swift @@ -26,19 +26,19 @@ open class PerspectiveCamera: Camera { } } - @Published public var lensShiftX: Float = 0.0 { + public var lensShiftX: Float = 0.0 { didSet { updateProjectionMatrix = true } } - @Published public var lensShiftY: Float = 0.0 { + public var lensShiftY: Float = 0.0 { didSet { updateProjectionMatrix = true } } - @Published public var enableFrustumShift: Bool = false { + public var enableFrustumShift: Bool = false { didSet { updateProjectionMatrix = true }