diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..1df6c35b --- /dev/null +++ b/.clang-format @@ -0,0 +1,199 @@ +# Commented out parameters are those with the same value as base LLVM style. +# We can uncomment them if we want to change their value, or enforce the +# chosen value in case the base style changes (last sync: Clang 14.0). +--- +### General config, applies to all languages ### +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +# AlignArrayOfStructures: None +# AlignConsecutiveMacros: None +# AlignConsecutiveAssignments: None +# AlignConsecutiveBitFields: None +# AlignConsecutiveDeclarations: None +# AlignEscapedNewlines: Right +AlignOperands: DontAlign +AlignTrailingComments: false +# AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +# AllowShortEnumsOnASingleLine: true +# AllowShortBlocksOnASingleLine: Never +# AllowShortCaseLabelsOnASingleLine: false +# AllowShortFunctionsOnASingleLine: All +# AllowShortLambdasOnASingleLine: All +# AllowShortIfStatementsOnASingleLine: Never +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +# AlwaysBreakTemplateDeclarations: MultiLine +# AttributeMacros: +# - __capability +# BinPackArguments: true +# BinPackParameters: true +# BraceWrapping: +# AfterCaseLabel: false +# AfterClass: false +# AfterControlStatement: Never +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# AfterExternBlock: false +# BeforeCatch: false +# BeforeElse: false +# BeforeLambdaBody: false +# BeforeWhile: false +# IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +# BreakBeforeBinaryOperators: None +# BreakBeforeConceptDeclarations: true +# BreakBeforeBraces: Attach +# BreakBeforeInheritanceComma: false +# BreakInheritanceList: BeforeColon +# BreakBeforeTernaryOperators: true +# BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +# BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' +# QualifierAlignment: Leave +# CompactNamespaces: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +# DeriveLineEnding: true +# DerivePointerAlignment: false +# DisableFormat: false +# EmptyLineAfterAccessModifier: Never +# EmptyLineBeforeAccessModifier: LogicalBlock +# ExperimentalAutoDetectBinPacking: false +# PackConstructorInitializers: BinPack +ConstructorInitializerAllOnOneLineOrOnePerLine: true +# AllowAllConstructorInitializersOnNextLine: true +# FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IfMacros: +# - KJ_IF_MAYBE +# IncludeBlocks: Preserve +IncludeCategories: + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 +# IncludeIsMainRegex: '(Test)?$' +# IncludeIsMainSourceRegex: '' +# IndentAccessModifiers: false +IndentCaseLabels: true +# IndentCaseBlocks: false +# IndentGotoLabels: true +# IndentPPDirectives: None +# IndentExternBlock: AfterExternBlock +# IndentRequires: false +IndentWidth: 4 +# IndentWrappedFunctionNames: false +# InsertTrailingCommas: None +# JavaScriptQuotes: Leave +# JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +# LambdaBodyIndentation: Signature +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# PenaltyBreakAssignment: 2 +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakOpenParenthesis: 0 +# PenaltyBreakString: 1000 +# PenaltyBreakTemplateDeclaration: 10 +# PenaltyExcessCharacter: 1000000 +# PenaltyReturnTypeOnItsOwnLine: 60 +# PenaltyIndentedWhitespace: 0 +# PointerAlignment: Right +# PPIndentWidth: -1 +# ReferenceAlignment: Pointer +# ReflowComments: true +# RemoveBracesLLVM: false +# SeparateDefinitionBlocks: Leave +# ShortNamespaceLines: 1 +# SortIncludes: CaseSensitive +# SortJavaStaticImport: Before +# SortUsingDeclarations: true +# SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false +# SpaceAfterTemplateKeyword: true +# SpaceBeforeAssignmentOperators: true +# SpaceBeforeCaseColon: false +# SpaceBeforeCpp11BracedList: false +# SpaceBeforeCtorInitializerColon: true +# SpaceBeforeInheritanceColon: true +# SpaceBeforeParens: ControlStatements +# SpaceBeforeParensOptions: +# AfterControlStatements: true +# AfterForeachMacros: true +# AfterFunctionDefinitionName: false +# AfterFunctionDeclarationName: false +# AfterIfMacros: true +# AfterOverloadedOperator: false +# BeforeNonEmptyParentheses: false +# SpaceAroundPointerQualifiers: Default +# SpaceBeforeRangeBasedForLoopColon: true +# SpaceInEmptyBlock: false +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: Never +# SpacesInConditionalStatement: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +## Godot TODO: We'll want to use a min of 1, but we need to see how to fix +## our comment capitalization at the same time. +SpacesInLineCommentPrefix: + Minimum: 0 + Maximum: -1 +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +# SpaceBeforeSquareBrackets: false +# BitFieldColonSpacing: Both +# StatementAttributeLikeMacros: +# - Q_EMIT +# StatementMacros: +# - Q_UNUSED +# - QT_REQUIRE_VERSION +TabWidth: 4 +# UseCRLF: false +UseTab: Always +# WhitespaceSensitiveMacros: +# - STRINGIZE +# - PP_STRINGIZE +# - BOOST_PP_STRINGIZE +# - NS_SWIFT_NAME +# - CF_SWIFT_NAME +--- +### C++ specific config ### +Language: Cpp +Standard: c++17 +--- +### ObjC specific config ### +Language: ObjC +# ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +# ObjCBreakBeforeNestedBlockParam: true +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +--- +### Java specific config ### +Language: Java +# BreakAfterJavaFieldAnnotations: false +JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..23c3f9a5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.gradle,AndroidManifest.xml}] +indent_style = space +indent_size = 4 + +[{*.py,SConstruct,SCsub}] +indent_style = space +indent_size = 4 + +# YAML requires indentation with spaces instead of tabs. +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.gdignore b/.gdignore new file mode 100644 index 00000000..e69de29b diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..8ad74f78 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.github/workflows/build-addons.yml b/.github/workflows/build-addons.yml index 2d42feeb..4a01b7ca 100644 --- a/.github/workflows/build-addons.yml +++ b/.github/workflows/build-addons.yml @@ -14,7 +14,7 @@ on: workflow_dispatch: env: - LIBRARY_PATH: addons/MotionMatching/bin + LIBRARY_PATH: demo/addons/MotionMatching/ jobs: build: @@ -99,7 +99,7 @@ jobs: - identifier: linux-debug-single os: ubuntu-latest name: 🐧 Linux Debug (Single Precision) - runner: ubuntu-22.04 + runner: ubuntu-24.04 target: template_debug platform: linux arch: x86_64 @@ -109,7 +109,7 @@ jobs: - identifier: linux-debug-double os: ubuntu-latest name: 🐧 Linux Debug (Double Precision) - runner: ubuntu-22.04 + runner: ubuntu-24.04 target: template_debug platform: linux arch: x86_64 @@ -119,7 +119,7 @@ jobs: - identifier: linux-release-single os: ubuntu-latest name: 🐧 Linux Release (Single Precision) - runner: ubuntu-22.04 + runner: ubuntu-24.04 target: template_release platform: linux arch: x86_64 @@ -129,7 +129,7 @@ jobs: - identifier: linux-release-double os: ubuntu-latest name: 🐧 Linux Release (Double Precision) - runner: ubuntu-22.04 + runner: ubuntu-24.04 target: template_release platform: linux arch: x86_64 @@ -160,7 +160,7 @@ jobs: id: install-boost uses: MarkusJx/install-boost@v2.4.4 with: - boost_version: 1.81.0 + boost_version: 1.83.0 # The toolset used to compile boost, e.g. "msvc" toolset: ${{ matrix.toolset }} # optional, default is # The platform version boost was compiled on, e.g. "18.04" @@ -197,16 +197,15 @@ jobs: ${{ github.workspace }}/godot-cpp/include ${{ github.workspace }}/godot-cpp/gen key: ${{ runner.os }}-${{ env.SUBMODULE_HASH }} - - - name: Compile Godot Library - if: steps.cache-godot-cpp.outputs.cache-hit != 'true' - run: | - scons -j8 platform=${{ matrix.platform }} target=${{ matrix.target }} arch=${{ matrix.arch }} - working-directory: godot-cpp + + # - name: Compile Godot Library + # if: steps.cache-godot-cpp.outputs.cache-hit != 'true' + # run: | + # scons platform=${{ matrix.platform }} target=${{ matrix.target }} arch=${{ matrix.arch }} precision=${{ matrix.precision }} + # working-directory: godot-cpp - name: Compile Addon run: scons - disable_exceptions=false platform=${{ matrix.platform }} target=${{ matrix.target }} arch=${{ matrix.arch }} diff --git a/.gitignore b/.gitignore index 64bb3961..fc1c1522 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,53 @@ -# Import settings -*.import +# Godot 4+ specific ignores +.godot/ -# Prerequisites -*.d -# Compiled Object files -*.slo -*.lo -*.o -*.obj +demo/assets/** +!demo/assets/animations +!demo/assets/animations/.gitkeep +!demo/assets/resources +!demo/assets/resources/.gitkeep +!demo/assets/models/** +# Ignore library files but not the gdextension file +# demo/addons/MotionMatching/bin/* +!demo/addons/MotionMatching/editor/** +!*/libMotionMatching* +!demo/addons/MotionMatching/MotionMatching.gdextension +.sconsign*.dblite -# Precompiled Headers -*.gch -*.pch +src/gen/* -# Compiled Dynamic libraries -*.dylib -*.pdb -*.dll -*.ilk -*.exp +!*.gitkeep + +# Ignore custom.py +custom.py -# Fortran module files -*.mod -*.smod +# Ignore generated compile_commands.json +compile_commands.json -# Compiled Static libraries -*.lai -*.la -*.a +# Binaries +*.o +*.os +*.so +*.obj +*.bc +*.pyc +*.dblite +*.pdb *.lib +*.config +*.creator +*.creator.user +*.files +*.includes +*.idb +*.exp -# Executables -*.exe -*.out -*.app +# Other stuff +*.log -# Scons -.vscode/ -.sconsign.dblite +# VSCode +.vscode/* +!.vscode/extensions.json +.DS_Store diff --git a/.gitmodules b/.gitmodules index 05ae0f06..1c0d850a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "godot-cpp"] path = godot-cpp url = https://github.com/godotengine/godot-cpp.git - branch = master \ No newline at end of file + branch = 4.3 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..d1f137b6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "ms-vscode.cpptools-extension-pack", + "ms-python.python" + ] +} \ No newline at end of file diff --git a/addons/MotionMatching/LICENSE b/LICENSE.md similarity index 96% rename from addons/MotionMatching/LICENSE rename to LICENSE.md index ca5b0dde..3ca9011a 100644 --- a/addons/MotionMatching/LICENSE +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Remi123 +Copyright (c) 2024 Remi123 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ 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. +SOFTWARE. \ No newline at end of file diff --git a/SConstruct b/SConstruct index 57a8e0c6..a5d657b6 100644 --- a/SConstruct +++ b/SConstruct @@ -1,96 +1,99 @@ #!/usr/bin/env python -from glob import glob -from pathlib import Path -import fnmatch import os -try: - env = Environment() - print(env.ARGUMENTS) -except: - # Default tools with no platform defaults to gnu toolchain. - # We apply platform specific toolchains via our custom tools. - env = Environment(tools=["default"], PLATFORM="") +def normalize_path(val, env): + return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) -# TODO: Do not copy environment after godot-cpp/test is updated . -env = SConscript("godot-cpp/SConstruct") -# For some reason the -print(env["CPPDEFINES"]) -print(env["CXXFLAGS"]) -print(env["disable_exceptions"]) -if env["disable_exceptions"]: - if env.get("is_msvc", False): - env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) - env.Append(CXXFLAGS=["/EHsc"]) - else: - env.Append(CXXFLAGS=["-fno-exceptions"]) -elif env.get("is_msvc", False): - env.Append(CXXFLAGS=["/EHsc"]) - - -# Initial options inheriting from CLI args -opts = Variables([], ARGUMENTS) +def validate_parent_dir(key, val, env): + if not os.path.isdir(normalize_path(os.path.dirname(val), env)): + raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val))) + + +libname = "MotionMatching" +projectdir = "demo" + +localEnv = Environment(tools=["default"], PLATFORM="") + +customs = ["custom.py"] +customs = [os.path.abspath(path) for path in customs] + +opts = Variables(customs, ARGUMENTS) opts.Add("Boost_INCLUDE_DIR", "boost library include path", "") opts.Add("Boost_LIBRARY_DIRS", "boost library library path", "") opts.Add("precision","floating point precision","single") -opts.Update(env) +opts.Add( + BoolVariable( + key="compiledb", + help="Generate compilation DB (`compile_commands.json`) for external tools", + default=localEnv.get("compiledb", False), + ) +) +opts.Add( + PathVariable( + key="compiledb_file", + help="Path to a custom `compile_commands.json` file", + default=localEnv.get("compiledb_file", "compile_commands.json"), + validator=validate_parent_dir, + ) +) +opts.Update(localEnv) + +Help(opts.GenerateHelpText(localEnv)) + +env = localEnv.Clone() +env["compiledb"] = False + boost_path = Dir(env['Boost_INCLUDE_DIR']) -env["precision"] = env['precision'] -# Add Included files. -env.Append(CPPPATH=["src/","thirdparty/",boost_path]) +env.Tool("compilation_db") +compilation_db = env.CompilationDatabase( + normalize_path(localEnv["compiledb_file"], localEnv) +) +env.Alias("compiledb", compilation_db) -sources = [] -for root,dirnames,filenames in os.walk("./src/"): - for filename in fnmatch.filter(filenames,"*.cpp"): - print(os.path.join(root, filename)) - sources.append(Glob(os.path.join(root, filename))) -for root,dirnames,filenames in os.walk("./thirdparty/"): - for filename in fnmatch.filter(filenames,"*.cpp"): - sources.append(Glob(os.path.join(root, filename))) +# env.Append(gdextension_dir= ("gdextension_api_files/")) +env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs}) +# Require C++20 +if env.get("is_msvc", False): + wtmp = str(env["CXXFLAGS"]) + wtmp = wtmp.replace("/std:c++17","/std:c++20") + env.Replace(CXXFLAGS = wtmp) +else: + # env["CXXFLAGS"].Replace("-std=c++17","-std=c++20") + wtmp = str(env["CXXFLAGS"]) + wtmp = wtmp.replace("-std=c++17","-std=c++20") + env.Replace(CXXFLAGS = wtmp) +env.Append(CPPPATH=["src/",boost_path]) +sources = Glob("src/*.cpp") -# Find gdextension path even if the directory or extension is renamed (e.g. project/addons/example/example.gdextension). -# (extension_path,) = glob("project/addons/*/*.gdextension") +print(env["CXXFLAGS"]) -# Find the addon path (e.g. project/addons/example). -addon_path = "addons/MotionMatching/" +if env["target"] in ["editor", "template_debug"]: + doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")) + sources.append(doc_data) -# Find the project name from the gdextension file (e.g. example). -project_name = "MotionMatching" +file = "{}{}{}".format(libname, env["suffix"], env["SHLIBSUFFIX"]) -# TODO: Cache is disabled currently. -scons_cache_path = os.environ.get("SCONS_CACHE") -if scons_cache_path != None: - CacheDir(scons_cache_path) - print("Scons cache enabled... (path: '" + scons_cache_path + "')") +if env["platform"] == "macos" or env["platform"] == "ios": + platlibname = "{}.{}.{}".format(libname, env["platform"], env["target"]) + file = "{}.framework/{}".format(env["platform"], platlibname, platlibname) +libraryfile = "bin/{}/{}".format(env["platform"], file) +library = env.SharedLibrary( + libraryfile, + source=sources, +) -if env["platform"] == "macos": - library = env.SharedLibrary( - addon_path + "bin/lib{0}.{1}.{2}.framework/{0}.{1}.{2}".format( - project_name, - env["platform"], - env["target"], - ), - source=sources, - ) -else: - library = env.SharedLibrary( - addon_path + "bin/lib{}.{}.{}.{}{}".format( - project_name, - env["platform"], - env["target"], - env["arch"], - env["SHLIBSUFFIX"], - ), - source=sources, - ) +copy = env.InstallAs("{}/addons/{}/bin/{}/lib{}".format(projectdir,libname, env["platform"], file), library) -Default(library) +default_args = [library, copy] +if localEnv.get("compiledb", False): + default_args += [compilation_db] +Default(*default_args) diff --git a/addons/MotionMatching/MMEditorGizmoPlugin.gd b/addons/MotionMatching/MMEditorGizmoPlugin.gd deleted file mode 100644 index eefc2ae0..00000000 --- a/addons/MotionMatching/MMEditorGizmoPlugin.gd +++ /dev/null @@ -1,46 +0,0 @@ -# @tool - -# class_name MMEditorGizmoPlugin extends EditorNode3DGizmoPlugin - -# class MMGizmo extends EditorNode3DGizmo: -# var gizmo_size = 3.0 -# var lines := PackedVector3Array() - -# func _redraw(): -# clear() -# var node3d = get_node_3d() - -# var instance : MMGizmo = null - -# func _init(): -# create_material("white", Color.WHITE) -# create_material("blue", Color.BLUE) -# create_material("red", Color.RED) -# create_material("green", Color.GREEN) -# create_material("orange", Color.ORANGE_RED) - -# prints("MMEditorGizmoPlugin") - -# func _create_gizmo(node): -# if node is Skeleton3D: -# if instance == null: -# instance = MMGizmo.new() -# return instance -# else: -# return null - -# func _get_gizmo_name() -> String: -# return "MMGizmo" - -# func set_lines(lines:PackedVector3Array): -# instance.lines = lines -# instance._redraw() - - -# func _has_gizmo(node): -# return node.name is Skeleton3D - - -# func _redraw(gizmo : EditorNode3DGizmo): -# pass - diff --git a/addons/MotionMatching/MMEditorPanel.gd b/addons/MotionMatching/MMEditorPanel.gd deleted file mode 100644 index 3cab90bc..00000000 --- a/addons/MotionMatching/MMEditorPanel.gd +++ /dev/null @@ -1,316 +0,0 @@ -@tool -class_name MMEditorPanel extends Control - -var _current : MMAnimationLibrary = null - -var plugin_ref : EditorPlugin - -@onready var rd : RichTextLabel = $TabContainer/Data/ScrollContainer/PoseData - -# @onready var gizmo := preload("res://addons/MotionMatching/MMEditorGizmoPlugin.gd") # setup by the plugin - -@onready var choose_anim :MenuButton= $TabContainer/Data/HBoxContainer/MarginContainer5/ChooseAnimation - -@onready var infotext : RichTextLabel = $TabContainer/Info/PanelContainer/InfoText - -func update_info()->void: - $TabContainer/Info/MarginContainer4/HBoxContainer/PathText.text = _current.resource_path - - if _current: - - var nb_dim = _current.nb_dimensions - - if nb_dim == 0: - nb_dim = 1 - - choose_anim.get_popup().clear() - for a in _current.get_animation_list(): - print(a) - choose_anim.get_popup().add_item(a) - choose_anim.get_popup().id_pressed.connect(_on_choose_animation_pressed) - - - infotext.clear() - var info := [ ["Nb Features", _current.motion_features.size()], - ["Nb Dimension",nb_dim], - ["Nb Poses",_current.MotionData.size()/nb_dim], - ] - infotext.push_table(info.size()) - for t in info: - infotext.push_cell() - infotext.set_cell_padding(Rect2i(5,0,5,5)) - infotext.set_cell_border_color(Color.LIGHT_GRAY) - infotext.append_text(t[0]+":"+str(t[1])) - infotext.pop() - infotext.pop() - prints("Gos") - - infotext.append_text("\n\nDimensional informations:\n") - - infotext.push_table(nb_dim+1) - - - info = [ - ["Dimensions",range(nb_dim)], - ["Means",_current.means], - ["Variances",_current.variances], - ["Weights",_current.weights], - ] - - for i in info: - infotext.push_cell() - infotext.set_cell_padding(Rect2i(5,0,5,5)) - infotext.set_cell_border_color(Color.LIGHT_GRAY) - infotext.append_text(i[0]) - infotext.pop() - prints(i[0],i[1].size()) - for subinfo in i[1]: - infotext.push_cell() - infotext.set_cell_row_background_color(Color(0.212, 0.239, 0.29)/0.9,Color(0.212, 0.239, 0.29)*0.8) - infotext.set_cell_border_color(Color.LIGHT_GRAY) - if subinfo is float: - infotext.append_text("%*.*f" % [0,3, subinfo ]) - elif subinfo is int: - infotext.append_text(str(subinfo)) - infotext.pop() - - infotext.pop() - - - -func bake_data_current()->void: - if _current == null: - return - _current.bake_data() - update_info() - -func update_shown_pose_data(value: float) -> void: - assert(_current.nb_dimensions > 0) - - var nb_dim := _current.nb_dimensions - - var pose_index :int = value as int - - if pose_index > _current.MotionData.size()/nb_dim: - return - - var pose := _current.MotionData.slice(pose_index*nb_dim,pose_index*nb_dim+nb_dim) - #for i in range(pose.size()): - #pose[i] = pose[i] * _current.variances[i] + _current.means[i] - - rd.clear() - var animlib :AnimationLibrary= _current as AnimationLibrary - var anim_name := animlib.get_animation_list()[_current.db_anim_index[pose_index]] - var anim := animlib.get_animation(anim_name) - var anim_timestep := _current.db_anim_timestamp[pose_index] - var anim_cat := _current.db_anim_category[pose_index] - -# if anim_name == "Idle": -# var v := anim.find_track("MotionPlayer:loco_category",Animation.TYPE_VALUE) -# prints("Values interpolated",anim.value_track_interpolate(v,anim_timestep)) - -# _current.set_skeleton_to_pose(anim,anim_timestep) -# var skel := _current.get("skeleton_node_path") as Skeleton3D -# var tr := skel.get_bone_global_pose(skel.find_bone("Root")) -# tr = Transform3D() - - rd.push_table(4) - for t in [anim_name,"%*.*f" % [0,3, anim_timestep ], str(anim.length),str(anim_cat)]: - rd.push_cell() - rd.set_cell_padding(Rect2i(5,0,5,5)) - rd.set_cell_border_color(Color.LIGHT_GRAY) - rd.append_text(t) - rd.pop() - rd.pop() - rd.append_text("\n") - - rd.push_table(pose.size() + 1,INLINE_ALIGNMENT_CENTER) - -# var debug_lines := PackedVector3Array() -# gizmo.instance.clear() - -# # gizmo.create_material("main",Color.BLUE) - var global_offset := pose_index*nb_dim - var offset := 0 - for r in _current.motion_features: - var f :MotionFeature= r as MotionFeature - rd.push_cell() - rd.set_cell_row_background_color(Color(0.212, 0.239, 0.29),Color(0.212, 0.239, 0.29)/0.7) - rd.set_cell_border_color(Color.LIGHT_GRAY) - rd.add_text("Feature " + r.resource_name +' dim ' +str(r.get_dimension())+ ' ') - rd.pop() - for x in range(0,nb_dim): - rd.push_cell() - rd.set_cell_row_background_color(Color(0.212, 0.239, 0.29),Color(0.212, 0.239, 0.29)/0.7) - - if x < r.get_dimension(): - rd.set_cell_border_color(Color.LIGHT_SLATE_GRAY) - var showed_value := pose[offset+x]# (pose[offset+x] * _current.variances[offset+x]) + _current.means[offset+x] - rd.add_text("%*.*f" % [0,3, showed_value ]) - else: - rd.add_text("") - rd.pop() - - var showed_values := pose.slice(offset,offset+r.get_dimension()) - - -# r.debug_pose_gizmo(gizmo.instance,showed_values,tr ) - - offset += r.get_dimension() - - rd.pop() - - -@onready var s := $TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox -@onready var e := $TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox2 -@onready var dmax := $TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox3 -@onready var a := $TabContainer/Calculation/MarginContainer/HFlowContainer/Answer2 -func _max_der_calculate(x): - Spring - a.text = str(Spring.maximum_spring_velocity_to_halflife(s.value,e.value,dmax.value)) - - -func on_recalculate_weights()->void: - _current.recalculate_weights() - prints(_current.weights) -# var animlib := _current.animation_library as AnimationLibrary - update_info() - - - - -func on_look_for_similar_pressed() -> void: - var pose_index = $TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer/SpinBox.value - - - var nb_dim = 0 - for r in _current.motion_features: - var f :MotionFeature= r as MotionFeature - nb_dim += r.get_dimension() - prints(pose_index, nb_dim) - var query := _current.MotionData.slice(pose_index*nb_dim,pose_index*nb_dim+nb_dim) - var result := _current.check_query_results(query,10) - - for r in result: - prints(r) - -# pass # Replace with function body. - - - - -func _on_choose_animation_pressed(ID) -> void: - if not _current.get_animation_list().is_empty() : - var lib :AnimationLibrary = _current - choose_anim.text = choose_anim.get_popup().get_item_text(ID) - - if _current.db_anim_index.size() != 0: - var i :int= 0 - for index in _current.db_anim_index: - if index == ID: - $TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer/SpinBox.value = i - update_shown_pose_data(i) - break - i += 1 - -# pass # Replace with function body. - - -func _on_add_category() -> void: - for animname in _current.get_animation_list(): - var anim := _current.get_animation(animname) - var category_track := anim.find_track(_current.category_track_names[0],Animation.TYPE_VALUE) - if category_track == -1: - category_track = anim.add_track(Animation.TYPE_VALUE) - anim.track_set_path(category_track,_current.category_track_names[0]) - - anim.track_insert_key(category_track,0.0,0) - var max_time := min(anim.length*0.98,anim.length-0.3) - anim.track_insert_key(category_track,max_time,4294967295) - - anim.value_track_set_update_mode(category_track,Animation.UPDATE_DISCRETE) - anim.track_set_interpolation_type(category_track,Animation.INTERPOLATION_NEAREST) - -# pass # Replace with function body. -var fileDialog : EditorFileDialog -func add_animation(): - fileDialog = EditorFileDialog.new() - fileDialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILES - fileDialog.mode = EditorFileDialog.MODE_MAXIMIZED - fileDialog.access = EditorFileDialog.ACCESS_RESOURCES - fileDialog.file_selected.connect(on_file_selected) - fileDialog.files_selected.connect(on_files_selected) - fileDialog.dir_selected.connect(on_file_selected) - var viewport = plugin_ref.get_editor_interface().get_editor_main_screen() - viewport.add_child(fileDialog) - fileDialog.set_meta("_created_by", self) # needed so the script is not directly freed after the run function. Would disconnect all signals otherwise - fileDialog.popup(Rect2(0,0, 700, 500)) # Giving the dialog a predefined size - print("end") - pass - -func on_files_selected(filesname : PackedStringArray): - for path in filesname: - var anim_name = path.get_file().split(".")[0] - var file := load(path) - if file is Animation: - prints(anim_name,file) - if _current.add_animation(anim_name,file) != OK: - prints("Didn't add",anim_name) - if (fileDialog != null): - fileDialog.queue_free() # Dialog has to be freed in order for the script t - - - pass - -func on_file_selected(filename : String) : - print(filename) - if (fileDialog != null): - fileDialog.queue_free() # Dialog has to be freed in order for the script t - -# Note: passing a value for the type parameter causes a crash -static func get_child_of_type(node: Node, child_type, recursive := false): - for i in range(node.get_child_count()): - var child = node.get_child(i) - if is_instance_of(child, child_type): - return child - if recursive: - var child_node = get_child_of_type(child, child_type, recursive) - if child_node: - return child_node - - -func set_skeleton_to_pose(index:float): - # Find skeleton - var skeleton :Skeleton3D= get_child_of_type(EditorInterface.get_edited_scene_root(),Skeleton3D,true) - assert(skeleton != null,"Skeleton not found") - var nb_dim := _current.nb_dimensions - - var pose_index :int = index as int - - if pose_index > _current.MotionData.size()/nb_dim: - return - - var pose := _current.MotionData.slice(pose_index*nb_dim,pose_index*nb_dim+nb_dim) - for i in range(pose.size()): - pose[i] = pose[i] * _current.variances[i] + _current.means[i] - - var animlib :AnimationLibrary= _current as AnimationLibrary - var anim_name := animlib.get_animation_list()[_current.db_anim_index[pose_index]] - var anim := animlib.get_animation(anim_name) - var anim_timestep := _current.db_anim_timestamp[pose_index] - - for i in range(anim.get_track_count()): - var bone_id = skeleton.find_bone(anim.track_get_path(i).get_subname(0)) - if bone_id == -1: - continue - elif anim.track_get_type(i) == Animation.TYPE_POSITION_3D: - var position := anim.position_track_interpolate(i,anim_timestep) - skeleton.set_bone_pose_position(bone_id,position * skeleton.get_motion_scale()) - elif anim.track_get_type(i) == Animation.TYPE_ROTATION_3D: - var rotation := anim.rotation_track_interpolate(i,anim_timestep) - skeleton.set_bone_pose_rotation(bone_id,rotation) - elif anim.track_get_type(i) == Animation.TYPE_SCALE_3D: - var scale := anim.scale_track_interpolate(i,anim_timestep) - skeleton.set_bone_pose_scale(bone_id,scale) - - diff --git a/addons/MotionMatching/MMEditorPanel.tscn b/addons/MotionMatching/MMEditorPanel.tscn deleted file mode 100644 index 0a5ad391..00000000 --- a/addons/MotionMatching/MMEditorPanel.tscn +++ /dev/null @@ -1,237 +0,0 @@ -[gd_scene load_steps=2 format=3 uid="uid://cgx5x8caibsnl"] - -[ext_resource type="Script" path="res://addons/MotionMatching/MMEditorPanel.gd" id="1_n132c"] - -[node name="MMEditorPanel" type="Control"] -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_n132c") - -[node name="TabContainer" type="TabContainer" parent="."] -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 - -[node name="Info" type="VBoxContainer" parent="TabContainer"] -clip_contents = true -layout_mode = 2 - -[node name="MarginContainer4" type="MarginContainer" parent="TabContainer/Info"] -layout_mode = 2 -size_flags_vertical = 0 -theme_override_constants/margin_left = 20 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 20 -theme_override_constants/margin_bottom = 20 - -[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Info/MarginContainer4"] -layout_mode = 2 - -[node name="PathLabel" type="Label" parent="TabContainer/Info/MarginContainer4/HBoxContainer"] -layout_mode = 2 -text = "Path:" - -[node name="PathText" type="Label" parent="TabContainer/Info/MarginContainer4/HBoxContainer"] -layout_mode = 2 -text = " -" - -[node name="BakeButton" type="Button" parent="TabContainer/Info/MarginContainer4/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 10 -size_flags_vertical = 4 -text = "Bake Data" - -[node name="WeightButton" type="Button" parent="TabContainer/Info/MarginContainer4/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 8 -text = "Recalculate weights" - -[node name="PanelContainer" type="ScrollContainer" parent="TabContainer/Info"] -layout_mode = 2 -size_flags_vertical = 3 -follow_focus = true -horizontal_scroll_mode = 2 -vertical_scroll_mode = 2 - -[node name="InfoText" type="RichTextLabel" parent="TabContainer/Info/PanelContainer"] -layout_mode = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -size_flags_stretch_ratio = 10.0 -bbcode_enabled = true -fit_content = true -autowrap_mode = 0 -threaded = true - -[node name="Data" type="VBoxContainer" parent="TabContainer"] -visible = false -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Data"] -layout_mode = 2 - -[node name="MarginContainer" type="MarginContainer" parent="TabContainer/Data/HBoxContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 20 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 20 -theme_override_constants/margin_bottom = 20 - -[node name="MarginContainer2" type="MarginContainer" parent="TabContainer/Data/HBoxContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 20 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 20 -theme_override_constants/margin_bottom = 20 - -[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Data/HBoxContainer/MarginContainer2"] -layout_mode = 2 - -[node name="Label" type="Label" parent="TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer"] -layout_mode = 2 -text = "Show lign" - -[node name="SpinBox" type="SpinBox" parent="TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer"] -layout_mode = 2 -rounded = true -allow_greater = true - -[node name="MarginContainer3" type="MarginContainer" parent="TabContainer/Data/HBoxContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 20 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 20 -theme_override_constants/margin_bottom = 20 - -[node name="Button" type="Button" parent="TabContainer/Data/HBoxContainer/MarginContainer3"] -layout_mode = 2 -text = "Look for similar" - -[node name="MarginContainer5" type="MarginContainer" parent="TabContainer/Data/HBoxContainer"] -layout_mode = 2 -theme_override_constants/margin_left = 20 -theme_override_constants/margin_top = 20 -theme_override_constants/margin_right = 20 -theme_override_constants/margin_bottom = 20 - -[node name="ChooseAnimation" type="MenuButton" parent="TabContainer/Data/HBoxContainer/MarginContainer5"] -layout_mode = 2 -text = "Animation:" -flat = false - -[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Data"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="PoseData" type="RichTextLabel" parent="TabContainer/Data/ScrollContainer"] -clip_contents = false -custom_minimum_size = Vector2(0, 100) -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 2 -theme_override_colors/font_selected_color = Color(0, 0, 0, 1) -bbcode_enabled = true -text = "Select an index" -fit_content = true -scroll_following = true -autowrap_mode = 0 -context_menu_enabled = true -selection_enabled = true - -[node name="Calculation" type="HFlowContainer" parent="TabContainer"] -visible = false -layout_mode = 2 - -[node name="MarginContainer" type="MarginContainer" parent="TabContainer/Calculation"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="HFlowContainer" type="HFlowContainer" parent="TabContainer/Calculation/MarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Label2" type="Label" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -text = "Discover Halflife from Derivative - Start:" - -[node name="SpinBox" type="SpinBox" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -step = 0.1 -value = 1.0 -allow_greater = true -allow_lesser = true - -[node name="Label3" type="Label" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -text = "End -" - -[node name="SpinBox2" type="SpinBox" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -step = 0.1 -value = 1.0 -allow_greater = true -allow_lesser = true - -[node name="Label4" type="Label" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -text = "Max Derivative" - -[node name="SpinBox3" type="SpinBox" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -step = 0.1 -value = 1.0 -allow_greater = true -allow_lesser = true - -[node name="equal" type="Label" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 -text = "=" - -[node name="Answer2" type="Label" parent="TabContainer/Calculation/MarginContainer/HFlowContainer"] -layout_mode = 2 - -[node name="Animation" type="HFlowContainer" parent="TabContainer"] -visible = false -layout_mode = 2 - -[node name="MarginContainer" type="MarginContainer" parent="TabContainer/Animation"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_constants/margin_left = 10 -theme_override_constants/margin_top = 5 -theme_override_constants/margin_right = 10 -theme_override_constants/margin_bottom = 5 - -[node name="HFlowContainer" type="HFlowContainer" parent="TabContainer/Animation/MarginContainer"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="Button" type="Button" parent="TabContainer/Animation/MarginContainer/HFlowContainer"] -layout_mode = 2 -size_flags_horizontal = 2 -text = "Add Category Track" - -[node name="Button2" type="Button" parent="TabContainer/Animation/MarginContainer/HFlowContainer"] -layout_mode = 2 -size_flags_horizontal = 2 -text = "Select Animations to Add" - -[connection signal="pressed" from="TabContainer/Info/MarginContainer4/HBoxContainer/BakeButton" to="." method="bake_data_current"] -[connection signal="pressed" from="TabContainer/Info/MarginContainer4/HBoxContainer/WeightButton" to="." method="on_recalculate_weights"] -[connection signal="value_changed" from="TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer/SpinBox" to="." method="update_shown_pose_data"] -[connection signal="value_changed" from="TabContainer/Data/HBoxContainer/MarginContainer2/HBoxContainer/SpinBox" to="." method="set_skeleton_to_pose"] -[connection signal="pressed" from="TabContainer/Data/HBoxContainer/MarginContainer3/Button" to="." method="on_look_for_similar_pressed"] -[connection signal="value_changed" from="TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox" to="." method="_max_der_calculate"] -[connection signal="value_changed" from="TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox2" to="." method="_max_der_calculate"] -[connection signal="value_changed" from="TabContainer/Calculation/MarginContainer/HFlowContainer/SpinBox3" to="." method="_max_der_calculate"] -[connection signal="pressed" from="TabContainer/Animation/MarginContainer/HFlowContainer/Button" to="." method="_on_add_category"] -[connection signal="pressed" from="TabContainer/Animation/MarginContainer/HFlowContainer/Button2" to="." method="add_animation"] diff --git a/addons/MotionMatching/MMPlugin.gd b/addons/MotionMatching/MMPlugin.gd deleted file mode 100644 index 9041dc17..00000000 --- a/addons/MotionMatching/MMPlugin.gd +++ /dev/null @@ -1,55 +0,0 @@ -@tool -class_name MMPlugin -extends EditorPlugin - -var bottompanel :MMEditorPanel= preload("res://addons/MotionMatching/MMEditorPanel.tscn").instantiate() - -# const MMEditorGizmoPlugin :MMEditorGizmoPlugin= preload("res://addons/MotionMatching/MMEditorGizmoPlugin.gd") - -# var gizmo_plugin := MMEditorGizmoPlugin.new() - -var last_path := "" -var al :MMAnimationLibrary - -func _enter_tree() -> void: - pass - -func _get_plugin_icon() -> Texture2D: - return preload("res://addons/MotionMatching/icons/icon_mm.svg") - -func _input(event: InputEvent) -> void: - if event is InputEventMouseButton and event.is_released(): - if last_path != get_editor_interface().get_current_path(): - last_path = get_editor_interface().get_current_path() - remove_control_from_bottom_panel(bottompanel) - visibility() - - pass - -func _exit_tree() -> void: - remove_control_from_bottom_panel(bottompanel) - bottompanel.queue_free() - pass - -func _has_main_screen()->bool: - return false - -func visibility() -> void: - - var nodes :Array= get_editor_interface().get_selection().get_selected_nodes() - #var v = nodes.any(func(x):return x is MotionMatcher) - var l = ResourceLoader.load(get_editor_interface().get_current_path()) - if l is MMAnimationLibrary: - prints("Selected MMAL",l.resource_path) - al = l - bottompanel._current = al - bottompanel.plugin_ref = self - - add_control_to_bottom_panel(bottompanel,"MotionMatching") - make_bottom_panel_item_visible(bottompanel) - bottompanel.update_info() - else : - remove_control_from_bottom_panel(bottompanel) - - - diff --git a/addons/MotionMatching/bin/LICENSE b/addons/MotionMatching/bin/LICENSE deleted file mode 100644 index 691ccdd5..00000000 --- a/addons/MotionMatching/bin/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright: - * 2018 Christoph Dalitz and Jens Wilberg - Niederrhein University of Applied Sciences, - Institute for Pattern Recognition, - Reinarzstr. 49, 47805 Krefeld, Germany - - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/addons/MotionMatching/motionmatching.gdextension b/addons/MotionMatching/motionmatching.gdextension deleted file mode 100644 index b4569ed6..00000000 --- a/addons/MotionMatching/motionmatching.gdextension +++ /dev/null @@ -1,18 +0,0 @@ -[configuration] - -entry_symbol = "gdextension_motion_matching_init" -compatibility_minimum = 4.2 -reloadable = true - -[icons] -MMAnimationPlayer = "res://addons/MotionMatching/icons/icon_mmap.svg" -MMAnimationLibrary = "res://addons/MotionMatching/icons/icon_mmal.svg" - -[libraries] - -macos.debug = "bin/libMotionMatching.macos.template_debug.framework" -macos.release = "bin/libMotionMatching.macos.template_release.framework" -windows.debug.x86_64 = "bin/libMotionMatching.windows.template_debug.x86_64.dll" -windows.release.x86_64 = "bin/libMotionMatching.windows.template_release.x86_64.dll" -linux.debug.x86_64 = "bin/libMotionMatching.linux.template_debug.x86_64.so" -linux.release.x86_64 = "bin/libMotionMatching.linux.template_release.x86_64.so" diff --git a/addons/MotionMatching/resources/Humanoid.tres b/addons/MotionMatching/resources/Humanoid.tres deleted file mode 100644 index c4b5e753..00000000 --- a/addons/MotionMatching/resources/Humanoid.tres +++ /dev/null @@ -1,351 +0,0 @@ -[gd_resource type="SkeletonProfile" format=3 uid="uid://buuq2akfojjo1"] - -[resource] -root_bone = &"Root" -scale_base_bone = &"Hips" -group_size = 1 -bone_size = 50 -groups/0/group_name = &"" -bones/0/bone_name = &"Root" -bones/0/bone_parent = &"" -bones/0/tail_direction = 0 -bones/0/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) -bones/0/handle_offset = Vector2(0.499999, 0.874775) -bones/0/group = &"" -bones/0/require = false -bones/1/bone_name = &"Hips" -bones/1/bone_parent = &"Root" -bones/1/tail_direction = 0 -bones/1/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.876276, 1.41694e-07) -bones/1/handle_offset = Vector2(0.499999, 0.487001) -bones/1/group = &"" -bones/1/require = false -bones/2/bone_name = &"Spine" -bones/2/bone_parent = &"Hips" -bones/2/tail_direction = 0 -bones/2/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.32308e-09, 0.102766, -0.0155287) -bones/2/handle_offset = Vector2(0.499999, 0.441524) -bones/2/group = &"" -bones/2/require = false -bones/3/bone_name = &"Chest" -bones/3/bone_parent = &"Spine" -bones/3/tail_direction = 0 -bones/3/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.40261e-09, 0.181287, 0.00902051) -bones/3/handle_offset = Vector2(0.499999, 0.3613) -bones/3/group = &"" -bones/3/require = false -bones/4/bone_name = &"UpperChest" -bones/4/bone_parent = &"Chest" -bones/4/tail_direction = 0 -bones/4/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.97542e-09, 0.177117, -0.026154) -bones/4/handle_offset = Vector2(0.499999, 0.282921) -bones/4/group = &"" -bones/4/require = false -bones/5/bone_name = &"Neck" -bones/5/bone_parent = &"UpperChest" -bones/5/tail_direction = 0 -bones/5/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.34131e-09, 0.111566, 0.00849989) -bones/5/handle_offset = Vector2(0.499999, 0.233551) -bones/5/group = &"" -bones/5/require = false -bones/6/bone_name = &"Head" -bones/6/bone_parent = &"Neck" -bones/6/tail_direction = 0 -bones/6/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.70274e-10, 0.120101, 0.0193229) -bones/6/handle_offset = Vector2(0.499999, 0.180403) -bones/6/group = &"" -bones/6/require = false -bones/7/bone_name = &"Eyes" -bones/7/bone_parent = &"Head" -bones/7/tail_direction = 0 -bones/7/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.0808e-09, 0.0944761, 0.122722) -bones/7/handle_offset = Vector2(0.499999, 0.138595) -bones/7/group = &"" -bones/7/require = false -bones/8/bone_name = &"Eyebrows" -bones/8/bone_parent = &"Head" -bones/8/tail_direction = 0 -bones/8/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.36385e-09, 0.127669, 0.122722) -bones/8/handle_offset = Vector2(0.499999, 0.123906) -bones/8/group = &"" -bones/8/require = false -bones/9/bone_name = &"LeftShoulder" -bones/9/bone_parent = &"UpperChest" -bones/9/tail_direction = 0 -bones/9/reference_pose = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.0744716, 0.0546601, 0.0462001) -bones/9/handle_offset = Vector2(0.532955, 0.258733) -bones/9/group = &"" -bones/9/require = false -bones/10/bone_name = &"LeftUpperArm" -bones/10/bone_parent = &"LeftShoulder" -bones/10/tail_direction = 0 -bones/10/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0619603, 0.116518, -0.00171292) -bones/10/handle_offset = Vector2(0.584517, 0.259491) -bones/10/group = &"" -bones/10/require = false -bones/11/bone_name = &"LeftLowerArm" -bones/11/bone_parent = &"LeftUpperArm" -bones/11/tail_direction = 0 -bones/11/reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0.00478927, 0.338166, 0.0294515) -bones/11/handle_offset = Vector2(0.734164, 0.272524) -bones/11/group = &"" -bones/11/require = false -bones/12/bone_name = &"LeftHand" -bones/12/bone_parent = &"LeftLowerArm" -bones/12/tail_direction = 0 -bones/12/reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -2.11845e-07, 0.270476, 0.0190378) -bones/12/handle_offset = Vector2(0.853857, 0.272524) -bones/12/group = &"" -bones/12/require = false -bones/13/bone_name = &"LeftThumbMetacarpal" -bones/13/bone_parent = &"LeftHand" -bones/13/tail_direction = 0 -bones/13/reference_pose = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.0450557, 0.0351404, 0.0117073) -bones/13/handle_offset = Vector2(0.869407, 0.277705) -bones/13/group = &"" -bones/13/require = false -bones/14/bone_name = &"LeftThumbProximal" -bones/14/bone_parent = &"LeftThumbMetacarpal" -bones/14/tail_direction = 0 -bones/14/reference_pose = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, -0.0116278, 0.0625858, 0.00325962) -bones/14/handle_offset = Vector2(0.892839, 0.28285) -bones/14/group = &"" -bones/14/require = false -bones/15/bone_name = &"LeftThumbDistal" -bones/15/bone_parent = &"LeftThumbProximal" -bones/15/tail_direction = 0 -bones/15/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0256601, 0.048261, 0.01501) -bones/15/handle_offset = Vector2(0.914099, 0.294205) -bones/15/group = &"" -bones/15/require = false -bones/16/bone_name = &"LeftIndexProximal" -bones/16/bone_parent = &"LeftHand" -bones/16/tail_direction = 0 -bones/16/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0267265, 0.103965, -0.00398671) -bones/16/handle_offset = Vector2(0.899864, 0.27076) -bones/16/group = &"" -bones/16/require = false -bones/17/bone_name = &"LeftIndexIntermediate" -bones/17/bone_parent = &"LeftIndexProximal" -bones/17/tail_direction = 0 -bones/17/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00537508, 0.0405795, 0.000348719) -bones/17/handle_offset = Vector2(0.917821, 0.270914) -bones/17/group = &"" -bones/17/require = false -bones/18/bone_name = &"LeftIndexDistal" -bones/18/bone_parent = &"LeftIndexIntermediate" -bones/18/tail_direction = 0 -bones/18/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00407702, 0.0368905, 0.00773728) -bones/18/handle_offset = Vector2(0.934146, 0.274338) -bones/18/group = &"" -bones/18/require = false -bones/19/bone_name = &"IndexFinger_04" -bones/19/bone_parent = &"LeftIndexDistal" -bones/19/tail_direction = 0 -bones/19/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00203745, 0.0358255, 0.011876) -bones/19/handle_offset = Vector2(0.95, 0.279593) -bones/19/group = &"" -bones/19/require = false -bones/20/bone_name = &"LeftMiddleProximal" -bones/20/bone_parent = &"LeftHand" -bones/20/tail_direction = 0 -bones/20/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0328334, 0.0998258, 0.00115979) -bones/20/handle_offset = Vector2(0.898032, 0.273037) -bones/20/group = &"" -bones/20/require = false -bones/21/bone_name = &"LeftMiddleIntermediate" -bones/21/bone_parent = &"LeftMiddleProximal" -bones/21/tail_direction = 0 -bones/21/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00370724, 0.0420522, 0.00479883) -bones/21/handle_offset = Vector2(0.916641, 0.275161) -bones/21/group = &"" -bones/21/require = false -bones/22/bone_name = &"LeftMiddleDistal" -bones/22/bone_parent = &"LeftMiddleIntermediate" -bones/22/tail_direction = 0 -bones/22/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00168516, 0.0346668, 0.00545114) -bones/22/handle_offset = Vector2(0.931982, 0.277573) -bones/22/group = &"" -bones/22/require = false -bones/23/bone_name = &"Finger_04" -bones/23/bone_parent = &"LeftMiddleDistal" -bones/23/tail_direction = 0 -bones/23/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0055174, 0.027755, 0.0100588) -bones/23/handle_offset = Vector2(0.944265, 0.282024) -bones/23/group = &"" -bones/23/require = false -bones/24/bone_name = &"RightShoulder" -bones/24/bone_parent = &"UpperChest" -bones/24/tail_direction = 0 -bones/24/reference_pose = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.0744715, 0.0546637, 0.0462001) -bones/24/handle_offset = Vector2(0.467044, 0.258731) -bones/24/group = &"" -bones/24/require = false -bones/25/bone_name = &"RightUpperArm" -bones/25/bone_parent = &"RightShoulder" -bones/25/tail_direction = 0 -bones/25/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0619602, 0.116517, -0.00171945) -bones/25/handle_offset = Vector2(0.415482, 0.259492) -bones/25/group = &"" -bones/25/require = false -bones/26/bone_name = &"RightLowerArm" -bones/26/bone_parent = &"RightUpperArm" -bones/26/tail_direction = 0 -bones/26/reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.00478923, 0.338167, 0.0294501) -bones/26/handle_offset = Vector2(0.265835, 0.272525) -bones/26/group = &"" -bones/26/require = false -bones/27/bone_name = &"RightHand" -bones/27/bone_parent = &"RightLowerArm" -bones/27/tail_direction = 0 -bones/27/reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 2.1687e-07, 0.270476, 0.0190379) -bones/27/handle_offset = Vector2(0.146142, 0.272525) -bones/27/group = &"" -bones/27/require = false -bones/28/bone_name = &"RightThumbMetacarpal" -bones/28/bone_parent = &"RightHand" -bones/28/tail_direction = 0 -bones/28/reference_pose = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.0450557, 0.0351398, 0.01171) -bones/28/handle_offset = Vector2(0.130592, 0.277707) -bones/28/group = &"" -bones/28/require = false -bones/29/bone_name = &"RightThumbProximal" -bones/29/bone_parent = &"RightThumbMetacarpal" -bones/29/tail_direction = 0 -bones/29/reference_pose = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0.01162, 0.0625866, 0.00326014) -bones/29/handle_offset = Vector2(0.10716, 0.282849) -bones/29/group = &"" -bones/29/require = false -bones/30/bone_name = &"RightThumbDistal" -bones/30/bone_parent = &"RightThumbProximal" -bones/30/tail_direction = 0 -bones/30/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0256599, 0.0482608, 0.0150097) -bones/30/handle_offset = Vector2(0.0859001, 0.294204) -bones/30/group = &"" -bones/30/require = false -bones/31/bone_name = &"RightIndexProximal" -bones/31/bone_parent = &"RightHand" -bones/31/tail_direction = 0 -bones/31/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0267266, 0.103966, -0.0039897) -bones/31/handle_offset = Vector2(0.100135, 0.270759) -bones/31/group = &"" -bones/31/require = false -bones/32/bone_name = &"RightIndexIntermediate" -bones/32/bone_parent = &"RightIndexProximal" -bones/32/tail_direction = 0 -bones/32/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00537507, 0.0405792, 0.000349302) -bones/32/handle_offset = Vector2(0.0821775, 0.270914) -bones/32/group = &"" -bones/32/require = false -bones/33/bone_name = &"RightIndexDistal" -bones/33/bone_parent = &"RightIndexIntermediate" -bones/33/tail_direction = 0 -bones/33/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00407706, 0.0368902, 0.00774038) -bones/33/handle_offset = Vector2(0.0658526, 0.274339) -bones/33/group = &"" -bones/33/require = false -bones/34/bone_name = &"IndexFinger_04.001" -bones/34/bone_parent = &"RightIndexDistal" -bones/34/tail_direction = 0 -bones/34/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00203747, 0.0358229, 0.0118694) -bones/34/handle_offset = Vector2(0.05, 0.279591) -bones/34/group = &"" -bones/34/require = false -bones/35/bone_name = &"RightMiddleProximal" -bones/35/bone_parent = &"RightHand" -bones/35/tail_direction = 0 -bones/35/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0328334, 0.0998256, 0.00115992) -bones/35/handle_offset = Vector2(0.101967, 0.273038) -bones/35/group = &"" -bones/35/require = false -bones/36/bone_name = &"RightMiddleIntermediate" -bones/36/bone_parent = &"RightMiddleProximal" -bones/36/tail_direction = 0 -bones/36/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00370714, 0.0420522, 0.00479969) -bones/36/handle_offset = Vector2(0.0833578, 0.275162) -bones/36/group = &"" -bones/36/require = false -bones/37/bone_name = &"RightMiddleDistal" -bones/37/bone_parent = &"RightMiddleIntermediate" -bones/37/tail_direction = 0 -bones/37/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00168524, 0.0346668, 0.00545025) -bones/37/handle_offset = Vector2(0.0680169, 0.277574) -bones/37/group = &"" -bones/37/require = false -bones/38/bone_name = &"Finger_04.001" -bones/38/bone_parent = &"RightMiddleDistal" -bones/38/tail_direction = 0 -bones/38/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0055174, 0.0277533, 0.0100599) -bones/38/handle_offset = Vector2(0.0557353, 0.282025) -bones/38/group = &"" -bones/38/require = false -bones/39/bone_name = &"RightUpperLeg" -bones/39/bone_parent = &"Hips" -bones/39/tail_direction = 0 -bones/39/reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.0989671, -0.0447178, -0.020469) -bones/39/handle_offset = Vector2(0.456204, 0.50679) -bones/39/group = &"" -bones/39/require = false -bones/40/bone_name = &"RightLowerLeg" -bones/40/bone_parent = &"RightUpperLeg" -bones/40/tail_direction = 0 -bones/40/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0126746, 0.399108, -1.33503e-08) -bones/40/handle_offset = Vector2(0.450595, 0.683405) -bones/40/group = &"" -bones/40/require = false -bones/41/bone_name = &"RightFoot" -bones/41/bone_parent = &"RightLowerLeg" -bones/41/tail_direction = 0 -bones/41/reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, -0.00280195, 0.376756, 0.0164047) -bones/41/handle_offset = Vector2(0.449355, 0.850129) -bones/41/group = &"" -bones/41/require = false -bones/42/bone_name = &"Ball_R" -bones/42/bone_parent = &"RightFoot" -bones/42/tail_direction = 0 -bones/42/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.6738e-08, 0.0963863, -0.0586739) -bones/42/handle_offset = Vector2(0.449355, 0.876094) -bones/42/group = &"" -bones/42/require = false -bones/43/bone_name = &"RightToes" -bones/43/bone_parent = &"Ball_R" -bones/43/tail_direction = 0 -bones/43/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -3.7708e-08, 0.0729576, 2.46848e-08) -bones/43/handle_offset = Vector2(0.449355, 0.876094) -bones/43/group = &"" -bones/43/require = false -bones/44/bone_name = &"LeftUpperLeg" -bones/44/bone_parent = &"Hips" -bones/44/tail_direction = 0 -bones/44/reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.0989671, -0.0447181, -0.0204691) -bones/44/handle_offset = Vector2(0.543795, 0.50679) -bones/44/group = &"" -bones/44/require = false -bones/45/bone_name = &"LeftLowerLeg" -bones/45/bone_parent = &"LeftUpperLeg" -bones/45/tail_direction = 0 -bones/45/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0126742, 0.399108, -5.16533e-07) -bones/45/handle_offset = Vector2(0.549404, 0.683405) -bones/45/group = &"" -bones/45/require = false -bones/46/bone_name = &"LeftFoot" -bones/46/bone_parent = &"LeftLowerLeg" -bones/46/tail_direction = 0 -bones/46/reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0.00280044, 0.376756, 0.0164051) -bones/46/handle_offset = Vector2(0.550643, 0.850129) -bones/46/group = &"" -bones/46/require = false -bones/47/bone_name = &"Ball_L" -bones/47/bone_parent = &"LeftFoot" -bones/47/tail_direction = 0 -bones/47/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.28463e-07, 0.0963862, -0.058674) -bones/47/handle_offset = Vector2(0.550643, 0.876094) -bones/47/group = &"" -bones/47/require = false -bones/48/bone_name = &"LeftToes" -bones/48/bone_parent = &"Ball_L" -bones/48/tail_direction = 0 -bones/48/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 1.68256e-07, 0.0729575, -1.06382e-07) -bones/48/handle_offset = Vector2(0.550643, 0.876094) -bones/48/group = &"" -bones/48/require = false diff --git a/bin/android/.gitkeep b/bin/android/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/bin/ios/ios.framework/Info.plist b/bin/ios/ios.framework/Info.plist new file mode 100644 index 00000000..e53964fd --- /dev/null +++ b/bin/ios/ios.framework/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + libEXTENSION-NAME.macos.template_release + CFBundleName + Godot Template Cpp + CFBundleDisplayName + Godot Template Cpp + CFBundleIdentifier + org.godot.godot-template-cpp + NSHumanReadableCopyright + Unlicensed + CFBundleVersion + 1.0.0 + CFBundleShortVersionString + 1.0.0 + CFBundlePackageType + FMWK + CSResourcesFileMapped + + DTPlatformName + iphoneos + MinimumOSVersion + 12.0 + + diff --git a/bin/linux/.gitkeep b/bin/linux/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/bin/macos/.gitkeep b/bin/macos/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/bin/macos/macos.framework/Resources/Info.plist b/bin/macos/macos.framework/Resources/Info.plist new file mode 100644 index 00000000..2397cb18 --- /dev/null +++ b/bin/macos/macos.framework/Resources/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + CFBundleExecutable + libEXTENSION-NAME.macos.template_release + CFBundleName + Godot Cpp Template + CFBundleDisplayName + Godot Cpp Template + CFBundleIdentifier + org.godot.godot-template-cpp + NSHumanReadableCopyright + Unlicensed + CFBundleVersion + 1.0.0 + CFBundleShortVersionString + 1.0.0 + CFBundlePackageType + FMWK + CSResourcesFileMapped + + DTPlatformName + macosx + LSMinimumSystemVersion + 10.12 + + diff --git a/bin/windows/.gitkeep b/bin/windows/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.dll b/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.dll new file mode 100644 index 00000000..00b56ad3 Binary files /dev/null and b/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.dll differ diff --git a/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.ilk b/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.ilk new file mode 100644 index 00000000..c869cec3 Binary files /dev/null and b/bin/windows/MotionMatching.windows.template_debug.dev.x86_64.ilk differ diff --git a/bin/windows/MotionMatching.windows.template_debug.x86_64.dll b/bin/windows/MotionMatching.windows.template_debug.x86_64.dll new file mode 100644 index 00000000..335d4eaf Binary files /dev/null and b/bin/windows/MotionMatching.windows.template_debug.x86_64.dll differ diff --git a/bin/windows/MotionMatching.windows.template_release.x86_64.dll b/bin/windows/MotionMatching.windows.template_release.x86_64.dll new file mode 100644 index 00000000..0fe7e632 Binary files /dev/null and b/bin/windows/MotionMatching.windows.template_release.x86_64.dll differ diff --git a/LICENSE b/demo/addons/MotionMatching/LICENSE similarity index 100% rename from LICENSE rename to demo/addons/MotionMatching/LICENSE diff --git a/demo/addons/MotionMatching/MMEditorPlugin.gd b/demo/addons/MotionMatching/MMEditorPlugin.gd new file mode 100644 index 00000000..04aa2294 --- /dev/null +++ b/demo/addons/MotionMatching/MMEditorPlugin.gd @@ -0,0 +1,102 @@ +@tool +class_name MMEditorPlugin +extends EditorPlugin + +signal lib_changed(lib:MMAnimationLibrary) + +var preview_scene_path := "res://addons/MotionMatching/scenes/main_scene/MMPreviewScene.tscn" +var preview_scene : MMPreviewSkeleton +const MM_BOTTOM_SCENE = preload("res://addons/MotionMatching/scenes/bottom_scene/MM_BottomScene.tscn") +var bottom_panel_instance : MMBottonScene + +var gizmo_skeleton :MMEditorGizmoSkeletonPlugin + +var current_lib : MMAnimationLibrary = null: + set(value): + if value && current_lib != value : + current_lib = value + prints("Lib changed") + lib_changed.emit(current_lib) + + get: + return current_lib + + + + +func _get_plugin_icon() -> Texture2D: + return preload("res://addons/MotionMatching/icons/icon_mm.svg") + +func _enter_tree() -> void: + prints("MotionMatching Plugin Enter tree") + # Gizmo + gizmo_skeleton = MMEditorGizmoSkeletonPlugin.new(self) + add_node_3d_gizmo_plugin(gizmo_skeleton) + # PreviewScene + var found := EditorInterface.get_open_scenes().find(preview_scene_path) + if found < 0: + EditorInterface.open_scene_from_path(preview_scene_path) + preview_scene = EditorInterface.get_edited_scene_root() as MMPreviewSkeleton + + # BOTTOM PANEL + bottom_panel_instance = MM_BOTTOM_SCENE.instantiate() + add_control_to_bottom_panel(bottom_panel_instance,"MotionMatching") + + + + # Hide the main panel. + _make_visible(false) + + # Links + lib_changed.connect(bottom_panel_instance.on_lib_selected) + lib_changed.connect(func(lib:MMAnimationLibrary):preview_scene.set_bones(lib.skeleton_profile)) + + + #lib_changed.connect(main_panel_instance.on_lib_selected) + #bottom_panel_instance.pose_selected.connect(main_panel_instance.on_pose_selected) + #bottom_panel_instance.pose_selected.connect(func(l,a,t): gizmo_skeleton.please_redraw.emit()) + bottom_panel_instance.pose_selected.connect(gizmo_skeleton.on_pose_selected.emit) + +func _exit_tree() -> void: + prints("MotionMatching Plugin Exiting tree") + + #if main_panel_instance: + #EditorInterface.get_editor_main_screen().remove_child(main_panel_instance) + #main_panel_instance.queue_free() + if bottom_panel_instance: + remove_control_from_bottom_panel(bottom_panel_instance) + bottom_panel_instance.queue_free() + if gizmo_skeleton: + remove_node_3d_gizmo_plugin(gizmo_skeleton) + #EditorInterface.remo(preview_scene_path) + pass + +func _has_main_screen() -> bool: + return false + + +func _make_visible(visible: bool) -> void: + if bottom_panel_instance: + if visible: + EditorInterface.open_scene_from_path(preview_scene_path) + preview_scene = EditorInterface.get_edited_scene_root() as MMPreviewSkeleton + #main_panel_instance.show() + EditorInterface.set_main_screen_editor("3D") + bottom_panel_instance.show() + make_bottom_panel_item_visible(bottom_panel_instance) + else: + #main_panel_instance.hide() + hide_bottom_panel() + bottom_panel_instance.hide() + + +func _get_plugin_name() -> String: + return "MotionMatching" + + +func _edit(object: Object) -> void: + current_lib = object as MMAnimationLibrary + + +func _handles(obj: Object) -> bool: + return obj is MMAnimationLibrary diff --git a/demo/addons/MotionMatching/MotionMatching.gdextension b/demo/addons/MotionMatching/MotionMatching.gdextension new file mode 100644 index 00000000..723f53a3 --- /dev/null +++ b/demo/addons/MotionMatching/MotionMatching.gdextension @@ -0,0 +1,26 @@ +[configuration] + +entry_symbol = "mm_library_init" +compatibility_minimum = "4.3" +reloadable = true + +[libraries] + +macos.debug = "bin/macos/libMotionMatching.macos.template_debug.framework" +macos.release = "bin/macos/libMotionMatching.macos.template_release.framework" +ios.debug = "bin/ios/libMotionMatching.ios.template_debug.framework" +ios.release = "bin/ios/libMotionMatching.ios.template_release.framework" +windows.debug.x86_32 = "bin/windows/libMotionMatching.windows.template_debug.x86_32.dll" +windows.release.x86_32 = "bin/windows/libMotionMatching.windows.template_release.x86_32.dll" +windows.debug.x86_64 = "bin/windows/libMotionMatching.windows.template_debug.x86_64.dll" +windows.release.x86_64 = "bin/windows/libMotionMatching.windows.template_release.x86_64.dll" +linux.debug.x86_64 = "bin/linux/libMotionMatching.linux.template_debug.x86_64.so" +linux.release.x86_64 = "bin/linux/libMotionMatching.linux.template_release.x86_64.so" +linux.debug.arm64 = "bin/linux/libMotionMatching.linux.template_debug.arm64.so" +linux.release.arm64 = "bin/linux/libMotionMatching.linux.template_release.arm64.so" +linux.debug.rv64 = "bin/linux/libMotionMatching.linux.template_debug.rv64.so" +linux.release.rv64 = "bin/linux/libMotionMatching.linux.template_release.rv64.so" +android.debug.x86_64 = "bin/android/libMotionMatching.android.template_debug.x86_64.so" +android.release.x86_64 = "bin/android/libMotionMatching.android.template_release.x86_64.so" +android.debug.arm64 = "bin/android/libMotionMatching.android.template_debug.arm64.so" +android.release.arm64 = "bin/android/libMotionMatching.android.template_release.arm64.so" \ No newline at end of file diff --git a/demo/addons/MotionMatching/bin/android/.gitkeep b/demo/addons/MotionMatching/bin/android/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/addons/MotionMatching/bin/ios/.gitkeep b/demo/addons/MotionMatching/bin/ios/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/addons/MotionMatching/bin/linux/.gitkeep b/demo/addons/MotionMatching/bin/linux/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/addons/MotionMatching/bin/macos/.gitkeep b/demo/addons/MotionMatching/bin/macos/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/addons/MotionMatching/bin/windows/.gitkeep b/demo/addons/MotionMatching/bin/windows/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_debug.x86_64.dll b/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_debug.x86_64.dll new file mode 100644 index 00000000..335d4eaf Binary files /dev/null and b/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_debug.x86_64.dll differ diff --git a/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_release.x86_64.dll b/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_release.x86_64.dll new file mode 100644 index 00000000..0fe7e632 Binary files /dev/null and b/demo/addons/MotionMatching/bin/windows/libMotionMatching.windows.template_release.x86_64.dll differ diff --git a/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.gd b/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.gd new file mode 100644 index 00000000..ae9085bd --- /dev/null +++ b/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.gd @@ -0,0 +1,208 @@ +@tool +class_name AnimationPanel extends Control + +var al :AnimationLibrary +@onready var tree: Tree = %Tree +@onready var add_anim_button: Button = %AddAnimButton + +enum Column {Id,Name,Duration,Actions} +enum Action_ID {Rename,MirrorX,Delete = 99} + +signal on_animation_selected(lib: MMAnimationLibrary,name:String) + + +func _ready() -> void: + add_anim_button.pressed.connect(add_animation) + var add_icon :Texture2D= EditorInterface.get_editor_theme().get_icon("Load","EditorIcons") + add_anim_button.icon = add_icon + tree.item_activated.connect(_on_row_selected) + pass + +func _on_row_selected(): + var treeitem :TreeItem= tree.get_selected() + var index := treeitem.get_index() + prints(al.get_animation_list()[index],treeitem.get_text(Column.Name)) + on_animation_selected.emit(al,al.get_animation_list()[index]) + + +func _on_mm_editor_on_library_change(lib: MMAnimationLibrary) -> void: + al = lib + queue_redraw() + +func _draw() -> void: + tree.clear() + tree.column_titles_visible = true + + var root := tree.create_item() + tree.columns = Column.size() + tree.set_column_expand(Column.Id,true) + tree.set_column_expand(Column.Name,true) + tree.set_column_expand_ratio(Column.Name,10) + tree.set_column_expand(Column.Duration,true) + tree.set_column_expand(Column.Actions,true) + + for i in range(Column.size()): + tree.set_column_title(i,str(Column.keys()[i])) + tree.set_column_title_alignment(i,HORIZONTAL_ALIGNMENT_LEFT) + tree.set_column_clip_content(i,false) + tree.set_column_title_alignment(Column.Duration,HORIZONTAL_ALIGNMENT_RIGHT) + tree.set_column_title_alignment(Column.Actions,HORIZONTAL_ALIGNMENT_RIGHT) + + var index := 0 + if al != null: + for animname in al.get_animation_list(): + var anim := al.get_animation(animname) + var animitem := tree.create_item(root) + animitem.set_cell_mode(0,TreeItem.CELL_MODE_RANGE) + animitem.set_range(Column.Id,index) + animitem.set_text(Column.Name,animname) + animitem.set_text(Column.Duration,"%0.3f" % anim.length) + animitem.set_suffix(Column.Duration,"s") + animitem.set_text_alignment(Column.Duration,HORIZONTAL_ALIGNMENT_RIGHT) + + var rename_icon := EditorInterface.get_editor_theme().get_icon("Edit","EditorIcons") + var delete_icon := EditorInterface.get_editor_theme().get_icon("Remove","EditorIcons") + var mirror_icon := EditorInterface.get_editor_theme().get_icon("MirrorX","EditorIcons") + animitem.add_button(Column.Actions,rename_icon,Action_ID.Rename,false,"Rename") + animitem.add_button(Column.Actions,mirror_icon,Action_ID.MirrorX,false,"Mirror Animation on X Axis") + animitem.add_button(Column.Actions,delete_icon,Action_ID.Delete,false,"Delete Animation") + animitem.set_text_alignment(Column.Actions,HORIZONTAL_ALIGNMENT_RIGHT) + + + animitem.set_text_overrun_behavior(Column.Actions,TextServer.OVERRUN_NO_TRIMMING) + index += 1 + + tree.queue_redraw() + +#region Add Animations + +var fileDialog : EditorFileDialog +func add_animation(): + fileDialog = EditorFileDialog.new() + fileDialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILES + fileDialog.mode = EditorFileDialog.MODE_MAXIMIZED + fileDialog.access = EditorFileDialog.ACCESS_RESOURCES + fileDialog.file_selected.connect(on_file_selected) + fileDialog.files_selected.connect(on_files_selected) + fileDialog.dir_selected.connect(on_file_selected) + var viewport = EditorInterface.get_editor_main_screen() + viewport.add_child(fileDialog) + fileDialog.set_meta("_created_by", self) # needed so the script is not directly freed after the run function. Would disconnect all signals otherwise + fileDialog.popup(Rect2(0,0, 700, 500)) # Giving the dialog a predefined size + print("end") + pass + +func on_files_selected(filesname : PackedStringArray): + for path in filesname: + var anim_name = path.get_file().split(".")[0] + var file := load(path) + if file is Animation: + prints(anim_name,file) + if al.add_animation(anim_name,file) != OK: + prints("Didn't add",anim_name) + if (fileDialog != null): + fileDialog.queue_free() # Dialog has to be freed in order for the script t + + + pass + +func on_file_selected(filename : String) : + if (fileDialog != null): + fileDialog.queue_free() # Dialog has to be freed in order for the script t +#endregion + + + + + + + + + +func _on_mirror_anim_button_pressed(anim_name:String) -> void: + # mirror on X + var anim := al.get_animation(anim_name) + var mirror := Animation.new() + + if anim == null || mirror == null: + push_warning("Animation not selected or not found") + return + + #clean mirror + mirror.clear() + mirror.length = anim.length + for track in range(anim.get_track_count()): + anim.copy_track(track,mirror) + if mirror.track_get_type(track) == Animation.TYPE_POSITION_3D: + for key in range(mirror.track_get_key_count(track)): + var value : Vector3 = mirror.track_get_key_value(track,key) + value.x *= -1 + mirror.track_set_key_value(track,key,value) + if mirror.track_get_type(track) == Animation.TYPE_ROTATION_3D: + for key in range(mirror.track_get_key_count(track)): + var value : Quaternion = mirror.track_get_key_value(track,key) + value.y *= -1 + value.z *= -1 + mirror.track_set_key_value(track,key,value) + var path := mirror.track_get_path(track) + if path.get_concatenated_subnames().begins_with("Left"): + var new_path := path.get_concatenated_names() + ":Right" + path.get_concatenated_subnames().trim_prefix("Left") + mirror.track_set_path(track,new_path) + elif path.get_concatenated_subnames().begins_with("Right"): + var new_path := path.get_concatenated_names() + ":Left" + path.get_concatenated_subnames().trim_prefix("Right") + mirror.track_set_path(track,new_path) + + # Save the animation + al.add_animation(anim_name + "_Mx",mirror) + queue_redraw() + pass # Replace with function body. + +@onready var confirmation_dialog: ConfirmationDialog = $ConfirmationDialog + +func _on_remove_anim_button_pressed() -> void: + if tree.get_selected() == null: + return + var anim_name :StringName= tree.get_selected().get_text(1) + confirmation_dialog.popup_centered() + confirmation_dialog.dialog_text = "Delete the animation named " + anim_name + "?" + + pass # Replace with function body. + + +func _on_confirm_delete_anim(anim_name:StringName) -> void: + al.remove_animation(anim_name) + queue_redraw() + + +func _on_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void: + print(column,id) + if column == Column.Actions: + match id: + Action_ID.Delete: + var anim_name = item.get_text(1) + confirmation_dialog.popup_centered() + confirmation_dialog.dialog_text = "Delete the animation named " + anim_name + "?" + if confirmation_dialog.confirmed.is_connected(_on_confirm_delete_anim): + confirmation_dialog.confirmed.disconnect(_on_confirm_delete_anim) + confirmation_dialog.confirmed.connect(_on_confirm_delete_anim.bind(anim_name),Object.CONNECT_ONE_SHOT) + Action_ID.MirrorX: + var anim_name = item.get_text(1) + _on_mirror_anim_button_pressed(anim_name) + Action_ID.Rename: + old_name = item.get_text(Column.Name) + item.set_editable(Column.Name,true) + tree.set_selected(item,Column.Name) + tree.edit_selected() + pass + + pass # Replace with function body. + +var old_name := "" +func _on_tree_item_edited() -> void: + var item := tree.get_edited() + var column := tree.get_edited_column() + if column == Column.Name: + var new_name := item.get_text(Column.Name) + al.rename_animation(old_name,new_name) + item.set_editable(Column.Name,false) + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.tscn b/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.tscn new file mode 100644 index 00000000..acb56958 --- /dev/null +++ b/demo/addons/MotionMatching/controls/AnimationPanel/AnimationPanel.tscn @@ -0,0 +1,53 @@ +[gd_scene load_steps=4 format=3 uid="uid://ey2osuvm28ql"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/AnimationPanel/AnimationPanel.gd" id="1_a4beg"] + +[sub_resource type="Image" id="Image_qcm5q"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 76, 224, 224, 224, 228, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 228, 224, 224, 224, 73, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 229, 224, 224, 224, 72, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 73, 224, 224, 224, 226, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 181, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 178, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 234, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 191, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 107, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 148, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 150, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 106, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 192, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 227, 227, 227, 63, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 235, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 228, 224, 224, 224, 73, 224, 224, 224, 81, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 195, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 73, 224, 224, 224, 226, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 223, 228, 228, 228, 55, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_saobd"] +image = SubResource("Image_qcm5q") + +[node name="Animations" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_a4beg") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Tree" type="Tree" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +columns = 4 +column_titles_visible = true +hide_root = true +select_mode = 1 + +[node name="ButtonsPanel" type="VFlowContainer" parent="HBoxContainer"] +clip_contents = true +layout_mode = 2 + +[node name="AddAnimButton" type="Button" parent="HBoxContainer/ButtonsPanel"] +unique_name_in_owner = true +layout_mode = 2 +text = "Add Animation(s)" +icon = SubResource("ImageTexture_saobd") + +[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."] +initial_position = 4 + +[connection signal="button_clicked" from="HBoxContainer/Tree" to="." method="_on_tree_button_clicked"] +[connection signal="item_edited" from="HBoxContainer/Tree" to="." method="_on_tree_item_edited"] diff --git a/demo/addons/MotionMatching/controls/CalculationPanel/CalculationPanel.tscn b/demo/addons/MotionMatching/controls/CalculationPanel/CalculationPanel.tscn new file mode 100644 index 00000000..89638e46 --- /dev/null +++ b/demo/addons/MotionMatching/controls/CalculationPanel/CalculationPanel.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=2 format=3 uid="uid://t1me7mggbgko"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/CalculationPanel/calculation_panel.gd" id="1_onu5j"] + +[node name="Calculation" type="HFlowContainer"] +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 93.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +script = ExtResource("1_onu5j") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HFlowContainer" type="HFlowContainer" parent="MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label2" type="Label" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +text = "Discover Halflife from Derivative - Start:" + +[node name="SpinBox" type="SpinBox" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +step = 0.05 +value = 1.0 +allow_greater = true +allow_lesser = true + +[node name="Label3" type="Label" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +text = "End +" + +[node name="SpinBox2" type="SpinBox" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +step = 0.05 +value = 1.0 +allow_greater = true +allow_lesser = true + +[node name="Label4" type="Label" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +text = "Max Derivative" + +[node name="SpinBox3" type="SpinBox" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +step = 0.05 +value = 1.0 +allow_greater = true +allow_lesser = true + +[node name="equal" type="Label" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +text = "=" + +[node name="Answer2" type="Label" parent="MarginContainer/HFlowContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "test +" + +[connection signal="value_changed" from="MarginContainer/HFlowContainer/SpinBox" to="." method="on_discover_halflife"] +[connection signal="value_changed" from="MarginContainer/HFlowContainer/SpinBox2" to="." method="on_discover_halflife"] +[connection signal="value_changed" from="MarginContainer/HFlowContainer/SpinBox3" to="." method="on_discover_halflife"] diff --git a/demo/addons/MotionMatching/controls/CalculationPanel/calculation_panel.gd b/demo/addons/MotionMatching/controls/CalculationPanel/calculation_panel.gd new file mode 100644 index 00000000..b9aba91d --- /dev/null +++ b/demo/addons/MotionMatching/controls/CalculationPanel/calculation_panel.gd @@ -0,0 +1,10 @@ +@tool +extends HFlowContainer +@onready var spin_box: SpinBox = $MarginContainer/HFlowContainer/SpinBox +@onready var spin_box_2: SpinBox = $MarginContainer/HFlowContainer/SpinBox2 +@onready var spin_box_3: SpinBox = $MarginContainer/HFlowContainer/SpinBox3 +@onready var answer_2: Label = $MarginContainer/HFlowContainer/Answer2 + + +func on_discover_halflife(value:float): + answer_2.text = str(Spring.maximum_spring_velocity_to_halflife(spin_box.value,spin_box_2.value,spin_box_3.value)) diff --git a/demo/addons/MotionMatching/controls/DataViewer/DataPanel.gd b/demo/addons/MotionMatching/controls/DataViewer/DataPanel.gd new file mode 100644 index 00000000..a333090b --- /dev/null +++ b/demo/addons/MotionMatching/controls/DataViewer/DataPanel.gd @@ -0,0 +1,188 @@ +@tool +class_name DataPanel extends HBoxContainer + +@onready var tree: Tree = %Tree +@onready var lign_selector: SpinBox = %LignSelector +@onready var choose_animation: MenuButton = %ChooseAnimation +@onready var lookup_similar_button: Button = %LookupSimilarButton + +var lib : MMAnimationLibrary +var current_animation : Animation +var current_animation_name : StringName + +signal request_pose(animation_name:StringName,timestamp:float) + + + + +func _on_mm_editor_on_new_library_change(_lib: MMAnimationLibrary) -> void: + lib = _lib + reset() + +func reset(): + tree.clear() + lign_selector.min_value = 0 + lign_selector.value = 0 + choose_animation.text = "Select Animation" + var popup := choose_animation.get_popup() + popup.clear() + for a in lib.get_animation_list(): + popup.add_item(a) + if !popup.id_pressed.is_connected(on_select_animation_popup): + popup.id_pressed.connect(on_select_animation_popup) + tree.clear() + tree.queue_redraw() + choose_animation.queue_redraw() + + #show_lign(0) + pass # Replace with function body. + +func on_select_animation_popup(ID:int): + if not lib.get_animation_list().is_empty() : + choose_animation.text = choose_animation.get_popup().get_item_text(ID) + tree.clear() + if lib.db_anim_index.size() != 0: + var i :int= 0 + for index in lib.db_anim_index: + if index == ID: + lign_selector.value = i # will call show_lign by events + return + i += 1 + + +func show_data(lib:MMAnimationLibrary,animname:String,time:float): + tree.clear() + tree.columns = 1 + # + if lib.MotionData.size() <= 0: + lign_selector.value = 0 + return + elif lib.motion_features.is_empty(): + lign_selector.value = 0 + return + + var dim := lib.nb_dimensions + var data := PackedFloat32Array() #lib.MotionData.slice(lign*lib.nb_dimensions,lign*lib.nb_dimensions+lib.nb_dimensions) + var anim :Animation= lib.get_animation(animname) + + + for f in lib.motion_features as Array[MotionFeature]: + var feature_data := PackedFloat32Array() + feature_data.resize(f.get_dimension()) + data.append_array(f.bake_pose(lib,animname,time)) + if f.get_dimension() > tree.columns: + tree.columns = f.get_dimension() + + + var root := tree.create_item() + + var data_folder := tree.create_item(root,0) + data_folder.set_text(0,"Features Data") + data_folder.set_custom_bg_color(0,Color.WHITE,true) + + var category_folder := tree.create_item(root,1) + category_folder.set_text(0,"Category") + category_folder.set_custom_bg_color(0,Color.WHITE,true) + var category_item :TreeItem= tree.create_item(category_folder) + #category_item.set_text(0, str(lib.db_anim_category[lign])) + category_item.set_text(0, str(0)) + + var info_folder := tree.create_item(root,2) + info_folder.set_text(0,"TimeStamp") + info_folder.set_custom_bg_color(0,Color.WHITE,true) + var timestamp_item :TreeItem= tree.create_item(info_folder) + #timestamp_item.set_text(0, str(lib.db_anim_timestamp[lign])) + timestamp_item.set_text(0, str(0)) + + var biases_folder := tree.create_item(root,3) + biases_folder.set_text(0,"Biases") + biases_folder.set_custom_bg_color(0,Color.WHITE,true) + var biases_item :TreeItem= tree.create_item(biases_folder) + #biases_item.set_text(0, str(lib.db_biases[lign])) + biases_item.set_text(0, str(0)) + + tree.columns += 1 + tree.set_column_title(0,"Info") + tree.button_clicked.connect(func(item: TreeItem, column: int, id: int, mouse_button_index: int): + if item.get_metadata(0) is MotionFeature: + EditorInterface.inspect_object(item.get_metadata(0)) + ) + + var offset = 0 + var f_index := 0 + for f in lib.motion_features as Array[MotionFeature]: + var feature_row :TreeItem= tree.create_item(data_folder,f_index) + + + var dim_row := tree.create_item(feature_row,0) + var hint_row := tree.create_item(feature_row,1) + var data_row := tree.create_item(feature_row,2) + var weights_row := tree.create_item(feature_row,3) + + feature_row.set_text(0,f.resource_name if not f.resource_name.is_empty() else f.get_class()) + feature_row.set_metadata(0,f) + #feature_row.set_text_alignment(0,HORIZONTAL_ALIGNMENT_LEFT) + var texture := EditorInterface.get_base_control().get_theme_icon("Zoom","EditorIcons") + + feature_row.add_button(0,texture,-1,false,f.resource_name if not f.resource_name.is_empty() else f.get_class()) + + + data_row.set_text(0, "Data") + data_row.set_text_alignment(0,HORIZONTAL_ALIGNMENT_RIGHT) + hint_row.set_text(0, "Hints") + hint_row.set_text_alignment(0,HORIZONTAL_ALIGNMENT_RIGHT) + dim_row.set_text(0, "Dimension") + dim_row.set_text_alignment(0,HORIZONTAL_ALIGNMENT_RIGHT) + weights_row.set_text(0, "Weights(static)") + weights_row.set_text_alignment(0,HORIZONTAL_ALIGNMENT_RIGHT) + var hints :PackedStringArray= f.get_hints() + for i in range(f.get_dimension()): + var scale :float = lib.dimension_stddev[offset+i] + var avg :float = lib.dimension_means[offset+i] + data_row.set_text(i+1, "%0.3f" % ((data[offset+i] * scale) + avg ) ) + data_row.set_text_alignment(i+1,HORIZONTAL_ALIGNMENT_CENTER) + + dim_row.set_text(i+1,str(offset + i)) + dim_row.set_text_alignment(i+1,HORIZONTAL_ALIGNMENT_CENTER) + + hint_row.set_text(i+1,hints[i]) + hint_row.set_text_alignment(i+1,HORIZONTAL_ALIGNMENT_CENTER) + + weights_row.set_cell_mode(i+1,TreeItem.CELL_MODE_RANGE) + weights_row.set_range_config(i+1,0.001,1000,0.1,true) + weights_row.set_editable(i+1,true) + weights_row.set_selectable(i+1,true) + weights_row.set_range(i+1,lib.weights[offset+i]) + offset += f.get_dimension() + f_index += 1 + + + + #var pose_index :int = lign as int + #if pose_index > lib.MotionData.size()/lib.nb_dimensions: + #return +# + #var animlib :MMAnimationLibrary= lib as MMAnimationLibrary + #var anim_name := animlib.get_animation_list()[lib.db_anim_index[pose_index]] + #var anim_timestep := lib.db_anim_timestamp[pose_index] +# + #request_pose.emit(anim_name,anim_timestep) + + tree.queue_redraw() + + pass + + +func _on_tree_item_edited() -> void: + # Navigate the tree to get in index offset of the weights + var item := tree.get_edited() + if item.get_text(0) == "Weights(static)": + var feature_nb := item.get_parent().get_index() + var offset := 0 + for f in range(feature_nb): + offset+= lib.motion_features[f].get_dimension() + offset += tree.get_edited_column() - 1 + lib.weights[offset] = item.get_range(tree.get_edited_column()) + + + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/controls/DataViewer/DataPanel.tscn b/demo/addons/MotionMatching/controls/DataViewer/DataPanel.tscn new file mode 100644 index 00000000..f423b43c --- /dev/null +++ b/demo/addons/MotionMatching/controls/DataViewer/DataPanel.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=4 format=3 uid="uid://ceb0rxuep0ibs"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/DataViewer/DataPanel.gd" id="1_axpvn"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yutts"] +bg_color = Color(0.14902, 0.168627, 0.203922, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7i2fw"] +bg_color = Color(0.14902, 0.168627, 0.203922, 1) + +[node name="Data" type="HBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_axpvn") + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_yutts") + +[node name="Tree" type="Tree" parent="PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_constants/icon_max_width = 10 +hide_root = true + +[node name="HBoxContainer" type="VBoxContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="HBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Show lign" + +[node name="LignSelector" type="SpinBox" parent="HBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +rounded = true +allow_greater = true + +[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer"] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_7i2fw") + +[node name="ChooseAnimation" type="MenuButton" parent="HBoxContainer/PanelContainer"] +unique_name_in_owner = true +clip_contents = true +layout_mode = 2 +toggle_mode = false +text = "Select Animation" +flat = false + +[node name="LookupSimilarButton" type="Button" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = "Look for similar" + +[connection signal="item_edited" from="PanelContainer/Tree" to="." method="_on_tree_item_edited"] diff --git a/demo/addons/MotionMatching/controls/Information/infoPanel.tscn b/demo/addons/MotionMatching/controls/Information/infoPanel.tscn new file mode 100644 index 00000000..b220a783 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Information/infoPanel.tscn @@ -0,0 +1,24 @@ +[gd_scene format=3 uid="uid://bhqok5suthh0o"] + +[node name="PanelContainer" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PanelContainer" type="ScrollContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +follow_focus = true + +[node name="InfoText" type="RichTextLabel" parent="PanelContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +size_flags_stretch_ratio = 10.0 +bbcode_enabled = true +fit_content = true +autowrap_mode = 0 +threaded = true diff --git a/demo/addons/MotionMatching/controls/MMEditorGizmoPlugin.gd b/demo/addons/MotionMatching/controls/MMEditorGizmoPlugin.gd new file mode 100644 index 00000000..35ac927e --- /dev/null +++ b/demo/addons/MotionMatching/controls/MMEditorGizmoPlugin.gd @@ -0,0 +1,75 @@ +@tool + +class_name MMEditorGizmoPlugin extends EditorNode3DGizmoPlugin + +var current_animlib :MMAnimationLibrary + +var current_animation_name :String= "" : + set(value): + current_animation_name = value + get: + return current_animation_name +var current_timestamp := 0.0 + +signal on_pose_changed(lib:MMAnimationLibrary,animname:String,timestamp:float) + +func _init(): + prints("Gizmo Init") + create_material("white", Color.WHITE) + create_material("blue", Color.BLUE) + create_material("red", Color.RED) + create_material("green", Color.GREEN) + create_material("orange", Color.ORANGE_RED) + + create_handle_material("handles") + +func _get_gizmo_name() -> String: + return "MMGizmo" + + +func _has_gizmo(node): + if node is MMSkeletonEditor : + print(node.name) + return node is MMSkeletonEditor + +func _create_gizmo(for_node_3d: Node3D) -> EditorNode3DGizmo: + var skel_gizmo := MMEditorNode3DGizmo.new() + + return skel_gizmo + + + + +func _redraw(gizmo : EditorNode3DGizmo): + gizmo.clear() + #if current_animlib == null || current_animation_name == null: + #return + + var node3d :Skeleton3D = gizmo.get_node_3d() + var lines = PackedVector3Array() + + var tr := node3d.global_transform * node3d.get_bone_global_pose(node3d.find_bone("Root")) + + + lines.push_back(tr.origin) + lines.push_back(tr.origin + tr.basis.z + Vector3(0,0.1,0)) + gizmo.add_lines(lines, get_material("orange", gizmo),true) + + if current_animlib == null || current_animation_name.is_empty(): + return + + var current_animation :Animation= current_animlib.get_animation(current_animation_name) + var trail :=PackedVector3Array() + for i in range(current_animation.get_length()/current_animlib.time_interval): + var box := BoxMesh.new() + box.size = Vector3.ONE * 0.05 + var TR = current_animlib.sample_bone_global(current_animation_name,i * current_animlib.time_interval,"%GeneralSkeleton:Root") + tr = Transform3D(Basis(TR.rotation),TR.position) + gizmo.add_mesh(box,get_material("white"),tr) + trail.append(tr.origin) + + gizmo.add_lines(trail,get_material("orange"),true) + + + + pass diff --git a/demo/addons/MotionMatching/controls/MMEditorNode3DGizmo.gd b/demo/addons/MotionMatching/controls/MMEditorNode3DGizmo.gd new file mode 100644 index 00000000..919479f7 --- /dev/null +++ b/demo/addons/MotionMatching/controls/MMEditorNode3DGizmo.gd @@ -0,0 +1,30 @@ +class_name MMEditorNode3DGizmo extends EditorNode3DGizmo + +func changed_anim(lib:MMAnimationLibrary,animname:String,timestamp:float): + var skeleton :MMSkeletonEditor= get_node_3d() + var anim := lib.get_animation(animname) + var anim_timestep := timestamp + + # Set the skeleton to pose + for i in range(anim.get_track_count()): + var bone_id = skeleton.find_bone(anim.track_get_path(i).get_subname(0)) + if bone_id == -1: + continue + elif anim.track_get_type(i) == Animation.TYPE_POSITION_3D: + var position := anim.position_track_interpolate(i,anim_timestep) + skeleton.set_bone_pose_position(bone_id,position * skeleton.get_motion_scale()) + elif anim.track_get_type(i) == Animation.TYPE_ROTATION_3D: + var rotation := anim.rotation_track_interpolate(i,anim_timestep) + skeleton.set_bone_pose_rotation(bone_id,rotation) + elif anim.track_get_type(i) == Animation.TYPE_SCALE_3D: + var scale := anim.scale_track_interpolate(i,anim_timestep) + skeleton.set_bone_pose_scale(bone_id,scale) + + # Retrieve root bone transform + var root_transform := skeleton.get_bone_pose(skeleton.find_bone(lib.skeleton_profile.root_bone)) + + # Call every feature + for feature in lib.motion_features: + pass + + pass diff --git a/demo/addons/MotionMatching/controls/Stats/stats_panel.gd b/demo/addons/MotionMatching/controls/Stats/stats_panel.gd new file mode 100644 index 00000000..4433338e --- /dev/null +++ b/demo/addons/MotionMatching/controls/Stats/stats_panel.gd @@ -0,0 +1,80 @@ +@tool +class_name StatsPanel extends PanelContainer + +#TODO Save the data in the mmlib. + +@onready var mmlib : MMAnimationLibrary +@onready var data := Array() + +@onready var dimension_number: SpinBox = %DimensionNumber +@onready var simple_stats: Tree = %SimpleStats +@onready var histogram: HBoxContainer = %Histogram +@onready var hints: Label = %Hints + +@onready var histogram_item: VBoxContainer = %HistogramItem + +func _on_lib_changed(_mmlib : MMAnimationLibrary): + mmlib = _mmlib + prints("lib setup") + +func _on_dimension_number_value_changed(value: float) -> void: + if mmlib == null: + prints("oups") + return + dimension_number.max_value = mmlib.nb_dimensions + data = mmlib.get_stats() + var f_hints := PackedStringArray() + for f in mmlib.motion_features: + f_hints.append_array(f.get_hints()) + hints.text = f_hints[int(value)] + + + + queue_redraw() + pass + +func _draw() -> void: + if data.size() <= 0: + return + dimension_number.queue_redraw() + var dict :Dictionary= data[int(dimension_number.value)] + simple_stats.clear() + var keys := dict.keys().filter(func(k:String) : return not k.begins_with('density')) + + simple_stats.columns = keys.size() + + var root := simple_stats.create_item() + var item := simple_stats.create_item(root) + for i in range(keys.size()): + simple_stats.set_column_title(i,keys[i]) + simple_stats.set_column_title_alignment(i,HORIZONTAL_ALIGNMENT_FILL) + item.set_text(i,str(dict[keys[i]])) + item.set_text_alignment(i,HORIZONTAL_ALIGNMENT_CENTER) + + prints(dict) + + var hist_bounds :PackedFloat32Array= dict['density_hist_bounds'] + var hist_values :PackedFloat32Array= dict['density_hist_values'] + + for c in histogram.get_children(): + histogram.remove_child(c) + c.queue_free() + for i in range(hist_bounds.size()): + var new_hist_item := histogram_item.duplicate() + var label :Label= new_hist_item.get_child(1) + var progress :TextureProgressBar= new_hist_item.get_child(0) + new_hist_item.visible = true + label.text = "%0.4f" % hist_bounds[i] + label.clip_text = false + + progress.value = hist_values[i] + if i == 0: + label.text = '<'+label.text + elif i == hist_bounds.size() - 1: + label.text = '>'+label.text + histogram.add_child(new_hist_item) + + pass + + + diff --git a/demo/addons/MotionMatching/controls/Stats/stats_panel.tscn b/demo/addons/MotionMatching/controls/Stats/stats_panel.tscn new file mode 100644 index 00000000..25f0755d --- /dev/null +++ b/demo/addons/MotionMatching/controls/Stats/stats_panel.tscn @@ -0,0 +1,102 @@ +[gd_scene load_steps=6 format=3 uid="uid://dnq5mwu8hbyn7"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Stats/stats_panel.gd" id="1_nypl2"] + +[sub_resource type="Gradient" id="Gradient_tcx4w"] +colors = PackedColorArray(0, 0, 0, 0.501961, 1, 1, 1, 0.501961) + +[sub_resource type="GradientTexture1D" id="GradientTexture1D_kdcho"] +gradient = SubResource("Gradient_tcx4w") + +[sub_resource type="Gradient" id="Gradient_tdngf"] +colors = PackedColorArray(0, 0, 0, 1, 1, 0.08, 0.08, 1) + +[sub_resource type="GradientTexture1D" id="GradientTexture1D_0sxxe"] +gradient = SubResource("Gradient_tdngf") + +[node name="Stats" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_nypl2") + +[node name="HSplitContainer" type="HSplitContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 0 + +[node name="DimensionsChoice" type="HBoxContainer" parent="HSplitContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 1 + +[node name="Label" type="Label" parent="HSplitContainer/VBoxContainer/DimensionsChoice"] +layout_mode = 2 +text = "Dimension" + +[node name="DimensionNumber" type="SpinBox" parent="HSplitContainer/VBoxContainer/DimensionsChoice"] +unique_name_in_owner = true +layout_mode = 2 +alignment = 1 +prefix = "#" + +[node name="Hints" type="Label" parent="HSplitContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Stats" type="VSplitContainer" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +split_offset = 10 +metadata/_tab_index = 3 + +[node name="SimpleStats" type="Tree" parent="HSplitContainer/Stats"] +unique_name_in_owner = true +clip_contents = false +layout_mode = 2 +columns = 5 +column_titles_visible = true +hide_root = true + +[node name="Histogram" type="HBoxContainer" parent="HSplitContainer/Stats"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 +alignment = 1 + +[node name="Templates" type="Control" parent="."] +visible = false +layout_mode = 2 + +[node name="HistogramItem" type="VBoxContainer" parent="Templates"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +alignment = 1 + +[node name="TextureProgressBar" type="TextureProgressBar" parent="Templates/HistogramItem"] +layout_mode = 2 +size_flags_vertical = 3 +max_value = 1.0 +step = 0.01 +value = 0.75 +fill_mode = 3 +nine_patch_stretch = true +texture_under = SubResource("GradientTexture1D_kdcho") +texture_progress = SubResource("GradientTexture1D_0sxxe") + +[node name="Label" type="Label" parent="Templates/HistogramItem"] +layout_mode = 2 +size_flags_vertical = 8 +text = "0.00" +horizontal_alignment = 3 + +[connection signal="value_changed" from="HSplitContainer/VBoxContainer/DimensionsChoice/DimensionNumber" to="." method="_on_dimension_number_value_changed"] diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Category_Event_Bar.tscn b/demo/addons/MotionMatching/controls/Tags/EventBar/Category_Event_Bar.tscn new file mode 100644 index 00000000..60e861c9 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Category_Event_Bar.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=3 format=3 uid="uid://wk0nm1n18m4n"] + +[ext_resource type="PackedScene" uid="uid://ws63tatc17cq" path="res://addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn" id="1_rfw1y"] +[ext_resource type="Texture2D" uid="uid://xbscafx042dq" path="res://addons/MotionMatching/icons/categories.res" id="2_mqnnn"] + +[node name="EventBar" instance=ExtResource("1_rfw1y")] + +[node name="Button" parent="MarginContainer" index="0"] +theme_type_variation = &"CategoryButton" +text = "CategoryMF" +icon = ExtResource("2_mqnnn") diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Distance_Event_Bar.tscn b/demo/addons/MotionMatching/controls/Tags/EventBar/Distance_Event_Bar.tscn new file mode 100644 index 00000000..173b5a6d --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Distance_Event_Bar.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=3 format=3 uid="uid://bxr485ohp4827"] + +[ext_resource type="PackedScene" uid="uid://ws63tatc17cq" path="res://addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn" id="1_pfmf7"] +[ext_resource type="Texture2D" uid="uid://bprh3hw453tqb" path="res://addons/MotionMatching/icons/distance.res" id="2_au3qf"] + +[node name="EventBar" instance=ExtResource("1_pfmf7")] + +[node name="Button" parent="MarginContainer" index="0"] +theme_type_variation = &"DistanceButton" +text = "DistanceMF" +icon = ExtResource("2_au3qf") diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Movable.gd b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Movable.gd new file mode 100644 index 00000000..f6c76a87 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Movable.gd @@ -0,0 +1,41 @@ +@tool +class_name MoveableUI extends Button +var offset := Vector2.ZERO +var ref : EventButton +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + if data is MoveableUI and data.ref == ref : + return true + if data is ResizeableUI and data.ref == ref : + return true + else: + return false +func _get_drag_data(at_position: Vector2) -> Variant: + var cpb = ref.duplicate() + # Allows us to center the color picker on the mouse + var preview = Control.new() + preview.add_child(cpb) + offset = Vector2(-at_position.x,-0.5*size.y) + cpb.position = offset + cpb.size = size + # Sets what the user will see they are dragging + set_drag_preview(preview) + return self +func _drop_data(at_position: Vector2, data: Variant) -> void: + if data is MoveableUI: + data.ref.position.x += at_position.x + data.offset.x + data.ref.tag.timestamp = max(0,(data.ref.position.x/ data.ref.get_parent().size.x) * data.ref.animation.length) + data.ref.tag.duration = min(data.ref.tag.duration,ref.animation.length - data.ref.tag.timestamp) + + elif data is ResizeableUI: + if data.drag_left == true: + var diff = (at_position.x)/ data.ref.get_parent().size.x * data.ref.animation.length + data.ref.tag.duration -= diff + data.ref.tag.timestamp += diff + data.ref.tag.duration = max(0,data.ref.tag.duration) + data.ref.tag.timestamp = max(0,data.ref.tag.timestamp) + elif data.drag_right == true: + var diff = (data.ref.size.x - at_position.x)/ data.ref.get_parent().size.x * data.ref.animation.length + data.ref.tag.duration -= diff + data.ref.tag.duration = max(0,data.ref.tag.duration) + data.ref.get_parent().queue_redraw() + diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Resizable.gd b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Resizable.gd new file mode 100644 index 00000000..25a652f7 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/Resizable.gd @@ -0,0 +1,18 @@ +@tool +class_name ResizeableUI extends MarginContainer + +var ref : EventButton +var drag_left := false +var drag_right := false +func _get_drag_data(at_position: Vector2) -> Variant: + return self +func _notification(what: int) -> void: + match what: + NOTIFICATION_DRAG_BEGIN: + if (get_local_mouse_position().x - size.x/2) > 0: + drag_right = true + else: + drag_left = true + NOTIFICATION_DRAG_END: + drag_left = false + drag_right = false diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/event_button.gd b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/event_button.gd new file mode 100644 index 00000000..8b9694bc --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Scripts/event_button.gd @@ -0,0 +1,46 @@ +@tool +class_name EventButton extends PanelContainer + +@onready var margin_container: ResizeableUI = $MarginContainer + + +@onready var button: Button = $MarginContainer/Button +@onready var popup_menu: PopupMenu = $PopupMenu + + +signal duplicate_event +signal delete_event + +@export var tag : TagInfo +var animation :Animation = null + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + margin_container.ref = self + button.ref = self + + + +func _button_gui_input(event: InputEvent) -> void: + var mouse := event as InputEventMouseButton + if mouse != null and mouse.button_index == MOUSE_BUTTON_RIGHT: + popup_menu.popup(Rect2i(get_global_mouse_position(),popup_menu.size)) + elif mouse != null and mouse.button_index == MOUSE_BUTTON_LEFT: + EditorInterface.inspect_object(tag) + + +enum {DELETE = 1} +func _on_popup_menu_id_pressed(id: int) -> void: + match id: + DELETE: + delete_event.emit(tag) + queue_free() + + +func _on_button_pressed_all() -> void: + if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + EditorInterface.inspect_object(tag) + elif Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): + popup_menu.popup(Rect2i(get_global_mouse_position(),popup_menu.size)) + + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/Themes/Events.tres b/demo/addons/MotionMatching/controls/Tags/EventBar/Themes/Events.tres new file mode 100644 index 00000000..8e454385 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/Themes/Events.tres @@ -0,0 +1,151 @@ +[gd_resource type="Theme" load_steps=27 format=3 uid="uid://c4fn4740sh1k1"] + +[ext_resource type="Texture2D" uid="uid://xbscafx042dq" path="res://addons/MotionMatching/icons/categories.res" id="1_djdr6"] +[ext_resource type="Texture2D" uid="uid://jvcrioes2c0n" path="res://addons/MotionMatching/icons/junk.res" id="2_k0ayk"] +[ext_resource type="Texture2D" uid="uid://di1ro7x38jy0v" path="res://addons/MotionMatching/icons/time.res" id="4_2vjug"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6cuyf"] +bg_color = Color(0.278431, 0.403922, 0.498039, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6hrgu"] +bg_color = Color(0.0775711, 0.515718, 0.767856, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lp54f"] +bg_color = Color(0.0315597, 0.360859, 0.546509, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bu1v3"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.241433, 0.57083, 0.212039, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6vry1"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.243137, 0.572549, 0.211765, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3obpr"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.164706, 0.4, 0.145098, 1) +corner_detail = 1 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sncyc"] +bg_color = Color(0.356863, 0.337255, 0.290196, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_utakf"] +bg_color = Color(0.862745, 0.529412, 0.105882, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_htrm8"] +bg_color = Color(0.584314, 0.341176, 0.0352941, 1) + +[sub_resource type="Image" id="Image_gq50e"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 224, 224, 224, 139, 224, 224, 224, 232, 224, 224, 224, 231, 225, 225, 225, 135, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 140, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 123, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 232, 224, 224, 224, 255, 227, 227, 227, 62, 229, 229, 229, 29, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 52, 224, 224, 224, 255, 224, 224, 224, 203, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 205, 224, 224, 224, 255, 225, 225, 225, 51, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 156, 224, 224, 224, 255, 224, 224, 224, 99, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 102, 224, 224, 224, 255, 224, 224, 224, 153, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 224, 224, 224, 218, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 41, 224, 224, 224, 255, 224, 224, 224, 214, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 224, 224, 224, 247, 224, 224, 224, 255, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 234, 234, 234, 12, 224, 224, 224, 255, 224, 224, 224, 243, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 219, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 42, 224, 224, 224, 255, 224, 224, 224, 214, 224, 224, 224, 185, 224, 224, 224, 189, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 156, 224, 224, 224, 255, 224, 224, 224, 99, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 103, 224, 224, 224, 255, 224, 224, 224, 152, 224, 224, 224, 184, 224, 224, 224, 188, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 52, 224, 224, 224, 255, 224, 224, 224, 203, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 205, 224, 224, 224, 255, 225, 225, 225, 51, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_h1jbx"] +image = SubResource("Image_gq50e") + +[sub_resource type="Image" id="Image_i4vp1"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 227, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 225, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 73, 224, 224, 224, 226, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 225, 226, 226, 226, 70, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_nst21"] +image = SubResource("Image_i4vp1") + +[sub_resource type="Image" id="Image_j4pgt"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_7n24n"] +image = SubResource("Image_j4pgt") + +[sub_resource type="Image" id="Image_3a2fg"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 73, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 181, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_dn1u1"] +image = SubResource("Image_3a2fg") + +[sub_resource type="Image" id="Image_0reu6"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 211, 224, 224, 224, 255, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 210, 231, 231, 231, 21, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 195, 224, 224, 224, 255, 224, 224, 224, 210, 230, 230, 230, 20, 224, 224, 224, 255, 224, 224, 224, 255, 231, 231, 231, 21, 224, 224, 224, 210, 224, 224, 224, 255, 224, 224, 224, 194, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 178, 224, 224, 224, 194, 230, 230, 230, 20, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 231, 231, 231, 21, 224, 224, 224, 194, 224, 224, 224, 179, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 180, 224, 224, 224, 180, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_3vxqn"] +image = SubResource("Image_0reu6") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wnjke"] +bg_color = Color(0.290196, 0.188235, 0.176471, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i5g62"] +bg_color = Color(0.856756, 0.209476, 0.179991, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7vjg4"] +bg_color = Color(0.615686, 0.117647, 0.113725, 1) + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_k357h"] + +[resource] +Button/styles/disabled = SubResource("StyleBoxFlat_6cuyf") +Button/styles/focus = SubResource("StyleBoxFlat_6hrgu") +Button/styles/hover = SubResource("StyleBoxFlat_6hrgu") +Button/styles/normal = SubResource("StyleBoxFlat_lp54f") +Button/styles/pressed = SubResource("StyleBoxFlat_6hrgu") +CategoryButton/base_type = &"Button" +CategoryButton/styles/disabled = SubResource("StyleBoxFlat_bu1v3") +CategoryButton/styles/focus = SubResource("StyleBoxFlat_6vry1") +CategoryButton/styles/hover = SubResource("StyleBoxFlat_6vry1") +CategoryButton/styles/normal = SubResource("StyleBoxFlat_3obpr") +CategoryButton/styles/pressed = SubResource("StyleBoxFlat_6vry1") +DistanceButton/base_type = &"Button" +DistanceButton/styles/disabled = SubResource("StyleBoxFlat_sncyc") +DistanceButton/styles/focus = SubResource("StyleBoxFlat_utakf") +DistanceButton/styles/hover = SubResource("StyleBoxFlat_utakf") +DistanceButton/styles/normal = SubResource("StyleBoxFlat_htrm8") +DistanceButton/styles/pressed = SubResource("StyleBoxFlat_utakf") +EditorIcons/icons/Categories = ExtResource("1_djdr6") +EditorIcons/icons/Junk = ExtResource("2_k0ayk") +EditorIcons/icons/MemberMethod = SubResource("ImageTexture_h1jbx") +EditorIcons/icons/Remove = SubResource("ImageTexture_nst21") +EditorIcons/icons/Ruler = SubResource("ImageTexture_7n24n") +EditorIcons/icons/Time = ExtResource("4_2vjug") +FileDialog/icons/folder = SubResource("ImageTexture_dn1u1") +FileDialog/icons/parent_folder = SubResource("ImageTexture_3vxqn") +JunkButton/base_type = &"Button" +JunkButton/styles/disabled = SubResource("StyleBoxFlat_wnjke") +JunkButton/styles/focus = SubResource("StyleBoxFlat_i5g62") +JunkButton/styles/hover = SubResource("StyleBoxFlat_i5g62") +JunkButton/styles/normal = SubResource("StyleBoxFlat_7vjg4") +JunkButton/styles/pressed = SubResource("StyleBoxFlat_i5g62") +Margin/base_type = &"PanelContainer" +Margin/styles/panel = SubResource("StyleBoxFlat_k357h") diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn b/demo/addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn new file mode 100644 index 00000000..5b472320 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn @@ -0,0 +1,50 @@ +[gd_scene load_steps=6 format=3 uid="uid://ws63tatc17cq"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Tags/EventBar/Scripts/event_button.gd" id="1_7evyp"] +[ext_resource type="Theme" uid="uid://c4fn4740sh1k1" path="res://addons/MotionMatching/controls/Tags/EventBar/Themes/Events.tres" id="1_otv65"] +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Tags/EventBar/Scripts/Resizable.gd" id="1_u1wsl"] +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Tags/EventBar/Scripts/Movable.gd" id="3_5bwjk"] +[ext_resource type="Texture2D" uid="uid://di1ro7x38jy0v" path="res://addons/MotionMatching/icons/time.res" id="4_txeaj"] + +[node name="EventBar" type="PanelContainer"] +custom_minimum_size = Vector2(0, 30) +anchors_preset = 14 +anchor_top = 0.5 +anchor_right = 1.0 +anchor_bottom = 0.5 +offset_top = -10.0 +offset_bottom = 10.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_otv65") +theme_type_variation = &"Margin" +script = ExtResource("1_7evyp") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +mouse_filter = 0 +mouse_default_cursor_shape = 10 +theme = ExtResource("1_otv65") +theme_type_variation = &"MarginContainer" +theme_override_constants/margin_left = 4 +theme_override_constants/margin_right = 4 +script = ExtResource("1_u1wsl") + +[node name="Button" type="Button" parent="MarginContainer"] +layout_mode = 2 +theme = ExtResource("1_otv65") +button_mask = 3 +text = "EventMF" +icon = ExtResource("4_txeaj") +alignment = 0 +clip_text = true +script = ExtResource("3_5bwjk") + +[node name="PopupMenu" type="PopupMenu" parent="."] +size = Vector2i(150, 116) +item_count = 1 +item_0/text = "Delete" +item_0/id = 1 + +[connection signal="gui_input" from="MarginContainer/Button" to="." method="_button_gui_input"] +[connection signal="id_pressed" from="PopupMenu" to="." method="_on_popup_menu_id_pressed"] diff --git a/demo/addons/MotionMatching/controls/Tags/EventBar/junk_event_bar.tscn b/demo/addons/MotionMatching/controls/Tags/EventBar/junk_event_bar.tscn new file mode 100644 index 00000000..83884028 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/EventBar/junk_event_bar.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=3 format=3 uid="uid://b7a6t74o0gurc"] + +[ext_resource type="PackedScene" uid="uid://ws63tatc17cq" path="res://addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn" id="1_2n6pj"] +[ext_resource type="Texture2D" uid="uid://jvcrioes2c0n" path="res://addons/MotionMatching/icons/junk.res" id="2_34o1s"] + +[node name="EventBar" instance=ExtResource("1_2n6pj")] +offset_top = -11.5 +offset_bottom = 11.5 + +[node name="Button" parent="MarginContainer" index="0"] +theme_type_variation = &"JunkButton" +text = "JunkMF" +icon = ExtResource("2_34o1s") diff --git a/demo/addons/MotionMatching/controls/Tags/TagsEditor.gd b/demo/addons/MotionMatching/controls/Tags/TagsEditor.gd new file mode 100644 index 00000000..6d243010 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/TagsEditor.gd @@ -0,0 +1,179 @@ +@tool +class_name TagEditor extends Control + +@onready var animation_selector: MenuButton = %AnimationSelector +@onready var zoom: HSlider = %Zoom +@onready var timeline: HSlider = %Timeline +@onready var track_list: VBoxContainer = %TrackList +@onready var category_flag_edit: LineEdit = %CategoryFlagEdit + +signal request_pose(animation_name:StringName,timestamp) + +var current_animlib : MMAnimationLibrary +var current_animation : Animation +var current_animation_name : StringName + +const TRACK_BAR = preload("res://addons/MotionMatching/controls/Tags/TracksBar/track_bar.tscn") + +const CATEGORY_EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/Category_Event_Bar.tscn") +const EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn") +const JUNK_EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/junk_event_bar.tscn") + +@export var tags :Array[TagInfo] = [] : + get: + return [] as Array[TagInfo] if current_animlib == null else current_animlib.tags + #set(value) + + +func _on_mm_editor_on_library_change(lib: MMAnimationLibrary) -> void: + pass # Replace with function body. + current_animlib = lib + animation_selector.get_popup().clear(true) + for a in current_animlib.get_animation_list(): + animation_selector.get_popup().add_item(a) + if !animation_selector.get_popup().id_pressed.is_connected(_on_anim_selected): + animation_selector.get_popup().id_pressed.connect(_on_anim_selected) + + timeline.value = 0; + category_flag_edit.text = lib.category_hint_string + _on_anim_selected(0) + pass + +@onready var time_label: Label = %TimeLabel + +func _on_timeline_value_changed(value:float): + var animname :StringName= current_animation_name + var timestamp := timeline.value + time_label.text = "%02.2f" % timestamp + time_label.position.x = get_local_mouse_position().x + request_pose.emit(animname,timestamp) + +func get_length() -> int: + return $MarginContainer/ScrollContainer.size.x / zoom.value + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + zoom.value = 1.0 + zoom.value_changed.connect(_on_zoom_changed) + if current_animlib == null: + return + + var popup = animation_selector.get_popup() + animation_selector.get_popup().clear() + for a in current_animlib.get_animation_list(): + animation_selector.get_popup().add_item(a) + if !animation_selector.get_popup().id_pressed.is_connected(_on_anim_selected): + animation_selector.get_popup().id_pressed.connect(_on_anim_selected) + category_flag_edit.text = current_animlib.category_hint_string + category_flag_edit.text_submitted.emit(category_flag_edit.text) + +var current_tags :Array[TagInfo]= [] + +func _on_anim_selected(index:int): + current_animation_name = current_animlib.get_animation_list()[index] + current_animation = current_animlib.get_animation(current_animation_name) + animation_selector.text = current_animlib.get_animation_list()[index] + + zoom.value = 1.0 + + current_tags = tags.filter(func(tag:TagInfo): + return current_animation_name == tag.animation_name) + + for track in track_list.get_children() as Array[TrackBar]: + track.queue_free() + + var max_track := 0 + if current_tags.size() > 0: + max_track = current_tags.reduce( func(max:TagInfo, val:TagInfo):return val if val.track_id >= max.track_id else max).track_id + # add new empty tracks + for t in range(max_track+2): + var new_track :TrackBar= TRACK_BAR.instantiate() + track_list.add_child(new_track) + track_list.move_child(new_track,t) + new_track.added_track.connect(add_track) + new_track.delete_track.connect(on_delete_track) + new_track.added_event.connect(on_new_tag) + new_track.current_animation = current_animation + new_track.current_library = current_animlib + for t in track_list.get_children(): + t.size.y = 30 + + + for events in current_tags as Array[TagInfo]: + var track := track_list.get_child(events.track_id) as TrackBar + # Add events panel there + prints("Events",events.track_id,events.timestamp,track.get_index()) + track.populate_tag(events,false) + + + timeline.step = 0.016 + timeline.tick_count = 8 + timeline.max_value = current_animation.length + + _on_zoom_changed(zoom.value) + pass + +func _on_zoom_changed(value:float): + if current_animation == null: + return; + + timeline.custom_minimum_size.x = get_length() + $MarginContainer/ScrollContainer/VBoxContainer.queue_redraw() + for child in track_list.get_children(): + var track := child as TrackBar + if track == null: + continue + track.current_library = current_animlib + track.current_animation = current_animation + track.queue_redraw() + timeline.queue_redraw() + track_list.queue_redraw() + + +func add_track(index:int): + var new_track :TrackBar= TRACK_BAR.instantiate() + if(index < track_list.get_child_count()): + track_list.get_child(index).add_sibling(new_track) + else: + track_list.add_child(new_track) + + new_track.size.y = 30 + new_track.added_track.connect(add_track) + new_track.delete_track.connect(on_delete_track) + new_track.added_event.connect(on_new_tag) + new_track.current_animation = current_animation + new_track.current_library = current_animlib + queue_redraw() + + +# the track bar is responsible to delete itself from the tree. +func on_delete_track(index:int): + for tag in tags: + if tag.track_id == index: + tags.erase(tag) + pass + +func on_new_tag(tag:TagInfo): + tag.animation_name = current_animation_name + #tag.duration = 0.016 + tags.append(tag) + for t in track_list.get_children() as Array[Control]: + t.queue_redraw() + return; + + + + +func _on_category_flag_edit_text_submitted(new_text: String) -> void: + current_animlib.category_hint_string = new_text + for tag in current_animlib.tags as Array[TagInfo]: + if tag is TagCategory: + var category_tag := tag as TagCategory + category_tag.property_hint_string = new_text + category_tag.notify_property_list_changed() + pass # Replace with function body. + + +func _on_bias_pressed() -> void: + EditorInterface.inspect_object(current_animlib.get_curvecost_animname(current_animation_name)) + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/controls/Tags/TagsEditor.tscn b/demo/addons/MotionMatching/controls/Tags/TagsEditor.tscn new file mode 100644 index 00000000..a94a3192 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/TagsEditor.tscn @@ -0,0 +1,253 @@ +[gd_scene load_steps=2 format=3 uid="uid://bj4qic5cdef61"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Tags/TagsEditor.gd" id="1_gnt8s"] + +[node name="TagEditorPanel" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_gnt8s") + +[node name="HSplitContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HSplitContainer" type="HBoxContainer" parent="HSplitContainer/PanelContainer"] +layout_mode = 2 + +[node name="AnimationSelector" type="MenuButton" parent="HSplitContainer/PanelContainer/HSplitContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Select Animation" +item_count = 72 +popup/item_0/text = "001-walk-start-forward_rightfoot" +popup/item_1/text = "002-idle_right_foot_front" +popup/item_1/id = 1 +popup/item_2/text = "002-walk-start-left45-rightFoot" +popup/item_2/id = 2 +popup/item_3/text = "003-walk-start-left90-rightFoot" +popup/item_3/id = 3 +popup/item_4/text = "004-walk-start-left135-rightFoot" +popup/item_4/id = 4 +popup/item_5/text = "005-walk-start-left180-rightFoot" +popup/item_5/id = 5 +popup/item_6/text = "006-walk-start-right45-rightFoot" +popup/item_6/id = 6 +popup/item_7/text = "007-walk-start-right90-rightFoot" +popup/item_7/id = 7 +popup/item_8/text = "008-walk-start-right135-rightFoot" +popup/item_8/id = 8 +popup/item_9/text = "009-walk-start-right180-rightFoot" +popup/item_9/id = 9 +popup/item_10/text = "022-walk_loop_leftFoot" +popup/item_10/id = 10 +popup/item_11/text = "023-walk_loop_leftFoot2" +popup/item_11/id = 11 +popup/item_12/text = "025-walk_loop_leftFoot3" +popup/item_12/id = 12 +popup/item_13/text = "029-walk_start_forward" +popup/item_13/id = 13 +popup/item_14/text = "030-walk-start-left45-leftFoot" +popup/item_14/id = 14 +popup/item_15/text = "031-walk-start-left90-leftFoot" +popup/item_15/id = 15 +popup/item_16/text = "032-walk-start-left135-leftFoot" +popup/item_16/id = 16 +popup/item_17/text = "033-walk-start-left180-leftFoot" +popup/item_17/id = 17 +popup/item_18/text = "034-walk-start-right45-leftFoot" +popup/item_18/id = 18 +popup/item_19/text = "035-walk-start-right90-leftFoot" +popup/item_19/id = 19 +popup/item_20/text = "036-walk-start-right135-leftFoot" +popup/item_20/id = 20 +popup/item_21/text = "037-walk-start-right180-leftFoot" +popup/item_21/id = 21 +popup/item_22/text = "038-start_run_leftFoot" +popup/item_22/id = 22 +popup/item_23/text = "039-start_run_leftFoot-45left" +popup/item_23/id = 23 +popup/item_24/text = "040-start_run_leftFoot-90left" +popup/item_24/id = 24 +popup/item_25/text = "041-start_run_leftFoot-135left" +popup/item_25/id = 25 +popup/item_26/text = "042-start_run_leftFoot-180left" +popup/item_26/id = 26 +popup/item_27/text = "043-start_run_leftFoot-45right" +popup/item_27/id = 27 +popup/item_28/text = "044-start_run_leftFoot-90right" +popup/item_28/id = 28 +popup/item_29/text = "045-start_run_leftFoot-135right" +popup/item_29/id = 29 +popup/item_30/text = "046-start_run_leftFoot-180right" +popup/item_30/id = 30 +popup/item_31/text = "047-start_run_rightFoot" +popup/item_31/id = 31 +popup/item_32/text = "048-start_run_rightFoot-45right" +popup/item_32/id = 32 +popup/item_33/text = "049-start_run_rightFoot-90right" +popup/item_33/id = 33 +popup/item_34/text = "050-start_run_rightFoot-135right" +popup/item_34/id = 34 +popup/item_35/text = "051-start_run_rightFoot-180right" +popup/item_35/id = 35 +popup/item_36/text = "052-start_run_rightFoot-45left" +popup/item_36/id = 36 +popup/item_37/text = "053-start_run_rightFoot-90left" +popup/item_37/id = 37 +popup/item_38/text = "054-start_run_rightFoot-135left" +popup/item_38/id = 38 +popup/item_39/text = "055-start_run_rightFoot-180left" +popup/item_39/id = 39 +popup/item_40/text = "056-moveWalkStopRightFoot" +popup/item_40/id = 40 +popup/item_41/text = "057-moveWalkStopLefttFoot" +popup/item_41/id = 41 +popup/item_42/text = "058-moveRunStopLeftFoot" +popup/item_42/id = 42 +popup/item_43/text = "059-moveRunStopRightFoot" +popup/item_43/id = 43 +popup/item_44/text = "060-snake-wide" +popup/item_44/id = 44 +popup/item_45/text = "062-snake-tight" +popup/item_45/id = 45 +popup/item_46/text = "063-acceleration-deceleration-walkRun" +popup/item_46/id = 46 +popup/item_47/text = "064-acceleration-deceleration-walkRun2" +popup/item_47/id = 47 +popup/item_48/text = "065-Arc-Left_Wide" +popup/item_48/id = 48 +popup/item_49/text = "065-Arc-Right_Wide" +popup/item_49/id = 49 +popup/item_50/text = "066-Arc-Left_Medium" +popup/item_50/id = 50 +popup/item_51/text = "066-Arc-Right_Medium" +popup/item_51/id = 51 +popup/item_52/text = "067-Arc-Left_Tight" +popup/item_52/id = 52 +popup/item_53/text = "067-Arc-Right_Tight" +popup/item_53/id = 53 +popup/item_54/text = "068-shuffle-leftFoot-Forward" +popup/item_54/id = 54 +popup/item_55/text = "069-shuffle-leftFoot-45Left" +popup/item_55/id = 55 +popup/item_56/text = "071-shuffle-leftFoot-90Left" +popup/item_56/id = 56 +popup/item_57/text = "072-shuffle-leftFoot-135Left" +popup/item_57/id = 57 +popup/item_58/text = "073-shuffle-leftFoot-180Left" +popup/item_58/id = 58 +popup/item_59/text = "074-shuffle-leftFoot-45Right" +popup/item_59/id = 59 +popup/item_60/text = "075-shuffle-leftFoot-90Right" +popup/item_60/id = 60 +popup/item_61/text = "076-shuffle-leftFoot-135Right" +popup/item_61/id = 61 +popup/item_62/text = "077-shuffle-leftFoot-180Right" +popup/item_62/id = 62 +popup/item_63/text = "078-shuffle-rightFoot-forward" +popup/item_63/id = 63 +popup/item_64/text = "079-shuffle-rightFoot-right45" +popup/item_64/id = 64 +popup/item_65/text = "080-shuffle-rightFoot-right90" +popup/item_65/id = 65 +popup/item_66/text = "081-shuffle-rightFoot-right135" +popup/item_66/id = 66 +popup/item_67/text = "082-shuffle-rightFoot-right180" +popup/item_67/id = 67 +popup/item_68/text = "083-shuffle-rightFoot-Left45" +popup/item_68/id = 68 +popup/item_69/text = "084-shuffle-rightFoot-Left90" +popup/item_69/id = 69 +popup/item_70/text = "085-shuffle-rightFoot-Left135" +popup/item_70/id = 70 +popup/item_71/text = "086-shuffle-rightFoot-Left180" +popup/item_71/id = 71 + +[node name="Button" type="Button" parent="HSplitContainer/PanelContainer/HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 10 +text = "Modify Bias" + +[node name="HSeparator" type="VSeparator" parent="HSplitContainer"] +layout_mode = 2 + +[node name="PanelContainer2" type="HBoxContainer" parent="HSplitContainer"] +layout_mode = 2 +size_flags_horizontal = 8 + +[node name="CategoryFlagEdit" type="LineEdit" parent="HSplitContainer/PanelContainer2"] +unique_name_in_owner = true +custom_minimum_size = Vector2(300, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Idle,Walk,Run,Strafe" +placeholder_text = "Categories flag string" + +[node name="Zoom" type="HSlider" parent="HSplitContainer/PanelContainer2"] +unique_name_in_owner = true +clip_contents = true +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +size_flags_vertical = 1 +min_value = 0.05 +max_value = 1.0 +step = 0.05 +value = 1.0 +tick_count = 5 +ticks_on_borders = true + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/margin_left = 5 +theme_override_constants/margin_right = 5 + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer"] +layout_mode = 2 +tooltip_text = "15" + +[node name="VBoxContainer" type="VSplitContainer" parent="MarginContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +collapsed = true + +[node name="Timeline" type="HSlider" parent="MarginContainer/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(8, 0) +layout_mode = 2 +size_flags_vertical = 1 +max_value = 2.63333 +step = 0.016 +tick_count = 8 +ticks_on_borders = true + +[node name="TimeLabel" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer/Timeline"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 4 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_top = -13.0 +offset_right = 41.0 +offset_bottom = 13.0 +grow_vertical = 2 +size_flags_vertical = 1 +text = "Time" + +[node name="TrackList" type="VBoxContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[connection signal="pressed" from="HSplitContainer/PanelContainer/HSplitContainer/Button" to="." method="_on_bias_pressed"] +[connection signal="text_submitted" from="HSplitContainer/PanelContainer2/CategoryFlagEdit" to="." method="_on_category_flag_edit_text_submitted"] +[connection signal="value_changed" from="MarginContainer/ScrollContainer/VBoxContainer/Timeline" to="." method="_on_timeline_value_changed"] diff --git a/demo/addons/MotionMatching/controls/Tags/TracksBar/Scripts/track_bar.gd b/demo/addons/MotionMatching/controls/Tags/TracksBar/Scripts/track_bar.gd new file mode 100644 index 00000000..f7053230 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/TracksBar/Scripts/track_bar.gd @@ -0,0 +1,144 @@ +@tool +class_name TrackBar extends Panel + +@onready var popup_menu: PopupMenu = $PopupMenu + +var current_library : MMAnimationLibrary = null +var current_animation :Animation = null + +signal added_track(id:int) +signal delete_track(id:int) +signal added_event(tag:TagInfo) + +var last_right_click_pos := Vector2.ZERO + +const CATEGORY_EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/Category_Event_Bar.tscn") +const EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/event_bar.tscn") +const JUNK_EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/junk_event_bar.tscn") +const DISTANCE_EVENT_BAR = preload("res://addons/MotionMatching/controls/Tags/EventBar/Distance_Event_Bar.tscn") + +func _can_drop_data(at_position: Vector2, data: Variant) -> bool: + return data is MoveableUI or (data is ResizeableUI and data.ref.tag.track_id == get_index()) + +func _drop_data(at_position: Vector2, data: Variant) -> void: + var mouse = get_local_mouse_position() + if data is MoveableUI: + var ev := (data as MoveableUI).ref + ev.reparent(self,false) + var released_pos = (at_position.x + data.offset.x) / size.x + ev.tag.timestamp = max(0,current_animation.length * released_pos) + ev.tag.duration = min(ev.tag.duration,current_animation.length - ev.tag.timestamp) + ev.tag.track_id = get_index() + elif data is ResizeableUI: + var old_pos = data.ref.tag.timestamp + var old_size = data.ref.tag.duration + if data.drag_left: + # A B C + var end_time = data.ref.tag.timestamp + data.ref.tag.duration + data.ref.tag.timestamp = current_animation.length * at_position.x / size.x + data.ref.tag.duration = (end_time - data.ref.tag.timestamp) + data.ref.tag.duration = max(0,data.ref.tag.duration) + data.ref.tag.timestamp = max(0,data.ref.tag.timestamp) + elif data.drag_right: + var end_time :float= current_animation.length * at_position.x / size.x + data.ref.tag.duration = (end_time - data.ref.tag.timestamp) + data.ref.tag.timestamp = data.ref.tag.timestamp + data.ref.tag.duration = max(0,data.ref.tag.duration) + data.ref.tag.timestamp = max(0,data.ref.tag.timestamp) + pass + + queue_redraw() + pass + +func _gui_input(event: InputEvent) -> void: + var mouse := event as InputEventMouseButton + if mouse != null and mouse.button_index == MOUSE_BUTTON_RIGHT: + last_right_click_pos = get_local_mouse_position() + popup_menu.popup(Rect2i(get_global_mouse_position(),popup_menu.size)) + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + #popup_menu.id_pressed.connect(_on_popup_select) + pass + +enum {ADDCATEGORY=01, ADDTIMING = 02, ADDDISTANCE=03 , ADDJUNK = 04, + ADDTRACK = 10, + DELETETRACK = 20} + +func populate_tag(tag:TagInfo,emit:bool = true): + var EVB :EventButton + if tag is TagJunk: + EVB = JUNK_EVENT_BAR.instantiate() + elif tag is TagCategory: + EVB = CATEGORY_EVENT_BAR.instantiate() + EVB.property_hint_string = current_library.category_hint_string + elif tag is TagMFEvent: + EVB = EVENT_BAR.instantiate() + elif tag is TagMFDistance: + EVB = DISTANCE_EVENT_BAR.instantiate() + else: + prints("Not implemented",tag.resource_name) + EVB.tag = tag + EVB.animation = current_animation + add_child(EVB) + EVB.delete_event.connect(func(tag:TagInfo): + current_library.tags.erase(tag) + ) + + if emit: + added_event.emit(tag) + queue_redraw() + + +func _clear_no_delete(): + for c in get_children(): + if c is EventButton: + c.queue_free() + queue_redraw() + +func _draw() -> void: + # the size should be set now. + for evbutton in get_children() as Array[Control]: + #var evbutton := child as EventButton + if evbutton == null or not evbutton is EventButton: + continue; + + evbutton.animation = current_animation + evbutton.position.x = size.x * evbutton.tag.timestamp / current_animation.length + evbutton.size.x = size.x * evbutton.tag.duration / current_animation.length + evbutton.custom_minimum_size.x = current_animation.length * size.x * (1.0 / Engine.physics_ticks_per_second) + evbutton.queue_redraw() + pass + + +func _on_popup_menu_id_pressed(id: int) -> void: + var index := get_index() + match id: + ADDTRACK: + added_track.emit(index) + DELETETRACK: + delete_track.emit(index) + if index != 0: + queue_free() + ADDCATEGORY: + var new_tag = TagCategory.new() + new_tag.timestamp = (last_right_click_pos.x / size.x) * current_animation.length + new_tag.track_id = get_index() + populate_tag(new_tag) + ADDTIMING: + var new_tag = TagMFEvent.new() + new_tag.timestamp = (last_right_click_pos.x / size.x) * current_animation.length + new_tag.track_id = get_index() + populate_tag(new_tag) + ADDDISTANCE: + var new_tag = TagMFDistance.new() + new_tag.timestamp = (last_right_click_pos.x / size.x) * current_animation.length + new_tag.track_id = get_index() + populate_tag(new_tag) + ADDJUNK: + var new_tag = TagJunk.new() + new_tag.timestamp = (last_right_click_pos.x / size.x) * current_animation.length + new_tag.track_id = get_index() + populate_tag(new_tag) + queue_redraw() diff --git a/demo/addons/MotionMatching/controls/Tags/TracksBar/track_bar.tscn b/demo/addons/MotionMatching/controls/Tags/TracksBar/track_bar.tscn new file mode 100644 index 00000000..679e1685 --- /dev/null +++ b/demo/addons/MotionMatching/controls/Tags/TracksBar/track_bar.tscn @@ -0,0 +1,103 @@ +[gd_scene load_steps=10 format=3 uid="uid://cvfu2yfl33icj"] + +[ext_resource type="Script" path="res://addons/MotionMatching/controls/Tags/TracksBar/Scripts/track_bar.gd" id="1_b7x0t"] + +[sub_resource type="Image" id="Image_h3ro8"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 73, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 181, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 181, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_boa2m"] +image = SubResource("Image_h3ro8") + +[sub_resource type="Image" id="Image_1ewd3"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 4, 227, 227, 227, 36, 227, 227, 227, 36, 255, 255, 255, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 131, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 5, 225, 225, 225, 76, 224, 224, 224, 255, 224, 224, 224, 255, 226, 226, 226, 77, 255, 255, 255, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 99, 224, 224, 224, 232, 224, 224, 224, 244, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 244, 224, 224, 224, 233, 224, 224, 224, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 135, 224, 224, 224, 247, 224, 224, 224, 115, 234, 234, 234, 12, 224, 224, 224, 130, 224, 224, 224, 130, 234, 234, 234, 12, 225, 225, 225, 116, 224, 224, 224, 248, 224, 224, 224, 132, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 77, 224, 224, 224, 251, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 66, 224, 224, 224, 252, 225, 225, 225, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 201, 224, 224, 224, 146, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 2, 224, 224, 224, 146, 224, 224, 224, 106, 255, 255, 255, 0, 225, 225, 225, 150, 224, 224, 224, 195, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 255, 226, 226, 226, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 233, 233, 233, 23, 225, 225, 225, 166, 224, 224, 224, 237, 228, 228, 228, 47, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 16, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 67, 224, 224, 224, 255, 225, 225, 225, 215, 227, 227, 227, 9, 255, 255, 255, 0, 255, 255, 255, 0, 223, 223, 223, 239, 224, 224, 224, 253, 224, 224, 224, 49, 255, 255, 255, 0, 230, 230, 230, 30, 224, 224, 224, 230, 224, 224, 224, 255, 224, 224, 224, 49, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 41, 224, 224, 224, 255, 225, 225, 225, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 139, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 5, 225, 225, 225, 117, 224, 224, 224, 255, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 240, 226, 226, 226, 87, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 96, 224, 224, 224, 236, 255, 255, 255, 3, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 143, 224, 224, 224, 211, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 232, 232, 232, 11, 224, 224, 224, 216, 225, 225, 225, 141, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 238, 238, 238, 15, 224, 224, 224, 220, 224, 224, 224, 178, 238, 238, 238, 15, 255, 255, 255, 0, 225, 225, 225, 51, 225, 225, 225, 51, 255, 255, 255, 0, 227, 227, 227, 18, 224, 224, 224, 184, 224, 224, 224, 218, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 36, 224, 224, 224, 212, 224, 224, 224, 232, 225, 225, 225, 133, 224, 224, 224, 251, 224, 224, 224, 240, 225, 225, 225, 135, 224, 224, 224, 234, 224, 224, 224, 208, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 10, 224, 224, 224, 107, 224, 224, 224, 197, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 196, 224, 224, 224, 104, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_ox10s"] +image = SubResource("Image_1ewd3") + +[sub_resource type="Image" id="Image_s6xcw"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_y7ian"] +image = SubResource("Image_s6xcw") + +[sub_resource type="Image" id="Image_dnnmf"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 227, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 225, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 73, 224, 224, 224, 226, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 225, 226, 226, 226, 70, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_ckkpx"] +image = SubResource("Image_dnnmf") + +[node name="TrackBar" type="Panel"] +custom_minimum_size = Vector2(0, 30) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +script = ExtResource("1_b7x0t") + +[node name="PopupMenu" type="PopupMenu" parent="."] +size = Vector2i(271, 312) +visible = true +unfocusable = true +item_count = 11 +item_0/text = "Motion Matching" +item_0/id = 99 +item_0/disabled = true +item_0/separator = true +item_1/text = "Add Category" +item_1/icon = SubResource("ImageTexture_boa2m") +item_1/id = 1 +item_2/text = "Add Timing Feature" +item_2/icon = SubResource("ImageTexture_ox10s") +item_2/id = 2 +item_3/text = "Add Distance Feature" +item_3/icon = SubResource("ImageTexture_y7ian") +item_3/id = 3 +item_4/text = "Add Junk" +item_4/icon = SubResource("ImageTexture_ckkpx") +item_4/id = 4 +item_5/text = "Events" +item_5/id = 99 +item_5/disabled = true +item_5/separator = true +item_6/text = "Add Motion Warping" +item_6/id = 11 +item_7/text = "Tracks" +item_7/id = 99 +item_7/disabled = true +item_7/separator = true +item_8/text = "Add Track Below" +item_8/id = 10 +item_9/text = "Deletion" +item_9/id = 99 +item_9/disabled = true +item_9/separator = true +item_10/text = "[!] Delete this track and events" +item_10/id = 20 + +[connection signal="id_pressed" from="PopupMenu" to="." method="_on_popup_menu_id_pressed"] diff --git a/demo/addons/MotionMatching/icons/categories.res b/demo/addons/MotionMatching/icons/categories.res new file mode 100644 index 00000000..4d719519 Binary files /dev/null and b/demo/addons/MotionMatching/icons/categories.res differ diff --git a/demo/addons/MotionMatching/icons/distance.res b/demo/addons/MotionMatching/icons/distance.res new file mode 100644 index 00000000..3015bc69 Binary files /dev/null and b/demo/addons/MotionMatching/icons/distance.res differ diff --git a/addons/MotionMatching/icons/icon_mm.svg b/demo/addons/MotionMatching/icons/icon_mm.svg similarity index 100% rename from addons/MotionMatching/icons/icon_mm.svg rename to demo/addons/MotionMatching/icons/icon_mm.svg diff --git a/demo/addons/MotionMatching/icons/icon_mm.svg.import b/demo/addons/MotionMatching/icons/icon_mm.svg.import new file mode 100644 index 00000000..67cc6a07 --- /dev/null +++ b/demo/addons/MotionMatching/icons/icon_mm.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bbinu64yf6v80" +path="res://.godot/imported/icon_mm.svg-de91a774c567af5958e8d9d4cc4452ba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/MotionMatching/icons/icon_mm.svg" +dest_files=["res://.godot/imported/icon_mm.svg-de91a774c567af5958e8d9d4cc4452ba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/MotionMatching/icons/icon_mmal.svg b/demo/addons/MotionMatching/icons/icon_mmal.svg similarity index 100% rename from addons/MotionMatching/icons/icon_mmal.svg rename to demo/addons/MotionMatching/icons/icon_mmal.svg diff --git a/demo/addons/MotionMatching/icons/icon_mmal.svg.import b/demo/addons/MotionMatching/icons/icon_mmal.svg.import new file mode 100644 index 00000000..2123b26a --- /dev/null +++ b/demo/addons/MotionMatching/icons/icon_mmal.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d4j5jqxmne8lh" +path="res://.godot/imported/icon_mmal.svg-7afcf97a80f7a8a7b68bc52aacec530a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/MotionMatching/icons/icon_mmal.svg" +dest_files=["res://.godot/imported/icon_mmal.svg-7afcf97a80f7a8a7b68bc52aacec530a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/MotionMatching/icons/icon_mmap.svg b/demo/addons/MotionMatching/icons/icon_mmap.svg similarity index 100% rename from addons/MotionMatching/icons/icon_mmap.svg rename to demo/addons/MotionMatching/icons/icon_mmap.svg diff --git a/demo/addons/MotionMatching/icons/icon_mmap.svg.import b/demo/addons/MotionMatching/icons/icon_mmap.svg.import new file mode 100644 index 00000000..897ce5a7 --- /dev/null +++ b/demo/addons/MotionMatching/icons/icon_mmap.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbavjk060qkhc" +path="res://.godot/imported/icon_mmap.svg-72bfc0b317444a9f63eae9b98b4891e7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/MotionMatching/icons/icon_mmap.svg" +dest_files=["res://.godot/imported/icon_mmap.svg-72bfc0b317444a9f63eae9b98b4891e7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/demo/addons/MotionMatching/icons/junk.res b/demo/addons/MotionMatching/icons/junk.res new file mode 100644 index 00000000..cd5d0a02 Binary files /dev/null and b/demo/addons/MotionMatching/icons/junk.res differ diff --git a/demo/addons/MotionMatching/icons/load.res b/demo/addons/MotionMatching/icons/load.res new file mode 100644 index 00000000..cb4f28ca Binary files /dev/null and b/demo/addons/MotionMatching/icons/load.res differ diff --git a/demo/addons/MotionMatching/icons/reload.res b/demo/addons/MotionMatching/icons/reload.res new file mode 100644 index 00000000..e01794c0 Binary files /dev/null and b/demo/addons/MotionMatching/icons/reload.res differ diff --git a/demo/addons/MotionMatching/icons/time.res b/demo/addons/MotionMatching/icons/time.res new file mode 100644 index 00000000..801b050c Binary files /dev/null and b/demo/addons/MotionMatching/icons/time.res differ diff --git a/addons/MotionMatching/plugin.cfg b/demo/addons/MotionMatching/plugin.cfg similarity index 86% rename from addons/MotionMatching/plugin.cfg rename to demo/addons/MotionMatching/plugin.cfg index ad3d065f..f79ef5e5 100644 --- a/addons/MotionMatching/plugin.cfg +++ b/demo/addons/MotionMatching/plugin.cfg @@ -4,4 +4,4 @@ name="MotionMatching" description="Implementation Motion Matching for GodotEngine. Based on Daniel Holden aka OrangeDuck" author="Remi" version="v0.6-alpha" -script="MMPlugin.gd" +script="MMEditorPlugin.gd" diff --git a/demo/addons/MotionMatching/scenes/MMSkeletonEditor.gd b/demo/addons/MotionMatching/scenes/MMSkeletonEditor.gd new file mode 100644 index 00000000..42c9b79c --- /dev/null +++ b/demo/addons/MotionMatching/scenes/MMSkeletonEditor.gd @@ -0,0 +1,35 @@ +@tool +## meta-name : MM Editor Skeleton +## meta-description : Don't touch +class_name MMSkeletonEditor extends Skeleton3D + +var current_animlib : MMAnimationLibrary +var current_anim : Animation +var current_time : float = 0.0 + +func _enter_tree() -> void: + prints("MMSkeleton enter_tree") + + + +func _exit_tree() -> void: + prints("MMSkeleton exit_tree") + +func set_bones(profile : SkeletonProfile) -> void: + clear_bones() + for counter in range(profile.bone_size): + var name = profile.get_bone_name(counter) + var rest := profile.get_reference_pose(counter) + var parent = profile.get_bone_parent(counter) + var tail = profile.get_bone_tail(counter) + var id := add_bone(name) + set_bone_rest(id,rest) + set_bone_parent(id,find_bone(parent)) + reset_bone_poses() + force_update_all_bone_transforms() + +func preview_anim(): + + pass + + diff --git a/demo/addons/MotionMatching/scenes/TagsScene/TagsPanel.gd b/demo/addons/MotionMatching/scenes/TagsScene/TagsPanel.gd new file mode 100644 index 00000000..ebd5edd9 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/TagsScene/TagsPanel.gd @@ -0,0 +1,86 @@ +@tool +class_name TagsPanel extends MarginContainer + +var _lib : MMAnimationLibrary +var _anim : Animation +var _animname + +@onready var text_category: LineEdit = %TextCategory + +@onready var tracklist: VBoxContainer = %Tracklist +const TAGGED_BAR = preload("res://addons/MotionMatching/scenes/TagsScene/tagged_bar.tscn") + +func _on_lib_changed(lib:MMAnimationLibrary): + _lib = lib + _animname = _lib.get_animation_list()[0] + text_category.text = _lib.category_hint_string + queue_redraw() + +func _on_anim_changed(lib:MMAnimationLibrary,animname:String): + _lib = lib + text_category.text = _lib.category_hint_string + _anim = _lib.get_animation(animname) + _animname = animname + queue_redraw() + +func _draw() -> void: + for previous_tags in tracklist.get_children(): + tracklist.remove_child(previous_tags) + previous_tags.queue_free() + + if _lib == null: + return + elif _anim == null: + return; + + var animtags :Array[TagInfo]= _lib.tags.filter(func (x:TagInfo): + return x.animation_name == _animname + ) + + for tag in animtags: + var new_tag :TaggedBar= TAGGED_BAR.instantiate() + new_tag.lib = _lib + new_tag.tag = tag + new_tag.duration = _lib.get_animation(_animname).length + + tracklist.add_child(new_tag) + new_tag.set_owner(tracklist) + + + + +func _on_text_category_text_submitted(new_text: String) -> void: + if _lib != null: + _lib.category_hint_string = new_text # will update the rest. + _lib.emit_changed() + ResourceSaver.save(_lib) + pass # Replace with function body. + +@onready var options := %PopupMenu +func _on_scroll_container_gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.is_pressed(): + match event.button_index: + MOUSE_BUTTON_LEFT: + pass + MOUSE_BUTTON_RIGHT: + options.visible = true + options.position = get_global_mouse_position() + accept_event() + pass + pass # Replace with function body. + +class EditorTagSelector extends RefCounted: + @export var tag : TagInfo + + +func _on_popup_menu_index_pressed(index: int) -> void: + if options.get_item_text(index) == "Add New Tag": + var new_tag :TaggedBar= TAGGED_BAR.instantiate() + new_tag.lib = _lib + new_tag.animname = _animname + new_tag.duration = _lib.get_animation(_animname).length + + tracklist.add_child(new_tag) + new_tag.set_owner(self) + var tag_selector := EditorTagSelector.new() + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.gd b/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.gd new file mode 100644 index 00000000..1adde8d9 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.gd @@ -0,0 +1,80 @@ +@tool +class_name TaggedBar extends Button + +@export var tag : TagInfo : + get: + return tag + set(value): + tag = value + notify_property_list_changed() +@onready var panel: Panel = %Panel + +var lib : MMAnimationLibrary +var animname : String +var duration : float + +func _ready(): + if tag != null: + tag.changed.connect(queue_redraw) + duration = lib.get_animation(tag.animation_name).length + text = tag.get_class() + options.index_pressed.connect(_on_option_index) + +func _on_option_index(index:int): + if options.get_item_text(index) == "Delete": + lib.tags.erase(tag) + queue_free() + if options.get_item_text(index) == "Duplicate": + var new_tag := tag.duplicate() + lib.tags.append(new_tag) + +func _draw() -> void: + if tag != null: + if duration > 0.0: + panel.position.x = (tag.timestamp / duration) * size.x + panel.size.x = (tag.duration / duration) * size.x + text = tag.get_class() + if not tag.resource_name.is_empty(): + text += tag.resource_name + +@onready var options :PopupMenu= %PopupMenu + +class EditorTagSelector extends RefCounted: + @export var tag : TagInfo: + get: + return tag + set(value): + tag = value + notify_property_list_changed() +@onready var tag_selector := EditorTagSelector.new() + +func _on_gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton and event.is_pressed(): + if tag != null: + EditorInterface.inspect_object(tag) + else: + tag_selector.tag = null + EditorInterface.inspect_object(tag_selector) + tag_selector.property_list_changed.connect(func(): + tag = tag_selector.tag + lib.tags.append(tag) + tag.animation_name = animname + text = tag.get_class() + + if tag is TagCategory: + tag.property_hint_string = get_owner().text_category.text + owner.queue_redraw() + self.call_deferred("_inspect_tag_deferred") + ,CONNECT_ONE_SHOT) + match event.button_index: + MOUSE_BUTTON_LEFT: + pass + MOUSE_BUTTON_RIGHT: + options.visible = true + options.position = get_global_mouse_position() + accept_event() + pass + pass # Replace with function body. + +func _inspect_tag_deferred(): + EditorInterface.inspect_object(tag) diff --git a/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.tscn b/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.tscn new file mode 100644 index 00000000..64d08836 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/TagsScene/tagged_bar.tscn @@ -0,0 +1,50 @@ +[gd_scene load_steps=4 format=3 uid="uid://k1u326tu28tv"] + +[ext_resource type="Texture2D" uid="uid://jvcrioes2c0n" path="res://addons/MotionMatching/icons/junk.res" id="1_tsxyu"] +[ext_resource type="Script" path="res://addons/MotionMatching/scenes/TagsScene/tagged_bar.gd" id="2_pwmyi"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n1rc7"] +bg_color = Color(0.19864, 0.1092, 0.78, 1) +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +border_color = Color(0.4161, 0.464835, 0.57, 1) + +[node name="TaggedBar" type="Button"] +custom_minimum_size = Vector2(0, 30) +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 31.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +button_mask = 3 +text = "Choose a Tag type in the Inspector" +alignment = 0 +script = ExtResource("2_pwmyi") + +[node name="Panel" type="Panel" parent="."] +unique_name_in_owner = true +show_behind_parent = true +custom_minimum_size = Vector2(1, 0) +layout_direction = 2 +layout_mode = 1 +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 106.0 +grow_vertical = 2 +size_flags_horizontal = 0 +mouse_filter = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_n1rc7") + +[node name="PopupMenu" type="PopupMenu" parent="."] +unique_name_in_owner = true +size = Vector2i(110, 100) +item_count = 2 +item_0/text = "Duplicate" +item_0/id = 1 +item_1/text = "Delete" +item_1/icon = ExtResource("1_tsxyu") +item_1/id = 1 + +[connection signal="gui_input" from="." to="." method="_on_gui_input"] diff --git a/demo/addons/MotionMatching/scenes/TagsScene/tags.tscn b/demo/addons/MotionMatching/scenes/TagsScene/tags.tscn new file mode 100644 index 00000000..c31b053f --- /dev/null +++ b/demo/addons/MotionMatching/scenes/TagsScene/tags.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=2 format=3 uid="uid://dre3pynuy4g67"] + +[ext_resource type="Script" path="res://addons/MotionMatching/scenes/TagsScene/TagsPanel.gd" id="1_eq3tu"] + +[node name="Tags" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 2 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 2 +script = ExtResource("1_eq3tu") +metadata/_tab_index = 1 +metadata/_edit_use_anchors_ = true + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="HBoxContainer" type="CenterContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 4 + +[node name="TextCategory" type="LineEdit" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Enter comma-separated name for your categories (Standing=1,Crouching=2,...)" +expand_to_text_length = true + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/PanelContainer"] +layout_mode = 2 +size_flags_vertical = 3 +vertical_scroll_mode = 2 + +[node name="Tracklist" type="VBoxContainer" parent="VBoxContainer/PanelContainer/ScrollContainer"] +unique_name_in_owner = true +clip_contents = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="PopupMenu" type="PopupMenu" parent="."] +unique_name_in_owner = true +size = Vector2i(134, 100) +item_count = 1 +item_0/text = "Add New Tag" + +[connection signal="text_submitted" from="VBoxContainer/HBoxContainer/TextCategory" to="." method="_on_text_category_text_submitted"] +[connection signal="gui_input" from="VBoxContainer/PanelContainer/ScrollContainer" to="." method="_on_scroll_container_gui_input"] +[connection signal="index_pressed" from="PopupMenu" to="." method="_on_popup_menu_index_pressed"] diff --git a/demo/addons/MotionMatching/scenes/bottom_scene/MM_BottomScene.tscn b/demo/addons/MotionMatching/scenes/bottom_scene/MM_BottomScene.tscn new file mode 100644 index 00000000..adfe67b1 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/bottom_scene/MM_BottomScene.tscn @@ -0,0 +1,142 @@ +[gd_scene load_steps=6 format=3 uid="uid://g18eonv06rv0"] + +[ext_resource type="Script" path="res://addons/MotionMatching/scenes/bottom_scene/mm_bottom_scene.gd" id="1_xkmiy"] +[ext_resource type="PackedScene" uid="uid://ey2osuvm28ql" path="res://addons/MotionMatching/controls/AnimationPanel/AnimationPanel.tscn" id="2_ubkmk"] +[ext_resource type="PackedScene" uid="uid://dre3pynuy4g67" path="res://addons/MotionMatching/scenes/TagsScene/tags.tscn" id="3_gek87"] +[ext_resource type="PackedScene" uid="uid://ceb0rxuep0ibs" path="res://addons/MotionMatching/controls/DataViewer/DataPanel.tscn" id="3_pa2q6"] +[ext_resource type="PackedScene" uid="uid://dnq5mwu8hbyn7" path="res://addons/MotionMatching/controls/Stats/stats_panel.tscn" id="5_lo6tn"] + +[node name="MmBottomScene" type="PanelContainer"] +custom_minimum_size = Vector2(0, 300) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_xkmiy") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="TabContainer" type="TabContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +current_tab = 0 + +[node name="Animations" parent="VBoxContainer/TabContainer" instance=ExtResource("2_ubkmk")] +unique_name_in_owner = true +layout_mode = 2 +metadata/_tab_index = 0 + +[node name="Tags" parent="VBoxContainer/TabContainer" instance=ExtResource("3_gek87")] +unique_name_in_owner = true +visible = false +layout_mode = 2 + +[node name="Data" parent="VBoxContainer/TabContainer" instance=ExtResource("3_pa2q6")] +unique_name_in_owner = true +visible = false +layout_mode = 2 +metadata/_tab_index = 2 + +[node name="Stats" parent="VBoxContainer/TabContainer" instance=ExtResource("5_lo6tn")] +unique_name_in_owner = true +visible = false +layout_mode = 2 +metadata/_tab_index = 3 + +[node name="InfoContainer" type="VBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/InfoContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_right = 20 + +[node name="PoseSlider" type="HSlider" parent="VBoxContainer/InfoContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +step = 0.1 +ticks_on_borders = true + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/InfoContainer"] +layout_mode = 2 + +[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/InfoContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_right = 10 + +[node name="HBoxContainer" type="HSplitContainer" parent="VBoxContainer/InfoContainer/MarginContainer2"] +layout_mode = 2 +theme_override_constants/separation = 20 +split_offset = 400 +dragger_visibility = 2 + +[node name="MarginContainer" type="HBoxContainer" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_constants/separation = 10 + +[node name="PrevButton" type="Button" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = " < " + +[node name="NextButton" type="Button" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +disabled = true +text = " > " + +[node name="AnimLabel" type="Label" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +text = "selected animation" +clip_text = true + +[node name="WeightsButton" type="Button" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Recalculate Weights +" + +[node name="BakeButton" type="Button" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Bake +" + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer"] +layout_mode = 2 + +[node name="TimeEdit" type="SpinBox" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +step = 0.016 +alignment = 2 +suffix = "/00.0s" + +[node name="TimeEditTimer" type="Timer" parent="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/TimeEdit"] +unique_name_in_owner = true +wait_time = 0.1 +one_shot = true + +[node name="Options" type="PanelContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Options"] +layout_mode = 2 +theme_override_constants/separation = 10 +alignment = 2 + +[connection signal="pressed" from="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer/PrevButton" to="." method="_on_prev_button_pressed"] +[connection signal="pressed" from="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer/NextButton" to="." method="_on_next_button_pressed"] +[connection signal="pressed" from="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer/WeightsButton" to="." method="_on_weights_button_pressed"] +[connection signal="pressed" from="VBoxContainer/InfoContainer/MarginContainer2/HBoxContainer/MarginContainer/BakeButton" to="." method="_on_bake_button_pressed"] diff --git a/demo/addons/MotionMatching/scenes/bottom_scene/mm_bottom_scene.gd b/demo/addons/MotionMatching/scenes/bottom_scene/mm_bottom_scene.gd new file mode 100644 index 00000000..9535dc77 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/bottom_scene/mm_bottom_scene.gd @@ -0,0 +1,136 @@ +@tool +class_name MMBottonScene extends Control + + +signal animation_selected(mmlib:MMAnimationLibrary,anim :String) +signal pose_selected(mmlib:MMAnimationLibrary,anim :String, time:float) + +@onready var prev_button: Button = %PrevButton +@onready var next_button: Button = %NextButton +@onready var anim_label: Label = %AnimLabel + +@onready var pose_slider: HSlider = %PoseSlider +@onready var time: SpinBox = %TimeEdit +@onready var time_timer: Timer = %TimeEditTimer + +@onready var animations: AnimationPanel = %Animations +@onready var data: DataPanel = %Data +@onready var tags: TagsPanel = %Tags +@onready var stats: StatsPanel = %Stats + +var current_lib : MMAnimationLibrary = null +var current_animation : String +var current_time : float = 0.0 + +func _ready() -> void: + pose_slider.value_changed.connect(time.set_value) + time.value_changed.connect(pose_slider.set_value_no_signal) + + time_timer.timeout.connect(func(): + pose_selected.emit(current_lib,current_animation,current_time)) + time.value_changed.connect(func(t): + if current_time != t: + current_time = t + if time_timer.is_stopped(): + time_timer.start() + ) + + _connect_tab_animation() + _connect_tab_data() + _connect_tab_stats() + _connect_tab_tags() + + + +func _connect_tab_animation(): + animations.on_animation_selected.connect(_on_animation_selected) + pass +func _connect_tab_tags(): + animation_selected.connect(tags._on_anim_changed) + pass +func _connect_tab_data(): + pose_selected.connect(data.show_data) + pass +func _connect_tab_stats(): + pass + +func on_lib_selected(lib:MMAnimationLibrary): + if current_lib: + current_lib.changed.disconnect(_reset_slider) + current_lib = lib + current_animation = current_lib.get_animation_list()[0] + current_time = 0.0 + current_lib.changed.connect(_reset_slider) + + animations._on_mm_editor_on_library_change(lib) + stats._on_lib_changed(lib) + + prev_button.disabled = false; + next_button.disabled = false; + + _reset_slider() + +func _on_animation_selected(lib:MMAnimationLibrary,n :String): + current_lib = lib + current_animation = n + current_time = 0.0 + time.value = current_time + _reset_slider() + pose_selected.emit(current_lib,current_animation,current_time) + animation_selected.emit(current_lib,current_animation) + anim_label.text = n + + +func _reset_slider(): + if not current_animation in current_lib.get_animation_list(): + current_animation = current_lib.get_animation_list()[0] + current_time = 0.0 + pose_selected.emit(current_lib,current_animation,current_time) + + var anim := current_lib.get_animation(current_animation) + var multiple :float= current_lib.time_interval + var nearest :float= anim.length - fposmod(anim.length ,multiple) + pose_slider.step = current_lib.time_interval + pose_slider.tick_count = nearest/multiple + 1 + pose_slider.min_value = 0.0 + pose_slider.max_value = nearest + pose_slider.queue_redraw() + time.min_value = 0.0 + time.max_value = anim.length + time.step = pose_slider.step + time.suffix = '/' + "%0.3f" % anim.length + "s" + time.queue_redraw() + pass + + + + + +func _on_bake_button_pressed() -> void: + current_lib.bake_data() + ResourceSaver.save(current_lib) + pass # Replace with function body. + + +func _on_weights_button_pressed() -> void: + current_lib.recalculate_weights() + ResourceSaver.save(current_lib) + pass # Replace with function body. + + +func _on_prev_button_pressed() -> void: + var curr_index := current_lib.get_animation_list().find(current_animation) + var prev_index := clampi(curr_index-1,0,current_lib.get_animation_list().size()) + var prev_animation_name := current_lib.get_animation_list()[prev_index] + if current_animation != prev_animation_name: + _on_animation_selected(current_lib,prev_animation_name) + pass # Replace with function body. + + +func _on_next_button_pressed() -> void: + var curr_index := current_lib.get_animation_list().find(current_animation) + var next_index := clampi(curr_index+1,0,current_lib.get_animation_list().size()) + var next_animation_name := current_lib.get_animation_list()[next_index] + if current_animation != next_animation_name: + _on_animation_selected(current_lib,next_animation_name) + pass # Replace with function body. diff --git a/demo/addons/MotionMatching/scenes/main_scene/MMPreviewScene.tscn b/demo/addons/MotionMatching/scenes/main_scene/MMPreviewScene.tscn new file mode 100644 index 00000000..09ca2f7b --- /dev/null +++ b/demo/addons/MotionMatching/scenes/main_scene/MMPreviewScene.tscn @@ -0,0 +1,301 @@ +[gd_scene load_steps=2 format=3 uid="uid://c2qpcfup28qqv"] + +[ext_resource type="Script" path="res://addons/MotionMatching/scenes/main_scene/MMPreviewSkeleton.gd" id="1_rp8gc"] + +[node name="DontTouch" type="Skeleton3D" groups=["MM_Preview"]] +bones/0/name = "Root" +bones/0/parent = -1 +bones/0/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) +bones/0/enabled = true +bones/0/position = Vector3(-0.0128831, 0, 0.0401578) +bones/0/rotation = Quaternion(0, 0.607478, 0, -0.794336) +bones/0/scale = Vector3(1, 1, 1) +bones/1/name = "Hips" +bones/1/parent = 0 +bones/1/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.000211779, 0.940095, 0.0353721) +bones/1/enabled = true +bones/1/position = Vector3(0, 0.664246, 0) +bones/1/rotation = Quaternion(0.0140643, -2.98023e-08, -0.0716207, -0.997333) +bones/1/scale = Vector3(1, 1, 1) +bones/2/name = "Spine" +bones/2/parent = 1 +bones/2/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0968958, -0.0027467) +bones/2/enabled = true +bones/2/position = Vector3(0, 0.0968958, -0.0027467) +bones/2/rotation = Quaternion(0.264851, 0.0782553, 0.0163314, 0.96097) +bones/2/scale = Vector3(1, 1, 1) +bones/3/name = "Chest" +bones/3/parent = 2 +bones/3/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.113045, -0.00320449) +bones/3/enabled = true +bones/3/position = Vector3(-1.86265e-11, 0.113045, -0.00320449) +bones/3/rotation = Quaternion(0.136838, 0.0137069, -0.0835354, 0.98697) +bones/3/scale = Vector3(1, 1, 1) +bones/4/name = "UpperChest" +bones/4/parent = 3 +bones/4/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.129194, -0.00366227) +bones/4/enabled = true +bones/4/position = Vector3(-1.86265e-11, 0.129194, -0.00366227) +bones/4/rotation = Quaternion(0.135562, 0.0264197, -0.0790961, 0.987253) +bones/4/scale = Vector3(1, 1, 1) +bones/5/name = "Neck" +bones/5/parent = 4 +bones/5/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.145344, -0.00412005) +bones/5/enabled = true +bones/5/position = Vector3(-1.86265e-11, 0.145344, -0.00412005) +bones/5/rotation = Quaternion(-0.0802921, 0.0745944, -0.00263337, 0.993973) +bones/5/scale = Vector3(1, 1, 1) +bones/6/name = "Head" +bones/6/parent = 5 +bones/6/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0867303, 0.0105468) +bones/6/enabled = true +bones/6/position = Vector3(0, 0.0867303, 0.0105468) +bones/6/rotation = Quaternion(-0.172592, 0.291498, -0.0469073, 0.939703) +bones/6/scale = Vector3(1, 1, 1) +bones/7/name = "HeadTop_End" +bones/7/parent = 6 +bones/7/rest = Transform3D(1, 0, 0, 0, 1, 6.51926e-08, 0, -6.51926e-08, 1, 0, 0.291221, 0.0354138) +bones/7/enabled = true +bones/7/position = Vector3(0, 0.291221, 0.0354138) +bones/7/rotation = Quaternion(-3.25963e-08, 0, 0, 1) +bones/7/scale = Vector3(1, 1, 1) +bones/8/name = "LeftShoulder" +bones/8/parent = 4 +bones/8/rest = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.0526969, 0.121643, -0.00379498) +bones/8/enabled = true +bones/8/position = Vector3(0.0526969, 0.121643, -0.00379498) +bones/8/rotation = Quaternion(-0.498738, -0.590552, -0.481523, 0.413092) +bones/8/scale = Vector3(1, 1, 1) +bones/9/name = "LeftUpperArm" +bones/9/parent = 8 +bones/9/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00065014, 0.10667, -0.0459293) +bones/9/enabled = true +bones/9/position = Vector3(0.00065014, 0.10667, -0.0459293) +bones/9/rotation = Quaternion(0.106066, 0.708466, -0.457923, 0.526433) +bones/9/scale = Vector3(1, 1, 1) +bones/10/name = "LeftLowerArm" +bones/10/parent = 9 +bones/10/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0.00604859, 0.235004, 0.0270691) +bones/10/enabled = true +bones/10/position = Vector3(0.00604859, 0.235004, 0.0270691) +bones/10/rotation = Quaternion(0.694265, -0.667915, -0.264755, -0.0423099) +bones/10/scale = Vector3(1, 1, 1) +bones/11/name = "LeftHand" +bones/11/parent = 10 +bones/11/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0.0285643, 0.305546, 0.0302502) +bones/11/enabled = true +bones/11/position = Vector3(0.0285643, 0.305546, 0.0302502) +bones/11/rotation = Quaternion(-0.0302037, 0.539948, -0.0189342, 0.840943) +bones/11/scale = Vector3(1, 1, 1) +bones/12/name = "LeftThumbMetacarpal" +bones/12/parent = 11 +bones/12/rest = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.0432138, 0.0118055, 0.0109467) +bones/12/enabled = true +bones/12/position = Vector3(-0.0432138, 0.0118055, 0.0109467) +bones/12/rotation = Quaternion(-0.13451, 0.699428, 0.195949, 0.674026) +bones/12/scale = Vector3(1, 0.999392, 0.999392) +bones/13/name = "LeftThumbProximal" +bones/13/parent = 12 +bones/13/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, -0.00875785, 0.0276878, -0.0227883) +bones/13/enabled = true +bones/13/position = Vector3(-0.00875785, 0.0276878, -0.0227883) +bones/13/rotation = Quaternion(0.11054, 0.0511474, 0.015851, 0.992428) +bones/13/scale = Vector3(1, 1, 1) +bones/14/name = "LeftThumbDistal" +bones/14/parent = 13 +bones/14/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0160604, 0.0410821, -0.020493) +bones/14/enabled = true +bones/14/position = Vector3(-0.0160604, 0.0410821, -0.020493) +bones/14/rotation = Quaternion(-0.154605, -0.150243, -0.141114, 0.966236) +bones/14/scale = Vector3(1, 1, 1) +bones/15/name = "LeftHandThumb4" +bones/15/parent = 14 +bones/15/rest = Transform3D(0.108359, -0.301235, -0.947373, 0.457174, 0.861984, -0.221793, 0.883432, -0.409081, 0.231121, -0.0107891, 0.0381448, -0.00767406) +bones/15/enabled = true +bones/15/position = Vector3(-0.0107891, 0.0381448, -0.00767406) +bones/15/rotation = Quaternion(-0.0631475, -0.616916, 0.255475, 0.741727) +bones/15/scale = Vector3(1.00061, 1.00056, 1.00005) +bones/16/name = "LeftIndexProximal" +bones/16/parent = 11 +bones/16/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00944374, 0.0286387, 0.00405895) +bones/16/enabled = true +bones/16/position = Vector3(-0.00944374, 0.0286387, 0.00405895) +bones/16/rotation = Quaternion(0.177586, 0.089247, 0.00985536, 0.980001) +bones/16/scale = Vector3(1, 1, 1) +bones/17/name = "LeftIndexIntermediate" +bones/17/parent = 16 +bones/17/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00243786, 0.0689005, 0.0130873) +bones/17/enabled = true +bones/17/position = Vector3(-0.00243786, 0.0689005, 0.0130873) +bones/17/rotation = Quaternion(0.244977, 0.0159729, -0.0266229, 0.969032) +bones/17/scale = Vector3(1, 1, 1) +bones/18/name = "LeftIndexDistal" +bones/18/parent = 17 +bones/18/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000967048, 0.0541595, 0.023595) +bones/18/enabled = true +bones/18/position = Vector3(-0.000967048, 0.0541595, 0.023595) +bones/18/rotation = Quaternion(0.245764, 0.0156584, -0.0268463, 0.968831) +bones/18/scale = Vector3(1, 1, 1) +bones/19/name = "LeftHandIndex4" +bones/19/parent = 18 +bones/19/rest = Transform3D(0.96512, -0.0226626, 0.260823, 0.100367, 0.952157, -0.288657, -0.241802, 0.304767, 0.92122, -0.00112531, 0.0393408, 0.0126457) +bones/19/enabled = true +bones/19/position = Vector3(-0.00112531, 0.0393408, 0.0126457) +bones/19/rotation = Quaternion(0.151445, 0.128272, 0.0313977, 0.979604) +bones/19/scale = Vector3(0.999999, 1, 0.999999) +bones/20/name = "RightShoulder" +bones/20/parent = 4 +bones/20/rest = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.0526969, 0.121643, -0.00444512) +bones/20/enabled = true +bones/20/position = Vector3(-0.0526969, 0.121643, -0.00444512) +bones/20/rotation = Quaternion(0.512658, -0.564597, -0.482671, -0.430629) +bones/20/scale = Vector3(1, 1, 1) +bones/21/name = "RightUpperArm" +bones/21/parent = 20 +bones/21/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.000650136, 0.10667, -0.0459292) +bones/21/enabled = true +bones/21/position = Vector3(0.000650136, 0.10667, -0.0459292) +bones/21/rotation = Quaternion(-0.514104, 0.834672, -0.174402, -0.0927533) +bones/21/scale = Vector3(1, 1, 1) +bones/22/name = "RightLowerArm" +bones/22/parent = 21 +bones/22/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.0042967, 0.235003, 0.0270691) +bones/22/enabled = true +bones/22/position = Vector3(-0.0042967, 0.235003, 0.0270691) +bones/22/rotation = Quaternion(0.676826, 0.650784, -0.290565, 0.18428) +bones/22/scale = Vector3(1, 1, 1) +bones/23/name = "RightHand" +bones/23/parent = 22 +bones/23/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, -0.028564, 0.305546, 0.0323816) +bones/23/enabled = true +bones/23/position = Vector3(-0.028564, 0.305546, 0.0323816) +bones/23/rotation = Quaternion(0.0504705, -0.719954, -0.141913, 0.677481) +bones/23/scale = Vector3(1, 1, 1) +bones/24/name = "RightThumbMetacarpal" +bones/24/parent = 23 +bones/24/rest = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.0427395, 0.0132968, 0.0109151) +bones/24/enabled = true +bones/24/position = Vector3(0.0427395, 0.0132968, 0.0109151) +bones/24/rotation = Quaternion(-0.229387, -0.752399, -0.127944, 0.604077) +bones/24/scale = Vector3(1, 0.999392, 0.999392) +bones/25/name = "RightThumbProximal" +bones/25/parent = 24 +bones/25/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0.00873183, 0.0284436, -0.0217889) +bones/25/enabled = true +bones/25/position = Vector3(0.00873183, 0.0284436, -0.0217889) +bones/25/rotation = Quaternion(0.176885, -0.0726824, -0.0253814, 0.981216) +bones/25/scale = Vector3(1, 1, 1) +bones/26/name = "RightThumbDistal" +bones/26/parent = 25 +bones/26/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0170176, 0.0394534, -0.0214269) +bones/26/enabled = true +bones/26/position = Vector3(0.0170176, 0.0394534, -0.0214269) +bones/26/rotation = Quaternion(0.251464, -0.00291076, 0.0843179, 0.964183) +bones/26/scale = Vector3(1.00003, 0.999978, 0.999996) +bones/27/name = "RightHandThumb4" +bones/27/parent = 26 +bones/27/rest = Transform3D(0.0191473, 0.166372, 0.985877, -0.419388, 0.897112, -0.143247, -0.908274, -0.410722, 0.0869519, 0.00597439, 0.0381001, -0.00505783) +bones/27/enabled = true +bones/27/position = Vector3(0.00597439, 0.0381001, -0.00505783) +bones/27/rotation = Quaternion(-0.0944823, 0.669066, -0.20684, 0.70756) +bones/27/scale = Vector3(1.00061, 1.00059, 1.00002) +bones/28/name = "RightIndexProximal" +bones/28/parent = 23 +bones/28/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00842737, 0.031252, 0.00653073) +bones/28/enabled = true +bones/28/position = Vector3(0.00842737, 0.031252, 0.00653073) +bones/28/rotation = Quaternion(0.0441365, -0.0105015, 0.0505881, 0.997689) +bones/28/scale = Vector3(1, 1, 1) +bones/29/name = "RightIndexIntermediate" +bones/29/parent = 28 +bones/29/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00111553, 0.0708153, 0.0125644) +bones/29/enabled = true +bones/29/position = Vector3(0.00111553, 0.0708153, 0.0125644) +bones/29/rotation = Quaternion(0.100367, -0.00420631, 0.0089778, 0.994901) +bones/29/scale = Vector3(1, 1, 1) +bones/30/name = "RightIndexDistal" +bones/30/parent = 29 +bones/30/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000102064, 0.0493442, 0.0242563) +bones/30/enabled = true +bones/30/position = Vector3(-0.000102064, 0.0493442, 0.0242563) +bones/30/rotation = Quaternion(0.288026, -0.0113591, 0.0260749, 0.9572) +bones/30/scale = Vector3(1, 1, 1) +bones/31/name = "RightHandIndex4" +bones/31/parent = 30 +bones/31/rest = Transform3D(0.975045, -0.00417861, -0.221966, -0.0752528, 0.93441, -0.348158, 0.208862, 0.356174, 0.910777, 0.000182196, 0.038542, 0.0147775) +bones/31/enabled = true +bones/31/position = Vector3(0.000182196, 0.038542, 0.0147775) +bones/31/rotation = Quaternion(0.180179, -0.110212, -0.0181818, 0.977271) +bones/31/scale = Vector3(1, 1, 0.999999) +bones/32/name = "LeftUpperLeg" +bones/32/parent = 1 +bones/32/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.0802526, -0.0538372, 0.00147327) +bones/32/enabled = true +bones/32/position = Vector3(0.0802526, -0.0538372, 0.00147327) +bones/32/rotation = Quaternion(-0.0265237, 0.591895, 0.803428, 0.0588296) +bones/32/scale = Vector3(1, 1, 1) +bones/33/name = "LeftLowerLeg" +bones/33/parent = 32 +bones/33/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.00837398, 0.423197, -0.00522811) +bones/33/enabled = true +bones/33/position = Vector3(-0.00837398, 0.423197, -0.00522811) +bones/33/rotation = Quaternion(0.0175843, 0.616796, -0.786518, -0.0253746) +bones/33/scale = Vector3(1, 1, 1) +bones/34/name = "LeftFoot" +bones/34/parent = 33 +bones/34/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, -0.00422749, 0.335405, 0.0834064) +bones/34/enabled = true +bones/34/position = Vector3(-0.00422749, 0.335405, 0.0834064) +bones/34/rotation = Quaternion(0.000610076, 0.820806, -0.570253, 0.0330076) +bones/34/scale = Vector3(1, 1, 1) +bones/35/name = "LeftToes" +bones/35/parent = 34 +bones/35/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0450058, 0.158499, -0.117969) +bones/35/enabled = true +bones/35/position = Vector3(-0.0450058, 0.158499, -0.117969) +bones/35/rotation = Quaternion(0.000119837, 0.928624, 0.369135, 0.0373583) +bones/35/scale = Vector3(1, 1, 1) +bones/36/name = "LeftToe_End" +bones/36/parent = 35 +bones/36/rest = Transform3D(-0.963968, 0.265966, -0.00534146, 0.265807, 0.963806, 0.0206052, 0.0106284, 0.0184428, -0.999773, 0.019985, 0.0724217, 0.00138581) +bones/36/enabled = true +bones/36/position = Vector3(0.019985, 0.0724217, 0.00138581) +bones/36/rotation = Quaternion(0.134164, 0.990902, 0.0098516, -0.00402913) +bones/36/scale = Vector3(1, 1, 1) +bones/37/name = "RightUpperLeg" +bones/37/parent = 1 +bones/37/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.0802526, -0.0538372, 0.00557836) +bones/37/enabled = true +bones/37/position = Vector3(-0.0802526, -0.0538372, 0.00557836) +bones/37/rotation = Quaternion(-0.0481291, 0.557329, 0.824136, 0.0886961) +bones/37/scale = Vector3(1, 1, 1) +bones/38/name = "RightLowerLeg" +bones/38/parent = 37 +bones/38/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00837398, 0.423197, -0.0116405) +bones/38/enabled = true +bones/38/position = Vector3(0.00837398, 0.423197, -0.0116405) +bones/38/rotation = Quaternion(-0.0209317, 0.676375, -0.736228, -0.00688188) +bones/38/scale = Vector3(1, 1, 1) +bones/39/name = "RightFoot" +bones/39/parent = 38 +bones/39/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0.00422749, 0.335405, 0.0792746) +bones/39/enabled = true +bones/39/position = Vector3(0.00422749, 0.335405, 0.0792746) +bones/39/rotation = Quaternion(-0.0361426, 0.535688, -0.842694, 0.0399781) +bones/39/scale = Vector3(1, 1, 1) +bones/40/name = "RightToes" +bones/40/parent = 39 +bones/40/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0450058, 0.153645, -0.117969) +bones/40/enabled = true +bones/40/position = Vector3(0.0450058, 0.153645, -0.117969) +bones/40/rotation = Quaternion(-0.0226409, 0.999621, 0.015361, -0.00296849) +bones/40/scale = Vector3(1, 1, 1) +bones/41/name = "RightToe_End" +bones/41/parent = 40 +bones/41/rest = Transform3D(-0.96294, -0.269489, 0.0111084, -0.269197, 0.962822, 0.022508, -0.0167611, 0.0186834, -0.999685, -0.0199851, 0.071402, 0.00138553) +bones/41/enabled = true +bones/41/position = Vector3(-0.0199851, 0.071402, 0.00138553) +bones/41/rotation = Quaternion(-0.135944, 0.990637, 0.0103952, 0.00703323) +bones/41/scale = Vector3(1, 1, 1) +script = ExtResource("1_rp8gc") +metadata/_edit_lock_ = true diff --git a/demo/addons/MotionMatching/scenes/main_scene/MMPreviewSkeleton.gd b/demo/addons/MotionMatching/scenes/main_scene/MMPreviewSkeleton.gd new file mode 100644 index 00000000..3b34c804 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/main_scene/MMPreviewSkeleton.gd @@ -0,0 +1,42 @@ +@tool +class_name MMPreviewSkeleton extends Skeleton3D + +var __profile : SkeletonProfile + + +func _ready() -> void: + + #self.add_gizmo() + pass + +func set_bones(profile : SkeletonProfile) -> void: + __profile = profile + clear_bones() + for counter in range(profile.bone_size): + var name = profile.get_bone_name(counter) + var rest := profile.get_reference_pose(counter) + var parent = profile.get_bone_parent(counter) + var tail = profile.get_bone_tail(counter) + var id := add_bone(name) + set_bone_rest(id,rest) + set_bone_parent(id,find_bone(parent)) + reset_bone_poses() + force_update_all_bone_transforms() + +func set_skeleton_to_pose(library:MMAnimationLibrary,anim_name:StringName,timestamp:float): + var anim := library.get_animation(anim_name) + var anim_timestep := timestamp + + for i in range(anim.get_track_count()): + var bone_id = find_bone(anim.track_get_path(i).get_subname(0)) + if bone_id == -1: + continue + elif anim.track_get_type(i) == Animation.TYPE_POSITION_3D: + var position := anim.position_track_interpolate(i,anim_timestep) + set_bone_pose_position(bone_id,position * get_motion_scale()) + elif anim.track_get_type(i) == Animation.TYPE_ROTATION_3D: + var rotation := anim.rotation_track_interpolate(i,anim_timestep) + set_bone_pose_rotation(bone_id,rotation) + elif anim.track_get_type(i) == Animation.TYPE_SCALE_3D: + var scale := anim.scale_track_interpolate(i,anim_timestep) + set_bone_pose_scale(bone_id,scale) diff --git a/demo/addons/MotionMatching/scenes/main_scene/gizmos/MMEditorGizmoSkeletonPlugin.gd b/demo/addons/MotionMatching/scenes/main_scene/gizmos/MMEditorGizmoSkeletonPlugin.gd new file mode 100644 index 00000000..571cf8c1 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/main_scene/gizmos/MMEditorGizmoSkeletonPlugin.gd @@ -0,0 +1,74 @@ +@tool +class_name MMEditorGizmoSkeletonPlugin extends EditorNode3DGizmoPlugin + +const PREVIEWSKEL := preload("res://addons/MotionMatching/scenes/main_scene/MMPreviewSkeleton.gd") +var _plugin : MMEditorPlugin +signal please_redraw +signal on_pose_selected(mmlib : MMAnimationLibrary,name : String,time :float) + +class MMPreviewGizmo extends EditorNode3DGizmo: + var trs : Array[Transform3D] = [] + var mf_vel := MFRootVelocity.new() + var dict := Dictionary() + func on_pose_selected(mmlib : MMAnimationLibrary,name : String,time :float): + trs.clear() + mf_vel.debug_color = Color.RED + get_plugin().create_material(mf_vel.resource_path,mf_vel.debug_color) + + dict.lib = mmlib + dict.name = name + dict.time = time + + var current_animation := mmlib.get_animation(name) + for i in range(1+current_animation.get_length()/mmlib.time_interval): + var TR = mmlib.sample_bone_global(name,i * mmlib.time_interval,"%GeneralSkeleton:Root") + var tr = Transform3D(Basis(Quaternion(TR.rotation)),TR.position) + trs.append(Transform3D(Basis(TR.rotation),TR.position)) + + (get_node_3d() as MMPreviewSkeleton).set_skeleton_to_pose(mmlib,name,time) + + _redraw() + + func _redraw() -> void: + clear() + if get_plugin()._plugin == null || get_plugin()._plugin.current_lib == null || dict == Dictionary(): + return; + var mfs :Array[MotionFeature] = get_plugin()._plugin.current_lib.motion_features + + for mf in mfs: + if mf.has_method("show_debug_info"): + mf.show_debug_info(self,dict.lib,dict.name,dict.time,self.get_node_3d() as Skeleton3D) + for i in range(trs.size()): + var box = PrismMesh.new() + + box.size = Vector3(1,1.5,1)*0.05 + var transform :Transform3D= trs[i] + add_mesh(box,get_plugin().get_material("orange",self),transform.rotated_local(Vector3.RIGHT,deg_to_rad(90))) + +func _init(plugin:MMEditorPlugin) -> void: + prints("Creating MM Gizmo") + _plugin = plugin + create_material("orange",Color.ORANGE,false,true) + create_material("blue",Color.BLUE,false,true) + _plugin.bottom_panel_instance.pose_selected.connect(on_pose_selected.emit) + + + +func _get_gizmo_name(): + return "MM_Gizmos" + + +func _create_gizmo(for_node_3d) -> EditorNode3DGizmo: + if for_node_3d is MMPreviewSkeleton: + prints("Creating gizmo",for_node_3d.name) + var preview := MMPreviewGizmo.new() + #please_redraw.connect(preview._redraw) + on_pose_selected.connect(preview.on_pose_selected) + return preview + return null + +func _redraw(gizmo: EditorNode3DGizmo) -> void: + + pass + + diff --git a/demo/addons/MotionMatching/scenes/main_scene/mm_scene.gd b/demo/addons/MotionMatching/scenes/main_scene/mm_scene.gd new file mode 100644 index 00000000..955e53a4 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/main_scene/mm_scene.gd @@ -0,0 +1,13 @@ +@tool +class_name MMScene extends VBoxContainer + +var gizmoplugin := EditorNode3DGizmoPlugin.new() + +func _ready() -> void: + pass + +func on_pose_selected(lib:MMAnimationLibrary,animname : String, time:float): + prints("Showing Poses",animname,time) + +func on_lib_selected(lib:MMAnimationLibrary): + on_pose_selected.call(lib,lib.get_animation_list()[0],0.0) diff --git a/demo/addons/MotionMatching/scenes/mm_editor_inspect.tscn b/demo/addons/MotionMatching/scenes/mm_editor_inspect.tscn new file mode 100644 index 00000000..e9e0c6d3 --- /dev/null +++ b/demo/addons/MotionMatching/scenes/mm_editor_inspect.tscn @@ -0,0 +1,363 @@ +[gd_scene load_steps=2 format=3 uid="uid://v2nwmvpmfgyx"] + +[ext_resource type="Script" path="res://addons/MotionMatching/scenes/MMSkeletonEditor.gd" id="1_hi2it"] + +[node name="MmEditorInspect" type="Skeleton3D"] +bones/0/name = "Root" +bones/0/parent = -1 +bones/0/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) +bones/0/enabled = true +bones/0/position = Vector3(0, 0, 0) +bones/0/rotation = Quaternion(0, 0, 0, 1) +bones/0/scale = Vector3(1, 1, 1) +bones/1/name = "Hips" +bones/1/parent = 0 +bones/1/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.876276, 2.37794e-07) +bones/1/enabled = true +bones/1/position = Vector3(0, 0.876276, 2.37794e-07) +bones/1/rotation = Quaternion(0, 0, 0, 1) +bones/1/scale = Vector3(1, 1, 1) +bones/2/name = "Spine" +bones/2/parent = 1 +bones/2/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.81831e-10, 0.102766, -0.0155287) +bones/2/enabled = true +bones/2/position = Vector3(-8.81831e-10, 0.102766, -0.0155287) +bones/2/rotation = Quaternion(0, 0, 0, 1) +bones/2/scale = Vector3(1, 1, 1) +bones/3/name = "Chest" +bones/3/parent = 2 +bones/3/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.6669e-10, 0.181286, 0.00902027) +bones/3/enabled = true +bones/3/position = Vector3(3.6669e-10, 0.181286, 0.00902027) +bones/3/rotation = Quaternion(0, 0, 0, 1) +bones/3/scale = Vector3(1, 1, 1) +bones/4/name = "UpperChest" +bones/4/parent = 3 +bones/4/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.30548e-09, 0.177116, -0.0261538) +bones/4/enabled = true +bones/4/position = Vector3(-1.30548e-09, 0.177116, -0.0261538) +bones/4/rotation = Quaternion(0, 0, 0, 1) +bones/4/scale = Vector3(1, 1, 1) +bones/5/name = "LeftShoulder" +bones/5/parent = 4 +bones/5/rest = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.0744715, 0.0546601, 0.0462002) +bones/5/enabled = true +bones/5/position = Vector3(0.0744715, 0.0546601, 0.0462002) +bones/5/rotation = Quaternion(0.5, 0.5, 0.5, -0.5) +bones/5/scale = Vector3(1, 1, 1) +bones/6/name = "LeftUpperArm" +bones/6/parent = 5 +bones/6/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0619603, 0.116518, -0.00171367) +bones/6/enabled = true +bones/6/position = Vector3(-0.0619603, 0.116518, -0.00171367) +bones/6/rotation = Quaternion(0, 1, 0, 0) +bones/6/scale = Vector3(1, 1, 1) +bones/7/name = "LeftLowerArm" +bones/7/parent = 6 +bones/7/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0.00478919, 0.338166, 0.0294515) +bones/7/enabled = true +bones/7/position = Vector3(0.00478919, 0.338166, 0.0294515) +bones/7/rotation = Quaternion(0, -0.707107, 0, 0.707107) +bones/7/scale = Vector3(1, 1, 1) +bones/8/name = "LeftHand" +bones/8/parent = 7 +bones/8/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 3.8568e-09, 0.270477, 0.0190378) +bones/8/enabled = true +bones/8/position = Vector3(3.8568e-09, 0.270477, 0.0190378) +bones/8/rotation = Quaternion(0, 0.707107, 0, 0.707107) +bones/8/scale = Vector3(1, 1, 1) +bones/9/name = "LeftMiddleProximal" +bones/9/parent = 8 +bones/9/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0328334, 0.0998254, 0.00115979) +bones/9/enabled = true +bones/9/position = Vector3(0.0328334, 0.0998254, 0.00115979) +bones/9/rotation = Quaternion(0, 0, 0, 1) +bones/9/scale = Vector3(1, 1, 1) +bones/10/name = "LeftMiddleIntermediate" +bones/10/parent = 9 +bones/10/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00370719, 0.0420529, 0.00479966) +bones/10/enabled = true +bones/10/position = Vector3(0.00370719, 0.0420529, 0.00479966) +bones/10/rotation = Quaternion(0, 0, 0, 1) +bones/10/scale = Vector3(1, 1, 1) +bones/11/name = "LeftMiddleDistal" +bones/11/parent = 10 +bones/11/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00168571, 0.0346672, 0.00545071) +bones/11/enabled = true +bones/11/position = Vector3(0.00168571, 0.0346672, 0.00545071) +bones/11/rotation = Quaternion(0, 0, 0, 1) +bones/11/scale = Vector3(1, 1, 1) +bones/12/name = "Finger_04_L" +bones/12/parent = 11 +bones/12/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00551679, 0.0277547, 0.0100589) +bones/12/enabled = true +bones/12/position = Vector3(0.00551679, 0.0277547, 0.0100589) +bones/12/rotation = Quaternion(0, 0, 0, 1) +bones/12/scale = Vector3(1, 1, 1) +bones/13/name = "IndexFinger_01_L" +bones/13/parent = 8 +bones/13/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0267265, 0.103966, -0.00398671) +bones/13/enabled = true +bones/13/position = Vector3(-0.0267265, 0.103966, -0.00398671) +bones/13/rotation = Quaternion(0, 0, 0, 1) +bones/13/scale = Vector3(1, 1, 1) +bones/14/name = "LeftIndexProximal" +bones/14/parent = 13 +bones/14/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00537493, 0.0405805, 0.00034863) +bones/14/enabled = true +bones/14/position = Vector3(-0.00537493, 0.0405805, 0.00034863) +bones/14/rotation = Quaternion(0, 0, 0, 1) +bones/14/scale = Vector3(1, 1, 1) +bones/15/name = "LeftIndexIntermediate" +bones/15/parent = 14 +bones/15/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00407716, 0.0368895, 0.00773701) +bones/15/enabled = true +bones/15/position = Vector3(-0.00407716, 0.0368895, 0.00773701) +bones/15/rotation = Quaternion(0, 0, 0, 1) +bones/15/scale = Vector3(1, 1, 1) +bones/16/name = "LeftIndexDistal" +bones/16/parent = 15 +bones/16/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00203751, 0.0358257, 0.0118759) +bones/16/enabled = true +bones/16/position = Vector3(-0.00203751, 0.0358257, 0.0118759) +bones/16/rotation = Quaternion(0, 0, 0, 1) +bones/16/scale = Vector3(1, 1, 1) +bones/17/name = "LeftThumbMetacarpal" +bones/17/parent = 8 +bones/17/rest = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.0450554, 0.0351405, 0.0117075) +bones/17/enabled = true +bones/17/position = Vector3(-0.0450554, 0.0351405, 0.0117075) +bones/17/rotation = Quaternion(-0.214187, 0.673887, 0.214187, 0.673887) +bones/17/scale = Vector3(1, 0.999392, 0.999392) +bones/18/name = "LeftThumbProximal" +bones/18/parent = 17 +bones/18/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, -0.011628, 0.0625851, 0.0032593) +bones/18/enabled = true +bones/18/position = Vector3(-0.011628, 0.0625851, 0.0032593) +bones/18/rotation = Quaternion(-1.49011e-08, 0, 0, 1) +bones/18/scale = Vector3(1, 1, 1) +bones/19/name = "LeftThumbDistal" +bones/19/parent = 18 +bones/19/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0256592, 0.0482622, 0.0150101) +bones/19/enabled = true +bones/19/position = Vector3(-0.0256592, 0.0482622, 0.0150101) +bones/19/rotation = Quaternion(0, 0, 0, 1) +bones/19/scale = Vector3(1, 1, 1) +bones/20/name = "RightShoulder" +bones/20/parent = 4 +bones/20/rest = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.0744715, 0.0546635, 0.0462001) +bones/20/enabled = true +bones/20/position = Vector3(-0.0744715, 0.0546635, 0.0462001) +bones/20/rotation = Quaternion(0.5, -0.5, -0.5, -0.5) +bones/20/scale = Vector3(1, 1, 1) +bones/21/name = "RightUpperArm" +bones/21/parent = 20 +bones/21/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0619602, 0.116517, -0.00171968) +bones/21/enabled = true +bones/21/position = Vector3(0.0619602, 0.116517, -0.00171968) +bones/21/rotation = Quaternion(0, 1, 0, 0) +bones/21/scale = Vector3(1, 1, 1) +bones/22/name = "RightLowerArm" +bones/22/parent = 21 +bones/22/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.00478916, 0.338167, 0.0294504) +bones/22/enabled = true +bones/22/position = Vector3(-0.00478916, 0.338167, 0.0294504) +bones/22/rotation = Quaternion(0, 0.707107, 0, 0.707107) +bones/22/scale = Vector3(1, 1, 1) +bones/23/name = "RightHand" +bones/23/parent = 22 +bones/23/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 1.38569e-07, 0.270476, 0.0190378) +bones/23/enabled = true +bones/23/position = Vector3(1.38569e-07, 0.270476, 0.0190378) +bones/23/rotation = Quaternion(0, -0.707107, 0, 0.707107) +bones/23/scale = Vector3(1, 1, 1) +bones/24/name = "RightMiddleProximal" +bones/24/parent = 23 +bones/24/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0328334, 0.099825, 0.0011599) +bones/24/enabled = true +bones/24/position = Vector3(-0.0328334, 0.099825, 0.0011599) +bones/24/rotation = Quaternion(0, 0, 0, 1) +bones/24/scale = Vector3(1, 1, 1) +bones/25/name = "RightMiddleIntermediate" +bones/25/parent = 24 +bones/25/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00370718, 0.0420527, 0.0047998) +bones/25/enabled = true +bones/25/position = Vector3(-0.00370718, 0.0420527, 0.0047998) +bones/25/rotation = Quaternion(0, 0, 0, 1) +bones/25/scale = Vector3(1, 1, 1) +bones/26/name = "RightMiddleDistal" +bones/26/parent = 25 +bones/26/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00168567, 0.0346672, 0.0054501) +bones/26/enabled = true +bones/26/position = Vector3(-0.00168567, 0.0346672, 0.0054501) +bones/26/rotation = Quaternion(0, 0, 0, 1) +bones/26/scale = Vector3(1, 1, 1) +bones/27/name = "Finger_04_R" +bones/27/parent = 26 +bones/27/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00551692, 0.027753, 0.0100597) +bones/27/enabled = true +bones/27/position = Vector3(-0.00551692, 0.027753, 0.0100597) +bones/27/rotation = Quaternion(0, 0, 0, 1) +bones/27/scale = Vector3(1, 1, 1) +bones/28/name = "IndexFinger_01_R" +bones/28/parent = 23 +bones/28/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0267265, 0.103966, -0.00398982) +bones/28/enabled = true +bones/28/position = Vector3(0.0267265, 0.103966, -0.00398982) +bones/28/rotation = Quaternion(0, 0, 0, 1) +bones/28/scale = Vector3(1, 1, 1) +bones/29/name = "RightIndexProximal" +bones/29/parent = 28 +bones/29/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00537494, 0.0405804, 0.000349531) +bones/29/enabled = true +bones/29/position = Vector3(0.00537494, 0.0405804, 0.000349531) +bones/29/rotation = Quaternion(0, 0, 0, 1) +bones/29/scale = Vector3(1, 1, 1) +bones/30/name = "RightIndexIntermediate" +bones/30/parent = 29 +bones/30/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0040772, 0.036889, 0.00773975) +bones/30/enabled = true +bones/30/position = Vector3(0.0040772, 0.036889, 0.00773975) +bones/30/rotation = Quaternion(0, 0, 0, 1) +bones/30/scale = Vector3(1, 1, 1) +bones/31/name = "RightIndexDistal" +bones/31/parent = 30 +bones/31/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00203749, 0.0358229, 0.0118703) +bones/31/enabled = true +bones/31/position = Vector3(0.00203749, 0.0358229, 0.0118703) +bones/31/rotation = Quaternion(0, 0, 0, 1) +bones/31/scale = Vector3(1, 1, 1) +bones/32/name = "RightThumbMetacarpal" +bones/32/parent = 23 +bones/32/rest = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.0450554, 0.0351399, 0.0117102) +bones/32/enabled = true +bones/32/position = Vector3(0.0450554, 0.0351399, 0.0117102) +bones/32/rotation = Quaternion(-0.214187, -0.673887, -0.214187, 0.673887) +bones/32/scale = Vector3(1, 0.999392, 0.999392) +bones/33/name = "RightThumbProximal" +bones/33/parent = 32 +bones/33/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0.0116203, 0.0625863, 0.00325998) +bones/33/enabled = true +bones/33/position = Vector3(0.0116203, 0.0625863, 0.00325998) +bones/33/rotation = Quaternion(-1.49011e-08, 0, 0, 1) +bones/33/scale = Vector3(1, 1, 1) +bones/34/name = "RightThumbDistal" +bones/34/parent = 33 +bones/34/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0256595, 0.0482614, 0.0150095) +bones/34/enabled = true +bones/34/position = Vector3(0.0256595, 0.0482614, 0.0150095) +bones/34/rotation = Quaternion(0, 0, 0, 1) +bones/34/scale = Vector3(1, 1, 1) +bones/35/name = "Neck" +bones/35/parent = 4 +bones/35/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.65926e-10, 0.111566, 0.00849949) +bones/35/enabled = true +bones/35/position = Vector3(3.65926e-10, 0.111566, 0.00849949) +bones/35/rotation = Quaternion(0, 0, 0, 1) +bones/35/scale = Vector3(1, 1, 1) +bones/36/name = "Head" +bones/36/parent = 35 +bones/36/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.78004e-10, 0.120099, 0.0193233) +bones/36/enabled = true +bones/36/position = Vector3(8.78004e-10, 0.120099, 0.0193233) +bones/36/rotation = Quaternion(0, 0, 0, 1) +bones/36/scale = Vector3(1, 1, 1) +bones/37/name = "Eyebrows" +bones/37/parent = 36 +bones/37/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.0761e-08, 0.127669, 0.122722) +bones/37/enabled = true +bones/37/position = Vector3(1.0761e-08, 0.127669, 0.122722) +bones/37/rotation = Quaternion(0, 0, 0, 1) +bones/37/scale = Vector3(1, 1, 1) +bones/38/name = "Eyes" +bones/38/parent = 36 +bones/38/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.49478e-08, 0.0944759, 0.122722) +bones/38/enabled = true +bones/38/position = Vector3(1.49478e-08, 0.0944759, 0.122722) +bones/38/rotation = Quaternion(0, 0, 0, 1) +bones/38/scale = Vector3(1, 1, 1) +bones/39/name = "SM_Chr_Attach_Female_Hair_01" +bones/39/parent = 36 +bones/39/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.88541e-14, 1.22526e-07, -7.22787e-09) +bones/39/enabled = true +bones/39/position = Vector3(-5.88541e-14, 1.22526e-07, -7.22787e-09) +bones/39/rotation = Quaternion(0, 0, 0, 1) +bones/39/scale = Vector3(1, 1, 1) +bones/40/name = "SM_Chr_Attach_Male_Hair_01" +bones/40/parent = 36 +bones/40/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.88541e-14, 1.22526e-07, -7.22787e-09) +bones/40/enabled = true +bones/40/position = Vector3(-5.88541e-14, 1.22526e-07, -7.22787e-09) +bones/40/rotation = Quaternion(0, 0, 0, 1) +bones/40/scale = Vector3(1, 1, 1) +bones/41/name = "LeftUpperLeg" +bones/41/parent = 1 +bones/41/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.0989671, -0.0447181, -0.0204691) +bones/41/enabled = true +bones/41/position = Vector3(0.0989671, -0.0447181, -0.0204691) +bones/41/rotation = Quaternion(0, 0, 1, 0) +bones/41/scale = Vector3(1, 1, 1) +bones/42/name = "LeftLowerLeg" +bones/42/parent = 41 +bones/42/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0126748, 0.399108, -1.57902e-07) +bones/42/enabled = true +bones/42/position = Vector3(-0.0126748, 0.399108, -1.57902e-07) +bones/42/rotation = Quaternion(0, 1, 0, 0) +bones/42/scale = Vector3(1, 1, 1) +bones/43/name = "LeftFoot" +bones/43/parent = 42 +bones/43/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0.00280093, 0.376756, 0.0164049) +bones/43/enabled = true +bones/43/position = Vector3(0.00280093, 0.376756, 0.0164049) +bones/43/rotation = Quaternion(0, 0.707107, -0.707107, 0) +bones/43/scale = Vector3(1, 1, 1) +bones/44/name = "LeftToes" +bones/44/parent = 43 +bones/44/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 4.09574e-08, 0.0963863, -0.0586739) +bones/44/enabled = true +bones/44/position = Vector3(4.09574e-08, 0.0963863, -0.0586739) +bones/44/rotation = Quaternion(0, 1, 0, 0) +bones/44/scale = Vector3(1, 1, 1) +bones/45/name = "Toes_L" +bones/45/parent = 44 +bones/45/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.34888e-08, 0.0729575, 1.96286e-08) +bones/45/enabled = true +bones/45/position = Vector3(-1.34888e-08, 0.0729575, 1.96286e-08) +bones/45/rotation = Quaternion(0, 0, 0, 1) +bones/45/scale = Vector3(1, 1, 1) +bones/46/name = "RightUpperLeg" +bones/46/parent = 1 +bones/46/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.0989671, -0.044718, -0.0204691) +bones/46/enabled = true +bones/46/position = Vector3(-0.0989671, -0.044718, -0.0204691) +bones/46/rotation = Quaternion(0, 0, 1, 0) +bones/46/scale = Vector3(1, 1, 1) +bones/47/name = "RightLowerLeg" +bones/47/parent = 46 +bones/47/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0126744, 0.399108, -1.7152e-07) +bones/47/enabled = true +bones/47/position = Vector3(0.0126744, 0.399108, -1.7152e-07) +bones/47/rotation = Quaternion(0, 1, 0, 0) +bones/47/scale = Vector3(1, 1, 1) +bones/48/name = "RightFoot" +bones/48/parent = 47 +bones/48/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, -0.00280184, 0.376756, 0.0164049) +bones/48/enabled = true +bones/48/position = Vector3(-0.00280184, 0.376756, 0.0164049) +bones/48/rotation = Quaternion(0, 0.707107, -0.707107, 0) +bones/48/scale = Vector3(1, 1, 1) +bones/49/name = "RightToes" +bones/49/parent = 48 +bones/49/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 5.89779e-08, 0.0963863, -0.0586739) +bones/49/enabled = true +bones/49/position = Vector3(5.89779e-08, 0.0963863, -0.0586739) +bones/49/rotation = Quaternion(0, 1, 0, 0) +bones/49/scale = Vector3(1, 1, 1) +bones/50/name = "Toes_R" +bones/50/parent = 49 +bones/50/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.48308e-08, 0.0729575, 3.21661e-08) +bones/50/enabled = true +bones/50/position = Vector3(-1.48308e-08, 0.0729575, 3.21661e-08) +bones/50/rotation = Quaternion(0, 0, 0, 1) +bones/50/scale = Vector3(1, 1, 1) +script = ExtResource("1_hi2it") diff --git a/demo/addons/MotionMatching/scripts/EditorScriptAnimationAddRootMotion.gd b/demo/addons/MotionMatching/scripts/EditorScriptAnimationAddRootMotion.gd new file mode 100644 index 00000000..13a5132c --- /dev/null +++ b/demo/addons/MotionMatching/scripts/EditorScriptAnimationAddRootMotion.gd @@ -0,0 +1,103 @@ +@tool +# CAUTION : Make sure that the hips_track_name and root_track_name are correct +# Modify the animation to take the position and rotation of the hips to the root bone. +# This is intended for animation that put all movement in the hips bone. +# Don't forget to add a root bone to your skeleton in order to see the effect. +class_name EditorScriptAnimationAddRootMotion extends EditorScript + +@export var hips_track_name := "%GeneralSkeleton:Hips" +@export var root_track_name := "%GeneralSkeleton:Root" + +var fileDialog : EditorFileDialog = null + +# Let the user choose which animations will be modified +func _run() -> void: + fileDialog = EditorFileDialog.new() + fileDialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILES + fileDialog.access = EditorFileDialog.ACCESS_RESOURCES + fileDialog.files_selected.connect(on_file_selected) + var viewport = EditorInterface.get_editor_main_screen() + viewport.add_child(fileDialog) + fileDialog.set_meta("_created_by", self) # needed so the script is not directly freed after the run function. Would disconnect all signals otherwise + fileDialog.popup_centered() # Giving the dialog a predefined size + + +func on_file_selected(files : PackedStringArray) : + var i = 0 + prints("Files selected") + + for file in files: + var anim : Animation = ResourceLoader.load(file) + prints(i,file,anim.get_path()) + if anim == null : + prints(file,"isn't loaded correctly") + return + + add_root_motion(anim) + i += 1 + if (fileDialog != null): + fileDialog.queue_free() # Dialog has to be freed in order for the script to be called again. + + pass + +func get_twist(q : Quaternion, p_axis : Vector3) -> Quaternion: + var rotationAxis := Vector3(q.x, q.y, q.z); + var dotProd :float= p_axis.dot(rotationAxis); + var projection :Vector3= p_axis * dotProd; + var twist = Quaternion(projection.x, projection.y, projection.z, q.w).normalized(); + if (dotProd < 0.0): + twist = -twist + return twist; +func get_swing(q : Quaternion, axis : Vector3) -> Quaternion: + return q * get_twist(q,axis).inverse() + +func add_root_motion(anim:Animation): + + assert(anim != null) + + var proot := anim.find_track(root_track_name,Animation.TYPE_POSITION_3D) + var rroot := anim.find_track(root_track_name,Animation.TYPE_ROTATION_3D) + + if proot == -1: + proot = anim.add_track(Animation.TYPE_POSITION_3D) + anim.track_set_path(proot,root_track_name) + anim.track_move_to(proot,0) + proot = 0 + else: + push_warning("Root Track Detected, aborting for animation",anim.get_path()) + return + + if rroot == -1: + rroot = anim.add_track(Animation.TYPE_ROTATION_3D) + anim.track_set_path(rroot,root_track_name) + anim.track_move_to(rroot,0) + else: + push_warning("Root Track Detected, aborting for animation",anim.get_path()) + return + + proot = anim.find_track(root_track_name,Animation.TYPE_POSITION_3D) + assert(proot != -1) + rroot = anim.find_track(root_track_name,Animation.TYPE_ROTATION_3D) + assert(rroot != -1) + var phips := anim.find_track(hips_track_name,Animation.TYPE_POSITION_3D) + assert(phips != -1) + var rhips := anim.find_track(hips_track_name,Animation.TYPE_ROTATION_3D) + assert(rhips != -1) + + if phips != -1 : + for key in range(anim.track_get_key_count(phips)): + var time := anim.track_get_key_time(phips,key) + var position :Vector3= anim.track_get_key_value(phips,key) + anim.position_track_insert_key(proot,time,position * Vector3(1,0,1)) + anim.track_set_key_value(phips,key,position * Vector3(0,1,0)) + + if rhips != -1: + for key in range(anim.track_get_key_count(rhips)): + var time := anim.track_get_key_time(rhips,key) + var rotation :Quaternion= anim.track_get_key_value(rhips,key) + var twist := get_twist(rotation,Vector3.UP) + var swing := get_swing(rotation,Vector3.UP) + anim.rotation_track_insert_key(rroot,time,twist) + anim.track_set_key_value(rhips,key,twist.inverse() * rotation) + + ResourceSaver.save(anim,anim.get_path(),ResourceSaver.FLAG_NONE) diff --git a/demo/addons/kenney_prototype_textures/LICENSE.txt b/demo/addons/kenney_prototype_textures/LICENSE.txt new file mode 100644 index 00000000..839f1044 --- /dev/null +++ b/demo/addons/kenney_prototype_textures/LICENSE.txt @@ -0,0 +1,23 @@ + + + Prototype Textures 1.0 + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 08-04-2020 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL diff --git a/demo/addons/kenney_prototype_textures/dark/texture_05.png b/demo/addons/kenney_prototype_textures/dark/texture_05.png new file mode 100644 index 00000000..cd01f8c4 Binary files /dev/null and b/demo/addons/kenney_prototype_textures/dark/texture_05.png differ diff --git a/demo/addons/kenney_prototype_textures/dark/texture_05.png.import b/demo/addons/kenney_prototype_textures/dark/texture_05.png.import new file mode 100644 index 00000000..01f0efb2 --- /dev/null +++ b/demo/addons/kenney_prototype_textures/dark/texture_05.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bkyqrq1o4d3sm" +path.s3tc="res://.godot/imported/texture_05.png-ed8122ecdc41ff5aeccab84e8db1e4f0.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/kenney_prototype_textures/dark/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-ed8122ecdc41ff5aeccab84e8db1e4f0.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/demo/addons/kenney_prototype_textures/orange/texture_09.png b/demo/addons/kenney_prototype_textures/orange/texture_09.png new file mode 100644 index 00000000..a7f8b0b9 Binary files /dev/null and b/demo/addons/kenney_prototype_textures/orange/texture_09.png differ diff --git a/demo/addons/kenney_prototype_textures/orange/texture_09.png.import b/demo/addons/kenney_prototype_textures/orange/texture_09.png.import new file mode 100644 index 00000000..d8efe2a6 --- /dev/null +++ b/demo/addons/kenney_prototype_textures/orange/texture_09.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ct65p5i3supkk" +path.s3tc="res://.godot/imported/texture_09.png-d3e0d0da868b68102c983480f9cde71d.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://addons/kenney_prototype_textures/orange/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-d3e0d0da868b68102c983480f9cde71d.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/demo/assets/animations/.gitkeep b/demo/assets/animations/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/assets/resources/.gitkeep b/demo/assets/resources/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/demo/icon.svg b/demo/icon.svg new file mode 100644 index 00000000..e3d63738 --- /dev/null +++ b/demo/icon.svg @@ -0,0 +1,59 @@ + + diff --git a/demo/icon.svg.import b/demo/icon.svg.import new file mode 100644 index 00000000..c8d2b125 --- /dev/null +++ b/demo/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dbx66sovxd1" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/demo/project.godot b/demo/project.godot new file mode 100644 index 00000000..552ecd27 --- /dev/null +++ b/demo/project.godot @@ -0,0 +1,24 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="MotionMatchingDemo" +run/main_scene="res://scenes/DemoScene.tscn" +config/features=PackedStringArray("4.3", "Forward Plus") +config/icon="res://icon.svg" + +[dotnet] + +project/assembly_name="godot cpp template" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/MotionMatching/plugin.cfg") diff --git a/demo/resources/skeleton_profile.tres b/demo/resources/skeleton_profile.tres new file mode 100644 index 00000000..ee4042fe --- /dev/null +++ b/demo/resources/skeleton_profile.tres @@ -0,0 +1,302 @@ +[gd_resource type="SkeletonProfile" format=3 uid="uid://dgrt5pdh5pryn"] + +[resource] +root_bone = &"Root" +scale_base_bone = &"Hips" +group_size = 1 +bone_size = 42 +groups/0/group_name = &"" +bones/0/bone_name = &"Root" +bones/0/bone_parent = &"" +bones/0/tail_direction = 0 +bones/0/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) +bones/0/handle_offset = Vector2(0.499623, 0.95) +bones/0/group = &"" +bones/0/require = false +bones/1/bone_name = &"Hips" +bones/1/bone_parent = &"Root" +bones/1/tail_direction = 0 +bones/1/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.000211779, 0.940095, 0.0353721) +bones/1/handle_offset = Vector2(0.499729, 0.480611) +bones/1/group = &"" +bones/1/require = false +bones/2/bone_name = &"Spine" +bones/2/bone_parent = &"Hips" +bones/2/tail_direction = 0 +bones/2/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0968958, -0.0027467) +bones/2/handle_offset = Vector2(0.499729, 0.432231) +bones/2/group = &"" +bones/2/require = false +bones/3/bone_name = &"Chest" +bones/3/bone_parent = &"Spine" +bones/3/tail_direction = 0 +bones/3/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.113045, -0.00320449) +bones/3/handle_offset = Vector2(0.499729, 0.375787) +bones/3/group = &"" +bones/3/require = false +bones/4/bone_name = &"UpperChest" +bones/4/bone_parent = &"Chest" +bones/4/tail_direction = 0 +bones/4/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.129194, -0.00366227) +bones/4/handle_offset = Vector2(0.499729, 0.311281) +bones/4/group = &"" +bones/4/require = false +bones/5/bone_name = &"Neck" +bones/5/bone_parent = &"UpperChest" +bones/5/tail_direction = 0 +bones/5/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.145344, -0.00412005) +bones/5/handle_offset = Vector2(0.499729, 0.238711) +bones/5/group = &"" +bones/5/require = false +bones/6/bone_name = &"Head" +bones/6/bone_parent = &"Neck" +bones/6/tail_direction = 0 +bones/6/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0867303, 0.0105468) +bones/6/handle_offset = Vector2(0.499729, 0.195406) +bones/6/group = &"" +bones/6/require = false +bones/7/bone_name = &"HeadTop_End" +bones/7/bone_parent = &"Head" +bones/7/tail_direction = 0 +bones/7/reference_pose = Transform3D(1, 0, 0, 0, 1, 6.51926e-08, 0, -6.51926e-08, 1, 0, 0.291221, 0.0354138) +bones/7/handle_offset = Vector2(0.499729, 0.05) +bones/7/group = &"" +bones/7/require = false +bones/8/bone_name = &"LeftShoulder" +bones/8/bone_parent = &"UpperChest" +bones/8/tail_direction = 0 +bones/8/reference_pose = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.0526969, 0.121643, -0.00379498) +bones/8/handle_offset = Vector2(0.52604, 0.250545) +bones/8/group = &"" +bones/8/require = false +bones/9/bone_name = &"LeftUpperArm" +bones/9/bone_parent = &"LeftShoulder" +bones/9/tail_direction = 0 +bones/9/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00065014, 0.10667, -0.0459293) +bones/9/handle_offset = Vector2(0.5793, 0.273477) +bones/9/group = &"" +bones/9/require = false +bones/10/bone_name = &"LeftLowerArm" +bones/10/bone_parent = &"LeftUpperArm" +bones/10/tail_direction = 0 +bones/10/reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0.00604859, 0.235004, 0.0270691) +bones/10/handle_offset = Vector2(0.696638, 0.286993) +bones/10/group = &"" +bones/10/require = false +bones/11/bone_name = &"LeftHand" +bones/11/bone_parent = &"LeftLowerArm" +bones/11/tail_direction = 0 +bones/11/reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0.0285643, 0.305546, 0.0302502) +bones/11/handle_offset = Vector2(0.849196, 0.301255) +bones/11/group = &"" +bones/11/require = false +bones/12/bone_name = &"LeftThumbMetacarpal" +bones/12/bone_parent = &"LeftHand" +bones/12/tail_direction = 0 +bones/12/reference_pose = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.0432138, 0.0118055, 0.0109467) +bones/12/handle_offset = Vector2(0.855091, 0.30672) +bones/12/group = &"" +bones/12/require = false +bones/13/bone_name = &"LeftThumbProximal" +bones/13/bone_parent = &"LeftThumbMetacarpal" +bones/13/tail_direction = 0 +bones/13/reference_pose = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, -0.00875785, 0.0276878, -0.0227883) +bones/13/handle_offset = Vector2(0.859807, 0.311093) +bones/13/group = &"" +bones/13/require = false +bones/14/bone_name = &"LeftThumbDistal" +bones/14/bone_parent = &"LeftThumbProximal" +bones/14/tail_direction = 0 +bones/14/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0160604, 0.0410821, -0.020493) +bones/14/handle_offset = Vector2(0.870641, 0.319112) +bones/14/group = &"" +bones/14/require = false +bones/15/bone_name = &"LeftHandThumb4" +bones/15/bone_parent = &"LeftThumbDistal" +bones/15/tail_direction = 0 +bones/15/reference_pose = Transform3D(0.108359, -0.301235, -0.947373, 0.457174, 0.861984, -0.221793, 0.883432, -0.409081, 0.231121, -0.0107891, 0.0381448, -0.00767406) +bones/15/handle_offset = Vector2(0.883971, 0.324499) +bones/15/group = &"" +bones/15/require = false +bones/16/bone_name = &"LeftIndexProximal" +bones/16/bone_parent = &"LeftHand" +bones/16/tail_direction = 0 +bones/16/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00944374, 0.0286387, 0.00405895) +bones/16/handle_offset = Vector2(0.863496, 0.303281) +bones/16/group = &"" +bones/16/require = false +bones/17/bone_name = &"LeftIndexIntermediate" +bones/17/bone_parent = &"LeftIndexProximal" +bones/17/tail_direction = 0 +bones/17/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00243786, 0.0689005, 0.0130873) +bones/17/handle_offset = Vector2(0.897898, 0.309816) +bones/17/group = &"" +bones/17/require = false +bones/18/bone_name = &"LeftIndexDistal" +bones/18/bone_parent = &"LeftIndexIntermediate" +bones/18/tail_direction = 0 +bones/18/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000967048, 0.0541595, 0.023595) +bones/18/handle_offset = Vector2(0.92494, 0.321597) +bones/18/group = &"" +bones/18/require = false +bones/19/bone_name = &"LeftHandIndex4" +bones/19/bone_parent = &"LeftIndexDistal" +bones/19/tail_direction = 0 +bones/19/reference_pose = Transform3D(0.96512, -0.0226626, 0.260823, 0.100367, 0.952157, -0.288657, -0.241802, 0.304767, 0.92122, -0.00112531, 0.0393408, 0.0126457) +bones/19/handle_offset = Vector2(0.944582, 0.327911) +bones/19/group = &"" +bones/19/require = false +bones/20/bone_name = &"RightShoulder" +bones/20/bone_parent = &"UpperChest" +bones/20/tail_direction = 0 +bones/20/reference_pose = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.0526969, 0.121643, -0.00444512) +bones/20/handle_offset = Vector2(0.473417, 0.250545) +bones/20/group = &"" +bones/20/require = false +bones/21/bone_name = &"RightUpperArm" +bones/21/bone_parent = &"RightShoulder" +bones/21/tail_direction = 0 +bones/21/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.000650136, 0.10667, -0.0459292) +bones/21/handle_offset = Vector2(0.420157, 0.273477) +bones/21/group = &"" +bones/21/require = false +bones/22/bone_name = &"RightLowerArm" +bones/22/bone_parent = &"RightUpperArm" +bones/22/tail_direction = 0 +bones/22/reference_pose = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.0042967, 0.235003, 0.0270691) +bones/22/handle_offset = Vector2(0.30282, 0.286993) +bones/22/group = &"" +bones/22/require = false +bones/23/bone_name = &"RightHand" +bones/23/bone_parent = &"RightLowerArm" +bones/23/tail_direction = 0 +bones/23/reference_pose = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, -0.028564, 0.305546, 0.0323816) +bones/23/handle_offset = Vector2(0.150261, 0.301255) +bones/23/group = &"" +bones/23/require = false +bones/24/bone_name = &"RightThumbMetacarpal" +bones/24/bone_parent = &"RightHand" +bones/24/tail_direction = 0 +bones/24/reference_pose = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.0427395, 0.0132968, 0.0109151) +bones/24/handle_offset = Vector2(0.143622, 0.306704) +bones/24/group = &"" +bones/24/require = false +bones/25/bone_name = &"RightThumbProximal" +bones/25/bone_parent = &"RightThumbMetacarpal" +bones/25/tail_direction = 0 +bones/25/reference_pose = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0.00873183, 0.0284436, -0.0217889) +bones/25/handle_offset = Vector2(0.138311, 0.311064) +bones/25/group = &"" +bones/25/require = false +bones/26/bone_name = &"RightThumbDistal" +bones/26/bone_parent = &"RightThumbProximal" +bones/26/tail_direction = 0 +bones/26/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0170176, 0.0394534, -0.0214269) +bones/26/handle_offset = Vector2(0.128409, 0.319561) +bones/26/group = &"" +bones/26/require = false +bones/27/bone_name = &"RightHandThumb4" +bones/27/bone_parent = &"RightThumbDistal" +bones/27/tail_direction = 0 +bones/27/reference_pose = Transform3D(0.0191473, 0.166372, 0.985877, -0.419388, 0.897112, -0.143247, -0.908274, -0.410722, 0.0869519, 0.00597439, 0.0381001, -0.00505783) +bones/27/handle_offset = Vector2(0.114343, 0.322544) +bones/27/group = &"" +bones/27/require = false +bones/28/bone_name = &"RightIndexProximal" +bones/28/bone_parent = &"RightHand" +bones/28/tail_direction = 0 +bones/28/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00842737, 0.031252, 0.00653073) +bones/28/handle_offset = Vector2(0.134657, 0.304515) +bones/28/group = &"" +bones/28/require = false +bones/29/bone_name = &"RightIndexIntermediate" +bones/29/bone_parent = &"RightIndexProximal" +bones/29/tail_direction = 0 +bones/29/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00111553, 0.0708153, 0.0125644) +bones/29/handle_offset = Vector2(0.0992991, 0.310789) +bones/29/group = &"" +bones/29/require = false +bones/30/bone_name = &"RightIndexDistal" +bones/30/bone_parent = &"RightIndexIntermediate" +bones/30/tail_direction = 0 +bones/30/reference_pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000102064, 0.0493442, 0.0242563) +bones/30/handle_offset = Vector2(0.0746616, 0.3229) +bones/30/group = &"" +bones/30/require = false +bones/31/bone_name = &"RightHandIndex4" +bones/31/bone_parent = &"RightIndexDistal" +bones/31/tail_direction = 0 +bones/31/reference_pose = Transform3D(0.975045, -0.00417861, -0.221966, -0.0752528, 0.93441, -0.348158, 0.208862, 0.356174, 0.910777, 0.000182196, 0.038542, 0.0147775) +bones/31/handle_offset = Vector2(0.0554176, 0.330278) +bones/31/group = &"" +bones/31/require = false +bones/32/bone_name = &"LeftUpperLeg" +bones/32/bone_parent = &"Hips" +bones/32/tail_direction = 0 +bones/32/reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.0802526, -0.0538372, 0.00147327) +bones/32/handle_offset = Vector2(0.539799, 0.507492) +bones/32/group = &"" +bones/32/require = false +bones/33/bone_name = &"LeftLowerLeg" +bones/33/bone_parent = &"LeftUpperLeg" +bones/33/tail_direction = 0 +bones/33/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.00837398, 0.423197, -0.00522811) +bones/33/handle_offset = Vector2(0.54398, 0.718794) +bones/33/group = &"" +bones/33/require = false +bones/34/bone_name = &"LeftFoot" +bones/34/bone_parent = &"LeftLowerLeg" +bones/34/tail_direction = 0 +bones/34/reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, -0.00422749, 0.335405, 0.0834064) +bones/34/handle_offset = Vector2(0.541869, 0.886261) +bones/34/group = &"" +bones/34/require = false +bones/35/bone_name = &"LeftToes" +bones/35/bone_parent = &"LeftFoot" +bones/35/tail_direction = 0 +bones/35/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0450058, 0.158499, -0.117969) +bones/35/handle_offset = Vector2(0.564341, 0.945163) +bones/35/group = &"" +bones/35/require = false +bones/36/bone_name = &"LeftToe_End" +bones/36/bone_parent = &"LeftToes" +bones/36/tail_direction = 0 +bones/36/reference_pose = Transform3D(-0.963968, 0.265966, -0.00534146, 0.265807, 0.963806, 0.0206052, 0.0106284, 0.0184428, -0.999773, 0.019985, 0.0724217, 0.00138581) +bones/36/handle_offset = Vector2(0.574319, 0.945855) +bones/36/group = &"" +bones/36/require = false +bones/37/bone_name = &"RightUpperLeg" +bones/37/bone_parent = &"Hips" +bones/37/tail_direction = 0 +bones/37/reference_pose = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.0802526, -0.0538372, 0.00557836) +bones/37/handle_offset = Vector2(0.459659, 0.507492) +bones/37/group = &"" +bones/37/require = false +bones/38/bone_name = &"RightLowerLeg" +bones/38/bone_parent = &"RightUpperLeg" +bones/38/tail_direction = 0 +bones/38/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00837398, 0.423197, -0.0116405) +bones/38/handle_offset = Vector2(0.455478, 0.718794) +bones/38/group = &"" +bones/38/require = false +bones/39/bone_name = &"RightFoot" +bones/39/bone_parent = &"RightLowerLeg" +bones/39/tail_direction = 0 +bones/39/reference_pose = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0.00422749, 0.335405, 0.0792746) +bones/39/handle_offset = Vector2(0.457588, 0.886262) +bones/39/group = &"" +bones/39/require = false +bones/40/bone_name = &"RightToes" +bones/40/bone_parent = &"RightFoot" +bones/40/tail_direction = 0 +bones/40/reference_pose = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0450058, 0.153645, -0.117969) +bones/40/handle_offset = Vector2(0.435117, 0.945163) +bones/40/group = &"" +bones/40/require = false +bones/41/bone_name = &"RightToe_End" +bones/41/bone_parent = &"RightToes" +bones/41/tail_direction = 0 +bones/41/reference_pose = Transform3D(-0.96294, -0.269489, 0.0111084, -0.269197, 0.962822, 0.022508, -0.0167611, 0.0186834, -0.999685, -0.0199851, 0.071402, 0.00138553) +bones/41/handle_offset = Vector2(0.425138, 0.945855) +bones/41/group = &"" +bones/41/require = false diff --git a/demo/scenes/Characters/character.tscn b/demo/scenes/Characters/character.tscn new file mode 100644 index 00000000..4f7943e2 --- /dev/null +++ b/demo/scenes/Characters/character.tscn @@ -0,0 +1,514 @@ +[gd_scene load_steps=11 format=3 uid="uid://yyuwsiyi6vc1"] + +[ext_resource type="ArrayMesh" uid="uid://dck384rqwtixa" path="res://assets/models/Example_characterLargeMale_002.res" id="1_1v5in"] +[ext_resource type="Script" path="res://scenes/Characters/simplecharacter.gd" id="1_rxdap"] +[ext_resource type="Script" path="res://scenes/Characters/mm_timer.gd" id="3_rlvfq"] +[ext_resource type="MMAnimationLibrary" uid="uid://dv4vqfunufbxr" path="res://assets/resources/locomotion.res" id="3_y0uif"] + +[sub_resource type="Skin" id="Skin_n4hiu"] +resource_name = "Skin" +bind_count = 42 +bind/0/name = &"Root" +bind/0/bone = -1 +bind/0/pose = Transform3D(1, 0, 0, 0, 1, -5.55112e-15, 0, 5.55112e-15, 1, 0, 0, 0) +bind/1/name = &"Hips" +bind/1/bone = -1 +bind/1/pose = Transform3D(1, 0, 0, 0, 1, -1.42109e-14, 0, 1.42109e-14, 1, -0.000211779, -0.940095, -0.0353721) +bind/2/name = &"Spine" +bind/2/bone = -1 +bind/2/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000211779, -1.03699, -0.0326254) +bind/3/name = &"Chest" +bind/3/bone = -1 +bind/3/pose = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000211779, -1.15004, -0.0294209) +bind/4/name = &"UpperChest" +bind/4/bone = -1 +bind/4/pose = Transform3D(1, 0, 0, 0, 1, -1.86265e-09, 0, 1.86265e-09, 1, -0.000211779, -1.27923, -0.0257586) +bind/5/name = &"Neck" +bind/5/bone = -1 +bind/5/pose = Transform3D(1, 0, 0, 0, 1, -8.44265e-09, 0, 8.44265e-09, 1, -0.000211779, -1.42457, -0.0216386) +bind/6/name = &"Head" +bind/6/bone = -1 +bind/6/pose = Transform3D(1, 0, 0, 0, 1, -8.44266e-09, 0, 8.44266e-09, 1, -0.000211779, -1.5113, -0.0321854) +bind/7/name = &"HeadTop_End" +bind/7/bone = -1 +bind/7/pose = Transform3D(1, 0, 0, 0, 1, -7.36353e-08, 0, 7.36353e-08, 1, -0.000211779, -1.80253, -0.0675994) +bind/8/name = &"LeftShoulder" +bind/8/bone = -1 +bind/8/pose = Transform3D(-2.70084e-08, 1.41561e-07, 1, 1, 2.98023e-08, -1.52737e-07, -5.96046e-08, 1, -8.75443e-08, -0.0219638, -0.0529087, -1.40087) +bind/9/name = &"LeftUpperArm" +bind/9/bone = -1 +bind/9/pose = Transform3D(2.51457e-08, -1.41561e-07, -1, 1, 0, -1.55764e-07, 1.71363e-07, -1, 8.19564e-08, 0.022614, -0.159578, 1.35494) +bind/10/name = &"LeftLowerArm" +bind/10/bone = -1 +bind/10/pose = Transform3D(1.41561e-07, -1, 8.75443e-08, 1, 7.45058e-09, -1.45053e-07, -2.58442e-08, 1.41561e-07, 1, 1.32788, -0.394582, -0.0165654) +bind/11/name = &"LeftHand" +bind/11/bone = -1 +bind/11/pose = Transform3D(-1.16415e-07, -2.98023e-07, -1, 1, -3.72529e-08, -2.24449e-07, 1.37836e-07, -1, 2.68221e-07, 0.0468159, -0.700128, 1.29931) +bind/12/name = &"LeftThumbMetacarpal" +bind/12/bone = -1 +bind/12/pose = Transform3D(-1.41561e-07, 0.999999, -2.5332e-07, 0.816993, 1.3411e-07, 0.577702, 0.577702, -2.38419e-07, -0.816992, -1.28836, -0.633654, -0.337732) +bind/13/name = &"LeftThumbProximal" +bind/13/bone = -1 +bind/13/pose = Transform3D(-1.93715e-07, 1, -3.27826e-07, 0.816993, 1.19209e-07, 0.577702, 0.577702, -2.23517e-07, -0.816993, -1.27961, -0.661342, -0.314943) +bind/14/name = &"LeftThumbDistal" +bind/14/bone = -1 +bind/14/pose = Transform3D(-1.78814e-07, 1, -3.27826e-07, 0.816993, 1.63913e-07, 0.577702, 0.577702, -1.93715e-07, -0.816993, -1.26355, -0.702424, -0.29445) +bind/15/name = &"LeftHandThumb4" +bind/15/bone = -1 +bind/15/pose = Transform3D(0.882794, 0.108359, -0.457091, 0.467339, -0.301235, 0.831174, -0.0476264, -0.947373, -0.316569, -0.726945, -0.143036, 1.28468) +bind/16/name = &"LeftIndexProximal" +bind/16/bone = -1 +bind/16/pose = Transform3D(-1.04308e-07, -3.12924e-07, -1, 1, -1.49012e-08, -2.68221e-07, 1.63913e-07, -1, 2.5332e-07, 0.0562596, -0.728766, 1.29525) +bind/17/name = &"LeftIndexIntermediate" +bind/17/bone = -1 +bind/17/pose = Transform3D(-1.19209e-07, -2.83122e-07, -1, 1, -5.96046e-08, -2.68221e-07, 1.78814e-07, -1, 2.5332e-07, 0.0586976, -0.797667, 1.28216) +bind/18/name = &"LeftIndexDistal" +bind/18/bone = -1 +bind/18/pose = Transform3D(-1.19209e-07, -3.12924e-07, -1, 1, -8.9407e-08, -2.75671e-07, 8.9407e-08, -0.999999, 2.5332e-07, 0.0596645, -0.851826, 1.25857) +bind/19/name = &"LeftHandIndex4" +bind/19/bone = -1 +bind/19/pose = Transform3D(0.100367, 0.241802, -0.96512, 0.952157, -0.304767, 0.0226623, -0.288657, -0.921221, -0.260822, -0.332042, -0.470191, 1.42087) +bind/20/name = &"RightShoulder" +bind/20/bone = -1 +bind/20/pose = Transform3D(1.47149e-07, -1.35973e-07, -1, -1, -5.96046e-08, -2.62633e-07, 1.49012e-07, 1, -2.79397e-08, 0.0213137, -0.052485, -1.40087) +bind/21/name = &"RightUpperArm" +bind/21/bone = -1 +bind/21/pose = Transform3D(-1.47615e-07, 1.37836e-07, 1, -1, -9.68575e-08, -2.63099e-07, -7.45058e-09, -1, 2.98023e-08, -0.0206636, -0.159155, 1.35494) +bind/22/name = &"RightLowerArm" +bind/22/bone = -1 +bind/22/pose = Transform3D(2.98023e-08, 1, -2.98023e-08, -1, -9.68575e-08, -2.84985e-07, -1.59489e-07, 1.37836e-07, 1, -1.32787, -0.394158, -0.0163669) +bind/23/name = &"RightHand" +bind/23/bone = -1 +bind/23/pose = Transform3D(-1.6205e-07, 1.78814e-07, 1, -1, -5.96046e-08, -4.2282e-07, -1.49012e-08, -1, 5.96046e-08, -0.0487486, -0.699704, 1.29931) +bind/24/name = &"RightThumbMetacarpal" +bind/24/bone = -1 +bind/24/pose = Transform3D(1.49012e-08, -1, 2.98023e-08, -0.816993, 1.49012e-08, 0.577701, -0.577702, -1.71363e-07, -0.816993, 1.2884, -0.635369, -0.337157) +bind/25/name = &"RightThumbProximal" +bind/25/bone = -1 +bind/25/pose = Transform3D(1.49012e-08, -1, 2.98023e-08, -0.816992, 5.96046e-08, 0.577701, -0.577702, -1.86265e-07, -0.816993, 1.27966, -0.663813, -0.315368) +bind/26/name = &"RightThumbDistal" +bind/26/bone = -1 +bind/26/pose = Transform3D(-7.45058e-09, -1, 2.23517e-08, -0.816992, 5.96046e-08, 0.577701, -0.577702, -1.93715e-07, -0.816993, 1.26265, -0.703266, -0.293941) +bind/27/name = &"RightHandThumb4" +bind/27/bone = -1 +bind/27/pose = Transform3D(0.866295, -0.0191474, 0.499165, -0.495057, -0.166372, 0.852783, 0.0667186, -0.985877, -0.153606, 0.596671, -0.336699, 1.31991) +bind/28/name = &"RightIndexProximal" +bind/28/bone = -1 +bind/28/pose = Transform3D(-9.68575e-08, 2.08616e-07, 0.999999, -1, -8.9407e-08, -3.09199e-07, 0, -1, 5.96046e-08, -0.0571759, -0.730956, 1.29278) +bind/29/name = &"RightIndexIntermediate" +bind/29/bone = -1 +bind/29/pose = Transform3D(-5.96046e-08, 1.93715e-07, 1, -1, -2.98023e-08, -2.98023e-07, -1.19209e-07, -0.999999, 8.9407e-08, -0.0582914, -0.801771, 1.28022) +bind/30/name = &"RightIndexDistal" +bind/30/bone = -1 +bind/30/pose = Transform3D(-6.70552e-08, 1.63913e-07, 0.999999, -1, -8.9407e-08, -3.05474e-07, -8.9407e-08, -0.999999, 8.9407e-08, -0.0581894, -0.851115, 1.25596) +bind/31/name = &"RightHandIndex4" +bind/31/bone = -1 +bind/31/pose = Transform3D(0.0752528, -0.208862, 0.975045, -0.93441, -0.356174, -0.0041788, 0.348159, -0.910778, -0.221966, 0.269271, -0.388984, 1.45314) +bind/32/name = &"LeftUpperLeg" +bind/32/bone = -1 +bind/32/pose = Transform3D(-1, 2.04866e-10, 6.10822e-09, -5.4598e-09, -1, 1.86265e-09, -6.17825e-09, 5.58794e-09, 1, 0.0804644, 0.886258, -0.0368454) +bind/33/name = &"LeftLowerLeg" +bind/33/bone = -1 +bind/33/pose = Transform3D(1, 9.54606e-09, -6.0536e-09, -4.13274e-09, -1, 5.96046e-08, 5.82077e-09, -4.47035e-08, -1, -0.0888384, 0.463061, 0.0316173) +bind/34/name = &"LeftFoot" +bind/34/bone = -1 +bind/34/pose = Transform3D(-1, 4.47035e-08, 2.23517e-08, -7.45058e-09, -2.98023e-08, 1, 8.19564e-08, 1, 8.9407e-08, 0.0846109, 0.0517891, -0.127656) +bind/35/name = &"LeftToes" +bind/35/bone = -1 +bind/35/pose = Transform3D(1, -6.61239e-08, -6.45668e-08, -3.29019e-08, -1.32248e-07, 1, -9.91859e-08, -1, -1.76951e-07, -0.129617, -0.10671, 0.00968673) +bind/36/name = &"LeftToe_End" +bind/36/bone = -1 +bind/36/pose = Transform3D(-0.963968, -0.0106284, 0.265807, 0.265966, -0.0184431, 0.963806, -0.00534139, 0.999773, 0.0206052, 0.0966848, -0.212285, -0.011191) +bind/37/name = &"RightUpperLeg" +bind/37/bone = -1 +bind/37/pose = Transform3D(-1, 9.8263e-09, -1.56738e-07, 2.96314e-09, -1, 1.86265e-09, 1.56776e-07, 1.86265e-09, 1, -0.0800408, 0.886258, -0.0409504) +bind/38/name = &"RightLowerLeg" +bind/38/bone = -1 +bind/38/pose = Transform3D(1, -5.58794e-09, 1.57394e-07, -7.04313e-09, -1, -1.49012e-08, -1.56695e-07, -2.98023e-08, -1, 0.0884148, 0.463061, 0.02931) +bind/39/name = &"RightFoot" +bind/39/bone = -1 +bind/39/pose = Transform3D(-1, 7.45058e-09, -8.9407e-08, 2.23517e-07, -8.9407e-08, 1, -3.72529e-08, 1, 2.38419e-07, -0.0841873, 0.0499646, -0.127656) +bind/40/name = &"RightToes" +bind/40/bone = -1 +bind/40/pose = Transform3D(1, 4.19095e-08, 1.34809e-07, 2.21422e-07, -1.91852e-07, 1, 4.37722e-08, -1, -2.17929e-07, 0.129193, -0.10368, 0.0096871) +bind/41/name = &"RightToe_End" +bind/41/bone = -1 +bind/41/pose = Transform3D(-0.962939, 0.0167611, -0.269197, -0.269489, -0.0186837, 0.962822, 0.0111084, 0.999685, 0.0225081, -0.0966572, -0.208619, -0.0105825) + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_tw3ea"] +radius = 0.3 +height = 1.8 + +[sub_resource type="SphereShape3D" id="SphereShape3D_dtrpb"] +radius = 0.25 + +[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_8v5ya"] +use_custom_timeline = true +timeline_length = 1.0 +stretch_time_scale = false +start_offset = 0.0 +loop_mode = 1 + +[sub_resource type="AnimationNodeTimeSeek" id="AnimationNodeTimeSeek_edbho"] + +[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_otud8"] +nodes/Locomotion/node = SubResource("AnimationNodeAnimation_8v5ya") +nodes/Locomotion/position = Vector2(-460, 300) +nodes/TimeSeek/node = SubResource("AnimationNodeTimeSeek_edbho") +nodes/TimeSeek/position = Vector2(-200, 240) +nodes/output/position = Vector2(60, 260) +node_connections = [&"output", 0, &"Locomotion"] + +[node name="Character" type="CharacterBody3D"] +collision_layer = 0 +script = ExtResource("1_rxdap") + +[node name="Armature" type="Node3D" parent="."] + +[node name="GeneralSkeleton" type="Skeleton3D" parent="Armature"] +unique_name_in_owner = true +motion_scale = 0.940095 +bones/0/name = "Root" +bones/0/parent = -1 +bones/0/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) +bones/0/enabled = true +bones/0/position = Vector3(0, 0, 0) +bones/0/rotation = Quaternion(0, 0, 0, 1) +bones/0/scale = Vector3(1, 1, 1) +bones/1/name = "Hips" +bones/1/parent = 0 +bones/1/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.000211779, 0.940095, 0.0353721) +bones/1/enabled = true +bones/1/position = Vector3(0, 0.792365, 0) +bones/1/rotation = Quaternion(-0.0311311, 1.57783e-08, -0.0987859, 0.994622) +bones/1/scale = Vector3(1, 1, 1) +bones/2/name = "Spine" +bones/2/parent = 1 +bones/2/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0968958, -0.0027467) +bones/2/enabled = true +bones/2/position = Vector3(0, 0.0968958, -0.0027467) +bones/2/rotation = Quaternion(0.134469, 0.00579701, -0.0269781, 0.990534) +bones/2/scale = Vector3(1, 1, 1) +bones/3/name = "Chest" +bones/3/parent = 2 +bones/3/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.113045, -0.00320449) +bones/3/enabled = true +bones/3/position = Vector3(-1.86265e-11, 0.113045, -0.00320449) +bones/3/rotation = Quaternion(0.132533, -0.0103766, 0.0583457, 0.989405) +bones/3/scale = Vector3(1, 1, 1) +bones/4/name = "UpperChest" +bones/4/parent = 3 +bones/4/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.129194, -0.00366227) +bones/4/enabled = true +bones/4/position = Vector3(-1.86265e-11, 0.129194, -0.00366227) +bones/4/rotation = Quaternion(0.12752, 0.00167819, 0.0668022, 0.989582) +bones/4/scale = Vector3(1, 1, 1) +bones/5/name = "Neck" +bones/5/parent = 4 +bones/5/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.86265e-11, 0.145344, -0.00412005) +bones/5/enabled = true +bones/5/position = Vector3(-1.86265e-11, 0.145344, -0.00412005) +bones/5/rotation = Quaternion(-0.148526, 0.0409817, -0.0270438, 0.987689) +bones/5/scale = Vector3(1, 1, 1) +bones/6/name = "Head" +bones/6/parent = 5 +bones/6/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0867303, 0.0105468) +bones/6/enabled = true +bones/6/position = Vector3(0, 0.0867303, 0.0105468) +bones/6/rotation = Quaternion(-0.024008, 0.283491, -0.0207193, 0.95845) +bones/6/scale = Vector3(1, 1, 1) +bones/7/name = "HeadTop_End" +bones/7/parent = 6 +bones/7/rest = Transform3D(1, 0, 0, 0, 1, 6.51926e-08, 0, -6.51926e-08, 1, 0, 0.291221, 0.0354138) +bones/7/enabled = true +bones/7/position = Vector3(0, 0.291221, 0.0354138) +bones/7/rotation = Quaternion(-3.25963e-08, 0, 0, 1) +bones/7/scale = Vector3(1, 1, 1) +bones/8/name = "LeftShoulder" +bones/8/parent = 4 +bones/8/rest = Transform3D(0, 1, 0, 0, 0, 1, 1, 0, 0, 0.0526969, 0.121643, -0.00379498) +bones/8/enabled = true +bones/8/position = Vector3(0.0526969, 0.121643, -0.00379498) +bones/8/rotation = Quaternion(0.398242, 0.567141, 0.586034, -0.419903) +bones/8/scale = Vector3(1, 1, 1) +bones/9/name = "LeftUpperArm" +bones/9/parent = 8 +bones/9/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00065014, 0.10667, -0.0459293) +bones/9/enabled = true +bones/9/position = Vector3(0.00065014, 0.10667, -0.0459293) +bones/9/rotation = Quaternion(0.0792089, 0.846959, -0.293585, 0.436113) +bones/9/scale = Vector3(1, 1, 1) +bones/10/name = "LeftLowerArm" +bones/10/parent = 9 +bones/10/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, 0.00604859, 0.235004, 0.0270691) +bones/10/enabled = true +bones/10/position = Vector3(0.00604859, 0.235004, 0.0270691) +bones/10/rotation = Quaternion(0.731257, -0.641701, -0.219938, -0.0714786) +bones/10/scale = Vector3(1, 1, 1) +bones/11/name = "LeftHand" +bones/11/parent = 10 +bones/11/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, 0.0285643, 0.305546, 0.0302502) +bones/11/enabled = true +bones/11/position = Vector3(0.0285643, 0.305546, 0.0302502) +bones/11/rotation = Quaternion(0.0495354, 0.441097, 0.0165071, 0.895939) +bones/11/scale = Vector3(1, 1, 1) +bones/12/name = "LeftThumbMetacarpal" +bones/12/parent = 11 +bones/12/rest = Transform3D(0, -0.577, 0.816, 0, 0.816, 0.577, -1, 0, 0, -0.0432138, 0.0118055, 0.0109467) +bones/12/enabled = true +bones/12/position = Vector3(-0.0432138, 0.0118055, 0.0109467) +bones/12/rotation = Quaternion(-0.13451, 0.699428, 0.195949, 0.674026) +bones/12/scale = Vector3(1, 0.999392, 0.999392) +bones/13/name = "LeftThumbProximal" +bones/13/parent = 12 +bones/13/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, -0.00875785, 0.0276878, -0.0227883) +bones/13/enabled = true +bones/13/position = Vector3(-0.00875785, 0.0276878, -0.0227883) +bones/13/rotation = Quaternion(0.110539, 0.0511475, 0.015851, 0.992428) +bones/13/scale = Vector3(1, 1, 1) +bones/14/name = "LeftThumbDistal" +bones/14/parent = 13 +bones/14/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0160604, 0.0410821, -0.020493) +bones/14/enabled = true +bones/14/position = Vector3(-0.0160604, 0.0410821, -0.020493) +bones/14/rotation = Quaternion(-0.154605, -0.150243, -0.141114, 0.966236) +bones/14/scale = Vector3(1, 1, 1) +bones/15/name = "LeftHandThumb4" +bones/15/parent = 14 +bones/15/rest = Transform3D(0.108359, -0.301235, -0.947373, 0.457174, 0.861984, -0.221793, 0.883432, -0.409081, 0.231121, -0.0107891, 0.0381448, -0.00767406) +bones/15/enabled = true +bones/15/position = Vector3(-0.0107891, 0.0381448, -0.00767406) +bones/15/rotation = Quaternion(-0.0631475, -0.616916, 0.255475, 0.741727) +bones/15/scale = Vector3(1.00061, 1.00056, 1.00005) +bones/16/name = "LeftIndexProximal" +bones/16/parent = 11 +bones/16/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00944374, 0.0286387, 0.00405895) +bones/16/enabled = true +bones/16/position = Vector3(-0.00944374, 0.0286387, 0.00405895) +bones/16/rotation = Quaternion(0.177586, 0.0892467, 0.00985566, 0.980001) +bones/16/scale = Vector3(1, 1, 1) +bones/17/name = "LeftIndexIntermediate" +bones/17/parent = 16 +bones/17/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00243786, 0.0689005, 0.0130873) +bones/17/enabled = true +bones/17/position = Vector3(-0.00243786, 0.0689005, 0.0130873) +bones/17/rotation = Quaternion(0.244978, 0.015973, -0.0266237, 0.969032) +bones/17/scale = Vector3(1, 1, 1) +bones/18/name = "LeftIndexDistal" +bones/18/parent = 17 +bones/18/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000967048, 0.0541595, 0.023595) +bones/18/enabled = true +bones/18/position = Vector3(-0.000967048, 0.0541595, 0.023595) +bones/18/rotation = Quaternion(0.245766, 0.0156584, -0.0268463, 0.968831) +bones/18/scale = Vector3(1, 1, 1) +bones/19/name = "LeftHandIndex4" +bones/19/parent = 18 +bones/19/rest = Transform3D(0.96512, -0.0226626, 0.260823, 0.100367, 0.952157, -0.288657, -0.241802, 0.304767, 0.92122, -0.00112531, 0.0393408, 0.0126457) +bones/19/enabled = true +bones/19/position = Vector3(-0.00112531, 0.0393408, 0.0126457) +bones/19/rotation = Quaternion(0.151445, 0.128272, 0.0313977, 0.979604) +bones/19/scale = Vector3(0.999999, 1, 0.999999) +bones/20/name = "RightShoulder" +bones/20/parent = 4 +bones/20/rest = Transform3D(0, -1, 0, 0, 0, 1, -1, 0, 0, -0.0526969, 0.121643, -0.00444512) +bones/20/enabled = true +bones/20/position = Vector3(-0.0526969, 0.121643, -0.00444512) +bones/20/rotation = Quaternion(0.327324, -0.534689, -0.639681, -0.44472) +bones/20/scale = Vector3(1, 1, 1) +bones/21/name = "RightUpperArm" +bones/21/parent = 20 +bones/21/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.000650136, 0.10667, -0.0459292) +bones/21/enabled = true +bones/21/position = Vector3(0.000650136, 0.10667, -0.0459292) +bones/21/rotation = Quaternion(-0.355708, 0.895328, -0.213151, -0.162562) +bones/21/scale = Vector3(1, 1, 1) +bones/22/name = "RightLowerArm" +bones/22/parent = 21 +bones/22/rest = Transform3D(0, 0, 1, 0, 1, 0, -1, 0, 0, -0.0042967, 0.235003, 0.0270691) +bones/22/enabled = true +bones/22/position = Vector3(-0.0042967, 0.235003, 0.0270691) +bones/22/rotation = Quaternion(0.619781, 0.556978, -0.483585, 0.267943) +bones/22/scale = Vector3(1, 1, 1) +bones/23/name = "RightHand" +bones/23/parent = 22 +bones/23/rest = Transform3D(0, 0, -1, 0, 1, 0, 1, 0, 0, -0.028564, 0.305546, 0.0323816) +bones/23/enabled = true +bones/23/position = Vector3(-0.028564, 0.305546, 0.0323816) +bones/23/rotation = Quaternion(0.0108917, -0.57737, -0.0706189, 0.81335) +bones/23/scale = Vector3(1, 1, 1) +bones/24/name = "RightThumbMetacarpal" +bones/24/parent = 23 +bones/24/rest = Transform3D(0, 0.577, -0.816, 0, 0.816, 0.577, 1, 0, 0, 0.0427395, 0.0132968, 0.0109151) +bones/24/enabled = true +bones/24/position = Vector3(0.0427395, 0.0132968, 0.0109151) +bones/24/rotation = Quaternion(-0.229387, -0.752398, -0.127943, 0.604077) +bones/24/scale = Vector3(1, 0.999392, 0.999392) +bones/25/name = "RightThumbProximal" +bones/25/parent = 24 +bones/25/rest = Transform3D(1, 0, 0, 0, 1, 2.98023e-08, 0, -2.98023e-08, 1, 0.00873183, 0.0284436, -0.0217889) +bones/25/enabled = true +bones/25/position = Vector3(0.00873183, 0.0284436, -0.0217889) +bones/25/rotation = Quaternion(0.176885, -0.0726807, -0.025381, 0.981216) +bones/25/scale = Vector3(1, 1, 1) +bones/26/name = "RightThumbDistal" +bones/26/parent = 25 +bones/26/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0170176, 0.0394534, -0.0214269) +bones/26/enabled = true +bones/26/position = Vector3(0.0170176, 0.0394534, -0.0214269) +bones/26/rotation = Quaternion(0.251464, -0.00291094, 0.0843176, 0.964183) +bones/26/scale = Vector3(1.00003, 0.999978, 0.999996) +bones/27/name = "RightHandThumb4" +bones/27/parent = 26 +bones/27/rest = Transform3D(0.0191473, 0.166372, 0.985877, -0.419388, 0.897112, -0.143247, -0.908274, -0.410722, 0.0869519, 0.00597439, 0.0381001, -0.00505783) +bones/27/enabled = true +bones/27/position = Vector3(0.00597439, 0.0381001, -0.00505783) +bones/27/rotation = Quaternion(-0.0944823, 0.669066, -0.20684, 0.70756) +bones/27/scale = Vector3(1.00061, 1.00059, 1.00002) +bones/28/name = "RightIndexProximal" +bones/28/parent = 23 +bones/28/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00842737, 0.031252, 0.00653073) +bones/28/enabled = true +bones/28/position = Vector3(0.00842737, 0.031252, 0.00653073) +bones/28/rotation = Quaternion(0.044136, -0.0105012, 0.0505882, 0.997689) +bones/28/scale = Vector3(1, 1, 1) +bones/29/name = "RightIndexIntermediate" +bones/29/parent = 28 +bones/29/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.00111553, 0.0708153, 0.0125644) +bones/29/enabled = true +bones/29/position = Vector3(0.00111553, 0.0708153, 0.0125644) +bones/29/rotation = Quaternion(0.100369, -0.00420676, 0.00897789, 0.994901) +bones/29/scale = Vector3(1, 1, 1) +bones/30/name = "RightIndexDistal" +bones/30/parent = 29 +bones/30/rest = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.000102064, 0.0493442, 0.0242563) +bones/30/enabled = true +bones/30/position = Vector3(-0.000102064, 0.0493442, 0.0242563) +bones/30/rotation = Quaternion(0.288026, -0.0113591, 0.0260744, 0.9572) +bones/30/scale = Vector3(1, 1, 1) +bones/31/name = "RightHandIndex4" +bones/31/parent = 30 +bones/31/rest = Transform3D(0.975045, -0.00417861, -0.221966, -0.0752528, 0.93441, -0.348158, 0.208862, 0.356174, 0.910777, 0.000182196, 0.038542, 0.0147775) +bones/31/enabled = true +bones/31/position = Vector3(0.000182196, 0.038542, 0.0147775) +bones/31/rotation = Quaternion(0.180179, -0.110212, -0.0181818, 0.977271) +bones/31/scale = Vector3(1, 1, 0.999999) +bones/32/name = "LeftUpperLeg" +bones/32/parent = 1 +bones/32/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, 0.0802526, -0.0538372, 0.00147327) +bones/32/enabled = true +bones/32/position = Vector3(0.0802526, -0.0538372, 0.00147327) +bones/32/rotation = Quaternion(0.141314, 0.398254, 0.871306, -0.249501) +bones/32/scale = Vector3(1, 1, 1) +bones/33/name = "LeftLowerLeg" +bones/33/parent = 32 +bones/33/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.00837398, 0.423197, -0.00522811) +bones/33/enabled = true +bones/33/position = Vector3(-0.00837398, 0.423197, -0.00522811) +bones/33/rotation = Quaternion(0.0229167, 0.929403, -0.367593, 0.0236491) +bones/33/scale = Vector3(1, 1, 1) +bones/34/name = "LeftFoot" +bones/34/parent = 33 +bones/34/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, -0.00422749, 0.335405, 0.0834064) +bones/34/enabled = true +bones/34/position = Vector3(-0.00422749, 0.335405, 0.0834064) +bones/34/rotation = Quaternion(-0.0167404, 0.714118, -0.699811, 0.00445483) +bones/34/scale = Vector3(1, 1, 1) +bones/35/name = "LeftToes" +bones/35/parent = 34 +bones/35/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, -0.0450058, 0.158499, -0.117969) +bones/35/enabled = true +bones/35/position = Vector3(-0.0450058, 0.158499, -0.117969) +bones/35/rotation = Quaternion(0.00241416, 0.996219, 0.0192321, 0.0846844) +bones/35/scale = Vector3(1, 1, 1) +bones/36/name = "LeftToe_End" +bones/36/parent = 35 +bones/36/rest = Transform3D(-0.963968, 0.265966, -0.00534146, 0.265807, 0.963806, 0.0206052, 0.0106284, 0.0184428, -0.999773, 0.019985, 0.0724217, 0.00138581) +bones/36/enabled = true +bones/36/position = Vector3(0.019985, 0.0724217, 0.00138581) +bones/36/rotation = Quaternion(0.134164, 0.990902, 0.0098516, -0.00402913) +bones/36/scale = Vector3(1, 1, 1) +bones/37/name = "RightUpperLeg" +bones/37/parent = 1 +bones/37/rest = Transform3D(-1, 0, 0, 0, -1, 0, 0, 0, 1, -0.0802526, -0.0538372, 0.00557836) +bones/37/enabled = true +bones/37/position = Vector3(-0.0802526, -0.0538372, 0.00557836) +bones/37/rotation = Quaternion(0.181248, 0.187815, 0.964953, -0.0272212) +bones/37/scale = Vector3(1, 1, 1) +bones/38/name = "RightLowerLeg" +bones/38/parent = 37 +bones/38/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.00837398, 0.423197, -0.0116405) +bones/38/enabled = true +bones/38/position = Vector3(0.00837398, 0.423197, -0.0116405) +bones/38/rotation = Quaternion(-0.0173609, 0.772848, -0.63381, -0.0262526) +bones/38/scale = Vector3(1, 1, 1) +bones/39/name = "RightFoot" +bones/39/parent = 38 +bones/39/rest = Transform3D(-1, 0, 0, 0, 0, -1, 0, -1, 0, 0.00422749, 0.335405, 0.0792746) +bones/39/enabled = true +bones/39/position = Vector3(0.00422749, 0.335405, 0.0792746) +bones/39/rotation = Quaternion(-0.0316443, 0.809054, -0.586873, -0.00312965) +bones/39/scale = Vector3(1, 1, 1) +bones/40/name = "RightToes" +bones/40/parent = 39 +bones/40/rest = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0.0450058, 0.153645, -0.117969) +bones/40/enabled = true +bones/40/position = Vector3(0.0450058, 0.153645, -0.117969) +bones/40/rotation = Quaternion(-0.0093812, 0.992891, 0.106017, -0.0532823) +bones/40/scale = Vector3(1, 1, 1) +bones/41/name = "RightToe_End" +bones/41/parent = 40 +bones/41/rest = Transform3D(-0.96294, -0.269489, 0.0111084, -0.269197, 0.962822, 0.022508, -0.0167611, 0.0186834, -0.999685, -0.0199851, 0.071402, 0.00138553) +bones/41/enabled = true +bones/41/position = Vector3(-0.0199851, 0.071402, 0.00138553) +bones/41/rotation = Quaternion(-0.135944, 0.990637, 0.0103952, 0.00703323) +bones/41/scale = Vector3(1, 1, 1) + +[node name="characterLargeMale_001" type="MeshInstance3D" parent="Armature/GeneralSkeleton"] +mesh = ExtResource("1_1v5in") +skin = SubResource("Skin_n4hiu") + +[node name="MMInertialization3D" type="MMInertialization3D" parent="Armature/GeneralSkeleton"] +halflife = 0.04 +unique_name_in_owner = true + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.9, 0) +shape = SubResource("CapsuleShape3D_tw3ea") + +[node name="CameraRig" type="SpringArm3D" parent="."] +unique_name_in_owner = true +transform = Transform3D(1, 0, 0, 0, 0.984414, 0.175865, 0, -0.175865, 0.984414, 0, 1.8, 0) +top_level = true +shape = SubResource("SphereShape3D_dtrpb") +spring_length = 2.5 + +[node name="Camera3D" type="Camera3D" parent="CameraRig"] +unique_name_in_owner = true +current = true + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +active = false +root_motion_track = NodePath("%GeneralSkeleton:Root") +callback_mode_process = 0 +libraries = { +"locomotion": ExtResource("3_y0uif") +} + +[node name="AnimationTree" type="AnimationTree" parent="."] +unique_name_in_owner = true +root_node = NodePath("%AnimationTree/..") +root_motion_track = NodePath("%GeneralSkeleton:Root") +tree_root = SubResource("AnimationNodeBlendTree_otud8") +anim_player = NodePath("../AnimationPlayer") +parameters/TimeSeek/seek_request = -1.0 + +[node name="MMTimer" type="Timer" parent="AnimationTree"] +process_callback = 0 +wait_time = 0.1 +autostart = true +script = ExtResource("3_rlvfq") + +[node name="RootMotionView" type="RootMotionView" parent="AnimationTree"] +animation_path = NodePath("..") + +[connection signal="timeout" from="AnimationTree/MMTimer" to="AnimationTree/MMTimer" method="_on_timeout"] diff --git a/demo/scenes/Characters/mm_timer.gd b/demo/scenes/Characters/mm_timer.gd new file mode 100644 index 00000000..e7e9183e --- /dev/null +++ b/demo/scenes/Characters/mm_timer.gd @@ -0,0 +1,53 @@ +class_name simpleMotionMatchingSetupTimer extends Timer + +@onready var MM := preload("res://assets/resources/locomotion.res") +@onready var mm_inertialization_3d: MMInertialization3D = %"MMInertialization3D" +@onready var best_index = -1 +@onready var animation_tree: AnimationTree = %AnimationTree +@onready var body :simpleMMCharacterBody = owner + + +func _on_timeout(): + var query := PackedFloat32Array() + # this is important to be in order. + var bones_feature :MFBonesInfo= MM.motion_features[0] + var root_velocity_feature :MFRootVelocity= MM.motion_features[1] + #var trajectory_feature :MFTrajectory = MM.motion_features[2] + + # in order + query.append_array(bones_feature.serialize_MMInertialization3D(mm_inertialization_3d)) + query.append_array(root_velocity_feature.serialize_CharacterBody3d(owner)) + + # var prediction = body.kform.character_prediction( linear_acceleration, + # # desired_velocity, + # desired_rotation, + # real_t halflife_velocity, real_t halflife_rotation, + # deltas + + + # Construct the query + var queryoptions := MMQueryOptions.new() + queryoptions.query = query + queryoptions.custom_weights = MM.weights + queryoptions.result_count = 1 + queryoptions.continuation_index = best_index + queryoptions.continuation_bias = 0.5 + queryoptions.ignore_surrounding_frames = 4 + + var best_pose :Dictionary= MM.query_pose_aabb(queryoptions)[0] + + if best_pose.size() != 0 && best_pose["index"] != best_index: + best_index = best_pose["index"] + prints(best_pose) + var anim :AnimationNodeAnimation= animation_tree.tree_root.get_node("Locomotion") + anim.animation = "locomotion/" + best_pose["animation"] + anim.start_offset = best_pose["timestamp"] + animation_tree["parameters/TimeSeek/seek_request"] = best_pose["timestamp"] + #animation_tree.clear_caches() + else : + best_index += 1 + + + + + pass diff --git a/demo/scenes/Characters/simplecharacter.gd b/demo/scenes/Characters/simplecharacter.gd new file mode 100644 index 00000000..f628d6d5 --- /dev/null +++ b/demo/scenes/Characters/simplecharacter.gd @@ -0,0 +1,62 @@ +class_name simpleMMCharacterBody extends CharacterBody3D + +@export var current_linear_halflife := 0.1 +@export var current_angular_halflife := 0.1 +@export var max_speed := 5.0 + + +@onready var camera_rig: SpringArm3D = %CameraRig +@onready var animation_tree: AnimationTree = %AnimationTree + +@onready var cam: Camera3D = %Camera3D +@onready var body :simpleMMCharacterBody= self + +@onready var kform := Kform.new() +@onready var linear_acceleration := Vector3() + +func _ready() -> void: + animation_tree.active = true + + + + +func _physics_process(delta: float) -> void: + var input_2d := Input.get_vector("ui_left","ui_right","ui_up","ui_down") + var input_3d := Vector3(input_2d.x,0.0,input_2d.y) + + var dir := cam.global_basis.get_rotation_quaternion() + + var desired_rotation := MMUtil.get_twist(dir,body.up_direction) + var desired_velocity := desired_rotation * input_3d * max_speed + + if input_3d == Vector3.ZERO and !Input.is_action_pressed("ui_accept") : + desired_rotation = body.quaternion + elif Input.is_action_pressed("ui_accept"): + desired_rotation *= Quaternion.from_euler(Vector3(0,deg_to_rad(180),0)) + else: + desired_rotation = Quaternion(Vector3.MODEL_FRONT,desired_velocity.normalized()).normalized() + + var char_update := Spring.character_update(\ + global_position,velocity,linear_acceleration, \ + quaternion,kform.angular_velocity,\ + desired_velocity ,desired_rotation,\ + current_linear_halflife,current_angular_halflife,\ + delta) + + var next = char_update + + kform.angular_velocity = next.angular_velocity + linear_acceleration = next.linear_acceleration + velocity = next.linear_velocity + + quaternion = MMUtil.get_twist(next.angular_rotation,up_direction) + + velocity += -up_direction * 0.01 * delta + + body.move_and_slide() + body.apply_floor_snap() + + camera_rig.global_position = global_position + Vector3(0,1.8,0) + + + diff --git a/demo/scenes/DemoScene.tscn b/demo/scenes/DemoScene.tscn new file mode 100644 index 00000000..cb5ac403 --- /dev/null +++ b/demo/scenes/DemoScene.tscn @@ -0,0 +1,49 @@ +[gd_scene load_steps=9 format=3 uid="uid://6flglvor4ovs"] + +[ext_resource type="Texture2D" uid="uid://bkyqrq1o4d3sm" path="res://addons/kenney_prototype_textures/dark/texture_05.png" id="1_003o2"] +[ext_resource type="PackedScene" uid="uid://yyuwsiyi6vc1" path="res://scenes/Characters/character.tscn" id="2_q6if2"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_xljkg"] +sky_top_color = Color(0.14902, 0.454902, 0.694118, 1) + +[sub_resource type="Sky" id="Sky_cbv34"] +sky_material = SubResource("ProceduralSkyMaterial_xljkg") + +[sub_resource type="Environment" id="Environment_1wst2"] +background_mode = 2 +sky = SubResource("Sky_cbv34") +ambient_light_source = 3 +tonemap_mode = 3 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ur2ki"] +albedo_texture = ExtResource("1_003o2") +uv1_triplanar = true + +[sub_resource type="BoxMesh" id="BoxMesh_oapco"] +material = SubResource("StandardMaterial3D_ur2ki") +size = Vector3(1000, 0.1, 1000) + +[sub_resource type="ConvexPolygonShape3D" id="ConvexPolygonShape3D_wknx8"] +points = PackedVector3Array(-500, -0.05, -500, -500, 0.05, -500, 500, -0.05, -500, -500, -0.05, 500, -500, 0.05, 500, 500, 0.05, -500, 500, -0.05, 500, 500, 0.05, 500) + +[node name="DemoScene" type="Node3D"] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_1wst2") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="WorldEnvironment"] +transform = Transform3D(1, 0, 0, 0, 0.381925, 0.924193, 0, -0.924193, 0.381925, 0, 1.66641, 0) + +[node name="Level" type="Node3D" parent="."] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="Level"] +mesh = SubResource("BoxMesh_oapco") +skeleton = NodePath("../..") + +[node name="StaticBody3D" type="StaticBody3D" parent="Level/MeshInstance3D"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Level/MeshInstance3D/StaticBody3D"] +shape = SubResource("ConvexPolygonShape3D_wknx8") + +[node name="Character" parent="." instance=ExtResource("2_q6if2")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.0500001, 0) diff --git a/doc_classes/CircularBuffer.xml b/doc_classes/CircularBuffer.xml new file mode 100644 index 00000000..e6b83e6e --- /dev/null +++ b/doc_classes/CircularBuffer.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/Kform.xml b/doc_classes/Kform.xml new file mode 100644 index 00000000..c0479e9b --- /dev/null +++ b/doc_classes/Kform.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Inverse the kform + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MFBonesInfo.xml b/doc_classes/MFBonesInfo.xml new file mode 100644 index 00000000..e557f607 --- /dev/null +++ b/doc_classes/MFBonesInfo.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MFEvents.xml b/doc_classes/MFEvents.xml new file mode 100644 index 00000000..8d3ffb87 --- /dev/null +++ b/doc_classes/MFEvents.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MFRootVelocity.xml b/doc_classes/MFRootVelocity.xml new file mode 100644 index 00000000..53918e9d --- /dev/null +++ b/doc_classes/MFRootVelocity.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MFTrajectory.xml b/doc_classes/MFTrajectory.xml new file mode 100644 index 00000000..a40dfc0a --- /dev/null +++ b/doc_classes/MFTrajectory.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMAnimationLibrary.xml b/doc_classes/MMAnimationLibrary.xml new file mode 100644 index 00000000..b5ac4a8a --- /dev/null +++ b/doc_classes/MMAnimationLibrary.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMAnimationPlayer.xml b/doc_classes/MMAnimationPlayer.xml new file mode 100644 index 00000000..15f19a87 --- /dev/null +++ b/doc_classes/MMAnimationPlayer.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMIKLookAt3D.xml b/doc_classes/MMIKLookAt3D.xml new file mode 100644 index 00000000..d15fa341 --- /dev/null +++ b/doc_classes/MMIKLookAt3D.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMIKTwoBone3D.xml b/doc_classes/MMIKTwoBone3D.xml new file mode 100644 index 00000000..63b3fd6b --- /dev/null +++ b/doc_classes/MMIKTwoBone3D.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMInertialization3D.xml b/doc_classes/MMInertialization3D.xml new file mode 100644 index 00000000..6d26f22c --- /dev/null +++ b/doc_classes/MMInertialization3D.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MMUtil.xml b/doc_classes/MMUtil.xml new file mode 100644 index 00000000..ae8844dc --- /dev/null +++ b/doc_classes/MMUtil.xml @@ -0,0 +1,39 @@ + + + + Small set of tools that are generic to this library + + + Functions used everywhere + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/MotionFeature.xml b/doc_classes/MotionFeature.xml new file mode 100644 index 00000000..eec136a6 --- /dev/null +++ b/doc_classes/MotionFeature.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/SetRangeIndex.xml b/doc_classes/SetRangeIndex.xml new file mode 100644 index 00000000..e378f551 --- /dev/null +++ b/doc_classes/SetRangeIndex.xml @@ -0,0 +1,54 @@ + + + + Classe representing multi range of integer. Concatenate ranges that overlap. Only store the limits of the ranges. + + + + + + + + + + + + + + + + + + + + + + + In case you manipulate the range property and you want to fix the overlaps. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/Spring.xml b/doc_classes/Spring.xml new file mode 100644 index 00000000..7dc3076e --- /dev/null +++ b/doc_classes/Spring.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/TagAnimation.xml b/doc_classes/TagAnimation.xml new file mode 100644 index 00000000..6b639727 --- /dev/null +++ b/doc_classes/TagAnimation.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc_classes/TagCategory.xml b/doc_classes/TagCategory.xml new file mode 100644 index 00000000..663b2179 --- /dev/null +++ b/doc_classes/TagCategory.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc_classes/TagInfo.xml b/doc_classes/TagInfo.xml new file mode 100644 index 00000000..a452df5c --- /dev/null +++ b/doc_classes/TagInfo.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc_classes/TagJunk.xml b/doc_classes/TagJunk.xml new file mode 100644 index 00000000..94a04248 --- /dev/null +++ b/doc_classes/TagJunk.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc_classes/TagMFDistance.xml b/doc_classes/TagMFDistance.xml new file mode 100644 index 00000000..1ae9de20 --- /dev/null +++ b/doc_classes/TagMFDistance.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc_classes/TagMFEvent.xml b/doc_classes/TagMFEvent.xml new file mode 100644 index 00000000..0e312f36 --- /dev/null +++ b/doc_classes/TagMFEvent.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/doc_classes/TagMotionMatching.xml b/doc_classes/TagMotionMatching.xml new file mode 100644 index 00000000..ae400396 --- /dev/null +++ b/doc_classes/TagMotionMatching.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/doc_classes/TagRootWarp.xml b/doc_classes/TagRootWarp.xml new file mode 100644 index 00000000..06d964c1 --- /dev/null +++ b/doc_classes/TagRootWarp.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/godot-cpp b/godot-cpp index c1196a1a..daf6ad36 160000 --- a/godot-cpp +++ b/godot-cpp @@ -1 +1 @@ -Subproject commit c1196a1ab0a1ca166d0e5e2f08f9fe4156118c5e +Subproject commit daf6ad3649898e019baeeefafd05d0390e9f78bd diff --git a/src/AnimTags/AnimTag.hpp b/src/AnimTags/AnimTag.hpp new file mode 100644 index 00000000..f8026446 --- /dev/null +++ b/src/AnimTags/AnimTag.hpp @@ -0,0 +1,270 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +using namespace godot; +using u = godot::UtilityFunctions; +// Base Class +struct TagInfo : godot::Resource { + GDCLASS(TagInfo, Resource); + +public: + GETSET(StringName, animation_name); + real_t timestamp{}; + real_t get_timestamp() { return timestamp; } + void set_timestamp(real_t value) { + const real_t snap = godot::Engine::get_singleton()->get_physics_ticks_per_second(); + timestamp = std::max(real_t(0.0), std::floor(value * snap) / snap); + emit_changed(); + } + real_t duration{ real_t(0.016) }; + real_t get_duration() { return duration; } + void set_duration(real_t value) { + const real_t snap = godot::Engine::get_singleton()->get_physics_ticks_per_second(); + duration = std::abs(value); // std::max(real_t(0.0), std::floor(value * snap) / snap ); + emit_changed(); + } + + GDVIRTUAL0RC(Color,get_tag_color); + + Color _get_tag_color(){ + return Color::get_named_color(Color::find_named_color("GREEN_YELLOW")); + } + +protected: + static void _bind_methods() { + GDVIRTUAL_BIND(get_tag_color); + + ClassDB::bind_method(D_METHOD("get_tag_color"),&TagInfo::_get_tag_color); + + ClassDB::bind_method(D_METHOD("set_animation_name", "value"), &TagInfo::set_animation_name); + ClassDB::bind_method(D_METHOD("get_animation_name"), &TagInfo::get_animation_name); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING_NAME, "animation_name"), "set_animation_name", "get_animation_name"); + + ClassDB::bind_method(D_METHOD("set_timestamp", "value"), &TagInfo::set_timestamp); + ClassDB::bind_method(D_METHOD("get_timestamp"), &TagInfo::get_timestamp); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "timestamp"), "set_timestamp", "get_timestamp"); + + ClassDB::bind_method(D_METHOD("set_duration", "value"), &TagInfo::set_duration, DEFVAL(real_t(0.016))); + ClassDB::bind_method(D_METHOD("get_duration"), &TagInfo::get_duration); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "duration"), "set_duration", "get_duration"); + } +}; + +struct TagMotionMatching : TagInfo { + GDCLASS(TagMotionMatching, TagInfo); + +public: +protected: + static void _bind_methods() { + } +}; + +struct TagJunk : TagMotionMatching { + GDCLASS(TagJunk, TagMotionMatching); + +public: +protected: + static void _bind_methods() { + } +}; + +struct TagCategory : TagMotionMatching { + GDCLASS(TagCategory, TagMotionMatching); + +public: + GETSET(int, category); + GETSET(StringName, property_hint_string, ""); + +protected: + void _get_property_list(List *r_props) const { // return list of properties + TagMotionMatching::_get_property_list(r_props); + PropertyInfo catego{ Variant::INT, "category", godot::PROPERTY_HINT_FLAGS, property_hint_string }; + r_props->push_back(catego); + + PropertyInfo hint{ Variant::STRING_NAME, "property_hint_string", godot::PROPERTY_HINT_NONE, "", godot::PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED | godot::PROPERTY_USAGE_DEFAULT }; + r_props->push_back(hint); + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + if (p_name == StringName("category")) { + r_ret = category; + return true; + } else if (p_name == StringName("property_hint_string")) { + r_ret = property_hint_string; + return true; + } else { + return TagMotionMatching::_get(p_name, r_ret); + } + return false; + } + + bool _set(const StringName &p_name, const Variant &p_value) { + if (p_name == StringName("category")) { + category = (int)p_value; + return true; + } else if (p_name == StringName("property_hint_string")) { + property_hint_string = (StringName)p_value; + return true; + } else { + return TagMotionMatching::_set(p_name, p_value); + } + return false; + } + +protected: + static void _bind_methods() { + } +}; + +struct TagMFEvent : TagMotionMatching { + GDCLASS(TagMFEvent, TagMotionMatching); + +public: + GETSET(StringName, event_name); + // GETSET(real_t, reference_time); + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_event_name", "value"), &TagMFEvent::set_event_name); + ClassDB::bind_method(D_METHOD("get_event_name"), &TagMFEvent::get_event_name); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING_NAME, "event_name"), "set_event_name", "get_event_name"); + + // ClassDB::bind_method( D_METHOD("set_reference_time" ,"value"), &TagMFEvent::set_reference_time); + // ClassDB::bind_method( D_METHOD("get_reference_time" ), &TagMFEvent::get_reference_time); + // godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"reference_time"), "set_reference_time", "get_reference_time"); + } +}; + +struct TagMFDistance : TagMotionMatching { + GDCLASS(TagMFDistance, TagMotionMatching); + +public: + enum Strategy { + RootPos, + AnchorPoint, + AnchorBone + }; + GETSET(StringName, event_name); + GETSET(int, anchor_point_strategy, RootPos); + GETSET(StringName, reference_bone); + GETSET(Vector3, reference_position); + + void _get_property_list(List *r_props) const { // return list of properties + auto name = PropertyInfo(Variant::STRING_NAME, "event_name"); + r_props->push_back(name); + + auto strat = PropertyInfo(Variant::INT, "anchor_point_strategy", PROPERTY_HINT_ENUM, "UseRootPosition,UsePoint,UseBone", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + r_props->push_back(strat); + + auto ref_point = PropertyInfo(Variant::VECTOR3, "anchor_point", PROPERTY_HINT_NONE, "", anchor_point_strategy == AnchorPoint ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE); + r_props->push_back(ref_point); + + auto ref_bone = PropertyInfo(Variant::STRING_NAME, "bone_name", PROPERTY_HINT_NONE, "", anchor_point_strategy == AnchorBone ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_STORAGE); + r_props->push_back(ref_bone); + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + if (p_name == StringName("event_name")) { + r_ret = event_name; + return true; + } else if (p_name == StringName("anchor_point_strategy")) { + r_ret = anchor_point_strategy; + return true; + } else if (p_name == StringName("bone_name")) { + r_ret = reference_bone; + return true; + } else if (p_name == StringName("anchor_point")) { + r_ret = reference_position; + return true; + } else { + return TagMotionMatching::_get(p_name, r_ret); + } + return false; + } + + bool _set(const StringName &p_name, const Variant &p_value) { + if (p_name == StringName("event_name")) { + event_name = (StringName)p_value; + return true; + } else if (p_name == StringName("anchor_point_strategy")) { + anchor_point_strategy = (int)p_value; + return true; + } else if (p_name == StringName("bone_name")) { + reference_bone = (StringName)p_value; + return true; + } else if (p_name == StringName("anchor_point")) { + reference_position = (Vector3)p_value; + return true; + } else { + return TagMotionMatching::_set(p_name, p_value); + } + return false; + } + +protected: + static void _bind_methods() {} +}; + +struct TagAnimation : TagInfo { + GDCLASS(TagAnimation, TagInfo); + +public: +protected: + static void _bind_methods() { + } +}; + +struct TagRootWarp : TagAnimation { + GDCLASS(TagRootWarp, TagAnimation); + +public: + enum warp_rule_t { + Z = 0, + Zr, + XY, + XYr, + XYZ, + XYZr, + R + }; + enum rotation_type_t { + Align = 0, + Facing + }; + + GETSET(warp_rule_t, warp_rule); + GETSET(rotation_type_t, rotation_type); + +protected: + static void _bind_methods() { + BIND_ENUM_CONSTANT(Z); + BIND_ENUM_CONSTANT(Zr); + BIND_ENUM_CONSTANT(XY); + BIND_ENUM_CONSTANT(XYr); + BIND_ENUM_CONSTANT(XYZ); + BIND_ENUM_CONSTANT(XYZr); + BIND_ENUM_CONSTANT(R); + ClassDB::bind_method(D_METHOD("set_warp_rule", "value"), &TagRootWarp::set_warp_rule, DEFVAL(XYZr)); + ClassDB::bind_method(D_METHOD("get_warp_rule"), &TagRootWarp::get_warp_rule); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "warp_rule", godot::PROPERTY_HINT_ENUM, "Z,Zr,XY,XYr,XYZ,XYZr,R"), "set_warp_rule", "get_warp_rule"); + BIND_ENUM_CONSTANT(Align); + BIND_ENUM_CONSTANT(Facing); + ClassDB::bind_method(D_METHOD("set_rotation_type", "value"), &TagRootWarp::set_rotation_type, DEFVAL(Align)); + ClassDB::bind_method(D_METHOD("get_rotation_type"), &TagRootWarp::get_rotation_type); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "rotation_type", godot::PROPERTY_HINT_ENUM, "Align,Facing"), "set_rotation_type", "get_rotation_type"); + } +}; +VARIANT_ENUM_CAST(TagRootWarp::warp_rule_t); +VARIANT_ENUM_CAST(TagRootWarp::rotation_type_t); diff --git a/src/AnimTags/IndexSet.hpp b/src/AnimTags/IndexSet.hpp new file mode 100644 index 00000000..0e7f4059 --- /dev/null +++ b/src/AnimTags/IndexSet.hpp @@ -0,0 +1,801 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace godot; + +auto range_less = [](auto lhs, auto rhs) { + if (lhs.begin() != rhs.begin()) + return lhs.begin() < rhs.begin(); + else + return lhs.end() < rhs.end(); +}; + +template +void spans_simplify(R &r, bool skip_sorting = true) { + using range_type = R; + using span_type = R::value_type; + if (!skip_sorting) + std::ranges::sort(r, range_less); + size_t i = 0; + span_type _c = r[0]; + for (auto &_r : r) { + if (_c.end() < _r.begin()) { + r[i++] = _c; + _c = _r; + } else { + _c = span_type(std::min(_c.begin(), _r.begin()), + std::max(_c.end(), _r.end())); + } + } + r[i++] = _c; + r.resize(i); +} + +template +void spans_difference(R &out, const R &lhs, const R &rhs) { + using span_type = R::value_type; + out.resize(lhs.size() + rhs.size()); + // Activation state of each list of ranges + bool out_active = false; + bool lhs_active = false; + bool rhs_active = false; + + // Event index for each list of ranges + int out_i = 0; + int lhs_i = 0; + int rhs_i = 0; + + // While both ranges have events to process + while (lhs_i < lhs.size() * 2 && rhs_i < rhs.size() * 2) { + // Are the next lhs, and rhs events active or inactive + bool lhs_active_next = lhs_i % 2 == 0; + bool rhs_active_next = rhs_i % 2 == 0; + + // Time of the next lhs, and rhs events + auto lhs_t = + lhs_active_next ? lhs[lhs_i / 2].begin() : lhs[lhs_i / 2].end(); + auto rhs_t = + rhs_active_next ? rhs[rhs_i / 2].begin() : rhs[rhs_i / 2].end(); + + // Event coming from lhs first + if (lhs_t < rhs_t) { + // Activate output + if (!out_active && !rhs_active && lhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + lhs_i++; + } + // Event coming from rhs first + else if (rhs_t < lhs_t) { + // Activate output + if (!out_active && lhs_active && !rhs_active_next) { + out_active = true; + out[out_i] = span_type(rhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && rhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), rhs_t); + out_i++; + } + + rhs_active = rhs_active_next; + rhs_i++; + } + // Event from lhs and rhs coming at same time + else { + // Activate output + if (!out_active && lhs_active_next && !rhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && rhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + rhs_active = rhs_active_next; + lhs_i++; + rhs_i++; + } + } + + // Process any remaining lhs events + while (lhs_i < lhs.size() * 2) { + bool lhs_active_next = lhs_i % 2 == 0; + auto lhs_t = + lhs_active_next ? lhs[lhs_i / 2].begin() : lhs[lhs_i / 2].end(); + + // Activate output + if (!out_active && lhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + lhs_i++; + } + out.resize(out_i); +} + +template +void spans_intersection(R &out, const R &lhs, const R &rhs) { + using span_type = R::value_type; + out.resize(lhs.size() + rhs.size()); + // Activation state of each list of ranges + bool out_active = false; + bool lhs_active = false; + bool rhs_active = false; + + // Event index for each list of ranges + int out_i = 0; + int lhs_i = 0; + int rhs_i = 0; + + // While both ranges have events to process + while (lhs_i < lhs.size() * 2 && rhs_i < rhs.size() * 2) { + // Are the next lhs, and rhs events active or inactive + bool lhs_active_next = lhs_i % 2 == 0; + bool rhs_active_next = rhs_i % 2 == 0; + + // Time of the next lhs, and rhs events + auto lhs_t = + lhs_active_next ? lhs[lhs_i / 2].begin() : lhs[lhs_i / 2].end(); + auto rhs_t = + rhs_active_next ? rhs[rhs_i / 2].begin() : rhs[rhs_i / 2].end(); + + // Event from lhs coming first + if (lhs_t < rhs_t) { + // Activate output + if (!out_active && rhs_active && lhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + lhs_i++; + } + // Event from rhs coming first + else if (rhs_t < lhs_t) { + // Activate output + if (!out_active && lhs_active && rhs_active_next) { + out_active = true; + out[out_i] = span_type(rhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !rhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), rhs_t); + out_i++; + } + + rhs_active = rhs_active_next; + rhs_i++; + } + // Event from lhs and rhs coming at same time + else { + // Activate output + if (!out_active && (lhs_active_next && rhs_active_next)) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && (!lhs_active_next || !rhs_active_next)) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + rhs_active = rhs_active_next; + lhs_i++; + rhs_i++; + } + } + out.resize(out_i); +} + +template +void spans_union(R &out, const R1 &lhs, const R2 &rhs) { + using span_type = R::value_type; + out.resize(lhs.size() + rhs.size()); + + bool out_active = false; + bool lhs_active = false; + bool rhs_active = false; + + // Event index for each list of ranges + int out_i = 0; + int lhs_i = 0; + int rhs_i = 0; + + // While both ranges have events to process + while (lhs_i < lhs.size() * 2 && rhs_i < rhs.size() * 2) { + // Are the next lhs, and rhs events active or inactive + bool lhs_active_next = lhs_i % 2 == 0; + bool rhs_active_next = rhs_i % 2 == 0; + + // Time of the next lhs, and rhs events + auto lhs_t = + lhs_active_next ? lhs[lhs_i / 2].begin() : lhs[lhs_i / 2].end(); + auto rhs_t = + rhs_active_next ? rhs[rhs_i / 2].begin() : rhs[rhs_i / 2].end(); + + // Event from lhs is coming first + if (lhs_t < rhs_t) { + // Activate output + if (!out_active && lhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active_next && !rhs_active) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + lhs_i++; + } + // Event from rhs is coming first + else if (rhs_t < lhs_t) { + // Activate output + if (!out_active && rhs_active_next) { + out_active = true; + out[out_i] = span_type(rhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active && !rhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), rhs_t); + out_i++; + } + + rhs_active = rhs_active_next; + rhs_i++; + } + // Event from lhs and rhs coming at same time + else { + // Activate output + if (!out_active && (lhs_active_next || rhs_active_next)) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !(lhs_active_next || rhs_active_next)) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + rhs_active = rhs_active_next; + lhs_i++; + rhs_i++; + } + } + + // Process any remaining lhs events + while (lhs_i < lhs.size() * 2) { + bool lhs_active_next = lhs_i % 2 == 0; + auto lhs_t = + lhs_active_next ? lhs[lhs_i / 2].begin() : lhs[lhs_i / 2].end(); + + // Activate output + if (!out_active && lhs_active_next) { + out_active = true; + out[out_i] = span_type(lhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !lhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), lhs_t); + out_i++; + } + + lhs_active = lhs_active_next; + lhs_i++; + } + + // Process any remaining rhs events + while (rhs_i < rhs.size() * 2) { + bool rhs_active_next = rhs_i % 2 == 0; + auto rhs_t = + rhs_active_next ? rhs[rhs_i / 2].begin() : rhs[rhs_i / 2].end(); + + // Activate output + if (!out_active && rhs_active_next) { + out_active = true; + out[out_i] = span_type(rhs_t, out[out_i].end()); + } + // Deactivate output + else if (out_active && !rhs_active_next) { + out_active = false; + out[out_i] = span_type(out[out_i].begin(), rhs_t); + out_i++; + } + + rhs_active = rhs_active_next; + rhs_i++; + } + out.resize(out_i); +} + +#include + +class IndexRange { +public: + using value_type = IndexRange; + size_t FROM = 0; + size_t TO = 0; + // member typedefs provided through inheriting from std::iterator + class iterator + : public std::iterator { + friend class IndexRange; + size_t num = 0; + size_t FROM = 0; + size_t TO = 0; + + public: + iterator() : + FROM{ 0 }, TO{ 0 } {} + iterator(const iterator &) = default; + explicit iterator(long _num, size_t from, size_t to) : + num(_num), FROM{ from }, TO{ to } {} + iterator &operator++() { + num = TO >= FROM ? num + 1 : num - 1; + return *this; + } + iterator operator++(int) { + iterator retval = *this; + ++(*this); + return retval; + } + iterator &operator=(iterator &other) = default; + iterator &operator=(const iterator &other) = default; + + bool operator==(iterator other) const { return num == other.num; } + bool operator<=(iterator other) const { return num <= other.num; } + bool operator<(iterator other) const { return num < other.num; } + bool operator>=(iterator other) const { return num >= other.num; } + bool operator>(iterator other) const { return num > other.num; } + bool operator!=(iterator other) const { return !(*this == other); } + reference operator*() const { return num; } + }; + IndexRange() = default; + IndexRange(size_t from, size_t to) : + FROM{ from }, TO{ to } {} + IndexRange(size_t to) : + FROM{ 0 }, TO{ to } {} + IndexRange(iterator from, iterator to) : + FROM{ from.num }, TO{ to.num } {} + IndexRange(const IndexRange &other) = default; + IndexRange &operator=(const IndexRange &other) = default; + iterator begin() { return iterator(FROM, FROM, TO); } + iterator end() { return iterator(TO, FROM, TO); } + iterator begin() const { return iterator(FROM, FROM, TO); } + iterator end() const { return iterator(TO, FROM, TO); } + size_t front() { return FROM; } + size_t back() { return TO; } + size_t size() { return TO - FROM; } + + IndexRange subspan(size_t offset, size_t size) const { + return IndexRange(offset, offset + size); + } + IndexRange subrange(size_t start, size_t end) const { + return IndexRange(start, end); + } +}; + +#include + +class IndexSet { + IndexRange _lhs; + std::vector _subviews; + +public: + using value_type = IndexRange; + IndexSet() = default; + IndexSet(const size_t start, const size_t end) : + _lhs{ start, end }, _subviews{ IndexRange{ start, end } } { + } + IndexSet(const IndexRange _v, + const std::initializer_list &_sub = {}) : + _lhs{ _v }, _subviews{ _sub } {} + IndexSet(const IndexSet &r) = default; + + const IndexRange subspan(size_t offset, size_t count) const { + return _lhs.subspan(offset, count); + } + const IndexRange subrange(size_t index_begin, size_t index_end) const { + assert(index_begin <= index_end); + return _lhs.subrange(index_begin, index_end); + } + + void operator|=(const IndexRange &_rhs) { + assert(_lhs.begin() <= _rhs.begin() && _rhs.end() <= _lhs.end()); + spans_union(_subviews, std::vector{ _subviews }, std::vector{ _rhs }); + } + void operator|=(const IndexSet &_rhs) { + spans_union(_subviews, std::vector{ _subviews }, + _rhs._subviews); + } + IndexSet operator||(const IndexSet &_rhs) const { + IndexSet result{ _lhs }; + spans_union(result._subviews, _subviews, _rhs._subviews); + return result; + } + + void operator-=(const IndexRange &_rhs) { + assert(_lhs.begin() <= _rhs.begin() && _rhs.end() <= _lhs.end()); + spans_difference(_subviews, std::vector{ _subviews }, { _rhs }); + } + void operator-=(const IndexSet &_rhs) { + // assert(_lhs.begin() <= _rhs.begin() && _rhs.end() <= _lhs.end()); + spans_difference(_subviews, std::vector{ _subviews }, + _rhs._subviews); + } + IndexSet operator-(const IndexSet &_rhs) const { + IndexSet result{ _lhs }; + spans_difference(result._subviews, _subviews, _rhs._subviews); + return result; + } + + void operator&=(const IndexRange &_rhs) { + assert(_lhs.begin() <= _rhs.begin() && _rhs.end() <= _lhs.end()); + spans_intersection(_subviews, std::vector{ _subviews }, + { _rhs }); + } + IndexSet operator&&(const IndexRange &_rhs) const { + assert(_lhs.begin() <= _rhs.begin() && _rhs.end() <= _lhs.end()); + IndexSet result = *this; + spans_intersection(result._subviews, std::vector{ _subviews }, + { _rhs }); + return result; + } + + IndexSet operator&&(const IndexSet &_rhs) const { + IndexSet result{ _lhs }; + spans_intersection(result._subviews, _subviews, _rhs._subviews); + return result; + } + void operator&=(const IndexSet &_rhs) { + spans_intersection(_subviews, std::vector{ _subviews }, + _rhs._subviews); + } + + IndexRange &operator[](const std::size_t index) { return _subviews[index]; } + const IndexRange &operator[](const std::size_t index) const { + return _subviews[index]; + } + + auto begin() { return _subviews.begin(); } + auto end() { return _subviews.end(); } + auto cbegin() { return _subviews.cbegin(); } + auto cend() { return _subviews.cend(); } + + using iter_t = decltype(_subviews)::iterator; + iter_t iterator; + typename IndexRange::iterator sub_iterator; + bool _iter_init(Variant arg) { + iterator = _subviews.begin(); + sub_iterator = iterator->begin(); + return iterator != _subviews.end() && sub_iterator != iterator->end(); + } + bool _iter_next(Variant arg) { + if (iterator == _subviews.end()) + return false; + ++sub_iterator; + if (sub_iterator == iterator->end()) { + ++iterator; + sub_iterator = iterator->begin(); + } + return iterator != _subviews.end(); + // return iterator != buffer.end(); + } + Variant _iter_get(Variant arg) { + return (int)*sub_iterator; + } + + // IndexSet RasterizeBit(size_t _mask) { + + // IndexSet out{_lhs}; + // out._subviews.clear(); + + // bool out_active = false; + // int out_i = 0; + // int start = 0; + + // for (auto i : + // std::views::iota(0) | std::ranges::views::take(_lhs.size())) { + // std::bitset bit = _lhs[i]; + // // Activate output + // if (!out_active && bit.test(_mask)) { + // start = i; + // out_active = true; + // } + // // Deactivate output + // else if (out_active && !bit.test(_mask)) { + // out._subviews.push_back(_lhs.subspan(start, i - start)); + // out_active = false; + // out_i++; + // } + // } + // if (out_active) { + // out._subviews.push_back(_lhs.subspan(start, _lhs.size() - + // start)); out_i++; + // } + // return out; + // } + + // span_view RasterizeValue(const T& _mask) { + // span_view out{_lhs}; + // out._subviews.clear(); + + // bool out_active = false; + // int out_i = 0; + // int start = 0; + + // for (auto i : + // std::views::iota(0) | std::ranges::views::take(_lhs.size())) { + // auto& bit = _lhs[i]; + // // Activate output + // if (!out_active && bit == _mask) { + // start = i; + // out_active = true; + // } + // // Deactivate output + // else if (out_active && bit != _mask) { + // out._subviews.push_back(_lhs.subspan(start, i - start)); + // out_active = false; + // out_i++; + // } + // } + // if (out_active) { + // out._subviews.push_back(_lhs.subspan(start, _lhs.size() - + // start)); out_i++; + // } + // return out; + // } +}; + +#include +#include +#include + + +struct RangeIndex : godot::RefCounted { +public: + GDCLASS(RangeIndex, RefCounted); + +public: + void _init(int _f, int _t) { + from = _f; + to = _t; + } + GETSET(int, from, 0); + GETSET(int, to, 1); + + int begin() { return from; } + int end() { return to; } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("_init", "from", "to"), &RangeIndex::_init); + + ClassDB::bind_method(D_METHOD("set_from", "value"), &RangeIndex::set_from); + ClassDB::bind_method(D_METHOD("get_from"), &RangeIndex::get_from); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "from"), "set_from", "get_from"); + + ClassDB::bind_method(D_METHOD("set_to", "value"), &RangeIndex::set_to); + ClassDB::bind_method(D_METHOD("get_to"), &RangeIndex::get_to); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "to"), "set_to", "get_to"); + } +}; + +struct SetRangeIndex : godot::Resource { +public: + GDCLASS(SetRangeIndex, Resource) +public: + GETSET(TypedArray, ranges); + + void AddRange(int from, int to) { + if (ranges.size() == 0) { + Ref _r{}; + _r.instantiate(); + _r->from = from; + _r->to = to; + ranges.append(_r); + return; + } + + Ref _tmp{}; + _tmp.instantiate(); + Ref _r{}; + _r.instantiate(); + _r->from = from; + _r->to = to; + _tmp->ranges.append(_r); + Union(_tmp); + } + void RemoveRange(int from, int to) { + if (ranges.size() == 0) { + return; + } + + Ref _tmp{}; + _tmp.instantiate(); + Ref _r{}; + _r.instantiate(); + _r->from = from; + _r->to = to; + _tmp->ranges.append(_r); + Difference(_tmp); + } + + void Fix() { + std::vector _tmp_lhs{}; + for (size_t i = 0; i < ranges.size(); ++i) { + auto _rg = *cast_to(ranges[i]); + _tmp_lhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + + auto limit_it = std::remove_if(_tmp_lhs.begin(), _tmp_lhs.end(), [](auto r) { + return r.FROM > r.TO; + }); + _tmp_lhs.erase(limit_it, _tmp_lhs.end()); + + spans_simplify(_tmp_lhs, false); + + ranges.clear(); + for (size_t i = 0; i < _tmp_lhs.size(); ++i) { + Ref _ri{}; + _ri.instantiate(); + _ri->from = _tmp_lhs[i].front(); + _ri->to = _tmp_lhs[i].back(); + ranges.append(_ri); + } + } + + void Union(Ref other) { + std::vector _tmp_lhs{}, _tmp_rhs{}, _tmp_out{}; + + for (size_t i = 0; i < ranges.size(); ++i) { + auto _rg = *cast_to(ranges[i]); + _tmp_lhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + for (size_t i = 0; i < other->ranges.size(); ++i) { + auto _rg = *cast_to(other->ranges[i]); + _tmp_rhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + + spans_union(_tmp_out, _tmp_lhs, _tmp_rhs); + + ranges.clear(); + for (size_t i = 0; i < _tmp_out.size(); ++i) { + Ref _ri{}; + _ri.instantiate(); + _ri->from = _tmp_out[i].front(); + _ri->to = _tmp_out[i].back(); + ranges.append(_ri); + } + } + void Difference(Ref other) { + std::vector _tmp_lhs{}, _tmp_rhs{}, _tmp_out{}; + + for (size_t i = 0; i < ranges.size(); ++i) { + auto _rg = *cast_to(ranges[i]); + _tmp_lhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + for (size_t i = 0; i < other->ranges.size(); ++i) { + auto _rg = *cast_to(other->ranges[i]); + _tmp_rhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + spans_difference(_tmp_out, _tmp_lhs, _tmp_rhs); + + ranges.clear(); + for (size_t i = 0; i < _tmp_out.size(); ++i) { + Ref _ri{}; + _ri.instantiate(); + _ri->from = _tmp_out[i].front(); + _ri->to = _tmp_out[i].back(); + ranges.append(_ri); + } + } + void Intersection(Ref other) { + std::vector _tmp_lhs{}, _tmp_rhs{}, _tmp_out{}; + + for (size_t i = 0; i < ranges.size(); ++i) { + auto _rg = *cast_to(ranges[i]); + _tmp_lhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + for (size_t i = 0; i < other->ranges.size(); ++i) { + auto _rg = *cast_to(other->ranges[i]); + _tmp_rhs.push_back({ (size_t)_rg.from, (size_t)_rg.to }); + } + + spans_intersection(_tmp_out, _tmp_lhs, _tmp_rhs); + + ranges.clear(); + for (size_t i = 0; i < _tmp_out.size(); ++i) { + Ref _ri{}; + _ri.instantiate(); + _ri->from = _tmp_out[i].front(); + _ri->to = _tmp_out[i].back(); + ranges.append(_ri); + } + } + + bool Contain(int index) const { + for (auto v : std::ranges::iota_view{ ranges.size() }) { + auto r = cast_to(ranges[v]); + if (r == nullptr) + return false; + if (r->from <= index && index <= r->to) { + return true; + } else if (r->from > index) + return false; + } + return false; + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_ranges", "value"), &SetRangeIndex::set_ranges); + ClassDB::bind_method(D_METHOD("get_ranges"), &SetRangeIndex::get_ranges); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::ARRAY, "ranges", godot::PROPERTY_HINT_TYPE_STRING, u::str(Variant::OBJECT) + '/' + u::str(Variant::BASIS) + ":RangeIndex", PROPERTY_USAGE_DEFAULT), "set_ranges", "get_ranges"); + + ClassDB::bind_method(D_METHOD("AddRange", "from", "to"), &SetRangeIndex::AddRange); + ClassDB::bind_method(D_METHOD("RemoveRange", "from", "to"), &SetRangeIndex::RemoveRange); + + ClassDB::bind_method(D_METHOD("Union", "other"), &SetRangeIndex::Union); + + ClassDB::bind_method(D_METHOD("Difference", "other"), &SetRangeIndex::Difference); + + ClassDB::bind_method(D_METHOD("Intersection", "other"), &SetRangeIndex::Intersection); + + ClassDB::bind_method(D_METHOD("Fix"), &SetRangeIndex::Fix); + } +}; \ No newline at end of file diff --git a/src/CircularBuffer.hpp b/src/CircularBuffer.hpp deleted file mode 100644 index 7913ae95..00000000 --- a/src/CircularBuffer.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -using namespace godot; - -struct CircularBuffer : public godot::RefCounted -{ - GDCLASS(CircularBuffer,RefCounted); - using capacity_t = typename boost::circular_buffer_space_optimized::capacity_type; - using value_type = typename boost::circular_buffer_space_optimized::value_type; - - - - void set_capacity(int i){ - if (i < 0) return; - buffer.resize(i); - } - int get_capacity(){ - return buffer.capacity().capacity(); - } - - void push_back(Variant v){ - buffer.push_back(v); - } - void push_front(Variant v){ - buffer.push_front(v); - } - void pop_back(Variant v){ - buffer.push_back(v); - } - void pop_front(Variant v){ - buffer.push_front(v); - } - - void insert(int index,Variant v){ - if(index > 0) - buffer.insert(std::next(buffer.begin(),index),v); - } - void erase(int index){ - if(index > 0) - buffer.erase(std::next(buffer.begin(),index)); - } - void clear(){ - buffer.clear(); - } - - Variant get(int index) - { - return buffer[godot::Math::posmod(index,get_capacity())]; - } - - using iter_t = typename boost::circular_buffer_space_optimized::iterator; - iter_t iterator; - bool _iter_init(Variant arg){ - iterator= buffer.begin(); - return iterator != buffer.end(); - } - bool _iter_next(Variant arg){ - ++iterator; - return iterator != buffer.end(); - } - Variant _iter_get(Variant arg){ - return *iterator; - } - - - protected: - static void _bind_methods() - { - ClassDB::bind_method( D_METHOD("set_capacity","new_capacity"), &CircularBuffer::set_capacity,DEFVAL(1)); - ClassDB::bind_method( D_METHOD("get_capacity"), &CircularBuffer::get_capacity); - ClassDB::add_property("CircularBuffer",PropertyInfo(Variant::INT,"capacity",PROPERTY_HINT_RANGE,"1,100,or_greater"),"set_capacity","get_capacity"); - - ClassDB::bind_method( D_METHOD("push_back","value"), &CircularBuffer::push_back); - ClassDB::bind_method( D_METHOD("push_front","value"), &CircularBuffer::push_front); - ClassDB::bind_method( D_METHOD("pop_back"), &CircularBuffer::pop_back); - ClassDB::bind_method( D_METHOD("pop_front"), &CircularBuffer::pop_front); - - ClassDB::bind_method( D_METHOD("insert","index","value"), &CircularBuffer::insert); - ClassDB::bind_method( D_METHOD("erase","index"), &CircularBuffer::erase); - ClassDB::bind_method( D_METHOD("clear"), &CircularBuffer::clear); - - ClassDB::bind_method( D_METHOD("get","index"),&CircularBuffer::get); - - ClassDB::bind_method( D_METHOD("_iter_init","arg"),&CircularBuffer::_iter_init); - ClassDB::bind_method( D_METHOD("_iter_next","arg"),&CircularBuffer::_iter_next); - ClassDB::bind_method( D_METHOD("_iter_get","arg"),&CircularBuffer::_iter_get); - } - boost::circular_buffer_space_optimized buffer{capacity_t{1,1}}; -}; \ No newline at end of file diff --git a/src/KForm.hpp b/src/KForm.hpp deleted file mode 100644 index 73a531c3..00000000 --- a/src/KForm.hpp +++ /dev/null @@ -1,262 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include "godot_cpp/core/math.hpp" -#include "godot_cpp/variant/vector3.hpp" -#include - -#include -#include -#include -#include - -#include -#include -#include - -using namespace godot; - -struct kform -{ - Quaternion rot = Quaternion(); - Vector3 pos = Vector3(); - Vector3 scl= Vector3(1.0,1.0,1.0); - Vector3 vel= Vector3(); - Vector3 ang= Vector3(); - Vector3 svl= Vector3(); - - kform() = default; - ~kform() = default; - - kform(Transform3D tr): - pos{tr.origin}, - rot{tr.basis.get_rotation_quaternion()}, - scl{tr.basis.get_scale()}, - vel{},ang{},svl{} - {} - - kform(Vector3 p,Quaternion r,Vector3 s,Vector3 lv,Vector3 av,Vector3 sv) : - pos{p},rot{r}, scl{s}, vel{lv}, ang{av}, svl{sv} - {} - - kform(Ref skel,NodePath bonepath,Ref anim,double time) : - kform{skel->get_reference_pose(skel->find_bone(bonepath.get_concatenated_subnames()))} - { - auto tpos = anim->find_track(bonepath,Animation::TrackType::TYPE_POSITION_3D); - auto trot = anim->find_track(bonepath,Animation::TrackType::TYPE_ROTATION_3D); - auto tscl = anim->find_track(bonepath,Animation::TrackType::TYPE_SCALE_3D); - kform s1 = *this; - if (tpos != -1) - { - pos = anim->position_track_interpolate(tpos, time); - s1.pos = anim->position_track_interpolate(tpos, time + dt); - } - if (trot != -1) - { - rot = anim->rotation_track_interpolate(trot, time); - s1.rot = anim->rotation_track_interpolate(trot, time + dt); - } - if (tscl != -1) - { - scl = anim->scale_track_interpolate(tscl, time); - s1.scl = anim->scale_track_interpolate(tscl, time + dt); - } - *this = finite_difference(*this,s1,dt); - } - - enum Space { - Local,Model,RootMotion,Global - }; - - - - kform(Ref skel, Ref anim, double time, NodePath bonepath, Space space) - : kform(skel->get_reference_pose(skel->find_bone(bonepath.get_concatenated_subnames()))) - { - switch (space) - { - case Local: - _local(skel, anim, time, bonepath); - break; - case Model: - _model(skel, anim, time, bonepath); - break; - case RootMotion: - _root(skel, anim, time, bonepath); - break; - case Global: - _global(skel, anim, time, bonepath); - break; - } - } - static constexpr double dt = 0.016; - //DONE - void _local(Ref skel,Ref anim,double time,NodePath bonepath){ - *this = skel->get_reference_pose(skel->find_bone(bonepath.get_concatenated_subnames())); - auto tpos = anim->find_track(bonepath,Animation::TrackType::TYPE_POSITION_3D); - auto trot = anim->find_track(bonepath,Animation::TrackType::TYPE_ROTATION_3D); - auto tscl = anim->find_track(bonepath,Animation::TrackType::TYPE_SCALE_3D); - kform s1 = *this; - if (tpos != -1) - { - pos = anim->position_track_interpolate(tpos, time); - s1.pos = anim->position_track_interpolate(tpos, time + dt); - } - if (trot != -1) - { - rot = anim->rotation_track_interpolate(trot, time); - s1.rot = anim->rotation_track_interpolate(trot, time + dt); - } - if (tscl != -1) - { - scl = anim->scale_track_interpolate(tscl, time); - s1.scl = anim->scale_track_interpolate(tscl, time + dt); - } - *this = finite_difference(*this,s1,dt); - } - // Done - void _model(Ref skel,Ref anim,double time,NodePath bonepath){ - _global(skel,anim,time,bonepath); - auto root = kform{}; - root._local(skel,anim,time,bonepath.get_concatenated_names() + String(":") + skel->get_root_bone()); - *this = *this / root; - } - //TODO - void _root(Ref skel,Ref anim,double time,NodePath bonepath){ - _global(skel,anim,time,bonepath); - auto root = kform{}; - root._local(skel,anim,time,bonepath.get_concatenated_names() + String(":") + skel->get_root_bone()); - *this = root / *this; - } - //DONE - void _global(Ref skel, Ref anim, double time, NodePath bonepath) - { - kform s1 = *this; - - std::vector locals{}; - String skelpath = bonepath.get_concatenated_names(); - String bone = bonepath.get_concatenated_subnames(); - int bone_id = skel->find_bone(bone); - - do - { - kform l = *this; - l._local(skel, anim, time, skelpath + ':' + u::str(bone)); - locals.push_back(l); - - bone = skel->get_bone_parent(bone_id); - bone_id = skel->find_bone(bone); - } while (bone_id != -1); - - *this = std::accumulate(locals.rbegin(), locals.rend(), kform{}, - [](const kform &acc, const kform &i) - { - return acc * i; - }); - } - - static Vector3 _log(Vector3 v){ - return Vector3(std::log(v.x),std::log(v.y),std::log(v.z)); - } - - kform& finite_difference(kform input_next, real_t _dt) - { - vel = (input_next.pos - pos) / _dt; - - ang = Spring::quat_to_scaled_angle_axis(Spring::quat_abs( - input_next.rot * rot.inverse())) / - _dt; - - svl = _log(input_next.scl / scl) / _dt; - return *this; - } - - static kform finite_difference(const kform& input_curr,const kform& input_next, real_t _dt) - { - kform out = input_curr; - out.vel = (input_next.pos - out.pos) / _dt; - - out.ang = Spring::quat_to_scaled_angle_axis(Spring::quat_abs( - input_next.rot * out.rot.inverse())) / - _dt; - - out.svl = _log(input_next.scl / out.scl) / _dt; - return out; - } - - friend kform operator*(const kform v,const kform w){ - kform out; - out.pos = v.rot.xform(w.pos * v.scl) + v.pos; - out.rot = v.rot * w.rot; - out.scl = w.scl * v.scl; - out.vel = v.rot.xform( w.vel * v.scl)+ v.vel + - v.ang.cross(v.rot.xform( w.pos * v.scl)) + - v.rot.xform( w.pos * v.scl * v.svl); - out.ang = v.rot.xform(w.ang) + v.ang; - out.svl = w.svl + v.svl; - return out; - } - friend kform operator/(const kform v,const kform w) - { - kform out; - out.pos = v.rot.xform_inv(w.pos - v.pos); - out.rot = v.rot.inverse() * w.rot; - out.scl = w.scl / v.scl; - out.vel = v.rot.xform_inv(w.vel - v.vel - v.ang.cross(v.rot.xform(out.pos * v.scl))) - - v.rot.xform(out.pos * v.scl * v.svl); - out.ang = v.rot.xform_inv(w.ang - v.ang); - out.svl = w.svl - v.svl; - return out; - } - kform inverse() const{ - return kform() / (*this); - } -}; - -struct kforms -{ - template - using b_vector = std::vector; - b_vector pos; // Position - b_vector rot; // Rotation - b_vector scl; // Scale - b_vector vel; // Linear Velocity - b_vector ang; // Angular Velocity - b_vector svl; // Scalar Velocity - - kforms(std::size_t N): pos(N,Vector3()),rot(N,Quaternion()),scl(N,Vector3(1,1,1)),vel(N,Vector3()),ang(N,Vector3()),svl(N,Vector3()) - {} - - Transform3D get_transform(std::size_t N) - { - return Transform3D(Basis(rot[N],scl[N]),pos[N]); - } - - void reserve(std::size_t N){ - pos.reserve(N); rot.reserve(N); scl.reserve(N);vel.reserve(N); ang.reserve(N); svl.reserve(N); - } - - std::size_t count() const noexcept { - return pos.size(); - } - - inline const kform operator[](const std::size_t N) noexcept{ - kform out{}; - out.pos = pos[N]; - out.rot = rot[N]; - out.scl = scl[N]; - out.vel = vel[N]; - out.ang = ang[N]; - out.svl = svl[N]; - return out; - } - - void reset(const std::size_t N){ - pos[N] = Vector3() ;rot[N] = Quaternion(); scl[N] = Vector3(1,1,1) ;vel[N] = Vector3();ang[N] = Vector3();svl[N] = Vector3(); - } -}; \ No newline at end of file diff --git a/src/MMAnimationLibrary.hpp b/src/MMAnimationLibrary.hpp index 75678531..d2a155ce 100644 --- a/src/MMAnimationLibrary.hpp +++ b/src/MMAnimationLibrary.hpp @@ -1,16 +1,19 @@ #pragma once +#include + +#include #include #include #include #include +#include #include #include -#include #include -#include +#include #include @@ -23,674 +26,906 @@ #include #include +#include #include -#include #include -#include +#include #include +#include +#include #include "godot_cpp/core/math.hpp" -#include "kdtree-cpp/kdtree.hpp" #include "MotionFeatures/MotionFeatures.hpp" +#include +#include +#include #include #include - using namespace godot; -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - -// TODO : Save the array in a hashed data structure, so that multiple object doesn't add a new array for each schema. +struct MMQueryOptions : public godot::Resource { + GDCLASS(MMQueryOptions, Resource) + using u = godot::UtilityFunctions; + +public: + GETSET(int, result_count, 1); + GETSET(bool, use_acceleration, true); + GETSET(PackedFloat32Array, query); + GETSET(PackedFloat32Array, custom_weights, {}); + GETSET(Ref, custom_ranges, nullptr); + GETSET(int, ignore_surrounding_frames, 1); + GETSET(int, continuation_index, -1); + GETSET(real_t, continuation_bias, -1.0) + + static void _bind_methods() { + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::INT, result_count, PROPERTY_HINT_RANGE, "1, 10, 1, or_greater"); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::BOOL, use_acceleration); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::PACKED_FLOAT32_ARRAY, query); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::PACKED_FLOAT32_ARRAY, custom_weights); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::OBJECT, custom_ranges, PROPERTY_HINT_TYPE_STRING, "SetRangeIndex"); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::INT, continuation_index); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::INT, ignore_surrounding_frames); + BINDER_PROPERTY_PARAMS(MMQueryOptions, Variant::FLOAT, continuation_bias, PROPERTY_HINT_RANGE, "0.01, 1, 0.01, or_greater"); + } +}; + struct MMAnimationLibrary : public AnimationLibrary { - using u = godot::UtilityFunctions; - GDCLASS(MMAnimationLibrary,AnimationLibrary) - - MMAnimationLibrary() : AnimationLibrary() - { - u::prints("MMAL", "Constructor",MotionData.size()); - } - ~MMAnimationLibrary() - { - u::prints("MMAL", "Destructor"); - if (kdt != nullptr) - { - delete kdt; - } - } - - void _notification(int what) - { - switch (what) - { - case NOTIFICATION_POSTINITIALIZE: // Constructor - { - u::prints("MMAL NOTIFICATION_POSTINITIALIZE", "InEditor:", godot::Engine::get_singleton()->is_editor_hint()); - if (!Engine::get_singleton()->is_editor_hint()) - { - fill_kdtree(); - } - } - break; - case NOTIFICATION_PREDELETE: // Destructor - { - u::prints("MMAL NOTIFICATION_PREDELETE", "InEditor:", godot::Engine::get_singleton()->is_editor_hint()); - if (kdt != nullptr) - { - delete kdt; - } - } - break; - default: - u::prints("MMAL Default notification", what); - } - } - - GETSET(StringName,skeleton_path); - GETSET(Ref,skeleton_profile) - GETSET(float,time_interval); - - // Category tracks - GETSET(TypedArray,category_track_names) - // Array of the motion features. - GETSET(TypedArray, motion_features); - // The data - GETSET(PackedFloat32Array,MotionData); - - // Dimensional Stats. - GETSET(int,nb_dimensions) - GETSET(PackedFloat32Array,weights) - GETSET(PackedFloat32Array,means) - GETSET(PackedFloat32Array,variances) - GETSET(Array,densities) - - // Database. A pose is just the index of a row in the kdtree. - // Usage : db_anim_*[result.index] = - GETSET(PackedInt32Array, db_anim_index); // Index of the animation name in the animation library - GETSET(PackedFloat32Array, db_anim_timestamp); // timestamp of the pose in the animation - GETSET(PackedInt32Array, db_anim_category); // Category of the pose in the animation - - // The KdTree. - Kdtree::KdTree * kdt = nullptr; - - - // How the kdtree calculate the distance. - // 0 (L0) : Maximum of each difference in all dimensions. - // 1 (L1) : Manhattan distance (default) - // 2 (L2) : Distance squared. - int distance_type = 1; int get_distance_type(){return distance_type;} - void set_distance_type(int value){ - distance_type = value; - if(kdt != nullptr && 0 <= distance_type && distance_type <= 2) - kdt->set_distance(distance_type); - } - - void _cache_kdtree(bool reset = false){ - if(reset) - { - if(kdt != nullptr) - delete kdt; - kdt = nullptr; - } - if(kdt == nullptr) - { - fill_kdtree(); - } - } - - void fill_kdtree() - { - u::prints("MF size",motion_features.size()); - ERR_FAIL_COND_EDMSG(nb_dimensions == 0,"Number Dimensions is zero"); - ERR_FAIL_COND_EDMSG(MotionData.is_empty(),"Motion Data is Empty"); - - u::prints("Total Dimension", nb_dimensions); - if(kdt != nullptr) - delete kdt; - - // Now we bake all the data - u::prints("Preparing kdtree"); - Kdtree::KdNodeVector nodes{}; - for(size_t i = 0; i < MotionData.size()/nb_dimensions; ++i) - { - auto begin = MotionData.ptr(), end = MotionData.ptr(); // We use the ptr as iterator. - begin = std::next(begin,nb_dimensions * i); - end = std::next(begin,nb_dimensions); - std::vector point(begin,end); - nodes.push_back(Kdtree::KdNode(std::move(point),&db_anim_category[i],i)); - } - u::prints("Creating kdtree"); - kdt = new Kdtree::KdTree(&nodes,distance_type); - - weights.resize(nb_dimensions); - - auto begin = weights.ptr(), end = weights.ptr(); // We use the ptr as iterator. - begin = std::next(begin,0); - end = std::next(begin,nb_dimensions); - const std::vector tmp_weight(begin,end); - - kdt->set_distance(distance_type,&tmp_weight); - u::prints("KDTree Constructed"); - } - - - - - - void bake_data() - { - ERR_FAIL_COND_EDMSG(motion_features.is_empty(),"No Motion Features to extract data"); - ERR_FAIL_COND_EDMSG(skeleton_profile == nullptr,"Skeleton_profile is empty"); - ERR_FAIL_COND_EDMSG(skeleton_profile->get_root_bone().is_empty(),"SkeletonProfile requires a Root Bone"); - u::prints("Preparing Features..."); - size_t tmp_nb_dim = 0; - for(auto i = 0; i < motion_features.size(); ++i ) - { - MotionFeature* f = Object::cast_to(motion_features[i]); - ERR_FAIL_NULL_MSG(f,"Features no."+u::str(i) + "is null"); - u::prints("Feature no.",i,f->get_name(),"Dimensions:", f->get_dimension()); - if (false == f->setup_profile(NodePath(skeleton_path),skeleton_profile) ) - { - ERR_FAIL_EDMSG("Motion Feature failed when setting the profile at index " + u::str(i)); - } - tmp_nb_dim += (int)(f->get_dimension()); - } - nb_dimensions = tmp_nb_dim; - u::prints("Total Dimension", nb_dimensions); - - godot::TypedArray anim_names = get_animation_list(); - u::prints("Detecting",anim_names.size(),"animations. Preparing..."); - - means.clear(); means.resize(nb_dimensions); means.fill(0.0f); - variances.clear();variances.resize(nb_dimensions); variances.fill(0.0f); - densities.clear();densities.resize(nb_dimensions); densities.fill(Array::make(0.0,0.0)); - - PackedFloat32Array data = PackedFloat32Array(); - - - db_anim_category.clear();db_anim_index.clear();db_anim_timestamp.clear(); - - using namespace boost::accumulators; - using acc_stats = stats; - const accumulator_set default_acc (tag::density::num_bins = 10, tag::density::cache_size = 15); - std::vector> data_stats(nb_dimensions,default_acc); - - u::prints("Starting animation baking..."); - - for(auto anim_index = 0; anim_index < anim_names.size(); ++anim_index) - { - auto clock_start = std::chrono::system_clock::now(); - - auto anim_name = anim_names[anim_index]; - auto animation = get_animation(anim_name); - - int should_continue = -1; - for(auto features_index = 0; features_index < motion_features.size(); ++features_index ) - { - MotionFeature* f = Object::cast_to(motion_features[features_index]); - if( false == f->setup_for_animation(animation)) - { - should_continue = features_index; - break; - } - } - if (should_continue != -1) - { - WARN_PRINT_ED("Skipping Animation '" + (String)anim_name + "' because of motion feature index :" + u::str(should_continue)); - continue; - } - // ERR_CONTINUE_EDMSG(should_continue != -1,); - - std::vector category_tracks{}; - for(auto i = 0 ; ifind_track((String)category_track_names[i],Animation::TrackType::TYPE_VALUE); - animation->value_track_set_update_mode(category_track,Animation::UpdateMode::UPDATE_DISCRETE); - animation->track_set_interpolation_type(category_track,Animation::InterpolationType::INTERPOLATION_NEAREST); - if (category_track != -1) - { - category_tracks.push_back(category_track); - } - u::prints("Checking Category Track",category_track_names[i], "result:",category_track != -1); - } - - const auto length = animation->get_loop_mode() == Animation::LOOP_NONE ? animation->get_length() - 0.2 : animation->get_length() ; - - u::prints("Animations setup for",anim_name,"duration",length); - - auto counter = 0; - for(auto time = time_interval; time < length; time += time_interval) - { - int64_t tmp_category_value = 0; - for(const auto& category:category_tracks) - { - tmp_category_value = tmp_category_value | (int64_t)animation->value_track_interpolate(category,time); - } - // If the reserved category value contain DONOTUSE (31th bit set to true), then we skip. - if (std::bitset<64>(tmp_category_value).test(31)) - { - continue; - } - - PackedFloat32Array pose_data{}; - for(size_t features_index = 0; features_index < motion_features.size(); ++features_index ) - { - MotionFeature* f = Object::cast_to(motion_features[features_index]); - PackedFloat32Array feature_data = f->bake_animation_pose(animation,time); - ERR_FAIL_COND_MSG(feature_data.size() != f->get_dimension(),String("Features no.") + u::str(int(features_index))+"bake_animation_pose didn't return a array of the correct size:"); - pose_data.append_array(feature_data); - } - - - for(int i = 0; i (clock_end - clock_start).count()); - u::prints("Collecting animation data from ",animation->get_name(), " in ", duration, "ms. PoseCount",counter); - } - - u::prints("Animation Data Collected. Normalizing... "); - - for(auto i = 0; i< nb_dimensions;++i) - { - means[i] = mean(data_stats[i]); - variances[i] = variance(data_stats[i]); - if (variances[i] <= std::numeric_limits::epsilon() ) - { - variances[i] = 1.0f; - } - Array arr{}; - for(const auto& d : density(data_stats[i])) - { - arr.append(Array::make(d.first,d.second) ); - } - densities[i] = std::move(arr); - } - - // // Normalization - // for(size_t pose = 0; pose < data.size()/nb_dimensions; ++pose) - // { - // for(int offset = 0; offset(motion_features[features_index]); - - weights.append_array(f->get_weights() ); - u::prints(f->get_name(),f->get_weights()); - } - u::prints("New Weights Values:",weights); - } - - // Bypass the feature query, and ask directly which poses is the most similar. - // The query must be of the correct dimension. - Array check_query_results(PackedFloat32Array query,int64_t nb_result = 1) - { - _cache_kdtree(true); - - auto begin = weights.ptr(), end = weights.ptr(); // We use the ptr as iterator. - begin = std::next(begin,0); - end = std::next(begin,query.size()); - const std::vector tmp_weight(begin,end); - - kdt->set_distance(distance_type,&tmp_weight); - - auto query_data = Kdtree::CoordPoint(query.ptr(),std::next(query.ptr(),query.size())); - - u::prints("query Constructed"); - - Kdtree::KdNodeVector re{}; - kdt->k_nearest_neighbors(query_data,nb_result,&re); - u::prints("Results obtained"); - Array result; - for(auto i : re) - { - const auto anim_name = get_animation_list()[db_anim_index[i.index]]; - const auto anim_time = db_anim_timestamp[i.index]; - const auto anim_cat = db_anim_category[i.index]; - result.append(Array::make(anim_name,anim_time,anim_cat)); - } - return result; - } - - struct Category_Pred : Kdtree::KdNodePredicate - { - const std::bitset<64> m_desired_category; - const std::bitset<64> m_exclude_category; - Category_Pred(int64_t included_category_bitfield, int64_t excluded_category_bitfield = 0): - m_desired_category{static_cast(included_category_bitfield)} - ,m_exclude_category{static_cast(excluded_category_bitfield)} - {} - - virtual bool operator()(const Kdtree::KdNode& node) const { - static constexpr std::bitset<64> zero = {}; - const std::bitset<64> node_category = *((int32_t*)node.data); - const bool include = (m_desired_category & node_category) == node_category; - const bool exclude = (m_exclude_category & node_category) == zero; - return include && exclude; - } - }; - - - - Dictionary query_pose(PackedFloat32Array query,int64_t included_category = std::numeric_limits::max(), int64_t excluded_category = 0) - { - - ERR_FAIL_COND_V_MSG(query.size() != nb_dimensions, {}, "Query must the same size as nb_dimensions"); - // Create three if needs be - _cache_kdtree(); - - // Normalization - // for (size_t i = 0; i < means.size();++i) - // { - // query[i] = (query[i] - means[i])/variances[i]; - // } - - { - Kdtree::KdNodeVector re{}; - - auto query_data = Kdtree::CoordPoint(query.ptr(),std::next(query.ptr(),kdt->dimension)); - auto clock_start = std::chrono::system_clock::now(); - if(included_category == std::numeric_limits::max()) - kdt->k_nearest_neighbors(query_data,1,&re); - else - { - auto pred = Category_Pred(included_category,excluded_category); - kdt->k_nearest_neighbors(query_data,1,&re,&pred); - } - - auto clock_end = std::chrono::system_clock::now(); - - float duration = float(std::chrono::duration_cast (clock_end - clock_start).count()); - - - - - Dictionary results = {}; - - const StringName anim_name = get_animation_list()[db_anim_index[re[0].index]]; - const float anim_time = db_anim_timestamp[re[0].index]; - - results["animation"] = anim_name; - results["timestamp"] = std::move(anim_time); - - return results; - } - return {}; - - } - - enum Space - { - Local, - Model, - RootMotion, - Global - }; - - Dictionary sample_bone_global_info(StringName animation_name, double time, NodePath bone_path) - { - ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); - ERR_FAIL_COND_V(!has_animation(animation_name), {}); - std::vector trs{}; - String _skel = bone_path.get_concatenated_names(); - String bone = bone_path.get_concatenated_subnames(); - do - { - trs.emplace_back(kform{skeleton_profile, NodePath(_skel + ":" + bone), get_animation(animation_name), time}); - bone = skeleton_profile->get_bone_parent(skeleton_profile->find_bone(bone)); - } while (!bone.is_empty()); - - auto kbone = std::reduce(trs.rbegin(), trs.rend(), kform{}, - [](const kform &acc, const kform &i) - { - return acc * i; - }); - Dictionary result = Dictionary{}; - result["position"] = kbone.pos; - result["linear_vel"] = kbone.vel; - result["rotation"] = kbone.rot; - result["angular_vel"] = kbone.ang; - result["scale"] = kbone.scl; - result["scalar_vel"] = kbone.svl; - return result; - } - - Dictionary sample_bone_model_info(StringName animation_name, double time, NodePath bone_path) - { - - ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); - ERR_FAIL_COND_V(!has_animation(animation_name), {}); - std::vector trs{}; - String _skel = bone_path.get_concatenated_names(); - String bone = bone_path.get_concatenated_subnames(); - do - { - trs.emplace_back(kform{skeleton_profile, NodePath(_skel + ":" + bone), get_animation(animation_name), time}); - bone = skeleton_profile->get_bone_parent(skeleton_profile->find_bone(bone)); - } while (!bone.is_empty() && bone != skeleton_profile->get_root_bone()); - - auto kbone = std::reduce(trs.rbegin(), trs.rend(), kform{}, - [](const kform &acc, const kform &i) - { - return acc * i; - }); - Dictionary result = Dictionary{}; - result["position"] = kbone.pos; - result["linear_vel"] = kbone.vel; - result["rotation"] = kbone.rot; - result["angular_vel"] = kbone.ang; - result["scale"] = kbone.scl; - result["scalar_vel"] = kbone.svl; - return result; - } - - Dictionary sample_bone_rootmotion_info(StringName animation_name, double time, NodePath bone_path) - { - ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); - ERR_FAIL_COND_V(!has_animation(animation_name), {}); - std::vector trs{}; - String _skel = bone_path.get_concatenated_names(); - String bone = bone_path.get_concatenated_subnames(); - do - { - trs.emplace_back(kform{skeleton_profile, NodePath(_skel + ":" + bone), get_animation(animation_name), time}); - bone = skeleton_profile->get_bone_parent(skeleton_profile->find_bone(bone)); - } while (!bone.is_empty() && bone != skeleton_profile->get_root_bone()); - - kform root{skeleton_profile,NodePath(_skel + ":" + skeleton_profile->get_root_bone()), get_animation(animation_name),time}; - root.vel = root.rot.xform_inv(root.vel); - root.ang = root.rot.xform_inv(root.ang); - root.pos = Vector3(); - root.rot = Quaternion(); - - auto kbone = std::reduce(trs.rbegin(), trs.rend(), root, - [](const kform &acc, const kform &i) - { - return acc * i; - }); - Dictionary result = Dictionary{}; - result["position"] = kbone.pos; - result["linear_vel"] = kbone.vel; - result["rotation"] = kbone.rot; - result["angular_vel"] = kbone.ang; - result["scale"] = kbone.scl; - result["scalar_vel"] = kbone.svl; - return result; - } - - Dictionary sample_bone_local_info(StringName animation_name, double time, NodePath bone_path) - { - ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); - ERR_FAIL_COND_V(!has_animation(animation_name), {}); - kform bone = kform{skeleton_profile, bone_path, get_animation(animation_name), time}; - Dictionary result = Dictionary{}; - result["position"] = bone.pos; - result["linear_vel"] = bone.vel; - result["rotation"] = bone.rot; - result["angular_vel"] = bone.ang; - result["scale"] = bone.scl; - result["scalar_vel"] = bone.svl; - return result; - } + using u = godot::UtilityFunctions; + GDCLASS(MMAnimationLibrary, AnimationLibrary) + +public: + MMAnimationLibrary() : + AnimationLibrary() { + u::prints("MMAL", "Constructor", MotionData.size()); + } + ~MMAnimationLibrary() { + u::prints("MMAL", "Destructor"); + } + + Callable anim_added = Callable(this, "_event_on_anim_added"); + Callable anim_removed = Callable(this, "_event_on_anim_removed"); + + void _notification(int what) { + switch (what) { + case NOTIFICATION_POSTINITIALIZE: // Constructor + { + u::prints("MMAL NOTIFICATION_POSTINITIALIZE", "InEditor:", godot::Engine::get_singleton()->is_editor_hint(), MotionData.size()); + // connect("animation_added", anim_added); + // connect("animation_removed", anim_removed); + } break; + case NOTIFICATION_PREDELETE: // Destructor + { + u::prints("MMAL NOTIFICATION_PREDELETE", "InEditor:", godot::Engine::get_singleton()->is_editor_hint(), MotionData.size()); + // disconnect("animation_added", anim_added); + // disconnect("animation_removed", anim_removed); + } break; + default: + u::prints("MMAL Default notification", what); + } + } + + void _event_on_anim_added(StringName animname) { + Curve *c = new Curve(); + Ref anim = get_animation(animname); + c->set_min_value(-1.0); + c->set_max_value(1.0); + c->add_point(Vector2{ 0.0, 0.0 }); + // if (c->has_method("set_max_domain")) // Waiting for PR https://github.com/godotengine/godot/pull/67857 + // { + // c->call("set_min_domain", 0.0); + // c->call("set_max_domain", anim->get_length()); + // c->add_point(Vector2(anim->get_length(), 0.0)); + // } else { + c->add_point(Vector2{ 1.0, 0.0 }); + // } + + curvecost[animname] = c; + } + void _event_on_anim_removed(StringName animname) { + curvecost.erase(animname); + } + Ref get_curvecost_animname(StringName animname) { + return curvecost.get_or_add(animname, new Curve{}); + } + + TypedArray get_pose_tags(String animation_name, float time) { + TypedArray result{}; + for (auto i = 0; i < tags.size(); ++i) { + Ref tag = cast_to(tags[i]); + if (tag->animation_name == animation_name && time >= tag->timestamp && (tag->timestamp + tag->duration) >= time) { + result.append(tag); + } + } + return result; + } + + void bake_data() { + for (auto i = 0; i < motion_features.size(); ++i) { + MotionFeature *f = Object::cast_to(motion_features[i]); + ERR_FAIL_COND_EDMSG(!f->has_method("get_dimension"), "Feature # " + u::str(i) + " doesn't have a get_dimension method"); + // ERR_FAIL_COND_EDMSG(!f->has_method("setup_bake_init"), "Feature # " + u::str(i) + " doesn't have a setup_bake_init method"); + // ERR_FAIL_COND_EDMSG(!f->has_method("setup_bake_animation"), "Feature # " + u::str(i) + " doesn't have a setup_bake_animation method"); + // ERR_FAIL_COND_EDMSG(!f->has_method("bake_animation_pose"), "Feature # " + u::str(i) + " doesn't have a bake_animation_pose method"); + } + + ERR_FAIL_COND_EDMSG(time_interval < 0.016f, "Please choose a time inverval higher than 0.016s"); + ERR_FAIL_COND_EDMSG(motion_features.is_empty(), "No Motion Features to extract data"); + ERR_FAIL_COND_EDMSG(skeleton_profile == nullptr, "Skeleton_profile is empty"); + ERR_FAIL_COND_EDMSG(skeleton_profile->get_root_bone().is_empty(), "SkeletonProfile requires a Root Bone"); + u::prints("Preparing Features..."); + int tmp_nb_dim = 0; + for (auto i = 0; i < motion_features.size(); ++i) { + MotionFeature *f = Object::cast_to(motion_features[i]); + ERR_FAIL_NULL_MSG(f, "Features no." + u::str(i) + "is null"); + u::prints("Feature no.", i, f->get_name(), "Dimensions:", (int)f->call("get_dimension")); + int feature_dim = 0; + if (!GDVIRTUAL_CALL_PTR(f, get_dimension, feature_dim)) { + feature_dim = f->call("get_dimension"); + } + tmp_nb_dim += feature_dim; + } + nb_dimensions = tmp_nb_dim; + u::prints("Total Dimension", nb_dimensions); + + godot::TypedArray anim_names = get_animation_list(); + u::prints("Detecting", anim_names.size(), "animations. Preparing..."); + + PackedFloat32Array data = PackedFloat32Array(); + + db_biases.clear(); + db_anim_category.clear(); + db_anim_index.clear(); + db_anim_timestamp.clear(); + Rng_Start.resize(anim_names.size()); + Rng_Start.fill(-1); + Rng_Stop.resize(anim_names.size()); + Rng_Stop.fill(-1); + + using namespace boost::accumulators; + using acc_stats = stats; + const accumulator_set default_acc{}; + std::vector> data_stats(nb_dimensions, default_acc); + + u::prints("Starting animation baking..."); + + size_t range_counter = 0; + for (auto anim_index = 0; anim_index < anim_names.size(); ++anim_index) { + auto clock_start = std::chrono::system_clock::now(); + + auto anim_name = anim_names[anim_index]; + auto animation = get_animation(anim_name); + + Rng_Start[anim_index] = range_counter; + Rng_Stop[anim_index] = range_counter; + + auto current_tags = std::vector{}; + for (size_t i = 0; i < tags.size(); i++) { + TagInfo *tag = Object::cast_to(tags[i]); + if (tag != nullptr && tag->animation_name == (StringName)anim_name) { + current_tags.push_back(tag); + } + } + + u::prints("Animations setup for", anim_name, "duration", animation->get_length(), "found", (int)current_tags.size(), "tags for this animation"); + + const int _limit = animation->get_length() / time_interval; + IndexSet timed(0, _limit); + for (auto t = 0; t < current_tags.size(); ++t) { + if (TagJunk *junk = Object::cast_to(current_tags[t]); junk) { + auto _start = godot::CLAMP(int(junk->timestamp / time_interval), 0, _limit); + auto _end = godot::CLAMP(int((junk->timestamp + junk->duration) / time_interval), 0, _limit); + timed -= IndexRange(_start, _end); + } + } + + nb_poses = 0; + for (IndexRange interval : timed) { + for (size_t time_index = interval.front(); time_index <= interval.back(); ++time_index) { + auto time = time_index * time_interval; + + // If category, OR it + int64_t tmp_category_value = 0; + std::for_each(current_tags.begin(), current_tags.end(), + [&tmp_category_value, time](TagInfo *tag) { + TagCategory *category = Object::cast_to(tag); + if (category != nullptr && category->timestamp <= time && time <= category->timestamp + category->duration) { + tmp_category_value |= category->category; + } + }); + + PackedFloat32Array pose_data{}; + for (size_t features_index = 0; features_index < motion_features.size(); ++features_index) { + MotionFeature *f = Object::cast_to(motion_features[features_index]); + int const expected_dimension = (int)f->call("get_dimension"); + PackedFloat32Array feature_data{}; + if (!GDVIRTUAL_CALL_PTR(f, bake_pose, this, anim_name, time, feature_data)) { + feature_data = f->call("bake_pose", this, anim_name, time); + } + // f->GDVIRTUAL_CALL(bake_pose,this, anim_name, time, feature_data); + ERR_FAIL_COND_MSG(feature_data.size() != expected_dimension, String("Features no.") + u::str(int(features_index)) + " bake_pose didn't return a array of the correct size:" + u::str(feature_data.size()) + '/' + u::str(expected_dimension)); + pose_data.append_array(feature_data); + } + // Discover Biases + float _biases = 0.0f; + Ref default_curve = Ref{}; + default_curve.instantiate(); + Ref anim_bias = curvecost.get_or_add(anim_name, default_curve); + float curvecost_time = time; + // TODO : This calculation will be change when Curve have modifiable domain instead of just 0 to 1. + // A PR is ready, but isn't merge yet. + _biases = anim_bias->sample_baked(curvecost_time / animation->get_length()); + db_biases.append(_biases); + + for (int i = 0; i < nb_dimensions; ++i) { + data_stats[i](pose_data[i]); + } + data.append_array(pose_data); + db_anim_index.append(anim_index); + db_anim_timestamp.append(time); + db_anim_category.append(tmp_category_value); + + ++nb_poses; + Rng_Stop[anim_index] = range_counter; + ++range_counter; + } + } + auto clock_end = std::chrono::system_clock::now(); + float duration = float(std::chrono::duration_cast(clock_end - clock_start).count()); + u::prints("Collecting animation data from ", animation->get_name(), " in ", duration, "ms. PoseCount", nb_poses); + } + + u::prints("Animation Data Collected. Normalizing... "); + + dimension_means.clear(); + dimension_stddev.clear(); + dimension_means.resize(nb_dimensions); + dimension_stddev.resize(nb_dimensions); + dimension_means.fill(0.0f); + dimension_stddev.fill(1.0f); + + // Normalization + // There is some amount of logic here that must be taking care when baking and querying. + // A feature could expect to use the raw values instead of normalizing. + // I expect this part to be changed feature type get added. + for (size_t features_index = 0, offset = 0; features_index < motion_features.size(); ++features_index) { + MotionFeature *f = Object::cast_to(motion_features[features_index]); + int feature_dimension = (int)f->call("get_dimension"); + if (MotionFeature::NormalizationType::Standardized == f->get_normalization_type()) { + for (auto i = 0; i < feature_dimension; ++i) { + dimension_means[offset + i] = mean(data_stats[offset + i]); + dimension_stddev[offset + i] = ::sqrtf(variance(data_stats[offset + i])) < std::numeric_limits::epsilon() ? 1.0f : ::sqrtf(variance(data_stats[offset + i])); + } + } else if (MotionFeature::NormalizationType::RawValue == f->get_normalization_type()) { + for (auto i = 0; i < feature_dimension; ++i) { + dimension_means[offset + i] = 0.0f; + dimension_stddev[offset + i] = 1.0f; + } + } + offset += feature_dimension; + } + // Apply normalization to data. When using RawValue, means and stddev are 0 and 1 respectively. + for (size_t pose = 0; pose < nb_poses; ++pose) { + for (int offset = 0; offset < nb_dimensions; ++offset) { + data[pose * nb_dimensions + offset] = (data[pose * nb_dimensions + offset] - dimension_means[offset]) / dimension_stddev[offset]; + } + } + + u::prints("Data Normalized. Copy data to Motion Data property..."); + u::prints("Dimension Means", dimension_means); + u::prints("Dimension Standard Deviations", dimension_stddev); + MotionData = data.duplicate(); + + if (weights.size() != nb_dimensions) { + WARN_PRINT_ED("Weights resized to " + u::str(nb_dimensions) + " and reset to ones."); + weights.resize(nb_dimensions); + } + + u::prints("Creating bounds"); + build_bounds(); + + u::prints("Finished All Animations"); + u::prints("NbDim", nb_dimensions, "NbPoses:", data.size() / nb_dimensions, "Size", data.size()); + WARN_PRINT_ED("MMAnimationLibrary " + get_name() + " bake operation is finished"); + } + + // Calculate the weights using the features get_weights() functions. + // Take into consideration the number of dimensions. + // The calculation might be reconsidered, but it's the best I found. + void recalculate_weights() { + PackedFloat32Array all_weight{}; + + for (auto features_index = 0; features_index < motion_features.size(); ++features_index) { + PackedFloat32Array feature_weight{}; + MotionFeature *f = Object::cast_to(motion_features[features_index]); + ERR_FAIL_COND_EDMSG(!f->has_method("get_weights"), "Feature # " + u::str(features_index) + " doesn't have a get_weights method"); + if(!GDVIRTUAL_CALL_PTR(f, get_weights, feature_weight)) + feature_weight = f->call("get_weights"); + all_weight.append_array(feature_weight); + } + weights.clear(); + weights = MMUtil::softmax(all_weight); + + u::prints("New Weights Values:", weights); + } + + Array get_stats() { + if (MotionData.size() < get_nb_dimensions()) + return {}; + + Array result{}; + { + using namespace boost::accumulators; + using acc_stats = stats; + const accumulator_set default_acc(tag::density::num_bins = 11, tag::density::cache_size = 15); + std::vector> data_stats(nb_dimensions, default_acc); + + for (size_t p = 0; p < get_nb_poses(); ++p) { + for (size_t d = 0; d < get_nb_dimensions(); ++d) { + const auto data = MotionData[p * get_nb_dimensions() + d] * dimension_stddev[d] + dimension_means[d]; + data_stats[d](data); + } + } + + for (auto &&s : data_stats) { + Dictionary dimension_stats{}; + + dimension_stats["maximum"] = max(s); + dimension_stats["minimum"] = min(s); + dimension_stats["skewness"] = skewness(s); + dimension_stats["median"] = median(s); + dimension_stats["variance"] = variance(s); + auto hist = density(s); + PackedFloat32Array lower_bounds, values{}; + for (auto &&h : hist) { + lower_bounds.append(h.first); + values.append(h.second); + } + + dimension_stats["density_hist_bounds"] = lower_bounds; + dimension_stats["density_hist_values"] = values; + result.append(dimension_stats); + } + } + return result; + } + + void build_bounds() { + // Compute array size + const size_t nframe = MotionData.size() / nb_dimensions; + const size_t nbound_sm = ((nframe + BOUND_SM_SIZE - 1) / BOUND_SM_SIZE); + const size_t nbound_lr = ((nframe + BOUND_LR_SIZE - 1) / BOUND_LR_SIZE); + SM_MAX.resize(nbound_sm * nb_dimensions); + SM_MAX.fill(std::numeric_limits::max()); + SM_MIN.resize(nbound_sm * nb_dimensions); + SM_MIN.fill(std::numeric_limits::min()); + LR_MAX.resize(nbound_lr * nb_dimensions); + LR_MAX.fill(std::numeric_limits::max()); + LR_MIN.resize(nbound_lr * nb_dimensions); + LR_MIN.fill(std::numeric_limits::min()); + + BIAS_SM_MAX.resize(nbound_sm); + BIAS_SM_MAX.fill(std::numeric_limits::max()); + BIAS_SM_MIN.resize(nbound_sm); + BIAS_SM_MIN.fill(std::numeric_limits::min()); + BIAS_LR_MAX.resize(nbound_lr); + BIAS_LR_MAX.fill(std::numeric_limits::max()); + BIAS_LR_MIN.resize(nbound_lr); + BIAS_LR_MIN.fill(std::numeric_limits::min()); + + for (size_t i = 0; i < nframe; ++i) { + int i_sm = i / BOUND_SM_SIZE; + int i_lr = i / BOUND_LR_SIZE; + for (size_t j = 0; j < nb_dimensions; ++j) { + const size_t small_index = i_sm * nb_dimensions + j; + const size_t large_index = i_lr * nb_dimensions + j; + const size_t db_index = i * nb_dimensions + j; + SM_MIN[small_index] = fminf(SM_MIN[small_index], MotionData[db_index]); + SM_MAX[small_index] = fmaxf(SM_MAX[small_index], MotionData[db_index]); + LR_MIN[large_index] = fminf(LR_MIN[large_index], MotionData[db_index]); + LR_MAX[large_index] = fmaxf(LR_MAX[large_index], MotionData[db_index]); + } + BIAS_SM_MIN[i_sm] = fminf(BIAS_SM_MIN[i_sm], db_biases[i]); + BIAS_SM_MAX[i_sm] = fmaxf(BIAS_SM_MAX[i_sm], db_biases[i]); + BIAS_LR_MIN[i_lr] = fminf(BIAS_LR_MIN[i_lr], db_biases[i]); + BIAS_LR_MAX[i_lr] = fmaxf(BIAS_LR_MAX[i_lr], db_biases[i]); + } + } + + static inline float squaref(float x) { + return x * x; + } + static inline float clampf(float x, float min, float max) { + return x > max ? max : x < min ? min + : x; + } + + Ref get_indicies_of_animations() { + Ref out{}; + out.instantiate(); + + for (size_t i = 0; i < Rng_Start.size(); ++i) { + out->AddRange(Rng_Start[i], Rng_Stop[i]); + } + + return out; + } + + Ref get_indicies_of_category(int _mask) { + Ref out{}; + out.instantiate(); + + const std::bitset<32> mask = _mask; + + bool out_active = false; + int out_i = 0; + int start = 0; + + for (auto i : + std::ranges::iota_view{ 1, db_anim_category.size() }) { + std::bitset<32> bit = db_anim_category[i]; + // Activate output + if (!out_active && (bit & mask) == mask) { + start = i; + out_active = true; + } + // Deactivate output + else if (out_active && (bit & mask) != mask) { + out->AddRange(start, i); + out_active = false; + out_i++; + } + } + if (out_active) { + out->AddRange(start, db_anim_category.size()); + out_i++; + } + return out; + } + + TypedArray query_pose_noacceleration(const Ref option) { + auto query = option->query; + const auto ranges_search = option->custom_ranges; + const auto custom_weights = option->custom_weights; + const auto best_index = option->continuation_index; + const auto ignore_surrounding_frames = option->ignore_surrounding_frames; + + const real_t transition_cost = option->continuation_index >= 0 ? option->continuation_bias : default_continuation_bias; + const size_t nfeatures = nb_dimensions; + const size_t nranges = ranges_search == nullptr ? Rng_Start.size() : ranges_search->ranges.size(); + + using best_pose_type = std::pair; // First is cost, second is index + using namespace boost::accumulators; + using acc_stats = stats>; + accumulator_set accu_best_poses(tag::tail::cache_size = option->result_count); + + // Normalize query + for (size_t i = 0; i < dimension_means.size(); ++i) { + query[i] = (query[i] - dimension_means[i]) / dimension_stddev[i]; + } + + auto get_weights = [&](size_t i) { return custom_weights.size() > 0 ? custom_weights[i] : weights[i]; }; + auto query_normalized = [&](size_t i) { return query[i]; }; + auto features = [&](size_t i, size_t j) { return MotionData[i * nb_dimensions + j]; }; + auto range_starts = [&](size_t i) -> int { if (ranges_search == nullptr) return Rng_Start[i]; else return cast_to(ranges_search->ranges[i])->from; }; + auto range_stops = [&](size_t i) -> int { if (ranges_search == nullptr) return Rng_Stop[i]; else return cast_to(ranges_search->ranges[i])->to; }; + auto bound_lr_min = [&](size_t i, size_t j) { return LR_MIN[i * nb_dimensions + j]; }; + auto bound_lr_max = [&](size_t i, size_t j) { return LR_MAX[i * nb_dimensions + j]; }; + auto bound_sm_min = [&](size_t i, size_t j) { return SM_MIN[i * nb_dimensions + j]; }; + auto bound_sm_max = [&](size_t i, size_t j) { return SM_MAX[i * nb_dimensions + j]; }; + + for (int r = 0; r < nranges; ++r) { + int range_begin = range_starts(r); + int range_end = range_stops(r); + + // Check every frame. Index is inclusing so <= isn't an error + for (int curr_index = range_begin; curr_index <= range_end; ++curr_index) { + if (best_index >= 0 && abs(curr_index - best_index) <= ignore_surrounding_frames) { + continue; + } + // Check against each frame + auto curr_cost = transition_cost + db_biases[curr_index]; + for (int j = 0; j < nfeatures; j++) { + curr_cost += get_weights(j) * squaref(query_normalized(j) - features(curr_index, j)); + } + + accu_best_poses(best_pose_type{ curr_cost, curr_index }); + } + } + TypedArray result{}; + + for (best_pose_type best_pose : tail(accu_best_poses)) { + Dictionary data{}; + const real_t best_cost = best_pose.first; + const int best_index = best_pose.second; + const StringName anim_name = get_animation_list()[db_anim_index[best_index]]; + const float anim_time = db_anim_timestamp[best_index]; + data["index"] = best_index; + data["cost"] = best_cost; + data["animation"] = anim_name; + data["timestamp"] = std::move(anim_time); + data["category"] = db_anim_category[best_index]; + + result.append(data); + } + + return result; + } + + TypedArray query(Ref options) { + if (options->use_acceleration) { + return query_pose_aabb(options); + } + + return query_pose_noacceleration(options); + } + + // Document how the result isn't really the best N pose, only the very best poses is accuratly the best. + TypedArray query_pose_aabb(const Ref option) { + auto query = option->query; + const auto ranges_search = option->custom_ranges; + const auto custom_weights = option->custom_weights; + auto best_index = option->continuation_index; + const auto ignore_surrounding_frames = option->ignore_surrounding_frames; + + const real_t transition_cost = option->continuation_index >= 0 ? option->continuation_bias : default_continuation_bias; + const int nfeatures = nb_dimensions; + const int nranges = ranges_search == nullptr ? Rng_Start.size() : ranges_search->ranges.size(); + real_t best_cost = std::numeric_limits::max(); + int curr_index = best_index; + + for (int i = 0; i < dimension_means.size(); ++i) { + query[i] = (query[i] - dimension_means[i]) / dimension_stddev[i]; + } + + auto get_weights = [&](size_t i) { return custom_weights.size() > 0 ? custom_weights[i] : weights[i]; }; + auto query_normalized = [&](size_t i) { return query[i]; }; + auto features = [&](size_t i, size_t j) { return MotionData[i * nb_dimensions + j]; }; + auto range_starts = [&](size_t i) -> int { if (ranges_search == nullptr) return Rng_Start[i]; else return cast_to(ranges_search->ranges[i])->from; }; + auto range_stops = [&](size_t i) -> int { if (ranges_search == nullptr) return Rng_Stop[i]; else return cast_to(ranges_search->ranges[i])->to; }; + auto bound_lr_min = [&](size_t i, size_t j) { return LR_MIN[i * nb_dimensions + j]; }; + auto bound_lr_max = [&](size_t i, size_t j) { return LR_MAX[i * nb_dimensions + j]; }; + auto bound_sm_min = [&](size_t i, size_t j) { return SM_MIN[i * nb_dimensions + j]; }; + auto bound_sm_max = [&](size_t i, size_t j) { return SM_MAX[i * nb_dimensions + j]; }; + + using best_pose_type = std::pair; // First is cost, second is index + using namespace boost::accumulators; + using acc_stats = stats>; + accumulator_set accu_best_poses(tag::tail::cache_size = option->result_count); + + if (best_index >= 0) { + best_cost = 0.0 + db_biases[best_index]; + for (int f = 0; f < nfeatures; ++f) { + // Important to not add transition_cost + best_cost += get_weights(f) * squaref(query_normalized(f) - features(best_index, f)); + } + accu_best_poses(best_pose_type{ best_cost, best_index }); + } + + float curr_cost = 0.0f; + + // Search rest of database + for (int r = 0; r < nranges; r++) { + // Exclude end of ranges from search + int i = range_starts(r); + int range_end = range_stops(r); + + while (i < range_end) { + // Find index of current and next large box + int i_lr = i / BOUND_LR_SIZE; + int i_lr_next = (i_lr + 1) * BOUND_LR_SIZE; + + // Find distance to box + curr_cost = transition_cost + clampf(0.0, BIAS_LR_MIN[i_lr], BIAS_LR_MAX[i_lr]); + for (int j = 0; j < nfeatures; j++) { + curr_cost += get_weights(j) * squaref(query_normalized(j) - clampf(query_normalized(j), bound_lr_min(i_lr, j), bound_lr_max(i_lr, j))); + + if (curr_cost >= best_cost) { + break; + } + } + + // If distance is greater than current best jump to next box + if (curr_cost >= best_cost) { + i = i_lr_next; + continue; + } + + // Check against small box + while (i < i_lr_next && i < range_end) { + // Find index of current and next small box + int i_sm = i / BOUND_SM_SIZE; + int i_sm_next = (i_sm + 1) * BOUND_SM_SIZE; + + // Find distance to box + curr_cost = transition_cost + clampf(0.0, BIAS_SM_MIN[i_sm], BIAS_SM_MAX[i_sm]); + for (int j = 0; j < nfeatures; j++) { + curr_cost += get_weights(j) * squaref(query_normalized(j) - clampf(query_normalized(j), bound_sm_min(i_sm, j), bound_sm_max(i_sm, j))); + + if (curr_cost >= best_cost) { + break; + } + } + + // If distance is greater than current best jump to next box + if (curr_cost >= best_cost) { + i = i_sm_next; + continue; + } + + // Search inside small box + while (i < i_sm_next && i < range_end) { + // Skip surrounding frames + if (curr_index >= 0 && abs(i - curr_index) <= ignore_surrounding_frames) { + i++; + continue; + } + + // Check against each frame inside small box + curr_cost = transition_cost + db_biases[i]; + for (int j = 0; j < nfeatures; j++) { + curr_cost += get_weights(j) * squaref(query_normalized(j) - features(i, j)); + if (curr_cost >= best_cost) { + break; + } + } + + // If cost is lower than current best then update best + if (curr_cost < best_cost) { + best_index = i; + best_cost = curr_cost; + accu_best_poses(best_pose_type{ best_cost, best_index }); + } + + i++; + } + } + } + } + TypedArray result{}; + for (best_pose_type best_pose : tail(accu_best_poses)) { + Dictionary data{}; + const real_t best_cost = best_pose.first; + const int best_index = best_pose.second; + const StringName anim_name = get_animation_list()[db_anim_index[best_index]]; + const float anim_time = db_anim_timestamp[best_index]; + data["index"] = best_index; + data["cost"] = best_cost; + data["animation"] = anim_name; + data["timestamp"] = std::move(anim_time); + data["category"] = db_anim_category[best_index]; + + result.append(data); + } + return result; + } + + Dictionary sample_bone_global_info(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + return (Dictionary)get_global_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + } + + Ref sample_bone_global_kform(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + Ref result = Ref{}; + result.instantiate(); + result->k = get_global_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + return result; + } + + Dictionary sample_bone_model_info(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + return (Dictionary)get_model_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + } + + Ref sample_bone_model_kform(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + Ref result = new Kform{}; + result.instantiate(); + result->k = get_model_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + return result; + } + + Dictionary sample_bone_rootmotion_info(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + return (Dictionary)get_root_model_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + } + + Ref sample_bone_rootmotion_kform(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + Ref result = new Kform{}; + result.instantiate(); + result->k = get_root_model_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + return result; + } + + Dictionary sample_bone_local_info(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + return (Dictionary)get_local_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + } + + Ref sample_bone_local_kform(StringName animation_name, double time, NodePath bone_path) { + ERR_FAIL_COND_V(skeleton_profile == nullptr, {}); + ERR_FAIL_COND_V(!has_animation(animation_name), {}); + Ref result = new Kform{}; + result.instantiate(); + result->k = get_local_kform(skeleton_profile, get_animation(animation_name), time, bone_path); + return result; + } + + void prints_dimensions() { + for (auto features_index = 0; features_index < motion_features.size(); ++features_index) { + MotionFeature *f = Object::cast_to(motion_features[features_index]); + + u::prints("Features #", features_index, "nb dimension", f->call("get_dimension")); + u::prints("Features #", features_index, "hints", f->call("get_hints")); + // u::prints("Features #", features_index, "setup_bake_init", f->call("setup_bake_init", this)); + // u::prints("Features #", features_index, "setup_bake_animation", f->call("setup_bake_animation", nullptr)); + // u::prints("Features #", features_index, "bake", (PackedFloat32Array)f->call("bake_animation_pose", nullptr, 0.016)); + } + } + + // All the GETSET + GETSET(StringName, skeleton_path); + GETSET(Ref, skeleton_profile) + GETSET(float, time_interval, 0.1); + + GETSET(float, default_continuation_bias, 0.0); + + String category_hint_string{}; + String get_category_hint_string() { return category_hint_string; } + void set_category_hint_string(String value) { + category_hint_string = value; + int nb = 0; + for (int i = 0; i < tags.size(); ++i) { + TagCategory *category = Object::cast_to(tags[i]); + if (category != nullptr) { + category->property_hint_string = category_hint_string; + ++nb; + } + } + emit_changed(); + } + GETSET(TypedArray, tags); + GETSET(Dictionary, curvecost); // map -> map + + // Array of the motion features. + GETSET(TypedArray, motion_features); + // The data + GETSET(PackedFloat32Array, MotionData); + + // Dimensional Stats. + GETSET(int, nb_dimensions) + GETSET(int, nb_poses) + GETSET(PackedFloat32Array, weights) + GETSET(PackedFloat32Array, db_biases) + + GETSET(PackedFloat32Array, dimension_means); + GETSET(PackedFloat32Array, dimension_stddev); + + // Database. + // Usage : db_anim_*[result.index] = + GETSET(PackedInt32Array, db_anim_index); // Index of the animation name in the animation library + GETSET(PackedFloat32Array, db_anim_timestamp); // timestamp of the pose in the animation + GETSET(PackedInt32Array, db_anim_category); // Category of the pose in the animation + + /// AABB + GETSET(PackedInt32Array, Rng_Start); + GETSET(PackedInt32Array, Rng_Stop); + GETSET(PackedFloat32Array, SM_MIN); + GETSET(PackedFloat32Array, SM_MAX); + GETSET(PackedFloat32Array, LR_MIN); + GETSET(PackedFloat32Array, LR_MAX); + GETSET(int, BOUND_SM_SIZE, 16); + GETSET(int, BOUND_LR_SIZE, 64); + + GETSET(PackedFloat32Array, BIAS_SM_MIN); + GETSET(PackedFloat32Array, BIAS_SM_MAX); + GETSET(PackedFloat32Array, BIAS_LR_MIN); + GETSET(PackedFloat32Array, BIAS_LR_MAX); protected: - static void _bind_methods() - { - // Enum - { - BIND_ENUM_CONSTANT(Local); - BIND_ENUM_CONSTANT(Model); - BIND_ENUM_CONSTANT(RootMotion); - BIND_ENUM_CONSTANT(Global); - } - // Functions - { - ClassDB::bind_method(D_METHOD("sample_bone_local_info", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_local_info); - ClassDB::bind_method(D_METHOD("sample_bone_model_info", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_model_info); - ClassDB::bind_method(D_METHOD("sample_bone_rootmotion_info", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_rootmotion_info); - ClassDB::bind_method(D_METHOD("sample_bone_global_info", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_global_info); - - ClassDB::bind_method(D_METHOD("bake_data"), &MMAnimationLibrary::bake_data); - ClassDB::bind_method(D_METHOD("recalculate_weights"), &MMAnimationLibrary::recalculate_weights); - ClassDB::bind_method(D_METHOD("check_query_results", "Query", "Result count"), &MMAnimationLibrary::check_query_results); - ClassDB::bind_method(D_METHOD("query_pose", "serialized_query", "include_category", "exclude_category"), &MMAnimationLibrary::query_pose, DEFVAL(std::numeric_limits::max()), DEFVAL(0)); - } - // Internal properties - { - ClassDB::bind_method(D_METHOD("set_means", "value"), &MMAnimationLibrary::set_means); - ClassDB::bind_method(D_METHOD("get_means"), &MMAnimationLibrary::get_means); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "means", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY), "set_means", "get_means"); - ClassDB::bind_method(D_METHOD("set_variances", "value"), &MMAnimationLibrary::set_variances); - ClassDB::bind_method(D_METHOD("get_variances"), &MMAnimationLibrary::get_variances); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "variances", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY), "set_variances", "get_variances"); - ClassDB::bind_method(D_METHOD("set_densities", "value"), &MMAnimationLibrary::set_densities); - ClassDB::bind_method(D_METHOD("get_densities"), &MMAnimationLibrary::get_densities); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::ARRAY, "densities", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY), "set_densities", "get_densities"); - - - ClassDB::bind_method(D_METHOD("set_nb_dimensions", "value"), &MMAnimationLibrary::set_nb_dimensions); - ClassDB::bind_method(D_METHOD("get_nb_dimensions"), &MMAnimationLibrary::get_nb_dimensions); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "nb_dimensions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_nb_dimensions", "get_nb_dimensions"); - ClassDB::bind_method(D_METHOD("set_db_anim_index", "value"), &MMAnimationLibrary::set_db_anim_index); - ClassDB::bind_method(D_METHOD("get_db_anim_index"), &MMAnimationLibrary::get_db_anim_index); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_INT32_ARRAY, "db_anim_index", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE), "set_db_anim_index", "get_db_anim_index"); - ClassDB::bind_method(D_METHOD("set_db_anim_timestamp", "value"), &MMAnimationLibrary::set_db_anim_timestamp); - ClassDB::bind_method(D_METHOD("get_db_anim_timestamp"), &MMAnimationLibrary::get_db_anim_timestamp); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "db_anim_timestamp", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE), "set_db_anim_timestamp", "get_db_anim_timestamp"); - ClassDB::bind_method(D_METHOD("set_db_anim_category", "value"), &MMAnimationLibrary::set_db_anim_category); - ClassDB::bind_method(D_METHOD("get_db_anim_category"), &MMAnimationLibrary::get_db_anim_category); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_INT32_ARRAY, "db_anim_category", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE), "set_db_anim_category", "get_db_anim_category"); - } - ClassDB::add_property_group(get_class_static(), "Dependancy resources", ""); - { - ClassDB::bind_method( D_METHOD("set_time_interval" ,"value"), &MMAnimationLibrary::set_time_interval,DEFVAL(0.1f)); - ClassDB::bind_method( D_METHOD("get_time_interval" ), &MMAnimationLibrary::get_time_interval); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"time_interval",PROPERTY_HINT_RANGE,"0.01,2.0,0.01,or_greater"), "set_time_interval", "get_time_interval"); - - ClassDB::bind_method(D_METHOD("set_skeleton_path", "value"), &MMAnimationLibrary::set_skeleton_path); - ClassDB::bind_method(D_METHOD("get_skeleton_path"), &MMAnimationLibrary::get_skeleton_path); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING_NAME, "skeleton_path"), "set_skeleton_path", "get_skeleton_path"); - - ClassDB::bind_method(D_METHOD("set_skeleton_profile", "value"), &MMAnimationLibrary::set_skeleton_profile); - ClassDB::bind_method(D_METHOD("get_skeleton_profile"), &MMAnimationLibrary::get_skeleton_profile); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT, "skeleton_profile", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonProfile"), "set_skeleton_profile", "get_skeleton_profile"); - } - ClassDB::add_property_group(get_class_static(), "Features", ""); - { - ClassDB::bind_method(D_METHOD("set_category_track_names", "value"), &MMAnimationLibrary::set_category_track_names); - ClassDB::bind_method(D_METHOD("get_category_track_names"), &MMAnimationLibrary::get_category_track_names); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_STRING_ARRAY, "category_track_names", PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_DEFAULT), "set_category_track_names", "get_category_track_names"); - - ClassDB::bind_method(D_METHOD("set_motion_features", "value"), &MMAnimationLibrary::set_motion_features); - ClassDB::bind_method(D_METHOD("get_motion_features"), &MMAnimationLibrary::get_motion_features); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::ARRAY, "motion_features", godot::PROPERTY_HINT_TYPE_STRING, u::str(Variant::OBJECT) + '/' + u::str(Variant::BASIS) + ":MotionFeature", PROPERTY_USAGE_DEFAULT), "set_motion_features", "get_motion_features"); - } - ClassDB::add_property_group(get_class_static(), "Data & KdTree params", ""); - { - ClassDB::bind_method(D_METHOD("set_MotionData", "value"), &MMAnimationLibrary::set_MotionData); - ClassDB::bind_method(D_METHOD("get_MotionData"), &MMAnimationLibrary::get_MotionData); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "MotionData"), "set_MotionData", "get_MotionData"); - ClassDB::bind_method(D_METHOD("set_distance_type", "value"), &MMAnimationLibrary::set_distance_type); - ClassDB::bind_method(D_METHOD("get_distance_type"), &MMAnimationLibrary::get_distance_type); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "distance_type", PROPERTY_HINT_ENUM, "Manhattan:1,EuclidianSquared:2,Maximum:0"), "set_distance_type", "get_distance_type"); - ClassDB::bind_method(D_METHOD("set_weights", "value"), &MMAnimationLibrary::set_weights); - ClassDB::bind_method(D_METHOD("get_weights"), &MMAnimationLibrary::get_weights); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "weights"), "set_weights", "get_weights"); - } - } -public : - static kform sample_bone_rootmotion_kform(Ref animation, double time,Ref skeleton_profile, NodePath bone_path) - { - ERR_FAIL_COND_V(skeleton_profile == nullptr, kform{}); - std::vector trs{}; - String _skel = bone_path.get_concatenated_names(); - String bone = bone_path.get_concatenated_subnames(); - - do - { - trs.emplace_back(kform{skeleton_profile, NodePath(_skel + ":" + bone), animation, time}); - bone = skeleton_profile->get_bone_parent(skeleton_profile->find_bone(bone)); - } while (!bone.is_empty() && bone != skeleton_profile->get_root_bone()); - - const auto root_path = NodePath(_skel + ":" + skeleton_profile->get_root_bone()); - - kform root{skeleton_profile,root_path, animation,time}; - root.vel = root.rot.xform_inv(root.vel); - root.ang = root.rot.xform_inv(root.ang); - root.pos = Vector3(); - root.rot = Quaternion(); - - return std::reduce(trs.rbegin(), trs.rend(), root, - [](const kform &acc, const kform &i) - { - return acc * i; - }); - } + static void _bind_methods() { + // Refactored Default Inspector + ClassDB::add_property_group(get_class_static(), "Core", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::FLOAT, time_interval, PROPERTY_HINT_RANGE, "0.016, 1, 0.016, or_greater"); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::STRING_NAME, skeleton_path); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::OBJECT, skeleton_profile); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, nb_dimensions, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, nb_poses, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY); + } + + ClassDB::add_property_group(get_class_static(), "Features", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::ARRAY, motion_features, godot::PROPERTY_HINT_TYPE_STRING, u::str(Variant::OBJECT) + '/' + u::str(Variant::BASIS) + ":MotionFeature", PROPERTY_USAGE_DEFAULT); + } + + ClassDB::add_property_group(get_class_static(), "Tags", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::STRING, category_hint_string, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::ARRAY, tags, godot::PROPERTY_HINT_TYPE_STRING, u::str(Variant::OBJECT) + '/' + u::str(Variant::BASIS) + ":TagInfo", PROPERTY_USAGE_STORAGE); + } + + ClassDB::add_property_group(get_class_static(), "Query Options", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, weights); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::FLOAT, default_continuation_bias, PROPERTY_HINT_RANGE, "0.01, 1, 0.01, or_greater"); + } + + ClassDB::add_property_group(get_class_static(), "Database", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, MotionData); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, dimension_stddev, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, dimension_means, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY); + + ClassDB::add_property_subgroup(get_class_static(), "Acceleration Options", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, BOUND_LR_SIZE, PROPERTY_HINT_RANGE, "4, 100, 1, or_greater"); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, BOUND_SM_SIZE, PROPERTY_HINT_RANGE, "2, 100, 1, or_greater"); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::DICTIONARY, curvecost, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL); + } + ClassDB::add_property_subgroup(get_class_static(), "", ""); + { + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, db_anim_index, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_anim_timestamp, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, db_anim_category, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_biases, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_READ_ONLY); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, LR_MAX, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, LR_MIN, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, SM_MAX, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, SM_MIN, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, BIAS_LR_MAX, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, BIAS_LR_MIN, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, BIAS_SM_MAX, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, BIAS_SM_MIN, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, Rng_Start, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, Rng_Stop, PROPERTY_HINT_NONE, "", PropertyUsageFlags::PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_READ_ONLY); + } + } + + ClassDB::bind_method(D_METHOD("prints_dimensions"), &MMAnimationLibrary::prints_dimensions); + ClassDB::bind_method(D_METHOD("get_stats"), &MMAnimationLibrary::get_stats); + // Functions + { + ClassDB::bind_method(D_METHOD("sample_bone_local", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_local_kform); + ClassDB::bind_method(D_METHOD("sample_bone_model", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_model_kform); + ClassDB::bind_method(D_METHOD("sample_bone_rootmotion", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_rootmotion_kform); + ClassDB::bind_method(D_METHOD("sample_bone_global", "animation_name", "time", "bone_path"), &MMAnimationLibrary::sample_bone_global_kform); + + ClassDB::bind_method(D_METHOD("get_pose_tags", "animation_name", "time"), &MMAnimationLibrary::get_pose_tags); + + ClassDB::bind_method(D_METHOD("bake_data"), &MMAnimationLibrary::bake_data); + ClassDB::bind_method(D_METHOD("recalculate_weights"), &MMAnimationLibrary::recalculate_weights); + ClassDB::bind_method(D_METHOD("query_pose_aabb", "options"), &MMAnimationLibrary::query_pose_aabb); + ClassDB::bind_method(D_METHOD("query_pose_noacceleration", "options"), &MMAnimationLibrary::query_pose_noacceleration); + + // SetRangeIndex + ClassDB::bind_method(D_METHOD("get_indicies_of_animations"), &MMAnimationLibrary::get_indicies_of_animations); + ClassDB::bind_method(D_METHOD("get_indicies_of_category", "mask"), &MMAnimationLibrary::get_indicies_of_category); + } + // Internal functions + { + ClassDB::bind_method(D_METHOD("get_curvecost_animname", "anim"), &MMAnimationLibrary::get_curvecost_animname); + ClassDB::bind_method(D_METHOD("_event_on_anim_added", "anim"), &MMAnimationLibrary::_event_on_anim_added); + ClassDB::bind_method(D_METHOD("_event_on_anim_removed", "anim"), &MMAnimationLibrary::_event_on_anim_removed); + } + } + +public: }; - -VARIANT_ENUM_CAST(MMAnimationLibrary::Space); diff --git a/src/MMAnimationPlayer.hpp b/src/MMAnimationPlayer.hpp index 7778c802..2db0ab32 100644 --- a/src/MMAnimationPlayer.hpp +++ b/src/MMAnimationPlayer.hpp @@ -6,14 +6,13 @@ #include #include +#include #include #include -#include #include -#include +#include #include - #include #include #include @@ -21,6 +20,7 @@ #include #include +#include #include #include "godot_cpp/core/math.hpp" @@ -28,479 +28,401 @@ #include -#include -#include +#include +#include #include -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - - - - +#include /// @brief This animation node is for Motion Matching. /// It was made to get request for a pose from the list of animations, /// make a transition, then play the animation from then. -/// It can handle transition while -struct MMAnimationPlayer : godot::AnimationPlayer -{ - GDCLASS(MMAnimationPlayer,AnimationPlayer); - using u = godot::UtilityFunctions; - - kforms bones_kform{0}, bones_offset{0}; - - float default_halflife = 0.1f; - GETSET(float,halflife,0.1f); - NodePath skeleton_path{}; - Skeleton3D* _skeleton = nullptr; - - int32_t root_bone_id = -1; - - Ref pending_desired_anim = nullptr; - float pending_desired_time = 0.0f; - - virtual void _ready() override - { - AnimationPlayer::_ready(); - u::prints("MMAnimationPlayer Init",u::str(skeleton_path)); - - if(Engine::get_singleton()->is_editor_hint()) - { - return; - } - default_halflife = halflife; - skeleton_path = NodePath(get_root_motion_track().get_concatenated_names()); - _skeleton = get_node(NodePath(skeleton_path)); - ERR_FAIL_NULL(_skeleton); - - _skeleton->reset_bone_poses(); - inertialize_reset(); - root_bone_id = _skeleton->find_bone(get_root_motion_track().get_concatenated_subnames()); - connect("animation_finished",Callable(this,"_on_anim_finish")); - } - - void inertialize_reset(bool skeleton_to_rest = false) - { - ERR_FAIL_NULL(_skeleton); - if(skeleton_to_rest) - { - _skeleton->reset_bone_poses(); - } - - const auto bone_count = _skeleton->get_bone_count(); - bones_kform.reserve(bone_count); - bones_offset.reserve(bone_count); - for (int b = 0; b < bone_count; ++b) - { - bones_kform.reset(b); - bones_kform.pos[b] = _skeleton->get_bone_pose_position(b); - bones_kform.rot[b] = _skeleton->get_bone_pose_rotation(b); - bones_kform.scl[b] = _skeleton->get_bone_pose_scale(b); - - bones_offset.reset(b); - } - } - - void request_pose(StringName p_animation_name, float p_time = 0.0f,float new_halflife = -1.0f) - { - if ( new_halflife > 0.0f) - { - set_halflife(new_halflife); - } - else { - set_halflife(default_halflife); - } - last_anim = p_animation_name; - last_timestamp = p_time; - stop(); - } - - virtual bool request_animation(StringName p_animation_name, float p_time = 0.0f,float new_halflife = -1.0f, float time_diff = -1.0f) - { - _skeleton = get_node(NodePath(skeleton_path)); - ERR_FAIL_NULL_V(_skeleton,false); - const auto motion_scale = _skeleton->get_motion_scale(); - auto p_animation = get_animation(p_animation_name); - - ERR_FAIL_NULL_V(p_animation,false); - bones_kform.reserve(_skeleton->get_bone_count()); - bones_offset.reserve(_skeleton->get_bone_count()); - // - if(time_diff > 0.0f && p_animation_name == get_current_animation() && abs(p_time - get_current_animation_position()) < time_diff) - { - // We are already playing - return false; - } - if ( new_halflife > 0.0f) - { - set_halflife(new_halflife); - } - else { - set_halflife(default_halflife); - } - last_anim = p_animation_name; - last_timestamp = p_time; - - auto root_id = -1; - - const double delta = 0.016; - - p_time = u::clampf(p_time,0.0,p_animation->get_length()-halflife); - const auto future_time = u::clampf(p_time+delta,0.0,p_animation->get_length()); - for (auto bone_id = 0; bone_id < _skeleton->get_bone_count(); ++bone_id) - { - const Transform3D bone_rest = _skeleton->get_bone_rest(bone_id); - const String bone_path = u::str(skeleton_path) + String(":") + _skeleton->get_bone_name(bone_id); - - auto track_pos = p_animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D); - auto track_rot = p_animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D); - - //POSITION 3D - Vector3 desired_position = bones_kform.pos[bone_id] ,// _skeleton->get_bone_pose_position(bone_id), - desired_linear_vel = bones_kform.vel[bone_id]; // Vector3(); - if (track_pos != -1) - { - desired_position = p_animation->position_track_interpolate(track_pos, p_time)* motion_scale ; - desired_linear_vel = ((p_animation->position_track_interpolate(track_pos, future_time)* motion_scale)- desired_position) / abs(future_time - p_time); - } - - //ROTATION 3D - Quaternion desired_rotation = bones_kform.rot[bone_id] ;//_skeleton->get_bone_pose_rotation(bone_id); - Vector3 desired_angular_vel = bones_kform.ang[bone_id] ;//Vector3{}; - if ( track_rot != -1) - { - desired_rotation = p_animation->rotation_track_interpolate(track_rot, p_time).normalized(); - Quaternion r1 = p_animation->rotation_track_interpolate(track_rot, future_time).normalized(); - desired_angular_vel = Spring::quat_differentiate_angular_velocity(r1,desired_rotation,abs(future_time - p_time)).normalized(); - } - - - // ROOT BONE - // Root bone have a special process - if (NodePath(bone_path) == get_root_motion_track()) - { - auto rooting = desired_rotation.inverse(); - desired_position = Vector3(); - desired_linear_vel = rooting.xform(desired_linear_vel); - desired_rotation = Quaternion(); - //desired_angular_vel doesn't change - } - - // Offset are calculated Between current pos of the bone and the desired pose - Spring::inertialize_transition(bones_offset.pos[bone_id], bones_offset.vel[bone_id], - bones_kform.pos[bone_id], bones_kform.vel[bone_id], - desired_position, desired_linear_vel); - Spring::inertialize_transition(bones_offset.rot[bone_id], bones_offset.ang[bone_id], // Offset are calculated... - bones_kform.rot[bone_id], bones_kform.ang[bone_id], // Between current rot of the bone... - desired_rotation, desired_angular_vel); // and the desired pose - } - - - play(p_animation_name); - seek(p_time,false); - - return true; - } - - - void _on_anim_finish(StringName p_animation_name) - { - set_halflife(default_halflife); - auto p_animation = get_animation(p_animation_name); - auto p_time = p_animation->get_length(); - - last_anim = p_animation_name; - last_timestamp = p_time; - - return; - } - - StringName last_anim = ""; - double last_timestamp = 0.0; - - virtual void _physics_process(double _delta) override - { - if(Engine::get_singleton()->is_editor_hint() || last_anim.is_empty()) - { - return; - } - - - // Let's hope this is done after AnimationMixer's _notification - Ref animation = get_current_animation().is_empty() ? nullptr : get_animation(get_current_animation()); - - String skel_path = get_root_motion_track().get_concatenated_names(); - const auto motion_scale = _skeleton->get_motion_scale(); - - const auto current_time = animation == nullptr ? 0.0 : u::clampf(get_current_animation_position(), 0.0, animation->get_length()); - const auto future_time = animation == nullptr ? _delta : u::clampf(current_time + _delta, 0.0, animation->get_length()); - const auto delta_diff = abs(future_time-current_time); - - for (auto bone_id = 0; bone_id < _skeleton->get_bone_count(); ++bone_id) - { - const String bone_path = skel_path + String(":") + _skeleton->get_bone_name(bone_id); - - kform desired {}; - - if(get_current_animation().is_empty()) - { - animation = get_animation(last_anim); - - desired = bones_kform[bone_id]; - - const Transform3D bone_rest = _skeleton->get_bone_rest(bone_id).scaled_local(Vector3(1,1,1) * motion_scale); - const String bone_path = u::str(skeleton_path) + String(":") + _skeleton->get_bone_name(bone_id); - - auto track_pos = animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D); - if(track_pos != -1) - { - desired.pos = animation->position_track_interpolate(track_pos, last_timestamp) * motion_scale; - } - auto track_rot = animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D); - if(track_rot != -1) - { - desired.rot = animation->rotation_track_interpolate(track_rot, last_timestamp); - } - - if (bone_id == root_bone_id) - { - desired.pos = Vector3(); - desired.rot = Quaternion(); - } - - Spring::_simple_spring_damper_exact( - bones_kform.pos[bone_id],bones_kform.vel[bone_id] - ,desired.pos,halflife,_delta - ); - Spring::_simple_spring_damper_exact( - bones_kform.rot[bone_id],bones_kform.ang[bone_id] - ,desired.rot,halflife,_delta - ); - Spring::_decay_spring_damper_exact( - bones_offset.pos[bone_id],bones_offset.vel[bone_id], - halflife,_delta - ); - Spring::_decay_spring_damper_exact( - bones_offset.rot[bone_id],bones_offset.ang[bone_id], - halflife,_delta - ); - } - else - { - desired.pos = _skeleton->get_bone_pose_position(bone_id); // Have MotionScale - desired.vel = Vector3(); - desired.rot = _skeleton->get_bone_pose_rotation(bone_id); - desired.ang = Vector3(); - - - const int track_pos = animation->find_track(bone_path,Animation::TrackType::TYPE_POSITION_3D); - const int track_rot = animation->find_track(bone_path,Animation::TrackType::TYPE_ROTATION_3D); - if(track_pos != -1) - { - desired.pos = animation->position_track_interpolate(track_pos, current_time) * motion_scale; - desired.vel = u::is_zero_approx(delta_diff) ? Vector3() : ((animation->position_track_interpolate(track_pos, future_time)* motion_scale) - desired.pos) / delta_diff; - } - if(track_pos != -1) - { - desired.rot = animation->rotation_track_interpolate(track_rot, current_time); - desired.ang = u::is_zero_approx(delta_diff) ? Vector3() : Spring::quat_differentiate_angular_velocity( animation->rotation_track_interpolate(track_rot, future_time), desired.rot,delta_diff); - } - - if (bone_id == root_bone_id) - { - desired.vel = desired.rot.xform_inv(desired.vel); - // desired_angular = desired_rotation.xform_inv(desired_angular); - desired.pos = Vector3(); - desired.rot = Quaternion(); - } - Spring::inertialize_update( - bones_kform.pos[bone_id], bones_kform.vel[bone_id], // Current pos of the bone - bones_offset.pos[bone_id],bones_offset.vel[bone_id], // Current Offset pos, get reduced every frame - desired.pos, desired.vel, // Desired position from the animation - halflife, // Stats on how the offset decay - _delta * get_speed_scale()); // delta time between frames - Spring::inertialize_update( - bones_kform.rot[bone_id],bones_kform.ang[bone_id], // Current rot of the bone - bones_offset.rot[bone_id],bones_offset.ang[bone_id], // Current Offset rot, get reduced every frame - desired.rot, desired.ang, // Desired rotation from the animation - halflife, // Stats on how the offset decay - _delta * get_speed_scale()); // delta time between frames - } - - _skeleton->set_bone_pose_position(bone_id,bones_kform.pos[bone_id]); - _skeleton->set_bone_pose_rotation(bone_id,bones_kform.rot[bone_id]); - - } - } - - Dictionary get_local_bone_info(StringName bone_name) - { - ERR_FAIL_COND_V(_skeleton == nullptr,{}); - auto id = _skeleton->find_bone(bone_name); - ERR_FAIL_COND_V_MSG(id == -1,{},"Bone " +bone_name + " doesn't exist in skeleton"); - const auto kin = bones_kform[id]; - Dictionary result = Dictionary{}; - result["position"] = kin.pos; - result["linear_vel"] = kin.vel; - result["rotation"] = kin.rot; - result["angular_vel"] = kin.ang; - result["scale"] = kin.scl; - result["scalar_vel"] = kin.svl; - return result; - } - - //Assume bone_id is correct - kform get_bone_global_kform(int bone_id) - { - std::vector parents_id{bone_id}; - auto tmp_p = bone_id; - while( _skeleton->get_bone_parent(tmp_p) != -1) - { - auto new_parent = _skeleton->get_bone_parent(tmp_p); - parents_id.push_back(new_parent); - tmp_p = new_parent; - } - const auto motion_scale = _skeleton->get_motion_scale(); - return std::accumulate(parents_id.rbegin(), parents_id.rend(), kform{}, - [this,motion_scale](const kform &acc, int i) - { - auto info = bones_kform[i]; - // info.pos *= motion_scale; - return acc * info; - }); - } - - kform get_bone_model_kform(int bone_id) - { - if (bone_id == root_bone_id) - { - return kform{}; - } - std::vector parents_id{}; - auto tmp_p = bone_id; - - do - { - parents_id.push_back(tmp_p); - tmp_p = _skeleton->get_bone_parent(tmp_p); - } while (tmp_p != -1 && tmp_p != root_bone_id); - - const auto motion_scale = _skeleton->get_motion_scale(); - return std::accumulate(parents_id.rbegin(), parents_id.rend(), kform{}, - [this, motion_scale](const kform &acc, int i) - { - auto info = bones_kform[i]; - // info.pos *= motion_scale; - return acc * info; - }); - } - - kform get_bone_info(StringName bone_name,kform::Space space) - { - ERR_FAIL_COND_V(_skeleton == nullptr, {}); - auto id = _skeleton->find_bone(bone_name); - ERR_FAIL_COND_V_MSG(id == -1,{},"Bone " +bone_name + " doesn't exist in skeleton"); - if (space == kform::Space::Local) - { - return bones_kform[id]; - } - else if(space == kform::Space::Global) - { - return get_bone_global_kform(id); - } - else if(space == kform::Space::RootMotion) - { - kform global = get_bone_global_kform(id); - return bones_kform[root_bone_id] / global; - } - else if(space == kform::Space::Model) - { - return get_bone_model_kform(id); - } - return kform{}; - } - - Dictionary get_global_bone_info(StringName bone_name) - { - ERR_FAIL_COND_V(_skeleton == nullptr, {}); - auto id = _skeleton->find_bone(bone_name); - ERR_FAIL_COND_V_MSG(id == -1,{},"Bone " +bone_name + " doesn't exist in skeleton"); - kform global = get_bone_info(bone_name,kform::Space::Global); - - Dictionary result = Dictionary{}; - result["position"] = global.pos; - result["linear_vel"] = global.vel; - result["rotation"] = global.rot; - result["angular_vel"] = global.ang; - result["scale"] = global.scl; - result["scalar_vel"] = global.svl; - return result; - } - - Dictionary get_model_bone_info(StringName bone_name) - { - ERR_FAIL_COND_V(_skeleton == nullptr, {}); - auto id = _skeleton->find_bone(bone_name); - ERR_FAIL_COND_V_MSG(id == -1,{},"Bone " +bone_name + " doesn't exist in skeleton"); - kform global = get_bone_info(bone_name,kform::Space::Model); - - Dictionary result = Dictionary{}; - result["position"] = global.pos; - result["linear_vel"] = global.vel; - result["rotation"] = global.rot; - result["angular_vel"] = global.ang; - result["scale"] = global.scl; - result["scalar_vel"] = global.svl; - return result; - } - - Vector3 get_root_motion_velocity() - { - if (root_bone_id < 0) - { - return {}; - } - return bones_kform.vel[root_bone_id] * get_speed_scale(); - } - Quaternion get_root_motion_angular(float delta) - { - if (root_bone_id < 0) - { - return {}; - } - return Spring::quat_from_scaled_angle_axis(bones_kform.ang[root_bone_id]*delta*get_playing_speed()); - } - - - protected: - static void _bind_methods() - { - ClassDB::bind_method(D_METHOD("_on_anim_finish","anim"),&MMAnimationPlayer::_on_anim_finish); - - ClassDB::bind_method(D_METHOD("request_animation", "animation", "timestamp", "new_halflife","skip_same_anim_difference"), &MMAnimationPlayer::request_animation, (0.0f),(-1.0f),(-1.0f)); - ClassDB::bind_method(D_METHOD("request_pose", "animation", "timestamp", "new_halflife"), &MMAnimationPlayer::request_pose, (0.0f),(-1.0f)); - - - ClassDB::bind_method(D_METHOD("get_local_bone_info","bone_name"),&MMAnimationPlayer::get_local_bone_info); - ClassDB::bind_method(D_METHOD("get_model_bone_info","bone_name"),&MMAnimationPlayer::get_model_bone_info); - ClassDB::bind_method(D_METHOD("get_raw_bone_info","bone_name"),&MMAnimationPlayer::get_global_bone_info); - - ClassDB::bind_method(D_METHOD("get_inertialized_root_motion_velocity"), &MMAnimationPlayer::get_root_motion_velocity); - ClassDB::bind_method(D_METHOD("get_inertialized_root_motion_angular","delta_time"),&MMAnimationPlayer::get_root_motion_angular); - - ClassDB::bind_method( D_METHOD("set_halflife" ,"value"), &MMAnimationPlayer::set_halflife); - ClassDB::bind_method( D_METHOD("get_halflife" ), &MMAnimationPlayer::get_halflife); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"halflife" - , PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater"), "set_halflife", "get_halflife"); - - } -}; \ No newline at end of file +/// It can handle transition while +struct MMAnimationPlayer : godot::AnimationPlayer { + GDCLASS(MMAnimationPlayer, AnimationPlayer); + +public: + using u = godot::UtilityFunctions; + + enum Motion_Tags { + NoTag = 0, + StartUp, + Active, + Recovery + }; + + GETSET(Motion_Tags, motion_tag); + GETSET(TypedArray, transforms_queue); + + kforms bones_local{ 0 }, bones_offset{ 0 }, bones_root_model{ 0 }; + PackedInt32Array bone_parent{}; + + float default_halflife = 0.1f; + GETSET(float, halflife, 0.1f); + NodePath skeleton_path{}; + Skeleton3D *_skeleton = nullptr; + + int32_t root_bone_id = -1; + + Ref pending_desired_anim = nullptr; + float pending_desired_time = 0.0f; + + virtual void _ready() override { + AnimationPlayer::_ready(); + u::prints("MMAnimationPlayer Init", u::str(skeleton_path)); + + if (Engine::get_singleton()->is_editor_hint()) { + return; + } + + default_halflife = halflife; + skeleton_path = NodePath(get_root_motion_track().get_concatenated_names()); + _skeleton = get_node(NodePath(skeleton_path)); + ERR_FAIL_NULL(_skeleton); + + bone_parent.resize(_skeleton->get_bone_count()); + bone_parent.fill(-1); + + bones_root_model.reserve(_skeleton->get_bone_count()); + bones_local.reserve(_skeleton->get_bone_count()); + + for (auto i = 0; i < _skeleton->get_bone_count(); ++i) { + bone_parent[i] = _skeleton->get_bone_parent(i); + bones_local[i] = _skeleton->get_bone_rest(i); + // bones_local.pos[i] = _skeleton->get_bone_pose_position(i); + // bones_local.rot[i] = _skeleton->get_bone_pose_rotation(i); + // bones_local.scl[i] = _skeleton->get_bone_pose_scale(i); + } + + _skeleton->reset_bone_poses(); + inertialize_reset(true); + root_bone_id = _skeleton->find_bone(get_root_motion_track().get_concatenated_subnames()); + connect("animation_finished", Callable(this, "_on_anim_finish")); + } + + void inertialize_reset(bool skeleton_to_rest = false) { + ERR_FAIL_NULL(_skeleton); + if (skeleton_to_rest) { + _skeleton->reset_bone_poses(); + } + + const auto bone_count = _skeleton->get_bone_count(); + bones_local.reserve(bone_count); + bones_offset.reserve(bone_count); + for (int b = 0; b < bone_count; ++b) { + // bones_local.reset(b); + // bones_local.pos[b] = _skeleton->get_bone_pose_position(b); + // bones_local.rot[b] = _skeleton->get_bone_pose_rotation(b); + // bones_local.scl[b] = _skeleton->get_bone_pose_scale(b); + + bones_offset.reset(b); + } + } + + void request_pose(StringName p_animation_name, float p_time = 0.0f, float new_halflife = -1.0f) { + if (new_halflife > 0.0f) { + set_halflife(new_halflife); + } else { + set_halflife(default_halflife); + } + last_anim = p_animation_name; + last_timestamp = p_time; + stop(); + } + + virtual bool request_animation(StringName p_animation_name, float p_time = 0.0f, float new_halflife = -1.0f) { + _skeleton = get_node(NodePath(skeleton_path)); + ERR_FAIL_NULL_V(_skeleton, false); + const auto motion_scale = _skeleton->get_motion_scale(); + auto p_animation = get_animation(p_animation_name); + + ERR_FAIL_NULL_V(p_animation, false); + bones_local.reserve(_skeleton->get_bone_count()); + bones_offset.reserve(_skeleton->get_bone_count()); + + if (new_halflife > 0.0f) { + set_halflife(new_halflife); + } else { + set_halflife(default_halflife); + } + last_anim = p_animation_name; + last_timestamp = p_time; + + auto root_id = -1; + + const double delta = 0.016; + + p_time = u::clampf(p_time, 0.0, p_animation->get_length() - halflife); + const auto future_time = u::clampf(p_time + delta, 0.0, p_animation->get_length()); + for (auto bone_id = 0; bone_id < _skeleton->get_bone_count(); ++bone_id) { + const Transform3D bone_rest = _skeleton->get_bone_rest(bone_id); + const String bone_path = u::str(skeleton_path) + String(":") + _skeleton->get_bone_name(bone_id); + + auto track_pos = p_animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D); + auto track_rot = p_animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D); + + //POSITION 3D + Vector3 desired_position = bones_local.pos[bone_id], // _skeleton->get_bone_pose_position(bone_id), + desired_linear_vel = bones_local.vel[bone_id]; // Vector3(); + if (track_pos != -1) { + desired_position = p_animation->position_track_interpolate(track_pos, p_time) * motion_scale; + desired_linear_vel = ((p_animation->position_track_interpolate(track_pos, future_time) * motion_scale) - desired_position) / abs(future_time - p_time); + } + + //ROTATION 3D + Quaternion desired_rotation = bones_local.rot[bone_id]; //_skeleton->get_bone_pose_rotation(bone_id); + Vector3 desired_angular_vel = bones_local.ang[bone_id]; //Vector3{}; + if (track_rot != -1) { + desired_rotation = p_animation->rotation_track_interpolate(track_rot, p_time).normalized(); + Quaternion r1 = p_animation->rotation_track_interpolate(track_rot, future_time).normalized(); + desired_angular_vel = Spring::quat_differentiate_angular_velocity(r1, desired_rotation, abs(future_time - p_time)).normalized(); + } + + // ROOT BONE + // Root bone have a special process + if (NodePath(bone_path) == get_root_motion_track()) { + auto rooting = desired_rotation.inverse(); + desired_position = Vector3(); + desired_linear_vel = rooting.xform(desired_linear_vel); + desired_rotation = Quaternion(); + //desired_angular_vel doesn't change + } + + // Offset are calculated Between current pos of the bone and the desired pose + Spring::inertialize_transition(bones_offset.pos[bone_id], bones_offset.vel[bone_id], + bones_local.pos[bone_id], bones_local.vel[bone_id], + desired_position, desired_linear_vel); + Spring::inertialize_transition(bones_offset.rot[bone_id], bones_offset.ang[bone_id], // Offset are calculated... + bones_local.rot[bone_id], bones_local.ang[bone_id], // Between current rot of the bone... + desired_rotation, desired_angular_vel); // and the desired pose + } + + play(p_animation_name); + seek(p_time, false); + + return true; + } + + void _on_anim_finish(StringName p_animation_name) { + set_halflife(default_halflife); + auto p_animation = get_animation(p_animation_name); + auto p_time = p_animation->get_length(); + + last_anim = p_animation_name; + last_timestamp = p_time; + + return; + } + + StringName last_anim = ""; + double last_timestamp = 0.0; + + virtual void _physics_process(double _delta) override { + if (Engine::get_singleton()->is_editor_hint() || last_anim.is_empty()) { + return; + } + + emit_signal("pre_calculation"); + + // Let's hope this is done after AnimationMixer's _notification + Ref animation = get_current_animation().is_empty() ? nullptr : get_animation(get_current_animation()); + + String skel_path = get_root_motion_track().get_concatenated_names(); + const auto motion_scale = _skeleton->get_motion_scale(); + + const auto current_time = animation == nullptr ? 0.0 : u::clampf(get_current_animation_position(), 0.0, animation->get_length()); + const auto future_time = animation == nullptr ? _delta : u::clampf(current_time + _delta, 0.0, animation->get_length()); + const auto delta_diff = abs(future_time - current_time); + + for (auto bone_id = 0; bone_id < _skeleton->get_bone_count(); ++bone_id) { + const String bone_path = skel_path + String(":") + _skeleton->get_bone_name(bone_id); + + kform desired{}; + + if (get_current_animation().is_empty()) { + animation = get_animation(last_anim); + + desired = (kform)bones_local[bone_id]; + + const Transform3D bone_rest = _skeleton->get_bone_rest(bone_id).scaled_local(Vector3(1, 1, 1) * motion_scale); + const String bone_path = u::str(skeleton_path) + String(":") + _skeleton->get_bone_name(bone_id); + + auto track_pos = animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D); + if (track_pos != -1) { + desired.pos = animation->position_track_interpolate(track_pos, last_timestamp) * motion_scale; + } + auto track_rot = animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D); + if (track_rot != -1) { + desired.rot = animation->rotation_track_interpolate(track_rot, last_timestamp); + } + + if (bone_id == root_bone_id) { + desired.pos = Vector3(); + desired.rot = Quaternion(); + } + + Spring::_simple_spring_damper_exact( + bones_local.pos[bone_id], bones_local.vel[bone_id], desired.pos, halflife, _delta * get_speed_scale()); + Spring::_simple_spring_damper_exact( + bones_local.rot[bone_id], bones_local.ang[bone_id], desired.rot, halflife, _delta * get_speed_scale()); + Spring::_decay_spring_damper_exact( + bones_offset.pos[bone_id], bones_offset.vel[bone_id], + halflife, _delta * get_speed_scale()); + Spring::_decay_spring_damper_exact( + bones_offset.rot[bone_id], bones_offset.ang[bone_id], + halflife, _delta * get_speed_scale()); + } else { + desired.pos = _skeleton->get_bone_pose_position(bone_id); // Have MotionScale + desired.vel = Vector3(); + desired.rot = _skeleton->get_bone_pose_rotation(bone_id); + desired.ang = Vector3(); + + const int track_pos = animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D); + const int track_rot = animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D); + if (track_pos != -1) { + desired.pos = animation->position_track_interpolate(track_pos, current_time) * motion_scale; + desired.vel = u::is_zero_approx(delta_diff) ? Vector3() : ((animation->position_track_interpolate(track_pos, future_time) * motion_scale) - desired.pos) / delta_diff; + } + if (track_pos != -1) { + desired.rot = animation->rotation_track_interpolate(track_rot, current_time); + desired.ang = u::is_zero_approx(delta_diff) ? Vector3() : Spring::quat_differentiate_angular_velocity(animation->rotation_track_interpolate(track_rot, future_time), desired.rot, delta_diff); + } + + if (bone_id == root_bone_id) { + desired.vel = desired.rot.xform_inv(desired.vel); + desired.pos = Vector3(); + desired.rot = Quaternion(); + } + Spring::inertialize_update( + bones_local.pos[bone_id], bones_local.vel[bone_id], // Current pos of the bone + bones_offset.pos[bone_id], bones_offset.vel[bone_id], // Current Offset pos, get reduced every frame + desired.pos, desired.vel, // Desired position from the animation + halflife, // Stats on how the offset decay + _delta * get_speed_scale()); // delta time between frames + Spring::inertialize_update( + bones_local.rot[bone_id], bones_local.ang[bone_id], // Current rot of the bone + bones_offset.rot[bone_id], bones_offset.ang[bone_id], // Current Offset rot, get reduced every frame + desired.rot, desired.ang, // Desired rotation from the animation + halflife, // Stats on how the offset decay + _delta * get_speed_scale()); // delta time between frames + } + + // if (bone_id == root_bone_id) { + // _skeleton->set_bone_pose_position(root_bone_id, Vector3{}); + // _skeleton->set_bone_pose_rotation(root_bone_id, Quaternion{}); + // } else { + // _skeleton->set_bone_pose_position(bone_id, bones_local.pos[bone_id]); + // _skeleton->set_bone_pose_rotation(bone_id, bones_local.rot[bone_id]); + // } + } + + for (auto i = 0; i < bone_parent.size(); ++i) { + if (bone_parent[i] == -1) { + bones_root_model.pos[i] = bones_local.pos[i]; + bones_root_model.vel[i] = bones_local.vel[i]; + bones_root_model.rot[i] = bones_local.rot[i]; + bones_root_model.ang[i] = bones_local.ang[i]; + bones_root_model.scl[i] = bones_local.scl[i]; + bones_root_model.svl[i] = bones_local.svl[i]; + } else { + kform parent = (kform)bones_root_model[bone_parent[i]]; + kform result = parent * (kform)bones_local[i]; + bones_root_model.pos[i] = result.pos; + bones_root_model.vel[i] = result.vel; + bones_root_model.rot[i] = result.rot; + bones_root_model.ang[i] = result.ang; + bones_root_model.scl[i] = result.scl; + bones_root_model.svl[i] = result.svl; + } + _skeleton->set_bone_pose_position(i, bones_local[i].pos); + _skeleton->set_bone_pose_rotation(i, bones_local[i].rot); + _skeleton->set_bone_pose_scale(i, bones_local[i].scl); + } + _skeleton->force_update_bone_child_transform(root_bone_id); + emit_signal("post_calculation"); + } + + kform _get_local_kform(StringName bone_name) { + return (kform)bones_local[_skeleton->find_bone(bone_name)]; + } + Dictionary get_local_bone_info(StringName bone_name) { + ERR_FAIL_COND_V(_skeleton == nullptr, {}); + auto id = _skeleton->find_bone(bone_name); + ERR_FAIL_COND_V_MSG(id == -1, {}, "Bone " + bone_name + " doesn't exist in skeleton"); + return (Dictionary)bones_local[id]; + } + + kform _get_model_kform(StringName bone_name) { + return kform(bones_local[root_bone_id]).inverse() * (kform)bones_root_model[_skeleton->find_bone(bone_name)]; + } + Dictionary get_model_bone_info(StringName bone_name) { + ERR_FAIL_COND_V(_skeleton == nullptr, {}); + auto id = _skeleton->find_bone(bone_name); + ERR_FAIL_COND_V_MSG(id == -1, {}, "Bone " + bone_name + " doesn't exist in skeleton"); + kform root = (kform)bones_local[root_bone_id]; + return (Dictionary)(root.inverse() * (kform)bones_root_model[id]); + } + + kform _get_root_model_kform(StringName bone_name) { + return (kform)bones_root_model[_skeleton->find_bone(bone_name)]; + } + Dictionary get_root_model_bone_info(StringName bone_name) { + ERR_FAIL_COND_V(_skeleton == nullptr, {}); + auto id = _skeleton->find_bone(bone_name); + ERR_FAIL_COND_V_MSG(id == -1, {}, "Bone " + bone_name + " doesn't exist in skeleton"); + return (Dictionary)bones_root_model[id]; + } + + kform _get_global_model_kform(StringName bone_name) { + return (kform)bones_root_model[_skeleton->find_bone(bone_name)]; + } + Dictionary get_global_bone_info(StringName bone_name) { + return get_root_model_bone_info(bone_name); + } + + Vector3 get_root_motion_velocity() { + if (root_bone_id < 0) { + return {}; + } + return bones_local.vel[root_bone_id] * get_speed_scale(); + } + Quaternion get_root_motion_angular(float delta) { + if (root_bone_id < 0) { + return {}; + } + return Spring::quat_from_scaled_angle_axis(bones_local.ang[root_bone_id] * delta * get_playing_speed()); + } + +protected: + static void _bind_methods() { + ADD_SIGNAL(MethodInfo("pre_calculation")); + ADD_SIGNAL(MethodInfo("post_calculation")); + BIND_ENUM_CONSTANT(NoTag); + BIND_ENUM_CONSTANT(StartUp); + BIND_ENUM_CONSTANT(Active); + BIND_ENUM_CONSTANT(Recovery); + ClassDB::bind_method(D_METHOD("set_motion_tag", "value"), &MMAnimationPlayer::set_motion_tag, DEFVAL(MMAnimationPlayer::Motion_Tags::NoTag)); + ClassDB::bind_method(D_METHOD("get_motion_tag"), &MMAnimationPlayer::get_motion_tag); + ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_tag", godot::PROPERTY_HINT_ENUM, "NoTag,StartUp,Active,Recovery", godot::PROPERTY_USAGE_DEFAULT | godot::PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_motion_tag", "get_motion_tag"); + + ClassDB::bind_method(D_METHOD("_on_anim_finish", "anim"), &MMAnimationPlayer::_on_anim_finish); + + ClassDB::bind_method(D_METHOD("inertialize_reset", "reset_bones"), &MMAnimationPlayer::inertialize_reset, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("request_animation", "animation", "timestamp", "new_halflife"), &MMAnimationPlayer::request_animation, (0.0f), (-1.0f)); + ClassDB::bind_method(D_METHOD("request_pose", "animation", "timestamp", "new_halflife"), &MMAnimationPlayer::request_pose, (0.0f), (-1.0f)); + + ClassDB::bind_method(D_METHOD("get_local_bone_info", "bone_name"), &MMAnimationPlayer::get_local_bone_info); + ClassDB::bind_method(D_METHOD("get_model_bone_info", "bone_name"), &MMAnimationPlayer::get_model_bone_info); + ClassDB::bind_method(D_METHOD("get_root_model_bone_info", "bone_name"), &MMAnimationPlayer::get_root_model_bone_info); + ClassDB::bind_method(D_METHOD("get_global_bone_info", "bone_name"), &MMAnimationPlayer::get_global_bone_info); + + ClassDB::bind_method(D_METHOD("get_inertialized_root_motion_velocity"), &MMAnimationPlayer::get_root_motion_velocity); + ClassDB::bind_method(D_METHOD("get_inertialized_root_motion_angular", "delta_time"), &MMAnimationPlayer::get_root_motion_angular); + + ClassDB::bind_method(D_METHOD("set_halflife", "value"), &MMAnimationPlayer::set_halflife); + ClassDB::bind_method(D_METHOD("get_halflife"), &MMAnimationPlayer::get_halflife); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "halflife", PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater"), "set_halflife", "get_halflife"); + } +}; + +VARIANT_ENUM_CAST(MMAnimationPlayer::Motion_Tags); \ No newline at end of file diff --git a/src/Math/KForm.hpp b/src/Math/KForm.hpp new file mode 100644 index 00000000..0464c23a --- /dev/null +++ b/src/Math/KForm.hpp @@ -0,0 +1,432 @@ +// Acknowledgement : This file wouldn't be possible without the blog from Daniel Holden, a.k.a TheOrangeDuck +// https://theorangeduck.com/page/propagating-velocities-through-animation-systems +// The code has been adapted to work with Godot's Vector3 and Quaternion. + +#pragma once + +#include +#include + +#include "godot_cpp/core/math.hpp" +#include "godot_cpp/variant/vector3.hpp" +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace godot; + +using u = godot::UtilityFunctions; + +struct kform { + Quaternion rot = Quaternion(); + Vector3 pos = Vector3(); + Vector3 scl = Vector3(1.0, 1.0, 1.0); + Vector3 vel = Vector3(); + Vector3 ang = Vector3(); + Vector3 svl = Vector3(); + + kform() = default; + ~kform() = default; + + kform(Transform3D tr) : + pos{ tr.origin }, + rot{ tr.basis.get_rotation_quaternion() }, + scl{ tr.basis.get_scale() }, + vel{}, + ang{}, + svl{} {} + + kform(Vector3 p, Quaternion r, Vector3 s = Vector3{ 1, 1, 1 }, Vector3 lv = Vector3{}, Vector3 av = Vector3{}, Vector3 sv = Vector3{}) : + pos{ p }, rot{ r }, scl{ s }, vel{ lv }, ang{ av }, svl{ sv } {} + +private: + static Vector3 _log(Vector3 v) { + return Vector3(std::log(v.x), std::log(v.y), std::log(v.z)); + } + +public: + kform remove_velocities() const { + kform result = *this; + result.vel = {}; + result.ang = {}; + result.svl = {}; + return result; + } + + kform finite_difference(const kform input_next, real_t _dt) { + kform out = *this; + out.vel = (input_next.pos - pos) / _dt; + + out.ang = Spring::quat_to_scaled_angle_axis(Spring::quat_abs( + input_next.rot * rot.inverse())) / + _dt; + + out.svl = _log(input_next.scl / scl) / _dt; + return out; + } + + static kform finite_difference(const kform &input_curr, const kform &input_next, real_t _dt) { + kform out = input_curr; + out.vel = (input_next.pos - out.pos) / _dt; + + out.ang = Spring::quat_to_scaled_angle_axis(Spring::quat_abs( + input_next.rot * out.rot.inverse())) / + _dt; + + out.svl = _log(input_next.scl / out.scl) / _dt; + return out; + } + + inline operator Transform3D() const { + return Transform3D(Basis(rot, scl), pos); + } + inline explicit operator Dictionary() const { + Dictionary result{}; + result["position"] = pos; + result["velocity_linear"] = vel; + result["rotation"] = rot; + result["velocity_angular"] = ang; + result["scale"] = scl; + result["velocity_scalar"] = svl; + return result; + } + + friend kform operator*(const kform parent, const kform w) { + kform out; + out.pos = parent.rot.xform(w.pos * parent.scl) + parent.pos; + out.rot = parent.rot * w.rot; + out.scl = w.scl * parent.scl; + out.vel = parent.rot.xform(w.vel * parent.scl) + parent.vel + + parent.ang.cross(parent.rot.xform(w.pos * parent.scl)) + + parent.rot.xform(w.pos * parent.scl * parent.svl); + out.ang = parent.rot.xform(w.ang) + parent.ang; + out.svl = w.svl + parent.svl; + return out; + } + friend kform operator/(const kform v, const kform w) { + kform out; + out.pos = v.rot.xform_inv(w.pos - v.pos); + out.rot = v.rot.inverse() * w.rot; + out.scl = w.scl / v.scl; + out.vel = v.rot.xform_inv(w.vel - v.vel - v.ang.cross(v.rot.xform(out.pos * v.scl))) - + v.rot.xform(out.pos * v.scl * v.svl); + out.ang = v.rot.xform_inv(w.ang - v.ang); + out.svl = w.svl - v.svl; + return out; + } + kform inverse() const { + kform out; + out.pos = rot.xform_inv(-pos); + out.rot = rot.inverse(); + out.scl = Vector3(1.0f, 1.0f, 1.0f) / scl; + out.vel = rot.xform_inv(-vel - ang.cross(rot.xform(out.pos * scl))) - rot.xform(out.pos * scl * svl); + out.ang = rot.xform_inv(-ang); + out.svl = -svl; + return out; + } +}; + +struct kforms { + template + using b_vector = std::vector; + b_vector pos; // Position + b_vector rot; // Rotation + b_vector scl; // Scale + b_vector vel; // Linear Velocity + b_vector ang; // Angular Velocity + b_vector svl; // Scalar Velocity + + kforms(std::size_t N) : + pos(N, Vector3()), rot(N, Quaternion()), scl(N, Vector3(1, 1, 1)), vel(N, Vector3()), ang(N, Vector3()), svl(N, Vector3()) {} + + Transform3D get_transform(std::size_t N) { + return Transform3D(Basis(rot[N], scl[N]), pos[N]); + } + + void reserve(std::size_t N) { + pos.resize(N); + rot.resize(N); + scl.resize(N); + vel.resize(N); + ang.resize(N); + svl.resize(N); + } + + std::size_t count() const noexcept { + return pos.size(); + } + + template + struct kform_ref { + using vec3 = std::conditional_t; + using quat = std::conditional_t; + quat &rot; + vec3 &pos; + vec3 &scl; + vec3 &vel; + vec3 ∠ + vec3 &svl; + kform_ref() = delete; + kform_ref(kform_ref &other) = default; + kform_ref(kform other) : + pos{ other.pos }, rot{ other.rot }, scl{ other.scl }, vel{ other.vel }, ang{ other.ang }, svl{ other.svl } { + } + kform_ref(vec3 p, quat q, vec3 s, vec3 V, vec3 A, vec3 S) : + pos{ p }, rot{ q }, scl{ s }, vel{ V }, ang{ A }, svl{ S } { + } + void operator=(const kform &rhs) { + pos = rhs.pos; + rot = rhs.rot; + scl = rhs.scl; + vel = rhs.vel; + ang = rhs.ang; + svl = rhs.svl; + } + explicit operator kform() const { + return kform{ pos, rot, scl, vel, ang, svl }; + } + operator Dictionary() const { + return (Dictionary)kform{ pos, rot, scl, vel, ang, svl }; + } + operator Transform3D() const { + return Transform3D(Basis(rot, scl), pos); + } + }; + + inline kform_ref operator[](const std::size_t N) noexcept { + return { pos[N], rot[N], scl[N], vel[N], ang[N], svl[N] }; + } + inline const kform_ref operator[](const std::size_t N) const noexcept { + return { pos[N], rot[N], scl[N], vel[N], ang[N], svl[N] }; + } + + void reset(const std::size_t N) { + pos[N] = Vector3(); + rot[N] = Quaternion(); + scl[N] = Vector3(1, 1, 1); + vel[N] = Vector3(); + ang[N] = Vector3(); + svl[N] = Vector3(); + } +}; + +static kform get_local_kform(Ref skel, Ref anim, double time, NodePath bonepath) { + static constexpr double dt = 0.032; + kform out = skel->get_reference_pose(skel->find_bone(bonepath.get_concatenated_subnames())); + auto tpos = anim->find_track(bonepath, Animation::TrackType::TYPE_POSITION_3D); + auto trot = anim->find_track(bonepath, Animation::TrackType::TYPE_ROTATION_3D); + auto tscl = anim->find_track(bonepath, Animation::TrackType::TYPE_SCALE_3D); + kform s1 = out; + if (tpos != -1) { + out.pos = anim->position_track_interpolate(tpos, time); + s1.pos = anim->position_track_interpolate(tpos, time + dt); + } + if (trot != -1) { + out.rot = anim->rotation_track_interpolate(trot, time); + s1.rot = anim->rotation_track_interpolate(trot, time + dt); + } + if (tscl != -1) { + out.scl = anim->scale_track_interpolate(tscl, time); + s1.scl = anim->scale_track_interpolate(tscl, time + dt); + } + out = out.finite_difference(s1, dt); + return out; +} + +static kform get_root_model_kform(Ref skel, Ref anim, double time, NodePath bonepath) { + if (bonepath.is_empty()) + return kform{}; + const StringName _skel_path = bonepath.get_concatenated_names(); + StringName bone = bonepath.get_concatenated_subnames(); + std::vector trs{}; + do { + kform _local = get_local_kform(skel, anim, time, NodePath{ u::str(_skel_path) + u::str(":") + bone }); + kform &back = trs.emplace_back(std::move(_local)); + if (bone == skel->get_root_bone()) { + back.vel = back.rot.xform_inv(back.vel); + back.pos = Vector3{}; + back.rot = Quaternion(); + break; + } + bone = skel->get_bone_parent(skel->find_bone(bone)); // Now bone is its parent + } while (!bone.is_empty()); + + return std::reduce(trs.rbegin(), trs.rend(), kform{}, + [](const kform &acc, const kform &i) { + return acc * i; + }); +} + +static kform get_model_kform(Ref skel, Ref anim, double time, NodePath bonepath) { + if (bonepath.is_empty()) + return kform{}; + const StringName _skel_path = bonepath.get_concatenated_names(); + StringName bone = bonepath.get_concatenated_subnames(); + std::vector trs{}; + do { + kform _local = get_local_kform(skel, anim, time, NodePath{ u::str(_skel_path) + u::str(":") + bone }); + kform &back = trs.emplace_back(std::move(_local)); + if (bone == skel->get_root_bone()) { + back = {}; + break; + } + bone = skel->get_bone_parent(skel->find_bone(bone)); // Now bone is its parent + } while (!bone.is_empty()); + + return std::reduce(trs.rbegin(), trs.rend(), kform{}, + [](const kform &acc, const kform &i) { + return acc * i; + }); +} + +static kform get_global_kform(Ref skel, Ref anim, double time, NodePath bonepath) { + if (bonepath.is_empty()) + return kform{}; + const StringName _skel_path = bonepath.get_concatenated_names(); + StringName bone = bonepath.get_concatenated_subnames(); + std::vector trs{}; + do { + kform _local = get_local_kform(skel, anim, time, NodePath{ u::str(_skel_path) + u::str(":") + bone }); + trs.emplace_back(std::move(_local)); + if (bone == skel->get_root_bone()) { + break; + } + bone = skel->get_bone_parent(skel->find_bone(bone)); // Now bone is its parent + } while (!bone.is_empty()); + + return std::reduce(trs.rbegin(), trs.rend(), kform{}, + [](const kform &acc, const kform &i) { + return acc * i; + }); +} + +struct Kform : public RefCounted { + GDCLASS(Kform, RefCounted); + +private: +public: + kform k{}; +#define VAR(type, variable) \ + type get_##variable() const { return k.variable; } \ + void set_##variable(type value) { \ + k.variable = value; \ + } + VAR(Vector3, pos); + VAR(Quaternion, rot); + VAR(Vector3, scl); + VAR(Vector3, vel); + VAR(Vector3, ang); + VAR(Vector3, svl); +#undef VAR + void inverse() { + k.inverse(); + } + Kform() = default; + + Kform &operator=(const kform &rhs) noexcept { + k = rhs; + return *this; + } + + Ref finite_difference(Ref other, float delta) { + kform _other(other->k); + _other = k.finite_difference(_other, delta); + Ref result = new Kform(); + result.instantiate(); + result->k = _other; + return result; + } + + Ref multiply(Ref other) { + kform _other(other->k); + _other = k * _other; + Ref result{}; + result.instantiate(); + result->k = _other; + return result; + } + Ref divide(Ref other) { + kform _other(other->k); + _other = k / _other; + Ref result{}; + result.instantiate(); + result->k = _other; + return result; + } + + TypedArray character_prediction( + Vector3 linear_acceleration, + Vector3 desired_velocity, + Quaternion desired_rotation, + real_t halflife_velocity, real_t halflife_rotation, + PackedFloat32Array deltas) { + TypedArray result{}; + for (auto dt : deltas) { + kform _k = k; + auto a = linear_acceleration; + Spring::_character_update(_k.pos, _k.vel, a, _k.rot, _k.ang, desired_velocity, desired_rotation, halflife_velocity, halflife_rotation, dt); + Ref r{}; + r.instantiate(); + r->k = k; + result.append(r); + } + return result; + } + + Vector3 character_update( + Vector3 linear_acceleration, + Vector3 desired_velocity, + Quaternion desired_rotation, + real_t halflife_velocity, real_t halflife_rotation, + real_t delta) { + auto a = linear_acceleration; + Spring::_character_update(k.pos, k.vel, a, k.rot, k.ang, desired_velocity, desired_rotation, halflife_velocity, halflife_rotation, delta); + return a; + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_position", "value"), &Kform::set_pos); + ClassDB::bind_method(D_METHOD("get_position"), &Kform::get_pos); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "position"), "set_position", "get_position"); + + ClassDB::bind_method(D_METHOD("set_rotation", "value"), &Kform::set_rot); + ClassDB::bind_method(D_METHOD("get_rotation"), &Kform::get_rot); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::QUATERNION, "rotation"), "set_rotation", "get_rotation"); + + ClassDB::bind_method(D_METHOD("set_scale", "value"), &Kform::set_scl); + ClassDB::bind_method(D_METHOD("get_scale"), &Kform::get_scl); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); + + ClassDB::bind_method(D_METHOD("set_linear_velocity", "value"), &Kform::set_vel); + ClassDB::bind_method(D_METHOD("get_linear_velocity"), &Kform::get_vel); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + + ClassDB::bind_method(D_METHOD("set_angular_velocity", "value"), &Kform::set_ang); + ClassDB::bind_method(D_METHOD("get_angular_velocity"), &Kform::get_ang); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); + + ClassDB::bind_method(D_METHOD("set_scalar_velocity", "value"), &Kform::set_svl); + ClassDB::bind_method(D_METHOD("get_scalar_velocity"), &Kform::get_svl); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "scalar_velocity"), "set_scalar_velocity", "get_scalar_velocity"); + + ClassDB::bind_method(D_METHOD("inverse"), &Kform::inverse); + ClassDB::bind_method(D_METHOD("multiply"), &Kform::multiply); + ClassDB::bind_method(D_METHOD("divide"), &Kform::divide); + + ClassDB::bind_method(D_METHOD("character_update", "lin_acc", "desired_velocity", "desired_ang", "halflife_vel", "halflife_ang", "dt"), &Kform::character_update); + ClassDB::bind_method(D_METHOD("character_prediction", "lin_acc", "desired_velocity", "desired_ang", "halflife_vel", "halflife_ang", "dt"), &Kform::character_update); + } +}; \ No newline at end of file diff --git a/src/Math/Spring.hpp b/src/Math/Spring.hpp new file mode 100644 index 00000000..ba03fdcb --- /dev/null +++ b/src/Math/Spring.hpp @@ -0,0 +1,741 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace godot; + +struct Spring : public RefCounted { + GDCLASS(Spring, RefCounted) +public: + static constexpr real_t Ln2 = 0.69314718056; + + static real_t inline square(real_t x) { + return x * x; + } + + static Vector3 damp_adjustment_exact(Vector3 g, real_t halflife, real_t dt, real_t eps = 1e-8) { + real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)); + return g * factor; + } + + static Quaternion damp_adjustment_exact_quat(Quaternion g, real_t halflife, real_t dt, real_t eps = 1e-8) { + real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)); + return Quaternion().slerp(g, factor).normalized(); + } + static Variant damper_exponential(Variant variable, Variant goal, real_t damping, real_t dt) { + real_t ft = 1.0 / (real_t)ProjectSettings::get_singleton()->get("physics/common/physics_ticks_per_second"); + real_t factor = 1.0 - pow(1.0 / (1.0 - ft * damping), -dt / ft); + return Math::lerp(variable, goal, factor); + } + + static inline real_t fast_negexp(real_t x) { + return 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x); + } + + static inline real_t _damper_exact(real_t variable, real_t goal, real_t halflife, real_t dt, real_t eps = 1e-5) { + return Math::lerp(variable, goal, real_t(1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)))); + } + static inline Vector3 _damper_exact(Vector3 variable, Vector3 goal, real_t halflife, real_t dt, real_t eps = 1e-5) { + return variable.lerp(goal, 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps))); + } + static inline Quaternion _damper_exact(Quaternion variable, Quaternion goal, real_t halflife, real_t dt, real_t eps = 1e-5) { + return variable.slerp(goal, 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps))); + } + + static inline Variant damper_exact(Variant variable, Variant goal, real_t halflife, real_t dt, real_t eps = 1e-5) { + return Math::lerp(variable, goal, 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps))); + } + + static inline real_t halflife_to_damping(real_t halflife, real_t eps = 1e-5) { + return (4.0 * Ln2) / (halflife + eps); + } + + static inline real_t halflife_to_duration(real_t halflife, real_t initial_value = 1.0, real_t eps = 1e-5) { + return halflife * (log(eps / initial_value) / log(0.5)); + } + + static inline real_t duration_to_halflife(real_t duration, real_t precision_percent = 0.01) { + return -duration / std::log2(precision_percent); + } + + static inline real_t damping_to_halflife(real_t damping, real_t eps = 1e-5) { + return (4.0 * Ln2) / (damping + eps); + } + + static inline real_t frequency_to_stiffness(real_t frequency) { + return square(2.0 * Math_PI * frequency); + } + + static inline real_t stiffness_to_frequency(real_t stiffness) { + return sqrt(stiffness) / (2.0 * Math_PI); + } + + static inline real_t critical_halflife(real_t frequency) { + return damping_to_halflife(sqrt(frequency_to_stiffness(frequency) * 4.0)); + } + + static inline real_t critical_frequency(real_t halflife) { + return stiffness_to_frequency(square(halflife_to_damping(halflife)) / 4.0); + } + + static inline real_t damping_ratio_to_stiffness(real_t ratio, real_t damping) { + return square(damping / (ratio * 2.0)); + } + + static inline real_t damping_ratio_to_damping(real_t ratio, real_t stiffness) { + return ratio * 2.0 * sqrt(stiffness); + } + + static inline real_t maximum_spring_velocity_to_halflife(real_t x, real_t x_goal, real_t v_max) { + return damping_to_halflife(2.0 * ((v_max / (x_goal - x)) * exp(1.0))); + } + + static inline Quaternion quat_exp(Vector3 v, real_t eps = 1e-8) { + real_t halfangle = ::sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); + + if (halfangle < eps) { + Quaternion q{}; + q.w = 1.0; + q.x = v.x; + q.y = v.y; + q.z = v.z; + return q.normalized(); + } else { + real_t c = cosf(halfangle); + real_t s = sinf(halfangle) / halfangle; + Quaternion q{}; + q.w = c; + q.x = s * v.x; + q.y = s * v.y; + q.z = s * v.z; + return q.normalized(); + } + } + template + static inline T clampf(T x, T min, T max) { + static_assert(std::is_arithmetic_v, "Must be arithmetic"); + return x > max ? max : x < min ? min + : x; + } + + static inline Quaternion quat_abs(Quaternion q) { + return (q.w < 0.0 ? -q : q).normalized(); + } + + static inline Vector3 quat_log(Quaternion q, real_t eps = 1e-8) { + real_t length = sqrt(q.x * q.x + q.y * q.y + q.z * q.z); + if (length < eps) { + return Vector3(q.x, q.y, q.z); + } else { + real_t halfangle = acosf(clampf(q.w, real_t(-1.0), real_t(1.0))); + return halfangle * (Vector3(q.x, q.y, q.z) / length); + } + } + + static inline Quaternion quat_from_scaled_angle_axis(Vector3 v, real_t eps = 1e-8) { + return quat_exp(v / 2.0, eps).normalized(); + } + + static inline Vector3 quat_to_scaled_angle_axis(Quaternion q, real_t eps = 1e-8) { + return 2.0 * quat_log(q, eps); + } + + static inline Vector3 quat_differentiate_angular_velocity(Quaternion next, Quaternion curr, real_t dt, real_t eps = 1e-8) { + return quat_to_scaled_angle_axis(quat_abs(next * curr.inverse()), eps) / dt; + } + static inline Quaternion quat_integrate_angular_velocity( + Vector3 vel, Quaternion curr, float dt, float eps = 1e-8f) { + return quat_from_scaled_angle_axis(vel * dt, eps) * curr; + } + + static void _spring_damper_exact( + real_t &x, + real_t &v, + real_t x_goal, + real_t v_goal, + real_t damping_ratio, + real_t halflife, + real_t dt, + real_t eps = 1e-5) { + real_t g = x_goal; + real_t q = v_goal; + real_t d = halflife_to_damping(halflife); + real_t s = damping_ratio_to_stiffness(damping_ratio, d); + real_t c = g + (d * q) / (s + eps); + real_t y = d / 2.0; + + if (std::abs(s - (d * d) / 4.0) < eps) { // Critically Damped + real_t j0 = x - c; + real_t j1 = v + j0 * y; + real_t eydt = std::exp(-y * dt); + x = j0 * eydt + dt * j1 * eydt + c; + v = -y * j0 * eydt - y * dt * j1 * eydt + j1 * eydt; + } else if (s - (d * d) / 4.0 > 0.0) { // Under Damped + real_t w = std::sqrt(s - (d * d) / 4.0); + real_t j = std::sqrt(std::pow(v + y * (x - c), 2) / (std::pow(w, 2) + eps) + std::pow(x - c, 2)); + real_t p = std::atan((v + (x - c) * y) / (-(x - c) * w + eps)); + + j = (x - c) > 0.0 ? j : -j; + + real_t eydt = std::exp(-y * dt); + + x = j * eydt * std::cos(w * dt + p) + c; + v = -y * j * eydt * std::cos(w * dt + p) - w * j * eydt * std::sin(w * dt + p); + } else if (s - (d * d) / 4.0 < 0.0) { // Over Damped + real_t y0 = (d + std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0; + real_t y1 = (d - std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0; + real_t j1 = (c * y0 - x * y0 - v) / (y1 - y0); + real_t j0 = x - j1 - c; + + real_t ey0dt = std::exp(-y0 * dt); + real_t ey1dt = std::exp(-y1 * dt); + + x = j0 * ey0dt + j1 * ey1dt + c; + v = -y0 * j0 * ey0dt - y1 * j1 * ey1dt; + } + } + + static void _critical_spring_damper_exact( + real_t &x, + real_t &v, + real_t x_goal, + real_t v_goal, + real_t halflife, + real_t dt) { + real_t g = x_goal; + real_t q = v_goal; + real_t d = halflife_to_damping(halflife); + real_t c = g + (d * q) / ((d * d) / 4.0); + real_t y = d / 2.0; + real_t j0 = x - c; + real_t j1 = v + j0 * y; + real_t eydt = fast_negexp(y * dt); + x = eydt * (j0 + j1 * dt) + c; + v = eydt * (v - j1 * y * dt); + } + + static inline PackedFloat32Array critical_spring_damper_exact(real_t x, real_t v, real_t x_goal, real_t v_goal, real_t halflife, real_t dt) { + _critical_spring_damper_exact(x, v, x_goal, v_goal, halflife, dt); + PackedFloat32Array result; + result.append(x); + result.append(v); + return result; + } + + static void _simple_spring_damper_exact(real_t &x, real_t &v, real_t x_goal, real_t halflife, real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + real_t j0 = x - x_goal; + real_t j1 = v + j0 * y; + real_t eydt = fast_negexp(y * dt); + x = eydt * (j0 + j1 * dt) + x_goal; + v = eydt * (v - j1 * y * dt); + } + static void _simple_spring_damper_exact( + Vector3 &x, + Vector3 &v, + const Vector3 x_goal, + const real_t halflife, + const real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + Vector3 j0 = x - x_goal; + Vector3 j1 = v + j0 * y; + real_t eydt = fast_negexp(y * dt); + + x = eydt * (j0 + j1 * dt) + x_goal; + v = eydt * (v - j1 * y * dt); + } + + static void _simple_spring_damper_exact( + Quaternion &x, + Vector3 &v, + const Quaternion x_goal, + const real_t halflife, + const real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + + Vector3 j0 = quat_to_scaled_angle_axis(quat_abs(x * x_goal.inverse())); + Vector3 j1 = v + j0 * y; + + real_t eydt = fast_negexp(y * dt); + + x = quat_from_scaled_angle_axis(eydt * (j0 + j1 * dt)) * x_goal; + v = eydt * (v - j1 * y * dt); + } + + static inline Array simple_spring_damper_exact(Variant x, Variant v, Variant x_goal, real_t halflife, real_t dt) { + Array result; + if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::VECTOR3) { + Vector3 pos = (Vector3)x, vel = (Vector3)v, goal = (Vector3)x_goal; + _simple_spring_damper_exact(pos, vel, goal, halflife, dt); + result.append(pos); + result.append(vel); + } else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::QUATERNION) { + Quaternion pos = (Quaternion)x; + Vector3 vel = (Vector3)v; + Quaternion goal = (Quaternion)x_goal; + _simple_spring_damper_exact(pos, vel, goal, halflife, dt); + result.append(pos); + result.append(vel); + } else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT) { + real_t pos = (real_t)x; + real_t vel = (real_t)v, goal = (real_t)x_goal; + _simple_spring_damper_exact(pos, vel, goal, halflife, dt); + result.append(pos); + result.append(vel); + } + + return result; + } + + static inline void _decay_spring_damper_exact(real_t &x, real_t &v, real_t halflife, real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + real_t j1 = v + x * y; + real_t eydt = fast_negexp(y * dt); + x = eydt * (x + j1 * dt); + v = eydt * (v - j1 * y * dt); + } + static inline void _decay_spring_damper_exact(Vector3 &x, Vector3 &v, real_t halflife, real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + Vector3 j1 = v + x * y; + real_t eydt = fast_negexp(y * dt); + x = eydt * (x + j1 * dt); + v = eydt * (v - j1 * y * dt); + } + static inline void _decay_spring_damper_exact( + Quaternion &x, + Vector3 &v, + const real_t halflife, + const real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0; + + Vector3 j0 = quat_to_scaled_angle_axis(x); + Vector3 j1 = v + j0 * y; + + real_t eydt = fast_negexp(y * dt); + + x = quat_from_scaled_angle_axis(eydt * (j0 + j1 * dt)); + v = eydt * (v - j1 * y * dt); + } + static inline Array decay_spring_damper_exact(Variant x, Variant v, real_t halflife, real_t dt) { + Array result; + if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3) { + Vector3 pos = (Vector3)x, vel = (Vector3)v; + _decay_spring_damper_exact(pos, vel, halflife, dt); + result.append(pos); + result.append(vel); + } else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3) { + Quaternion pos = (Quaternion)x; + Vector3 vel = (Vector3)v; + _decay_spring_damper_exact(pos, vel, halflife, dt); + result.append(pos); + result.append(vel); + } else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT) { + real_t pos = (real_t)x; + real_t vel = (real_t)v; + _decay_spring_damper_exact(pos, vel, halflife, dt); + result.append(pos); + result.append(vel); + } + + return result; + } + + // Reach the x_goal at timed t_goal in the future + // Apprehension parameter controls how far into the future we try to track the linear interpolation + static void _timed_spring_damper_exact( + real_t &x, real_t &v, + real_t &xi, + const real_t x_goal, const real_t t_goal, + const real_t halflife, const real_t &dt, + real_t apprehension = 2.0) { + const real_t min_time = t_goal > dt ? t_goal : dt; + + const real_t v_goal = (x_goal - xi) / min_time; + + const real_t t_goal_future = dt + apprehension * halflife; + const real_t x_goal_future = t_goal_future < t_goal ? xi + v_goal * t_goal_future : x_goal; + + _simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); + xi += v_goal * dt; + } + static void _timed_spring_damper_exact( + Vector3 &x, Vector3 &v, + Vector3 &xi, + const Vector3 x_goal, const real_t t_goal, + const real_t halflife, const real_t &dt, + real_t apprehension = 2.0) { + const real_t min_time = t_goal > dt ? t_goal : dt; + + const Vector3 v_goal = (x_goal - xi) / min_time; + + const real_t t_goal_future = dt + apprehension * halflife; + const Vector3 x_goal_future = t_goal_future < t_goal ? xi + v_goal * t_goal_future : x_goal; + + _simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); + xi += v_goal * dt; + } + static void _timed_spring_damper_exact( + Quaternion &x, Vector3 &v, + Quaternion &xi, + const Quaternion x_goal, const real_t t_goal, + const real_t halflife, const real_t &dt, + real_t apprehension = 2.0) { + const real_t min_time = t_goal > dt ? t_goal : dt; + + const Vector3 v_goal = Spring::quat_to_scaled_angle_axis(Spring::quat_abs( + x_goal * xi.inverse())) / + min_time; + + const real_t t_goal_future = dt + apprehension * halflife; + const Quaternion x_goal_future = t_goal_future < t_goal ? xi * Spring::quat_from_scaled_angle_axis(v_goal * t_goal_future) : x_goal; + + _simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); + xi *= Spring::quat_from_scaled_angle_axis(v_goal * dt); + } + + static inline Array timed_spring_damper_exact(Variant x, Variant v, Variant xi, Variant x_goal, float t_goal, real_t halflife, real_t dt, real_t apprehension = 2.0) { + Array result; + if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::VECTOR3) { + Vector3 pos = (Vector3)x, vel = (Vector3)v, v3xi = (Vector3)xi, goal = (Vector3)x_goal; + _timed_spring_damper_exact(pos, vel, v3xi, goal, t_goal, halflife, dt, apprehension); + result.append(pos); + result.append(vel); + result.append(v3xi); + } else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::QUATERNION) { + Vector3 ang = (Vector3)v; + Quaternion rot = (Vector3)x, rot_goal = (Quaternion)x_goal, qxi = (Quaternion)xi; + _timed_spring_damper_exact(rot, ang, qxi, rot_goal, t_goal, halflife, dt, apprehension); + result.append(rot); + result.append(ang); + result.append(qxi); + } else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT) { + real_t pos = (real_t)x; + real_t vel = (real_t)v, fxi = (real_t)xi, goal = (real_t)x_goal; + _timed_spring_damper_exact(pos, vel, fxi, goal, t_goal, halflife, dt, apprehension); + result.append(pos); + result.append(vel); + result.append(fxi); + } + + return result; + } + + static inline void _trajectory_spring_damper( + Vector3 &x, + Vector3 &v, + Vector3 &a, + const Vector3 v_goal, + const real_t halflife, + const real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0f; + Vector3 j0 = v - v_goal; + Vector3 j1 = a + j0 * y; + real_t eydt = fast_negexp(y * dt); + + x = eydt * (((-j1) / (y * y)) + ((-j0 - j1 * dt) / y)) + + (j1 / (y * y)) + j0 / y + v_goal * dt + x; + v = eydt * (j0 + j1 * dt) + v_goal; + a = eydt * (a - j1 * y * dt); + } + + static inline void _trajectory_spring_damper( + Quaternion &x, + Vector3 &v, + Vector3 &a, + const Vector3 v_goal, + const real_t halflife, + const real_t dt) { + real_t y = halflife_to_damping(halflife) / 2.0f; + Vector3 j0 = v - v_goal; + Vector3 j1 = a + j0 * y; + real_t eydt = fast_negexp(y * dt); + + x = quat_from_scaled_angle_axis(eydt * (((-j1) / (y * y)) + ((-j0 - j1 * dt) / y)) + + (j1 / (y * y)) + j0 / y + v_goal * dt) * + x; + v = eydt * (j0 + j1 * dt) + v_goal; + a = eydt * (a - j1 * y * dt); + } + + static Dictionary trajectory_update( + Vector3 linear_position, + Vector3 linear_velocity, + Vector3 linear_acceleration, + Quaternion angular_rotation, + Vector3 angular_velocity, + Vector3 angular_acceleration, + real_t desired_linear_velocity, + real_t desired_angular_velocity, + real_t halflife_linear, + real_t halflife_angular, + real_t dt) { + Dictionary step{}; + + _trajectory_spring_damper( + angular_rotation, + angular_velocity, + angular_acceleration, + Vector3(0, desired_angular_velocity, 0), + halflife_angular, + dt); + + _trajectory_spring_damper( + linear_position, + linear_velocity, + linear_acceleration, + angular_rotation.xform(Vector3(0, 0, desired_linear_velocity)), + halflife_linear, + dt); + + step["linear_position"] = linear_position; + step["linear_velocity"] = linear_velocity; + step["linear_acceleration"] = linear_acceleration; + step["angular_rotation"] = angular_rotation; + step["angular_velocity"] = angular_velocity; + step["angular_acceleration"] = angular_acceleration; + return step; + } + + static TypedArray trajectory_predict( + int size, + Vector3 linear_position, + Vector3 linear_velocity, + Vector3 linear_acceleration, + Quaternion angular_rotation, + Vector3 angular_velocity, + Vector3 angular_acceleration, + real_t desired_linear_velocity, + real_t desired_angular_velocity, + real_t halflife_linear, + real_t halflife_angular, + real_t dt) { + TypedArray out{}; + for (size_t i = 0; i < size; ++i) { + _trajectory_spring_damper( + angular_rotation, + angular_velocity, + angular_acceleration, + Vector3(0, desired_angular_velocity, 0), + halflife_angular, + dt); + + _trajectory_spring_damper( + linear_position, + linear_velocity, + linear_acceleration, + angular_rotation.xform(Vector3(0, 0, desired_linear_velocity)), + halflife_linear, + dt); + Dictionary step{}; + step["linear_position"] = linear_position; + step["linear_velocity"] = linear_velocity; + step["linear_acceleration"] = linear_acceleration; + step["angular_rotation"] = angular_rotation; + step["angular_velocity"] = angular_velocity; + step["angular_acceleration"] = angular_acceleration; + out.append(step); + } + return out; + } + + static void _character_update( + Vector3 &pos, + Vector3 &vel, + Vector3 &acc, + Quaternion &quaternion, + Vector3 &angular_velocity, + const Vector3 v_goal, + const Quaternion &q_goal, + const real_t halflife_vel, + const real_t halflife_rot, + const real_t dt) { + { + real_t y = halflife_to_damping(halflife_vel) / 2.0; + Vector3 j0 = vel - v_goal; + Vector3 j1 = acc + j0 * y; + real_t eydt = fast_negexp(y * dt); + + pos = eydt * ((-j1 / (y * y)) + ((-j0 - j1 * dt) / y)) + + (j1 / (y * y)) + j0 / y + v_goal * dt + pos; + vel = eydt * (j0 + j1 * dt) + v_goal; + acc = eydt * (acc - j1 * y * dt); + } + { + real_t y = halflife_to_damping(halflife_rot) / 2.0; + Vector3 j0 = (quaternion * q_goal.inverse()).get_euler_xyz(); + Vector3 j1 = angular_velocity + j0 * y; + real_t eydt = fast_negexp(y * dt); + quaternion = (Quaternion(eydt * (j0 + j1 * dt)) * q_goal).normalized(); + angular_velocity = eydt * (angular_velocity - j1 * y * dt); + } + } + + static Dictionary + character_update( + Vector3 pos, + Vector3 vel, + Vector3 acc, + Quaternion quaternion, + Vector3 angular_velocity, + Vector3 v_goal, + Quaternion q_goal, + real_t halflife_vel, + real_t halflife_rot, + real_t dt) { + Dictionary answer; + { + _character_update(pos, vel, acc, quaternion, angular_velocity, v_goal, q_goal, halflife_vel, halflife_rot, dt); + + answer["linear_position"] = pos; + answer["linear_velocity"] = vel; + answer["linear_acceleration"] = acc; + + answer["angular_rotation"] = quaternion; + answer["angular_velocity"] = angular_velocity; + answer["delta"] = dt; + } + return answer; + } + + static TypedArray character_predict( + Vector3 x, Vector3 v, Vector3 a, + Quaternion q, Vector3 angular_v, + Vector3 v_goal, Quaternion q_goal, + real_t halflife_v, real_t halflife_q, + PackedFloat32Array dts) { + TypedArray answer; + dts.sort(); + for (int i = 0; i < dts.size(); i++) { + real_t dt = dts[i]; + _character_update(x, v, a, q, angular_v, v_goal, q_goal, halflife_v, halflife_q, i > 0 ? dt - dts[i - 1] : dt); + Dictionary step; + + step["linear_position"] = x; + step["linear_velocity"] = v; + step["linear_acceleration"] = a; + + step["angular_rotation"] = q; + step["angular_velocity"] = angular_v; + step["delta"] = dt; + answer.append(step); + } + return answer; + } + + static inline void inertialize_transition( + Vector3 &off_x, + Vector3 &off_v, + const Vector3 src_x, + const Vector3 src_v, + const Vector3 dst_x, + const Vector3 dst_v) { + off_x = (src_x + off_x) - dst_x; + off_v = (src_v + off_v) - dst_v; + } + + static inline void inertialize_update( + Vector3 &out_x, + Vector3 &out_v, + Vector3 &off_x, + Vector3 &off_v, + const Vector3 in_x, + const Vector3 in_v, + const real_t halflife, + const real_t dt) { + Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt); + out_x = in_x + off_x; + out_v = in_v + off_v; + } + + static inline void inertialize_transition( + Quaternion &off_x, + Vector3 &off_v, + const Quaternion src_x, + const Vector3 src_v, + const Quaternion dst_x, + const Vector3 dst_v) { + off_x = Spring::quat_abs((off_x * src_x) * dst_x.inverse()).normalized(); + off_v = (off_v + src_v) - dst_v; + } + static inline void inertialize_update( + Quaternion &out_x, + Vector3 &out_v, + Quaternion &off_x, + Vector3 &off_v, + const Quaternion in_x, + const Vector3 in_v, + const real_t halflife, + const real_t dt) { + Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt); + out_x = (off_x * in_x).normalized(); + out_v = off_v + off_x.xform(in_v); + } + + static inline Vector3 calculate_offset_vec3(const Vector3 src_x, const Vector3 dst_x, const Vector3 off_x = Vector3()) { + return (src_x + off_x) - dst_x; + } + static inline Quaternion calculate_offset_quat(const Quaternion src_q, const Quaternion dst_q, const Quaternion off_q = Quaternion()) { + return Spring::quat_abs((off_q * src_q) * dst_q.inverse()); + } + + static inline Dictionary binded_inertia_transition(const Vector3 off_x, + const Vector3 off_v, + const Vector3 src_x, + const Vector3 src_v, + const Vector3 dst_x, + const Vector3 dst_v, + const Quaternion off_q, + const Vector3 off_a, + const Quaternion src_q, + const Vector3 src_a, + const Quaternion dst_q, + const Vector3 dst_a) { + Dictionary result; + result["position_offset"] = (src_x + off_x) - dst_x; + result["velocity_offset"] = (src_v + off_v) - dst_v; + result["rotation_offset"] = Spring::quat_abs((off_q * src_q) * dst_q.inverse()); + result["angular_offset"] = (off_a + src_a) - dst_a; + return result; + } + +protected: + static void _bind_methods() { + ClassDB::bind_static_method("Spring", D_METHOD("damper_exponential", "variable", "goal", "damping", "dt"), &Spring::damper_exponential); + ClassDB::bind_static_method("Spring", D_METHOD("damp_adjustment_exact_quat", "g", "halflife", "dt", "eps"), &Spring::damp_adjustment_exact_quat, DEFVAL(1e-8)); + ClassDB::bind_static_method("Spring", D_METHOD("damp_adjustment_exact", "g", "halflife", "dt", "eps"), &Spring::damp_adjustment_exact, DEFVAL(1e-8)); + + ClassDB::bind_static_method("Spring", D_METHOD("halflife_to_duration", "halflife", "initial_value", "eps"), &Spring::halflife_to_duration, DEFVAL(1.0), DEFVAL(1e-5)); + ClassDB::bind_static_method("Spring", D_METHOD("duration_to_halflife", "duration", "precision_percentage"), &Spring::duration_to_halflife, DEFVAL(0.01)); + ClassDB::bind_static_method("Spring", D_METHOD("halflife_to_damping", "halflife", "eps"), &Spring::halflife_to_damping, DEFVAL(1e-5)); + ClassDB::bind_static_method("Spring", D_METHOD("damping_to_halflife", "damping", "eps"), &Spring::damping_to_halflife, DEFVAL(1e-5)); + ClassDB::bind_static_method("Spring", D_METHOD("frequency_to_stiffness", "frequency"), &Spring::frequency_to_stiffness); + ClassDB::bind_static_method("Spring", D_METHOD("stiffness_to_frequency", "stiffness"), &Spring::stiffness_to_frequency); + ClassDB::bind_static_method("Spring", D_METHOD("critical_halflife", "frequency"), &Spring::critical_halflife); + ClassDB::bind_static_method("Spring", D_METHOD("critical_frequency", "halflife"), &Spring::critical_frequency); + ClassDB::bind_static_method("Spring", D_METHOD("damping_ratio_to_stiffness", "ratio", "damping"), &Spring::damping_ratio_to_stiffness); + ClassDB::bind_static_method("Spring", D_METHOD("damping_ratio_to_damping", "ratio", "stiffness"), &Spring::damping_ratio_to_damping); + + ClassDB::bind_static_method("Spring", D_METHOD("maximum_spring_velocity_to_halflife", "x", "x_goal", "v_max"), &Spring::maximum_spring_velocity_to_halflife); + + ClassDB::bind_static_method("Spring", D_METHOD("timed_spring_damper_exact", "x", "v", "xi", "x_goal", "t_goal", "halflife", "dt", "apprehension"), &Spring::timed_spring_damper_exact, DEFVAL(2.0)); + ClassDB::bind_static_method("Spring", D_METHOD("decay_spring_damper_exact", "pos", "vel", "halflife", "dt"), &Spring::decay_spring_damper_exact); + ClassDB::bind_static_method("Spring", D_METHOD("simple_spring_damper_exact", "x", "v", "goal", "halflife", "dt"), &Spring::simple_spring_damper_exact); + ClassDB::bind_static_method("Spring", D_METHOD("critical_spring_damper_exact", "x", "v", "x_goal", "v_goal", "halflife", "dt"), &Spring::critical_spring_damper_exact); + // ClassDB::bind_static_method("Spring", D_METHOD("spring_damper_exact", "x", "v", "x_goal", "v_goal", "damping_ratio", "halflife", "dt", "eps"), &CritDampSpring::spring_damper_exact, DEFVAL(1e-5)); + + ClassDB::bind_static_method("Spring", D_METHOD("character_update", "pos", "vel", "acc", "quaternion", "angular_velocity", "v_goal", "q_goal", "halflife_vel", "halflife_rot", "dt"), &Spring::character_update); + ClassDB::bind_static_method("Spring", D_METHOD("character_predict", "x", "v", "a", "q", "angular_v", "v_goal", "q_goal", "halflife_v", "halflife_q", "dts"), &Spring::character_predict); + + ClassDB::bind_static_method("Spring", D_METHOD("trajectory_update", "linear_position", "linear_velocity", "linear_acceleration", "angular_rotation", "angular_velocity", "angular_acceleration", "desired_linear_velocity", "desired_angular_velocity", "halflife_linear", "halflife_angular", "dt"), &Spring::trajectory_update); + ClassDB::bind_static_method("Spring", D_METHOD("trajectory_predict", "size", "linear_position", "linear_velocity", "linear_acceleration", "angular_rotation", "angular_velocity", "angular_acceleration", "desired_linear_velocity", "desired_angular_velocity", "halflife_linear", "halflife_angular", "dt"), &Spring::trajectory_predict); + } +}; diff --git a/src/MotionFeatures/MFBonesInfo.hpp b/src/MotionFeatures/MFBonesInfo.hpp index 1cb83a04..ba224026 100644 --- a/src/MotionFeatures/MFBonesInfo.hpp +++ b/src/MotionFeatures/MFBonesInfo.hpp @@ -1,325 +1,343 @@ #pragma once -#include +#include +#include +#include +#include -#include -#include -#include +#include -#include -#include -#include -#include -#include -#include +// Friends +#include -#include +using namespace godot; -#include -#include -#include +struct MFBonesInfo : public MotionFeature { + GDCLASS(MFBonesInfo, MotionFeature) +public: + GETSET(Color, debug_color_position, godot::Color(1.0f, 1.0f, 1.0f)); + GETSET(Color, debug_color_velocity, godot::Color(0.0f, 0.0f, 0.0f)); + + GETSET(real_t, weight_bone_pos,1.0); + GETSET(real_t, weight_bone_vel,1.0); + GETSET(real_t, weight_bone_rot,1.0); + GETSET(real_t, weight_bone_ang,1.0); + GETSET(real_t, weight_inertialization,1.0); + + GETSET(String, relative_to_bone, ""); + + GETSET(PackedStringArray, bone_names); + + enum BoneInfoType { + Position, //3 + Velocity, //3 + Rotation, //3 + AngularVel, //3 + InertializationCost, //3 + + MAX_SIZE + }; + + GETSET(float, inertialization_halflife, 0.1); + std::bitset bone_info_type{}; + int get_bone_info_type() { return (int)bone_info_type.to_ulong(); } + void set_bone_info_type(int value) { bone_info_type = value; } + + int get_dimension() const { + return bone_names.size() * 3 * bone_info_type.count(); + } + + PackedFloat32Array get_weights() const { + PackedFloat32Array result{}; + + for (auto i = 0; i < bone_names.size(); ++i) { + if (bone_info_type.test(Position)) + for (auto i = 0; i < 3; ++i) + result.append(weight_bone_pos); + if (bone_info_type.test(Velocity)) + for (auto i = 0; i < 3; ++i) + result.append(weight_bone_vel); + if (bone_info_type.test(Rotation)) + for (auto i = 0; i < 3; ++i) + result.append(weight_bone_rot); + if (bone_info_type.test(AngularVel)) + for (auto i = 0; i < 3; ++i) + result.append(weight_bone_ang); + if (bone_info_type.test(InertializationCost)) + for (auto i = 0; i < 3; ++i) + result.append(weight_inertialization); + } + return result; + } + + PackedStringArray get_hints() const { + PackedStringArray result{}; + + for (auto i = 0; i < bone_names.size(); ++i) { + if (bone_info_type.test(Position)) { + result.append("PxB" + u::str(i)); + result.append("PyB" + u::str(i)); + result.append("PzB" + u::str(i)); + } + if (bone_info_type.test(Velocity)) { + result.append("VxB" + u::str(i)); + result.append("VyB" + u::str(i)); + result.append("VzB" + u::str(i)); + } + if (bone_info_type.test(Rotation)) { + result.append("RxB" + u::str(i)); + result.append("RyB" + u::str(i)); + result.append("RzB" + u::str(i)); + } + if (bone_info_type.test(AngularVel)) { + result.append("AxB" + u::str(i)); + result.append("AyB" + u::str(i)); + result.append("AzB" + u::str(i)); + } + if (bone_info_type.test(InertializationCost)) { + result.append("ICxB" + u::str(i)); + result.append("ICyB" + u::str(i)); + result.append("ICzB" + u::str(i)); + } + } + return result; + } + + PackedFloat32Array bake_pose(Ref mmlib, String animation_name, float time) { + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_path.is_empty(), {}, "SkeletonPath is Empty"); + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_profile == nullptr, {}, "SkeletonProfile is null"); + ERR_FAIL_COND_V_EDMSG(relative_to_bone != "" && mmlib->skeleton_profile->find_bone(relative_to_bone) == -1, {}, "SkeletonProfile doesn't contain the relative bone ( Empty for global)"); + PackedFloat32Array result{}; + Ref animation = mmlib->get_animation(animation_name); + + kform kbone{}; + auto relative_to_bone_path = u::str(mmlib->skeleton_path) + u::str(":") + relative_to_bone; + for (size_t index = 0; index < bone_names.size(); ++index) { + auto bone_path = u::str(mmlib->skeleton_path) + u::str(":") + bone_names[index]; + auto bone = bone_names[index]; + + kbone = get_model_kform(mmlib->skeleton_profile, animation, time, bone_path); + if (!relative_to_bone.is_empty() && relative_to_bone != mmlib->skeleton_profile->get_root_bone()) { + kbone = get_model_kform(mmlib->skeleton_profile, animation, time, relative_to_bone_path).inverse() * kbone; + } + + // Serialize + if (bone_info_type.test(Position)) { + result.push_back(kbone.pos.x); + result.push_back(kbone.pos.y); + result.push_back(kbone.pos.z); + } + if (bone_info_type.test(Velocity)) { + result.push_back(kbone.vel.x); + result.push_back(kbone.vel.y); + result.push_back(kbone.vel.z); + } + if (bone_info_type.test(Rotation)) { + Vector3 const dir = kbone.rot.xform(Vector3(0, 0, 1)); + result.push_back(dir.x); + result.push_back(dir.y); + result.push_back(dir.z); + } + if (bone_info_type.test(AngularVel)) { + result.push_back(kbone.ang.x); + result.push_back(kbone.ang.y); + result.push_back(kbone.ang.z); + } + if (bone_info_type.test(InertializationCost)) { + Vector3 const cost = inertialization_cost_function(kbone.pos, kbone.vel, inertialization_halflife); + result.append(cost.x); + result.append(cost.y); + result.append(cost.z); + } + } + return result; + } + +public: + Vector3 inertialization_cost_function(Vector3 pos, Vector3 vel, float halflife) { + const auto halfdamp = Spring::halflife_to_damping(halflife) / 2.0; + return (2 * pos) / halfdamp + vel / (halfdamp * halfdamp); + } + + PackedFloat32Array serialize_mminertialization3d(MMInertialization3D *node) { + PackedFloat32Array result{}; + for (size_t i = 0; i < bone_names.size(); ++i) { + String bone = bone_names[i]; + int id = node->get_skeleton()->find_bone(bone); + + kform kbone = (kform)node->bone_model[id]; + if (!relative_to_bone.is_empty()) { + const int relative_id = node->get_skeleton()->find_bone(relative_to_bone); + kbone = kform(node->bone_model[relative_id]).inverse() * kbone; + } + Vector3 const pos = kbone.pos, vel = kbone.vel, dir = kbone.rot.xform(Vector3(0, 0, 1)), ang = kbone.ang; + + if (bone_info_type.test(Position)) { + result.append(pos.x); + result.append(pos.y); + result.append(pos.z); + } + if (bone_info_type.test(Velocity)) { + result.append(vel.x); + result.append(vel.y); + result.append(vel.z); + } + if (bone_info_type.test(Rotation)) { + result.append(dir.x); + result.append(dir.y); + result.append(dir.z); + } + if (bone_info_type.test(AngularVel)) { + result.append(ang.x); + result.append(ang.y); + result.append(ang.z); + } + if (bone_info_type.test(InertializationCost)) { + Vector3 const cost = inertialization_cost_function(pos, vel, inertialization_halflife); + result.append(cost.x); + result.append(cost.y); + result.append(cost.z); + } + } + return result; + } + + PackedFloat32Array serialize_mmplayer(Ref mmlib, MMAnimationPlayer *mm_player) { + ERR_FAIL_NULL_V_MSG(mm_player, {}, "MMAnimationPlayer is null"); + constexpr size_t size = 3; + PackedFloat32Array result{}; + { + for (size_t i = 0; i < bone_names.size(); ++i) { + kform kbone = mm_player->_get_model_kform(bone_names[i]); + if (!relative_to_bone.is_empty()) { + kbone = mm_player->_get_model_kform(relative_to_bone).inverse() * kbone; + } + Vector3 const pos = kbone.pos, vel = kbone.vel, dir = kbone.rot.xform(Vector3(0, 0, 1)), ang = kbone.ang; + + if (bone_info_type.test(Position)) { + result.append(pos.x); + result.append(pos.y); + result.append(pos.z); + } + if (bone_info_type.test(Velocity)) { + result.append(vel.x); + result.append(vel.y); + result.append(vel.z); + } + if (bone_info_type.test(Rotation)) { + result.append(dir.x); + result.append(dir.y); + result.append(dir.z); + } + if (bone_info_type.test(AngularVel)) { + result.append(ang.x); + result.append(ang.y); + result.append(ang.z); + } + if (bone_info_type.test(InertializationCost)) { + Vector3 const cost = inertialization_cost_function(pos, vel, mm_player->halflife); + result.append(cost.x); + result.append(cost.y); + result.append(cost.z); + } + } + return result; + } + return result; + } + + virtual void show_debug_info(Ref gizmo, Ref library, String animation_name, float time, Skeleton3D *skel) { + const auto material_name = "bone" + get_path(); + if (gizmo->get_plugin()->get_material(material_name, gizmo) == nullptr) { + gizmo->get_plugin()->create_material(material_name, debug_color_position); + } + auto mat = gizmo->get_plugin()->get_material(material_name, gizmo); + + const Ref animation = library->get_animation(animation_name); + const String reference_path = (String)library->skeleton_path + ":" + relative_to_bone; + const Transform3D root_tr = get_global_kform(library->skeleton_profile, animation, time, reference_path); + for (size_t i = 0; i < bone_names.size(); ++i) { + const String bone_path = (String)library->skeleton_path + ":" + bone_names[i]; + + const kform relative = get_model_kform(library->skeleton_profile, animation, time, reference_path); + const kform model = get_model_kform(library->skeleton_profile, animation, time, bone_path); + const kform kbone = relative_to_bone.is_empty() || relative_to_bone == library->skeleton_profile->get_root_bone() ? model : relative.inverse() * model; + + Transform3D global = root_tr * (Transform3D)kbone; + + Ref mesh{}; + mesh.instantiate(); + mesh->set_size(Vector3{ 1, 1.2, 1 } * 0.05); + gizmo->add_mesh(mesh, mat, global); + + gizmo->add_lines(Array::make((root_tr * (Transform3D)relative).origin, global.origin), mat); + gizmo->add_lines(Array::make(global.origin, global.xform(kbone.vel)), mat); + } + } -#include -#include +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone_info_type", "value"), &MFBonesInfo::set_bone_info_type, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("get_bone_info_type"), &MFBonesInfo::get_bone_info_type); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "bone_info_type", godot::PROPERTY_HINT_FLAGS, "Position,Velocity,Rotation,AngularVel,InertializationCost", godot::PROPERTY_USAGE_DEFAULT), "set_bone_info_type", "get_bone_info_type"); -#include -#include -#include -#include + { + ClassDB::bind_method(D_METHOD("serialize_MMAnimationPlayer", "body"), &MFBonesInfo::serialize_mmplayer); + ClassDB::bind_method(D_METHOD("serialize_MMInertialization3D", "body"), &MFBonesInfo::serialize_mminertialization3d); + } -#include -#include + ClassDB::bind_method(D_METHOD("get_hints"), &MFBonesInfo::get_hints); -#include -#include -#include -#include + ClassDB::bind_method(D_METHOD("set_relative_to_bone", "value"), &MFBonesInfo::set_relative_to_bone, DEFVAL("Root")); + ClassDB::bind_method(D_METHOD("get_relative_to_bone"), &MFBonesInfo::get_relative_to_bone); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING, "relative_to_bone"), "set_relative_to_bone", "get_relative_to_bone"); -using namespace godot; + ClassDB::bind_method(D_METHOD("set_bone_names", "value"), &MFBonesInfo::set_bone_names); + ClassDB::bind_method(D_METHOD("get_bone_names"), &MFBonesInfo::get_bone_names); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "Bones Names"), "set_bone_names", "get_bone_names"); -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); -struct MFBonesInfo : public MotionFeature { - GDCLASS(MFBonesInfo,MotionFeature) - - // Skeleton - Ref _skel = nullptr; - - PackedStringArray bone_names{}; - void set_bone_names(PackedStringArray value){ - bone_names = value; - } - PackedStringArray get_bone_names(){return bone_names;} - - PackedInt32Array bones_id{}; - - HashMap bone_tracks{}; - - virtual int get_dimension()override{ - if(use_inertialization) - { - return bone_names.size() * 3; - } - return bone_names.size() * 3 * 2; - } - - virtual bool setup_for_animation(Ref animation)override{ - return true; - } - - - NodePath _skel_path; - - virtual bool setup_profile(NodePath skeleton_path,Ref skeleton_profile) override{ - ERR_FAIL_COND_V_EDMSG(skeleton_path.is_empty(), false,"SkeletonPath is Empty"); - ERR_FAIL_COND_V_EDMSG(skeleton_profile == nullptr, false,"SkeletonProfile is null"); - _skel = skeleton_profile; - _skel_path = skeleton_path; - bones_id.clear(); - if(_skel!=nullptr) - { - for(size_t i = 0; i < bone_names.size();++i) - { - const size_t id = _skel->find_bone(bone_names[i]); - if (id >= 0) - bones_id.push_back(id); - else - ERR_FAIL_V_EDMSG(false,"Missing Bone " + bone_names[i] + " in the SkeletonProfile"); - } - return true; - } - return false; - } - - virtual PackedFloat32Array bake_animation_pose(Ref animation,float time)override{ - - PackedFloat32Array result{}; - kform kbone{}; - - for (size_t index = 0; index < bone_names.size(); ++index) - { - auto path = u::str(_skel_path)+u::str(":")+bone_names[index]; - kbone = MMAnimationLibrary::sample_bone_rootmotion_kform(animation,time,_skel,path); - - // Serialize - if (use_inertialization) - { - const auto cost = inertialization_cost_function(kbone.pos, kbone.vel, inertialization_halflife); - result.push_back(cost.x); - result.push_back(cost.y); - result.push_back(cost.z); - } - else - { - result.push_back(kbone.pos.x); - result.push_back(kbone.pos.y); - result.push_back(kbone.pos.z); - result.push_back(kbone.vel.x); - result.push_back(kbone.vel.y); - result.push_back(kbone.vel.z); - } - } - - - return result; - } - - Vector3 inertialization_cost_function(Vector3 pos, Vector3 vel, float halflife) - { - const auto halfdamp = Spring::halflife_to_damping(halflife) / 2.0; - return (2*pos) / halfdamp + vel / (halfdamp * halfdamp); - } - - GETSET(PackedVector3Array,bones_pos); - GETSET(PackedVector3Array,bones_vel); - - float weight_bone_pos{1.0f}; float get_weight_bone_pos(){return weight_bone_pos;} void set_weight_bone_pos(float value){weight_bone_pos = value;} - float weight_bone_vel{1.0f}; float get_weight_bone_vel(){return weight_bone_vel;} void set_weight_bone_vel(float value){weight_bone_vel = value;} - float weight_inertialization{1.0f}; float get_weight_inertialization(){return weight_inertialization;} void set_weight_inertialization(float value){weight_inertialization = value;} - - virtual PackedFloat32Array get_weights() override{ - PackedFloat32Array result{}; - - if (use_inertialization) - { - for (auto i = 0; i < 3 * bone_names.size(); ++i) - { - result.append(weight_inertialization); - } - return result; - } - - for(auto i =0; i < 3 * bone_names.size(); ++i) - { - result.append(weight_bone_pos); - } - for(auto i =0; i < 3 * bone_names.size(); ++i) - { - result.append(weight_bone_vel); - } - return result; - } - - GETSET(bool,use_inertialization) - GETSET(float,inertialization_halflife,0.01) - - PackedFloat32Array serialize_mmplayer(MMAnimationPlayer* mm_player){ - ERR_FAIL_NULL_V_MSG(mm_player,{},"MMAnimationPlayer is null"); - constexpr size_t size = 3; - PackedFloat32Array result{}; - if (use_inertialization) - { - result.resize(bone_names.size() * 3); - for (size_t i = 0; i < bone_names.size(); ++i) - { - // _skel isn't init - kform b = mm_player->get_bone_global_kform(_skel->find_bone(bone_names[i])); - Vector3 pos = b.pos, vel = b.vel; - auto cost = inertialization_cost_function(pos, vel, inertialization_halflife); - result[i * size] = cost.x; - result[i * size + 1] = cost.y; - result[i * size + 2] = cost.z; - } - return result; - } - else - { - result.resize(bone_names.size() * 3 * 2); - for (size_t i = 0; i < bone_names.size(); ++i) - { - kform b = mm_player->get_bone_global_kform(_skel->find_bone(bone_names[i])); - Vector3 pos = b.pos, vel = b.vel; - - result[i * size * 2] = pos.x; - result[i * size * 2 + 1] = pos.y; - result[i * size * 2 + 2] = pos.z; - result[i * size * 2 + size] = vel.x; - result[i * size * 2 + size + 1] = vel.y; - result[i * size * 2 + size + 2] = vel.z; - } - return result; - } - return result; - } + ClassDB::bind_method(D_METHOD("set_weight_bone_pos", "value"), &MFBonesInfo::set_weight_bone_pos, DEFVAL(real_t{ 1.0 })); + ClassDB::bind_method(D_METHOD("get_weight_bone_pos"), &MFBonesInfo::get_weight_bone_pos); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_bone_pos"), "set_weight_bone_pos", "get_weight_bone_pos"); -protected: - static void _bind_methods() { - - { - ClassDB::bind_method(D_METHOD("serialize_MMAnimationPlayer", "body"), &MFBonesInfo::serialize_mmplayer); - } - - ClassDB::bind_method(D_METHOD("set_bone_names", "value"), &MFBonesInfo::set_bone_names); - ClassDB::bind_method(D_METHOD("get_bone_names"), &MFBonesInfo::get_bone_names); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "Bones Names"), "set_bone_names", "get_bone_names"); - - ClassDB::bind_method(D_METHOD("set_weight_bone_pos", "value"), &MFBonesInfo::set_weight_bone_pos); - ClassDB::bind_method(D_METHOD("get_weight_bone_pos"), &MFBonesInfo::get_weight_bone_pos); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_bone_pos"), "set_weight_bone_pos", "get_weight_bone_pos"); - ClassDB::bind_method(D_METHOD("set_weight_bone_vel", "value"), &MFBonesInfo::set_weight_bone_vel); - ClassDB::bind_method(D_METHOD("get_weight_bone_vel"), &MFBonesInfo::get_weight_bone_vel); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_bone_vel"), "set_weight_bone_vel", "get_weight_bone_vel"); - ClassDB::bind_method(D_METHOD("set_weight_inertialization", "value"), &MFBonesInfo::set_weight_inertialization); - ClassDB::bind_method(D_METHOD("get_weight_inertialization"), &MFBonesInfo::get_weight_inertialization); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_inertialization"), "set_weight_inertialization", "get_weight_inertialization"); - - - - ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); - { - ClassDB::bind_method( D_METHOD("set_use_inertialization" ,"value"), &MFBonesInfo::set_use_inertialization,DEFVAL(false)); - ClassDB::bind_method( D_METHOD("get_use_inertialization" ), &MFBonesInfo::get_use_inertialization); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"use_inertialization"), "set_use_inertialization", "get_use_inertialization"); - - ClassDB::bind_method( D_METHOD("set_inertialization_halflife" ,"value"), &MFBonesInfo::set_inertialization_halflife, DEFVAL(0.1f)); - ClassDB::bind_method( D_METHOD("get_inertialization_halflife" ), &MFBonesInfo::get_inertialization_halflife); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"inertialization_halflife"), "set_inertialization_halflife", "get_inertialization_halflife"); - - ClassDB::bind_method(D_METHOD("set_debug_color_position", "value"), &MFBonesInfo::set_debug_color_position); - ClassDB::bind_method(D_METHOD("get_debug_color_position"), &MFBonesInfo::get_debug_color_position); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_position"), "set_debug_color_position", "get_debug_color_position"); - - ClassDB::bind_method(D_METHOD("set_debug_color_velocity", "value"), &MFBonesInfo::set_debug_color_velocity); - ClassDB::bind_method(D_METHOD("get_debug_color_velocity"), &MFBonesInfo::get_debug_color_velocity); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_velocity"), "set_debug_color_velocity", "get_debug_color_velocity"); - } - - ClassDB::add_property_group(get_class_static(), "", ""); - - //BINDER_PROPERTY_PARAMS(MFBonesInfo,Variant::PACKED_VECTOR3_ARRAY,bones_pos,PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY); - ClassDB::bind_method( D_METHOD("set_bones_pos" ,"value"), &MFBonesInfo::set_bones_pos); - ClassDB::bind_method( D_METHOD("get_bones_pos" ), &MFBonesInfo::get_bones_pos); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_VECTOR3_ARRAY,"bones_pos",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY), "set_bones_pos", "get_bones_pos"); - - //BINDER_PROPERTY_PARAMS(MFBonesInfo,Variant::PACKED_VECTOR3_ARRAY,bones_vel,PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY); - ClassDB::bind_method( D_METHOD("set_bones_vel" ,"value"), &MFBonesInfo::set_bones_vel); - ClassDB::bind_method( D_METHOD("get_bones_vel" ), &MFBonesInfo::get_bones_vel); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_VECTOR3_ARRAY,"bones_vel",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_READ_ONLY), "set_bones_vel", "get_bones_vel"); - - ClassDB::bind_method( D_METHOD("get_weights"), &MFBonesInfo::get_weights); - ClassDB::bind_method( D_METHOD("get_dimension"), &MFBonesInfo::get_dimension); - ClassDB::bind_method( D_METHOD("setup_profile","skeleton_path","skeleton_profile"), &MFBonesInfo::setup_profile); - - ClassDB::bind_method( D_METHOD("setup_for_animation","animation"), &MFBonesInfo::setup_for_animation); - ClassDB::bind_method( D_METHOD("bake_animation_pose","animation","time"), &MFBonesInfo::bake_animation_pose); - - ClassDB::bind_method( D_METHOD("debug_pose_gizmo","gizmo","data","root_transform"), &MFBonesInfo::debug_pose_gizmo); - } - - GETSET(Color,debug_color_position,godot::Color(1.0f,1.0f,1.0f)); - GETSET(Color,debug_color_velocity,godot::Color(0.0f,0.0f,0.0f)); - - virtual void debug_pose_gizmo(Ref gizmo, const PackedFloat32Array data,godot::Transform3D tr = godot::Transform3D{}) override - { - const auto mat_name_pos = "pos" + get_path(); - const auto mat_name_vel = "vel" + get_path(); - if(gizmo->get_plugin()->get_material(mat_name_pos,gizmo) == nullptr) - { - gizmo->get_plugin()->create_material(mat_name_pos,debug_color_position); - } - if(gizmo->get_plugin()->get_material(mat_name_vel,gizmo) == nullptr) - { - gizmo->get_plugin()->create_material(mat_name_vel,debug_color_velocity); - } - - auto position_color = gizmo->get_plugin()->get_material(mat_name_pos,gizmo); - auto velocity_color = gizmo->get_plugin()->get_material(mat_name_vel,gizmo); - position_color->set_albedo(debug_color_position); - velocity_color->set_albedo(debug_color_velocity); - - if(use_inertialization) - { - return; - } - - constexpr int s = 3; - for(size_t index = 0; index < bone_names.size(); ++index) - { - //i*size*2+size+2 - Vector3 pos = Vector3(data[index * s * 2 + 0], data[index * s * 2 + 1], data[index * s * 2 + 2]); - Vector3 vel = Vector3(data[index * s * 2 + s + 0], data[index * s * 2 + s + 1], data[index * s * 2 + s + 2]); - pos = tr.xform(pos); - vel = tr.xform(vel); - - gizmo->add_lines(Array::make(pos, pos + vel), velocity_color); - auto box = Ref(); - box.instantiate(); - box->set_size(Vector3(0.05f,0.05f,0.05f)); - Transform3D tr = Transform3D(Basis(),pos); - gizmo->add_mesh(box,position_color,tr); - } - } + ClassDB::bind_method(D_METHOD("set_weight_bone_vel", "value"), &MFBonesInfo::set_weight_bone_vel, DEFVAL(real_t{ 1.0 })); + ClassDB::bind_method(D_METHOD("get_weight_bone_vel"), &MFBonesInfo::get_weight_bone_vel); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_bone_vel"), "set_weight_bone_vel", "get_weight_bone_vel"); + + ClassDB::bind_method(D_METHOD("set_weight_bone_ang", "value"), &MFBonesInfo::set_weight_bone_ang, DEFVAL(real_t{ 1.0 })); + ClassDB::bind_method(D_METHOD("get_weight_bone_ang"), &MFBonesInfo::get_weight_bone_ang); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_bone_ang"), "set_weight_bone_ang", "get_weight_bone_ang"); + + ClassDB::bind_method(D_METHOD("set_weight_inertialization", "value"), &MFBonesInfo::set_weight_inertialization, DEFVAL(real_t{ 1.0 })); + ClassDB::bind_method(D_METHOD("get_weight_inertialization"), &MFBonesInfo::get_weight_inertialization); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_inertialization"), "set_weight_inertialization", "get_weight_inertialization"); + + ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); + { + ClassDB::bind_method(D_METHOD("set_inertialization_halflife", "value"), &MFBonesInfo::set_inertialization_halflife, DEFVAL(0.1f)); + ClassDB::bind_method(D_METHOD("get_inertialization_halflife"), &MFBonesInfo::get_inertialization_halflife); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "inertialization_halflife"), "set_inertialization_halflife", "get_inertialization_halflife"); + + ClassDB::bind_method(D_METHOD("set_debug_color_position", "value"), &MFBonesInfo::set_debug_color_position); + ClassDB::bind_method(D_METHOD("get_debug_color_position"), &MFBonesInfo::get_debug_color_position); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_position"), "set_debug_color_position", "get_debug_color_position"); + + ClassDB::bind_method(D_METHOD("set_debug_color_velocity", "value"), &MFBonesInfo::set_debug_color_velocity); + ClassDB::bind_method(D_METHOD("get_debug_color_velocity"), &MFBonesInfo::get_debug_color_velocity); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_velocity"), "set_debug_color_velocity", "get_debug_color_velocity"); + } + + ClassDB::add_property_group(get_class_static(), "", ""); + + ClassDB::bind_method(D_METHOD("get_weights"), &MFBonesInfo::get_weights); + ClassDB::bind_method(D_METHOD("get_dimension"), &MFBonesInfo::get_dimension); + + ClassDB::bind_method(D_METHOD("bake_pose", "animation_library", "animation_name", "time"), &MFBonesInfo::bake_pose); + + ClassDB::bind_method(D_METHOD("show_debug_info", "gizmo", "lib", "animation_name", "timestamp" + "skeleton"), + &MFBonesInfo::show_debug_info); + } }; -#undef MAKE_RESOURCE_TYPE_HINT -#undef GETSET -#undef STR -#undef STRING_PREFIX \ No newline at end of file +// VARIANT_ENUM_CAST(MFBonesInfo::BoneInfoType); \ No newline at end of file diff --git a/src/MotionFeatures/MFDistance.hpp b/src/MotionFeatures/MFDistance.hpp new file mode 100644 index 00000000..d2d5849c --- /dev/null +++ b/src/MotionFeatures/MFDistance.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace godot; + +struct MFDistance : public MotionFeature { + GDCLASS(MFDistance, MotionFeature) + Ref mmlib = nullptr; + std::vector> animation_events{}; + +public: + GETSET(bool, relative_rotation, false); + enum EmbeddedAxis { + X, + Y, + Z + }; + std::bitset<3> embedded_axis{}; + int get_embedded_axis() { return embedded_axis.to_ulong(); } + void set_embedded_axis(int value) { embedded_axis = std::bitset<3>(value); } + + GETSET(float, default_value, (1 << 30)); + GETSET(bool, use_only_start, false); + GETSET(godot::PackedStringArray, events_names); + + static constexpr float delta = 0.016f; + + int get_dimension() const { return events_names.size() * 3; } + + PackedFloat32Array get_weights() const { return Array::make(1.0f, 1.0f, 1.0f); } + + PackedStringArray get_hints() const { + PackedStringArray hints = {}; + for (auto e : events_names) { + hints.append(e + ":x"); + hints.append(e + ":y"); + hints.append(e + ":z"); + } + return hints; + } + + PackedFloat32Array bake_pose(Ref mmlib, String animation_name, float time) { + Ref animation = mmlib->get_animation(animation_name); + String root_bone_track = u::str(mmlib->skeleton_path) + ":" + mmlib->skeleton_profile->get_root_bone(); + Transform3D rest_pose = mmlib->skeleton_profile->get_reference_pose(mmlib->skeleton_profile->find_bone(mmlib->skeleton_profile->get_root_bone())); + + PackedFloat32Array result = {}; + std::vector> current_events{}; + const float time_offset = 1.0f / Engine::get_singleton()->get_physics_ticks_per_second(); + // Get current events tags. + for (Ref tag : animation_events) { + if (tag->timestamp <= time && time < tag->timestamp + tag->duration + time_offset) { + current_events.push_back(tag); + } + } + + for (auto i = 0; i < events_names.size(); ++i) { + const auto event_name = events_names[i]; + auto it = std::find_if(animation_events.begin(), animation_events.end(), [event_name](Ref event) { return event->event_name == event_name; }); + if (it == animation_events.end()) { + result.append(default_value); + continue; + } + const auto event = *it; + Vector3 value{}; + + // Find distance from Root bone to the point depending on the tag. + Vector3 root_pos{}, anchor_pos{}; + Quaternion root_rot{}; + // Step 1 : Get root bone transform + Transform3D root_gtr = (Transform3D)get_global_kform(mmlib->skeleton_profile, animation, time, u::str(mmlib->skeleton_path) + ":" + mmlib->skeleton_profile->get_root_bone()); + Transform3D anchor_gtr{}; + // Step 2 : Get anchor point pos + if (event->anchor_point_strategy == TagMFDistance::Strategy::RootPos) { + kform const anchor_bone = get_global_kform(mmlib->skeleton_profile, animation, event->timestamp, u::str(mmlib->skeleton_path) + ":" + mmlib->skeleton_profile->get_root_bone()); + anchor_pos = anchor_bone.pos; + anchor_gtr = (Transform3D)anchor_bone; + } else if (event->anchor_point_strategy == TagMFDistance::Strategy::AnchorPoint) { + anchor_pos = event->reference_position; + anchor_gtr = Transform3D(Basis{}, event->reference_position); + } else if (event->anchor_point_strategy == TagMFDistance::Strategy::AnchorBone) { + kform const anchor_bone = get_global_kform(mmlib->skeleton_profile, animation, event->timestamp, u::str(mmlib->skeleton_path) + ":" + event->reference_bone); + anchor_pos = anchor_bone.pos; + anchor_gtr = (Transform3D)anchor_bone; + } + + value = (root_gtr.inverse() * anchor_gtr).origin; + + result.append(value.x); + result.append(value.y); + result.append(value.z); + } + + return result; + } + + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_events_names", "value"), &MFDistance::set_events_names); + ClassDB::bind_method(D_METHOD("get_events_names"), &MFDistance::get_events_names); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_STRING_ARRAY, "events_names"), "set_events_names", "get_events_names"); + + ClassDB::bind_method(D_METHOD("set_relative_rotation", "value"), &MFDistance::set_relative_rotation, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_relative_rotation"), &MFDistance::get_relative_rotation); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL, "relative_rotation"), "set_relative_rotation", "get_relative_rotation"); + + // auto prop_axis = PropertyInfo(Variant::INT,"embedded_axis" + // ,PROPERTY_HINT_ENUM,"Magnitude,X,Y,Z" + // ,PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED); + // ClassDB::bind_method( D_METHOD("set_embedded_axis" ,"value"), &MFDistance::set_embedded_axis); + // ClassDB::bind_method( D_METHOD("get_embedded_axis" ), &MFDistance::get_embedded_axis); + // godot::ClassDB::add_property(get_class_static(), prop_axis, "set_embedded_axis", "get_embedded_axis"); + + ClassDB::bind_method(D_METHOD("set_default_value", "value"), &MFDistance::set_default_value, DEFVAL(real_t(int32_t(1 << 30)))); + ClassDB::bind_method(D_METHOD("get_default_value"), &MFDistance::get_default_value); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "default_value"), "set_default_value", "get_default_value"); + + ClassDB::bind_method(D_METHOD("get_dimension"), &MFDistance::get_dimension); + + ClassDB::bind_method(D_METHOD("get_weights"), &MFDistance::get_weights); + + ClassDB::bind_method(D_METHOD("get_hints"), &MFDistance::get_hints); + + ClassDB::bind_method(D_METHOD("bake_pose", "mm_animation_library", "animation_name", "time"), &MFDistance::bake_pose); + } +}; diff --git a/src/MotionFeatures/MFEvents.hpp b/src/MotionFeatures/MFEvents.hpp index 32759a55..ce864fdb 100644 --- a/src/MotionFeatures/MFEvents.hpp +++ b/src/MotionFeatures/MFEvents.hpp @@ -5,13 +5,14 @@ #include #include #include +#include +#include #include #include -#include #include #include -#include +#include #include @@ -19,157 +20,148 @@ #include #include -#include #include +#include #include +#include #include #include #include -#include + +#include #include +#include +#include #include - -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); +namespace views = std::ranges::views; using namespace godot; -int sec_to_frame(float seconds, int fps = -1) -{ - if (fps <= 0 ) - { - fps = Engine::get_singleton()->get_physics_ticks_per_second(); - } - return (int)ceil(seconds * Engine::get_singleton()->get_physics_ticks_per_second()); +int sec_to_frame(float seconds, int fps = -1) { + if (fps <= 0) { + fps = Engine::get_singleton()->get_physics_ticks_per_second(); + } + return (int)ceil(seconds * Engine::get_singleton()->get_physics_ticks_per_second()); } struct MFEvents : public MotionFeature { - GDCLASS(MFEvents,MotionFeature) - - virtual ~MFEvents() = default; - - GETSET(bool,embed_as_frames); - GETSET(bool,embed_time_since_last_event); - GETSET(godot::PackedStringArray,events_tracks); - GETSET(godot::PackedStringArray,events_names); - - static constexpr float delta = 0.016f; - - virtual int get_dimension()override{return events_names.size();} - - virtual PackedFloat32Array get_weights()override{ return Array::make(1.0f);} - - virtual bool setup_profile(NodePath skeleton_path,Ref skel_profile)override{ - // returning false will abort the process. - // feel free to print more details - - return true; - } - - virtual bool setup_for_animation(Ref animation)override{ - // returning false will skip this animation and print a warning - // feel free to print more details - - bool has_tracks = false; - for(auto index_track = 0;index_track < events_names.size(); ++index_track) - { - const auto track_name = events_tracks[index_track]; - auto track_id = animation->find_track(track_name,Animation::TrackType::TYPE_METHOD); - if(track_id == -1) continue; - has_tracks = true; - } - if(has_tracks == false) - { - u::prints("No tracks found the animation",animation->get_path()); - } - - return has_tracks; - } - virtual PackedFloat32Array bake_animation_pose(Ref animation,float time) override { - PackedFloat32Array result = {}; - for(auto event_i = 0;event_i < events_names.size(); ++event_i) - { - String event_name = events_names[event_i]; - float closest_left = 0.0,closest_right = animation->get_length(); - for(auto index_track = 0;index_track < events_names.size(); ++index_track) - { - const auto track_name = events_tracks[index_track]; - auto track_id = animation->find_track(track_name,Animation::TrackType::TYPE_METHOD); - if(track_id == -1) continue; - - for(auto index_key=0;index_key < animation->track_get_key_count(track_id); ++index_key ) - { - auto method_name = animation->method_track_get_name(track_id,index_key); - auto method_args = animation->method_track_get_params(track_id,index_key); //0.1 - float method_time = (float)animation->track_get_key_time(track_id,index_key); // 0.33 - if(method_name == event_name || - (method_name == String("emit_signal") && method_args[0] == (StringName)event_name )) - { - if (time <= method_time ) - { - closest_right = std::min(closest_right,method_time); - } - else if (method_time < time) - { - closest_left = std::max(closest_left,method_time); - } - } - } - } - auto time_until = closest_right - time; - if(embed_as_frames) - { - time_until = sec_to_frame(time_until); - } - result.append(time_until); - - } - return result; - } - - virtual void debug_pose_gizmo(Ref gizmo, const PackedFloat32Array data,godot::Transform3D tr = godot::Transform3D{}){return;} - - - static void _bind_methods() { - - ClassDB::bind_method( D_METHOD("set_events_tracks" ,"value"), &MFEvents::set_events_tracks); - ClassDB::bind_method( D_METHOD("get_events_tracks" ), &MFEvents::get_events_tracks); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_STRING_ARRAY,"events_tracks"), "set_events_tracks", "get_events_tracks"); - - ClassDB::bind_method( D_METHOD("set_events_names" ,"value"), &MFEvents::set_events_names); - ClassDB::bind_method( D_METHOD("get_events_names" ), &MFEvents::get_events_names); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_STRING_ARRAY,"events_names"), "set_events_names", "get_events_names"); - - ClassDB::bind_method( D_METHOD("set_embed_as_frames" ,"value"), &MFEvents::set_embed_as_frames); - ClassDB::bind_method( D_METHOD("get_embed_as_frames" ), &MFEvents::get_embed_as_frames); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"embed_as_frames"), "set_embed_as_frames", "get_embed_as_frames"); - - ClassDB::bind_method( D_METHOD("set_embed_time_since_last_event" ,"value"), &MFEvents::set_embed_time_since_last_event); - ClassDB::bind_method( D_METHOD("get_embed_time_since_last_event" ), &MFEvents::get_embed_time_since_last_event); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"embed_time_since_last_event"), "set_embed_time_since_last_event", "get_embed_time_since_last_event"); - - ClassDB::bind_method( D_METHOD("get_dimension"), &MFEvents::get_dimension); - - ClassDB::bind_method( D_METHOD("get_weights"), &MFEvents::get_weights); - - ClassDB::bind_method( D_METHOD("setup_profile","skeleton_path","skeleton_profile"), &MFEvents::setup_profile); - - ClassDB::bind_method( D_METHOD("setup_for_animation","animation"), &MFEvents::setup_for_animation); - ClassDB::bind_method( D_METHOD("bake_animation_pose","animation","time"), &MFEvents::bake_animation_pose); - - ClassDB::bind_method( D_METHOD("debug_pose_gizmo","gizmo","data","root_transform"), &MFEvents::debug_pose_gizmo); - - } + GDCLASS(MFEvents, MotionFeature) + +public: + enum EventType { + Timing, + EmbedValue, + + }; + + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: + normalization_type = RawValue; + } + } + + GETSET(EventType, event_type); + + GETSET(bool, embed_as_frames, false); + GETSET(bool, use_only_start, false); + GETSET(real_t, max_signed_time, real_t(2.0)); + GETSET(godot::PackedStringArray, events_names); + + static constexpr float delta = 0.016f; + + int get_dimension() const { return events_names.size(); } + + PackedFloat32Array get_weights() const { return Array::make(1.0f); } + + PackedStringArray get_hints() const { return events_names; } + + // the current logic is this : Take the first event + + PackedFloat32Array bake_pose(Ref mmlib, String animation_name, float time) { + PackedFloat32Array result = {}; + + std::vector> animation_events{}; + // std::vector> current_events{}; + const float time_offset = 1.0f / Engine::get_singleton()->get_physics_ticks_per_second(); + + auto animation = mmlib->get_animation(animation_name); + + animation_events.clear(); + for (auto i = 0; i < mmlib->tags.size(); ++i) { + if (TagMFEvent *event = Object::cast_to(mmlib->tags[i]); event != nullptr && event->animation_name == animation->get_name()) { + animation_events.push_back(event); + } + } + + // [ 5 4 3 2 1 0 -1 -2 -3 3 2 1 0 0 0 0 -1 -2 2 1 0 -1 ] + // 0s are event. positive values are pre-event, negative are post-event + + for (auto i = 0; i < events_names.size(); ++i) { + const auto event_name = events_names[i]; + auto is_event_name = [event_name](Ref event) { return event->event_name == event_name; }; + std::vector> current_events{}; + std::ranges::copy(animation_events | std::ranges::views::filter(is_event_name), std::back_inserter(current_events)); + + if (current_events.size() == 0) { + result.append(max_signed_time); + continue; + } + + // Check if event is inside the duration + for (auto event : current_events) { + if (event->timestamp <= time && time < (event->timestamp + event->duration)) { + result.append(0.0f); + continue; + } + } + + // Get the nearest event, otherwise send max value. + auto nearest = *std::min_element(current_events.begin(), current_events.end(), + [time](Ref a, Ref b) { + const float A = time < a->timestamp ? abs(time - a->timestamp) : abs(time - (a->timestamp + a->duration)); + const float B = time < b->timestamp ? abs(time - b->timestamp) : abs(time - (b->timestamp + b->duration)); + return A < B; + }); + // 0.5 1.0 1.0 0.5 : 0.5 1.0 + float T = time < nearest->timestamp ? nearest->timestamp - time : nearest->timestamp + nearest->duration - time; + T = u::clampf(T, -max_signed_time, max_signed_time); + if (embed_as_frames) { + T *= (int)ProjectSettings::get_singleton()->get_setting("physics/common/physics_ticks_per_second"); + } + result.append(T); + } + return result; + } + + static void _bind_methods() { + BIND_ENUM_CONSTANT(Timing); + BIND_ENUM_CONSTANT(EmbedValue); + + ClassDB::bind_method(D_METHOD("get_dimension"), &MFEvents::get_dimension); + + ClassDB::bind_method(D_METHOD("get_weights"), &MFEvents::get_weights); + + ClassDB::bind_method(D_METHOD("get_hints"), &MFEvents::get_hints); + + ClassDB::bind_method(D_METHOD("set_events_names", "value"), &MFEvents::set_events_names); + ClassDB::bind_method(D_METHOD("get_events_names"), &MFEvents::get_events_names); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_STRING_ARRAY, "events_names"), "set_events_names", "get_events_names"); + + ClassDB::bind_method(D_METHOD("set_max_signed_time", "value"), &MFEvents::set_max_signed_time); + ClassDB::bind_method(D_METHOD("get_max_signed_time"), &MFEvents::get_max_signed_time); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "max_signed_time"), "set_max_signed_time", "get_max_signed_time"); + + ClassDB::bind_method(D_METHOD("set_embed_as_frames", "value"), &MFEvents::set_embed_as_frames); + ClassDB::bind_method(D_METHOD("get_embed_as_frames"), &MFEvents::get_embed_as_frames); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL, "embed_as_frames"), "set_embed_as_frames", "get_embed_as_frames"); + + ClassDB::bind_method(D_METHOD("bake_pose", "mm_animation_library", "animation", "time"), &MFEvents::bake_pose); + } }; + +VARIANT_ENUM_CAST(MFEvents::EventType); \ No newline at end of file diff --git a/src/MotionFeatures/MFRootVelocity.hpp b/src/MotionFeatures/MFRootVelocity.hpp index 64ec4ae3..3f2f4ea6 100644 --- a/src/MotionFeatures/MFRootVelocity.hpp +++ b/src/MotionFeatures/MFRootVelocity.hpp @@ -6,12 +6,12 @@ #include #include +#include #include #include -#include #include #include -#include +#include #include @@ -19,156 +19,127 @@ #include #include -#include #include +#include +#include #include #include #include -#include #include +#include #include using namespace godot; using u = godot::UtilityFunctions; -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__}; type get_##variable(){return variable;} void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable);\ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - struct MFRootVelocity : public MotionFeature { - GDCLASS(MFRootVelocity,MotionFeature); - - int root_track_pos =-1, root_track_quat = -1;//, root_track_scale = -1; - - String root_bone_track = "%GeneralSkeleton:Root"; - Transform3D rest_pose = Transform3D(); - - virtual int get_dimension()override{ - return 3; - } - - GETSET(float,weight,1.0f); - virtual PackedFloat32Array get_weights() override{ - return Array::make(weight,weight,weight); - } - - virtual bool setup_profile(NodePath skeleton_path,Ref skeleton_profile) override{ - ERR_FAIL_COND_V_EDMSG(skeleton_path.is_empty(), false,"SkeletonPath is Empty"); - ERR_FAIL_COND_V_EDMSG(skeleton_profile == nullptr, false,"SkeletonProfile is null"); - ERR_FAIL_COND_V_EDMSG(skeleton_profile->get_root_bone().is_empty(),false,"No Root bone to extract data"); - rest_pose = skeleton_profile->get_reference_pose(skeleton_profile->find_bone(skeleton_profile->get_root_bone())); - root_bone_track = u::str(skeleton_path) + ":" + skeleton_profile->get_root_bone(); - return true; - } - virtual bool setup_for_animation(Ref animation)override{ - root_track_pos = animation->find_track(NodePath(root_bone_track),Animation::TrackType::TYPE_POSITION_3D); - root_track_quat = animation->find_track(NodePath(root_bone_track),Animation::TrackType::TYPE_ROTATION_3D); - return true; - } - - virtual PackedFloat32Array bake_animation_pose(Ref animation,float time)override{ - Vector3 pos, prev_pos; - if(root_track_pos >= 0) - { - pos = animation->position_track_interpolate(root_track_pos,time + 0.05); - prev_pos = animation->position_track_interpolate(root_track_pos,time); - } else { - pos = rest_pose.get_origin(); - prev_pos = rest_pose.get_origin(); - } - - Quaternion rotation = root_track_quat >= 0 ? animation->rotation_track_interpolate(root_track_quat,time).normalized() : - rest_pose.get_basis().get_rotation_quaternion(); - - Vector3 vel = rotation.xform_inv(pos-prev_pos) / 0.05; - - PackedFloat32Array result{}; - result.push_back(vel.x); - result.push_back(vel.y); - result.push_back(vel.z); - return result; - } - - PackedFloat32Array serialize_charbody3d(CharacterBody3D * body) - { - PackedFloat32Array result{}; - auto vel = body->get_global_transform().basis.get_quaternion().xform_inv(body->get_velocity()); - result.push_back(vel.x); - result.push_back(vel.y); - result.push_back(vel.z); - return result; - } - PackedFloat32Array serialize_vec3(Vector3 local_vel) - { - PackedFloat32Array result{}; - result.push_back(local_vel.x); - result.push_back(local_vel.y); - result.push_back(local_vel.z); - return result; - } - + GDCLASS(MFRootVelocity, MotionFeature); + +public: + int get_dimension() const { + return 3; + } + + GETSET(float, weight, 1.0f); + + PackedFloat32Array get_weights() const { + return Array::make(weight, weight, weight); + } + PackedStringArray get_hints() const { + return Array::make("Vx", "Vy", "Vz"); + } + + PackedFloat32Array bake_pose(Ref mmlib, String animation_name, float time) { + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_path.is_empty(), {}, "SkeletonPath is Empty"); + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_profile == nullptr, {}, "SkeletonProfile is null"); + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_profile->get_root_bone().is_empty(), {}, "No Root bone to extract data"); + Ref anim = mmlib->get_animation(animation_name); + auto _root_bone_track = u::str(mmlib->skeleton_path) + ":" + mmlib->skeleton_profile->get_root_bone(); + + kform root_motion = get_root_model_kform(mmlib->skeleton_profile, anim, time, _root_bone_track); + + PackedFloat32Array result{}; + result.append(root_motion.vel.x); + result.append(root_motion.vel.y); + result.append(root_motion.vel.z); + return result; + } + + PackedFloat32Array serialize_charbody3d(CharacterBody3D *body) { + PackedFloat32Array result{}; + auto vel = body->get_global_transform().basis.get_quaternion().xform_inv(body->get_velocity()); + result.push_back(vel.x); + result.push_back(vel.y); + result.push_back(vel.z); + return result; + } + PackedFloat32Array serialize_vec3(Vector3 local_vel) { + PackedFloat32Array result{}; + result.push_back(local_vel.x); + result.push_back(local_vel.y); + result.push_back(local_vel.z); + return result; + } + + virtual float calculate_cost(PackedFloat32Array query, PackedFloat32Array data) const { + Vector3 v_query = Vector3(query[0], query[1], query[2]); + Vector3 v_data = Vector3(data[0], data[1], data[2]); + return v_query.distance_to(v_data) * weight; + } + + virtual void show_debug_info(Ref gizmo, Ref library, String animation_name, float timestamp, Skeleton3D *skel) const { + Ref animation = library->get_animation(animation_name); + String root_bone_path = String(library->skeleton_path) + ":" + library->skeleton_profile->get_root_bone(); + Vector3 local_vel = get_root_model_kform(library->skeleton_profile, animation, timestamp, root_bone_path).vel; + PackedVector3Array lines{}; + auto root_bone_tr = skel->get_bone_global_pose(skel->find_bone(library->skeleton_profile->get_root_bone())); + + lines.append(root_bone_tr.origin); + lines.append(root_bone_tr.xform(local_vel)); + + const auto material_name = "rootvel" + get_path(); + if (gizmo->get_plugin()->get_material(material_name) == nullptr) { + gizmo->get_plugin()->create_material(material_name, debug_color); + } + auto mat = gizmo->get_plugin()->get_material(material_name, gizmo); + gizmo->add_lines(lines, mat); + } protected: - static void _bind_methods() { - - { - ClassDB::bind_method(D_METHOD("serialize_CharacterBody3d", "body"), &MFRootVelocity::serialize_charbody3d); - ClassDB::bind_method(D_METHOD("serialize_Local_Velocity", "local_velocity"), &MFRootVelocity::serialize_vec3); - } - - ClassDB::bind_method(D_METHOD("set_weight", "value"), &MFRootVelocity::set_weight, DEFVAL(1.0f)); - ClassDB::bind_method(D_METHOD("get_weight"), &MFRootVelocity::get_weight); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight"), "set_weight", "get_weight"); - - ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); - { - ClassDB::bind_method(D_METHOD("set_debug_color", "value"), &MFRootVelocity::set_debug_color); - ClassDB::bind_method(D_METHOD("get_debug_color"), &MFRootVelocity::get_debug_color); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color"); - } - - ClassDB::add_property_group(get_class_static(), "", ""); - - ClassDB::bind_method( D_METHOD("get_weights"), &MFRootVelocity::get_weights); - ClassDB::bind_method( D_METHOD("get_dimension"), &MFRootVelocity::get_dimension); - - ClassDB::bind_method( D_METHOD("setup_profile","skeleton_path","skeleton_profile"), &MFRootVelocity::setup_profile); - - ClassDB::bind_method( D_METHOD("setup_for_animation","animation"), &MFRootVelocity::setup_for_animation); - ClassDB::bind_method( D_METHOD("bake_animation_pose","animation","time"), &MFRootVelocity::bake_animation_pose); - - ClassDB::bind_method( D_METHOD("debug_pose_gizmo","gizmo","data","root_transform"), &MFRootVelocity::debug_pose_gizmo); - } - - GETSET(Color,debug_color,godot::Color(1.0f,1.0f,1.0f)); - - - virtual void debug_pose_gizmo(Ref gizmo, const PackedFloat32Array data,godot::Transform3D tr = godot::Transform3D{}) override - { - const auto material_name = "rootvel" + get_path(); - if(gizmo->get_plugin()->get_material(material_name) == nullptr) - { - gizmo->get_plugin()->create_material(material_name,debug_color); - } - if (data.size() == get_dimension()) - { - Vector3 vel = tr.xform( Vector3(data[0], data[1] ,data[2]) ); - auto mat = gizmo->get_plugin()->get_material(material_name,gizmo); - mat->set_albedo(debug_color); - gizmo->add_lines(Array::make(tr.origin, tr.origin + vel), mat); - } - } -}; - -#undef MAKE_RESOURCE_TYPE_HINT -#undef GETSET -#undef STR -#undef STRING_PREFIX \ No newline at end of file + static void _bind_methods() { + { + ClassDB::bind_method(D_METHOD("serialize_CharacterBody3d", "body"), &MFRootVelocity::serialize_charbody3d); + ClassDB::bind_method(D_METHOD("serialize_Local_Velocity", "local_velocity"), &MFRootVelocity::serialize_vec3); + } + ClassDB::bind_method(D_METHOD("get_hints"), &MFRootVelocity::get_hints); + + ClassDB::bind_method(D_METHOD("set_weight", "value"), &MFRootVelocity::set_weight, DEFVAL(1.0f)); + ClassDB::bind_method(D_METHOD("get_weight"), &MFRootVelocity::get_weight); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight"), "set_weight", "get_weight"); + + ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); + { + ClassDB::bind_method(D_METHOD("set_debug_color", "value"), &MFRootVelocity::set_debug_color); + ClassDB::bind_method(D_METHOD("get_debug_color"), &MFRootVelocity::get_debug_color); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color"); + } + + ClassDB::add_property_group(get_class_static(), "", ""); + + ClassDB::bind_method(D_METHOD("get_weights"), &MFRootVelocity::get_weights); + ClassDB::bind_method(D_METHOD("get_dimension"), &MFRootVelocity::get_dimension); + + ClassDB::bind_method(D_METHOD("bake_pose", "animation_library", "animation_name", "time"), &MFRootVelocity::bake_pose); + + ClassDB::bind_method(D_METHOD("calculate_cost", "query", "data"), &MFRootVelocity::calculate_cost); + + ClassDB::bind_method(D_METHOD("show_debug_info", "gizmo", "lib", "animation_name", "timestamp" + "skeleton"), + &MFRootVelocity::show_debug_info); + } + + GETSET(Color, debug_color, godot::Color(1.0f, 1.0f, 1.0f)); +}; \ No newline at end of file diff --git a/src/MotionFeatures/MFTrajectory.hpp b/src/MotionFeatures/MFTrajectory.hpp index 99e67f6d..65e77a0a 100644 --- a/src/MotionFeatures/MFTrajectory.hpp +++ b/src/MotionFeatures/MFTrajectory.hpp @@ -1,17 +1,20 @@ #pragma once +#include #include #include #include #include +#include #include +#include #include -#include #include #include -#include +#include + #include @@ -19,292 +22,346 @@ #include #include -#include #include +#include +#include #include #include #include -#include +#include + +#include +#include #include +#include + +#include using namespace godot; using u = godot::UtilityFunctions; -// Macro setup. Mostly there to simplify writing all those -#define GETSET(type,variable,...) type variable{__VA_ARGS__}; type get_##variable(){return variable;} void set_##variable(type value){variable = value;} - -struct MFTrajectory : public MotionFeature{ - GDCLASS(MFTrajectory,MotionFeature) - - - virtual ~MFTrajectory() = default; - - Skeleton3D* skeleton{nullptr}; Skeleton3D* get_skeleton(){return skeleton;} void set_skeleton(Skeleton3D* value){skeleton = value;} - String root_bone_track = "%GeneralSkeleton:Root"; - - GETSET(NodePath,character_path); - - GETSET(float,halflife_velocity,0.2); - GETSET(float,halflife_angular_velocity,0.13); +struct MFTrajectoryOptions : public Resource { + GDCLASS(MFTrajectoryOptions, Resource) +public: + GETSET(float, time_offset, 0.0f) + GETSET(float, weights, 1.0f); + GETSET(int, coordinate, Coordinates::XYZ) + GETSET(int, options, 3); + GETSET(Color, debug_color, Color{ "RED" }); + + int get_dimensions() { + if (coordinate == Coordinates::XZ) + return 2 * std::bitset<32>(options).count(); + else if (coordinate == Coordinates::XYZ) + return 3 * std::bitset<32>(options).count(); + else + return 0; + } + + enum Coordinates { + XZ, + XYZ + }; + enum Options { + Position, + Velocity, + Direction + }; + + PackedFloat32Array serialize(const Kform local_kform) { + PackedFloat32Array result{}; + std::bitset<32> bit(options); + if (bit.test(Options::Position)) { + const auto pos = local_kform.get_pos(); + result.append(pos.x); + if (coordinate == Coordinates::XYZ) + result.append(pos.y); + result.append(pos.z); + } + if (bit.test(Options::Velocity)) { + const auto vel = local_kform.get_vel(); + result.append(vel.x); + if (coordinate == Coordinates::XYZ) + result.append(vel.y); + result.append(vel.z); + } + if (bit.test(Options::Direction)) { + const auto dir = local_kform.get_rot().xform(Vector3(0, 0, 1)); + result.append(dir.x); + if (coordinate == Coordinates::XYZ) + result.append(dir.y); + result.append(dir.z); + } + return result; + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_time_offset", "value"), &MFTrajectoryOptions::set_time_offset, DEFVAL(0.0)); + ClassDB::bind_method(D_METHOD("get_time_offset"), &MFTrajectoryOptions::get_time_offset); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "time_offset"), "set_time_offset", "get_time_offset"); + ClassDB::bind_method(D_METHOD("set_weights", "value"), &MFTrajectoryOptions::set_weights, DEFVAL(1.0)); + ClassDB::bind_method(D_METHOD("get_weights"), &MFTrajectoryOptions::get_weights); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weights"), "set_weights", "get_weights"); + ClassDB::bind_method(D_METHOD("set_coordinate", "value"), &MFTrajectoryOptions::set_coordinate); + ClassDB::bind_method(D_METHOD("get_coordinate"), &MFTrajectoryOptions::get_coordinate); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "coordinate", PROPERTY_HINT_ENUM, "XY,XYZ", PROPERTY_USAGE_DEFAULT), "set_coordinate", "get_coordinate"); + ClassDB::bind_method(D_METHOD("set_options", "value"), &MFTrajectoryOptions::set_options, DEFVAL(3)); + ClassDB::bind_method(D_METHOD("get_options"), &MFTrajectoryOptions::get_options); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::INT, "options", PROPERTY_HINT_FLAGS, "Position,Velocity,Direction,", PROPERTY_USAGE_DEFAULT), "set_options", "get_options"); + ClassDB::bind_method(D_METHOD("set_debug_color", "value"), &MFTrajectoryOptions::set_debug_color, Color{ "RED" }); + ClassDB::bind_method(D_METHOD("get_debug_color"), &MFTrajectoryOptions::get_debug_color); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color"), "set_debug_color", "get_debug_color"); + } +}; - GETSET(PackedFloat32Array,past_time_dt); - GETSET(PackedFloat32Array,future_time_dt); +struct MFTrajectory : public MotionFeature { + GDCLASS(MFTrajectory, MotionFeature) +public: + Skeleton3D *skeleton{ nullptr }; + Skeleton3D *get_skeleton() { return skeleton; } + void set_skeleton(Skeleton3D *value) { skeleton = value; } + String root_bone_track = "%GeneralSkeleton:Root"; - GETSET(float,weight_history_pos,1.0f); - GETSET(float,weight_prediction_pos,1.0f); - GETSET(float,weight_prediction_angle,1.0f); - virtual PackedFloat32Array get_weights() override{ - PackedFloat32Array result{}; - for(auto i =0; i < 2 * past_time_dt.size(); ++i) - { - result.append(weight_history_pos); - } - for(auto i =0; i < 2 * future_time_dt.size(); ++i) - { - result.append(weight_prediction_pos); - } - for(auto i =0; i < 1 * future_time_dt.size(); ++i) - { - result.append(weight_prediction_angle); - } - return result; - } + GETSET(TypedArray, options); + GETSET(float, weight_history_pos, 1.0f); + GETSET(float, weight_prediction_pos, 1.0f); + GETSET(float, weight_prediction_angle, 1.0f); public: - virtual int get_dimension() override - { - // Offset for each - const size_t past_pos = 2 * past_time_dt.size(); - const size_t future_pos = 2 * future_time_dt.size(); - const size_t future_rot_angle = future_time_dt.size(); - return past_pos + future_pos + future_rot_angle ; - } - - int root_tracks[3] = {0,0,0}; - Vector3 start_pos,start_vel,end_pos,end_vel; - Quaternion start_rot,end_rot, end_ang_vel; - float start_time = 0.0f, end_time = 0.0f; - - virtual bool setup_profile(NodePath skeleton_path,Ref skeleton_profile) override{ - ERR_FAIL_COND_V_EDMSG(skeleton_path.is_empty(), false,"SkeletonPath is Empty"); - ERR_FAIL_COND_V_EDMSG(skeleton_profile == nullptr, false,"SkeletonProfile is null"); - ERR_FAIL_COND_V_EDMSG(skeleton_profile->get_root_bone().is_empty(),false,"No Root bone to extract data"); - root_bone_track = u::str(skeleton_path) + ":" + skeleton_profile->get_root_bone(); - return true; - }; - - virtual bool setup_for_animation(Ref animation) override - { - start_time = 0.1f; - end_time = std::floor(animation->get_length() * 10)/10.0f; - root_tracks[0] = animation->find_track(root_bone_track, Animation::TrackType::TYPE_POSITION_3D); - root_tracks[1] = animation->find_track(root_bone_track, Animation::TrackType::TYPE_ROTATION_3D); - root_tracks[2] = animation->find_track(root_bone_track, Animation::TrackType::TYPE_SCALE_3D); - { - start_pos = animation->position_track_interpolate(root_tracks[0], 0.0); - start_rot = animation->rotation_track_interpolate(root_tracks[1], 0.0); - start_vel = (animation->position_track_interpolate(root_tracks[0], 0.1) - start_pos) / 0.1; - } - { - end_pos = animation->position_track_interpolate(root_tracks[0], end_time); - end_rot = animation->rotation_track_interpolate(root_tracks[1], end_time); - end_vel = (end_pos - animation->position_track_interpolate(root_tracks[0], end_time - 0.1)) / 0.1; - - end_ang_vel = animation->rotation_track_interpolate(root_tracks[1], animation->get_length() - delta - 0.1).inverse() * animation->rotation_track_interpolate(root_tracks[1], animation->get_length() - delta); - } - return true; - } - - virtual PackedFloat32Array bake_animation_pose(Ref animation,float time)override - { - PackedFloat32Array result{}; - Vector3 curr_pos = animation->position_track_interpolate(root_tracks[0],time); - Quaternion curr_rot = animation->rotation_track_interpolate(root_tracks[1],time); - - for (size_t index = 0; index < past_time_dt.size(); ++index) - { - const float t = time - abs(past_time_dt[index]); - Vector3 pos{}; - Quaternion rot{}; - if (t >= 0.0f) - { // The offset can be accessed through the anim data - pos = animation->position_track_interpolate(root_tracks[0], t) - curr_pos; - rot = animation->rotation_track_interpolate(root_tracks[1], t); - pos = curr_rot.xform_inv(pos); - } - else - { // The offset must be calculated using the starting velocity and extrapoling - pos = start_pos + (start_vel * t) - curr_pos; - pos = curr_rot.xform_inv(pos); - } - result.push_back(pos.x); - result.push_back(pos.z); - } - for (size_t index = 0; index < future_time_dt.size(); ++index) - { - const float t = time + abs(future_time_dt[index]); - Vector3 pos{}; - Quaternion rot{}; - if (t <= end_time) - { // The offset can be accessed through the anim data - pos = animation->position_track_interpolate(root_tracks[0], t) - curr_pos; - rot = animation->rotation_track_interpolate(root_tracks[1], t); - pos = curr_rot.xform_inv(pos); - } - else - { // The offset must be calculated using the end velocity and extrapoling - pos = end_pos + end_vel * (t - end_time) - curr_pos; - pos = curr_rot.xform_inv(pos); - } - - - result.push_back(pos.x); - result.push_back(pos.z); - } - for (size_t index = 0; index < future_time_dt.size(); ++index) - { - const float t = time + abs(future_time_dt[index]); - Vector3 pos{}; - Quaternion rot{}; - if (t <= end_time) - { // The offset can be accessed through the anim data - rot = animation->rotation_track_interpolate(root_tracks[1], t) * curr_rot.inverse(); - } - else - { // The offset must be calculated using the end velocity and extrapoling - rot = animation->rotation_track_interpolate(root_tracks[1], animation->get_length() - delta) * curr_rot.inverse(); - } - - result.push_back(rot.get_euler_xyz().y); - } - return result; - } - - GETSET(PackedVector3Array,history_pos) - GETSET(PackedVector3Array,future_pos) - GETSET(PackedFloat32Array,future_dir) - - PackedFloat32Array serialize_trajectory_local(PackedVector3Array history_pos,PackedVector3Array future_pos,PackedFloat32Array future_dir) - { - PackedFloat32Array result{}; - for(auto elem: history_pos) - { - result.append(elem.x); - result.append(elem.z); - } - for(auto elem: future_pos) - { - result.append(elem.x); - result.append(elem.z); - } - result.append_array(future_dir); - return result; - } - - protected: - static void _bind_methods() { - - { - ClassDB::bind_method(D_METHOD("serialize_trajectory_local", "history_local_pos","prediction_local_pos","prediction_local_dir_angle"), &MFTrajectory::serialize_trajectory_local); - } - - ClassDB::bind_method( D_METHOD("set_weight_history_pos","value"), &MFTrajectory::set_weight_history_pos ); ClassDB::bind_method( D_METHOD("get_weight_history_pos"), &MFTrajectory::get_weight_history_pos); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"weight_history_pos"), "set_weight_history_pos", "get_weight_history_pos"); - ClassDB::bind_method( D_METHOD("set_weight_prediction_pos","value"), &MFTrajectory::set_weight_prediction_pos ); ClassDB::bind_method( D_METHOD("get_weight_prediction_pos"), &MFTrajectory::get_weight_prediction_pos); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"weight_prediction_pos"), "set_weight_prediction_pos", "get_weight_prediction_pos"); - ClassDB::bind_method( D_METHOD("set_weight_prediction_angle","value"), &MFTrajectory::set_weight_prediction_angle ); ClassDB::bind_method( D_METHOD("get_weight_prediction_angle"), &MFTrajectory::get_weight_prediction_angle); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"weight_prediction_angle"), "set_weight_prediction_angle", "get_weight_prediction_angle"); - - ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); - { - PackedFloat32Array m_default{}; - m_default.push_back(0.2);m_default.push_back(0.4); - ClassDB::bind_method( D_METHOD("set_past_time_dt","value"), &MFTrajectory::set_past_time_dt,(m_default)); ClassDB::bind_method( D_METHOD("get_past_time_dt"), &MFTrajectory::get_past_time_dt); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY,"past_time_dt"), "set_past_time_dt", "get_past_time_dt"); - ClassDB::bind_method( D_METHOD("set_future_time_dt","value"), &MFTrajectory::set_future_time_dt ); ClassDB::bind_method( D_METHOD("get_future_time_dt"), &MFTrajectory::get_future_time_dt); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY,"future_time_dt"), "set_future_time_dt", "get_future_time_dt"); - - ClassDB::bind_method(D_METHOD("set_debug_color_history", "value"), &MFTrajectory::set_debug_color_history); - ClassDB::bind_method(D_METHOD("get_debug_color_history"), &MFTrajectory::get_debug_color_history); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_history"), "set_debug_color_history", "get_debug_color_history"); - - ClassDB::bind_method(D_METHOD("set_debug_color_future", "value"), &MFTrajectory::set_debug_color_future); - ClassDB::bind_method(D_METHOD("get_debug_color_future"), &MFTrajectory::get_debug_color_future); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_future"), "set_debug_color_future", "get_debug_color_future"); - - } - ClassDB::add_property_group(get_class_static(), "Queries to fill", "query"); - { - //BINDER_PROPERTY_PARAMS(MFTrajectory, Variant::PACKED_VECTOR3_ARRAY, history_pos); - ClassDB::bind_method(D_METHOD("set_history_pos", "value"), &MFTrajectory::set_history_pos); - ClassDB::bind_method(D_METHOD("get_history_pos"), &MFTrajectory::get_history_pos); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "query_history_pos"), "set_history_pos", "get_history_pos"); - - //BINDER_PROPERTY_PARAMS(MFTrajectory, Variant::PACKED_VECTOR3_ARRAY, future_pos); - ClassDB::bind_method(D_METHOD("set_future_pos", "value"), &MFTrajectory::set_future_pos); - ClassDB::bind_method(D_METHOD("get_future_pos"), &MFTrajectory::get_future_pos); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "query_future_pos"), "set_future_pos", "get_future_pos"); - - //BINDER_PROPERTY_PARAMS(MFTrajectory, Variant::PACKED_FLOAT32_ARRAY, future_dir); - ClassDB::bind_method(D_METHOD("set_future_dir", "value"), &MFTrajectory::set_future_dir); - ClassDB::bind_method(D_METHOD("get_future_dir"), &MFTrajectory::get_future_dir); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "query_future_dir"), "set_future_dir", "get_future_dir"); - } - ClassDB::add_property_group(get_class_static(), "", ""); - - ClassDB::bind_method( D_METHOD("get_weights"), &MFTrajectory::get_weights); - ClassDB::bind_method( D_METHOD("get_dimension"), &MFTrajectory::get_dimension); - - ClassDB::bind_method( D_METHOD("setup_for_animation","animation"), &MFTrajectory::setup_for_animation); - ClassDB::bind_method( D_METHOD("bake_animation_pose","animation","time"), &MFTrajectory::bake_animation_pose); - - ClassDB::bind_method( D_METHOD("debug_pose_gizmo","gizmo","data","root_transform"), &MFTrajectory::debug_pose_gizmo); - } - - GETSET(Color,debug_color_history,godot::Color(1.0f,1.0f,1.0f)); - GETSET(Color,debug_color_future,godot::Color(0.0f,0.0f,0.0f)); - - virtual void debug_pose_gizmo(Ref gizmo, const PackedFloat32Array data,godot::Transform3D tr = godot::Transform3D{})override - { - const auto mat_name_history = "history" + get_path(); - const auto mat_name_future = "future" + get_path(); - if(gizmo->get_plugin()->get_material(mat_name_history,gizmo) == nullptr) - { - gizmo->get_plugin()->create_material(mat_name_history,debug_color_history); - } - if(gizmo->get_plugin()->get_material(mat_name_future,gizmo) == nullptr) - { - gizmo->get_plugin()->create_material(mat_name_future,debug_color_future); - } - // if (data.size() == get_dimension()) - { - constexpr int s = 3; - auto history = gizmo->get_plugin()->get_material(mat_name_history,gizmo); - history->set_albedo(debug_color_history); - auto future = gizmo->get_plugin()->get_material(mat_name_future,gizmo); - future->set_albedo(debug_color_future); - for(size_t i = 0; i < past_time_dt.size(); ++i) - { - const size_t offset = i * 2; - Vector3 pos = Vector3(data[offset + 0],0,data[offset + 1]); - pos = tr.xform(pos); - gizmo->add_lines(Array::make(pos, pos + Vector3(0,1,0)), history); - } - const size_t pos_offset = past_time_dt.size(); - const size_t traj_offset = past_time_dt.size() * 2 + future_time_dt.size() * 2; - for(size_t i = 0; i < future_time_dt.size(); ++i) - { - const size_t offset = (pos_offset + i) * 2; - Vector3 pos = Vector3(data[offset + 0],0,data[offset + 1]); - Vector3 traj = tr.xform(Vector3(0,0,1)).rotated(Vector3(0,1,0),data[traj_offset + i]); - pos = tr.xform(pos); - // traj = tr.xform(traj); - gizmo->add_lines(Array::make(pos, pos + traj), future); - } - - } - } -}; - -#undef MAKE_RESOURCE_TYPE_HINT -#undef GETSET -#undef STR -#undef STRING_PREFIX \ No newline at end of file + int get_dimension() const { + int result = 0; + for (int i = 0; i < options.size(); ++i) { + auto *option = cast_to(options[i]); + if (option) { + result += option->get_dimensions(); + } + } + return result; + } + + PackedFloat32Array get_weights() const { + PackedFloat32Array result{}; + for (int i = 0; i < options.size(); ++i) { + auto *option = cast_to(options[i]); + if (option) { + float weight = option->weights; + for (int w = 0; w < option->get_dimensions(); ++w) { + result.append(weight); + } + } + } + // Standardize + return MMUtil::softmax(result); + } + + PackedStringArray get_hints() const { + PackedStringArray result{}; + for (int i = 0; i < options.size(); ++i) { + auto *option = cast_to(options[i]); + if (option) { + std::bitset<32> bit = option->options; + for (int o = 0; o < bit.size(); ++o) { + String I = ""; + if (o == 0 && bit.test(MFTrajectoryOptions::Options::Position)) { + I = "P"; + } else if (o == 1 && bit.test(MFTrajectoryOptions::Options::Velocity)) { + I = "V"; + } else if (o == 2 && bit.test(MFTrajectoryOptions::Options::Direction)) { + I = "D"; + } + if (I.is_empty()) { + continue; + } + if (option->coordinate == MFTrajectoryOptions::Coordinates::XZ) { + result.append(vformat("%sx%f",I,option->time_offset)); + result.append(vformat("%sz%f",I,option->time_offset)); + } else if (option->coordinate == MFTrajectoryOptions::Coordinates::XYZ) { + result.append(vformat("%sx%f",I,option->time_offset)); + result.append(vformat("%sy%f",I,option->time_offset)); + result.append(vformat("%sz%f",I,option->time_offset)); + } + } + } + } + return result; + } + + SkeletonProfile *profile = nullptr; + MMAnimationLibrary *m_library = nullptr; + + kform _get_global_root_kform(Ref p_lib, Ref animation, float time) { + auto root_path = u::str(p_lib->get_skeleton_path()) + ":" + p_lib->get_skeleton_profile()->get_root_bone(); + if (0.0 <= time && time <= animation->get_length()) { + // In range + return get_global_kform(p_lib->skeleton_profile, animation, time, root_bone_track); + } else if (animation->get_loop_mode() == Animation::LOOP_NONE) { + // Take last ( or first) velocities and extrapolate as if it continue. + auto starting_kform = get_global_kform(p_lib->skeleton_profile, animation, 0.0, root_path); + auto ending_kform = get_global_kform(p_lib->skeleton_profile, animation, animation->get_length() - 0.032f, root_path); + kform to_extrapolate = std::signbit(time) ? starting_kform : ending_kform; + float delta = std::signbit(time) ? time : time - animation->get_length(); + to_extrapolate.pos += to_extrapolate.vel * delta; + to_extrapolate.rot = Spring::quat_integrate_angular_velocity(to_extrapolate.ang, to_extrapolate.rot, delta); + return to_extrapolate; + } else if (animation->get_loop_mode() == Animation::LOOP_LINEAR) { + // Loop X time, then add the kform at the modulo. + auto starting_kform = get_global_kform(p_lib->skeleton_profile, animation, 0.0, root_path); + auto ending_kform = get_global_kform(p_lib->skeleton_profile, animation, animation->get_length() - 0.032f, root_path); + float delta = std::signbit(time) ? time : time - animation->get_length(); + Transform3D entire_k = std::signbit(time) ? (starting_kform.inverse() * ending_kform).inverse() : (starting_kform.inverse() * ending_kform); + Transform3D looped = starting_kform; + for (int loop = 0; loop < abs(floor((time) / animation->get_length())); ++loop) { + looped = looped * entire_k; + } + kform reference = starting_kform.remove_velocities(); + float safe_time = Math::fposmod(time, (float)animation->get_length()); + kform safe_k = reference.inverse() * get_global_kform(p_lib->skeleton_profile, animation, safe_time, root_bone_track); + + return (kform)looped * safe_k; + } else { + return get_global_kform(p_lib->skeleton_profile, animation, time, root_bone_track); + } + } + + PackedFloat32Array bake_pose(Ref mmlib, String animation_name, float time) { + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_path.is_empty(), {}, "SkeletonPath is Empty"); + ERR_FAIL_COND_V_EDMSG(mmlib->skeleton_profile == nullptr, {}, "SkeletonProfile is null"); + ERR_FAIL_COND_V_EDMSG(mmlib->get_skeleton_profile()->get_root_bone().is_empty(), {}, "No Root bone to extract data"); + root_bone_track = String(mmlib->skeleton_path) + ':' + mmlib->skeleton_profile->get_root_bone(); + PackedFloat32Array result{}; + Ref animation = mmlib->get_animation(animation_name); + + const kform current = get_global_kform(mmlib->skeleton_profile, animation, time, root_bone_track); + for (int i = 0; i < options.size(); ++i) { + auto *option = cast_to(options[i]); + if (option) { + kform global = _get_global_root_kform(mmlib, animation, time + option->time_offset); + kform difference = current.remove_velocities().inverse() * global; + std::bitset<32> bit = option->options; + for (int o = 0; o < bit.size(); ++o) { + Vector3 I{}; + if (o == MFTrajectoryOptions::Options::Position && bit.test(MFTrajectoryOptions::Options::Position)) { + I = difference.pos; + } else if (o == MFTrajectoryOptions::Options::Velocity && bit.test(MFTrajectoryOptions::Options::Velocity)) { + I = difference.vel; + } else if (o == MFTrajectoryOptions::Options::Direction && bit.test(MFTrajectoryOptions::Options::Direction)) { + I = difference.rot.xform(Vector3(0, 0, 1)); + } else { + continue; + } + if (option->coordinate == MFTrajectoryOptions::Coordinates::XZ) { + result.append(I.x); + result.append(I.z); + } else if (option->coordinate == MFTrajectoryOptions::Coordinates::XYZ) { + result.append(I.x); + result.append(I.y); + result.append(I.z); + } + } + } + } + return result; + } + + // TODO : Add check for same nb of kform + PackedFloat32Array serialize_trajectory_local(const TypedArray local_kform) { + ERR_FAIL_COND_V_MSG(local_kform.size() != options.size(), {}, "local_kform isn't the same size as the number of options."); + PackedFloat32Array result{}; + for (int i = 0; i < options.size(); ++i) { + MFTrajectoryOptions *opt = cast_to(options[i]); + ERR_FAIL_COND_V_EDMSG(opt == nullptr, {}, "MFTrajectoryOption object is null"); + const PackedFloat32Array _to_append = opt->serialize(*cast_to(local_kform[i])); + result.append_array(_to_append); + } + return result; + } + + virtual void show_debug_info(Ref gizmo, Ref library, String animation_name, float time, Skeleton3D *skel) { + auto root_bone_tr = skel->get_bone_global_pose(skel->find_bone(library->skeleton_profile->get_root_bone())); + + Ref animation = library->get_animation(animation_name); + const kform current = get_global_kform(library->skeleton_profile, animation, time, root_bone_track); + for (int i = 0; i < options.size(); ++i) { + auto *option = cast_to(options[i]); + + if (option) { + kform global = _get_global_root_kform(library, animation, time + option->time_offset); + kform offset = current.remove_velocities().inverse() * global; + + const auto material_name = "traj" + get_path(); + if (gizmo->get_plugin()->get_material(material_name, gizmo) == nullptr) { + gizmo->get_plugin()->create_material(material_name, option->debug_color); + } + auto mat = gizmo->get_plugin()->get_material(material_name, gizmo); + + Ref mesh{}; + mesh.instantiate(); + mesh->set_size(Vector3(1, 1.1, 1) * 0.1); + std::bitset<32> bit = option->options; + Transform3D global_point{}; + global_point.origin = global.pos; + global_point.set_basis(global.rot); + gizmo->add_mesh(mesh, mat, global_point.rotated_local(Vector3(1, 0, 0), godot::Math::deg_to_rad(90.0))); + + if (bit.test(MFTrajectoryOptions::Options::Velocity)) { + gizmo->add_lines(Array::make(global.pos, root_bone_tr.xform(offset.vel)), mat); + } + } + } + } + +protected: + static void _bind_methods() { + { + ClassDB::bind_method(D_METHOD("serialize_trajectory_local", "local_kforms"), &MFTrajectory::serialize_trajectory_local); + // ClassDB::bind_method(D_METHOD("serialize", "unnormalized_values_local_to_character"), &MFTrajectory::serialize); + } + + ClassDB::bind_method(D_METHOD("get_hints"), &MFTrajectory::get_hints); + + ClassDB::bind_method(D_METHOD("set_options", "value"), &MFTrajectory::set_options); + ClassDB::bind_method(D_METHOD("get_options"), &MFTrajectory::get_options); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::ARRAY, "options", godot::PROPERTY_HINT_TYPE_STRING, u::str(Variant::OBJECT) + '/' + u::str(Variant::BASIS) + ":MFTrajectoryOptions", PROPERTY_USAGE_DEFAULT), "set_options", "get_options"); + + ClassDB::bind_method(D_METHOD("set_debug_color_history", "value"), &MFTrajectory::set_debug_color_history); + ClassDB::bind_method(D_METHOD("get_debug_color_history"), &MFTrajectory::get_debug_color_history); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_history"), "set_debug_color_history", "get_debug_color_history"); + + ClassDB::bind_method(D_METHOD("set_weight_history_pos", "value"), &MFTrajectory::set_weight_history_pos); + ClassDB::bind_method(D_METHOD("get_weight_history_pos"), &MFTrajectory::get_weight_history_pos); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_history_pos"), "set_weight_history_pos", "get_weight_history_pos"); + ClassDB::bind_method(D_METHOD("set_weight_prediction_pos", "value"), &MFTrajectory::set_weight_prediction_pos); + ClassDB::bind_method(D_METHOD("get_weight_prediction_pos"), &MFTrajectory::get_weight_prediction_pos); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_prediction_pos"), "set_weight_prediction_pos", "get_weight_prediction_pos"); + ClassDB::bind_method(D_METHOD("set_weight_prediction_angle", "value"), &MFTrajectory::set_weight_prediction_angle); + ClassDB::bind_method(D_METHOD("get_weight_prediction_angle"), &MFTrajectory::get_weight_prediction_angle); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "weight_prediction_angle"), "set_weight_prediction_angle", "get_weight_prediction_angle"); + + ClassDB::add_property_group(get_class_static(), "Nodes & Resources Sources", ""); + { + ClassDB::bind_method(D_METHOD("set_debug_color_future", "value"), &MFTrajectory::set_debug_color_future); + ClassDB::bind_method(D_METHOD("get_debug_color_future"), &MFTrajectory::get_debug_color_future); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::COLOR, "debug_color_future"), "set_debug_color_future", "get_debug_color_future"); + } + ClassDB::add_property_group(get_class_static(), "Queries to fill", "query"); + { + //BINDER_PROPERTY_PARAMS(MFTrajectory, Variant::PACKED_VECTOR3_ARRAY, history_pos); + } + ClassDB::add_property_group(get_class_static(), "", ""); + + ClassDB::bind_method(D_METHOD("get_weights"), &MFTrajectory::get_weights); + ClassDB::bind_method(D_METHOD("get_dimension"), &MFTrajectory::get_dimension); + + ClassDB::bind_method(D_METHOD("bake_pose", "animation_library", "animation_name", "time"), &MFTrajectory::bake_pose); + ClassDB::bind_method(D_METHOD("show_debug_info", "gizmo", "lib", "animation_name", "timestamp" + "skeleton"), + &MFTrajectory::show_debug_info); + } + + GETSET(Color, debug_color_history, godot::Color(1.0f, 1.0f, 1.0f)); + GETSET(Color, debug_color_future, godot::Color(0.0f, 0.0f, 0.0f)); +}; \ No newline at end of file diff --git a/src/MotionFeatures/MotionFeatures.hpp b/src/MotionFeatures/MotionFeatures.hpp index 4f744b1d..fdbdcd32 100644 --- a/src/MotionFeatures/MotionFeatures.hpp +++ b/src/MotionFeatures/MotionFeatures.hpp @@ -1,17 +1,21 @@ #pragma once +#include #include +#include + +#include "godot_cpp/core/math.hpp" #include #include #include +#include #include #include -#include #include #include -#include +#include #include @@ -19,55 +23,123 @@ #include #include -#include #include -#include +#include +#include #include #include #include -#include - -struct MotionFeature : public Resource { - GDCLASS(MotionFeature,Resource) - - virtual ~MotionFeature() = default; - - static constexpr float delta = 0.016f; - - virtual int get_dimension(){return 0;} - - virtual PackedFloat32Array get_weights(){ return {};} - - virtual bool setup_profile(NodePath skeleton_path,Ref skel_profile){ - // returning false will abort the process. - // feel free to print more details - return true; - } +#include +#include +#include - virtual bool setup_for_animation(Ref animation){ - // returning false will skip this animation and print a warning - // feel free to print more details - return true; - } - virtual PackedFloat32Array bake_animation_pose(Ref animation,float time){return {};} +#include - virtual void debug_pose_gizmo(Ref gizmo, const PackedFloat32Array data,godot::Transform3D tr = godot::Transform3D{}){return;} +class MMAnimationLibrary; - - static void _bind_methods() { - - ClassDB::bind_method( D_METHOD("get_dimension"), &MotionFeature::get_dimension); - - ClassDB::bind_method( D_METHOD("get_weights"), &MotionFeature::get_weights); - - ClassDB::bind_method( D_METHOD("setup_profile","skeleton_path","skeleton_profile"), &MotionFeature::setup_profile); - - ClassDB::bind_method( D_METHOD("setup_for_animation","animation"), &MotionFeature::setup_for_animation); - ClassDB::bind_method( D_METHOD("bake_animation_pose","animation","time"), &MotionFeature::bake_animation_pose); - - ClassDB::bind_method( D_METHOD("debug_pose_gizmo","gizmo","data","root_transform"), &MotionFeature::debug_pose_gizmo); - - } +struct MotionFeature : public Resource { + GDCLASS(MotionFeature, Resource) + +public: + enum NormalizationType { + Standardized, + RawValue + }; + + void _notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: + set_local_to_scene(true); + break; + } + } + + GETSET(NormalizationType, normalization_type, RawValue); + + static constexpr float delta = 0.016f; + + GDVIRTUAL0RC(int, get_dimension); + GDVIRTUAL0RC(PackedStringArray, get_hints); + GDVIRTUAL0RC(PackedFloat32Array, get_weights); + GDVIRTUAL0RC(Color, get_tag_color); + + GDVIRTUAL3R(PackedFloat32Array, bake_pose, Ref, String, float); + GDVIRTUAL5C(show_debug_info, Ref, Ref, String, float, Skeleton3D *); + + static void _bind_methods() { + BIND_ENUM_CONSTANT(Standardized); + BIND_ENUM_CONSTANT(RawValue); + + BINDER_PROPERTY_PARAMS(MotionFeature, Variant::INT, normalization_type, godot::PROPERTY_HINT_ENUM, "Standardized,RawValue"); + + // REFACTOR : This is the new API. + GDVIRTUAL_BIND(get_dimension); + GDVIRTUAL_BIND(get_weights); + GDVIRTUAL_BIND(get_hints); + + GDVIRTUAL_BIND(bake_pose, "mmanimationlibrary", "animation_name", "time"); + GDVIRTUAL_BIND(show_debug_info, "gizmo", "profile", "animation", "timestamp", "skeleton"); + + { + MethodInfo mi; + mi.arguments.push_back(PropertyInfo(Variant::STRING, "variants")); + mi.name = "serialize_variants"; + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "serialize_variants", &MotionFeature::serialize_variants, mi); + } + } + + auto serialize_variants(const Variant **args, GDExtensionInt arg_count, GDExtensionCallError &error) -> PackedFloat32Array { + PackedFloat32Array result{}; + for (auto i = 0; i < arg_count; ++i) { + serialize_variant(*(args[i]), result); + } + return result; + } + + static void serialize_variant(const Variant &v, PackedFloat32Array &result) { + using namespace godot; + if (v.get_type() == Variant::BOOL) { + result.append((bool)v); + } else if (v.get_type() == Variant::INT) { + result.append((int)v); + } else if (v.get_type() == Variant::FLOAT) { + result.append((real_t)v); + } else if (v.get_type() == Variant::VECTOR2) { + godot::Vector2 v2 = (Vector2)v; + result.append(v2.x); + result.append(v2.y); + } else if (v.get_type() == Variant::VECTOR2I) { + godot::Vector2i v2 = (Vector2i)v; + result.append(v2.x); + result.append(v2.y); + } else if (v.get_type() == Variant::VECTOR3) { + Vector3 v3 = (Vector3)v; + result.append(v3.x); + result.append(v3.y); + result.append(v3.z); + } else if (v.get_type() == Variant::VECTOR3I) { + Vector3i v3 = (Vector3i)v; + result.append(v3.x); + result.append(v3.y); + result.append(v3.z); + } else if (v.get_type() == Variant::VECTOR4I) { + Vector4i v3 = (Vector4i)v; + result.append(v3.w); + result.append(v3.x); + result.append(v3.y); + result.append(v3.z); + } else if (v.get_type() == Variant::QUATERNION) { + Quaternion q = (Quaternion)v; + result.append(q.x); + result.append(q.y); + result.append(q.z); + result.append(q.w); + } else if (v.get_type() == Variant::PACKED_FLOAT32_ARRAY) { + result.append_array((PackedFloat32Array)v); + } + } }; + +VARIANT_ENUM_CAST(MotionFeature::NormalizationType); diff --git a/src/PostProcessAnimation/MMIKLookAt3D.hpp b/src/PostProcessAnimation/MMIKLookAt3D.hpp new file mode 100644 index 00000000..5e63204e --- /dev/null +++ b/src/PostProcessAnimation/MMIKLookAt3D.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace godot; + +struct MMIKLookAt3D : godot::SkeletonModifier3D { + GDCLASS(MMIKLookAt3D, SkeletonModifier3D); + +public: + using u = godot::UtilityFunctions; + + GETSET(String, bone); + + virtual void _process_modification() override { + // Find delta + const float delta = get_skeleton()->get_modifier_callback_mode_process() == Skeleton3D::ModifierCallbackModeProcess::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? get_process_delta_time() : get_physics_process_delta_time(); + if (is_active()) + advance(delta); + } + + void advance(double delta) { + if (is_active() == false || bone.is_empty()) + return; + auto * skeleton = get_skeleton(); + int bone_id = skeleton->find_bone(bone); + if (bone_id == -1) + return; + + Vector3 target_pos = get_global_position(); + Vector3 bone_global_pos = skeleton->get_global_position() + skeleton->get_bone_global_pose(bone_id).origin; + Quaternion bone_global_rot = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(bone_id).basis.get_rotation_quaternion(); + + Vector3 tar_pos = bone_global_rot.xform_inv(target_pos - bone_global_pos); + + Quaternion diff = Quaternion(get_global_basis().get_rotation_quaternion().xform(Vector3(0, 0, -1)), tar_pos.normalized()); + + // Rotate the head to face toward the target + Quaternion bone_local_rot = skeleton->get_bone_pose_rotation(bone_id); + skeleton->set_bone_pose_rotation(bone_id, bone_local_rot * diff); + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone", "value"), &MMIKLookAt3D::set_bone); + ClassDB::bind_method(D_METHOD("get_bone"), &MMIKLookAt3D::get_bone); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING, "bone"), "set_bone", "get_bone"); + } +}; \ No newline at end of file diff --git a/src/PostProcessAnimation/MMIKTwoBone3D.hpp b/src/PostProcessAnimation/MMIKTwoBone3D.hpp new file mode 100644 index 00000000..dfd28dd5 --- /dev/null +++ b/src/PostProcessAnimation/MMIKTwoBone3D.hpp @@ -0,0 +1,176 @@ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace godot; + +struct MMIKTwoBone3D : godot::SkeletonModifier3D { + GDCLASS(MMIKTwoBone3D, SkeletonModifier3D); + +public: + using u = godot::UtilityFunctions; + + GETSET(String, bone_A); // UpLeg + GETSET(String, bone_B); // Leg + GETSET(String, bone_C); // Heel + + GETSET(Vector3, forward, Vector3(0, 0, -1)); + + enum Bone_Type { + BONE_ROOT, + BONE_MIDDLE, + BONE_REACH, + BONE_PARENT, + + BONE_COUNT + }; + + virtual void _process_modification() override { + // Find delta + const float delta = get_skeleton()->get_modifier_callback_mode_process() == Skeleton3D::ModifierCallbackModeProcess::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? get_process_delta_time() : get_physics_process_delta_time(); + if (is_active()) + advance(delta); + } + + void advance(double delta) { + auto * skeleton = get_skeleton(); + kforms locals{ BONE_COUNT }, globals{ BONE_COUNT }; + if (bone_A.is_empty() || bone_B.is_empty() || bone_C.is_empty()) { + return; + }; + + int bone_A_id = skeleton->find_bone(bone_A); + int bone_B_id = skeleton->find_bone(bone_B); + int bone_C_id = skeleton->find_bone(bone_C); + if (bone_A_id == -1 || bone_B_id == -1 || bone_C_id == -1) { + return; + }; + + locals.pos[BONE_ROOT] = skeleton->get_bone_pose_position(bone_A_id); + locals.rot[BONE_ROOT] = skeleton->get_bone_pose_rotation(bone_A_id); + globals.pos[BONE_ROOT] = skeleton->get_bone_global_pose(bone_A_id).origin; + globals.rot[BONE_ROOT] = skeleton->get_bone_global_pose(bone_A_id).basis.get_rotation_quaternion(); + + locals.pos[BONE_MIDDLE] = skeleton->get_bone_pose_position(bone_B_id); + locals.rot[BONE_MIDDLE] = skeleton->get_bone_pose_rotation(bone_B_id); + kform middle_global = (kform)globals[BONE_ROOT] * (kform)locals[BONE_MIDDLE]; + globals.pos[BONE_MIDDLE] = skeleton->get_bone_global_pose(bone_B_id).origin; + globals.rot[BONE_MIDDLE] = skeleton->get_bone_global_pose(bone_B_id).basis.get_rotation_quaternion(); + + locals.pos[BONE_REACH] = skeleton->get_bone_pose_position(bone_C_id); + locals.rot[BONE_REACH] = skeleton->get_bone_pose_rotation(bone_C_id); + kform reach_global = (kform)globals[BONE_MIDDLE] * (kform)locals[BONE_REACH]; + globals.pos[BONE_REACH] = skeleton->get_bone_global_pose(bone_C_id).origin; + globals.rot[BONE_REACH] = skeleton->get_bone_global_pose(bone_C_id).basis.get_rotation_quaternion(); + + auto parent_id = skeleton->get_bone_parent(bone_A_id); + locals.pos[BONE_PARENT] = skeleton->get_bone_pose_position(parent_id); + locals.rot[BONE_PARENT] = skeleton->get_bone_pose_rotation(parent_id); + globals.pos[BONE_PARENT] = skeleton->get_bone_global_pose(parent_id).origin; + globals.rot[BONE_PARENT] = skeleton->get_bone_global_pose(parent_id).basis.get_rotation_quaternion(); + + auto target_local_to_skeleton = skeleton->get_global_transform().inverse() * get_global_transform(); + auto target_rotation = (globals.rot[BONE_MIDDLE]).xform(forward); + + if (Engine::get_singleton()->is_editor_hint()) { + target_rotation = target_local_to_skeleton.xform(forward); + } + + op_two_bone_ik_static( + locals, globals, target_local_to_skeleton.origin, target_rotation); + + skeleton->set_bone_pose_rotation(bone_A_id, locals.rot[BONE_ROOT].normalized()); + skeleton->set_bone_pose_rotation(bone_B_id, locals.rot[BONE_MIDDLE].normalized()); + // skeleton->set_bone_global_pose_override(bone_A_id,(Transform3D)globals[BONE_ROOT],1.0,true); + // skeleton->set_bone_global_pose_override(bone_B_id,(Transform3D)globals[BONE_MIDDLE],1.0,true); + + skeleton->force_update_bone_child_transform(parent_id); + emit_signal("post_calculation"); + } + + void op_two_bone_ik_static( + kforms &local, + kforms &global, + const Vector3 heel_target, + const Vector3 fwd = Vector3(0.0f, 1.0f, 0.0f), + const float max_length_buffer = 0.01f) { + float max_extension = + (global.pos[BONE_ROOT] - global.pos[BONE_MIDDLE]).length() + + (global.pos[BONE_MIDDLE] - global.pos[BONE_REACH]).length() - + max_length_buffer; + + Vector3 target_clamp = heel_target; + if ((heel_target - global.pos[BONE_ROOT]).length() > max_extension) { + target_clamp = global.pos[BONE_ROOT] + max_extension * (heel_target - global.pos[BONE_ROOT]).normalized(); + } + + Vector3 axis_dwn = (global.pos[BONE_REACH] - global.pos[BONE_ROOT]).normalized(); + Vector3 axis_rot = (axis_dwn.cross(fwd)).normalized(); + + Vector3 a = global.pos[BONE_ROOT]; + Vector3 b = global.pos[BONE_MIDDLE]; + Vector3 c = global.pos[BONE_REACH]; + Vector3 t = target_clamp; + + float lab = (b - a).length(); + float lcb = (b - c).length(); + float lat = (t - a).length(); + + float ac_ab_0 = acosf(u::clampf((c - a).normalized().dot((b - a).normalized()), -1.0f, 1.0f)); + float ba_bc_0 = acosf(u::clampf((a - b).normalized().dot((c - b).normalized()), -1.0f, 1.0f)); + + float ac_ab_1 = acosf(u::clampf((lab * lab + lat * lat - lcb * lcb) / (2.0f * lab * lat), -1.0f, 1.0f)); + float ba_bc_1 = acosf(u::clampf((lab * lab + lcb * lcb - lat * lat) / (2.0f * lab * lcb), -1.0f, 1.0f)); + + Quaternion r0 = Quaternion(axis_rot, ac_ab_1 - ac_ab_0); + Quaternion r1 = Quaternion(axis_rot, ba_bc_1 - ba_bc_0); + + Vector3 c_a = (global.pos[BONE_REACH] - global.pos[BONE_ROOT]).normalized(); + Vector3 t_a = (target_clamp - global.pos[BONE_ROOT]).normalized(); + + Quaternion r2 = Quaternion(c_a.cross(t_a).normalized(), acosf(u::clampf(c_a.dot(t_a), -1.0f, 1.0f))); + + local.rot[BONE_ROOT] = global.rot[BONE_PARENT].inverse() * (r2 * r0 * global.rot[BONE_ROOT]); + local.rot[BONE_MIDDLE] = global.rot[BONE_ROOT].inverse() * r1 * global.rot[BONE_MIDDLE]; + + global[BONE_ROOT] = (kform)global[BONE_PARENT] * (kform)local[BONE_ROOT]; + global[BONE_MIDDLE] = (kform)global[BONE_ROOT] * (kform)local[BONE_MIDDLE]; + global[BONE_REACH] = (kform)global[BONE_MIDDLE] * (kform)local[BONE_REACH]; + } + +protected: + static void _bind_methods() { + + ClassDB::bind_method(D_METHOD("set_bone_A", "value"), &MMIKTwoBone3D::set_bone_A); + ClassDB::bind_method(D_METHOD("get_bone_A"), &MMIKTwoBone3D::get_bone_A); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING, "bone_A"), "set_bone_A", "get_bone_A"); + ClassDB::bind_method(D_METHOD("set_bone_B", "value"), &MMIKTwoBone3D::set_bone_B); + ClassDB::bind_method(D_METHOD("get_bone_B"), &MMIKTwoBone3D::get_bone_B); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING, "bone_B"), "set_bone_B", "get_bone_B"); + ClassDB::bind_method(D_METHOD("set_bone_C", "value"), &MMIKTwoBone3D::set_bone_C); + ClassDB::bind_method(D_METHOD("get_bone_C"), &MMIKTwoBone3D::get_bone_C); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING, "bone_C"), "set_bone_C", "get_bone_C"); + + ClassDB::bind_method(D_METHOD("set_forward", "value"), &MMIKTwoBone3D::set_forward); + ClassDB::bind_method(D_METHOD("get_forward"), &MMIKTwoBone3D::get_forward); + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::VECTOR3, "forward"), "set_forward", "get_forward"); + } +}; \ No newline at end of file diff --git a/src/PostProcessAnimation/MMInertialization3D.hpp b/src/PostProcessAnimation/MMInertialization3D.hpp new file mode 100644 index 00000000..b5fdc85f --- /dev/null +++ b/src/PostProcessAnimation/MMInertialization3D.hpp @@ -0,0 +1,204 @@ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +using namespace godot; + +// TODO : Inherit SkeletonModifier +struct MMInertialization3D : godot::SkeletonModifier3D { + GDCLASS(MMInertialization3D, SkeletonModifier3D); + friend class MFBonesInfo; + +public: + using u = godot::UtilityFunctions; + + enum InertializationType { + Simple, + OffsetDecay + }; + + GETSET(InertializationType, type, Simple); + + kforms offsets = { 0 }; + kforms bones = { 0 }; + kforms bone_model = { 0 }; + + GETSET(float, halflife, 0.1f); + + virtual void _ready() override { + inertialize_reset(); + } + + void inertialize_reset() { + auto *skeleton = get_skeleton(); + if (skeleton == nullptr) + return; + const auto bone_count = skeleton->get_bone_count(); + bones.reserve(bone_count); + offsets.reserve(bone_count); + bone_model.reserve(bone_count); + for (int b = 0; b < bone_count; ++b) { + bones.reset(b); + bones.pos[b] = skeleton->get_bone_pose_position(b); + bones.rot[b] = skeleton->get_bone_pose_rotation(b); + bones.scl[b] = skeleton->get_bone_pose_scale(b); + + offsets.reset(b); + } + for (int id = 0; id < skeleton->get_bone_count(); ++id) { + int parent = skeleton->get_bone_parent(id); + if (parent != -1) { + bone_model[id] = (kform)bone_model[parent] * (kform)bones[id]; + } else { + bone_model[id] = (kform)bones[id]; + } + } + } + + virtual void _process_modification() override { + // Find delta + const float delta = get_skeleton()->get_modifier_callback_mode_process() == Skeleton3D::ModifierCallbackModeProcess::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? get_process_delta_time() : get_physics_process_delta_time(); + if (is_active()) + advance(delta); + } + + void advance(double delta) { + auto *skeleton = get_skeleton(); + if (is_active() == false || skeleton == nullptr) + return; + switch (type) { + case InertializationType::Simple: { + _simple(delta); + break; + } + case InertializationType::OffsetDecay: { + _decay(delta); + break; + } + } + + for (int id = 0; id < skeleton->get_bone_count(); ++id) { + int parent = skeleton->get_bone_parent(id); + kform step{}; + if (parent != -1) { + step = (kform)bone_model[parent] * (kform)bones[id]; + } else { + step = (kform)bones[id]; + } + bone_model.pos[id] = step.pos; + bone_model.vel[id] = step.vel; + bone_model.rot[id] = step.rot; + bone_model.ang[id] = step.ang; + bone_model.scl[id] = step.scl; + bone_model.svl[id] = step.svl; + } + } + + void _simple(double delta) { + auto *skeleton = get_skeleton(); + + bones.reserve(skeleton->get_bone_count()); + offsets.reserve(skeleton->get_bone_count()); + bone_model.reserve(skeleton->get_bone_count()); + + for (auto bone_id = 0; bone_id < skeleton->get_bone_count(); ++bone_id) { + kform desired{}; + desired.pos = skeleton->get_bone_pose_position(bone_id); + desired.rot = skeleton->get_bone_pose_rotation(bone_id); + // desired.vel = (desired.pos - bones.pos[bone_id]) / delta; + // desired.ang = Spring::quat_differentiate_angular_velocity(desired.rot, bones.rot[bone_id], delta); + + Spring::_simple_spring_damper_exact( + bones.pos[bone_id], bones.vel[bone_id], desired.pos, halflife, delta); + Spring::_simple_spring_damper_exact( + bones.rot[bone_id], bones.ang[bone_id], desired.rot, halflife, delta); + + skeleton->set_bone_pose_position(bone_id, bones.pos[bone_id]); + skeleton->set_bone_pose_rotation(bone_id, bones.rot[bone_id]); + } + } + + void _decay(double delta) { + auto *skeleton = get_skeleton(); + if (is_active() == false || skeleton == nullptr) + return; + + bones.reserve(skeleton->get_bone_count()); + offsets.reserve(skeleton->get_bone_count()); + + for (auto bone_id = 0; bone_id < skeleton->get_bone_count(); ++bone_id) { + kform desired{}; + desired.pos = skeleton->get_bone_pose_position(bone_id); + desired.rot = skeleton->get_bone_pose_rotation(bone_id); + + // Calculate offset + kform offset{}; + Spring::inertialize_transition( + offset.pos, offset.vel, + bones.pos[bone_id], bones.vel[bone_id], + desired.pos, desired.vel); + Spring::inertialize_transition( + offset.rot, offset.ang, + bones.rot[bone_id], bones.ang[bone_id], + desired.rot, desired.ang); + + // Reduce Offset + Spring::inertialize_update( + bones.pos[bone_id], bones.vel[bone_id], + offset.pos, offset.vel, + desired.pos, desired.vel, + halflife, + delta); + Spring::inertialize_update( + bones.rot[bone_id], bones.ang[bone_id], + offset.rot, offset.ang, + desired.rot, desired.ang, + halflife, + delta); + + skeleton->set_bone_pose_position(bone_id, bones.pos[bone_id]); + skeleton->set_bone_pose_rotation(bone_id, bones.rot[bone_id]); + } + } + + Dictionary get_bone_model(StringName bone) const { + return (Dictionary)bone_model[get_skeleton()->find_bone(bone)]; + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_type", "value"), &MMInertialization3D::set_type, DEFVAL(InertializationType::Simple)); + ClassDB::bind_method(D_METHOD("get_type"), &MMInertialization3D::get_type); + ADD_PROPERTY(PropertyInfo(Variant::INT, "type", godot::PROPERTY_HINT_ENUM, "Simple"), "set_type", "get_type"); + + ClassDB::bind_method(D_METHOD("set_halflife", "value"), &MMInertialization3D::set_halflife); + ClassDB::bind_method(D_METHOD("get_halflife"), &MMInertialization3D::get_halflife); + godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT, "halflife", PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater"), "set_halflife", "get_halflife"); + + ClassDB::bind_method(D_METHOD("get_bone_model", "bone"), &MMInertialization3D::get_bone_model); + + BIND_ENUM_CONSTANT(Simple); + BIND_ENUM_CONSTANT(OffsetDecay); + } +}; + +VARIANT_ENUM_CAST(MMInertialization3D::InertializationType); \ No newline at end of file diff --git a/src/PostProcessAnimation/PPIKLookAt3D.hpp b/src/PostProcessAnimation/PPIKLookAt3D.hpp deleted file mode 100644 index 5401ebe1..00000000 --- a/src/PostProcessAnimation/PPIKLookAt3D.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - -using namespace godot; - -struct PPIKLookAt3D : godot::Node3D -{ - GDCLASS(PPIKLookAt3D,Node3D); - using u = godot::UtilityFunctions; - - GETSET(AnimationMixer*,mixer,nullptr); - GETSET(Skeleton3D*,skeleton,nullptr); - - GETSET(String,bone); - GETSET(bool,active); - - virtual void _process(double delta) override{ - if( mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) - advance(delta); - } - - virtual void _physics_process(double delta) override{ - if(mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) - advance(delta); - } - - void advance(double delta) - { - if( active == false || bone.is_empty() || skeleton == nullptr) return; - int bone_id = skeleton->find_bone(bone); - if (bone_id == -1) return; - - Vector3 target_pos = get_global_position(); - Vector3 bone_global_pos = skeleton->get_global_position() + skeleton->get_bone_global_pose(bone_id).origin; - Quaternion bone_global_rot = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(bone_id).basis.get_rotation_quaternion(); - - Vector3 tar_pos = bone_global_rot.xform_inv(target_pos - bone_global_pos); - - Quaternion diff = Quaternion(get_global_basis().get_rotation_quaternion().xform(Vector3(0,0,1)),tar_pos.normalized()); - - // Rotate the head to face toward the target - Quaternion bone_local_rot = skeleton->get_bone_pose_rotation(bone_id); - skeleton->set_bone_pose_rotation(bone_id,bone_local_rot * diff); - } - - - protected: - static void _bind_methods() - { - ClassDB::bind_method( D_METHOD("set_active" ,"value"), &PPIKLookAt3D::set_active); - ClassDB::bind_method( D_METHOD("get_active" ), &PPIKLookAt3D::get_active); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"active"), "set_active", "get_active"); - - ClassDB::bind_method( D_METHOD("set_bone" ,"value"), &PPIKLookAt3D::set_bone); - ClassDB::bind_method( D_METHOD("get_bone" ), &PPIKLookAt3D::get_bone); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING,"bone"), "set_bone", "get_bone"); - - ClassDB::bind_method( D_METHOD("set_mixer" ,"value"), &PPIKLookAt3D::set_mixer); - ClassDB::bind_method( D_METHOD("get_mixer" ), &PPIKLookAt3D::get_mixer); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"mixer",PROPERTY_HINT_NODE_TYPE ,"AnimationMixer",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_mixer", "get_mixer"); - - ClassDB::bind_method( D_METHOD("set_skeleton" ,"value"), &PPIKLookAt3D::set_skeleton); - ClassDB::bind_method( D_METHOD("get_skeleton" ), &PPIKLookAt3D::get_skeleton); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"skeleton",PROPERTY_HINT_NODE_TYPE ,"Skeleton3D",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_skeleton", "get_skeleton"); - - } -}; \ No newline at end of file diff --git a/src/PostProcessAnimation/PPIKTwoBone3D.hpp b/src/PostProcessAnimation/PPIKTwoBone3D.hpp deleted file mode 100644 index bcab824c..00000000 --- a/src/PostProcessAnimation/PPIKTwoBone3D.hpp +++ /dev/null @@ -1,184 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - -using namespace godot; - -struct PPIKTwoBone3D : godot::Node3D -{ - GDCLASS(PPIKTwoBone3D,Node3D); - using u = godot::UtilityFunctions; - - GETSET(AnimationMixer*,mixer,nullptr); - GETSET(Skeleton3D*,skeleton,nullptr); - - GETSET(String,bone_A); // UpLeg - GETSET(String,bone_B); // Leg - GETSET(String,bone_C); // Heel - - GETSET(bool,active); - - enum Bone_Type{ - BONE_ROOT, - BONE_MIDDLE, - BONE_REACH, - BONE_PARENT, - - BONE_COUNT - }; - - virtual void _process(double delta) override{ - if( mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) - advance(delta); - } - - virtual void _physics_process(double delta) override{ - if(mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) - advance(delta); - } - - void advance(double delta) - { - if(active == false || skeleton == nullptr || mixer == nullptr) return; - kforms locals{BONE_COUNT},globals{BONE_COUNT}; - if(bone_A.is_empty() || bone_B.is_empty() || bone_C.is_empty()) - { - return; - }; - - int bone_A_id = skeleton->find_bone(bone_A); - int bone_B_id = skeleton->find_bone(bone_B); - int bone_C_id = skeleton->find_bone(bone_C); - if(bone_A_id == -1 || bone_B_id == -1 || bone_C_id == -1) - { - return; - }; - - locals.pos[BONE_ROOT] = skeleton->get_bone_pose_position(bone_A_id); - locals.rot[BONE_ROOT] = skeleton->get_bone_pose_rotation(bone_A_id); - globals.pos[BONE_ROOT] = skeleton->get_global_position() + skeleton->get_bone_global_pose(bone_A_id).origin; - globals.rot[BONE_ROOT] = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(bone_A_id).basis.get_rotation_quaternion(); - - locals.pos[BONE_MIDDLE] = skeleton->get_bone_pose_position(bone_B_id); - locals.rot[BONE_MIDDLE] = skeleton->get_bone_pose_rotation(bone_B_id); - globals.pos[BONE_MIDDLE] = skeleton->get_global_position() + skeleton->get_bone_global_pose(bone_B_id).origin; - globals.rot[BONE_MIDDLE] = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(bone_B_id).basis.get_rotation_quaternion(); - - locals.pos[BONE_REACH] = skeleton->get_bone_pose_position(bone_C_id); - locals.rot[BONE_REACH] = skeleton->get_bone_pose_rotation(bone_C_id); - globals.pos[BONE_REACH] = skeleton->get_global_position() + skeleton->get_bone_global_pose(bone_C_id).origin; - globals.rot[BONE_REACH] = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(bone_C_id).basis.get_rotation_quaternion(); - - auto parent_id = skeleton->get_bone_parent(bone_A_id); - locals.pos[BONE_PARENT] = skeleton->get_bone_pose_position(parent_id); - locals.rot[BONE_PARENT] = skeleton->get_bone_pose_rotation(parent_id); - globals.pos[BONE_PARENT] = skeleton->get_global_position() + skeleton->get_bone_global_pose(parent_id).origin; - globals.rot[BONE_PARENT] = skeleton->get_global_transform().get_basis().get_rotation_quaternion() * skeleton->get_bone_global_pose(parent_id).basis.get_rotation_quaternion(); - - op_two_bone_ik_static( - locals,globals,get_global_position(), get_global_basis().get_rotation_quaternion().xform(Vector3(0,0,-1)) - ); - skeleton->set_bone_pose_rotation(bone_A_id,locals.rot[BONE_ROOT]); - skeleton->set_bone_pose_rotation(bone_B_id,locals.rot[BONE_MIDDLE]); - } - - void op_two_bone_ik_static( - kforms& local, - const kforms& global, - const Vector3 heel_target, - const Vector3 fwd = Vector3(0.0f, 1.0f, 0.0f), - const float max_length_buffer = 0.01f) - { - float max_extension = - (global.pos[BONE_ROOT] - global.pos[BONE_MIDDLE]).length() + - (global.pos[BONE_MIDDLE] - global.pos[BONE_REACH]).length() - - max_length_buffer; - - Vector3 target_clamp = heel_target; - if ((heel_target - global.pos[BONE_ROOT]).length() > max_extension) - { - target_clamp = global.pos[BONE_ROOT] + max_extension * (heel_target - global.pos[BONE_ROOT]).normalized(); - } - - Vector3 axis_dwn = (global.pos[BONE_REACH] - global.pos[BONE_ROOT]).normalized(); - Vector3 axis_rot = (axis_dwn.cross(fwd)).normalized(); - - Vector3 a = global.pos[BONE_ROOT]; - Vector3 b = global.pos[BONE_MIDDLE]; - Vector3 c = global.pos[BONE_REACH]; - Vector3 t = target_clamp; - - float lab = (b - a).length(); - float lcb = (b - c).length(); - float lat = (t - a).length(); - - float ac_ab_0 = acosf(u::clampf((c - a).normalized().dot((b - a).normalized()), -1.0f, 1.0f)); - float ba_bc_0 = acosf(u::clampf((a - b).normalized().dot((c - b).normalized()), -1.0f, 1.0f)); - - float ac_ab_1 = acosf(u::clampf((lab * lab + lat * lat - lcb * lcb) / (2.0f * lab * lat), -1.0f, 1.0f)); - float ba_bc_1 = acosf(u::clampf((lab * lab + lcb * lcb - lat * lat) / (2.0f * lab * lcb), -1.0f, 1.0f)); - - - Quaternion r0 = Quaternion(axis_rot,ac_ab_1 - ac_ab_0);// Spring::quat_from_angle_axis(ac_ab_1 - ac_ab_0, axis_rot); - Quaternion r1 = Quaternion(axis_rot,ba_bc_1 - ba_bc_0);//Spring::quat_from_angle_axis(ba_bc_1 - ba_bc_0, axis_rot); - - Vector3 c_a = (global.pos[BONE_REACH] - global.pos[BONE_ROOT]).normalized(); - Vector3 t_a = (target_clamp - global.pos[BONE_ROOT]).normalized(); - - // Quaternion r2 = Spring::quat_from_angle_axis( - // acosf(u::clampf(c_a.dot(t_a), -1.0f, 1.0f)), - // (c_a.cross(t_a).normalized())); - Quaternion r2 = Quaternion(c_a.cross(t_a).normalized(),acosf(u::clampf(c_a.dot(t_a), -1.0f, 1.0f))); - - local.rot[BONE_ROOT] = global.rot[BONE_PARENT].inverse() * (r2 * r0 * global.rot[BONE_ROOT]); - // local.rot[BONE_ROOT] = quat_inv_mul(global.rot[BONE_PARENT], quat_mul(r2, quat_mul(r0, global.rot[BONE_ROOT]))); - local.rot[BONE_MIDDLE] = global.rot[BONE_ROOT]. inverse() * r1 * global.rot[BONE_MIDDLE]; - // local.rot[BONE_MIDDLE] = quat_inv_mul( global.rot[BONE_ROOT], quat_mul(r1, global.rot[BONE_MIDDLE])); - - } - - protected: - static void _bind_methods() - { - ClassDB::bind_method( D_METHOD("set_active" ,"value"), &PPIKTwoBone3D::set_active); - ClassDB::bind_method( D_METHOD("get_active" ), &PPIKTwoBone3D::get_active); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"active"), "set_active", "get_active"); - - ClassDB::bind_method( D_METHOD("set_bone_A" ,"value"), &PPIKTwoBone3D::set_bone_A); ClassDB::bind_method( D_METHOD("get_bone_A" ), &PPIKTwoBone3D::get_bone_A); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING,"bone_A"), "set_bone_A", "get_bone_A"); - ClassDB::bind_method( D_METHOD("set_bone_B" ,"value"), &PPIKTwoBone3D::set_bone_B); ClassDB::bind_method( D_METHOD("get_bone_B" ), &PPIKTwoBone3D::get_bone_B); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING,"bone_B"), "set_bone_B", "get_bone_B"); - ClassDB::bind_method( D_METHOD("set_bone_C" ,"value"), &PPIKTwoBone3D::set_bone_C); ClassDB::bind_method( D_METHOD("get_bone_C" ), &PPIKTwoBone3D::get_bone_C); godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::STRING,"bone_C"), "set_bone_C", "get_bone_C"); - - ClassDB::bind_method( D_METHOD("set_mixer" ,"value"), &PPIKTwoBone3D::set_mixer); - ClassDB::bind_method( D_METHOD("get_mixer" ), &PPIKTwoBone3D::get_mixer); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"mixer",PROPERTY_HINT_NODE_TYPE ,"AnimationMixer",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_mixer", "get_mixer"); - - ClassDB::bind_method( D_METHOD("set_skeleton" ,"value"), &PPIKTwoBone3D::set_skeleton); - ClassDB::bind_method( D_METHOD("get_skeleton" ), &PPIKTwoBone3D::get_skeleton); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"skeleton",PROPERTY_HINT_NODE_TYPE ,"Skeleton3D",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_skeleton", "get_skeleton"); - - } -}; \ No newline at end of file diff --git a/src/PostProcessAnimation/PPInertialization3D.hpp b/src/PostProcessAnimation/PPInertialization3D.hpp deleted file mode 100644 index f49bc73f..00000000 --- a/src/PostProcessAnimation/PPInertialization3D.hpp +++ /dev/null @@ -1,163 +0,0 @@ - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#define GETSET(type,variable,...) type variable{__VA_ARGS__};\ - type get_##variable(){return variable;} \ - void set_##variable(type value){variable = value;} -#define STR(x) #x -#define STRING_PREFIX(prefix,s) STR(prefix##s) -#define BINDER_PROPERTY_PARAMS(type,variant_type,variable,...)\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(set_,variable) ,"value"), &type::set_##variable);\ - ClassDB::bind_method( D_METHOD(STRING_PREFIX(get_,variable) ), &type::get_##variable); \ - ADD_PROPERTY(PropertyInfo(variant_type,#variable,__VA_ARGS__),STRING_PREFIX(set_,variable),STRING_PREFIX(get_,variable)); - -using namespace godot; - -struct PPInertialization3D : godot::Node -{ - GDCLASS(PPInertialization3D,Node); - using u = godot::UtilityFunctions; - - enum InertializationType { - Simple - }; - - GETSET(InertializationType,type,Simple); - - kforms offsets = {0}; - kforms bones = {0}; - - GETSET(AnimationMixer*,mixer,nullptr); - Skeleton3D* skeleton{nullptr}; - Skeleton3D* get_skeleton(){return skeleton;} - void set_skeleton(Skeleton3D* value) - { skeleton = value; - inertialize_reset(); - } - - GETSET(float,halflife,0.1f); - GETSET(float,ratio,1.0f); - - bool active{true}; - bool get_active(){return active;} - void set_active(bool value){ - active = value; - inertialize_reset(); - } - - - virtual void _ready() override{ - inertialize_reset(); - } - - void inertialize_reset(){ - if (skeleton == nullptr ) return; - const auto bone_count = skeleton->get_bone_count(); - bones.reserve(bone_count); - offsets.reserve(bone_count); - for (int b = 0; b < bone_count; ++b) - { - bones.reset(b); - bones.pos[b] = skeleton->get_bone_pose_position(b); - bones.rot[b] = skeleton->get_bone_pose_rotation(b); - bones.scl[b] = skeleton->get_bone_pose_scale(b); - - offsets.reset(b); - } - } - - virtual void _process(double delta) override{ - if( mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) - advance(delta); - } - - virtual void _physics_process(double delta) override{ - if(mixer != nullptr && mixer->get_callback_mode_process() == AnimationMixer::AnimationCallbackModeProcess::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) - advance(delta); - } - - void advance(double delta) - { - if(type == InertializationType::Simple) - { - _simple(delta); - } - } - - void _simple(double delta) - { - if(active == false || skeleton == nullptr) return; - - bones.reserve(skeleton->get_bone_count()); - offsets.reserve(skeleton->get_bone_count()); - - for(auto bone_id = 0; bone_id < skeleton->get_bone_count();++bone_id) - { - kform desired {}; - desired.pos = skeleton->get_bone_pose_position(bone_id); - desired.rot = skeleton->get_bone_pose_rotation(bone_id); - desired.vel = (desired.pos - bones.pos[bone_id])/delta; - desired.ang = Spring::quat_differentiate_angular_velocity(desired.rot, bones.rot[bone_id],delta); - - Spring::_simple_spring_damper_exact( - bones.pos[bone_id],bones.vel[bone_id] - ,desired.pos,halflife,delta - ); - Spring::_simple_spring_damper_exact( - bones.rot[bone_id],bones.ang[bone_id] - ,desired.rot,halflife,delta - ); - - skeleton->set_bone_pose_position(bone_id,bones.pos[bone_id]); - skeleton->set_bone_pose_rotation(bone_id,bones.rot[bone_id]); - } - - } - - protected: - static void _bind_methods() - { - ClassDB::bind_method( D_METHOD("set_type" ,"value"), &PPInertialization3D::set_type,DEFVAL(InertializationType::Simple)); - ClassDB::bind_method( D_METHOD("get_type" ), &PPInertialization3D::get_type); - ADD_PROPERTY(PropertyInfo(Variant::INT,"type",godot::PROPERTY_HINT_ENUM,"Simple"), "set_type", "get_type"); - - - ClassDB::bind_method( D_METHOD("set_active" ,"value"), &PPInertialization3D::set_active,DEFVAL(true)); - ClassDB::bind_method( D_METHOD("get_active" ), &PPInertialization3D::get_active); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::BOOL,"active"), "set_active", "get_active"); - - - ClassDB::bind_method( D_METHOD("set_halflife" ,"value"), &PPInertialization3D::set_halflife); - ClassDB::bind_method( D_METHOD("get_halflife" ), &PPInertialization3D::get_halflife); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::FLOAT,"halflife" - , PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater"), "set_halflife", "get_halflife"); - - ClassDB::bind_method( D_METHOD("set_mixer" ,"value"), &PPInertialization3D::set_mixer); - ClassDB::bind_method( D_METHOD("get_mixer" ), &PPInertialization3D::get_mixer); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"mixer",PROPERTY_HINT_NODE_TYPE ,"AnimationMixer",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_mixer", "get_mixer"); - - ClassDB::bind_method( D_METHOD("set_skeleton" ,"value"), &PPInertialization3D::set_skeleton); - ClassDB::bind_method( D_METHOD("get_skeleton" ), &PPInertialization3D::get_skeleton); - godot::ClassDB::add_property(get_class_static(), PropertyInfo(Variant::OBJECT,"skeleton",PROPERTY_HINT_NODE_TYPE ,"Skeleton3D",PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT), "set_skeleton", "get_skeleton"); - - BIND_ENUM_CONSTANT(Simple); - } -}; - -VARIANT_ENUM_CAST(PPInertialization3D::InertializationType); \ No newline at end of file diff --git a/src/Spring.hpp b/src/Spring.hpp deleted file mode 100644 index 7a08e1ee..00000000 --- a/src/Spring.hpp +++ /dev/null @@ -1,562 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace godot; - -struct Spring : public RefCounted -{ - GDCLASS(Spring,RefCounted) - - static constexpr real_t Ln2 = 0.69314718056; - - static real_t inline square(real_t x) { - return x * x; - } - - static Vector3 damp_adjustment_exact(Vector3 g, real_t halflife, real_t dt, real_t eps=1e-8) { - real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)); - return g * factor; - } - - static Quaternion damp_adjustment_exact_quat(Quaternion g, real_t halflife, real_t dt, real_t eps=1e-8) { - real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)); - return Quaternion().slerp(g, factor).normalized(); - } - static Variant damper_exponential(Variant variable, Variant goal, real_t damping, real_t dt) { - real_t ft = 1.0 / (real_t)ProjectSettings::get_singleton()->get("physics/common/physics_ticks_per_second"); - real_t factor = 1.0 - pow(1.0 / (1.0 - ft * damping), -dt / ft); - return Math::lerp(variable, goal, factor); - } - - static inline real_t fast_negexp(real_t x) { - return 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x); - } - - static inline Variant damper_exact(Variant variable, Variant goal, real_t halflife, real_t dt, real_t eps = 1e-5) { - return Math::lerp(variable, goal, 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps))); - } - - static inline real_t halflife_to_damping(real_t halflife, real_t eps = 1e-5) { - return (4.0 * Ln2) / (halflife + eps); - } - - static inline real_t halflife_to_duration(real_t halflife, real_t initial_value = 1.0, real_t eps = 1e-5){ - return halflife * (log(eps/initial_value) / log(0.5)); - } - - static inline real_t damping_to_halflife(real_t damping, real_t eps = 1e-5) { - return (4.0 * Ln2) / (damping + eps); - } - - static inline real_t frequency_to_stiffness(real_t frequency) { - return square(2.0 * Math_PI * frequency); - } - - static inline real_t stiffness_to_frequency(real_t stiffness) { - return sqrt(stiffness) / (2.0 * Math_PI); - } - - static inline real_t critical_halflife(real_t frequency) { - return damping_to_halflife(sqrt(frequency_to_stiffness(frequency) * 4.0)); - } - - static inline real_t critical_frequency(real_t halflife) { - return stiffness_to_frequency(square(halflife_to_damping(halflife)) / 4.0); - } - - static inline real_t damping_ratio_to_stiffness(real_t ratio, real_t damping) { - return square(damping / (ratio * 2.0)); - } - - static inline real_t damping_ratio_to_damping(real_t ratio, real_t stiffness) { - return ratio * 2.0 * sqrt(stiffness); - } - - - static inline real_t maximum_spring_velocity_to_halflife(real_t x,real_t x_goal,real_t v_max){ - return damping_to_halflife(2.0 * ((v_max / (x_goal - x)) * exp(1.0))); - } - - static inline Quaternion quat_exp(Vector3 v, real_t eps=1e-8) - { - real_t halfangle = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - - if (halfangle < eps) - { - Quaternion q{}; - q.w = 1.0; q.x = v.x; q.y = v.y; q.z = v.z; - return q.normalized(); - } - else - { - real_t c = cosf(halfangle); - real_t s = sinf(halfangle) / halfangle; - Quaternion q{}; - q.w = c; q.x = s * v.x; q.y = s * v.y; q.z = s * v.z; - return q.normalized(); - } - } - template - static inline T clampf(T x, T min, T max) - { - static_assert(std::is_arithmetic_v,"Must be arithmetic"); - return x > max ? max : x < min ? min : x; - } - - static inline Quaternion quat_abs(Quaternion q){ - return (q.w < 0.0 ? -q : q).normalized(); - } - - static inline Vector3 quat_log(Quaternion q, real_t eps=1e-8){ - real_t length = sqrt(q.x*q.x + q.y*q.y + q.z*q.z); - if (length < eps) - { - return Vector3(q.x, q.y, q.z); - } - else - { - real_t halfangle = acosf(clampf(q.w, real_t(-1.0), real_t(1.0))); - return halfangle * (Vector3(q.x, q.y, q.z) / length); - } - } - - static inline Quaternion quat_from_scaled_angle_axis(Vector3 v, real_t eps = 1e-8) - { - return quat_exp(v / 2.0, eps).normalized(); - } - - static inline Vector3 quat_to_scaled_angle_axis(Quaternion q, real_t eps = 1e-8) - { - return 2.0 * quat_log(q, eps); - } - - static inline Vector3 quat_differentiate_angular_velocity(Quaternion next, Quaternion curr, real_t dt, real_t eps = 1e-8) - { - return quat_to_scaled_angle_axis(quat_abs(next * curr.inverse()), eps) / dt; - } - - static void _spring_damper_exact( - real_t& x, - real_t& v, - real_t x_goal, - real_t v_goal, - real_t damping_ratio, - real_t halflife, - real_t dt, - real_t eps = 1e-5) - { - real_t g = x_goal; - real_t q = v_goal; - real_t d = halflife_to_damping(halflife); - real_t s = damping_ratio_to_stiffness(damping_ratio, d); - real_t c = g + (d * q) / (s + eps); - real_t y = d / 2.0; - - if (std::abs(s - (d * d) / 4.0) < eps) { // Critically Damped - real_t j0 = x - c; - real_t j1 = v + j0 * y; - real_t eydt = std::exp(-y * dt); - x = j0 * eydt + dt * j1 * eydt + c; - v = -y * j0 * eydt - y * dt * j1 * eydt + j1 * eydt; - } - else if (s - (d * d) / 4.0 > 0.0) { // Under Damped - real_t w = std::sqrt(s - (d * d) / 4.0); - real_t j = std::sqrt(std::pow(v + y * (x - c), 2) / (std::pow(w, 2) + eps) + std::pow(x - c, 2)); - real_t p = std::atan((v + (x - c) * y) / (-(x - c) * w + eps)); - - // j = (x - c) > 0.0 ? j : -j; - j = (x - c) > 0.0 ? j : -j; - - real_t eydt = std::exp(-y * dt); - - x = j * eydt * std::cos(w * dt + p) + c; - v = -y * j * eydt * std::cos(w * dt + p) - w * j * eydt * std::sin(w * dt + p); - } - else if (s - (d * d) / 4.0 < 0.0) { // Over Damped - real_t y0 = (d + std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0; - real_t y1 = (d - std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0; - real_t j1 = (c * y0 - x * y0 - v) / (y1 - y0); - real_t j0 = x - j1 - c; - - real_t ey0dt = std::exp(-y0 * dt); - real_t ey1dt = std::exp(-y1 * dt); - - x = j0 * ey0dt + j1 * ey1dt + c; - v = -y0 * j0 * ey0dt - y1 * j1 * ey1dt; - } - } - - static void _critical_spring_damper_exact( - real_t& x, - real_t& v, - real_t x_goal, - real_t v_goal, - real_t halflife, - real_t dt - ) { - real_t g = x_goal; - real_t q = v_goal; - real_t d = halflife_to_damping(halflife); - real_t c = g + (d * q) / ((d * d) / 4.0); - real_t y = d / 2.0; - real_t j0 = x - c; - real_t j1 = v + j0 * y; - real_t eydt = fast_negexp(y * dt); - x = eydt * (j0 + j1 * dt) + c; - v = eydt * (v - j1 * y * dt); - } - - static inline PackedFloat32Array critical_spring_damper_exact(real_t x, real_t v, real_t x_goal,real_t v_goal, real_t halflife, real_t dt) - { - _critical_spring_damper_exact(x,v,x_goal,v_goal,halflife,dt); - PackedFloat32Array result; - result.append(x); - result.append(v); - return result; - } - - static void _simple_spring_damper_exact(real_t& x, real_t& v, real_t x_goal, real_t halflife, real_t dt) { - real_t y = halflife_to_damping(halflife) / 2.0; - real_t j0 = x - x_goal; - real_t j1 = v + j0 * y; - real_t eydt = fast_negexp(y * dt); - x = eydt * (j0 + j1 * dt) + x_goal; - v = eydt * (v - j1 * y * dt); - } - static void _simple_spring_damper_exact( - Vector3& x, - Vector3& v, - const Vector3 x_goal, - const real_t halflife, - const real_t dt) - { - real_t y = halflife_to_damping(halflife) / 2.0; - Vector3 j0 = x - x_goal; - Vector3 j1 = v + j0*y; - real_t eydt = fast_negexp(y*dt); - - x = eydt*(j0 + j1*dt) + x_goal; - v = eydt*(v - j1*y*dt); - } - - static void _simple_spring_damper_exact( - Quaternion& x, - Vector3& v, - const Quaternion x_goal, - const real_t halflife, - const real_t dt) - { - real_t y = halflife_to_damping(halflife) / 2.0; - - Vector3 j0 = quat_to_scaled_angle_axis(quat_abs(x *x_goal.inverse())); - Vector3 j1 = v + j0*y; - - real_t eydt = fast_negexp(y*dt); - - x = quat_from_scaled_angle_axis(eydt*(j0 + j1*dt)) * x_goal; - v = eydt*(v - j1*y*dt); - } - - static inline Array simple_spring_damper_exact(Variant x, Variant v, Variant x_goal, real_t halflife, real_t dt){ - Array result; - if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::VECTOR3 ) - { - Vector3 pos = (Vector3)x, vel = (Vector3)v, goal = (Vector3) x_goal; - _simple_spring_damper_exact(pos,vel,goal,halflife,dt); - result.append(pos); - result.append(vel); - } - else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::QUATERNION ) - { - Quaternion pos = (Quaternion)x; Vector3 vel = (Vector3)v; Quaternion goal = (Quaternion) x_goal; - _simple_spring_damper_exact(pos,vel,goal,halflife,dt); - result.append(pos); - result.append(vel); - } - else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT ) - { - real_t pos = (real_t)x; real_t vel = (real_t)v, goal = (real_t)x_goal; - _simple_spring_damper_exact(pos,vel,goal,halflife,dt); - result.append(pos); - result.append(vel); - } - - return result; - } - - static inline void _decay_spring_damper_exact(real_t& x,real_t& v, real_t halflife, real_t dt) { - real_t y = halflife_to_damping(halflife) / 2.0; - real_t j1 = v + x * y; - real_t eydt = fast_negexp(y * dt); - x = eydt * (x + j1 * dt); - v = eydt * (v - j1 * y * dt); - } - static inline void _decay_spring_damper_exact(Vector3& x,Vector3& v, real_t halflife, real_t dt) { - real_t y = halflife_to_damping(halflife) / 2.0; - Vector3 j1 = v + x * y; - real_t eydt = fast_negexp(y * dt); - x = eydt * (x + j1 * dt); - v = eydt * (v - j1 * y * dt); - } - static inline void _decay_spring_damper_exact( - Quaternion& x, - Vector3& v, - const real_t halflife, - const real_t dt) - { - real_t y = halflife_to_damping(halflife) / 2.0; - - Vector3 j0 = quat_to_scaled_angle_axis(x); - Vector3 j1 = v + j0*y; - - real_t eydt = fast_negexp(y*dt); - - x = quat_from_scaled_angle_axis(eydt*(j0 + j1*dt)); - v = eydt*(v - j1*y*dt); - } - static inline Array decay_spring_damper_exact(Variant x, Variant v, real_t halflife,real_t dt){ - Array result; - if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3) - { - Vector3 pos = (Vector3)x, vel = (Vector3)v; - _decay_spring_damper_exact(pos,vel,halflife,dt); - result.append(pos); - result.append(vel); - } - else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3 ) - { - Quaternion pos = (Quaternion)x; Vector3 vel = (Vector3)v; - _decay_spring_damper_exact(pos,vel,halflife,dt); - result.append(pos); - result.append(vel); - } - else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT ) - { - real_t pos = (real_t)x; real_t vel = (real_t)v; - _decay_spring_damper_exact(pos,vel,halflife,dt); - result.append(pos); - result.append(vel); - } - - return result; - } - - - // Reach the x_goal at timed t_goal in the future - // Apprehension parameter controls how far into the future we try to track the linear interpolation - static void _timed_spring_damper_exact( - real_t& x, real_t& v, - real_t& xi, - const real_t x_goal, const real_t t_goal, - const real_t halflife, const real_t& dt, - real_t apprehension = 2.0 - ) - { - const real_t min_time = t_goal > dt ? t_goal : dt; - - const real_t v_goal = (x_goal - xi) / min_time; - - const real_t t_goal_future = dt + apprehension * halflife; - const real_t x_goal_future = t_goal_future < t_goal ? xi + v_goal * t_goal_future : x_goal; - - _simple_spring_damper_exact(x, v, x_goal_future, halflife, dt); - xi += v_goal * dt; - } - static inline PackedFloat32Array timed_spring_damper_exact(real_t x, real_t v, - real_t xi, - const real_t x_goal, const real_t t_goal, - const real_t halflife, const real_t dt, - const real_t apprehension = 2.0 - ){ - PackedFloat32Array result; - _timed_spring_damper_exact(x,v,xi,x_goal,t_goal,halflife,dt,apprehension); - result.append(x); - result.append(v); - result.append(xi); - return result; - } - - static Dictionary character_update( - Vector3 pos, - Vector3 vel, - Vector3 acc, - Quaternion quaternion, - Vector3 angular_velocity, - Vector3 v_goal, - Quaternion q_goal, - real_t halflife_vel, - real_t halflife_rot, - real_t dt - ) { - Dictionary answer; - { - real_t y = halflife_to_damping(halflife_vel) / 2.0; - Vector3 j0 = vel - v_goal; - Vector3 j1 = acc + j0 * y; - real_t eydt = fast_negexp(y * dt); - - answer["world_position"] = eydt * ((-j1 / (y * y)) + ((-j0 - j1 * dt) / y)) + - (j1 / (y * y)) + j0 / y + v_goal * dt + pos; - answer["velocity"] = eydt * (j0 + j1 * dt) + v_goal; - answer["acceleration"] = eydt * (acc - j1 * y * dt); - } - { - real_t y = halflife_to_damping(halflife_rot) / 2.0; - Vector3 j0 = (quaternion * q_goal.inverse()).get_euler(); - Vector3 j1 = angular_velocity + j0 * y; - real_t eydt = fast_negexp(y * dt); - answer["quaternion"] = (Quaternion(eydt * (j0 + j1 * dt)) * q_goal).normalized(); - answer["angular_velocity"] = eydt * (angular_velocity - j1 * y * dt); - answer["delta"] = dt; - } - return answer; - } - - static Dictionary character_predict( - Vector3 x, Vector3 v, Vector3 a, - Quaternion q, Vector3 angular_v, - Vector3 v_goal, Quaternion q_goal, - real_t halflife_v, real_t halflife_q, - const PackedFloat32Array dts) { - Dictionary answer; - for (int i = 0; i < dts.size(); i++) { - real_t dt = dts[i]; - answer[i] = character_update(x, v, a, q, angular_v, v_goal, q_goal, halflife_v, halflife_q, dt); - } - return answer; - } - - static inline void inertialize_transition( - Vector3& off_x, - Vector3& off_v, - const Vector3 src_x, - const Vector3 src_v, - const Vector3 dst_x, - const Vector3 dst_v) - { - off_x = (src_x + off_x) - dst_x; - off_v = (src_v + off_v) - dst_v; - } - - - - static inline void inertialize_update( - Vector3& out_x, - Vector3& out_v, - Vector3& off_x, - Vector3& off_v, - const Vector3 in_x, - const Vector3 in_v, - const real_t halflife, - const real_t dt) - { - Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt); - out_x = in_x + off_x; - out_v = in_v + off_v; - } - - static inline void inertialize_transition( - Quaternion &off_x, - Vector3 &off_v, - const Quaternion src_x, - const Vector3 src_v, - const Quaternion dst_x, - const Vector3 dst_v) - { - off_x = Spring::quat_abs((off_x * src_x) * dst_x.inverse()).normalized(); - off_v = (off_v + src_v) - dst_v; - } - static inline void inertialize_update( - Quaternion &out_x, - Vector3 &out_v, - Quaternion &off_x, - Vector3 &off_v, - const Quaternion in_x, - const Vector3 in_v, - const real_t halflife, - const real_t dt) - { - Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt); - out_x = (off_x * in_x).normalized(); - out_v = off_v + off_x.xform(in_v); - } - - static inline Vector3 calculate_offset_vec3(const Vector3 src_x,const Vector3 dst_x,const Vector3 off_x = Vector3()) - { - return (src_x + off_x) - dst_x; - } - static inline Quaternion calculate_offset_quat(const Quaternion src_q,const Quaternion dst_q,const Quaternion off_q = Quaternion()) - { - return Spring::quat_abs((off_q * src_q) * dst_q.inverse()); - } - - - - static inline Dictionary binded_inertia_transition(const Vector3 off_x, - const Vector3 off_v, - const Vector3 src_x, - const Vector3 src_v, - const Vector3 dst_x, - const Vector3 dst_v, - const Quaternion off_q, - const Vector3 off_a, - const Quaternion src_q, - const Vector3 src_a, - const Quaternion dst_q, - const Vector3 dst_a) - { - Dictionary result; - result["position_offset"] = (src_x + off_x) - dst_x; - result["velocity_offset"] = (src_v + off_v) - dst_v; - result["rotation_offset"] = Spring::quat_abs((off_q * src_q) * dst_q.inverse()); - result["angular_offset"] = (off_a + src_a) - dst_a; - return result; - } - - - -protected: - static void _bind_methods() { - - - ClassDB::bind_static_method("Spring", D_METHOD("damper_exponential", "variable", "goal", "damping", "dt"), &Spring::damper_exponential); - ClassDB::bind_static_method("Spring", D_METHOD("damp_adjustment_exact_quat", "g", "halflife", "dt", "eps"), &Spring::damp_adjustment_exact_quat, DEFVAL(1e-8)); - ClassDB::bind_static_method("Spring", D_METHOD("damp_adjustment_exact", "g", "halflife", "dt", "eps"), &Spring::damp_adjustment_exact, DEFVAL(1e-8)); - - ClassDB::bind_static_method("Spring", D_METHOD("halflife_to_duration", "halflife", "initial_value", "eps"), &Spring::halflife_to_duration, DEFVAL(1.0), DEFVAL(1e-5)); - ClassDB::bind_static_method("Spring", D_METHOD("halflife_to_damping", "halflife", "eps"), &Spring::halflife_to_damping, DEFVAL(1e-5)); - ClassDB::bind_static_method("Spring", D_METHOD("damping_to_halflife", "damping", "eps"), &Spring::damping_to_halflife, DEFVAL(1e-5)); - ClassDB::bind_static_method("Spring", D_METHOD("frequency_to_stiffness", "frequency"), &Spring::frequency_to_stiffness); - ClassDB::bind_static_method("Spring", D_METHOD("stiffness_to_frequency", "stiffness"), &Spring::stiffness_to_frequency); - ClassDB::bind_static_method("Spring", D_METHOD("critical_halflife", "frequency"), &Spring::critical_halflife); - ClassDB::bind_static_method("Spring", D_METHOD("critical_frequency", "halflife"), &Spring::critical_frequency); - ClassDB::bind_static_method("Spring", D_METHOD("damping_ratio_to_stiffness", "ratio", "damping"), &Spring::damping_ratio_to_stiffness); - ClassDB::bind_static_method("Spring", D_METHOD("damping_ratio_to_damping", "ratio", "stiffness"), &Spring::damping_ratio_to_damping); - - ClassDB::bind_static_method("Spring", D_METHOD("maximum_spring_velocity_to_halflife", "x", "x_goal", "v_max"), &Spring::maximum_spring_velocity_to_halflife); - - ClassDB::bind_static_method("Spring", D_METHOD("timed_spring_damper_exact", "x", "v", "xi", "x_goal", "t_goal", "halflife", "dt", "apprehension"), &Spring::timed_spring_damper_exact, DEFVAL(2.0)); - ClassDB::bind_static_method("Spring", D_METHOD("decay_spring_damper_exact", "pos", "vel", "halflife", "dt"), &Spring::decay_spring_damper_exact); - ClassDB::bind_static_method("Spring", D_METHOD("simple_spring_damper_exact", "x", "v", "goal", "halflife", "dt"), &Spring::simple_spring_damper_exact); - ClassDB::bind_static_method("Spring", D_METHOD("critical_spring_damper_exact", "x", "v", "x_goal", "v_goal", "halflife", "dt"), &Spring::critical_spring_damper_exact); - // ClassDB::bind_static_method("Spring", D_METHOD("spring_damper_exact", "x", "v", "x_goal", "v_goal", "damping_ratio", "halflife", "dt", "eps"), &CritDampSpring::spring_damper_exact, DEFVAL(1e-5)); - - ClassDB::bind_static_method("Spring", D_METHOD("character_update", "pos", "vel", "acc", "quaternion", "angular_velocity", "v_goal", "q_goal", "halflife_vel", "halflife_rot", "dt"), &Spring::character_update); - ClassDB::bind_static_method("Spring", D_METHOD("character_predict", "x", "v", "a", "q", "angular_v", "v_goal", "q_goal", "halflife_v", "halflife_q", "dts"),&Spring::character_predict); - } - -}; - - - - diff --git a/src/Util/CircularBuffer.hpp b/src/Util/CircularBuffer.hpp new file mode 100644 index 00000000..a06856bc --- /dev/null +++ b/src/Util/CircularBuffer.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + + +using namespace godot; + +struct CircularBuffer : public godot::RefCounted { + GDCLASS(CircularBuffer, RefCounted); + +public: + using capacity_t = typename boost::circular_buffer_space_optimized::capacity_type; + using value_type = typename boost::circular_buffer_space_optimized::value_type; + + void set_capacity(int i) { + if (i < 0) + return; + buffer.resize(i); + } + int get_capacity() { + return buffer.capacity().capacity(); + } + + void push_back(Variant v) { + buffer.push_back(v); + } + void push_front(Variant v) { + buffer.push_front(v); + } + void pop_back(Variant v) { + buffer.push_back(v); + } + void pop_front(Variant v) { + buffer.push_front(v); + } + + void insert(int index, Variant v) { + if (index > 0) + buffer.insert(std::next(buffer.begin(), index), v); + } + void erase(int index) { + if (index > 0) + buffer.erase(std::next(buffer.begin(), index)); + } + void clear() { + buffer.clear(); + } + + Variant get(int index) { + return buffer[godot::Math::posmod(index, buffer.size())]; + } + + using iter_t = typename boost::circular_buffer_space_optimized::iterator; + iter_t iterator; + bool _iter_init(Variant arg) { + iterator = buffer.begin(); + return iterator != buffer.end(); + } + bool _iter_next(Variant arg) { + ++iterator; + return iterator != buffer.end(); + } + Variant _iter_get(Variant arg) { + return *iterator; + } + +protected: + static void _bind_methods() { + ClassDB::bind_method(D_METHOD("set_capacity", "new_capacity"), &CircularBuffer::set_capacity, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("get_capacity"), &CircularBuffer::get_capacity); + ClassDB::add_property("CircularBuffer", PropertyInfo(Variant::INT, "capacity", PROPERTY_HINT_RANGE, "1,100,or_greater"), "set_capacity", "get_capacity"); + + ClassDB::bind_method(D_METHOD("push_back", "value"), &CircularBuffer::push_back); + ClassDB::bind_method(D_METHOD("push_front", "value"), &CircularBuffer::push_front); + ClassDB::bind_method(D_METHOD("pop_back"), &CircularBuffer::pop_back); + ClassDB::bind_method(D_METHOD("pop_front"), &CircularBuffer::pop_front); + + ClassDB::bind_method(D_METHOD("insert", "index", "value"), &CircularBuffer::insert); + ClassDB::bind_method(D_METHOD("erase", "index"), &CircularBuffer::erase); + ClassDB::bind_method(D_METHOD("clear"), &CircularBuffer::clear); + + ClassDB::bind_method(D_METHOD("get", "index"), &CircularBuffer::get); + + ClassDB::bind_method(D_METHOD("_iter_init", "arg"), &CircularBuffer::_iter_init); + ClassDB::bind_method(D_METHOD("_iter_next", "arg"), &CircularBuffer::_iter_next); + ClassDB::bind_method(D_METHOD("_iter_get", "arg"), &CircularBuffer::_iter_get); + } + boost::circular_buffer_space_optimized buffer{ capacity_t{ 1, 1 } }; +}; \ No newline at end of file diff --git a/src/Util/Util.hpp b/src/Util/Util.hpp new file mode 100644 index 00000000..2ef439b8 --- /dev/null +++ b/src/Util/Util.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace godot; +namespace mm { +namespace impl { +static inline void MMEMITCHANGED(Resource *that) { + // Call emit_changed signal + that->emit_changed(); +} +static inline void MMEMITCHANGED(Object *that) { + // Nothing +} +}; //namespace impl +}; //namespace mm + +// Macro setup. Mostly there to simplify writing all those +#define GETSET(type, variable, ...) \ + type variable{ __VA_ARGS__ }; \ + type get_##variable() { return variable; } \ + void set_##variable(type value) { \ + variable = value; \ + mm::impl::MMEMITCHANGED(this); \ + } +#define GETSET_NoVar(type, variable, ...) \ + type get_##variable() { return variable; } \ + void set_##variable(type value) { \ + variable = value; \ + mm::impl::MMEMITCHANGED(this); \ + } +#define STR(x) #x +#define BINDER_PROPERTY_PARAMS(type, variant_type, variable, ...) \ + ClassDB::bind_method(D_METHOD(STR(set_##variable), "value"), &type::set_##variable); \ + ClassDB::bind_method(D_METHOD(STR(get_##variable)), &type::get_##variable); \ + ::godot::ClassDB::add_property(get_class_static(), PropertyInfo{ variant_type, #variable, __VA_ARGS__ }, STR(set_##variable), STR(get_##variable)); + +using namespace godot; +struct MMUtil : godot::RefCounted { + GDCLASS(MMUtil, RefCounted) +public: + static PackedFloat32Array standardize(PackedFloat32Array arr) { + // Calculate the mean + double mean = 0; + for (const float x : arr) { + mean += x; + } + mean /= arr.size(); + + // Calculate the standard deviation + double stdev = 0; + for (const float x : arr) { + stdev += std::pow(x - mean, 2); + } + stdev = std::sqrt(stdev / arr.size()); + + PackedFloat32Array output{ arr }; + // Standardize the array + for (float &x : output) { + x = (x - mean) / stdev; + } + + return output; + } + + static PackedFloat32Array softmax(PackedFloat32Array input) { + // Calculate the sum of exponentials + float sum = 0.0f; + for (float value : input) { + sum += std::exp(value); + } + PackedFloat32Array output{ input }; + // Apply softmax to each element + for (size_t i = 0; i < input.size(); ++i) { + output[i] = std::exp(input[i]) / sum; + } + return output; + } + + static Quaternion get_twist(const Quaternion q, const Vector3 p_axis) { + Vector3 rotationAxis = Vector3(q.x, q.y, q.z); + double dotProd = p_axis.dot(rotationAxis); + Vector3 projection = p_axis * dotProd; + Quaternion twist = Quaternion(projection.x, projection.y, projection.z, q.w).normalized(); + if (dotProd < 0.0) { + twist = -twist; + } + return twist; + } + + static Quaternion get_swing(const Quaternion q, const Vector3 p_axis) { + return q * MMUtil::get_twist(q, p_axis).inverse(); + } + +protected: + static void _bind_methods() { + ClassDB::bind_static_method("MMUtil", D_METHOD("standardize", "arr_f32"), &MMUtil::standardize); + ClassDB::bind_static_method("MMUtil", D_METHOD("softmax", "arr_f32"), &MMUtil::softmax); + ClassDB::bind_static_method("MMUtil", D_METHOD("get_twist", "quaternion", "axis"), &MMUtil::get_twist); + ClassDB::bind_static_method("MMUtil", D_METHOD("get_swing", "quaternion", "axis"), &MMUtil::get_swing); + } +}; \ No newline at end of file diff --git a/src/register_types.cpp b/src/register_types.cpp index e748c218..1b08d8a2 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -1,82 +1,103 @@ -#include "register_types.h" +#pragma once +#include "register_types.h" #include #include #include -#include #include -#include "Spring.hpp" -#include "MotionFeatures/MotionFeatures.hpp" -#include "MotionFeatures/MFRootVelocity.hpp" +#include "Math/Spring.hpp" #include "MotionFeatures/MFBonesInfo.hpp" -#include "MotionFeatures/MFTrajectory.hpp" +#include "MotionFeatures/MFDistance.hpp" #include "MotionFeatures/MFEvents.hpp" +#include "MotionFeatures/MFRootVelocity.hpp" +#include "MotionFeatures/MFTrajectory.hpp" +#include "MotionFeatures/MotionFeatures.hpp" - +#include "Util/CircularBuffer.hpp" +#include "Util/Util.hpp" #include #include -#include -#include -#include -#include "CircularBuffer.hpp" +#include +#include +#include + +#include +#include -namespace boost -{ +namespace boost { #ifdef BOOST_NO_EXCEPTIONS -void throw_exception( std::exception const & e ){ - godot::UtilityFunctions::prints("MotionMatching catched exception : ",e.what()); - //throw 11; // This handle exceptions when dealing with no exception. - // TODO +void throw_exception(std::exception const &e) { + godot::UtilityFunctions::prints("MotionMatching catched exception : ", e.what()); + //throw 11; // This handle exceptions when dealing with no exception. }; #endif -}// namespace boost - +} // namespace boost using namespace godot; -void gdextension_MM_initialize(ModuleInitializationLevel p_level) -{ - if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) - { - ClassDB::register_class(); - - ClassDB::register_class(true); // Abstract class - - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - - ClassDB::register_class(); - ClassDB::register_class(); - - ClassDB::register_class(); - ClassDB::register_class(); - ClassDB::register_class(); - - ClassDB::register_class(); +void initialize_gdextension_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; } -} -void gdextension_MM_terminate(ModuleInitializationLevel p_level) -{ - if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) - { + GDREGISTER_ABSTRACT_CLASS(MMUtil); + + GDREGISTER_CLASS(MMAnimationPlayer); + GDREGISTER_CLASS(MMQueryOptions); + GDREGISTER_CLASS(MMAnimationLibrary); + + + { // Motion Features Resources + GDREGISTER_VIRTUAL_CLASS(MotionFeature); + + GDREGISTER_CLASS(MFRootVelocity); + GDREGISTER_CLASS(MFBonesInfo); + GDREGISTER_CLASS(MFTrajectory); + GDREGISTER_CLASS(MFTrajectoryOptions); + // GDREGISTER_CLASS(MFEvents); + // GDREGISTER_CLASS(MFDistance); } -} -extern "C" { + { // Animation Tags + GDREGISTER_VIRTUAL_CLASS(TagInfo); + GDREGISTER_VIRTUAL_CLASS(TagMotionMatching); + GDREGISTER_CLASS(TagJunk); + GDREGISTER_CLASS(TagCategory); + // GDREGISTER_CLASS(TagMFEvent); + // GDREGISTER_CLASS(TagMFDistance); -// Initialization. + GDREGISTER_INTERNAL_CLASS(RangeIndex); + GDREGISTER_CLASS(SetRangeIndex); + } - GDExtensionBool GDE_EXPORT gdextension_motion_matching_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { - godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + { // SkeletonModifier3D Nodes + GDREGISTER_CLASS(MMInertialization3D); + GDREGISTER_CLASS(MMIKLookAt3D); + GDREGISTER_CLASS(MMIKTwoBone3D); + } - init_obj.register_initializer(gdextension_MM_initialize); - init_obj.register_terminator(gdextension_MM_terminate); - init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + { // Various helper + GDREGISTER_CLASS(CircularBuffer); + GDREGISTER_CLASS(Spring); + GDREGISTER_CLASS(Kform); + } +} - return init_obj.init(); +void uninitialize_gdextension_types(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; } } + +extern "C" { +// Initialization +GDExtensionBool GDE_EXPORT mm_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + init_obj.register_initializer(initialize_gdextension_types); + init_obj.register_terminator(uninitialize_gdextension_types); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); +} +} \ No newline at end of file diff --git a/src/register_types.h b/src/register_types.h index c4b3ccc8..e6fe2b0a 100644 --- a/src/register_types.h +++ b/src/register_types.h @@ -1,8 +1,7 @@ -#pragma once +#ifndef EXAMPLE_REGISTER_TYPES_H +#define EXAMPLE_REGISTER_TYPES_H -#include +void initialize_gdextension_types(); +void uninitialize_gdextension_types(); -using namespace godot; - -void gdextension_MM_initialize(ModuleInitializationLevel p_level); -void gdextension_MM_terminate(ModuleInitializationLevel p_level); +#endif // EXAMPLE_REGISTER_TYPES_H \ No newline at end of file diff --git a/thirdparty/kdtree-cpp/LICENSE b/thirdparty/kdtree-cpp/LICENSE deleted file mode 100644 index 691ccdd5..00000000 --- a/thirdparty/kdtree-cpp/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright: - * 2018 Christoph Dalitz and Jens Wilberg - Niederrhein University of Applied Sciences, - Institute for Pattern Recognition, - Reinarzstr. 49, 47805 Krefeld, Germany - - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/thirdparty/kdtree-cpp/kdtree.cpp b/thirdparty/kdtree-cpp/kdtree.cpp deleted file mode 100644 index 7d4e9a8c..00000000 --- a/thirdparty/kdtree-cpp/kdtree.cpp +++ /dev/null @@ -1,475 +0,0 @@ -// -// Kd-Tree implementation. -// -// Copyright: Christoph Dalitz, 2018 -// Jens Wilberg, 2018 -// Version: 1.2 -// License: BSD style license -// (see the file LICENSE for details) -// - -// MODIFICATION -// -- Removed throw to make it pass fno-exceptions. The in case of throw nothing will -// be done to the current state of the object. This include the constructor -// -- Added logic to have a custom_weight for a single query. Good for paralellism -// and let user have custom query. - -#include "kdtree.hpp" -#include -#include -#include -#include -using namespace Kdtree; -namespace Kdtree { -typedef std::vector CoordPoint; -typedef std::vector WeightVector; - -//-------------------------------------------------------------- -// function object for comparing only dimension d of two vecotrs -//-------------------------------------------------------------- -class compare_dimension { - public: - compare_dimension(size_t dim) { d = dim; } - bool operator()(const Kdtree::KdNode& p, const Kdtree::KdNode& q) { - return (p.point[d] < q.point[d]); - } - size_t d; -}; - -//-------------------------------------------------------------- -// internal node structure used by kdtree -//-------------------------------------------------------------- -class kdtree_node { - public: - kdtree_node() { - dataindex = cutdim = 0; - loson = hison = (kdtree_node*)NULL; - } - ~kdtree_node() { - if (loson) delete loson; - if (hison) delete hison; - } - // index of node data in kdtree array "allnodes" - size_t dataindex; - // cutting dimension - size_t cutdim; - // value of point - // float cutval; // == point[cutdim] - CoordPoint point; - // roots of the two subtrees - kdtree_node *loson, *hison; - // bounding rectangle of this node's subtree - CoordPoint lobound, upbound; -}; - -//-------------------------------------------------------------- -// different distance metrics -//-------------------------------------------------------------- -class DistanceMeasure { - public: - DistanceMeasure() {} - virtual ~DistanceMeasure() {} - virtual float distance(const CoordPoint& p, const CoordPoint& q) = 0; - virtual float coordinate_distance(float x, float y, size_t dim) = 0; -}; -// Maximum distance (Linfinite norm) -class DistanceL0 : virtual public DistanceMeasure { - WeightVector* w; - - public: - DistanceL0(const WeightVector* weights = NULL) { - if (weights) - w = new WeightVector(*weights); - else - w = (WeightVector*)NULL; - } - ~DistanceL0() { - if (w) delete w; - } - float distance(const CoordPoint& p, const CoordPoint& q) { - size_t i; - float dist, test; - if (w) { - dist = (*w)[0] * fabs(p[0] - q[0]); - for (i = 1; i < p.size(); i++) { - test = (*w)[i] * fabs(p[i] - q[i]); - if (test > dist) dist = test; - } - } else { - dist = fabs(p[0] - q[0]); - for (i = 1; i < p.size(); i++) { - test = fabs(p[i] - q[i]); - if (test > dist) dist = test; - } - } - return dist; - } - float coordinate_distance(float x, float y, size_t dim) { - if (w) - return (*w)[dim] * fabs(x - y); - else - return fabs(x - y); - } -}; -// Manhatten distance (L1 norm) -class DistanceL1 : virtual public DistanceMeasure { - WeightVector* w; - - public: - DistanceL1(const WeightVector* weights = NULL) { - if (weights) - w = new WeightVector(*weights); - else - w = (WeightVector*)NULL; - } - ~DistanceL1() { - if (w) delete w; - } - float distance(const CoordPoint& p, const CoordPoint& q) { - size_t i; - float dist = 0.0; - if (w) { - for (i = 0; i < p.size(); i++) dist += (*w)[i] * fabs(p[i] - q[i]); - } else { - for (i = 0; i < p.size(); i++) dist += fabs(p[i] - q[i]); - } - return dist; - } - float coordinate_distance(float x, float y, size_t dim) { - if (w) - return (*w)[dim] * fabs(x - y); - else - return fabs(x - y); - } -}; -// Euklidean distance (L2 norm) (squared) -class DistanceL2 : virtual public DistanceMeasure { - WeightVector* w; - - public: - DistanceL2(const WeightVector* weights = NULL) { - if (weights) - w = new WeightVector(*weights); - else - w = (WeightVector*)NULL; - } - ~DistanceL2() { - if (w) delete w; - } - float distance(const CoordPoint& p, const CoordPoint& q) { - size_t i; - float dist = 0.0; - if (w) { - for (i = 0; i < p.size(); i++) - dist += (*w)[i] * (p[i] - q[i]) * (p[i] - q[i]); - } else { - for (i = 0; i < p.size(); i++) dist += (p[i] - q[i]) * (p[i] - q[i]); - } - return dist; - } - float coordinate_distance(float x, float y, size_t dim) { - if (w) - return (*w)[dim] * (x - y) * (x - y); - else - return (x - y) * (x - y); - } -}; - -//-------------------------------------------------------------- -// destructor and constructor of kdtree -//-------------------------------------------------------------- -KdTree::~KdTree() { - if (root) delete root; - delete default_distance; -} -// distance_type can be 0 (Maximum), 1 (Manhatten), or 2 (Euklidean [squared]) -KdTree::KdTree(const KdNodeVector* nodes, int distance_type /*=2*/) { - - size_t i, j; - float val; - // copy over input data - if (!nodes || nodes->empty()) - { - // throw std::invalid_argument( - // "kdtree::KdTree(): argument nodes must not be empty"); - return; - } - - dimension = nodes->begin()->point.size(); - allnodes = *nodes; - // initialize distance values - default_distance = NULL; - this->distance_type = -1; - set_distance(distance_type); - // compute global bounding box - lobound = nodes->begin()->point; - upbound = nodes->begin()->point; - for (i = 1; i < nodes->size(); i++) { - for (j = 0; j < dimension; j++) { - val = allnodes[i].point[j]; - if (lobound[j] > val) lobound[j] = val; - if (upbound[j] < val) upbound[j] = val; - } - } - // build tree recursively - root = build_tree(0, 0, allnodes.size()); -} - -// distance_type can be 0 (Maximum), 1 (Manhatten), or 2 (Euklidean [squared]) -void KdTree::set_distance(int distance_type, - const WeightVector* weights /*=NULL*/) { - if (default_distance) delete default_distance; - this->distance_type = distance_type; - if (distance_type == 0) { - default_distance = (DistanceMeasure*)new DistanceL0(weights); - } else if (distance_type == 1) { - default_distance = (DistanceMeasure*)new DistanceL1(weights); - } else { - default_distance = (DistanceMeasure*)new DistanceL2(weights); - } -} - -//-------------------------------------------------------------- -// recursive build of tree -// "a" and "b"-1 are the lower and upper indices -// from "allnodes" from which the subtree is to be built -//-------------------------------------------------------------- -kdtree_node* KdTree::build_tree(size_t depth, size_t a, size_t b) { - size_t m; - float temp, cutval; - kdtree_node* node = new kdtree_node(); - node->lobound = lobound; - node->upbound = upbound; - node->cutdim = depth % dimension; - if (b - a <= 1) { - node->dataindex = a; - node->point = allnodes[a].point; - } else { - m = (a + b) / 2; - std::nth_element(allnodes.begin() + a, allnodes.begin() + m, - allnodes.begin() + b, compare_dimension(node->cutdim)); - node->point = allnodes[m].point; - cutval = allnodes[m].point[node->cutdim]; - node->dataindex = m; - if (m - a > 0) { - temp = upbound[node->cutdim]; - upbound[node->cutdim] = cutval; - node->loson = build_tree(depth + 1, a, m); - upbound[node->cutdim] = temp; - } - if (b - m > 1) { - temp = lobound[node->cutdim]; - lobound[node->cutdim] = cutval; - node->hison = build_tree(depth + 1, m + 1, b); - lobound[node->cutdim] = temp; - } - } - return node; -} - -//-------------------------------------------------------------- -// k nearest neighbor search -// returns the *k* nearest neighbors of *point* in O(log(n)) -// time. The result is returned in *result* and is sorted by -// distance from *point*. -// The optional search predicate is a callable class (aka "functor") -// derived from KdNodePredicate. When Null (default, no search -// predicate is applied). -//-------------------------------------------------------------- -void KdTree::k_nearest_neighbors(const CoordPoint& point, size_t k, - KdNodeVector* result, - KdNodePredicate* pred /*=NULL*/, - const WeightVector * custom_weight /*NULL*/) { - size_t i; - KdNode temp; - searchpredicate = pred; - - result->clear(); - if (k < 1) return; - if (point.size() != dimension) - { - // throw std::invalid_argument( - // "kdtree::k_nearest_neighbors(): point must be of same dimension as " - // "kdtree"); - return; - } - DistanceMeasure * custom_distance = default_distance; - if (custom_weight != nullptr) - { - if (distance_type == 0) { - custom_distance = (DistanceMeasure*)new DistanceL0(custom_weight); - } else if (distance_type == 1) { - custom_distance = (DistanceMeasure*)new DistanceL1(custom_weight); - } else { - custom_distance = (DistanceMeasure*)new DistanceL2(custom_weight); - } - } - - // collect result of k values in neighborheap - //std::priority_queue, compare_nn4heap>* - //neighborheap = new std::priority_queue, compare_nn4heap>(); - SearchQueue* neighborheap = new SearchQueue(); - if (k > allnodes.size()) { - // when more neighbors asked than nodes in tree, return everything - k = allnodes.size(); - for (i = 0; i < k; i++) { - if (!(searchpredicate && !(*searchpredicate)(allnodes[i]))) - neighborheap->push( - nn4heap(i, custom_distance->distance(allnodes[i].point, point))); - } - } else { - neighbor_search(point, root, k, neighborheap,custom_distance); - } - - // copy over result sorted by distance - // (we must revert the vector for ascending order) - while (!neighborheap->empty()) { - i = neighborheap->top().dataindex; - neighborheap->pop(); - result->push_back(allnodes[i]); - } - // beware that less than k results might have been returned - k = result->size(); - for (i = 0; i < k / 2; i++) { - temp = (*result)[i]; - (*result)[i] = (*result)[k - 1 - i]; - (*result)[k - 1 - i] = temp; - } - delete neighborheap; - if(custom_weight != nullptr && custom_distance != nullptr && default_distance != custom_distance) - { - delete custom_distance; // ONLY if not the default; - } -} - -//-------------------------------------------------------------- -// range nearest neighbor search -// returns the nearest neighbors of *point* in the given range -// *r*. The result is returned in *result* and is sorted by -// distance from *point*. -//-------------------------------------------------------------- -void KdTree::range_nearest_neighbors(const CoordPoint& point, float r, - KdNodeVector* result) { - KdNode temp; - - result->clear(); - if (point.size() != dimension) - { - // throw std::invalid_argument( - // "kdtree::k_nearest_neighbors(): point must be of same dimension as " - // "kdtree"); - return; - } - if (this->distance_type == 2) { - // if euclidien distance is used the range must be squared because we - // get squared distances from this implementation - r *= r; - } - - // collect result in range_result - std::vector range_result; - range_search(point, root, r, &range_result,default_distance); - - // copy over result - for (std::vector::iterator i = range_result.begin(); - i != range_result.end(); ++i) { - result->push_back(allnodes[*i]); - } - - // clear vector - range_result.clear(); -} - -//-------------------------------------------------------------- -// recursive function for nearest neighbor search in subtree -// under *node*. Stores result in *neighborheap*. -// returns "true" when no nearer neighbor elsewhere possible -//-------------------------------------------------------------- -bool KdTree::neighbor_search(const CoordPoint& point, kdtree_node* node, - size_t k, SearchQueue* neighborheap, DistanceMeasure * distance) { - float curdist, dist; - - curdist = distance->distance(point, node->point); - if (!(searchpredicate && !(*searchpredicate)(allnodes[node->dataindex]))) { - if (neighborheap->size() < k) { - neighborheap->push(nn4heap(node->dataindex, curdist)); - } else if (curdist < neighborheap->top().distance) { - neighborheap->pop(); - neighborheap->push(nn4heap(node->dataindex, curdist)); - } - } - // first search on side closer to point - if (point[node->cutdim] < node->point[node->cutdim]) { - if (node->loson) - if (neighbor_search(point, node->loson, k, neighborheap,distance)) return true; - } else { - if (node->hison) - if (neighbor_search(point, node->hison, k, neighborheap,distance)) return true; - } - // second search on farther side, if necessary - if (neighborheap->size() < k) { - dist = std::numeric_limits::max(); - } else { - dist = neighborheap->top().distance; - } - if (point[node->cutdim] < node->point[node->cutdim]) { - if (node->hison && bounds_overlap_ball(point, dist, node->hison)) - if (neighbor_search(point, node->hison, k, neighborheap,distance)) return true; - } else { - if (node->loson && bounds_overlap_ball(point, dist, node->loson)) - if (neighbor_search(point, node->loson, k, neighborheap,distance)) return true; - } - - if (neighborheap->size() == k) dist = neighborheap->top().distance; - return ball_within_bounds(point, dist, node); -} - -//-------------------------------------------------------------- -// recursive function for range search in subtree under *node*. -// Stores result in *range_result*. -//-------------------------------------------------------------- -void KdTree::range_search(const CoordPoint& point, kdtree_node* node, - float r, std::vector* range_result,DistanceMeasure* distance) { - float curdist = distance->distance(point, node->point); - if (curdist <= r) { - range_result->push_back(node->dataindex); - } - if (node->loson != NULL && this->bounds_overlap_ball(point, r, node->loson)) { - range_search(point, node->loson, r, range_result); - } - if (node->hison != NULL && this->bounds_overlap_ball(point, r, node->hison)) { - range_search(point, node->hison, r, range_result); - } -} - -// returns true when the bounds of *node* overlap with the -// ball with radius *dist* around *point* -bool KdTree::bounds_overlap_ball(const CoordPoint& point, float dist, - kdtree_node* node,DistanceMeasure* distance) { - float distsum = 0.0; - size_t i; - for (i = 0; i < dimension; i++) { - if (point[i] < node->lobound[i]) { // lower than low boundary - distsum += distance->coordinate_distance(point[i], node->lobound[i], i); - if (distsum > dist) return false; - } else if (point[i] > node->upbound[i]) { // higher than high boundary - distsum += distance->coordinate_distance(point[i], node->upbound[i], i); - if (distsum > dist) return false; - } - } - return true; -} - -// returns true when the bounds of *node* completely contain the -// ball with radius *dist* around *point* -bool KdTree::ball_within_bounds(const CoordPoint& point, float dist, - kdtree_node* node,DistanceMeasure* distance) { - size_t i; - for (i = 0; i < dimension; i++) - if (distance->coordinate_distance(point[i], node->lobound[i], i) <= dist || - distance->coordinate_distance(point[i], node->upbound[i], i) <= dist) - return false; - return true; -} - -} // namespace Kdtree diff --git a/thirdparty/kdtree-cpp/kdtree.hpp b/thirdparty/kdtree-cpp/kdtree.hpp deleted file mode 100644 index d782d259..00000000 --- a/thirdparty/kdtree-cpp/kdtree.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#ifndef __kdtree_HPP -#define __kdtree_HPP - - -// -// Kd-Tree implementation. -// -// Copyright: Christoph Dalitz, 2018-2022 -// Jens Wilberg, 2018 -// Version: 1.2 -// License: BSD style license -// (see the file LICENSE for details) -// - -// MODIFICATION -// -- Removed throw to make it pass fno-exceptions. The in case of throw nothing will -// be done to the current state of the object. This include the constructor -// -- Added logic to have a custom_weight for a single query. Good for paralellism -// and let user have custom query. - -#include -#include -#include - -namespace Kdtree { - -typedef std::vector CoordPoint; -typedef std::vector WeightVector; - -// for passing points to the constructor of kdtree -struct KdNode { - CoordPoint point; - void* data; - int index; - KdNode(const CoordPoint& p, void* d = NULL, int i = -1) { - point = p; - data = d; - index = i; - } - KdNode() { data = NULL; } -}; -typedef std::vector KdNodeVector; - -// base function object for search predicate in knn search -// returns true when the given KdNode is an admissible neighbor -// To define an own search predicate, derive from this class -// and overwrite the call operator operator() -struct KdNodePredicate { - virtual ~KdNodePredicate() {} - virtual bool operator()(const KdNode&) const { return true; } -}; - -//-------------------------------------------------------- -// private helper classes used internally by KdTree -// -// the internal node structure used by kdtree -class kdtree_node; -// base class for different distance computations -class DistanceMeasure; -// helper class for priority queue in k nearest neighbor search -class nn4heap { - public: - size_t dataindex; // index of actual kdnode in *allnodes* - float distance; // distance of this neighbor from *point* - nn4heap(size_t i, float d) { - dataindex = i; - distance = d; - } -}; -class compare_nn4heap { - public: - bool operator()(const nn4heap& n, const nn4heap& m) { - return (n.distance < m.distance); - } -}; - typedef std::priority_queue, compare_nn4heap> SearchQueue; -//-------------------------------------------------------- - -// kdtree class -class KdTree { - private: - // recursive build of tree - kdtree_node* build_tree(size_t depth, size_t a, size_t b); - // helper variable for keeping track of subtree bounding box - CoordPoint lobound, upbound; - // helper variable to check the distance method - int distance_type; - bool neighbor_search(const CoordPoint& point, kdtree_node* node, size_t k, SearchQueue* neighborheap,DistanceMeasure * distance = nullptr); - void range_search(const CoordPoint& point, kdtree_node* node, float r, std::vector* range_result,DistanceMeasure* distance = nullptr); - bool bounds_overlap_ball(const CoordPoint& point, float dist, - kdtree_node* node, DistanceMeasure * distance = nullptr); - bool ball_within_bounds(const CoordPoint& point, float dist, - kdtree_node* node, DistanceMeasure * distance = nullptr); - // class implementing the distance computation - DistanceMeasure* default_distance; - // search predicate in knn searches - KdNodePredicate* searchpredicate; - - public: - KdNodeVector allnodes; - size_t dimension; - kdtree_node* root; - // distance_type can be 0 (max), 1 (city block), or 2 (euklid [squared]) - KdTree(const KdNodeVector* nodes, int distance_type = 2); - ~KdTree(); - void set_distance(int distance_type, const WeightVector* weights = NULL); - void k_nearest_neighbors(const CoordPoint& point, size_t k, - KdNodeVector* result, KdNodePredicate* pred = NULL,const WeightVector * custom_weight = nullptr); - void range_nearest_neighbors(const CoordPoint& point, float r, - KdNodeVector* result); -}; - - -} // end namespace Kdtree - -#endif \ No newline at end of file