master 697027f4da29 cached
74 files
196.3 KB
50.5k tokens
118 symbols
1 requests
Download .txt
Showing preview only (214K chars total). Download the full file or copy to clipboard to get everything.
Repository: GuilhermeGSousa/godot-motion-matching
Branch: master
Commit: 697027f4da29
Files: 74
Total size: 196.3 KB

Directory structure:
gitextract_5foufhgw/

├── .clang-format
├── .github/
│   ├── actions/
│   │   ├── build/
│   │   │   └── action.yml
│   │   └── sign/
│   │       └── action.yml
│   └── workflows/
│       └── builds.yml
├── .gitignore
├── .gitmodules
├── LICENSE.md
├── README.md
├── SConstruct
├── addons/
│   └── motion_matching/
│       └── gdmotionmatching.gdextension
├── config.py
├── doc_classes/
│   ├── DampedSkeletonModifier.xml
│   ├── MMAnimationLibrary.xml
│   ├── MMAnimationNode.xml
│   ├── MMBoneDataFeature.xml
│   ├── MMCharacter.xml
│   ├── MMClampSynchronizer.xml
│   ├── MMFeature.xml
│   ├── MMMixSynchronizer.xml
│   ├── MMQueryInput.xml
│   ├── MMRootMotionSynchronizer.xml
│   ├── MMSynchronizer.xml
│   └── MMTrajectoryFeature.xml
├── methods.py
└── src/
    ├── algo/
    │   ├── kd_tree.cpp
    │   └── kd_tree.h
    ├── circular_buffer.h
    ├── common.h
    ├── editor/
    │   ├── animation_post_import_plugin.cpp
    │   ├── animation_post_import_plugin.h
    │   ├── animation_tree_handler_plugin.cpp
    │   ├── animation_tree_handler_plugin.h
    │   ├── mm_data_tab.cpp
    │   ├── mm_data_tab.h
    │   ├── mm_editor.cpp
    │   ├── mm_editor.h
    │   ├── mm_editor_gizmo_plugin.cpp
    │   ├── mm_editor_gizmo_plugin.h
    │   ├── mm_editor_plugin.cpp
    │   ├── mm_editor_plugin.h
    │   ├── mm_visualization_tab.cpp
    │   └── mm_visualization_tab.h
    ├── features/
    │   ├── mm_bone_data_feature.cpp
    │   ├── mm_bone_data_feature.h
    │   ├── mm_feature.cpp
    │   ├── mm_feature.h
    │   ├── mm_trajectory_feature.cpp
    │   └── mm_trajectory_feature.h
    ├── math/
    │   ├── hash.h
    │   ├── spring.hpp
    │   ├── stats.hpp
    │   └── transforms.h
    ├── mm_animation_library.cpp
    ├── mm_animation_library.h
    ├── mm_animation_node.cpp
    ├── mm_animation_node.h
    ├── mm_bone_state.h
    ├── mm_character.cpp
    ├── mm_character.h
    ├── mm_query.h
    ├── mm_trajectory_point.cpp
    ├── mm_trajectory_point.h
    ├── modifiers/
    │   ├── damped_skeleton_modifier.cpp
    │   └── damped_skeleton_modifier.h
    ├── register_types.cpp
    ├── register_types.h
    └── synchronizers/
        ├── mm_clamp_synchronizer.cpp
        ├── mm_clamp_synchronizer.h
        ├── mm_mix_synchronizer.cpp
        ├── mm_mix_synchronizer.h
        ├── mm_rootmotion_synchronizer.cpp
        ├── mm_rootmotion_synchronizer.h
        ├── mm_synchronizer.cpp
        └── mm_synchronizer.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================

Language: Cpp
BasedOnStyle: LLVM
IndentWidth: 4
UseTab: Never
ColumnLimit: 0
AccessModifierOffset: -4
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: false
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: true
BinPackParameters: true
PointerAlignment: Left
SortIncludes: true


================================================
FILE: .github/actions/build/action.yml
================================================
name: GDExtension Build
description: Build GDExtension

inputs:
  platform:
    required: true
    description: Target platform.
  arch:
    required: true
    description: Target architecture.
  float-precision:
    default: 'single'
    description: Float precision (single or double).
  build-target-type:
    default: 'template_debug'
    description: Build type (template_debug or template_release).
  scons-cache:
    default: '.scons-cache/'
    description: Scons cache folder name, relative to each scons directory. Must not contain relative path signifiers (. or ..). Must be a transparent path part (empty or 'path/to/directory/', ending in a slash).
  em_version:
    default: 3.1.62
    description: Emscripten version.
  em-cache-directory:
    default: emsdk-cache
    description: Emscripten cache directory.
  gdextension-directory:
    default: ''
    description: Location of the gdextension project within the repository. Must not contain relative path signifiers (. or ..). Must be a transparent path part (empty or 'path/to/directory/', ending in a slash).

runs:
  using: composite
  steps:
# Android only
    - name: Android - Set up Java 17
      uses: actions/setup-java@v4
      if: ${{ inputs.platform == 'android' }}
      with:
        distribution: temurin
        java-version: 17

    - name: Android - Remove existing Android SDK, and set up ENV vars
      if: ${{ inputs.platform == 'android' }}
      shell: sh
      run: |
        sudo rm -r /usr/local/lib/android/sdk/**
        export ANDROID_HOME=/usr/local/lib/android/sdk
        export ANDROID_SDK_ROOT=$ANDROID_HOME
        export ANDROID_NDK_VERSION=23.2.8568313
        export ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}
        echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV"
        echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV"
        echo "ANDROID_NDK_VERSION=$ANDROID_NDK_VERSION" >> "$GITHUB_ENV"
        echo "ANDROID_NDK_ROOT=$ANDROID_NDK_ROOT" >> "$GITHUB_ENV"

    - name: Android - Set up Android SDK
      if: ${{ inputs.platform == 'android' }}
      uses: android-actions/setup-android@v3
      with:
        packages: "ndk;${{ env.ANDROID_NDK_VERSION }} cmdline-tools;latest build-tools;34.0.0 platforms;android-34 cmake;3.22.1"
# Linux only
    - name: Linux - dependencies
      if: ${{ inputs.platform == 'linux' }}
      shell: sh
      run: |
        sudo apt-get update -qq
        sudo apt-get install -qqq build-essential pkg-config
# Web only
    - name: Web - Set up Emscripten latest
      if: ${{ inputs.platform == 'web' }}
      uses: mymindstorm/setup-emsdk@v14
      with:
        version: ${{ inputs.em_version }}
        actions-cache-folder: ${{ inputs.em-cache-directory }}.${{ inputs.float-precision }}.${{ inputs.build-target-type }}
    - name: Web - Verify Emscripten setup
      if: ${{ inputs.platform == 'web' }}
      shell: sh
      run: |
        emcc -v
# Windows only
    - name: Windows - Setup MinGW for Windows/MinGW build
      uses: egor-tensin/setup-mingw@v2
      if: ${{ inputs.platform == 'windows' }}
      with:
        version: 12.2.0
# Dependencies of godot
    # Use python 3.x release (works cross platform)
    - name: Set up Python 3.x
      uses: actions/setup-python@v5
      with:
        # Semantic version range syntax or exact version of a Python version
        python-version: "3.x"
        # Optional - x64 or x86 architecture, defaults to x64
        architecture: "x64"
    - name: Setup scons
      shell: bash
      run: |
        python -c "import sys; print(sys.version)"
        python -m pip install scons==4.4.0
        scons --version
# Build
    - name: Cache .scons_cache
      uses: actions/cache@v4
      with:
        path: |
          ${{ github.workspace }}/${{ inputs.gdextension-directory }}${{ inputs.scons-cache }}
        key: ${{ inputs.platform }}_${{ inputs.arch }}_${{ inputs.float-precision }}_${{ inputs.build-target-type }}_cache
# Build gdextension
    - name: Build GDExtension Debug Build
      shell: sh
      env:
        SCONS_CACHE: ${{ github.workspace }}/${{ inputs.gdextension-directory }}${{ inputs.scons-cache }}
      run: |
        scons target=${{ inputs.build-target-type }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} precision=${{ inputs.float-precision }}
      working-directory: ${{ inputs.gdextension-directory }}


================================================
FILE: .github/actions/sign/action.yml
================================================
# This file incorporates work covered by the following copyright and permission notice:  
# 
#     Copyright (c) Mikael Hermansson and Godot Jolt contributors.
#     Copyright (c) Dragos Daian.
# 
#     Permission is hereby granted, free of charge, to any person obtaining a copy of
#     this software and associated documentation files (the "Software"), to deal in
#     the Software without restriction, including without limitation the rights to
#     use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
#     the Software, and to permit persons to whom the Software is furnished to do so,
#     subject to the following conditions:
# 
#     The above copyright notice and this permission notice shall be included in all
#     copies or substantial portions of the Software.
# 
#     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
#     FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
#     COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
#     IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
#     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

name: GDExtension Sign
description: Sign Mac GDExtension

inputs:
  FRAMEWORK_PATH:
    description: The path of the artifact. Eg. bin/addons/my_addon/bin/libmy_addon.macos.template_release.universal.framework
    required: true
  SIGN_FLAGS:
    description: The extra flags to use. Eg. --deep
    required: false
  APPLE_CERT_BASE64:
    required: true
    description: Base64 file from p12 certificate.
  APPLE_CERT_PASSWORD:
    required: true
    description: Password set when creating p12 certificate from .cer certificate.
  APPLE_DEV_PASSWORD:
    required: true
    description: Apple App-Specific Password. Eg. abcd-abcd-abcd-abcd
  APPLE_DEV_ID:
    required: true
    description: Email used for Apple Id. Eg. email@provider.com
  APPLE_DEV_TEAM_ID:
    required: true
    description: Apple Team Id. Eg. 1ABCD23EFG
  APPLE_DEV_APP_ID:
    required: true
    description: |
      Certificate name from get info -> Common name . Eg. Developer ID Application: Common Name (1ABCD23EFG)
outputs:
  zip_path:
    value: ${{ steps.sign.outputs.path }}


runs:
  using: composite
  steps:
  - name: Sign
    id: sign
    shell: pwsh
    run: |
      #!/usr/bin/env pwsh

      # Copyright (c) Mikael Hermansson and Godot Jolt contributors.

      # Permission is hereby granted, free of charge, to any person obtaining a copy of
      # this software and associated documentation files (the "Software"), to deal in
      # the Software without restriction, including without limitation the rights to
      # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
      # the Software, and to permit persons to whom the Software is furnished to do so,
      # subject to the following conditions:

      # The above copyright notice and this permission notice shall be included in all
      # copies or substantial portions of the Software.

      # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
      # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
      # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
      # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
      # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

      # Taken from https://github.com/godot-jolt/godot-jolt/blob/master/scripts/ci_sign_macos.ps1

      Set-StrictMode -Version Latest
      $ErrorActionPreference = "Stop"

      $CodesignPath = Get-Command codesign | Resolve-Path

      $CertificateBase64 = "${{inputs.APPLE_CERT_BASE64}}"
      $CertificatePassword = "${{inputs.APPLE_CERT_PASSWORD}}"
      $CertificatePath = [IO.Path]::ChangeExtension((New-TemporaryFile), "p12")

      $Keychain = "ephemeral.keychain"
      $KeychainPassword = (New-Guid).ToString().Replace("-", "")

      $DevId = "${{ inputs.APPLE_DEV_ID }}"
      $DevTeamId = "${{ inputs.APPLE_DEV_TEAM_ID }}"
      $DevPassword = "${{ inputs.APPLE_DEV_PASSWORD }}"
      $DeveloperIdApplication = "${{ inputs.APPLE_DEV_APP_ID }}"

      if (!$CertificateBase64) { throw "No certificate provided" }
      if (!$CertificatePassword) { throw "No certificate password provided" }
      if (!$DevId) { throw "No Apple Developer ID provided" }
      if (!$DeveloperIdApplication) { throw "No Apple Developer ID Application provided" }
      if (!$DevTeamId) { throw "No Apple Team ID provided" }
      if (!$DevPassword) { throw "No Apple Developer password provided" }

      Write-Output "Decoding certificate..."

      $Certificate = [Convert]::FromBase64String($CertificateBase64)

      Write-Output "Writing certificate to disk..."

      [IO.File]::WriteAllBytes($CertificatePath, $Certificate)

      Write-Output "Creating keychain..."

      security create-keychain -p $KeychainPassword $Keychain

      Write-Output "Setting keychain as default..."

      security default-keychain -s $Keychain

      Write-Output "Importing certificate into keychain..."
      security import $CertificatePath `
        -k ~/Library/Keychains/$Keychain `
        -P $CertificatePassword `
        -T $CodesignPath
      Write-Output "Check identities..."

      security find-identity

      Write-Output "Granting access to keychain..."

      security set-key-partition-list -S "apple-tool:,apple:" -s -k $KeychainPassword $Keychain

      $Framework = "${{ inputs.FRAMEWORK_PATH }}"
      $SignFlags = "${{ inputs.SIGN_FLAGS }}"
      $Archive = [IO.Path]::ChangeExtension((New-TemporaryFile), "zip")

      Write-Output "Signing '$Framework'..."

      & $CodesignPath --verify --timestamp --verbose "$SignFlags" --sign $DeveloperIdApplication "$Framework"

      Write-Output "Verifying signing..."

      & $CodesignPath --verify -dvvv "$Framework"

      Get-ChildItem -Force -Recurse -Path "$Framework"

      Write-Output "Archiving framework to '$Archive'..."

      ditto -ck -rsrc --sequesterRsrc --keepParent "$Framework" "$Archive"

      Write-Output "Submitting archive for notarization..."

      $output = xcrun notarytool submit "$Archive" `
        --apple-id $DevId `
        --team-id $DevTeamId `
        --password $DevPassword `
        --wait
      echo $output
      $matches = $output -match '((\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+)'
      if ($output) {
        $id_res = $matches[0].Substring(6)
      }
      xcrun notarytool log $id_res `
        --apple-id $DevId `
        --team-id $DevTeamId `
        --password $DevPassword `
        developer_log.json
      get-content developer_log.json

      echo "path=$Archive" >> $env:GITHUB_OUTPUT




================================================
FILE: .github/workflows/builds.yml
================================================
name: Build GDExtension
on:
  workflow_call:
  push:
  pull_request:
  merge_group:

env:
  LIBNAME: godot-motion-matching

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
        # Debug templates
          - platform: linux
            float-precision: single
            arch: x86_64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: windows
            float-precision: single
            arch: x86_32
            target-type: template_debug
            os: windows-latest

          - platform: windows
            float-precision: single
            arch: x86_64
            target-type: template_debug
            os: windows-latest

          - platform: macos
            float-precision: single
            arch: universal
            target-type: template_debug
            os: macos-latest

          - platform: android
            float-precision: single
            arch: arm64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: single
            arch: arm32
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: single
            arch: x86_64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: single
            arch: x86_32
            target-type: template_debug
            os: ubuntu-22.04

          - platform: ios
            float-precision: single
            arch: arm64
            target-type: template_debug
            os: macos-latest

          - platform: web
            float-precision: single
            arch: wasm32
            target-type: template_debug
            os: ubuntu-22.04

        # Release templates
          - platform: linux
            float-precision: single
            arch: x86_64
            target-type: template_release
            os: ubuntu-22.04

          # - platform: windows
          #   float-precision: single
          #   arch: x86_32
          #   target-type: template_release
          #   os: windows-latest

          # - platform: windows
          #   float-precision: single
          #   arch: x86_64
          #   target-type: template_release
          #   os: windows-latest

          # - platform: macos
          #   float-precision: single
          #   arch: universal
          #   target-type: template_release
          #   os: macos-latest

          # - platform: android
          #   float-precision: single
          #   arch: arm64
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: single
          #   arch: arm32
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: single
          #   arch: x86_64
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: single
          #   arch: x86_32
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: ios
          #   float-precision: single
          #   arch: arm64
          #   target-type: template_release
          #   os: macos-latest

          # - platform: web
          #   float-precision: single
          #   arch: wasm32
          #   target-type: template_release
          #   os: ubuntu-22.04

        # Double precision templates
        # Double precision debug templates
          - platform: linux
            float-precision: double
            arch: x86_64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: windows
            float-precision: double
            arch: x86_32
            target-type: template_debug
            os: windows-latest

          - platform: windows
            float-precision: double
            arch: x86_64
            target-type: template_debug
            os: windows-latest

          - platform: macos
            float-precision: double
            arch: universal
            target-type: template_debug
            os: macos-latest

          - platform: android
            float-precision: double
            arch: arm64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: double
            arch: arm32
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: double
            arch: x86_64
            target-type: template_debug
            os: ubuntu-22.04

          - platform: android
            float-precision: double
            arch: x86_32
            target-type: template_debug
            os: ubuntu-22.04

          - platform: ios
            float-precision: double
            arch: arm64
            target-type: template_debug
            os: macos-latest

          - platform: web
            float-precision: double
            arch: wasm32
            target-type: template_debug
            os: ubuntu-22.04

        # Double precision release templates
          - platform: linux
            float-precision: double
            arch: x86_64
            target-type: template_release
            os: ubuntu-22.04

          # - platform: windows
          #   float-precision: double
          #   arch: x86_32
          #   target-type: template_release
          #   os: windows-latest

          # - platform: windows
          #   float-precision: double
          #   arch: x86_64
          #   target-type: template_release
          #   os: windows-latest

          # - platform: macos
          #   float-precision: double
          #   arch: universal
          #   target-type: template_release
          #   os: macos-latest

          # - platform: android
          #   float-precision: double
          #   arch: arm64
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: double
          #   arch: arm32
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: double
          #   arch: x86_64
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: android
          #   float-precision: double
          #   arch: x86_32
          #   target-type: template_release
          #   os: ubuntu-22.04

          # - platform: ios
          #   float-precision: double
          #   arch: arm64
          #   target-type: template_release
          #   os: macos-latest

          # - platform: web
          #   float-precision: double
          #   arch: wasm32
          #   target-type: template_release
          #   os: ubuntu-22.04
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          submodules: true

# Lint
      #- name: Setup clang-format
      #  shell: bash
      #  run: |
      #    python -m pip install clang-format
      #- name: Run clang-format
      #  shell: bash
      #  run: |
      #    clang-format src/** --dry-run --Werror

# Build
      - name: 🔗 GDExtension Debug Build
        uses: ./.github/actions/build
        with:
          platform: ${{ matrix.platform }}
          arch: ${{ matrix.arch }}
          float-precision: ${{ matrix.float-precision }}
          build-target-type: ${{ matrix.target-type }}

# Sign
      - name: Mac Sign
        # Disable sign if secrets are not set
        if: ${{ matrix.platform == 'macos' && env.APPLE_CERT_BASE64 }}
        env:
          APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}
        uses: ./.github/actions/sign
        with:
          FRAMEWORK_PATH: bin/macos/macos.framework
          APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}
          APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}
          APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
          APPLE_DEV_ID: ${{ secrets.APPLE_DEV_ID }}
          APPLE_DEV_TEAM_ID: ${{ secrets.APPLE_DEV_TEAM_ID }}
          APPLE_DEV_APP_ID: ${{ secrets.APPLE_DEV_APP_ID }}

      - name: Windows - Delete compilation files
        if: ${{ matrix.platform == 'windows' }}
        shell: pwsh
        run: |
          Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force
      - name: Upload Artifact
        uses: actions/upload-artifact@v4
        with:
          name: godot-cpp-template-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.float-precision }}-${{ matrix.target-type }}
          path: |
            ${{ github.workspace }}/bin/**

  # Merges all the build artifacts together into a single godot-cpp-template artifact.
  # If you comment out this step, all the builds will be uploaded individually.
  merge:
    runs-on: ubuntu-22.04
    needs: build
    steps:
      - name: Merge Artifacts
        uses: actions/upload-artifact/merge@v4
        with:
          name: godot-cpp-template
          pattern: godot-cpp-template-*
          delete-merged: true


================================================
FILE: .gitignore
================================================
.sconsign.dblite

bin

*.os

*.obj

*.dll
*.exp
*.lib
*.ilk
*.pdb
*.so
*.universal
*.dylib
*.wasm
*.plist
*.enabled
*.tmp
*.TMP
*.uid

.vscode
.cache

compile_commands.json

src/gen

# Ignored demos
demo_*/

# Python
__pycache__


================================================
FILE: .gitmodules
================================================
[submodule "godot-cpp"]
	path = godot-cpp
	url = https://github.com/GuilhermeGSousa/godot-cpp.git


