blacktrigram 0.7.67 → 0.7.69

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.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +9 -9
@@ -1 +1 @@
1
- {"version":3,"file":"KeyframeConfig.js","names":[],"sources":["../../../../src/systems/animation/builders/KeyframeConfig.ts"],"sourcesContent":["/**\n * Keyframe Configuration Helper\n *\n * Helper class for configuring animation keyframes with bone rotations, positions,\n * and integrated anatomy state (hands, feet, facial expressions, muscle activations).\n * 애니메이션 키프레임 설정 헬퍼 클래스 (해부학 상태 통합)\n *\n * @module systems/animation/KeyframeConfig\n * @korean 키프레임설정\n */\n\nimport type { AnimationKeyframe, SkeletalAnimation } from \"@/types/skeletal\";\nimport { BoneName } from \"@/types/skeletal\";\nimport * as THREE from \"three\";\nimport type { GuardPositionType } from \"./KoreanGuardPositions\";\nimport { getGuardPosition } from \"./KoreanGuardPositions\";\n\n/** Hand highlight mode for striking surface visualization */\nexport type HandHighlightMode =\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n// Forward declaration for builder type\ninterface AnimationBuilderLike {\n _addKeyframe(kf: AnimationKeyframe): void;\n at(time: number, easing?: string): KeyframeConfig;\n build(): SkeletalAnimation;\n}\n\n/**\n * Keyframe configuration helper\n *\n * Provides a fluent API for configuring bone rotations, positions,\n * and anatomy state (hands, feet, facial expressions) within a single keyframe.\n *\n * @example\n * ```typescript\n * const kf = new KeyframeConfig();\n * kf.rotate(BoneName.SHOULDER_R, -0.5, 0, 0.3)\n * .position(BoneName.HAND_R, 0.1, 0, 0)\n * .setLeftHandPose(\"fist\")\n * .setRightHandPose(\"fist\", \"knuckles\")\n * .setFootHighlight(\"right\", true)\n * .setFacialExpression(\"focused\");\n * ```\n *\n * @korean 키프레임설정헬퍼\n */\nexport class KeyframeConfig {\n /** Bone rotation data (bone -> euler rotation) */\n rotations = new Map<BoneName, THREE.Euler>();\n\n /** Bone position data (bone -> position offset) */\n positions = new Map<BoneName, THREE.Vector3>();\n\n /** Parent builder reference for fluent chaining */\n private builder: AnimationBuilderLike | null = null;\n\n /** Keyframe time */\n private time: number = 0;\n\n /** Keyframe easing function */\n private easing: string = \"linear\";\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE (해부학 상태) - Integrated hand, foot, and facial animation\n // ═══════════════════════════════════════════════════════════════════════════\n\n /** Left hand pose type */\n private _leftHandPose?: string;\n\n /** Right hand pose type */\n private _rightHandPose?: string;\n\n /** Left hand highlight mode */\n private _leftHandHighlightMode?: HandHighlightMode;\n\n /** Right hand highlight mode */\n private _rightHandHighlightMode?: HandHighlightMode;\n\n /** Left foot highlight state */\n private _leftFootHighlight?: boolean;\n\n /** Right foot highlight state */\n private _rightFootHighlight?: boolean;\n\n /** Facial expression */\n private _facialExpression?: string;\n\n /** Muscle activation targets */\n private _muscleActivations?: Map<string, number>;\n\n /**\n * Associate this keyframe with a builder for the `done()` method\n * @internal\n */\n setBuilder(\n builder: AnimationBuilderLike,\n time: number,\n easing: string,\n ): void {\n this.builder = builder;\n this.time = time;\n this.easing = easing;\n }\n\n /**\n * Set bone rotation (euler angles in radians)\n *\n * @param bone - Target bone name\n * @param x - X rotation (radians)\n * @param y - Y rotation (radians)\n * @param z - Z rotation (radians)\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * kf.rotate(BoneName.SHOULDER_R, -0.5, 0, 0.3);\n * ```\n *\n * @korean 뼈회전설정\n */\n rotate(bone: BoneName, x: number, y: number, z: number): this {\n this.rotations.set(bone, new THREE.Euler(x, y, z));\n return this;\n }\n\n /**\n * Set bone position offset\n *\n * @param bone - Target bone name\n * @param x - X position offset\n * @param y - Y position offset\n * @param z - Z position offset\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * kf.position(BoneName.FOOT_R, 0, 0.5, 0.3);\n * ```\n *\n * @korean 뼈위치설정\n */\n position(bone: BoneName, x: number, y: number, z: number): this {\n this.positions.set(bone, new THREE.Vector3(x, y, z));\n return this;\n }\n\n /**\n * Apply Korean guard position to arms\n *\n * Sets proper Korean martial arts guard positions (막기자세) for defensive posture.\n * Supports applying guard to one side or both sides.\n *\n * @param guardType - Type of guard (\"HIGH_GUARD\" | \"MIDDLE_GUARD\" | \"LOW_GUARD\")\n * @param side - Which side to apply (\"left\" | \"right\" | \"both\")\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * // Apply middle guard to both hands\n * kf.withGuard(\"MIDDLE_GUARD\");\n *\n * // Apply middle guard only to left hand (right hand attacks)\n * kf.withGuard(\"MIDDLE_GUARD\", \"left\");\n * ```\n *\n * @korean 방어자세적용\n */\n withGuard(\n guardType: GuardPositionType,\n side: \"left\" | \"right\" | \"both\" = \"both\",\n ): this {\n const guard = getGuardPosition(guardType);\n\n if (side === \"left\" || side === \"both\") {\n this.rotate(\n BoneName.SHOULDER_L,\n guard.left.shoulder[0],\n guard.left.shoulder[1],\n guard.left.shoulder[2],\n );\n this.rotate(\n BoneName.ELBOW_L,\n guard.left.elbow[0],\n guard.left.elbow[1],\n guard.left.elbow[2],\n );\n this.rotate(\n BoneName.WRIST_L,\n guard.left.wrist[0],\n guard.left.wrist[1],\n guard.left.wrist[2],\n );\n }\n\n if (side === \"right\" || side === \"both\") {\n this.rotate(\n BoneName.SHOULDER_R,\n guard.right.shoulder[0],\n guard.right.shoulder[1],\n guard.right.shoulder[2],\n );\n this.rotate(\n BoneName.ELBOW_R,\n guard.right.elbow[0],\n guard.right.elbow[1],\n guard.right.elbow[2],\n );\n this.rotate(\n BoneName.WRIST_R,\n guard.right.wrist[0],\n guard.right.wrist[1],\n guard.right.wrist[2],\n );\n }\n\n return this;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE SETTERS (해부학 상태 설정)\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Set left hand pose for this keyframe\n *\n * @param pose - Hand pose type (e.g., \"fist\", \"knife_hand\", \"open\")\n * @param highlightMode - Optional highlight mode for striking surface\n * @returns this for chaining\n *\n * @korean 왼손자세설정\n */\n setLeftHandPose(pose: string, highlightMode?: HandHighlightMode): this {\n this._leftHandPose = pose;\n if (highlightMode) {\n this._leftHandHighlightMode = highlightMode;\n }\n return this;\n }\n\n /**\n * Set right hand pose for this keyframe\n *\n * @param pose - Hand pose type (e.g., \"fist\", \"knife_hand\", \"open\")\n * @param highlightMode - Optional highlight mode for striking surface\n * @returns this for chaining\n *\n * @korean 오른손자세설정\n */\n setRightHandPose(pose: string, highlightMode?: HandHighlightMode): this {\n this._rightHandPose = pose;\n if (highlightMode) {\n this._rightHandHighlightMode = highlightMode;\n }\n return this;\n }\n\n /**\n * Set both hands to the same pose\n *\n * @param pose - Hand pose type for both hands\n * @returns this for chaining\n *\n * @korean 양손자세설정\n */\n setBothHandPoses(pose: string): this {\n this._leftHandPose = pose;\n this._rightHandPose = pose;\n return this;\n }\n\n /**\n * Set foot highlight state for kicks\n *\n * @param side - Which foot (\"left\" | \"right\" | \"both\")\n * @param highlighted - Whether to highlight\n * @returns this for chaining\n *\n * @korean 발강조설정\n */\n setFootHighlight(\n side: \"left\" | \"right\" | \"both\",\n highlighted: boolean,\n ): this {\n if (side === \"left\" || side === \"both\") {\n this._leftFootHighlight = highlighted;\n }\n if (side === \"right\" || side === \"both\") {\n this._rightFootHighlight = highlighted;\n }\n return this;\n }\n\n /**\n * Set facial expression for this keyframe\n *\n * @param expression - Facial expression (e.g., \"neutral\", \"focused\", \"pained\")\n * @returns this for chaining\n *\n * @korean 얼굴표정설정\n */\n setFacialExpression(expression: string): this {\n this._facialExpression = expression;\n return this;\n }\n\n /**\n * Set muscle activation target for this keyframe\n *\n * @param muscleGroup - Muscle group name (e.g., \"BICEP_R\", \"QUAD_L\")\n * @param tension - Tension level (0-1)\n * @returns this for chaining\n *\n * @korean 근육활성화설정\n */\n setMuscleActivation(muscleGroup: string, tension: number): this {\n this._muscleActivations ??= new Map();\n this._muscleActivations.set(muscleGroup, Math.max(0, Math.min(1, tension)));\n return this;\n }\n\n /**\n * Set multiple muscle activations at once\n *\n * @param activations - Map or object of muscle group to tension\n * @returns this for chaining\n *\n * @korean 다중근육활성화설정\n */\n setMuscleActivations(\n activations: Map<string, number> | Record<string, number>,\n ): this {\n this._muscleActivations ??= new Map();\n if (activations instanceof Map) {\n activations.forEach((tension, group) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- initialized above\n this._muscleActivations!.set(group, Math.max(0, Math.min(1, tension)));\n });\n } else {\n Object.entries(activations).forEach(([group, tension]) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- initialized above\n this._muscleActivations!.set(group, Math.max(0, Math.min(1, tension)));\n });\n }\n return this;\n }\n\n /**\n * Complete keyframe configuration and return to builder\n *\n * @returns Parent builder for method chaining\n * @throws Error if not associated with a builder\n *\n * @korean 키프레임완료\n */\n done<T extends AnimationBuilderLike>(): T {\n if (!this.builder) {\n throw new Error(\"KeyframeConfig not associated with builder\");\n }\n this.builder._addKeyframe({\n time: this.time,\n easing: this.easing as \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\",\n boneRotations: this.rotations,\n bonePositions: this.positions,\n // Anatomy state\n leftHandPose: this._leftHandPose,\n rightHandPose: this._rightHandPose,\n leftHandHighlightMode: this._leftHandHighlightMode,\n rightHandHighlightMode: this._rightHandHighlightMode,\n leftFootHighlight: this._leftFootHighlight,\n rightFootHighlight: this._rightFootHighlight,\n facialExpression: this._facialExpression,\n muscleActivations: this._muscleActivations,\n });\n return this.builder as T;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,iBAAb,MAA4B;;CAE1B,4BAAY,IAAI,IAA2B;;CAG3C,4BAAY,IAAI,IAA6B;;CAG7C,UAA+C;;CAG/C,OAAuB;;CAGvB,SAAyB;;CAOzB;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;CAGA;;;;;CAMA,WACE,SACA,MACA,QACM;EACN,KAAK,UAAU;EACf,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;;;;;;;;;;;;;;;;;CAkBA,OAAO,MAAgB,GAAW,GAAW,GAAiB;EAC5D,KAAK,UAAU,IAAI,MAAM,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC;EACjD,OAAO;CACT;;;;;;;;;;;;;;;;;CAkBA,SAAS,MAAgB,GAAW,GAAW,GAAiB;EAC9D,KAAK,UAAU,IAAI,MAAM,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC;EACnD,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;CAuBA,UACE,WACA,OAAkC,QAC5B;EACN,MAAM,QAAQ,iBAAiB,SAAS;EAExC,IAAI,SAAS,UAAU,SAAS,QAAQ;GACtC,KAAK,OACH,SAAS,YACT,MAAM,KAAK,SAAS,IACpB,MAAM,KAAK,SAAS,IACpB,MAAM,KAAK,SAAS,EACtB;GACA,KAAK,OACH,SAAS,SACT,MAAM,KAAK,MAAM,IACjB,MAAM,KAAK,MAAM,IACjB,MAAM,KAAK,MAAM,EACnB;GACA,KAAK,OACH,SAAS,SACT,MAAM,KAAK,MAAM,IACjB,MAAM,KAAK,MAAM,IACjB,MAAM,KAAK,MAAM,EACnB;EACF;EAEA,IAAI,SAAS,WAAW,SAAS,QAAQ;GACvC,KAAK,OACH,SAAS,YACT,MAAM,MAAM,SAAS,IACrB,MAAM,MAAM,SAAS,IACrB,MAAM,MAAM,SAAS,EACvB;GACA,KAAK,OACH,SAAS,SACT,MAAM,MAAM,MAAM,IAClB,MAAM,MAAM,MAAM,IAClB,MAAM,MAAM,MAAM,EACpB;GACA,KAAK,OACH,SAAS,SACT,MAAM,MAAM,MAAM,IAClB,MAAM,MAAM,MAAM,IAClB,MAAM,MAAM,MAAM,EACpB;EACF;EAEA,OAAO;CACT;;;;;;;;;;CAeA,gBAAgB,MAAc,eAAyC;EACrE,KAAK,gBAAgB;EACrB,IAAI,eACF,KAAK,yBAAyB;EAEhC,OAAO;CACT;;;;;;;;;;CAWA,iBAAiB,MAAc,eAAyC;EACtE,KAAK,iBAAiB;EACtB,IAAI,eACF,KAAK,0BAA0B;EAEjC,OAAO;CACT;;;;;;;;;CAUA,iBAAiB,MAAoB;EACnC,KAAK,gBAAgB;EACrB,KAAK,iBAAiB;EACtB,OAAO;CACT;;;;;;;;;;CAWA,iBACE,MACA,aACM;EACN,IAAI,SAAS,UAAU,SAAS,QAC9B,KAAK,qBAAqB;EAE5B,IAAI,SAAS,WAAW,SAAS,QAC/B,KAAK,sBAAsB;EAE7B,OAAO;CACT;;;;;;;;;CAUA,oBAAoB,YAA0B;EAC5C,KAAK,oBAAoB;EACzB,OAAO;CACT;;;;;;;;;;CAWA,oBAAoB,aAAqB,SAAuB;EAC9D,KAAK,uCAAuB,IAAI,IAAI;EACpC,KAAK,mBAAmB,IAAI,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;EAC1E,OAAO;CACT;;;;;;;;;CAUA,qBACE,aACM;EACN,KAAK,uCAAuB,IAAI,IAAI;EACpC,IAAI,uBAAuB,KACzB,YAAY,SAAS,SAAS,UAAU;GAEtC,KAAK,mBAAoB,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;EACvE,CAAC;OAED,OAAO,QAAQ,WAAW,EAAE,SAAS,CAAC,OAAO,aAAa;GAExD,KAAK,mBAAoB,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;EACvE,CAAC;EAEH,OAAO;CACT;;;;;;;;;CAUA,OAA0C;EACxC,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MAAM,4CAA4C;EAE9D,KAAK,QAAQ,aAAa;GACxB,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,eAAe,KAAK;GACpB,eAAe,KAAK;GAEpB,cAAc,KAAK;GACnB,eAAe,KAAK;GACpB,uBAAuB,KAAK;GAC5B,wBAAwB,KAAK;GAC7B,mBAAmB,KAAK;GACxB,oBAAoB,KAAK;GACzB,kBAAkB,KAAK;GACvB,mBAAmB,KAAK;EAC1B,CAAC;EACD,OAAO,KAAK;CACd;AACF"}
1
+ {"version":3,"file":"KeyframeConfig.js","names":[],"sources":["../../../../src/systems/animation/builders/KeyframeConfig.ts"],"sourcesContent":["/**\n * Keyframe Configuration Helper\n *\n * Helper class for configuring animation keyframes with bone rotations, positions,\n * and integrated anatomy state (hands, feet, facial expressions, muscle activations).\n * 애니메이션 키프레임 설정 헬퍼 클래스 (해부학 상태 통합)\n *\n * @module systems/animation/KeyframeConfig\n * @korean 키프레임설정\n */\n\nimport type { AnimationKeyframe, SkeletalAnimation } from \"@/types/skeletal\";\nimport { BoneName } from \"@/types/skeletal\";\nimport * as THREE from \"three\";\nimport type { GuardPositionType } from \"./KoreanGuardPositions\";\nimport { getGuardPosition } from \"./KoreanGuardPositions\";\n\n/** Hand highlight mode for striking surface visualization */\nexport type HandHighlightMode =\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\";\n\n// Forward declaration for builder type\ninterface AnimationBuilderLike {\n _addKeyframe(kf: AnimationKeyframe): void;\n at(time: number, easing?: string): KeyframeConfig;\n build(): SkeletalAnimation;\n}\n\n/**\n * Keyframe configuration helper\n *\n * Provides a fluent API for configuring bone rotations, positions,\n * and anatomy state (hands, feet, facial expressions) within a single keyframe.\n *\n * @example\n * ```typescript\n * const kf = new KeyframeConfig();\n * kf.rotate(BoneName.SHOULDER_R, -0.5, 0, 0.3)\n * .position(BoneName.HAND_R, 0.1, 0, 0)\n * .setLeftHandPose(\"fist\")\n * .setRightHandPose(\"fist\", \"knuckles\")\n * .setFootHighlight(\"right\", true)\n * .setFacialExpression(\"focused\");\n * ```\n *\n * @korean 키프레임설정헬퍼\n */\nexport class KeyframeConfig {\n /** Bone rotation data (bone -> euler rotation) */\n rotations = new Map<BoneName, THREE.Euler>();\n\n /** Bone position data (bone -> position offset) */\n positions = new Map<BoneName, THREE.Vector3>();\n\n /** Parent builder reference for fluent chaining */\n private builder: AnimationBuilderLike | null = null;\n\n /** Keyframe time */\n private time: number = 0;\n\n /** Keyframe easing function */\n private easing: string = \"linear\";\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE (해부학 상태) - Integrated hand, foot, and facial animation\n // ═══════════════════════════════════════════════════════════════════════════\n\n /** Left hand pose type */\n private _leftHandPose?: string;\n\n /** Right hand pose type */\n private _rightHandPose?: string;\n\n /** Left hand highlight mode */\n private _leftHandHighlightMode?: HandHighlightMode;\n\n /** Right hand highlight mode */\n private _rightHandHighlightMode?: HandHighlightMode;\n\n /** Left foot highlight state */\n private _leftFootHighlight?: boolean;\n\n /** Right foot highlight state */\n private _rightFootHighlight?: boolean;\n\n /** Facial expression */\n private _facialExpression?: string;\n\n /** Muscle activation targets */\n private _muscleActivations?: Map<string, number>;\n\n /**\n * Associate this keyframe with a builder for the `done()` method\n * @internal\n */\n setBuilder(\n builder: AnimationBuilderLike,\n time: number,\n easing: string,\n ): void {\n this.builder = builder;\n this.time = time;\n this.easing = easing;\n }\n\n /**\n * Set bone rotation (euler angles in radians)\n *\n * @param bone - Target bone name\n * @param x - X rotation (radians)\n * @param y - Y rotation (radians)\n * @param z - Z rotation (radians)\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * kf.rotate(BoneName.SHOULDER_R, -0.5, 0, 0.3);\n * ```\n *\n * @korean 뼈회전설정\n */\n rotate(bone: BoneName, x: number, y: number, z: number): this {\n this.rotations.set(bone, new THREE.Euler(x, y, z));\n return this;\n }\n\n /**\n * Set bone position offset\n *\n * @param bone - Target bone name\n * @param x - X position offset\n * @param y - Y position offset\n * @param z - Z position offset\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * kf.position(BoneName.FOOT_R, 0, 0.5, 0.3);\n * ```\n *\n * @korean 뼈위치설정\n */\n position(bone: BoneName, x: number, y: number, z: number): this {\n this.positions.set(bone, new THREE.Vector3(x, y, z));\n return this;\n }\n\n /**\n * Apply Korean guard position to arms\n *\n * Sets proper Korean martial arts guard positions (막기자세) for defensive posture.\n * Supports applying guard to one side or both sides.\n *\n * @param guardType - Type of guard (\"HIGH_GUARD\" | \"MIDDLE_GUARD\" | \"LOW_GUARD\")\n * @param side - Which side to apply (\"left\" | \"right\" | \"both\")\n * @returns this for chaining\n *\n * @example\n * ```typescript\n * // Apply middle guard to both hands\n * kf.withGuard(\"MIDDLE_GUARD\");\n *\n * // Apply middle guard only to left hand (right hand attacks)\n * kf.withGuard(\"MIDDLE_GUARD\", \"left\");\n * ```\n *\n * @korean 방어자세적용\n */\n withGuard(\n guardType: GuardPositionType,\n side: \"left\" | \"right\" | \"both\" = \"both\",\n ): this {\n const guard = getGuardPosition(guardType);\n\n if (side === \"left\" || side === \"both\") {\n this.rotate(\n BoneName.SHOULDER_L,\n guard.left.shoulder[0],\n guard.left.shoulder[1],\n guard.left.shoulder[2],\n );\n this.rotate(\n BoneName.ELBOW_L,\n guard.left.elbow[0],\n guard.left.elbow[1],\n guard.left.elbow[2],\n );\n this.rotate(\n BoneName.WRIST_L,\n guard.left.wrist[0],\n guard.left.wrist[1],\n guard.left.wrist[2],\n );\n }\n\n if (side === \"right\" || side === \"both\") {\n this.rotate(\n BoneName.SHOULDER_R,\n guard.right.shoulder[0],\n guard.right.shoulder[1],\n guard.right.shoulder[2],\n );\n this.rotate(\n BoneName.ELBOW_R,\n guard.right.elbow[0],\n guard.right.elbow[1],\n guard.right.elbow[2],\n );\n this.rotate(\n BoneName.WRIST_R,\n guard.right.wrist[0],\n guard.right.wrist[1],\n guard.right.wrist[2],\n );\n }\n\n return this;\n }\n\n // ═══════════════════════════════════════════════════════════════════════════\n // ANATOMY STATE SETTERS (해부학 상태 설정)\n // ═══════════════════════════════════════════════════════════════════════════\n\n /**\n * Set left hand pose for this keyframe\n *\n * @param pose - Hand pose type (e.g., \"fist\", \"knife_hand\", \"open\")\n * @param highlightMode - Optional highlight mode for striking surface\n * @returns this for chaining\n *\n * @korean 왼손자세설정\n */\n setLeftHandPose(pose: string, highlightMode?: HandHighlightMode): this {\n this._leftHandPose = pose;\n if (highlightMode) {\n this._leftHandHighlightMode = highlightMode;\n }\n return this;\n }\n\n /**\n * Set right hand pose for this keyframe\n *\n * @param pose - Hand pose type (e.g., \"fist\", \"knife_hand\", \"open\")\n * @param highlightMode - Optional highlight mode for striking surface\n * @returns this for chaining\n *\n * @korean 오른손자세설정\n */\n setRightHandPose(pose: string, highlightMode?: HandHighlightMode): this {\n this._rightHandPose = pose;\n if (highlightMode) {\n this._rightHandHighlightMode = highlightMode;\n }\n return this;\n }\n\n /**\n * Set both hands to the same pose\n *\n * @param pose - Hand pose type for both hands\n * @returns this for chaining\n *\n * @korean 양손자세설정\n */\n setBothHandPoses(pose: string): this {\n this._leftHandPose = pose;\n this._rightHandPose = pose;\n return this;\n }\n\n /**\n * Set foot highlight state for kicks\n *\n * @param side - Which foot (\"left\" | \"right\" | \"both\")\n * @param highlighted - Whether to highlight\n * @returns this for chaining\n *\n * @korean 발강조설정\n */\n setFootHighlight(\n side: \"left\" | \"right\" | \"both\",\n highlighted: boolean,\n ): this {\n if (side === \"left\" || side === \"both\") {\n this._leftFootHighlight = highlighted;\n }\n if (side === \"right\" || side === \"both\") {\n this._rightFootHighlight = highlighted;\n }\n return this;\n }\n\n /**\n * Set facial expression for this keyframe\n *\n * @param expression - Facial expression (e.g., \"neutral\", \"focused\", \"pained\")\n * @returns this for chaining\n *\n * @korean 얼굴표정설정\n */\n setFacialExpression(expression: string): this {\n this._facialExpression = expression;\n return this;\n }\n\n /**\n * Set muscle activation target for this keyframe\n *\n * @param muscleGroup - Muscle group name (e.g., \"BICEP_R\", \"QUAD_L\")\n * @param tension - Tension level (0-1)\n * @returns this for chaining\n *\n * @korean 근육활성화설정\n */\n setMuscleActivation(muscleGroup: string, tension: number): this {\n this._muscleActivations ??= new Map();\n this._muscleActivations.set(muscleGroup, Math.max(0, Math.min(1, tension)));\n return this;\n }\n\n /**\n * Set multiple muscle activations at once\n *\n * @param activations - Map or object of muscle group to tension\n * @returns this for chaining\n *\n * @korean 다중근육활성화설정\n */\n setMuscleActivations(\n activations: Map<string, number> | Record<string, number>,\n ): this {\n this._muscleActivations ??= new Map();\n if (activations instanceof Map) {\n activations.forEach((tension, group) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- initialized above\n this._muscleActivations!.set(group, Math.max(0, Math.min(1, tension)));\n });\n } else {\n Object.entries(activations).forEach(([group, tension]) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- initialized above\n this._muscleActivations!.set(group, Math.max(0, Math.min(1, tension)));\n });\n }\n return this;\n }\n\n /**\n * Complete keyframe configuration and return to builder\n *\n * @returns Parent builder for method chaining\n * @throws Error if not associated with a builder\n *\n * @korean 키프레임완료\n */\n done<T extends AnimationBuilderLike>(): T {\n if (!this.builder) {\n throw new Error(\"KeyframeConfig not associated with builder\");\n }\n this.builder._addKeyframe({\n time: this.time,\n easing: this.easing as \"linear\" | \"ease-in\" | \"ease-out\" | \"ease-in-out\",\n boneRotations: this.rotations,\n bonePositions: this.positions,\n // Anatomy state\n leftHandPose: this._leftHandPose,\n rightHandPose: this._rightHandPose,\n leftHandHighlightMode: this._leftHandHighlightMode,\n rightHandHighlightMode: this._rightHandHighlightMode,\n leftFootHighlight: this._leftFootHighlight,\n rightFootHighlight: this._rightFootHighlight,\n facialExpression: this._facialExpression,\n muscleActivations: this._muscleActivations,\n });\n return this.builder as T;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"KeyframeInterpolation.js","names":[],"sources":["../../../../src/systems/animation/builders/KeyframeInterpolation.ts"],"sourcesContent":["/**\n * Keyframe interpolation for smooth skeletal animations\n * \n * Provides interpolation between animation keyframes for smooth 60fps playback.\n * Supports linear, ease-in, ease-out, and ease-in-out easing functions.\n * \n * @module systems/animation/KeyframeInterpolation\n * @category Animation System\n * @korean 키프레임보간\n */\n\nimport * as THREE from \"three\";\nimport type {\n AnimationKeyframe,\n SkeletalAnimation,\n SkeletalRig,\n} from \"@/types/skeletal\";\n\n/**\n * Easing function type\n * \n * @param t - Time value between 0 and 1\n * @returns Eased value between 0 and 1\n * \n * @korean 이징함수타입\n */\ntype EasingFunction = (t: number) => number;\n\n/**\n * Cubic bezier control points for easing curves\n * \n * **Korean**: 3차 베지어 제어점\n * \n * @category Animation\n * @korean 베지어제어점\n */\nexport interface BezierControlPoints {\n /** First control point x (0-1) */\n readonly p1x: number;\n /** First control point y (0-1) */\n readonly p1y: number;\n /** Second control point x (0-1) */\n readonly p2x: number;\n /** Second control point y (0-1) */\n readonly p2y: number;\n /** \n * Enable precision mode for exact bezier curve calculation\n * \n * **Reserved for future implementation.**\n * \n * When false (default), uses simplified approximation assuming x progresses linearly with t.\n * This is standard for CSS cubic-bezier() and provides good results with minimal overhead.\n * \n * When true (future), will perform iterative solving for accurate x-t mapping (higher CPU cost).\n * This would enable non-linear time progression for more complex easing curves.\n * \n * Currently unused - kept in interface for forward compatibility without breaking API changes.\n * \n * @korean 정밀모드\n */\n readonly precisionMode?: boolean;\n}\n\n/**\n * Cubic bezier easing for natural movement\n * \n * **Korean**: 3차 베지어 이징\n * \n * Implements cubic bezier curve interpolation for smooth, natural motion.\n * Based on CSS cubic-bezier() function specification.\n * \n * This function uses a configuration object approach for better API clarity.\n * By default, uses simplified approximation (x progresses linearly with t) which\n * provides good results for animation with minimal overhead. Enable precisionMode\n * for exact calculations when needed.\n * \n * @param t - Input time (0-1)\n * @param options - Bezier control points and configuration\n * @returns Eased value\n * \n * @example\n * ```typescript\n * // Natural Korean martial arts movement (physics-based)\n * const eased = cubicBezierWithOptions(0.5, { \n * p1x: 0.25, p1y: 0.1, p2x: 0.25, p2y: 1.0 \n * });\n * \n * // With precision mode for exact calculation\n * const precise = cubicBezierWithOptions(0.5, {\n * p1x: 0.42, p1y: 0, p2x: 0.58, p2y: 1.0,\n * precisionMode: true\n * });\n * ```\n * \n * @korean 3차베지어이징옵션\n */\nexport function cubicBezierWithOptions(\n t: number,\n options: BezierControlPoints\n): number {\n // Clamp t to [0, 1]\n const clampedT = Math.max(0, Math.min(1, t));\n \n // For performance, use direct calculation (standard for CSS cubic-bezier)\n // Note: options.precisionMode is currently unused and reserved for future implementation\n // where we could add iterative solving for exact x-t mapping\n const u = 1 - clampedT;\n \n // Cubic bezier formula: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃\n // For easing, P₀ = (0,0) and P₃ = (1,1), so:\n // y(t) = 3(1-t)²t*p1y + 3(1-t)t²*p2y + t³\n const result = 3 * u * u * clampedT * options.p1y + \n 3 * u * clampedT * clampedT * options.p2y + \n clampedT * clampedT * clampedT;\n \n return result;\n}\n\n/**\n * Cubic bezier easing (legacy function signature for backward compatibility)\n * \n * **Korean**: 3차 베지어 이징 (레거시)\n * \n * @param t - Input time (0-1)\n * @param p1x - First control point x (0-1) - currently unused, reserved for precision mode\n * @param p1y - First control point y (can exceed 0-1 for overshoot)\n * @param p2x - Second control point x (0-1) - currently unused, reserved for precision mode\n * @param p2y - Second control point y (can exceed 0-1 for overshoot)\n * @returns Eased value\n * \n * @deprecated Use cubicBezierWithOptions for clearer API\n * @korean 3차베지어이징레거시\n */\nexport function cubicBezier(\n t: number,\n p1x: number,\n p1y: number,\n p2x: number,\n p2y: number\n): number {\n return cubicBezierWithOptions(t, { p1x, p1y, p2x, p2y });\n}\n\n/**\n * Create a cubic bezier easing function with control points\n * \n * **Korean**: 베지어 이징 함수 생성\n * \n * Factory function to create reusable bezier easing functions.\n * \n * @param points - Bezier control points\n * @returns Easing function\n * \n * @korean 베지어이징함수생성\n */\nexport function createBezierEasing(points: BezierControlPoints): EasingFunction {\n return (t: number) => cubicBezier(t, points.p1x, points.p1y, points.p2x, points.p2y);\n}\n\n/**\n * Preset bezier easing curves for Korean martial arts movements\n * \n * **Korean**: 무도 동작 이징 곡선\n * \n * @category Animation\n * @korean 무도동작이징곡선\n */\nexport const BEZIER_PRESETS = {\n /**\n * Natural motion with physics-based acceleration/deceleration\n * Ideal for: Stance transitions, body rotations, weight shifts\n * **Korean**: 자연스러운 물리 기반 동작\n */\n naturalMotion: { p1x: 0.25, p1y: 0.1, p2x: 0.25, p2y: 1.0 },\n \n /**\n * Smooth S-curve for fluid transitions\n * Ideal for: Attack wind-ups, defensive positioning, footwork\n * **Korean**: 부드러운 S곡선 전환\n */\n smoothTransition: { p1x: 0.42, p1y: 0.0, p2x: 0.58, p2y: 1.0 },\n \n /**\n * Quick start with gentle landing\n * Ideal for: Strike recoil, guard recovery, step completion\n * **Korean**: 빠른 시작과 부드러운 착지\n */\n quickStart: { p1x: 0.1, p1y: 0.8, p2x: 0.25, p2y: 1.0 },\n \n /**\n * Explosive power curve\n * Ideal for: Explosive strikes, power techniques, ki projection\n * **Korean**: 폭발적 힘 곡선\n */\n explosivePower: { p1x: 0.05, p1y: 0.9, p2x: 0.2, p2y: 1.0 },\n \n /**\n * Controlled deceleration\n * Ideal for: Defensive withdrawals, cautious movements, guard stance\n * **Korean**: 제어된 감속\n */\n controlledSlow: { p1x: 0.6, p1y: 0.0, p2x: 0.9, p2y: 0.4 },\n} as const;\n\n/**\n * Linear easing (no easing)\n * \n * @param t - Input time (0-1)\n * @returns Same as input\n * \n * @korean 선형이징\n */\nexport const easeLinear: EasingFunction = (t: number): number => t;\n\n/**\n * Ease-in (slow start, fast end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈인\n */\nexport const easeIn: EasingFunction = (t: number): number => t * t;\n\n/**\n * Ease-out (fast start, slow end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈아웃\n */\nexport const easeOut: EasingFunction = (t: number): number => t * (2 - t);\n\n/**\n * Ease-in-out (slow start, slow end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈인아웃\n */\nexport const easeInOut: EasingFunction = (t: number): number => {\n if (t < 0.5) {\n return 2 * t * t;\n }\n return -1 + (4 - 2 * t) * t;\n};\n\n/**\n * Natural motion bezier easing (preset)\n * \n * **Korean**: 자연스러운 동작 이징\n * \n * Physics-based movement ideal for Korean martial arts.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 자연동작이징\n */\nexport const easeNaturalMotion: EasingFunction = createBezierEasing(BEZIER_PRESETS.naturalMotion);\n\n/**\n * Smooth transition bezier easing (preset)\n * \n * **Korean**: 부드러운 전환 이징\n * \n * S-curve for fluid stance transitions.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 부드러운전환이징\n */\nexport const easeSmoothTransition: EasingFunction = createBezierEasing(BEZIER_PRESETS.smoothTransition);\n\n/**\n * Explosive power bezier easing (preset)\n * \n * **Korean**: 폭발적 힘 이징\n * \n * Explosive acceleration for power strikes.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 폭발적힘이징\n */\nexport const easeExplosivePower: EasingFunction = createBezierEasing(BEZIER_PRESETS.explosivePower);\n\n/**\n * Extended easing function names including bezier presets\n * \n * **Korean**: 확장된 이징 함수 이름\n * \n * @category Animation\n * @korean 확장이징함수이름\n */\nexport type EasingName = \n | \"linear\"\n | \"ease-in\" \n | \"ease-out\" \n | \"ease-in-out\"\n | \"natural-motion\"\n | \"smooth-transition\"\n | \"quick-start\"\n | \"explosive-power\"\n | \"controlled-slow\";\n\n/**\n * Get easing function by name\n * \n * **Korean**: 이름으로 이징 함수 구하기\n * \n * @param name - Easing function name (supports bezier presets)\n * @returns Easing function\n * \n * @example\n * ```typescript\n * // Get natural motion easing for Korean martial arts\n * const easing = getEasingFunction(\"natural-motion\");\n * const easedValue = easing(0.5); // Smooth physics-based interpolation\n * \n * // Get explosive power for strike animations\n * const powerEasing = getEasingFunction(\"explosive-power\");\n * ```\n * \n * @korean 이징함수구하기\n */\nexport const getEasingFunction = (\n name: EasingName = \"linear\"\n): EasingFunction => {\n switch (name) {\n case \"ease-in\":\n return easeIn;\n case \"ease-out\":\n return easeOut;\n case \"ease-in-out\":\n return easeInOut;\n case \"natural-motion\":\n return easeNaturalMotion;\n case \"smooth-transition\":\n return easeSmoothTransition;\n case \"quick-start\":\n return createBezierEasing(BEZIER_PRESETS.quickStart);\n case \"explosive-power\":\n return easeExplosivePower;\n case \"controlled-slow\":\n return createBezierEasing(BEZIER_PRESETS.controlledSlow);\n default:\n return easeLinear;\n }\n};\n\n/**\n * Find keyframes surrounding current time\n * \n * Returns the two keyframes to interpolate between for current animation time.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation (seconds)\n * @returns Tuple of [previousKeyframe, nextKeyframe, interpolationFactor]\n * \n * @korean 주변키프레임찾기\n */\nexport const findSurroundingKeyframes = (\n animation: SkeletalAnimation,\n currentTime: number\n): [AnimationKeyframe, AnimationKeyframe, number] => {\n const { keyframes } = animation;\n\n // Clamp time to animation duration\n const clampedTime = Math.max(\n 0,\n Math.min(currentTime, animation.duration)\n );\n\n // Find keyframes\n let prevKeyframe = keyframes[0];\n let nextKeyframe = keyframes[keyframes.length - 1];\n let interpolationFactor = 0;\n\n for (let i = 0; i < keyframes.length - 1; i++) {\n const current = keyframes[i];\n const next = keyframes[i + 1];\n\n if (clampedTime >= current.time && clampedTime <= next.time) {\n prevKeyframe = current;\n nextKeyframe = next;\n\n // Calculate interpolation factor (0 to 1)\n const timeDelta = next.time - current.time;\n if (timeDelta > 0) {\n interpolationFactor = (clampedTime - current.time) / timeDelta;\n }\n break;\n }\n }\n\n return [prevKeyframe, nextKeyframe, interpolationFactor];\n};\n\n/**\n * Interpolate between two Euler rotations\n * \n * Performs spherical linear interpolation (slerp) for smooth rotation.\n * \n * @param from - Start rotation\n * @param to - End rotation\n * @param t - Interpolation factor (0-1)\n * @param easingFn - Easing function to apply\n * @returns Interpolated rotation\n * \n * @korean 회전보간\n */\nexport const interpolateRotation = (\n from: THREE.Euler,\n to: THREE.Euler,\n t: number,\n easingFn: EasingFunction = easeLinear\n): THREE.Euler => {\n const easedT = easingFn(t);\n\n // Convert Euler to Quaternion for proper slerp\n const fromQuat = new THREE.Quaternion().setFromEuler(from);\n const toQuat = new THREE.Quaternion().setFromEuler(to);\n\n // Slerp between quaternions\n const result = new THREE.Quaternion().slerpQuaternions(\n fromQuat,\n toQuat,\n easedT\n );\n\n // Convert back to Euler\n const resultEuler = new THREE.Euler().setFromQuaternion(result);\n return resultEuler;\n};\n\n/**\n * Interpolate between two Vector3 positions\n * \n * Performs linear interpolation for smooth position changes.\n * \n * @param from - Start position\n * @param to - End position\n * @param t - Interpolation factor (0-1)\n * @param easingFn - Easing function to apply\n * @returns Interpolated position\n * \n * @korean 위치보간\n */\nexport const interpolatePosition = (\n from: THREE.Vector3,\n to: THREE.Vector3,\n t: number,\n easingFn: EasingFunction = easeLinear\n): THREE.Vector3 => {\n const easedT = easingFn(t);\n return new THREE.Vector3().lerpVectors(from, to, easedT);\n};\n\n/**\n * Get interpolated keyframe at current time\n * \n * Calculates bone transformations by interpolating between keyframes.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation (seconds)\n * @returns Interpolated keyframe\n * \n * @korean 보간된키프레임구하기\n */\nexport const getInterpolatedKeyframe = (\n animation: SkeletalAnimation,\n currentTime: number\n): AnimationKeyframe => {\n const [prevKeyframe, nextKeyframe, t] =\n findSurroundingKeyframes(animation, currentTime);\n\n // Get easing function from next keyframe\n const easingFn = getEasingFunction(nextKeyframe.easing);\n\n // Interpolate bone rotations\n const boneRotations = new Map<string, THREE.Euler>();\n const allBoneNames = new Set([\n ...prevKeyframe.boneRotations.keys(),\n ...nextKeyframe.boneRotations.keys(),\n ]);\n\n for (const boneName of allBoneNames) {\n const prevRotation =\n prevKeyframe.boneRotations.get(boneName) ?? new THREE.Euler();\n const nextRotation =\n nextKeyframe.boneRotations.get(boneName) ?? new THREE.Euler();\n\n const interpolated = interpolateRotation(\n prevRotation,\n nextRotation,\n t,\n easingFn\n );\n boneRotations.set(boneName, interpolated);\n }\n\n // Interpolate bone positions\n const bonePositions = new Map<string, THREE.Vector3>();\n const allPositionBones = new Set([\n ...prevKeyframe.bonePositions.keys(),\n ...nextKeyframe.bonePositions.keys(),\n ]);\n\n for (const boneName of allPositionBones) {\n const prevPosition =\n prevKeyframe.bonePositions.get(boneName) ?? new THREE.Vector3();\n const nextPosition =\n nextKeyframe.bonePositions.get(boneName) ?? new THREE.Vector3();\n\n const interpolated = interpolatePosition(\n prevPosition,\n nextPosition,\n t,\n easingFn\n );\n bonePositions.set(boneName, interpolated);\n }\n\n return {\n time: currentTime,\n boneRotations,\n bonePositions,\n easing: nextKeyframe.easing,\n };\n};\n\n/**\n * Apply keyframe to skeletal rig\n * \n * Updates all bone transformations based on keyframe data.\n * \n * @param rig - Skeletal rig to update\n * @param keyframe - Keyframe to apply\n * \n * @korean 키프레임적용\n */\nexport const applyKeyframeToRig = (\n rig: SkeletalRig,\n keyframe: AnimationKeyframe\n): void => {\n // Apply bone rotations\n keyframe.boneRotations.forEach((rotation, boneName) => {\n const bone = rig.bones.get(boneName);\n if (bone) {\n bone.rotation.copy(rotation);\n }\n });\n\n // Apply bone positions (offset from rest pose)\n keyframe.bonePositions.forEach((position, boneName) => {\n const bone = rig.bones.get(boneName);\n if (bone) {\n // Add position offset to rest position\n bone.position.copy(bone.restPosition).add(position);\n }\n });\n};\n\n/**\n * Blend between two keyframes\n * \n * Creates smooth transition between two animations for animation blending.\n * Useful for transitioning between stance change and attack, etc.\n * \n * @param keyframe1 - First keyframe\n * @param keyframe2 - Second keyframe\n * @param blendFactor - Blend amount (0 = keyframe1, 1 = keyframe2)\n * @returns Blended keyframe\n * \n * @korean 키프레임블렌드\n */\nexport const blendKeyframes = (\n keyframe1: AnimationKeyframe,\n keyframe2: AnimationKeyframe,\n blendFactor: number\n): AnimationKeyframe => {\n const clampedBlend = Math.max(0, Math.min(1, blendFactor));\n\n // Blend rotations\n const boneRotations = new Map<string, THREE.Euler>();\n const allBones = new Set([\n ...keyframe1.boneRotations.keys(),\n ...keyframe2.boneRotations.keys(),\n ]);\n\n allBones.forEach((boneName) => {\n const rot1 = keyframe1.boneRotations.get(boneName) ?? new THREE.Euler();\n const rot2 = keyframe2.boneRotations.get(boneName) ?? new THREE.Euler();\n const blended = interpolateRotation(rot1, rot2, clampedBlend);\n boneRotations.set(boneName, blended);\n });\n\n // Blend positions\n const bonePositions = new Map<string, THREE.Vector3>();\n const allPositionBones = new Set([\n ...keyframe1.bonePositions.keys(),\n ...keyframe2.bonePositions.keys(),\n ]);\n\n allPositionBones.forEach((boneName) => {\n const pos1 = keyframe1.bonePositions.get(boneName) ?? new THREE.Vector3();\n const pos2 = keyframe2.bonePositions.get(boneName) ?? new THREE.Vector3();\n const blended = interpolatePosition(pos1, pos2, clampedBlend);\n bonePositions.set(boneName, blended);\n });\n\n return {\n time: 0,\n boneRotations,\n bonePositions,\n easing: \"linear\",\n };\n};\n\n/**\n * Update animation state\n * \n * Advances animation time and returns current interpolated keyframe.\n * Handles looping animations automatically.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation\n * @param deltaTime - Time since last update (seconds)\n * @param playbackSpeed - Speed multiplier (1.0 = normal)\n * @returns Updated time and current keyframe\n * \n * @korean 애니메이션상태업데이트\n */\nexport const updateAnimation = (\n animation: SkeletalAnimation,\n currentTime: number,\n deltaTime: number,\n playbackSpeed = 1.0\n): { time: number; keyframe: AnimationKeyframe; completed: boolean } => {\n // Advance time\n let newTime = currentTime + deltaTime * playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animation.duration) {\n if (animation.loop) {\n newTime = newTime % animation.duration;\n } else {\n newTime = animation.duration;\n completed = true;\n }\n }\n\n // Get interpolated keyframe\n const keyframe = getInterpolatedKeyframe(animation, newTime);\n\n return {\n time: newTime,\n keyframe,\n completed,\n };\n};\n\n/**\n * Cross-fade blend between two animations\n * \n * **Korean**: 크로스페이드 블렌드\n * \n * Smoothly blends between two overlapping animations to prevent popping.\n * Uses cubic bezier easing for natural transitions.\n * \n * @param animation1 - First animation\n * @param time1 - Current time in first animation\n * @param animation2 - Second animation\n * @param time2 - Current time in second animation\n * @param blendFactor - Blend weight (0 = animation1, 1 = animation2)\n * @param easingName - Easing curve for blend transition\n * @returns Blended keyframe\n * \n * @example\n * ```typescript\n * // Cross-fade from idle to attack over 100ms\n * const blended = crossFadeAnimations(\n * idleAnim, idleTime,\n * attackAnim, attackTime,\n * 0.5, // 50% blend\n * \"smooth-transition\"\n * );\n * applyKeyframeToRig(rig, blended);\n * ```\n * \n * @korean 크로스페이드블렌드\n */\nexport const crossFadeAnimations = (\n animation1: SkeletalAnimation,\n time1: number,\n animation2: SkeletalAnimation,\n time2: number,\n blendFactor: number,\n easingName: EasingName = \"smooth-transition\"\n): AnimationKeyframe => {\n const keyframe1 = getInterpolatedKeyframe(animation1, time1);\n const keyframe2 = getInterpolatedKeyframe(animation2, time2);\n \n // Apply easing to blend factor\n const easingFn = getEasingFunction(easingName);\n const easedBlend = easingFn(Math.max(0, Math.min(1, blendFactor)));\n \n return blendKeyframes(keyframe1, keyframe2, easedBlend);\n};\n\n/**\n * Motion prediction state for latency reduction\n * \n * **Korean**: 동작 예측 상태\n * \n * Stores recent animation velocities for motion prediction.\n * \n * @category Animation\n * @korean 동작예측상태\n */\nexport interface MotionPredictionState {\n /** Recent position velocities per bone */\n readonly velocities: Map<string, THREE.Vector3>;\n /** Recent rotation velocities per bone */\n readonly angularVelocities: Map<string, THREE.Euler>;\n /** Last update timestamp */\n readonly lastUpdateTime: number;\n}\n\n/**\n * Create motion prediction state\n * \n * **Korean**: 동작 예측 상태 생성\n * \n * @returns Initial motion prediction state\n * @korean 동작예측상태생성\n */\nexport const createMotionPredictionState = (): MotionPredictionState => ({\n velocities: new Map(),\n angularVelocities: new Map(),\n lastUpdateTime: 0,\n});\n\n/**\n * Update motion prediction state with new keyframe\n * \n * **Korean**: 동작 예측 상태 업데이트\n * \n * Calculates velocities from keyframe differences for motion prediction.\n * \n * @param state - Current prediction state\n * @param previousKeyframe - Previous animation keyframe\n * @param currentKeyframe - Current animation keyframe\n * @param deltaTime - Time elapsed between keyframes\n * @returns Updated prediction state\n * \n * @korean 동작예측상태업데이트\n */\nexport const updateMotionPrediction = (\n state: MotionPredictionState,\n previousKeyframe: AnimationKeyframe,\n currentKeyframe: AnimationKeyframe,\n deltaTime: number\n): MotionPredictionState => {\n if (deltaTime <= 0) return state;\n \n const newVelocities = new Map<string, THREE.Vector3>();\n const newAngularVelocities = new Map<string, THREE.Euler>();\n \n // Calculate position velocities\n currentKeyframe.bonePositions.forEach((currentPos, boneName) => {\n const prevPos = previousKeyframe.bonePositions.get(boneName);\n if (prevPos) {\n const velocity = new THREE.Vector3()\n .subVectors(currentPos, prevPos)\n .divideScalar(deltaTime);\n newVelocities.set(boneName, velocity);\n }\n });\n \n // Calculate angular velocities using quaternion-based rotation differences\n // This avoids gimbal lock and angle wrapping issues with Euler angle differences\n currentKeyframe.boneRotations.forEach((currentRot, boneName) => {\n const prevRot = previousKeyframe.boneRotations.get(boneName);\n if (prevRot) {\n // Convert Euler rotations to quaternions\n const qCurrent = new THREE.Quaternion().setFromEuler(currentRot);\n const qPrev = new THREE.Quaternion().setFromEuler(prevRot);\n \n // Calculate relative rotation: qDiff = qCurrent * qPrev^(-1)\n const qDiff = qCurrent.clone().multiply(qPrev.clone().invert());\n \n // Extract axis-angle representation for angular velocity\n const axis = new THREE.Vector3();\n const wClamped = THREE.MathUtils.clamp(qDiff.w, -1, 1);\n let angle = 2 * Math.acos(wClamped);\n const sinHalfAngle = Math.sqrt(1 - wClamped * wClamped);\n \n if (sinHalfAngle < 0.0001) {\n // If sinHalfAngle is too small, rotation is negligible - use zero velocity\n axis.set(0, 0, 0);\n angle = 0;\n } else {\n // Extract normalized axis from quaternion\n axis.set(\n qDiff.x / sinHalfAngle,\n qDiff.y / sinHalfAngle,\n qDiff.z / sinHalfAngle\n );\n }\n \n // Use shortest rotation path\n if (angle > Math.PI) {\n angle -= 2 * Math.PI;\n }\n \n // Angular velocity vector = axis * (angle / deltaTime)\n const angularVelocityVec = axis.multiplyScalar(angle / deltaTime);\n \n // Store as Euler for consistency with existing API\n const angularVel = new THREE.Euler(\n angularVelocityVec.x,\n angularVelocityVec.y,\n angularVelocityVec.z,\n currentRot.order\n );\n newAngularVelocities.set(boneName, angularVel);\n }\n });\n \n return {\n velocities: newVelocities,\n angularVelocities: newAngularVelocities,\n lastUpdateTime: performance.now(),\n };\n};\n\n/**\n * Predict future keyframe using motion prediction\n * \n * **Korean**: 동작 예측으로 미래 키프레임 예측\n * \n * Reduces perceived latency by predicting future bone positions/rotations\n * based on current velocities. Typical prediction: 16-33ms (1-2 frames at 60fps).\n * \n * @param currentKeyframe - Current animation keyframe\n * @param predictionState - Motion prediction state\n * @param predictionTime - Time ahead to predict (seconds, typically 0.016-0.033)\n * @returns Predicted keyframe\n * \n * @example\n * ```typescript\n * // Predict 1 frame ahead (16.67ms at 60fps) for <50ms total latency\n * const predicted = predictFutureKeyframe(\n * currentKeyframe,\n * motionState,\n * 0.01667\n * );\n * applyKeyframeToRig(rig, predicted);\n * ```\n * \n * @korean 미래키프레임예측\n */\nexport const predictFutureKeyframe = (\n currentKeyframe: AnimationKeyframe,\n predictionState: MotionPredictionState,\n predictionTime: number\n): AnimationKeyframe => {\n // Clamp prediction time to reasonable bounds (max 50ms)\n const clampedPrediction = Math.min(predictionTime, 0.05);\n \n // Predict bone positions\n const predictedPositions = new Map<string, THREE.Vector3>();\n currentKeyframe.bonePositions.forEach((currentPos, boneName) => {\n const velocity = predictionState.velocities.get(boneName);\n if (velocity) {\n // Apply damping to prevent overshoot (0.8 factor for natural motion)\n const predicted = currentPos.clone().add(\n velocity.clone().multiplyScalar(clampedPrediction * 0.8)\n );\n predictedPositions.set(boneName, predicted);\n } else {\n predictedPositions.set(boneName, currentPos.clone());\n }\n });\n \n // Predict bone rotations\n const predictedRotations = new Map<string, THREE.Euler>();\n currentKeyframe.boneRotations.forEach((currentRot, boneName) => {\n const angularVel = predictionState.angularVelocities.get(boneName);\n if (angularVel) {\n // Apply damping to prevent overshoot\n const predicted = new THREE.Euler(\n currentRot.x + angularVel.x * clampedPrediction * 0.8,\n currentRot.y + angularVel.y * clampedPrediction * 0.8,\n currentRot.z + angularVel.z * clampedPrediction * 0.8,\n currentRot.order\n );\n predictedRotations.set(boneName, predicted);\n } else {\n predictedRotations.set(boneName, currentRot.clone());\n }\n });\n \n return {\n time: currentKeyframe.time + clampedPrediction,\n boneRotations: predictedRotations,\n bonePositions: predictedPositions,\n easing: currentKeyframe.easing,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"KeyframeInterpolation.js","names":[],"sources":["../../../../src/systems/animation/builders/KeyframeInterpolation.ts"],"sourcesContent":["/**\n * Keyframe interpolation for smooth skeletal animations\n * \n * Provides interpolation between animation keyframes for smooth 60fps playback.\n * Supports linear, ease-in, ease-out, and ease-in-out easing functions.\n * \n * @module systems/animation/KeyframeInterpolation\n * @category Animation System\n * @korean 키프레임보간\n */\n\nimport * as THREE from \"three\";\nimport type {\n AnimationKeyframe,\n SkeletalAnimation,\n SkeletalRig,\n} from \"@/types/skeletal\";\n\n/**\n * Easing function type\n * \n * @param t - Time value between 0 and 1\n * @returns Eased value between 0 and 1\n * \n * @korean 이징함수타입\n */\ntype EasingFunction = (t: number) => number;\n\n/**\n * Cubic bezier control points for easing curves\n * \n * **Korean**: 3차 베지어 제어점\n * \n * @category Animation\n * @korean 베지어제어점\n */\nexport interface BezierControlPoints {\n /** First control point x (0-1) */\n readonly p1x: number;\n /** First control point y (0-1) */\n readonly p1y: number;\n /** Second control point x (0-1) */\n readonly p2x: number;\n /** Second control point y (0-1) */\n readonly p2y: number;\n /** \n * Enable precision mode for exact bezier curve calculation\n * \n * **Reserved for future implementation.**\n * \n * When false (default), uses simplified approximation assuming x progresses linearly with t.\n * This is standard for CSS cubic-bezier() and provides good results with minimal overhead.\n * \n * When true (future), will perform iterative solving for accurate x-t mapping (higher CPU cost).\n * This would enable non-linear time progression for more complex easing curves.\n * \n * Currently unused - kept in interface for forward compatibility without breaking API changes.\n * \n * @korean 정밀모드\n */\n readonly precisionMode?: boolean;\n}\n\n/**\n * Cubic bezier easing for natural movement\n * \n * **Korean**: 3차 베지어 이징\n * \n * Implements cubic bezier curve interpolation for smooth, natural motion.\n * Based on CSS cubic-bezier() function specification.\n * \n * This function uses a configuration object approach for better API clarity.\n * By default, uses simplified approximation (x progresses linearly with t) which\n * provides good results for animation with minimal overhead. Enable precisionMode\n * for exact calculations when needed.\n * \n * @param t - Input time (0-1)\n * @param options - Bezier control points and configuration\n * @returns Eased value\n * \n * @example\n * ```typescript\n * // Natural Korean martial arts movement (physics-based)\n * const eased = cubicBezierWithOptions(0.5, { \n * p1x: 0.25, p1y: 0.1, p2x: 0.25, p2y: 1.0 \n * });\n * \n * // With precision mode for exact calculation\n * const precise = cubicBezierWithOptions(0.5, {\n * p1x: 0.42, p1y: 0, p2x: 0.58, p2y: 1.0,\n * precisionMode: true\n * });\n * ```\n * \n * @korean 3차베지어이징옵션\n */\nexport function cubicBezierWithOptions(\n t: number,\n options: BezierControlPoints\n): number {\n // Clamp t to [0, 1]\n const clampedT = Math.max(0, Math.min(1, t));\n \n // For performance, use direct calculation (standard for CSS cubic-bezier)\n // Note: options.precisionMode is currently unused and reserved for future implementation\n // where we could add iterative solving for exact x-t mapping\n const u = 1 - clampedT;\n \n // Cubic bezier formula: B(t) = (1-t)³P₀ + 3(1-t)²tP₁ + 3(1-t)t²P₂ + t³P₃\n // For easing, P₀ = (0,0) and P₃ = (1,1), so:\n // y(t) = 3(1-t)²t*p1y + 3(1-t)t²*p2y + t³\n const result = 3 * u * u * clampedT * options.p1y + \n 3 * u * clampedT * clampedT * options.p2y + \n clampedT * clampedT * clampedT;\n \n return result;\n}\n\n/**\n * Cubic bezier easing (legacy function signature for backward compatibility)\n * \n * **Korean**: 3차 베지어 이징 (레거시)\n * \n * @param t - Input time (0-1)\n * @param p1x - First control point x (0-1) - currently unused, reserved for precision mode\n * @param p1y - First control point y (can exceed 0-1 for overshoot)\n * @param p2x - Second control point x (0-1) - currently unused, reserved for precision mode\n * @param p2y - Second control point y (can exceed 0-1 for overshoot)\n * @returns Eased value\n * \n * @deprecated Use cubicBezierWithOptions for clearer API\n * @korean 3차베지어이징레거시\n */\nexport function cubicBezier(\n t: number,\n p1x: number,\n p1y: number,\n p2x: number,\n p2y: number\n): number {\n return cubicBezierWithOptions(t, { p1x, p1y, p2x, p2y });\n}\n\n/**\n * Create a cubic bezier easing function with control points\n * \n * **Korean**: 베지어 이징 함수 생성\n * \n * Factory function to create reusable bezier easing functions.\n * \n * @param points - Bezier control points\n * @returns Easing function\n * \n * @korean 베지어이징함수생성\n */\nexport function createBezierEasing(points: BezierControlPoints): EasingFunction {\n return (t: number) => cubicBezier(t, points.p1x, points.p1y, points.p2x, points.p2y);\n}\n\n/**\n * Preset bezier easing curves for Korean martial arts movements\n * \n * **Korean**: 무도 동작 이징 곡선\n * \n * @category Animation\n * @korean 무도동작이징곡선\n */\nexport const BEZIER_PRESETS = {\n /**\n * Natural motion with physics-based acceleration/deceleration\n * Ideal for: Stance transitions, body rotations, weight shifts\n * **Korean**: 자연스러운 물리 기반 동작\n */\n naturalMotion: { p1x: 0.25, p1y: 0.1, p2x: 0.25, p2y: 1.0 },\n \n /**\n * Smooth S-curve for fluid transitions\n * Ideal for: Attack wind-ups, defensive positioning, footwork\n * **Korean**: 부드러운 S곡선 전환\n */\n smoothTransition: { p1x: 0.42, p1y: 0.0, p2x: 0.58, p2y: 1.0 },\n \n /**\n * Quick start with gentle landing\n * Ideal for: Strike recoil, guard recovery, step completion\n * **Korean**: 빠른 시작과 부드러운 착지\n */\n quickStart: { p1x: 0.1, p1y: 0.8, p2x: 0.25, p2y: 1.0 },\n \n /**\n * Explosive power curve\n * Ideal for: Explosive strikes, power techniques, ki projection\n * **Korean**: 폭발적 힘 곡선\n */\n explosivePower: { p1x: 0.05, p1y: 0.9, p2x: 0.2, p2y: 1.0 },\n \n /**\n * Controlled deceleration\n * Ideal for: Defensive withdrawals, cautious movements, guard stance\n * **Korean**: 제어된 감속\n */\n controlledSlow: { p1x: 0.6, p1y: 0.0, p2x: 0.9, p2y: 0.4 },\n} as const;\n\n/**\n * Linear easing (no easing)\n * \n * @param t - Input time (0-1)\n * @returns Same as input\n * \n * @korean 선형이징\n */\nexport const easeLinear: EasingFunction = (t: number): number => t;\n\n/**\n * Ease-in (slow start, fast end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈인\n */\nexport const easeIn: EasingFunction = (t: number): number => t * t;\n\n/**\n * Ease-out (fast start, slow end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈아웃\n */\nexport const easeOut: EasingFunction = (t: number): number => t * (2 - t);\n\n/**\n * Ease-in-out (slow start, slow end) - Simple quadratic\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 이즈인아웃\n */\nexport const easeInOut: EasingFunction = (t: number): number => {\n if (t < 0.5) {\n return 2 * t * t;\n }\n return -1 + (4 - 2 * t) * t;\n};\n\n/**\n * Natural motion bezier easing (preset)\n * \n * **Korean**: 자연스러운 동작 이징\n * \n * Physics-based movement ideal for Korean martial arts.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 자연동작이징\n */\nexport const easeNaturalMotion: EasingFunction = createBezierEasing(BEZIER_PRESETS.naturalMotion);\n\n/**\n * Smooth transition bezier easing (preset)\n * \n * **Korean**: 부드러운 전환 이징\n * \n * S-curve for fluid stance transitions.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 부드러운전환이징\n */\nexport const easeSmoothTransition: EasingFunction = createBezierEasing(BEZIER_PRESETS.smoothTransition);\n\n/**\n * Explosive power bezier easing (preset)\n * \n * **Korean**: 폭발적 힘 이징\n * \n * Explosive acceleration for power strikes.\n * \n * @param t - Input time (0-1)\n * @returns Eased value\n * \n * @korean 폭발적힘이징\n */\nexport const easeExplosivePower: EasingFunction = createBezierEasing(BEZIER_PRESETS.explosivePower);\n\n/**\n * Extended easing function names including bezier presets\n * \n * **Korean**: 확장된 이징 함수 이름\n * \n * @category Animation\n * @korean 확장이징함수이름\n */\nexport type EasingName = \n | \"linear\"\n | \"ease-in\" \n | \"ease-out\" \n | \"ease-in-out\"\n | \"natural-motion\"\n | \"smooth-transition\"\n | \"quick-start\"\n | \"explosive-power\"\n | \"controlled-slow\";\n\n/**\n * Get easing function by name\n * \n * **Korean**: 이름으로 이징 함수 구하기\n * \n * @param name - Easing function name (supports bezier presets)\n * @returns Easing function\n * \n * @example\n * ```typescript\n * // Get natural motion easing for Korean martial arts\n * const easing = getEasingFunction(\"natural-motion\");\n * const easedValue = easing(0.5); // Smooth physics-based interpolation\n * \n * // Get explosive power for strike animations\n * const powerEasing = getEasingFunction(\"explosive-power\");\n * ```\n * \n * @korean 이징함수구하기\n */\nexport const getEasingFunction = (\n name: EasingName = \"linear\"\n): EasingFunction => {\n switch (name) {\n case \"ease-in\":\n return easeIn;\n case \"ease-out\":\n return easeOut;\n case \"ease-in-out\":\n return easeInOut;\n case \"natural-motion\":\n return easeNaturalMotion;\n case \"smooth-transition\":\n return easeSmoothTransition;\n case \"quick-start\":\n return createBezierEasing(BEZIER_PRESETS.quickStart);\n case \"explosive-power\":\n return easeExplosivePower;\n case \"controlled-slow\":\n return createBezierEasing(BEZIER_PRESETS.controlledSlow);\n default:\n return easeLinear;\n }\n};\n\n/**\n * Find keyframes surrounding current time\n * \n * Returns the two keyframes to interpolate between for current animation time.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation (seconds)\n * @returns Tuple of [previousKeyframe, nextKeyframe, interpolationFactor]\n * \n * @korean 주변키프레임찾기\n */\nexport const findSurroundingKeyframes = (\n animation: SkeletalAnimation,\n currentTime: number\n): [AnimationKeyframe, AnimationKeyframe, number] => {\n const { keyframes } = animation;\n\n // Clamp time to animation duration\n const clampedTime = Math.max(\n 0,\n Math.min(currentTime, animation.duration)\n );\n\n // Find keyframes\n let prevKeyframe = keyframes[0];\n let nextKeyframe = keyframes[keyframes.length - 1];\n let interpolationFactor = 0;\n\n for (let i = 0; i < keyframes.length - 1; i++) {\n const current = keyframes[i];\n const next = keyframes[i + 1];\n\n if (clampedTime >= current.time && clampedTime <= next.time) {\n prevKeyframe = current;\n nextKeyframe = next;\n\n // Calculate interpolation factor (0 to 1)\n const timeDelta = next.time - current.time;\n if (timeDelta > 0) {\n interpolationFactor = (clampedTime - current.time) / timeDelta;\n }\n break;\n }\n }\n\n return [prevKeyframe, nextKeyframe, interpolationFactor];\n};\n\n/**\n * Interpolate between two Euler rotations\n * \n * Performs spherical linear interpolation (slerp) for smooth rotation.\n * \n * @param from - Start rotation\n * @param to - End rotation\n * @param t - Interpolation factor (0-1)\n * @param easingFn - Easing function to apply\n * @returns Interpolated rotation\n * \n * @korean 회전보간\n */\nexport const interpolateRotation = (\n from: THREE.Euler,\n to: THREE.Euler,\n t: number,\n easingFn: EasingFunction = easeLinear\n): THREE.Euler => {\n const easedT = easingFn(t);\n\n // Convert Euler to Quaternion for proper slerp\n const fromQuat = new THREE.Quaternion().setFromEuler(from);\n const toQuat = new THREE.Quaternion().setFromEuler(to);\n\n // Slerp between quaternions\n const result = new THREE.Quaternion().slerpQuaternions(\n fromQuat,\n toQuat,\n easedT\n );\n\n // Convert back to Euler\n const resultEuler = new THREE.Euler().setFromQuaternion(result);\n return resultEuler;\n};\n\n/**\n * Interpolate between two Vector3 positions\n * \n * Performs linear interpolation for smooth position changes.\n * \n * @param from - Start position\n * @param to - End position\n * @param t - Interpolation factor (0-1)\n * @param easingFn - Easing function to apply\n * @returns Interpolated position\n * \n * @korean 위치보간\n */\nexport const interpolatePosition = (\n from: THREE.Vector3,\n to: THREE.Vector3,\n t: number,\n easingFn: EasingFunction = easeLinear\n): THREE.Vector3 => {\n const easedT = easingFn(t);\n return new THREE.Vector3().lerpVectors(from, to, easedT);\n};\n\n/**\n * Get interpolated keyframe at current time\n * \n * Calculates bone transformations by interpolating between keyframes.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation (seconds)\n * @returns Interpolated keyframe\n * \n * @korean 보간된키프레임구하기\n */\nexport const getInterpolatedKeyframe = (\n animation: SkeletalAnimation,\n currentTime: number\n): AnimationKeyframe => {\n const [prevKeyframe, nextKeyframe, t] =\n findSurroundingKeyframes(animation, currentTime);\n\n // Get easing function from next keyframe\n const easingFn = getEasingFunction(nextKeyframe.easing);\n\n // Interpolate bone rotations\n const boneRotations = new Map<string, THREE.Euler>();\n const allBoneNames = new Set([\n ...prevKeyframe.boneRotations.keys(),\n ...nextKeyframe.boneRotations.keys(),\n ]);\n\n for (const boneName of allBoneNames) {\n const prevRotation =\n prevKeyframe.boneRotations.get(boneName) ?? new THREE.Euler();\n const nextRotation =\n nextKeyframe.boneRotations.get(boneName) ?? new THREE.Euler();\n\n const interpolated = interpolateRotation(\n prevRotation,\n nextRotation,\n t,\n easingFn\n );\n boneRotations.set(boneName, interpolated);\n }\n\n // Interpolate bone positions\n const bonePositions = new Map<string, THREE.Vector3>();\n const allPositionBones = new Set([\n ...prevKeyframe.bonePositions.keys(),\n ...nextKeyframe.bonePositions.keys(),\n ]);\n\n for (const boneName of allPositionBones) {\n const prevPosition =\n prevKeyframe.bonePositions.get(boneName) ?? new THREE.Vector3();\n const nextPosition =\n nextKeyframe.bonePositions.get(boneName) ?? new THREE.Vector3();\n\n const interpolated = interpolatePosition(\n prevPosition,\n nextPosition,\n t,\n easingFn\n );\n bonePositions.set(boneName, interpolated);\n }\n\n return {\n time: currentTime,\n boneRotations,\n bonePositions,\n easing: nextKeyframe.easing,\n };\n};\n\n/**\n * Apply keyframe to skeletal rig\n * \n * Updates all bone transformations based on keyframe data.\n * \n * @param rig - Skeletal rig to update\n * @param keyframe - Keyframe to apply\n * \n * @korean 키프레임적용\n */\nexport const applyKeyframeToRig = (\n rig: SkeletalRig,\n keyframe: AnimationKeyframe\n): void => {\n // Apply bone rotations\n keyframe.boneRotations.forEach((rotation, boneName) => {\n const bone = rig.bones.get(boneName);\n if (bone) {\n bone.rotation.copy(rotation);\n }\n });\n\n // Apply bone positions (offset from rest pose)\n keyframe.bonePositions.forEach((position, boneName) => {\n const bone = rig.bones.get(boneName);\n if (bone) {\n // Add position offset to rest position\n bone.position.copy(bone.restPosition).add(position);\n }\n });\n};\n\n/**\n * Blend between two keyframes\n * \n * Creates smooth transition between two animations for animation blending.\n * Useful for transitioning between stance change and attack, etc.\n * \n * @param keyframe1 - First keyframe\n * @param keyframe2 - Second keyframe\n * @param blendFactor - Blend amount (0 = keyframe1, 1 = keyframe2)\n * @returns Blended keyframe\n * \n * @korean 키프레임블렌드\n */\nexport const blendKeyframes = (\n keyframe1: AnimationKeyframe,\n keyframe2: AnimationKeyframe,\n blendFactor: number\n): AnimationKeyframe => {\n const clampedBlend = Math.max(0, Math.min(1, blendFactor));\n\n // Blend rotations\n const boneRotations = new Map<string, THREE.Euler>();\n const allBones = new Set([\n ...keyframe1.boneRotations.keys(),\n ...keyframe2.boneRotations.keys(),\n ]);\n\n allBones.forEach((boneName) => {\n const rot1 = keyframe1.boneRotations.get(boneName) ?? new THREE.Euler();\n const rot2 = keyframe2.boneRotations.get(boneName) ?? new THREE.Euler();\n const blended = interpolateRotation(rot1, rot2, clampedBlend);\n boneRotations.set(boneName, blended);\n });\n\n // Blend positions\n const bonePositions = new Map<string, THREE.Vector3>();\n const allPositionBones = new Set([\n ...keyframe1.bonePositions.keys(),\n ...keyframe2.bonePositions.keys(),\n ]);\n\n allPositionBones.forEach((boneName) => {\n const pos1 = keyframe1.bonePositions.get(boneName) ?? new THREE.Vector3();\n const pos2 = keyframe2.bonePositions.get(boneName) ?? new THREE.Vector3();\n const blended = interpolatePosition(pos1, pos2, clampedBlend);\n bonePositions.set(boneName, blended);\n });\n\n return {\n time: 0,\n boneRotations,\n bonePositions,\n easing: \"linear\",\n };\n};\n\n/**\n * Update animation state\n * \n * Advances animation time and returns current interpolated keyframe.\n * Handles looping animations automatically.\n * \n * @param animation - Skeletal animation\n * @param currentTime - Current time in animation\n * @param deltaTime - Time since last update (seconds)\n * @param playbackSpeed - Speed multiplier (1.0 = normal)\n * @returns Updated time and current keyframe\n * \n * @korean 애니메이션상태업데이트\n */\nexport const updateAnimation = (\n animation: SkeletalAnimation,\n currentTime: number,\n deltaTime: number,\n playbackSpeed = 1.0\n): { time: number; keyframe: AnimationKeyframe; completed: boolean } => {\n // Advance time\n let newTime = currentTime + deltaTime * playbackSpeed;\n let completed = false;\n\n // Handle looping or completion\n if (newTime >= animation.duration) {\n if (animation.loop) {\n newTime = newTime % animation.duration;\n } else {\n newTime = animation.duration;\n completed = true;\n }\n }\n\n // Get interpolated keyframe\n const keyframe = getInterpolatedKeyframe(animation, newTime);\n\n return {\n time: newTime,\n keyframe,\n completed,\n };\n};\n\n/**\n * Cross-fade blend between two animations\n * \n * **Korean**: 크로스페이드 블렌드\n * \n * Smoothly blends between two overlapping animations to prevent popping.\n * Uses cubic bezier easing for natural transitions.\n * \n * @param animation1 - First animation\n * @param time1 - Current time in first animation\n * @param animation2 - Second animation\n * @param time2 - Current time in second animation\n * @param blendFactor - Blend weight (0 = animation1, 1 = animation2)\n * @param easingName - Easing curve for blend transition\n * @returns Blended keyframe\n * \n * @example\n * ```typescript\n * // Cross-fade from idle to attack over 100ms\n * const blended = crossFadeAnimations(\n * idleAnim, idleTime,\n * attackAnim, attackTime,\n * 0.5, // 50% blend\n * \"smooth-transition\"\n * );\n * applyKeyframeToRig(rig, blended);\n * ```\n * \n * @korean 크로스페이드블렌드\n */\nexport const crossFadeAnimations = (\n animation1: SkeletalAnimation,\n time1: number,\n animation2: SkeletalAnimation,\n time2: number,\n blendFactor: number,\n easingName: EasingName = \"smooth-transition\"\n): AnimationKeyframe => {\n const keyframe1 = getInterpolatedKeyframe(animation1, time1);\n const keyframe2 = getInterpolatedKeyframe(animation2, time2);\n \n // Apply easing to blend factor\n const easingFn = getEasingFunction(easingName);\n const easedBlend = easingFn(Math.max(0, Math.min(1, blendFactor)));\n \n return blendKeyframes(keyframe1, keyframe2, easedBlend);\n};\n\n/**\n * Motion prediction state for latency reduction\n * \n * **Korean**: 동작 예측 상태\n * \n * Stores recent animation velocities for motion prediction.\n * \n * @category Animation\n * @korean 동작예측상태\n */\nexport interface MotionPredictionState {\n /** Recent position velocities per bone */\n readonly velocities: Map<string, THREE.Vector3>;\n /** Recent rotation velocities per bone */\n readonly angularVelocities: Map<string, THREE.Euler>;\n /** Last update timestamp */\n readonly lastUpdateTime: number;\n}\n\n/**\n * Create motion prediction state\n * \n * **Korean**: 동작 예측 상태 생성\n * \n * @returns Initial motion prediction state\n * @korean 동작예측상태생성\n */\nexport const createMotionPredictionState = (): MotionPredictionState => ({\n velocities: new Map(),\n angularVelocities: new Map(),\n lastUpdateTime: 0,\n});\n\n/**\n * Update motion prediction state with new keyframe\n * \n * **Korean**: 동작 예측 상태 업데이트\n * \n * Calculates velocities from keyframe differences for motion prediction.\n * \n * @param state - Current prediction state\n * @param previousKeyframe - Previous animation keyframe\n * @param currentKeyframe - Current animation keyframe\n * @param deltaTime - Time elapsed between keyframes\n * @returns Updated prediction state\n * \n * @korean 동작예측상태업데이트\n */\nexport const updateMotionPrediction = (\n state: MotionPredictionState,\n previousKeyframe: AnimationKeyframe,\n currentKeyframe: AnimationKeyframe,\n deltaTime: number\n): MotionPredictionState => {\n if (deltaTime <= 0) return state;\n \n const newVelocities = new Map<string, THREE.Vector3>();\n const newAngularVelocities = new Map<string, THREE.Euler>();\n \n // Calculate position velocities\n currentKeyframe.bonePositions.forEach((currentPos, boneName) => {\n const prevPos = previousKeyframe.bonePositions.get(boneName);\n if (prevPos) {\n const velocity = new THREE.Vector3()\n .subVectors(currentPos, prevPos)\n .divideScalar(deltaTime);\n newVelocities.set(boneName, velocity);\n }\n });\n \n // Calculate angular velocities using quaternion-based rotation differences\n // This avoids gimbal lock and angle wrapping issues with Euler angle differences\n currentKeyframe.boneRotations.forEach((currentRot, boneName) => {\n const prevRot = previousKeyframe.boneRotations.get(boneName);\n if (prevRot) {\n // Convert Euler rotations to quaternions\n const qCurrent = new THREE.Quaternion().setFromEuler(currentRot);\n const qPrev = new THREE.Quaternion().setFromEuler(prevRot);\n \n // Calculate relative rotation: qDiff = qCurrent * qPrev^(-1)\n const qDiff = qCurrent.clone().multiply(qPrev.clone().invert());\n \n // Extract axis-angle representation for angular velocity\n const axis = new THREE.Vector3();\n const wClamped = THREE.MathUtils.clamp(qDiff.w, -1, 1);\n let angle = 2 * Math.acos(wClamped);\n const sinHalfAngle = Math.sqrt(1 - wClamped * wClamped);\n \n if (sinHalfAngle < 0.0001) {\n // If sinHalfAngle is too small, rotation is negligible - use zero velocity\n axis.set(0, 0, 0);\n angle = 0;\n } else {\n // Extract normalized axis from quaternion\n axis.set(\n qDiff.x / sinHalfAngle,\n qDiff.y / sinHalfAngle,\n qDiff.z / sinHalfAngle\n );\n }\n \n // Use shortest rotation path\n if (angle > Math.PI) {\n angle -= 2 * Math.PI;\n }\n \n // Angular velocity vector = axis * (angle / deltaTime)\n const angularVelocityVec = axis.multiplyScalar(angle / deltaTime);\n \n // Store as Euler for consistency with existing API\n const angularVel = new THREE.Euler(\n angularVelocityVec.x,\n angularVelocityVec.y,\n angularVelocityVec.z,\n currentRot.order\n );\n newAngularVelocities.set(boneName, angularVel);\n }\n });\n \n return {\n velocities: newVelocities,\n angularVelocities: newAngularVelocities,\n lastUpdateTime: performance.now(),\n };\n};\n\n/**\n * Predict future keyframe using motion prediction\n * \n * **Korean**: 동작 예측으로 미래 키프레임 예측\n * \n * Reduces perceived latency by predicting future bone positions/rotations\n * based on current velocities. Typical prediction: 16-33ms (1-2 frames at 60fps).\n * \n * @param currentKeyframe - Current animation keyframe\n * @param predictionState - Motion prediction state\n * @param predictionTime - Time ahead to predict (seconds, typically 0.016-0.033)\n * @returns Predicted keyframe\n * \n * @example\n * ```typescript\n * // Predict 1 frame ahead (16.67ms at 60fps) for <50ms total latency\n * const predicted = predictFutureKeyframe(\n * currentKeyframe,\n * motionState,\n * 0.01667\n * );\n * applyKeyframeToRig(rig, predicted);\n * ```\n * \n * @korean 미래키프레임예측\n */\nexport const predictFutureKeyframe = (\n currentKeyframe: AnimationKeyframe,\n predictionState: MotionPredictionState,\n predictionTime: number\n): AnimationKeyframe => {\n // Clamp prediction time to reasonable bounds (max 50ms)\n const clampedPrediction = Math.min(predictionTime, 0.05);\n \n // Predict bone positions\n const predictedPositions = new Map<string, THREE.Vector3>();\n currentKeyframe.bonePositions.forEach((currentPos, boneName) => {\n const velocity = predictionState.velocities.get(boneName);\n if (velocity) {\n // Apply damping to prevent overshoot (0.8 factor for natural motion)\n const predicted = currentPos.clone().add(\n velocity.clone().multiplyScalar(clampedPrediction * 0.8)\n );\n predictedPositions.set(boneName, predicted);\n } else {\n predictedPositions.set(boneName, currentPos.clone());\n }\n });\n \n // Predict bone rotations\n const predictedRotations = new Map<string, THREE.Euler>();\n currentKeyframe.boneRotations.forEach((currentRot, boneName) => {\n const angularVel = predictionState.angularVelocities.get(boneName);\n if (angularVel) {\n // Apply damping to prevent overshoot\n const predicted = new THREE.Euler(\n currentRot.x + angularVel.x * clampedPrediction * 0.8,\n currentRot.y + angularVel.y * clampedPrediction * 0.8,\n currentRot.z + angularVel.z * clampedPrediction * 0.8,\n currentRot.order\n );\n predictedRotations.set(boneName, predicted);\n } else {\n predictedRotations.set(boneName, currentRot.clone());\n }\n });\n \n return {\n time: currentKeyframe.time + clampedPrediction,\n boneRotations: predictedRotations,\n bonePositions: predictedPositions,\n easing: currentKeyframe.easing,\n };\n};\n"],"mappings":"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"}