Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 16 additions & 3 deletions Sources/FirebladeMath/Matrix/Matrices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public typealias Mat2x2d = Matrix2x2<Storage2x2<Double>>
/// - row: The row (zero based).
/// - width: The width of a column.
/// - Returns: The flat index into the matrix storage.
/// - Important: This computes a **row-major** index, which does not match the column-major
/// storage of FirebladeMath matrices (see ``columnRow(index:width:)``).
@available(*, deprecated, message: "Computes a row-major index that does not match FirebladeMath's column-major storage. Use the matrix's subscript(column:row:) instead.")
@inlinable
public func index(column: Int, row: Int, width: Int) -> Int {
(row * width) + column
Expand All @@ -53,9 +56,19 @@ public func index(column: Int, row: Int, width: Int) -> Int {
/// - index: The index into the matrix.
/// - width: The width of a column.
/// - Returns: A tuple containing the column and row indices.
@available(*, deprecated, message: "Implementation detail of matrix storage. Use the matrix's subscript(column:row:) or subscript(index:) instead.")
@inlinable
public func columnRow(index: Int, width: Int) -> (column: Int, row: Int) {
let row: Int = index % width
let column: Int = index / width
return (column, row)
columnMajorPosition(of: index, rows: width)
}

/// Splits a flat column-major index into its column and row.
/// - Parameters:
/// - index: The flat index into the matrix storage.
/// - rows: The number of rows, i.e. the length of a column.
/// - Returns: A tuple containing the column and row indices.
@inlinable
@inline(__always)
func columnMajorPosition(of index: Int, rows: Int) -> (column: Int, row: Int) { // swiftlint:disable:this explicit_top_level_acl
(index / rows, index % rows)
}
12 changes: 6 additions & 6 deletions Sources/FirebladeMath/Matrix/MatrixStorage+NO_SIMD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ public struct Storage2x2<Value: StorageScalar>: Storage2x2Protocol {
@inlinable
public subscript(index: Int) -> Value {
get {
let (column, row) = columnRow(index: index, width: 2)
let (column, row) = columnMajorPosition(of: index, rows: 2)
return self[column, row]
}
set {
let (column, row) = columnRow(index: index, width: 2)
let (column, row) = columnMajorPosition(of: index, rows: 2)
self[column, row] = newValue
}
}
Expand Down Expand Up @@ -173,11 +173,11 @@ public struct Storage3x3<Value: StorageScalar>: Storage3x3Protocol {
@inlinable
public subscript(index: Int) -> Value {
get {
let (column, row) = columnRow(index: index, width: 3)
let (column, row) = columnMajorPosition(of: index, rows: 3)
return self[column, row]
}
set {
let (column, row) = columnRow(index: index, width: 3)
let (column, row) = columnMajorPosition(of: index, rows: 3)
self[column, row] = newValue
}
}
Expand Down Expand Up @@ -287,11 +287,11 @@ public struct Storage4x4<Value: StorageScalar>: Storage4x4Protocol {
@inlinable
public subscript(index: Int) -> Value {
get {
let (column, row) = columnRow(index: index, width: 4)
let (column, row) = columnMajorPosition(of: index, rows: 4)
return self[column, row]
}
set {
let (column, row) = columnRow(index: index, width: 4)
let (column, row) = columnMajorPosition(of: index, rows: 4)
self[column, row] = newValue
}
}
Expand Down
24 changes: 12 additions & 12 deletions Sources/FirebladeMath/Matrix/MatrixStorage+SIMD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension simd_float2x2: Storage2x2Protocol, @retroactive @unchecked Sendable, @
@inlinable
public subscript(index: Int) -> Float {
get {
let (col, row) = columnRow(index: index, width: 2)
let (col, row) = columnMajorPosition(of: index, rows: 2)
switch col {
case 0:
return columns.0[row]
Expand All @@ -41,7 +41,7 @@ extension simd_float2x2: Storage2x2Protocol, @retroactive @unchecked Sendable, @
}
}
set {
let (col, row) = columnRow(index: index, width: 2)
let (col, row) = columnMajorPosition(of: index, rows: 2)
switch col {
case 0:
columns.0[row] = newValue
Expand Down Expand Up @@ -76,7 +76,7 @@ extension simd_double2x2: Storage2x2Protocol, @retroactive @unchecked Sendable,
@inlinable
public subscript(index: Int) -> Double {
get {
let (col, row) = columnRow(index: index, width: 2)
let (col, row) = columnMajorPosition(of: index, rows: 2)
switch col {
case 0:
return columns.0[row]
Expand All @@ -89,7 +89,7 @@ extension simd_double2x2: Storage2x2Protocol, @retroactive @unchecked Sendable,
}
}
set {
let (col, row) = columnRow(index: index, width: 2)
let (col, row) = columnMajorPosition(of: index, rows: 2)
switch col {
case 0:
columns.0[row] = newValue
Expand Down Expand Up @@ -126,7 +126,7 @@ extension simd_float3x3: Storage3x3Protocol, @retroactive @unchecked Sendable, @
@inlinable
public subscript(index: Int) -> Float {
get {
let (col, row) = columnRow(index: index, width: 3)
let (col, row) = columnMajorPosition(of: index, rows: 3)
switch col {
case 0:
return columns.0[row]
Expand All @@ -142,7 +142,7 @@ extension simd_float3x3: Storage3x3Protocol, @retroactive @unchecked Sendable, @
}
}
set {
let (col, row) = columnRow(index: index, width: 3)
let (col, row) = columnMajorPosition(of: index, rows: 3)
switch col {
case 0:
columns.0[row] = newValue
Expand Down Expand Up @@ -180,7 +180,7 @@ extension simd_double3x3: Storage3x3Protocol, @retroactive @unchecked Sendable,
@inlinable
public subscript(index: Int) -> Double {
get {
let (col, row) = columnRow(index: index, width: 3)
let (col, row) = columnMajorPosition(of: index, rows: 3)
switch col {
case 0:
return columns.0[row]
Expand All @@ -196,7 +196,7 @@ extension simd_double3x3: Storage3x3Protocol, @retroactive @unchecked Sendable,
}
}
set {
let (col, row) = columnRow(index: index, width: 3)
let (col, row) = columnMajorPosition(of: index, rows: 3)
switch col {
case 0:
columns.0[row] = newValue
Expand Down Expand Up @@ -236,7 +236,7 @@ extension simd_float4x4: Storage4x4Protocol, @retroactive @unchecked Sendable, @
@inlinable
public subscript(index: Int) -> Float {
get {
let (col, row) = columnRow(index: index, width: 4)
let (col, row) = columnMajorPosition(of: index, rows: 4)
switch col {
case 0:
return columns.0[row]
Expand All @@ -255,7 +255,7 @@ extension simd_float4x4: Storage4x4Protocol, @retroactive @unchecked Sendable, @
}
}
set {
let (col, row) = columnRow(index: index, width: 4)
let (col, row) = columnMajorPosition(of: index, rows: 4)
switch col {
case 0:
columns.0[row] = newValue
Expand Down Expand Up @@ -296,7 +296,7 @@ extension simd_double4x4: Storage4x4Protocol, @retroactive @unchecked Sendable,
@inlinable
public subscript(index: Int) -> Double {
get {
let (col, row) = columnRow(index: index, width: 4)
let (col, row) = columnMajorPosition(of: index, rows: 4)
switch col {
case 0:
return columns.0[row]
Expand All @@ -315,7 +315,7 @@ extension simd_double4x4: Storage4x4Protocol, @retroactive @unchecked Sendable,
}
}
set {
let (col, row) = columnRow(index: index, width: 4)
let (col, row) = columnMajorPosition(of: index, rows: 4)
switch col {
case 0:
columns.0[row] = newValue
Expand Down
64 changes: 64 additions & 0 deletions Tests/FirebladeMathTests/MatrixIndexingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// MatrixIndexingTests.swift
// FirebladeMathTests
//

import FirebladeMath
import Testing

struct MatrixIndexingTests {
// The flat subscript must follow the column-major storage layout:
// index = column * rows + row.

@Test func mat2x2FlatIndexIsColumnMajor() {
let mf: Mat2x2f = [1, 2, 3, 4]
let md: Mat2x2d = [1, 2, 3, 4]
for index in 0..<4 {
#expect(mf[index] == mf[index / 2, index % 2])
#expect(md[index] == md[index / 2, index % 2])
}
}

@Test func mat3x3FlatIndexIsColumnMajor() {
let mf = Mat3x3f((1...9).map(Float.init))
let md = Mat3x3d((1...9).map(Double.init))
for index in 0..<9 {
#expect(mf[index] == mf[index / 3, index % 3])
#expect(md[index] == md[index / 3, index % 3])
}
}

@Test func mat4x4FlatIndexIsColumnMajor() {
let mf = Mat4x4f((1...16).map(Float.init))
let md = Mat4x4d((1...16).map(Double.init))
for index in 0..<16 {
#expect(mf[index] == mf[index / 4, index % 4])
#expect(md[index] == md[index / 4, index % 4])
}
}

@Test func flatSubscriptSetterWritesColumnMajor() {
var matrix = Mat4x4f.identity
matrix[6] = 42 // column 1, row 2
#expect(matrix[1, 2] == 42)
#expect(matrix.columns.1.z == 42)
}

// MARK: - Deprecated helpers keep their behavior

@Test
@available(*, deprecated, message: "Testing deprecated API")
func deprecatedColumnRowIsColumnMajor() {
#expect(columnRow(index: 6, width: 4) == (column: 1, row: 2))
#expect(columnRow(index: 0, width: 3) == (column: 0, row: 0))
#expect(columnRow(index: 8, width: 3) == (column: 2, row: 2))
}

@Test
@available(*, deprecated, message: "Testing deprecated API")
func deprecatedIndexIsRowMajor() {
// Documented mismatch: index(column:row:width:) is row-major, columnRow is column-major.
#expect(index(column: 1, row: 2, width: 4) == 9)
#expect(columnRow(index: index(column: 1, row: 2, width: 4), width: 4) != (column: 1, row: 2))
}
}