================================================
FILE: LICENSE.md
================================================
Copyright (c) 2024 - Guilherme Sousa

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
# Motion Matching for Godot 4.4
[![Build GDExtension](https://github.com/GuilhermeGSousa/godot-motion-matching/actions/workflows/builds.yml/badge.svg?branch=master)](https://github.com/GuilhermeGSousa/godot-motion-matching/actions/workflows/builds.yml) [![Demo](https://img.shields.io/badge/Extension-Demo-blue)](https://github.com/GuilhermeGSousa/godot-motion-matching-demo)

![](https://github.com/GuilhermeGSousa/godot-motion-matching/blob/master/motion_matching_demo.gif)

Motion Matching is an animation technique that allows you to easily setup character movement animations from large amounts of unlabeled animation data, without requiring any blend trees or state machines.

This extension is fully integrated into Godot's `AnimationTree` system, and can be used in tandem with more traditional animation techniques.

### :gear: How it Works
Motion Matching uses a set of animations contained in an animation library to build a **pose database**, which contains **features** that describe different animation frames in different ways. At runtime, these **features** are periodically compared against what the character is doing, and the animation that best matches those **features** is played.

The only requirement for all this to work is to have animations with both root motion, and a root bone at the foot level.

You can find more on how to set all this up on the wiki [here!](https://github.com/GuilhermeGSousa/godot-motion-matching/wiki)


### :raised_hands: Credits
I want to thank all the contributors that made this project possible!

[Fire](https://github.com/fire)
[GeorgeS](https://github.com/GeorgeS2019)
[Remi](https://github.com/Remi123)
[Roberts Kalnins](https://github.com/rkalnins)

### Sources

- [Road to Next Gen Animation - GDC Talk](https://www.gdcvault.com/play/1023280/Motion-Matching-and-The-Road)
- [Simon Clavet's implementation video](https://www.youtube.com/watch?v=jcpIrw38E-s&ab_channel=SimonClavet)
- [Orange Duck's Blog](https://theorangeduck.com/)
- [Remi's Motion Matching implementation](https://github.com/Remi123/MotionMatching)
- Demo data taken from [O3DE Motion Matching Implementation](https://github.com/o3de/o3de/tree/development/Gems/MotionMatching)

A motion matching implementation in Godot 4.4, implemented following [Dan Holden's article](https://www.theorangeduck.com/page/code-vs-data-driven-displacement).


================================================
FILE: SConstruct
================================================
#!/usr/bin/env python
import os
import sys
from pathlib import Path
from methods import print_error

from SCons.Environment import Environment
from SCons.Variables import Variables
from SCons.Script import ARGUMENTS

libname = "gdmotionmatching"
projectdir = "addons/motion_matching"
double_api_file = "godot-cpp/gdextension/extension_api_double.json"

if ARGUMENTS.get("precision", "single") == "double":
    ARGUMENTS["custom_api_file"] = double_api_file
    print("Using double precision API file: {}".format(double_api_file))
    
localEnv = Environment(tools=["default"], PLATFORM="")

customs = ["custom.py"]
customs = [os.path.abspath(path) for path in customs]

opts = Variables(customs, ARGUMENTS)
opts.Update(localEnv)

Help(opts.GenerateHelpText(localEnv))

env = localEnv.Clone()

submodule_initialized = False
dir_name = 'godot-cpp'
if os.path.isdir(dir_name):
    if os.listdir(dir_name):
        submodule_initialized = True

if not submodule_initialized:
    print_error("""godot-cpp is not available within this folder, as Git submodules haven't been initialized.
Run the following command to download godot-cpp:

    git submodule update --init --recursive""")
    sys.exit(1)

env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs})

env.Append(CPPPATH=["src/"])
sources = Glob("src/*.cpp")
sources += Glob("src/algo/*.cpp")
sources += Glob("src/editor/*.cpp")
sources += Glob("src/features/*.cpp")
sources += Glob("src/math/*.cpp")
sources += Glob("src/modifiers/*.cpp")
sources += Glob("src/synchronizers/*.cpp")

if env["target"] in ["editor", "template_debug"]:
    try:
        doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml"))
        sources.append(doc_data)
    except AttributeError:
        print("Not including class reference as we're targeting a pre-4.3 baseline.")

suffix = env["suffix"].replace(".dev", "")
file = "{}{}{}".format(libname, suffix, env["SHLIBSUFFIX"])
filepath = ""

if env["platform"] == "macos" or env["platform"] == "ios":
    filepath = "{}.framework/".format(env["platform"])
    file = "{}.{}.{}".format(libname, env["platform"], env["target"])

libraryfile = "bin/{}/{}lib{}".format(env["platform"], filepath, file)
library = env.SharedLibrary(
    libraryfile,
    source=sources,
)

copy = env.InstallAs("{}/bin/{}/{}lib{}".format(projectdir, env["platform"], filepath, file), library)

default_args = [library, copy]
Default(*default_args)


================================================
FILE: addons/motion_matching/gdmotionmatching.gdextension
================================================
[configuration]

entry_symbol = "example_library_init"
compatibility_minimum = "4.4"
reloadable = true

[libraries]

macos.debug = "bin/macos/macos.framework/libgdmotionmatching.macos.template_debug"
macos.release = "bin/macos/macos.framework/libgdmotionmatching.macos.template_release"
ios.debug = "bin/ios/ios.framework/libgdmotionmatching.ios.template_debug"
ios.release = "bin/ios/ios.framework/libgdmotionmatching.ios.template_release"
windows.debug.x86_32 = "bin/windows/libgdmotionmatching.windows.template_debug.x86_32.dll"
windows.release.x86_32 = "bin/windows/libgdmotionmatching.windows.template_release.x86_32.dll"
windows.debug.x86_64 = "bin/windows/libgdmotionmatching.windows.template_debug.x86_64.dll"
windows.release.x86_64 = "bin/windows/libgdmotionmatching.windows.template_release.x86_64.dll"
windows.debug.double.x86_32 = "bin/windows/libgdmotionmatching.windows.template_debug.double.x86_32.dll"
windows.release.double.x86_32 = "bin/windows/libgdmotionmatching.windows.template_release.double.x86_32.dll"
windows.debug.double.x86_64 = "bin/windows/libgdmotionmatching.windows.template_debug.double.x86_64.dll"
windows.release.double.x86_64 = "bin/windows/libgdmotionmatching.windows.template_release.double.x86_64.dll"
linux.debug.x86_64 = "bin/linux/libgdmotionmatching.linux.template_debug.x86_64.so"
linux.release.x86_64 = "bin/linux/libgdmotionmatching.linux.template_release.x86_64.so"
linux.debug.double.x86_64 = "bin/linux/libgdmotionmatching.linux.template_debug.double.x86_64.so"
linux.release.double.x86_64 = "bin/linux/libgdmotionmatching.linux.template_release.double.x86_64.so"
linux.debug.arm64 = "bin/linux/libgdmotionmatching.linux.template_debug.arm64.so"
linux.release.arm64 = "bin/linux/libgdmotionmatching.linux.template_release.arm64.so"
linux.debug.rv64 = "bin/linux/libgdmotionmatching.linux.template_debug.rv64.so"
linux.release.rv64 = "bin/linux/libgdmotionmatching.linux.template_release.rv64.so"
android.debug.x86_64 = "bin/android/libgdmotionmatching.android.template_debug.x86_64.so"
android.release.x86_64 = "bin/android/libgdmotionmatching.android.template_release.x86_64.so"
android.debug.x86_32 = "bin/android/libgdmotionmatching.android.template_debug.x86_32.so"
android.release.x86_32 = "bin/android/libgdmotionmatching.android.template_release.x86_32.so"
android.debug.arm64 = "bin/android/libgdmotionmatching.android.template_debug.arm64.so"
android.release.arm64 = "bin/android/libgdmotionmatching.android.template_release.arm64.so"
android.debug.arm32 = "bin/android/libgdmotionmatching.android.template_debug.arm32.so"
android.release.arm32 = "bin/android/libgdmotionmatching.android.template_release.arm32.so"

android.debug.double.x86_64 = "bin/android/libgdmotionmatching.android.template_debug.double.x86_64.so"
android.release.double.x86_64 = "bin/android/libgdmotionmatching.android.template_release.double.x86_64.so"
android.debug.double.x86_32 = "bin/android/libgdmotionmatching.android.template_debug.double.x86_32.so"
android.release.double.x86_32 = "bin/android/libgdmotionmatching.android.template_release.double.x86_32.so"
android.debug.double.arm64 = "bin/android/libgdmotionmatching.android.template_debug.double.arm64.so"
android.release.double.arm64 = "bin/android/libgdmotionmatching.android.template_release.double.arm64.so"
android.debug.double.arm32 = "bin/android/libgdmotionmatching.android.template_debug.double.arm32.so"
android.release.double.arm32 = "bin/android/libgdmotionmatching.android.template_release.double.arm32.so"

web.debug.wasm32 = "bin/web/libgdmotionmatching.web.template_debug.wasm32.wasm"
web.release.wasm32 = "bin/web/libgdmotionmatching.web.template_release.wasm32.wasm"
web.debug.double.wasm32 = "bin/web/libgdmotionmatching.web.template_debug.double.wasm32.wasm"
web.release.double.wasm32 = "bin/web/libgdmotionmatching.web.template_release.double.wasm32.wasm"


================================================
FILE: config.py
================================================
def can_build(env, platform):
    return True


def configure(env):
    pass


def get_doc_classes():
    return [
        "DampedSkeletonModifier",
        "MMAnimationLibrary",
        "MMBoneDataFeature",
        "MMCharacter",
        "MMClampSynchronizer",
        "MMFeature",
        "MMRootMotionSynchronizer",
        "MMSynchronizer",
        "MMTrajectoryFeature",
        "MMAnimationNode",
        "MMQueryInput",
    ]


def get_doc_path():
    return "doc_classes"


================================================
FILE: doc_classes/DampedSkeletonModifier.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="DampedSkeletonModifier" inherits="SkeletonModifier3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A [SkeletonModifier3D] that dampens the motion of a skeleton.
	</brief_description>
	<description>
		This [SkeletonModifier3D] dampens the motion of a skeleton by applying a damping force to each bone. This can be useful for creating more realistic motion, or for smoothing out the motion of a character.
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="halflife" type="float" setter="set_halflife" getter="get_halflife" default="0.1">
			The halflife of the spring used to dampen the motion of the skeleton. This is the time it takes for the spring to reach half of its maximum displacement. A lower value will result in a stiffer spring, while a higher value will result in a softer spring.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMAnimationLibrary.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMAnimationLibrary" inherits="AnimationLibrary" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		An [AnimationLibrary] used for motion matching.
	</brief_description>
	<description>
		This class is used both to store animations to use for motion matching, and to store the pose database generated from those animations. This pose database is used to find the best matching animation for a given input pose. 
		The pose database is generated by the Motion Matching editor tool, and must be up to date with the animations in this library.
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="db_anim_index" type="PackedInt32Array" setter="set_db_anim_index" getter="get_db_anim_index" default="PackedInt32Array()">
			Matches pose indices in the database to their corresponding animation in the library. Generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="db_pose_offset" type="PackedInt32Array" setter="set_db_pose_offset" getter="get_db_pose_offset" default="PackedInt32Array()">
			Matches animation indices in the database to their corresponding starting index in [member motion_data]. Generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="db_time_index" type="PackedFloat32Array" setter="set_db_time_index" getter="get_db_time_index" default="PackedFloat32Array()">
			Matches pose indices in the database to their corresponding time in the animation. Generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="features" type="MMFeature[]" setter="set_features" getter="get_features" default="[]">
			Array of features used to select animation poses in the database.
			Each feature is a [MMFeature] object that defines what to use to select animation poses. The features are used to compute the similarity between the current pose and the poses in the database.
		</member>
		<member name="include_cost_results" type="bool" setter="set_include_cost_results" getter="get_include_cost_results" default="false">
			If true, the cost results will be included in the motion matching results. This is used to debug the motion matching process, and has increased performance cost.
		</member>
		<member name="motion_data" type="PackedFloat32Array" setter="set_motion_data" getter="get_motion_data" default="PackedFloat32Array()">
			Pose database generated by the Motion Matching editor tool from the animations in this library. Corresponds to a N by M matrix, where N is the number of poses in the database and M is the total number of features used to select animation poses.
			Feature values are stored in the same order as the features in the [features] array and may be normalized.
		</member>
		<member name="node_indices" type="PackedInt32Array" setter="set_node_indices" getter="get_node_indices" default="PackedInt32Array()">
			The pose database searches are accelerated using a K-D tree. This array contains the indices of the nodes in the K-D tree. Generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="sampling_rate" type="float" setter="set_sampling_rate" getter="get_sampling_rate" default="1.0">
			The sampling rate used to generate the pose database. This rate describes how many samples per second were taken from the animations in this library to generate the pose database.
		</member>
		<member name="schema_hash" type="int" setter="set_schema_hash" getter="get_schema_hash" default="0">
			The hash of the schema used to generate the pose database. This is used to check if the schema has changed since the last time the pose database was generated. Generated by the Motion Matching editor tool and should not be modified.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMAnimationNode.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMAnimationNode" inherits="AnimationNodeExtension" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		An AnimationNode that uses motion matching to select the best animation to play based on the current state of the character.
	</brief_description>
	<description>
		This node is used to select the best animation to play based on the current state of the character. It selects an animation and animation frame from a given [MMAnimationLibrary].
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="blending_enabled" type="bool" setter="set_blending_enabled" getter="get_blending_enabled" default="true">
			Whether to enable blending between animations. If enabled, the node will blend between the selected animation and the previous animation based on the transition halflife.
		</member>
		<member name="library" type="StringName" setter="set_library" getter="get_library" default="&amp;&quot;&quot;">
			The [MMAnimationLibrary] to use for motion matching. This library contains the animations and their corresponding motion data.
		</member>
		<member name="query_frequency" type="float" setter="set_query_frequency" getter="get_query_frequency" default="2.0">
			The frequency at which the motion matching query is performed. A higher value means more frequent queries, which can lead to smoother transitions but may increase CPU usage.
		</member>
		<member name="transition_halflife" type="float" setter="set_transition_halflife" getter="get_transition_halflife" default="0.1">
			The halflife of the transition between animations. This value determines how quickly the node transitions between animations when a new animation is selected.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMBoneDataFeature.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMBoneDataFeature" inherits="MMFeature" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A [MMFeature] that selects animation based on bone position and rotation.
	</brief_description>
	<description>
		A [MMFeature] that selects animation based on bone position and rotation.
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="bone_names" type="PackedStringArray" setter="set_bone_names" getter="get_bone_names" default="PackedStringArray()">
			Names of the skeleton bones to be used for this feature.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMCharacter.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMCharacter" inherits="CharacterBody3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A [CharacterBody3D] that provides the necessary functionality to use motion matching.
	</brief_description>
	<description>
		[MMCharacter] that characters that use [Skeleton3D] to be animated through motion matching. Motion matching works by searching for the best animation in a database of animations that matches the current state of the character. This node is responsible for compiling the character's state, and send it to an [AnimationTree] to run motion matching searches.
	</description>
	<tutorials>
	</tutorials>
	<methods>
		<method name="get_skeleton_state" qualifiers="const">
			<return type="Dictionary[]" />
			<description>
				Returns an array of dictionaries each containing the [code]position[/code] and [code]velocity[/code] of each bone. Position and velocity are both [Vector3]s, and are relative to the skeleton's root bone.
			</description>
		</method>
		<method name="get_trajectory" qualifiers="const">
			<return type="Dictionary[]" />
			<description>
				Returns an array of dictionaries describing the character's trajectory. Each dictionary contains the [code]position[/code], [code]velocity[/code], [code]facing[/code] and [code]on_floor[/code] at given future point in time.
			</description>
		</method>
		<method name="get_trajectory_history" qualifiers="const">
			<return type="Dictionary[]" />
			<description>
				Returns an array of dictionaries describing the character's past trajectory. Each dictionary contains the [code]position[/code], [code]velocity[/code], [code]facing[/code] and [code]on_floor[/code] at given past point.
			</description>
		</method>
	</methods>
	<members>
		<member name="animation_tree" type="AnimationTree" setter="set_animation_tree" getter="get_animation_tree">
		</member>
		<member name="check_environment" type="bool" setter="set_check_environment" getter="get_check_environment" default="true">
			Used to enable or disable environmental checks along the character's trajectory. When enabled, trajectory generation will also consider collisions and gravity.
		</member>
		<member name="emit_result_signal" type="bool" setter="set_emit_result_signal" getter="get_emit_result_signal" default="false">
			When enabled, the [signal on_query_result] signal will be emitted when a query is made. This signal will contain the result of the query. May increase performance overhead.
		</member>
		<member name="halflife" type="float" setter="set_halflife" getter="get_halflife" default="0.5">
			Describes the time it takes for the character's current velocity to reach its target velocity. Higher values will make the character's movement (and trajectory) smoother, but also less responsive.
		</member>
		<member name="history_delta_time" type="float" setter="set_history_delta_time" getter="get_history_delta_time" default="0.5">
			Time between each point in the trajectory history.
		</member>
		<member name="history_point_count" type="int" setter="set_history_point_count" getter="get_history_point_count" default="3">
			Number of past points to store in the trajectory history.
		</member>
		<member name="is_strafing" type="bool" setter="set_is_strafing" getter="get_is_strafing" default="false">
			When enabled, the character's facing direction on every trajectory point will be set to [member strafe_facing]. This may be useful to implement strafing movement.
			When disabled, the character's facing direction will be calculated based on its velocity.
		</member>
		<member name="skeleton" type="Skeleton3D" setter="set_skeleton" getter="get_skeleton">
			The [Skeleton3D] that will be used to animate the character.
		</member>
		<member name="strafe_facing" type="float" setter="set_strafe_facing" getter="get_strafe_facing" default="0.0">
			When [member is_strafing] is enabled, this value will be used as the facing direction on every trajectory point. When implementing strafing movement, this value is typically set to to the camera's facing direction.
		</member>
		<member name="synchronizer" type="MMSynchronizer" setter="set_synchronizer" getter="get_synchronizer">
			The [MMSynchronizer] that will be used to synchronize the character to with its skeleton.
		</member>
		<member name="target_velocity" type="Vector3" setter="set_target_velocity" getter="get_target_velocity" default="Vector3(0, 0, 0)">
			The character's target velocity. The character's velocity will be interpolated towards this value over time, controlled by [member halflife].
		</member>
		<member name="trajectory_delta_time" type="float" setter="set_trajectory_delta_time" getter="get_trajectory_delta_time" default="0.5">
			Time between each point in the trajectory.
		</member>
		<member name="trajectory_point_count" type="int" setter="set_trajectory_point_count" getter="get_trajectory_point_count" default="10">
			Number of future points to generate in the trajectory.
		</member>
	</members>
	<signals>
		<signal name="on_query_result">
			<param index="0" name="data" type="Dictionary" />
			<description>
				Emitted when a query is made and [member emit_result_signal] is enabled. The signal will contain the result of the query.
			</description>
		</signal>
	</signals>
</class>


================================================
FILE: doc_classes/MMClampSynchronizer.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMClampSynchronizer" inherits="MMSynchronizer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A [MMSynchronizer] that clamps the skeleton's distance to the character's position.
	</brief_description>
	<description>
		A [MMSynchronizer] that clamps the skeleton's distance to the character's position.
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="clamp_distance" type="float" setter="set_clamp_distance" getter="get_clamp_distance" default="10.0">
			The maximum distance between the character and the skeleton. If the distance is greater than this value, the skeleton will be clamped to the character's position.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMFeature.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMFeature" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		Base class for features used in motion matching.
	</brief_description>
	<description>
		[MMFeature] is a base class for features used in motion matching. A feature describes a specific aspect of animations, that can be used to match against at runtime. When baking animation libraries, [MMFeature]s are used to generate motion data.
		[MMFeature]s can be normalized in different ways, such as raw, standard, or min-max normalization. The normalization ensures that different features are on the same scale, and therefore have (by default) an equal impact on the matching result.
	</description>
	<tutorials>
	</tutorials>
	<methods>
		<method name="get_dimension_count" qualifiers="const">
			<return type="int" />
			<description>
				Returns the number of dimensions of the feature. This is the number of values that the feature will output per animation frame.
			</description>
		</method>
	</methods>
	<members>
		<member name="maxes" type="PackedFloat32Array" setter="set_maxes" getter="get_maxes" default="PackedFloat32Array()">
			Maximum values for each dimension of the feature. This is used for normalization, is generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="means" type="PackedFloat32Array" setter="set_means" getter="get_means" default="PackedFloat32Array()">
			Mean values for each dimension of the feature. This is used for normalization, is generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="mins" type="PackedFloat32Array" setter="set_mins" getter="get_mins" default="PackedFloat32Array()">
			Minimum values for each dimension of the feature. This is used for normalization, is generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="normalization_mode" type="int" setter="set_normalization_mode" getter="get_normalization_mode" enum="MMFeature.NormalizationMode" default="1">
			The normalization mode used for the feature. Can be one of [constant Raw], [constant Standard], or [constant MinMax].
		</member>
		<member name="std_devs" type="PackedFloat32Array" setter="set_std_devs" getter="get_std_devs" default="PackedFloat32Array()">
			Standard deviation values for each dimension of the feature. This is used for normalization, is generated by the Motion Matching editor tool and should not be modified.
		</member>
		<member name="weight" type="float" setter="set_weight" getter="get_weight" default="1.0">
			The weight of the feature. This is used to scale the feature's output, and therefore its impact on the matching result. Higher values will make the feature more important in the matching process.
		</member>
	</members>
	<constants>
		<constant name="Raw" value="0" enum="NormalizationMode">
			No normalization is applied to the feature. The feature's output will be used as is.
		</constant>
		<constant name="Standard" value="1" enum="NormalizationMode">
			Uses standard normalization for the feature.
		</constant>
		<constant name="MinMax" value="2" enum="NormalizationMode">
			Uses min-max normalization for the feature.
		</constant>
	</constants>
</class>


================================================
FILE: doc_classes/MMMixSynchronizer.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMMixSynchronizer" inherits="MMSynchronizer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A [MMSynchronizer] that synchronizes the character and skeleton's position and facing direction as a blend between root motion and character motion.
	</brief_description>
	<description>
	</description>
	<tutorials>
	</tutorials>
	<members>
		<member name="root_motion_amount" type="float" setter="set_root_motion_amount" getter="get_root_motion_amount" default="1.0">
			The amount of root motion to apply to the character. A value of 0 means the character will follow its trajectory exactly, while a value of 1 means the character will follow the root motion exactly. Values between 0 and 1 will blend between the two.
		</member>
	</members>
</class>


================================================
FILE: doc_classes/MMQueryInput.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMQueryInput" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		Input for the motion matching query.
	</brief_description>
	<description>
		This class is used to store the input data used to perform queries on a [MMAnimationLibrary]. It is generated by a [MMCharacter] and passed to its [AnimationTree], to be consumed by a [MMAnimationNode] in that tree.
	</description>
	<tutorials>
	</tutorials>
</class>


================================================
FILE: doc_classes/MMRootMotionSynchronizer.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMRootMotionSynchronizer" inherits="MMSynchronizer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		Root motion synchronizer for motion matching.
	</brief_description>
	<description>
		A [MMSynchronizer] that synchronizes the character and its skeleton by using the root motion of the animation.
	</description>
	<tutorials>
	</tutorials>
</class>


================================================
FILE: doc_classes/MMSynchronizer.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMSynchronizer" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A class that synchronizes the a [MMCharacter] with its [Skeleton3D] node.
	</brief_description>
	<description>
		A [MMCharacter] always sets its [Skeleton3D] node to be a top level node, causing both to move independently. By default, the result of motion matching will bring the [Skeleton3D] to follow its [MMCharacter] through root motion.
		While this can look acceptable in some cases, it is generally necessary to synchronize the [Skeleton3D] with its [MMCharacter]. The way this synchronization happens also depends on the use case of motion matching.
	</description>
	<tutorials>
	</tutorials>
</class>


================================================
FILE: doc_classes/MMTrajectoryFeature.xml
================================================
<?xml version="1.0" encoding="UTF-8" ?>
<class name="MMTrajectoryFeature" inherits="MMFeature" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/godotengine/godot/master/doc/class.xsd">
	<brief_description>
		A feature describing the trajectory of a character.
	</brief_description>
	<description>
		This feature is used to match the character's past and future trajectory, with similar trajectories in the animation database. The trajectory is defined by a series of points, each containing the character's position, facing direction, and height.
	</description>
	<tutorials>
	</tutorials>
	<methods>
		<method name="get_trajectory_points" qualifiers="const">
			<return type="Dictionary[]" />
			<param index="0" name="character_transform" type="Transform3D" />
			<param index="1" name="trajectory_data" type="PackedFloat32Array" />
			<description>
				Generates a dictionary of trajectory points from a given character transform and trajectory motion data. The motion data's length should be equal to the number of trajectory points, times the number of trajectory features.
			</description>
		</method>
	</methods>
	<members>
		<member name="facing_weight" type="float" setter="set_facing_weight" getter="get_facing_weight" default="1.0">
		</member>
		<member name="future_delta_time" type="float" setter="set_future_delta_time" getter="get_future_delta_time" default="0.1">
			Time in seconds between each trajectory point in the future.
		</member>
		<member name="future_frames" type="int" setter="set_future_frames" getter="get_future_frames" default="5">
			Number of trajectory points in the future.
		</member>
		<member name="height_weight" type="float" setter="set_height_weight" getter="get_height_weight" default="1.0">
		</member>
		<member name="include_facing" type="bool" setter="set_include_facing" getter="get_include_facing" default="true">
			Whether to include the facing direction in the trajectory points.
		</member>
		<member name="include_height" type="bool" setter="set_include_height" getter="get_include_height" default="false">
			Whether to include the character's height in the trajectory points.
		</member>
		<member name="past_delta_time" type="float" setter="set_past_delta_time" getter="get_past_delta_time" default="0.1">
			Time in seconds between each trajectory point in the past.
		</member>
		<member name="past_frames" type="int" setter="set_past_frames" getter="get_past_frames" default="1">
			Number of trajectory points in the past.
		</member>
	</members>
</class>


================================================
FILE: methods.py
================================================
import os
import sys
from enum import Enum

# Colors are disabled in non-TTY environments such as pipes. This means
# that if output is redirected to a file, it won't contain color codes.
# Colors are always enabled on continuous integration.
_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))


class ANSI(Enum):
    """
    Enum class for adding ansi colorcodes directly into strings.
    Automatically converts values to strings representing their
    internal value, or an empty string in a non-colorized scope.
    """

    RESET = "\x1b[0m"

    BOLD = "\x1b[1m"
    ITALIC = "\x1b[3m"
    UNDERLINE = "\x1b[4m"
    STRIKETHROUGH = "\x1b[9m"
    REGULAR = "\x1b[22;23;24;29m"

    BLACK = "\x1b[30m"
    RED = "\x1b[31m"
    GREEN = "\x1b[32m"
    YELLOW = "\x1b[33m"
    BLUE = "\x1b[34m"
    MAGENTA = "\x1b[35m"
    CYAN = "\x1b[36m"
    WHITE = "\x1b[37m"

    PURPLE = "\x1b[38;5;93m"
    PINK = "\x1b[38;5;206m"
    ORANGE = "\x1b[38;5;214m"
    GRAY = "\x1b[38;5;244m"

    def __str__(self) -> str:
        global _colorize
        return str(self.value) if _colorize else ""


def print_warning(*values: object) -> None:
    """Prints a warning message with formatting."""
    print(f"{ANSI.YELLOW}{ANSI.BOLD}WARNING:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr)


def print_error(*values: object) -> None:
    """Prints an error message with formatting."""
    print(f"{ANSI.RED}{ANSI.BOLD}ERROR:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr)


================================================
FILE: src/algo/kd_tree.cpp
================================================
#include "kd_tree.h"

#include <algorithm>
#include <limits>
#include <numeric>

KDTree::KDTree(const float* data, int point_dimension, int point_count)
    : _point_dim(point_dimension),
      _root(nullptr) {

    _build_tree(data, point_dimension, point_count);
}

KDTree::KDTree(int point_count)
    : _point_dim(point_count),
      _root(nullptr) {
}

KDTree::~KDTree() {
    _clear_tree();
}

int KDTree::search_nn(
    const float* pose_data,
    const float* query,
    const std::vector<float>& dimension_weigths) const {
    float best_distance_squared = std::numeric_limits<float>::max();
    int best_index = -1;
    _search_nn_recursive(pose_data, query, dimension_weigths, _root, best_distance_squared, best_index, 0);
    return best_index;
}

std::vector<int> KDTree::search_knn(const float* pose_data,
                                    const float* query,
                                    const std::vector<float>& dimension_weigths,
                                    int k) const {
    // TODO
    return std::vector<int>();
}

PackedInt32Array KDTree::get_node_indices() const {
    PackedInt32Array indices;
    _get_node_indices_recursive(_root, indices);
    return std::move(indices);
}

void KDTree::rebuild_tree(int point_count, const PackedInt32Array& indices) {
    _clear_tree();

    if (_root) {
        delete _root;
    }

    int value_index = 0;
    _rebuild_tree_recursive(_root, indices, value_index, 0);
}

void KDTree::_build_tree(const float* data, int point_dimension, int point_count) {

    std::vector<int> indices(point_count);
    std::iota(std::begin(indices), std::end(indices), 0);
    _root = _build_tree_recursive(data, indices.data(), point_count, 0);
}

KDTree::TreeNode* KDTree::_build_tree_recursive(const float* data, int* indices, int npoints, int current_depth) {

    if (npoints <= 0) {
        return nullptr;
    }

    KDTree::TreeNode* node = new KDTree::TreeNode();

    const int axis = current_depth % _point_dim;
    node->axis = axis;

    int mid_index = (npoints - 1) / 2;
    std::nth_element(
        indices,
        indices + mid_index,
        indices + npoints,
        [this, &data, &axis](int lhs, int rhs) {
            return data[lhs * _point_dim + axis] < data[rhs * _point_dim + axis];
        });
    data[indices[mid_index] * _point_dim + axis];
    node->index = indices[mid_index];
    node->children[0] = _build_tree_recursive(data, indices, mid_index, current_depth + 1);
    node->children[1] = _build_tree_recursive(data, indices + mid_index + 1, npoints - mid_index - 1, current_depth + 1);

    return node;
}

void KDTree::_clear_tree() {
    _clear_tree_recursive(_root);
}

void KDTree::_clear_tree_recursive(TreeNode* node) {
    if (!node) {
        return;
    }

    _clear_tree_recursive(node->children[0]);
    _clear_tree_recursive(node->children[1]);

    delete node;
}

void KDTree::_search_nn_recursive(
    const float* pose_data,
    const float* query,
    const std::vector<float>& dimension_weigths,
    const TreeNode* node,
    float& best_distance_squared,
    int& best_index,
    int depth) const {

    if (!node) {
        return;
    }

    float dist_squared = 0.0f;
    for (int i = 0; i < _point_dim; i++) {
        float diff = pose_data[node->index * _point_dim + i] - query[i];
        dist_squared += diff * diff * dimension_weigths[i];
    }

    if (dist_squared < best_distance_squared) {
        best_distance_squared = dist_squared;
        best_index = node->index;
    }
    const int axis = depth % _point_dim;
    const float diff = query[axis] - pose_data[node->index * _point_dim + axis];
    const TreeNode* near_child = diff < 0 ? node->children[0] : node->children[1];
    const TreeNode* far_child = diff < 0 ? node->children[1] : node->children[0];

    _search_nn_recursive(
        pose_data,
        query,
        dimension_weigths,
        near_child,
        best_distance_squared,
        best_index,
        depth + 1);

    if (diff * diff * dimension_weigths[axis] < best_distance_squared) {
        _search_nn_recursive(
            pose_data,
            query,
            dimension_weigths,
            far_child,
            best_distance_squared,
            best_index,
            depth + 1);
    }
}

void KDTree::_get_node_indices_recursive(const TreeNode* node, PackedInt32Array& indices) const {

    if (!node) {
        indices.push_back(-1);
        return;
    }

    indices.push_back(node->index);
    _get_node_indices_recursive(node->children[0], indices);
    _get_node_indices_recursive(node->children[1], indices);
}

void KDTree::_rebuild_tree_recursive(TreeNode*& node, const PackedInt32Array& indices, int& value_index, int current_depth) {
    if (value_index >= indices.size() || indices[value_index] == -1) {
        value_index++;
        return;
    }

    node = new TreeNode();
    node->children[0] = nullptr;
    node->children[1] = nullptr;
    node->index = indices[value_index];
    node->axis = current_depth % _point_dim;

    value_index++;
    _rebuild_tree_recursive(node->children[0], indices, value_index, current_depth + 1);
    _rebuild_tree_recursive(node->children[1], indices, value_index, current_depth + 1);
}


================================================
FILE: src/algo/kd_tree.h
================================================
#pragma once

#include "godot_cpp/variant/packed_float32_array.hpp"
#include "godot_cpp/variant/packed_int32_array.hpp"

#include <functional>
#include <vector>

using namespace godot;

class KDTree {

public:
    KDTree(const float* data, int point_dimension, int point_count);
    KDTree(int point_count);
    ~KDTree();

    int search_nn(const float* pose_data, const float* query, const std::vector<float>& dimension_weigths) const;
    std::vector<int> search_knn(const float* pose_data,
                                const float* query,
                                const std::vector<float>& dimension_weigths,
                                int k) const;

    PackedInt32Array get_node_indices() const;

    void rebuild_tree(int point_count, const PackedInt32Array& indices);

private:
    struct TreeNode {

        int axis;
        TreeNode* children[2];
        int index;
    };

    void _build_tree(const float* data, int point_dimension, int point_count);
    TreeNode* _build_tree_recursive(const float* data, int* indices, int npoints, int current_depth);

    void _clear_tree();
    void _clear_tree_recursive(TreeNode* node);

    void _search_nn_recursive(const float* pose_data, const float* query, const std::vector<float>& dimension_weigths, const TreeNode* node, float& best_distance, int& best_index, int depth) const;

    void _get_node_indices_recursive(const TreeNode* node, PackedInt32Array& indices) const;

    void _rebuild_tree_recursive(TreeNode*& node, const PackedInt32Array& indices, int& value_index, int current_depth);
    const int _point_dim = 0;
    TreeNode* _root = nullptr;
};

================================================
FILE: src/circular_buffer.h
================================================
#ifndef CIRCULAR_BUFFER_H
#define CIRCULAR_BUFFER_H

#include <cstddef>
#include <deque>
#include <vector>

template <typename T>
class CircularBuffer {

private:
    std::deque<T> buffer;
    size_t max_size;

public:
    explicit CircularBuffer(size_t size)
        : max_size(size) {
    }

    void clear() {
        buffer.clear();
    }

    void push(T item) {
        if (buffer.size() == max_size) {
            buffer.pop_front();
        }
        buffer.push_back(item);
    }

    T pop() {
        T val = buffer.front();
        buffer.pop_front();
        return val;
    }

    bool empty() const {
        return buffer.empty();
    }

    bool is_full() const {
        return buffer.size() == max_size;
    }

    bool is_empty() const {
        return buffer.empty();
    }

    size_t capacity() const {
        return max_size;
    }

    size_t size() const {
        return buffer.size();
    }

    void resize(size_t size) {
        max_size = size;
        while (buffer.size() > max_size) {
            buffer.pop_front();
        }
    }

    T& operator[](size_t index) {
        return buffer[index];
    }

    const T& operator[](size_t index) const {
        return buffer[index];
    }

    std::vector<T> to_vector() const {
        return std::vector<T>(buffer.begin(), buffer.end());
    }
};
#endif // CIRCULAR_BUFFER_H


================================================
FILE: src/common.h
================================================
#ifndef COMMON_H
#define COMMON_H

#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/core/object.hpp>

#define GETSET(type, variable, ...)   \
    type variable{__VA_ARGS__};       \
    type get_##variable() const {     \
        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);          \
    ClassDB::add_property(get_class_static(), PropertyInfo(variant_type, #variable, ##__VA_ARGS__), STRING_PREFIX(set_, variable), STRING_PREFIX(get_, variable));

#define SMALL_NUMBER 1.e-8
#define KINDA_SMALL_NUMBER 1.e-4

#if defined(DEBUG_ENABLED)
#define DEBUG_PROPERTY_STORAGE_FLAG godot::PropertyUsageFlags::PROPERTY_USAGE_DEFAULT
#else
#define DEBUG_PROPERTY_STORAGE_FLAG godot::PropertyUsageFlags::PROPERTY_USAGE_STORAGE
#endif

#endif // COMMON_H


================================================
FILE: src/editor/animation_post_import_plugin.cpp
================================================
#include "animation_post_import_plugin.h"

#include "godot_cpp/classes/animation_player.hpp"
#include "godot_cpp/classes/file_access.hpp"
#include "godot_cpp/classes/node.hpp"

Variant AnimationPostImportPlugin::_get_option_visibility(const String& p_path, bool p_for_animation, const String& p_option) const {
    if (p_option == "export/animation_export_path") {
        return p_for_animation;
    }
    return EditorScenePostImportPlugin::_get_option_visibility(p_path, p_for_animation, p_option);
}

void AnimationPostImportPlugin::_get_import_options(const String& p_path) {
    add_import_option_advanced(Variant::STRING, "export/animation_export_path", "", PROPERTY_HINT_DIR, "");
}

void AnimationPostImportPlugin::_pre_process(Node* p_scene) {

    const String export_path = get_option_value("export/animation_export_path");

    if (export_path.is_empty()) {
        return;
    }

    Dictionary animations;
    _export_animations(p_scene, animations, export_path);

    Dictionary subresources = get_option_value("_subresources");
    subresources["animations"] = animations;
}

void AnimationPostImportPlugin::_bind_methods() {
}

void AnimationPostImportPlugin::_export_animations(Node* p_node, Dictionary& p_animations, const String& p_export_path) {
    AnimationPlayer* anim_node = Object::cast_to<AnimationPlayer>(p_node);

    if (anim_node) {
        PackedStringArray anim_list = anim_node->get_animation_list();

        for (int32_t i = 0; i < anim_list.size(); i++) {
            StringName anim_name = anim_list[i];

            Dictionary animation;
            animation["save_to_file/enabled"] = true;
            animation["save_to_file/keep_custom_tracks"] = "";

            String clean_anim_name = anim_name.validate_filename();
            String file_path = p_export_path.path_join(clean_anim_name) + ".res";
            int idx = 1;
            while (FileAccess::file_exists(file_path)) {
                file_path = p_export_path.path_join(clean_anim_name + String::num_int64(idx)) + ".res";
                idx++;
            }

            animation["save_to_file/path"] = file_path;

            p_animations[anim_name] = animation;
        }
    }

    for (int32_t i = 0; i < p_node->get_child_count(); i++) {
        _export_animations(p_node->get_child(i), p_animations, p_export_path);
    }
}


================================================
FILE: src/editor/animation_post_import_plugin.h
================================================
#pragma once

#include <godot_cpp/classes/editor_scene_post_import_plugin.hpp>

using namespace godot;

class AnimationPostImportPlugin : public EditorScenePostImportPlugin {
    GDCLASS(AnimationPostImportPlugin, EditorScenePostImportPlugin)

public:
    virtual Variant _get_option_visibility(const String& p_path, bool p_for_animation, const String& p_option) const override;

    virtual void _get_import_options(const String& p_path) override;
    virtual void _pre_process(Node* p_scene) override;

protected:
    static void _bind_methods();

private:
    void _export_animations(Node* p_node, Dictionary& p_animations, const String& p_export_path);
};


================================================
FILE: src/editor/animation_tree_handler_plugin.cpp
================================================
#include "animation_tree_handler_plugin.h"

AnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::_singleton = nullptr;

AnimationTreeHandlerPlugin::AnimationTreeHandlerPlugin() {
    _singleton = this;
    _animation_tree = nullptr;
}

bool AnimationTreeHandlerPlugin::_handles(Object* p_node) const {
    return Object::cast_to<AnimationTree>(p_node) != nullptr;
}

void AnimationTreeHandlerPlugin::_edit(Object* p_object) {
    _animation_tree = Object::cast_to<AnimationTree>(p_object);
}

AnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::get_singleton() {
    return _singleton;
}

void AnimationTreeHandlerPlugin::_bind_methods() {
}

================================================
FILE: src/editor/animation_tree_handler_plugin.h
================================================
#ifndef ANIMATION_TREE_HANDLER_PLUGIN_H
#define ANIMATION_TREE_HANDLER_PLUGIN_H

#include <godot_cpp/classes/animation_tree.hpp>
#include <godot_cpp/classes/editor_plugin.hpp>

using namespace godot;

// The only reason this class exists is to provide a way for MMAnimationNode
// to access the AnimationTree in editor.
// If a new and better way of doing this comes along, please remove this class!
class AnimationTreeHandlerPlugin : public EditorPlugin {
    GDCLASS(AnimationTreeHandlerPlugin, EditorPlugin)

public:
    AnimationTreeHandlerPlugin();
    virtual ~AnimationTreeHandlerPlugin() = default;

    virtual bool _handles(Object* p_node) const override;
    virtual void _edit(Object* p_object) override;

    static AnimationTreeHandlerPlugin* get_singleton();
    AnimationTree* get_animation_tree() const {
        return _animation_tree;
    }

protected:
    static void _bind_methods();

    static AnimationTreeHandlerPlugin* _singleton;

private:
    AnimationTree* _animation_tree;
};

#endif // ANIMATION_TREE_HANDLER_PLUGIN_H

================================================
FILE: src/editor/mm_data_tab.cpp
================================================
#include "mm_data_tab.h"

#include <godot_cpp/classes/h_box_container.hpp>
#include <godot_cpp/classes/label.hpp>
#include <godot_cpp/classes/scroll_container.hpp>
#include <godot_cpp/classes/v_box_container.hpp>

void MMDataTab::set_animation_library(Ref<MMAnimationLibrary> p_library) {
    if (p_library.is_null()) {
        return;
    }

    _clear_data();

    if (p_library->needs_baking()) {
        return;
    }

    Label* min_label = memnew(Label);
    min_label->set_text("Min");
    _stats_data_container->add_child(min_label);

    Label* max_label = memnew(Label);
    max_label->set_text("Max");
    _stats_data_container->add_child(max_label);

    Label* avg_label = memnew(Label);
    avg_label->set_text("Avg");
    _stats_data_container->add_child(avg_label);

    Label* std_label = memnew(Label);
    std_label->set_text("Std");
    _stats_data_container->add_child(std_label);

    TypedArray<MMFeature> features = p_library->get_features();
    for (int i = 0; i < features.size(); i++) {

        Ref<MMFeature> feature = features[i];
        if (feature.is_null()) {
            return;
        }

        for (int i = 0; i < feature->get_dimension_count(); i++) {
            Label* min_value = memnew(Label);
            min_value->set_text(String::num(feature->get_mins()[i]));
            _stats_data_container->add_child(min_value);

            Label* max_value = memnew(Label);
            max_value->set_text(String::num(feature->get_maxes()[i]));
            _stats_data_container->add_child(max_value);

            Label* avg_value = memnew(Label);
            avg_value->set_text(String::num(feature->get_means()[i]));
            _stats_data_container->add_child(avg_value);

            Label* std_value = memnew(Label);
            std_value->set_text(String::num(feature->get_std_devs()[i]));
            _stats_data_container->add_child(std_value);
        }
    }
}

void MMDataTab::clear() {
    _clear_data();
}

void MMDataTab::_bind_methods() {
}

void MMDataTab::_notification(int p_notification) {
    switch (p_notification) {
    case NOTIFICATION_ENTER_TREE: {
        set_name("Data");

        ScrollContainer* scroll_container = memnew(ScrollContainer);
        scroll_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        add_child(scroll_container);

        VBoxContainer* main_container = memnew(VBoxContainer);
        scroll_container->add_child(main_container);

        _stats_data_container = memnew(GridContainer);
        _stats_data_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _stats_data_container->set_columns(4); // min, max, avg, std
        main_container->add_child(_stats_data_container);

        _motion_data_container = memnew(GridContainer);
        _motion_data_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        main_container->add_child(_motion_data_container);

    } break;
    }
}

void MMDataTab::_clear_data() {
    TypedArray<Node> stat_cells = _stats_data_container->get_children();
    for (int i = 0; i < stat_cells.size(); i++) {
        Node* cell = Object::cast_to<Node>(stat_cells[i]);
        cell->queue_free();
    }
}


================================================
FILE: src/editor/mm_data_tab.h
================================================
#pragma once

#include "mm_animation_library.h"

#include <godot_cpp/classes/grid_container.hpp>
#include <godot_cpp/classes/option_button.hpp>
#include <godot_cpp/classes/tab_bar.hpp>

using namespace godot;

class MMDataTab : public TabBar {
    GDCLASS(MMDataTab, TabBar)

public:
    void set_animation_library(Ref<MMAnimationLibrary> p_library);
    void clear();

protected:
    static void _bind_methods();
    void _notification(int p_notification);

private:
    void _clear_data();

    // Data
    GridContainer* _motion_data_container;
    GridContainer* _stats_data_container;
};

================================================
FILE: src/editor/mm_editor.cpp
================================================
#include "editor/mm_editor.h"

#include "mm_animation_library.h"
#include "mm_character.h"
#include "mm_editor.h"

#include <godot_cpp/classes/h_box_container.hpp>
#include <godot_cpp/classes/h_split_container.hpp>
#include <godot_cpp/classes/resource_saver.hpp>

void MMEditor::_bind_methods() {
    ADD_SIGNAL(MethodInfo("animation_visualization_requested", PropertyInfo(Variant::STRING, "animation_lib_name"), PropertyInfo(Variant::STRING, "animation_name"), PropertyInfo(Variant::INT, "pose_index")));
}

void MMEditor::_notification(int p_notification) {
    switch (p_notification) {
    case NOTIFICATION_ENTER_TREE: {
        HSplitContainer* main_container = memnew(HSplitContainer);
        main_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        add_child(main_container);

        VBoxContainer* left_vbox = memnew(VBoxContainer);
        left_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        left_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);

        _library_selector = memnew(OptionButton);
        _library_selector->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _library_selector->connect("item_selected", callable_mp(this, &MMEditor::_anim_lib_selected));
        left_vbox->add_child(_library_selector);

        _bake_button = memnew(Button);
        _bake_button->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _bake_button->connect("pressed", callable_mp(this, &MMEditor::_bake_button_pressed));
        _bake_button->set_text("Bake");
        // Make a bake all button

        left_vbox->add_child(_bake_button);
        main_container->add_child(left_vbox);

        TabContainer* tab_container = memnew(TabContainer);
        tab_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        tab_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);

        _visualization_tab = memnew(MMVisualizationTab);
        _visualization_tab->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _visualization_tab->connect("animation_visualization_requested", callable_mp(this, &MMEditor::_emit_animation_viz_request));
        tab_container->add_child(_visualization_tab);

        _data_tab = memnew(MMDataTab);
        _data_tab->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        tab_container->add_child(_data_tab);

        main_container->add_child(tab_container);
    } break;
    }
}

void MMEditor::_bake_all_animation_libraries(const MMCharacter* p_character, const AnimationMixer* p_mixer, const Skeleton3D* p_skeleton) {

    TypedArray<StringName> animation_libraries = p_mixer->get_animation_library_list();
    for (int64_t i = 0; i < animation_libraries.size(); i++) {
        const StringName& anim_lib_name = animation_libraries[i];
        Ref<MMAnimationLibrary> anim_lib = p_mixer->get_animation_library(anim_lib_name);

        if (anim_lib.is_null()) {
            continue;
        }

        anim_lib->bake_data(p_character, p_mixer, p_skeleton);
        ResourceSaver* resource_saver = ResourceSaver::get_singleton();
        for (int64_t i = 0; i < anim_lib->features.size(); i++) {
            resource_saver->save(anim_lib->features[i]);
        }
        resource_saver->save(anim_lib);
    }
}

void MMEditor::_refresh(bool character_changed) {
    if (!_current_controller) {
        return;
    }

    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();

    if (!animation_mixer) {
        return;
    }

    TypedArray<StringName> animation_library_list = animation_mixer->get_animation_library_list();
    _library_selector->clear();
    for (int64_t i = 0; i < animation_library_list.size(); i++) {
        Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_library_list[i]);

        if (anim_lib.is_null()) {
            continue;
        }

        _library_selector->add_item(animation_library_list[i]);
    }
    if (animation_library_list.is_empty()) {
        _library_selector->select(-1);
    } else if (character_changed) {
        _library_selector->select(0);
    }

    _visualization_tab->clear();
    _data_tab->clear();

    const int32_t selected_index = _library_selector->get_selected();
    if (selected_index != -1) {
        _anim_lib_selected(selected_index);
    }
}

void MMEditor::_bake_button_pressed() {
    if (!_current_controller) {
        return;
    }

    Skeleton3D* skeleton = _current_controller->get_skeleton();

    if (!skeleton) {
        return;
    }

    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();

    if (!animation_mixer) {
        return;
    }

    String selected_lib = _library_selector->get_text();
    if (selected_lib.is_empty()) {
        return;
    }

    Ref<MMAnimationLibrary> mm_lib = animation_mixer->get_animation_library(selected_lib); //

    if (mm_lib.is_null()) {
        return;
    }

    mm_lib->bake_data(_current_controller, animation_mixer, skeleton);
    ResourceSaver::get_singleton()->save(mm_lib);

    _visualization_tab->refresh();
    _data_tab->set_animation_library(mm_lib);
}

void MMEditor::_anim_lib_selected(int p_index) {
    if (p_index == -1) {
        return;
    }

    if (!_current_controller) {
        return;
    }

    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();

    if (!animation_mixer) {
        return;
    }

    // We only want the animation libraries that are MMAnimationLibrary
    TypedArray<StringName> mm_animation_library_list;
    PackedStringArray animation_library_list = animation_mixer->get_animation_library_list();
    for (int i = 0; i < animation_library_list.size(); i++) {
        Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_library_list[i]);

        if (anim_lib.is_null()) {
            continue;
        }

        mm_animation_library_list.push_back(animation_library_list[i]);
    }

    if (p_index >= mm_animation_library_list.size()) {
        return;
    }

    String animation_lib_name = mm_animation_library_list[p_index];
    Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_lib_name);
    if (anim_lib.is_null()) {
        return;
    }

    _visualization_tab->set_animation_library(anim_lib);
    _data_tab->set_animation_library(anim_lib);
}

void MMEditor::_emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {
    emit_signal("animation_visualization_requested", p_animation_lib, p_animation_name, p_pose_index);
    _current_controller->update_gizmos();
}


================================================
FILE: src/editor/mm_editor.h
================================================
#pragma once

#include "mm_data_tab.h"
#include "mm_visualization_tab.h"

#include <godot_cpp/classes/animation_mixer.hpp>
#include <godot_cpp/classes/button.hpp>
#include <godot_cpp/classes/control.hpp>
#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/classes/tab_container.hpp>
#include <godot_cpp/variant/string.hpp>

using namespace godot;

class MMAnimationLibrary;
class MMCharacter;

class MMEditor : public Control {
    GDCLASS(MMEditor, Control)

public:
    void set_character(MMCharacter* p_controller) {
        const bool changed = p_controller != _current_controller;

        _current_controller = p_controller;
        _refresh(changed);
    }

protected:
    static void _bind_methods();
    void _notification(int p_notification);

private:
    static void _bake_all_animation_libraries(const MMCharacter* p_character, const AnimationMixer* p_mixer, const Skeleton3D* p_skeleton);
    void _refresh(bool character_changed);
    void _bake_button_pressed();
    void _anim_lib_selected(int p_index);

    void _emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index);

    MMCharacter* _current_controller;
    String _current_animation_library_name;
    String _current_animation_name;

    OptionButton* _library_selector;
    Button* _bake_button;

    MMVisualizationTab* _visualization_tab;
    MMDataTab* _data_tab;
};


================================================
FILE: src/editor/mm_editor_gizmo_plugin.cpp
================================================
#include "editor/mm_editor_gizmo_plugin.h"

#include <godot_cpp/classes/animation_mixer.hpp>
#include <godot_cpp/classes/editor_node3d_gizmo.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#include "mm_animation_library.h"
#include "mm_character.h"
#include "mm_editor_gizmo_plugin.h"

MMEditorGizmoPlugin::MMEditorGizmoPlugin() {
    create_material("trajectory_material", Color(1, 0, 0, 1), false, true);
    create_material("trajectory_history_material", Color(0, 1, 0, 1), false, true);
    create_material("bone_material", Color(0, 0, 1, 1), false, true);
}

bool MMEditorGizmoPlugin::_has_gizmo(Node3D* p_for_node_3d) const {
    return p_for_node_3d->is_class("MMCharacter");
}

String MMEditorGizmoPlugin::_get_gizmo_name() const {
    return "MMEditorGizmo";
}

void MMEditorGizmoPlugin::_redraw(const Ref<EditorNode3DGizmo>& p_gizmo) {
    p_gizmo->clear();

    MMCharacter* controller = Object::cast_to<MMCharacter>(p_gizmo->get_node_3d());
    if (!controller) {
        return;
    }

    AnimationMixer* animation_mixer = controller->get_animation_mixer();
    if (!animation_mixer) {
        return;
    }

    if (!animation_mixer->has_animation_library(_animation_lib)) {
        return;
    }

    Ref<MMAnimationLibrary> animation_library = animation_mixer->get_animation_library(_animation_lib);
    if (animation_library.is_null()) {
        return;
    }

    animation_library->display_data(
        p_gizmo,
        controller->get_global_transform(),
        _animation_name,
        _pose_index);
}

void MMEditorGizmoPlugin::on_anim_viz_requested(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {
    _animation_lib = p_animation_lib;
    _animation_name = p_animation_name;
    _pose_index = p_pose_index;
}

void MMEditorGizmoPlugin::_bind_methods() {
    ClassDB::bind_method(D_METHOD("on_anim_viz_requested", "animation_lib", "animation_name", "pose_index"), &MMEditorGizmoPlugin::on_anim_viz_requested);
}


================================================
FILE: src/editor/mm_editor_gizmo_plugin.h
================================================
#ifndef MM_EDITOR_GIZMO_PLUGIN_H
#define MM_EDITOR_GIZMO_PLUGIN_H

#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>
#include <godot_cpp/classes/node3d.hpp>

using namespace godot;

class MMEditorGizmoPlugin : public EditorNode3DGizmoPlugin {
    GDCLASS(MMEditorGizmoPlugin, EditorNode3DGizmoPlugin)

public:
    MMEditorGizmoPlugin();

    virtual bool _has_gizmo(Node3D* p_for_node_3d) const override;
    virtual String _get_gizmo_name() const override;
    virtual void _redraw(const Ref<EditorNode3DGizmo>& p_gizmo) override;
    virtual int32_t _get_priority() const override {
        return -1;
    }

    void on_anim_viz_requested(String p_animation_lib, String p_animation_name, int32_t p_pose_index);

protected:
    static void _bind_methods();

private:
    String _animation_lib;
    String _animation_name;
    int32_t _pose_index;
};
#endif // MM_EDITOR_GIZMO_PLUGIN_H


================================================
FILE: src/editor/mm_editor_plugin.cpp
================================================

#include "editor/mm_editor_plugin.h"

#include <godot_cpp/variant/utility_functions.hpp>

#include <godot_cpp/classes/button.hpp>
#include <godot_cpp/classes/tab_bar.hpp>
#include <godot_cpp/classes/tab_container.hpp>
#include <godot_cpp/classes/v_box_container.hpp>

MMEditorPlugin::MMEditorPlugin() {
    _editor = memnew(MMEditor);

    _gizmo_plugin.instantiate();
    add_node_3d_gizmo_plugin(_gizmo_plugin);

    _post_import_plugin.instantiate();
    add_scene_post_import_plugin(_post_import_plugin);

    _bottom_panel_button = add_control_to_bottom_panel(_editor, "MMEditor");
    _editor->connect("animation_visualization_requested", callable_mp(_gizmo_plugin.ptr(), &MMEditorGizmoPlugin::on_anim_viz_requested));
    _make_visible(false);
}

MMEditorPlugin::~MMEditorPlugin() {
    remove_control_from_bottom_panel(_bottom_panel_button);
    remove_node_3d_gizmo_plugin(_gizmo_plugin);
    remove_control_from_bottom_panel(_editor);
    if (_gizmo_plugin.is_valid()) {
        _gizmo_plugin.unref();
    }
}

void MMEditorPlugin::_make_visible(bool p_visible) {
    if (p_visible) {
        _bottom_panel_button->show();
    } else {
        _bottom_panel_button->hide();
        hide_bottom_panel();
    }
}

bool MMEditorPlugin::_handles(Object* p_node) const {
    return (Object::cast_to<MMCharacter>(p_node) != nullptr);
}

void MMEditorPlugin::_edit(Object* p_object) {
    MMCharacter* character = Object::cast_to<MMCharacter>(p_object);
    _make_visible(character != nullptr);

    if (!character) {
        return;
    }

    _editor->set_character(character);
}


================================================
FILE: src/editor/mm_editor_plugin.h
================================================
#ifndef MM_EDITOR_PLUGIN_H
#define MM_EDITOR_PLUGIN_H

#include "animation_post_import_plugin.h"
#include "mm_character.h"
#include "mm_editor.h"
#include "mm_editor_gizmo_plugin.h"

#include <godot_cpp/classes/button.hpp>
#include <godot_cpp/classes/editor_plugin.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/ref_counted.hpp>

using namespace godot;

class MMEditorPlugin : public EditorPlugin {
    GDCLASS(MMEditorPlugin, EditorPlugin)

public:
    MMEditorPlugin();
    ~MMEditorPlugin();

    virtual void _make_visible(bool p_visible) override;
    virtual bool _handles(Object* p_node) const override;
    virtual void _edit(Object* p_object) override;

private:
    static void _bind_methods() {};

    Ref<MMEditorGizmoPlugin> _gizmo_plugin;
    Ref<AnimationPostImportPlugin> _post_import_plugin;

    MMEditor* _editor;
    Button* _bottom_panel_button;
    MMCharacter* _current_controller;
};

#endif // MM_EDITOR_PLUGIN_H


================================================
FILE: src/editor/mm_visualization_tab.cpp
================================================
#include "mm_visualization_tab.h"

void MMVisualizationTab::set_enabled(bool p_enabled) {
    if (p_enabled) {
        _warning_label->hide();
    } else {
        _warning_label->show();
        _warning_label->set_text("Library data is out of date. Bake data to enable visualization.");
    }
    _viz_animation_option_button->set_disabled(!p_enabled);
    _viz_time_slider->set_editable(p_enabled);
}

void MMVisualizationTab::_bind_methods() {
    ADD_SIGNAL(MethodInfo("animation_visualization_requested", PropertyInfo(Variant::STRING, "animation_lib_name"), PropertyInfo(Variant::STRING, "animation_name"), PropertyInfo(Variant::INT, "pose_index")));
}

void MMVisualizationTab::_notification(int p_notification) {
    switch (p_notification) {
    case NOTIFICATION_ENTER_TREE: {
        set_name("Visualization");

        _visualization_vbox = memnew(VBoxContainer);
        _visualization_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        add_child(_visualization_vbox);

        _warning_label = memnew(Label);
        _warning_label->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _warning_label->set_text("No character selected");
        _visualization_vbox->add_child(_warning_label);

        _viz_animation_option_button = memnew(OptionButton);
        _viz_animation_option_button->connect("item_selected", callable_mp(this, &MMVisualizationTab::_viz_anim_selected));
        _visualization_vbox->add_child(_viz_animation_option_button);

        _viz_time_slider = memnew(HSlider);
        _viz_time_slider->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
        _viz_time_slider->set_min(0);
        _viz_time_slider->set_max(1);
        _viz_time_slider->set_step(1);
        _viz_time_slider->connect("value_changed", callable_mp(this, &MMVisualizationTab::_viz_time_changed));
        _visualization_vbox->add_child(_viz_time_slider);
    }
    }
}

void MMVisualizationTab::_viz_time_changed(float p_value) {

    if (_selected_animation_index == -1) {
        return;
    }

    if (_current_animation_library.is_null()) {
        return;
    }

    TypedArray<StringName> animation_list = _current_animation_library->get_animation_list();
    StringName animation_name = animation_list[_selected_animation_index];
    String anim_lib_name = _current_animation_library->get_path().get_file().get_basename();

    _emit_animation_viz_request(anim_lib_name, animation_name, p_value);
}

void MMVisualizationTab::_viz_anim_selected(int p_index) {
    _selected_animation_index = p_index;

    if (_current_animation_library.is_null()) {
        return;
    }

    TypedArray<StringName> animation_list = _current_animation_library->get_animation_list();
    StringName animation_name = animation_list[p_index];

    _viz_time_slider->set_max(_current_animation_library->get_animation_pose_count(animation_name) - 1);
}

void MMVisualizationTab::_emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {
    emit_signal("animation_visualization_requested", p_animation_lib, p_animation_name, p_pose_index);
}

void MMVisualizationTab::refresh() {
    clear();

    if (_current_animation_library.is_null()) {
        return;
    }

    TypedArray<StringName> animations = _current_animation_library->get_animation_list();

    for (int i = 0; i < animations.size(); i++) {
        StringName animation_name = animations[i];
        _viz_animation_option_button->add_item(animation_name, i);
    }

    if (!animations.is_empty()) {
        _viz_animation_option_button->select(0);
        _viz_anim_selected(0);
    }

    set_enabled(!_current_animation_library->needs_baking());
}

void MMVisualizationTab::clear() {
    _selected_animation_index = -1;
    _viz_animation_option_button->clear();
    _viz_animation_option_button->select(-1);

    set_enabled(false);
}


================================================
FILE: src/editor/mm_visualization_tab.h
================================================
#pragma once

#include "mm_animation_library.h"

#include <godot_cpp/classes/grid_container.hpp>
#include <godot_cpp/classes/h_slider.hpp>
#include <godot_cpp/classes/label.hpp>
#include <godot_cpp/classes/option_button.hpp>
#include <godot_cpp/classes/tab_bar.hpp>
#include <godot_cpp/classes/v_box_container.hpp>

using namespace godot;

class MMVisualizationTab : public TabBar {
    GDCLASS(MMVisualizationTab, TabBar)

public:
    void set_animation_library(Ref<MMAnimationLibrary> p_library) {
        _current_animation_library = p_library;
        refresh();
    }

    void set_enabled(bool p_enabled);
    void refresh();
    void clear();

protected:
    static void _bind_methods();
    void _notification(int p_notification);

private:
    void _viz_time_changed(float p_value);
    void _viz_anim_selected(int p_index);
    void _emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index);

    Ref<MMAnimationLibrary> _current_animation_library;

    // Visualization
    Label* _warning_label;
    VBoxContainer* _visualization_vbox;
    OptionButton* _viz_animation_option_button;
    HSlider* _viz_time_slider;
    int _selected_animation_index = -1;
};

================================================
FILE: src/features/mm_bone_data_feature.cpp
================================================
#include "features/mm_bone_data_feature.h"

#include "mm_bone_data_feature.h"
#include "mm_bone_state.h"

#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>
#include <godot_cpp/classes/point_mesh.hpp>
#include <godot_cpp/classes/sphere_mesh.hpp>
#include <godot_cpp/classes/standard_material3d.hpp>
#include <numeric>

void MMBoneDataFeature::setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {
    _skeleton = p_skeleton;
    _skeleton_path = p_player->get_root_motion_track().get_concatenated_names();
    const StringName root_bone_name = p_player->get_root_motion_track().get_concatenated_subnames();
    _root_bone_index = p_skeleton->find_bone(root_bone_name);
}

void MMBoneDataFeature::setup_for_animation(Ref<Animation> animation) {
}

int64_t MMBoneDataFeature::get_dimension_count() const {
    return bone_names.size() * 3;
}

PackedFloat32Array MMBoneDataFeature::bake_animation_pose(Ref<Animation> p_animation, double time) const {
    PackedFloat32Array result;

    for (int64_t i = 0; i < bone_names.size(); ++i) {
        BoneState bone_state = _sample_bone_state(p_animation, time, bone_names[i]);

        result.append(bone_state.pos.x);
        result.append(bone_state.pos.y);
        result.append(bone_state.pos.z);
    }
    return result;
}

PackedFloat32Array MMBoneDataFeature::evaluate_runtime_data(const MMQueryInput& p_query_input) const {
    PackedFloat32Array result;
    for (int64_t i = 0; i < bone_names.size(); ++i) {
        const BoneState& bone_state = p_query_input.skeleton_state.find_bone_state(bone_names[i]);
        const Vector3& pos = bone_state.pos;
        const Quaternion& rot = bone_state.rot;

        result.append(pos.x);
        result.append(pos.y);
        result.append(pos.z);
    }
    return result;
}

void MMBoneDataFeature::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {
    Ref<StandardMaterial3D> material = p_gizmo->get_plugin()->get_material("bone_material", p_gizmo);
    if (material.is_null()) {
        p_gizmo->get_plugin()->create_material("bone_material", Color(0, 0, 1, 1));
        material = p_gizmo->get_plugin()->get_material("bone_material", p_gizmo);
    }

    float* dernomalized_data = new float[get_dimension_count()];
    memcpy(dernomalized_data, p_data, sizeof(float) * get_dimension_count());
    denormalize(dernomalized_data);

    for (int64_t i = 0; i < get_dimension_count(); i += 3) {
        Ref<SphereMesh> sphere_mesh;
        sphere_mesh.instantiate();
        sphere_mesh->set_radius(0.05);
        sphere_mesh->set_height(0.05);
        sphere_mesh->set_material(material);

        const Vector3 pos = Vector3(dernomalized_data[i], dernomalized_data[i + 1], dernomalized_data[i + 2]);

        Transform3D point_transform = Transform3D();
        point_transform.origin = pos;
        p_gizmo->add_mesh(sphere_mesh, material, point_transform, nullptr);
    }

    delete[] dernomalized_data;
}

void MMBoneDataFeature::_bind_methods() {
    BINDER_PROPERTY_PARAMS(MMBoneDataFeature, Variant::PACKED_STRING_ARRAY, bone_names);
}

BoneState MMBoneDataFeature::_sample_bone_state(Ref<Animation> p_animation, double p_time, const String& p_bone_path) const {

    std::vector<Transform3D> bone_transforms;
    int32_t current_bone_index = _skeleton->find_bone(p_bone_path);
    String current_bone;
    const int32_t root_bone_index = _root_bone_index;
    while (current_bone_index != root_bone_index && current_bone_index != -1) {
        current_bone = _skeleton->get_bone_name(current_bone_index);
        const String bone_path = String(_skeleton_path) + String(":") + current_bone;

        Transform3D bone_transform = _skeleton->get_bone_rest(current_bone_index);
        int32_t pos_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D);
        int32_t rot_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D);
        int32_t scl_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_SCALE_3D);

        if (pos_track != -1) {
            bone_transform.origin = p_animation->position_track_interpolate(pos_track, p_time);
        }

        if (rot_track != -1) {
            bone_transform.basis.set_quaternion(p_animation->rotation_track_interpolate(rot_track, p_time));
        }

        if (scl_track != -1) {
            bone_transform.basis.scale(p_animation->scale_track_interpolate(scl_track, p_time));
        }

        bone_transforms.emplace_back(bone_transform);
        current_bone_index = _skeleton->get_bone_parent(current_bone_index);
    }

    Transform3D global_transform = _skeleton->get_bone_global_rest(root_bone_index);
    global_transform = std::reduce(
        bone_transforms.rbegin(),
        bone_transforms.rend(),
        global_transform,
        [](const Transform3D& acc, const Transform3D& bone_transform) {
            return acc * bone_transform;
        });

    BoneState bone_state;
    bone_state.pos = global_transform.origin;
    bone_state.rot = global_transform.basis.get_quaternion();
    bone_state.scl = global_transform.basis.get_scale();
    return bone_state;
}


================================================
FILE: src/features/mm_bone_data_feature.h
================================================
#ifndef MM_BONE_DATA_FEATURE_H
#define MM_BONE_DATA_FEATURE_H

#include "common.h"
#include "features/mm_feature.h"

#include <godot_cpp/classes/skeleton3d.hpp>

using namespace godot;

class MMBoneDataFeature : public MMFeature {
    GDCLASS(MMBoneDataFeature, MMFeature)

public:
    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) override;

    virtual void setup_for_animation(Ref<Animation> animation) override;

    virtual int64_t get_dimension_count() const override;

    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const override;

    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const override;

    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const override;

    GETSET(PackedStringArray, bone_names, PackedStringArray());

protected:
    static void _bind_methods();

private:
    BoneState _sample_bone_state(Ref<Animation> p_animation, double p_time, const String& p_bone_path) const;
    StringName _skeleton_path;
    const Skeleton3D* _skeleton;
    int32_t _root_bone_index;
};

#endif // MM_BONE_DATA_FEATURE_H


================================================
FILE: src/features/mm_feature.cpp
================================================
#include "mm_feature.h"

MMFeature::MMFeature() {
}

MMFeature::~MMFeature() {
}

void MMFeature::normalize(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in normalize.");
        return;
    }
    switch (normalization_mode) {
    case Standard:
        _normalize_standard(p_data);
        break;
    case MinMax:
        _normalize_minmax(p_data);
        break;
    case Raw:
    default:
        break;
    }
}

void MMFeature::denormalize(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in denormalize.");
        return;
    }
    switch (normalization_mode) {
    case Standard:
        _denormalize_standard(p_data);
        break;
    case MinMax:
        _denormalize_minmax(p_data);
        break;
    case Raw:
    default:
        break;
    }
}

void MMFeature::_normalize_minmax(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in _normalize_minmax.");
        return;
    }
    for (int64_t i = 0; i < get_dimension_count(); ++i) {
        const float delta = maxes[i] - mins[i];
        if (abs(delta) < KINDA_SMALL_NUMBER) {
            continue;
        }
        p_data[i] = (p_data[i] - mins[i]) / delta;
    }
}

