Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor VRM import to use bone names; change some properties #1

Merged
merged 2 commits into from Oct 20, 2020
Merged
Changes from all commits
Commits
File filter
Filter file types
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.

Always

Just for now

@@ -251,6 +251,10 @@ func _get_skel_godot_node(gstate: GLTFState, nodes: Array, skeletons: Array, ske
if nodes[i].skeleton == skel_id:
return gstate.get_scene_node(i)
return null

class SkelBone:
var skel: Skeleton
var bone_name: String


# https://github.com/vrm-c/vrm-specification/blob/master/specification/0.0/schema/vrm.humanoid.bone.schema.json
@@ -277,12 +281,14 @@ func _create_meta(root_node: Node, animplayer: AnimationPlayer, vrm_extension: D
var gltfskel: GLTFSkeleton = skeletons[hipsNode.skeleton]
var skeleton: Skeleton = _get_skel_godot_node(gstate, nodes, skeletons, hipsNode.skeleton)
var skeletonPath: NodePath = root_node.get_path_to(skeleton)
root_node.set("vrm_skeleton", skeletonPath)

var animPath: NodePath = root_node.get_path_to(animplayer)
root_node.set("vrm_animplayer", animPath)

var firstperson = vrm_extension.get("firstPerson", null)
var eyeOffset: Vector3;
var mouthOffset: Vector3;

if firstperson:
# FIXME: Technically this is supposed to be offset relative to the "firstPersonBone"
# However, firstPersonBone defaults to Head...
@@ -293,44 +299,36 @@ func _create_meta(root_node: Node, animplayer: AnimationPlayer, vrm_extension: D
# Which implies that the Head bone is used, not the firstPersonBone.
var fpboneoffsetxyz = firstperson["firstPersonBoneOffset"] # example: 0,0.06,0
eyeOffset = Vector3(fpboneoffsetxyz["x"], fpboneoffsetxyz["y"], fpboneoffsetxyz["z"])
# Assuming this position for now.
# This data is not stored in any model metadata.
# As an alternative, we could get the centroid of vertices moved by viseme blend shapes.
# But for now, we'll assume this position:
mouthOffset = Vector3(fpboneoffsetxyz["x"], 0.0, fpboneoffsetxyz["z"])

var humanBoneDictionary: Dictionary = {}
for humanBoneName in human_bone_to_idx:
humanBoneDictionary[humanBoneName] = poolintarray_find(gltfskel.joints, human_bone_to_idx[humanBoneName])
humanBoneDictionary[humanBoneName] = skeleton.get_bone_name(poolintarray_find(gltfskel.joints, human_bone_to_idx[humanBoneName]))

var vrm_meta: Resource = load("res://addons/vrm/vrm_meta.gd").new()

vrm_meta.animplayer = animPath
vrm_meta.skeleton = skeletonPath

vrm_meta.exporterVersion = vrm_extension.get("exporterVersion", "")
vrm_meta.specVersion = vrm_extension.get("specVersion", "")

vrm_meta.resource_name = "CLICK TO SEE METADATA"
vrm_meta.exporter_version = vrm_extension.get("exporterVersion", "")
vrm_meta.spec_version = vrm_extension.get("specVersion", "")
var vrm_extension_meta = vrm_extension.get("meta")
if vrm_extension_meta:
vrm_meta.title = vrm_extension["meta"].get("title", "")
vrm_meta.version = vrm_extension["meta"].get("version", "")
vrm_meta.author = vrm_extension["meta"].get("author", "")
vrm_meta.contactInformation = vrm_extension["meta"].get("contactInformation", "")
vrm_meta.contact_information = vrm_extension["meta"].get("contactInformation", "")
vrm_meta.reference = vrm_extension["meta"].get("reference", "")
var tex: int = vrm_extension["meta"].get("texture", -1)
if tex >= 0:
var gltftex: GLTFTexture = gstate.get_textures()[tex]
vrm_meta.texture = gstate.get_images()[gltftex.src_image]
vrm_meta.allowedUserName = vrm_extension["meta"].get("allowedUserName", "")
vrm_meta.violentUsage = vrm_extension["meta"].get("violentUssageName", "")
vrm_meta.sexualUsage = vrm_extension["meta"].get("sexualUssageName", "")
vrm_meta.commercialUsage = vrm_extension["meta"].get("commercialUssageName", "")
vrm_meta.otherPermissionUrl = vrm_extension["meta"].get("otherPermissionUrl", "")
vrm_meta.licenseName = vrm_extension["meta"].get("licenseName", "")
vrm_meta.otherLicenseUrl = vrm_extension["meta"].get("otherLicenseUrl", "")
vrm_meta.allowed_user_name = vrm_extension["meta"].get("allowedUserName", "")
vrm_meta.violent_usage = vrm_extension["meta"].get("violentUssageName", "") # Ussage (sic.) in VRM spec
vrm_meta.sexual_usage = vrm_extension["meta"].get("sexualUssageName", "") # Ussage (sic.) in VRM spec
vrm_meta.commercial_usage = vrm_extension["meta"].get("commercialUssageName", "") # Ussage (sic.) in VRM spec
vrm_meta.other_permission_url = vrm_extension["meta"].get("otherPermissionUrl", "")
vrm_meta.license_name = vrm_extension["meta"].get("licenseName", "")
vrm_meta.other_license_url = vrm_extension["meta"].get("otherLicenseUrl", "")

vrm_meta.eye_offset = eyeOffset
vrm_meta.mouth_offset = mouthOffset
vrm_meta.humanoid_bone_mapping = humanBoneDictionary
return vrm_meta.duplicate(true)

@@ -534,6 +532,108 @@ func _create_animation_player(animplayer: AnimationPlayer, vrm_extension: Dictio

return animplayer


func _parse_secondary_node(secondary_node: Node, vrm_extension: Dictionary, gstate: GLTFState):
var nodes = gstate.get_nodes()
var skeletons = gstate.get_skeletons()

var vrm_secondary:GDScript = load("res://addons/vrm/vrm_secondary.gd")
var vrm_collidergroup:GDScript = load("res://addons/vrm/vrm_collidergroup.gd")
var vrm_springbone:GDScript = load("res://addons/vrm/vrm_springbone.gd")

# humanBoneDictionary[humanBoneName] = skeleton.get_bone_name(poolintarray_find(gltfskel.joints, human_bone_to_idx[humanBoneName]))

var collider_groups: Array = Array()
for cgroup in vrm_extension["secondaryAnimation"]["colliderGroups"]:
var gltfnode: GLTFNode = nodes[int(cgroup["node"])]
var collider_group = vrm_collidergroup.new()
collider_group.sphere_colliders = Array() # HACK HACK HACK
if gltfnode.skeleton == -1:
var found_node: Node = gstate.get_scene_node(int(cgroup["node"]))
collider_group.skeleton_or_node = secondary_node.get_path_to(found_node)
collider_group.bone = ""
collider_group.resource_name = found_node.name
else:
var gltfskel: GLTFSkeleton = skeletons[gltfnode.skeleton]
var skeleton: Skeleton = _get_skel_godot_node(gstate, nodes, skeletons,gltfnode.skeleton)
collider_group.skeleton_or_node = secondary_node.get_path_to(skeleton)
collider_group.bone = skeleton.get_bone_name(poolintarray_find(gltfskel.joints, int(cgroup["node"])))
collider_group.resource_name = collider_group.bone

for collider_info in cgroup["colliders"]:
var offset_obj = collider_info.get("offset", {"x": 0.0, "y": 0.0, "z": 0.0})
var local_pos: Vector3 = Vector3(offset_obj["x"], offset_obj["y"], offset_obj["z"])
var radius: float = collider_info.get("radius", 0.0)
collider_group.sphere_colliders.append(Plane(local_pos, radius))
collider_groups.append(collider_group)

var spring_bones: Array = Array()
for sbone in vrm_extension["secondaryAnimation"]["boneGroups"]:
if sbone.get("bones", []).size() == 0:
continue
var first_bone_node: int = sbone["bones"][0]
var gltfnode: GLTFNode = nodes[int(first_bone_node)]
var gltfskel: GLTFSkeleton = skeletons[gltfnode.skeleton]
var skeleton: Skeleton = _get_skel_godot_node(gstate, nodes, skeletons,gltfnode.skeleton)

var spring_bone = vrm_springbone.new()
spring_bone.skeleton = secondary_node.get_path_to(skeleton)
spring_bone.comment = sbone.get("comment", "")
spring_bone.stiffness_force = float(sbone.get("stiffiness", 1.0))
spring_bone.gravity_power = float(sbone.get("gravityPower", 0.0))
var gravity_dir = sbone.get("gravity_dir", {"x": 0.0, "y": -1.0, "z": 0.0})
spring_bone.gravity_dir = Vector3(gravity_dir["x"], gravity_dir["y"], gravity_dir["z"])
spring_bone.drag_force = float(sbone.get("drag_force", 0.4))
spring_bone.hit_radius = float(sbone.get("hitRadius", 0.02))

if spring_bone.comment != "":
spring_bone.resource_name = spring_bone.comment.split("\n")[0]
else:
var tmpname: String = ""
if sbone["bones"].size() > 1:
tmpname += " + " + str(sbone["bones"].size() - 1) + " roots"
tmpname = skeleton.get_bone_name(poolintarray_find(gltfskel.joints, int(first_bone_node))) + tmpname
spring_bone.resource_name = tmpname

spring_bone.collider_groups = Array() # HACK HACK HACK
for cgroup_idx in sbone.get("colliderGroups", []):
spring_bone.collider_groups.append(collider_groups[int(cgroup_idx)])

spring_bone.root_bones = Array() # HACK HACK HACK
for bone_node in sbone["bones"]:
var bone_idx: int = poolintarray_find(gltfskel.joints, int(bone_node))
if bone_idx == -1:
# Note that we make an assumption that a given SpringBone object is
# only part of a single Skeleton*. This error might print if a given
# SpringBone references bones from multiple Skeleton's.
printerr("Failed to find node " + str(bone_node) + " in skel " + str(skeleton))
else:
spring_bone.root_bones.append(skeleton.get_bone_name(bone_idx))

# Center commonly points outside of the glTF Skeleton, such as the root node.
spring_bone.center_node = secondary_node.get_path_to(secondary_node)
spring_bone.center_bone = ""
var center_node_idx = sbone.get("center", -1)
if center_node_idx != -1:
var center_gltfnode: GLTFNode = nodes[int(center_node_idx)]
var bone_idx: int = poolintarray_find(gltfskel.joints, int(center_node_idx))
if center_gltfnode.skeleton == gltfnode.skeleton and bone_idx != -1:
spring_bone.center_bone = skeleton.get_bone_name(bone_idx)
spring_bone.center_node = NodePath()
else:
spring_bone.center_bone = ""
spring_bone.center_node = secondary_node.get_path_to(gstate.get_scene_node(int(center_node_idx)))
if spring_bone.center_node == NodePath():
printerr("Failed to find center scene node " + str(center_node_idx))
spring_bone.center_node = secondary_node.get_path_to(secondary_node) # Fallback

spring_bones.append(spring_bone)

secondary_node.set_script(vrm_secondary)
secondary_node.set("spring_bones", spring_bones)
secondary_node.set("collider_groups", collider_groups)


func _import_scene(path: String, flags: int, bake_fps: int):
var f = File.new()
if f.open(path, File.READ) != OK:
@@ -584,11 +684,28 @@ func _import_scene(path: String, flags: int, bake_fps: int):
animplayer.owner = root_node
_create_animation_player(animplayer, vrm_extension, gstate, human_bone_to_idx)

var vrm_meta: Resource = _create_meta(root_node, animplayer, vrm_extension, gstate, human_bone_to_idx)
var vrm_top_level:GDScript = load("res://addons/vrm/vrm_toplevel.gd")

root_node.set_script(vrm_top_level)

var vrm_meta: Resource = _create_meta(root_node, animplayer, vrm_extension, gstate, human_bone_to_idx)
root_node.set("vrm_meta", vrm_meta)
root_node.set("vrm_secondary", NodePath())

if (vrm_extension.has("secondaryAnimation") and \
(vrm_extension["secondaryAnimation"].get("colliderGroups", []).size() > 0 or \
vrm_extension["secondaryAnimation"].get("boneGroups", []).size() > 0)):

var secondary_node: Node = root_node.get_node("secondary")
if secondary_node == null:
secondary_node = Spatial.new()
root_node.add_child(secondary_node)
secondary_node.set_owner(root_node)

var secondary_path: NodePath = root_node.get_path_to(secondary_node)
root_node.set("vrm_secondary", secondary_path)

_parse_secondary_node(secondary_node, vrm_extension, gstate)


if (!ResourceLoader.exists(path + ".res")):
ResourceSaver.save(path + ".res", gstate)
@@ -0,0 +1,17 @@
extends Resource

# Bone name references are only valid within the given Skeleton.
# If the node was not a skeleton, bone is "" and contains a path to the node.
export var skeleton_or_node: NodePath

# The bone within the skeleton with the collider, or "" if not a bone.
export var bone: String

# Note that Plane is commonly used in Godot in place of a Vector4.
# The "normal" property of Plane holds a Vector3 of data.
# There is a comment saying it "must be normalized".
# However, this is not enforced and regularly violated in the core engine itself.

# Plane.normal = The local coordinate from the node of the collider group in *left-handed* Y-up coordinate.
# Plane.d = The radius of the collider.
export (Array, Plane) var sphere_colliders: Array # DO NOT INITIALIZE HERE
@@ -1,37 +1,65 @@
extends Resource
# Declare member variables here. Examples:

# VRM extension is for 3d humanoid avatars (and models) in VR applications.
# Meta schema:

# Title of VRM model
export var title: String

# Version of VRM model
export var version: String

# Author of VRM model
export var author: String
export var contactInformation: String

# Contact Information of VRM model author
export var contact_information: String

# Reference of VRM model
export var reference: String

# Thumbnail of VRM model
export var texture: Texture
export(String,"","OnlyAuthor","ExplicitlyLicensedPerson","Everyone") var allowedUserName: String
export(String,"","Disallow","Allow") var violentUsage: String
export(String,"","Disallow","Allow") var sexualUsage: String
export(String,"","Disallow","Allow") var commercialUsage: String
export var otherPermissionUrl: String
export(String,"","Redistribution_Prohibited","CC0","CC_BY","CC_BY_NC","CC_BY_SA","CC_BY_NC_SA","CC_BY_ND","CC_BY_NC_ND","Other") var licenseName: String
export var otherLicenseUrl: String

export var skeleton: NodePath
export var animplayer: NodePath
# A person who can perform with this avatar
export(String,"","OnlyAuthor","ExplicitlyLicensedPerson","Everyone") var allowed_user_name: String

export var humanoid_bone_mapping: Dictionary # VRM boneName -> bone idx (within skeleton)
# Permission to perform violent acts with this avatar
export(String,"","Disallow","Allow") var violent_usage: String

export var eye_offset: Vector3
export var mouth_offset: Vector3 # Inferred
# Permission to perform sexual acts with this avatar
export(String,"","Disallow","Allow") var sexual_usage: String

# Toplevel schema:
export var exporterVersion: String
export var specVersion: String
# For commercial use
export(String,"","Disallow","Allow") var commercial_usage: String

# If there are any conditions not mentioned above, put the URL link of the license document here.
export var other_permission_url: String

# License type
export(String,"","Redistribution_Prohibited","CC0","CC_BY","CC_BY_NC","CC_BY_SA","CC_BY_NC_SA","CC_BY_ND","CC_BY_NC_ND","Other") var license_name: String

# If "Other" is selected, put the URL link of the license document here.
export var other_license_url: String


# Human bone name -> Reference node index
# NOTE: We are currently discarding all Unity-specific data.
# We may need to store it somewhere in case we wish to re-export.
export var humanoid_bone_mapping: Dictionary # VRM boneName -> bone name (within skeleton)

# firstPersonBoneOffset:
# The target position of the VR headset in first-person view.
# It is assumed that an offset from the head bone to the VR headset is added.
export var eye_offset: Vector3
# NOTE: Mouth offset is not stored in any model metadata.
# As an alternative, we could get the centroid of vertices moved by viseme blend shapes.
# But for now, users should assume same as eyeOffset with y=0 (relative to head)

# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.

# Toplevel schema, belongs in vrm_meta:
# Version of exporter that vrm created. UniVRM-0.46
export var exporter_version: String

# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
# Version of VRM specification. 0.0
export var spec_version: String
@@ -0,0 +1,17 @@
extends Resource


export var spring_bones: Array
export var collider_groups: Array


# Called when the node enters the scene tree for the first time.
func _ready():
for bg in spring_bones:
bg._ready()


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
for bg in spring_bones:
bg._process(delta)
ProTip! Use n and p to navigate between commits in a pull request.