Skip to content

Commit

Permalink
Initial revision
Browse files Browse the repository at this point in the history
  • Loading branch information
Malcolmnixon committed Mar 28, 2024
1 parent 97aedfd commit fe21801
Show file tree
Hide file tree
Showing 28 changed files with 2,189 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
59 changes: 59 additions & 0 deletions .github/workflows/build-on-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Workflow to automatically create deliverables
name: Build on push

on:
[push, pull_request]

jobs:
build:
name: Assembling artifacts
runs-on: ubuntu-20.04

# Note, to satisfy the asset library we need to make sure our zip files have a root folder
# this is why we checkout into demo/godot_rpm_avatar
# and build plugin/godot_rpm_avatar
steps:
- name: Checkout
uses: actions/checkout@v2
with:
path: demo/godot_rpm_avatar
- name: Create Godot RPM Avatar plugin
run: |
mkdir plugin
mkdir plugin/godot_rpm_avatar
mkdir plugin/godot_rpm_avatar/addons
cp -r demo/godot_rpm_avatar/addons/godot_rpm_avatar plugin/godot_rpm_avatar/addons
cp demo/godot_rpm_avatar/LICENSE plugin/godot_rpm_avatar/addons/godot_rpm_avatar
cp demo/godot_rpm_avatar/CONTRIBUTORS.md plugin/godot_rpm_avatar/addons/godot_rpm_avatar
cp demo/godot_rpm_avatar/VERSIONS.md plugin/godot_rpm_avatar/addons/godot_rpm_avatar
rm -rf demo/godot_rpm_avatar/.git
rm -rf demo/godot_rpm_avatar/.github
- name: Create Godot RPM Avatar library artifact
uses: actions/upload-artifact@v2
with:
name: godot_rpm_avatar
path: |
plugin
- name: Create Godot RPM Avatar demo artifact
uses: actions/upload-artifact@v2
with:
name: godot_rpm_avatar_demo
path: |
demo
- name: Zip asset
run: |
cd plugin
zip -qq -r ../godot_rpm_avatar.zip godot_rpm_avatar
cd ../demo
zip -qq -r ../godot_rpm_avatar_demo.zip godot_rpm_avatar
cd ..
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
- name: Create and upload asset
uses: ncipollo/release-action@v1
with:
allowUpdates: true
artifacts: "godot_rpm_avatar.zip,godot_rpm_avatar_demo.zip"
omitNameDuringUpdate: true
omitBodyDuringUpdate: true
token: ${{ secrets.GITHUB_TOKEN }}
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
26 changes: 26 additions & 0 deletions .github/workflows/gdlint-on-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Workflow to automatically lint gdscript code
name: gdlint on push

on:
[push, pull_request]

jobs:
gdlint:
name: gdlint scripts
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
python -m pip install 'gdtoolkit==4.*'
- name: Lint Godot VMC Tracker
run: |
gdlint addons/godot_rpm_avatar
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
android/
11 changes: 11 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Contributors
============