void MMFeature::_denormalize_minmax(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in _denormalize_minmax.");
        return;
    }
    for (int64_t i = 0; i < get_dimension_count(); ++i) {
        const float delta = maxes[i] - mins[i];
        if (abs(delta) < KINDA_SMALL_NUMBER) {
            continue;
        }
        p_data[i] = (p_data[i] * delta) + mins[i];
    }
}

void MMFeature::_normalize_standard(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in _normalize_standard.");
        return;
    }
    for (int64_t i = 0; i < get_dimension_count(); ++i) {
        p_data[i] = (p_data[i] - means[i]) / (std_devs[i] + KINDA_SMALL_NUMBER);
    }
}

void MMFeature::_denormalize_standard(float* p_data) const {
    if (!p_data) {
        ERR_PRINT_ONCE("Invalid data provided in _denormalize_standard.");
        return;
    }
    ERR_FAIL_COND(std_devs.size() != get_dimension_count());
    for (int64_t i = 0; i < get_dimension_count(); ++i) {
        p_data[i] = (p_data[i] * (std_devs[i] + KINDA_SMALL_NUMBER)) + means[i];
    }
}

void MMFeature::_bind_methods() {
    ClassDB::bind_method(D_METHOD("get_dimension_count"), &MMFeature::get_dimension_count);
    ClassDB::bind_method(D_METHOD("set_normalization_mode", "value"), &MMFeature::set_normalization_mode, DEFVAL(MMFeature::NormalizationMode::Standard));
    ClassDB::bind_method(D_METHOD("get_normalization_mode"), &MMFeature::get_normalization_mode);
    ADD_PROPERTY(PropertyInfo(Variant::INT, "normalization_mode", PROPERTY_HINT_ENUM, "Raw,Standard,MinMax", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_normalization_mode", "get_normalization_mode");

    BINDER_PROPERTY_PARAMS(MMFeature, Variant::FLOAT, weight);
    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, means, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, std_devs, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, maxes, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, mins, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);

    BIND_ENUM_CONSTANT(Raw);
    BIND_ENUM_CONSTANT(Standard);
    BIND_ENUM_CONSTANT(MinMax);
}

VARIANT_ENUM_CAST(MMFeature::NormalizationMode);


================================================
FILE: src/features/mm_feature.h
================================================
#ifndef MM_FEATURE_H
#define MM_FEATURE_H

#include "common.h"
#include "mm_query.h"

#include <godot_cpp/classes/animation.hpp>
#include <godot_cpp/classes/animation_player.hpp>
#include <godot_cpp/classes/editor_node3d_gizmo.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/variant/packed_float32_array.hpp>

using namespace godot;

class MMCharacter;

class MMFeature : public Resource {
    GDCLASS(MMFeature, Resource)

public:
    enum NormalizationMode { Raw,
                             Standard,
                             MinMax };

public:
    MMFeature(/* args */);
    virtual ~MMFeature();
    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {};

    virtual void setup_for_animation(Ref<Animation> animation) {};

    virtual int64_t get_dimension_count() const = 0;

    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const = 0;

    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const = 0;

    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {};

    virtual float calculate_normalized_weight(int64_t p_feature_dim) const {
        return weight / get_dimension_count();
    }

    void normalize(float* p_data) const;
    void denormalize(float* p_data) const;

    GETSET(float, weight, 1.0f);
    GETSET(NormalizationMode, normalization_mode, Standard);
    GETSET(PackedFloat32Array, means);
    GETSET(PackedFloat32Array, std_devs);
    GETSET(PackedFloat32Array, maxes);
    GETSET(PackedFloat32Array, mins);

protected:
    static void _bind_methods();
    void _normalize_minmax(float* p_data) const;
    void _denormalize_minmax(float* p_data) const;
    void _normalize_standard(float* p_data) const;
    void _denormalize_standard(float* p_data) const;
};

#endif // MM_FEATURE_H


================================================
FILE: src/features/mm_trajectory_feature.cpp
================================================
#include "mm_trajectory_feature.h"

#include "math/transforms.h"
#include "mm_character.h"

#include <godot_cpp/classes/animation_player.hpp>
#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>
#include <godot_cpp/classes/point_mesh.hpp>
#include <godot_cpp/classes/sphere_mesh.hpp>
#include <godot_cpp/classes/standard_material3d.hpp>

#include <cstdint>

MMTrajectoryFeature::MMTrajectoryFeature() {
}

MMTrajectoryFeature::~MMTrajectoryFeature() {
}

int64_t MMTrajectoryFeature::get_dimension_count() const {
    return _get_point_dimension_count() * (past_frames + future_frames);
}

void MMTrajectoryFeature::setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {
    const StringName skel_path = p_player->get_root_motion_track().get_concatenated_names();
    const StringName root_bone_name = p_player->get_root_motion_track().get_concatenated_subnames();
    _root_bone = p_skeleton->find_bone(root_bone_name);

    _root_bone_path = String(skel_path) + ":" + String(root_bone_name);

    past_delta_time = p_character->history_delta_time;
    future_delta_time = p_character->trajectory_delta_time;
    past_frames = p_character->history_point_count;
    future_frames = p_character->trajectory_point_count;
}

void MMTrajectoryFeature::setup_for_animation(Ref<Animation> animation) {
    _root_position_track = animation->find_track(_root_bone_path, Animation::TrackType::TYPE_POSITION_3D);
    _root_rotation_track = animation->find_track(_root_bone_path, Animation::TrackType::TYPE_ROTATION_3D);
}

PackedFloat32Array MMTrajectoryFeature::bake_animation_pose(Ref<Animation> p_animation, double time) const {
    PackedFloat32Array result;

    const Vector3 current_pos =
        _root_position_track == -1 ? Vector3() : p_animation->position_track_interpolate(_root_position_track, time);

    const Quaternion current_rotation =
        _root_rotation_track == -1 ? Quaternion() : p_animation->rotation_track_interpolate(_root_rotation_track, time);

    const double animation_length = p_animation->get_length();
    auto add_frame = [this, &result, &p_animation, &current_pos, &current_rotation, &animation_length](double p_time) {
        Vector3 position;
        Quaternion rotation;
        Vector3 extrapolation_velocity;
        const double clamped_time = CLAMP(p_time, 0.0, animation_length);
        const double extrapolation_dt = 0.1;
        if (_root_position_track != -1) {
            Vector3 interpolated_position = p_animation->position_track_interpolate(_root_position_track, clamped_time);
            double extrapolation_time = 0.0;
            if (p_time > animation_length) {
                extrapolation_velocity = (p_animation->position_track_interpolate(_root_position_track, animation_length) -
                                          p_animation->position_track_interpolate(_root_position_track, animation_length - extrapolation_dt)) /
                    extrapolation_dt;
                extrapolation_time = p_time - animation_length;
            } else if (p_time < 0) {
                extrapolation_velocity = (p_animation->position_track_interpolate(_root_position_track, 0.0) -
                                          p_animation->position_track_interpolate(_root_position_track, extrapolation_dt)) /
                    extrapolation_dt;
                extrapolation_time = abs(p_time);
            }

            position = interpolated_position + extrapolation_velocity * extrapolation_time - current_pos;
            position = current_rotation.xform_inv(position);
        }

        if (_root_rotation_track != -1) {
            rotation = p_animation->rotation_track_interpolate(_root_rotation_track, clamped_time) * current_rotation.inverse();
        }

        result.append(position.x);

        if (include_height) {
            result.append(position.y);
        }

        result.append(position.z);

        if (include_facing) {
            const Vector3 facing = rotation.get_euler();
            result.append(facing.y);
        }
    };

    // We do not include the first frame
    for (int64_t i = 1; i < future_frames + 1; i++) {
        add_frame(time + future_delta_time * i);
    }

    for (int64_t i = 1; i < past_frames + 1; i++) {
        add_frame(time - past_delta_time * i);
    }

    return result;
}

PackedFloat32Array MMTrajectoryFeature::evaluate_runtime_data(const MMQueryInput& p_query_input) const {
    const Transform3D& character_transform = p_query_input.character_transform;

    // Get the trajectory points in local space
    PackedFloat32Array result;
    auto add_point = [this, &character_transform, &result](int index, const MMTrajectoryPoint& trajectory_point) {
        const Vector3 local_position = character_transform.basis.xform_inv(trajectory_point.position - character_transform.origin);

        result.append(local_position.x);
        if (include_height) {
            result.append(local_position.y);
        }
        result.append(local_position.z);
        if (include_facing) {
            result.append(global_to_local_facing_angle(trajectory_point.facing_angle, character_transform));
        }
    };

    // The first point of the trajectory represents the player's current state
    // We do not match the first point of the trajectory (character position)
    for (int64_t i = 1; i < future_frames + 1; i++) {
        const size_t max_size = p_query_input.trajectory.size();
        if (max_size == 0) {
            add_point(i, MMTrajectoryPoint());
        } else if (i >= max_size) {
            add_point(i, p_query_input.trajectory[max_size - 1]);
        } else {
            add_point(i, p_query_input.trajectory[i]);
        }
    }

    for (int64_t i = 0; i < past_frames; i++) {
        const size_t max_size = p_query_input.trajectory_history.size();

        if (max_size == 0) {
            add_point(i, MMTrajectoryPoint());
        } else if (i >= max_size) {
            add_point(i, p_query_input.trajectory_history[max_size - 1]);
        } else {
            add_point(i, p_query_input.trajectory_history[i]);
        }
    }

    return result;
}

void MMTrajectoryFeature::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {

    Ref<StandardMaterial3D> trajectory_material = p_gizmo->get_plugin()->get_material("trajectory_material", p_gizmo);
    if (trajectory_material.is_null()) {
        p_gizmo->get_plugin()->create_material("trajectory_material", Color(1, 0, 0, 1));
        trajectory_material = p_gizmo->get_plugin()->get_material("trajectory_material", p_gizmo);
    }

    Ref<StandardMaterial3D> history_material = p_gizmo->get_plugin()->get_material("trajectory_history_material", p_gizmo);
    if (history_material.is_null()) {
        p_gizmo->get_plugin()->create_material("trajectory_history_material", Color(0, 1, 0, 1));
        history_material = p_gizmo->get_plugin()->get_material("trajectory_history_material", p_gizmo);
    }

    float* dernomalized_data = new float[get_dimension_count()];
    memcpy(dernomalized_data, p_data, sizeof(float) * get_dimension_count());
    denormalize(dernomalized_data);

    auto draw_point = [this,
                       &p_gizmo,
                       dernomalized_data](int64_t i, Ref<StandardMaterial3D> material) {
        Ref<SphereMesh> sphere_mesh;
        sphere_mesh.instantiate();
        sphere_mesh->set_radius(0.10);
        sphere_mesh->set_height(0.10);
        sphere_mesh->set_material(material);

        MMTrajectoryPoint point;
        point.position = Vector3(
            dernomalized_data[i],
            include_height ? dernomalized_data[i + 1] : 0,
            include_height ? dernomalized_data[i + 2] : dernomalized_data[i + 1]);

        Transform3D point_transform = Transform3D();
        point_transform.origin = point.position;
        p_gizmo->add_mesh(sphere_mesh, material, point_transform, nullptr);

        if (include_facing) {
            const float facing_angle = dernomalized_data[i + (include_height ? 3 : 2)];
            const Vector3 facing = Vector3(UtilityFunctions::sin(facing_angle), 0.f, UtilityFunctions::cos(facing_angle));
            const Vector3 facing_end = point.position + facing * 0.5f;
            PackedVector3Array lines;
            lines.push_back(point.position);
            lines.push_back(facing_end);
            p_gizmo->add_lines(lines, material);
        }
    };
    int64_t i = 0;
    for (; i < future_frames * _get_point_dimension_count(); i += _get_point_dimension_count()) {
        draw_point(i, trajectory_material);
    }

    for (; i < get_dimension_count(); i += _get_point_dimension_count()) {
        draw_point(i, history_material);
    }

    delete[] dernomalized_data;
}

float MMTrajectoryFeature::calculate_normalized_weight(int64_t p_feature_dim) const {

    float weight = MMFeature::calculate_normalized_weight(p_feature_dim);

    const uint32_t point_dim = _get_point_dimension_count();

    const bool is_height = include_height && (p_feature_dim % point_dim) == 2;
    const bool is_facing = include_facing && (p_feature_dim % point_dim) == (include_height ? 3 : 2);

    if (is_height) {
        weight *= height_weight;
    } else if (is_facing) {
        weight *= facing_weight;
    }

    return weight;
}

TypedArray<Dictionary> MMTrajectoryFeature::get_trajectory_points(const Transform3D& p_character_transform, const PackedFloat32Array& p_trajectory_data) const {
    ERR_FAIL_COND_V(p_trajectory_data.is_empty(), TypedArray<Dictionary>());

    PackedFloat32Array denormalized_data = PackedFloat32Array(p_trajectory_data);
    denormalize(denormalized_data.ptrw());

    TypedArray<Dictionary> result;
    const int offset = _get_point_dimension_count();
    for (int i = 0; i < future_frames * offset; i += offset) {
        MMTrajectoryPoint point;
        point.position = Vector3(denormalized_data[i], include_height ? denormalized_data[i + 1] : 0, include_height ? denormalized_data[i + 2] : denormalized_data[i + 1]);
        point.position = p_character_transform.xform(point.position);

        if (include_facing) {
            // const float local_facing_angle = denormalized_data[i + (include_height ? 3 : 2)];
            point.facing_angle = 0.0; // TODO
        }

        Dictionary data;
        data.get_or_add("position", point.position);
        data.get_or_add("facing", point.facing_angle);

        result.push_back(data);
    }
    return result;
}

bool MMTrajectoryFeature::get_include_height() const {
    return include_height;
}

void MMTrajectoryFeature::set_include_height(bool value) {
    include_height = value;
    notify_property_list_changed();
}

bool MMTrajectoryFeature::get_include_facing() const {
    return include_facing;
}

void MMTrajectoryFeature::set_include_facing(bool value) {
    include_facing = value;
    notify_property_list_changed();
}

void MMTrajectoryFeature::_validate_property(PropertyInfo& p_property) const {
    if (p_property.name == StringName("facing_weight")) {
        if (!include_facing) {
            p_property.usage = PROPERTY_USAGE_NO_EDITOR;
        }
    }

    if (p_property.name == StringName("height_weight")) {
        if (!include_height) {
            p_property.usage = PROPERTY_USAGE_NO_EDITOR;
        }
    }
}

void MMTrajectoryFeature::_bind_methods() {
    ClassDB::bind_method(D_METHOD("get_trajectory_points", "character_transform", "trajectory_data"), &MMTrajectoryFeature::get_trajectory_points);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, past_delta_time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::INT, past_frames, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, future_delta_time, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::INT, future_frames, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::BOOL, include_height);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, height_weight);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::BOOL, include_facing);
    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, facing_weight);
}

