Skip to content

Commit 57f1f97

Browse files
committed
add float4x4 extensions
1 parent 9f7f2be commit 57f1f97

File tree

4 files changed

+160
-12
lines changed

4 files changed

+160
-12
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<img src="Sources/SIMDTools/SIMDTools.docc/Resources/documentation-art/[email protected]", width="120">
88
</p>
99

10-
Welcome to the documentation for the `SIMDTools` Swift package. This package provides utility functions and extensions for working with SIMD matrices and vectors in Swift.
10+
`SIMDTools` provides utility functions and extensions for working with SIMD matrices and vectors in Swift.
1111

1212
## Overview
1313

-359 KB
Loading

Sources/SIMDTools/float4x4+Extensions.swift

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,65 @@ public extension simd_float4x4 {
365365
[dd + offset, ee, ff, 1]
366366
)
367367
}
368+
369+
/// Returns a scaling matrix to fill the original size exactly within the bounding size, without maintaining the aspect ratio
370+
/// - Parameters:
371+
/// - originalSize: The original size of the object
372+
/// - boundingSize: The bounding size to fill the object into
373+
/// - Returns: A new scaling matrix
374+
static func fillScale(
375+
originalSize: SIMD2<Float32>,
376+
boundingSize: SIMD2<Float32>
377+
) -> float4x4 {
378+
let scaleFactors = boundingSize / originalSize
379+
return .scale(value: SIMD3<Float32>(scaleFactors.x, scaleFactors.y, 1))
380+
}
381+
382+
/// Returns a scaling matrix to fill the original size within the bounding size, maintaining the aspect ratio and cropping excess
383+
/// - Parameters:
384+
/// - originalSize: The original size of the object
385+
/// - boundingSize: The bounding size to fill the object into
386+
/// - Returns: A new scaling matrix
387+
static func aspectFillScale(
388+
originalSize: SIMD2<Float32>,
389+
boundingSize: SIMD2<Float32>
390+
) -> float4x4 {
391+
var newSize = boundingSize
392+
let mW = newSize.x / originalSize.x
393+
let mH = newSize.y / originalSize.y
394+
395+
if mH > mW {
396+
newSize.x = newSize.y / originalSize.y * originalSize.x
397+
} else if mW > mH {
398+
newSize.y = newSize.x / originalSize.x * originalSize.y
399+
}
400+
401+
let scaleFactors = newSize / originalSize
402+
return .scale(value: SIMD3<Float32>(scaleFactors.x, scaleFactors.y, 1))
403+
}
404+
405+
/// Returns a scaling matrix to fit the original size within the bounding size, maintaining the aspect ratio
406+
/// - Parameters:
407+
/// - originalSize: The original size of the object
408+
/// - boundingSize: The bounding size to fit the object into
409+
/// - Returns: A new scaling matrix
410+
static func aspectFitScale(
411+
originalSize: SIMD2<Float32>,
412+
boundingSize: SIMD2<Float32>
413+
) -> float4x4 {
414+
var newSize = boundingSize
415+
let mW = newSize.x / originalSize.x
416+
let mH = newSize.y / originalSize.y
417+
418+
if mH < mW {
419+
newSize.x = newSize.y / originalSize.y * originalSize.x
420+
} else if mW < mH {
421+
newSize.y = newSize.x / originalSize.x * originalSize.y
422+
}
423+
424+
let scaleFactors = newSize / originalSize
425+
return .scale(value: SIMD3<Float32>(scaleFactors.x, scaleFactors.y, 1))
426+
}
368427
}
369428

370429
// MARK: - Codable
@@ -397,5 +456,5 @@ extension float4x4: Codable {
397456
try container.encode(self.columns.2, forKey: .column3)
398457
try container.encode(self.columns.3, forKey: .column4)
399458
}
400-
459+
401460
}

Tests/SIMDToolsTests/Float4x4Tests.swift

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import simd
33
@testable import SIMDTools
44

