Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion Sources/VRMKit/VRM/Material.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension GLTF {

public struct PbrMetallicRoughness: Codable {
let _baseColorFactor: Color4?
public var baseColorFactor: Color4 { return _baseColorFactor ?? .init(r: 0, g: 0, b: 0, a: 0) }
public var baseColorFactor: Color4 { return _baseColorFactor ?? .init(r: 1, g: 1, b: 1, a: 1) }
public let baseColorTexture: TextureInfo?
let _metallicFactor: Float?
public var metallicFactor: Float { return _metallicFactor ?? 1 }
Expand Down
11 changes: 7 additions & 4 deletions Sources/VRMKit/VRMLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ open class VRMLoader {
}

open func loadThumbnail(from vrm: VRM) throws -> VRMImage {
guard let textureIndex = vrm.meta.texture, textureIndex >= 0 else {
throw VRMError.thumbnailNotFound
switch vrm {
case .v0(let vrm0):
return try loadThumbnail(from: vrm0)
case .v1(let vrm1):
return try loadThumbnail(from: vrm1)
}
return try loadImage(from: vrm.gltf, at: textureIndex)
}

open func loadThumbnail(from vrm0: VRM0) throws -> VRMImage {
guard let textureIndex = vrm0.meta.texture, textureIndex >= 0 else {
throw VRMError.thumbnailNotFound
}
return try loadImage(from: vrm0.gltf, at: textureIndex)
let texture = try vrm0.gltf.jsonData.load(\.textures)[textureIndex]
Comment thread
soramikan marked this conversation as resolved.
Outdated
return try loadImage(from: vrm0.gltf, at: texture.source)
}

open func loadThumbnail(from vrm1: VRM1) throws -> VRMImage {
Expand Down
113 changes: 111 additions & 2 deletions Sources/VRMKitRuntime/BlendShapeTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public enum BlendShapeKey: Hashable {
}
}

/// VRM 0.x Blend Shape Preset
/// VRM expression preset.
public enum BlendShapePreset: String {
case unknown
case neutral
Expand All @@ -30,8 +30,117 @@ public enum BlendShapePreset: String {
case lookRight = "lookright"
case blinkL = "blink_l"
case blinkR = "blink_r"
case happy
case sad
case relaxed
case surprised
case aa
case ih
case ou
case ee
case oh
case blinkLeft
case blinkRight

public init(name: String) {
self = BlendShapePreset(rawValue: name.lowercased()) ?? .unknown
switch name.replacingOccurrences(of: "_", with: "").lowercased() {
case "neutral":
self = .neutral
case "a":
self = .a
case "i":
self = .i
case "u":
self = .u
case "e":
self = .e
case "o":
self = .o
case "blink":
self = .blink
case "joy":
self = .joy
case "angry":
self = .angry
case "sorrow":
self = .sorrow
case "fun":
self = .fun
case "lookup":
self = .lookUp
case "lookdown":
self = .lookDown
case "lookleft":
self = .lookLeft
case "lookright":
self = .lookRight
case "blinkl":
self = .blinkL
case "blinkr":
self = .blinkR
case "happy":
self = .happy
case "sad":
self = .sad
case "relaxed":
self = .relaxed
case "surprised":
self = .surprised
case "aa":
self = .aa
case "ih":
self = .ih
case "ou":
self = .ou
case "ee":
self = .ee
case "oh":
self = .oh
case "blinkleft":
self = .blinkLeft
case "blinkright":
self = .blinkRight
default:
self = .unknown
}
}
}

package extension BlendShapePreset {
var aliases: [BlendShapePreset] {
switch self {
case .joy: return [.happy]
case .happy: return [.joy]
case .sorrow: return [.sad]
case .sad: return [.sorrow]
case .fun: return [.relaxed]
case .relaxed: return [.fun]
case .a: return [.aa]
case .aa: return [.a]
case .i: return [.ih]
case .ih: return [.i]
case .u: return [.ou]
case .ou: return [.u]
case .e: return [.ee]
case .ee: return [.e]
case .o: return [.oh]
case .oh: return [.o]
case .blinkL: return [.blinkLeft]
case .blinkLeft: return [.blinkL]
case .blinkR: return [.blinkRight]
case .blinkRight: return [.blinkR]
default: return []
}
}
}

package extension BlendShapeKey {
var aliases: [BlendShapeKey] {
switch self {
case .preset(let preset):
return preset.aliases.map(BlendShapeKey.preset)
case .custom:
return []
}
}
}
4 changes: 4 additions & 0 deletions Sources/VRMKitRuntime/FirstPersonRenderMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public enum FirstPersonRenderMode {
case firstPerson
case thirdPerson
}
70 changes: 70 additions & 0 deletions Sources/VRMKitRuntime/Humanoid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,73 @@ public final class Humanoid<Node> {
}
}

package func setUp(humanoid: VRM1.Humanoid, nodes: [Node?]) {
let humanBones = humanoid.humanBones
let mappings: [(Bones, VRM1.Humanoid.HumanBones.HumanBone?)] = [
(.hips, humanBones.hips),
(.spine, humanBones.spine),
(.chest, humanBones.chest),
(.upperChest, humanBones.upperChest),
(.neck, humanBones.neck),
(.head, humanBones.head),
(.leftEye, humanBones.leftEye),
(.rightEye, humanBones.rightEye),
(.jaw, humanBones.jaw),
(.leftUpperLeg, humanBones.leftUpperLeg),
(.leftLowerLeg, humanBones.leftLowerLeg),
(.leftFoot, humanBones.leftFoot),
(.leftToes, humanBones.leftToes),
(.rightUpperLeg, humanBones.rightUpperLeg),
(.rightLowerLeg, humanBones.rightLowerLeg),
(.rightFoot, humanBones.rightFoot),
(.rightToes, humanBones.rightToes),
(.leftShoulder, humanBones.leftShoulder),
(.leftUpperArm, humanBones.leftUpperArm),
(.leftLowerArm, humanBones.leftLowerArm),
(.leftHand, humanBones.leftHand),
(.rightShoulder, humanBones.rightShoulder),
(.rightUpperArm, humanBones.rightUpperArm),
(.rightLowerArm, humanBones.rightLowerArm),
(.rightHand, humanBones.rightHand),
(.leftThumbMetacarpal, humanBones.leftThumbMetacarpal),
(.leftThumbProximal, humanBones.leftThumbProximal),
(.leftThumbDistal, humanBones.leftThumbDistal),
(.leftIndexProximal, humanBones.leftIndexProximal),
(.leftIndexIntermediate, humanBones.leftIndexIntermediate),
(.leftIndexDistal, humanBones.leftIndexDistal),
(.leftMiddleProximal, humanBones.leftMiddleProximal),
(.leftMiddleIntermediate, humanBones.leftMiddleIntermediate),
(.leftMiddleDistal, humanBones.leftMiddleDistal),
(.leftRingProximal, humanBones.leftRingProximal),
(.leftRingIntermediate, humanBones.leftRingIntermediate),
(.leftRingDistal, humanBones.leftRingDistal),
(.leftLittleProximal, humanBones.leftLittleProximal),
(.leftLittleIntermediate, humanBones.leftLittleIntermediate),
(.leftLittleDistal, humanBones.leftLittleDistal),
(.rightThumbMetacarpal, humanBones.rightThumbMetacarpal),
(.rightThumbProximal, humanBones.rightThumbProximal),
(.rightThumbDistal, humanBones.rightThumbDistal),
(.rightIndexProximal, humanBones.rightIndexProximal),
(.rightIndexIntermediate, humanBones.rightIndexIntermediate),
(.rightIndexDistal, humanBones.rightIndexDistal),
(.rightMiddleProximal, humanBones.rightMiddleProximal),
(.rightMiddleIntermediate, humanBones.rightMiddleIntermediate),
(.rightMiddleDistal, humanBones.rightMiddleDistal),
(.rightRingProximal, humanBones.rightRingProximal),
(.rightRingIntermediate, humanBones.rightRingIntermediate),
(.rightRingDistal, humanBones.rightRingDistal),
(.rightLittleProximal, humanBones.rightLittleProximal),
(.rightLittleIntermediate, humanBones.rightLittleIntermediate),
(.rightLittleDistal, humanBones.rightLittleDistal)
]
bones = mappings.reduce(into: [:]) { result, mapping in
guard let humanBone = mapping.1,
nodes.indices.contains(humanBone.node),
let node = nodes[humanBone.node] else { return }
result[mapping.0] = node
}
}

public func node(for bone: Bones) -> Node? {
return bones[bone]
}
Expand All @@ -27,6 +94,7 @@ public final class Humanoid<Node> {
case leftFoot
case rightFoot
case spine
case chest
case neck
case head
case leftShoulder
Expand All @@ -43,6 +111,7 @@ public final class Humanoid<Node> {
case rightEye
case jaw
case leftThumbProximal
case leftThumbMetacarpal
case leftThumbIntermediate
case leftThumbDistal
case leftIndexProximal
Expand All @@ -58,6 +127,7 @@ public final class Humanoid<Node> {
case leftLittleIntermediate
case leftLittleDistal
case rightThumbProximal
case rightThumbMetacarpal
case rightThumbIntermediate
case rightThumbDistal
case rightIndexProximal
Expand Down
40 changes: 40 additions & 0 deletions Sources/VRMKitRuntime/VRM1Expressions+Runtime.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import VRMKit

package extension VRM1.Expressions {
var runtimeClips: [(name: String, preset: BlendShapePreset, expression: VRM1.Expressions.Expression)] {
var clips: [(String, BlendShapePreset, VRM1.Expressions.Expression)] = [
("Happy", .happy, preset.happy),
("Angry", .angry, preset.angry),
("Sad", .sad, preset.sad),
("Relaxed", .relaxed, preset.relaxed),
("Surprised", .surprised, preset.surprised),
("Aa", .aa, preset.aa),
("Ih", .ih, preset.ih),
("Ou", .ou, preset.ou),
("Ee", .ee, preset.ee),
("Oh", .oh, preset.oh),
("Blink", .blink, preset.blink),
("BlinkLeft", .blinkLeft, preset.blinkLeft),
("BlinkRight", .blinkRight, preset.blinkRight),
("LookUp", .lookUp, preset.lookUp),
("LookDown", .lookDown, preset.lookDown),
("LookLeft", .lookLeft, preset.lookLeft),
("LookRight", .lookRight, preset.lookRight),
("Neutral", .neutral, preset.neutral)
]

guard let customMap = custom?.value as? [String: Any] else {
return clips
}

let decoder = DictionaryDecoder()
for name in customMap.keys.sorted() {
guard let raw = customMap[name] as? [String: Any],
let expression = try? decoder.decode(VRM1.Expressions.Expression.self, from: raw) else {
continue
}
clips.append((name, .unknown, expression))
}
return clips
}
}
Loading
Loading