uint32_t MMTrajectoryFeature::_get_point_dimension_count() const {
    size_t dimensions = 2; // x, z
    if (include_height) {
        dimensions++; // y
    }
    if (include_facing) {
        dimensions++; // facing
    }
    return dimensions;
}


================================================
FILE: src/features/mm_trajectory_feature.h
================================================
#ifndef MM_TRAJECTORY_FEATURE_H
#define MM_TRAJECTORY_FEATURE_H

#include "common.h"
#include "features/mm_feature.h"
#include <cstdint>

using namespace godot;

class MMTrajectoryFeature : public MMFeature {
    GDCLASS(MMTrajectoryFeature, MMFeature)

public:
    MMTrajectoryFeature(/* args */);
    virtual ~MMTrajectoryFeature();

    virtual int64_t get_dimension_count() const override;

    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) override;

    virtual void setup_for_animation(Ref<Animation> animation) override;

    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const override;

    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const override;

    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const override;

    virtual float calculate_normalized_weight(int64_t p_feature_dim) const override;

    TypedArray<Dictionary> get_trajectory_points(const Transform3D& p_character_transform, const PackedFloat32Array& p_trajectory_data) const;

    GETSET(double, past_delta_time, 0.1);
    GETSET(int64_t, past_frames, 1);
    GETSET(double, future_delta_time, 0.1);
    GETSET(int64_t, future_frames, 5);

    bool include_height{false};
    bool get_include_height() const;
    void set_include_height(bool value);

    GETSET(float, height_weight, 1.0);

    bool include_facing{true};
    bool get_include_facing() const;
    void set_include_facing(bool value);

    GETSET(float, facing_weight, 1.0);

