diff --git a/modules/many_bone_ik/.github/ISSUE_TEMPLATE/bug_report.md b/modules/many_bone_ik/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000000..9b77ea713f98
--- /dev/null
+++ b/modules/many_bone_ik/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,40 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ""
+labels: ""
+assignees: ""
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+
+- OS: [e.g. iOS]
+- Browser [e.g. chrome, safari]
+- Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+
+- Device: [e.g. iPhone6]
+- OS: [e.g. iOS8.1]
+- Browser [e.g. stock browser, safari]
+- Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/modules/many_bone_ik/.github/ISSUE_TEMPLATE/feature_request.md b/modules/many_bone_ik/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 000000000000..2bc5d5f71186
--- /dev/null
+++ b/modules/many_bone_ik/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ""
+labels: ""
+assignees: ""
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/modules/many_bone_ik/.gitignore b/modules/many_bone_ik/.gitignore
new file mode 100644
index 000000000000..3946cc96e540
--- /dev/null
+++ b/modules/many_bone_ik/.gitignore
@@ -0,0 +1,380 @@
+# Godot .gitignore config
+#
+# Aims to encompass the most commonly found files that we don't want committed
+# to Git, such as compilation output, IDE specific files, etc.
+#
+# It doesn't cover *all* thirdparty IDE extensions under the sun so if you have
+# specific needs covered here, you can add them to:
+# .git/info/exclude
+#
+# Or contribute them to this file if they're common enough that a good number of
+# users would benefit from the shared rules.
+#
+# This file is organized by sections, with subsections ordered alphabetically.
+# - Build configuration
+# - Godot generated files
+# - General build output
+# - IDE and tool specific
+# - Visual Studio specific
+# - OS specific
+
+###########################
+### Build configuration ###
+###########################
+
+/custom.py
+misc/hooks/pre-commit-custom-*
+
+#############################
+### Godot generated files ###
+#############################
+
+# Buildsystem
+bin
+*.gen.*
+compile_commands.json
+platform/windows/godot_res.res
+
+# Ninja build files
+build.ninja
+.ninja
+run_ninja_env.bat
+
+# Generated by Godot binary
+.import/
+/gdextension_interface.h
+extension_api.json
+logs/
+
+# Generated by unit tests
+tests/data/*.translation
+tests/data/crypto/out*
+
+############################
+### General build output ###
+############################
+
+# C/C++ generated
+*.a
+*.ax
+*.d
+*.dll
+*.lib
+*.lo
+*.o
+*.os
+*.ox
+*.Plo
+*.so
+# Binutils tmp linker output of the form "stXXXXXX" where "X" is alphanumeric
+st[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9]
+
+# Python development
+.venv
+venv
+
+# Python generated
+__pycache__/
+*.pyc
+
+# Documentation
+doc/_build/
+
+# Android
+.gradle/
+local.properties
+*.iml
+.gradletasknamecache
+project.properties
+platform/android/java/*/.cxx/
+platform/android/java/*/build/
+platform/android/java/*/libs/
+
+# iOS
+*.dSYM
+
+# Web platform
+*.bc
+platform/web/node_modules/
+
+# Misc
+*.debug
+
+#############################
+### IDE and tool specific ###
+#############################
+
+# Automake
+.deps/*
+.dirstamp
+
+# ccls
+.ccls-cache/
+
+# clangd
+.clangd/
+.cache/
+
+# CLion
+cmake-build-debug
+
+# Code::Blocks
+*.cbp
+*.layout
+*.depend
+
+# CodeLite
+*.project
+*.workspace
+.codelite/
+
+# Cppcheck
+*.cppcheck
+cppcheck-cppcheck-build-dir/
+
+# Eclipse CDT
+.cproject
+.settings/
+*.pydevproject
+*.launch
+
+# Emacs
+\#*\#
+.\#*
+
+# GCOV code coverage
+*.gcda
+*.gcno
+
+# Geany
+*.geany
+.geanyprj
+
+# Gprof
+gmon.out
+
+# Jetbrains IDEs
+.idea/
+.fleet/
+
+# Kate
+*.kate-swp
+
+# Kdevelop
+*.kdev4
+
+# Mypy
+.mypy_cache
+
+# Qt Creator
+*.config
+*.creator
+*.creator.*
+*.files
+*.includes
+*.cflags
+*.cxxflags
+
+# SCons
+.sconf_temp
+.sconsign*.dblite
+.scons_env.json
+.scons_node_count
+
+# Sourcetrail
+*.srctrl*
+
+# Tags
+# https://github.com/github/gitignore/blob/master/Global/Tags.gitignore
+# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
+TAGS
+!TAGS/
+tags
+*.tags
+!tags/
+gtags.files
+GTAGS
+GRTAGS
+GPATH
+cscope.files
+cscope.out
+cscope.in.out
+cscope.po.out
+
+# Vim
+*.swo
+*.swp
+
+# Visual Studio Code
+.vscode/
+*.code-workspace
+.history/
+
+# Xcode
+xcuserdata/
+*.xcscmblueprint
+*.xccheckout
+*.xcodeproj/*
+
+##############################
+### Visual Studio specific ###
+##############################
+
+# https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+# Ignore Visual Studio temporary files, build results, and
+# files generated by popular Visual Studio add-ons.
+
+# Actual VS project files we don't use
+*.sln
+*.vcxproj*
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Do not ignore arch-specific folders anywhere under thirdparty libraries
+!thirdparty/**/x64/
+!thirdparty/**/x86/
+!thirdparty/**/arm/
+!thirdparty/**/arm64/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+
+# Others
+ClientBin/
+enc_temp_folder/
+~$*
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# Hint file for IntelliSense
+cpp.hint
+
+###################
+### OS specific ###
+###################
+
+# Linux
+*~
+.directory
+
+# macOS
+.DS_Store
+__MACOSX
+
+# Windows
+# https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
+[Tt]humbs.db
+[Tt]humbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+*.stackdump
+[Dd]esktop.ini
+$RECYCLE.BIN/
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+*.lnk
+*.generated.props
diff --git a/modules/many_bone_ik/.gitrepo b/modules/many_bone_ik/.gitrepo
new file mode 100644
index 000000000000..335e0efb84b6
--- /dev/null
+++ b/modules/many_bone_ik/.gitrepo
@@ -0,0 +1,12 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme
+;
+[subrepo]
+ remote = https://github.com/V-Sekai/many_bone_ik.git
+ branch = main
+ commit = ddc539e5153214d1f74a11d3316eb6679357e261
+ parent = a66d6293c8c00b28dcde529e105741bdf0d82ae3
+ method = merge
+ cmdver = 0.4.6
diff --git a/modules/many_bone_ik/LICENSE b/modules/many_bone_ik/LICENSE
new file mode 100644
index 000000000000..24a6b933f27a
--- /dev/null
+++ b/modules/many_bone_ik/LICENSE
@@ -0,0 +1,23 @@
+MIT License
+
+Copyright (c) 2014-2022 Eron Gjoni
+Copyright (c) 2019-2022 K. S. Ernest (iFire) Lee
+Copyright (c) 2021 Rafael Martinez Gordillo.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/modules/many_bone_ik/SCsub b/modules/many_bone_ik/SCsub
new file mode 100644
index 000000000000..715fb0101706
--- /dev/null
+++ b/modules/many_bone_ik/SCsub
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env_many_bone_ik = env.Clone()
+env_many_bone_ik.Prepend(CPPPATH=["#modules/many_bone_ik"])
+env_many_bone_ik.Prepend(CPPPATH=["#modules/many_bone_ik/src/math"])
+env_many_bone_ik.Prepend(CPPPATH=["#modules/many_bone_ik/src"])
+env_many_bone_ik.add_source_files(env.modules_sources, "constraints/*.cpp")
+env_many_bone_ik.add_source_files(env.modules_sources, "src/math/*.cpp")
+env_many_bone_ik.add_source_files(env.modules_sources, "src/*.cpp")
+env_many_bone_ik.add_source_files(env.modules_sources, "*.cpp")
+
+if env.editor_build:
+ env_many_bone_ik.add_source_files(env.modules_sources, "editor/*.cpp")
diff --git a/modules/many_bone_ik/config.py b/modules/many_bone_ik/config.py
new file mode 100644
index 000000000000..b27d55d5d10c
--- /dev/null
+++ b/modules/many_bone_ik/config.py
@@ -0,0 +1,24 @@
+def can_build(env, platform):
+ return not env["disable_3d"]
+
+
+def configure(env):
+ pass
+
+
+def get_doc_classes():
+ return [
+ "ManyBoneIK3D",
+ "IKBone3D",
+ "IKEffector3D",
+ "IKBoneSegment3D",
+ "IKEffectorTemplate3D",
+ "IKKusudama3D",
+ "IKRay3D",
+ "IKNode3D",
+ "IKLimitCone3D",
+ ]
+
+
+def get_doc_path():
+ return "doc_classes"
diff --git a/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB22CAE13E99E3E24B.png b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB22CAE13E99E3E24B.png
new file mode 100644
index 000000000000..8db02055081d
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB22CAE13E99E3E24B.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB360C51B9B2145043.png b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB360C51B9B2145043.png
new file mode 100644
index 000000000000..a565d772d250
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CB360C51B9B2145043.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CBF393B4C3206D97F3.png b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CBF393B4C3206D97F3.png
new file mode 100644
index 000000000000..8be46c6e6ebc
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/1000000000000139000000CBF393B4C3206D97F3.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/100000000000016C000002AAA2059A0E43B2B468.png b/modules/many_bone_ik/design_docs/Pictures/100000000000016C000002AAA2059A0E43B2B468.png
new file mode 100644
index 000000000000..9a8fa6af5bb3
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/100000000000016C000002AAA2059A0E43B2B468.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E1025A91C2576C186.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E1025A91C2576C186.png
new file mode 100644
index 000000000000..0daccbefc874
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E1025A91C2576C186.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E173E86C323891A74.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E173E86C323891A74.png
new file mode 100644
index 000000000000..e45691aee337
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E173E86C323891A74.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E6996B4A5DF706332.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E6996B4A5DF706332.png
new file mode 100644
index 000000000000..40a36acf88a8
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E6996B4A5DF706332.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E832AFC8FF38BF53F.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E832AFC8FF38BF53F.png
new file mode 100644
index 000000000000..fe5c87f9b02f
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E832AFC8FF38BF53F.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E845771BD15F39314.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E845771BD15F39314.png
new file mode 100644
index 000000000000..871a661f3aa8
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E845771BD15F39314.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E853FACED42A51700.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E853FACED42A51700.png
new file mode 100644
index 000000000000..e53044de435e
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E853FACED42A51700.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E9D87E7B297A94C9F.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E9D87E7B297A94C9F.png
new file mode 100644
index 000000000000..d18d3a42cc54
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018E9D87E7B297A94C9F.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EA6B7A87AEB9D9F40.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EA6B7A87AEB9D9F40.png
new file mode 100644
index 000000000000..f77f5b60d8d0
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EA6B7A87AEB9D9F40.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EBC567E962B9B4C87.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EBC567E962B9B4C87.png
new file mode 100644
index 000000000000..2919df40c0ce
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EBC567E962B9B4C87.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ECB3181C74F9EDC35.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ECB3181C74F9EDC35.png
new file mode 100644
index 000000000000..d30d4927b56c
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ECB3181C74F9EDC35.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ED76B18851DB9DC34.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ED76B18851DB9DC34.png
new file mode 100644
index 000000000000..9373407d7bb5
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018ED76B18851DB9DC34.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDE8A6B18141900B0.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDE8A6B18141900B0.png
new file mode 100644
index 000000000000..efb7b31856f7
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDE8A6B18141900B0.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDF01F2A95A1519D7.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDF01F2A95A1519D7.png
new file mode 100644
index 000000000000..c11b75c6c1be
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EDF01F2A95A1519D7.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EE63F4ECE1A82B606.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EE63F4ECE1A82B606.png
new file mode 100644
index 000000000000..196f99804799
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EE63F4ECE1A82B606.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EEE6FC1E017012EDA.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EEE6FC1E017012EDA.png
new file mode 100644
index 000000000000..66ab6e24eb43
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002070000018EEE6FC1E017012EDA.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002C90000027876C8370C9358FB94.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002C90000027876C8370C9358FB94.png
new file mode 100644
index 000000000000..50e9f91d321a
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002C90000027876C8370C9358FB94.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278AEBB2DF17488D353.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278AEBB2DF17488D353.png
new file mode 100644
index 000000000000..4618a5411f08
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278AEBB2DF17488D353.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278EEE925F5104E07BE.png b/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278EEE925F5104E07BE.png
new file mode 100644
index 000000000000..a2263757b5ba
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000002C900000278EEE925F5104E07BE.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000000000004000000040014FDC3377E0AD7E4.png b/modules/many_bone_ik/design_docs/Pictures/10000000000004000000040014FDC3377E0AD7E4.png
new file mode 100644
index 000000000000..9acbcd9dd982
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000000000004000000040014FDC3377E0AD7E4.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/1000000000000400000004007DA1871E5E883934.png b/modules/many_bone_ik/design_docs/Pictures/1000000000000400000004007DA1871E5E883934.png
new file mode 100644
index 000000000000..ca6ceecb5be6
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/1000000000000400000004007DA1871E5E883934.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/10000201000002020000018EF75E743EDC6A705E.png b/modules/many_bone_ik/design_docs/Pictures/10000201000002020000018EF75E743EDC6A705E.png
new file mode 100644
index 000000000000..a949b58c73f1
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/10000201000002020000018EF75E743EDC6A705E.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/1000020100000286000001D10AA6EC3093AC56D5.png b/modules/many_bone_ik/design_docs/Pictures/1000020100000286000001D10AA6EC3093AC56D5.png
new file mode 100644
index 000000000000..86258c569625
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/1000020100000286000001D10AA6EC3093AC56D5.png differ
diff --git a/modules/many_bone_ik/design_docs/Pictures/100002010000030D0000026B7F70BE2E85BEF4BE.png b/modules/many_bone_ik/design_docs/Pictures/100002010000030D0000026B7F70BE2E85BEF4BE.png
new file mode 100644
index 000000000000..f473a770fa7b
Binary files /dev/null and b/modules/many_bone_ik/design_docs/Pictures/100002010000030D0000026B7F70BE2E85BEF4BE.png differ
diff --git a/modules/many_bone_ik/design_docs/readme.md b/modules/many_bone_ik/design_docs/readme.md
new file mode 100644
index 000000000000..a3105edae229
--- /dev/null
+++ b/modules/many_bone_ik/design_docs/readme.md
@@ -0,0 +1,555 @@
+# EWBIK: A Highly General, Fast, Constrainable, and Stable Inverse Kinematics algorithm
+
+_Eron Gjoni_
+
+This document introduces Entirely Wahba's-problem Based Inverse
+Kinematics (EWBIK). EWBIK is fast and stable and remains so under
+arbitrary joint orientation constraints, multiple end-effectors,
+intermediary effectors, position and orientation targets of arbitrary
+target weight priority, and any mix of the aforementioned. This document
+additionally introduces Kusudama constraints, which provide a means for
+fast, continuous, and expressive joint orientation constraints. A full
+implementation of the EWB-IK algorithm and Kusudama constraint system is
+available on the \*Everything WIll Be IK\* Java library github page^[^1]^,
+a Processing demo implementation of the library is also available^[^2]^
+for quick visualization and use-case testing.
+
+## 0. Preamble and Authors:
+
+The Godot Engine many bone IK project is a port from a Java project by Eron Gjoni [Everything-Will-Be-IK](https://github.com/EGjoni/Everything-Will-Be-IK).
+
+The authors' GitHub usernames are indicated in parentheses.
+
+- Eron Gjoni (EGjoni)
+- K. S. Ernest (iFire) Lee (fire)
+- rafallus Rafael M. G. (rafallus)
+- lyuma (lyuma)
+
+## 1. Introduction and Motivation:
+
+The two most popular approaches to the Inverse Kinematics problem for
+interactive or real-time use cases are Cyclic Coordinate Descent^[^3]^
+(CCD) and Forward And Backward Reaching Inverse Kinematics (FABRIK).
+
+CCD offers highly stable and fast solutions that behave well under
+joint constraints. However, CCD can solve only for single
+target-effector pairs, and becomes highly unstable when negotiating
+between multiple effectors aiming for potentially mutually exclusive
+targets.
+
+FABRIK offers highly stable and fast solutions that naturally handle
+multiple effectors with potentially mutually exclusive targets.
+However, FABRIK is, in practice, extremely unstable when used with
+joint constraints.
+
+Foregoing joint constraints to ensure stable FABRIK solutions results
+in highly unnatural (often extremely painful) looking poses. While
+foregoing consideration of more than one target-effector pair per bone
+to ensure stable but well constrained CCD solutions results in
+incomplete poses where, even if two separate bone chains with two
+separate effectors could theoretically reach both of their targets
+while obeying all constraints, only one effector actually does.
+
+From this, it should be apparent that the strengths and weaknesses of
+these two approaches are approximate complements of one another, in
+effect leaving developers to pick their poison. Additionally, both
+approaches fail to meaningfully account for target orientations, which
+can result in extremely unnatural pose solutions where the
+end-effector must be rotated post-hoc to bear the brunt of orientation
+alignment.
+
+## 2. The Importance of Orientation Targets:
+
+Consider a humanoid (perhaps yourself, if you happen to be
+approximately humanoid in shape) sitting at a table, with the back of
+its hand flat against the table such that its fingers are pointing
+directly away from its torso. If the humanoid were instructed to
+rotate its hand around its middle knuckle, such that the back of its
+hand remained flat against the table, but its fingers now pointed
+toward its torso; the orientation of all of the bones in the humanoid,
+from its wrist up to its shoulder, and perhaps even part of its spine,
+would have to change drastically to allow for this.
+
+If we treat the humanoid's pelvis as one effector and the chair as
+that effector's target, and treat its knuckle bone as another
+effector, and the spot on the table to which the knuckle bone must
+remain affixed as the knuckle bone's target, we observe that even if
+the *positions *of the targets do not change at all, there can be
+massive differences in the poses an armature must adopt based solely
+on the *orientations *of its targets.
+
+This should illustrate the importance of treating target orientations
+as first class citizens throughout the entire IK procedure. If we
+solve only for positions and leave target orientations as an
+afterthought (as CCD and FABRIK implementations most often do) we are
+left to decide between "cheating" by violating joint constraints so an
+effector is still aligned with its target (often resulting in effector
+joints that look like they're painfully hyperextending), or else
+strictly obeying the joint constraints but failing to solve for an
+otherwise reachable target.
+
+## 3. The Basic Idea:
+
+EWBIK can be thought of as a "blockwise" generalization of Inverse
+Kinematics by CCD. The primary distinction is that, where CCD seeks to
+iteratively minimize the angular discrepancy between a bone-chain's
+end-effector and its corresponding target from the perspective of
+every bone in the chain, EWBIK instead seeks to minimize an _average_
+discrepancy between _all_ effector-target pairs for *every *bone in
+the chain.
+
+Broadly EWBIK starts from the outermost bones of an armature, and
+proceeds rootward as follows:
+
+1. Create working copies of the origin and basis vectors and origins of
+
+ all target and effector transforms relevant to a given bone, and
+ translate them along with the given bone transform's origin such
+ that the bone transform's origin is at (0,0,0).
+
+2. Find the rotation that minimizes the average of the discrepancy
+
+ between each effector-target pair, and apply that rotation to the
+ bone.
+
+3. Rectify the bone's orientation to reside back within an allowable
+
+ orientation as per any limits imposed by dampening parameters or
+ joint constraint on the bone if necessary, then proceed to the
+ next bone.
+
+4. Once the root bone has been reached, repeat the process starting
+ from the outermost bones until convergence or budget exhaustion.
+
+\*Figure 1.1 - 1.2: A simplified sketch of a step in the algorithm.
+Effector basis vectors are marked in orange, target basis vectors are
+marked in blue. Dashed magenta lines indicate the deltas between each
+effector basis vector/origin and its corresponding target basis vector
+/ origin. **\*⊕** _indicates the origin of the bone under consideration
+at this step in the algorithm_.
+
+![](./Pictures/1000000000000400000004007DA1871E5E883934.png){width="3.0984in"
+height="2.6563in"}![](./Pictures/10000000000004000000040014FDC3377E0AD7E4.png){width="3.1193in"
+height="2.6457in"}
+
+
+
+Figure 1.1 (left): \*Armature prior to rotation about **\*⊕** so as to
+_minimize average discrepancies between effector points and
+corresponding target points. _
+
+Figure 1.2 (right): *Armature *after rotation about **⊕** so as to
+minimize average *discrepancies *between all descendant effector points
+and their corresponding target points.
+
+
+
+
+
+Naively, we might expect difficulties in the EWBIK procedure to arise
+in step 2. However, step 2 amounts to a special case of _Wahba's
+Problem_: that of finding the orthogonal transformation that best
+aligns one set of vectors with a corresponding target set of vectors.
+This type of problem arises often in bioinformatics, astronomy,
+crystallography and computer vision, and can be solved extremely
+quickly by any number of existing algorithms. Of these, the Kabsch
+alignment algorithm and the Quaternion Characteristic Polynomial (QCP)
+algorithms are perhaps the most popular.
+
+EWBIK has been verified to work fine with either algorithm, though QCP
+is recommended for its stability, simplicity, framework agnosticism,
+lack of edge-cases^[^4]^, and speed.
+
+Efficient implementations of both the QCP and Kabsch alignment
+algorithms are widely available in a number of languages, and since
+they (and related algorithms) are roughly interchangeable for our
+purposes, their mathematical peculiarities will not be covered here,
+and the rest of this document will refer to whatever algorithm you
+choose for the purpose of minimizing the discrepancy between
+point-pairs as _The Minimizer_.
+
+## 4. Role of The Minimizer:
+
+Chief among EWBIK's strengths is that no distinction is made by the
+solver between position and orientation targets. Both orientation and
+position are encoded simply as point pairs for the Minimizer to solve
+over.
+
+This is achieved by representing each target as a set of up to 7
+points. One point representing the target origin, three points
+representing the basis vectors emanating from that origin, and three
+points representing the opposites of the basis vectors with respect to
+the target origin. Effectors are represented in precisely the same
+way. These 7 point-pairs are then fed to the minimizer, which attempts
+to find the orthogonal transformation (usually just rotation) that
+minimizes the average distance between all effector-target pairs.
+
+## 5. Multiple End and Intermediary-Effectors:
+
+\*\* \*\*Since the Minimizer blindly operates on point-pairs, generalizing
+to solve for multiple effectors is trivial. We simply feed the Minimizer
+all additional effector-target point-pairs for any other effectors we
+wish to optimize a bone's orientation for. If the Minimizer optimizes
+for the average euclidean distance between effector-target pairs, we can
+even weigh some targets more strongly than others by just scaling the
+effector-target pairs about the bone origin in proportion to the
+precedence we want to place on that target-effector pair. This works
+because rotation of any point closer to the origin results in a smaller
+change in euclidean distance than does rotation of any point further
+from the origin.^[^5]^
+
+Additionally, we can weigh a target\'s orientation more or less
+strongly than its position by scaling the basis vectors of the target
+and/or effector about their respective origins.
+
+## 6. Preprocessing:
+
+When using EWBIK to solve for a single effector-target pair, no
+preprocessing of the armature is required. However, if solving for
+multiple effector-target pairs, the armature must be segmented prior to
+solve time so as to ensure that ancestor bones are only solved for after
+all of their descendant bones have been solved for, otherwise an
+ancestor might end up minimizing for stale effector pairs as descendant
+lineages have yet to finish solving.
+
+Such a problem scenario is depicted in _Figure 2.1_, the appropriate
+segmentation of which is depicted in _Figure 2.2_. The rectangles
+indicate logical segments. The numbers and letters indicate processing
+order. With the only processing rule being that no bone of a greater
+letter may be processed before any bone of a lesser letter, and no bone
+of a greater number may be processed before any bone of a lesser number
+in the same segment.
+
+![Figure 2.1](./Pictures/1000020100000286000001D10AA6EC3093AC56D5.png)
+_Figure 2.1 (left): An example armature, with effectored bones indicated in orange._
+
+![Figure 2.2](./Pictures/100002010000030D0000026B7F70BE2E85BEF4BE.png)
+_Figure 2.2 (right): Segmented representation of the example armature in 2.1._
+
+## 7. Solving:
+
+Once the segments have been created, we create working copies of the
+basis vectors and origins representing all descendant target and
+effector transforms on the segment, as well as opposing basis vectors
+(copies of the original basis vectors flipped about their transform's
+origin). Below, we will call any copies representing target basis
+vectors and their opposites **_c_basisTargets_**, and any copies
+representing effector basis vectors and their opposites
+**_c_basisEffectors_**. We will call any copies representing origins
+**_c_originTargets_** and **_c_originEffectors_**.
+
+From there, the simplest and fastest version of the EWBIK procedure
+starts from the outermost segments and works inward to the root segment,
+doing as follows for each bone in each segment:
+
+1. Reset the working copies of the targets and effector to correspond
+
+ to their original values. Subtract the current bone's origin from
+ all _c_basisTarget_, *c_basisEffector, c_originTarget, *and*
+ c_originEffector * points.
+
+ a. Scale any *c_basisTargets *about their corresponding *c_originTargets *such that their distance from their corresponding _c_originTarget_ is no less than 1, and also no less than the magnitude of their corresponding _c_originTarget, _
+
+ b. Scale any *c_basisEffectors *about their corresponding _c_originEffectors_ such that their distance from their corresponding _c_originEffectors_ is no less than 1, and no less than the magnitude of their corresponding _c_originTargets._
+
+2. Use The Minimizer to compute the rotation that brings all
+
+ _c\_\*Effector_ points as close as possible to their corresponding
+ _c\_\*Target_ points.
+
+ a. Clamp this rotation by the desired dampening parameter.
+
+ b. Apply the clamped rotation to the bone.
+
+3. Check if the bone has violated any of its orientation constraints as
+
+ a result of this rotation. If it has, rotate the bone to reside
+ within a valid region of its orientation constraint.
+
+4. If the bone's parent is contained in the current segment, repeat
+ this process for the parent bone. Otherwise, traversal for this
+ segment is complete.
+
+Repeat the whole process until the armature has converged or the
+computation budget has been exceeded.
+
+## 8. Constraints:
+
+\*\* \*\*In theory, EWBIK should work well with any type of commonly used
+joint constraint (doing so requires no more than implementing step 3 in
+the introductory section). Unfortunately, in practice, most commonly
+used joint constraints come with their own set of tradeoffs. An ideal
+orientation constraint system would provide the following
+
+1. **Continuity**: No sharp concave corners for a bone to get "stuck" in.
+
+2. **Versatility**: It should be possible to specify any conceivable orientation region.
+
+3. **Expressiveness**: The desired shape of the allowable orientation region should be fully specifiable with as few parameters as possible.
+
+4. **Speed**: as few operations as possible should be required to determine if a Bone is within the valid orientation region, or to determine the smallest rotation that brings it back within a valid orientation (note that this follows naturally from the previous criterion).
+
+5. **Extensibility**: The constraints should be amenable to specification of any number of additional properties that may vary continuously throughout or beyond the allowable orientations region (hard vs soft boundaries, high vs low friction regions, etc).
+
+The simplest conceivable systems for orientation constraints are Euler
+angles, which offer speed, but not much else; and Reach cones, which
+offer continuity, speed, and extensibility, but lack expressiveness or
+versatility.
+
+More versatile constraint systems allow for per-vertex specification of
+a polygonal bounding region on the surface of a sphere. Much like reach
+cones, these operate on the principle that any point which resides
+outside of the polygonal region should be transformed so as to reside on
+the edge or vertex to which it is closest (see _figure 3.1 for a planar
+representation_).
+Unfortunately, the fewer edges the polygonal region is specified by, the greater the probability that it is closest to a vertex of the polygon than to an edge, which often results in the point getting "stuck" in corners (see _Figure 3.2 for a planar representation_).
+
+![Figure 3.1](./Pictures/10000000000002C900000278AEBB2DF17488D353.png)
+![Figure 3.2](./Pictures/10000000000002C90000027876C8370C9358FB94.png)
+
+_Figure 3.1 (left): A sampling of points outside of the bounding region, with dotted lines indicating the area on the bounding region to which the constraint would transform them._
+
+_Figure 3.2 (right): Colored areas indicate the edge to which any point within that area would be transformed so as to reside within the bounding polygon. Red regions indicate areas where all points would be transformed onto a single vertex._
+
+These discontinuous corners can cause problems for an IK solver because they create local minima that are very difficult for solvers to find a way out of. Worse still, if the solver does get out, it tends to do so very suddenly, leading to jarring and unnatural "pops" between solutions.
+
+A common workaround is to smooth these corners out using splines or bezier curves, (see _Figure 4_ for a planar representation). However, while this solves the discontinuity problem, it does so at a significant performance penalty, because the only way to check whether or not a point on a sphere lies within the bounding spline is by segmenting the spline into very tiny linear segments, which then each have to be checked individually.
+
+![Figure 4](./Pictures/10000000000002C900000278EEE925F5104E07BE.png)
+_Figure 4: A sampling of points outside of a continuous, spline-based bounding region, with dotted lines indicating the area on the bounding region to which the constraint would transform the point._
+
+Aside from the performance penalty, the spline scheme is also somewhat strange conceptually in that it attempts to overcome the consequences of relying on a polygonal specification by adding an approximation of curvature by an increase in the number of line segments, and then mapping that approximation onto a sphere -- a domain in which curvature is already the rule, and linearity is inherently unnatural.
+
+If we start from scratch, and develop our bounding scheme with the upfront understanding that it will be mapped onto a sphere, instead of using points and lines as the fundamental units of our bounding region, we should prefer instead to think in terms of circles. Under such a scheme, a bounding region similar to that defined by the seven parameters (vertices) of Figure 5.1 might be represented as that defined by the six parameters (three circle centers, and three radii) of Figure 5.2.
+
+![Figure 5.1](./Pictures/10000000000002070000018EA6B7A87AEB9D9F40.png)
+![Figure 5.2](./Pictures/10000000000002070000018EDE8A6B18141900B0.png)
+
+_Figure 5.1 (left): A polygonal bounding region, specified in terms of points and lines._
+
+_Figure 5.2 (right): An approximation of the polygonal bounding region in Figure 5.1, specified as a connected sequence of circles of varying radii._
+
+Note that because the bounding "lines" connecting any pair of circles are tangent to both circles, the entire boundary remains continuous. Of course, since we're mapping onto a sphere, these tangent "lines" are actually themselves circles of whatever radius is sufficient to contact both circle pairs (see Figure 6). Because there are an infinite number of circles which can contact two circles (both on the plane and on a sphere) we are also free to specify varying degrees of curvature to the regions bounding any two circles, as depicted in Figures 7.1 and 7.2.
+
+![Figure 6](./Pictures/100000000000016C000002AAA2059A0E43B2B468.png)
+![Figure 7.1](./Pictures/10000201000002020000018EF75E743EDC6A705E.png)
+![Figure 7.2](./Pictures/1000000000000139000000CB360C51B9B2145043.png)
+![Figure 7.3](./Pictures/1000000000000139000000CBF393B4C3206D97F3.png)
+![Figure 7.4](./Pictures/1000000000000139000000CB22CAE13E99E3E24B.png)
+
+_Figure 6: Spherical representation of bounding regions._
+
+_Figure 7.1 (middle): Two circles (dark outline) and a sampling of the circles which lie tangent to both._
+
+_Figure 7.2 (right): Result of choosing connecting circles of various radii._
+
+These optional curvatures give us similar flexibility to that of the
+spline approach, but need specify only one additional radius value per
+pair of sequence-circles they connect (as there can only be at most two
+tangent-circles satisfying a given radius). We'll look at the specifics
+of representing our bounding region on a sphere in the next section, but
+for now we'll limit ourselves to the plane so as to more easily
+illustrate the form of the algorithm for checking whether we are within
+the bounding region.
+
+We will presume our bounding region is made up of the three full circles
+(which we will refer to as "sequence-circles") connected by a dotted
+line depicted in _figure 8(a),_ and the six tangent-circles depicted in
+light gray outlines in _figure_ _8(a2)_.
+
+1. We check to see if the point is within the two sequence-circles depicted in blue and green in _figure 8(b)_.
+
+ a. If the point is within either sequence-circle, we terminate, as the point is within the allowable region.
+
+ b. Otherwise, we proceed to step 2.
+
+2. We check to see if the point is within either of the two triangles depicted in amethyst in figure 8(c), which are formed by the centers of our pair of sequence-circle with the centers of the adjacent pair of tangent-circles.
+
+ a. If the point is within either triangle, we proceed to step 3
+
+ b. Otherwise, we skip to step 4.
+
+3. We check to see if the point is within either of the adjacent tangent circles as depicted in _figure 8(d)_.
+
+ a. If it is within one of the tangent-circles, then we transform it away from the center of the tangent-circle within which it resides such that its distance from the tangent-circle's center is equal to the radius of that tangent-circle (_figure 8(d2)_). Then terminate, as we have finished moving the point to the boundary of the allowable region.
+
+ b. If it isn't within either circle, then proceed to step 4.
+
+4. Proceed to the next pair in the sequence (_figure 8(f)_), treating the blue sequence-circle from the previous steps as if it were the new green sequence-circle, and treating the next circle in the sequence as being the new blue sequence-circle. Repeat steps 1 through 4 (_figure 8(g)_) until the blue sequence-circle under consideration is the last one in the sequence, then proceed to step 6.
+
+5. If the point wasn't in any of the regions checked so far, then by process of elimination, it resides outside of any of the sequence circles, and the regions connecting the sequence circles, and anywhere which should be transformed to the regions connecting the sequence circles. So we just iterate through each sequence-circle individually, store the translation that would bring us (h) to its boundary, and apply whichever of the translations was smallest (i).
+
+| ![a](./Pictures/10000000000002070000018E853FACED42A51700.png) | ![a2](./Pictures/10000000000002070000018E1025A91C2576C186.png) |
+| :-----------------------------------------------------------: | :------------------------------------------------------------: |
+| (a) | (a2) |
+
+| ![b](./Pictures/10000000000002070000018E9D87E7B297A94C9F.png) | ![c](./Pictures/10000000000002070000018EE63F4ECE1A82B606.png) |
+| :-----------------------------------------------------------: | :-----------------------------------------------------------: |
+| (b) | (c) |
+
+| ![d](./Pictures/10000000000002070000018EDF01F2A95A1519D7.png) | ![d2](./Pictures/10000000000002070000018ED76B18851DB9DC34.png) |
+| :-----------------------------------------------------------: | :------------------------------------------------------------: |
+| (d) | (d2) |
+
+![f](./Pictures/10000000000002070000018EBC567E962B9B4C87.png)
+(f)
+
+(g)
+![g1](./Pictures/10000000000002070000018E832AFC8FF38BF53F.png)
+![g2](./Pictures/10000000000002070000018ECB3181C74F9EDC35.png)
+![g3](./Pictures/10000000000002070000018E173E86C323891A74.png)
+![g4](./Pictures/10000000000002070000018EEE6FC1E017012EDA.png)
+
+| ![h](./Pictures/10000000000002070000018E6996B4A5DF706332.png) | ![i](./Pictures/10000000000002070000018E845771BD15F39314.png) |
+| :-----------------------------------------------------------: | :-----------------------------------------------------------: |
+| (h) | (i) |
+
+_Figure 8 (black regions indicate areas which have been eliminated from
+further consideration by previous steps in the algorithm)_
+
+## 9. Kusudamas:
+
+The spherical representation of such a bounding region uses cones
+instead of circles. We replace the centers of the circles with vectors
+pointing away from the constraint's origin, and replace their radii
+with the half-angle of that cone's apex.
+
+This representation may be considered a generalization of the reach
+cone concept. We call it a "Kusudama", as it resembles the spherical
+japanese papercraft models made by sewing or glueing pyramidal units
+through a common vertex.
+
+We are free to choose the apex angle of our tangent cones (and
+therefore the curvature of the bounding region connecting our
+sequence-cones) however we wish, so long as the following properties
+are met.
+
+1. As the sum of the apex angles of our pair of sequence-cones
+
+ approaches zero, the apex angles of the tangent cones must
+ approach π.
+
+2. As the sum of the apex angles of our pair of sequence-cones
+ approaches π, the apex angles of the tangent cones must approach 0.
+
+A straightforward function for automatically determining a reasonable
+tangent cone radius in absence of any information aside from the radii
+of the two sequence cones being connected is
+
+$$\frac{\pi - {({\theta{a + \theta}b})}}{2}$$
+
+Where $$\theta a$$ and $$\theta b$$ are the apex angles of the two
+sequence-cones being connected.
+
+Once we've determined an appropriate radius for a given tangent-cone,
+all that's left is to find the direction in which it should point. To
+do this, take the vectors representing the axis of the sequence-cones
+being connected, and scale them to a magnitude of
+$$\cos\left(\frac{\theta_{s} + \theta_{t}}{2}\right)$$
+Where $\theta_{s}$ is the apex angle of the sequence-cone to which the
+vector corresponds, and $\theta_{t}$ is the apex angle of the
+tangent cone we're determining the direction of. The two planes which
+run through and are perpendicular to the tips of these scaled axis
+vectors will intersect on a line running through the unit sphere. The
+two points where this line intersects the unit sphere may be treated
+as vectors representing the directions of our tangent-cone axes.
+
+Our full procedure for checking collisions is much the same as in the
+planar case, with only minor modifications to account for the change in
+topology. It goes as follows:
+
+1. We check to see if the angle between the bone's direction and the
+
+ direction of the axis of each sequence-cone is less than the apex
+ angle of the sequence cones under consideration.
+
+ a. If it is, we terminate, as the bone orientation is within the allowable region.
+
+ b. Otherwise, we proceed to step 2.
+
+2. For each adjacent pair of sequence cones, we check to see if the
+
+ bone direction is within either of the tetrahedrons formed by the
+ constraint origin, the line connecting the vectors representing
+ the two sequence-cone axes to each other, and the lines connecting
+ each sequence-cone axis to each tangent cone axis.
+
+ a. If the bone direction is within either tetrahedron, we proceed to step 3
+
+ b. Otherwise, we skip to step 4.
+
+3. We check to see if the angle between the bone's direction and the direction of the axis of the tangent-cone coinciding with this tetrahedron is less than the apex half-angle of the tangent-cone under consideration.
+
+ a. If it is, then we find the rotation which would transform the bone direction away from the tangent-cone in which it resides such that the angle between the bone direction and the tangent-cone's direction is equal to the apex half-angle of that tangent-cone. We do not terminate or apply this rotation. If the angle of this rotation is less than any currently stored rotation, we replace that rotation with this rotation, otherwise, we ignore this rotation. We then proceed to step 4.
+
+ b. If it isn't, then proceed to step 4.
+
+4. We shift to the next pair of adjacent sequence cones and repeat steps 1 through 4 until one of the sequence cones under consideration is the last cone defined. Then proceed to step 5
+
+5. We iterate again through each sequence-cone individually, and for each sequence-cone find the rotation which would transform the bone such that its angle from that sequence-cone axis is less than half the apex-angle of that sequence cone. We update the currently stored smallest rotation whenever we find a smaller rotation. (In effect, preferring to rotate the bone to the nearest constraint boundary)
+
+6. We apply the currently stored smallest rotation, and terminate.
+
+Note that as presented above, Kusudamas are only constraining the
+direction (aka swing) of the bone. To constrain their axial orientation
+(aka twist), a swing-twist decomposition may be used (as is common for
+reach cone constraints). For best results, the constraint axis against
+which twist is determined should be pointing as far away as possible
+from the constraint\'s allowable region.
+
+**11. Robustness under enhancement:**
+
+In the canonical form presented above, Kusudumas empirically play quite
+well with the EWBIK procedure. However, because the Kusudama scheme is
+flexible enough to allow for "soft" or "springy" constraints, it is
+possible to create a constraint landscape in which the solver undulates
+around some optimum^[^6]^ . If it is absolutely critical that such
+undulation be avoided, we can do so by incurring only a minor
+performance penalty to check that the RMSD of our solution after
+rotating and constraining a bone is less than or equal to our RMSD
+before rotating and constraining the bone. If our RMSD is greater, we
+simply discard our rotation + constraint transformation that step, and
+allow the other bones in the chain to pick up the slack.
+
+
+
+[^1]: [_https://github.com/EGjoni/Everything-Will-Be-IK_](https://github.com/EGjoni/Everything-Will-Be-IK)
+[^2]: [_https://github.com/EGjoni/Everything-Will-Be-IK-Processing_](https://github.com/EGjoni/Everything-Will-Be-IK-Processing)
+[^3]:
+ There is some terminological ambiguity as to what constitutes
+ "Coordinate Descent." Here, as is the case with the original CCD IK
+ algorithm, Coordinate Descent is used to refer to problems in which
+ each coordinate (e.g, a single variable at some cross section of a
+ multivariable function) can be precisely minimized. Cyclic
+ Coordinate Descent guarantees that so long as the function being
+ minimized is continuous and convex, successive minimization along
+ any permutation of its coordinates will converge on the global
+ minimum of the function (so long as evaluation of any coordinate is
+ preferred by no more than a constant factor).
+
+ Procedures where multiple variables are minimized per iteration are
+ most often referred to as Blockwise Coordinate Descent.
+
+[^4]:
+ With single precision floating point, QCP may under some
+ degenerate conditions require renormalization of the resultant
+ quaternion. This renormalization does not affect solution quality,
+ but may be worth considering for anyone looking to hyper-optimize
+ performance, as it does require an additional square root operation.
+ In theory, double precision floating point should occasionally
+ require similar rectification, though for somewhat mysterious
+ reasons it never seems to in practice.
+
+[^5]:
+ This can occur incidentally, where one set of target-effector
+ point-pairs happens to be closer to the origin of a bone being
+ solved for than another set of target-effector point-pairs. For this
+ reason, the full EWBIK procedure scales the basis vectors of any
+ target and effector transforms by the distance between the target
+ transform's origin and the origin of the transform of the bone being
+ solved for.
+
+[^6]:
+ In theory, even the simple canonical form of kusudamas should be
+ subject to this. Though, in practice it's almost never an issue
+ until soft constraints enter the mix.
diff --git a/modules/many_bone_ik/doc_classes/IKBone3D.xml b/modules/many_bone_ik/doc_classes/IKBone3D.xml
new file mode 100644
index 000000000000..d7eb00fa46f1
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKBone3D.xml
@@ -0,0 +1,50 @@
+
+
+
+ A class representing a 3D bone in an Inverse Kinematics system.
+
+
+ The IKBone3D class represents a 3D bone in an Inverse Kinematics (IK) system. It provides methods to get and set constraints, pins, and check if the bone is pinned.
+
+
+
+
+
+
+
+ Returns the IKKusudama3D object representing the constraint of the bone.
+
+
+
+
+
+ Returns the IKNode3D object representing the orientation transform of the constraint.
+
+
+
+
+
+ Returns the IKNode3D object representing the twist transform of the constraint.
+
+
+
+
+
+ Returns the IKEffector3D object representing the pin of the bone.
+
+
+
+
+
+ Returns true if the bone is pinned, false otherwise.
+
+
+
+
+
+
+ Sets the IKEffector3D object representing the pin of the bone.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKBoneSegment3D.xml b/modules/many_bone_ik/doc_classes/IKBoneSegment3D.xml
new file mode 100644
index 000000000000..0773e79ed95e
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKBoneSegment3D.xml
@@ -0,0 +1,26 @@
+
+
+
+ A class representing a 3D bone segment in an Inverse Kinematics system.
+
+
+ The IKBoneSegment3D class represents a 3D bone segment in an Inverse Kinematics (IK) system. It provides methods to get the IK bone associated with the segment and check if the segment is pinned.
+
+
+
+
+
+
+
+
+ Returns the IKBone3D object associated with the given bone index.
+
+
+
+
+
+ Returns true if the bone segment is pinned, false otherwise.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKEffector3D.xml b/modules/many_bone_ik/doc_classes/IKEffector3D.xml
new file mode 100644
index 000000000000..58ce01e189d0
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKEffector3D.xml
@@ -0,0 +1,36 @@
+
+
+
+ A class representing a 3D effector in an Inverse Kinematics system.
+
+
+ The IKEffector3D class represents an effector in a 3D Inverse Kinematics (IK) system. An effector is the end point of a chain of bones, and its position is used to calculate the rotations of all preceding bones in the IK chain. This class provides methods for getting and setting the target node of the effector.
+
+
+
+
+
+
+
+ Returns the NodePath of the target node for this effector. The target node is the node that the effector aims to reach.
+
+
+
+
+
+
+
+ Sets the target node for this effector using a Skeleton3D and a NodePath. The effector will aim to reach this target node.
+
+
+
+
+
+ Pins can be ultimate targets or intermediary targets.
+ By default, each pin is treated as an ultimate target, meaning any bones which are ancestors to that pin's effector are not aware of any pins which are the target of bones descending from that effector.
+ Changing this value makes ancestor bones aware and determines how much less they care with each level down.
+ Presuming all descendants of this pin have a falloff of 1, then: A pin falloff of 0 on this pin means only this pin is reported to ancestors. A pin falloff of 1 on this pin means ancestors care about all descendant pins equally (after accounting for their pin weight), regardless of how many levels down they are. A pin falloff of 0.5 means each descendant pin is used about half as much as its ancestor. The pin's falloff of a descendant is taken into account for each level with each level.
+ Meaning, if this pin has a falloff of 1 and its descendent has a falloff of 0.5, then it will be reported with total weight. Then, its descendant will be calculated with total weight; the descendant of that pin will be calculated with half weight. Finally, the descendant of that one's descendant will be with calculated quarter weight.
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKEffectorTemplate3D.xml b/modules/many_bone_ik/doc_classes/IKEffectorTemplate3D.xml
new file mode 100644
index 000000000000..e0c695472e01
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKEffectorTemplate3D.xml
@@ -0,0 +1,26 @@
+
+
+
+ A template class for creating 3D effectors in an Inverse Kinematics system.
+
+
+ The IKEffectorTemplate3D class provides a template for creating effectors in a 3D Inverse Kinematics (IK) system. It includes properties for direction priorities, passthrough factor, target node, and weight. These properties can be set to customize the behavior of the effector.
+
+
+
+
+
+ Specifies the priority of movement in each direction (X, Y, Z). Higher values indicate higher priority.
+
+
+
+
+
+
+ The NodePath of the target node that the effector aims to reach.
+
+
+ The weight of the effector. This determines how much the effector's position influences the IK calculation. Higher values result in greater influence.
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKKusudama3D.xml b/modules/many_bone_ik/doc_classes/IKKusudama3D.xml
new file mode 100644
index 000000000000..d1ab073a8542
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKKusudama3D.xml
@@ -0,0 +1,26 @@
+
+
+
+ Kusudamas are sequential collections of reach cones that form a path through their tangents.
+
+
+ A Kusudama represents a ball with multiple reach-cones protruding from it. These reach cones are arranged in sequence, creating an automatic smooth path from one cone to another. The term 'Kusudama' originates from Japanese, referring to a ball with multiple cones protruding from it.
+
+
+
+
+
+
+
+ This method returns an array of limit cones associated with the Kusudama.
+
+
+
+
+
+
+ This method sets the limit cones associated with the Kusudama.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKLimitCone3D.xml b/modules/many_bone_ik/doc_classes/IKLimitCone3D.xml
new file mode 100644
index 000000000000..05b2d596dff6
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKLimitCone3D.xml
@@ -0,0 +1,11 @@
+
+
+
+ A 3D cone used to open the rotation of a ball-and-socket joint in inverse kinematics calculations.
+
+
+ A open cone is a cone freeing the rotation of a ball-and-socket joint. A open cone is defined as a vector pointing in the direction which the cone is opening, and a radius (in radians) representing how much the cone is opening up.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKNode3D.xml b/modules/many_bone_ik/doc_classes/IKNode3D.xml
new file mode 100644
index 000000000000..969341bd8f3c
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKNode3D.xml
@@ -0,0 +1,87 @@
+
+
+
+ A 3D node used for inverse kinematics calculations.
+
+
+ The IKNode3D class provides a node that can be used in an inverse kinematics chain. It includes methods for getting and setting the global and local transforms of the node, as well as disabling scaling and converting between local and global coordinates.
+
+
+
+
+
+
+
+ Returns the global transform of this node.
+
+
+
+
+
+ Returns the parent of this node.
+
+
+
+
+
+ Returns the local transform of this node.
+
+
+
+
+
+ Returns whether scaling is disabled for this node.
+
+
+
+
+
+
+
+ Rotates the local transform of this node with a global basis. If propagate is true, the rotation is also applied to the children of this node.
+
+
+
+
+
+
+ Disables or enables scaling for this node.
+
+
+
+
+
+
+ Sets the global transform of this node.
+
+
+
+
+
+
+ Sets the parent of this node.
+
+
+
+
+
+
+ Sets the local transform of this node.
+
+
+
+
+
+
+ Converts a point from local space to global space.
+
+
+
+
+
+
+ Converts a point from global space to local space.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/IKRay3D.xml b/modules/many_bone_ik/doc_classes/IKRay3D.xml
new file mode 100644
index 000000000000..c2ef554912dd
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/IKRay3D.xml
@@ -0,0 +1,35 @@
+
+
+
+ A class representing a 3D ray in an Inverse Kinematics system.
+
+
+ The IKRay3D class represents a 3D ray in an Inverse Kinematics (IK) system. It provides methods to get the heading of the ray, check if the ray intersects a plane, and get a scaled projection of the ray.
+
+
+
+
+
+
+
+ Returns the heading of the ray as a Vector3.
+
+
+
+
+
+
+
+
+ Checks if the ray intersects a plane defined by three points (a, b, c). Returns the intersection point as a Vector3, or null if there is no intersection.
+
+
+
+
+
+
+ Returns the scaled projection of the ray onto the input vector.
+
+
+
+
diff --git a/modules/many_bone_ik/doc_classes/ManyBoneIK3D.xml b/modules/many_bone_ik/doc_classes/ManyBoneIK3D.xml
new file mode 100644
index 000000000000..dab81b413d29
--- /dev/null
+++ b/modules/many_bone_ik/doc_classes/ManyBoneIK3D.xml
@@ -0,0 +1,298 @@
+
+
+
+ A general inverse kinematics system with constraints.
+
+
+ The ManyBoneIK3D class provides a comprehensive system for inverse kinematics (IK) with support for various constraints. It allows for complex IK setups involving multiple bones, each with their own constraints and parameters.
+
+
+
+
+
+
+
+
+ Returns the index of the constraint with the given name. If no such constraint exists, returns -1.
+
+
+
+
+
+
+
+
+
+
+
+ Returns the total number of bones in the IK system.
+
+
+
+
+
+ Returns the total number of constraints in the IK system.
+
+
+
+
+
+
+ Returns the name of the constraint at the specified index.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Returns the center of the limit cone for the kusudama at the specified index.
+
+
+
+
+
+
+ Returns the count of limit cones for the kusudama at the specified index.
+
+
+
+
+
+
+
+ Returns the radius of the limit cone for the kusudama at the specified index.
+
+
+
+
+
+
+
+
+
+
+
+ Returns the total number of pins in the IK system.
+
+
+
+
+
+
+ Returns the direction priorities of the pin at the specified index.
+
+
+
+
+
+
+ Returns whether the pin at the specified index is enabled or not.
+
+
+
+
+
+
+ Returns the passthrough factor of the pin at the specified index.
+
+
+
+
+
+
+ Returns the weight of the pin at the specified index.
+
+
+
+
+
+
+
+
+
+
+
+ Registers the skeleton to the IK system. This should be called after all bones and constraints have been added to the system.
+
+
+
+
+
+
+
+
+
+
+
+ Resets all constraints in the IK system to their default state.
+
+
+
+
+
+
+ Sets the total number of constraints.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Marks the IK system as dirty, indicating that it needs to be updated. This should be called whenever a significant change is made to the system.
+
+
+
+
+
+
+
+ Sets the name of the bone at the specified pin index.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the center of the limit cone for the kusudama at the specified index.
+
+
+
+
+
+
+
+ Sets the count of limit cones for the kusudama at the specified index.
+
+
+
+
+
+
+
+
+ Sets the radius of the limit cone for the kusudama at the specified index.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Sets the direction priorities of the pin at the specified index.
+
+
+
+
+
+
+
+ Sets the passthrough factor of the pin at the specified index.
+
+
+
+
+
+
+
+ Sets the weight of the pin at the specified index.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A boolean value indicating whether the IK system is in constraint mode or not.
+
+
+ The default maximum number of radians a bone is allowed to rotate per solver iteration. The lower this value, the more natural the pose results. However, this will increase the number of iterations_per_frame the solver requires to converge.
+
+
+ The number of iterations performed by the solver per frame.
+
+
+ The number of stabilization passes performed by the solver. This can help to improve the stability of the IK solution.
+
+
+ The index of the bone currently selected in the user interface.
+
+
+
diff --git a/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.cpp b/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.cpp
new file mode 100644
index 000000000000..5481406c28e2
--- /dev/null
+++ b/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.cpp
@@ -0,0 +1,600 @@
+/**************************************************************************/
+/* many_bone_ik_3d_gizmo_plugin.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "core/math/transform_3d.h"
+#include "core/templates/local_vector.h"
+#include "editor/editor_interface.h"
+#include "editor/editor_node.h"
+#include "editor/many_bone_ik_shader.h"
+#include "editor/plugins/node_3d_editor_gizmos.h"
+#include "editor/plugins/node_3d_editor_plugin.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/skeleton_3d.h"
+#include "scene/resources/surface_tool.h"
+
+#include "../src/ik_bone_3d.h"
+#include "../src/ik_kusudama_3d.h"
+#include "../src/many_bone_ik_3d.h"
+#include "many_bone_ik_3d_gizmo_plugin.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
+#endif
+
+void ManyBoneIK3DGizmoPlugin::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_get_gizmo_name"), &ManyBoneIK3DGizmoPlugin::get_gizmo_name);
+}
+
+bool ManyBoneIK3DGizmoPlugin::has_gizmo(Node3D *p_spatial) {
+ return cast_to(p_spatial);
+}
+
+String ManyBoneIK3DGizmoPlugin::get_gizmo_name() const {
+ return "ManyBoneIK3D";
+}
+
+void ManyBoneIK3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
+ many_bone_ik = Object::cast_to(p_gizmo->get_node_3d());
+ Skeleton3D *skeleton = Object::cast_to(p_gizmo->get_node_3d())->get_skeleton();
+ p_gizmo->clear();
+ if (!skeleton || !skeleton->get_bone_count()) {
+ return;
+ }
+ if (handles_mesh_instance && !handles_mesh_instance->is_inside_tree()) {
+ skeleton->call_deferred("add_child", handles_mesh_instance);
+ handles_mesh_instance->set_skeleton_path(NodePath(""));
+ }
+ int selected = -1;
+ Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
+ if (se) {
+ selected = se->get_selected_bone();
+ }
+ Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone");
+
+ int current_bone_index = 0;
+ Vector bones_to_process = skeleton->get_parentless_bones();
+
+ while (bones_to_process.size() > current_bone_index) {
+ int current_bone_idx = bones_to_process[current_bone_index];
+
+ Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color;
+
+ for (const Ref &segmented_skeleton : many_bone_ik->get_segmented_skeletons()) {
+ if (segmented_skeleton.is_null()) {
+ continue;
+ }
+ Ref ik_bone = segmented_skeleton->get_ik_bone(current_bone_idx);
+ if (ik_bone.is_null() || ik_bone->get_constraint().is_null()) {
+ continue;
+ }
+ create_gizmo_mesh(current_bone_idx, ik_bone, p_gizmo, current_bone_color, skeleton, many_bone_ik);
+ }
+
+ current_bone_index++;
+
+ Vector child_bones_vector;
+ child_bones_vector = skeleton->get_bone_children(current_bone_idx);
+ int child_bones_size = child_bones_vector.size();
+ for (int i = 0; i < child_bones_size; i++) {
+ // Something wrong.
+ if (child_bones_vector[i] < 0) {
+ continue;
+ }
+ // Add the bone's children to the list of bones to be processed.
+ bones_to_process.push_back(child_bones_vector[i]);
+ }
+ }
+}
+
+void ManyBoneIK3DGizmoPlugin::create_gizmo_mesh(BoneId current_bone_idx, Ref ik_bone, EditorNode3DGizmo *p_gizmo, Color current_bone_color, Skeleton3D *many_bone_ik_skeleton, ManyBoneIK3D *p_many_bone_ik) {
+ Ref ik_kusudama = ik_bone->get_constraint();
+ if (ik_kusudama.is_null()) {
+ return;
+ }
+ const TypedArray &open_cones = ik_kusudama->get_open_cones();
+ if (!open_cones.size()) {
+ return;
+ }
+ BoneId parent_idx = many_bone_ik_skeleton->get_bone_parent(current_bone_idx);
+ LocalVector bones;
+ LocalVector weights;
+ bones.resize(4);
+ weights.resize(4);
+ for (int i = 0; i < 4; i++) {
+ bones[i] = 0;
+ weights[i] = 0;
+ }
+ bones[0] = parent_idx;
+ weights[0] = 1;
+
+ Transform3D constraint_relative_to_the_skeleton = p_many_bone_ik->get_relative_transform(p_many_bone_ik->get_owner()).affine_inverse() * many_bone_ik_skeleton->get_relative_transform(many_bone_ik_skeleton->get_owner()) * p_many_bone_ik->get_godot_skeleton_transform_inverse() * ik_bone->get_constraint_orientation_transform()->get_global_transform();
+ PackedFloat32Array kusudama_open_cones;
+ Ref kusudama = ik_bone->get_constraint();
+ for (int32_t cone_i = 0; cone_i < open_cones.size(); cone_i++) {
+ Ref open_cone = open_cones[cone_i];
+ Vector3 control_point = open_cone->get_control_point();
+ PackedFloat32Array new_kusudama_open_cones;
+ new_kusudama_open_cones.resize(4 * 3);
+ new_kusudama_open_cones.fill(0.0f);
+ new_kusudama_open_cones.write[0] = control_point.x;
+ new_kusudama_open_cones.write[1] = control_point.y;
+ new_kusudama_open_cones.write[2] = control_point.z;
+ float radius = open_cone->get_radius();
+ new_kusudama_open_cones.write[3] = radius;
+
+ Vector3 tangent_center_1 = open_cone->get_tangent_circle_center_next_1();
+ new_kusudama_open_cones.write[4] = tangent_center_1.x;
+ new_kusudama_open_cones.write[5] = tangent_center_1.y;
+ new_kusudama_open_cones.write[6] = tangent_center_1.z;
+ float tangent_radius = open_cone->get_tangent_circle_radius_next();
+ new_kusudama_open_cones.write[7] = tangent_radius;
+
+ Vector3 tangent_center_2 = open_cone->get_tangent_circle_center_next_2();
+ new_kusudama_open_cones.write[8] = tangent_center_2.x;
+ new_kusudama_open_cones.write[9] = tangent_center_2.y;
+ new_kusudama_open_cones.write[10] = tangent_center_2.z;
+ new_kusudama_open_cones.write[11] = tangent_radius;
+
+ kusudama_open_cones.append_array(new_kusudama_open_cones);
+ }
+ if (current_bone_idx >= many_bone_ik_skeleton->get_bone_count()) {
+ return;
+ }
+ if (current_bone_idx == -1) {
+ return;
+ }
+ if (parent_idx >= many_bone_ik_skeleton->get_bone_count()) {
+ return;
+ }
+ if (parent_idx <= -1) {
+ return;
+ }
+ // Code copied from the SphereMesh.
+ int rings = 8;
+
+ int i = 0, j = 0, prevrow = 0, thisrow = 0, point = 0;
+ float x, y, z;
+
+ Vector points;
+ Vector normals;
+ Vector indices;
+ point = 0;
+
+ thisrow = 0;
+ prevrow = 0;
+ for (j = 0; j <= (rings + 1); j++) {
+ int radial_segments = 8;
+ float v = j;
+ float w;
+
+ v /= (rings + 1);
+ w = sin(Math_PI * v);
+ y = cos(Math_PI * v);
+
+ for (i = 0; i <= radial_segments; i++) {
+ float u = i;
+ u /= radial_segments;
+
+ x = sin(u * Math_TAU);
+ z = cos(u * Math_TAU);
+
+ Vector3 p = Vector3(x * w, y, z * w) * 0.02f;
+ points.push_back(p);
+ Vector3 normal = Vector3(x * w, y, z * w);
+ normals.push_back(normal.normalized());
+ point++;
+
+ if (i > 0 && j > 0) {
+ indices.push_back(prevrow + i - 1);
+ indices.push_back(prevrow + i);
+ indices.push_back(thisrow + i - 1);
+
+ indices.push_back(prevrow + i);
+ indices.push_back(thisrow + i);
+ indices.push_back(thisrow + i - 1);
+ };
+ };
+
+ prevrow = thisrow;
+ thisrow = point;
+ }
+ if (!indices.size()) {
+ return;
+ }
+ Ref surface_tool;
+ surface_tool.instantiate();
+ surface_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
+ const int32_t MESH_CUSTOM_0 = 0;
+ surface_tool->set_custom_format(MESH_CUSTOM_0, SurfaceTool::CustomFormat::CUSTOM_RGBA_HALF);
+ for (int32_t point_i = 0; point_i < points.size(); point_i++) {
+ surface_tool->set_bones(bones);
+ surface_tool->set_weights(weights);
+ Color c;
+ c.r = normals[point_i].x;
+ c.g = normals[point_i].y;
+ c.b = normals[point_i].z;
+ c.a = 0;
+ surface_tool->set_custom(MESH_CUSTOM_0, c);
+ surface_tool->set_normal(normals[point_i]);
+ surface_tool->add_vertex(points[point_i]);
+ }
+ for (int32_t index_i : indices) {
+ surface_tool->add_index(index_i);
+ }
+ Ref kusudama_material;
+ kusudama_material.instantiate();
+ kusudama_material->set_shader(kusudama_shader);
+ kusudama_material->set_shader_parameter("cone_sequence", kusudama_open_cones);
+ int32_t cone_count = kusudama->get_open_cones().size();
+ kusudama_material->set_shader_parameter("cone_count", cone_count);
+ kusudama_material->set_shader_parameter("kusudama_color", current_bone_color);
+ p_gizmo->add_mesh(
+ surface_tool->commit(Ref(), RS::ARRAY_CUSTOM_RGBA_HALF << RS::ARRAY_FORMAT_CUSTOM0_SHIFT),
+ kusudama_material, constraint_relative_to_the_skeleton);
+}
+
+int32_t ManyBoneIK3DGizmoPlugin::get_priority() const {
+ return -1;
+}
+
+EditorPluginManyBoneIK::EditorPluginManyBoneIK() {
+ Ref many_bone_ik_gizmo_plugin;
+ many_bone_ik_gizmo_plugin.instantiate();
+ Node3DEditor::get_singleton()->add_gizmo_plugin(many_bone_ik_gizmo_plugin);
+}
+
+int ManyBoneIK3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
+ Skeleton3D *skeleton = Object::cast_to(p_gizmo->get_node_3d())->get_skeleton();
+ ERR_FAIL_COND_V(!skeleton, -1);
+
+ if (!edit_mode) {
+ return -1;
+ }
+
+ if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
+ return -1;
+ }
+
+ // Select bone.
+ real_t grab_threshold = 4 * EDSCALE;
+ Vector3 ray_from = p_camera->get_global_transform().origin;
+ Transform3D gt = skeleton->get_global_transform();
+ int closest_idx = -1;
+ real_t closest_dist = 1e10;
+ const int bone_count = skeleton->get_bone_count();
+
+ for (int i = 0; i < bone_count; i++) {
+ Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin);
+ Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
+ real_t dist_3d = ray_from.distance_to(joint_pos_3d);
+ real_t dist_2d = p_point.distance_to(joint_pos_2d);
+ if (dist_2d < grab_threshold && dist_3d < closest_dist) {
+ closest_dist = dist_3d;
+ closest_idx = i;
+ }
+ }
+
+ if (closest_idx >= 0) {
+ many_bone_ik->set_ui_selected_bone(closest_idx);
+ return closest_idx;
+ }
+
+ many_bone_ik->set_ui_selected_bone(-1);
+ return -1;
+}
+
+Transform3D ManyBoneIK3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const {
+ Skeleton3D *skeleton = Object::cast_to(p_gizmo->get_node_3d())->get_skeleton();
+ ERR_FAIL_COND_V(!skeleton, Transform3D());
+
+ Transform3D constraint_relative_to_the_skeleton = many_bone_ik->get_relative_transform(many_bone_ik->get_owner()).affine_inverse() *
+ skeleton->get_relative_transform(skeleton->get_owner()) * many_bone_ik->get_godot_skeleton_transform_inverse();
+ return constraint_relative_to_the_skeleton * skeleton->get_bone_global_pose(p_id);
+}
+
+void ManyBoneIK3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) {
+ Skeleton3D *skeleton = Object::cast_to(p_gizmo->get_node_3d())->get_skeleton();
+ ERR_FAIL_COND(!skeleton);
+ // Prepare for global to local.
+ Transform3D original_to_local;
+ int parent_idx = skeleton->get_bone_parent(p_id);
+ if (parent_idx >= 0) {
+ original_to_local = skeleton->get_bone_global_pose(parent_idx);
+ }
+ Basis to_local = original_to_local.get_basis().inverse();
+
+ // Prepare transform.
+ Transform3D t;
+
+ // Basis.
+ t.basis = to_local * p_transform.get_basis();
+
+ Transform3D constraint_relative_to_the_skeleton = many_bone_ik->get_relative_transform(many_bone_ik->get_owner()).affine_inverse() *
+ skeleton->get_relative_transform(skeleton->get_owner()) * many_bone_ik->get_godot_skeleton_transform_inverse();
+ p_transform = constraint_relative_to_the_skeleton.affine_inverse() * p_transform;
+ // Origin.
+ Vector3 orig = skeleton->get_bone_pose(p_id).origin;
+ Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin;
+ t.origin = orig + to_local.xform(sub);
+
+ // Apply transform.
+ skeleton->set_bone_pose_position(p_id, t.origin);
+ skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion());
+ skeleton->set_bone_pose_scale(p_id, t.basis.get_scale());
+ many_bone_ik->update_gizmos();
+}
+
+void ManyBoneIK3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector &p_ids, const Vector &p_restore, bool p_cancel) {
+ Skeleton3D *skeleton = Object::cast_to(p_gizmo->get_node_3d())->get_skeleton();
+ ERR_FAIL_COND(!skeleton);
+
+ Node3DEditor *ne = Node3DEditor::get_singleton();
+ ERR_FAIL_COND(!ne);
+
+ EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
+ ur->create_action(TTR("Set Bone Transform"));
+ if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) {
+ for (int i = 0; i < p_ids.size(); i++) {
+ int32_t constraint_i = many_bone_ik->find_constraint(skeleton->get_bone_name(p_ids[i]));
+ float from_original = many_bone_ik->get_joint_twist(constraint_i).x;
+ float range = many_bone_ik->get_joint_twist(constraint_i).y;
+ ur->add_do_method(many_bone_ik, "set_kusudama_twist", constraint_i, Vector2(skeleton->get_bone_pose(p_ids[i]).get_basis().get_euler().y, range));
+ ur->add_undo_method(many_bone_ik, "set_kusudama_twist", constraint_i, Vector2(from_original, range));
+ ur->add_do_method(many_bone_ik, "set_dirty");
+ ur->add_undo_method(many_bone_ik, "set_dirty");
+ }
+ }
+ ur->commit_action();
+}
+
+void ManyBoneIK3DGizmoPlugin::_draw_handles() {
+ if (!many_bone_ik) {
+ return;
+ }
+ Skeleton3D *skeleton = many_bone_ik->get_skeleton();
+ ERR_FAIL_COND(!skeleton);
+ const int bone_count = skeleton->get_bone_count();
+
+ handles_mesh->clear_surfaces();
+
+ if (bone_count) {
+ handles_mesh_instance->show();
+
+ handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS);
+
+ for (int i = 0; i < bone_count; i++) {
+ Color c;
+ if (i == many_bone_ik->get_ui_selected_bone()) {
+ c = Color(1, 1, 0);
+ } else {
+ c = Color(0.1, 0.25, 0.8);
+ }
+ Vector3 point = skeleton->get_bone_global_pose(i).origin;
+ handles_mesh->surface_set_color(c);
+ handles_mesh->surface_add_vertex(point);
+ }
+ handles_mesh->surface_end();
+ handles_mesh->surface_set_material(0, handle_material);
+ } else {
+ handles_mesh_instance->hide();
+ }
+}
+
+void ManyBoneIK3DGizmoPlugin::_draw_gizmo() {
+ if (!many_bone_ik) {
+ return;
+ }
+ Skeleton3D *skeleton = many_bone_ik->get_skeleton();
+ if (!skeleton) {
+ return;
+ }
+
+ // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode,
+ // the skeleton update will be done first and
+ // the drawing surface will be interrupted once and an error will occur.
+ skeleton->force_update_all_dirty_bones();
+
+ // Handles.
+ if (edit_mode) {
+ _draw_handles();
+ } else {
+ _hide_handles();
+ }
+}
+
+void ManyBoneIK3DGizmoPlugin::_update_gizmo_visible() {
+ if (!many_bone_ik) {
+ return;
+ }
+ Skeleton3D *skeleton = many_bone_ik->get_skeleton();
+ if (!skeleton) {
+ return;
+ }
+ _subgizmo_selection_change();
+ if (edit_mode) {
+ int32_t selected_bone = many_bone_ik->get_ui_selected_bone();
+ if (selected_bone == -1) {
+#ifdef TOOLS_ENABLED
+ skeleton->set_transform_gizmo_visible(false);
+#endif
+ } else {
+#ifdef TOOLS_ENABLED
+ if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) {
+ skeleton->set_transform_gizmo_visible(true);
+ } else {
+ skeleton->set_transform_gizmo_visible(false);
+ }
+#endif
+ }
+ } else {
+#ifdef TOOLS_ENABLED
+ skeleton->set_transform_gizmo_visible(true);
+#endif
+ }
+ _draw_gizmo();
+}
+
+void ManyBoneIK3DGizmoPlugin::_subgizmo_selection_change() {
+ if (!many_bone_ik) {
+ return;
+ }
+ Skeleton3D *skeleton = many_bone_ik->get_skeleton();
+ if (!skeleton) {
+ return;
+ }
+ // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree.
+ if (!edit_mode) {
+ skeleton->clear_subgizmo_selection();
+ return;
+ }
+ int selected = -1;
+ if (many_bone_ik) {
+ selected = many_bone_ik->get_ui_selected_bone();
+ }
+
+ if (selected >= 0) {
+ Vector[> gizmos = skeleton->get_gizmos();
+ for (int i = 0; i < gizmos.size(); i++) {
+ Ref gizmo = gizmos[i];
+ if (!gizmo.is_valid()) {
+ continue;
+ }
+ Ref plugin = gizmo->get_plugin();
+ if (!plugin.is_valid()) {
+ continue;
+ }
+ skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected));
+ break;
+ }
+ } else {
+ skeleton->clear_subgizmo_selection();
+ }
+}
+
+void ManyBoneIK3DGizmoPlugin::edit_mode_toggled(const bool pressed) {
+ edit_mode = pressed;
+ _update_gizmo_visible();
+}
+
+void ManyBoneIK3DGizmoPlugin::_hide_handles() {
+ handles_mesh_instance->hide();
+}
+
+void ManyBoneIK3DGizmoPlugin::_notifications(int32_t p_what) {
+ switch (p_what) {
+ case EditorNode3DGizmoPlugin::NOTIFICATION_POSTINITIALIZE: {
+ kusudama_shader->set_code(MANY_BONE_IKKUSUDAMA_SHADER);
+ handle_material = Ref(memnew(ShaderMaterial));
+ handle_shader = Ref(memnew(Shader));
+ handle_shader->set_code(R"(
+// Skeleton 3D gizmo handle shader.
+
+shader_type spatial;
+render_mode unshaded, shadows_disabled, depth_draw_always;
+uniform sampler2D texture_albedo : source_color;
+uniform float point_size : hint_range(0,128) = 32;
+void vertex() {
+ if (!OUTPUT_IS_SRGB) {
+ COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
+ }
+ VERTEX = VERTEX;
+ POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
+ POSITION.z = mix(POSITION.z, 0, 0.999);
+ POINT_SIZE = point_size;
+}
+void fragment() {
+ vec4 albedo_tex = texture(texture_albedo,POINT_COORD);
+ vec3 col = albedo_tex.rgb + COLOR.rgb;
+ col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0));
+ ALBEDO = col;
+ if (albedo_tex.a < 0.5) { discard; }
+ ALPHA = albedo_tex.a;
+}
+)");
+ handle_material->set_shader(handle_shader);
+ Ref handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("EditorBoneHandle"), SNAME("EditorIcons"));
+ handle_material->set_shader_parameter("point_size", handle->get_width());
+ handle_material->set_shader_parameter("texture_albedo", handle);
+
+ handles_mesh_instance = memnew(MeshInstance3D);
+ handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF);
+ handles_mesh_instance->set_mesh(handles_mesh);
+ edit_mode_button = memnew(Button);
+ edit_mode_button->set_text(TTR("Edit Mode"));
+ edit_mode_button->set_flat(true);
+ edit_mode_button->set_toggle_mode(true);
+ edit_mode_button->set_focus_mode(Control::FOCUS_NONE);
+ edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints."));
+ edit_mode_button->connect("toggled", callable_mp(this, &ManyBoneIK3DGizmoPlugin::edit_mode_toggled));
+ edit_mode = false;
+ create_material("lines_primary", Color(0.93725490570068, 0.19215686619282, 0.22352941334248), true, true, true);
+
+ unselected_mat = Ref(memnew(StandardMaterial3D));
+ unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED);
+ unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA);
+ unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
+ unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true);
+
+ selected_mat = Ref(memnew(ShaderMaterial));
+ selected_sh = Ref(memnew(Shader));
+ selected_sh->set_code(R"(
+// Skeleton 3D gizmo bones shader.
+
+shader_type spatial;
+render_mode unshaded, shadows_disabled;
+void vertex() {
+ if (!OUTPUT_IS_SRGB) {
+ COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) );
+ }
+ VERTEX = VERTEX;
+ POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
+ POSITION.z = mix(POSITION.z, POSITION.w, 0.999);
+}
+void fragment() {
+ ALBEDO = COLOR.rgb;
+ ALPHA = COLOR.a;
+}
+)");
+ selected_mat->set_shader(selected_sh);
+
+ // Register properties in editor settings.
+ EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4));
+ EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0));
+ EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1);
+ EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1);
+ EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron"));
+ Node3DEditor::get_singleton()->add_control_to_menu_panel(edit_mode_button);
+ } break;
+ case EditorNode3DGizmoPlugin::NOTIFICATION_PREDELETE: {
+ Node3DEditor::get_singleton()->remove_control_from_menu_panel(edit_mode_button);
+ } break;
+ }
+}
diff --git a/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.h b/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.h
new file mode 100644
index 000000000000..99c7e3fd74e3
--- /dev/null
+++ b/modules/many_bone_ik/editor/many_bone_ik_3d_gizmo_plugin.h
@@ -0,0 +1,101 @@
+/**************************************************************************/
+/* many_bone_ik_3d_gizmo_plugin.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef MANY_BONE_IK_3D_GIZMO_PLUGIN_H
+#define MANY_BONE_IK_3D_GIZMO_PLUGIN_H
+
+#include "../src/ik_bone_3d.h"
+#include "../src/many_bone_ik_3d.h"
+
+#include "editor/editor_inspector.h"
+#include "editor/editor_settings.h"
+#include "editor/plugins/skeleton_3d_editor_plugin.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/node_3d.h"
+#include "scene/3d/skeleton_3d.h"
+#include "scene/resources/immediate_mesh.h"
+#include "scene/resources/material.h"
+
+class Joint;
+class PhysicalBone3D;
+class ManyBoneIKEditorPlugin;
+class Button;
+
+class ManyBoneIK3DGizmoPlugin : public EditorNode3DGizmoPlugin {
+ GDCLASS(ManyBoneIK3DGizmoPlugin, EditorNode3DGizmoPlugin);
+ Ref kusudama_shader = memnew(Shader);
+
+ Ref unselected_mat;
+ Ref selected_mat;
+ Ref selected_sh = memnew(Shader);
+
+ MeshInstance3D *handles_mesh_instance = nullptr;
+ Ref handles_mesh = memnew(ImmediateMesh);
+ Ref handle_material = memnew(ShaderMaterial);
+ Ref handle_shader;
+ ManyBoneIK3D *many_bone_ik = nullptr;
+ Button *edit_mode_button = nullptr;
+ bool edit_mode = false;
+
+protected:
+ static void _bind_methods();
+ void _notifications(int32_t p_what);
+
+public:
+ const Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton");
+ const int32_t KUSUDAMA_MAX_CONES = 10;
+ bool has_gizmo(Node3D *p_spatial) override;
+ String get_gizmo_name() const override;
+ void redraw(EditorNode3DGizmo *p_gizmo) override;
+ int32_t get_priority() const override;
+ void create_gizmo_mesh(BoneId current_bone_idx, Ref ik_bone, EditorNode3DGizmo *p_gizmo, Color current_bone_color, Skeleton3D *many_bone_ik_skeleton, ManyBoneIK3D *p_many_bone_ik);
+ int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override;
+ Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
+ void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override;
+ void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector &p_ids, const Vector &p_restore, bool p_cancel) override;
+
+ void edit_mode_toggled(const bool pressed);
+ void _subgizmo_selection_change();
+ void _update_gizmo_visible();
+ void _draw_gizmo();
+
+ void _draw_handles();
+ void _hide_handles();
+};
+
+class EditorPluginManyBoneIK : public EditorPlugin {
+ GDCLASS(EditorPluginManyBoneIK, EditorPlugin);
+
+public:
+ EditorPluginManyBoneIK();
+};
+
+#endif // MANY_BONE_IK_3D_GIZMO_PLUGIN_H
diff --git a/modules/many_bone_ik/editor/many_bone_ik_shader.h b/modules/many_bone_ik/editor/many_bone_ik_shader.h
new file mode 100644
index 000000000000..d0b13d335ffc
--- /dev/null
+++ b/modules/many_bone_ik/editor/many_bone_ik_shader.h
@@ -0,0 +1,207 @@
+/**************************************************************************/
+/* many_bone_ik_shader.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef MANY_BONE_IK_SHADER_H
+#define MANY_BONE_IK_SHADER_H
+
+// Skeleton 3D gizmo kusudama constraint shader.
+static constexpr char MANY_BONE_IKKUSUDAMA_SHADER[] = R"(
+shader_type spatial;
+render_mode depth_draw_always;
+
+uniform vec4 kusudama_color : source_color = vec4(0.58039218187332, 0.27058824896812, 0.00784313771874, 1.0);
+uniform int cone_count = 0;
+
+// 0,0,0 is the center of the kusudama. The kusudamas have their own bases that automatically get reoriented such that +y points in the direction that is the weighted average of the limitcones on the kusudama.
+// But, if you have a kusuduma with just 1 open_cone, then in general that open_cone should be 0,1,0 in the kusudama's basis unless the user has specifically specified otherwise.
+
+uniform vec4 cone_sequence[30];
+
+// This shader can display up to 10 cones (represented by 30 4d vectors)
+// Each group of 4 represents the xyz coordinates of the cone direction
+// vector in model space and the fourth element represents radius
+
+// TODO: fire 2022-05-26
+// Use a texture to store bone parameters.
+// Use the uv to get the row of the bone.
+
+varying vec3 normal_model_dir;
+varying vec4 vert_model_color;
+
+bool is_in_inter_cone_path(in vec3 normal_dir, in vec4 tangent_1, in vec4 cone_1, in vec4 tangent_2, in vec4 cone_2) {
+ vec3 c1xc2 = cross(cone_1.xyz, cone_2.xyz);
+ float c1c2dir = dot(normal_dir, c1xc2);
+
+ if (c1c2dir < 0.0) {
+ vec3 c1xt1 = cross(cone_1.xyz, tangent_1.xyz);
+ vec3 t1xc2 = cross(tangent_1.xyz, cone_2.xyz);
+ float c1t1dir = dot(normal_dir, c1xt1);
+ float t1c2dir = dot(normal_dir, t1xc2);
+
+ return (c1t1dir > 0.0 && t1c2dir > 0.0);
+
+ } else {
+ vec3 t2xc1 = cross(tangent_2.xyz, cone_1.xyz);
+ vec3 c2xt2 = cross(cone_2.xyz, tangent_2.xyz);
+ float t2c1dir = dot(normal_dir, t2xc1);
+ float c2t2dir = dot(normal_dir, c2xt2);
+
+ return (c2t2dir > 0.0 && t2c1dir > 0.0);
+ }
+}
+
+//determines the current draw condition based on the desired draw condition in the setToArgument
+// -3 = disallowed entirely;
+// -2 = disallowed and on tangent_cone boundary
+// -1 = disallowed and on control_cone boundary
+// 0 = allowed and empty;
+// 1 = allowed and on control_cone boundary
+// 2 = allowed and on tangent_cone boundary
+int get_allowability_condition(in int current_condition, in int set_to) {
+ if((current_condition == -1 || current_condition == -2)
+ && set_to >= 0) {
+ return current_condition *= -1;
+ } else if(current_condition == 0 && (set_to == -1 || set_to == -2)) {
+ return set_to *=-2;
+ }
+ return max(current_condition, set_to);
+}
+
+// returns 1 if normal_dir is beyond (cone.a) radians from the cone.rgb
+// returns 0 if normal_dir is within (cone.a + boundary_width) radians from the cone.rgb
+// return -1 if normal_dir is less than (cone.a) radians from the cone.rgb
+int is_in_cone(in vec3 normal_dir, in vec4 cone, in float boundary_width) {
+ float arc_dist_to_cone = acos(dot(normal_dir, cone.rgb));
+ if (arc_dist_to_cone > (cone.a+(boundary_width/2.))) {
+ return 1;
+ }
+ if (arc_dist_to_cone < (cone.a-(boundary_width/2.))) {
+ return -1;
+ }
+ return 0;
+}
+
+// Returns a color corresponding to the allowability of this region,
+// or otherwise the boundaries corresponding
+// to various cones and tangent_cone.
+vec4 color_allowed(in vec3 normal_dir, in int cone_counts, in float boundary_width) {
+ int current_condition = -3;
+ if (cone_counts == 1) {
+ vec4 cone = cone_sequence[0];
+ int in_cone = is_in_cone(normal_dir, cone, boundary_width);
+ bool is_in_cone = in_cone == 0;
+ if (is_in_cone) {
+ in_cone = -1;
+ } else {
+ if (in_cone < 0) {
+ in_cone = 0;
+ } else {
+ in_cone = -3;
+ }
+ }
+ current_condition = get_allowability_condition(current_condition, in_cone);
+ } else {
+ for(int i=0; i < (cone_counts-1)*3; i=i+3) {
+ normal_dir = normalize(normal_dir);
+
+ vec4 cone_1 = cone_sequence[i+0];
+ vec4 tangent_1 = cone_sequence[i+1];
+ vec4 tangent_2 = cone_sequence[i+2];
+ vec4 cone_2 = cone_sequence[i+3];
+
+ int inCone1 = is_in_cone(normal_dir, cone_1, boundary_width);
+ if (inCone1 == 0) {
+ inCone1 = -1;
+ } else {
+ if (inCone1 < 0) {
+ inCone1 = 0;
+ } else {
+ inCone1 = -3;
+ }
+ }
+ current_condition = get_allowability_condition(current_condition, inCone1);
+
+ int inCone2 = is_in_cone(normal_dir, cone_2, boundary_width);
+ if (inCone2 == 0) {
+ inCone2 = -1;
+ } else {
+ if (inCone2 < 0) {
+ inCone2 = 0;
+ } else {
+ inCone2 = -3;
+ }
+ }
+ current_condition = get_allowability_condition(current_condition, inCone2);
+
+ int in_tan_1 = is_in_cone(normal_dir, tangent_1, boundary_width);
+ int in_tan_2 = is_in_cone(normal_dir, tangent_2, boundary_width);
+
+ if (float(in_tan_1) < 1. || float(in_tan_2) < 1.) {
+ in_tan_1 = in_tan_1 == 0 ? -2 : -3;
+ current_condition = get_allowability_condition(current_condition, in_tan_1);
+ in_tan_2 = in_tan_2 == 0 ? -2 : -3;
+ current_condition = get_allowability_condition(current_condition, in_tan_2);
+ } else {
+ bool in_intercone = is_in_inter_cone_path(normal_dir, tangent_1, cone_1, tangent_2, cone_2);
+ int intercone_condition = in_intercone ? 0 : -3;
+ current_condition = get_allowability_condition(current_condition, intercone_condition);
+ }
+ }
+ }
+ vec4 result = vert_model_color;
+ bool is_disallowed_entirely = current_condition == -3;
+ bool is_disallowed_on_tangent_cone_boundary = current_condition == -2;
+ bool is_disallowed_on_control_cone_boundary = current_condition == -1;
+ if (is_disallowed_entirely || is_disallowed_on_tangent_cone_boundary || is_disallowed_on_control_cone_boundary) {
+ return result;
+ } else {
+ return vec4(0.0, 0.0, 0.0, 0.0);
+ }
+ return result;
+}
+
+void vertex() {
+ normal_model_dir = CUSTOM0.rgb;
+ vert_model_color.rgb = kusudama_color.rgb;
+ // Draw the spheres in front of the background.
+ VERTEX = VERTEX;
+ POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0);
+ POSITION.z = mix(POSITION.z, POSITION.w, 0.999);
+}
+
+void fragment() {
+ vec4 result_color_allowed = vec4(0.0, 0.0, 0.0, 0.0);
+ result_color_allowed = color_allowed(normal_model_dir, cone_count, 0.02);
+ ALBEDO = result_color_allowed.rgb;
+ ALPHA = 0.8;
+}
+)";
+
+#endif // MANY_BONE_IK_SHADER_H
diff --git a/modules/many_bone_ik/register_types.cpp b/modules/many_bone_ik/register_types.cpp
new file mode 100644
index 000000000000..5b7ed4fd15c0
--- /dev/null
+++ b/modules/many_bone_ik/register_types.cpp
@@ -0,0 +1,68 @@
+/**************************************************************************/
+/* register_types.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "register_types.h"
+
+#include "src/ik_bone_3d.h"
+#include "src/ik_effector_3d.h"
+#include "src/ik_effector_template_3d.h"
+#include "src/ik_kusudama_3d.h"
+#include "src/many_bone_ik_3d.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/many_bone_ik_3d_gizmo_plugin.h"
+#endif
+
+void initialize_many_bone_ik_module(ModuleInitializationLevel p_level) {
+ if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {
+ }
+#ifdef TOOLS_ENABLED
+ if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
+ EditorPlugins::add_by_type();
+ }
+#endif
+ if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) {
+ GDREGISTER_CLASS(IKEffectorTemplate3D);
+ GDREGISTER_CLASS(ManyBoneIK3D);
+ GDREGISTER_CLASS(IKBone3D);
+ GDREGISTER_CLASS(IKNode3D);
+ GDREGISTER_CLASS(IKEffector3D);
+ GDREGISTER_CLASS(IKBoneSegment3D);
+ GDREGISTER_CLASS(IKKusudama3D);
+ GDREGISTER_CLASS(IKRay3D);
+ GDREGISTER_CLASS(IKLimitCone3D);
+ }
+}
+
+void uninitialize_many_bone_ik_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+}
diff --git a/modules/many_bone_ik/register_types.h b/modules/many_bone_ik/register_types.h
new file mode 100644
index 000000000000..7bb117a1b9a3
--- /dev/null
+++ b/modules/many_bone_ik/register_types.h
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* register_types.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef MANY_BONE_IK_REGISTER_TYPES_H
+#define MANY_BONE_IK_REGISTER_TYPES_H
+
+#include "modules/register_module_types.h"
+
+void initialize_many_bone_ik_module(ModuleInitializationLevel p_level);
+void uninitialize_many_bone_ik_module(ModuleInitializationLevel p_level);
+
+#endif // MANY_BONE_IK_REGISTER_TYPES_H
diff --git a/modules/many_bone_ik/src/ik_bone_3d.cpp b/modules/many_bone_ik/src/ik_bone_3d.cpp
new file mode 100644
index 000000000000..473475887ea1
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_bone_3d.cpp
@@ -0,0 +1,364 @@
+/**************************************************************************/
+/* ik_bone_3d.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "ik_bone_3d.h"
+#include "ik_kusudama_3d.h"
+#include "many_bone_ik_3d.h"
+#include "math/ik_node_3d.h"
+#include
+
+void IKBone3D::set_bone_id(BoneId p_bone_id, Skeleton3D *p_skeleton) {
+ ERR_FAIL_NULL(p_skeleton);
+ bone_id = p_bone_id;
+}
+
+BoneId IKBone3D::get_bone_id() const {
+ return bone_id;
+}
+
+void IKBone3D::set_parent(const Ref &p_parent) {
+ ERR_FAIL_NULL(p_parent);
+ parent = p_parent;
+ if (parent.is_valid()) {
+ parent->children.push_back(this);
+ godot_skeleton_aligned_transform->set_parent(parent->godot_skeleton_aligned_transform);
+ constraint_orientation_transform->set_parent(parent->godot_skeleton_aligned_transform);
+ constraint_twist_transform->set_parent(parent->godot_skeleton_aligned_transform);
+ }
+}
+
+void IKBone3D::update_default_bone_direction_transform(Skeleton3D *p_skeleton) {
+ Vector3 child_centroid;
+ int child_count = 0;
+
+ for (Ref &ik_bone : children) {
+ child_centroid += ik_bone->get_ik_transform()->get_global_transform().origin;
+ child_count++;
+ }
+
+ if (child_count > 0) {
+ child_centroid /= child_count;
+ } else {
+ const PackedInt32Array &bone_children = p_skeleton->get_bone_children(bone_id);
+ for (BoneId child_bone_idx : bone_children) {
+ child_centroid += p_skeleton->get_bone_global_pose(child_bone_idx).origin;
+ }
+ child_centroid /= bone_children.size();
+ }
+
+ const Vector3 &godot_bone_origin = godot_skeleton_aligned_transform->get_global_transform().origin;
+ child_centroid -= godot_bone_origin;
+
+ if (Math::is_zero_approx(child_centroid.length_squared())) {
+ if (parent.is_valid()) {
+ child_centroid = parent->get_bone_direction_transform()->get_global_transform().basis.get_column(Vector3::AXIS_Y);
+ } else {
+ child_centroid = get_bone_direction_transform()->get_global_transform().basis.get_column(Vector3::AXIS_Y);
+ }
+ }
+
+ if (!Math::is_zero_approx(child_centroid.length_squared()) && (children.size() || p_skeleton->get_bone_children(bone_id).size())) {
+ child_centroid.normalize();
+ Vector3 bone_direction = bone_direction_transform->get_global_transform().basis.get_column(Vector3::AXIS_Y);
+ bone_direction.normalize();
+ bone_direction_transform->rotate_local_with_global(Quaternion(child_centroid, bone_direction));
+ }
+}
+
+void IKBone3D::update_default_constraint_transform() {
+ Ref parent_bone = get_parent();
+ if (parent_bone.is_valid()) {
+ Transform3D parent_bone_aligned_transform = get_parent_bone_aligned_transform();
+ constraint_orientation_transform->set_global_transform(parent_bone_aligned_transform);
+ }
+
+ Transform3D set_constraint_twist_transform = get_set_constraint_twist_transform();
+ constraint_twist_transform->set_global_transform(set_constraint_twist_transform);
+
+ if (constraint.is_null()) {
+ return;
+ }
+
+ TypedArray cones = constraint->get_open_cones();
+ Vector3 direction;
+ if (cones.size() == 0) {
+ direction = bone_direction_transform->get_global_transform().basis.get_column(Vector3::AXIS_Y);
+ } else {
+ float total_radius_sum = calculate_total_radius_sum(cones);
+ direction = calculate_weighted_direction(cones, total_radius_sum);
+ direction -= constraint_orientation_transform->get_global_transform().origin;
+ }
+
+ Vector3 twist_axis = set_constraint_twist_transform.basis.get_column(Vector3::AXIS_Y);
+ Quaternion align_dir = Quaternion(twist_axis, direction);
+ constraint_twist_transform->rotate_local_with_global(align_dir);
+}
+
+Ref IKBone3D::get_parent() const {
+ return parent;
+}
+
+void IKBone3D::set_pin(const Ref &p_pin) {
+ ERR_FAIL_NULL(p_pin);
+ pin = p_pin;
+}
+
+Ref IKBone3D::get_pin() const {
+ return pin;
+}
+
+void IKBone3D::set_pose(const Transform3D &p_transform) {
+ godot_skeleton_aligned_transform->set_transform(p_transform);
+}
+
+Transform3D IKBone3D::get_pose() const {
+ return godot_skeleton_aligned_transform->get_transform();
+}
+
+void IKBone3D::set_global_pose(const Transform3D &p_transform) {
+ godot_skeleton_aligned_transform->set_global_transform(p_transform);
+ Transform3D transform = constraint_orientation_transform->get_transform();
+ transform.origin = godot_skeleton_aligned_transform->get_transform().origin;
+ constraint_orientation_transform->set_transform(transform);
+ constraint_orientation_transform->_propagate_transform_changed();
+}
+
+Transform3D IKBone3D::get_global_pose() const {
+ return godot_skeleton_aligned_transform->get_global_transform();
+}
+
+Transform3D IKBone3D::get_bone_direction_global_pose() const {
+ return bone_direction_transform->get_global_transform();
+}
+
+void IKBone3D::set_initial_pose(Skeleton3D *p_skeleton) {
+ ERR_FAIL_NULL(p_skeleton);
+ if (bone_id == -1) {
+ return;
+ }
+ Transform3D bone_origin_to_parent_origin = p_skeleton->get_bone_pose(bone_id);
+ set_pose(bone_origin_to_parent_origin);
+}
+
+void IKBone3D::set_skeleton_bone_pose(Skeleton3D *p_skeleton) {
+ ERR_FAIL_NULL(p_skeleton);
+ Transform3D bone_to_parent = get_pose();
+ p_skeleton->set_bone_pose_position(bone_id, bone_to_parent.origin);
+ if (!bone_to_parent.basis.is_finite()) {
+ bone_to_parent.basis = Basis();
+ }
+ p_skeleton->set_bone_pose_rotation(bone_id, bone_to_parent.basis.get_rotation_quaternion());
+ p_skeleton->set_bone_pose_scale(bone_id, bone_to_parent.basis.get_scale());
+}
+
+void IKBone3D::create_pin() {
+ pin = Ref(memnew(IKEffector3D(this)));
+}
+
+bool IKBone3D::is_pinned() const {
+ return pin.is_valid();
+}
+
+void IKBone3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_pin"), &IKBone3D::get_pin);
+ ClassDB::bind_method(D_METHOD("set_pin", "pin"), &IKBone3D::set_pin);
+ ClassDB::bind_method(D_METHOD("is_pinned"), &IKBone3D::is_pinned);
+ ClassDB::bind_method(D_METHOD("get_constraint"), &IKBone3D::get_constraint);
+ ClassDB::bind_method(D_METHOD("get_constraint_orientation_transform"), &IKBone3D::get_constraint_orientation_transform);
+ ClassDB::bind_method(D_METHOD("get_constraint_twist_transform"), &IKBone3D::get_constraint_twist_transform);
+}
+
+IKBone3D::IKBone3D(StringName p_bone, Skeleton3D *p_skeleton, const Ref &p_parent, Vector][> &p_pins, float p_default_dampening,
+ ManyBoneIK3D *p_many_bone_ik) {
+ ERR_FAIL_NULL(p_skeleton);
+
+ default_dampening = p_default_dampening;
+ cos_half_dampen = cos(default_dampening / real_t(2.0));
+ set_name(p_bone);
+ bone_id = p_skeleton->find_bone(p_bone);
+ if (p_parent.is_valid()) {
+ set_parent(p_parent);
+ }
+ for (Ref elem : p_pins) {
+ if (elem.is_null()) {
+ continue;
+ }
+ if (elem->get_name() == p_bone) {
+ create_pin();
+ Ref effector = get_pin();
+ effector->set_target_node(p_skeleton, elem->get_target_node());
+ effector->set_motion_propagation_factor(elem->get_motion_propagation_factor());
+ effector->set_weight(elem->get_weight());
+ effector->set_direction_priorities(elem->get_direction_priorities());
+ break;
+ }
+ }
+ bone_direction_transform->set_parent(godot_skeleton_aligned_transform);
+
+ float predamp = 1.0 - get_stiffness();
+ dampening = get_parent().is_null() ? Math_PI : predamp * p_default_dampening;
+ float iterations = p_many_bone_ik->get_iterations_per_frame();
+ if (get_constraint().is_null()) {
+ Ref new_constraint;
+ new_constraint.instantiate();
+ add_constraint(new_constraint);
+ }
+ float returnfulness = get_constraint()->get_resistance();
+ float falloff = 0.2f;
+ half_returnfulness_dampened.resize(iterations);
+ cos_half_returnfulness_dampened.resize(iterations);
+ float iterations_pow = Math::pow(iterations, falloff * iterations * returnfulness);
+ for (float i = 0; i < iterations; i++) {
+ float iteration_scalar = ((iterations_pow)-Math::pow(i, falloff * iterations * returnfulness)) / (iterations_pow);
+ float iteration_return_clamp = iteration_scalar * returnfulness * dampening;
+ float cos_iteration_return_clamp = Math::cos(iteration_return_clamp / 2.0);
+ half_returnfulness_dampened.write[i] = iteration_return_clamp;
+ cos_half_returnfulness_dampened.write[i] = cos_iteration_return_clamp;
+ }
+}
+
+float IKBone3D::get_cos_half_dampen() const {
+ return cos_half_dampen;
+}
+
+void IKBone3D::set_cos_half_dampen(float p_cos_half_dampen) {
+ cos_half_dampen = p_cos_half_dampen;
+}
+
+Ref IKBone3D::get_constraint() const {
+ return constraint;
+}
+
+void IKBone3D::add_constraint(Ref p_constraint) {
+ constraint = p_constraint;
+}
+
+Ref IKBone3D::get_ik_transform() {
+ return godot_skeleton_aligned_transform;
+}
+
+Ref IKBone3D::get_constraint_orientation_transform() {
+ return constraint_orientation_transform;
+}
+
+Ref IKBone3D::get_constraint_twist_transform() {
+ return constraint_twist_transform;
+}
+
+void IKBone3D::set_constraint_orientation_transform(Ref p_transform) {
+ constraint_orientation_transform = p_transform;
+}
+
+void IKBone3D::set_bone_direction_transform(Ref p_bone_direction) {
+ bone_direction_transform = p_bone_direction;
+}
+
+Ref IKBone3D::get_bone_direction_transform() {
+ return bone_direction_transform;
+}
+
+bool IKBone3D::is_orientationally_constrained() {
+ if (get_constraint().is_null()) {
+ return false;
+ }
+ return get_constraint()->is_orientationally_constrained();
+}
+
+bool IKBone3D::is_axially_constrained() {
+ if (get_constraint().is_null()) {
+ return false;
+ }
+ return get_constraint()->is_axially_constrained();
+}
+
+Vector &IKBone3D::get_cos_half_returnfullness_dampened() {
+ return cos_half_returnfulness_dampened;
+}
+
+void IKBone3D::set_cos_half_returnfullness_dampened(const Vector &p_value) {
+ cos_half_returnfulness_dampened = p_value;
+}
+
+Vector &IKBone3D::get_half_returnfullness_dampened() {
+ return half_returnfulness_dampened;
+}
+
+void IKBone3D::set_half_returnfullness_dampened(const Vector &p_value) {
+ half_returnfulness_dampened = p_value;
+}
+
+void IKBone3D::set_stiffness(double p_stiffness) {
+ stiffness = p_stiffness;
+}
+
+double IKBone3D::get_stiffness() const {
+ return stiffness;
+}
+
+Transform3D IKBone3D::get_parent_bone_aligned_transform() {
+ Ref parent_bone = get_parent();
+ if (parent_bone.is_null()) {
+ return Transform3D();
+ }
+ Transform3D parent_bone_aligned_transform = parent_bone->get_ik_transform()->get_global_transform();
+ parent_bone_aligned_transform.origin = get_bone_direction_transform()->get_global_transform().origin;
+ return parent_bone_aligned_transform;
+}
+
+Transform3D IKBone3D::get_set_constraint_twist_transform() const {
+ return constraint_orientation_transform->get_global_transform();
+}
+
+float IKBone3D::calculate_total_radius_sum(const TypedArray &p_cones) const {
+ float total_radius_sum = 0.0f;
+ for (int32_t i = 0; i < p_cones.size(); ++i) {
+ const Ref &cone = p_cones[i];
+ if (cone.is_null()) {
+ break;
+ }
+ total_radius_sum += cone->get_radius();
+ }
+ return total_radius_sum;
+}
+
+Vector3 IKBone3D::calculate_weighted_direction(const TypedArray &p_cones, float p_total_radius_sum) const {
+ Vector3 direction = Vector3();
+ for (int32_t i = 0; i < p_cones.size(); ++i) {
+ const Ref &cone = p_cones[i];
+ if (cone.is_null()) {
+ break;
+ }
+ float weight = cone->get_radius() / p_total_radius_sum;
+ direction += cone->get_control_point() * weight;
+ }
+ direction.normalize();
+ direction = constraint_orientation_transform->get_global_transform().basis.xform(direction);
+ return direction;
+}
diff --git a/modules/many_bone_ik/src/ik_bone_3d.h b/modules/many_bone_ik/src/ik_bone_3d.h
new file mode 100644
index 000000000000..17b6062f6771
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_bone_3d.h
@@ -0,0 +1,122 @@
+/**************************************************************************/
+/* ik_bone_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef IK_BONE_3D_H
+#define IK_BONE_3D_H
+
+#include "ik_effector_template_3d.h"
+#include "ik_kusudama_3d.h"
+#include "ik_open_cone_3d.h"
+#include "math/ik_node_3d.h"
+
+#include "core/io/resource.h"
+#include "core/object/ref_counted.h"
+#include "scene/3d/skeleton_3d.h"
+
+class IKEffector3D;
+class ManyBoneIK3D;
+class IKBone3D;
+
+class IKBone3D : public Resource {
+ GDCLASS(IKBone3D, Resource);
+
+ BoneId bone_id = -1;
+ Ref parent;
+ Vector][> children;
+ Ref pin;
+
+ float default_dampening = Math_PI;
+ float dampening = get_parent().is_null() ? Math_PI : default_dampening;
+ float cos_half_dampen = Math::cos(dampening / 2.0f);
+ double cos_half_return_damp = 0.0f;
+ double return_damp = 0.0f;
+ Vector cos_half_returnfulness_dampened;
+ Vector half_returnfulness_dampened;
+ double stiffness = 0.0;
+ Ref constraint;
+ // In the space of the local parent bone transform.
+ // The origin is the origin of the bone direction transform
+ // Can be independent and should be calculated
+ // to keep -y to be the opposite of its bone forward orientation
+ // To avoid singularity that is ambiguous.
+ Ref constraint_orientation_transform = Ref(memnew(IKNode3D()));
+ Ref constraint_twist_transform = Ref(memnew(IKNode3D()));
+ Ref godot_skeleton_aligned_transform = Ref(memnew(IKNode3D())); // The bone's actual transform.
+ Ref bone_direction_transform = Ref(memnew(IKNode3D())); // Physical direction of the bone. Calculate Y is the bone up.
+
+protected:
+ static void _bind_methods();
+
+public:
+ Vector &get_cos_half_returnfullness_dampened();
+ void set_cos_half_returnfullness_dampened(const Vector &p_value);
+ Vector &get_half_returnfullness_dampened();
+ void set_half_returnfullness_dampened(const Vector &p_value);
+ void set_stiffness(double p_stiffness);
+ double get_stiffness() const;
+ bool is_axially_constrained();
+ bool is_orientationally_constrained();
+ Transform3D get_bone_direction_global_pose() const;
+ Ref get_bone_direction_transform();
+ void set_bone_direction_transform(Ref p_bone_direction);
+ void update_default_bone_direction_transform(Skeleton3D *p_skeleton);
+ void set_constraint_orientation_transform(Ref p_transform);
+ Ref get_constraint_orientation_transform();
+ Ref get_constraint_twist_transform();
+ void update_default_constraint_transform();
+ void add_constraint(Ref p_constraint);
+ Ref get_constraint() const;
+ void set_bone_id(BoneId p_bone_id, Skeleton3D *p_skeleton = nullptr);
+ BoneId get_bone_id() const;
+ void set_parent(const Ref &p_parent);
+ Ref get_parent() const;
+ void set_pin(const Ref &p_pin);
+ Ref get_pin() const;
+ void set_global_pose(const Transform3D &p_transform);
+ Transform3D get_global_pose() const;
+ void set_pose(const Transform3D &p_transform);
+ Transform3D get_pose() const;
+ void set_initial_pose(Skeleton3D *p_skeleton);
+ void set_skeleton_bone_pose(Skeleton3D *p_skeleton);
+ void create_pin();
+ bool is_pinned() const;
+ Ref get_ik_transform();
+ IKBone3D() {}
+ IKBone3D(StringName p_bone, Skeleton3D *p_skeleton, const Ref &p_parent, Vector][> &p_pins, float p_default_dampening = Math_PI, ManyBoneIK3D *p_many_bone_ik = nullptr);
+ ~IKBone3D() {}
+ float get_cos_half_dampen() const;
+ void set_cos_half_dampen(float p_cos_half_dampen);
+ Transform3D get_parent_bone_aligned_transform();
+ Transform3D get_set_constraint_twist_transform() const;
+ float calculate_total_radius_sum(const TypedArray &p_cones) const;
+ Vector3 calculate_weighted_direction(const TypedArray &p_cones, float p_total_radius_sum) const;
+};
+
+#endif // IK_BONE_3D_H
diff --git a/modules/many_bone_ik/src/ik_bone_segment_3d.cpp b/modules/many_bone_ik/src/ik_bone_segment_3d.cpp
new file mode 100644
index 000000000000..c0968f616b0c
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_bone_segment_3d.cpp
@@ -0,0 +1,427 @@
+/**************************************************************************/
+/* ik_bone_segment_3d.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "ik_bone_segment_3d.h"
+
+#include "core/string/string_builder.h"
+#include "ik_effector_3d.h"
+#include "ik_kusudama_3d.h"
+#include "many_bone_ik_3d.h"
+#include "scene/3d/skeleton_3d.h"
+
+Ref IKBoneSegment3D::get_root() const {
+ return root;
+}
+
+Ref IKBoneSegment3D::get_tip() const {
+ return tip;
+}
+
+bool IKBoneSegment3D::is_pinned() const {
+ ERR_FAIL_NULL_V(tip, false);
+ return tip->is_pinned();
+}
+
+Vector][> IKBoneSegment3D::get_child_segments() const {
+ return child_segments;
+}
+
+void IKBoneSegment3D::create_bone_list(Vector][> &p_list, bool p_recursive) const {
+ if (p_recursive) {
+ for (int32_t child_i = 0; child_i < child_segments.size(); child_i++) {
+ child_segments[child_i]->create_bone_list(p_list, p_recursive);
+ }
+ }
+ Ref current_bone = tip;
+ Vector][> list;
+ while (current_bone.is_valid()) {
+ list.push_back(current_bone);
+ if (current_bone == root) {
+ break;
+ }
+ current_bone = current_bone->get_parent();
+ }
+ p_list.append_array(list);
+}
+
+void IKBoneSegment3D::update_pinned_list(Vector> &r_weights) {
+ for (int32_t chain_i = 0; chain_i < child_segments.size(); chain_i++) {
+ Ref chain = child_segments[chain_i];
+ chain->update_pinned_list(r_weights);
+ }
+ if (is_pinned()) {
+ effector_list.push_back(tip->get_pin());
+ }
+ double motion_propagation_factor = is_pinned() ? tip->get_pin()->motion_propagation_factor : 1.0;
+ if (motion_propagation_factor > 0.0) {
+ for (Ref child : child_segments) {
+ effector_list.append_array(child->effector_list);
+ }
+ }
+}
+
+void IKBoneSegment3D::_update_optimal_rotation(Ref p_for_bone, double p_damp, bool p_translate, bool p_constraint_mode, int32_t current_iteration, int32_t total_iterations) {
+ ERR_FAIL_NULL(p_for_bone);
+ _update_target_headings(p_for_bone, &heading_weights, &target_headings);
+ _update_tip_headings(p_for_bone, &tip_headings);
+ _set_optimal_rotation(p_for_bone, &tip_headings, &target_headings, &heading_weights, p_damp, p_translate, p_constraint_mode);
+}
+
+Quaternion IKBoneSegment3D::clamp_to_cos_half_angle(Quaternion p_quat, double p_cos_half_angle) {
+ if (p_quat.w < 0.0) {
+ p_quat = p_quat * -1;
+ }
+ double previous_coefficient = (1.0 - (p_quat.w * p_quat.w));
+ if (p_cos_half_angle <= p_quat.w || previous_coefficient == 0.0) {
+ return p_quat;
+ } else {
+ double composite_coefficient = Math::sqrt((1.0 - (p_cos_half_angle * p_cos_half_angle)) / previous_coefficient);
+ p_quat.w = p_cos_half_angle;
+ p_quat.x *= composite_coefficient;
+ p_quat.y *= composite_coefficient;
+ p_quat.z *= composite_coefficient;
+ }
+ return p_quat;
+}
+
+float IKBoneSegment3D::_get_manual_msd(const PackedVector3Array &r_htip, const PackedVector3Array &r_htarget, const Vector &p_weights) {
+ float manual_RMSD = 0.0f;
+ float w_sum = 0.0f;
+ for (int i = 0; i < r_htarget.size(); i++) {
+ float x_d = r_htarget[i].x - r_htip[i].x;
+ float y_d = r_htarget[i].y - r_htip[i].y;
+ float z_d = r_htarget[i].z - r_htip[i].z;
+ float mag_sq = p_weights[i] * (x_d * x_d + y_d * y_d + z_d * z_d);
+ manual_RMSD += mag_sq;
+ w_sum += p_weights[i];
+ }
+ manual_RMSD /= w_sum * w_sum;
+ return manual_RMSD;
+}
+
+void IKBoneSegment3D::_set_optimal_rotation(Ref p_for_bone, PackedVector3Array *r_htip, PackedVector3Array *r_htarget, Vector *r_weights, float p_dampening, bool p_translate, bool p_constraint_mode, double current_iteration, double total_iterations) {
+ ERR_FAIL_NULL(p_for_bone);
+ ERR_FAIL_NULL(r_htip);
+ ERR_FAIL_NULL(r_htarget);
+ ERR_FAIL_NULL(r_weights);
+
+ _update_target_headings(p_for_bone, &heading_weights, &target_headings);
+ Transform3D prev_transform = p_for_bone->get_pose();
+ bool got_closer = true;
+ double bone_damp = p_for_bone->get_cos_half_dampen();
+ int i = 0;
+ do {
+ _update_tip_headings(p_for_bone, &tip_headings);
+ if (!p_constraint_mode) {
+ QCP qcp = QCP(evec_prec);
+ Basis rotation = qcp.weighted_superpose(*r_htip, *r_htarget, *r_weights, p_translate);
+ Vector3 translation = qcp.get_translation();
+ double dampening = (p_dampening != -1.0) ? p_dampening : bone_damp;
+ rotation = clamp_to_cos_half_angle(rotation.get_rotation_quaternion(), cos(dampening / 2.0));
+ if (current_iteration == 0) {
+ current_iteration = 0.0001;
+ }
+ rotation = rotation.slerp(p_for_bone->get_global_pose().basis, static_cast(total_iterations) / current_iteration);
+ p_for_bone->get_ik_transform()->rotate_local_with_global(rotation);
+ Transform3D result = Transform3D(p_for_bone->get_global_pose().basis, p_for_bone->get_global_pose().origin + translation);
+ p_for_bone->set_global_pose(result);
+ }
+ bool is_parent_valid = p_for_bone->get_parent().is_valid();
+ if (is_parent_valid && p_for_bone->is_orientationally_constrained()) {
+ p_for_bone->get_constraint()->snap_to_orientation_limit(p_for_bone->get_bone_direction_transform(), p_for_bone->get_ik_transform(), p_for_bone->get_constraint_orientation_transform(), bone_damp, p_for_bone->get_cos_half_dampen());
+ }
+ if (is_parent_valid && p_for_bone->is_axially_constrained()) {
+ p_for_bone->get_constraint()->set_snap_to_twist_limit(p_for_bone->get_bone_direction_transform(), p_for_bone->get_ik_transform(), p_for_bone->get_constraint_twist_transform(), bone_damp, p_for_bone->get_cos_half_dampen());
+ }
+ if (default_stabilizing_pass_count > 0) {
+ _update_tip_headings(p_for_bone, &tip_headings_uniform);
+ double current_msd = _get_manual_msd(tip_headings_uniform, target_headings, heading_weights);
+ if (current_msd <= previous_deviation * 1.0001) {
+ previous_deviation = current_msd;
+ got_closer = true;
+ break;
+ } else {
+ got_closer = false;
+ p_for_bone->set_pose(prev_transform);
+ }
+ }
+ i++;
+ } while (i < default_stabilizing_pass_count && !got_closer);
+
+ if (root == p_for_bone) {
+ previous_deviation = INFINITY;
+ }
+}
+
+void IKBoneSegment3D::_update_target_headings(Ref p_for_bone, Vector *r_weights, PackedVector3Array *r_target_headings) {
+ ERR_FAIL_NULL(p_for_bone);
+ ERR_FAIL_NULL(r_weights);
+ ERR_FAIL_NULL(r_target_headings);
+ int32_t last_index = 0;
+ for (int32_t effector_i = 0; effector_i < effector_list.size(); effector_i++) {
+ Ref effector = effector_list[effector_i];
+ if (effector.is_null()) {
+ continue;
+ }
+ last_index = effector->update_effector_target_headings(r_target_headings, last_index, p_for_bone, &heading_weights);
+ }
+}
+
+void IKBoneSegment3D::_update_tip_headings(Ref p_for_bone, PackedVector3Array *r_heading_tip) {
+ ERR_FAIL_NULL(r_heading_tip);
+ ERR_FAIL_NULL(p_for_bone);
+ int32_t last_index = 0;
+ for (int32_t effector_i = 0; effector_i < effector_list.size(); effector_i++) {
+ Ref effector = effector_list[effector_i];
+ if (effector.is_null()) {
+ continue;
+ }
+ last_index = effector->update_effector_tip_headings(r_heading_tip, last_index, p_for_bone);
+ }
+}
+
+void IKBoneSegment3D::segment_solver(const Vector &p_damp, float p_default_damp, bool p_constraint_mode, int32_t p_current_iteration, int32_t p_total_iteration) {
+ for (Ref child : child_segments) {
+ if (child.is_null()) {
+ continue;
+ }
+ child->segment_solver(p_damp, p_default_damp, p_constraint_mode, p_current_iteration, p_total_iteration);
+ }
+ bool is_translate = parent_segment.is_null();
+ if (is_translate) {
+ Vector damp = p_damp;
+ damp.fill(Math_PI);
+ _qcp_solver(damp, Math_PI, is_translate, p_constraint_mode, p_current_iteration, p_total_iteration);
+ return;
+ }
+ _qcp_solver(p_damp, p_default_damp, is_translate, p_constraint_mode, p_current_iteration, p_total_iteration);
+}
+
+void IKBoneSegment3D::_qcp_solver(const Vector &p_damp, float p_default_damp, bool p_translate, bool p_constraint_mode, int32_t p_current_iteration, int32_t p_total_iterations) {
+ for (Ref current_bone : bones) {
+ float damp = p_default_damp;
+ bool is_valid_access = !(unlikely((p_damp.size()) < 0 || (current_bone->get_bone_id()) >= (p_damp.size())));
+ if (is_valid_access) {
+ damp = p_damp[current_bone->get_bone_id()];
+ }
+ bool is_non_default_damp = p_default_damp < damp;
+ if (is_non_default_damp) {
+ damp = p_default_damp;
+ }
+ _update_optimal_rotation(current_bone, damp, p_translate, p_constraint_mode, p_current_iteration, p_total_iterations);
+ }
+}
+
+void IKBoneSegment3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("is_pinned"), &IKBoneSegment3D::is_pinned);
+ ClassDB::bind_method(D_METHOD("get_ik_bone", "bone"), &IKBoneSegment3D::get_ik_bone);
+}
+
+IKBoneSegment3D::IKBoneSegment3D(Skeleton3D *p_skeleton, StringName p_root_bone_name, Vector][> &p_pins, ManyBoneIK3D *p_many_bone_ik, const Ref &p_parent,
+ BoneId p_root, BoneId p_tip, int32_t p_stabilizing_pass_count) {
+ root = p_root;
+ tip = p_tip;
+ skeleton = p_skeleton;
+ root = Ref(memnew(IKBone3D(p_root_bone_name, p_skeleton, p_parent, p_pins, Math_PI, p_many_bone_ik)));
+ if (p_parent.is_valid()) {
+ root_segment = p_parent->root_segment;
+ } else {
+ root_segment = Ref(this);
+ }
+ root_segment->bone_map[root->get_bone_id()] = root;
+ if (p_parent.is_valid()) {
+ parent_segment = p_parent;
+ root->set_parent(p_parent->get_tip());
+ }
+ default_stabilizing_pass_count = p_stabilizing_pass_count;
+}
+
+void IKBoneSegment3D::_enable_pinned_descendants() {
+ pinned_descendants = true;
+}
+
+bool IKBoneSegment3D::_has_pinned_descendants() {
+ return pinned_descendants;
+}
+
+Ref IKBoneSegment3D::get_ik_bone(BoneId p_bone) const {
+ if (!bone_map.has(p_bone)) {
+ return Ref();
+ }
+ return bone_map[p_bone];
+}
+
+void IKBoneSegment3D::create_headings_arrays() {
+ Vector> penalty_array;
+ Vector][> new_pinned_bones;
+ recursive_create_penalty_array(this, penalty_array, new_pinned_bones, 1.0);
+ pinned_bones.resize(new_pinned_bones.size());
+ int32_t total_headings = 0;
+ for (const Vector ¤t_penalty_array : penalty_array) {
+ total_headings += current_penalty_array.size();
+ }
+ for (int32_t bone_i = 0; bone_i < new_pinned_bones.size(); bone_i++) {
+ pinned_bones.write[bone_i] = new_pinned_bones[bone_i];
+ }
+ target_headings.resize(total_headings);
+ tip_headings.resize(total_headings);
+ tip_headings_uniform.resize(total_headings);
+ heading_weights.resize(total_headings);
+ int currentHeading = 0;
+ for (const Vector ¤t_penalty_array : penalty_array) {
+ for (double ad : current_penalty_array) {
+ heading_weights.write[currentHeading] = ad;
+ target_headings.write[currentHeading] = Vector3();
+ tip_headings.write[currentHeading] = Vector3();
+ tip_headings_uniform.write[currentHeading] = Vector3();
+ currentHeading++;
+ }
+ }
+}
+
+void IKBoneSegment3D::recursive_create_penalty_array(Ref p_bone_segment, Vector> &r_penalty_array, Vector][> &r_pinned_bones, double p_falloff) {
+ if (p_falloff <= 0.0) {
+ return;
+ }
+
+ double current_falloff = 1.0;
+
+ if (p_bone_segment->is_pinned()) {
+ Ref current_tip = p_bone_segment->get_tip();
+ Ref pin = current_tip->get_pin();
+ double weight = pin->get_weight();
+ Vector inner_weight_array;
+ inner_weight_array.push_back(weight * p_falloff);
+
+ double max_pin_weight = MAX(MAX(pin->get_direction_priorities().x, pin->get_direction_priorities().y), pin->get_direction_priorities().z);
+ max_pin_weight = max_pin_weight == 0.0 ? 1.0 : max_pin_weight;
+
+ for (int i = 0; i < 3; ++i) {
+ double priority = pin->get_direction_priorities()[i];
+ if (priority > 0.0) {
+ double sub_target_weight = weight * (priority / max_pin_weight) * p_falloff;
+ inner_weight_array.push_back(sub_target_weight);
+ inner_weight_array.push_back(sub_target_weight);
+ }
+ }
+
+ r_penalty_array.push_back(inner_weight_array);
+ r_pinned_bones.push_back(current_tip);
+ current_falloff = pin->get_motion_propagation_factor();
+ }
+
+ for (Ref s : p_bone_segment->get_child_segments()) {
+ recursive_create_penalty_array(s, r_penalty_array, r_pinned_bones, p_falloff * current_falloff);
+ }
+}
+
+void IKBoneSegment3D::recursive_create_headings_arrays_for(Ref p_bone_segment) {
+ p_bone_segment->create_headings_arrays();
+ for (Ref segments : p_bone_segment->get_child_segments()) {
+ recursive_create_headings_arrays_for(segments);
+ }
+}
+
+void IKBoneSegment3D::generate_default_segments(Vector][> &p_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik) {
+ Ref current_tip = root;
+ Vector children;
+
+ while (!_is_parent_of_tip(current_tip, p_tip_bone)) {
+ children = skeleton->get_bone_children(current_tip->get_bone_id());
+
+ if (children.is_empty() || _has_multiple_children_or_pinned(children, current_tip)) {
+ _process_children(children, current_tip, p_pins, p_root_bone, p_tip_bone, p_many_bone_ik);
+ break;
+ } else {
+ Vector::Iterator bone_id_iterator = children.begin();
+ current_tip = _create_next_bone(*bone_id_iterator, current_tip, p_pins, p_many_bone_ik);
+ }
+ }
+
+ _finalize_segment(current_tip);
+}
+
+bool IKBoneSegment3D::_is_parent_of_tip(Ref p_current_tip, BoneId p_tip_bone) {
+ return skeleton->get_bone_parent(p_current_tip->get_bone_id()) >= p_tip_bone && p_tip_bone != -1;
+}
+
+bool IKBoneSegment3D::_has_multiple_children_or_pinned(Vector &r_children, Ref p_current_tip) {
+ return r_children.size() > 1 || p_current_tip->is_pinned();
+}
+
+void IKBoneSegment3D::_process_children(Vector &r_children, Ref p_current_tip, Vector][> &r_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik) {
+ tip = p_current_tip;
+ Ref parent(this);
+
+ for (int32_t child_i = 0; child_i < r_children.size(); child_i++) {
+ BoneId child_bone = r_children[child_i];
+ String child_name = skeleton->get_bone_name(child_bone);
+ Ref child_segment = _create_child_segment(child_name, r_pins, p_root_bone, p_tip_bone, p_many_bone_ik, parent);
+
+ child_segment->generate_default_segments(r_pins, p_root_bone, p_tip_bone, p_many_bone_ik);
+
+ if (child_segment->_has_pinned_descendants()) {
+ _enable_pinned_descendants();
+ child_segments.push_back(child_segment);
+ }
+ }
+}
+
+Ref IKBoneSegment3D::_create_child_segment(String &p_child_name, Vector][> &p_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik, Ref &p_parent) {
+ return Ref(memnew(IKBoneSegment3D(skeleton, p_child_name, p_pins, p_many_bone_ik, p_parent, p_root_bone, p_tip_bone)));
+}
+
+Ref IKBoneSegment3D::_create_next_bone(BoneId p_bone_id, Ref p_current_tip, Vector][> &p_pins, ManyBoneIK3D *p_many_bone_ik) {
+ String bone_name = skeleton->get_bone_name(p_bone_id);
+ Ref next_bone = Ref(memnew(IKBone3D(bone_name, skeleton, p_current_tip, p_pins, p_many_bone_ik->get_default_damp(), p_many_bone_ik)));
+ root_segment->bone_map[p_bone_id] = next_bone;
+
+ return next_bone;
+}
+
+void IKBoneSegment3D::_finalize_segment(Ref p_current_tip) {
+ tip = p_current_tip;
+
+ if (tip->is_pinned()) {
+ _enable_pinned_descendants();
+ }
+
+ StringBuilder name_builder;
+ name_builder.append("IKBoneSegment");
+ name_builder.append(root->get_name());
+ name_builder.append("Root");
+ name_builder.append(tip->get_name());
+ name_builder.append("Tip");
+
+ String ik_bone_name = name_builder.as_string();
+ set_name(ik_bone_name);
+ bones.clear();
+ create_bone_list(bones, false);
+}
diff --git a/modules/many_bone_ik/src/ik_bone_segment_3d.h b/modules/many_bone_ik/src/ik_bone_segment_3d.h
new file mode 100644
index 000000000000..e5d33c54ce2c
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_bone_segment_3d.h
@@ -0,0 +1,105 @@
+/**************************************************************************/
+/* ik_bone_segment_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef IK_BONE_SEGMENT_3D_H
+#define IK_BONE_SEGMENT_3D_H
+
+#include "ik_bone_3d.h"
+#include "ik_effector_3d.h"
+#include "ik_effector_template_3d.h"
+#include "math/qcp.h"
+#include "scene/3d/skeleton_3d.h"
+
+#include "core/io/resource.h"
+#include "core/object/ref_counted.h"
+
+class IKEffector3D;
+class IKBone3D;
+class IKLimitCone3D;
+
+class IKBoneSegment3D : public Resource {
+ GDCLASS(IKBoneSegment3D, Resource);
+ Ref root;
+ Ref tip;
+ Vector][> bones;
+ Vector][> pinned_bones;
+ Vector][> child_segments; // Contains only direct child chains that end with effectors or have child that end with effectors
+ Ref parent_segment;
+ Ref root_segment;
+ Vector][> effector_list;
+ PackedVector3Array target_headings;
+ PackedVector3Array tip_headings;
+ PackedVector3Array tip_headings_uniform;
+ Vector heading_weights;
+ Skeleton3D *skeleton = nullptr;
+ bool pinned_descendants = false;
+ double previous_deviation = INFINITY;
+ int32_t default_stabilizing_pass_count = 0; // Move to the stabilizing pass to the ik solver. Set it free.
+ bool _has_pinned_descendants();
+ void _enable_pinned_descendants();
+ void _update_target_headings(Ref p_for_bone, Vector *r_weights, PackedVector3Array *r_htarget);
+ void _update_tip_headings(Ref p_for_bone, PackedVector3Array *r_heading_tip);
+ void _set_optimal_rotation(Ref p_for_bone, PackedVector3Array *r_htip, PackedVector3Array *r_heading_tip, Vector *r_weights, float p_dampening = -1, bool p_translate = false, bool p_constraint_mode = false, double current_iteration = 0, double total_iterations = 0);
+ void _qcp_solver(const Vector &p_damp, float p_default_damp, bool p_translate, bool p_constraint_mode, int32_t p_current_iteration, int32_t p_total_iterations);
+ void _update_optimal_rotation(Ref p_for_bone, double p_damp, bool p_translate, bool p_constraint_mode, int32_t current_iteration, int32_t total_iterations);
+ float _get_manual_msd(const PackedVector3Array &r_htip, const PackedVector3Array &r_htarget, const Vector &p_weights);
+ HashMap> bone_map;
+ bool _is_parent_of_tip(Ref p_current_tip, BoneId p_tip_bone);
+ bool _has_multiple_children_or_pinned(Vector &r_children, Ref p_current_tip);
+ void _process_children(Vector &r_children, Ref p_current_tip, Vector][> &r_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik);
+ Ref _create_child_segment(String &p_child_name, Vector][> &p_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik, Ref &p_parent);
+ Ref _create_next_bone(BoneId p_bone_id, Ref p_current_tip, Vector][> &p_pins, ManyBoneIK3D *p_many_bone_ik);
+ void _finalize_segment(Ref p_current_tip);
+
+protected:
+ static void _bind_methods();
+
+public:
+ const double evec_prec = static_cast(1E-6);
+ void update_pinned_list(Vector> &r_weights);
+ static Quaternion clamp_to_cos_half_angle(Quaternion p_quat, double p_cos_half_angle);
+ static void recursive_create_headings_arrays_for(Ref p_bone_segment);
+ void create_headings_arrays();
+ void recursive_create_penalty_array(Ref p_bone_segment, Vector> &r_penalty_array, Vector][> &r_pinned_bones, double p_falloff);
+ void segment_solver(const Vector &p_damp, float p_default_damp, bool p_constraint_mode, int32_t p_current_iteration, int32_t p_total_iteration);
+ Ref get_root() const;
+ Ref get_tip() const;
+ bool is_pinned() const;
+ Vector][> get_child_segments() const;
+ void create_bone_list(Vector][> &p_list, bool p_recursive = false) const;
+ Ref get_ik_bone(BoneId p_bone) const;
+ void generate_default_segments(Vector][> &p_pins, BoneId p_root_bone, BoneId p_tip_bone, ManyBoneIK3D *p_many_bone_ik);
+ IKBoneSegment3D() {}
+ IKBoneSegment3D(Skeleton3D *p_skeleton, StringName p_root_bone_name, Vector][> &p_pins, ManyBoneIK3D *p_many_bone_ik, const Ref &p_parent = nullptr,
+ BoneId root = -1, BoneId tip = -1, int32_t p_stabilizing_pass_count = 0);
+ ~IKBoneSegment3D() {}
+};
+
+#endif // IK_BONE_SEGMENT_3D_H
diff --git a/modules/many_bone_ik/src/ik_effector_3d.cpp b/modules/many_bone_ik/src/ik_effector_3d.cpp
new file mode 100644
index 000000000000..0a225884f897
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_effector_3d.cpp
@@ -0,0 +1,183 @@
+/**************************************************************************/
+/* ik_effector_3d.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "ik_effector_3d.h"
+
+#include "core/typedefs.h"
+#include "ik_bone_3d.h"
+#include "many_bone_ik_3d.h"
+#include "math/ik_node_3d.h"
+#include "scene/3d/node_3d.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_data.h"
+#include "editor/editor_node.h"
+#endif
+
+void IKEffector3D::set_target_node(Skeleton3D *p_skeleton, const NodePath &p_target_node_path) {
+ ERR_FAIL_NULL(p_skeleton);
+ target_node_path = p_target_node_path;
+}
+
+NodePath IKEffector3D::get_target_node() const {
+ return target_node_path;
+}
+
+void IKEffector3D::set_target_node_rotation(bool p_use) {
+ use_target_node_rotation = p_use;
+}
+
+bool IKEffector3D::get_target_node_rotation() const {
+ return use_target_node_rotation;
+}
+
+Ref IKEffector3D::get_ik_bone_3d() const {
+ return for_bone;
+}
+
+bool IKEffector3D::is_following_translation_only() const {
+ return Math::is_zero_approx(direction_priorities.length_squared());
+}
+
+void IKEffector3D::set_direction_priorities(Vector3 p_direction_priorities) {
+ direction_priorities = p_direction_priorities;
+}
+
+Vector3 IKEffector3D::get_direction_priorities() const {
+ return direction_priorities;
+}
+
+void IKEffector3D::update_target_global_transform(Skeleton3D *p_skeleton, ManyBoneIK3D *p_many_bone_ik) {
+ ERR_FAIL_NULL(p_skeleton);
+ ERR_FAIL_NULL(for_bone);
+ Node3D *current_target_node = cast_to(p_many_bone_ik->get_node_or_null(target_node_path));
+ if (current_target_node && current_target_node->is_visible_in_tree()) {
+ target_relative_to_skeleton_origin = p_skeleton->get_global_transform().affine_inverse() * current_target_node->get_global_transform();
+ }
+}
+
+Transform3D IKEffector3D::get_target_global_transform() const {
+ return target_relative_to_skeleton_origin;
+}
+
+int32_t IKEffector3D::update_effector_target_headings(PackedVector3Array *p_headings, int32_t p_index, Ref p_for_bone, const Vector *p_weights) const {
+ ERR_FAIL_COND_V(p_index == -1, -1);
+ ERR_FAIL_NULL_V(p_headings, -1);
+ ERR_FAIL_NULL_V(p_for_bone, -1);
+ ERR_FAIL_NULL_V(p_weights, -1);
+
+ int32_t index = p_index;
+ Vector3 bone_origin_relative_to_skeleton_origin = for_bone->get_bone_direction_global_pose().origin;
+ p_headings->write[index] = target_relative_to_skeleton_origin.origin - bone_origin_relative_to_skeleton_origin;
+ index++;
+ Vector3 priority = get_direction_priorities();
+ for (int axis = Vector3::AXIS_X; axis <= Vector3::AXIS_Z; ++axis) {
+ if (priority[axis] > 0.0) {
+ real_t w = p_weights->get(index);
+ Vector3 column = target_relative_to_skeleton_origin.basis.get_column(axis);
+
+ p_headings->write[index] = (column + target_relative_to_skeleton_origin.origin) - bone_origin_relative_to_skeleton_origin;
+ p_headings->write[index] *= Vector3(w, w, w);
+ index++;
+ p_headings->write[index] = (target_relative_to_skeleton_origin.origin - column) - bone_origin_relative_to_skeleton_origin;
+ p_headings->write[index] *= Vector3(w, w, w);
+ index++;
+ }
+ }
+
+ return index;
+}
+
+int32_t IKEffector3D::update_effector_tip_headings(PackedVector3Array *p_headings, int32_t p_index, Ref p_for_bone) const {
+ ERR_FAIL_COND_V(p_index == -1, -1);
+ ERR_FAIL_NULL_V(p_headings, -1);
+ ERR_FAIL_NULL_V(p_for_bone, -1);
+
+ Transform3D tip_xform_relative_to_skeleton_origin = for_bone->get_bone_direction_global_pose();
+ Basis tip_basis = tip_xform_relative_to_skeleton_origin.basis;
+ Vector3 bone_origin_relative_to_skeleton_origin = p_for_bone->get_bone_direction_global_pose().origin;
+
+ int32_t index = p_index;
+ p_headings->write[index] = tip_xform_relative_to_skeleton_origin.origin - bone_origin_relative_to_skeleton_origin;
+ index++;
+ double distance = target_relative_to_skeleton_origin.origin.distance_to(bone_origin_relative_to_skeleton_origin);
+ double scale_by = MIN(distance, 1.0f);
+ const Vector3 priority = get_direction_priorities();
+
+ for (int axis = Vector3::AXIS_X; axis <= Vector3::AXIS_Z; ++axis) {
+ if (priority[axis] > 0.0) {
+ Vector3 column = tip_basis.get_column(axis) * priority[axis];
+
+ p_headings->write[index] = (column + tip_xform_relative_to_skeleton_origin.origin) - bone_origin_relative_to_skeleton_origin;
+ p_headings->write[index] *= scale_by;
+ index++;
+
+ p_headings->write[index] = (tip_xform_relative_to_skeleton_origin.origin - column) - bone_origin_relative_to_skeleton_origin;
+ p_headings->write[index] *= scale_by;
+ index++;
+ }
+ }
+
+ return index;
+}
+
+void IKEffector3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_target_node", "skeleton", "node"),
+ &IKEffector3D::set_target_node);
+ ClassDB::bind_method(D_METHOD("get_target_node"),
+ &IKEffector3D::get_target_node);
+ ClassDB::bind_method(D_METHOD("set_motion_propagation_factor", "amount"),
+ &IKEffector3D::set_motion_propagation_factor);
+ ClassDB::bind_method(D_METHOD("get_motion_propagation_factor"),
+ &IKEffector3D::get_motion_propagation_factor);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_propagation_factor"), "set_motion_propagation_factor", "get_motion_propagation_factor");
+}
+
+void IKEffector3D::set_weight(real_t p_weight) {
+ weight = p_weight;
+}
+
+real_t IKEffector3D::get_weight() const {
+ return weight;
+}
+
+IKEffector3D::IKEffector3D(const Ref &p_current_bone) {
+ ERR_FAIL_NULL(p_current_bone);
+ for_bone = p_current_bone;
+}
+
+void IKEffector3D::set_motion_propagation_factor(float p_motion_propagation_factor) {
+ motion_propagation_factor = CLAMP(p_motion_propagation_factor, 0.0, 1.0);
+}
+
+float IKEffector3D::get_motion_propagation_factor() const {
+ return motion_propagation_factor;
+}
diff --git a/modules/many_bone_ik/src/ik_effector_3d.h b/modules/many_bone_ik/src/ik_effector_3d.h
new file mode 100644
index 000000000000..c0c56e359244
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_effector_3d.h
@@ -0,0 +1,92 @@
+/**************************************************************************/
+/* ik_effector_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef IK_EFFECTOR_3D_H
+#define IK_EFFECTOR_3D_H
+
+#include "math/ik_node_3d.h"
+
+#include "core/object/ref_counted.h"
+#include "scene/3d/skeleton_3d.h"
+
+#define MIN_SCALE 0.1
+
+class ManyBoneIK3D;
+class IKBone3D;
+
+class IKEffector3D : public Resource {
+ GDCLASS(IKEffector3D, Resource);
+ friend class IKBone3D;
+ friend class IKBoneSegment3D;
+
+ Ref for_bone;
+ bool use_target_node_rotation = true;
+ NodePath target_node_path;
+ ObjectID target_node_cache;
+ Node *target_node_reference = nullptr;
+ bool target_static = false;
+ Transform3D target_transform;
+
+ Transform3D target_relative_to_skeleton_origin;
+ int32_t num_headings = 7;
+ // See IKEffectorTemplate to change the defaults.
+ real_t weight = 0.0;
+ real_t motion_propagation_factor = 0.0;
+ PackedVector3Array target_headings;
+ PackedVector3Array tip_headings;
+ Vector heading_weights;
+ Vector3 direction_priorities;
+
+protected:
+ static void _bind_methods();
+
+public:
+ IKEffector3D() = default;
+ void set_weight(real_t p_weight);
+ real_t get_weight() const;
+ void set_direction_priorities(Vector3 p_direction_priorities);
+ Vector3 get_direction_priorities() const;
+ void update_target_global_transform(Skeleton3D *p_skeleton, ManyBoneIK3D *p_modification = nullptr);
+ const float MAX_KUSUDAMA_OPEN_CONES = 30;
+ float get_motion_propagation_factor() const;
+ void set_motion_propagation_factor(float p_motion_propagation_factor);
+ void set_target_node(Skeleton3D *p_skeleton, const NodePath &p_target_node_path);
+ NodePath get_target_node() const;
+ Transform3D get_target_global_transform() const;
+ void set_target_node_rotation(bool p_use);
+ bool get_target_node_rotation() const;
+ Ref get_ik_bone_3d() const;
+ bool is_following_translation_only() const;
+ int32_t update_effector_target_headings(PackedVector3Array *p_headings, int32_t p_index, Ref p_for_bone, const Vector *p_weights) const;
+ int32_t update_effector_tip_headings(PackedVector3Array *p_headings, int32_t p_index, Ref p_for_bone) const;
+ IKEffector3D(const Ref &p_current_bone);
+};
+
+#endif // IK_EFFECTOR_3D_H
diff --git a/modules/many_bone_ik/src/ik_effector_template_3d.cpp b/modules/many_bone_ik/src/ik_effector_template_3d.cpp
new file mode 100644
index 000000000000..d9d8d5963dee
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_effector_template_3d.cpp
@@ -0,0 +1,83 @@
+/**************************************************************************/
+/* ik_effector_template_3d.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "ik_effector_template_3d.h"
+
+#include "many_bone_ik_3d.h"
+
+void IKEffectorTemplate3D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_root_bone"), &IKEffectorTemplate3D::get_root_bone);
+ ClassDB::bind_method(D_METHOD("set_root_bone", "target_node"), &IKEffectorTemplate3D::set_root_bone);
+
+ ClassDB::bind_method(D_METHOD("get_target_node"), &IKEffectorTemplate3D::get_target_node);
+ ClassDB::bind_method(D_METHOD("set_target_node", "target_node"), &IKEffectorTemplate3D::set_target_node);
+
+ ClassDB::bind_method(D_METHOD("get_motion_propagation_factor"), &IKEffectorTemplate3D::get_motion_propagation_factor);
+ ClassDB::bind_method(D_METHOD("set_motion_propagation_factor", "motion_propagation_factor"), &IKEffectorTemplate3D::set_motion_propagation_factor);
+
+ ClassDB::bind_method(D_METHOD("get_weight"), &IKEffectorTemplate3D::get_weight);
+ ClassDB::bind_method(D_METHOD("set_weight", "weight"), &IKEffectorTemplate3D::set_weight);
+
+ ClassDB::bind_method(D_METHOD("get_direction_priorities"), &IKEffectorTemplate3D::get_direction_priorities);
+ ClassDB::bind_method(D_METHOD("set_direction_priorities", "direction_priorities"), &IKEffectorTemplate3D::set_direction_priorities);
+
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "motion_propagation_factor"), "set_motion_propagation_factor", "get_motion_propagation_factor");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "weight"), "set_weight", "get_weight");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "direction_priorities"), "set_direction_priorities", "get_direction_priorities");
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "root_bone"), "set_root_bone", "get_root_bone");
+}
+
+NodePath IKEffectorTemplate3D::get_target_node() const {
+ return target_node;
+}
+
+void IKEffectorTemplate3D::set_target_node(NodePath p_node_path) {
+ target_node = p_node_path;
+}
+
+float IKEffectorTemplate3D::get_motion_propagation_factor() const {
+ return motion_propagation_factor;
+}
+
+void IKEffectorTemplate3D::set_motion_propagation_factor(float p_motion_propagation_factor) {
+ motion_propagation_factor = p_motion_propagation_factor;
+}
+
+IKEffectorTemplate3D::IKEffectorTemplate3D() {
+}
+
+String IKEffectorTemplate3D::get_root_bone() const {
+ return root_bone;
+}
+
+void IKEffectorTemplate3D::set_root_bone(String p_node_path) {
+ root_bone = p_node_path;
+}
diff --git a/modules/many_bone_ik/src/ik_effector_template_3d.h b/modules/many_bone_ik/src/ik_effector_template_3d.h
new file mode 100644
index 000000000000..a6f7add77f00
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_effector_template_3d.h
@@ -0,0 +1,64 @@
+/**************************************************************************/
+/* ik_effector_template_3d.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef IK_EFFECTOR_TEMPLATE_3D_H
+#define IK_EFFECTOR_TEMPLATE_3D_H
+
+#include "core/io/resource.h"
+#include "core/string/node_path.h"
+
+class IKEffectorTemplate3D : public Resource {
+ GDCLASS(IKEffectorTemplate3D, Resource);
+
+ StringName root_bone;
+ NodePath target_node;
+ bool target_static = false;
+ real_t motion_propagation_factor = 1.0f;
+ real_t weight = 0.0f;
+ Vector3 priority_direction = Vector3(0.2f, 0.0f, 0.2f); // Purported ideal values are 1.0 / 3.0 for one direction, 1.0 / 5.0 for two directions and 1.0 / 7.0 for three directions.
+protected:
+ static void _bind_methods();
+
+public:
+ String get_root_bone() const;
+ void set_root_bone(String p_root_bone);
+ NodePath get_target_node() const;
+ void set_target_node(NodePath p_node_path);
+ float get_motion_propagation_factor() const;
+ void set_motion_propagation_factor(float p_motion_propagation_factor);
+ real_t get_weight() const { return weight; }
+ void set_weight(real_t p_weight) { weight = p_weight; }
+ Vector3 get_direction_priorities() const { return priority_direction; }
+ void set_direction_priorities(Vector3 p_priority_direction) { priority_direction = p_priority_direction; }
+
+ IKEffectorTemplate3D();
+};
+
+#endif // IK_EFFECTOR_TEMPLATE_3D_H
diff --git a/modules/many_bone_ik/src/ik_kusudama_3d.cpp b/modules/many_bone_ik/src/ik_kusudama_3d.cpp
new file mode 100644
index 000000000000..d2bba623e197
--- /dev/null
+++ b/modules/many_bone_ik/src/ik_kusudama_3d.cpp
@@ -0,0 +1,427 @@
+/**************************************************************************/
+/* ik_kusudama_3d.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "ik_kusudama_3d.h"
+
+#include "core/math/quaternion.h"
+#include "ik_open_cone_3d.h"
+#include "math/ik_node_3d.h"
+
+void IKKusudama3D::_update_constraint(Ref p_limiting_axes) {
+ // Avoiding antipodal singularities by reorienting the axes.
+ Vector directions;
+
+ if (open_cones.size() == 1 && open_cones[0] != nullptr) {
+ directions.push_back(open_cones[0]->get_control_point());
+ } else {
+ for (int i = 0; i < open_cones.size() - 1; i++) {
+ if (open_cones[i] == nullptr || open_cones[i + 1] == nullptr) {
+ continue;
+ }
+
+ Vector3 this_control_point = open_cones[i]->get_control_point();
+ Vector3 next_control_point = open_cones[i + 1]->get_control_point();
+
+ Quaternion this_to_next = Quaternion(this_control_point, next_control_point);
+
+ Vector3 axis = this_to_next.get_axis();
+ double angle = this_to_next.get_angle() / 2.0;
+
+ Vector3 half_angle = this_control_point.rotated(axis, angle);
+ half_angle *= this_to_next.get_angle();
+ half_angle.normalize();
+
+ directions.push_back(half_angle);
+ }
+ }
+
+ Vector3 new_y;
+ for (Vector3 direction_vector : directions) {
+ new_y += direction_vector;
+ }
+
+ if (!directions.is_empty()) {
+ new_y /= directions.size();
+ new_y.normalize();
+ }
+
+ Transform3D new_y_ray = Transform3D(Basis(), new_y);
+ Quaternion old_y_to_new_y = Quaternion(p_limiting_axes->get_global_transform().get_basis().get_column(Vector3::AXIS_Y).normalized(), p_limiting_axes->get_global_transform().get_basis().xform(new_y_ray.origin).normalized());
+ p_limiting_axes->rotate_local_with_global(old_y_to_new_y);
+
+ for (Ref open_cone : open_cones) {
+ if (open_cone == nullptr) {
+ continue;
+ }
+
+ Vector3 control_point = open_cone->get_control_point();
+ open_cone->set_control_point(control_point.normalized());
+ }
+
+ update_tangent_radii();
+}
+
+void IKKusudama3D::update_tangent_radii() {
+ for (int i = 0; i < open_cones.size(); i++) {
+ Ref current = open_cones.write[i];
+ Ref next;
+ if (i < open_cones.size() - 1) {
+ next = open_cones.write[i + 1];
+ }
+ Ref cone = open_cones[i];
+ cone->update_tangent_handles(next);
+ }
+}
+
+void IKKusudama3D::set_axial_limits(real_t min_angle, real_t in_range) {
+ min_axial_angle = min_angle;
+ range_angle = in_range;
+ Vector3 y_axis = Vector3(0.0f, 1.0f, 0.0f);
+ Vector3 z_axis = Vector3(0.0f, 0.0f, 1.0f);
+ twist_min_rot = IKKusudama3D::get_quaternion_axis_angle(y_axis, min_axial_angle);
+ twist_min_vec = twist_min_rot.xform(z_axis).normalized();
+ twist_center_vec = twist_min_rot.xform(twist_min_vec).normalized();
+ twist_center_rot = Quaternion(z_axis, twist_center_vec);
+ twist_half_range_half_cos = Math::cos(in_range / real_t(4.0)); // For the quadrance angle. We need half the range angle since starting from the center, and half of that since quadrance takes cos(angle/2).
+ twist_max_vec = IKKusudama3D::get_quaternion_axis_angle(y_axis, in_range).xform(twist_min_vec).normalized();
+ twist_max_rot = Quaternion(z_axis, twist_max_vec);
+}
+
+void IKKusudama3D::set_snap_to_twist_limit(Ref p_bone_direction, Ref p_to_set, Ref p_constraint_axes, real_t p_dampening, real_t p_cos_half_dampen) {
+ if (!is_axially_constrained()) {
+ return;
+ }
+ Transform3D global_transform_constraint = p_constraint_axes->get_global_transform();
+ Transform3D global_transform_to_set = p_to_set->get_global_transform();
+ Basis parent_global_inverse = p_to_set->get_parent()->get_global_transform().basis.inverse();
+ Basis global_twist_center = global_transform_constraint.basis * twist_center_rot;
+ Basis align_rot = (global_twist_center.inverse() * global_transform_to_set.basis).orthonormalized();
+ Quaternion twist_rotation, swing_rotation; // Hold the ik transform's decomposed swing and twist away from global_twist_centers's global basis.
+ get_swing_twist(align_rot.get_rotation_quaternion(), Vector3(0, 1, 0), swing_rotation, twist_rotation);
+ twist_rotation = IKBoneSegment3D::clamp_to_cos_half_angle(twist_rotation, twist_half_range_half_cos);
+ Basis recomposition = (global_twist_center * (swing_rotation * twist_rotation)).orthonormalized();
+ Basis rotation = parent_global_inverse * recomposition;
+ p_to_set->set_transform(Transform3D(rotation, p_to_set->get_transform().origin));
+}
+
+void IKKusudama3D::get_swing_twist(
+ Quaternion p_rotation,
+ Vector3 p_axis,
+ Quaternion &r_swing,
+ Quaternion &r_twist) {
+#ifdef MATH_CHECKS
+ ERR_FAIL_COND_MSG(!p_rotation.is_normalized(), "The quaternion must be normalized.");
+#endif
+ if (Math::is_zero_approx(p_axis.length_squared())) {
+ r_swing = Quaternion();
+ r_twist = Quaternion();
+ return;
+ }
+ Quaternion rotation = p_rotation;
+ if (rotation.w < real_t(0.0)) {
+ rotation *= -1;
+ }
+ Vector3 p = p_axis * (rotation.x * p_axis.x + rotation.y * p_axis.y + rotation.z * p_axis.z);
+ r_twist = Quaternion(p.x, p.y, p.z, rotation.w).normalized();
+ real_t d = Vector3(r_twist.x, r_twist.y, r_twist.z).dot(p_axis);
+ if (d < real_t(0.0)) {
+ r_twist *= real_t(-1.0);
+ }
+ r_swing = (rotation * r_twist.inverse()).normalized();
+}
+
+void IKKusudama3D::add_open_cone(
+ Ref p_cone) {
+ ERR_FAIL_COND(p_cone.is_null());
+ ERR_FAIL_COND(p_cone->get_attached_to().is_null());
+ open_cones.push_back(p_cone);
+ update_tangent_radii();
+}
+
+void IKKusudama3D::remove_open_cone(Ref limitCone) {
+ ERR_FAIL_COND(limitCone.is_null());
+ open_cones.erase(limitCone);
+}
+
+real_t IKKusudama3D::get_min_axial_angle() {
+ return min_axial_angle;
+}
+
+real_t IKKusudama3D::get_range_angle() {
+ return range_angle;
+}
+
+bool IKKusudama3D::is_axially_constrained() {
+ return axially_constrained;
+}
+
+bool IKKusudama3D::is_orientationally_constrained() {
+ return orientationally_constrained;
+}
+
+void IKKusudama3D::disable_orientational_limits() {
+ orientationally_constrained = false;
+}
+
+void IKKusudama3D::enable_orientational_limits() {
+ orientationally_constrained = true;
+}
+
+void IKKusudama3D::toggle_orientational_limits() {
+ orientationally_constrained = !orientationally_constrained;
+}
+
+void IKKusudama3D::disable_axial_limits() {
+ axially_constrained = false;
+}
+
+void IKKusudama3D::enable_axial_limits() {
+ axially_constrained = true;
+}
+
+void IKKusudama3D::toggle_axial_limits() {
+ axially_constrained = !axially_constrained;
+}
+
+bool IKKusudama3D::is_enabled() {
+ return axially_constrained || orientationally_constrained;
+}
+
+void IKKusudama3D::disable() {
+ axially_constrained = false;
+ orientationally_constrained = false;
+}
+
+void IKKusudama3D::enable() {
+ axially_constrained = true;
+ orientationally_constrained = true;
+}
+
+TypedArray IKKusudama3D::get_open_cones() const {
+ TypedArray cones;
+ for (Ref cone : open_cones) {
+ cones.append(cone);
+ }
+ return cones;
+}
+
+Vector3 IKKusudama3D::local_point_on_path_sequence(Vector3 p_in_point, Ref p_limiting_axes) {
+ double closest_point_dot = 0;
+ Vector3 point = p_limiting_axes->get_transform().xform(p_in_point);
+ point.normalize();
+ Vector3 result = point;
+
+ if (open_cones.size() == 1) {
+ Ref cone = open_cones[0];
+ result = cone->get_control_point();
+ } else {
+ for (int i = 0; i < open_cones.size() - 1; i++) {
+ Ref next_cone = open_cones[i + 1];
+ Ref]