|
1 | 1 | import Foundation |
2 | 2 |
|
3 | | -public struct VRM: VRMFile { |
4 | | - public let gltf: BinaryGLTF |
5 | | - public let meta: Meta |
6 | | - public let version: String? |
7 | | - public let materialProperties: [MaterialProperty] |
8 | | - public let humanoid: Humanoid |
9 | | - public let blendShapeMaster: BlendShapeMaster |
10 | | - public let firstPerson: FirstPerson |
11 | | - public let secondaryAnimation: SecondaryAnimation |
12 | | - |
13 | | - public let materialPropertyNameMap: [String: MaterialProperty] |
| 3 | +/// VRM data, supporting both VRM0 and VRM1 formats |
| 4 | +public enum VRM { |
| 5 | + case v0(VRM0) |
| 6 | + case v1(VRM1) |
14 | 7 |
|
15 | 8 | public init(data: Data) throws { |
16 | | - gltf = try BinaryGLTF(data: data) |
17 | | - |
| 9 | + let gltf = try BinaryGLTF(data: data) |
18 | 10 | let rawExtensions = try gltf.jsonData.extensions ??? .keyNotFound("extensions") |
19 | 11 | let extensions = try rawExtensions.value as? [String: [String: Any]] ??? .dataInconsistent("extension type mismatch") |
20 | 12 |
|
21 | | - let decoder = DictionaryDecoder() |
22 | | - |
23 | 13 | if extensions.keys.contains("VRMC_vrm") { |
24 | | - // VRM 1.0 Support |
25 | | - let vrm1 = try VRM1(data: data) |
26 | | - |
27 | | - // Version |
28 | | - version = vrm1.specVersion |
29 | | - |
30 | | - // Meta |
31 | | - meta = Meta(vrm1: vrm1.meta) |
32 | | - |
33 | | - // Humanoid |
34 | | - humanoid = Humanoid(vrm1: vrm1.humanoid) |
35 | | - |
36 | | - // BlendShapeMaster |
37 | | - blendShapeMaster = BlendShapeMaster(vrm1: vrm1.expressions, gltf: gltf) |
38 | | - |
39 | | - // FirstPerson |
40 | | - firstPerson = FirstPerson(vrm1: vrm1.firstPerson, lookAt: vrm1.lookAt) |
41 | | - |
42 | | - // SecondaryAnimation (SpringBone) |
43 | | - secondaryAnimation = SecondaryAnimation(vrm1: vrm1.springBone) |
44 | | - |
45 | | - // MaterialProperties (MToon) |
46 | | - materialProperties = try VRM.migrateMaterials(gltf: gltf, vrm1: vrm1) |
47 | | - |
48 | | - materialPropertyNameMap = materialProperties.reduce(into: [:]) { $0[$1.name] = $1 } |
49 | | - |
| 14 | + self = .v1(try VRM1(data: data)) |
50 | 15 | } else { |
51 | | - // VRM 0.x Support |
52 | | - let vrm = try extensions["VRM"] ??? .keyNotFound("VRM") |
53 | | - |
54 | | - meta = try decoder.decode(Meta.self, from: try vrm["meta"] ??? .keyNotFound("meta")) |
55 | | - version = vrm["version"] as? String |
56 | | - materialProperties = try decoder.decode([MaterialProperty].self, from: try vrm["materialProperties"] ??? .keyNotFound("materialProperties")) |
57 | | - humanoid = try decoder.decode(Humanoid.self, from: try vrm["humanoid"] ??? .keyNotFound("humanoid")) |
58 | | - blendShapeMaster = try decoder.decode(BlendShapeMaster.self, from: try vrm["blendShapeMaster"] ??? .keyNotFound("blendShapeMaster")) |
59 | | - firstPerson = try decoder.decode(FirstPerson.self, from: try vrm["firstPerson"] ??? .keyNotFound("firstPerson")) |
60 | | - secondaryAnimation = try decoder.decode(SecondaryAnimation.self, from: try vrm["secondaryAnimation"] ??? .keyNotFound("secondaryAnimation")) |
61 | | - |
62 | | - materialPropertyNameMap = materialProperties.reduce(into: [:]) { $0[$1.name] = $1 } |
| 16 | + self = .v0(try VRM0(data: data)) |
63 | 17 | } |
64 | 18 | } |
65 | | -} |
66 | | - |
67 | | -public extension VRM { |
68 | | - struct Meta: Codable { |
69 | | - public let title: String? |
70 | | - public let author: String? |
71 | | - public let contactInformation: String? |
72 | | - public let reference: String? |
73 | | - public let texture: Int? |
74 | | - public let version: String? |
75 | 19 |
|
76 | | - public let allowedUserName: String? |
77 | | - public let violentUssageName: String? |
78 | | - public let sexualUssageName: String? |
79 | | - public let commercialUssageName: String? |
80 | | - public let otherPermissionUrl: String? |
| 20 | + // MARK: - Common Interface |
81 | 21 |
|
82 | | - public let licenseName: String? |
83 | | - public let otherLicenseUrl: String? |
| 22 | + /// The underlying BinaryGLTF data |
| 23 | + public var gltf: BinaryGLTF { |
| 24 | + switch self { |
| 25 | + case .v0(let vrm): return vrm.gltf |
| 26 | + case .v1(let vrm): return vrm.gltf |
| 27 | + } |
84 | 28 | } |
85 | 29 |
|
86 | | - struct MaterialProperty: Codable { |
87 | | - public let name: String |
88 | | - public let shader: String |
89 | | - public let renderQueue: Int |
90 | | - public let floatProperties: CodableAny |
91 | | - public let keywordMap: [String: Bool] |
92 | | - public let tagMap: [String: String] |
93 | | - public let textureProperties: [String: Int] |
94 | | - public let vectorProperties: CodableAny |
| 30 | + /// VRM spec version string |
| 31 | + public var specVersion: String { |
| 32 | + switch self { |
| 33 | + case .v0(let vrm): return vrm.version ?? "0.x" |
| 34 | + case .v1(let vrm): return vrm.specVersion |
| 35 | + } |
95 | 36 | } |
96 | 37 |
|
97 | | - struct Humanoid: Codable { |
98 | | - public let armStretch: Double |
99 | | - public let feetSpacing: Double |
100 | | - public let hasTranslationDoF: Bool |
101 | | - public let legStretch: Double |
102 | | - public let lowerArmTwist: Double |
103 | | - public let lowerLegTwist: Double |
104 | | - public let upperArmTwist: Double |
105 | | - public let upperLegTwist: Double |
106 | | - public let humanBones: [HumanBone] |
| 38 | + // MARK: - VRM0 Format interfaces (for current migration period) |
| 39 | + // In the future, these will be replaced with VRM1 native types |
107 | 40 |
|
108 | | - public struct HumanBone: Codable { |
109 | | - public let bone: String |
110 | | - public let node: Int |
111 | | - public let useDefaultValues: Bool |
| 41 | + /// Meta information (VRM0 format) |
| 42 | + public var meta: VRM0.Meta { |
| 43 | + switch self { |
| 44 | + case .v0(let vrm): return vrm.meta |
| 45 | + case .v1(let vrm): return VRM0.Meta(vrm1: vrm.meta) |
112 | 46 | } |
113 | 47 | } |
114 | 48 |
|
115 | | - struct BlendShapeMaster: Codable { |
116 | | - public let blendShapeGroups: [BlendShapeGroup] |
117 | | - public struct BlendShapeGroup: Codable { |
118 | | - public let binds: [Bind]? |
119 | | - public let materialValues: [MaterialValueBind]? |
120 | | - public let name: String |
121 | | - public let presetName: String |
122 | | - let _isBinary: Bool? |
123 | | - public var isBinary: Bool { return _isBinary ?? false } |
124 | | - private enum CodingKeys: String, CodingKey { |
125 | | - case binds |
126 | | - case materialValues |
127 | | - case name |
128 | | - case presetName |
129 | | - case _isBinary = "isBinary" |
130 | | - } |
131 | | - public struct Bind: Codable { |
132 | | - public let index: Int |
133 | | - public let mesh: Int |
134 | | - public let weight: Double |
135 | | - } |
136 | | - public struct MaterialValueBind: Codable { |
137 | | - public let materialName: String |
138 | | - public let propertyName: String |
139 | | - public let targetValue: [Double] |
140 | | - } |
| 49 | + /// Humanoid bone mapping (VRM0 format) |
| 50 | + public var humanoid: VRM0.Humanoid { |
| 51 | + switch self { |
| 52 | + case .v0(let vrm): return vrm.humanoid |
| 53 | + case .v1(let vrm): return VRM0.Humanoid(vrm1: vrm.humanoid) |
141 | 54 | } |
142 | 55 | } |
143 | 56 |
|
144 | | - struct FirstPerson: Codable { |
145 | | - public let firstPersonBone: Int |
146 | | - public let firstPersonBoneOffset: Vector3 |
147 | | - public let meshAnnotations: [MeshAnnotation] |
148 | | - public let lookAtTypeName: LookAtType |
149 | | - |
150 | | - public struct MeshAnnotation: Codable { |
151 | | - public let firstPersonFlag: String |
152 | | - public let mesh: Int |
| 57 | + /// Material properties (VRM0 format) |
| 58 | + public var materialProperties: [VRM0.MaterialProperty] { |
| 59 | + switch self { |
| 60 | + case .v0(let vrm): return vrm.materialProperties |
| 61 | + case .v1(let vrm): return VRM0(migratedFrom: vrm).materialProperties |
153 | 62 | } |
154 | | - public enum LookAtType: String, Codable { |
155 | | - case none = "None" |
156 | | - case bone = "Bone" |
157 | | - case blendShape = "BlendShape" |
| 63 | + } |
| 64 | + |
| 65 | + /// Material property name map (VRM0 format) |
| 66 | + public var materialPropertyNameMap: [String: VRM0.MaterialProperty] { |
| 67 | + switch self { |
| 68 | + case .v0(let vrm): return vrm.materialPropertyNameMap |
| 69 | + case .v1(let vrm): return VRM0(migratedFrom: vrm).materialPropertyNameMap |
158 | 70 | } |
159 | 71 | } |
160 | 72 |
|
161 | | - struct SecondaryAnimation: Codable { |
162 | | - public let boneGroups: [BoneGroup] |
163 | | - public let colliderGroups: [ColliderGroup] |
164 | | - public struct BoneGroup: Codable { |
165 | | - public let bones: [Int] |
166 | | - public let center: Int |
167 | | - public let colliderGroups: [Int] |
168 | | - public let comment: String? |
169 | | - public let dragForce: Double |
170 | | - public let gravityDir: Vector3 |
171 | | - public let gravityPower: Double |
172 | | - public let hitRadius: Double |
173 | | - public let stiffiness: Double |
| 73 | + /// BlendShape master (VRM0 format) |
| 74 | + public var blendShapeMaster: VRM0.BlendShapeMaster { |
| 75 | + switch self { |
| 76 | + case .v0(let vrm): return vrm.blendShapeMaster |
| 77 | + case .v1(let vrm): return VRM0(migratedFrom: vrm).blendShapeMaster |
174 | 78 | } |
175 | | - |
176 | | - public struct ColliderGroup: Codable { |
177 | | - public let node: Int |
178 | | - public let colliders: [Collider] |
179 | | - |
180 | | - public struct Collider: Codable { |
181 | | - public let offset: Vector3 |
182 | | - public let radius: Double |
183 | | - } |
| 79 | + } |
| 80 | + |
| 81 | + /// First person settings (VRM0 format) |
| 82 | + public var firstPerson: VRM0.FirstPerson { |
| 83 | + switch self { |
| 84 | + case .v0(let vrm): return vrm.firstPerson |
| 85 | + case .v1(let vrm): return VRM0(migratedFrom: vrm).firstPerson |
184 | 86 | } |
185 | 87 | } |
186 | 88 |
|
187 | | - struct Vector3: Codable { |
188 | | - public let x, y, z: Double |
| 89 | + /// Secondary animation / spring bones (VRM0 format) |
| 90 | + public var secondaryAnimation: VRM0.SecondaryAnimation { |
| 91 | + switch self { |
| 92 | + case .v0(let vrm): return vrm.secondaryAnimation |
| 93 | + case .v1(let vrm): return VRM0(migratedFrom: vrm).secondaryAnimation |
| 94 | + } |
189 | 95 | } |
190 | 96 | } |
0 commit comments