protected:
    void _validate_property(PropertyInfo& p_property) const;
    static void _bind_methods();

private:
    uint32_t _get_point_dimension_count() const;

    int _root_bone{-1};
    NodePath _root_bone_path;
    int _root_position_track{-1};
    int _root_rotation_track{-1};
};

#endif // MM_TRAJECTORY_FEATURE_H


================================================
FILE: src/math/hash.h
================================================
#pragma once

#include <godot_cpp/core/type_info.hpp>

using namespace godot;

int64_t hash_combine(int64_t a, int64_t b) {
    return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}

================================================
FILE: src/math/spring.hpp
================================================
#pragma once

#include <math.h>

#include <cmath>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/classes/resource.hpp>
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/core/method_bind.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/variant.hpp>

using namespace godot;

namespace Spring {

static constexpr real_t Ln2 = 0.69314718056;

static real_t inline square(real_t x) {
    return x * x;
}

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 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 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 <typename T>
static inline T clampf(T x, T min, T max) {
    static_assert(std::is_arithmetic_v<T>, "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 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;
}
}; // namespace Spring

================================================
FILE: src/math/stats.hpp
================================================
#pragma once

#include <algorithm>
#include <cfloat>
#include <cmath>

class StatsAccumulator {
private:
    float sum;
    float sum_of_squares;
    float max_value;
    float min_value;
    int count;

public:
    StatsAccumulator()
        : sum(0.0f), sum_of_squares(0.0f), max_value(-INFINITY), min_value(INFINITY), count(0) {
    }

    void add_sample(float sample) {
        sum += sample;
        sum_of_squares += sample * sample;
        max_value = std::max(max_value, sample);
        min_value = std::min(min_value, sample);
        count++;
    }

    float get_mean() const {
        return sum / count;
    }

    float get_max() const {
        return max_value;
    }

    float get_min() const {
        return min_value;
    }

    float get_standard_deviation() const {
        const float mean = get_mean();
        const float variance = (sum_of_squares / count) - (mean * mean);
        if (variance < FLT_EPSILON) {
            return 0.0f;
        }
        return sqrt(variance);
    }

    void reset() {
        sum = 0.0f;
        sum_of_squares = 0.0f;
        max_value = -INFINITY;
        min_value = INFINITY;
        count = 0;
    }
};

float distance_squared(const float* a, const float* b, int dim) {
    float distance = 0.0f;
    for (int i = 0; i < dim; i++) {
        const float diff = a[i] - b[i];
        distance += diff * diff;
    }
    return distance;
}

================================================
FILE: src/math/transforms.h
================================================
#pragma once

#include <godot_cpp/variant/transform3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

using namespace godot;

float global_to_local_facing_angle(float local_facing_angle, const Transform3D& transform) {

    const Vector3 global_dir(UtilityFunctions::cos(local_facing_angle), 0.f, UtilityFunctions::sin(local_facing_angle));

    const Vector3 local_dir = transform.get_basis().get_quaternion().xform(global_dir);

    return UtilityFunctions::atan2(local_dir.z, local_dir.x);
}

================================================
FILE: src/mm_animation_library.cpp
================================================
#include "mm_animation_library.h"

#include "common.h"
#include "features/mm_feature.h"
#include "math/hash.h"
#include "math/stats.hpp"
#include "mm_character.h"

void MMAnimationLibrary::bake_data(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {
    motion_data.clear();
    db_anim_index.clear();
    db_time_index.clear();
    db_pose_offset.clear();

    int32_t dim_count = 0;
    for (auto i = 0; i < features.size(); ++i) {
        MMFeature* f = Object::cast_to<MMFeature>(features[i]);
        dim_count += f->get_dimension_count();
        f->setup_skeleton(p_character, p_player, p_skeleton);
    }

    TypedArray<StringName> animation_list = get_animation_list();

    // Normalization data
    std::vector<std::vector<StatsAccumulator>> stats(features.size());

    PackedFloat32Array data;
    int32_t current_pose_offset = 0;
    // For every animation
    for (int64_t animation_index = 0; animation_index < animation_list.size(); animation_index++) {
        const StringName& anim_name = animation_list[animation_index];
        Ref<Animation> animation = get_animation(anim_name);
        db_pose_offset.push_back(current_pose_offset);

        // Initialize features
        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
            MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
            stats[feature_index].resize(feature->get_dimension_count());
            feature->setup_for_animation(animation);
        }

        const double animation_length = animation->get_length();
        const double time_step = 1.0f / get_sampling_rate();
        int pose_count = 0;
        // Every time step
        for (double time = 0; time < animation_length; time += time_step) {
            PackedFloat32Array pose_data;
            // For every feature
            for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
                const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
                const PackedFloat32Array feature_data = feature->bake_animation_pose(animation, time);

                ERR_FAIL_COND(feature_data.size() != feature->get_dimension_count());

                // Update stats
                for (int64_t feature_element_index = 0; feature_element_index < feature_data.size(); feature_element_index++) {
                    stats[feature_index][feature_element_index].add_sample(feature_data[feature_element_index]);
                }

                pose_data.append_array(feature_data);
                current_pose_offset += feature->get_dimension_count();
            }

            ERR_FAIL_COND(pose_data.size() != dim_count);

            // Update dataset
            data.append_array(pose_data);
            db_anim_index.push_back(animation_index);
            db_time_index.push_back(time);
            pose_count++;
        }
    }

    // Compute mean and standard deviation
    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
        MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);

        PackedFloat32Array feature_means;
        feature_means.resize(feature->get_dimension_count());
        PackedFloat32Array feature_std_devs;
        feature_std_devs.resize(feature->get_dimension_count());
        PackedFloat32Array feature_mins;
        feature_mins.resize(feature->get_dimension_count());
        PackedFloat32Array feature_maxes;
        feature_maxes.resize(feature->get_dimension_count());

        for (int64_t feature_element_index = 0; feature_element_index < feature->get_dimension_count(); feature_element_index++) {
            feature_means.set(feature_element_index, stats[feature_index][feature_element_index].get_mean());
            feature_std_devs.set(feature_element_index, stats[feature_index][feature_element_index].get_standard_deviation());
            feature_mins.set(feature_element_index, stats[feature_index][feature_element_index].get_min());
            feature_maxes.set(feature_element_index, stats[feature_index][feature_element_index].get_max());
        }

        feature->set_means(feature_means);
        feature->set_std_devs(feature_std_devs);
        feature->set_mins(feature_mins);
        feature->set_maxes(feature_maxes);
    }

    _normalize_data(data, dim_count);

    motion_data = data.duplicate();

    schema_hash = compute_features_hash();

    _kd_tree = std::make_unique<KDTree>(motion_data.ptr(), dim_count, ((int32_t)motion_data.size()) / dim_count);
    node_indices = PackedInt32Array(_kd_tree->get_node_indices());
}

MMQueryOutput MMAnimationLibrary::query(const MMQueryInput& p_query_input) {
    PackedFloat32Array query_vector = PackedFloat32Array();
    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
        if (!feature) {
            continue;
        }
        PackedFloat32Array feature_data = feature->evaluate_runtime_data(p_query_input);
        feature->normalize(feature_data.ptrw());
        query_vector.append_array(feature_data);
    }

    MMQueryOutput result;
    result = _search_kd_tree(query_vector);
    return std::move(result);
}

int64_t MMAnimationLibrary::get_dim_count() const {
    int64_t dim_count = 0;
    for (auto i = 0; i < features.size(); ++i) {
        MMFeature* f = Object::cast_to<MMFeature>(features[i]);
        dim_count += f->get_dimension_count();
    }

    return dim_count;
}

int64_t MMAnimationLibrary::get_animation_pose_count(String p_animation_name) const {
    TypedArray<StringName> animation_list = get_animation_list();
    Ref<Animation> animation = get_animation(p_animation_name);
    if (animation.is_null()) {
        return 0;
    };
    const double animation_length = animation->get_length();
    const double time_step = 1.0f / get_sampling_rate();
    return static_cast<int32_t>(UtilityFunctions::floor(animation_length / time_step));
}

void MMAnimationLibrary::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D& p_transform, String p_animation_name, int32_t p_pose_index) const {
    const int32_t anim_index = get_animation_list().find(p_animation_name);
    const int32_t dim_count = get_dim_count();
    int32_t start_frame_index = db_pose_offset[anim_index];
    int32_t frame_index = start_frame_index + p_pose_index * dim_count;

    for (size_t feature_index = 0; feature_index < features.size(); feature_index++) {
        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
        const float* frame_motion_data = motion_data.ptr() + frame_index;
        feature->display_data(p_gizmo, p_transform, frame_motion_data);
        frame_index += feature->get_dimension_count();
    }
}

int64_t MMAnimationLibrary::compute_features_hash() const {
    int64_t hash = 0;
    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
        MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
        TypedArray<Dictionary> feature_properties = feature->get_property_list();
        for (int64_t property_index = 0; property_index < feature_properties.size(); property_index++) {
            // TODO: This needs work, but works for now
            const Dictionary feature_property = Dictionary(feature_properties[property_index]);
            const String property_name = feature_property["name"];
            const uint32_t property_type = feature_property["type"];
            const bool property_is_stats =
                property_name == "means" ||
                property_name == "std_devs" ||
                property_name == "mins" ||
                property_name == "maxes";
            const bool is_resource_property = property_name.contains("resource");
            if (property_type != Variant::OBJECT &&
                property_type != Variant::NIL &&
                !property_is_stats &&
                !is_resource_property) {
                hash = hash_combine(hash, property_name.hash());
                hash = hash_combine(hash, feature->get(property_name).hash());
            }
        }
    }
    return hash;
}

bool MMAnimationLibrary::needs_baking() const {
    return schema_hash != compute_features_hash();
}

void MMAnimationLibrary::_normalize_data(PackedFloat32Array& p_data, size_t p_dim_count) const {
    ERR_FAIL_COND(p_data.size() % p_dim_count != 0);

    for (int64_t frame_index = 0; frame_index < p_data.size(); frame_index += p_dim_count) {

        int dim_index = 0;
        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
            const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
            feature->normalize(p_data.ptrw() + frame_index + dim_index);
            dim_index += feature->get_dimension_count();
        }
    }
}

float MMAnimationLibrary::_compute_feature_costs(int p_pose_index, const PackedFloat32Array& p_query, Dictionary* p_feature_costs) const {
    float pose_cost = 0.f;
    int start_frame_index = p_pose_index * p_query.size();
    int start_feature_index = 0;
    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
        if (!feature) {
            continue;
        }

        float feature_cost = 0.f;
        for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {
            feature_cost += distance_squared((motion_data.ptr() + start_frame_index + start_feature_index + feature_dim_index),
                                             (p_query.ptr() + start_feature_index + feature_dim_index),
                                             1) *
                feature->calculate_normalized_weight(feature_dim_index);
        }

        if (p_feature_costs) {
            p_feature_costs->get_or_add(feature->get_class(), feature_cost);
        }

        pose_cost += feature_cost;
        start_feature_index += feature->get_dimension_count();
    }
    return pose_cost;
}

MMQueryOutput MMAnimationLibrary::_search_naive(const PackedFloat32Array& p_query) const {
    float cost = FLT_MAX;
    MMQueryOutput result;
    TypedArray<StringName> animation_list = get_animation_list();
    int32_t dim_count = p_query.size();
    int64_t best_pose_index = -1;
    for (int64_t start_frame_index = 0; start_frame_index < motion_data.size(); start_frame_index += dim_count) {
        int start_feature_index = start_frame_index;
        float pose_cost = 0.f;
        Dictionary feature_costs;
        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
            const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
            if (!feature) {
                continue;
            }

            float feature_cost = 0.f;
            for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {
                feature_cost += distance_squared((motion_data.ptr() + start_frame_index + start_feature_index + feature_dim_index),
                                                 (p_query.ptr() + start_feature_index + feature_dim_index),
                                                 1) *
                    feature->calculate_normalized_weight(feature_dim_index);
            }

            feature_costs.get_or_add(feature->get_class(), feature_cost);
            pose_cost += feature_cost;
            start_feature_index += feature->get_dimension_count();
        }

        if (pose_cost < cost) {
            cost = pose_cost;
            best_pose_index = start_frame_index / dim_count;
        }
    }

    String library_name = get_path().get_file().get_basename() + "/";
    if (library_name.is_empty()) {
        library_name = get_name() + "/";
    }
    result.animation_match = library_name + UtilityFunctions::str(animation_list[db_anim_index[best_pose_index]]);
    result.time_match = db_time_index[best_pose_index];

    if (include_cost_results) {
        result.matched_frame_data = motion_data.slice(
            best_pose_index * dim_count,
            (best_pose_index + 1) * dim_count);

        result.cost = _compute_feature_costs(
            best_pose_index,
            p_query,
            &result.feature_costs);
    }

    return result;
}

MMQueryOutput MMAnimationLibrary::_search_kd_tree(const PackedFloat32Array& p_query) {
    if (!_kd_tree) {
        int32_t dim_count = p_query.size();
        _kd_tree = std::make_unique<KDTree>(dim_count);
        _kd_tree->rebuild_tree(((int32_t)motion_data.size()) / dim_count, node_indices);
    }

    std::vector<float> dimension_weights;
    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {
        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);
        if (!feature) {
            continue;
        }
        for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {
            dimension_weights.push_back(feature->calculate_normalized_weight(feature_dim_index));
        }
    }

    int nodes_visited = 0;
    int best_pose_index = _kd_tree->search_nn(
        motion_data.ptr(),
        p_query.ptr(),
        dimension_weights);

    MMQueryOutput result;
    String library_name = get_path().get_file().get_basename() + "/";
    if (library_name.is_empty()) {
        library_name = get_name() + "/";
    }
    TypedArray<StringName> animation_list = get_animation_list();

    result.animation_match = library_name + UtilityFunctions::str(animation_list[db_anim_index[best_pose_index]]);
    result.time_match = db_time_index[best_pose_index];

    if (include_cost_results) {
        result.matched_frame_data = motion_data.slice(
            best_pose_index * p_query.size(),
            (best_pose_index + 1) * p_query.size());

        result.cost =
            _compute_feature_costs(
                best_pose_index,
                p_query,
                &result.feature_costs);
    }

    return result;
}