55
class Float4x4Tests: XCTestCase {
6-
6+
77
func testIdentityMatrix() {
88
let identity = float4x4.identity
99
XCTAssertEqual(identity, float4x4(diagonal: SIMD4<Float>(1, 1, 1, 1)))
1010
}
11-
11+
1212
func testTranslationMatrix() {
1313
let translation = float4x4.translate(value: SIMD3<Float>(2.0, 3.0, 4.0))
1414
let expected = float4x4(
@@ -19,7 +19,7 @@ class Float4x4Tests: XCTestCase {
1919
)
2020
XCTAssertEqual(translation, expected)
2121
}
22-
22+
2323
func testRotationMatrixX() {
2424
let angle = Angle(degrees: 90)
2525
let rotation = float4x4.rotate(x: angle)
@@ -31,7 +31,7 @@ class Float4x4Tests: XCTestCase {
3131
)
3232
XCTAssertEqual(rotation, expected)
3333
}
34-
34+
3535
func testRotationMatrixY() {
3636
let angle = Angle(degrees: 90)
3737
let rotation = float4x4.rotate(y: angle)
@@ -43,7 +43,7 @@ class Float4x4Tests: XCTestCase {
4343
)
4444
XCTAssertEqual(rotation, expected)
4545
}
46-
46+
4747
func testRotationMatrixZ() {
4848
let angle = Angle(degrees: 90)
4949
let rotation = float4x4.rotate(z: angle)
@@ -55,7 +55,7 @@ class Float4x4Tests: XCTestCase {
5555
)
5656
XCTAssertEqual(rotation, expected)
5757
}
58-
58+
5959
func testShearXYMatrix() {
6060
let shear = float4x4.shear(xy: SIMD2<Float>(1.0, 0.5))
6161
let expected = float4x4(
@@ -66,7 +66,7 @@ class Float4x4Tests: XCTestCase {
6666
)
6767
XCTAssertEqual(shear, expected)
6868
}
69-
69+
7070
func testShearXZMatrix() {
7171
let shear = float4x4.shear(xz: SIMD2<Float>(1.0, 0.5))
7272
let expected = float4x4(
@@ -77,7 +77,7 @@ class Float4x4Tests: XCTestCase {
7777
)
7878
XCTAssertEqual(shear, expected)
7979
}
80-
80+
8181
func testShearYZMatrix() {
8282
let shear = float4x4.shear(yz: SIMD2<Float>(1.0, 0.5))
8383
let expected = float4x4(
@@ -88,7 +88,7 @@ class Float4x4Tests: XCTestCase {
8888
)
8989
XCTAssertEqual(shear, expected)
9090
}
91-
91+
9292
func testScalingMatrix() {
9393
let scaling = float4x4.scale(value: SIMD3<Float>(2.0, 3.0, 4.0))
9494
let expected = float4x4(
@@ -99,5 +99,94 @@ class Float4x4Tests: XCTestCase {
9999
)
100100
XCTAssertEqual(scaling, expected)
101101
}
102-
102+
103+
// Helper function to compare float4x4 matrices
104+
func assertMatrix(_ matrix1: float4x4, isCloseTo matrix2: float4x4, accuracy: Float = 1e-6) {
105+
for i in 0..<4 {
106+
for j in 0..<4 {
107+
XCTAssertEqual(matrix1[i][j], matrix2[i][j], accuracy: accuracy, "Matrices differ at [\(i)][\(j)]")
108+
}
109+
}
110+
}
111+
112+
func testFillScale() {
113+
let originalSize = SIMD2<Float>(100, 50)
114+
let boundingSize = SIMD2<Float>(200, 150)
115+
116+
let expectedMatrix = float4x4(
117+
[2, 0, 0, 0],
118+
[0, 3, 0, 0],
119+
[0, 0, 1, 0],
120+
[0, 0, 0, 1]
121+
)
122+
123+
let resultMatrix = float4x4.fillScale(originalSize: originalSize, boundingSize: boundingSize)
124+
125+
assertMatrix(resultMatrix, isCloseTo: expectedMatrix)
126+
}
127+
128+
func testAspectFillScale() {
129+
let originalSize = SIMD2<Float>(100, 50)
130+
let boundingSize = SIMD2<Float>(200, 150)
131+
132+
let expectedMatrix = float4x4(
133+
[3, 0, 0, 0],
134+
[0, 3, 0, 0],
135+
[0, 0, 1, 0],
136+
[0, 0, 0, 1]
137+
)
138+
139+
let resultMatrix = float4x4.aspectFillScale(originalSize: originalSize, boundingSize: boundingSize)
140+
141+
assertMatrix(resultMatrix, isCloseTo: expectedMatrix)
142+
}
143+
144+
func testAspectFitScale() {
145+
let originalSize = SIMD2<Float>(100, 50)
146+
let boundingSize = SIMD2<Float>(200, 150)
147+
148+
let expectedMatrix = float4x4(
149+
[2, 0, 0, 0],
150+
[0, 2, 0, 0],
151+
[0, 0, 1, 0],
152+
[0, 0, 0, 1]
153+
)
154+
155+
let resultMatrix = float4x4.aspectFitScale(originalSize: originalSize, boundingSize: boundingSize)
156+
157+
assertMatrix(resultMatrix, isCloseTo: expectedMatrix)
158+
}
159+
160+
func testAspectFillScaleSquareBounds() {
161+
let originalSize = SIMD2<Float>(100, 50)
162+
let boundingSize = SIMD2<Float>(200, 200)
163+
164+
let expectedMatrix = float4x4(
165+
[4, 0, 0, 0],
166+
[0, 4, 0, 0],
167+
[0, 0, 1, 0],
168+
[0, 0, 0, 1]
169+
)
170+
171+
let resultMatrix = float4x4.aspectFillScale(originalSize: originalSize, boundingSize: boundingSize)
172+
173+
assertMatrix(resultMatrix, isCloseTo: expectedMatrix)
174+
}
175+
176+
func testAspectFitScaleSquareBounds() {
177+
let originalSize = SIMD2<Float>(100, 50)
178+
let boundingSize = SIMD2<Float>(200, 200)
179+
180+
let expectedMatrix = float4x4(
181+
[2, 0, 0, 0],
182+
[0, 2, 0, 0],
183+
[0, 0, 1, 0],
184+
[0, 0, 0, 1]
185+
)
186+
187+
let resultMatrix = float4x4.aspectFitScale(originalSize: originalSize, boundingSize: boundingSize)
188+
189+
assertMatrix(resultMatrix, isCloseTo: expectedMatrix)
190+
}
191+
103192
}

0 commit comments

Comments
 (0)