[
  {
    "path": ".clang-format",
    "content": "\nLanguage: Cpp\nBasedOnStyle: LLVM\nIndentWidth: 4\nUseTab: Never\nColumnLimit: 0\nAccessModifierOffset: -4\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlines: Left\nAlignOperands: false\nAlignTrailingComments: true\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: None\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: true\nBinPackParameters: true\nPointerAlignment: Left\nSortIncludes: true\n"
  },
  {
    "path": ".github/actions/build/action.yml",
    "content": "name: GDExtension Build\ndescription: Build GDExtension\n\ninputs:\n  platform:\n    required: true\n    description: Target platform.\n  arch:\n    required: true\n    description: Target architecture.\n  float-precision:\n    default: 'single'\n    description: Float precision (single or double).\n  build-target-type:\n    default: 'template_debug'\n    description: Build type (template_debug or template_release).\n  scons-cache:\n    default: '.scons-cache/'\n    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).\n  em_version:\n    default: 3.1.62\n    description: Emscripten version.\n  em-cache-directory:\n    default: emsdk-cache\n    description: Emscripten cache directory.\n  gdextension-directory:\n    default: ''\n    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).\n\nruns:\n  using: composite\n  steps:\n# Android only\n    - name: Android - Set up Java 17\n      uses: actions/setup-java@v4\n      if: ${{ inputs.platform == 'android' }}\n      with:\n        distribution: temurin\n        java-version: 17\n\n    - name: Android - Remove existing Android SDK, and set up ENV vars\n      if: ${{ inputs.platform == 'android' }}\n      shell: sh\n      run: |\n        sudo rm -r /usr/local/lib/android/sdk/**\n        export ANDROID_HOME=/usr/local/lib/android/sdk\n        export ANDROID_SDK_ROOT=$ANDROID_HOME\n        export ANDROID_NDK_VERSION=23.2.8568313\n        export ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}\n        echo \"ANDROID_HOME=$ANDROID_HOME\" >> \"$GITHUB_ENV\"\n        echo \"ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT\" >> \"$GITHUB_ENV\"\n        echo \"ANDROID_NDK_VERSION=$ANDROID_NDK_VERSION\" >> \"$GITHUB_ENV\"\n        echo \"ANDROID_NDK_ROOT=$ANDROID_NDK_ROOT\" >> \"$GITHUB_ENV\"\n\n    - name: Android - Set up Android SDK\n      if: ${{ inputs.platform == 'android' }}\n      uses: android-actions/setup-android@v3\n      with:\n        packages: \"ndk;${{ env.ANDROID_NDK_VERSION }} cmdline-tools;latest build-tools;34.0.0 platforms;android-34 cmake;3.22.1\"\n# Linux only\n    - name: Linux - dependencies\n      if: ${{ inputs.platform == 'linux' }}\n      shell: sh\n      run: |\n        sudo apt-get update -qq\n        sudo apt-get install -qqq build-essential pkg-config\n# Web only\n    - name: Web - Set up Emscripten latest\n      if: ${{ inputs.platform == 'web' }}\n      uses: mymindstorm/setup-emsdk@v14\n      with:\n        version: ${{ inputs.em_version }}\n        actions-cache-folder: ${{ inputs.em-cache-directory }}.${{ inputs.float-precision }}.${{ inputs.build-target-type }}\n    - name: Web - Verify Emscripten setup\n      if: ${{ inputs.platform == 'web' }}\n      shell: sh\n      run: |\n        emcc -v\n# Windows only\n    - name: Windows - Setup MinGW for Windows/MinGW build\n      uses: egor-tensin/setup-mingw@v2\n      if: ${{ inputs.platform == 'windows' }}\n      with:\n        version: 12.2.0\n# Dependencies of godot\n    # Use python 3.x release (works cross platform)\n    - name: Set up Python 3.x\n      uses: actions/setup-python@v5\n      with:\n        # Semantic version range syntax or exact version of a Python version\n        python-version: \"3.x\"\n        # Optional - x64 or x86 architecture, defaults to x64\n        architecture: \"x64\"\n    - name: Setup scons\n      shell: bash\n      run: |\n        python -c \"import sys; print(sys.version)\"\n        python -m pip install scons==4.4.0\n        scons --version\n# Build\n    - name: Cache .scons_cache\n      uses: actions/cache@v4\n      with:\n        path: |\n          ${{ github.workspace }}/${{ inputs.gdextension-directory }}${{ inputs.scons-cache }}\n        key: ${{ inputs.platform }}_${{ inputs.arch }}_${{ inputs.float-precision }}_${{ inputs.build-target-type }}_cache\n# Build gdextension\n    - name: Build GDExtension Debug Build\n      shell: sh\n      env:\n        SCONS_CACHE: ${{ github.workspace }}/${{ inputs.gdextension-directory }}${{ inputs.scons-cache }}\n      run: |\n        scons target=${{ inputs.build-target-type }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} precision=${{ inputs.float-precision }}\n      working-directory: ${{ inputs.gdextension-directory }}\n"
  },
  {
    "path": ".github/actions/sign/action.yml",
    "content": "# This file incorporates work covered by the following copyright and permission notice:  \n# \n#     Copyright (c) Mikael Hermansson and Godot Jolt contributors.\n#     Copyright (c) Dragos Daian.\n# \n#     Permission is hereby granted, free of charge, to any person obtaining a copy of\n#     this software and associated documentation files (the \"Software\"), to deal in\n#     the Software without restriction, including without limitation the rights to\n#     use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n#     the Software, and to permit persons to whom the Software is furnished to do so,\n#     subject to the following conditions:\n# \n#     The above copyright notice and this permission notice shall be included in all\n#     copies or substantial portions of the Software.\n# \n#     THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n#     FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n#     COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n#     IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n#     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\nname: GDExtension Sign\ndescription: Sign Mac GDExtension\n\ninputs:\n  FRAMEWORK_PATH:\n    description: The path of the artifact. Eg. bin/addons/my_addon/bin/libmy_addon.macos.template_release.universal.framework\n    required: true\n  SIGN_FLAGS:\n    description: The extra flags to use. Eg. --deep\n    required: false\n  APPLE_CERT_BASE64:\n    required: true\n    description: Base64 file from p12 certificate.\n  APPLE_CERT_PASSWORD:\n    required: true\n    description: Password set when creating p12 certificate from .cer certificate.\n  APPLE_DEV_PASSWORD:\n    required: true\n    description: Apple App-Specific Password. Eg. abcd-abcd-abcd-abcd\n  APPLE_DEV_ID:\n    required: true\n    description: Email used for Apple Id. Eg. email@provider.com\n  APPLE_DEV_TEAM_ID:\n    required: true\n    description: Apple Team Id. Eg. 1ABCD23EFG\n  APPLE_DEV_APP_ID:\n    required: true\n    description: |\n      Certificate name from get info -> Common name . Eg. Developer ID Application: Common Name (1ABCD23EFG)\noutputs:\n  zip_path:\n    value: ${{ steps.sign.outputs.path }}\n\n\nruns:\n  using: composite\n  steps:\n  - name: Sign\n    id: sign\n    shell: pwsh\n    run: |\n      #!/usr/bin/env pwsh\n\n      # Copyright (c) Mikael Hermansson and Godot Jolt contributors.\n\n      # Permission is hereby granted, free of charge, to any person obtaining a copy of\n      # this software and associated documentation files (the \"Software\"), to deal in\n      # the Software without restriction, including without limitation the rights to\n      # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\n      # the Software, and to permit persons to whom the Software is furnished to do so,\n      # subject to the following conditions:\n\n      # The above copyright notice and this permission notice shall be included in all\n      # copies or substantial portions of the Software.\n\n      # THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n      # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\n      # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n      # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\n      # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\n      # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n      # Taken from https://github.com/godot-jolt/godot-jolt/blob/master/scripts/ci_sign_macos.ps1\n\n      Set-StrictMode -Version Latest\n      $ErrorActionPreference = \"Stop\"\n\n      $CodesignPath = Get-Command codesign | Resolve-Path\n\n      $CertificateBase64 = \"${{inputs.APPLE_CERT_BASE64}}\"\n      $CertificatePassword = \"${{inputs.APPLE_CERT_PASSWORD}}\"\n      $CertificatePath = [IO.Path]::ChangeExtension((New-TemporaryFile), \"p12\")\n\n      $Keychain = \"ephemeral.keychain\"\n      $KeychainPassword = (New-Guid).ToString().Replace(\"-\", \"\")\n\n      $DevId = \"${{ inputs.APPLE_DEV_ID }}\"\n      $DevTeamId = \"${{ inputs.APPLE_DEV_TEAM_ID }}\"\n      $DevPassword = \"${{ inputs.APPLE_DEV_PASSWORD }}\"\n      $DeveloperIdApplication = \"${{ inputs.APPLE_DEV_APP_ID }}\"\n\n      if (!$CertificateBase64) { throw \"No certificate provided\" }\n      if (!$CertificatePassword) { throw \"No certificate password provided\" }\n      if (!$DevId) { throw \"No Apple Developer ID provided\" }\n      if (!$DeveloperIdApplication) { throw \"No Apple Developer ID Application provided\" }\n      if (!$DevTeamId) { throw \"No Apple Team ID provided\" }\n      if (!$DevPassword) { throw \"No Apple Developer password provided\" }\n\n      Write-Output \"Decoding certificate...\"\n\n      $Certificate = [Convert]::FromBase64String($CertificateBase64)\n\n      Write-Output \"Writing certificate to disk...\"\n\n      [IO.File]::WriteAllBytes($CertificatePath, $Certificate)\n\n      Write-Output \"Creating keychain...\"\n\n      security create-keychain -p $KeychainPassword $Keychain\n\n      Write-Output \"Setting keychain as default...\"\n\n      security default-keychain -s $Keychain\n\n      Write-Output \"Importing certificate into keychain...\"\n      security import $CertificatePath `\n        -k ~/Library/Keychains/$Keychain `\n        -P $CertificatePassword `\n        -T $CodesignPath\n      Write-Output \"Check identities...\"\n\n      security find-identity\n\n      Write-Output \"Granting access to keychain...\"\n\n      security set-key-partition-list -S \"apple-tool:,apple:\" -s -k $KeychainPassword $Keychain\n\n      $Framework = \"${{ inputs.FRAMEWORK_PATH }}\"\n      $SignFlags = \"${{ inputs.SIGN_FLAGS }}\"\n      $Archive = [IO.Path]::ChangeExtension((New-TemporaryFile), \"zip\")\n\n      Write-Output \"Signing '$Framework'...\"\n\n      & $CodesignPath --verify --timestamp --verbose \"$SignFlags\" --sign $DeveloperIdApplication \"$Framework\"\n\n      Write-Output \"Verifying signing...\"\n\n      & $CodesignPath --verify -dvvv \"$Framework\"\n\n      Get-ChildItem -Force -Recurse -Path \"$Framework\"\n\n      Write-Output \"Archiving framework to '$Archive'...\"\n\n      ditto -ck -rsrc --sequesterRsrc --keepParent \"$Framework\" \"$Archive\"\n\n      Write-Output \"Submitting archive for notarization...\"\n\n      $output = xcrun notarytool submit \"$Archive\" `\n        --apple-id $DevId `\n        --team-id $DevTeamId `\n        --password $DevPassword `\n        --wait\n      echo $output\n      $matches = $output -match '((\\d|[a-z])+-(\\d|[a-z])+-(\\d|[a-z])+-(\\d|[a-z])+-(\\d|[a-z])+)'\n      if ($output) {\n        $id_res = $matches[0].Substring(6)\n      }\n      xcrun notarytool log $id_res `\n        --apple-id $DevId `\n        --team-id $DevTeamId `\n        --password $DevPassword `\n        developer_log.json\n      get-content developer_log.json\n\n      echo \"path=$Archive\" >> $env:GITHUB_OUTPUT\n\n\n"
  },
  {
    "path": ".github/workflows/builds.yml",
    "content": "name: Build GDExtension\non:\n  workflow_call:\n  push:\n  pull_request:\n  merge_group:\n\nenv:\n  LIBNAME: godot-motion-matching\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n        # Debug templates\n          - platform: linux\n            float-precision: single\n            arch: x86_64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: windows\n            float-precision: single\n            arch: x86_32\n            target-type: template_debug\n            os: windows-latest\n\n          - platform: windows\n            float-precision: single\n            arch: x86_64\n            target-type: template_debug\n            os: windows-latest\n\n          - platform: macos\n            float-precision: single\n            arch: universal\n            target-type: template_debug\n            os: macos-latest\n\n          - platform: android\n            float-precision: single\n            arch: arm64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: single\n            arch: arm32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: single\n            arch: x86_64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: single\n            arch: x86_32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: ios\n            float-precision: single\n            arch: arm64\n            target-type: template_debug\n            os: macos-latest\n\n          - platform: web\n            float-precision: single\n            arch: wasm32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n        # Release templates\n          - platform: linux\n            float-precision: single\n            arch: x86_64\n            target-type: template_release\n            os: ubuntu-22.04\n\n          # - platform: windows\n          #   float-precision: single\n          #   arch: x86_32\n          #   target-type: template_release\n          #   os: windows-latest\n\n          # - platform: windows\n          #   float-precision: single\n          #   arch: x86_64\n          #   target-type: template_release\n          #   os: windows-latest\n\n          # - platform: macos\n          #   float-precision: single\n          #   arch: universal\n          #   target-type: template_release\n          #   os: macos-latest\n\n          # - platform: android\n          #   float-precision: single\n          #   arch: arm64\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: single\n          #   arch: arm32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: single\n          #   arch: x86_64\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: single\n          #   arch: x86_32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: ios\n          #   float-precision: single\n          #   arch: arm64\n          #   target-type: template_release\n          #   os: macos-latest\n\n          # - platform: web\n          #   float-precision: single\n          #   arch: wasm32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n        # Double precision templates\n        # Double precision debug templates\n          - platform: linux\n            float-precision: double\n            arch: x86_64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: windows\n            float-precision: double\n            arch: x86_32\n            target-type: template_debug\n            os: windows-latest\n\n          - platform: windows\n            float-precision: double\n            arch: x86_64\n            target-type: template_debug\n            os: windows-latest\n\n          - platform: macos\n            float-precision: double\n            arch: universal\n            target-type: template_debug\n            os: macos-latest\n\n          - platform: android\n            float-precision: double\n            arch: arm64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: double\n            arch: arm32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: double\n            arch: x86_64\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: android\n            float-precision: double\n            arch: x86_32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n          - platform: ios\n            float-precision: double\n            arch: arm64\n            target-type: template_debug\n            os: macos-latest\n\n          - platform: web\n            float-precision: double\n            arch: wasm32\n            target-type: template_debug\n            os: ubuntu-22.04\n\n        # Double precision release templates\n          - platform: linux\n            float-precision: double\n            arch: x86_64\n            target-type: template_release\n            os: ubuntu-22.04\n\n          # - platform: windows\n          #   float-precision: double\n          #   arch: x86_32\n          #   target-type: template_release\n          #   os: windows-latest\n\n          # - platform: windows\n          #   float-precision: double\n          #   arch: x86_64\n          #   target-type: template_release\n          #   os: windows-latest\n\n          # - platform: macos\n          #   float-precision: double\n          #   arch: universal\n          #   target-type: template_release\n          #   os: macos-latest\n\n          # - platform: android\n          #   float-precision: double\n          #   arch: arm64\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: double\n          #   arch: arm32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: double\n          #   arch: x86_64\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: android\n          #   float-precision: double\n          #   arch: x86_32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n\n          # - platform: ios\n          #   float-precision: double\n          #   arch: arm64\n          #   target-type: template_release\n          #   os: macos-latest\n\n          # - platform: web\n          #   float-precision: double\n          #   arch: wasm32\n          #   target-type: template_release\n          #   os: ubuntu-22.04\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: true\n\n# Lint\n      #- name: Setup clang-format\n      #  shell: bash\n      #  run: |\n      #    python -m pip install clang-format\n      #- name: Run clang-format\n      #  shell: bash\n      #  run: |\n      #    clang-format src/** --dry-run --Werror\n\n# Build\n      - name: 🔗 GDExtension Debug Build\n        uses: ./.github/actions/build\n        with:\n          platform: ${{ matrix.platform }}\n          arch: ${{ matrix.arch }}\n          float-precision: ${{ matrix.float-precision }}\n          build-target-type: ${{ matrix.target-type }}\n\n# Sign\n      - name: Mac Sign\n        # Disable sign if secrets are not set\n        if: ${{ matrix.platform == 'macos' && env.APPLE_CERT_BASE64 }}\n        env:\n          APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}\n        uses: ./.github/actions/sign\n        with:\n          FRAMEWORK_PATH: bin/macos/macos.framework\n          APPLE_CERT_BASE64: ${{ secrets.APPLE_CERT_BASE64 }}\n          APPLE_CERT_PASSWORD: ${{ secrets.APPLE_CERT_PASSWORD }}\n          APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}\n          APPLE_DEV_ID: ${{ secrets.APPLE_DEV_ID }}\n          APPLE_DEV_TEAM_ID: ${{ secrets.APPLE_DEV_TEAM_ID }}\n          APPLE_DEV_APP_ID: ${{ secrets.APPLE_DEV_APP_ID }}\n\n      - name: Windows - Delete compilation files\n        if: ${{ matrix.platform == 'windows' }}\n        shell: pwsh\n        run: |\n          Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: godot-cpp-template-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.float-precision }}-${{ matrix.target-type }}\n          path: |\n            ${{ github.workspace }}/bin/**\n\n  # Merges all the build artifacts together into a single godot-cpp-template artifact.\n  # If you comment out this step, all the builds will be uploaded individually.\n  merge:\n    runs-on: ubuntu-22.04\n    needs: build\n    steps:\n      - name: Merge Artifacts\n        uses: actions/upload-artifact/merge@v4\n        with:\n          name: godot-cpp-template\n          pattern: godot-cpp-template-*\n          delete-merged: true\n"
  },
  {
    "path": ".gitignore",
    "content": ".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*.tmp\n*.TMP\n*.uid\n\n.vscode\n.cache\n\ncompile_commands.json\n\nsrc/gen\n\n# Ignored demos\ndemo_*/\n\n# Python\n__pycache__\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"godot-cpp\"]\n\tpath = godot-cpp\n\turl = https://github.com/GuilhermeGSousa/godot-cpp.git\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2024 - Guilherme Sousa\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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."
  },
  {
    "path": "README.md",
    "content": "# Motion Matching for Godot 4.4\n[![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)\n\n![](https://github.com/GuilhermeGSousa/godot-motion-matching/blob/master/motion_matching_demo.gif)\n\nMotion 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.\n\nThis extension is fully integrated into Godot's `AnimationTree` system, and can be used in tandem with more traditional animation techniques.\n\n### :gear: How it Works\nMotion 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.\n\nThe only requirement for all this to work is to have animations with both root motion, and a root bone at the foot level.\n\nYou can find more on how to set all this up on the wiki [here!](https://github.com/GuilhermeGSousa/godot-motion-matching/wiki)\n\n\n### :raised_hands: Credits\nI want to thank all the contributors that made this project possible!\n\n[Fire](https://github.com/fire)\n[GeorgeS](https://github.com/GeorgeS2019)\n[Remi](https://github.com/Remi123)\n[Roberts Kalnins](https://github.com/rkalnins)\n\n### Sources\n\n- [Road to Next Gen Animation - GDC Talk](https://www.gdcvault.com/play/1023280/Motion-Matching-and-The-Road)\n- [Simon Clavet's implementation video](https://www.youtube.com/watch?v=jcpIrw38E-s&ab_channel=SimonClavet)\n- [Orange Duck's Blog](https://theorangeduck.com/)\n- [Remi's Motion Matching implementation](https://github.com/Remi123/MotionMatching)\n- Demo data taken from [O3DE Motion Matching Implementation](https://github.com/o3de/o3de/tree/development/Gems/MotionMatching)\n\nA motion matching implementation in Godot 4.4, implemented following [Dan Holden's article](https://www.theorangeduck.com/page/code-vs-data-driven-displacement).\n"
  },
  {
    "path": "SConstruct",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\nfrom pathlib import Path\nfrom methods import print_error\n\nfrom SCons.Environment import Environment\nfrom SCons.Variables import Variables\nfrom SCons.Script import ARGUMENTS\n\nlibname = \"gdmotionmatching\"\nprojectdir = \"addons/motion_matching\"\ndouble_api_file = \"godot-cpp/gdextension/extension_api_double.json\"\n\nif ARGUMENTS.get(\"precision\", \"single\") == \"double\":\n    ARGUMENTS[\"custom_api_file\"] = double_api_file\n    print(\"Using double precision API file: {}\".format(double_api_file))\n    \nlocalEnv = Environment(tools=[\"default\"], PLATFORM=\"\")\n\ncustoms = [\"custom.py\"]\ncustoms = [os.path.abspath(path) for path in customs]\n\nopts = Variables(customs, ARGUMENTS)\nopts.Update(localEnv)\n\nHelp(opts.GenerateHelpText(localEnv))\n\nenv = localEnv.Clone()\n\nsubmodule_initialized = False\ndir_name = 'godot-cpp'\nif os.path.isdir(dir_name):\n    if os.listdir(dir_name):\n        submodule_initialized = True\n\nif not submodule_initialized:\n    print_error(\"\"\"godot-cpp is not available within this folder, as Git submodules haven't been initialized.\nRun the following command to download godot-cpp:\n\n    git submodule update --init --recursive\"\"\")\n    sys.exit(1)\n\nenv = SConscript(\"godot-cpp/SConstruct\", {\"env\": env, \"customs\": customs})\n\nenv.Append(CPPPATH=[\"src/\"])\nsources = Glob(\"src/*.cpp\")\nsources += Glob(\"src/algo/*.cpp\")\nsources += Glob(\"src/editor/*.cpp\")\nsources += Glob(\"src/features/*.cpp\")\nsources += Glob(\"src/math/*.cpp\")\nsources += Glob(\"src/modifiers/*.cpp\")\nsources += Glob(\"src/synchronizers/*.cpp\")\n\nif env[\"target\"] in [\"editor\", \"template_debug\"]:\n    try:\n        doc_data = env.GodotCPPDocData(\"src/gen/doc_data.gen.cpp\", source=Glob(\"doc_classes/*.xml\"))\n        sources.append(doc_data)\n    except AttributeError:\n        print(\"Not including class reference as we're targeting a pre-4.3 baseline.\")\n\nsuffix = env[\"suffix\"].replace(\".dev\", \"\")\nfile = \"{}{}{}\".format(libname, suffix, env[\"SHLIBSUFFIX\"])\nfilepath = \"\"\n\nif env[\"platform\"] == \"macos\" or env[\"platform\"] == \"ios\":\n    filepath = \"{}.framework/\".format(env[\"platform\"])\n    file = \"{}.{}.{}\".format(libname, env[\"platform\"], env[\"target\"])\n\nlibraryfile = \"bin/{}/{}lib{}\".format(env[\"platform\"], filepath, file)\nlibrary = env.SharedLibrary(\n    libraryfile,\n    source=sources,\n)\n\ncopy = env.InstallAs(\"{}/bin/{}/{}lib{}\".format(projectdir, env[\"platform\"], filepath, file), library)\n\ndefault_args = [library, copy]\nDefault(*default_args)\n"
  },
  {
    "path": "addons/motion_matching/gdmotionmatching.gdextension",
    "content": "[configuration]\n\nentry_symbol = \"example_library_init\"\ncompatibility_minimum = \"4.4\"\nreloadable = true\n\n[libraries]\n\nmacos.debug = \"bin/macos/macos.framework/libgdmotionmatching.macos.template_debug\"\nmacos.release = \"bin/macos/macos.framework/libgdmotionmatching.macos.template_release\"\nios.debug = \"bin/ios/ios.framework/libgdmotionmatching.ios.template_debug\"\nios.release = \"bin/ios/ios.framework/libgdmotionmatching.ios.template_release\"\nwindows.debug.x86_32 = \"bin/windows/libgdmotionmatching.windows.template_debug.x86_32.dll\"\nwindows.release.x86_32 = \"bin/windows/libgdmotionmatching.windows.template_release.x86_32.dll\"\nwindows.debug.x86_64 = \"bin/windows/libgdmotionmatching.windows.template_debug.x86_64.dll\"\nwindows.release.x86_64 = \"bin/windows/libgdmotionmatching.windows.template_release.x86_64.dll\"\nwindows.debug.double.x86_32 = \"bin/windows/libgdmotionmatching.windows.template_debug.double.x86_32.dll\"\nwindows.release.double.x86_32 = \"bin/windows/libgdmotionmatching.windows.template_release.double.x86_32.dll\"\nwindows.debug.double.x86_64 = \"bin/windows/libgdmotionmatching.windows.template_debug.double.x86_64.dll\"\nwindows.release.double.x86_64 = \"bin/windows/libgdmotionmatching.windows.template_release.double.x86_64.dll\"\nlinux.debug.x86_64 = \"bin/linux/libgdmotionmatching.linux.template_debug.x86_64.so\"\nlinux.release.x86_64 = \"bin/linux/libgdmotionmatching.linux.template_release.x86_64.so\"\nlinux.debug.double.x86_64 = \"bin/linux/libgdmotionmatching.linux.template_debug.double.x86_64.so\"\nlinux.release.double.x86_64 = \"bin/linux/libgdmotionmatching.linux.template_release.double.x86_64.so\"\nlinux.debug.arm64 = \"bin/linux/libgdmotionmatching.linux.template_debug.arm64.so\"\nlinux.release.arm64 = \"bin/linux/libgdmotionmatching.linux.template_release.arm64.so\"\nlinux.debug.rv64 = \"bin/linux/libgdmotionmatching.linux.template_debug.rv64.so\"\nlinux.release.rv64 = \"bin/linux/libgdmotionmatching.linux.template_release.rv64.so\"\nandroid.debug.x86_64 = \"bin/android/libgdmotionmatching.android.template_debug.x86_64.so\"\nandroid.release.x86_64 = \"bin/android/libgdmotionmatching.android.template_release.x86_64.so\"\nandroid.debug.x86_32 = \"bin/android/libgdmotionmatching.android.template_debug.x86_32.so\"\nandroid.release.x86_32 = \"bin/android/libgdmotionmatching.android.template_release.x86_32.so\"\nandroid.debug.arm64 = \"bin/android/libgdmotionmatching.android.template_debug.arm64.so\"\nandroid.release.arm64 = \"bin/android/libgdmotionmatching.android.template_release.arm64.so\"\nandroid.debug.arm32 = \"bin/android/libgdmotionmatching.android.template_debug.arm32.so\"\nandroid.release.arm32 = \"bin/android/libgdmotionmatching.android.template_release.arm32.so\"\n\nandroid.debug.double.x86_64 = \"bin/android/libgdmotionmatching.android.template_debug.double.x86_64.so\"\nandroid.release.double.x86_64 = \"bin/android/libgdmotionmatching.android.template_release.double.x86_64.so\"\nandroid.debug.double.x86_32 = \"bin/android/libgdmotionmatching.android.template_debug.double.x86_32.so\"\nandroid.release.double.x86_32 = \"bin/android/libgdmotionmatching.android.template_release.double.x86_32.so\"\nandroid.debug.double.arm64 = \"bin/android/libgdmotionmatching.android.template_debug.double.arm64.so\"\nandroid.release.double.arm64 = \"bin/android/libgdmotionmatching.android.template_release.double.arm64.so\"\nandroid.debug.double.arm32 = \"bin/android/libgdmotionmatching.android.template_debug.double.arm32.so\"\nandroid.release.double.arm32 = \"bin/android/libgdmotionmatching.android.template_release.double.arm32.so\"\n\nweb.debug.wasm32 = \"bin/web/libgdmotionmatching.web.template_debug.wasm32.wasm\"\nweb.release.wasm32 = \"bin/web/libgdmotionmatching.web.template_release.wasm32.wasm\"\nweb.debug.double.wasm32 = \"bin/web/libgdmotionmatching.web.template_debug.double.wasm32.wasm\"\nweb.release.double.wasm32 = \"bin/web/libgdmotionmatching.web.template_release.double.wasm32.wasm\"\n"
  },
  {
    "path": "config.py",
    "content": "def can_build(env, platform):\n    return True\n\n\ndef configure(env):\n    pass\n\n\ndef get_doc_classes():\n    return [\n        \"DampedSkeletonModifier\",\n        \"MMAnimationLibrary\",\n        \"MMBoneDataFeature\",\n        \"MMCharacter\",\n        \"MMClampSynchronizer\",\n        \"MMFeature\",\n        \"MMRootMotionSynchronizer\",\n        \"MMSynchronizer\",\n        \"MMTrajectoryFeature\",\n        \"MMAnimationNode\",\n        \"MMQueryInput\",\n    ]\n\n\ndef get_doc_path():\n    return \"doc_classes\"\n"
  },
  {
    "path": "doc_classes/DampedSkeletonModifier.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA [SkeletonModifier3D] that dampens the motion of a skeleton.\n\t</brief_description>\n\t<description>\n\t\tThis [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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"halflife\" type=\"float\" setter=\"set_halflife\" getter=\"get_halflife\" default=\"0.1\">\n\t\t\tThe 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.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMAnimationLibrary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tAn [AnimationLibrary] used for motion matching.\n\t</brief_description>\n\t<description>\n\t\tThis 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. \n\t\tThe pose database is generated by the Motion Matching editor tool, and must be up to date with the animations in this library.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"db_anim_index\" type=\"PackedInt32Array\" setter=\"set_db_anim_index\" getter=\"get_db_anim_index\" default=\"PackedInt32Array()\">\n\t\t\tMatches pose indices in the database to their corresponding animation in the library. Generated by the Motion Matching editor tool and should not be modified.\n\t\t</member>\n\t\t<member name=\"db_pose_offset\" type=\"PackedInt32Array\" setter=\"set_db_pose_offset\" getter=\"get_db_pose_offset\" default=\"PackedInt32Array()\">\n\t\t\tMatches 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.\n\t\t</member>\n\t\t<member name=\"db_time_index\" type=\"PackedFloat32Array\" setter=\"set_db_time_index\" getter=\"get_db_time_index\" default=\"PackedFloat32Array()\">\n\t\t\tMatches pose indices in the database to their corresponding time in the animation. Generated by the Motion Matching editor tool and should not be modified.\n\t\t</member>\n\t\t<member name=\"features\" type=\"MMFeature[]\" setter=\"set_features\" getter=\"get_features\" default=\"[]\">\n\t\t\tArray of features used to select animation poses in the database.\n\t\t\tEach 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.\n\t\t</member>\n\t\t<member name=\"include_cost_results\" type=\"bool\" setter=\"set_include_cost_results\" getter=\"get_include_cost_results\" default=\"false\">\n\t\t\tIf 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.\n\t\t</member>\n\t\t<member name=\"motion_data\" type=\"PackedFloat32Array\" setter=\"set_motion_data\" getter=\"get_motion_data\" default=\"PackedFloat32Array()\">\n\t\t\tPose 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.\n\t\t\tFeature values are stored in the same order as the features in the [features] array and may be normalized.\n\t\t</member>\n\t\t<member name=\"node_indices\" type=\"PackedInt32Array\" setter=\"set_node_indices\" getter=\"get_node_indices\" default=\"PackedInt32Array()\">\n\t\t\tThe 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.\n\t\t</member>\n\t\t<member name=\"sampling_rate\" type=\"float\" setter=\"set_sampling_rate\" getter=\"get_sampling_rate\" default=\"1.0\">\n\t\t\tThe 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.\n\t\t</member>\n\t\t<member name=\"schema_hash\" type=\"int\" setter=\"set_schema_hash\" getter=\"get_schema_hash\" default=\"0\">\n\t\t\tThe 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.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMAnimationNode.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tAn AnimationNode that uses motion matching to select the best animation to play based on the current state of the character.\n\t</brief_description>\n\t<description>\n\t\tThis 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].\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"blending_enabled\" type=\"bool\" setter=\"set_blending_enabled\" getter=\"get_blending_enabled\" default=\"true\">\n\t\t\tWhether to enable blending between animations. If enabled, the node will blend between the selected animation and the previous animation based on the transition halflife.\n\t\t</member>\n\t\t<member name=\"library\" type=\"StringName\" setter=\"set_library\" getter=\"get_library\" default=\"&amp;&quot;&quot;\">\n\t\t\tThe [MMAnimationLibrary] to use for motion matching. This library contains the animations and their corresponding motion data.\n\t\t</member>\n\t\t<member name=\"query_frequency\" type=\"float\" setter=\"set_query_frequency\" getter=\"get_query_frequency\" default=\"2.0\">\n\t\t\tThe 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.\n\t\t</member>\n\t\t<member name=\"transition_halflife\" type=\"float\" setter=\"set_transition_halflife\" getter=\"get_transition_halflife\" default=\"0.1\">\n\t\t\tThe halflife of the transition between animations. This value determines how quickly the node transitions between animations when a new animation is selected.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMBoneDataFeature.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA [MMFeature] that selects animation based on bone position and rotation.\n\t</brief_description>\n\t<description>\n\t\tA [MMFeature] that selects animation based on bone position and rotation.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"bone_names\" type=\"PackedStringArray\" setter=\"set_bone_names\" getter=\"get_bone_names\" default=\"PackedStringArray()\">\n\t\t\tNames of the skeleton bones to be used for this feature.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMCharacter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA [CharacterBody3D] that provides the necessary functionality to use motion matching.\n\t</brief_description>\n\t<description>\n\t\t[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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<methods>\n\t\t<method name=\"get_skeleton_state\" qualifiers=\"const\">\n\t\t\t<return type=\"Dictionary[]\" />\n\t\t\t<description>\n\t\t\t\tReturns 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.\n\t\t\t</description>\n\t\t</method>\n\t\t<method name=\"get_trajectory\" qualifiers=\"const\">\n\t\t\t<return type=\"Dictionary[]\" />\n\t\t\t<description>\n\t\t\t\tReturns 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.\n\t\t\t</description>\n\t\t</method>\n\t\t<method name=\"get_trajectory_history\" qualifiers=\"const\">\n\t\t\t<return type=\"Dictionary[]\" />\n\t\t\t<description>\n\t\t\t\tReturns 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.\n\t\t\t</description>\n\t\t</method>\n\t</methods>\n\t<members>\n\t\t<member name=\"animation_tree\" type=\"AnimationTree\" setter=\"set_animation_tree\" getter=\"get_animation_tree\">\n\t\t</member>\n\t\t<member name=\"check_environment\" type=\"bool\" setter=\"set_check_environment\" getter=\"get_check_environment\" default=\"true\">\n\t\t\tUsed to enable or disable environmental checks along the character's trajectory. When enabled, trajectory generation will also consider collisions and gravity.\n\t\t</member>\n\t\t<member name=\"emit_result_signal\" type=\"bool\" setter=\"set_emit_result_signal\" getter=\"get_emit_result_signal\" default=\"false\">\n\t\t\tWhen 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.\n\t\t</member>\n\t\t<member name=\"halflife\" type=\"float\" setter=\"set_halflife\" getter=\"get_halflife\" default=\"0.5\">\n\t\t\tDescribes 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.\n\t\t</member>\n\t\t<member name=\"history_delta_time\" type=\"float\" setter=\"set_history_delta_time\" getter=\"get_history_delta_time\" default=\"0.5\">\n\t\t\tTime between each point in the trajectory history.\n\t\t</member>\n\t\t<member name=\"history_point_count\" type=\"int\" setter=\"set_history_point_count\" getter=\"get_history_point_count\" default=\"3\">\n\t\t\tNumber of past points to store in the trajectory history.\n\t\t</member>\n\t\t<member name=\"is_strafing\" type=\"bool\" setter=\"set_is_strafing\" getter=\"get_is_strafing\" default=\"false\">\n\t\t\tWhen 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.\n\t\t\tWhen disabled, the character's facing direction will be calculated based on its velocity.\n\t\t</member>\n\t\t<member name=\"skeleton\" type=\"Skeleton3D\" setter=\"set_skeleton\" getter=\"get_skeleton\">\n\t\t\tThe [Skeleton3D] that will be used to animate the character.\n\t\t</member>\n\t\t<member name=\"strafe_facing\" type=\"float\" setter=\"set_strafe_facing\" getter=\"get_strafe_facing\" default=\"0.0\">\n\t\t\tWhen [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.\n\t\t</member>\n\t\t<member name=\"synchronizer\" type=\"MMSynchronizer\" setter=\"set_synchronizer\" getter=\"get_synchronizer\">\n\t\t\tThe [MMSynchronizer] that will be used to synchronize the character to with its skeleton.\n\t\t</member>\n\t\t<member name=\"target_velocity\" type=\"Vector3\" setter=\"set_target_velocity\" getter=\"get_target_velocity\" default=\"Vector3(0, 0, 0)\">\n\t\t\tThe character's target velocity. The character's velocity will be interpolated towards this value over time, controlled by [member halflife].\n\t\t</member>\n\t\t<member name=\"trajectory_delta_time\" type=\"float\" setter=\"set_trajectory_delta_time\" getter=\"get_trajectory_delta_time\" default=\"0.5\">\n\t\t\tTime between each point in the trajectory.\n\t\t</member>\n\t\t<member name=\"trajectory_point_count\" type=\"int\" setter=\"set_trajectory_point_count\" getter=\"get_trajectory_point_count\" default=\"10\">\n\t\t\tNumber of future points to generate in the trajectory.\n\t\t</member>\n\t</members>\n\t<signals>\n\t\t<signal name=\"on_query_result\">\n\t\t\t<param index=\"0\" name=\"data\" type=\"Dictionary\" />\n\t\t\t<description>\n\t\t\t\tEmitted when a query is made and [member emit_result_signal] is enabled. The signal will contain the result of the query.\n\t\t\t</description>\n\t\t</signal>\n\t</signals>\n</class>\n"
  },
  {
    "path": "doc_classes/MMClampSynchronizer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA [MMSynchronizer] that clamps the skeleton's distance to the character's position.\n\t</brief_description>\n\t<description>\n\t\tA [MMSynchronizer] that clamps the skeleton's distance to the character's position.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"clamp_distance\" type=\"float\" setter=\"set_clamp_distance\" getter=\"get_clamp_distance\" default=\"10.0\">\n\t\t\tThe 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.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMFeature.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tBase class for features used in motion matching.\n\t</brief_description>\n\t<description>\n\t\t[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.\n\t\t[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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<methods>\n\t\t<method name=\"get_dimension_count\" qualifiers=\"const\">\n\t\t\t<return type=\"int\" />\n\t\t\t<description>\n\t\t\t\tReturns the number of dimensions of the feature. This is the number of values that the feature will output per animation frame.\n\t\t\t</description>\n\t\t</method>\n\t</methods>\n\t<members>\n\t\t<member name=\"maxes\" type=\"PackedFloat32Array\" setter=\"set_maxes\" getter=\"get_maxes\" default=\"PackedFloat32Array()\">\n\t\t\tMaximum 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.\n\t\t</member>\n\t\t<member name=\"means\" type=\"PackedFloat32Array\" setter=\"set_means\" getter=\"get_means\" default=\"PackedFloat32Array()\">\n\t\t\tMean 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.\n\t\t</member>\n\t\t<member name=\"mins\" type=\"PackedFloat32Array\" setter=\"set_mins\" getter=\"get_mins\" default=\"PackedFloat32Array()\">\n\t\t\tMinimum 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.\n\t\t</member>\n\t\t<member name=\"normalization_mode\" type=\"int\" setter=\"set_normalization_mode\" getter=\"get_normalization_mode\" enum=\"MMFeature.NormalizationMode\" default=\"1\">\n\t\t\tThe normalization mode used for the feature. Can be one of [constant Raw], [constant Standard], or [constant MinMax].\n\t\t</member>\n\t\t<member name=\"std_devs\" type=\"PackedFloat32Array\" setter=\"set_std_devs\" getter=\"get_std_devs\" default=\"PackedFloat32Array()\">\n\t\t\tStandard 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.\n\t\t</member>\n\t\t<member name=\"weight\" type=\"float\" setter=\"set_weight\" getter=\"get_weight\" default=\"1.0\">\n\t\t\tThe 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.\n\t\t</member>\n\t</members>\n\t<constants>\n\t\t<constant name=\"Raw\" value=\"0\" enum=\"NormalizationMode\">\n\t\t\tNo normalization is applied to the feature. The feature's output will be used as is.\n\t\t</constant>\n\t\t<constant name=\"Standard\" value=\"1\" enum=\"NormalizationMode\">\n\t\t\tUses standard normalization for the feature.\n\t\t</constant>\n\t\t<constant name=\"MinMax\" value=\"2\" enum=\"NormalizationMode\">\n\t\t\tUses min-max normalization for the feature.\n\t\t</constant>\n\t</constants>\n</class>\n"
  },
  {
    "path": "doc_classes/MMMixSynchronizer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA [MMSynchronizer] that synchronizes the character and skeleton's position and facing direction as a blend between root motion and character motion.\n\t</brief_description>\n\t<description>\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<members>\n\t\t<member name=\"root_motion_amount\" type=\"float\" setter=\"set_root_motion_amount\" getter=\"get_root_motion_amount\" default=\"1.0\">\n\t\t\tThe 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.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "doc_classes/MMQueryInput.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tInput for the motion matching query.\n\t</brief_description>\n\t<description>\n\t\tThis 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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n</class>\n"
  },
  {
    "path": "doc_classes/MMRootMotionSynchronizer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tRoot motion synchronizer for motion matching.\n\t</brief_description>\n\t<description>\n\t\tA [MMSynchronizer] that synchronizes the character and its skeleton by using the root motion of the animation.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n</class>\n"
  },
  {
    "path": "doc_classes/MMSynchronizer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA class that synchronizes the a [MMCharacter] with its [Skeleton3D] node.\n\t</brief_description>\n\t<description>\n\t\tA [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.\n\t\tWhile 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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n</class>\n"
  },
  {
    "path": "doc_classes/MMTrajectoryFeature.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<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\">\n\t<brief_description>\n\t\tA feature describing the trajectory of a character.\n\t</brief_description>\n\t<description>\n\t\tThis 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.\n\t</description>\n\t<tutorials>\n\t</tutorials>\n\t<methods>\n\t\t<method name=\"get_trajectory_points\" qualifiers=\"const\">\n\t\t\t<return type=\"Dictionary[]\" />\n\t\t\t<param index=\"0\" name=\"character_transform\" type=\"Transform3D\" />\n\t\t\t<param index=\"1\" name=\"trajectory_data\" type=\"PackedFloat32Array\" />\n\t\t\t<description>\n\t\t\t\tGenerates 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.\n\t\t\t</description>\n\t\t</method>\n\t</methods>\n\t<members>\n\t\t<member name=\"facing_weight\" type=\"float\" setter=\"set_facing_weight\" getter=\"get_facing_weight\" default=\"1.0\">\n\t\t</member>\n\t\t<member name=\"future_delta_time\" type=\"float\" setter=\"set_future_delta_time\" getter=\"get_future_delta_time\" default=\"0.1\">\n\t\t\tTime in seconds between each trajectory point in the future.\n\t\t</member>\n\t\t<member name=\"future_frames\" type=\"int\" setter=\"set_future_frames\" getter=\"get_future_frames\" default=\"5\">\n\t\t\tNumber of trajectory points in the future.\n\t\t</member>\n\t\t<member name=\"height_weight\" type=\"float\" setter=\"set_height_weight\" getter=\"get_height_weight\" default=\"1.0\">\n\t\t</member>\n\t\t<member name=\"include_facing\" type=\"bool\" setter=\"set_include_facing\" getter=\"get_include_facing\" default=\"true\">\n\t\t\tWhether to include the facing direction in the trajectory points.\n\t\t</member>\n\t\t<member name=\"include_height\" type=\"bool\" setter=\"set_include_height\" getter=\"get_include_height\" default=\"false\">\n\t\t\tWhether to include the character's height in the trajectory points.\n\t\t</member>\n\t\t<member name=\"past_delta_time\" type=\"float\" setter=\"set_past_delta_time\" getter=\"get_past_delta_time\" default=\"0.1\">\n\t\t\tTime in seconds between each trajectory point in the past.\n\t\t</member>\n\t\t<member name=\"past_frames\" type=\"int\" setter=\"set_past_frames\" getter=\"get_past_frames\" default=\"1\">\n\t\t\tNumber of trajectory points in the past.\n\t\t</member>\n\t</members>\n</class>\n"
  },
  {
    "path": "methods.py",
    "content": "import os\nimport sys\nfrom enum import Enum\n\n# Colors are disabled in non-TTY environments such as pipes. This means\n# that if output is redirected to a file, it won't contain color codes.\n# Colors are always enabled on continuous integration.\n_colorize = bool(sys.stdout.isatty() or os.environ.get(\"CI\"))\n\n\nclass ANSI(Enum):\n    \"\"\"\n    Enum class for adding ansi colorcodes directly into strings.\n    Automatically converts values to strings representing their\n    internal value, or an empty string in a non-colorized scope.\n    \"\"\"\n\n    RESET = \"\\x1b[0m\"\n\n    BOLD = \"\\x1b[1m\"\n    ITALIC = \"\\x1b[3m\"\n    UNDERLINE = \"\\x1b[4m\"\n    STRIKETHROUGH = \"\\x1b[9m\"\n    REGULAR = \"\\x1b[22;23;24;29m\"\n\n    BLACK = \"\\x1b[30m\"\n    RED = \"\\x1b[31m\"\n    GREEN = \"\\x1b[32m\"\n    YELLOW = \"\\x1b[33m\"\n    BLUE = \"\\x1b[34m\"\n    MAGENTA = \"\\x1b[35m\"\n    CYAN = \"\\x1b[36m\"\n    WHITE = \"\\x1b[37m\"\n\n    PURPLE = \"\\x1b[38;5;93m\"\n    PINK = \"\\x1b[38;5;206m\"\n    ORANGE = \"\\x1b[38;5;214m\"\n    GRAY = \"\\x1b[38;5;244m\"\n\n    def __str__(self) -> str:\n        global _colorize\n        return str(self.value) if _colorize else \"\"\n\n\ndef print_warning(*values: object) -> None:\n    \"\"\"Prints a warning message with formatting.\"\"\"\n    print(f\"{ANSI.YELLOW}{ANSI.BOLD}WARNING:{ANSI.REGULAR}\", *values, ANSI.RESET, file=sys.stderr)\n\n\ndef print_error(*values: object) -> None:\n    \"\"\"Prints an error message with formatting.\"\"\"\n    print(f\"{ANSI.RED}{ANSI.BOLD}ERROR:{ANSI.REGULAR}\", *values, ANSI.RESET, file=sys.stderr)\n"
  },
  {
    "path": "src/algo/kd_tree.cpp",
    "content": "#include \"kd_tree.h\"\n\n#include <algorithm>\n#include <limits>\n#include <numeric>\n\nKDTree::KDTree(const float* data, int point_dimension, int point_count)\n    : _point_dim(point_dimension),\n      _root(nullptr) {\n\n    _build_tree(data, point_dimension, point_count);\n}\n\nKDTree::KDTree(int point_count)\n    : _point_dim(point_count),\n      _root(nullptr) {\n}\n\nKDTree::~KDTree() {\n    _clear_tree();\n}\n\nint KDTree::search_nn(\n    const float* pose_data,\n    const float* query,\n    const std::vector<float>& dimension_weigths) const {\n    float best_distance_squared = std::numeric_limits<float>::max();\n    int best_index = -1;\n    _search_nn_recursive(pose_data, query, dimension_weigths, _root, best_distance_squared, best_index, 0);\n    return best_index;\n}\n\nstd::vector<int> KDTree::search_knn(const float* pose_data,\n                                    const float* query,\n                                    const std::vector<float>& dimension_weigths,\n                                    int k) const {\n    // TODO\n    return std::vector<int>();\n}\n\nPackedInt32Array KDTree::get_node_indices() const {\n    PackedInt32Array indices;\n    _get_node_indices_recursive(_root, indices);\n    return std::move(indices);\n}\n\nvoid KDTree::rebuild_tree(int point_count, const PackedInt32Array& indices) {\n    _clear_tree();\n\n    if (_root) {\n        delete _root;\n    }\n\n    int value_index = 0;\n    _rebuild_tree_recursive(_root, indices, value_index, 0);\n}\n\nvoid KDTree::_build_tree(const float* data, int point_dimension, int point_count) {\n\n    std::vector<int> indices(point_count);\n    std::iota(std::begin(indices), std::end(indices), 0);\n    _root = _build_tree_recursive(data, indices.data(), point_count, 0);\n}\n\nKDTree::TreeNode* KDTree::_build_tree_recursive(const float* data, int* indices, int npoints, int current_depth) {\n\n    if (npoints <= 0) {\n        return nullptr;\n    }\n\n    KDTree::TreeNode* node = new KDTree::TreeNode();\n\n    const int axis = current_depth % _point_dim;\n    node->axis = axis;\n\n    int mid_index = (npoints - 1) / 2;\n    std::nth_element(\n        indices,\n        indices + mid_index,\n        indices + npoints,\n        [this, &data, &axis](int lhs, int rhs) {\n            return data[lhs * _point_dim + axis] < data[rhs * _point_dim + axis];\n        });\n    data[indices[mid_index] * _point_dim + axis];\n    node->index = indices[mid_index];\n    node->children[0] = _build_tree_recursive(data, indices, mid_index, current_depth + 1);\n    node->children[1] = _build_tree_recursive(data, indices + mid_index + 1, npoints - mid_index - 1, current_depth + 1);\n\n    return node;\n}\n\nvoid KDTree::_clear_tree() {\n    _clear_tree_recursive(_root);\n}\n\nvoid KDTree::_clear_tree_recursive(TreeNode* node) {\n    if (!node) {\n        return;\n    }\n\n    _clear_tree_recursive(node->children[0]);\n    _clear_tree_recursive(node->children[1]);\n\n    delete node;\n}\n\nvoid KDTree::_search_nn_recursive(\n    const float* pose_data,\n    const float* query,\n    const std::vector<float>& dimension_weigths,\n    const TreeNode* node,\n    float& best_distance_squared,\n    int& best_index,\n    int depth) const {\n\n    if (!node) {\n        return;\n    }\n\n    float dist_squared = 0.0f;\n    for (int i = 0; i < _point_dim; i++) {\n        float diff = pose_data[node->index * _point_dim + i] - query[i];\n        dist_squared += diff * diff * dimension_weigths[i];\n    }\n\n    if (dist_squared < best_distance_squared) {\n        best_distance_squared = dist_squared;\n        best_index = node->index;\n    }\n    const int axis = depth % _point_dim;\n    const float diff = query[axis] - pose_data[node->index * _point_dim + axis];\n    const TreeNode* near_child = diff < 0 ? node->children[0] : node->children[1];\n    const TreeNode* far_child = diff < 0 ? node->children[1] : node->children[0];\n\n    _search_nn_recursive(\n        pose_data,\n        query,\n        dimension_weigths,\n        near_child,\n        best_distance_squared,\n        best_index,\n        depth + 1);\n\n    if (diff * diff * dimension_weigths[axis] < best_distance_squared) {\n        _search_nn_recursive(\n            pose_data,\n            query,\n            dimension_weigths,\n            far_child,\n            best_distance_squared,\n            best_index,\n            depth + 1);\n    }\n}\n\nvoid KDTree::_get_node_indices_recursive(const TreeNode* node, PackedInt32Array& indices) const {\n\n    if (!node) {\n        indices.push_back(-1);\n        return;\n    }\n\n    indices.push_back(node->index);\n    _get_node_indices_recursive(node->children[0], indices);\n    _get_node_indices_recursive(node->children[1], indices);\n}\n\nvoid KDTree::_rebuild_tree_recursive(TreeNode*& node, const PackedInt32Array& indices, int& value_index, int current_depth) {\n    if (value_index >= indices.size() || indices[value_index] == -1) {\n        value_index++;\n        return;\n    }\n\n    node = new TreeNode();\n    node->children[0] = nullptr;\n    node->children[1] = nullptr;\n    node->index = indices[value_index];\n    node->axis = current_depth % _point_dim;\n\n    value_index++;\n    _rebuild_tree_recursive(node->children[0], indices, value_index, current_depth + 1);\n    _rebuild_tree_recursive(node->children[1], indices, value_index, current_depth + 1);\n}\n"
  },
  {
    "path": "src/algo/kd_tree.h",
    "content": "#pragma once\n\n#include \"godot_cpp/variant/packed_float32_array.hpp\"\n#include \"godot_cpp/variant/packed_int32_array.hpp\"\n\n#include <functional>\n#include <vector>\n\nusing namespace godot;\n\nclass KDTree {\n\npublic:\n    KDTree(const float* data, int point_dimension, int point_count);\n    KDTree(int point_count);\n    ~KDTree();\n\n    int search_nn(const float* pose_data, const float* query, const std::vector<float>& dimension_weigths) const;\n    std::vector<int> search_knn(const float* pose_data,\n                                const float* query,\n                                const std::vector<float>& dimension_weigths,\n                                int k) const;\n\n    PackedInt32Array get_node_indices() const;\n\n    void rebuild_tree(int point_count, const PackedInt32Array& indices);\n\nprivate:\n    struct TreeNode {\n\n        int axis;\n        TreeNode* children[2];\n        int index;\n    };\n\n    void _build_tree(const float* data, int point_dimension, int point_count);\n    TreeNode* _build_tree_recursive(const float* data, int* indices, int npoints, int current_depth);\n\n    void _clear_tree();\n    void _clear_tree_recursive(TreeNode* node);\n\n    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;\n\n    void _get_node_indices_recursive(const TreeNode* node, PackedInt32Array& indices) const;\n\n    void _rebuild_tree_recursive(TreeNode*& node, const PackedInt32Array& indices, int& value_index, int current_depth);\n    const int _point_dim = 0;\n    TreeNode* _root = nullptr;\n};"
  },
  {
    "path": "src/circular_buffer.h",
    "content": "#ifndef CIRCULAR_BUFFER_H\n#define CIRCULAR_BUFFER_H\n\n#include <cstddef>\n#include <deque>\n#include <vector>\n\ntemplate <typename T>\nclass CircularBuffer {\n\nprivate:\n    std::deque<T> buffer;\n    size_t max_size;\n\npublic:\n    explicit CircularBuffer(size_t size)\n        : max_size(size) {\n    }\n\n    void clear() {\n        buffer.clear();\n    }\n\n    void push(T item) {\n        if (buffer.size() == max_size) {\n            buffer.pop_front();\n        }\n        buffer.push_back(item);\n    }\n\n    T pop() {\n        T val = buffer.front();\n        buffer.pop_front();\n        return val;\n    }\n\n    bool empty() const {\n        return buffer.empty();\n    }\n\n    bool is_full() const {\n        return buffer.size() == max_size;\n    }\n\n    bool is_empty() const {\n        return buffer.empty();\n    }\n\n    size_t capacity() const {\n        return max_size;\n    }\n\n    size_t size() const {\n        return buffer.size();\n    }\n\n    void resize(size_t size) {\n        max_size = size;\n        while (buffer.size() > max_size) {\n            buffer.pop_front();\n        }\n    }\n\n    T& operator[](size_t index) {\n        return buffer[index];\n    }\n\n    const T& operator[](size_t index) const {\n        return buffer[index];\n    }\n\n    std::vector<T> to_vector() const {\n        return std::vector<T>(buffer.begin(), buffer.end());\n    }\n};\n#endif // CIRCULAR_BUFFER_H\n"
  },
  {
    "path": "src/common.h",
    "content": "#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#define GETSET(type, variable, ...)   \\\n    type variable{__VA_ARGS__};       \\\n    type get_##variable() const {     \\\n        return variable;              \\\n    }                                 \\\n    void set_##variable(type value) { \\\n        variable = value;             \\\n    }\n\n#define STR(x) #x\n\n#define STRING_PREFIX(prefix, s) STR(prefix##s)\n\n#define BINDER_PROPERTY_PARAMS(type, variant_type, variable, ...)                                  \\\n    ClassDB::bind_method(D_METHOD(STRING_PREFIX(set_, variable), \"value\"), &type::set_##variable); \\\n    ClassDB::bind_method(D_METHOD(STRING_PREFIX(get_, variable)), &type::get_##variable);          \\\n    ClassDB::add_property(get_class_static(), PropertyInfo(variant_type, #variable, ##__VA_ARGS__), STRING_PREFIX(set_, variable), STRING_PREFIX(get_, variable));\n\n#define SMALL_NUMBER 1.e-8\n#define KINDA_SMALL_NUMBER 1.e-4\n\n#if defined(DEBUG_ENABLED)\n#define DEBUG_PROPERTY_STORAGE_FLAG godot::PropertyUsageFlags::PROPERTY_USAGE_DEFAULT\n#else\n#define DEBUG_PROPERTY_STORAGE_FLAG godot::PropertyUsageFlags::PROPERTY_USAGE_STORAGE\n#endif\n\n#endif // COMMON_H\n"
  },
  {
    "path": "src/editor/animation_post_import_plugin.cpp",
    "content": "#include \"animation_post_import_plugin.h\"\n\n#include \"godot_cpp/classes/animation_player.hpp\"\n#include \"godot_cpp/classes/file_access.hpp\"\n#include \"godot_cpp/classes/node.hpp\"\n\nVariant AnimationPostImportPlugin::_get_option_visibility(const String& p_path, bool p_for_animation, const String& p_option) const {\n    if (p_option == \"export/animation_export_path\") {\n        return p_for_animation;\n    }\n    return EditorScenePostImportPlugin::_get_option_visibility(p_path, p_for_animation, p_option);\n}\n\nvoid AnimationPostImportPlugin::_get_import_options(const String& p_path) {\n    add_import_option_advanced(Variant::STRING, \"export/animation_export_path\", \"\", PROPERTY_HINT_DIR, \"\");\n}\n\nvoid AnimationPostImportPlugin::_pre_process(Node* p_scene) {\n\n    const String export_path = get_option_value(\"export/animation_export_path\");\n\n    if (export_path.is_empty()) {\n        return;\n    }\n\n    Dictionary animations;\n    _export_animations(p_scene, animations, export_path);\n\n    Dictionary subresources = get_option_value(\"_subresources\");\n    subresources[\"animations\"] = animations;\n}\n\nvoid AnimationPostImportPlugin::_bind_methods() {\n}\n\nvoid AnimationPostImportPlugin::_export_animations(Node* p_node, Dictionary& p_animations, const String& p_export_path) {\n    AnimationPlayer* anim_node = Object::cast_to<AnimationPlayer>(p_node);\n\n    if (anim_node) {\n        PackedStringArray anim_list = anim_node->get_animation_list();\n\n        for (int32_t i = 0; i < anim_list.size(); i++) {\n            StringName anim_name = anim_list[i];\n\n            Dictionary animation;\n            animation[\"save_to_file/enabled\"] = true;\n            animation[\"save_to_file/keep_custom_tracks\"] = \"\";\n\n            String clean_anim_name = anim_name.validate_filename();\n            String file_path = p_export_path.path_join(clean_anim_name) + \".res\";\n            int idx = 1;\n            while (FileAccess::file_exists(file_path)) {\n                file_path = p_export_path.path_join(clean_anim_name + String::num_int64(idx)) + \".res\";\n                idx++;\n            }\n\n            animation[\"save_to_file/path\"] = file_path;\n\n            p_animations[anim_name] = animation;\n        }\n    }\n\n    for (int32_t i = 0; i < p_node->get_child_count(); i++) {\n        _export_animations(p_node->get_child(i), p_animations, p_export_path);\n    }\n}\n"
  },
  {
    "path": "src/editor/animation_post_import_plugin.h",
    "content": "#pragma once\n\n#include <godot_cpp/classes/editor_scene_post_import_plugin.hpp>\n\nusing namespace godot;\n\nclass AnimationPostImportPlugin : public EditorScenePostImportPlugin {\n    GDCLASS(AnimationPostImportPlugin, EditorScenePostImportPlugin)\n\npublic:\n    virtual Variant _get_option_visibility(const String& p_path, bool p_for_animation, const String& p_option) const override;\n\n    virtual void _get_import_options(const String& p_path) override;\n    virtual void _pre_process(Node* p_scene) override;\n\nprotected:\n    static void _bind_methods();\n\nprivate:\n    void _export_animations(Node* p_node, Dictionary& p_animations, const String& p_export_path);\n};\n"
  },
  {
    "path": "src/editor/animation_tree_handler_plugin.cpp",
    "content": "#include \"animation_tree_handler_plugin.h\"\n\nAnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::_singleton = nullptr;\n\nAnimationTreeHandlerPlugin::AnimationTreeHandlerPlugin() {\n    _singleton = this;\n    _animation_tree = nullptr;\n}\n\nbool AnimationTreeHandlerPlugin::_handles(Object* p_node) const {\n    return Object::cast_to<AnimationTree>(p_node) != nullptr;\n}\n\nvoid AnimationTreeHandlerPlugin::_edit(Object* p_object) {\n    _animation_tree = Object::cast_to<AnimationTree>(p_object);\n}\n\nAnimationTreeHandlerPlugin* AnimationTreeHandlerPlugin::get_singleton() {\n    return _singleton;\n}\n\nvoid AnimationTreeHandlerPlugin::_bind_methods() {\n}"
  },
  {
    "path": "src/editor/animation_tree_handler_plugin.h",
    "content": "#ifndef ANIMATION_TREE_HANDLER_PLUGIN_H\n#define ANIMATION_TREE_HANDLER_PLUGIN_H\n\n#include <godot_cpp/classes/animation_tree.hpp>\n#include <godot_cpp/classes/editor_plugin.hpp>\n\nusing namespace godot;\n\n// The only reason this class exists is to provide a way for MMAnimationNode\n// to access the AnimationTree in editor.\n// If a new and better way of doing this comes along, please remove this class!\nclass AnimationTreeHandlerPlugin : public EditorPlugin {\n    GDCLASS(AnimationTreeHandlerPlugin, EditorPlugin)\n\npublic:\n    AnimationTreeHandlerPlugin();\n    virtual ~AnimationTreeHandlerPlugin() = default;\n\n    virtual bool _handles(Object* p_node) const override;\n    virtual void _edit(Object* p_object) override;\n\n    static AnimationTreeHandlerPlugin* get_singleton();\n    AnimationTree* get_animation_tree() const {\n        return _animation_tree;\n    }\n\nprotected:\n    static void _bind_methods();\n\n    static AnimationTreeHandlerPlugin* _singleton;\n\nprivate:\n    AnimationTree* _animation_tree;\n};\n\n#endif // ANIMATION_TREE_HANDLER_PLUGIN_H"
  },
  {
    "path": "src/editor/mm_data_tab.cpp",
    "content": "#include \"mm_data_tab.h\"\n\n#include <godot_cpp/classes/h_box_container.hpp>\n#include <godot_cpp/classes/label.hpp>\n#include <godot_cpp/classes/scroll_container.hpp>\n#include <godot_cpp/classes/v_box_container.hpp>\n\nvoid MMDataTab::set_animation_library(Ref<MMAnimationLibrary> p_library) {\n    if (p_library.is_null()) {\n        return;\n    }\n\n    _clear_data();\n\n    if (p_library->needs_baking()) {\n        return;\n    }\n\n    Label* min_label = memnew(Label);\n    min_label->set_text(\"Min\");\n    _stats_data_container->add_child(min_label);\n\n    Label* max_label = memnew(Label);\n    max_label->set_text(\"Max\");\n    _stats_data_container->add_child(max_label);\n\n    Label* avg_label = memnew(Label);\n    avg_label->set_text(\"Avg\");\n    _stats_data_container->add_child(avg_label);\n\n    Label* std_label = memnew(Label);\n    std_label->set_text(\"Std\");\n    _stats_data_container->add_child(std_label);\n\n    TypedArray<MMFeature> features = p_library->get_features();\n    for (int i = 0; i < features.size(); i++) {\n\n        Ref<MMFeature> feature = features[i];\n        if (feature.is_null()) {\n            return;\n        }\n\n        for (int i = 0; i < feature->get_dimension_count(); i++) {\n            Label* min_value = memnew(Label);\n            min_value->set_text(String::num(feature->get_mins()[i]));\n            _stats_data_container->add_child(min_value);\n\n            Label* max_value = memnew(Label);\n            max_value->set_text(String::num(feature->get_maxes()[i]));\n            _stats_data_container->add_child(max_value);\n\n            Label* avg_value = memnew(Label);\n            avg_value->set_text(String::num(feature->get_means()[i]));\n            _stats_data_container->add_child(avg_value);\n\n            Label* std_value = memnew(Label);\n            std_value->set_text(String::num(feature->get_std_devs()[i]));\n            _stats_data_container->add_child(std_value);\n        }\n    }\n}\n\nvoid MMDataTab::clear() {\n    _clear_data();\n}\n\nvoid MMDataTab::_bind_methods() {\n}\n\nvoid MMDataTab::_notification(int p_notification) {\n    switch (p_notification) {\n    case NOTIFICATION_ENTER_TREE: {\n        set_name(\"Data\");\n\n        ScrollContainer* scroll_container = memnew(ScrollContainer);\n        scroll_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        add_child(scroll_container);\n\n        VBoxContainer* main_container = memnew(VBoxContainer);\n        scroll_container->add_child(main_container);\n\n        _stats_data_container = memnew(GridContainer);\n        _stats_data_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _stats_data_container->set_columns(4); // min, max, avg, std\n        main_container->add_child(_stats_data_container);\n\n        _motion_data_container = memnew(GridContainer);\n        _motion_data_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        main_container->add_child(_motion_data_container);\n\n    } break;\n    }\n}\n\nvoid MMDataTab::_clear_data() {\n    TypedArray<Node> stat_cells = _stats_data_container->get_children();\n    for (int i = 0; i < stat_cells.size(); i++) {\n        Node* cell = Object::cast_to<Node>(stat_cells[i]);\n        cell->queue_free();\n    }\n}\n"
  },
  {
    "path": "src/editor/mm_data_tab.h",
    "content": "#pragma once\n\n#include \"mm_animation_library.h\"\n\n#include <godot_cpp/classes/grid_container.hpp>\n#include <godot_cpp/classes/option_button.hpp>\n#include <godot_cpp/classes/tab_bar.hpp>\n\nusing namespace godot;\n\nclass MMDataTab : public TabBar {\n    GDCLASS(MMDataTab, TabBar)\n\npublic:\n    void set_animation_library(Ref<MMAnimationLibrary> p_library);\n    void clear();\n\nprotected:\n    static void _bind_methods();\n    void _notification(int p_notification);\n\nprivate:\n    void _clear_data();\n\n    // Data\n    GridContainer* _motion_data_container;\n    GridContainer* _stats_data_container;\n};"
  },
  {
    "path": "src/editor/mm_editor.cpp",
    "content": "#include \"editor/mm_editor.h\"\n\n#include \"mm_animation_library.h\"\n#include \"mm_character.h\"\n#include \"mm_editor.h\"\n\n#include <godot_cpp/classes/h_box_container.hpp>\n#include <godot_cpp/classes/h_split_container.hpp>\n#include <godot_cpp/classes/resource_saver.hpp>\n\nvoid MMEditor::_bind_methods() {\n    ADD_SIGNAL(MethodInfo(\"animation_visualization_requested\", PropertyInfo(Variant::STRING, \"animation_lib_name\"), PropertyInfo(Variant::STRING, \"animation_name\"), PropertyInfo(Variant::INT, \"pose_index\")));\n}\n\nvoid MMEditor::_notification(int p_notification) {\n    switch (p_notification) {\n    case NOTIFICATION_ENTER_TREE: {\n        HSplitContainer* main_container = memnew(HSplitContainer);\n        main_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        add_child(main_container);\n\n        VBoxContainer* left_vbox = memnew(VBoxContainer);\n        left_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        left_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);\n\n        _library_selector = memnew(OptionButton);\n        _library_selector->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _library_selector->connect(\"item_selected\", callable_mp(this, &MMEditor::_anim_lib_selected));\n        left_vbox->add_child(_library_selector);\n\n        _bake_button = memnew(Button);\n        _bake_button->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _bake_button->connect(\"pressed\", callable_mp(this, &MMEditor::_bake_button_pressed));\n        _bake_button->set_text(\"Bake\");\n        // Make a bake all button\n\n        left_vbox->add_child(_bake_button);\n        main_container->add_child(left_vbox);\n\n        TabContainer* tab_container = memnew(TabContainer);\n        tab_container->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        tab_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);\n\n        _visualization_tab = memnew(MMVisualizationTab);\n        _visualization_tab->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _visualization_tab->connect(\"animation_visualization_requested\", callable_mp(this, &MMEditor::_emit_animation_viz_request));\n        tab_container->add_child(_visualization_tab);\n\n        _data_tab = memnew(MMDataTab);\n        _data_tab->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        tab_container->add_child(_data_tab);\n\n        main_container->add_child(tab_container);\n    } break;\n    }\n}\n\nvoid MMEditor::_bake_all_animation_libraries(const MMCharacter* p_character, const AnimationMixer* p_mixer, const Skeleton3D* p_skeleton) {\n\n    TypedArray<StringName> animation_libraries = p_mixer->get_animation_library_list();\n    for (int64_t i = 0; i < animation_libraries.size(); i++) {\n        const StringName& anim_lib_name = animation_libraries[i];\n        Ref<MMAnimationLibrary> anim_lib = p_mixer->get_animation_library(anim_lib_name);\n\n        if (anim_lib.is_null()) {\n            continue;\n        }\n\n        anim_lib->bake_data(p_character, p_mixer, p_skeleton);\n        ResourceSaver* resource_saver = ResourceSaver::get_singleton();\n        for (int64_t i = 0; i < anim_lib->features.size(); i++) {\n            resource_saver->save(anim_lib->features[i]);\n        }\n        resource_saver->save(anim_lib);\n    }\n}\n\nvoid MMEditor::_refresh(bool character_changed) {\n    if (!_current_controller) {\n        return;\n    }\n\n    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();\n\n    if (!animation_mixer) {\n        return;\n    }\n\n    TypedArray<StringName> animation_library_list = animation_mixer->get_animation_library_list();\n    _library_selector->clear();\n    for (int64_t i = 0; i < animation_library_list.size(); i++) {\n        Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_library_list[i]);\n\n        if (anim_lib.is_null()) {\n            continue;\n        }\n\n        _library_selector->add_item(animation_library_list[i]);\n    }\n    if (animation_library_list.is_empty()) {\n        _library_selector->select(-1);\n    } else if (character_changed) {\n        _library_selector->select(0);\n    }\n\n    _visualization_tab->clear();\n    _data_tab->clear();\n\n    const int32_t selected_index = _library_selector->get_selected();\n    if (selected_index != -1) {\n        _anim_lib_selected(selected_index);\n    }\n}\n\nvoid MMEditor::_bake_button_pressed() {\n    if (!_current_controller) {\n        return;\n    }\n\n    Skeleton3D* skeleton = _current_controller->get_skeleton();\n\n    if (!skeleton) {\n        return;\n    }\n\n    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();\n\n    if (!animation_mixer) {\n        return;\n    }\n\n    String selected_lib = _library_selector->get_text();\n    if (selected_lib.is_empty()) {\n        return;\n    }\n\n    Ref<MMAnimationLibrary> mm_lib = animation_mixer->get_animation_library(selected_lib); //\n\n    if (mm_lib.is_null()) {\n        return;\n    }\n\n    mm_lib->bake_data(_current_controller, animation_mixer, skeleton);\n    ResourceSaver::get_singleton()->save(mm_lib);\n\n    _visualization_tab->refresh();\n    _data_tab->set_animation_library(mm_lib);\n}\n\nvoid MMEditor::_anim_lib_selected(int p_index) {\n    if (p_index == -1) {\n        return;\n    }\n\n    if (!_current_controller) {\n        return;\n    }\n\n    AnimationMixer* animation_mixer = _current_controller->get_animation_mixer();\n\n    if (!animation_mixer) {\n        return;\n    }\n\n    // We only want the animation libraries that are MMAnimationLibrary\n    TypedArray<StringName> mm_animation_library_list;\n    PackedStringArray animation_library_list = animation_mixer->get_animation_library_list();\n    for (int i = 0; i < animation_library_list.size(); i++) {\n        Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_library_list[i]);\n\n        if (anim_lib.is_null()) {\n            continue;\n        }\n\n        mm_animation_library_list.push_back(animation_library_list[i]);\n    }\n\n    if (p_index >= mm_animation_library_list.size()) {\n        return;\n    }\n\n    String animation_lib_name = mm_animation_library_list[p_index];\n    Ref<MMAnimationLibrary> anim_lib = animation_mixer->get_animation_library(animation_lib_name);\n    if (anim_lib.is_null()) {\n        return;\n    }\n\n    _visualization_tab->set_animation_library(anim_lib);\n    _data_tab->set_animation_library(anim_lib);\n}\n\nvoid MMEditor::_emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {\n    emit_signal(\"animation_visualization_requested\", p_animation_lib, p_animation_name, p_pose_index);\n    _current_controller->update_gizmos();\n}\n"
  },
  {
    "path": "src/editor/mm_editor.h",
    "content": "#pragma once\n\n#include \"mm_data_tab.h\"\n#include \"mm_visualization_tab.h\"\n\n#include <godot_cpp/classes/animation_mixer.hpp>\n#include <godot_cpp/classes/button.hpp>\n#include <godot_cpp/classes/control.hpp>\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/classes/tab_container.hpp>\n#include <godot_cpp/variant/string.hpp>\n\nusing namespace godot;\n\nclass MMAnimationLibrary;\nclass MMCharacter;\n\nclass MMEditor : public Control {\n    GDCLASS(MMEditor, Control)\n\npublic:\n    void set_character(MMCharacter* p_controller) {\n        const bool changed = p_controller != _current_controller;\n\n        _current_controller = p_controller;\n        _refresh(changed);\n    }\n\nprotected:\n    static void _bind_methods();\n    void _notification(int p_notification);\n\nprivate:\n    static void _bake_all_animation_libraries(const MMCharacter* p_character, const AnimationMixer* p_mixer, const Skeleton3D* p_skeleton);\n    void _refresh(bool character_changed);\n    void _bake_button_pressed();\n    void _anim_lib_selected(int p_index);\n\n    void _emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index);\n\n    MMCharacter* _current_controller;\n    String _current_animation_library_name;\n    String _current_animation_name;\n\n    OptionButton* _library_selector;\n    Button* _bake_button;\n\n    MMVisualizationTab* _visualization_tab;\n    MMDataTab* _data_tab;\n};\n"
  },
  {
    "path": "src/editor/mm_editor_gizmo_plugin.cpp",
    "content": "#include \"editor/mm_editor_gizmo_plugin.h\"\n\n#include <godot_cpp/classes/animation_mixer.hpp>\n#include <godot_cpp/classes/editor_node3d_gizmo.hpp>\n#include <godot_cpp/variant/utility_functions.hpp>\n\n#include \"mm_animation_library.h\"\n#include \"mm_character.h\"\n#include \"mm_editor_gizmo_plugin.h\"\n\nMMEditorGizmoPlugin::MMEditorGizmoPlugin() {\n    create_material(\"trajectory_material\", Color(1, 0, 0, 1), false, true);\n    create_material(\"trajectory_history_material\", Color(0, 1, 0, 1), false, true);\n    create_material(\"bone_material\", Color(0, 0, 1, 1), false, true);\n}\n\nbool MMEditorGizmoPlugin::_has_gizmo(Node3D* p_for_node_3d) const {\n    return p_for_node_3d->is_class(\"MMCharacter\");\n}\n\nString MMEditorGizmoPlugin::_get_gizmo_name() const {\n    return \"MMEditorGizmo\";\n}\n\nvoid MMEditorGizmoPlugin::_redraw(const Ref<EditorNode3DGizmo>& p_gizmo) {\n    p_gizmo->clear();\n\n    MMCharacter* controller = Object::cast_to<MMCharacter>(p_gizmo->get_node_3d());\n    if (!controller) {\n        return;\n    }\n\n    AnimationMixer* animation_mixer = controller->get_animation_mixer();\n    if (!animation_mixer) {\n        return;\n    }\n\n    if (!animation_mixer->has_animation_library(_animation_lib)) {\n        return;\n    }\n\n    Ref<MMAnimationLibrary> animation_library = animation_mixer->get_animation_library(_animation_lib);\n    if (animation_library.is_null()) {\n        return;\n    }\n\n    animation_library->display_data(\n        p_gizmo,\n        controller->get_global_transform(),\n        _animation_name,\n        _pose_index);\n}\n\nvoid MMEditorGizmoPlugin::on_anim_viz_requested(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {\n    _animation_lib = p_animation_lib;\n    _animation_name = p_animation_name;\n    _pose_index = p_pose_index;\n}\n\nvoid MMEditorGizmoPlugin::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"on_anim_viz_requested\", \"animation_lib\", \"animation_name\", \"pose_index\"), &MMEditorGizmoPlugin::on_anim_viz_requested);\n}\n"
  },
  {
    "path": "src/editor/mm_editor_gizmo_plugin.h",
    "content": "#ifndef MM_EDITOR_GIZMO_PLUGIN_H\n#define MM_EDITOR_GIZMO_PLUGIN_H\n\n#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>\n#include <godot_cpp/classes/node3d.hpp>\n\nusing namespace godot;\n\nclass MMEditorGizmoPlugin : public EditorNode3DGizmoPlugin {\n    GDCLASS(MMEditorGizmoPlugin, EditorNode3DGizmoPlugin)\n\npublic:\n    MMEditorGizmoPlugin();\n\n    virtual bool _has_gizmo(Node3D* p_for_node_3d) const override;\n    virtual String _get_gizmo_name() const override;\n    virtual void _redraw(const Ref<EditorNode3DGizmo>& p_gizmo) override;\n    virtual int32_t _get_priority() const override {\n        return -1;\n    }\n\n    void on_anim_viz_requested(String p_animation_lib, String p_animation_name, int32_t p_pose_index);\n\nprotected:\n    static void _bind_methods();\n\nprivate:\n    String _animation_lib;\n    String _animation_name;\n    int32_t _pose_index;\n};\n#endif // MM_EDITOR_GIZMO_PLUGIN_H\n"
  },
  {
    "path": "src/editor/mm_editor_plugin.cpp",
    "content": "\n#include \"editor/mm_editor_plugin.h\"\n\n#include <godot_cpp/variant/utility_functions.hpp>\n\n#include <godot_cpp/classes/button.hpp>\n#include <godot_cpp/classes/tab_bar.hpp>\n#include <godot_cpp/classes/tab_container.hpp>\n#include <godot_cpp/classes/v_box_container.hpp>\n\nMMEditorPlugin::MMEditorPlugin() {\n    _editor = memnew(MMEditor);\n\n    _gizmo_plugin.instantiate();\n    add_node_3d_gizmo_plugin(_gizmo_plugin);\n\n    _post_import_plugin.instantiate();\n    add_scene_post_import_plugin(_post_import_plugin);\n\n    _bottom_panel_button = add_control_to_bottom_panel(_editor, \"MMEditor\");\n    _editor->connect(\"animation_visualization_requested\", callable_mp(_gizmo_plugin.ptr(), &MMEditorGizmoPlugin::on_anim_viz_requested));\n    _make_visible(false);\n}\n\nMMEditorPlugin::~MMEditorPlugin() {\n    remove_control_from_bottom_panel(_bottom_panel_button);\n    remove_node_3d_gizmo_plugin(_gizmo_plugin);\n    remove_control_from_bottom_panel(_editor);\n    if (_gizmo_plugin.is_valid()) {\n        _gizmo_plugin.unref();\n    }\n}\n\nvoid MMEditorPlugin::_make_visible(bool p_visible) {\n    if (p_visible) {\n        _bottom_panel_button->show();\n    } else {\n        _bottom_panel_button->hide();\n        hide_bottom_panel();\n    }\n}\n\nbool MMEditorPlugin::_handles(Object* p_node) const {\n    return (Object::cast_to<MMCharacter>(p_node) != nullptr);\n}\n\nvoid MMEditorPlugin::_edit(Object* p_object) {\n    MMCharacter* character = Object::cast_to<MMCharacter>(p_object);\n    _make_visible(character != nullptr);\n\n    if (!character) {\n        return;\n    }\n\n    _editor->set_character(character);\n}\n"
  },
  {
    "path": "src/editor/mm_editor_plugin.h",
    "content": "#ifndef MM_EDITOR_PLUGIN_H\n#define MM_EDITOR_PLUGIN_H\n\n#include \"animation_post_import_plugin.h\"\n#include \"mm_character.h\"\n#include \"mm_editor.h\"\n#include \"mm_editor_gizmo_plugin.h\"\n\n#include <godot_cpp/classes/button.hpp>\n#include <godot_cpp/classes/editor_plugin.hpp>\n#include <godot_cpp/classes/input_event.hpp>\n#include <godot_cpp/classes/ref_counted.hpp>\n\nusing namespace godot;\n\nclass MMEditorPlugin : public EditorPlugin {\n    GDCLASS(MMEditorPlugin, EditorPlugin)\n\npublic:\n    MMEditorPlugin();\n    ~MMEditorPlugin();\n\n    virtual void _make_visible(bool p_visible) override;\n    virtual bool _handles(Object* p_node) const override;\n    virtual void _edit(Object* p_object) override;\n\nprivate:\n    static void _bind_methods() {};\n\n    Ref<MMEditorGizmoPlugin> _gizmo_plugin;\n    Ref<AnimationPostImportPlugin> _post_import_plugin;\n\n    MMEditor* _editor;\n    Button* _bottom_panel_button;\n    MMCharacter* _current_controller;\n};\n\n#endif // MM_EDITOR_PLUGIN_H\n"
  },
  {
    "path": "src/editor/mm_visualization_tab.cpp",
    "content": "#include \"mm_visualization_tab.h\"\n\nvoid MMVisualizationTab::set_enabled(bool p_enabled) {\n    if (p_enabled) {\n        _warning_label->hide();\n    } else {\n        _warning_label->show();\n        _warning_label->set_text(\"Library data is out of date. Bake data to enable visualization.\");\n    }\n    _viz_animation_option_button->set_disabled(!p_enabled);\n    _viz_time_slider->set_editable(p_enabled);\n}\n\nvoid MMVisualizationTab::_bind_methods() {\n    ADD_SIGNAL(MethodInfo(\"animation_visualization_requested\", PropertyInfo(Variant::STRING, \"animation_lib_name\"), PropertyInfo(Variant::STRING, \"animation_name\"), PropertyInfo(Variant::INT, \"pose_index\")));\n}\n\nvoid MMVisualizationTab::_notification(int p_notification) {\n    switch (p_notification) {\n    case NOTIFICATION_ENTER_TREE: {\n        set_name(\"Visualization\");\n\n        _visualization_vbox = memnew(VBoxContainer);\n        _visualization_vbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        add_child(_visualization_vbox);\n\n        _warning_label = memnew(Label);\n        _warning_label->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _warning_label->set_text(\"No character selected\");\n        _visualization_vbox->add_child(_warning_label);\n\n        _viz_animation_option_button = memnew(OptionButton);\n        _viz_animation_option_button->connect(\"item_selected\", callable_mp(this, &MMVisualizationTab::_viz_anim_selected));\n        _visualization_vbox->add_child(_viz_animation_option_button);\n\n        _viz_time_slider = memnew(HSlider);\n        _viz_time_slider->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);\n        _viz_time_slider->set_min(0);\n        _viz_time_slider->set_max(1);\n        _viz_time_slider->set_step(1);\n        _viz_time_slider->connect(\"value_changed\", callable_mp(this, &MMVisualizationTab::_viz_time_changed));\n        _visualization_vbox->add_child(_viz_time_slider);\n    }\n    }\n}\n\nvoid MMVisualizationTab::_viz_time_changed(float p_value) {\n\n    if (_selected_animation_index == -1) {\n        return;\n    }\n\n    if (_current_animation_library.is_null()) {\n        return;\n    }\n\n    TypedArray<StringName> animation_list = _current_animation_library->get_animation_list();\n    StringName animation_name = animation_list[_selected_animation_index];\n    String anim_lib_name = _current_animation_library->get_path().get_file().get_basename();\n\n    _emit_animation_viz_request(anim_lib_name, animation_name, p_value);\n}\n\nvoid MMVisualizationTab::_viz_anim_selected(int p_index) {\n    _selected_animation_index = p_index;\n\n    if (_current_animation_library.is_null()) {\n        return;\n    }\n\n    TypedArray<StringName> animation_list = _current_animation_library->get_animation_list();\n    StringName animation_name = animation_list[p_index];\n\n    _viz_time_slider->set_max(_current_animation_library->get_animation_pose_count(animation_name) - 1);\n}\n\nvoid MMVisualizationTab::_emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index) {\n    emit_signal(\"animation_visualization_requested\", p_animation_lib, p_animation_name, p_pose_index);\n}\n\nvoid MMVisualizationTab::refresh() {\n    clear();\n\n    if (_current_animation_library.is_null()) {\n        return;\n    }\n\n    TypedArray<StringName> animations = _current_animation_library->get_animation_list();\n\n    for (int i = 0; i < animations.size(); i++) {\n        StringName animation_name = animations[i];\n        _viz_animation_option_button->add_item(animation_name, i);\n    }\n\n    if (!animations.is_empty()) {\n        _viz_animation_option_button->select(0);\n        _viz_anim_selected(0);\n    }\n\n    set_enabled(!_current_animation_library->needs_baking());\n}\n\nvoid MMVisualizationTab::clear() {\n    _selected_animation_index = -1;\n    _viz_animation_option_button->clear();\n    _viz_animation_option_button->select(-1);\n\n    set_enabled(false);\n}\n"
  },
  {
    "path": "src/editor/mm_visualization_tab.h",
    "content": "#pragma once\n\n#include \"mm_animation_library.h\"\n\n#include <godot_cpp/classes/grid_container.hpp>\n#include <godot_cpp/classes/h_slider.hpp>\n#include <godot_cpp/classes/label.hpp>\n#include <godot_cpp/classes/option_button.hpp>\n#include <godot_cpp/classes/tab_bar.hpp>\n#include <godot_cpp/classes/v_box_container.hpp>\n\nusing namespace godot;\n\nclass MMVisualizationTab : public TabBar {\n    GDCLASS(MMVisualizationTab, TabBar)\n\npublic:\n    void set_animation_library(Ref<MMAnimationLibrary> p_library) {\n        _current_animation_library = p_library;\n        refresh();\n    }\n\n    void set_enabled(bool p_enabled);\n    void refresh();\n    void clear();\n\nprotected:\n    static void _bind_methods();\n    void _notification(int p_notification);\n\nprivate:\n    void _viz_time_changed(float p_value);\n    void _viz_anim_selected(int p_index);\n    void _emit_animation_viz_request(String p_animation_lib, String p_animation_name, int32_t p_pose_index);\n\n    Ref<MMAnimationLibrary> _current_animation_library;\n\n    // Visualization\n    Label* _warning_label;\n    VBoxContainer* _visualization_vbox;\n    OptionButton* _viz_animation_option_button;\n    HSlider* _viz_time_slider;\n    int _selected_animation_index = -1;\n};"
  },
  {
    "path": "src/features/mm_bone_data_feature.cpp",
    "content": "#include \"features/mm_bone_data_feature.h\"\n\n#include \"mm_bone_data_feature.h\"\n#include \"mm_bone_state.h\"\n\n#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>\n#include <godot_cpp/classes/point_mesh.hpp>\n#include <godot_cpp/classes/sphere_mesh.hpp>\n#include <godot_cpp/classes/standard_material3d.hpp>\n#include <numeric>\n\nvoid MMBoneDataFeature::setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {\n    _skeleton = p_skeleton;\n    _skeleton_path = p_player->get_root_motion_track().get_concatenated_names();\n    const StringName root_bone_name = p_player->get_root_motion_track().get_concatenated_subnames();\n    _root_bone_index = p_skeleton->find_bone(root_bone_name);\n}\n\nvoid MMBoneDataFeature::setup_for_animation(Ref<Animation> animation) {\n}\n\nint64_t MMBoneDataFeature::get_dimension_count() const {\n    return bone_names.size() * 3;\n}\n\nPackedFloat32Array MMBoneDataFeature::bake_animation_pose(Ref<Animation> p_animation, double time) const {\n    PackedFloat32Array result;\n\n    for (int64_t i = 0; i < bone_names.size(); ++i) {\n        BoneState bone_state = _sample_bone_state(p_animation, time, bone_names[i]);\n\n        result.append(bone_state.pos.x);\n        result.append(bone_state.pos.y);\n        result.append(bone_state.pos.z);\n    }\n    return result;\n}\n\nPackedFloat32Array MMBoneDataFeature::evaluate_runtime_data(const MMQueryInput& p_query_input) const {\n    PackedFloat32Array result;\n    for (int64_t i = 0; i < bone_names.size(); ++i) {\n        const BoneState& bone_state = p_query_input.skeleton_state.find_bone_state(bone_names[i]);\n        const Vector3& pos = bone_state.pos;\n        const Quaternion& rot = bone_state.rot;\n\n        result.append(pos.x);\n        result.append(pos.y);\n        result.append(pos.z);\n    }\n    return result;\n}\n\nvoid MMBoneDataFeature::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {\n    Ref<StandardMaterial3D> material = p_gizmo->get_plugin()->get_material(\"bone_material\", p_gizmo);\n    if (material.is_null()) {\n        p_gizmo->get_plugin()->create_material(\"bone_material\", Color(0, 0, 1, 1));\n        material = p_gizmo->get_plugin()->get_material(\"bone_material\", p_gizmo);\n    }\n\n    float* dernomalized_data = new float[get_dimension_count()];\n    memcpy(dernomalized_data, p_data, sizeof(float) * get_dimension_count());\n    denormalize(dernomalized_data);\n\n    for (int64_t i = 0; i < get_dimension_count(); i += 3) {\n        Ref<SphereMesh> sphere_mesh;\n        sphere_mesh.instantiate();\n        sphere_mesh->set_radius(0.05);\n        sphere_mesh->set_height(0.05);\n        sphere_mesh->set_material(material);\n\n        const Vector3 pos = Vector3(dernomalized_data[i], dernomalized_data[i + 1], dernomalized_data[i + 2]);\n\n        Transform3D point_transform = Transform3D();\n        point_transform.origin = pos;\n        p_gizmo->add_mesh(sphere_mesh, material, point_transform, nullptr);\n    }\n\n    delete[] dernomalized_data;\n}\n\nvoid MMBoneDataFeature::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(MMBoneDataFeature, Variant::PACKED_STRING_ARRAY, bone_names);\n}\n\nBoneState MMBoneDataFeature::_sample_bone_state(Ref<Animation> p_animation, double p_time, const String& p_bone_path) const {\n\n    std::vector<Transform3D> bone_transforms;\n    int32_t current_bone_index = _skeleton->find_bone(p_bone_path);\n    String current_bone;\n    const int32_t root_bone_index = _root_bone_index;\n    while (current_bone_index != root_bone_index && current_bone_index != -1) {\n        current_bone = _skeleton->get_bone_name(current_bone_index);\n        const String bone_path = String(_skeleton_path) + String(\":\") + current_bone;\n\n        Transform3D bone_transform = _skeleton->get_bone_rest(current_bone_index);\n        int32_t pos_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_POSITION_3D);\n        int32_t rot_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_ROTATION_3D);\n        int32_t scl_track = p_animation->find_track(bone_path, Animation::TrackType::TYPE_SCALE_3D);\n\n        if (pos_track != -1) {\n            bone_transform.origin = p_animation->position_track_interpolate(pos_track, p_time);\n        }\n\n        if (rot_track != -1) {\n            bone_transform.basis.set_quaternion(p_animation->rotation_track_interpolate(rot_track, p_time));\n        }\n\n        if (scl_track != -1) {\n            bone_transform.basis.scale(p_animation->scale_track_interpolate(scl_track, p_time));\n        }\n\n        bone_transforms.emplace_back(bone_transform);\n        current_bone_index = _skeleton->get_bone_parent(current_bone_index);\n    }\n\n    Transform3D global_transform = _skeleton->get_bone_global_rest(root_bone_index);\n    global_transform = std::reduce(\n        bone_transforms.rbegin(),\n        bone_transforms.rend(),\n        global_transform,\n        [](const Transform3D& acc, const Transform3D& bone_transform) {\n            return acc * bone_transform;\n        });\n\n    BoneState bone_state;\n    bone_state.pos = global_transform.origin;\n    bone_state.rot = global_transform.basis.get_quaternion();\n    bone_state.scl = global_transform.basis.get_scale();\n    return bone_state;\n}\n"
  },
  {
    "path": "src/features/mm_bone_data_feature.h",
    "content": "#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#include <godot_cpp/classes/skeleton3d.hpp>\n\nusing namespace godot;\n\nclass MMBoneDataFeature : public MMFeature {\n    GDCLASS(MMBoneDataFeature, MMFeature)\n\npublic:\n    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) override;\n\n    virtual void setup_for_animation(Ref<Animation> animation) override;\n\n    virtual int64_t get_dimension_count() const override;\n\n    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const override;\n\n    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const override;\n\n    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const override;\n\n    GETSET(PackedStringArray, bone_names, PackedStringArray());\n\nprotected:\n    static void _bind_methods();\n\nprivate:\n    BoneState _sample_bone_state(Ref<Animation> p_animation, double p_time, const String& p_bone_path) const;\n    StringName _skeleton_path;\n    const Skeleton3D* _skeleton;\n    int32_t _root_bone_index;\n};\n\n#endif // MM_BONE_DATA_FEATURE_H\n"
  },
  {
    "path": "src/features/mm_feature.cpp",
    "content": "#include \"mm_feature.h\"\n\nMMFeature::MMFeature() {\n}\n\nMMFeature::~MMFeature() {\n}\n\nvoid MMFeature::normalize(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in normalize.\");\n        return;\n    }\n    switch (normalization_mode) {\n    case Standard:\n        _normalize_standard(p_data);\n        break;\n    case MinMax:\n        _normalize_minmax(p_data);\n        break;\n    case Raw:\n    default:\n        break;\n    }\n}\n\nvoid MMFeature::denormalize(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in denormalize.\");\n        return;\n    }\n    switch (normalization_mode) {\n    case Standard:\n        _denormalize_standard(p_data);\n        break;\n    case MinMax:\n        _denormalize_minmax(p_data);\n        break;\n    case Raw:\n    default:\n        break;\n    }\n}\n\nvoid MMFeature::_normalize_minmax(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in _normalize_minmax.\");\n        return;\n    }\n    for (int64_t i = 0; i < get_dimension_count(); ++i) {\n        const float delta = maxes[i] - mins[i];\n        if (abs(delta) < KINDA_SMALL_NUMBER) {\n            continue;\n        }\n        p_data[i] = (p_data[i] - mins[i]) / delta;\n    }\n}\n\nvoid MMFeature::_denormalize_minmax(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in _denormalize_minmax.\");\n        return;\n    }\n    for (int64_t i = 0; i < get_dimension_count(); ++i) {\n        const float delta = maxes[i] - mins[i];\n        if (abs(delta) < KINDA_SMALL_NUMBER) {\n            continue;\n        }\n        p_data[i] = (p_data[i] * delta) + mins[i];\n    }\n}\n\nvoid MMFeature::_normalize_standard(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in _normalize_standard.\");\n        return;\n    }\n    for (int64_t i = 0; i < get_dimension_count(); ++i) {\n        p_data[i] = (p_data[i] - means[i]) / (std_devs[i] + KINDA_SMALL_NUMBER);\n    }\n}\n\nvoid MMFeature::_denormalize_standard(float* p_data) const {\n    if (!p_data) {\n        ERR_PRINT_ONCE(\"Invalid data provided in _denormalize_standard.\");\n        return;\n    }\n    ERR_FAIL_COND(std_devs.size() != get_dimension_count());\n    for (int64_t i = 0; i < get_dimension_count(); ++i) {\n        p_data[i] = (p_data[i] * (std_devs[i] + KINDA_SMALL_NUMBER)) + means[i];\n    }\n}\n\nvoid MMFeature::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_dimension_count\"), &MMFeature::get_dimension_count);\n    ClassDB::bind_method(D_METHOD(\"set_normalization_mode\", \"value\"), &MMFeature::set_normalization_mode, DEFVAL(MMFeature::NormalizationMode::Standard));\n    ClassDB::bind_method(D_METHOD(\"get_normalization_mode\"), &MMFeature::get_normalization_mode);\n    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\");\n\n    BINDER_PROPERTY_PARAMS(MMFeature, Variant::FLOAT, weight);\n    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, means, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, std_devs, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, maxes, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMFeature, Variant::PACKED_FLOAT32_ARRAY, mins, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n\n    BIND_ENUM_CONSTANT(Raw);\n    BIND_ENUM_CONSTANT(Standard);\n    BIND_ENUM_CONSTANT(MinMax);\n}\n\nVARIANT_ENUM_CAST(MMFeature::NormalizationMode);\n"
  },
  {
    "path": "src/features/mm_feature.h",
    "content": "#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/animation.hpp>\n#include <godot_cpp/classes/animation_player.hpp>\n#include <godot_cpp/classes/editor_node3d_gizmo.hpp>\n#include <godot_cpp/classes/resource.hpp>\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/variant/packed_float32_array.hpp>\n\nusing namespace godot;\n\nclass MMCharacter;\n\nclass MMFeature : public Resource {\n    GDCLASS(MMFeature, Resource)\n\npublic:\n    enum NormalizationMode { Raw,\n                             Standard,\n                             MinMax };\n\npublic:\n    MMFeature(/* args */);\n    virtual ~MMFeature();\n    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {};\n\n    virtual void setup_for_animation(Ref<Animation> animation) {};\n\n    virtual int64_t get_dimension_count() const = 0;\n\n    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const = 0;\n\n    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const = 0;\n\n    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {};\n\n    virtual float calculate_normalized_weight(int64_t p_feature_dim) const {\n        return weight / get_dimension_count();\n    }\n\n    void normalize(float* p_data) const;\n    void denormalize(float* p_data) const;\n\n    GETSET(float, weight, 1.0f);\n    GETSET(NormalizationMode, normalization_mode, Standard);\n    GETSET(PackedFloat32Array, means);\n    GETSET(PackedFloat32Array, std_devs);\n    GETSET(PackedFloat32Array, maxes);\n    GETSET(PackedFloat32Array, mins);\n\nprotected:\n    static void _bind_methods();\n    void _normalize_minmax(float* p_data) const;\n    void _denormalize_minmax(float* p_data) const;\n    void _normalize_standard(float* p_data) const;\n    void _denormalize_standard(float* p_data) const;\n};\n\n#endif // MM_FEATURE_H\n"
  },
  {
    "path": "src/features/mm_trajectory_feature.cpp",
    "content": "#include \"mm_trajectory_feature.h\"\n\n#include \"math/transforms.h\"\n#include \"mm_character.h\"\n\n#include <godot_cpp/classes/animation_player.hpp>\n#include <godot_cpp/classes/editor_node3d_gizmo_plugin.hpp>\n#include <godot_cpp/classes/point_mesh.hpp>\n#include <godot_cpp/classes/sphere_mesh.hpp>\n#include <godot_cpp/classes/standard_material3d.hpp>\n\n#include <cstdint>\n\nMMTrajectoryFeature::MMTrajectoryFeature() {\n}\n\nMMTrajectoryFeature::~MMTrajectoryFeature() {\n}\n\nint64_t MMTrajectoryFeature::get_dimension_count() const {\n    return _get_point_dimension_count() * (past_frames + future_frames);\n}\n\nvoid MMTrajectoryFeature::setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {\n    const StringName skel_path = p_player->get_root_motion_track().get_concatenated_names();\n    const StringName root_bone_name = p_player->get_root_motion_track().get_concatenated_subnames();\n    _root_bone = p_skeleton->find_bone(root_bone_name);\n\n    _root_bone_path = String(skel_path) + \":\" + String(root_bone_name);\n\n    past_delta_time = p_character->history_delta_time;\n    future_delta_time = p_character->trajectory_delta_time;\n    past_frames = p_character->history_point_count;\n    future_frames = p_character->trajectory_point_count;\n}\n\nvoid MMTrajectoryFeature::setup_for_animation(Ref<Animation> animation) {\n    _root_position_track = animation->find_track(_root_bone_path, Animation::TrackType::TYPE_POSITION_3D);\n    _root_rotation_track = animation->find_track(_root_bone_path, Animation::TrackType::TYPE_ROTATION_3D);\n}\n\nPackedFloat32Array MMTrajectoryFeature::bake_animation_pose(Ref<Animation> p_animation, double time) const {\n    PackedFloat32Array result;\n\n    const Vector3 current_pos =\n        _root_position_track == -1 ? Vector3() : p_animation->position_track_interpolate(_root_position_track, time);\n\n    const Quaternion current_rotation =\n        _root_rotation_track == -1 ? Quaternion() : p_animation->rotation_track_interpolate(_root_rotation_track, time);\n\n    const double animation_length = p_animation->get_length();\n    auto add_frame = [this, &result, &p_animation, &current_pos, &current_rotation, &animation_length](double p_time) {\n        Vector3 position;\n        Quaternion rotation;\n        Vector3 extrapolation_velocity;\n        const double clamped_time = CLAMP(p_time, 0.0, animation_length);\n        const double extrapolation_dt = 0.1;\n        if (_root_position_track != -1) {\n            Vector3 interpolated_position = p_animation->position_track_interpolate(_root_position_track, clamped_time);\n            double extrapolation_time = 0.0;\n            if (p_time > animation_length) {\n                extrapolation_velocity = (p_animation->position_track_interpolate(_root_position_track, animation_length) -\n                                          p_animation->position_track_interpolate(_root_position_track, animation_length - extrapolation_dt)) /\n                    extrapolation_dt;\n                extrapolation_time = p_time - animation_length;\n            } else if (p_time < 0) {\n                extrapolation_velocity = (p_animation->position_track_interpolate(_root_position_track, 0.0) -\n                                          p_animation->position_track_interpolate(_root_position_track, extrapolation_dt)) /\n                    extrapolation_dt;\n                extrapolation_time = abs(p_time);\n            }\n\n            position = interpolated_position + extrapolation_velocity * extrapolation_time - current_pos;\n            position = current_rotation.xform_inv(position);\n        }\n\n        if (_root_rotation_track != -1) {\n            rotation = p_animation->rotation_track_interpolate(_root_rotation_track, clamped_time) * current_rotation.inverse();\n        }\n\n        result.append(position.x);\n\n        if (include_height) {\n            result.append(position.y);\n        }\n\n        result.append(position.z);\n\n        if (include_facing) {\n            const Vector3 facing = rotation.get_euler();\n            result.append(facing.y);\n        }\n    };\n\n    // We do not include the first frame\n    for (int64_t i = 1; i < future_frames + 1; i++) {\n        add_frame(time + future_delta_time * i);\n    }\n\n    for (int64_t i = 1; i < past_frames + 1; i++) {\n        add_frame(time - past_delta_time * i);\n    }\n\n    return result;\n}\n\nPackedFloat32Array MMTrajectoryFeature::evaluate_runtime_data(const MMQueryInput& p_query_input) const {\n    const Transform3D& character_transform = p_query_input.character_transform;\n\n    // Get the trajectory points in local space\n    PackedFloat32Array result;\n    auto add_point = [this, &character_transform, &result](int index, const MMTrajectoryPoint& trajectory_point) {\n        const Vector3 local_position = character_transform.basis.xform_inv(trajectory_point.position - character_transform.origin);\n\n        result.append(local_position.x);\n        if (include_height) {\n            result.append(local_position.y);\n        }\n        result.append(local_position.z);\n        if (include_facing) {\n            result.append(global_to_local_facing_angle(trajectory_point.facing_angle, character_transform));\n        }\n    };\n\n    // The first point of the trajectory represents the player's current state\n    // We do not match the first point of the trajectory (character position)\n    for (int64_t i = 1; i < future_frames + 1; i++) {\n        const size_t max_size = p_query_input.trajectory.size();\n        if (max_size == 0) {\n            add_point(i, MMTrajectoryPoint());\n        } else if (i >= max_size) {\n            add_point(i, p_query_input.trajectory[max_size - 1]);\n        } else {\n            add_point(i, p_query_input.trajectory[i]);\n        }\n    }\n\n    for (int64_t i = 0; i < past_frames; i++) {\n        const size_t max_size = p_query_input.trajectory_history.size();\n\n        if (max_size == 0) {\n            add_point(i, MMTrajectoryPoint());\n        } else if (i >= max_size) {\n            add_point(i, p_query_input.trajectory_history[max_size - 1]);\n        } else {\n            add_point(i, p_query_input.trajectory_history[i]);\n        }\n    }\n\n    return result;\n}\n\nvoid MMTrajectoryFeature::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const {\n\n    Ref<StandardMaterial3D> trajectory_material = p_gizmo->get_plugin()->get_material(\"trajectory_material\", p_gizmo);\n    if (trajectory_material.is_null()) {\n        p_gizmo->get_plugin()->create_material(\"trajectory_material\", Color(1, 0, 0, 1));\n        trajectory_material = p_gizmo->get_plugin()->get_material(\"trajectory_material\", p_gizmo);\n    }\n\n    Ref<StandardMaterial3D> history_material = p_gizmo->get_plugin()->get_material(\"trajectory_history_material\", p_gizmo);\n    if (history_material.is_null()) {\n        p_gizmo->get_plugin()->create_material(\"trajectory_history_material\", Color(0, 1, 0, 1));\n        history_material = p_gizmo->get_plugin()->get_material(\"trajectory_history_material\", p_gizmo);\n    }\n\n    float* dernomalized_data = new float[get_dimension_count()];\n    memcpy(dernomalized_data, p_data, sizeof(float) * get_dimension_count());\n    denormalize(dernomalized_data);\n\n    auto draw_point = [this,\n                       &p_gizmo,\n                       dernomalized_data](int64_t i, Ref<StandardMaterial3D> material) {\n        Ref<SphereMesh> sphere_mesh;\n        sphere_mesh.instantiate();\n        sphere_mesh->set_radius(0.10);\n        sphere_mesh->set_height(0.10);\n        sphere_mesh->set_material(material);\n\n        MMTrajectoryPoint point;\n        point.position = Vector3(\n            dernomalized_data[i],\n            include_height ? dernomalized_data[i + 1] : 0,\n            include_height ? dernomalized_data[i + 2] : dernomalized_data[i + 1]);\n\n        Transform3D point_transform = Transform3D();\n        point_transform.origin = point.position;\n        p_gizmo->add_mesh(sphere_mesh, material, point_transform, nullptr);\n\n        if (include_facing) {\n            const float facing_angle = dernomalized_data[i + (include_height ? 3 : 2)];\n            const Vector3 facing = Vector3(UtilityFunctions::sin(facing_angle), 0.f, UtilityFunctions::cos(facing_angle));\n            const Vector3 facing_end = point.position + facing * 0.5f;\n            PackedVector3Array lines;\n            lines.push_back(point.position);\n            lines.push_back(facing_end);\n            p_gizmo->add_lines(lines, material);\n        }\n    };\n    int64_t i = 0;\n    for (; i < future_frames * _get_point_dimension_count(); i += _get_point_dimension_count()) {\n        draw_point(i, trajectory_material);\n    }\n\n    for (; i < get_dimension_count(); i += _get_point_dimension_count()) {\n        draw_point(i, history_material);\n    }\n\n    delete[] dernomalized_data;\n}\n\nfloat MMTrajectoryFeature::calculate_normalized_weight(int64_t p_feature_dim) const {\n\n    float weight = MMFeature::calculate_normalized_weight(p_feature_dim);\n\n    const uint32_t point_dim = _get_point_dimension_count();\n\n    const bool is_height = include_height && (p_feature_dim % point_dim) == 2;\n    const bool is_facing = include_facing && (p_feature_dim % point_dim) == (include_height ? 3 : 2);\n\n    if (is_height) {\n        weight *= height_weight;\n    } else if (is_facing) {\n        weight *= facing_weight;\n    }\n\n    return weight;\n}\n\nTypedArray<Dictionary> MMTrajectoryFeature::get_trajectory_points(const Transform3D& p_character_transform, const PackedFloat32Array& p_trajectory_data) const {\n    ERR_FAIL_COND_V(p_trajectory_data.is_empty(), TypedArray<Dictionary>());\n\n    PackedFloat32Array denormalized_data = PackedFloat32Array(p_trajectory_data);\n    denormalize(denormalized_data.ptrw());\n\n    TypedArray<Dictionary> result;\n    const int offset = _get_point_dimension_count();\n    for (int i = 0; i < future_frames * offset; i += offset) {\n        MMTrajectoryPoint point;\n        point.position = Vector3(denormalized_data[i], include_height ? denormalized_data[i + 1] : 0, include_height ? denormalized_data[i + 2] : denormalized_data[i + 1]);\n        point.position = p_character_transform.xform(point.position);\n\n        if (include_facing) {\n            // const float local_facing_angle = denormalized_data[i + (include_height ? 3 : 2)];\n            point.facing_angle = 0.0; // TODO\n        }\n\n        Dictionary data;\n        data.get_or_add(\"position\", point.position);\n        data.get_or_add(\"facing\", point.facing_angle);\n\n        result.push_back(data);\n    }\n    return result;\n}\n\nbool MMTrajectoryFeature::get_include_height() const {\n    return include_height;\n}\n\nvoid MMTrajectoryFeature::set_include_height(bool value) {\n    include_height = value;\n    notify_property_list_changed();\n}\n\nbool MMTrajectoryFeature::get_include_facing() const {\n    return include_facing;\n}\n\nvoid MMTrajectoryFeature::set_include_facing(bool value) {\n    include_facing = value;\n    notify_property_list_changed();\n}\n\nvoid MMTrajectoryFeature::_validate_property(PropertyInfo& p_property) const {\n    if (p_property.name == StringName(\"facing_weight\")) {\n        if (!include_facing) {\n            p_property.usage = PROPERTY_USAGE_NO_EDITOR;\n        }\n    }\n\n    if (p_property.name == StringName(\"height_weight\")) {\n        if (!include_height) {\n            p_property.usage = PROPERTY_USAGE_NO_EDITOR;\n        }\n    }\n}\n\nvoid MMTrajectoryFeature::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_trajectory_points\", \"character_transform\", \"trajectory_data\"), &MMTrajectoryFeature::get_trajectory_points);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, past_delta_time, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::INT, past_frames, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, future_delta_time, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::INT, future_frames, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::BOOL, include_height);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, height_weight);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::BOOL, include_facing);\n    BINDER_PROPERTY_PARAMS(MMTrajectoryFeature, Variant::FLOAT, facing_weight);\n}\n\nuint32_t MMTrajectoryFeature::_get_point_dimension_count() const {\n    size_t dimensions = 2; // x, z\n    if (include_height) {\n        dimensions++; // y\n    }\n    if (include_facing) {\n        dimensions++; // facing\n    }\n    return dimensions;\n}\n"
  },
  {
    "path": "src/features/mm_trajectory_feature.h",
    "content": "#ifndef MM_TRAJECTORY_FEATURE_H\n#define MM_TRAJECTORY_FEATURE_H\n\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n#include <cstdint>\n\nusing namespace godot;\n\nclass MMTrajectoryFeature : public MMFeature {\n    GDCLASS(MMTrajectoryFeature, MMFeature)\n\npublic:\n    MMTrajectoryFeature(/* args */);\n    virtual ~MMTrajectoryFeature();\n\n    virtual int64_t get_dimension_count() const override;\n\n    virtual void setup_skeleton(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) override;\n\n    virtual void setup_for_animation(Ref<Animation> animation) override;\n\n    virtual PackedFloat32Array bake_animation_pose(Ref<Animation> p_animation, double time) const override;\n\n    virtual PackedFloat32Array evaluate_runtime_data(const MMQueryInput& p_query_input) const override;\n\n    virtual void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D p_transform, const float* p_data) const override;\n\n    virtual float calculate_normalized_weight(int64_t p_feature_dim) const override;\n\n    TypedArray<Dictionary> get_trajectory_points(const Transform3D& p_character_transform, const PackedFloat32Array& p_trajectory_data) const;\n\n    GETSET(double, past_delta_time, 0.1);\n    GETSET(int64_t, past_frames, 1);\n    GETSET(double, future_delta_time, 0.1);\n    GETSET(int64_t, future_frames, 5);\n\n    bool include_height{false};\n    bool get_include_height() const;\n    void set_include_height(bool value);\n\n    GETSET(float, height_weight, 1.0);\n\n    bool include_facing{true};\n    bool get_include_facing() const;\n    void set_include_facing(bool value);\n\n    GETSET(float, facing_weight, 1.0);\n\nprotected:\n    void _validate_property(PropertyInfo& p_property) const;\n    static void _bind_methods();\n\nprivate:\n    uint32_t _get_point_dimension_count() const;\n\n    int _root_bone{-1};\n    NodePath _root_bone_path;\n    int _root_position_track{-1};\n    int _root_rotation_track{-1};\n};\n\n#endif // MM_TRAJECTORY_FEATURE_H\n"
  },
  {
    "path": "src/math/hash.h",
    "content": "#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) {\n    return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));\n}"
  },
  {
    "path": "src/math/spring.hpp",
    "content": "#pragma once\n\n#include <math.h>\n\n#include <cmath>\n#include <godot_cpp/classes/project_settings.hpp>\n#include <godot_cpp/classes/ref.hpp>\n#include <godot_cpp/classes/ref_counted.hpp>\n#include <godot_cpp/classes/resource.hpp>\n#include <godot_cpp/core/class_db.hpp>\n#include <godot_cpp/core/method_bind.hpp>\n#include <godot_cpp/variant/dictionary.hpp>\n#include <godot_cpp/variant/variant.hpp>\n\nusing namespace godot;\n\nnamespace Spring {\n\nstatic constexpr real_t Ln2 = 0.69314718056;\n\nstatic real_t inline square(real_t x) {\n    return x * x;\n}\n\nstatic inline real_t fast_negexp(real_t x) {\n    return 1.0 / (1.0 + x + 0.48 * x * x + 0.235 * x * x * x);\n}\n\nstatic Vector3 damp_adjustment_exact(Vector3 g, real_t halflife, real_t dt, real_t eps = 1e-8) {\n    real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps));\n    return g * factor;\n}\n\nstatic Quaternion damp_adjustment_exact_quat(Quaternion g, real_t halflife, real_t dt, real_t eps = 1e-8) {\n    real_t factor = 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps));\n    return Quaternion().slerp(g, factor).normalized();\n}\n\nstatic Variant damper_exponential(Variant variable, Variant goal, real_t damping, real_t dt) {\n    real_t ft = 1.0 / (real_t)ProjectSettings::get_singleton()->get(\"physics/common/physics_ticks_per_second\");\n    real_t factor = 1.0 - pow(1.0 / (1.0 - ft * damping), -dt / ft);\n    return Math::lerp(variable, goal, factor);\n}\n\nstatic inline Variant damper_exact(Variant variable, Variant goal, real_t halflife, real_t dt, real_t eps = 1e-5) {\n    return Math::lerp(variable, goal, 1.0 - fast_negexp((Spring::Ln2 * dt) / (halflife + eps)));\n}\n\nstatic inline real_t halflife_to_damping(real_t halflife, real_t eps = 1e-5) {\n    return (4.0 * Ln2) / (halflife + eps);\n}\n\nstatic inline real_t halflife_to_duration(real_t halflife, real_t initial_value = 1.0, real_t eps = 1e-5) {\n    return halflife * (log(eps / initial_value) / log(0.5));\n}\n\nstatic inline real_t damping_to_halflife(real_t damping, real_t eps = 1e-5) {\n    return (4.0 * Ln2) / (damping + eps);\n}\n\nstatic inline real_t frequency_to_stiffness(real_t frequency) {\n    return square(2.0 * Math_PI * frequency);\n}\n\nstatic inline real_t stiffness_to_frequency(real_t stiffness) {\n    return sqrt(stiffness) / (2.0 * Math_PI);\n}\n\nstatic inline real_t critical_halflife(real_t frequency) {\n    return damping_to_halflife(sqrt(frequency_to_stiffness(frequency) * 4.0));\n}\n\nstatic inline real_t critical_frequency(real_t halflife) {\n    return stiffness_to_frequency(square(halflife_to_damping(halflife)) / 4.0);\n}\n\nstatic inline real_t damping_ratio_to_stiffness(real_t ratio, real_t damping) {\n    return square(damping / (ratio * 2.0));\n}\n\nstatic inline real_t damping_ratio_to_damping(real_t ratio, real_t stiffness) {\n    return ratio * 2.0 * sqrt(stiffness);\n}\n\nstatic inline real_t maximum_spring_velocity_to_halflife(real_t x, real_t x_goal, real_t v_max) {\n    return damping_to_halflife(2.0 * ((v_max / (x_goal - x)) * exp(1.0)));\n}\n\nstatic inline Quaternion quat_exp(Vector3 v, real_t eps = 1e-8) {\n    real_t halfangle = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);\n\n    if (halfangle < eps) {\n        Quaternion q{};\n        q.w = 1.0;\n        q.x = v.x;\n        q.y = v.y;\n        q.z = v.z;\n        return q.normalized();\n    } else {\n        real_t c = cosf(halfangle);\n        real_t s = sinf(halfangle) / halfangle;\n        Quaternion q{};\n        q.w = c;\n        q.x = s * v.x;\n        q.y = s * v.y;\n        q.z = s * v.z;\n        return q.normalized();\n    }\n}\ntemplate <typename T>\nstatic inline T clampf(T x, T min, T max) {\n    static_assert(std::is_arithmetic_v<T>, \"Must be arithmetic\");\n    return x > max ? max : x < min ? min\n                                   : x;\n}\n\nstatic inline Quaternion quat_abs(Quaternion q) {\n    return (q.w < 0.0 ? -q : q).normalized();\n}\n\nstatic inline Vector3 quat_log(Quaternion q, real_t eps = 1e-8) {\n    real_t length = sqrt(q.x * q.x + q.y * q.y + q.z * q.z);\n    if (length < eps) {\n        return Vector3(q.x, q.y, q.z);\n    } else {\n        real_t halfangle = acosf(clampf(q.w, real_t(-1.0), real_t(1.0)));\n        return halfangle * (Vector3(q.x, q.y, q.z) / length);\n    }\n}\n\nstatic inline Quaternion quat_from_scaled_angle_axis(Vector3 v, real_t eps = 1e-8) {\n    return quat_exp(v / 2.0, eps).normalized();\n}\n\nstatic inline Vector3 quat_to_scaled_angle_axis(Quaternion q, real_t eps = 1e-8) {\n    return 2.0 * quat_log(q, eps);\n}\n\nstatic inline Vector3 quat_differentiate_angular_velocity(Quaternion next, Quaternion curr, real_t dt, real_t eps = 1e-8) {\n    return quat_to_scaled_angle_axis(quat_abs(next * curr.inverse()), eps) / dt;\n}\n\nstatic 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) {\n    real_t g = x_goal;\n    real_t q = v_goal;\n    real_t d = halflife_to_damping(halflife);\n    real_t s = damping_ratio_to_stiffness(damping_ratio, d);\n    real_t c = g + (d * q) / (s + eps);\n    real_t y = d / 2.0;\n\n    if (std::abs(s - (d * d) / 4.0) < eps) { // Critically Damped\n        real_t j0 = x - c;\n        real_t j1 = v + j0 * y;\n        real_t eydt = std::exp(-y * dt);\n        x = j0 * eydt + dt * j1 * eydt + c;\n        v = -y * j0 * eydt - y * dt * j1 * eydt + j1 * eydt;\n    } else if (s - (d * d) / 4.0 > 0.0) { // Under Damped\n        real_t w = std::sqrt(s - (d * d) / 4.0);\n        real_t j = std::sqrt(std::pow(v + y * (x - c), 2) / (std::pow(w, 2) + eps) + std::pow(x - c, 2));\n        real_t p = std::atan((v + (x - c) * y) / (-(x - c) * w + eps));\n\n        // j = (x - c) > 0.0 ? j : -j;\n        j = (x - c) > 0.0 ? j : -j;\n\n        real_t eydt = std::exp(-y * dt);\n\n        x = j * eydt * std::cos(w * dt + p) + c;\n        v = -y * j * eydt * std::cos(w * dt + p) - w * j * eydt * std::sin(w * dt + p);\n    } else if (s - (d * d) / 4.0 < 0.0) { // Over Damped\n        real_t y0 = (d + std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0;\n        real_t y1 = (d - std::sqrt(std::pow(d, 2) - 4.0 * s)) / 2.0;\n        real_t j1 = (c * y0 - x * y0 - v) / (y1 - y0);\n        real_t j0 = x - j1 - c;\n\n        real_t ey0dt = std::exp(-y0 * dt);\n        real_t ey1dt = std::exp(-y1 * dt);\n\n        x = j0 * ey0dt + j1 * ey1dt + c;\n        v = -y0 * j0 * ey0dt - y1 * j1 * ey1dt;\n    }\n}\n\nstatic 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) {\n    real_t g = x_goal;\n    real_t q = v_goal;\n    real_t d = halflife_to_damping(halflife);\n    real_t c = g + (d * q) / ((d * d) / 4.0);\n    real_t y = d / 2.0;\n    real_t j0 = x - c;\n    real_t j1 = v + j0 * y;\n    real_t eydt = fast_negexp(y * dt);\n    x = eydt * (j0 + j1 * dt) + c;\n    v = eydt * (v - j1 * y * dt);\n}\n\nstatic 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) {\n    _critical_spring_damper_exact(x, v, x_goal, v_goal, halflife, dt);\n    PackedFloat32Array result;\n    result.append(x);\n    result.append(v);\n    return result;\n}\n\nstatic void _simple_spring_damper_exact(real_t& x, real_t& v, real_t x_goal, real_t halflife, real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n    real_t j0 = x - x_goal;\n    real_t j1 = v + j0 * y;\n    real_t eydt = fast_negexp(y * dt);\n    x = eydt * (j0 + j1 * dt) + x_goal;\n    v = eydt * (v - j1 * y * dt);\n}\nstatic void _simple_spring_damper_exact(Vector3& x, Vector3& v, const Vector3 x_goal, const real_t halflife, const real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n    Vector3 j0 = x - x_goal;\n    Vector3 j1 = v + j0 * y;\n    real_t eydt = fast_negexp(y * dt);\n\n    x = eydt * (j0 + j1 * dt) + x_goal;\n    v = eydt * (v - j1 * y * dt);\n}\n\nstatic void _simple_spring_damper_exact(Quaternion& x, Vector3& v, const Quaternion x_goal, const real_t halflife, const real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n\n    Vector3 j0 = quat_to_scaled_angle_axis(quat_abs(x * x_goal.inverse()));\n    Vector3 j1 = v + j0 * y;\n\n    real_t eydt = fast_negexp(y * dt);\n\n    x = quat_from_scaled_angle_axis(eydt * (j0 + j1 * dt)) * x_goal;\n    v = eydt * (v - j1 * y * dt);\n}\n\nstatic inline Array simple_spring_damper_exact(Variant x, Variant v, Variant x_goal, real_t halflife, real_t dt) {\n    Array result;\n    if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::VECTOR3) {\n        Vector3 pos = (Vector3)x, vel = (Vector3)v, goal = (Vector3)x_goal;\n        _simple_spring_damper_exact(pos, vel, goal, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    } else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3 && x_goal.get_type() == Variant::Type::QUATERNION) {\n        Quaternion pos = (Quaternion)x;\n        Vector3 vel = (Vector3)v;\n        Quaternion goal = (Quaternion)x_goal;\n        _simple_spring_damper_exact(pos, vel, goal, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    } else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT) {\n        real_t pos = (real_t)x;\n        real_t vel = (real_t)v, goal = (real_t)x_goal;\n        _simple_spring_damper_exact(pos, vel, goal, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    }\n\n    return result;\n}\n\nstatic inline void _decay_spring_damper_exact(real_t& x, real_t& v, real_t halflife, real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n    real_t j1 = v + x * y;\n    real_t eydt = fast_negexp(y * dt);\n    x = eydt * (x + j1 * dt);\n    v = eydt * (v - j1 * y * dt);\n}\nstatic inline void _decay_spring_damper_exact(Vector3& x, Vector3& v, real_t halflife, real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n    Vector3 j1 = v + x * y;\n    real_t eydt = fast_negexp(y * dt);\n    x = eydt * (x + j1 * dt);\n    v = eydt * (v - j1 * y * dt);\n}\nstatic inline void _decay_spring_damper_exact(Quaternion& x, Vector3& v, const real_t halflife, const real_t dt) {\n    real_t y = halflife_to_damping(halflife) / 2.0;\n\n    Vector3 j0 = quat_to_scaled_angle_axis(x);\n    Vector3 j1 = v + j0 * y;\n\n    real_t eydt = fast_negexp(y * dt);\n\n    x = quat_from_scaled_angle_axis(eydt * (j0 + j1 * dt));\n    v = eydt * (v - j1 * y * dt);\n}\nstatic inline Array decay_spring_damper_exact(Variant x, Variant v, real_t halflife, real_t dt) {\n    Array result;\n    if (x.get_type() == Variant::Type::VECTOR3 && v.get_type() == Variant::Type::VECTOR3) {\n        Vector3 pos = (Vector3)x, vel = (Vector3)v;\n        _decay_spring_damper_exact(pos, vel, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    } else if (x.get_type() == Variant::Type::QUATERNION && v.get_type() == Variant::Type::VECTOR3) {\n        Quaternion pos = (Quaternion)x;\n        Vector3 vel = (Vector3)v;\n        _decay_spring_damper_exact(pos, vel, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    } else if (x.get_type() == Variant::Type::FLOAT && v.get_type() == Variant::Type::FLOAT) {\n        real_t pos = (real_t)x;\n        real_t vel = (real_t)v;\n        _decay_spring_damper_exact(pos, vel, halflife, dt);\n        result.append(pos);\n        result.append(vel);\n    }\n\n    return result;\n}\n\n//\tReach the x_goal at timed t_goal in the future\n//\tApprehension parameter controls how far into the future we try to track\n// the linear interpolation\nstatic 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) {\n    const real_t min_time = t_goal > dt ? t_goal : dt;\n\n    const real_t v_goal = (x_goal - xi) / min_time;\n\n    const real_t t_goal_future = dt + apprehension * halflife;\n    const real_t x_goal_future = t_goal_future < t_goal ? xi + v_goal * t_goal_future : x_goal;\n\n    _simple_spring_damper_exact(x, v, x_goal_future, halflife, dt);\n    xi += v_goal * dt;\n}\nstatic 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) {\n    PackedFloat32Array result;\n    _timed_spring_damper_exact(x, v, xi, x_goal, t_goal, halflife, dt, apprehension);\n    result.append(x);\n    result.append(v);\n    result.append(xi);\n    return result;\n}\n\nstatic 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) {\n    off_x = (src_x + off_x) - dst_x;\n    off_v = (src_v + off_v) - dst_v;\n}\n\nstatic 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) {\n    Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt);\n    out_x = in_x + off_x;\n    out_v = in_v + off_v;\n}\n\nstatic 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) {\n    off_x = Spring::quat_abs((off_x * src_x) * dst_x.inverse()).normalized();\n    off_v = (off_v + src_v) - dst_v;\n}\nstatic 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) {\n    Spring::_decay_spring_damper_exact(off_x, off_v, halflife, dt);\n    out_x = (off_x * in_x).normalized();\n    out_v = off_v + off_x.xform(in_v);\n}\n\nstatic inline Vector3 calculate_offset_vec3(const Vector3 src_x, const Vector3 dst_x, const Vector3 off_x = Vector3()) {\n    return (src_x + off_x) - dst_x;\n}\nstatic inline Quaternion calculate_offset_quat(const Quaternion src_q, const Quaternion dst_q, const Quaternion off_q = Quaternion()) {\n    return Spring::quat_abs((off_q * src_q) * dst_q.inverse());\n}\n\nstatic 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) {\n    Dictionary result;\n    result[\"position_offset\"] = (src_x + off_x) - dst_x;\n    result[\"velocity_offset\"] = (src_v + off_v) - dst_v;\n    result[\"rotation_offset\"] = Spring::quat_abs((off_q * src_q) * dst_q.inverse());\n    result[\"angular_offset\"] = (off_a + src_a) - dst_a;\n    return result;\n}\n}; // namespace Spring"
  },
  {
    "path": "src/math/stats.hpp",
    "content": "#pragma once\n\n#include <algorithm>\n#include <cfloat>\n#include <cmath>\n\nclass StatsAccumulator {\nprivate:\n    float sum;\n    float sum_of_squares;\n    float max_value;\n    float min_value;\n    int count;\n\npublic:\n    StatsAccumulator()\n        : sum(0.0f), sum_of_squares(0.0f), max_value(-INFINITY), min_value(INFINITY), count(0) {\n    }\n\n    void add_sample(float sample) {\n        sum += sample;\n        sum_of_squares += sample * sample;\n        max_value = std::max(max_value, sample);\n        min_value = std::min(min_value, sample);\n        count++;\n    }\n\n    float get_mean() const {\n        return sum / count;\n    }\n\n    float get_max() const {\n        return max_value;\n    }\n\n    float get_min() const {\n        return min_value;\n    }\n\n    float get_standard_deviation() const {\n        const float mean = get_mean();\n        const float variance = (sum_of_squares / count) - (mean * mean);\n        if (variance < FLT_EPSILON) {\n            return 0.0f;\n        }\n        return sqrt(variance);\n    }\n\n    void reset() {\n        sum = 0.0f;\n        sum_of_squares = 0.0f;\n        max_value = -INFINITY;\n        min_value = INFINITY;\n        count = 0;\n    }\n};\n\nfloat distance_squared(const float* a, const float* b, int dim) {\n    float distance = 0.0f;\n    for (int i = 0; i < dim; i++) {\n        const float diff = a[i] - b[i];\n        distance += diff * diff;\n    }\n    return distance;\n}"
  },
  {
    "path": "src/math/transforms.h",
    "content": "#pragma once\n\n#include <godot_cpp/variant/transform3d.hpp>\n#include <godot_cpp/variant/utility_functions.hpp>\n\nusing namespace godot;\n\nfloat global_to_local_facing_angle(float local_facing_angle, const Transform3D& transform) {\n\n    const Vector3 global_dir(UtilityFunctions::cos(local_facing_angle), 0.f, UtilityFunctions::sin(local_facing_angle));\n\n    const Vector3 local_dir = transform.get_basis().get_quaternion().xform(global_dir);\n\n    return UtilityFunctions::atan2(local_dir.z, local_dir.x);\n}"
  },
  {
    "path": "src/mm_animation_library.cpp",
    "content": "#include \"mm_animation_library.h\"\n\n#include \"common.h\"\n#include \"features/mm_feature.h\"\n#include \"math/hash.h\"\n#include \"math/stats.hpp\"\n#include \"mm_character.h\"\n\nvoid MMAnimationLibrary::bake_data(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton) {\n    motion_data.clear();\n    db_anim_index.clear();\n    db_time_index.clear();\n    db_pose_offset.clear();\n\n    int32_t dim_count = 0;\n    for (auto i = 0; i < features.size(); ++i) {\n        MMFeature* f = Object::cast_to<MMFeature>(features[i]);\n        dim_count += f->get_dimension_count();\n        f->setup_skeleton(p_character, p_player, p_skeleton);\n    }\n\n    TypedArray<StringName> animation_list = get_animation_list();\n\n    // Normalization data\n    std::vector<std::vector<StatsAccumulator>> stats(features.size());\n\n    PackedFloat32Array data;\n    int32_t current_pose_offset = 0;\n    // For every animation\n    for (int64_t animation_index = 0; animation_index < animation_list.size(); animation_index++) {\n        const StringName& anim_name = animation_list[animation_index];\n        Ref<Animation> animation = get_animation(anim_name);\n        db_pose_offset.push_back(current_pose_offset);\n\n        // Initialize features\n        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n            MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n            stats[feature_index].resize(feature->get_dimension_count());\n            feature->setup_for_animation(animation);\n        }\n\n        const double animation_length = animation->get_length();\n        const double time_step = 1.0f / get_sampling_rate();\n        int pose_count = 0;\n        // Every time step\n        for (double time = 0; time < animation_length; time += time_step) {\n            PackedFloat32Array pose_data;\n            // For every feature\n            for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n                const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n                const PackedFloat32Array feature_data = feature->bake_animation_pose(animation, time);\n\n                ERR_FAIL_COND(feature_data.size() != feature->get_dimension_count());\n\n                // Update stats\n                for (int64_t feature_element_index = 0; feature_element_index < feature_data.size(); feature_element_index++) {\n                    stats[feature_index][feature_element_index].add_sample(feature_data[feature_element_index]);\n                }\n\n                pose_data.append_array(feature_data);\n                current_pose_offset += feature->get_dimension_count();\n            }\n\n            ERR_FAIL_COND(pose_data.size() != dim_count);\n\n            // Update dataset\n            data.append_array(pose_data);\n            db_anim_index.push_back(animation_index);\n            db_time_index.push_back(time);\n            pose_count++;\n        }\n    }\n\n    // Compute mean and standard deviation\n    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n\n        PackedFloat32Array feature_means;\n        feature_means.resize(feature->get_dimension_count());\n        PackedFloat32Array feature_std_devs;\n        feature_std_devs.resize(feature->get_dimension_count());\n        PackedFloat32Array feature_mins;\n        feature_mins.resize(feature->get_dimension_count());\n        PackedFloat32Array feature_maxes;\n        feature_maxes.resize(feature->get_dimension_count());\n\n        for (int64_t feature_element_index = 0; feature_element_index < feature->get_dimension_count(); feature_element_index++) {\n            feature_means.set(feature_element_index, stats[feature_index][feature_element_index].get_mean());\n            feature_std_devs.set(feature_element_index, stats[feature_index][feature_element_index].get_standard_deviation());\n            feature_mins.set(feature_element_index, stats[feature_index][feature_element_index].get_min());\n            feature_maxes.set(feature_element_index, stats[feature_index][feature_element_index].get_max());\n        }\n\n        feature->set_means(feature_means);\n        feature->set_std_devs(feature_std_devs);\n        feature->set_mins(feature_mins);\n        feature->set_maxes(feature_maxes);\n    }\n\n    _normalize_data(data, dim_count);\n\n    motion_data = data.duplicate();\n\n    schema_hash = compute_features_hash();\n\n    _kd_tree = std::make_unique<KDTree>(motion_data.ptr(), dim_count, ((int32_t)motion_data.size()) / dim_count);\n    node_indices = PackedInt32Array(_kd_tree->get_node_indices());\n}\n\nMMQueryOutput MMAnimationLibrary::query(const MMQueryInput& p_query_input) {\n    PackedFloat32Array query_vector = PackedFloat32Array();\n    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n        if (!feature) {\n            continue;\n        }\n        PackedFloat32Array feature_data = feature->evaluate_runtime_data(p_query_input);\n        feature->normalize(feature_data.ptrw());\n        query_vector.append_array(feature_data);\n    }\n\n    MMQueryOutput result;\n    result = _search_kd_tree(query_vector);\n    return std::move(result);\n}\n\nint64_t MMAnimationLibrary::get_dim_count() const {\n    int64_t dim_count = 0;\n    for (auto i = 0; i < features.size(); ++i) {\n        MMFeature* f = Object::cast_to<MMFeature>(features[i]);\n        dim_count += f->get_dimension_count();\n    }\n\n    return dim_count;\n}\n\nint64_t MMAnimationLibrary::get_animation_pose_count(String p_animation_name) const {\n    TypedArray<StringName> animation_list = get_animation_list();\n    Ref<Animation> animation = get_animation(p_animation_name);\n    if (animation.is_null()) {\n        return 0;\n    };\n    const double animation_length = animation->get_length();\n    const double time_step = 1.0f / get_sampling_rate();\n    return static_cast<int32_t>(UtilityFunctions::floor(animation_length / time_step));\n}\n\nvoid MMAnimationLibrary::display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D& p_transform, String p_animation_name, int32_t p_pose_index) const {\n    const int32_t anim_index = get_animation_list().find(p_animation_name);\n    const int32_t dim_count = get_dim_count();\n    int32_t start_frame_index = db_pose_offset[anim_index];\n    int32_t frame_index = start_frame_index + p_pose_index * dim_count;\n\n    for (size_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n        const float* frame_motion_data = motion_data.ptr() + frame_index;\n        feature->display_data(p_gizmo, p_transform, frame_motion_data);\n        frame_index += feature->get_dimension_count();\n    }\n}\n\nint64_t MMAnimationLibrary::compute_features_hash() const {\n    int64_t hash = 0;\n    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n        TypedArray<Dictionary> feature_properties = feature->get_property_list();\n        for (int64_t property_index = 0; property_index < feature_properties.size(); property_index++) {\n            // TODO: This needs work, but works for now\n            const Dictionary feature_property = Dictionary(feature_properties[property_index]);\n            const String property_name = feature_property[\"name\"];\n            const uint32_t property_type = feature_property[\"type\"];\n            const bool property_is_stats =\n                property_name == \"means\" ||\n                property_name == \"std_devs\" ||\n                property_name == \"mins\" ||\n                property_name == \"maxes\";\n            const bool is_resource_property = property_name.contains(\"resource\");\n            if (property_type != Variant::OBJECT &&\n                property_type != Variant::NIL &&\n                !property_is_stats &&\n                !is_resource_property) {\n                hash = hash_combine(hash, property_name.hash());\n                hash = hash_combine(hash, feature->get(property_name).hash());\n            }\n        }\n    }\n    return hash;\n}\n\nbool MMAnimationLibrary::needs_baking() const {\n    return schema_hash != compute_features_hash();\n}\n\nvoid MMAnimationLibrary::_normalize_data(PackedFloat32Array& p_data, size_t p_dim_count) const {\n    ERR_FAIL_COND(p_data.size() % p_dim_count != 0);\n\n    for (int64_t frame_index = 0; frame_index < p_data.size(); frame_index += p_dim_count) {\n\n        int dim_index = 0;\n        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n            const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n            feature->normalize(p_data.ptrw() + frame_index + dim_index);\n            dim_index += feature->get_dimension_count();\n        }\n    }\n}\n\nfloat MMAnimationLibrary::_compute_feature_costs(int p_pose_index, const PackedFloat32Array& p_query, Dictionary* p_feature_costs) const {\n    float pose_cost = 0.f;\n    int start_frame_index = p_pose_index * p_query.size();\n    int start_feature_index = 0;\n    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n        if (!feature) {\n            continue;\n        }\n\n        float feature_cost = 0.f;\n        for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {\n            feature_cost += distance_squared((motion_data.ptr() + start_frame_index + start_feature_index + feature_dim_index),\n                                             (p_query.ptr() + start_feature_index + feature_dim_index),\n                                             1) *\n                feature->calculate_normalized_weight(feature_dim_index);\n        }\n\n        if (p_feature_costs) {\n            p_feature_costs->get_or_add(feature->get_class(), feature_cost);\n        }\n\n        pose_cost += feature_cost;\n        start_feature_index += feature->get_dimension_count();\n    }\n    return pose_cost;\n}\n\nMMQueryOutput MMAnimationLibrary::_search_naive(const PackedFloat32Array& p_query) const {\n    float cost = FLT_MAX;\n    MMQueryOutput result;\n    TypedArray<StringName> animation_list = get_animation_list();\n    int32_t dim_count = p_query.size();\n    int64_t best_pose_index = -1;\n    for (int64_t start_frame_index = 0; start_frame_index < motion_data.size(); start_frame_index += dim_count) {\n        int start_feature_index = start_frame_index;\n        float pose_cost = 0.f;\n        Dictionary feature_costs;\n        for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n            const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n            if (!feature) {\n                continue;\n            }\n\n            float feature_cost = 0.f;\n            for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {\n                feature_cost += distance_squared((motion_data.ptr() + start_frame_index + start_feature_index + feature_dim_index),\n                                                 (p_query.ptr() + start_feature_index + feature_dim_index),\n                                                 1) *\n                    feature->calculate_normalized_weight(feature_dim_index);\n            }\n\n            feature_costs.get_or_add(feature->get_class(), feature_cost);\n            pose_cost += feature_cost;\n            start_feature_index += feature->get_dimension_count();\n        }\n\n        if (pose_cost < cost) {\n            cost = pose_cost;\n            best_pose_index = start_frame_index / dim_count;\n        }\n    }\n\n    String library_name = get_path().get_file().get_basename() + \"/\";\n    if (library_name.is_empty()) {\n        library_name = get_name() + \"/\";\n    }\n    result.animation_match = library_name + UtilityFunctions::str(animation_list[db_anim_index[best_pose_index]]);\n    result.time_match = db_time_index[best_pose_index];\n\n    if (include_cost_results) {\n        result.matched_frame_data = motion_data.slice(\n            best_pose_index * dim_count,\n            (best_pose_index + 1) * dim_count);\n\n        result.cost = _compute_feature_costs(\n            best_pose_index,\n            p_query,\n            &result.feature_costs);\n    }\n\n    return result;\n}\n\nMMQueryOutput MMAnimationLibrary::_search_kd_tree(const PackedFloat32Array& p_query) {\n    if (!_kd_tree) {\n        int32_t dim_count = p_query.size();\n        _kd_tree = std::make_unique<KDTree>(dim_count);\n        _kd_tree->rebuild_tree(((int32_t)motion_data.size()) / dim_count, node_indices);\n    }\n\n    std::vector<float> dimension_weights;\n    for (int64_t feature_index = 0; feature_index < features.size(); feature_index++) {\n        const MMFeature* feature = Object::cast_to<MMFeature>(features[feature_index]);\n        if (!feature) {\n            continue;\n        }\n        for (int64_t feature_dim_index = 0; feature_dim_index < feature->get_dimension_count(); feature_dim_index++) {\n            dimension_weights.push_back(feature->calculate_normalized_weight(feature_dim_index));\n        }\n    }\n\n    int nodes_visited = 0;\n    int best_pose_index = _kd_tree->search_nn(\n        motion_data.ptr(),\n        p_query.ptr(),\n        dimension_weights);\n\n    MMQueryOutput result;\n    String library_name = get_path().get_file().get_basename() + \"/\";\n    if (library_name.is_empty()) {\n        library_name = get_name() + \"/\";\n    }\n    TypedArray<StringName> animation_list = get_animation_list();\n\n    result.animation_match = library_name + UtilityFunctions::str(animation_list[db_anim_index[best_pose_index]]);\n    result.time_match = db_time_index[best_pose_index];\n\n    if (include_cost_results) {\n        result.matched_frame_data = motion_data.slice(\n            best_pose_index * p_query.size(),\n            (best_pose_index + 1) * p_query.size());\n\n        result.cost =\n            _compute_feature_costs(\n                best_pose_index,\n                p_query,\n                &result.feature_costs);\n    }\n\n    return result;\n}\n\nvoid MMAnimationLibrary::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::ARRAY, features, PROPERTY_HINT_TYPE_STRING, UtilityFunctions::str(Variant::OBJECT) + '/' + UtilityFunctions::str(Variant::BASIS) + \":MMFeature\");\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::FLOAT, sampling_rate);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::BOOL, include_cost_results);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, motion_data, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, db_anim_index, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_time_index, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_FLOAT32_ARRAY, db_pose_offset, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::INT, schema_hash, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n    BINDER_PROPERTY_PARAMS(MMAnimationLibrary, Variant::PACKED_INT32_ARRAY, node_indices, PROPERTY_HINT_NONE, \"\", DEBUG_PROPERTY_STORAGE_FLAG);\n}\n"
  },
  {
    "path": "src/mm_animation_library.h",
    "content": "#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#include <godot_cpp/classes/animation_library.hpp>\n#include <godot_cpp/classes/editor_node3d_gizmo.hpp>\n#include <godot_cpp/classes/node3d.hpp>\n#include <godot_cpp/classes/object.hpp>\n#include <godot_cpp/classes/ref.hpp>\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/variant/typed_array.hpp>\n\n#include <memory>\n\nclass MMFeature;\nclass MMCharacter;\n\nclass MMAnimationLibrary : public AnimationLibrary {\n    GDCLASS(MMAnimationLibrary, AnimationLibrary)\n\npublic:\n    void bake_data(const MMCharacter* p_character, const AnimationMixer* p_player, const Skeleton3D* p_skeleton);\n    MMQueryOutput query(const MMQueryInput& p_query_input);\n    int64_t get_dim_count() const;\n    int64_t get_animation_pose_count(String p_animation_name) const;\n\n    void display_data(const Ref<EditorNode3DGizmo>& p_gizmo, const Transform3D& p_transform, String p_animation_name, int32_t p_pose_index) const;\n\n    int64_t compute_features_hash() const;\n\n    bool needs_baking() const;\n\n    GETSET(TypedArray<MMFeature>, features)\n    GETSET(float, sampling_rate, 1.f)\n    GETSET(bool, include_cost_results, false);\n\n    // Database data\n    GETSET(PackedFloat32Array, motion_data)\n    GETSET(PackedInt32Array, db_anim_index)\n    GETSET(PackedFloat32Array, db_time_index)\n    GETSET(PackedInt32Array, db_pose_offset)\n    GETSET(int64_t, schema_hash)\n\n    // KD Tree data\n    GETSET(PackedInt32Array, node_indices);\n\nprotected:\n    static void _bind_methods();\n\nprivate:\n    void _normalize_data(PackedFloat32Array& p_data, size_t p_dim_count) const;\n    float _compute_feature_costs(int p_pose_index, const PackedFloat32Array& p_query, Dictionary* p_feature_costs) const;\n\n    MMQueryOutput _search_naive(const PackedFloat32Array& p_query) const;\n    MMQueryOutput _search_kd_tree(const PackedFloat32Array& p_query);\n\n    std::unique_ptr<KDTree> _kd_tree;\n};\n"
  },
  {
    "path": "src/mm_animation_node.cpp",
    "content": "#include \"mm_animation_node.h\"\n\n#include \"editor/animation_tree_handler_plugin.h\"\n#include \"math/spring.hpp\"\n#include \"mm_query.h\"\n\n#include <godot_cpp/classes/animation.hpp>\n#include <godot_cpp/classes/animation_library.hpp>\n#include <godot_cpp/classes/animation_tree.hpp>\n#include <godot_cpp/classes/engine.hpp>\n\n// Only play the matched animation if the matched time position\n// is QUERY_TIME_ERROR away from the current time\nconstexpr float QUERY_TIME_ERROR = 0.05;\n\nPackedFloat32Array MMAnimationNode::_process_animation_node(const PackedFloat64Array& p_playback_info, bool p_test_only) {\n    PackedFloat32Array default_result;\n    default_result.resize(6);\n    default_result.fill(0.0);\n    if (Engine::get_singleton()->is_editor_hint()) {\n        return default_result;\n    }\n\n    if (library.is_empty()) {\n        return default_result;\n    }\n    const double time = p_playback_info[0];\n    const double delta_time = p_playback_info[1];\n    _current_animation_info.time = time;\n    _current_animation_info.delta = delta_time;\n    _current_animation_info.seeked = p_playback_info[4] > 0.5;\n    _current_animation_info.is_external_seeking = p_playback_info[5] > 0.5;\n\n    const bool is_about_to_end = false; // TODO: Implement this\n\n    // We run queries periodically, or when the animation is about to end\n    const bool has_current_animation = !_last_query_output.animation_match.is_empty();\n    const bool should_query = (_time_since_last_query > (1.0 / query_frequency)) || is_about_to_end || !has_current_animation;\n\n    if (!should_query) {\n        _time_since_last_query += delta_time;\n        return _update_current_animation(p_test_only);\n    }\n\n    MMQueryInput* query_input = Object::cast_to<MMQueryInput>(get_parameter(\"motion_matching_input\"));\n\n    if (!query_input || !query_input->is_valid()) {\n        _time_since_last_query += delta_time;\n        return _update_current_animation(p_test_only);\n    }\n\n    _time_since_last_query = 0.f;\n\n    // Run query\n    AnimationTree* animation_tree = Object::cast_to<AnimationTree>(ObjectDB::get_instance(get_processing_animation_tree_instance_id()));\n    Ref<MMAnimationLibrary> animation_library = animation_tree->get_animation_library(library);\n    ERR_FAIL_COND_V_MSG(animation_library.is_null(), PackedFloat32Array(), \"Library not found: \" + library);\n    ERR_FAIL_COND_V_MSG(\n        animation_library->db_anim_index.is_empty() || animation_library->db_time_index.is_empty(),\n        PackedFloat32Array(),\n        \"Library not baked: \" + library);\n    const MMQueryOutput query_output = animation_library->query(*query_input);\n\n    const bool is_same_animation = query_output.animation_match == _last_query_output.animation_match;\n    const bool is_same_time = abs(query_output.time_match - time) < QUERY_TIME_ERROR;\n\n    // Play selected animation\n    if (!is_same_animation || !is_same_time) {\n        const String animation_match = query_output.animation_match;\n        const float time_match = query_output.time_match;\n        if (!p_test_only) {\n            _start_transition(animation_match, time_match);\n        }\n        _last_query_output = query_output;\n        query_input->on_query_result(query_output);\n    }\n\n    return _update_current_animation(p_test_only);\n}\n\nvoid MMAnimationNode::_start_transition(const StringName p_animation, float p_time) {\n    AnimationTree* animation_tree = Object::cast_to<AnimationTree>(ObjectDB::get_instance(get_processing_animation_tree_instance_id()));\n    Ref<Animation> anim = animation_tree->get_animation(p_animation);\n    ERR_FAIL_COND_MSG(anim.is_null(), vformat(\"Animation not found: %s\", p_animation));\n\n    if (!_current_animation_info.name.is_empty() && blending_enabled) {\n        _prev_animation_queue.push_front(_current_animation_info);\n    }\n\n    _current_animation_info.name = p_animation;\n    _current_animation_info.length = anim->get_length();\n    _current_animation_info.time = p_time;\n    _current_animation_info.weight = blending_enabled ? 0.f : 1.f;\n}\n\nPackedFloat32Array MMAnimationNode::_update_current_animation(bool p_test_only) {\n    // const bool will_end = Animation::is_greater_or_equal_approx(\n    //     _current_animation_info.time + _current_animation_info.delta,\n    //     _current_animation_info.length);\n\n    Spring::_simple_spring_damper_exact(\n        _current_animation_info.weight,\n        _current_animation_info.blend_spring_speed,\n        1.,\n        transition_halflife,\n        (real_t)_current_animation_info.delta);\n\n    int pop_count = 0;\n    for (AnimationInfo& prev_info : _prev_animation_queue) {\n        Spring::_simple_spring_damper_exact(\n            prev_info.weight,\n            prev_info.blend_spring_speed,\n            0.f,\n            transition_halflife,\n            _current_animation_info.delta);\n        if (prev_info.weight <= SMALL_NUMBER) {\n            pop_count++;\n        }\n    }\n\n    for (int i = 0; i < pop_count; i++) {\n        _prev_animation_queue.pop_back();\n    }\n\n    // Normalized blend weights in the queue\n    const float inv_blend = 1.f - _current_animation_info.weight;\n    float prev_blend_total = 0.f;\n    for (AnimationInfo& prev_info : _prev_animation_queue) {\n        prev_blend_total += prev_info.weight;\n    }\n\n    for (AnimationInfo& prev_info : _prev_animation_queue) {\n        prev_info.weight *= inv_blend / prev_blend_total;\n    }\n\n    if (!p_test_only) {\n        for (AnimationInfo& prev_info : _prev_animation_queue) {\n            blend_animation(\n                prev_info.name,\n                prev_info.time,\n                prev_info.delta,\n                prev_info.seeked,\n                prev_info.is_external_seeking,\n                prev_info.weight);\n        }\n        blend_animation(\n            _current_animation_info.name,\n            _current_animation_info.time,\n            _current_animation_info.delta,\n            _current_animation_info.seeked,\n            _current_animation_info.is_external_seeking,\n            _current_animation_info.weight);\n    }\n\n    PackedFloat32Array result;\n    result.append(0.0);\n    result.append(_current_animation_info.time);\n    result.append(_current_animation_info.delta);\n    result.append(static_cast<float>(Animation::LoopMode::LOOP_NONE));\n    result.append(false); // TODO: Will End\n    result.append(false); // Is Infinity\n    return result;\n}\n\nArray MMAnimationNode::_get_parameter_list() const {\n    Array parameter_list;\n    parameter_list.push_back(Dictionary(PropertyInfo(Variant::Type::OBJECT, \"motion_matching_input\", PROPERTY_HINT_RESOURCE_TYPE, \"MMQueryInput\", PROPERTY_USAGE_STORAGE)));\n\n    return parameter_list;\n}\n\nVariant MMAnimationNode::_get_parameter_default_value(const StringName& p_parameter) const {\n    Variant ret = AnimationNodeExtension::_get_parameter_default_value(p_parameter);\n    if (ret != Variant()) {\n        return ret;\n    }\n\n    if (p_parameter == StringName(\"motion_matching_input\")) {\n        Ref<MMQueryInput> p;\n        p.instantiate();\n        return p;\n    }\n\n    return Variant();\n}\n\nbool MMAnimationNode::_is_parameter_read_only(const StringName& p_parameter) const {\n    if (AnimationNodeExtension::_is_parameter_read_only(p_parameter)) {\n        return true;\n    }\n\n    if (p_parameter == StringName(\"motion_matching_input\")) {\n        return false;\n    }\n\n    return false;\n}\n\nString MMAnimationNode::_get_caption() const {\n    return \"Motion Matching\";\n}\n\nbool MMAnimationNode::_has_filter() const {\n    return true;\n}\n\nvoid MMAnimationNode::_validate_property(PropertyInfo& p_property) const {\n    if (p_property.name == StringName(\"transition_halflife\")) {\n        if (!blending_enabled) {\n            p_property.usage = PROPERTY_USAGE_NO_EDITOR;\n        }\n    }\n\n    if (p_property.name == StringName(\"library\")) {\n        AnimationTreeHandlerPlugin* plugin = AnimationTreeHandlerPlugin::get_singleton();\n        if (!plugin) {\n            return;\n        }\n        AnimationTree* tree = plugin->get_animation_tree();\n        if (!tree) {\n            return;\n        }\n        String animations;\n        TypedArray<StringName> library_names = tree->get_animation_library_list();\n\n        for (int i = 0; i < library_names.size(); i++) {\n            Ref<MMAnimationLibrary> lib = tree->get_animation_library(library_names[i]);\n            if (lib.is_null()) {\n                continue;\n            }\n            if (!animations.is_empty()) {\n                animations += \",\";\n            }\n            animations += (String)library_names[i];\n        }\n        if (animations.is_empty()) {\n            return;\n        }\n        p_property.hint = PROPERTY_HINT_ENUM;\n        p_property.hint_string = animations;\n    }\n\n    AnimationNodeExtension::_validate_property(p_property);\n}\n\nvoid MMAnimationNode::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::STRING_NAME, library);\n    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, query_frequency);\n    ClassDB::bind_method(D_METHOD(\"get_blending_enabled\"), &MMAnimationNode::get_blending_enabled);\n    ClassDB::bind_method(D_METHOD(\"set_blending_enabled\", \"value\"), &MMAnimationNode::set_blending_enabled);\n    ADD_PROPERTY(PropertyInfo(Variant::BOOL, \"blending_enabled\"), \"set_blending_enabled\", \"get_blending_enabled\");\n\n    BINDER_PROPERTY_PARAMS(MMAnimationNode, Variant::FLOAT, transition_halflife);\n}\n\nDictionary MMAnimationNode::_output_to_dict(const MMQueryOutput& output) {\n    Dictionary result;\n\n    result.get_or_add(\"animation\", output.animation_match);\n    result.get_or_add(\"time\", output.time_match);\n    result.get_or_add(\"frame_data\", output.matched_frame_data);\n    result.merge(output.feature_costs);\n    result.get_or_add(\"total_cost\", output.cost);\n\n    return result;\n}\n\nbool MMAnimationNode::get_blending_enabled() const {\n    return blending_enabled;\n}\n\nvoid MMAnimationNode::set_blending_enabled(bool value) {\n    blending_enabled = value;\n    notify_property_list_changed();\n}\n"
  },
  {
    "path": "src/mm_animation_node.h",
    "content": "#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 <godot_cpp/classes/animation_node_extension.hpp>\n\n#include <queue>\n\nclass MMAnimationNode : public AnimationNodeExtension {\n    GDCLASS(MMAnimationNode, AnimationNodeExtension);\n\npublic:\n    GETSET(StringName, library);\n    GETSET(real_t, query_frequency, 2.0f)\n    GETSET(real_t, transition_halflife, 0.1f)\n\n    bool blending_enabled{true};\n    bool get_blending_enabled() const;\n\n    void set_blending_enabled(bool value);\n\n    virtual PackedFloat32Array _process_animation_node(const PackedFloat64Array& p_playback_info, bool p_test_only);\n    virtual Array _get_parameter_list() const override;\n    virtual Variant _get_parameter_default_value(const StringName& p_parameter) const override;\n    virtual bool _is_parameter_read_only(const StringName& p_parameter) const override;\n    virtual String _get_caption() const override;\n    virtual bool _has_filter() const override;\n\nprotected:\n    void _validate_property(PropertyInfo& p_property) const;\n    static void _bind_methods();\n\nprivate:\n    static Dictionary _output_to_dict(const MMQueryOutput& output);\n\n    struct AnimationInfo {\n        StringName name;\n        double length;\n        double time;\n        double delta;\n        bool seeked;\n        bool is_external_seeking;\n        real_t weight;\n        real_t blend_spring_speed;\n    };\n\n    std::deque<AnimationInfo> _prev_animation_queue;\n    AnimationInfo _current_animation_info;\n\n    void _start_transition(const StringName p_animation, float p_time);\n    PackedFloat32Array _update_current_animation(bool p_test_only);\n\n    MMQueryOutput _last_query_output;\n    float _time_since_last_query{0.f};\n};\n\n#endif // MM_ANIMATION_NODE_H\n"
  },
  {
    "path": "src/mm_bone_state.h",
    "content": "#ifndef MM_BONE_STATE_H\n#define MM_BONE_STATE_H\n\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/core/math.hpp>\n#include <godot_cpp/variant/utility_functions.hpp>\n\n#include <map>\n#include <vector>\n\nusing namespace godot;\n\nstruct BoneState {\n\n    void reset() {\n        pos = Vector3();\n        rot = Quaternion();\n        scl = Vector3(1.0, 1.0, 1.0);\n        vel = Vector3();\n        ang_vel = Vector3();\n        scl_vel = Vector3();\n    }\n\n    Vector3 pos;\n    Quaternion rot;\n    Vector3 scl;\n    Vector3 vel;\n    Vector3 ang_vel;\n    Vector3 scl_vel;\n};\n\nstruct SkeletonState {\n\n    SkeletonState() = default;\n    ~SkeletonState() = default;\n    SkeletonState(size_t size)\n        : bone_states(size) {\n    }\n    SkeletonState(const Skeleton3D* skeleton) {\n        const int32_t bone_count = skeleton->get_bone_count();\n        bone_states = std::vector<BoneState>(bone_count);\n        for (int b = 0; b < bone_count; ++b) {\n            bone_name_to_index[skeleton->get_bone_name(b)] = b;\n        }\n    }\n\n    const BoneState& operator[](int32_t idx) const {\n        return bone_states[idx];\n    }\n\n    BoneState& operator[](int32_t idx) {\n        return bone_states[idx];\n    }\n\n    const BoneState& find_bone_state(const String& name) const {\n        return bone_states[(int32_t)bone_name_to_index.get(name, -1)];\n    }\n\n    BoneState& find_bone_state(const String& name) {\n        return bone_states[(int32_t)bone_name_to_index.get(name, -1)];\n    }\n\n    void reset_velocities() {\n        for (BoneState& state : bone_states) {\n            state.vel = Vector3();\n            state.ang_vel = Vector3();\n            state.scl_vel = Vector3();\n        }\n    }\n\n    std::vector<BoneState> bone_states;\n    Dictionary bone_name_to_index; // Give me a real unordered_map please :(\n};\n\n#endif // MM_BONE_STATE_H\n"
  },
  {
    "path": "src/mm_character.cpp",
    "content": "#include \"mm_character.h\"\n\n#include \"math/spring.hpp\"\n#include \"mm_animation_library.h\"\n#include \"mm_animation_node.h\"\n\n#include <godot_cpp/classes/animation_root_node.hpp>\n#include <godot_cpp/classes/engine.hpp>\n#include <godot_cpp/classes/input.hpp>\n#include <godot_cpp/classes/input_event_mouse_motion.hpp>\n#include <godot_cpp/classes/node3d.hpp>\n#include <godot_cpp/classes/physics_server3d.hpp>\n#include <godot_cpp/classes/physics_test_motion_parameters3d.hpp>\n#include <godot_cpp/classes/physics_test_motion_result3d.hpp>\n#include <godot_cpp/classes/project_settings.hpp>\n#include <godot_cpp/classes/shape3d.hpp>\n#include <godot_cpp/classes/world3d.hpp>\n#include <godot_cpp/core/property_info.hpp>\n#include <godot_cpp/variant/transform3d.hpp>\n#include <godot_cpp/variant/utility_functions.hpp>\n\n#include <cstdint>\n\nMMCharacter::MMCharacter()\n    : CharacterBody3D() {\n}\n\nMMCharacter::~MMCharacter() {\n}\n\nvoid MMCharacter::_update_character(float delta_t) {\n    Vector3 new_velocity = _update_trajectory(delta_t);\n    set_velocity(new_velocity);\n\n    // Rotate the character to face the direction of velocity\n    if (!is_strafing) {\n        Vector3 direction = new_velocity.normalized();\n        set_rotation(Vector3(0.0f, Math::atan2(direction.x, direction.z), 0.0f));\n    } else {\n        set_rotation(Vector3(0.0f, strafe_facing, 0.0f));\n    }\n    move_and_slide();\n}\n\nVector3 MMCharacter::_update_trajectory(float delta_t) {\n    Vector3 current_velocity = get_velocity();\n    Vector3 current_up_direction = get(\"up_direction\");\n\n    // Update the velocity.\n    const Vector3 up_velocity = current_velocity.dot(current_up_direction) * current_up_direction;\n    Vector3 ground_velocity = current_velocity - up_velocity;\n    Array result = Spring::simple_spring_damper_exact(\n        ground_velocity,\n        _spring_acceleration,\n        target_velocity,\n        halflife,\n        delta_t);\n    Vector3 new_velocity = (Vector3)result[0] + up_velocity;\n    _spring_acceleration = result[1];\n\n    if (!is_on_floor()) {\n        new_velocity += get_gravity() * delta_t;\n    }\n\n    _generate_trajectory(delta_t);\n\n    _update_history(delta_t);\n\n    return new_velocity;\n}\n\nMMTrajectoryPoint MMCharacter::_get_current_trajectory_point() const {\n    MMTrajectoryPoint point;\n    point.position = get_global_position();\n    point.velocity = get_velocity();\n    point.collision_state.on_floor = is_on_floor();\n    point.collision_state.against_wall = is_on_wall();\n    point.collision_state.floor_normal = get_floor_normal();\n    point.collision_state.wall_normal = get_wall_normal();\n\n    if (get_velocity().length_squared() > SMALL_NUMBER && !is_strafing) {\n        Vector3 direction = get_velocity().normalized();\n        point.facing_angle = Math::atan2(direction.x, direction.z);\n    } else {\n        point.facing_angle = strafe_facing;\n    }\n    return point;\n}\n\nvoid MMCharacter::_generate_trajectory(float delta_time) {\n    const float delta_t = trajectory_delta_time;\n\n    MMTrajectoryPoint point = _get_current_trajectory_point();\n\n    Vector3 spring_acceleration = _spring_acceleration;\n\n    // The first point represents the player's current position, and is not considered part\n    // of the trajectory for motion matching\n    for (size_t i = 0; i < trajectory_point_count + 1; i++) {\n        _trajectory[i] = point;\n\n        Vector3 current_up_direction = get(\"up_direction\");\n\n        // Update velocity\n        const Vector3 up_velocity = point.velocity.dot(current_up_direction) * current_up_direction;\n        Vector3 ground_velocity = point.velocity - up_velocity;\n        Array result = Spring::simple_spring_damper_exact(ground_velocity, spring_acceleration, target_velocity, halflife, delta_t);\n        point.velocity = (Vector3)result[0] + up_velocity;\n        spring_acceleration = result[1];\n\n        // Update facing\n        if (point.velocity.length_squared() > SMALL_NUMBER && !is_strafing) {\n            Vector3 direction = point.velocity.normalized();\n            point.facing_angle = Math::atan2(direction.x, direction.z);\n        } else {\n            point.facing_angle = strafe_facing;\n        }\n\n        if (check_environment) {\n            // Update point with environment\n            _move_with_collisions(point, delta_t);\n            _fall_to_floor(point, delta_t);\n        } else {\n            point.position += point.velocity * delta_t;\n        }\n    }\n}\n\nvoid MMCharacter::_update_history(double delta_t) {\n    if (!_history_buffer.is_empty()) {\n        float time_in_past = 0.f;\n        uint32_t current_index = 0;\n        for (int i = _history_buffer.size() - 1; i >= 0; --i) {\n            if (time_in_past >= history_delta_time) {\n                _trajectory_history[current_index] = _history_buffer[i];\n                time_in_past = 0.f;\n                current_index++;\n            }\n\n            if (current_index >= history_point_count) {\n                break;\n            }\n\n            time_in_past += delta_t;\n        }\n    }\n\n    _history_buffer.push(_get_current_trajectory_point());\n}\n\nvoid MMCharacter::_move_with_collisions(MMTrajectoryPoint& point, float delta_t) {\n    const Vector3 motion = point.velocity * delta_t;\n\n    Ref<PhysicsTestMotionParameters3D> params;\n    params.instantiate();\n    Vector3 current_up_direction = get(\"up_direction\");\n    params->set_from(point.get_transform(current_up_direction));\n    params->set_motion(motion);\n    params->set_max_collisions(6);\n    params->set_recovery_as_collision_enabled(false);\n\n    Ref<PhysicsTestMotionResult3D> collision_result;\n    collision_result.instantiate();\n\n    bool is_colliding = PhysicsServer3D::get_singleton()->body_test_motion(\n        get_rid(),\n        params,\n        collision_result);\n\n    if (!is_colliding) {\n        // We move in the direction of motion as usual\n        point.position += motion;\n\n        // We reset the collision state\n        point.collision_state.reset();\n        return;\n    }\n\n    _fill_collision_state(collision_result, point.collision_state);\n\n    // Update final position\n    Vector3 result_velocity = point.velocity;\n    point.position += collision_result->get_travel();\n\n    if (point.collision_state.against_wall) {\n\n        result_velocity = result_velocity.slide(point.collision_state.wall_normal);\n        // Slide must be horizontal, no climbing walls!\n        // Note: This would be a good place to add a climbing feature\n        result_velocity.y = 0.0;\n    }\n\n    if (point.collision_state.on_floor) {\n        result_velocity = result_velocity.slide(point.collision_state.floor_normal);\n    }\n\n    point.velocity = result_velocity;\n    // Move the remaining part of motion\n    point.position += point.velocity * delta_t * (1.0 - collision_result->get_collision_safe_fraction());\n}\n\nvoid MMCharacter::_fill_collision_state(const Ref<PhysicsTestMotionResult3D> collision_result, MMCollisionState& state) {\n    real_t wall_depth = -1.0;\n    real_t floor_depth = -1.0;\n\n    state.on_floor = false;\n    for (int i = collision_result->get_collision_count() - 1; i >= 0; i--) {\n        Vector3 current_up_direction = get(\"up_direction\");\n        real_t floor_angle = Math::acos(collision_result->get_collision_normal(i).dot(current_up_direction));\n        if (floor_angle <= get_floor_max_angle() && collision_result->get_collision_depth(i) > floor_depth) {\n            state.on_floor = true;\n            state.floor_normal = collision_result->get_collision_normal(i);\n            state.floor_position = collision_result->get_collision_point(i);\n            floor_depth = collision_result->get_collision_depth(i);\n            continue;\n        }\n\n        state.against_wall = true;\n        if (collision_result->get_collision_depth(i) > wall_depth) {\n            state.against_wall = true;\n            state.wall_normal = collision_result->get_collision_normal(i);\n            wall_depth = collision_result->get_collision_depth(i);\n        }\n    }\n}\n\nvoid MMCharacter::_fall_to_floor(MMTrajectoryPoint& point, float delta_t) {\n    const Vector3 motion = get_gravity() * delta_t * delta_t;\n    Ref<PhysicsTestMotionParameters3D> params;\n    params.instantiate();\n    Vector3 current_up_direction = get(\"up_direction\");\n    params->set_from(point.get_transform(current_up_direction));\n    params->set_motion(motion);\n\n    Ref<PhysicsTestMotionResult3D> collision_result;\n    collision_result.instantiate();\n\n    if (PhysicsServer3D::get_singleton()->body_test_motion(\n            get_rid(),\n            params,\n            collision_result)) {\n        point.position += collision_result->get_travel();\n        point.velocity.y = 0.0;\n        point.collision_state.on_floor = true;\n        point.collision_state.floor_normal = collision_result->get_collision_normal();\n    } else {\n        point.position += motion;\n        point.velocity += get_gravity() * delta_t;\n        point.collision_state.on_floor = false;\n    }\n}\n\nvoid MMCharacter::_fill_query_input(MMQueryInput& input) {\n    input.controller_velocity = get_velocity();\n    input.trajectory = get_trajectory();\n    input.trajectory_history = get_trajectory_history();\n    input.controller_transform = get_global_transform();\n    input.character_transform = skeleton->get_global_transform();\n    input.skeleton_state = _skeleton_state;\n    input.on_query_result = std::bind(&MMCharacter::_on_query_result, this, std::placeholders::_1);\n}\n\nvoid MMCharacter::_update_query() {\n    // Fill query_input with data from the controller\n    if (animation_tree) {\n        Ref<MMQueryInput> query_input;\n        query_input.instantiate();\n        _fill_query_input(**query_input);\n        for (const StringName& param : _mm_input_params) {\n            animation_tree->set(param, query_input);\n        }\n    }\n}\n\nvoid MMCharacter::_apply_root_motion() {\n    skeleton->set_quaternion(\n        skeleton->get_quaternion() * animation_tree->get_root_motion_rotation());\n\n    const Vector3 movement_delta = (animation_tree->get_root_motion_rotation_accumulator().inverse() *\n                                    skeleton->get_quaternion())\n                                       .xform(animation_tree->get_root_motion_position());\n\n    skeleton->set_global_position(skeleton->get_global_position() + movement_delta);\n}\n\nvoid MMCharacter::_update_synchronizer(double delta_t) {\n    if (synchronizer.is_null()) {\n        return;\n    }\n\n    synchronizer->sync(this, skeleton, delta_t);\n}\n\nvoid MMCharacter::_fill_current_skeleton_state(SkeletonState& p_state) const {\n    for (int b = 0; b < skeleton->get_bone_count(); ++b) {\n        Transform3D bone_pose = skeleton->get_bone_global_pose(b);\n        p_state[b].pos = bone_pose.origin;\n        p_state[b].vel = Vector3();\n        p_state[b].rot = bone_pose.basis.get_rotation_quaternion();\n        p_state[b].ang_vel = Vector3();\n        p_state[b].scl = bone_pose.basis.get_scale();\n        p_state[b].scl_vel = Vector3();\n    }\n}\n\nvoid MMCharacter::_reset_skeleton_state() {\n    _fill_current_skeleton_state(_skeleton_state);\n    _skeleton_state.reset_velocities();\n}\n\nvoid MMCharacter::_update_skeleton_state(double delta_t) {\n    SkeletonState current_state(skeleton);\n    _fill_current_skeleton_state(current_state);\n\n    for (int b = 0; b < skeleton->get_bone_count(); ++b) {\n        BoneState& state = _skeleton_state[b];\n        const BoneState& current = current_state[b];\n\n        state.vel = (current.pos - state.pos) / delta_t;\n        state.ang_vel = Spring::quat_differentiate_angular_velocity(current.rot, state.rot, delta_t);\n        state.scl_vel = (current.scl - state.scl) / delta_t;\n\n        state.pos = current.pos;\n        state.rot = current.rot;\n        state.scl = current.scl;\n    }\n}\n\nvoid MMCharacter::_on_query_result(const MMQueryOutput& output) {\n    if (emit_result_signal) {\n        emit_signal(\"on_query_result\", _output_to_dict(output));\n    }\n}\n\nDictionary MMCharacter::_output_to_dict(const MMQueryOutput& output) {\n    Dictionary result;\n\n    result.get_or_add(\"animation\", output.animation_match);\n    result.get_or_add(\"time\", output.time_match);\n    result.get_or_add(\"frame_data\", output.matched_frame_data);\n    result.merge(output.feature_costs);\n    result.get_or_add(\"total_cost\", output.cost);\n\n    return result;\n}\n\nAnimationMixer* MMCharacter::get_animation_mixer() const {\n    return animation_tree;\n}\n\nvoid MMCharacter::_bind_methods() {\n    ClassDB::bind_method(D_METHOD(\"get_trajectory\"), &MMCharacter::get_trajectory_typed_array);\n    ClassDB::bind_method(D_METHOD(\"get_trajectory_history\"), &MMCharacter::get_trajectory_history_typed_array);\n    ClassDB::bind_method(D_METHOD(\"get_skeleton_state\"), &MMCharacter::get_skeleton_state);\n\n    ADD_SIGNAL(MethodInfo(\"on_query_result\", PropertyInfo(Variant::DICTIONARY, \"data\")));\n\n    ADD_GROUP(\"Motion Matching\", \"\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, skeleton, PROPERTY_HINT_NODE_TYPE, \"Skeleton3D\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, animation_tree, PROPERTY_HINT_NODE_TYPE, \"AnimationTree\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::OBJECT, synchronizer, PROPERTY_HINT_RESOURCE_TYPE, \"MMSynchronizer\");\n\n    ADD_GROUP(\"Movement\", \"\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, halflife, PROPERTY_HINT_RANGE, \"0.0,1.0,0.01,or_greater\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, is_strafing);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, strafe_facing, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::VECTOR3, target_velocity, PROPERTY_HINT_NONE, \"\", PROPERTY_USAGE_STORAGE);\n\n    ADD_GROUP(\"Trajectory\", \"\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, check_environment);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::INT, trajectory_point_count);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, trajectory_delta_time);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::INT, history_point_count);\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::FLOAT, history_delta_time);\n\n    ADD_GROUP(\"Debug\", \"\");\n    BINDER_PROPERTY_PARAMS(MMCharacter, Variant::BOOL, emit_result_signal);\n}\n\nvoid MMCharacter::_notification(int p_what) {\n    switch (p_what) {\n    case NOTIFICATION_PHYSICS_PROCESS: {\n        if (Engine::get_singleton()->is_editor_hint()) {\n            return;\n        }\n        double delta = get_physics_process_delta_time();\n\n        _update_character(delta);\n\n        if (!skeleton) {\n            return;\n        }\n\n        _update_skeleton_state(delta);\n\n        _update_query();\n\n        _apply_root_motion();\n\n        _update_synchronizer(delta);\n    } break;\n    case NOTIFICATION_READY: {\n        set_physics_process(true);\n\n        if (Engine::get_singleton()->is_editor_hint()) {\n            return;\n        }\n\n        while (!_history_buffer.is_full()) {\n            _history_buffer.push(_get_current_trajectory_point());\n        }\n\n        _trajectory.resize(trajectory_point_count + 1);\n        _trajectory_history.resize(history_point_count);\n\n        if (animation_tree) {\n            animation_tree->set_callback_mode_process(AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS);\n\n            TypedArray<Dictionary> properties = animation_tree->get_property_list();\n            for (int property_idx = 0; property_idx < properties.size(); ++property_idx) {\n                const Dictionary& prop = properties[property_idx];\n                const bool is_mm_input = prop[\"hint_string\"] == \"MMQueryInput\";\n                if (is_mm_input) {\n                    _mm_input_params.push_back(prop[\"name\"]);\n                }\n            }\n        }\n\n        if (skeleton && animation_tree) {\n            StringName root_node_path = animation_tree->get_root_motion_track().get_concatenated_subnames();\n            _root_bone_idx = skeleton->find_bone(root_node_path);\n\n            skeleton->set_as_top_level(true);\n            skeleton->reset_bone_poses();\n\n            _skeleton_state = SkeletonState(skeleton);\n            _reset_skeleton_state();\n        }\n\n    } break;\n    }\n}\n"
  },
  {
    "path": "src/mm_character.h",
    "content": "#ifndef MM_CHARACTER_H\n#define MM_CHARACTER_H\n\n#include \"circular_buffer.h\"\n#include \"common.h\"\n#include \"mm_character.h\"\n#include \"mm_query.h\"\n#include \"mm_trajectory_point.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\n#include <godot_cpp/classes/animation_tree.hpp>\n#include <godot_cpp/classes/character_body3d.hpp>\n#include <godot_cpp/classes/input_event.hpp>\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/variant/array.hpp>\n#include <godot_cpp/variant/variant.hpp>\n\nusing namespace godot;\n\nclass MMCharacter : public CharacterBody3D {\n    GDCLASS(MMCharacter, CharacterBody3D)\n\npublic:\n    MMCharacter();\n    virtual ~MMCharacter();\n\npublic:\n    const std::vector<MMTrajectoryPoint>& get_trajectory() const {\n        return _trajectory;\n    }\n\n    const std::vector<MMTrajectoryPoint>& get_trajectory_history() const {\n        return _trajectory_history;\n    }\n\n    TypedArray<Dictionary> get_skeleton_state() const {\n        TypedArray<Dictionary> result;\n        for (const BoneState& state : _skeleton_state.bone_states) {\n            Dictionary character_data;\n            character_data.get_or_add(\"position\", state.pos);\n            character_data.get_or_add(\"velocity\", state.vel);\n            result.push_back(character_data);\n        }\n        return result;\n    }\n\n    TypedArray<Dictionary> get_trajectory_typed_array() const {\n        return trajectory_to_dict(_trajectory);\n    }\n\n    TypedArray<Dictionary> get_trajectory_history_typed_array() const {\n        return trajectory_to_dict(_trajectory_history);\n    }\n\n    TypedArray<Dictionary> trajectory_to_dict(const std::vector<MMTrajectoryPoint>& p_trajectory) const {\n        TypedArray<Dictionary> result;\n        for (const MMTrajectoryPoint& point : p_trajectory) {\n            Dictionary trajectory_data;\n            trajectory_data.get_or_add(\"position\", point.position);\n            trajectory_data.get_or_add(\"velocity\", point.velocity);\n            trajectory_data.get_or_add(\"facing\", point.facing_angle);\n            trajectory_data.get_or_add(\"on_floor\", point.collision_state.on_floor);\n            result.push_back(trajectory_data);\n        }\n        return result;\n    }\n\n    AnimationMixer* get_animation_mixer() const;\n\n    // Trajectory\n    GETSET(float, trajectory_delta_time, 0.5f);\n    GETSET(float, history_delta_time, 0.5f);\n    GETSET(uint32_t, trajectory_point_count, 10);\n    GETSET(uint32_t, history_point_count, 3);\n    GETSET(bool, check_environment, true);\n\n    // Movement\n    GETSET(float, halflife, 0.5f);\n    GETSET(bool, is_strafing, false);\n    GETSET(float, strafe_facing, 0.f);\n    GETSET(Vector3, target_velocity);\n\n    // Motion Matching\n    GETSET(Skeleton3D*, skeleton);\n    GETSET(AnimationTree*, animation_tree);\n    GETSET(Ref<MMSynchronizer>, synchronizer);\n\n    // Debug\n    GETSET(bool, emit_result_signal, false);\n\nprotected:\n    static constexpr size_t HISTORY_BUFFER_SIZE{100}; // Around 1.6s\n    static void _bind_methods();\n\npublic:\n    void _notification(int p_what);\n\nprivate:\n    void _update_character(float delta_t);\n\n    // Trajectory\n    Vector3 _update_trajectory(float p_delta_t);\n    MMTrajectoryPoint _get_current_trajectory_point() const;\n    void _generate_trajectory(float delta_time);\n    void _update_history(double delta_t);\n    void _move_with_collisions(MMTrajectoryPoint& point, float delta_t);\n    void _fill_collision_state(const Ref<PhysicsTestMotionResult3D> collision_result, MMCollisionState& state);\n    void _fall_to_floor(MMTrajectoryPoint& point, float delta_t);\n\n    // Motion Matching\n    void _fill_query_input(MMQueryInput& input);\n    void _update_query();\n    void _apply_root_motion();\n    void _update_synchronizer(double delta_t);\n\n    // Skeleton State\n    void _fill_current_skeleton_state(SkeletonState& p_state) const;\n    void _reset_skeleton_state();\n    void _update_skeleton_state(double delta_t);\n\n    void _on_query_result(const MMQueryOutput& output);\n\n    static Dictionary _output_to_dict(const MMQueryOutput& output);\n\nprivate:\n    // Controller\n    Vector3 _spring_acceleration;\n\n    // Trajectory\n    std::vector<MMTrajectoryPoint> _trajectory;\n    std::vector<MMTrajectoryPoint> _trajectory_history;\n    CircularBuffer<MMTrajectoryPoint> _history_buffer{HISTORY_BUFFER_SIZE};\n\n    // Skeleton State\n    SkeletonState _skeleton_state;\n    int32_t _root_bone_idx{-1};\n\n    // Motion Matching parameters\n    List<StringName> _mm_input_params;\n};\n\n#endif // MM_CHARACTER_H\n"
  },
  {
    "path": "src/mm_query.h",
    "content": "#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\"\n\n#include <functional>\n#include <godot_cpp/classes/skeleton3d.hpp>\n#include <godot_cpp/variant/string.hpp>\n#include <godot_cpp/variant/transform3d.hpp>\n#include <godot_cpp/variant/vector3.hpp>\n\nusing namespace godot;\n\nstruct MMQueryOutput {\n    String animation_match;\n    float time_match;\n    float cost;\n    PackedFloat32Array matched_frame_data;\n    Dictionary feature_costs;\n};\n\nclass MMQueryInput : public RefCounted {\n    GDCLASS(MMQueryInput, RefCounted);\n\npublic:\n    // Add data required for the query here\n    Vector3 controller_velocity;\n    Transform3D controller_transform;\n    Transform3D character_transform;\n    std::vector<MMTrajectoryPoint> trajectory;\n    std::vector<MMTrajectoryPoint> trajectory_history;\n    SkeletonState skeleton_state;\n    std::function<void(const MMQueryOutput&)> on_query_result;\n\n    bool is_valid() const {\n        // Add validation logic here\n        return !trajectory.empty();\n    }\n\nprotected:\n    static void _bind_methods() {\n    }\n};\n\n#endif // MM_QUERY_H\n"
  },
  {
    "path": "src/mm_trajectory_point.cpp",
    "content": "#include \"mm_trajectory_point.h\""
  },
  {
    "path": "src/mm_trajectory_point.h",
    "content": "#ifndef MM_TRAJECTORY_POINT_H\n#define MM_TRAJECTORY_POINT_H\n\n#include \"common.h\"\n\n#include <godot_cpp/classes/ref_counted.hpp>\n#include <godot_cpp/variant/vector3.hpp>\n\nusing namespace godot;\n\nstruct MMCollisionState {\n    bool on_floor = false;\n    Vector3 floor_normal;\n    Vector3 floor_position;\n    bool against_wall = false;\n    Vector3 wall_normal;\n\n    void reset() {\n        on_floor = false;\n        against_wall = false;\n    }\n};\n\nstruct MMTrajectoryPoint {\n    Vector3 position;\n    Vector3 velocity;\n    float facing_angle;\n\n    MMCollisionState collision_state;\n\n    Transform3D get_transform(const Vector3& up_axis = Vector3(0, 1, 0)) const {\n        Transform3D result;\n        result.origin = position;\n        result.set_basis(Basis(up_axis, facing_angle));\n        return result;\n    }\n};\n#endif // MM_TRAJECTORY_POINT_H\n"
  },
  {
    "path": "src/modifiers/damped_skeleton_modifier.cpp",
    "content": "#include \"modifiers/damped_skeleton_modifier.h\"\n\n#include \"damped_skeleton_modifier.h\"\n#include \"math/spring.hpp\"\n\nvoid DampedSkeletonModifier::_process_modification() {\n    if (!is_active()) {\n        return;\n    }\n\n    Skeleton3D* skeleton = get_skeleton();\n    if (!skeleton) {\n        return;\n    }\n    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();\n    if (!skeleton->get_bone_count()) {\n        return;\n    }\n    for (int32_t bone_id = 0; bone_id < skeleton->get_bone_count(); ++bone_id) {\n        if (skeleton->get_bone_parent(bone_id) == -1) {\n            // Skip root bone\n            continue;\n        }\n\n        const Vector3 desired_pos = skeleton->get_bone_pose_position(bone_id);\n        const Quaternion desired_rot = skeleton->get_bone_pose_rotation(bone_id);\n\n        Spring::_simple_spring_damper_exact(\n            _skeleton_state[bone_id].pos,\n            _skeleton_state[bone_id].vel,\n            desired_pos,\n            halflife,\n            delta);\n\n        Spring::_simple_spring_damper_exact(\n            _skeleton_state[bone_id].rot,\n            _skeleton_state[bone_id].ang_vel,\n            desired_rot,\n            halflife,\n            delta);\n\n        skeleton->set_bone_pose_position(bone_id, _skeleton_state[bone_id].pos);\n        skeleton->set_bone_pose_rotation(bone_id, _skeleton_state[bone_id].rot);\n    }\n}\n\nvoid DampedSkeletonModifier::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(DampedSkeletonModifier, Variant::FLOAT, halflife);\n}\nvoid DampedSkeletonModifier::_notification(int32_t p_what) {\n    switch (p_what) {\n    case NOTIFICATION_READY:\n        Skeleton3D* skeleton = get_skeleton();\n\n        if (skeleton) {\n            _skeleton_state = SkeletonState(skeleton);\n            for (int b = 0; b < skeleton->get_bone_count(); ++b) {\n                BoneState& bone = _skeleton_state[b];\n                bone.pos = skeleton->get_bone_pose_position(b);\n                bone.rot = skeleton->get_bone_pose_rotation(b);\n                bone.scl = skeleton->get_bone_pose_scale(b);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/modifiers/damped_skeleton_modifier.h",
    "content": "#ifndef DAMPED_SKELETON_MODIFIER_H\n#define DAMPED_SKELETON_MODIFIER_H\n\n#include \"common.h\"\n#include \"mm_bone_state.h\"\n\n#include <godot_cpp/classes/skeleton_modifier3d.hpp>\n\nusing namespace godot;\n\nclass DampedSkeletonModifier : public SkeletonModifier3D {\n    GDCLASS(DampedSkeletonModifier, SkeletonModifier3D)\n\npublic:\n    void _notification(int32_t p_what);\n    virtual void _process_modification() override;\n\n    GETSET(float, halflife, 0.1);\n\nprotected:\n    static void _bind_methods();\n\nprivate:\n    SkeletonState _skeleton_state;\n};\n#endif // DAMPED_SKELETON_MODIFIER_H\n"
  },
  {
    "path": "src/register_types.cpp",
    "content": "#include \"register_types.h\"\n\n#include <gdextension_interface.h>\n\n#include \"mm_character.h\"\n\n#include \"editor/animation_post_import_plugin.h\"\n#include \"editor/animation_tree_handler_plugin.h\"\n#include \"editor/mm_data_tab.h\"\n#include \"editor/mm_editor.h\"\n#include \"editor/mm_editor_gizmo_plugin.h\"\n#include \"editor/mm_editor_plugin.h\"\n#include \"editor/mm_visualization_tab.h\"\n\n#include \"features/mm_bone_data_feature.h\"\n#include \"features/mm_feature.h\"\n#include \"features/mm_trajectory_feature.h\"\n\n#include \"modifiers/damped_skeleton_modifier.h\"\n\n#include \"synchronizers/mm_clamp_synchronizer.h\"\n#include \"synchronizers/mm_mix_synchronizer.h\"\n#include \"synchronizers/mm_rootmotion_synchronizer.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\n#include \"mm_animation_library.h\"\n#include \"mm_animation_node.h\"\n#include \"mm_query.h\"\n#include \"mm_trajectory_point.h\"\n\nvoid initialize_motion_matching_module(ModuleInitializationLevel p_level) {\n    if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) {\n        ClassDB::register_abstract_class<MMFeature>();\n        ClassDB::register_class<MMTrajectoryFeature>();\n        ClassDB::register_class<MMBoneDataFeature>();\n\n        ClassDB::register_class<MMAnimationLibrary>();\n        ClassDB::register_class<MMAnimationNode>();\n        ClassDB::register_class<MMQueryInput>();\n\n        ClassDB::register_class<MMCharacter>();\n\n        ClassDB::register_class<DampedSkeletonModifier>();\n        ClassDB::register_abstract_class<MMSynchronizer>();\n        ClassDB::register_class<MMClampSynchronizer>();\n        ClassDB::register_class<MMRootMotionSynchronizer>();\n        ClassDB::register_class<MMMixSynchronizer>();\n    }\n\n    if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {\n        ClassDB::register_internal_class<AnimationTreeHandlerPlugin>();\n        ClassDB::register_internal_class<AnimationPostImportPlugin>();\n        ClassDB::register_internal_class<MMEditorGizmoPlugin>();\n        ClassDB::register_internal_class<MMEditor>();\n        ClassDB::register_internal_class<MMEditorPlugin>();\n        ClassDB::register_internal_class<MMDataTab>();\n        ClassDB::register_internal_class<MMVisualizationTab>();\n\n        EditorPlugins::add_by_type<MMEditorPlugin>();\n        EditorPlugins::add_by_type<AnimationTreeHandlerPlugin>();\n        EditorPlugins::add_by_type<AnimationPostImportPlugin>();\n    }\n}\n\nvoid uninitialize_motion_matching_module(ModuleInitializationLevel p_level) {\n}\n\nextern \"C\" {\n// Initialization.\nGDExtensionBool GDE_EXPORT example_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) {\n    godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);\n\n    init_obj.register_initializer(initialize_motion_matching_module);\n    init_obj.register_terminator(uninitialize_motion_matching_module);\n    init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SERVERS);\n\n    return init_obj.init();\n}\n}\n"
  },
  {
    "path": "src/register_types.h",
    "content": "#ifndef MOTION_MATCHING_REGISTER_TYPES_H\n#define MOTION_MATCHING_REGISTER_TYPES_H\n\n#include <godot_cpp/core/class_db.hpp>\n\nusing namespace godot;\n\nvoid initialize_motion_matching_module(ModuleInitializationLevel p_level);\nvoid uninitialize_motion_matching_module(ModuleInitializationLevel p_level);\n#endif // MOTION_MATCHING_REGISTER_TYPES_H\n"
  },
  {
    "path": "src/synchronizers/mm_clamp_synchronizer.cpp",
    "content": "#include \"synchronizers/mm_clamp_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMClampSynchronizer::sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) {\n    const Vector3 position_delta = p_character->get_global_position() - p_controller->get_global_position();\n    const real_t delta_length = position_delta.length();\n\n    Vector3 character_position = p_character->get_global_position();\n    if (position_delta.length() > clamp_distance) {\n        character_position = p_controller->get_global_position() + position_delta.normalized() * clamp_distance;\n    }\n    character_position.y = p_character->get_global_position().y;\n\n    p_character->set_global_position(character_position);\n}\n\nvoid MMClampSynchronizer::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(MMClampSynchronizer, Variant::FLOAT, clamp_distance);\n}\n"
  },
  {
    "path": "src/synchronizers/mm_clamp_synchronizer.h",
    "content": "#ifndef MM_CLAMP_SYNCHRONIZER_H\n#define MM_CLAMP_SYNCHRONIZER_H\n\n#include \"common.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\nclass MMClampSynchronizer : public MMSynchronizer {\n    GDCLASS(MMClampSynchronizer, MMSynchronizer)\n\npublic:\n    virtual void sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) override;\n\n    GETSET(float, clamp_distance, 10.f);\n\nprotected:\n    static void _bind_methods();\n};\n\n#endif // MM_CLAMP_SYNCHRONIZER_H\n"
  },
  {
    "path": "src/synchronizers/mm_mix_synchronizer.cpp",
    "content": "#include \"mm_mix_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMMixSynchronizer::sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) {\n\n    Vector3 position_delta = p_character->get_global_position() - p_controller->get_global_position();\n    Vector3 rotation_delta = p_character->get_global_rotation() - p_controller->get_global_rotation();\n\n    Vector3 controller_position = p_controller->get_global_position() + position_delta * root_motion_amount;\n\n    Vector3 character_position = p_character->get_global_position() - position_delta * (1.0 - root_motion_amount);\n    Vector3 character_rotation = p_character->get_global_rotation() - rotation_delta * (1.0 - root_motion_amount);\n\n    controller_position.y = p_controller->get_global_position().y;\n    character_position.y = p_controller->get_global_position().y;\n\n    p_controller->set_global_position(controller_position);\n    p_character->set_global_position(character_position);\n\n    // p_character->set_global_rotation(character_rotation);\n}\n\nvoid MMMixSynchronizer::_bind_methods() {\n    BINDER_PROPERTY_PARAMS(MMMixSynchronizer, Variant::FLOAT, root_motion_amount, PROPERTY_HINT_RANGE, \"0.0,1.0,0.01\");\n}\n"
  },
  {
    "path": "src/synchronizers/mm_mix_synchronizer.h",
    "content": "#pragma once\n\n#include \"common.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\nclass MMMixSynchronizer : public MMSynchronizer {\n    GDCLASS(MMMixSynchronizer, MMSynchronizer)\n\npublic:\n    virtual void sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) override;\n\n    GETSET(float, root_motion_amount, 1.0f)\nprotected:\n    static void _bind_methods();\n};"
  },
  {
    "path": "src/synchronizers/mm_rootmotion_synchronizer.cpp",
    "content": "#include \"mm_rootmotion_synchronizer.h\"\n\n#include \"mm_character.h\"\n\nvoid MMRootMotionSynchronizer::sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) {\n    const Vector3 position_delta = p_character->get_global_position() - p_controller->get_global_position();\n    Vector3 controller_position = p_controller->get_global_position();\n    Vector3 character_position = p_character->get_global_position();\n\n    controller_position.x = p_character->get_global_position().x;\n    controller_position.z = p_character->get_global_position().z;\n\n    character_position.y = p_controller->get_global_position().y;\n\n    p_controller->set_global_position(controller_position);\n    p_controller->set_global_basis(p_character->get_global_basis());\n\n    p_character->set_global_position(character_position);\n}\nvoid MMRootMotionSynchronizer::_bind_methods() {\n}\n"
  },
  {
    "path": "src/synchronizers/mm_rootmotion_synchronizer.h",
    "content": "#ifndef MM_ROOTMOTION_SYNCHRONIZER_H\n#define MM_ROOTMOTION_SYNCHRONIZER_H\n\n#include \"common.h\"\n#include \"synchronizers/mm_synchronizer.h\"\n\nclass MMRootMotionSynchronizer : public MMSynchronizer {\n    GDCLASS(MMRootMotionSynchronizer, MMSynchronizer)\n\npublic:\n    virtual void sync(MMCharacter* p_controller, Node3D* p_character, float p_delta_time) override;\n\nprotected:\n    static void _bind_methods();\n};\n\n#endif // MM_ROOTMOTION_SYNCHRONIZER_H\n"
  },
  {
    "path": "src/synchronizers/mm_synchronizer.cpp",
    "content": "#include \"synchronizers/mm_synchronizer.h\"\n\n#include \"mm_synchronizer.h\"\n\nvoid MMSynchronizer::_bind_methods() {\n}\n"
  },
  {
    "path": "src/synchronizers/mm_synchronizer.h",
    "content": "#ifndef MM_SYNCHRONIZER_H\n#define MM_SYNCHRONIZER_H\n\n#include \"common.h\"\n\n#include <godot_cpp/classes/node3d.hpp>\n#include <godot_cpp/classes/resource.hpp>\n\nusing namespace godot;\n\nclass MMCharacter;\n\nclass MMSynchronizer : public Resource {\n    GDCLASS(MMSynchronizer, Resource)\n\npublic:\n    // TODO: This API isn't great and will change, and should be improved to better support GD Script\n    virtual void sync(MMCharacter* p_controller, Node3D* p_character, float delta_time) = 0;\n\nprotected:\n    static void _bind_methods();\n};\n#endif // MM_SYNCHRONIZER_H\n"
  }
]