void MMAnimationLibrary::_bind_methods() {
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::ARRAY, features, PROPERTY_HINT_TYPE_STRING, UtilityFunctions::str(Variant::OBJECT) + '/' + UtilityFunctions::str(Variant::BASIS) + ":MMFeature");
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::FLOAT, sampling_rate);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::BOOL, include_cost_results);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, motion_data, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, db_anim_index, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_time_index, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_pose_offset, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, schema_hash, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, node_indices, PROPERTY_HINT_NONE, "", DEBUG_PROPERTY_STORAGE_FLAG);
}


================================================
FILE: src/mm_animation_library.h
================================================
#pragma once

#include "algo/kd_tree.h"
#include "common.h"
#include "features/mm_feature.h"
#include "mm_query.h"

#include <godot_cpp/classes/animation_library.hpp>
#include <godot_cpp/classes/editor_node3d_gizmo.hpp>
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/variant/typed_array.hpp>

#include <memory>

class MMFeature;
class MMCharacter;

class MMAnimationLibrary : public AnimationLibrary {
    GDCLASS(MMAnimationLibrary, AnimationLibrary)

public:
    void bake_data(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton);
    MMQueryOutput query(const MMQueryInput& p_query_input);
    int64_t get_dim_count() const;
    int64_t get_animation_pose_count(String p_animation_name) const;

    void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D& p_transform, String p_animation_name, int32_t p_pose_index) const;

    int64_t compute_features_hash() const;

    bool needs_baking() const;

    GETSET(TypedArray<MMFeature>, features)
    GETSET(float, sampling_rate, 1.f)
    GETSET(bool, include_cost_results, false);

    // Database data
    GETSET(PackedFloat32Array, motion_data)
    GETSET(PackedInt32Array, db_anim_index)
    GETSET(PackedFloat32Array, db_time_index)
    GETSET(PackedInt32Array, db_pose_offset)
    GETSET(int64_t, schema_hash)

    // KD Tree data
    GETSET(PackedInt32Array, node_indices);

protected:
    static void _bind_methods();

private:
    void _normalize_data(PackedFloat32Array& p_data, size_t p_dim_count) const;
    float _compute_feature_costs(int p_pose_index, const PackedFloat32Array& p_query, Dictionary* p_feature_costs) const;

    MMQueryOutput _search_naive(const PackedFloat32Array& p_query) const;
    MMQueryOutput _search_kd_tree(const PackedFloat32Array& p_query);

    std::unique_ptr<KDTree> _kd_tree;
};


================================================
FILE: src/mm_animation_node.cpp
================================================
#include "mm_animation_node.h"

#include "editor/animation_tree_handler_plugin.h"
#include "math/spring.hpp"
#include "mm_query.h"

#include <godot_cpp/classes/animation.hpp>
#include <godot_cpp/classes/animation_library.hpp>
#include <godot_cpp/classes/animation_tree.hpp>
#include <godot_cpp/classes/engine.hpp>

// Only play the matched animation if the matched time position
// is QUERY_TIME_ERROR away from the current time
constexpr float QUERY_TIME_ERROR = 0.05;

PackedFloat32Array MMAnimationNode::_process_animation_node(const PackedFloat64Array& p_playback_info, bool p_test_only) {
    PackedFloat32Array default_result;
    default_result.resize(6);
    default_result.fill(0.0);
    if (Engine::get_singleton()->is_editor_hint()) {
        return default_result;
    }

    if (library.is_empty()) {
        return default_result;
    }
    const double time = p_playback_info[0];
    const double delta_time = p_playback_info[1];
    _current_animation_info.time = time;
    _current_animation_info.delta = delta_time;
    _current_animation_info.seeked = p_playback_info[4] > 0.5;
    _current_animation_info.is_external_seeking = p_playback_info[5] > 0.5;

    const bool is_about_to_end = false; // TODO: Implement this

    // We run queries periodically, or when the animation is about to end
    const bool has_current_animation = !_last_query_output.animation_match.is_empty();
    const bool should_query = (_time_since_last_query > (1.0 / query_frequency)) || is_about_to_end || !has_current_animation;

    if (!should_query) {
        _time_since_last_query += delta_time;
        return _update_current_animation(p_test_only);
    }

    MMQueryInput* query_input = Object::cast_to<MMQueryInput>(get_parameter("motion_matching_input"));

    if (!query_input || !query_input->is_valid()) {
        _time_since_last_query += delta_time;
        return _update_current_animation(p_test_only);
    }

    _time_since_last_query = 0.f;

    // Run query
    AnimationTree* animation_tree = Object::cast_to<AnimationTree>(ObjectDB::get_instance(get_processing_animation_tree_instance_id()));
    Ref<MMAnimationLibrary> animation_library = animation_tree->get_animation_library(library);
    ERR_FAIL_COND_V_MSG(animation_library.is_null(), PackedFloat32Array(), "Library not found: " + library);
    ERR_FAIL_COND_V_MSG(
        animation_library->db_anim_index.is_empty() || animation_library->db_time_index.is_empty(),
        PackedFloat32Array(),
        "Library not baked: " + library);
    const MMQueryOutput query_output = animation_library->query(*query_input);

    const bool is_same_animation = query_output.animation_match == _last_query_output.animation_match;
    const bool is_same_time = abs(query_output.time_match - time) < QUERY_TIME_ERROR;

    // Play selected animation
    if (!is_same_animation || !is_same_time) {
        const String animation_match = query_output.animation_match;
        const float time_match = query_output.time_match;
        if (!p_test_only) {
            _start_transition(animation_match, time_match);
        }
        _last_query_output = query_output;
        query_input->on_query_result(query_output);
    }

    return _update_current_animation(p_test_only);
}

void MMAnimationNode::_start_transition(const StringName p_animation, float p_time) {
    AnimationTree* animation_tree = Object::cast_to<AnimationTree>(ObjectDB::get_instance(get_processing_animation_tree_instance_id()));
    Ref<Animation> anim = animation_tree->get_animation(p_animation);
    ERR_FAIL_COND_MSG(anim.is_null(), vformat("Animation not found: %s", p_animation));

    if (!_current_animation_info.name.is_empty() && blending_enabled) {
        _prev_animation_queue.push_front(_current_animation_info);
    }

    _current_animation_info.name = p_animation;
    _current_animation_info.length = anim->get_length();
    _current_animation_info.time = p_time;
    _current_animation_info.weight = blending_enabled ? 0.f : 1.f;
}

PackedFloat32Array MMAnimationNode::_update_current_animation(bool p_test_only) {
    // const bool will_end = Animation::is_greater_or_equal_approx(
    //     _current_animation_info.time + _current_animation_info.delta,
    //     _current_animation_info.length);

    Spring::_simple_spring_damper_exact(
        _current_animation_info.weight,
        _current_animation_info.blend_spring_speed,
        1.,
        transition_halflife,
        (real_t)_current_animation_info.delta);

    int pop_count = 0;
    for (AnimationInfo& prev_info : _prev_animation_queue) {
        Spring::_simple_spring_damper_exact(
            prev_info.weight,
            prev_info.blend_spring_speed,
            0.f,
            transition_halflife,
            _current_animation_info.delta);
        if (prev_info.weight <= SMALL_NUMBER) {
            pop_count++;
        }
    }

    for (int i = 0; i < pop_count; i++) {
        _prev_animation_queue.pop_back();
    }

    // Normalized blend weights in the queue
    const float inv_blend = 1.f - _current_animation_info.weight;
    float prev_blend_total = 0.f;
    for (AnimationInfo& prev_info : _prev_animation_queue) {
        prev_blend_total += prev_info.weight;
    }

    for (AnimationInfo& prev_info : _prev_animation_queue) {
        prev_info.weight *= inv_blend / prev_blend_total;
    }

    if (!p_test_only) {
        for (AnimationInfo& prev_info : _prev_animation_queue) {
            blend_animation(
                prev_info.name,
                prev_info.time,
                prev_info.delta,
                prev_info.seeked,
                prev_info.is_external_seeking,
                prev_info.weight);
        }
        blend_animation(
            _current_animation_info.name,
            _current_animation_info.time,
            _current_animation_info.delta,
            _current_animation_info.seeked,
            _current_animation_info.is_external_seeking,
            _current_animation_info.weight);
    }

    PackedFloat32Array result;
    result.append(0.0);
    result.append(_current_animation_info.time);
    result.append(_current_animation_info.delta);
    result.append(static_cast<float>(Animation::LoopMode::LOOP_NONE));
    result.append(false); // TODO: Will End
    result.append(false); // Is Infinity
    return result;
}

Array MMAnimationNode::_get_parameter_list() const {
    Array parameter_list;
    parameter_list.push_back(Dictionary(PropertyInfo(Variant::Type::OBJECT, "motion_matching_input", PROPERTY_HINT_RESOURCE_TYPE, "MMQueryInput", PROPERTY_USAGE_STORAGE)));

    return parameter_list;
}

Variant MMAnimationNode::_get_parameter_default_value(const StringName& p_parameter) const {
    Variant ret = AnimationNodeExtension::_get_parameter_default_value(p_parameter);
    if (ret != Variant()) {
        return ret;
    }

    if (p_parameter == StringName("motion_matching_input")) {
        Ref<MMQueryInput> p;
        p.instantiate();
        return p;
    }

    return Variant();
}

bool MMAnimationNode::_is_parameter_read_only(const StringName& p_parameter) const {
    if (AnimationNodeExtension::_is_parameter_read_only(p_parameter)) {
        return true;
    }

    if (p_parameter == StringName("motion_matching_input")) {
        return false;
    }

    return false;
}

String MMAnimationNode::_get_caption() const {
    return "Motion Matching";
}

bool MMAnimationNode::_has_filter() const {
    return true;
}

void MMAnimationNode::_validate_property(PropertyInfo& p_property) const {
    if (p_property.name == StringName("transition_halflife")) {
        if (!blending_enabled) {
            p_property.usage = PROPERTY_USAGE_NO_EDITOR;
        }
    }

    if (p_property.name == StringName("library")) {
        AnimationTreeHandlerPlugin* plugin = AnimationTreeHandlerPlugin::get_singleton();
        if (!plugin) {
            return;
        }
        AnimationTree* tree = plugin->get_animation_tree();
        if (!tree) {
            return;
        }
        String animations;
        TypedArray<StringName> library_names = tree->get_animation_library_list();

        for (int i = 0; i < library_names.size(); i++) {
            Ref<MMAnimationLibrary> lib = tree->get_animation_library(library_names[i]);
            if (lib.is_null()) {
                continue;
            }
            if (!animations.is_empty()) {
                animations += ",";
            }
            animations += (String)library_names[i];
        }
        if (animations.is_empty()) {
            return;
        }
        p_property.hint = PROPERTY_HINT_ENUM;
        p_property.hint_string = animations;
    }

    AnimationNodeExtension::_validate_property(p_property);
}

void MMAnimationNode::_bind_methods() {
    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::STRING_NAME, library);
    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, query_frequency);
    ClassDB::bind_method(D_METHOD("get_blending_enabled"), &MMAnimationNode::get_blending_enabled);
    ClassDB::bind_method(D_METHOD("set_blending_enabled", "value"), &MMAnimationNode::set_blending_enabled);
    ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blending_enabled"), "set_blending_enabled", "get_blending_enabled");

    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, transition_halflife);
}

Dictionary MMAnimationNode::_output_to_dict(const MMQueryOutput& output) {
    Dictionary result;

    result.get_or_add("animation", output.animation_match);
    result.get_or_add("time", output.time_match);
    result.get_or_add("frame_data", output.matched_frame_data);
    result.merge(output.feature_costs);
    result.get_or_add("total_cost", output.cost);

    return result;
}

bool MMAnimationNode::get_blending_enabled() const {
    return blending_enabled;
}

void MMAnimationNode::set_blending_enabled(bool value) {
    blending_enabled = value;
    notify_property_list_changed();
}


================================================
FILE: src/mm_animation_node.h
================================================
#ifndef MM_ANIMATION_NODE_H
#define MM_ANIMATION_NODE_H

#include "common.h"
#include "mm_animation_library.h"

#include <godot_cpp/classes/animation_node_extension.hpp>

#include <queue>

class MMAnimationNode : public AnimationNodeExtension {
    GDCLASS(MMAnimationNode, AnimationNodeExtension);

public:
    GETSET(StringName, library);
    GETSET(real_t, query_frequency, 2.0f)
    GETSET(real_t, transition_halflife, 0.1f)

    bool blending_enabled{true};
    bool get_blending_enabled() const;

    void set_blending_enabled(bool value);

    virtual PackedFloat32Array _process_animation_node(const PackedFloat64Array& p_playback_info, bool p_test_only);
    virtual Array _get_parameter_list() const override;
    virtual Variant _get_parameter_default_value(const StringName& p_parameter) const override;
    virtual bool _is_parameter_read_only(const StringName& p_parameter) const override;
    virtual String _get_caption() const override;
    virtual bool _has_filter() const override;

protected:
    void _validate_property(PropertyInfo& p_property) const;
    static void _bind_methods();

private:
    static Dictionary _output_to_dict(const MMQueryOutput& output);

    struct AnimationInfo {
        StringName name;
        double length;
        double time;
        double delta;
        bool seeked;
        bool is_external_seeking;
        real_t weight;
        real_t blend_spring_speed;
    };

    std::deque<AnimationInfo> _prev_animation_queue;
    AnimationInfo _current_animation_info;

    void _start_transition(const StringName p_animation, float p_time);
    PackedFloat32Array _update_current_animation(bool p_test_only);

    MMQueryOutput _last_query_output;
    float _time_since_last_query{0.f};
};

#endif // MM_ANIMATION_NODE_H


================================================
FILE: src/mm_bone_state.h
================================================
#ifndef MM_BONE_STATE_H
#define MM_BONE_STATE_H

#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/core/math.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#include <map>
#include <vector>

using namespace godot;

struct BoneState {

    void reset() {
        pos = Vector3();
        rot = Quaternion();
        scl = Vector3(1.0, 1.0, 1.0);
        vel = Vector3();
        ang_vel = Vector3();
        scl_vel = Vector3();
    }

    Vector3 pos;
    Quaternion rot;
    Vector3 scl;
    Vector3 vel;
    Vector3 ang_vel;
    Vector3 scl_vel;
};

struct SkeletonState {

    SkeletonState() = default;
    ~SkeletonState() = default;
    SkeletonState(size_t size)
        : bone_states(size) {
    }
    SkeletonState(const Skeleton3D* skeleton) {
        const int32_t bone_count = skeleton->get_bone_count();
        bone_states = std::vector<BoneState>(bone_count);
        for (int b = 0; b < bone_count; ++b) {
            bone_name_to_index[skeleton->get_bone_name(b)] = b;
        }
    }

    const BoneState& operator[](int32_t idx) const {
        return bone_states[idx];
    }

    BoneState& operator[](int32_t idx) {
        return bone_states[idx];
    }

    const BoneState& find_bone_state(const String& name) const {
        return bone_states[(int32_t)bone_name_to_index.get(name, -1)];
    }

    BoneState& find_bone_state(const String& name) {
        return bone_states[(int32_t)bone_name_to_index.get(name, -1)];
    }

    void reset_velocities() {
        for (BoneState& state : bone_states) {
            state.vel = Vector3();
            state.ang_vel = Vector3();
            state.scl_vel = Vector3();
        }
    }

    std::vector<BoneState> bone_states;
    Dictionary bone_name_to_index; // Give me a real unordered_map please :(
};

#endif // MM_BONE_STATE_H


================================================
FILE: src/mm_character.cpp
================================================
#include "mm_character.h"

#include "math/spring.hpp"
#include "mm_animation_library.h"
#include "mm_animation_node.h"

#include <godot_cpp/classes/animation_root_node.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/input.hpp>
#include <godot_cpp/classes/input_event_mouse_motion.hpp>
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/classes/physics_server3d.hpp>
#include <godot_cpp/classes/physics_test_motion_parameters3d.hpp>
#include <godot_cpp/classes/physics_test_motion_result3d.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/shape3d.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/core/property_info.hpp>
#include <godot_cpp/variant/transform3d.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#include <cstdint>

MMCharacter::MMCharacter()
    : CharacterBody3D() {
}

MMCharacter::~MMCharacter() {
}

void MMCharacter::_update_character(float delta_t) {
    Vector3 new_velocity = _update_trajectory(delta_t);
    set_velocity(new_velocity);

    // Rotate the character to face the direction of velocity
    if (!is_strafing) {
        Vector3 direction = new_velocity.normalized();
        set_rotation(Vector3(0.0f, Math::atan2(direction.x, direction.z), 0.0f));
    } else {
        set_rotation(Vector3(0.0f, strafe_facing, 0.0f));
    }
    move_and_slide();
}

Vector3 MMCharacter::_update_trajectory(float delta_t) {
    Vector3 current_velocity = get_velocity();
    Vector3 current_up_direction = get("up_direction");

    // Update the velocity.
    const Vector3 up_velocity = current_velocity.dot(current_up_direction) * current_up_direction;
    Vector3 ground_velocity = current_velocity - up_velocity;
    Array result = Spring::simple_spring_damper_exact(
        ground_velocity,
        _spring_acceleration,
        target_velocity,
        halflife,
        delta_t);
    Vector3 new_velocity = (Vector3)result[0] + up_velocity;
    _spring_acceleration = result[1];

    if (!is_on_floor()) {
        new_velocity += get_gravity() * delta_t;
    }

    _generate_trajectory(delta_t);

    _update_history(delta_t);

    return new_velocity;
}

MMTrajectoryPoint MMCharacter::_get_current_trajectory_point() const {
    MMTrajectoryPoint point;
    point.position = get_global_position();
    point.velocity = get_velocity();
    point.collision_state.on_floor = is_on_floor();
    point.collision_state.against_wall = is_on_wall();
    point.collision_state.floor_normal = get_floor_normal();
    point.collision_state.wall_normal = get_wall_normal();

    if (get_velocity().length_squared() > SMALL_NUMBER && !is_strafing) {
        Vector3 direction = get_velocity().normalized();
        point.facing_angle = Math::atan2(direction.x, direction.z);
    } else {
        point.facing_angle = strafe_facing;
    }
    return point;
}

void MMCharacter::_generate_trajectory(float delta_time) {
    const float delta_t = trajectory_delta_time;

    MMTrajectoryPoint point = _get_current_trajectory_point();

    Vector3 spring_acceleration = _spring_acceleration;

    // The first point represents the player's current position, and is not considered part
    // of the trajectory for motion matching
    for (size_t i = 0; i < trajectory_point_count + 1; i++) {
        _trajectory[i] = point;

        Vector3 current_up_direction = get("up_direction");

        // Update velocity
        const Vector3 up_velocity = point.velocity.dot(current_up_direction) * current_up_direction;
        Vector3 ground_velocity = point.velocity - up_velocity;
        Array result = Spring::simple_spring_damper_exact(ground_velocity, spring_acceleration, target_velocity, halflife, delta_t);
        point.velocity = (Vector3)result[0] + up_velocity;
        spring_acceleration = result[1];

        // Update facing
        if (point.velocity.length_squared() > SMALL_NUMBER && !is_strafing) {
            Vector3 direction = point.velocity.normalized();
            point.facing_angle = Math::atan2(direction.x, direction.z);
        } else {
            point.facing_angle = strafe_facing;
        }

        if (check_environment) {
            // Update point with environment
            _move_with_collisions(point, delta_t);
            _fall_to_floor(point, delta_t);
        } else {
            point.position += point.velocity * delta_t;
        }
    }
}

void MMCharacter::_update_history(double delta_t) {
    if (!_history_buffer.is_empty()) {
        float time_in_past = 0.f;
        uint32_t current_index = 0;
        for (int i = _history_buffer.size() - 1; i >= 0; --i) {
            if (time_in_past >= history_delta_time) {
                _trajectory_history[current_index] = _history_buffer[i];
                time_in_past = 0.f;
                current_index++;
            }

            if (current_index >= history_point_count) {
                break;
            }

            time_in_past += delta_t;
        }
    }

    _history_buffer.push(_get_current_trajectory_point());
}

void MMCharacter::_move_with_collisions(MMTrajectoryPoint& point, float delta_t) {
    const Vector3 motion = point.velocity * delta_t;

    Ref<PhysicsTestMotionParameters3D> params;
    params.instantiate();
    Vector3 current_up_direction = get("up_direction");
    params->set_from(point.get_transform(current_up_direction));
    params->set_motion(motion);
    params->set_max_collisions(6);
    params->set_recovery_as_collision_enabled(false);

    Ref<PhysicsTestMotionResult3D> collision_result;
    collision_result.instantiate();

    bool is_colliding = PhysicsServer3D::get_singleton()->body_test_motion(
        get_rid(),
        params,
        collision_result);

    if (!is_colliding) {
        // We move in the direction of motion as usual
        point.position += motion;

        // We reset the collision state
        point.collision_state.reset();
        return;
    }

    _fill_collision_state(collision_result, point.collision_state);

    // Update final position
    Vector3 result_velocity = point.velocity;
    point.position += collision_result->get_travel();

    if (point.collision_state.against_wall) {

        result_velocity = result_velocity.slide(point.collision_state.wall_normal);
        // Slide must be horizontal, no climbing walls!
        // Note: This would be a good place to add a climbing feature
        result_velocity.y = 0.0;
    }

    if (point.collision_state.on_floor) {
        result_velocity = result_velocity.slide(point.collision_state.floor_normal);
    }

    point.velocity = result_velocity;
    // Move the remaining part of motion
    point.position += point.velocity * delta_t * (1.0 - collision_result->get_collision_safe_fraction());
}

void MMCharacter::_fill_collision_state(const Ref<PhysicsTestMotionResult3D> collision_result, MMCollisionState& state) {
    real_t wall_depth = -1.0;
    real_t floor_depth = -1.0;

    state.on_floor = false;
    for (int i = collision_result->get_collision_count() - 1; i >= 0; i--) {
        Vector3 current_up_direction = get("up_direction");
        real_t floor_angle = Math::acos(collision_result->get_collision_normal(i).dot(current_up_direction));
        if (floor_angle <= get_floor_max_angle() && collision_result->get_collision_depth(i) > floor_depth) {
            state.on_floor = true;
            state.floor_normal = collision_result->get_collision_normal(i);
            state.floor_position = collision_result->get_collision_point(i);
            floor_depth = collision_result->get_collision_depth(i);
            continue;
        }

        state.against_wall = true;
        if (collision_result->get_collision_depth(i) > wall_depth) {
            state.against_wall = true;
            state.wall_normal = collision_result->get_collision_normal(i);
            wall_depth = collision_result->get_collision_depth(i);
        }
    }
}

void MMCharacter::_fall_to_floor(MMTrajectoryPoint& point, float delta_t) {
    const Vector3 motion = get_gravity() * delta_t * delta_t;
    Ref<PhysicsTestMotionParameters3D> params;
    params.instantiate();
    Vector3 current_up_direction = get("up_direction");
    params->set_from(point.get_transform(current_up_direction));
    params->set_motion(motion);

    Ref<PhysicsTestMotionResult3D> collision_result;
    collision_result.instantiate();

    if (PhysicsServer3D::get_singleton()->body_test_motion(
            get_rid(),
            params,
            collision_result)) {
        point.position += collision_result->get_travel();
        point.velocity.y = 0.0;
        point.collision_state.on_floor = true;
        point.collision_state.floor_normal = collision_result->get_collision_normal();
    } else {
        point.position += motion;
        point.velocity += get_gravity() * delta_t;
        point.collision_state.on_floor = false;
    }
}

void MMCharacter::_fill_query_input(MMQueryInput& input) {
    input.controller_velocity = get_velocity();
    input.trajectory = get_trajectory();
    input.trajectory_history = get_trajectory_history();
    input.controller_transform = get_global_transform();
    input.character_transform = skeleton->get_global_transform();
    input.skeleton_state = _skeleton_state;
    input.on_query_result = std::bind(&MMCharacter::_on_query_result, this, std::placeholders::_1);
}

void MMCharacter::_update_query() {
    // Fill query_input with data from the controller
    if (animation_tree) {
        Ref<MMQueryInput> query_input;
        query_input.instantiate();
        _fill_query_input(**query_input);
        for (const StringName& param : _mm_input_params) {
            animation_tree->set(param, query_input);
        }
    }
}

