diff --git a/shared/AVLiveWire/Sources/AVLiveWire/FrameHeader.swift b/shared/AVLiveWire/Sources/AVLiveWire/FrameHeader.swift index bdd4956..c37080b 100644 --- a/shared/AVLiveWire/Sources/AVLiveWire/FrameHeader.swift +++ b/shared/AVLiveWire/Sources/AVLiveWire/FrameHeader.swift @@ -7,6 +7,7 @@ public enum FrameTag: UInt8 { case hands = 4 case face = 5 case skeleton2D = 6 + case topology = 7 } /// Fixed-size frame header. Layout (big-endian): diff --git a/shared/AVLiveWire/Sources/AVLiveWire/WirePayloads.swift b/shared/AVLiveWire/Sources/AVLiveWire/WirePayloads.swift index ccf94bf..a1e8846 100644 --- a/shared/AVLiveWire/Sources/AVLiveWire/WirePayloads.swift +++ b/shared/AVLiveWire/Sources/AVLiveWire/WirePayloads.swift @@ -195,3 +195,52 @@ public struct FacePayload: Equatable { points = pts } } + +/// Self-describing skeleton topology: joint names + parent indices. +/// Sent once per connection so consumers draw bones generically +/// (parent -> child) with no hardcoded joint mapping. +/// Layout (big-endian): u16 jointCount; per joint [u8 nameLen + utf8]; +/// then per joint i16 parent (-1 = root). +public struct TopologyPayload: Equatable { + public var jointNames: [String] + public var parents: [Int16] + public init(jointNames: [String], parents: [Int16]) { + self.jointNames = jointNames + self.parents = parents + } + + public func encoded() -> Data { + var d = Data() + d.appendBE(UInt16(min(jointNames.count, 65535))) + for name in jointNames { + let bytes = Array(name.utf8.prefix(255)) + d.append(UInt8(bytes.count)) + d.append(contentsOf: bytes) + } + for p in parents { d.appendBE(UInt16(bitPattern: p)) } + return d + } + + public init?(decoding data: Data) { + let b = [UInt8](data) + guard b.count >= 2 else { return nil } + let n = Int(UInt16(bigEndianBytes: b[0..<2])) + var o = 2 + var names: [String] = [] + for _ in 0..