The main author of this project is [Malcolm Nixon](https://github.com/Malcolmnixon) who manages the source repository found at:
https://github.com/Malcolmnixon/GodotReadyPlayerMeAvatar

Other people who have helped out by submitting fixes, enhancements, etc are:

- TODO

Want to be on this list? We would love your help.
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
# GodotReadyPlayerMeAvatar
Godot ReadyPlayerMe Avatar Reader
# Godot ReadyPlayerMe Avatar

![GitHub forks](https://img.shields.io/github/forks/Malcolmnixon/GodotReadyPlayerMeAvatar?style=plastic)
![GitHub Repo stars](https://img.shields.io/github/stars/Malcolmnixon/GodotReadyPlayerMeAvatar?style=plastic)
![GitHub contributors](https://img.shields.io/github/contributors/Malcolmnixon/GodotReadyPlayerMeAvatar?style=plastic)
![GitHub](https://img.shields.io/github/license/Malcolmnixon/GodotReadyPlayerMeAvatar?style=plastic)

This repository contains a ReadyPlayerMe avatar loader for Godot that can
load avatars at runtime from the internet or local files, and can configure
them to be driven through the XR tracker system.

## Versions

Official releases are tagged and can be found [here](https://github.com/Malcolmnixon/GodotXRAxisStudioTracker/releases).

The following branches are in active development:
| Branch | Description | Godot version |
|-----------|-------------------------------|------------------|
| master | Current development branch | Godot 4.3-dev6+ |

## Licensing

Code in this repository is licensed under the MIT license.

## About this repository

This repository was created by Malcolm Nixon

It is primarily maintained by:
- [Malcolm Nixon](https://github.com/Malcolmnixon/)

For further contributors please see `CONTRIBUTORS.md`
2 changes: 2 additions & 0 deletions VERSIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 1.0.0
- Initial Revision
7 changes: 7 additions & 0 deletions addons/godot_rpm_avatar/plugin.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[plugin]

name="Godot ReadyPlayerMe Avatar"
description="Godot ReadyPlayerMe Avatar plugin"
author="Malcolm Nixon and Contributors"
version="1.0.0"
script="plugin.gd"
9 changes: 9 additions & 0 deletions addons/godot_rpm_avatar/plugin.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@tool
extends EditorPlugin


func _enter_tree():
# Register our autoload downloader object
add_autoload_singleton(
"RpmLoader",
"res://addons/godot_rpm_avatar/rpm_loader.gd")
182 changes: 182 additions & 0 deletions addons/godot_rpm_avatar/rpm_body.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
class_name RpmBody


## ReadyPlayerMe Body Script
##
## This script converts ReadyPlayerMe avatars into Godot Humanoid format by
## renaming and rotating the bones.


# Mapping from RPM to Godot Humanoid bones
const _RPM_TO_HUMANOID = {
"Hips" : "Hips",
"Spine" : "Spine",
"Spine1" : "Chest",
"Spine2" : "UpperChest",
"Neck" : "Neck",
"Head" : "Head",
"LeftEye" : "LeftEye",
"RightEye" : "RightEye",
"LeftShoulder" : "LeftShoulder",
"LeftArm" : "LeftUpperArm",
"LeftForeArm" : "LeftLowerArm",
"LeftHand" : "LeftHand",
"LeftHandThumb1" : "LeftThumbMetacarpal",
"LeftHandThumb2" : "LeftThumbProximal",
"LeftHandThumb3" : "LeftThumbDistal",
"LeftHandIndex1" : "LeftIndexProximal",
"LeftHandIndex2" : "LeftIndexIntermediate",
"LeftHandIndex3" : "LeftIndexDistal",
"LeftHandMiddle1" : "LeftMiddleProximal",
"LeftHandMiddle2" : "LeftMiddleIntermediate",
"LeftHandMiddle3" : "LeftMiddleDistal",
"LeftHandRing1" : "LeftRingProximal",
"LeftHandRing2" : "LeftRingIntermediate",
"LeftHandRing3" : "LeftRingDistal",
"LeftHandPinky1" : "LeftLittleProximal",
"LeftHandPinky2" : "LeftLittleIntermediate",
"LeftHandPinky3" : "LeftLittleDistal",
"RightShoulder" : "RightShoulder",
"RightArm" : "RightUpperArm",
"RightForeArm" : "RightLowerArm",
"RightHand" : "RightHand",
"RightHandThumb1" : "RightThumbMetacarpal",
"RightHandThumb2" : "RightThumbProximal",
"RightHandThumb3" : "RightThumbDistal",
"RightHandIndex1" : "RightIndexProximal",
"RightHandIndex2" : "RightIndexIntermediate",
"RightHandIndex3" : "RightIndexDistal",
"RightHandMiddle1" : "RightMiddleProximal",
"RightHandMiddle2" : "RightMiddleIntermediate",
"RightHandMiddle3" : "RightMiddleDistal",
"RightHandRing1" : "RightRingProximal",
"RightHandRing2" : "RightRingIntermediate",
"RightHandRing3" : "RightRingDistal",
"RightHandPinky1" : "RightLittleProximal",
"RightHandPinky2" : "RightLittleIntermediate",
"RightHandPinky3" : "RightLittleDistal",
"LeftUpLeg" : "LeftUpperLeg",
"LeftLeg" : "LeftLowerLeg",
"LeftFoot" : "LeftFoot",
"LeftToeBase" : "LeftToes",
"RightUpLeg" : "RightUpperLeg",
"RightLeg" : "RightLowerLeg",
"RightFoot" : "RightFoot",
"RightToeBase" : "RightToes"
}


## Retarget a skeleton mesh to conform to the Godot Humanoid standard
static func retarget(src_skeleton : Skeleton3D) -> void:
# Rename the bones to Godot Humanoid
_rename_bones(src_skeleton)

# Save the original skeleton global rest
var original_global_rest : Array[Transform3D] = []
for i in src_skeleton.get_bone_count():
original_global_rest.append(src_skeleton.get_bone_global_rest(i))

# Rotate the bones
_rotate_bones(src_skeleton)

# Fix the skin to counteract the bone rotation
for mesh : MeshInstance3D in src_skeleton.find_children("*", "MeshInstance3D"):
var skin := mesh.skin
if not skin: continue
for i in skin.get_bind_count():
var bone_name := skin.get_bind_name(i)
var bone_idx := src_skeleton.find_bone(bone_name)
if bone_idx < 0: continue
var adjust_transform := \
src_skeleton.get_bone_global_rest(bone_idx).affine_inverse() * \
original_global_rest[bone_idx]
skin.set_bind_pose(i, adjust_transform * skin.get_bind_pose(i))

# Move skeleton to rest
_to_rest(src_skeleton)


# This method renames the bones in the skeleton to the Godot Humanoid standard
static func _rename_bones(src_skeleton : Skeleton3D) -> void:
# Rename bones from RPM to Godot Humanoid
for i in src_skeleton.get_bone_count():
var old_name := src_skeleton.get_bone_name(i)
var new_name := _RPM_TO_HUMANOID.get(old_name, "")
if new_name != "":
src_skeleton.set_bone_name(i, new_name)

# Rename skin binds to match the new bone names
for mesh : MeshInstance3D in src_skeleton.find_children("*", "MeshInstance3D"):
var skin := mesh.skin
if not skin: continue
for i in skin.get_bind_count():
var old_name := skin.get_bind_name(i)
var new_name := _RPM_TO_HUMANOID.get(old_name, "")
if new_name != "":
skin.set_bind_name(i, new_name)


# This method rotates the bones as defined in the Godot Humanoid standard
static func _rotate_bones(src_skeleton : Skeleton3D) -> void:
# Build the Godot Humanoid profile skeleton
var profile := SkeletonProfileHumanoid.new()
var prof_skeleton := Skeleton3D.new()
for i in profile.bone_size: # Create bones
prof_skeleton.add_bone(profile.get_bone_name(i))
prof_skeleton.set_bone_rest(i, profile.get_reference_pose(i))
for i in profile.bone_size: # Set bone parents
var parent := profile.find_bone(profile.get_bone_parent(i))
if parent >= 0:
prof_skeleton.set_bone_parent(i, parent)

# Save the diffs when rotating
var diffs : Array[Basis] = []
diffs.resize(src_skeleton.get_bone_count())
diffs.fill(Basis.IDENTITY)

# Overwrite the axes
var bones_to_process := src_skeleton.get_parentless_bones()
while bones_to_process.size(): # Walk bones from root to leaf
var src_idx := bones_to_process[0]
bones_to_process.remove_at(0)
bones_to_process.append_array(src_skeleton.get_bone_children(src_idx))

# Get the parent global rest
var src_pg := Basis.IDENTITY
var src_parent_idx := src_skeleton.get_bone_parent(src_idx)
if src_parent_idx >= 0:
src_pg = src_skeleton.get_bone_global_rest(src_parent_idx).basis

# Get the rotation as defined by the profile
var tgt_rot := Basis.IDENTITY
var prof_idx := profile.find_bone(src_skeleton.get_bone_name(src_idx))
if prof_idx >= 0:
tgt_rot = src_pg.inverse() * prof_skeleton.get_bone_global_rest(prof_idx).basis

# Save the differences for each bone
if src_parent_idx >= 0:
diffs[src_idx] = \
tgt_rot.inverse() * \
diffs[src_parent_idx] * \
src_skeleton.get_bone_rest(src_idx).basis
else:
diffs[src_idx] = tgt_rot.inverse() * src_skeleton.get_bone_rest(src_idx).basis

var diff := Basis.IDENTITY
if src_parent_idx >= 0:
diff = diffs[src_parent_idx]

src_skeleton.set_bone_rest(
src_idx,
Transform3D(
tgt_rot,
diff * src_skeleton.get_bone_rest(src_idx).origin))


# This method moves the skeleton pose to the rest pose
static func _to_rest(src_skeleton : Skeleton3D) -> void:
# Init skeleton pose to new rest
for i in src_skeleton.get_bone_count():
var fixed_rest := src_skeleton.get_bone_rest(i)
src_skeleton.set_bone_pose_position(i, fixed_rest.origin)
src_skeleton.set_bone_pose_rotation(i, fixed_rest.basis.get_rotation_quaternion())
Loading

0 comments on commit fe21801

Please sign in to comment.