void MMCharacter::_apply_root_motion() {
    skeleton->set_quaternion(
        skeleton->get_quaternion() * animation_tree->get_root_motion_rotation());

    const Vector3 movement_delta = (animation_tree->get_root_motion_rotation_accumulator().inverse() *
                                    skeleton->get_quaternion())
                                       .xform(animation_tree->get_root_motion_position());

    skeleton->set_global_position(skeleton->get_global_position() + movement_delta);
}

void MMCharacter::_update_synchronizer(double delta_t) {
    if (synchronizer.is_null()) {
        return;
    }

    synchronizer->sync(this, skeleton, delta_t);
}

void MMCharacter::_fill_current_skeleton_state(SkeletonState& p_state) const {
    for (int b = 0; b < skeleton->get_bone_count(); ++b) {
        Transform3D bone_pose = skeleton->get_bone_global_pose(b);
        p_state[b].pos = bone_pose.origin;
        p_state[b].vel = Vector3();
        p_state[b].rot = bone_pose.basis.get_rotation_quaternion();
        p_state[b].ang_vel = Vector3();
        p_state[b].scl = bone_pose.basis.get_scale();
        p_state[b].scl_vel = Vector3();
    }
}

void MMCharacter::_reset_skeleton_state() {
    _fill_current_skeleton_state(_skeleton_state);
    _skeleton_state.reset_velocities();
}

void MMCharacter::_update_skeleton_state(double delta_t) {
    SkeletonState current_state(skeleton);
    _fill_current_skeleton_state(current_state);

    for (int b = 0; b < skeleton->get_bone_count(); ++b) {
        BoneState& state = _skeleton_state[b];
        const BoneState& current = current_state[b];

        state.vel = (current.pos - state.pos) / delta_t;
        state.ang_vel = Spring::quat_differentiate_angular_velocity(current.rot, state.rot, delta_t);
        state.scl_vel = (current.scl - state.scl) / delta_t;

        state.pos = current.pos;
        state.rot = current.rot;
        state.scl = current.scl;
    }
}

void MMCharacter::_on_query_result(const MMQueryOutput& output) {
    if (emit_result_signal) {
        emit_signal("on_query_result", _output_to_dict(output));
    }
}

Dictionary MMCharacter::_output_to_dict(const MMQueryOutput& output) {
    Dictionary result;

    result.get_or_add("animation", output.animation_match);
    result.get_or_add("time", output.time_match);
    result.get_or_add("frame_data", output.matched_frame_data);
    result.merge(output.feature_costs);
    result.get_or_add("total_cost", output.cost);

    return result;
}

AnimationMixer* MMCharacter::get_animation_mixer() const {
    return animation_tree;
}

void MMCharacter::_bind_methods() {
    ClassDB::bind_method(D_METHOD("get_trajectory"), &MMCharacter::get_trajectory_typed_array);
    ClassDB::bind_method(D_METHOD("get_trajectory_history"), &MMCharacter::get_trajectory_history_typed_array);
    ClassDB::bind_method(D_METHOD("get_skeleton_state"), &MMCharacter::get_skeleton_state);

    ADD_SIGNAL(MethodInfo("on_query_result", PropertyInfo(Variant::DICTIONARY, "data")));

    ADD_GROUP("Motion Matching", "");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, skeleton, PROPERTY_HINT_NODE_TYPE, "Skeleton3D");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, animation_tree, PROPERTY_HINT_NODE_TYPE, "AnimationTree");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, synchronizer, PROPERTY_HINT_RESOURCE_TYPE, "MMSynchronizer");

    ADD_GROUP("Movement", "");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, halflife, PROPERTY_HINT_RANGE, "0.0,1.0,0.01,or_greater");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, is_strafing);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, strafe_facing, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::VECTOR3, target_velocity, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE);

    ADD_GROUP("Trajectory", "");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, check_environment);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::INT, trajectory_point_count);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, trajectory_delta_time);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::INT, history_point_count);
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, history_delta_time);

    ADD_GROUP("Debug", "");
    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, emit_result_signal);
}

void MMCharacter::_notification(int p_what) {
    switch (p_what) {
    case NOTIFICATION_PHYSICS_PROCESS: {
        if (Engine::get_singleton()->is_editor_hint()) {
            return;
        }
        double delta = get_physics_process_delta_time();

        _update_character(delta);

        if (!skeleton) {
            return;
        }

        _update_skeleton_state(delta);

        _update_query();

        _apply_root_motion();

        _update_synchronizer(delta);
    } break;
    case NOTIFICATION_READY: {
        set_physics_process(true);

        if (Engine::get_singleton()->is_editor_hint()) {
            return;
        }

        while (!_history_buffer.is_full()) {
            _history_buffer.push(_get_current_trajectory_point());
        }

        _trajectory.resize(trajectory_point_count + 1);
        _trajectory_history.resize(history_point_count);

        if (animation_tree) {
            animation_tree->set_callback_mode_process(AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS);

            TypedArray<Dictionary> properties = animation_tree->get_property_list();
            for (int property_idx = 0; property_idx < properties.size(); ++property_idx) {
                const Dictionary& prop = properties[property_idx];
                const bool is_mm_input = prop["hint_string"] == "MMQueryInput";
                if (is_mm_input) {
                    _mm_input_params.push_back(prop["name"]);
                }
            }
        }

        if (skeleton && animation_tree) {
            StringName root_node_path = animation_tree->get_root_motion_track().get_concatenated_subnames();
            _root_bone_idx = skeleton->find_bone(root_node_path);

            skeleton->set_as_top_level(true);
            skeleton->reset_bone_poses();

            _skeleton_state = SkeletonState(skeleton);
            _reset_skeleton_state();
        }

    } break;
    }
}


================================================
FILE: src/mm_character.h
================================================
#ifndef MM_CHARACTER_H
#define MM_CHARACTER_H

#include "circular_buffer.h"
#include "common.h"
#include "mm_character.h"
#include "mm_query.h"
#include "mm_trajectory_point.h"
#include "synchronizers/mm_synchronizer.h"

#include <godot_cpp/classes/animation_tree.hpp>
#include <godot_cpp/classes/character_body3d.hpp>
#include <godot_cpp/classes/input_event.hpp>
#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/variant/array.hpp>
#include <godot_cpp/variant/variant.hpp>

using namespace godot;

class MMCharacter : public CharacterBody3D {
    GDCLASS(MMCharacter, CharacterBody3D)

public:
    MMCharacter();
    virtual ~MMCharacter();

public:
    const std::vector<MMTrajectoryPoint>& get_trajectory() const {
        return _trajectory;
    }

    const std::vector<MMTrajectoryPoint>& get_trajectory_history() const {
        return _trajectory_history;
    }

    TypedArray<Dictionary> get_skeleton_state() const {
        TypedArray<Dictionary> result;
        for (const BoneState& state : _skeleton_state.bone_states) {
            Dictionary character_data;
            character_data.get_or_add("position", state.pos);
            character_data.get_or_add("velocity", state.vel);
            result.push_back(character_data);
        }
        return result;
    }

    TypedArray<Dictionary> get_trajectory_typed_array() const {
        return trajectory_to_dict(_trajectory);
    }

    TypedArray<Dictionary> get_trajectory_history_typed_array() const {
        return trajectory_to_dict(_trajectory_history);
    }

    TypedArray<Dictionary> trajectory_to_dict(const std::vector<MMTrajectoryPoint>& p_trajectory) const {
        TypedArray<Dictionary> result;
        for (const MMTrajectoryPoint& point : p_trajectory) {
            Dictionary trajectory_data;
            trajectory_data.get_or_add("position", point.position);
            trajectory_data.get_or_add("velocity", point.velocity);
            trajectory_data.get_or_add("facing", point.facing_angle);
            trajectory_data.get_or_add("on_floor", point.collision_state.on_floor);
            result.push_back(trajectory_data);
        }
        return result;
    }

    AnimationMixer* get_animation_mixer() const;

    // Trajectory
    GETSET(float, trajectory_delta_time, 0.5f);
    GETSET(float, history_delta_time, 0.5f);
    GETSET(uint32_t, trajectory_point_count, 10);
    GETSET(uint32_t, history_point_count, 3);
    GETSET(bool, check_environment, true);

    // Movement
    GETSET(float, halflife, 0.5f);
    GETSET(bool, is_strafing, false);
    GETSET(float, strafe_facing, 0.f);
    GETSET(Vector3, target_velocity);

    // Motion Matching
    GETSET(Skeleton3D*, skeleton);
    GETSET(AnimationTree*, animation_tree);
    GETSET(Ref<MMSynchronizer>, synchronizer);

    // Debug
    GETSET(bool, emit_result_signal, false);

protected:
    static constexpr size_t HISTORY_BUFFER_SIZE{100}; // Around 1.6s
    static void _bind_methods();

public:
    void _notification(int p_what);

private:
    void _update_character(float delta_t);

    // Trajectory
    Vector3 _update_trajectory(float p_delta_t);
    MMTrajectoryPoint _get_current_trajectory_point() const;
    void _generate_trajectory(float delta_time);
    void _update_history(double delta_t);
    void _move_with_collisions(MMTrajectoryPoint& point, float delta_t);
    void _fill_collision_state(const Ref<PhysicsTestMotionResult3D> collision_result, MMCollisionState& state);
    void _fall_to_floor(MMTrajectoryPoint& point, float delta_t);

    // Motion Matching
    void _fill_query_input(MMQueryInput& input);
    void _update_query();
    void _apply_root_motion();
    void _update_synchronizer(double delta_t);

    // Skeleton State
    void _fill_current_skeleton_state(SkeletonState& p_state) const;
    void _reset_skeleton_state();
    void _update_skeleton_state(double delta_t);

    void _on_query_result(const MMQueryOutput& output);

    static Dictionary _output_to_dict(const MMQueryOutput& output);

private:
    // Controller
    Vector3 _spring_acceleration;

    // Trajectory
    std::vector<MMTrajectoryPoint> _trajectory;
    std::vector<MMTrajectoryPoint> _trajectory_history;
    CircularBuffer<MMTrajectoryPoint> _history_buffer{HISTORY_BUFFER_SIZE};

    // Skeleton State
    SkeletonState _skeleton_state;
    int32_t _root_bone_idx{-1};

    // Motion Matching parameters
    List<StringName> _mm_input_params;
};

#endif // MM_CHARACTER_H


================================================
FILE: src/mm_query.h
================================================
#ifndef MM_QUERY_H
#define MM_QUERY_H

#include "mm_bone_state.h"
#include "mm_query.h"
#include "mm_trajectory_point.h"

#include <functional>
#include <godot_cpp/classes/skeleton3d.hpp>
#include <godot_cpp/variant/string.hpp>
#include <godot_cpp/variant/transform3d.hpp>
#include <godot_cpp/variant/vector3.hpp>

using namespace godot;

struct MMQueryOutput {
    String animation_match;
    float time_match;
    float cost;
    PackedFloat32Array matched_frame_data;
    Dictionary feature_costs;
};

class MMQueryInput : public RefCounted {
    GDCLASS(MMQueryInput, RefCounted);

public:
    // Add data required for the query here
    Vector3 controller_velocity;
    Transform3D controller_transform;
    Transform3D character_transform;
    std::vector<MMTrajectoryPoint> trajectory;
    std::vector<MMTrajectoryPoint> trajectory_history;
    SkeletonState skeleton_state;
    std::function<void(const MMQueryOutput&)> on_query_result;

    bool is_valid() const {
        // Add validation logic here
        return !trajectory.empty();
    }

protected:
    static void _bind_methods() {
    }
};

#endif // MM_QUERY_H


================================================
FILE: src/mm_trajectory_point.cpp
================================================
#include "mm_trajectory_point.h"

================================================
FILE: src/mm_trajectory_point.h
================================================
#ifndef MM_TRAJECTORY_POINT_H
#define MM_TRAJECTORY_POINT_H

#include "common.h"

#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/variant/vector3.hpp>

using namespace godot;

struct MMCollisionState {
    bool on_floor = false;
    Vector3 floor_normal;
    Vector3 floor_position;
    bool against_wall = false;
    Vector3 wall_normal;

    void reset() {
        on_floor = false;
        against_wall = false;
    }
};

struct MMTrajectoryPoint {
    Vector3 position;
    Vector3 velocity;
    float facing_angle;

    MMCollisionState collision_state;

    Transform3D get_transform(const Vector3& up_axis = Vector3(0, 1, 0)) const {
        Transform3D result;
        result.origin = position;
        result.set_basis(Basis(up_axis, facing_angle));
        return result;
    }
};
#endif // MM_TRAJECTORY_POINT_H


================================================
FILE: src/modifiers/damped_skeleton_modifier.cpp
================================================
#include "modifiers/damped_skeleton_modifier.h"

#include "damped_skeleton_modifier.h"
#include "math/spring.hpp"

void DampedSkeletonModifier::_process_modification() {
    if (!is_active()) {
        return;
    }

    Skeleton3D* skeleton = get_skeleton();
    if (!skeleton) {
        return;
    }
    const double delta = skeleton->get_modifier_callback_mode_process() == Skeleton3D::ModifierCallbackModeProcess::MODIFIER_CALLBACK_MODE_PROCESS_IDLE ? get_process_delta_time() : get_physics_process_delta_time();
    if (!skeleton->get_bone_count()) {
        return;
    }
    for (int32_t bone_id = 0; bone_id < skeleton->get_bone_count(); ++bone_id) {
        if (skeleton->get_bone_parent(bone_id) == -1) {
            // Skip root bone
            continue;
        }

        const Vector3 desired_pos = skeleton->get_bone_pose_position(bone_id);
        const Quaternion desired_rot = skeleton->get_bone_pose_rotation(bone_id);

        Spring::_simple_spring_damper_exact(
            _skeleton_state[bone_id].pos,
            _skeleton_state[bone_id].vel,
            desired_pos,
            halflife,
            delta);

        Spring::_simple_spring_damper_exact(
            _skeleton_state[bone_id].rot,
            _skeleton_state[bone_id].ang_vel,
            desired_rot,
            halflife,
            delta);

        skeleton->set_bone_pose_position(bone_id, _skeleton_state[bone_id].pos);
        skeleton->set_bone_pose_rotation(bone_id, _skeleton_state[bone_id].rot);
    }
}

