Skip to content

Commit

Permalink
Add VRM physics (#58)
Browse files Browse the repository at this point in the history
* Add VRM physics

* Split VRM files
  • Loading branch information
magicien authored Dec 19, 2021
1 parent 76ce7ef commit 5d70ca2
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 7 deletions.
18 changes: 18 additions & 0 deletions GLTFSceneKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
12132870231A552900083524 /* GLTFExtrasTargetNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1213286C231A535600083524 /* GLTFExtrasTargetNames.swift */; };
46A8CD2726E6B240008819AB /* GLTFSceneKit+BundleFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A8CD2626E6B240008819AB /* GLTFSceneKit+BundleFinder.swift */; };
46A8CD2826E6B240008819AB /* GLTFSceneKit+BundleFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A8CD2626E6B240008819AB /* GLTFSceneKit+BundleFinder.swift */; };
491BC4FA276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4F9276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift */; };
491BC4FB276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4F9276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift */; };
491BC4FD276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4FC276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift */; };
491BC4FE276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4FC276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift */; };
491BC500276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4FF276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift */; };
491BC501276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491BC4FF276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift */; };
49B64F6A276A645F000380C7 /* GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader in Resources */ = {isa = PBXBuildFile; fileRef = 49B64F69276A645E000380C7 /* GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader */; };
49B64F6B276A645F000380C7 /* GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader in Resources */ = {isa = PBXBuildFile; fileRef = 49B64F69276A645E000380C7 /* GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader */; };
49DFA9BE26A45B290009965E /* GLTFSceneKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 49DFA9BD26A45A710009965E /* GLTFSceneKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -179,6 +185,9 @@
/* Begin PBXFileReference section */
1213286C231A535600083524 /* GLTFExtrasTargetNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLTFExtrasTargetNames.swift; sourceTree = "<group>"; };
46A8CD2626E6B240008819AB /* GLTFSceneKit+BundleFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GLTFSceneKit+BundleFinder.swift"; sourceTree = "<group>"; };
491BC4F9276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLTFVRM_VRMSpringBone.swift; sourceTree = "<group>"; };
491BC4FC276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLTFVRM_VRMSpringBoneLogic.swift; sourceTree = "<group>"; };
491BC4FF276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GLTFVRM_VRMTypes.swift; sourceTree = "<group>"; };
49B64F69276A645E000380C7 /* GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GLTFShaderModifierFragment_VRMUnlitTexture_Cutoff.shader; sourceTree = "<group>"; };
49D5C8AD2767081E0076FB22 /* GLTFShaderModifierFragment_VRMUnlitTexture.shader */ = {isa = PBXFileReference; lastKnownFileType = text; path = GLTFShaderModifierFragment_VRMUnlitTexture.shader; sourceTree = "<group>"; };
49D5C8AE2767081E0076FB22 /* GLTFShaderModifierSurface_VRMMToon.shader */ = {isa = PBXFileReference; lastKnownFileType = text; path = GLTFShaderModifierSurface_VRMMToon.shader; sourceTree = "<group>"; };
Expand Down Expand Up @@ -292,6 +301,9 @@
isa = PBXGroup;
children = (
49DFAA2F26A4FC9F0009965E /* GLTFVRM_VRM.swift */,
491BC4FF276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift */,
491BC4F9276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift */,
491BC4FC276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift */,
);
path = VRM;
sourceTree = "<group>";
Expand Down Expand Up @@ -712,6 +724,7 @@
DD3EB0841F524783009F32D2 /* GLTFCamera.swift in Sources */,
DD3EB0791F524783009F32D2 /* GLTFAccessor.swift in Sources */,
DD3EB0821F524783009F32D2 /* GLTFBuffer.swift in Sources */,
491BC501276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift in Sources */,
DD3EB08C1F524783009F32D2 /* GLTFGlTFProperty.swift in Sources */,
DD3EB09D1F524790009F32D2 /* GLTFSceneSource.swift in Sources */,
DD3EB08B1F524783009F32D2 /* GLTFGlTFid.swift in Sources */,
Expand All @@ -729,6 +742,7 @@
DD3EB09F1F524790009F32D2 /* GLTFFunctions.swift in Sources */,
DD3EB08F1F524783009F32D2 /* GLTFMaterialNormalTextureInfo.swift in Sources */,
DD3EB08A1F524783009F32D2 /* GLTFGlTFChildOfRootProperty.swift in Sources */,
491BC4FB276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift in Sources */,
DD3EB07F1F524783009F32D2 /* GLTFAnimationChannelTarget.swift in Sources */,
DD3EB09C1F524790009F32D2 /* GLTFUnarchiver.swift in Sources */,
DD3EB0861F524783009F32D2 /* GLTFCameraPerspective.swift in Sources */,
Expand All @@ -744,6 +758,7 @@
DD3EB0921F524783009F32D2 /* GLTFMesh.swift in Sources */,
DD3EB08D1F524783009F32D2 /* GLTFImage.swift in Sources */,
DD3EB0941F524783009F32D2 /* GLTFNode.swift in Sources */,
491BC4FE276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift in Sources */,
DD3EB07E1F524783009F32D2 /* GLTFAnimationChannel.swift in Sources */,
DD3EB0991F524783009F32D2 /* GLTFTextureInfo.swift in Sources */,
DD3EB0871F524783009F32D2 /* GLTFExtension.swift in Sources */,
Expand All @@ -763,6 +778,7 @@
DDE6FD6B1F466F3600CB11D6 /* GLTFCamera.swift in Sources */,
DDE6FD601F466F3600CB11D6 /* GLTFAccessor.swift in Sources */,
DDE6FD691F466F3600CB11D6 /* GLTFBuffer.swift in Sources */,
491BC500276F7F0E00E7A448 /* GLTFVRM_VRMTypes.swift in Sources */,
DDE6FD731F466F3600CB11D6 /* GLTFGlTFProperty.swift in Sources */,
DDE6FD721F466F3600CB11D6 /* GLTFGlTFid.swift in Sources */,
DDE6FD701F466F3600CB11D6 /* GLTFGlTF.swift in Sources */,
Expand All @@ -780,6 +796,7 @@
DDE6FD781F466F3600CB11D6 /* GLTFMaterialPbrMetallicRoughness.swift in Sources */,
DDE6FD761F466F3600CB11D6 /* GLTFMaterialNormalTextureInfo.swift in Sources */,
DDE6FD711F466F3600CB11D6 /* GLTFGlTFChildOfRootProperty.swift in Sources */,
491BC4FA276F0A7A00E7A448 /* GLTFVRM_VRMSpringBone.swift in Sources */,
DDE6FD661F466F3600CB11D6 /* GLTFAnimationChannelTarget.swift in Sources */,
DDE6FD6D1F466F3600CB11D6 /* GLTFCameraPerspective.swift in Sources */,
DDE6FD7A1F466F3600CB11D6 /* GLTFMeshPrimitive.swift in Sources */,
Expand All @@ -795,6 +812,7 @@
DDE6FD791F466F3600CB11D6 /* GLTFMesh.swift in Sources */,
DDE6FD741F466F3600CB11D6 /* GLTFImage.swift in Sources */,
DDE6FD7B1F466F3600CB11D6 /* GLTFNode.swift in Sources */,
491BC4FD276F7E5700E7A448 /* GLTFVRM_VRMSpringBoneLogic.swift in Sources */,
DDE6FD651F466F3600CB11D6 /* GLTFAnimationChannel.swift in Sources */,
DDE6FD801F466F3600CB11D6 /* GLTFTextureInfo.swift in Sources */,
DDE6FDDA1F46785100CB11D6 /* GLTFSceneSource.swift in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions Sample/iOS/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class GameViewController: UIViewController {
var gameView: SCNView? {
get { return self.view as? SCNView }
}
var scene: SCNScene?

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -41,11 +42,15 @@ class GameViewController: UIViewController {

// configure the view
self.gameView!.backgroundColor = UIColor.gray

self.gameView!.delegate = self
}

func setScene(_ scene: SCNScene) {
// set the scene to the view
self.gameView!.scene = scene
self.scene = scene

//to give nice reflections :)
scene.lightingEnvironment.contents = "art.scnassets/shinyRoom.jpg"
scene.lightingEnvironment.intensity = 2;
Expand Down Expand Up @@ -73,3 +78,9 @@ class GameViewController: UIViewController {
}

}

extension GameViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, didApplyAnimationsAtTime time: TimeInterval) {
self.scene?.rootNode.updateVRMSpringBones(time: time)
}
}
3 changes: 1 addition & 2 deletions Sample/macOS/GameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@

import SceneKit

class GameView: SCNView {
class GameView: SCNView {}

}
7 changes: 7 additions & 0 deletions Sample/macOS/GameViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class GameViewController: NSViewController {
self.gameView!.backgroundColor = NSColor.gray

self.gameView!.addObserver(self, forKeyPath: "pointOfView", options: [.new], context: nil)

self.gameView!.delegate = self
}

func setScene(_ scene: SCNScene) {
Expand Down Expand Up @@ -122,3 +124,8 @@ class GameViewController: NSViewController {
}
}

extension GameViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer, didApplyAnimationsAtTime time: TimeInterval) {
self.gameView.scene?.rootNode.updateVRMSpringBones(time: time)
}
}
2 changes: 2 additions & 0 deletions Sources/GLTFSceneKit/GLTFTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ let keyPathMap: [String: String] = [

#if os(macOS)
typealias Image = NSImage
typealias Color = NSColor
#elseif os(iOS) || os(tvOS) || os(watchOS)
typealias Image = UIImage
typealias Color = UIColor
#endif

public protocol GLTFCodable: Codable {
Expand Down
91 changes: 86 additions & 5 deletions Sources/GLTFSceneKit/schema/extensions/VRM/GLTFVRM_VRM.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
import Foundation
import SceneKit

public let GLTFVRM_VRMNodeKey = "GLTFVRM_NodeKey"
var physicsSceneSettings: [String: GLTFVRM_VRMPhysicsSettings] = [:]
var physicsUpdatedAt: [String: TimeInterval] = [:]

struct GLTFVRM_GLTFVRMExtension: GLTFCodable {
static let humanoidBonesKey = "VRMHumanoidBones"
static let blendShapesKey = "VRMBlendShapes"
static let metaKey = "VRMMeta"

struct GLTFVRM_VRM: Codable {
let exportVersion: String?
let meta: GLTFVRM_GLTFVRMMeta
Expand Down Expand Up @@ -169,7 +177,7 @@ struct GLTFVRM_GLTFVRMExtension: GLTFCodable {
humanoidBoneMap[humanBone.bone] = boneName
}
}
scene.rootNode.setValue(humanoidBoneMap, forKey: "VRMHumanoidBones")
scene.rootNode.setValue(humanoidBoneMap, forKey: GLTFVRM_GLTFVRMExtension.humanoidBonesKey)

// Load materialProperties
// TODO: Implement shaders
Expand Down Expand Up @@ -270,7 +278,9 @@ struct GLTFVRM_GLTFVRMExtension: GLTFCodable {
}
blendShapes[shapeName] = morpherWeights
}
scene.rootNode.setValue(blendShapes, forKey: "VRMBlendShapes")
scene.rootNode.setValue(blendShapes, forKey: GLTFVRM_GLTFVRMExtension.blendShapesKey)

self.setupPhysics(for: scene, unarchiver: unarchiver)
}

func setMetadata(_ meta: GLTFVRM_GLTFVRMMeta, to scene: SCNScene) {
Expand All @@ -289,24 +299,95 @@ struct GLTFVRM_GLTFVRMExtension: GLTFCodable {
"licenseName": meta.licenseName ?? "",
"otherLicenseUrl": meta.otherLicenseUrl ?? ""
]
scene.setValue(dict, forKey: "VRMMeta")
scene.setValue(dict, forKey: GLTFVRM_GLTFVRMExtension.metaKey)
}

func setupPhysics(for scene: SCNScene, unarchiver: GLTFUnarchiver) {
guard let data = self.data else { return }

var colliderGroups: [GLTFVRM_VRMSpringBoneColliderGroup] = []
var springBones: [GLTFVRM_VRMSpringBone] = []

data.secondaryAnimation.colliderGroups.forEach { colliderGroup in
let nodeNo = colliderGroup.node
guard let nodeName = unarchiver.nodes[nodeNo]?.name else { return }
guard let colliderNode = scene.rootNode.childNode(withName: nodeName, recursively: true) else { return }

let colliders: [GLTFVRM_VRMSphereCollider] = colliderGroup.colliders.map { collider in
let offset = simd_float3(collider.offset.x, collider.offset.y, collider.offset.z)
return GLTFVRM_VRMSphereCollider(offset: offset, radius: collider.radius)
}

let group = GLTFVRM_VRMSpringBoneColliderGroup(node: colliderNode, colliders: colliders)
colliderGroups.append(group)
}

data.secondaryAnimation.boneGroups.forEach { boneGroup in
var rootBones: [SCNNode] = []

boneGroup.bones.forEach { boneNo in
guard let boneName = unarchiver.nodes[boneNo]?.name else { return }
guard let bone = scene.rootNode.childNode(withName: boneName, recursively: true) else { return }

rootBones.append(bone)
}

let colliders = boneGroup.colliderGroups.map { colliderGroups[$0] }

let springBone = GLTFVRM_VRMSpringBone(
center: nil,
rootBones: rootBones,
comment: boneGroup.comment,
stiffnessForce: boneGroup.stiffiness,
gravityPower: boneGroup.gravityPower,
gravityDir: simd_float3(boneGroup.gravityDir.x, boneGroup.gravityDir.y, boneGroup.gravityDir.z),
dragForce: boneGroup.dragForce,
hitRadius: boneGroup.hitRadius,
colliderGroups: colliders
)
springBones.append(springBone)
}

let physics = GLTFVRM_VRMPhysicsSettings(colliderGroups: colliderGroups, springBones: springBones)
let nodeId = UUID().uuidString
scene.rootNode.setValue(nodeId, forKey: GLTFVRM_VRMNodeKey)
physicsSceneSettings[nodeId] = physics
}
}

extension SCNNode {
// TODO: Blending some shapes which have the same keyPath
public func setVRMBlendShape(name: String, weight: CGFloat) {
guard let shapes = self.value(forKey: "VRMBlendShapes") as? [String : [String : CGFloat]] else { return }
guard let shapes = self.value(forKey: GLTFVRM_GLTFVRMExtension.blendShapesKey) as? [String : [String : CGFloat]] else { return }

shapes[name]?.forEach { (keyPath, weightRatio) in
self.setValue(weight * weightRatio, forKeyPath: keyPath)
}
}

public func getVRMHumanoidBone(name: String) -> SCNNode? {
guard let boneMap = self.value(forKey: "VRMHumanoidBones") as? [String: String] else { return nil }
guard let boneMap = self.value(forKey: GLTFVRM_GLTFVRMExtension.humanoidBonesKey) as? [String: String] else { return nil }
guard let boneName = boneMap[name] else { return nil }

return self.childNode(withName: boneName, recursively: true)
}

public func updateVRMSpringBones(time: TimeInterval) {
self.enumerateHierarchy { node, _ in
guard let nodeId = node.value(forKey: GLTFVRM_VRMNodeKey) as? String else { return }
guard let settings = physicsSceneSettings[nodeId] else { return }

var deltaTime: TimeInterval
if let previousTime = physicsUpdatedAt[nodeId] {
deltaTime = time - previousTime
} else {
deltaTime = 0
}
physicsUpdatedAt[nodeId] = time

settings.springBones.forEach {
$0.update(deltaTime: deltaTime, colliders: settings.colliderGroups)
}
}
}
}
Loading

0 comments on commit 5d70ca2

Please sign in to comment.