如何编码 Swift 中的 simd_float4x4 元素数组(将 simd_float4x4 转换为数据)?

How can I encode an array of simd_float4x4 elements in Swift (convert simd_float4x4 to Data)?

我正在构建一个应用程序,用于从 iPhone TrueDepth 相机捕获面部跟踪数据。

我需要将此数据写入文件,以便我可以将其用作另一个应用程序的基础。

在应用程序中,数据被保存到四个单独的数组中,一个包含 ARFaceGeometry 对象,另外三个具有作为 simd_float4x4 矩阵的变换坐标。

我正在使用 archivedData(withRootObject: requiringSecureCoding:) 将数组转换为数据对象,然后调用 write(to:) 来创建文件。

包含 ARFaceGeometry 数据的文件被正确写入和读回。但是三个 simd_float4x4 数组并没有被写入,尽管这样做的代码是相同的。连同我的打印日志,给出的错误是 'unrecognized selector sent to instance'.

属性:

var faceGeometryCapture = [ARFaceGeometry]()
var faceTransformCapture = [simd_float4x4]()
var leftEyeTransformCapture = [simd_float4x4]()
var rightEyeTransformCapture = [simd_float4x4]()

var faceGeometryCaptureFilePath: URL!
var faceTransformCaptureFilePath: URL!
var leftEyeTransformCaptureFilePath: URL!
var rightEyeTransformCaptureFilePath: URL!

建立文件URL的代码:

let fileManager = FileManager.default
let dirPaths = fileManager.urls(for: .documentDirectory,
                        in: .userDomainMask)
        
faceGeometryCaptureFilePath = dirPaths[0].appendingPathComponent("face-geometries.txt")
faceTransformCaptureFilePath = dirPaths[0].appendingPathComponent("face-transforms.txt")
leftEyeTransformCaptureFilePath = dirPaths[0].appendingPathComponent("left-eye-transforms.txt")
rightEyeTransformCaptureFilePath = dirPaths[0].appendingPathComponent("right-eye-transforms.txt")

将数据写入文件的代码:

do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: faceGeometryCapture, requiringSecureCoding: false)
    try data.write(to: faceGeometryCaptureFilePath)
    } catch { print("Error writing face geometries to file") }
do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: faceTransformCapture, requiringSecureCoding: false)
    try data.write(to: faceTransformCaptureFilePath)
    } catch { print("Error writing face transforms to file") }
do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: leftEyeTransformCapture, requiringSecureCoding: false)
    try data.write(to: leftEyeTransformCaptureFilePath)
    } catch { print("Error writing left eye transforms to file") }
do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: rightEyeTransformCapture, requiringSecureCoding: false)
    try data.write(to: rightEyeTransformCaptureFilePath)
    } catch { print("Error writing right eye transforms to file") }

我猜是 simd_float4x4 结构导致了这个问题,因为这是工作和不工作之间的唯一区别。谁能确认并提出解决方案?

提前致谢。

正如评论中提到的,结构不符合 NSCoding 但你可以使 simd_float4x4 符合 Codable 并保留其数据:

extension simd_float4x4: Codable {
    public init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        try self.init(container.decode([SIMD4<Float>].self))
    }
    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode([columns.0,columns.1, columns.2, columns.3])
    }
}

游乐场测试:

do {
    let vector = simd_float4x4(2.7)  // simd_float4x4([[2.7, 0.0, 0.0, 0.0], [0.0, 2.7, 0.0, 0.0], [0.0, 0.0, 2.7, 0.0], [0.0, 0.0, 0.0, 2.7]])
    let data = try JSONEncoder().encode(vector)  // 111 bytes
    let json = String(data: data, encoding: .utf8)
    print(json ?? "")  // [[[2.7000000476837158,0,0,0],[0,2.7000000476837158,0,0],[0,0,2.7000000476837158,0],[0,0,0,2.7000000476837158]]]\n"
    let decoded = try JSONDecoder().decode(simd_float4x4.self, from: data)
    print(decoded)     // "simd_float4x4([[2.7, 0.0, 0.0, 0.0], [0.0, 2.7, 0.0, 0.0], [0.0, 0.0, 2.7, 0.0], [0.0, 0.0, 0.0, 2.7]])\n"
    decoded == vector  // true
} catch {
    print(error)
}

edit/update:

另一种选择是保存其原始字节。它将仅使用 64 个字节:

extension simd_float4x4: ContiguousBytes {
    public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
        try Swift.withUnsafeBytes(of: self) { try body([=12=]) }
    }
}

extension ContiguousBytes {
    init<T: ContiguousBytes>(_ bytes: T) {
        self = bytes.withUnsafeBytes { [=13=].load(as: Self.self) }
    }
    var bytes: [UInt8] { withUnsafeBytes { .init([=13=]) } }
    var data: Data { withUnsafeBytes { .init([=13=]) } }
    func object<T>() -> T { withUnsafeBytes { [=13=].load(as: T.self) } }
    func objects<T>() -> [T] { withUnsafeBytes { .init([=13=].bindMemory(to: T.self)) } }
    var simdFloat4x4: simd_float4x4 { object() }
    var simdFloat4x4Collection: [simd_float4x4] { objects() }
}

extension Array where Element: ContiguousBytes {
    var bytes: [UInt8] { withUnsafeBytes { .init([=14=]) } }
    var data: Data { withUnsafeBytes { .init([=14=]) } }
}

let vector1 = simd_float4x4(.init(2, 1, 1, 1), .init(1, 2, 1, 1), .init(1, 1, 2, 1), .init(1, 1, 1, 2))
let vector2 = simd_float4x4(.init(3, 1, 1, 1), .init(1, 3, 1, 1), .init(1, 1, 3, 1), .init(1, 1, 1, 3))
let data = [vector1,vector2].data          // 128 bytes
let loaded = data.simdFloat4x4Collection
print(loaded)    // "[simd_float4x4([[2.0, 1.0, 1.0, 1.0], [1.0, 2.0, 1.0, 1.0], [1.0, 1.0, 2.0, 1.0], [1.0, 1.0, 1.0, 2.0]]), simd_float4x4([[3.0, 1.0, 1.0, 1.0], [1.0, 3.0, 1.0, 1.0], [1.0, 1.0, 3.0, 1.0], [1.0, 1.0, 1.0, 3.0]])]\n"
loaded[0] == vector1  // true
loaded[1] == vector2  // true