void DampedSkeletonModifier::_bind_methods() {
    BINDER
Download .txt
gitextract_5foufhgw/

├── .clang-format
├── .github/
│   ├── actions/
│   │   ├── build/
│   │   │   └── action.yml
│   │   └── sign/
│   │       └── action.yml
│   └── workflows/
│       └── builds.yml
├── .gitignore
├── .gitmodules
├── LICENSE.md
├── README.md
├── SConstruct
├── addons/
│   └── motion_matching/
│       └── gdmotionmatching.gdextension
├── config.py
├── doc_classes/
│   ├── DampedSkeletonModifier.xml
│   ├── MMAnimationLibrary.xml
│   ├── MMAnimationNode.xml
│   ├── MMBoneDataFeature.xml
│   ├── MMCharacter.xml
│   ├── MMClampSynchronizer.xml
│   ├── MMFeature.xml
│   ├── MMMixSynchronizer.xml
│   ├── MMQueryInput.xml
│   ├── MMRootMotionSynchronizer.xml
│   ├── MMSynchronizer.xml
│   └── MMTrajectoryFeature.xml
├── methods.py
└── src/
    ├── algo/
    │   ├── kd_tree.cpp
    │   └── kd_tree.h
    ├── circular_buffer.h
    ├── common.h
    ├── editor/
    │   ├── animation_post_import_plugin.cpp
    │   ├── animation_post_import_plugin.h
    │   ├── animation_tree_handler_plugin.cpp
    │   ├── animation_tree_handler_plugin.h
    │   ├── mm_data_tab.cpp
    │   ├── mm_data_tab.h
    │   ├── mm_editor.cpp
    │   ├── mm_editor.h
    │   ├── mm_editor_gizmo_plugin.cpp
    │   ├── mm_editor_gizmo_plugin.h
    │   ├── mm_editor_plugin.cpp
    │   ├── mm_editor_plugin.h
    │   ├── mm_visualization_tab.cpp
    │   └── mm_visualization_tab.h
    ├── features/
    │   ├── mm_bone_data_feature.cpp
    │   ├── mm_bone_data_feature.h
    │   ├── mm_feature.cpp
    │   ├── mm_feature.h
    │   ├── mm_trajectory_feature.cpp
    │   └── mm_trajectory_feature.h
    ├── math/
    │   ├── hash.h
    │   ├── spring.hpp
    │   ├── stats.hpp
    │   └── transforms.h
    ├── mm_animation_library.cpp
    ├── mm_animation_library.h
    ├── mm_animation_node.cpp
    ├── mm_animation_node.h
    ├── mm_bone_state.h
    ├── mm_character.cpp
    ├── mm_character.h
    ├── mm_query.h
    ├── mm_trajectory_point.cpp
    ├── mm_trajectory_point.h
    ├── modifiers/
    │   ├── damped_skeleton_modifier.cpp
    │   └── damped_skeleton_modifier.h
    ├── register_types.cpp
    ├── register_types.h
    └── synchronizers/
        ├── mm_clamp_synchronizer.cpp
        ├── mm_clamp_synchronizer.h
        ├── mm_mix_synchronizer.cpp
        ├── mm_mix_synchronizer.h
        ├── mm_rootmotion_synchronizer.cpp
        ├── mm_rootmotion_synchronizer.h
        ├── mm_synchronizer.cpp
        └── mm_synchronizer.h
Download .txt
SYMBOL INDEX (118 symbols across 38 files)

FILE: config.py
  function can_build (line 1) | def can_build(env, platform):
  function configure (line 5) | def configure(env):
  function get_doc_classes (line 9) | def get_doc_classes():
  function get_doc_path (line 25) | def get_doc_path():

FILE: methods.py
  class ANSI (line 11) | class ANSI(Enum):
    method __str__ (line 40) | def __str__(self) -> str:
  function print_warning (line 45) | def print_warning(*values: object) -> None:
  function print_error (line 50) | def print_error(*values: object) -> None:

FILE: src/algo/kd_tree.cpp
  function PackedInt32Array (line 41) | PackedInt32Array KDTree::get_node_indices() const {

FILE: src/algo/kd_tree.h
  function class (line 11) | class KDTree {

FILE: src/circular_buffer.h
  function clear (line 20) | void clear() {
  function push (line 24) | void push(T item) {
  function T (line 31) | T pop() {
  function resize (line 57) | void resize(size_t size) {
  function T (line 68) | const T& operator[](size_t index) const {

FILE: src/editor/animation_post_import_plugin.cpp
  function Variant (line 7) | Variant AnimationPostImportPlugin::_get_option_visibility(const String& ...

FILE: src/editor/animation_post_import_plugin.h
  function class (line 7) | class AnimationPostImportPlugin : public EditorScenePostImportPlugin {

FILE: src/editor/animation_tree_handler_plugin.cpp
  function AnimationTreeHandlerPlugin (line 18) | AnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::get_singleton() {

FILE: src/editor/animation_tree_handler_plugin.h
  function class (line 12) | class AnimationTreeHandlerPlugin : public EditorPlugin {

FILE: src/editor/mm_data_tab.h
  function class (line 11) | class MMDataTab : public TabBar {

FILE: src/editor/mm_editor.h
  function class (line 18) | class MMEditor : public Control {

FILE: src/editor/mm_editor_gizmo_plugin.cpp
  function String (line 21) | String MMEditorGizmoPlugin::_get_gizmo_name() const {

FILE: src/editor/mm_editor_gizmo_plugin.h
  function class (line 9) | class MMEditorGizmoPlugin : public EditorNode3DGizmoPlugin {

FILE: src/editor/mm_editor_plugin.h
  function class (line 16) | class MMEditorPlugin : public EditorPlugin {

FILE: src/editor/mm_visualization_tab.h
  function class (line 14) | class MMVisualizationTab : public TabBar {

FILE: src/features/mm_bone_data_feature.cpp
  function PackedFloat32Array (line 26) | PackedFloat32Array MMBoneDataFeature::bake_animation_pose(Ref<Animation>...
  function PackedFloat32Array (line 39) | PackedFloat32Array MMBoneDataFeature::evaluate_runtime_data(const MMQuer...
  function BoneState (line 85) | BoneState MMBoneDataFeature::_sample_bone_state(Ref<Animation> p_animati...

FILE: src/features/mm_bone_data_feature.h
  function class (line 11) | class MMBoneDataFeature : public MMFeature {

FILE: src/features/mm_feature.h
  function class (line 18) | class MMFeature : public Resource {

FILE: src/features/mm_trajectory_feature.cpp
  function PackedFloat32Array (line 42) | PackedFloat32Array MMTrajectoryFeature::bake_animation_pose(Ref<Animatio...
  function PackedFloat32Array (line 107) | PackedFloat32Array MMTrajectoryFeature::evaluate_runtime_data(const MMQu...

FILE: src/features/mm_trajectory_feature.h
  function _root_rotation_track (line 60) | int _root_rotation_track{-1};

FILE: src/math/hash.h
  function hash_combine (line 7) | int64_t hash_combine(int64_t a, int64_t b) {

FILE: src/math/spring.hpp
  type Spring (line 17) | namespace Spring {
    function real_t (line 21) | static real_t inline square(real_t x) {
    function real_t (line 25) | static inline real_t fast_negexp(real_t x) {
    function Vector3 (line 29) | static Vector3 damp_adjustment_exact(Vector3 g, real_t halflife, real_...
    function Quaternion (line 34) | static Quaternion damp_adjustment_exact_quat(Quaternion g, real_t half...
    function Variant (line 39) | static Variant damper_exponential(Variant variable, Variant goal, real...
    function Variant (line 45) | static inline Variant damper_exact(Variant variable, Variant goal, rea...
    function real_t (line 49) | static inline real_t halflife_to_damping(real_t halflife, real_t eps =...
    function real_t (line 53) | static inline real_t halflife_to_duration(real_t halflife, real_t init...
    function real_t (line 57) | static inline real_t damping_to_halflife(real_t damping, real_t eps = ...
    function real_t (line 61) | static inline real_t frequency_to_stiffness(real_t frequency) {
    function real_t (line 65) | static inline real_t stiffness_to_frequency(real_t stiffness) {
    function real_t (line 69) | static inline real_t critical_halflife(real_t frequency) {
    function real_t (line 73) | static inline real_t critical_frequency(real_t halflife) {
    function real_t (line 77) | static inline real_t damping_ratio_to_stiffness(real_t ratio, real_t d...
    function real_t (line 81) | static inline real_t damping_ratio_to_damping(real_t ratio, real_t sti...
    function real_t (line 85) | static inline real_t maximum_spring_velocity_to_halflife(real_t x, rea...
    function Quaternion (line 89) | static inline Quaternion quat_exp(Vector3 v, real_t eps = 1e-8) {
    function T (line 111) | static inline T clampf(T x, T min, T max) {
    function Quaternion (line 117) | static inline Quaternion quat_abs(Quaternion q) {
    function Vector3 (line 121) | static inline Vector3 quat_log(Quaternion q, real_t eps = 1e-8) {
    function Quaternion (line 131) | static inline Quaternion quat_from_scaled_angle_axis(Vector3 v, real_t...
    function Vector3 (line 135) | static inline Vector3 quat_to_scaled_angle_axis(Quaternion q, real_t e...
    function Vector3 (line 139) | static inline Vector3 quat_differentiate_angular_velocity(Quaternion n...
    function _spring_damper_exact (line 143) | static void _spring_damper_exact(real_t& x, real_t& v, real_t x_goal, ...
    function _critical_spring_damper_exact (line 183) | static void _critical_spring_damper_exact(real_t& x, real_t& v, real_t...
    function PackedFloat32Array (line 196) | static inline PackedFloat32Array critical_spring_damper_exact(real_t x...
    function _simple_spring_damper_exact (line 204) | static void _simple_spring_damper_exact(real_t& x, real_t& v, real_t x...
    function _simple_spring_damper_exact (line 212) | static void _simple_spring_damper_exact(Vector3& x, Vector3& v, const ...
    function _simple_spring_damper_exact (line 222) | static void _simple_spring_damper_exact(Quaternion& x, Vector3& v, con...
    function Array (line 234) | static inline Array simple_spring_damper_exact(Variant x, Variant v, V...
    function _decay_spring_damper_exact (line 259) | static inline void _decay_spring_damper_exact(real_t& x, real_t& v, re...
    function _decay_spring_damper_exact (line 266) | static inline void _decay_spring_damper_exact(Vector3& x, Vector3& v, ...
    function _decay_spring_damper_exact (line 273) | static inline void _decay_spring_damper_exact(Quaternion& x, Vector3& ...
    function Array (line 284) | static inline Array decay_spring_damper_exact(Variant x, Variant v, re...
    function _timed_spring_damper_exact (line 311) | static void _timed_spring_damper_exact(real_t& x, real_t& v, real_t& x...
    function PackedFloat32Array (line 322) | static inline PackedFloat32Array timed_spring_damper_exact(real_t x, r...
    function inertialize_transition (line 331) | static inline void inertialize_transition(Vector3& off_x, Vector3& off...
    function inertialize_update (line 336) | static inline void inertialize_update(Vector3& out_x, Vector3& out_v, ...
    function inertialize_transition (line 342) | static inline void inertialize_transition(Quaternion& off_x, Vector3& ...
    function inertialize_update (line 346) | static inline void inertialize_update(Quaternion& out_x, Vector3& out_...
    function Vector3 (line 352) | static inline Vector3 calculate_offset_vec3(const Vector3 src_x, const...
    function Quaternion (line 355) | static inline Quaternion calculate_offset_quat(const Quaternion src_q,...
    function Dictionary (line 359) | static inline Dictionary binded_inertia_transition(const Vector3 off_x...

FILE: src/math/stats.hpp
  class StatsAccumulator (line 7) | class StatsAccumulator {
    method StatsAccumulator (line 16) | StatsAccumulator()
    method add_sample (line 20) | void add_sample(float sample) {
    method get_mean (line 28) | float get_mean() const {
    method get_max (line 32) | float get_max() const {
    method get_min (line 36) | float get_min() const {
    method get_standard_deviation (line 40) | float get_standard_deviation() const {
    method reset (line 49) | void reset() {
  function distance_squared (line 58) | float distance_squared(const float* a, const float* b, int dim) {

FILE: src/math/transforms.h
  function global_to_local_facing_angle (line 8) | float global_to_local_facing_angle(float local_facing_angle, const Trans...

FILE: src/mm_animation_library.cpp
  function MMQueryOutput (line 110) | MMQueryOutput MMAnimationLibrary::query(const MMQueryInput& p_query_inpu...
  function MMQueryOutput (line 236) | MMQueryOutput MMAnimationLibrary::_search_naive(const PackedFloat32Array...
  function MMQueryOutput (line 292) | MMQueryOutput MMAnimationLibrary::_search_kd_tree(const PackedFloat32Arr...

FILE: src/mm_animation_library.h
  function class (line 21) | class MMAnimationLibrary : public AnimationLibrary {

FILE: src/mm_animation_node.cpp
  function PackedFloat32Array (line 16) | PackedFloat32Array MMAnimationNode::_process_animation_node(const Packed...
  function PackedFloat32Array (line 96) | PackedFloat32Array MMAnimationNode::_update_current_animation(bool p_tes...
  function Array (line 165) | Array MMAnimationNode::_get_parameter_list() const {
  function Variant (line 172) | Variant MMAnimationNode::_get_parameter_default_value(const StringName& ...
  function String (line 199) | String MMAnimationNode::_get_caption() const {
  function Dictionary (line 256) | Dictionary MMAnimationNode::_output_to_dict(const MMQueryOutput& output) {

FILE: src/mm_animation_node.h
  function class (line 11) | class MMAnimationNode : public AnimationNodeExtension {

FILE: src/mm_bone_state.h
  type BoneState (line 13) | struct BoneState {
  function const (line 32) | struct SkeletonState {
  function BoneState (line 55) | const BoneState& find_bone_state(const String& name) const {

FILE: src/mm_character.cpp
  function Vector3 (line 45) | Vector3 MMCharacter::_update_trajectory(float delta_t) {
  function MMTrajectoryPoint (line 72) | MMTrajectoryPoint MMCharacter::_get_current_trajectory_point() const {
  function Dictionary (line 335) | Dictionary MMCharacter::_output_to_dict(const MMQueryOutput& output) {
  function AnimationMixer (line 347) | AnimationMixer* MMCharacter::get_animation_mixer() const {

FILE: src/mm_query.h
  type MMQueryOutput (line 16) | struct MMQueryOutput {
  function class (line 24) | class MMQueryInput : public RefCounted {

FILE: src/mm_trajectory_point.h
  type MMCollisionState (line 11) | struct MMCollisionState {
  type MMTrajectoryPoint (line 24) | struct MMTrajectoryPoint {

FILE: src/modifiers/damped_skeleton_modifier.h
  function class (line 11) | class DampedSkeletonModifier : public SkeletonModifier3D {

FILE: src/register_types.cpp
  function initialize_motion_matching_module (line 31) | void initialize_motion_matching_module(ModuleInitializationLevel p_level) {
  function uninitialize_motion_matching_module (line 65) | void uninitialize_motion_matching_module(ModuleInitializationLevel p_lev...
  function GDExtensionBool (line 70) | GDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetP...

FILE: src/synchronizers/mm_clamp_synchronizer.h
  function class (line 7) | class MMClampSynchronizer : public MMSynchronizer {

FILE: src/synchronizers/mm_mix_synchronizer.h
  function class (line 6) | class MMMixSynchronizer : public MMSynchronizer {

FILE: src/synchronizers/mm_rootmotion_synchronizer.h
  function class (line 7) | class MMRootMotionSynchronizer : public MMSynchronizer {

FILE: src/synchronizers/mm_synchronizer.h
  function class (line 13) | class MMSynchronizer : public Resource {
Condensed preview — 74 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (213K chars).
[
  {
    "path": ".clang-format",
    "chars": 706,
    "preview": "\nLanguage: Cpp\nBasedOnStyle: LLVM\nIndentWidth: 4\nUseTab: Never\nColumnLimit: 0\nAccessModifierOffset: -4\nAlignConsecutiveA"
  },
  {
    "path": ".github/actions/build/action.yml",
    "chars": 4383,
    "preview": "name: GDExtension Build\ndescription: Build GDExtension\n\ninputs:\n  platform:\n    required: true\n    description: Target p"
  },
  {
    "path": ".github/actions/sign/action.yml",
    "chars": 7016,
    "preview": "# This file incorporates work covered by the following copyright and permission notice:  \n# \n#     Copyright (c) Mikael "
  },
  {
    "path": ".github/workflows/builds.yml",
    "chars": 9280,
    "preview": "name: Build GDExtension\non:\n  workflow_call:\n  push:\n  pull_request:\n  merge_group:\n\nenv:\n  LIBNAME: godot-motion-matchi"
  },
  {
    "path": ".gitignore",
    "chars": 229,
    "preview": ".sconsign.dblite\n\nbin\n\n*.os\n\n*.obj\n\n*.dll\n*.exp\n*.lib\n*.ilk\n*.pdb\n*.so\n*.universal\n*.dylib\n*.wasm\n*.plist\n*.enabled\n*.tm"
  },
  {
    "path": ".gitmodules",
    "chars": 98,
    "preview": "[submodule \"godot-cpp\"]\n\tpath = godot-cpp\n\turl = https://github.com/GuilhermeGSousa/godot-cpp.git\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1060,
    "preview": "Copyright (c) 2024 - Guilherme Sousa\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of th"
  },
  {
    "path": "README.md",
    "chars": 2369,
    "preview": "# Motion Matching for Godot 4.4\n[![Build GDExtension](https://github.com/GuilhermeGSousa/godot-motion-matching/actions/w"
  },
  {
    "path": "SConstruct",
    "chars": 2462,
    "preview": "#!/usr/bin/env python\nimport os\nimport sys\nfrom pathlib import Path\nfrom methods import print_error\n\nfrom SCons.Environm"
  },
  {
    "path": "addons/motion_matching/gdmotionmatching.gdextension",
    "chars": 3871,
    "preview": "[configuration]\n\nentry_symbol = \"example_library_init\"\ncompatibility_minimum = \"4.4\"\nreloadable = true\n\n[libraries]\n\nmac"
  },
  {
    "path": "config.py",
    "chars": 480,
    "preview": "def can_build(env, platform):\n    return True\n\n\ndef configure(env):\n    pass\n\n\ndef get_doc_classes():\n    return [\n     "
  },
  {
    "path": "doc_classes/DampedSkeletonModifier.xml",
    "chars": 1037,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"DampedSkeletonModifier\" inherits=\"SkeletonModifier3D\" xmlns:xsi=\"ht"
  },
  {
    "path": "doc_classes/MMAnimationLibrary.xml",
    "chars": 3966,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMAnimationLibrary\" inherits=\"AnimationLibrary\" xmlns:xsi=\"http://w"
  },
  {
    "path": "doc_classes/MMAnimationNode.xml",
    "chars": 1882,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMAnimationNode\" inherits=\"AnimationNodeExtension\" xmlns:xsi=\"http:"
  },
  {
    "path": "doc_classes/MMBoneDataFeature.xml",
    "chars": 742,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMBoneDataFeature\" inherits=\"MMFeature\" xmlns:xsi=\"http://www.w3.or"
  },
  {
    "path": "doc_classes/MMCharacter.xml",
    "chars": 5441,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMCharacter\" inherits=\"CharacterBody3D\" xmlns:xsi=\"http://www.w3.or"
  },
  {
    "path": "doc_classes/MMClampSynchronizer.xml",
    "chars": 860,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMClampSynchronizer\" inherits=\"MMSynchronizer\" xmlns:xsi=\"http://ww"
  },
  {
    "path": "doc_classes/MMFeature.xml",
    "chars": 3419,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMFeature\" inherits=\"Resource\" xmlns:xsi=\"http://www.w3.org/2001/XM"
  },
  {
    "path": "doc_classes/MMMixSynchronizer.xml",
    "chars": 934,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMMixSynchronizer\" inherits=\"MMSynchronizer\" xmlns:xsi=\"http://www."
  },
  {
    "path": "doc_classes/MMQueryInput.xml",
    "chars": 616,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMQueryInput\" inherits=\"RefCounted\" xmlns:xsi=\"http://www.w3.org/20"
  },
  {
    "path": "doc_classes/MMRootMotionSynchronizer.xml",
    "chars": 536,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMRootMotionSynchronizer\" inherits=\"MMSynchronizer\" xmlns:xsi=\"http"
  },
  {
    "path": "doc_classes/MMSynchronizer.xml",
    "chars": 882,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMSynchronizer\" inherits=\"Resource\" xmlns:xsi=\"http://www.w3.org/20"
  },
  {
    "path": "doc_classes/MMTrajectoryFeature.xml",
    "chars": 2590,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<class name=\"MMTrajectoryFeature\" inherits=\"MMFeature\" xmlns:xsi=\"http://www.w3."
  },
  {
    "path": "methods.py",
    "chars": 1487,
    "preview": "import os\nimport sys\nfrom enum import Enum\n\n# Colors are disabled in non-TTY environments such as pipes. This means\n# th"
  },
  {
    "path": "src/algo/kd_tree.cpp",
    "chars": 5222,
    "preview": "#include \"kd_tree.h\"\n\n#include <algorithm>\n#include <limits>\n#include <numeric>\n\nKDTree::KDTree(const float* data, int p"
  },
  {
    "path": "src/algo/kd_tree.h",
    "chars": 1632,
    "preview": "#pragma once\n\n#include \"godot_cpp/variant/packed_float32_array.hpp\"\n#include \"godot_cpp/variant/packed_int32_array.hpp\"\n"
  },
  {
    "path": "src/circular_buffer.h",
    "chars": 1360,
    "preview": "#ifndef CIRCULAR_BUFFER_H\n#define CIRCULAR_BUFFER_H\n\n#include <cstddef>\n#include <deque>\n#include <vector>\n\ntemplate <ty"
  },
  {
    "path": "src/common.h",
    "chars": 1231,
    "preview": "#ifndef COMMON_H\n#define COMMON_H\n\n#include <godot_cpp/core/error_macros.hpp>\n#include <godot_cpp/core/object.hpp>\n\n#def"
  },
  {
    "path": "src/editor/animation_post_import_plugin.cpp",
    "chars": 2342,
    "preview": "#include \"animation_post_import_plugin.h\"\n\n#include \"godot_cpp/classes/animation_player.hpp\"\n#include \"godot_cpp/classes"
  },
  {
    "path": "src/editor/animation_post_import_plugin.h",
    "chars": 660,
    "preview": "#pragma once\n\n#include <godot_cpp/classes/editor_scene_post_import_plugin.hpp>\n\nusing namespace godot;\n\nclass AnimationP"
  },
  {
    "path": "src/editor/animation_tree_handler_plugin.cpp",
    "chars": 648,
    "preview": "#include \"animation_tree_handler_plugin.h\"\n\nAnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::_singleton = nullptr"
  },
  {
    "path": "src/editor/animation_tree_handler_plugin.h",
    "chars": 1048,
    "preview": "#ifndef ANIMATION_TREE_HANDLER_PLUGIN_H\n#define ANIMATION_TREE_HANDLER_PLUGIN_H\n\n#include <godot_cpp/classes/animation_t"
  },
  {
    "path": "src/editor/mm_data_tab.cpp",
    "chars": 3204,
    "preview": "#include \"mm_data_tab.h\"\n\n#include <godot_cpp/classes/h_box_container.hpp>\n#include <godot_cpp/classes/label.hpp>\n#inclu"
  },
  {
    "path": "src/editor/mm_data_tab.h",
    "chars": 592,
    "preview": "#pragma once\n\n#include \"mm_animation_library.h\"\n\n#include <godot_cpp/classes/grid_container.hpp>\n#include <godot_cpp/cla"
  },
  {
    "path": "src/editor/mm_editor.cpp",
    "chars": 6654,
    "preview": "#include \"editor/mm_editor.h\"\n\n#include \"mm_animation_library.h\"\n#include \"mm_character.h\"\n#include \"mm_editor.h\"\n\n#incl"
  },
  {
    "path": "src/editor/mm_editor.h",
    "chars": 1400,
    "preview": "#pragma once\n\n#include \"mm_data_tab.h\"\n#include \"mm_visualization_tab.h\"\n\n#include <godot_cpp/classes/animation_mixer.hp"
  },
  {
    "path": "src/editor/mm_editor_gizmo_plugin.cpp",
    "chars": 1971,
    "preview": "#include \"editor/mm_editor_gizmo_plugin.h\"\n\n#include <godot_cpp/classes/animation_mixer.hpp>\n#include <godot_cpp/classes"
  },
  {
    "path": "src/editor/mm_editor_gizmo_plugin.h",
    "chars": 897,
    "preview": "#ifndef MM_EDITOR_GIZMO_PLUGIN_H\n#define MM_EDITOR_GIZMO_PLUGIN_H\n\n#include <godot_cpp/classes/editor_node3d_gizmo_plugi"
  },
  {
    "path": "src/editor/mm_editor_plugin.cpp",
    "chars": 1586,
    "preview": "\n#include \"editor/mm_editor_plugin.h\"\n\n#include <godot_cpp/variant/utility_functions.hpp>\n\n#include <godot_cpp/classes/b"
  },
  {
    "path": "src/editor/mm_editor_plugin.h",
    "chars": 969,
    "preview": "#ifndef MM_EDITOR_PLUGIN_H\n#define MM_EDITOR_PLUGIN_H\n\n#include \"animation_post_import_plugin.h\"\n#include \"mm_character."
  },
  {
    "path": "src/editor/mm_visualization_tab.cpp",
    "chars": 3890,
    "preview": "#include \"mm_visualization_tab.h\"\n\nvoid MMVisualizationTab::set_enabled(bool p_enabled) {\n    if (p_enabled) {\n        _"
  },
  {
    "path": "src/editor/mm_visualization_tab.h",
    "chars": 1210,
    "preview": "#pragma once\n\n#include \"mm_animation_library.h\"\n\n#include <godot_cpp/classes/grid_container.hpp>\n#include <godot_cpp/cla"
  },
  {
    "path": "src/features/mm_bone_data_feature.cpp",
    "chars": 5239,
    "preview": "#include \"features/mm_bone_data_feature.h\"\n\n#include \"mm_bone_data_feature.h\"\n#include \"mm_bone_state.h\"\n\n#include <godo"
  },
  {
    "path": "src/features/mm_bone_data_feature.h",
    "chars": 1264,
    "preview": "#ifndef MM_BONE_DATA_FEATURE_H\n#define MM_BONE_DATA_FEATURE_H\n\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n\n#in"
  },
  {
    "path": "src/features/mm_feature.cpp",
    "chars": 3715,
    "preview": "#include \"mm_feature.h\"\n\nMMFeature::MMFeature() {\n}\n\nMMFeature::~MMFeature() {\n}\n\nvoid MMFeature::normalize(float* p_dat"
  },
  {
    "path": "src/features/mm_feature.h",
    "chars": 2002,
    "preview": "#ifndef MM_FEATURE_H\n#define MM_FEATURE_H\n\n#include \"common.h\"\n#include \"mm_query.h\"\n\n#include <godot_cpp/classes/animat"
  },
  {
    "path": "src/features/mm_trajectory_feature.cpp",
    "chars": 12637,
    "preview": "#include \"mm_trajectory_feature.h\"\n\n#include \"math/transforms.h\"\n#include \"mm_character.h\"\n\n#include <godot_cpp/classes/"
  },
  {
    "path": "src/features/mm_trajectory_feature.h",
    "chars": 1977,
    "preview": "#ifndef MM_TRAJECTORY_FEATURE_H\n#define MM_TRAJECTORY_FEATURE_H\n\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n#i"
  },
  {
    "path": "src/math/hash.h",
    "chars": 180,
    "preview": "#pragma once\n\n#include <godot_cpp/core/type_info.hpp>\n\nusing namespace godot;\n\nint64_t hash_combine(int64_t a, int64_t b"
  },
  {
    "path": "src/math/spring.hpp",
    "chars": 14641,
    "preview": "#pragma once\n\n#include <math.h>\n\n#include <cmath>\n#include <godot_cpp/classes/project_settings.hpp>\n#include <godot_cpp/"
  },
  {
    "path": "src/math/stats.hpp",
    "chars": 1405,
    "preview": "#pragma once\n\n#include <algorithm>\n#include <cfloat>\n#include <cmath>\n\nclass StatsAccumulator {\nprivate:\n    float sum;\n"
  },
  {
    "path": "src/math/transforms.h",
    "chars": 503,
    "preview": "#pragma once\n\n#include <godot_cpp/variant/transform3d.hpp>\n#include <godot_cpp/variant/utility_functions.hpp>\n\nusing nam"
  },
  {
    "path": "src/mm_animation_library.cpp",
    "chars": 15607,
    "preview": "#include \"mm_animation_library.h\"\n\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n#include \"math/hash.h\"\n#include "
  },
  {
    "path": "src/mm_animation_library.h",
    "chars": 1970,
    "preview": "#pragma once\n\n#include \"algo/kd_tree.h\"\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n#include \"mm_query.h\"\n\n#inc"
  },
  {
    "path": "src/mm_animation_node.cpp",
    "chars": 9907,
    "preview": "#include \"mm_animation_node.h\"\n\n#include \"editor/animation_tree_handler_plugin.h\"\n#include \"math/spring.hpp\"\n#include \"m"
  },
  {
    "path": "src/mm_animation_node.h",
    "chars": 1774,
    "preview": "#ifndef MM_ANIMATION_NODE_H\n#define MM_ANIMATION_NODE_H\n\n#include \"common.h\"\n#include \"mm_animation_library.h\"\n\n#include"
  },
  {
    "path": "src/mm_bone_state.h",
    "chars": 1828,
    "preview": "#ifndef MM_BONE_STATE_H\n#define MM_BONE_STATE_H\n\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/core/ma"
  },
  {
    "path": "src/mm_character.cpp",
    "chars": 16026,
    "preview": "#include \"mm_character.h\"\n\n#include \"math/spring.hpp\"\n#include \"mm_animation_library.h\"\n#include \"mm_animation_node.h\"\n\n"
  },
  {
    "path": "src/mm_character.h",
    "chars": 4467,
    "preview": "#ifndef MM_CHARACTER_H\n#define MM_CHARACTER_H\n\n#include \"circular_buffer.h\"\n#include \"common.h\"\n#include \"mm_character.h"
  },
  {
    "path": "src/mm_query.h",
    "chars": 1130,
    "preview": "#ifndef MM_QUERY_H\n#define MM_QUERY_H\n\n#include \"mm_bone_state.h\"\n#include \"mm_query.h\"\n#include \"mm_trajectory_point.h\""
  },
  {
    "path": "src/mm_trajectory_point.cpp",
    "chars": 32,
    "preview": "#include \"mm_trajectory_point.h\""
  },
  {
    "path": "src/mm_trajectory_point.h",
    "chars": 840,
    "preview": "#ifndef MM_TRAJECTORY_POINT_H\n#define MM_TRAJECTORY_POINT_H\n\n#include \"common.h\"\n\n#include <godot_cpp/classes/ref_counte"
  },
  {
    "path": "src/modifiers/damped_skeleton_modifier.cpp",
    "chars": 2218,
    "preview": "#include \"modifiers/damped_skeleton_modifier.h\"\n\n#include \"damped_skeleton_modifier.h\"\n#include \"math/spring.hpp\"\n\nvoid "
  },
  {
    "path": "src/modifiers/damped_skeleton_modifier.h",
    "chars": 577,
    "preview": "#ifndef DAMPED_SKELETON_MODIFIER_H\n#define DAMPED_SKELETON_MODIFIER_H\n\n#include \"common.h\"\n#include \"mm_bone_state.h\"\n\n#"
  },
  {
    "path": "src/register_types.cpp",
    "chars": 3029,
    "preview": "#include \"register_types.h\"\n\n#include <gdextension_interface.h>\n\n#include \"mm_character.h\"\n\n#include \"editor/animation_p"
  },
  {
    "path": "src/register_types.h",
    "chars": 342,
    "preview": "#ifndef MOTION_MATCHING_REGISTER_TYPES_H\n#define MOTION_MATCHING_REGISTER_TYPES_H\n\n#include <godot_cpp/core/class_db.hpp"
  },
  {
    "path": "src/synchronizers/mm_clamp_synchronizer.cpp",
    "chars": 839,
    "preview": "#include \"synchronizers/mm_clamp_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMClampSynchronizer::sync(MMCharacter*"
  },
  {
    "path": "src/synchronizers/mm_clamp_synchronizer.h",
    "chars": 464,
    "preview": "#ifndef MM_CLAMP_SYNCHRONIZER_H\n#define MM_CLAMP_SYNCHRONIZER_H\n\n#include \"common.h\"\n#include \"synchronizers/mm_synchron"
  },
  {
    "path": "src/synchronizers/mm_mix_synchronizer.cpp",
    "chars": 1190,
    "preview": "#include \"mm_mix_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMMixSynchronizer::sync(MMCharacter* p_controller, Nod"
  },
  {
    "path": "src/synchronizers/mm_mix_synchronizer.h",
    "chars": 375,
    "preview": "#pragma once\n\n#include \"common.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\nclass MMMixSynchronizer : public MMSynchro"
  },
  {
    "path": "src/synchronizers/mm_rootmotion_synchronizer.cpp",
    "chars": 865,
    "preview": "#include \"mm_rootmotion_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMRootMotionSynchronizer::sync(MMCharacter* p_c"
  },
  {
    "path": "src/synchronizers/mm_rootmotion_synchronizer.h",
    "chars": 447,
    "preview": "#ifndef MM_ROOTMOTION_SYNCHRONIZER_H\n#define MM_ROOTMOTION_SYNCHRONIZER_H\n\n#include \"common.h\"\n#include \"synchronizers/m"
  },
  {
    "path": "src/synchronizers/mm_synchronizer.cpp",
    "chars": 115,
    "preview": "#include \"synchronizers/mm_synchronizer.h\"\n\n#include \"mm_synchronizer.h\"\n\nvoid MMSynchronizer::_bind_methods() {\n}\n"
  },
  {
    "path": "src/synchronizers/mm_synchronizer.h",
    "chars": 560,
    "preview": "#ifndef MM_SYNCHRONIZER_H\n#define MM_SYNCHRONIZER_H\n\n#include \"common.h\"\n\n#include <godot_cpp/classes/node3d.hpp>\n#inclu"
  }
]

About this extraction

This page contains the full source code of the GuilhermeGSousa/godot-motion-matching GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 74 files (196.3 KB), approximately 50.5k tokens, and a symbol index with 118 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!