From 220b20495163045c5f16872816acec4fb50e98f1 Mon Sep 17 00:00:00 2001 From: "tattn (Tatsuya Tanaka)" Date: Tue, 3 Feb 2026 23:05:08 +0900 Subject: [PATCH 1/2] feat: Introduce VRMKitRuntime for common implementation --- Package.swift | 8 +++- Sources/VRMKitRuntime/BlendShapeTypes.swift | 37 ++++++++++++++++++ .../GLTF+Runtime.swift} | 2 +- Sources/VRMKitRuntime/SIMD+Runtime.swift | 37 ++++++++++++++++++ Sources/VRMKitRuntime/UnityTransform.swift | 18 +++++++++ .../VRMRealityKit/CustomType/BlendShape.swift | 37 +----------------- .../VRMRealityKit/CustomType/VRMEntity.swift | 2 +- .../CustomType/VRMEntitySpringBone.swift | 1 + .../Extensions/Entity+UnityTransform.swift | 19 +-------- Sources/VRMRealityKit/Extensions/Simd+.swift | 36 ----------------- Sources/VRMRealityKit/RuntimeExports.swift | 1 + Sources/VRMRealityKit/VRMEntityLoader.swift | 1 + .../VRMSceneKit/CustomType/BlendShape.swift | 39 +------------------ Sources/VRMSceneKit/CustomType/VRMNode.swift | 1 + .../CustomType/VRMSpringBone.swift | 1 + Sources/VRMSceneKit/Extensions/GLTF+.swift | 11 ------ .../Extensions/SCNNode+UnityTransform.swift | 21 +--------- Sources/VRMSceneKit/Extensions/simd+.swift | 22 ----------- .../GLTF2SCN/SCNMaterialProperty+GLTF.swift | 1 + Sources/VRMSceneKit/RuntimeExports.swift | 1 + 20 files changed, 111 insertions(+), 185 deletions(-) create mode 100644 Sources/VRMKitRuntime/BlendShapeTypes.swift rename Sources/{VRMRealityKit/Extensions/GLTF+.swift => VRMKitRuntime/GLTF+Runtime.swift} (86%) create mode 100644 Sources/VRMKitRuntime/SIMD+Runtime.swift create mode 100644 Sources/VRMKitRuntime/UnityTransform.swift create mode 100644 Sources/VRMRealityKit/RuntimeExports.swift delete mode 100644 Sources/VRMSceneKit/Extensions/GLTF+.swift create mode 100644 Sources/VRMSceneKit/RuntimeExports.swift diff --git a/Package.swift b/Package.swift index 6f1de91..9f85092 100644 --- a/Package.swift +++ b/Package.swift @@ -12,12 +12,16 @@ let package = Package( targets: [ .target(name: "VRMKit"), .target( - name: "VRMSceneKit", + name: "VRMKitRuntime", dependencies: ["VRMKit"] ), + .target( + name: "VRMSceneKit", + dependencies: ["VRMKit", "VRMKitRuntime"] + ), .target( name: "VRMRealityKit", - dependencies: ["VRMKit"] + dependencies: ["VRMKit", "VRMKitRuntime"] ), .testTarget( diff --git a/Sources/VRMKitRuntime/BlendShapeTypes.swift b/Sources/VRMKitRuntime/BlendShapeTypes.swift new file mode 100644 index 0000000..6de628c --- /dev/null +++ b/Sources/VRMKitRuntime/BlendShapeTypes.swift @@ -0,0 +1,37 @@ +public enum BlendShapeKey: Hashable { + case preset(BlendShapePreset) + case custom(String) + + public var isPreset: Bool { + switch self { + case .preset: return true + case .custom: return false + } + } +} + +/// VRM 0.x Blend Shape Preset +public enum BlendShapePreset: String { + case unknown + case neutral + case a + case i + case u + case e + case o + case blink + case joy + case angry + case sorrow + case fun + case lookUp = "lookup" + case lookDown = "lookdown" + case lookLeft = "lookleft" + case lookRight = "lookright" + case blinkL = "blink_l" + case blinkR = "blink_r" + + package init(name: String) { + self = BlendShapePreset(rawValue: name.lowercased()) ?? .unknown + } +} diff --git a/Sources/VRMRealityKit/Extensions/GLTF+.swift b/Sources/VRMKitRuntime/GLTF+Runtime.swift similarity index 86% rename from Sources/VRMRealityKit/Extensions/GLTF+.swift rename to Sources/VRMKitRuntime/GLTF+Runtime.swift index b2e8903..8072370 100644 --- a/Sources/VRMRealityKit/Extensions/GLTF+.swift +++ b/Sources/VRMKitRuntime/GLTF+Runtime.swift @@ -1,6 +1,6 @@ import VRMKit -protocol GLTFTextureInfoProtocol { +public protocol GLTFTextureInfoProtocol { var index: Int { get } var texCoord: Int { get } } diff --git a/Sources/VRMKitRuntime/SIMD+Runtime.swift b/Sources/VRMKitRuntime/SIMD+Runtime.swift new file mode 100644 index 0000000..9dbd90f --- /dev/null +++ b/Sources/VRMKitRuntime/SIMD+Runtime.swift @@ -0,0 +1,37 @@ +import simd + +public extension SIMD3 where Scalar == Float { + var normalized: SIMD3 { + simd_normalize(self) + } + + var length: Scalar { + simd_length(self) + } + + var length_squared: Scalar { + simd_length_squared(self) + } + + mutating func normalize() { + self = normalized + } +} + +public extension simd_quatf { + static func * (_ left: simd_quatf, _ right: SIMD3) -> SIMD3 { + simd_act(left, right) + } +} + +public let quat_identity_float = simd_quatf(matrix_identity_float4x4) + +public func cross(_ left: SIMD3, _ right: SIMD3) -> SIMD3 { + simd_cross(left, right) +} + +public func normal(_ v0: SIMD3, _ v1: SIMD3, _ v2: SIMD3) -> SIMD3 { + let e1 = v1 - v0 + let e2 = v2 - v0 + return simd_normalize(simd_cross(e1, e2)) +} diff --git a/Sources/VRMKitRuntime/UnityTransform.swift b/Sources/VRMKitRuntime/UnityTransform.swift new file mode 100644 index 0000000..da5c08a --- /dev/null +++ b/Sources/VRMKitRuntime/UnityTransform.swift @@ -0,0 +1,18 @@ +public protocol UnityTransformCompatible { + associatedtype CompatibleType + var utx: CompatibleType { get } +} + +public final class UnityTransform { + package let base: Base + + public init(_ base: Base) { + self.base = base + } +} + +public extension UnityTransformCompatible { + var utx: UnityTransform { + UnityTransform(self) + } +} diff --git a/Sources/VRMRealityKit/CustomType/BlendShape.swift b/Sources/VRMRealityKit/CustomType/BlendShape.swift index dd0ca8e..f75f529 100644 --- a/Sources/VRMRealityKit/CustomType/BlendShape.swift +++ b/Sources/VRMRealityKit/CustomType/BlendShape.swift @@ -1,5 +1,6 @@ #if canImport(RealityKit) import RealityKit +import VRMKitRuntime struct BlendShapeClip { let name: String @@ -23,40 +24,4 @@ struct MaterialValueBinding { let targetValue: SIMD4 let baseValue: SIMD4 } - -public enum BlendShapeKey: Hashable { - case preset(BlendShapePreset) - case custom(String) - var isPreset: Bool { - switch self { - case .preset: return true - case .custom: return false - } - } -} - -public enum BlendShapePreset: String { - case unknown - case neutral - case a - case i - case u - case e - case o - case blink - case joy - case angry - case sorrow - case fun - case lookUp = "lookup" - case lookDown = "lookdown" - case lookLeft = "lookleft" - case lookRight = "lookright" - case blinkL = "blink_l" - case blinkR = "blink_r" - - init(name: String) { - self = BlendShapePreset(rawValue: name.lowercased()) ?? .unknown - } -} #endif diff --git a/Sources/VRMRealityKit/CustomType/VRMEntity.swift b/Sources/VRMRealityKit/CustomType/VRMEntity.swift index 2331f7b..6cac717 100644 --- a/Sources/VRMRealityKit/CustomType/VRMEntity.swift +++ b/Sources/VRMRealityKit/CustomType/VRMEntity.swift @@ -3,6 +3,7 @@ import CoreGraphics import Foundation import RealityKit import VRMKit +import VRMKitRuntime @available(iOS 18.0, macOS 15.0, visionOS 2.0, *) struct BlendShapeNormalTangentComponent: Component { @@ -360,4 +361,3 @@ public final class VRMEntity { } } #endif - diff --git a/Sources/VRMRealityKit/CustomType/VRMEntitySpringBone.swift b/Sources/VRMRealityKit/CustomType/VRMEntitySpringBone.swift index b3b0fab..844a3d0 100644 --- a/Sources/VRMRealityKit/CustomType/VRMEntitySpringBone.swift +++ b/Sources/VRMRealityKit/CustomType/VRMEntitySpringBone.swift @@ -1,6 +1,7 @@ #if canImport(RealityKit) import RealityKit import VRMKit +import VRMKitRuntime import Foundation @available(iOS 18.0, macOS 15.0, visionOS 2.0, *) diff --git a/Sources/VRMRealityKit/Extensions/Entity+UnityTransform.swift b/Sources/VRMRealityKit/Extensions/Entity+UnityTransform.swift index 10df5c4..5514a99 100644 --- a/Sources/VRMRealityKit/Extensions/Entity+UnityTransform.swift +++ b/Sources/VRMRealityKit/Extensions/Entity+UnityTransform.swift @@ -1,24 +1,7 @@ #if canImport(RealityKit) import RealityKit import simd - -public protocol UnityTransformCompatible { - associatedtype CompatibleType - var utx: CompatibleType { get } -} - -public final class UnityTransform { - private let base: Base - public init(_ base: Base) { - self.base = base - } -} - -public extension UnityTransformCompatible { - var utx: UnityTransform { - UnityTransform(self) - } -} +import VRMKitRuntime extension Entity: UnityTransformCompatible {} diff --git a/Sources/VRMRealityKit/Extensions/Simd+.swift b/Sources/VRMRealityKit/Extensions/Simd+.swift index e9c53cb..ca3e9ab 100644 --- a/Sources/VRMRealityKit/Extensions/Simd+.swift +++ b/Sources/VRMRealityKit/Extensions/Simd+.swift @@ -1,23 +1,5 @@ import simd -extension SIMD3 where Scalar == Float { - var normalized: SIMD3 { - simd_normalize(self) - } - - var length: Scalar { - simd_length(self) - } - - var length_squared: Scalar { - simd_length_squared(self) - } - - mutating func normalize() { - self = normalized - } -} - extension simd_float4x4 { var translation: SIMD3 { SIMD3(columns.3.x, columns.3.y, columns.3.z) @@ -31,21 +13,3 @@ extension simd_float4x4 { return SIMD3(result.x / result.w, result.y / result.w, result.z / result.w) } } - -extension simd_quatf { - static func * (_ left: simd_quatf, _ right: SIMD3) -> SIMD3 { - simd_act(left, right) - } -} - -let quat_identity_float = simd_quatf(matrix_identity_float4x4) - -func cross(_ left: SIMD3, _ right: SIMD3) -> SIMD3 { - simd_cross(left, right) -} - -func normal(_ v0: SIMD3, _ v1: SIMD3, _ v2: SIMD3) -> SIMD3 { - let e1 = v1 - v0 - let e2 = v2 - v0 - return simd_normalize(simd_cross(e1, e2)) -} diff --git a/Sources/VRMRealityKit/RuntimeExports.swift b/Sources/VRMRealityKit/RuntimeExports.swift new file mode 100644 index 0000000..36bfd2b --- /dev/null +++ b/Sources/VRMRealityKit/RuntimeExports.swift @@ -0,0 +1 @@ +@_exported import VRMKitRuntime diff --git a/Sources/VRMRealityKit/VRMEntityLoader.swift b/Sources/VRMRealityKit/VRMEntityLoader.swift index c69ba2f..d49b73a 100644 --- a/Sources/VRMRealityKit/VRMEntityLoader.swift +++ b/Sources/VRMRealityKit/VRMEntityLoader.swift @@ -3,6 +3,7 @@ import CoreGraphics import RealityKit import Metal import VRMKit +import VRMKitRuntime @available(iOS 18.0, macOS 15.0, visionOS 2.0, *) @MainActor diff --git a/Sources/VRMSceneKit/CustomType/BlendShape.swift b/Sources/VRMSceneKit/CustomType/BlendShape.swift index 2212eb4..7b3dc93 100644 --- a/Sources/VRMSceneKit/CustomType/BlendShape.swift +++ b/Sources/VRMSceneKit/CustomType/BlendShape.swift @@ -1,4 +1,5 @@ import SceneKit +import VRMKitRuntime @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") struct BlendShapeClip { @@ -26,41 +27,3 @@ struct MaterialValueBinding { let targetValue: SCNVector4 let baseValue: SCNVector4 } - -@available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") -public enum BlendShapeKey: Hashable { - case preset(BlendShapePreset) - case custom(String) - var isPreset: Bool { - switch self { - case .preset: return true - case .custom: return false - } - } -} - -@available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") -public enum BlendShapePreset: String { - case unknown - case neutral - case a - case i - case u - case e - case o - case blink - case joy - case angry - case sorrow - case fun - case lookUp = "lookup" - case lookDown = "lookdown" - case lookLeft = "lookleft" - case lookRight = "lookright" - case blinkL = "blink_l" - case blinkR = "blink_r" - - init(name: String) { - self = BlendShapePreset(rawValue: name.lowercased()) ?? .unknown - } -} diff --git a/Sources/VRMSceneKit/CustomType/VRMNode.swift b/Sources/VRMSceneKit/CustomType/VRMNode.swift index 1b782e5..4cc769e 100644 --- a/Sources/VRMSceneKit/CustomType/VRMNode.swift +++ b/Sources/VRMSceneKit/CustomType/VRMNode.swift @@ -1,5 +1,6 @@ import SceneKit import VRMKit +import VRMKitRuntime @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") open class VRMNode: SCNNode { diff --git a/Sources/VRMSceneKit/CustomType/VRMSpringBone.swift b/Sources/VRMSceneKit/CustomType/VRMSpringBone.swift index b212a33..62a6dfe 100644 --- a/Sources/VRMSceneKit/CustomType/VRMSpringBone.swift +++ b/Sources/VRMSceneKit/CustomType/VRMSpringBone.swift @@ -1,6 +1,7 @@ import SceneKit import GameKit import VRMKit +import VRMKitRuntime @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") final class VRMSpringBone { diff --git a/Sources/VRMSceneKit/Extensions/GLTF+.swift b/Sources/VRMSceneKit/Extensions/GLTF+.swift deleted file mode 100644 index dff62a2..0000000 --- a/Sources/VRMSceneKit/Extensions/GLTF+.swift +++ /dev/null @@ -1,11 +0,0 @@ -import VRMKit -import Foundation - -protocol GLTFTextureInfoProtocol { - var index: Int { get } - var texCoord: Int { get } -} - -extension GLTF.TextureInfo: GLTFTextureInfoProtocol {} -extension GLTF.Material.NormalTextureInfo: GLTFTextureInfoProtocol {} -extension GLTF.Material.OcclusionTextureInfo: GLTFTextureInfoProtocol {} diff --git a/Sources/VRMSceneKit/Extensions/SCNNode+UnityTransform.swift b/Sources/VRMSceneKit/Extensions/SCNNode+UnityTransform.swift index 3f46a6b..e0c4261 100644 --- a/Sources/VRMSceneKit/Extensions/SCNNode+UnityTransform.swift +++ b/Sources/VRMSceneKit/Extensions/SCNNode+UnityTransform.swift @@ -1,26 +1,7 @@ import SceneKit - -public protocol UnityTransformCompatible { - associatedtype CompatibleType - - var utx: CompatibleType { get } -} - -@available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") -public final class UnityTransform { - private let base: Base - public init(_ base: Base) { - self.base = base - } -} +import VRMKitRuntime @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") -public extension UnityTransformCompatible { - var utx: UnityTransform { - return UnityTransform(self) - } -} - extension SCNNode: UnityTransformCompatible {} @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") diff --git a/Sources/VRMSceneKit/Extensions/simd+.swift b/Sources/VRMSceneKit/Extensions/simd+.swift index f28068a..3c117c3 100644 --- a/Sources/VRMSceneKit/Extensions/simd+.swift +++ b/Sources/VRMSceneKit/Extensions/simd+.swift @@ -1,20 +1,6 @@ import simd import SceneKit -extension SIMD3 where Scalar == Float { - var normalized: SIMD3 { - simd_normalize(self) - } - - var length: Scalar { - simd_length(self) - } - - var length_squared: Scalar { - simd_length_squared(self) - } -} - extension simd_float4x4 { func multiplyPoint(_ v: SIMD3) -> SIMD3 { let scn = SCNMatrix4(self) @@ -40,11 +26,3 @@ extension simd_float4x4 { return vector3 } } - -extension simd_quatf { - static func * (_ left: simd_quatf, _ right: SIMD3) -> SIMD3 { - simd_act(left, right) - } -} - -nonisolated(unsafe) var quat_identity_float = simd_quatf(matrix_identity_float4x4) diff --git a/Sources/VRMSceneKit/GLTF2SCN/SCNMaterialProperty+GLTF.swift b/Sources/VRMSceneKit/GLTF2SCN/SCNMaterialProperty+GLTF.swift index 0739ae6..302e8db 100644 --- a/Sources/VRMSceneKit/GLTF2SCN/SCNMaterialProperty+GLTF.swift +++ b/Sources/VRMSceneKit/GLTF2SCN/SCNMaterialProperty+GLTF.swift @@ -1,4 +1,5 @@ import VRMKit +import VRMKitRuntime import SceneKit @available(*, deprecated, message: "Deprecated. Use VRMRealityKit instead.") diff --git a/Sources/VRMSceneKit/RuntimeExports.swift b/Sources/VRMSceneKit/RuntimeExports.swift new file mode 100644 index 0000000..36bfd2b --- /dev/null +++ b/Sources/VRMSceneKit/RuntimeExports.swift @@ -0,0 +1 @@ +@_exported import VRMKitRuntime From a83279ede0a2356ea75103e6be8b08bb638fdf4a Mon Sep 17 00:00:00 2001 From: "tattn (Tatsuya Tanaka)" Date: Tue, 3 Feb 2026 23:41:30 +0900 Subject: [PATCH 2/2] Clean code --- .../Example/RealityKitViewController.swift | 25 +++---------------- Sources/VRMKitRuntime/GLTF+Runtime.swift | 2 +- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/Example/Example/RealityKitViewController.swift b/Example/Example/RealityKitViewController.swift index e831bc9..739b8a4 100644 --- a/Example/Example/RealityKitViewController.swift +++ b/Example/Example/RealityKitViewController.swift @@ -15,7 +15,7 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg private var orbitPitch: Float = -0.1 private var orbitDistance: Float = 2 private var orbitTarget = SIMD3(0, 0.8, 0) - private var currentExpression: RKExpression = .neutral + private var currentExpression: Expression = .neutral override func viewDidLoad() { super.viewDidLoad() @@ -52,7 +52,7 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg segmentedControl.translatesAutoresizingMaskIntoConstraints = false view.addSubview(segmentedControl) - let expressionItems = RKExpression.allCases.map { $0.displayName } + let expressionItems = Expression.allCases.map { $0.displayName } let expressionSegmentedControl = UISegmentedControl(items: expressionItems) expressionSegmentedControl.selectedSegmentIndex = 0 expressionSegmentedControl.addTarget(self, action: #selector(expressionSegmentChanged(_:)), for: .valueChanged) @@ -73,7 +73,7 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg } @objc private func expressionSegmentChanged(_ sender: UISegmentedControl) { - let expression = RKExpression.allCases[sender.selectedSegmentIndex] + let expression = Expression.allCases[sender.selectedSegmentIndex] loadedEntity?.setBlendShape(value: 0.0, for: .preset(currentExpression.preset)) currentExpression = expression loadedEntity?.setBlendShape(value: 1.0, for: .preset(currentExpression.preset)) @@ -249,22 +249,3 @@ final class RealityKitViewController: UIViewController, UIGestureRecognizerDeleg return true } } - -@available(iOS 18.0, *) -private enum RKExpression: String, CaseIterable { - case neutral, joy, angry, sorrow, fun - - var preset: BlendShapePreset { - switch self { - case .neutral: return .neutral - case .joy: return .joy - case .angry: return .angry - case .sorrow: return .sorrow - case .fun: return .fun - } - } - - var displayName: String { - return rawValue.capitalized - } -} diff --git a/Sources/VRMKitRuntime/GLTF+Runtime.swift b/Sources/VRMKitRuntime/GLTF+Runtime.swift index 8072370..fc65eb0 100644 --- a/Sources/VRMKitRuntime/GLTF+Runtime.swift +++ b/Sources/VRMKitRuntime/GLTF+Runtime.swift @@ -1,6 +1,6 @@ import VRMKit -public protocol GLTFTextureInfoProtocol { +package protocol GLTFTextureInfoProtocol { var index: Int { get } var texCoord: Int { get } }