Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
067C5C522AAD6D8B00F8FBB3 /* VRMSceneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 067C5C512AAD6D8B00F8FBB3 /* VRMSceneKit */; };
06E1164E2F277C6D00D74CA4 /* VRMKit in Frameworks */ = {isa = PBXBuildFile; productRef = 06E1164D2F277C6D00D74CA4 /* VRMKit */; };
06E116502F277C6D00D74CA4 /* VRMRealityKit in Frameworks */ = {isa = PBXBuildFile; productRef = 06E1164F2F277C6D00D74CA4 /* VRMRealityKit */; };
06E116552F333D8700D74CA4 /* VRMSceneKit in Frameworks */ = {isa = PBXBuildFile; productRef = 06E116562F333D8700D74CA4 /* VRMSceneKit */; };
06E116522F277D1800D74CA4 /* AliciaSolid.vrm in Resources */ = {isa = PBXBuildFile; fileRef = 06E116512F277D1700D74CA4 /* AliciaSolid.vrm */; };
06E116542F277ED600D74CA4 /* AliciaSolid.vrm in Resources */ = {isa = PBXBuildFile; fileRef = 06E116512F277D1700D74CA4 /* AliciaSolid.vrm */; };
06F0BD792AAD81A40089488C /* WatchExample Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 06F0BD6C2AAD81A30089488C /* WatchExample Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -106,6 +107,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
06E116552F333D8700D74CA4 /* VRMSceneKit in Frameworks */,
06E116502F277C6D00D74CA4 /* VRMRealityKit in Frameworks */,
06E1164E2F277C6D00D74CA4 /* VRMKit in Frameworks */,
);
Expand Down Expand Up @@ -212,6 +214,7 @@
packageProductDependencies = (
06E1164D2F277C6D00D74CA4 /* VRMKit */,
06E1164F2F277C6D00D74CA4 /* VRMRealityKit */,
06E116562F333D8700D74CA4 /* VRMSceneKit */,
);
productName = MacExample;
productReference = 06E116422F277AEA00D74CA4 /* MacExample.app */;
Expand Down Expand Up @@ -866,6 +869,10 @@
isa = XCSwiftPackageProductDependency;
productName = VRMRealityKit;
};
06E116562F333D8700D74CA4 /* VRMSceneKit */ = {
isa = XCSwiftPackageProductDependency;
productName = VRMSceneKit;
};
06F0BD7E2AAD82120089488C /* VRMKit */ = {
isa = XCSwiftPackageProductDependency;
package = A1B2C3D4E5F60718293A4B61 /* XCLocalSwiftPackageReference ".." */;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "06E116412F277AEA00D74CA4"
BuildableName = "MacExample.app"
BlueprintName = "MacExample"
ReferencedContainer = "container:Example.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "06E116412F277AEA00D74CA4"
BuildableName = "MacExample.app"
BlueprintName = "MacExample"
ReferencedContainer = "container:Example.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "06E116412F277AEA00D74CA4"
BuildableName = "MacExample.app"
BlueprintName = "MacExample"
ReferencedContainer = "container:Example.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
97 changes: 97 additions & 0 deletions Example/Example/ExampleModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import CoreGraphics
import Foundation
internal import VRMKit
internal import VRMSceneKit

#if canImport(RealityKit)
internal import VRMRealityKit
#endif

enum VRMExampleModel: String, CaseIterable, Identifiable {
case alicia = "AliciaSolid.vrm"
case vrm1 = "VRM1_Constraint_Twist_Sample.vrm"

var id: String { rawValue }

var displayName: String {
switch self {
case .alicia: return "Alicia"
case .vrm1: return "VRM 1.0"
}
}

var initialRotation: Float {
switch self {
case .alicia: return 0
case .vrm1: return .pi
}
}
}

enum ExampleExpression: String, CaseIterable {
case neutral
case joy
case angry
case sorrow
case fun

var blendShapePreset: BlendShapePreset {
switch self {
case .neutral: return .neutral
case .joy: return .joy
case .angry: return .angry
case .sorrow: return .sorrow
case .fun: return .fun
}
}

var expressionPreset: ExpressionPreset {
switch self {
case .neutral: return .neutral
case .joy: return .happy
case .angry: return .angry
case .sorrow: return .sad
case .fun: return .relaxed
}
}

func displayName(for model: VRMExampleModel) -> String {
switch model {
case .alicia:
return rawValue.capitalized
case .vrm1:
switch self {
case .neutral: return "Neutral"
case .joy: return "Happy"
case .angry: return "Angry"
case .sorrow: return "Sad"
case .fun: return "Relaxed"
}
}
}
}

extension VRMNode {
func setExampleExpression(_ expression: ExampleExpression, value: CGFloat) {
switch vrm {
case .v0:
setBlendShape(value: value, for: .preset(expression.blendShapePreset))
case .v1:
setExpression(value: value, for: .preset(expression.expressionPreset))
}
}
}

#if canImport(RealityKit)
@available(iOS 18.0, *)
extension VRMEntity {
func setExampleExpression(_ expression: ExampleExpression, value: CGFloat) {
switch vrm {
case .v0:
setBlendShape(value: value, for: .preset(expression.blendShapePreset))
case .v1:
setExpression(value: value, for: .preset(expression.expressionPreset))
}
}
}
#endif
30 changes: 24 additions & 6 deletions Example/Example/RealityKitViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg
private var loadedEntity: VRMEntity?
private var cameraAnchor: AnchorEntity?
private var cameraEntity: PerspectiveCamera?
private var expressionSegmentedControl: UISegmentedControl?
private var orbitYaw: Float = 0
private var orbitPitch: Float = -0.1
private var orbitDistance: Float = 2
private var orbitTarget = SIMD3<Float>(0, 0.8, 0)
private var currentExpression: Expression = .neutral
private var currentModel: VRMExampleModel = .alicia
private var currentExpression: ExampleExpression = .neutral

override func viewDidLoad() {
super.viewDidLoad()
Expand Down Expand Up @@ -52,12 +54,13 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(segmentedControl)

let expressionItems = Expression.allCases.map { $0.displayName }
let expressionItems = ExampleExpression.allCases.map { $0.displayName(for: currentModel) }
let expressionSegmentedControl = UISegmentedControl(items: expressionItems)
expressionSegmentedControl.selectedSegmentIndex = 0
expressionSegmentedControl.addTarget(self, action: #selector(expressionSegmentChanged(_:)), for: .valueChanged)
expressionSegmentedControl.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(expressionSegmentedControl)
self.expressionSegmentedControl = expressionSegmentedControl

NSLayoutConstraint.activate([
segmentedControl.centerXAnchor.constraint(equalTo: view.centerXAnchor),
Expand All @@ -73,15 +76,18 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg
}

@objc private func expressionSegmentChanged(_ sender: UISegmentedControl) {
let expression = Expression.allCases[sender.selectedSegmentIndex]
loadedEntity?.setBlendShape(value: 0.0, for: .preset(currentExpression.preset))
let expression = ExampleExpression.allCases[sender.selectedSegmentIndex]
loadedEntity?.setExampleExpression(currentExpression, value: 0.0)
currentExpression = expression
loadedEntity?.setBlendShape(value: 1.0, for: .preset(currentExpression.preset))
loadedEntity?.setExampleExpression(currentExpression, value: 1.0)
}

private func loadVRM(model: VRMExampleModel) {
guard let arView = arView else { return }

currentModel = model
updateExpressionLabels()

if let loadedEntity = loadedEntity {
loadedEntity.entity.removeFromParent()
self.loadedEntity = nil
Expand Down Expand Up @@ -122,7 +128,7 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg
if let rightArm {
rightArm.transform.rotation = rightArm.transform.rotation * armRotation
}
vrmEntity.setBlendShape(value: 1.0, for: .preset(currentExpression.preset))
vrmEntity.setExampleExpression(currentExpression, value: 1.0)

loadedEntity = vrmEntity

Expand Down Expand Up @@ -153,6 +159,18 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg
}
}

private func updateExpressionLabels() {
guard let expressionSegmentedControl else { return }
let selectedIndex = expressionSegmentedControl.selectedSegmentIndex
expressionSegmentedControl.removeAllSegments()
for (index, expression) in ExampleExpression.allCases.enumerated() {
expressionSegmentedControl.insertSegment(withTitle: expression.displayName(for: currentModel),
at: index,
animated: false)
}
expressionSegmentedControl.selectedSegmentIndex = selectedIndex >= 0 ? selectedIndex : 0
}

private func setUpCamera() {
guard let arView = arView else { return }
let cameraAnchor = AnchorEntity(world: .zero)
Expand Down
Loading
Loading