cuju-render 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cuju/render/__init__.py +759 -0
- cuju/render/__main__.py +1065 -0
- cuju/render/_export/__init__.py +3 -0
- cuju/render/_export/feed.py +542 -0
- cuju/render/_export/sizes.py +157 -0
- cuju/render/_export/video.py +1618 -0
- cuju/render/_export/video_jobs.py +408 -0
- cuju/render/_format/__init__.py +3 -0
- cuju/render/_format/annotations.py +235 -0
- cuju/render/_format/codec.py +307 -0
- cuju/render/_format/keep.py +121 -0
- cuju/render/_format/recording.py +860 -0
- cuju/render/_format/writer.py +54 -0
- cuju/render/_guide/__init__.py +3 -0
- cuju/render/_guide/doctor.py +523 -0
- cuju/render/_guide/help.py +62 -0
- cuju/render/_guide/tutorial.py +254 -0
- cuju/render/_http/__init__.py +3 -0
- cuju/render/_http/commands.py +57 -0
- cuju/render/_http/headers.py +103 -0
- cuju/render/_http/launch.py +500 -0
- cuju/render/_http/launchers.py +408 -0
- cuju/render/_http/pwa.py +128 -0
- cuju/render/_http/server.py +2105 -0
- cuju/render/_http/window.py +360 -0
- cuju/render/_moments/__init__.py +3 -0
- cuju/render/_moments/captions.py +284 -0
- cuju/render/_moments/events.py +1123 -0
- cuju/render/_moments/search.py +694 -0
- cuju/render/_packages/__init__.py +3 -0
- cuju/render/_packages/glsl.py +746 -0
- cuju/render/_packages/loudness.py +334 -0
- cuju/render/_packages/pack.py +1959 -0
- cuju/render/_packages/presentation.py +1376 -0
- cuju/render/_packages/vocabulary.py +609 -0
- cuju/render/_playing/__init__.py +3 -0
- cuju/render/_playing/achievements.py +463 -0
- cuju/render/_playing/bench.py +609 -0
- cuju/render/_playing/challenges.py +1813 -0
- cuju/render/_playing/demo.py +323 -0
- cuju/render/_playing/draw.py +211 -0
- cuju/render/_playing/errors.py +20 -0
- cuju/render/_playing/game.py +645 -0
- cuju/render/_playing/limits.py +339 -0
- cuju/render/_playing/play.py +926 -0
- cuju/render/_playing/policy_worker.py +250 -0
- cuju/render/_playing/strict.py +810 -0
- cuju/render/_registry/__init__.py +3 -0
- cuju/render/_registry/clips.py +394 -0
- cuju/render/_registry/joints.py +108 -0
- cuju/render/_registry/teams.py +2058 -0
- cuju/render/_store/__init__.py +3 -0
- cuju/render/_store/home.py +70 -0
- cuju/render/_store/library.py +604 -0
- cuju/render/_store/samples.py +163 -0
- cuju/render/_text/__init__.py +3 -0
- cuju/render/_text/languages.py +637 -0
- cuju/render/_text/messages.py +1396 -0
- cuju/render/_tournaments/__init__.py +3 -0
- cuju/render/_tournaments/results.py +547 -0
- cuju/render/_tournaments/tournament.py +2742 -0
- cuju/render/_tournaments/tournament_cli.py +587 -0
- cuju/render/_tournaments/tournament_runner.py +1975 -0
- cuju/render/_version.py +6 -0
- cuju/render/_watch/__init__.py +3 -0
- cuju/render/_watch/grid.py +265 -0
- cuju/render/_watch/live.py +664 -0
- cuju/render/_watch/notebook.py +79 -0
- cuju/render/_watch/show.py +171 -0
- cuju/render/samples/sample-11v11.cjr +0 -0
- cuju/render/samples/sample-5v5.cjr +0 -0
- cuju/render/samples/teams/starter/main.py +33 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_easy.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_hard.py +46 -0
- cuju/render/scenarios/benchmark_full_game_11_vs_11_medium.py +46 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_easy.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_hard.py +34 -0
- cuju/render/scenarios/benchmark_full_game_5_vs_5_medium.py +34 -0
- cuju/render/web/assets/adboard_polygon.webp +0 -0
- cuju/render/web/assets/adboard_your_ad_here.webp +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-combatmelee.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-general.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementadvanced.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-movementbasic.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-simulation.glb +0 -0
- cuju/render/web/assets/animations/kaykit-rig-medium-special.glb +0 -0
- cuju/render/web/assets/ball.glb +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/3_vs_1_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/corner.mp4 +0 -0
- cuju/render/web/assets/challenges/corner.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_easy.webp +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/counterattack_hard.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal.webp +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.mp4 +0 -0
- cuju/render/web/assets/challenges/empty_goal_close.webp +0 -0
- cuju/render/web/assets/challenges/manifest.json +375 -0
- cuju/render/web/assets/challenges/one_on_one.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one.webp +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.mp4 +0 -0
- cuju/render/web/assets/challenges/one_on_one_hard.webp +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_pass_and_shoot_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score.webp +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.mp4 +0 -0
- cuju/render/web/assets/challenges/run_to_score_with_keeper.webp +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.mp4 +0 -0
- cuju/render/web/assets/challenges/single_goal_versus_lazy.webp +0 -0
- cuju/render/web/assets/crowd.webp +0 -0
- cuju/render/web/assets/crowd_normal.webp +0 -0
- cuju/render/web/assets/cuju-logo-384.png +0 -0
- cuju/render/web/assets/cuju-logo.png +0 -0
- cuju/render/web/assets/cuju-logo.svg +46 -0
- cuju/render/web/assets/cuju-mark-light.svg +33 -0
- cuju/render/web/assets/cuju-mark.svg +33 -0
- cuju/render/web/assets/goal.glb +0 -0
- cuju/render/web/assets/grass.webp +0 -0
- cuju/render/web/assets/hair_black.png +0 -0
- cuju/render/web/assets/hair_blonde.png +0 -0
- cuju/render/web/assets/hair_brown.png +0 -0
- cuju/render/web/assets/hair_darkblonde.png +0 -0
- cuju/render/web/assets/hair_long01.glb +0 -0
- cuju/render/web/assets/hair_long02.glb +0 -0
- cuju/render/web/assets/hair_medium01.glb +0 -0
- cuju/render/web/assets/hair_red.png +0 -0
- cuju/render/web/assets/hair_short01.glb +0 -0
- cuju/render/web/assets/hair_short02.glb +0 -0
- cuju/render/web/assets/icons/cuju-192.png +0 -0
- cuju/render/web/assets/icons/cuju-512.png +0 -0
- cuju/render/web/assets/icons/cuju-maskable-512.png +0 -0
- cuju/render/web/assets/kit_goalkeeper.webp +0 -0
- cuju/render/web/assets/kit_mask.png +0 -0
- cuju/render/web/assets/kit_normal.webp +0 -0
- cuju/render/web/assets/manifest.json +601 -0
- cuju/render/web/assets/models/kaykit-barbarian.glb +0 -0
- cuju/render/web/assets/models/kaykit-barbarian.json +23 -0
- cuju/render/web/assets/models/kaykit-knight.glb +0 -0
- cuju/render/web/assets/models/kaykit-knight.json +23 -0
- cuju/render/web/assets/models/kaykit-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.glb +0 -0
- cuju/render/web/assets/models/kaykit-mannequin-medium.json +23 -0
- cuju/render/web/assets/models/kaykit-ranger.glb +0 -0
- cuju/render/web/assets/models/kaykit-ranger.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue-hooded.json +23 -0
- cuju/render/web/assets/models/kaykit-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-mage.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-minion.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-rogue.json +23 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.glb +0 -0
- cuju/render/web/assets/models/kaykit-skeleton-warrior.json +23 -0
- cuju/render/web/assets/pitch.webp +0 -0
- cuju/render/web/assets/pitch_normal.webp +0 -0
- cuju/render/web/assets/player.glb +0 -0
- cuju/render/web/assets/shoe.webp +0 -0
- cuju/render/web/assets/shoe_sole.png +0 -0
- cuju/render/web/assets/skin_01.png +0 -0
- cuju/render/web/assets/skin_02.png +0 -0
- cuju/render/web/assets/skin_03.png +0 -0
- cuju/render/web/assets/skin_04.png +0 -0
- cuju/render/web/assets/sounds/applause.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-1.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-2.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-3.ogg +0 -0
- cuju/render/web/assets/sounds/beach-wave-4.ogg +0 -0
- cuju/render/web/assets/sounds/city-horn.ogg +0 -0
- cuju/render/web/assets/sounds/city-loop.ogg +0 -0
- cuju/render/web/assets/sounds/city-siren.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-cheer.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-loop.ogg +0 -0
- cuju/render/web/assets/sounds/crowd-ooh.ogg +0 -0
- cuju/render/web/assets/sounds/gulls.ogg +0 -0
- cuju/render/web/assets/sounds/neon-sting.ogg +0 -0
- cuju/render/web/assets/sounds/rain-loop.ogg +0 -0
- cuju/render/web/assets/sounds/sounds.json +46 -0
- cuju/render/web/assets/sounds/space-beep.ogg +0 -0
- cuju/render/web/assets/sounds/space-hum.ogg +0 -0
- cuju/render/web/assets/sounds/space-radio.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-long.ogg +0 -0
- cuju/render/web/assets/sounds/whistle-short.ogg +0 -0
- cuju/render/web/assets/sounds/wind-loop.ogg +0 -0
- cuju/render/web/easy.css +357 -0
- cuju/render/web/export.html +23 -0
- cuju/render/web/feed.css +262 -0
- cuju/render/web/feed.html +28 -0
- cuju/render/web/flat.css +120 -0
- cuju/render/web/home.css +837 -0
- cuju/render/web/index.html +373 -0
- cuju/render/web/js/boot.js +50 -0
- cuju/render/web/js/broadcast/audio.js +2141 -0
- cuju/render/web/js/broadcast/celebrate.js +1238 -0
- cuju/render/web/js/broadcast/decisions.js +609 -0
- cuju/render/web/js/broadcast/hud.js +352 -0
- cuju/render/web/js/broadcast/show.js +3614 -0
- cuju/render/web/js/broadcast/transition.js +2231 -0
- cuju/render/web/js/broadcast/voices.js +153 -0
- cuju/render/web/js/capture.js +101 -0
- cuju/render/web/js/core/accessibility.js +90 -0
- cuju/render/web/js/core/api.js +429 -0
- cuju/render/web/js/core/comfort.js +135 -0
- cuju/render/web/js/core/i18n.js +705 -0
- cuju/render/web/js/core/icons.js +167 -0
- cuju/render/web/js/core/input-settings.js +156 -0
- cuju/render/web/js/core/keys.js +277 -0
- cuju/render/web/js/core/language-ui.js +125 -0
- cuju/render/web/js/core/menu.js +853 -0
- cuju/render/web/js/core/names.js +113 -0
- cuju/render/web/js/core/page-structure.js +110 -0
- cuju/render/web/js/core/scenarios.js +109 -0
- cuju/render/web/js/core/spoilers.js +236 -0
- cuju/render/web/js/core/typeahead.js +38 -0
- cuju/render/web/js/export/dom-picture.js +330 -0
- cuju/render/web/js/export/export-host.js +97 -0
- cuju/render/web/js/export/feed-dialog.js +191 -0
- cuju/render/web/js/export/feed-params.js +266 -0
- cuju/render/web/js/export/feed.js +363 -0
- cuju/render/web/js/export/mp4-mux.js +295 -0
- cuju/render/web/js/export/photo-core.js +313 -0
- cuju/render/web/js/export/photo.js +1314 -0
- cuju/render/web/js/export/safe-frame.js +59 -0
- cuju/render/web/js/export/size-fields.js +103 -0
- cuju/render/web/js/export/sizes.js +178 -0
- cuju/render/web/js/export/video-boot.js +55 -0
- cuju/render/web/js/export/video-browser.js +325 -0
- cuju/render/web/js/export/video-export.js +417 -0
- cuju/render/web/js/export/video-plan.js +207 -0
- cuju/render/web/js/export/video-range.js +185 -0
- cuju/render/web/js/export/webm-mux.js +198 -0
- cuju/render/web/js/feed-boot.js +151 -0
- cuju/render/web/js/feed-frame.js +101 -0
- cuju/render/web/js/help/doctor.js +488 -0
- cuju/render/web/js/help/firstrun.js +379 -0
- cuju/render/web/js/help/help-center.js +438 -0
- cuju/render/web/js/help/install-offer.js +164 -0
- cuju/render/web/js/help/install-state.js +104 -0
- cuju/render/web/js/help/lessons-core.js +207 -0
- cuju/render/web/js/help/lessons.js +463 -0
- cuju/render/web/js/help/palette.js +584 -0
- cuju/render/web/js/help/pwa.js +257 -0
- cuju/render/web/js/help/tour-core.js +241 -0
- cuju/render/web/js/help/tour.js +461 -0
- cuju/render/web/js/main.js +2513 -0
- cuju/render/web/js/pages/draw.js +188 -0
- cuju/render/web/js/pages/fieldpage.js +530 -0
- cuju/render/web/js/pages/home.js +521 -0
- cuju/render/web/js/pages/matches.js +779 -0
- cuju/render/web/js/pages/moments-ui.js +359 -0
- cuju/render/web/js/pages/queue-filters.js +63 -0
- cuju/render/web/js/pages/recording-groups.js +80 -0
- cuju/render/web/js/pages/recordings.js +1069 -0
- cuju/render/web/js/play/bench.js +347 -0
- cuju/render/web/js/play/challenges.js +1302 -0
- cuju/render/web/js/play/medals.js +127 -0
- cuju/render/web/js/play/play.js +1953 -0
- cuju/render/web/js/play/teasers.js +196 -0
- cuju/render/web/js/play/touch.js +350 -0
- cuju/render/web/js/play/tournament-layout.js +487 -0
- cuju/render/web/js/play/tournament-results.js +441 -0
- cuju/render/web/js/play/tournament-strict.js +560 -0
- cuju/render/web/js/play/tournament.js +2585 -0
- cuju/render/web/js/players/animate.js +972 -0
- cuju/render/web/js/players/clips.js +224 -0
- cuju/render/web/js/players/kaykit.js +162 -0
- cuju/render/web/js/players/kits.js +676 -0
- cuju/render/web/js/players/number-patch.js +82 -0
- cuju/render/web/js/players/players.js +2694 -0
- cuju/render/web/js/players/recolor.js +893 -0
- cuju/render/web/js/players/retarget.js +325 -0
- cuju/render/web/js/players/rig-numbers.js +174 -0
- cuju/render/web/js/presentation/animator.js +231 -0
- cuju/render/web/js/presentation/assign.js +85 -0
- cuju/render/web/js/presentation/behaviors.js +440 -0
- cuju/render/web/js/presentation/budget.js +209 -0
- cuju/render/web/js/presentation/catalog.js +1674 -0
- cuju/render/web/js/presentation/clocks.js +72 -0
- cuju/render/web/js/presentation/commands.js +301 -0
- cuju/render/web/js/presentation/contract.js +240 -0
- cuju/render/web/js/presentation/effects.js +904 -0
- cuju/render/web/js/presentation/flash.js +605 -0
- cuju/render/web/js/presentation/freeze-worklet.js +22 -0
- cuju/render/web/js/presentation/glsl.js +464 -0
- cuju/render/web/js/presentation/host.js +323 -0
- cuju/render/web/js/presentation/index.js +2066 -0
- cuju/render/web/js/presentation/loudness-worklet.js +235 -0
- cuju/render/web/js/presentation/loudness.js +740 -0
- cuju/render/web/js/presentation/moments.js +536 -0
- cuju/render/web/js/presentation/post.js +327 -0
- cuju/render/web/js/presentation/preview.js +180 -0
- cuju/render/web/js/presentation/protocol.js +174 -0
- cuju/render/web/js/presentation/reach.js +158 -0
- cuju/render/web/js/presentation/rigging.js +130 -0
- cuju/render/web/js/presentation/sandbox.js +157 -0
- cuju/render/web/js/presentation/shaders.js +235 -0
- cuju/render/web/js/presentation/stage.js +402 -0
- cuju/render/web/js/presentation/teamaudio.js +722 -0
- cuju/render/web/js/presentation/tracks.js +197 -0
- cuju/render/web/js/presentation/values.js +81 -0
- cuju/render/web/js/recording/annotation-data.js +203 -0
- cuju/render/web/js/recording/captions.js +491 -0
- cuju/render/web/js/recording/codec.js +185 -0
- cuju/render/web/js/recording/decode-worker.js +70 -0
- cuju/render/web/js/recording/decode.js +94 -0
- cuju/render/web/js/recording/episodes.js +46 -0
- cuju/render/web/js/recording/format.js +461 -0
- cuju/render/web/js/recording/live.js +348 -0
- cuju/render/web/js/recording/moments-query.js +59 -0
- cuju/render/web/js/recording/moments.js +149 -0
- cuju/render/web/js/recording/pose.js +86 -0
- cuju/render/web/js/recording/timeline.js +275 -0
- cuju/render/web/js/teams/art.js +50 -0
- cuju/render/web/js/teams/clip-editor.js +293 -0
- cuju/render/web/js/teams/editor.js +1979 -0
- cuju/render/web/js/teams/library.js +344 -0
- cuju/render/web/js/teams/painter.js +633 -0
- cuju/render/web/js/teams/surprise-ui.js +195 -0
- cuju/render/web/js/teams/surprise.js +497 -0
- cuju/render/web/js/teams/team-check.js +127 -0
- cuju/render/web/js/watch/annotations.js +270 -0
- cuju/render/web/js/watch/flat.js +366 -0
- cuju/render/web/js/watch/grid.js +436 -0
- cuju/render/web/js/watch/pitch2d.js +289 -0
- cuju/render/web/js/watch/player-tools.js +192 -0
- cuju/render/web/js/watch/presets.js +201 -0
- cuju/render/web/js/world/arenas.js +997 -0
- cuju/render/web/js/world/ball.js +204 -0
- cuju/render/web/js/world/cameras.js +982 -0
- cuju/render/web/js/world/crowd.js +376 -0
- cuju/render/web/js/world/field.js +227 -0
- cuju/render/web/js/world/framing.js +145 -0
- cuju/render/web/js/world/occlusion.js +48 -0
- cuju/render/web/js/world/pitch.js +703 -0
- cuju/render/web/js/world/scene.js +395 -0
- cuju/render/web/js/world/sky.js +325 -0
- cuju/render/web/js/world/stadium.js +1262 -0
- cuju/render/web/js/world/vendor-sky.js +367 -0
- cuju/render/web/js/world/weather.js +264 -0
- cuju/render/web/languages/en.json +4750 -0
- cuju/render/web/lessons.json +137 -0
- cuju/render/web/manifest.webmanifest +23 -0
- cuju/render/web/style.css +8399 -0
- cuju/render/web/sw.js +235 -0
- cuju/render/web/vendor/fabric/LICENSE +16 -0
- cuju/render/web/vendor/fabric/fabric.min.mjs +1 -0
- cuju/render/web/vendor/lucide/LICENSE +43 -0
- cuju/render/web/vendor/lucide/icons.js +141 -0
- cuju/render/web/vendor/three/LICENSE +21 -0
- cuju/render/web/vendor/three/build/three.core.js +60586 -0
- cuju/render/web/vendor/three/build/three.module.js +19719 -0
- cuju/render/web/vendor/three/examples/jsm/loaders/GLTFLoader.js +4925 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/EffectComposer.js +365 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/MaskPass.js +195 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/OutputPass.js +148 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/Pass.js +191 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/RenderPass.js +193 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/ShaderPass.js +135 -0
- cuju/render/web/vendor/three/examples/jsm/postprocessing/UnrealBloomPass.js +541 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/CopyShader.js +52 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/LuminosityHighPassShader.js +68 -0
- cuju/render/web/vendor/three/examples/jsm/shaders/OutputShader.js +103 -0
- cuju/render/web/vendor/three/examples/jsm/utils/BufferGeometryUtils.js +1501 -0
- cuju/render/web/vendor/three/examples/jsm/utils/SkeletonUtils.js +496 -0
- cuju_render-0.1.0.dist-info/METADATA +116 -0
- cuju_render-0.1.0.dist-info/RECORD +382 -0
- cuju_render-0.1.0.dist-info/WHEEL +4 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSE +202 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/CC0-1.0.txt +127 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/ISC.txt +16 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/MIT.txt +24 -0
- cuju_render-0.1.0.dist-info/licenses/LICENSES/Unlicense.txt +13 -0
- cuju_render-0.1.0.dist-info/licenses/NOTICE +35 -0
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
// Copyright 2026 The cuju Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Procedural animations on GRF's skeleton of 14 nodes, celebrations, idles, entrances and the
|
|
5
|
+
// small gestures of restarts. Every animation writes one player's nodes in the engine frame, z up
|
|
6
|
+
// in meters with the player facing engine minus y at heading 0, the same buffer a recording
|
|
7
|
+
// holds, so every model follows it through its own retargeting.
|
|
8
|
+
//
|
|
9
|
+
// An animation is either keys or a function of time. A key holds joint angles in degrees, local
|
|
10
|
+
// to the parent node and applied x first, then y, then z, and a few other channels, the lift of
|
|
11
|
+
// the lowest point of the body above the ground, a move of the whole player forward and to its
|
|
12
|
+
// left, a turn, and targets for the hands and feet that two bone inverse kinematics reaches.
|
|
13
|
+
// Keys blend along cubic Hermite curves. Without planted feet the body is lowered or raised
|
|
14
|
+
// until its lowest point, a heel, toe, knee, hand, hip, chest or head, sits at the lift, so
|
|
15
|
+
// crouches and kneels stay on the ground by themselves.
|
|
16
|
+
//
|
|
17
|
+
// A turn of node body bends at the hips and leaves the legs upright, unless the animation sets
|
|
18
|
+
// trunk false, then the whole body turns about the pelvis, as in a flip.
|
|
19
|
+
//
|
|
20
|
+
// Angle signs in the engine frame. On x, a positive angle tips the body, the chest and the head
|
|
21
|
+
// forward, swings a thigh back, bends a knee, swings an arm back, straightens an elbow and points
|
|
22
|
+
// the toes down. On y, a positive angle tilts the trunk to the player's left and swings the right
|
|
23
|
+
// arm or leg out, a negative one the left arm or leg out. On z, a positive angle turns left.
|
|
24
|
+
// Targets are meters as left, forward and up, from the chest node for the hands and from the
|
|
25
|
+
// ground point for the feet.
|
|
26
|
+
|
|
27
|
+
import * as THREE from 'three';
|
|
28
|
+
import { NODE_COUNT, NODE_PARENTS, PLAYER_FLOATS } from '../recording/format.js';
|
|
29
|
+
import { t } from '../core/i18n.js';
|
|
30
|
+
|
|
31
|
+
export const REST_LOCAL = [
|
|
32
|
+
[0, 0, 0], [0, 0, 0.96], [0, 0, 0.15], [0, -0.03, 0.5], [0.16, -0.01, 0.48], [-0.01, 0, -0.33],
|
|
33
|
+
[-0.16, -0.01, 0.48], [0.01, 0, -0.33], [0.087, 0, -0.01], [0, 0, -0.42], [0, -0.04, -0.44],
|
|
34
|
+
[-0.087, 0, -0.01], [0, 0, -0.42], [0, -0.04, -0.44],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// Node indices.
|
|
38
|
+
const BODY = 1;
|
|
39
|
+
const MIDDLE = 2;
|
|
40
|
+
const NECK = 3;
|
|
41
|
+
const L_SHOULDER = 4;
|
|
42
|
+
const L_ELBOW = 5;
|
|
43
|
+
const R_SHOULDER = 6;
|
|
44
|
+
const R_ELBOW = 7;
|
|
45
|
+
const L_THIGH = 8;
|
|
46
|
+
const L_KNEE = 9;
|
|
47
|
+
const L_ANKLE = 10;
|
|
48
|
+
const R_THIGH = 11;
|
|
49
|
+
const R_KNEE = 12;
|
|
50
|
+
const R_ANKLE = 13;
|
|
51
|
+
|
|
52
|
+
// Channels of an animation sample.
|
|
53
|
+
const LIFT = 42;
|
|
54
|
+
const FWD = 43;
|
|
55
|
+
const SIDE = 44;
|
|
56
|
+
const YAW = 45;
|
|
57
|
+
const HZ = 46;
|
|
58
|
+
const LH = 47;
|
|
59
|
+
const RH = 50;
|
|
60
|
+
const LF = 53;
|
|
61
|
+
const RF = 56;
|
|
62
|
+
const LP = 59;
|
|
63
|
+
const RP = 62;
|
|
64
|
+
const CHANNELS = 65;
|
|
65
|
+
|
|
66
|
+
const UPPER_ARM = 0.33;
|
|
67
|
+
const FOREARM = 0.30;
|
|
68
|
+
const THIGH = 0.42;
|
|
69
|
+
const SHIN = Math.hypot(0.04, 0.44);
|
|
70
|
+
const ANKLE_HEIGHT = 0.09;
|
|
71
|
+
const DEG = Math.PI / 180;
|
|
72
|
+
|
|
73
|
+
// The hands at rest, seen from the chest node, and the default poles of the elbows and knees.
|
|
74
|
+
const HAND_REST_L = [0.19, 0.0, -0.13];
|
|
75
|
+
const HAND_REST_R = [-0.19, 0.0, -0.13];
|
|
76
|
+
const POLE_L = [1, -0.6, -0.5];
|
|
77
|
+
const POLE_R = [-1, -0.6, -0.5];
|
|
78
|
+
const FOOT_L = [0.11, 0.0, 0];
|
|
79
|
+
const FOOT_R = [-0.11, 0.0, 0];
|
|
80
|
+
|
|
81
|
+
// Scratch objects, the library allocates nothing per call.
|
|
82
|
+
const euler = new THREE.Euler();
|
|
83
|
+
const qa = new THREE.Quaternion();
|
|
84
|
+
const qb = new THREE.Quaternion();
|
|
85
|
+
const qYaw = new THREE.Quaternion();
|
|
86
|
+
const va = new THREE.Vector3();
|
|
87
|
+
const vb = new THREE.Vector3();
|
|
88
|
+
const vc = new THREE.Vector3();
|
|
89
|
+
const vd = new THREE.Vector3();
|
|
90
|
+
const Z_AXIS = new THREE.Vector3(0, 0, 1);
|
|
91
|
+
const localQ = Array.from({ length: NODE_COUNT }, () => new THREE.Quaternion());
|
|
92
|
+
const localT = Array.from({ length: NODE_COUNT }, () => new THREE.Vector3());
|
|
93
|
+
const worldQ = Array.from({ length: NODE_COUNT }, () => new THREE.Quaternion());
|
|
94
|
+
const worldP = Array.from({ length: NODE_COUNT }, () => new THREE.Vector3());
|
|
95
|
+
const sample = new Float32Array(CHANNELS);
|
|
96
|
+
const scratchA = new Float32Array(CHANNELS);
|
|
97
|
+
const scratchB = new Float32Array(CHANNELS);
|
|
98
|
+
const scratchC = new Float32Array(CHANNELS);
|
|
99
|
+
|
|
100
|
+
function smoothstep(u) {
|
|
101
|
+
return u * u * (3 - 2 * u);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Key authoring. A key names joints by short names and lists their angles in degrees.
|
|
105
|
+
const JOINT = {
|
|
106
|
+
body: BODY, middle: MIDDLE, neck: NECK, ls: L_SHOULDER, le: L_ELBOW, rs: R_SHOULDER, re: R_ELBOW,
|
|
107
|
+
lt: L_THIGH, lk: L_KNEE, la: L_ANKLE, rt: R_THIGH, rk: R_KNEE, ra: R_ANKLE,
|
|
108
|
+
};
|
|
109
|
+
const VECTOR = { lh: LH, rh: RH, lf: LF, rf: RF, lp: LP, rp: RP };
|
|
110
|
+
|
|
111
|
+
function defaults(out) {
|
|
112
|
+
out.fill(0);
|
|
113
|
+
out.set(HAND_REST_L, LH);
|
|
114
|
+
out.set(HAND_REST_R, RH);
|
|
115
|
+
out.set(FOOT_L, LF);
|
|
116
|
+
out.set(FOOT_R, RF);
|
|
117
|
+
out.set(POLE_L, LP);
|
|
118
|
+
out.set(POLE_R, RP);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function writeKey(spec, out) {
|
|
123
|
+
for (const [name, value] of Object.entries(spec)) {
|
|
124
|
+
if (name in JOINT) {
|
|
125
|
+
const j = JOINT[name] * 3;
|
|
126
|
+
out[j] = value[0] || 0;
|
|
127
|
+
out[j + 1] = value[1] || 0;
|
|
128
|
+
out[j + 2] = value[2] || 0;
|
|
129
|
+
} else if (name in VECTOR) {
|
|
130
|
+
out.set(value, VECTOR[name]);
|
|
131
|
+
} else if (name === 'lift') {
|
|
132
|
+
out[LIFT] = value;
|
|
133
|
+
} else if (name === 'f') {
|
|
134
|
+
out[FWD] = value;
|
|
135
|
+
} else if (name === 's') {
|
|
136
|
+
out[SIDE] = value;
|
|
137
|
+
} else if (name === 'yaw') {
|
|
138
|
+
out[YAW] = value;
|
|
139
|
+
} else if (name === 'hz') {
|
|
140
|
+
out[HZ] = value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Turns an animation's keys into flat channel vectors, each key over the animation's base.
|
|
147
|
+
function compile(anim) {
|
|
148
|
+
if (anim.compiled || !anim.keys) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const base = writeKey(anim.base || {}, defaults(new Float32Array(CHANNELS)));
|
|
152
|
+
anim.compiled = anim.keys.map((k) => ({
|
|
153
|
+
t: k.t,
|
|
154
|
+
e: k.e || anim.ease || 'smooth',
|
|
155
|
+
v: writeKey(k, Float32Array.from(base)),
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Samples keys at time t into out with Hermite curves through the keys. A looping animation
|
|
160
|
+
// wraps around its duration, any other holds its first and last keys.
|
|
161
|
+
function sampleKeys(anim, t, out) {
|
|
162
|
+
compile(anim);
|
|
163
|
+
const keys = anim.compiled;
|
|
164
|
+
const n = keys.length;
|
|
165
|
+
const D = anim.duration;
|
|
166
|
+
if (n === 1) {
|
|
167
|
+
out.set(keys[0].v);
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
if (anim.loop) {
|
|
171
|
+
t = ((t % D) + D) % D;
|
|
172
|
+
} else if (t <= keys[0].t) {
|
|
173
|
+
out.set(keys[0].v);
|
|
174
|
+
return out;
|
|
175
|
+
} else if (t >= keys[n - 1].t) {
|
|
176
|
+
out.set(keys[n - 1].v);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
let i = n - 1;
|
|
180
|
+
for (let k = 0; k < n; k++) {
|
|
181
|
+
if (keys[k].t > t) {
|
|
182
|
+
i = k - 1;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const at = (k) => {
|
|
187
|
+
if (anim.loop) {
|
|
188
|
+
const wrap = Math.floor(k / n);
|
|
189
|
+
const key = keys[((k % n) + n) % n];
|
|
190
|
+
return { t: key.t + wrap * D, v: key.v, e: key.e };
|
|
191
|
+
}
|
|
192
|
+
const key = keys[Math.max(0, Math.min(n - 1, k))];
|
|
193
|
+
return { t: key.t, v: key.v, e: key.e, edge: k < 0 || k > n - 1 };
|
|
194
|
+
};
|
|
195
|
+
if (i < 0) {
|
|
196
|
+
// Before the first key of a loop, the segment from the last key of the previous turn.
|
|
197
|
+
i = -1;
|
|
198
|
+
}
|
|
199
|
+
const k0 = at(i - 1);
|
|
200
|
+
const k1 = at(i);
|
|
201
|
+
const k2 = at(i + 1);
|
|
202
|
+
const k3 = at(i + 2);
|
|
203
|
+
let tt = t;
|
|
204
|
+
if (tt < k1.t) {
|
|
205
|
+
tt += D;
|
|
206
|
+
}
|
|
207
|
+
const span = k2.t - k1.t || 1;
|
|
208
|
+
let u = Math.max(0, Math.min(1, (tt - k1.t) / span));
|
|
209
|
+
const ease = k2.e;
|
|
210
|
+
if (ease === 'hold') {
|
|
211
|
+
out.set(k1.v);
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
if (ease === 'linear') {
|
|
215
|
+
for (let c = 0; c < CHANNELS; c++) {
|
|
216
|
+
out[c] = k1.v[c] + (k2.v[c] - k1.v[c]) * u;
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
let zeroTangents = ease === 'inout' || ease === 'snap';
|
|
221
|
+
if (ease === 'snap') {
|
|
222
|
+
u = smoothstep(smoothstep(u));
|
|
223
|
+
}
|
|
224
|
+
const u2 = u * u;
|
|
225
|
+
const u3 = u2 * u;
|
|
226
|
+
const h00 = 2 * u3 - 3 * u2 + 1;
|
|
227
|
+
const h10 = u3 - 2 * u2 + u;
|
|
228
|
+
const h01 = -2 * u3 + 3 * u2;
|
|
229
|
+
const h11 = u3 - u2;
|
|
230
|
+
const d0 = k2.t - k0.t || 1;
|
|
231
|
+
const d1 = k3.t - k1.t || 1;
|
|
232
|
+
// The first and last keys of a single play start and end at rest.
|
|
233
|
+
const m1s = zeroTangents || k0.edge || (!anim.loop && i === 0) ? 0 : span / d0;
|
|
234
|
+
const m2s = zeroTangents || k3.edge || (!anim.loop && i + 1 === n - 1) ? 0 : span / d1;
|
|
235
|
+
for (let c = 0; c < CHANNELS; c++) {
|
|
236
|
+
const p1 = k1.v[c];
|
|
237
|
+
const p2 = k2.v[c];
|
|
238
|
+
const m1 = (p2 - k0.v[c]) * m1s;
|
|
239
|
+
const m2 = (k3.v[c] - p1) * m2s;
|
|
240
|
+
out[c] = h00 * p1 + h10 * m1 + h01 * p2 + h11 * m2;
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Two bone inverse kinematics. Root, target and pole in one frame, the result is the middle
|
|
246
|
+
// joint's position in out.
|
|
247
|
+
function reach(root, target, pole, a, b, out) {
|
|
248
|
+
vd.subVectors(target, root);
|
|
249
|
+
let d = vd.length();
|
|
250
|
+
if (d < 1e-6) {
|
|
251
|
+
vd.set(0, 0, -1);
|
|
252
|
+
d = 1e-6;
|
|
253
|
+
}
|
|
254
|
+
vd.divideScalar(d);
|
|
255
|
+
d = Math.max(Math.abs(a - b) + 1e-3, Math.min(a + b - 1e-4, d));
|
|
256
|
+
const along = (a * a - b * b + d * d) / (2 * d);
|
|
257
|
+
const h = Math.sqrt(Math.max(0, a * a - along * along));
|
|
258
|
+
vc.copy(pole).addScaledVector(vd, -pole.dot(vd));
|
|
259
|
+
if (vc.lengthSq() < 1e-10) {
|
|
260
|
+
vc.set(0, 0, 1).addScaledVector(vd, -vd.z);
|
|
261
|
+
}
|
|
262
|
+
vc.normalize();
|
|
263
|
+
return out.copy(root).addScaledVector(vd, along).addScaledVector(vc, h);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const REST_UPPER_L = new THREE.Vector3(...REST_LOCAL[L_ELBOW]).normalize();
|
|
267
|
+
const REST_UPPER_R = new THREE.Vector3(...REST_LOCAL[R_ELBOW]).normalize();
|
|
268
|
+
const REST_SHIN = new THREE.Vector3(...REST_LOCAL[L_ANKLE]).normalize();
|
|
269
|
+
const DOWN = new THREE.Vector3(0, 0, -1);
|
|
270
|
+
|
|
271
|
+
// Engine vector from left, forward and up.
|
|
272
|
+
function lfu(v, o, out) {
|
|
273
|
+
return out.set(v[o], -v[o + 1], v[o + 2]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Sets the shoulder and elbow rotations of one arm so its hand reaches a target in the chest
|
|
277
|
+
// frame, or in the ground frame for an animation that plants its feet.
|
|
278
|
+
function armIK(v, shoulder, hand, pole, ground) {
|
|
279
|
+
const elbow = shoulder + 1;
|
|
280
|
+
const S = vb.set(...REST_LOCAL[shoulder]);
|
|
281
|
+
const T = lfu(v, hand, new THREE.Vector3());
|
|
282
|
+
if (ground) {
|
|
283
|
+
// A target on the ground, brought into the chest frame of the trunk posed so far.
|
|
284
|
+
T.applyQuaternion(qYaw).add(worldP[0]).sub(worldP[MIDDLE]);
|
|
285
|
+
T.applyQuaternion(qa.copy(worldQ[MIDDLE]).invert());
|
|
286
|
+
}
|
|
287
|
+
const P = lfu(v, pole, new THREE.Vector3());
|
|
288
|
+
const E = reach(S, T, P, UPPER_ARM, FOREARM, new THREE.Vector3());
|
|
289
|
+
const upper = va.subVectors(E, S).normalize();
|
|
290
|
+
localQ[shoulder].setFromUnitVectors(shoulder === L_SHOULDER ? REST_UPPER_L : REST_UPPER_R, upper);
|
|
291
|
+
const fore = T.sub(E).normalize().applyQuaternion(qa.copy(localQ[shoulder]).invert());
|
|
292
|
+
localQ[elbow].setFromUnitVectors(DOWN, fore);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Sets the thigh and knee rotations of one leg so its ankle reaches a target on the ground.
|
|
296
|
+
function legIK(v, thigh, foot, side) {
|
|
297
|
+
const knee = thigh + 1;
|
|
298
|
+
const H = new THREE.Vector3(...REST_LOCAL[thigh]).applyQuaternion(worldQ[BODY]).add(worldP[BODY]);
|
|
299
|
+
const T = lfu(v, foot, new THREE.Vector3());
|
|
300
|
+
T.z += ANKLE_HEIGHT + v[LIFT];
|
|
301
|
+
T.applyQuaternion(qYaw).add(worldP[0]);
|
|
302
|
+
const P = new THREE.Vector3(side * 0.15, -1, 0.1).applyQuaternion(qYaw);
|
|
303
|
+
const K = reach(H, T, P, THIGH, SHIN, new THREE.Vector3());
|
|
304
|
+
const thighDir = va.subVectors(K, H).normalize();
|
|
305
|
+
const qThigh = new THREE.Quaternion().setFromUnitVectors(DOWN, thighDir);
|
|
306
|
+
// Keep the thigh's twist with the body, a turn of the hips turns the knees.
|
|
307
|
+
const facing = new THREE.Vector3(0, -1, 0).applyQuaternion(worldQ[BODY]);
|
|
308
|
+
const now = new THREE.Vector3(0, -1, 0).applyQuaternion(qThigh);
|
|
309
|
+
const projNow = now.addScaledVector(thighDir, -now.dot(thighDir)).normalize();
|
|
310
|
+
const projWant = facing.addScaledVector(thighDir, -facing.dot(thighDir)).normalize();
|
|
311
|
+
if (projNow.lengthSq() > 0.5 && projWant.lengthSq() > 0.5) {
|
|
312
|
+
qb.setFromUnitVectors(projNow, projWant);
|
|
313
|
+
qThigh.premultiply(qb);
|
|
314
|
+
}
|
|
315
|
+
localQ[thigh].copy(worldQ[BODY]).invert().multiply(qThigh);
|
|
316
|
+
const shin = vb.subVectors(T, K).normalize().applyQuaternion(qa.copy(qThigh).invert());
|
|
317
|
+
localQ[knee].setFromUnitVectors(REST_SHIN, shin);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Forward kinematics from node from on, ankles level with the ground unless the animation
|
|
321
|
+
// moves them with the legs.
|
|
322
|
+
function forward(v, from, footLocal) {
|
|
323
|
+
for (let i = from; i < NODE_COUNT; i++) {
|
|
324
|
+
const p = NODE_PARENTS[i];
|
|
325
|
+
if (p < 0) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
worldP[i].copy(localT[i]).applyQuaternion(worldQ[p]).add(worldP[p]);
|
|
329
|
+
if ((i === L_ANKLE || i === R_ANKLE) && !footLocal) {
|
|
330
|
+
euler.set(v[i * 3] * DEG, v[i * 3 + 1] * DEG, v[i * 3 + 2] * DEG, 'ZYX');
|
|
331
|
+
worldQ[i].setFromEuler(euler).premultiply(qYaw);
|
|
332
|
+
} else {
|
|
333
|
+
worldQ[i].multiplyQuaternions(worldQ[p], localQ[i]);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// The lowest point of the body above the ground, heels, toes, knees, hands, hips, chest and head.
|
|
339
|
+
const CONTACTS = [
|
|
340
|
+
[L_ANKLE, 0, 0.06, -0.085, 0], [L_ANKLE, 0, -0.17, -0.085, 0],
|
|
341
|
+
[R_ANKLE, 0, 0.06, -0.085, 0], [R_ANKLE, 0, -0.17, -0.085, 0],
|
|
342
|
+
[L_KNEE, 0, 0, 0, 0.06], [R_KNEE, 0, 0, 0, 0.06],
|
|
343
|
+
[L_ELBOW, 0, 0, -FOREARM, 0.04], [R_ELBOW, 0, 0, -FOREARM, 0.04],
|
|
344
|
+
[L_ELBOW, 0, 0, 0, 0.05], [R_ELBOW, 0, 0, 0, 0.05],
|
|
345
|
+
[BODY, 0, 0, 0, 0.11], [MIDDLE, 0, 0, 0.2, 0.13], [NECK, 0, -0.02, 0.14, 0.12],
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
function lowest() {
|
|
349
|
+
let low = Infinity;
|
|
350
|
+
for (const [node, x, y, z, r] of CONTACTS) {
|
|
351
|
+
va.set(x, y, z).applyQuaternion(worldQ[node]).add(worldP[node]);
|
|
352
|
+
low = Math.min(low, va.z - r);
|
|
353
|
+
}
|
|
354
|
+
return low;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Poses one player from a channel vector, writing its nodes into out from offset on.
|
|
358
|
+
function solve(v, anim, opts, out, offset) {
|
|
359
|
+
const heading = (opts.heading || 0) + v[YAW] * DEG;
|
|
360
|
+
qYaw.setFromAxisAngle(Z_AXIS, heading);
|
|
361
|
+
const h0 = opts.heading || 0;
|
|
362
|
+
// Root motion is along the heading the animation started with.
|
|
363
|
+
const fx = Math.sin(h0);
|
|
364
|
+
const fy = -Math.cos(h0);
|
|
365
|
+
const px = (opts.x || 0) + v[FWD] * fx + v[SIDE] * Math.cos(h0);
|
|
366
|
+
const py = (opts.y || 0) + v[FWD] * fy + v[SIDE] * Math.sin(h0);
|
|
367
|
+
worldP[0].set(px, py, 0);
|
|
368
|
+
worldQ[0].copy(qYaw);
|
|
369
|
+
for (let i = 1; i < NODE_COUNT; i++) {
|
|
370
|
+
euler.set(v[i * 3] * DEG, v[i * 3 + 1] * DEG, v[i * 3 + 2] * DEG, 'ZYX');
|
|
371
|
+
localQ[i].setFromEuler(euler);
|
|
372
|
+
localT[i].set(...REST_LOCAL[i]);
|
|
373
|
+
}
|
|
374
|
+
const plant = !!anim.plant;
|
|
375
|
+
if (plant) {
|
|
376
|
+
localT[BODY].z += v[HZ] + v[LIFT];
|
|
377
|
+
} else if (anim.trunk !== false) {
|
|
378
|
+
// A turn of the body bends at the hips, the thighs keep their angles from upright.
|
|
379
|
+
qa.copy(localQ[BODY]).invert();
|
|
380
|
+
localQ[L_THIGH].premultiply(qa);
|
|
381
|
+
localQ[R_THIGH].premultiply(qa);
|
|
382
|
+
}
|
|
383
|
+
forward(v, 1, anim.footLocal);
|
|
384
|
+
if (anim.hands) {
|
|
385
|
+
if (anim.hands.includes('l')) {
|
|
386
|
+
armIK(v, L_SHOULDER, LH, LP, anim.handFrame === 'ground');
|
|
387
|
+
}
|
|
388
|
+
if (anim.hands.includes('r')) {
|
|
389
|
+
armIK(v, R_SHOULDER, RH, RP, anim.handFrame === 'ground');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (plant) {
|
|
393
|
+
legIK(v, L_THIGH, LF, 1);
|
|
394
|
+
legIK(v, R_THIGH, RF, -1);
|
|
395
|
+
}
|
|
396
|
+
forward(v, 1, anim.footLocal);
|
|
397
|
+
if (!plant) {
|
|
398
|
+
const dz = v[LIFT] - lowest();
|
|
399
|
+
for (let i = 1; i < NODE_COUNT; i++) {
|
|
400
|
+
worldP[i].z += dz;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (let i = 0; i < NODE_COUNT; i++) {
|
|
404
|
+
const o = offset + i * 7;
|
|
405
|
+
const p = worldP[i];
|
|
406
|
+
const q = worldQ[i];
|
|
407
|
+
out[o] = p.x;
|
|
408
|
+
out[o + 1] = p.y;
|
|
409
|
+
out[o + 2] = p.z;
|
|
410
|
+
out[o + 3] = q.x;
|
|
411
|
+
out[o + 4] = q.y;
|
|
412
|
+
out[o + 5] = q.z;
|
|
413
|
+
out[o + 6] = q.w;
|
|
414
|
+
}
|
|
415
|
+
return out;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Channel helpers for animations written as functions.
|
|
419
|
+
function ang(v, node, x, y = 0, z = 0) {
|
|
420
|
+
const j = node * 3;
|
|
421
|
+
v[j] = x;
|
|
422
|
+
v[j + 1] = y;
|
|
423
|
+
v[j + 2] = z;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function addAng(v, node, x, y = 0, z = 0) {
|
|
427
|
+
const j = node * 3;
|
|
428
|
+
v[j] += x;
|
|
429
|
+
v[j + 1] += y;
|
|
430
|
+
v[j + 2] += z;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function setVec(v, o, a, b, c) {
|
|
434
|
+
v[o] = a;
|
|
435
|
+
v[o + 1] = b;
|
|
436
|
+
v[o + 2] = c;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// A running gait into v, phase in turns, amount from 0 for a walk to 1 for a sprint.
|
|
440
|
+
function gait(v, phase, amount) {
|
|
441
|
+
const p = phase * Math.PI * 2;
|
|
442
|
+
const s = Math.sin(p);
|
|
443
|
+
const c = Math.cos(p);
|
|
444
|
+
const swingL = Math.max(0, c);
|
|
445
|
+
const swingR = Math.max(0, -c);
|
|
446
|
+
const thigh = 22 + 26 * amount;
|
|
447
|
+
ang(v, L_THIGH, -thigh * s, -2);
|
|
448
|
+
ang(v, R_THIGH, thigh * s, 2);
|
|
449
|
+
ang(v, L_KNEE, 6 + (30 + 60 * amount) * swingL ** 1.5 + 8 * amount);
|
|
450
|
+
ang(v, R_KNEE, 6 + (30 + 60 * amount) * swingR ** 1.5 + 8 * amount);
|
|
451
|
+
ang(v, L_ANKLE, -8 * s);
|
|
452
|
+
ang(v, R_ANKLE, 8 * s);
|
|
453
|
+
const arm = 16 + 30 * amount;
|
|
454
|
+
ang(v, L_SHOULDER, arm * s, -6);
|
|
455
|
+
ang(v, R_SHOULDER, -arm * s, 6);
|
|
456
|
+
ang(v, L_ELBOW, -25 - 55 * amount - 10 * Math.max(0, -s));
|
|
457
|
+
ang(v, R_ELBOW, -25 - 55 * amount - 10 * Math.max(0, s));
|
|
458
|
+
ang(v, BODY, 3 + 9 * amount, 0, 5 * s * (1 - amount * 0.5));
|
|
459
|
+
ang(v, MIDDLE, 2, 0, -7 * s);
|
|
460
|
+
ang(v, NECK, -4 - 6 * amount, 0, 2 * s);
|
|
461
|
+
// A flight phase while running.
|
|
462
|
+
v[LIFT] = amount > 0.3 ? 0.07 * amount * Math.max(0, Math.sin(p * 2)) : 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// A quiet stance with breathing, t in seconds, repeating every period seconds.
|
|
466
|
+
function stance(v, t, period = 6) {
|
|
467
|
+
const f = (Math.PI * 2) / period;
|
|
468
|
+
const b = Math.sin(t * f * Math.max(1, Math.round(3 / f)));
|
|
469
|
+
const w = Math.sin(t * f);
|
|
470
|
+
ang(v, BODY, 1, 1.6 * w, 1.5 * w);
|
|
471
|
+
ang(v, MIDDLE, 1.2 * b, -1.2 * w);
|
|
472
|
+
ang(v, NECK, -1 - b, 0, 3 * Math.sin(t * f + 1));
|
|
473
|
+
ang(v, L_SHOULDER, 2 + 2 * b, -7);
|
|
474
|
+
ang(v, R_SHOULDER, 2 + 2 * b, 7);
|
|
475
|
+
ang(v, L_ELBOW, -14 - 2 * b);
|
|
476
|
+
ang(v, R_ELBOW, -14 - 2 * b);
|
|
477
|
+
ang(v, L_THIGH, -2, -4 + 1.5 * w);
|
|
478
|
+
ang(v, R_THIGH, -2, 4 + 1.5 * w);
|
|
479
|
+
ang(v, L_KNEE, 4 - 2 * w);
|
|
480
|
+
ang(v, R_KNEE, 4 + 2 * w);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const HEART = 0.36;
|
|
484
|
+
|
|
485
|
+
// The library. Each entry has a label, a kind, a duration in seconds and whether it loops, and
|
|
486
|
+
// keys or a function fn of time and the channel vector.
|
|
487
|
+
const LIBRARY = {
|
|
488
|
+
// Celebrations.
|
|
489
|
+
arms_up: {
|
|
490
|
+
label: t('animate.arms_up'), kind: 'celebration', duration: 1.6, loop: true, hands: 'lr',
|
|
491
|
+
base: { lp: [1, -0.3, -0.4], rp: [-1, -0.3, -0.4], neck: [-18], middle: [-6] },
|
|
492
|
+
keys: [
|
|
493
|
+
{ t: 0, lh: [0.42, 0.08, 1.0], rh: [-0.42, 0.08, 1.0], lift: 0 },
|
|
494
|
+
{ t: 0.4, lh: [0.5, 0.1, 0.92], rh: [-0.5, 0.1, 0.92], lift: 0.08, lk: [10], rk: [10] },
|
|
495
|
+
{ t: 0.8, lh: [0.4, 0.06, 1.02], rh: [-0.4, 0.06, 1.02], lift: 0 },
|
|
496
|
+
{ t: 1.2, lh: [0.5, 0.1, 0.92], rh: [-0.5, 0.1, 0.92], lift: 0.08, lk: [10], rk: [10] },
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
knee_slide: {
|
|
500
|
+
label: t('animate.knee_slide'), kind: 'celebration', duration: 3.2, loop: false, footLocal: true, hands: 'lr',
|
|
501
|
+
base: { lp: [1, -0.5, 0], rp: [-1, -0.5, 0] },
|
|
502
|
+
keys: [
|
|
503
|
+
{
|
|
504
|
+
t: 0, f: 0, body: [10], lt: [-30], rt: [25], lk: [40], rk: [70], la: [10], ra: [30],
|
|
505
|
+
lh: [0.2, -0.15, -0.05], rh: [-0.2, 0.3, 0.05],
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
t: 0.35, f: 1.6, body: [-5], lt: [-5], rt: [-5], lk: [88], rk: [88], la: [60], ra: [60],
|
|
509
|
+
lh: [0.45, 0.2, 0.35], rh: [-0.45, 0.2, 0.35], lift: 0.1,
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
t: 0.7, f: 3.0, body: [-12], middle: [-6], neck: [-16], lt: [4], rt: [4], lk: [90], rk: [90],
|
|
513
|
+
la: [70], ra: [70], lh: [0.6, 0.0, 0.6], rh: [-0.6, 0.0, 0.6], e: 'inout',
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
t: 1.5, f: 4.2, body: [-16], middle: [-8], neck: [-20], lt: [4], rt: [4], lk: [88], rk: [88],
|
|
517
|
+
la: [70], ra: [70], lh: [0.5, 0.05, 0.95], rh: [-0.5, 0.05, 0.95], e: 'inout',
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
t: 2.1, f: 4.3, body: [-13], middle: [-6], neck: [-18], lt: [4], rt: [4], lk: [88], rk: [88],
|
|
521
|
+
la: [70], ra: [70], lh: [0.55, 0.1, 0.8], rh: [-0.55, 0.1, 0.8],
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
t: 2.6, f: 4.3, body: [-16], middle: [-8], neck: [-22], lt: [4], rt: [4], lk: [88], rk: [88],
|
|
525
|
+
la: [70], ra: [70], lh: [0.5, 0.05, 0.98], rh: [-0.5, 0.05, 0.98],
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
t: 3.2, f: 4.3, body: [-14], middle: [-7], neck: [-20], lt: [4], rt: [4], lk: [88], rk: [88],
|
|
529
|
+
la: [70], ra: [70], lh: [0.55, 0.1, 0.85], rh: [-0.55, 0.1, 0.85],
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
},
|
|
533
|
+
jump_fist: {
|
|
534
|
+
label: t('animate.jump_fist'), kind: 'celebration', duration: 1.5, loop: true, plant: true, hands: 'lr',
|
|
535
|
+
base: { lp: [1, -0.6, -0.3], rp: [-1, -0.3, -0.2] },
|
|
536
|
+
keys: [
|
|
537
|
+
{ t: 0, hz: -0.05, lh: [0.2, 0.15, -0.05], rh: [-0.25, 0.1, 0.15] },
|
|
538
|
+
{ t: 0.3, hz: -0.32, body: [24], neck: [-10], lh: [0.22, 0.05, -0.15], rh: [-0.22, -0.05, -0.12], e: 'inout' },
|
|
539
|
+
{ t: 0.55, hz: 0.02, lift: 0.4, body: [-6], neck: [-20], lf: [0.12, 0.05, 0.12], rf: [-0.12, -0.05, 0.05], lh: [0.18, 0.2, 0.05], rh: [-0.18, 0.12, 1.1] },
|
|
540
|
+
{ t: 0.75, hz: -0.1, lift: 0.5, body: [-4], neck: [-22], lf: [0.12, 0.1, 0.3], rf: [-0.12, 0.0, 0.25], lh: [0.18, 0.22, 0.05], rh: [-0.2, 0.15, 1.08] },
|
|
541
|
+
{ t: 1.0, hz: -0.3, lift: 0, body: [18], neck: [-10], lh: [0.22, 0.1, -0.1], rh: [-0.3, 0.25, 0.4], e: 'inout' },
|
|
542
|
+
{ t: 1.3, hz: -0.05, body: [2], lh: [0.2, 0.15, -0.05], rh: [-0.25, 0.1, 0.15] },
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
team_huddle: {
|
|
546
|
+
label: t('animate.team_huddle'), kind: 'celebration', duration: 1.0, loop: true, plant: true, hands: 'lr',
|
|
547
|
+
base: {
|
|
548
|
+
body: [30], middle: [12], neck: [-25], lp: [1, -0.2, 0.6], rp: [-1, -0.2, 0.6],
|
|
549
|
+
lf: [0.2, 0.05, 0], rf: [-0.2, 0.05, 0],
|
|
550
|
+
},
|
|
551
|
+
keys: [
|
|
552
|
+
{ t: 0, hz: -0.14, lh: [0.62, 0.15, 0.42], rh: [-0.62, 0.15, 0.42] },
|
|
553
|
+
{ t: 0.5, hz: -0.03, lift: 0.12, lh: [0.6, 0.18, 0.48], rh: [-0.6, 0.18, 0.48] },
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
dance: {
|
|
557
|
+
label: t('animate.dance'), kind: 'celebration', duration: 2.0, loop: true, hands: 'lr',
|
|
558
|
+
fn(t, v) {
|
|
559
|
+
const beat = (t / 0.5) * Math.PI;
|
|
560
|
+
const s = Math.sin(beat);
|
|
561
|
+
const c = Math.cos(beat);
|
|
562
|
+
const bar = Math.sin(t * Math.PI);
|
|
563
|
+
ang(v, BODY, 4, 8 * s, 14 * bar);
|
|
564
|
+
ang(v, MIDDLE, 2, -10 * s, -16 * bar);
|
|
565
|
+
ang(v, NECK, -6 + 6 * Math.abs(c), 6 * s, 8 * bar);
|
|
566
|
+
ang(v, L_THIGH, -18 - 12 * Math.max(0, s), -8);
|
|
567
|
+
ang(v, R_THIGH, -18 - 12 * Math.max(0, -s), 8);
|
|
568
|
+
ang(v, L_KNEE, 28 + 30 * Math.max(0, s));
|
|
569
|
+
ang(v, R_KNEE, 28 + 30 * Math.max(0, -s));
|
|
570
|
+
v[SIDE] = 0.12 * bar;
|
|
571
|
+
v[LIFT] = 0.03 * Math.abs(s);
|
|
572
|
+
setVec(v, LH, 0.3 + 0.12 * bar, 0.28 + 0.1 * c, 0.25 + 0.25 * Math.max(0, s));
|
|
573
|
+
setVec(v, RH, -0.3 + 0.12 * bar, 0.28 - 0.1 * c, 0.25 + 0.25 * Math.max(0, -s));
|
|
574
|
+
setVec(v, LP, 1, -0.5, -0.6);
|
|
575
|
+
setVec(v, RP, -1, -0.5, -0.6);
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
airplane: {
|
|
579
|
+
label: t('animate.airplane'), kind: 'celebration', duration: 6.0, loop: true,
|
|
580
|
+
fn(t, v) {
|
|
581
|
+
const r = 3.2;
|
|
582
|
+
const turn = (t / 6.0) * Math.PI * 2;
|
|
583
|
+
gait(v, t / 0.6, 0.55);
|
|
584
|
+
v[FWD] = r * Math.sin(turn);
|
|
585
|
+
v[SIDE] = r * (1 - Math.cos(turn));
|
|
586
|
+
v[YAW] = (turn / DEG);
|
|
587
|
+
// Lean into the turn with the wings tipped.
|
|
588
|
+
ang(v, BODY, 10, 14, 0);
|
|
589
|
+
ang(v, MIDDLE, 4, 6, 0);
|
|
590
|
+
ang(v, NECK, -10, -10);
|
|
591
|
+
ang(v, L_SHOULDER, 10, -88);
|
|
592
|
+
ang(v, R_SHOULDER, 10, 88);
|
|
593
|
+
ang(v, L_ELBOW, -6);
|
|
594
|
+
ang(v, R_ELBOW, -6);
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
backflip: {
|
|
598
|
+
label: t('animate.backflip'), kind: 'celebration', duration: 2.2, loop: false, footLocal: true, trunk: false, hands: 'lr',
|
|
599
|
+
base: { lp: [1, -0.4, -0.2], rp: [-1, -0.4, -0.2] },
|
|
600
|
+
keys: [
|
|
601
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
602
|
+
{ t: 0.35, body: [25], lt: [-50], rt: [-50], lk: [80], rk: [80], la: [-25], ra: [-25], lh: [0.2, -0.35, -0.1], rh: [-0.2, -0.35, -0.1], e: 'inout' },
|
|
603
|
+
{ t: 0.55, body: [-15], lift: 0.25, la: [40], ra: [40], lh: [0.25, 0.1, 1.0], rh: [-0.25, 0.1, 1.0] },
|
|
604
|
+
{ t: 0.75, body: [-130], lift: 0.75, lt: [-100], rt: [-100], lk: [125], rk: [125], lh: [0.2, 0.35, 0.05], rh: [-0.2, 0.35, 0.05], e: 'linear' },
|
|
605
|
+
{ t: 0.95, body: [-250], lift: 0.65, lt: [-105], rt: [-105], lk: [125], rk: [125], lh: [0.2, 0.35, 0.05], rh: [-0.2, 0.35, 0.05], e: 'linear' },
|
|
606
|
+
{ t: 1.15, body: [-330], lift: 0.12, lt: [-40], rt: [-40], lk: [50], rk: [50], la: [20], ra: [20], lh: [0.45, 0.2, 0.6], rh: [-0.45, 0.2, 0.6] },
|
|
607
|
+
{ t: 1.3, body: [-340], lift: 0, lt: [-55], rt: [-55], lk: [85], rk: [85], la: [-30], ra: [-30], lh: [0.45, 0.3, 0.45], rh: [-0.45, 0.3, 0.45] },
|
|
608
|
+
{ t: 1.8, body: [-360], neck: [-15], lt: [-3], rt: [-3], lk: [4], rk: [4], la: [0], ra: [0], lh: [0.42, 0.08, 1.0], rh: [-0.42, 0.08, 1.0], e: 'inout' },
|
|
609
|
+
{ t: 2.2, body: [-360], neck: [-18], lh: [0.45, 0.08, 0.98], rh: [-0.45, 0.08, 0.98] },
|
|
610
|
+
],
|
|
611
|
+
},
|
|
612
|
+
salute: {
|
|
613
|
+
label: t('animate.salute'), kind: 'celebration', duration: 2.4, loop: false, hands: 'lr', ease: 'inout',
|
|
614
|
+
base: { lp: [1, -0.6, -0.5], rp: [-1, 0.1, 0.35], middle: [-4], neck: [-4] },
|
|
615
|
+
keys: [
|
|
616
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R, rp: POLE_R },
|
|
617
|
+
{ t: 0.5, lh: [0.15, 0.0, -0.13], rh: [-0.11, 0.1, 0.71], neck: [-6] },
|
|
618
|
+
{ t: 2.4, lh: [0.15, 0.0, -0.13], rh: [-0.11, 0.1, 0.71], neck: [-6] },
|
|
619
|
+
],
|
|
620
|
+
},
|
|
621
|
+
shush: {
|
|
622
|
+
label: t('animate.shush'), kind: 'celebration', duration: 2.4, loop: false, hands: 'lr', ease: 'inout',
|
|
623
|
+
base: { lp: [1, -0.6, -0.5], rp: [-1, -0.2, -0.8] },
|
|
624
|
+
keys: [
|
|
625
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
626
|
+
{ t: 0.45, rh: [-0.01, 0.16, 0.56], lh: [0.28, 0.12, -0.12], body: [4], neck: [6], middle: [3] },
|
|
627
|
+
{ t: 2.4, rh: [-0.01, 0.16, 0.56], lh: [0.28, 0.12, -0.12], body: [4], neck: [8], middle: [3] },
|
|
628
|
+
],
|
|
629
|
+
},
|
|
630
|
+
heart_hands: {
|
|
631
|
+
label: t('animate.heart_hands'), kind: 'celebration', duration: 2.6, loop: false, hands: 'lr', ease: 'inout',
|
|
632
|
+
base: { lp: [1, -0.1, -0.6], rp: [-1, -0.1, -0.6] },
|
|
633
|
+
keys: [
|
|
634
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
635
|
+
{ t: 0.5, lh: [0.06, HEART, 0.3], rh: [-0.06, HEART, 0.3], neck: [6], middle: [-3] },
|
|
636
|
+
{ t: 1.5, lh: [0.06, HEART + 0.03, 0.34], rh: [-0.06, HEART + 0.03, 0.34], neck: [4], middle: [-4] },
|
|
637
|
+
{ t: 2.6, lh: [0.06, HEART, 0.3], rh: [-0.06, HEART, 0.3], neck: [6], middle: [-3] },
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
robot_dance: {
|
|
641
|
+
label: t('animate.robot_dance'), kind: 'celebration', duration: 2.4, loop: true, hands: 'lr', ease: 'snap',
|
|
642
|
+
base: { lp: [1, 0, -0.2], rp: [-1, 0, -0.2], lk: [12], rk: [12], lt: [-6], rt: [-6] },
|
|
643
|
+
keys: [
|
|
644
|
+
{ t: 0, lh: [0.38, 0.22, 0.42], rh: [-0.3, 0.28, 0.0], middle: [0, 0, 20], neck: [0, 0, -20] },
|
|
645
|
+
{ t: 0.6, lh: [0.3, 0.28, 0.0], rh: [-0.38, 0.22, 0.42], middle: [0, 0, -20], neck: [0, 0, 20] },
|
|
646
|
+
{ t: 1.2, lh: [0.45, 0.0, 0.5], rh: [-0.45, 0.0, 0.5], middle: [0, 0, 0], neck: [0, 0, 0], lp: [1, 0, -1], rp: [-1, 0, -1], lift: 0.0 },
|
|
647
|
+
{ t: 1.8, lh: [0.3, 0.32, 0.25], rh: [-0.3, 0.32, 0.25], middle: [0, 0, 0], neck: [12, 0, 0], body: [0, 0, 30] },
|
|
648
|
+
],
|
|
649
|
+
},
|
|
650
|
+
bow: {
|
|
651
|
+
label: t('animate.bow'), kind: 'celebration', duration: 2.6, loop: false, hands: 'lr', ease: 'inout',
|
|
652
|
+
base: { lp: [1, 0.3, -0.3], rp: [-1, -0.2, -0.3] },
|
|
653
|
+
keys: [
|
|
654
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
655
|
+
{ t: 0.6, rh: [-0.05, 0.2, 0.05], lh: [0.05, -0.2, 0.0], rt: [10, 0, 0], lt: [-10, 0, 0], rk: [12], la: [0] },
|
|
656
|
+
{ t: 1.3, body: [42], middle: [10], neck: [20], rh: [-0.05, 0.2, 0.05], lh: [0.05, -0.2, 0.0], rt: [10], lt: [-10], rk: [12] },
|
|
657
|
+
{ t: 1.9, body: [42], middle: [10], neck: [20], rh: [-0.05, 0.2, 0.05], lh: [0.05, -0.2, 0.0], rt: [10], lt: [-10], rk: [12] },
|
|
658
|
+
{ t: 2.6, body: [0], middle: [0], neck: [-6], rh: [-0.05, 0.2, 0.05], lh: [0.05, -0.2, 0.0], rt: [10], lt: [-10], rk: [12] },
|
|
659
|
+
],
|
|
660
|
+
},
|
|
661
|
+
worm: {
|
|
662
|
+
label: t('animate.worm'), kind: 'celebration', duration: 4.0, loop: false, footLocal: true, trunk: false,
|
|
663
|
+
fn(t, v) {
|
|
664
|
+
const down = smoothstep(Math.min(1, t / 0.7));
|
|
665
|
+
const wave = Math.max(0, t - 0.7);
|
|
666
|
+
const p = wave * Math.PI * 2 * 1.1;
|
|
667
|
+
const amp = Math.min(1, wave * 2);
|
|
668
|
+
stance(v, t, 6);
|
|
669
|
+
for (let c = 0; c < 42; c++) {
|
|
670
|
+
v[c] *= 1 - down;
|
|
671
|
+
}
|
|
672
|
+
ang(v, BODY, 90 * down + amp * 14 * Math.sin(p), 0, 0);
|
|
673
|
+
ang(v, MIDDLE, -amp * 22 * Math.sin(p - 0.9), 0, 0);
|
|
674
|
+
ang(v, NECK, -25 * down - amp * 15 * Math.sin(p - 1.8), 0, 0);
|
|
675
|
+
ang(v, L_THIGH, amp * 20 * Math.sin(p + 1.0), -4);
|
|
676
|
+
ang(v, R_THIGH, amp * 20 * Math.sin(p + 1.0), 4);
|
|
677
|
+
ang(v, L_KNEE, 10 * down + amp * (22 + 22 * Math.sin(p + 2.0)));
|
|
678
|
+
ang(v, R_KNEE, 10 * down + amp * (22 + 22 * Math.sin(p + 2.0)));
|
|
679
|
+
ang(v, L_ANKLE, 60 * down);
|
|
680
|
+
ang(v, R_ANKLE, 60 * down);
|
|
681
|
+
// Hands flat under the shoulders, push ups make the wave.
|
|
682
|
+
ang(v, L_SHOULDER, -70 * down + amp * 10 * Math.sin(p), -12 * down);
|
|
683
|
+
ang(v, R_SHOULDER, -70 * down + amp * 10 * Math.sin(p), 12 * down);
|
|
684
|
+
ang(v, L_ELBOW, -60 * down - amp * 25 * Math.max(0, Math.sin(p)));
|
|
685
|
+
ang(v, R_ELBOW, -60 * down - amp * 25 * Math.max(0, Math.sin(p)));
|
|
686
|
+
v[FWD] = 0.9 * down + 0.35 * wave;
|
|
687
|
+
v[LIFT] = 0.05 * amp * Math.max(0, Math.sin(p - 0.4));
|
|
688
|
+
},
|
|
689
|
+
},
|
|
690
|
+
clap: {
|
|
691
|
+
label: t('animate.clap'), kind: 'celebration', duration: 0.6, loop: true, hands: 'lr',
|
|
692
|
+
base: { lp: [1, -0.3, -0.7], rp: [-1, -0.3, -0.7], middle: [-2], neck: [-5] },
|
|
693
|
+
keys: [
|
|
694
|
+
{ t: 0, lh: [0.03, 0.32, 0.36], rh: [-0.03, 0.32, 0.36] },
|
|
695
|
+
{ t: 0.3, lh: [0.24, 0.26, 0.4], rh: [-0.24, 0.26, 0.4] },
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
|
|
699
|
+
// Utility.
|
|
700
|
+
idle: {
|
|
701
|
+
label: t('animate.idle'), kind: 'utility', duration: 6.0, loop: true,
|
|
702
|
+
fn(t, v) {
|
|
703
|
+
stance(v, t, 6);
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
walk: {
|
|
707
|
+
label: t('animate.walk'), kind: 'utility', duration: 1.1, loop: true, speed: 1.4,
|
|
708
|
+
fn(t, v) {
|
|
709
|
+
gait(v, t / 1.1, 0);
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
jog: {
|
|
713
|
+
label: t('animate.jog'), kind: 'utility', duration: 0.7, loop: true, speed: 3.4,
|
|
714
|
+
fn(t, v) {
|
|
715
|
+
gait(v, t / 0.7, 0.6);
|
|
716
|
+
},
|
|
717
|
+
},
|
|
718
|
+
card_raise: {
|
|
719
|
+
label: t('animate.card_raise'), kind: 'utility', duration: 2.0, loop: false, hands: 'lr', ease: 'inout',
|
|
720
|
+
base: { lp: [1, -0.6, -0.5], rp: [-1, 0.2, -0.4] },
|
|
721
|
+
keys: [
|
|
722
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
723
|
+
{ t: 0.25, rh: [-0.24, 0.3, 0.3], neck: [-4] },
|
|
724
|
+
{ t: 0.55, rh: [-0.16, 0.12, 1.1], neck: [-10], middle: [-4] },
|
|
725
|
+
{ t: 2.0, rh: [-0.16, 0.12, 1.1], neck: [-10], middle: [-4] },
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
point: {
|
|
729
|
+
label: t('animate.point'), kind: 'utility', duration: 1.6, loop: false, hands: 'lr', ease: 'inout',
|
|
730
|
+
base: { lp: [1, -0.6, -0.5], rp: [-1, 0, -0.2] },
|
|
731
|
+
keys: [
|
|
732
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
733
|
+
{ t: 0.4, rh: [-0.42, 0.55, 0.52], neck: [0, 0, -14], middle: [0, 0, -8] },
|
|
734
|
+
{ t: 1.6, rh: [-0.42, 0.55, 0.52], neck: [0, 0, -14], middle: [0, 0, -8] },
|
|
735
|
+
],
|
|
736
|
+
},
|
|
737
|
+
whistle: {
|
|
738
|
+
label: t('animate.whistle'), kind: 'utility', duration: 1.6, loop: false, hands: 'lr', ease: 'inout',
|
|
739
|
+
base: { lp: [1, -0.2, -0.3], rp: [-1, -0.3, -0.7] },
|
|
740
|
+
keys: [
|
|
741
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R },
|
|
742
|
+
{ t: 0.3, rh: [-0.02, 0.16, 0.55], lh: [0.35, 0.5, 0.6], neck: [-6] },
|
|
743
|
+
{ t: 1.6, rh: [-0.02, 0.16, 0.55], lh: [0.35, 0.5, 0.62], neck: [-6] },
|
|
744
|
+
],
|
|
745
|
+
},
|
|
746
|
+
throw_in_hold: {
|
|
747
|
+
label: t('animate.throw_in_hold'), kind: 'utility', duration: 1.2, loop: true, hands: 'lr',
|
|
748
|
+
base: { lp: [1, -0.4, 0.5], rp: [-1, -0.4, 0.5], middle: [-8], neck: [-6], lt: [-4], rt: [6], lk: [6] },
|
|
749
|
+
keys: [
|
|
750
|
+
{ t: 0, lh: [0.11, -0.12, 0.84], rh: [-0.11, -0.12, 0.84] },
|
|
751
|
+
{ t: 0.6, lh: [0.11, -0.1, 0.86], rh: [-0.11, -0.1, 0.86], middle: [-10] },
|
|
752
|
+
],
|
|
753
|
+
},
|
|
754
|
+
place_ball: {
|
|
755
|
+
label: t('animate.place_ball'), kind: 'utility', duration: 2.2, loop: false, plant: true, hands: 'lr', handFrame: 'ground', ease: 'inout',
|
|
756
|
+
base: { lp: [1, 0.2, 0.2], rp: [-1, 0.2, 0.2], lf: [0.15, -0.05, 0], rf: [-0.15, 0.1, 0] },
|
|
757
|
+
keys: [
|
|
758
|
+
{ t: 0, hz: 0, lh: [0.12, 0.2, 1.0], rh: [-0.12, 0.2, 1.0] },
|
|
759
|
+
{ t: 0.8, hz: -0.42, body: [38], middle: [12], neck: [-10], lh: [0.12, 0.42, 0.13], rh: [-0.12, 0.42, 0.13] },
|
|
760
|
+
{ t: 1.3, hz: -0.42, body: [38], middle: [12], neck: [-10], lh: [0.14, 0.42, 0.14], rh: [-0.14, 0.42, 0.14] },
|
|
761
|
+
{ t: 2.2, hz: 0, body: [2], lh: [0.2, 0.05, 0.8], rh: [-0.2, 0.05, 0.8] },
|
|
762
|
+
],
|
|
763
|
+
},
|
|
764
|
+
hands_on_head: {
|
|
765
|
+
label: t('animate.hands_on_head'), kind: 'utility', duration: 2.0, loop: false, hands: 'lr', ease: 'inout',
|
|
766
|
+
base: { lp: [1, -0.1, 0.4], rp: [-1, -0.1, 0.4] },
|
|
767
|
+
keys: [
|
|
768
|
+
{ t: 0, lh: HAND_REST_L, rh: HAND_REST_R, lp: POLE_L, rp: POLE_R },
|
|
769
|
+
{ t: 0.45, lh: [0.08, 0.04, 0.78], rh: [-0.08, 0.04, 0.78], neck: [-12], body: [-3] },
|
|
770
|
+
{ t: 2.0, lh: [0.08, 0.04, 0.78], rh: [-0.08, 0.04, 0.78], neck: [-16], body: [-3] },
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
|
|
774
|
+
// Idles and entrances for the podium and the stage of the player of the match.
|
|
775
|
+
idle_confident: {
|
|
776
|
+
label: t('animate.idle_confident'), kind: 'idle', duration: 4.0, loop: true, hands: 'lr',
|
|
777
|
+
base: { lp: [1, -0.6, 0.1], rp: [-1, -0.6, 0.1] },
|
|
778
|
+
fn(t, v) {
|
|
779
|
+
stance(v, t, 4);
|
|
780
|
+
ang(v, MIDDLE, -5 + Math.sin(t * Math.PI) * 1.2, 0, 0);
|
|
781
|
+
ang(v, NECK, -6, 0, 6 * Math.sin(t * Math.PI / 2));
|
|
782
|
+
ang(v, L_THIGH, -2, -8);
|
|
783
|
+
ang(v, R_THIGH, -2, 8);
|
|
784
|
+
setVec(v, LH, 0.22, 0.02, -0.04);
|
|
785
|
+
setVec(v, RH, -0.22, 0.02, -0.04);
|
|
786
|
+
setVec(v, LP, 1, -0.6, 0.1);
|
|
787
|
+
setVec(v, RP, -1, -0.6, 0.1);
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
idle_bounce: {
|
|
791
|
+
label: t('animate.idle_bounce'), kind: 'idle', duration: 4.0, loop: true,
|
|
792
|
+
fn(t, v) {
|
|
793
|
+
const p = (t / 0.8) * Math.PI * 2;
|
|
794
|
+
stance(v, t, 4);
|
|
795
|
+
v[LIFT] = 0.05 * Math.max(0, Math.sin(p));
|
|
796
|
+
ang(v, L_KNEE, 14 - 10 * Math.max(0, Math.sin(p)));
|
|
797
|
+
ang(v, R_KNEE, 14 - 10 * Math.max(0, Math.sin(p)));
|
|
798
|
+
ang(v, L_THIGH, -6, -5);
|
|
799
|
+
ang(v, R_THIGH, -6, 5);
|
|
800
|
+
ang(v, L_ANKLE, 18 * Math.max(0, Math.sin(p)));
|
|
801
|
+
ang(v, R_ANKLE, 18 * Math.max(0, Math.sin(p)));
|
|
802
|
+
ang(v, L_SHOULDER, 4 * Math.sin(p * 2), -10);
|
|
803
|
+
ang(v, R_SHOULDER, -4 * Math.sin(p * 2), 10);
|
|
804
|
+
ang(v, L_ELBOW, -20 + 8 * Math.sin(p * 2));
|
|
805
|
+
ang(v, R_ELBOW, -20 - 8 * Math.sin(p * 2));
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
walkon_wave: {
|
|
809
|
+
label: t('animate.walkon_wave'), kind: 'walkon', duration: 2.2, loop: true, hands: 'r', speed: 1.4,
|
|
810
|
+
fn(t, v) {
|
|
811
|
+
gait(v, t / 1.1, 0);
|
|
812
|
+
const w = Math.sin(t * Math.PI * 2 / 0.55);
|
|
813
|
+
setVec(v, RH, -0.38 + 0.12 * w, 0.12, 0.9);
|
|
814
|
+
setVec(v, RP, -1, 0.2, -0.2);
|
|
815
|
+
addAng(v, NECK, 0, 0, -10);
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// The animations by name, each with label, kind, duration in seconds, loop and speed in meters
|
|
821
|
+
// per second for the gaits.
|
|
822
|
+
export const ANIMATIONS = Object.fromEntries(Object.entries(LIBRARY).map(([name, a]) => [name, {
|
|
823
|
+
label: a.label, kind: a.kind, duration: a.duration, loop: !!a.loop, speed: a.speed || 0,
|
|
824
|
+
}]));
|
|
825
|
+
|
|
826
|
+
export function names(kind = null) {
|
|
827
|
+
return Object.keys(LIBRARY).filter((n) => !kind || LIBRARY[n].kind === kind);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export function has(name) {
|
|
831
|
+
return Object.prototype.hasOwnProperty.call(LIBRARY, name);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
export function duration(name) {
|
|
835
|
+
return has(name) ? LIBRARY[name].duration : 0;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export function loops(name) {
|
|
839
|
+
return has(name) && !!LIBRARY[name].loop;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// The ground speed that matches the gait, for callers that move the player.
|
|
843
|
+
export function speed(name) {
|
|
844
|
+
return has(name) ? LIBRARY[name].speed || 0 : 0;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function channels(anim, t, out) {
|
|
848
|
+
if (anim.keys) {
|
|
849
|
+
sampleKeys(anim, t, out);
|
|
850
|
+
} else {
|
|
851
|
+
defaults(out);
|
|
852
|
+
if (anim.base) {
|
|
853
|
+
writeKey(anim.base, out);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (anim.fn) {
|
|
857
|
+
anim.fn(t, out);
|
|
858
|
+
}
|
|
859
|
+
return out;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Writes one player's nodes for animation name at time t seconds, standing at x, y of the
|
|
863
|
+
// engine frame and facing heading, radians about engine z with 0 facing engine minus y. Root
|
|
864
|
+
// motion of the animation moves along that heading. Returns out, or null for an unknown name.
|
|
865
|
+
export function pose(name, t, out = new Float32Array(PLAYER_FLOATS), opts = {}) {
|
|
866
|
+
const anim = LIBRARY[name];
|
|
867
|
+
if (!anim) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
channels(anim, Math.max(0, t), sample);
|
|
871
|
+
return solve(sample, anim, opts, out, opts.offset || 0);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Blends two animations, w from 0 for the first alone to 1 for the second alone, a crossfade.
|
|
875
|
+
export function poseBlend(a, ta, b, tb, w, out = new Float32Array(PLAYER_FLOATS), opts = {}) {
|
|
876
|
+
const A = LIBRARY[a];
|
|
877
|
+
const B = LIBRARY[b];
|
|
878
|
+
if (!A || !B) {
|
|
879
|
+
return pose(A ? a : b, A ? ta : tb, out, opts);
|
|
880
|
+
}
|
|
881
|
+
channels(A, ta, scratchA);
|
|
882
|
+
channels(B, tb, scratchB);
|
|
883
|
+
for (let c = 0; c < CHANNELS; c++) {
|
|
884
|
+
scratchC[c] = scratchA[c] + (scratchB[c] - scratchA[c]) * w;
|
|
885
|
+
}
|
|
886
|
+
// The flags of the animation that weighs more decide the solve.
|
|
887
|
+
return solve(scratchC, w < 0.5 ? A : B, opts, out, opts.offset || 0);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Blends two node buffers of players, positions linearly and rotations spherically, w of b.
|
|
891
|
+
export function blendNodes(a, b, w, out, offset = 0, count = 1) {
|
|
892
|
+
const n = count * PLAYER_FLOATS;
|
|
893
|
+
for (let o = offset; o < offset + n; o += 7) {
|
|
894
|
+
out[o] = a[o] + (b[o] - a[o]) * w;
|
|
895
|
+
out[o + 1] = a[o + 1] + (b[o + 1] - a[o + 1]) * w;
|
|
896
|
+
out[o + 2] = a[o + 2] + (b[o + 2] - a[o + 2]) * w;
|
|
897
|
+
qa.set(a[o + 3], a[o + 4], a[o + 5], a[o + 6]);
|
|
898
|
+
qb.set(b[o + 3], b[o + 4], b[o + 5], b[o + 6]);
|
|
899
|
+
if (qa.lengthSq() < 1e-8) {
|
|
900
|
+
qa.copy(qb);
|
|
901
|
+
}
|
|
902
|
+
if (qb.lengthSq() < 1e-8) {
|
|
903
|
+
qb.copy(qa);
|
|
904
|
+
}
|
|
905
|
+
qa.slerp(qb, w);
|
|
906
|
+
out[o + 3] = qa.x;
|
|
907
|
+
out[o + 4] = qa.y;
|
|
908
|
+
out[o + 5] = qa.z;
|
|
909
|
+
out[o + 6] = qa.w;
|
|
910
|
+
}
|
|
911
|
+
return out;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// The hands of a posed player in the engine frame, left then right, from the elbows of any
|
|
915
|
+
// pose buffer, a match frame included. base is the player's first float.
|
|
916
|
+
export function handPoints(nodes, base = 0, out = new Float32Array(6)) {
|
|
917
|
+
for (let s = 0; s < 2; s++) {
|
|
918
|
+
const o = base + (s ? R_ELBOW : L_ELBOW) * 7;
|
|
919
|
+
qa.set(nodes[o + 3], nodes[o + 4], nodes[o + 5], nodes[o + 6]);
|
|
920
|
+
va.set(0, 0, -FOREARM).applyQuaternion(qa);
|
|
921
|
+
out[s * 3] = nodes[o] + va.x;
|
|
922
|
+
out[s * 3 + 1] = nodes[o + 1] + va.y;
|
|
923
|
+
out[s * 3 + 2] = nodes[o + 2] + va.z;
|
|
924
|
+
}
|
|
925
|
+
return out;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const hands = new Float32Array(6);
|
|
929
|
+
|
|
930
|
+
// Where a ball held in both hands sits, between the hands, for throw ins and placing the ball.
|
|
931
|
+
export function holdPoint(nodes, base = 0, out = new THREE.Vector3()) {
|
|
932
|
+
handPoints(nodes, base, hands);
|
|
933
|
+
return out.set((hands[0] + hands[3]) / 2, (hands[1] + hands[4]) / 2, (hands[2] + hands[5]) / 2);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// The heading that faces from a point towards another, both in the engine frame.
|
|
937
|
+
export function headingTo(dx, dy) {
|
|
938
|
+
return Math.atan2(dx, -dy);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function hash(a, b) {
|
|
942
|
+
let h = Math.imul((a | 0) ^ 0x9e3779b9, 0x85ebca6b) ^ Math.imul((b | 0) + 0x632be5ab, 0xc2b2ae35);
|
|
943
|
+
h ^= h >>> 15;
|
|
944
|
+
h = Math.imul(h, 0x2c1b3c6d);
|
|
945
|
+
return (h ^ (h >>> 13)) >>> 0;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// The celebration a player performs after a goal, its own choice, else one of its team's, else
|
|
949
|
+
// one of every celebration the library holds, picked by seed so a replay shows the same one.
|
|
950
|
+
// Clip names come back as they are, the caller plays them where the model has that clip.
|
|
951
|
+
export function pickCelebration(style, index, seed = 0) {
|
|
952
|
+
const roster = style && style.players ? style.players[index] : null;
|
|
953
|
+
if (roster && roster.celebration && roster.celebration !== 'team') {
|
|
954
|
+
return roster.celebration;
|
|
955
|
+
}
|
|
956
|
+
let list = style && Array.isArray(style.celebrations) ? style.celebrations : [];
|
|
957
|
+
if (!list.length) {
|
|
958
|
+
list = names('celebration');
|
|
959
|
+
}
|
|
960
|
+
return list[hash(seed, index) % list.length];
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// The procedural stand in for a celebration name the library lacks, a clip on a model without
|
|
964
|
+
// it among them.
|
|
965
|
+
export function fallbackCelebration(name, seed = 0) {
|
|
966
|
+
if (has(name)) {
|
|
967
|
+
return name;
|
|
968
|
+
}
|
|
969
|
+
const list = names('celebration');
|
|
970
|
+
return list[hash(seed, name ? name.length : 0) % list.length];
|
|
971
|
+
}
|
|
972
|
+
|