blacktrigram 0.7.39 → 0.7.41

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 (354) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/audio/AudioAssetLoader.js.map +1 -1
  3. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  4. package/lib/audio/AudioCache.js.map +1 -1
  5. package/lib/audio/AudioManager.js.map +1 -1
  6. package/lib/audio/AudioMonitor.js.map +1 -1
  7. package/lib/audio/AudioPool.js.map +1 -1
  8. package/lib/audio/AudioProvider.js.map +1 -1
  9. package/lib/audio/AudioUtils.js.map +1 -1
  10. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  11. package/lib/audio/VariantSelector.js.map +1 -1
  12. package/lib/audio/types.js.map +1 -1
  13. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  14. package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
  15. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
  18. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  19. package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
  20. package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  25. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  26. package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
  27. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  28. package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
  29. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  30. package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
  31. package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
  32. package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
  34. package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
  35. package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
  36. package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
  37. package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
  38. package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
  39. package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
  40. package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
  41. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  42. package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
  43. package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
  44. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  45. package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
  46. package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
  47. package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
  48. package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
  49. package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
  50. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  51. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  52. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  53. package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
  54. package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
  55. package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
  56. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  57. package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
  58. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  59. package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
  60. package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
  61. package/lib/components/screens/controls/components/Key3D.js.map +1 -1
  62. package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
  63. package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
  64. package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
  65. package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
  66. package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
  67. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  68. package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
  69. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  70. package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
  71. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  72. package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
  73. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  74. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  75. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  76. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  77. package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
  78. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  79. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  80. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  81. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  82. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  83. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  84. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  85. package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
  86. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  87. package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
  88. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  89. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  90. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  91. package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
  92. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  93. package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
  94. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  95. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  96. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  97. package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
  98. package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
  99. package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
  100. package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
  101. package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
  102. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  103. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  104. package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
  105. package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
  106. package/lib/components/shared/base/BaseButton.js.map +1 -1
  107. package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
  108. package/lib/components/shared/base/BasePanel.js.map +1 -1
  109. package/lib/components/shared/base/BaseText.js.map +1 -1
  110. package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
  111. package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
  112. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  113. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  114. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  115. package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
  116. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  117. package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
  118. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  119. package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
  120. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  121. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  122. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  123. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  124. package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
  125. package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
  126. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  127. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  128. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  129. package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
  130. package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
  131. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  132. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  133. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  134. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  135. package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
  136. package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
  137. package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
  138. package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
  139. package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
  140. package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
  141. package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
  142. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  143. package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
  144. package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
  145. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  146. package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
  147. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  148. package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
  149. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  150. package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
  151. package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
  152. package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
  153. package/lib/components/shared/three/ui/MenuList.js.map +1 -1
  154. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  155. package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
  156. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  157. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  158. package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
  159. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  160. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  161. package/lib/components/shared/ui/BackButton.js.map +1 -1
  162. package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
  163. package/lib/components/shared/ui/CombatTimer.js.map +1 -1
  164. package/lib/components/shared/ui/ErrorModal.js.map +1 -1
  165. package/lib/components/shared/ui/LoadingState.js.map +1 -1
  166. package/lib/components/shared/ui/SplashScreen.js +2 -2
  167. package/lib/components/shared/ui/SplashScreen.js.map +1 -1
  168. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  169. package/lib/components/shared/ui/VolumeControl.js.map +1 -1
  170. package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
  171. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  172. package/lib/constants/bodyDimensions.js.map +1 -1
  173. package/lib/constants/bodyRenderingConstants.js.map +1 -1
  174. package/lib/data/archetypeClothing.js.map +1 -1
  175. package/lib/data/archetypePhysicalAttributes.js.map +1 -1
  176. package/lib/data/techniqueMappings.js.map +1 -1
  177. package/lib/data/techniques.js.map +1 -1
  178. package/lib/hooks/useActionFeedback.js.map +1 -1
  179. package/lib/hooks/useBalanceAnimations.js.map +1 -1
  180. package/lib/hooks/useCombatTimer.js.map +1 -1
  181. package/lib/hooks/useDebounce.js.map +1 -1
  182. package/lib/hooks/useHUDLayout.js.map +1 -1
  183. package/lib/hooks/useHandPoseTransitions.js.map +1 -1
  184. package/lib/hooks/useKeyboardControls.js.map +1 -1
  185. package/lib/hooks/useMatchCountdown.js.map +1 -1
  186. package/lib/hooks/useMuscleActivation.js.map +1 -1
  187. package/lib/hooks/usePauseMenu.js.map +1 -1
  188. package/lib/hooks/usePlayerAnimation.js.map +1 -1
  189. package/lib/hooks/useResponsiveLayout.js.map +1 -1
  190. package/lib/hooks/useRoundTransition.js.map +1 -1
  191. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  192. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  193. package/lib/hooks/useThrottle.js.map +1 -1
  194. package/lib/hooks/useTouchControls.js.map +1 -1
  195. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  196. package/lib/hooks/useWindowSize.js.map +1 -1
  197. package/lib/systems/CombatSystem.js.map +1 -1
  198. package/lib/systems/EffectCalculator.js.map +1 -1
  199. package/lib/systems/LayoutSystem.js.map +1 -1
  200. package/lib/systems/PlayerEffectManager.js.map +1 -1
  201. package/lib/systems/ResponsiveScaling.js.map +1 -1
  202. package/lib/systems/TrigramSystem.js.map +1 -1
  203. package/lib/systems/VitalPointSystem.js.map +1 -1
  204. package/lib/systems/ai/AIPersonality.js.map +1 -1
  205. package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
  206. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  207. package/lib/systems/ai/ComboSystem.js.map +1 -1
  208. package/lib/systems/ai/DecisionTree.js.map +1 -1
  209. package/lib/systems/ai/TrainingAI.js.map +1 -1
  210. package/lib/systems/ai/types.js.map +1 -1
  211. package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
  212. package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
  213. package/lib/systems/animation/builders/HandPoses.js.map +1 -1
  214. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  215. package/lib/systems/animation/builders/KeyframeInterpolation.js +3 -90
  216. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  217. package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
  218. package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
  219. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  220. package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
  221. package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
  222. package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
  223. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  224. package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
  225. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  226. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  227. package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
  228. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  229. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  230. package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
  231. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  232. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  233. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  234. package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
  235. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  236. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  237. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  238. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  239. package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
  240. package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
  241. package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
  242. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  243. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  244. package/lib/systems/animation/core/types.js.map +1 -1
  245. package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
  246. package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
  247. package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
  248. package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
  249. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  250. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  251. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  252. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  253. package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
  254. package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
  255. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  256. package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
  257. package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
  258. package/lib/systems/bodypart/types.js.map +1 -1
  259. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  260. package/lib/systems/breathing/feedback.js.map +1 -1
  261. package/lib/systems/breathing/integration.js.map +1 -1
  262. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  263. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  264. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  265. package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
  266. package/lib/systems/combat/FallIntegration.js.map +1 -1
  267. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  268. package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
  269. package/lib/systems/combat/PainResponseSystem.js.map +1 -1
  270. package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
  271. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  272. package/lib/systems/combat/typeGuards.js.map +1 -1
  273. package/lib/systems/effects.js.map +1 -1
  274. package/lib/systems/game.js.map +1 -1
  275. package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
  276. package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
  277. package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
  278. package/lib/systems/movement/integration.js.map +1 -1
  279. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  280. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  281. package/lib/systems/physics/CoordinateMapper.js.map +1 -1
  282. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  283. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  284. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  285. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  286. package/lib/systems/trigram/KoreanCulture.js.map +1 -1
  287. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  288. package/lib/systems/trigram/StanceManager.js.map +1 -1
  289. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  290. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  291. package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
  292. package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
  293. package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
  294. package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
  295. package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
  296. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  297. package/lib/systems/trigram/techniques/index.js.map +1 -1
  298. package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
  299. package/lib/systems/trigram/types.js.map +1 -1
  300. package/lib/systems/types.js.map +1 -1
  301. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  302. package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
  303. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  304. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  305. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  306. package/lib/types/AccessibilityTypes.js.map +1 -1
  307. package/lib/types/PhysicsTypes.js.map +1 -1
  308. package/lib/types/common.js.map +1 -1
  309. package/lib/types/constants/colors.js.map +1 -1
  310. package/lib/types/constants/designSystem.js.map +1 -1
  311. package/lib/types/constants/layout.js.map +1 -1
  312. package/lib/types/constants/performance.js.map +1 -1
  313. package/lib/types/constants/typography.js.map +1 -1
  314. package/lib/types/facial.js.map +1 -1
  315. package/lib/types/hand-animation.js.map +1 -1
  316. package/lib/types/injury.js.map +1 -1
  317. package/lib/types/physics.js.map +1 -1
  318. package/lib/types/skeletal.js.map +1 -1
  319. package/lib/types/techniqueId.js.map +1 -1
  320. package/lib/utils/accessibility.js.map +1 -1
  321. package/lib/utils/arenaWorldDimensions.js.map +1 -1
  322. package/lib/utils/assetConfig.js.map +1 -1
  323. package/lib/utils/characterScaling.js.map +1 -1
  324. package/lib/utils/colorHelpers.js.map +1 -1
  325. package/lib/utils/colorUtils.js.map +1 -1
  326. package/lib/utils/combatReadiness.js.map +1 -1
  327. package/lib/utils/controlMapping.js.map +1 -1
  328. package/lib/utils/deviceDetection.js.map +1 -1
  329. package/lib/utils/effectUtils.js.map +1 -1
  330. package/lib/utils/fabricTextures.js.map +1 -1
  331. package/lib/utils/hapticFeedback.js.map +1 -1
  332. package/lib/utils/haptics.js.map +1 -1
  333. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  334. package/lib/utils/inputSystem.js.map +1 -1
  335. package/lib/utils/koreanThemeHelpers.js.map +1 -1
  336. package/lib/utils/math.js.map +1 -1
  337. package/lib/utils/mobileLayoutHelpers.js.map +1 -1
  338. package/lib/utils/mobileUIUtils.js.map +1 -1
  339. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  340. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  341. package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
  342. package/lib/utils/performanceOptimization.js.map +1 -1
  343. package/lib/utils/player3DHelpers.js.map +1 -1
  344. package/lib/utils/playerUtils.js.map +1 -1
  345. package/lib/utils/responsiveLayout.js.map +1 -1
  346. package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
  347. package/lib/utils/responsiveOrientationConstants.js.map +1 -1
  348. package/lib/utils/safeAreaUtils.js.map +1 -1
  349. package/lib/utils/sharedPhysicsConfig.js.map +1 -1
  350. package/lib/utils/skeletonScaling.js.map +1 -1
  351. package/lib/utils/stanceHelpers.js.map +1 -1
  352. package/lib/utils/threeObjectPool.js.map +1 -1
  353. package/lib/utils/visualEffects.js.map +1 -1
  354. package/package.json +8 -8
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualDPad.js","names":[],"sources":["../../../../src/components/shared/mobile/VirtualDPad.tsx"],"sourcesContent":["/**\n * VirtualDPad Component\n *\n * 8-directional virtual D-Pad for mobile touch controls\n * Provides tactile movement control with visual feedback and haptic response\n *\n * WCAG 2.1 Level AA Compliance:\n * - ARIA labels for all 8 directions\n * - Keyboard navigation (Arrow keys)\n * - Visible focus indicators (2px cyan border)\n * - role=\"group\" with descriptive label\n * - 48x48px minimum touch targets\n *\n * @module components/mobile/VirtualDPad\n * @category Mobile Controls\n * @korean 가상 방향 패드\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useState, useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { triggerHaptic } from \"../../../utils/haptics\";\nimport { getColorRGB } from \"../../../utils/colorHelpers\";\nimport { handleKeyboardNav, getFocusStyle } from \"../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../types/AccessibilityTypes\";\nimport { useThrottle } from \"../../../hooks/useThrottle\";\n\n/**\n * 8 directions for movement control\n */\nexport type Direction =\n | \"up\"\n | \"up-right\"\n | \"right\"\n | \"down-right\"\n | \"down\"\n | \"down-left\"\n | \"left\"\n | \"up-left\";\n\n/**\n * Event type for D-Pad interactions\n */\nexport type DPadEventType = \"start\" | \"end\";\n\n/**\n * Props for VirtualDPad component\n */\nexport interface VirtualDPadProps {\n /** Callback when direction changes */\n readonly onMove: (\n direction: Direction | null,\n eventType: DPadEventType,\n ) => void;\n /** Whether D-Pad is disabled */\n readonly disabled?: boolean;\n /** Size of the D-Pad in pixels (default: 140) */\n readonly size?: number;\n /** Position from bottom in pixels (default: 34 for safe area) */\n readonly bottom?: number;\n /** Position from left in pixels (default: 20) */\n readonly left?: number;\n /** Opacity of the D-Pad (default: 0.8) */\n readonly opacity?: number;\n}\n\n/**\n * Direction configuration for D-Pad buttons\n */\ninterface DirectionConfig {\n readonly direction: Direction;\n readonly angle: number; // Angle in degrees for positioning\n readonly korean: string; // Korean label\n readonly englishLabel: string; // English label for ARIA\n}\n\n/**\n * 8-directional configuration\n * Arranged clockwise starting from up (0°)\n */\nconst DIRECTIONS: readonly DirectionConfig[] = [\n { direction: \"up\", angle: 0, korean: \"↑\", englishLabel: \"Up\" },\n { direction: \"up-right\", angle: 45, korean: \"↗\", englishLabel: \"Up Right\" },\n { direction: \"right\", angle: 90, korean: \"→\", englishLabel: \"Right\" },\n {\n direction: \"down-right\",\n angle: 135,\n korean: \"↘\",\n englishLabel: \"Down Right\",\n },\n { direction: \"down\", angle: 180, korean: \"↓\", englishLabel: \"Down\" },\n {\n direction: \"down-left\",\n angle: 225,\n korean: \"↙\",\n englishLabel: \"Down Left\",\n },\n { direction: \"left\", angle: 270, korean: \"←\", englishLabel: \"Left\" },\n { direction: \"up-left\", angle: 315, korean: \"↖\", englishLabel: \"Up Left\" },\n] as const;\n\n/**\n * Individual D-Pad button component\n */\ninterface DPadButtonProps {\n readonly config: DirectionConfig;\n readonly active: boolean;\n readonly focused: boolean;\n readonly onStart: (e: React.TouchEvent | React.MouseEvent) => void;\n readonly onEnd: (e: React.TouchEvent | React.MouseEvent) => void;\n readonly onKeyDown: (e: React.KeyboardEvent) => void;\n readonly onFocus: () => void;\n readonly onBlur: () => void;\n readonly radius: number; // Radius for button positioning\n readonly buttonSize: number;\n}\n\n/**\n * D-Pad button positioned around the center\n * Memoized to prevent unnecessary re-renders\n */\nconst DPadButton = React.memo<DPadButtonProps>(\n ({\n config,\n active,\n focused,\n onStart,\n onEnd,\n onKeyDown,\n onFocus,\n onBlur,\n radius,\n buttonSize,\n }) => {\n // Calculate position using polar coordinates\n const radian = (config.angle - 90) * (Math.PI / 180); // -90 to start from top\n const x = Math.cos(radian) * radius;\n const y = Math.sin(radian) * radius;\n\n // Extract RGB colors using shared utility\n const goldColor = getColorRGB(KOREAN_COLORS.ACCENT_GOLD);\n const primaryColor = getColorRGB(KOREAN_COLORS.PRIMARY_CYAN);\n\n const ariaLabel = createBilingualLabel(\n `이동 ${config.korean}`,\n `Move ${config.englishLabel}`,\n ).label;\n\n return (\n <button\n onTouchStart={onStart}\n onTouchEnd={onEnd}\n onMouseDown={onStart}\n onMouseUp={onEnd}\n onMouseLeave={onEnd}\n onKeyDown={onKeyDown}\n onFocus={onFocus}\n onBlur={onBlur}\n style={{\n position: \"absolute\",\n left: `calc(50% + ${x}px - ${buttonSize / 2}px)`,\n top: `calc(50% + ${y}px - ${buttonSize / 2}px)`,\n width: `${buttonSize}px`,\n height: `${buttonSize}px`,\n borderRadius: \"50%\",\n background: active\n ? `rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.9)`\n : \"rgba(0, 0, 0, 0.5)\",\n border: focused\n ? `3px solid rgb(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b})`\n : `2px solid rgba(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b}, ${active ? 1 : 0.6})`,\n fontSize: \"20px\",\n color: \"#fff\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.2s ease, opacity 0.2s ease\",\n transform: active ? \"scale(1.1)\" : \"scale(1)\",\n boxShadow: focused\n ? `0 0 0 4px rgba(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b}, 0.5), 0 0 15px rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.8)`\n : active\n ? `0 0 15px rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.8)`\n : \"none\",\n ...getFocusStyle(focused),\n }}\n aria-label={ariaLabel}\n aria-pressed={active}\n role=\"button\"\n tabIndex={0}\n data-testid={`dpad-button-${config.direction}`}\n >\n {config.korean}\n </button>\n );\n },\n (prevProps, nextProps) => {\n // Only re-render if active or focused state changes\n // config.direction comparison removed as config is a stable reference\n return (\n prevProps.active === nextProps.active &&\n prevProps.focused === nextProps.focused\n );\n },\n);\n\nDPadButton.displayName = \"DPadButton\";\n\n/**\n * VirtualDPad Component\n *\n * 8-directional virtual D-Pad for touch-based movement control\n * Features:\n * - 8 directional buttons arranged in a circle\n * - Visual feedback on active direction\n * - Haptic feedback on touch\n * - Korean theming with cyberpunk aesthetics\n * - 48x48px minimum touch targets (improved from iOS 44px guideline)\n * - 140x140px default size for better mobile usability\n *\n * Usage in Combat/Training:\n * - Provides tactical positioning and footwork\n * - Alternative to keyboard WASD controls\n * - Essential for mobile gameplay\n *\n * @example\n * ```tsx\n * <VirtualDPad\n * onMove={(direction, eventType) => {\n * if (eventType === 'start' && direction) {\n * handleMovement(direction);\n * } else if (eventType === 'end') {\n * stopMovement();\n * }\n * }}\n * disabled={isPaused}\n * size={140}\n * bottom={34}\n * />\n * ```\n *\n * @public\n * @korean 가상방향패드\n */\nconst VirtualDPadComponent: React.FC<VirtualDPadProps> = ({\n onMove,\n disabled = false,\n size = 140,\n bottom = 34,\n left = 20,\n opacity = 0.8,\n}) => {\n const [activeDirection, setActiveDirection] = useState<Direction | null>(\n null,\n );\n const [focusedDirection, setFocusedDirection] = useState<Direction | null>(\n null,\n );\n\n // Throttle onMove callbacks to ~60fps for performance\n const throttledOnMove = useThrottle(onMove, 16);\n\n /**\n * Handle touch or mouse start on a direction button\n * Throttled for performance\n */\n const handleStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent, direction: Direction) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n setActiveDirection(direction);\n throttledOnMove(direction, \"start\");\n triggerHaptic(\"light\");\n },\n [disabled, throttledOnMove],\n );\n\n /**\n * Handle touch or mouse end\n * Throttled for performance\n */\n const handleEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n setActiveDirection(null);\n throttledOnMove(null, \"end\");\n },\n [disabled, throttledOnMove],\n );\n\n /**\n * Handle keyboard navigation for D-Pad\n */\n const handleKeyDown = useCallback(\n (direction: Direction) => (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setActiveDirection(direction);\n throttledOnMove(direction, \"start\");\n triggerHaptic(\"light\");\n // Release after brief delay\n setTimeout(() => {\n setActiveDirection(null);\n throttledOnMove(null, \"end\");\n }, 150);\n },\n });\n },\n [disabled, throttledOnMove],\n );\n\n // Memoize calculated dimensions to avoid recalculation\n const dimensions = useMemo(\n () => ({\n buttonSize: Math.max(48, size * 0.3),\n radius: (size - Math.max(48, size * 0.3)) / 2,\n }),\n [size],\n );\n\n // Memoize color values\n const colors = useMemo(\n () => ({\n primary: getColorRGB(KOREAN_COLORS.PRIMARY_CYAN),\n gold: getColorRGB(KOREAN_COLORS.ACCENT_GOLD),\n }),\n [],\n );\n\n return (\n <Html fullscreen>\n <div\n style={{\n position: \"absolute\",\n bottom: `${bottom}px`,\n left: `${left}px`,\n width: `${size}px`,\n height: `${size}px`,\n opacity: disabled ? 0.3 : opacity,\n pointerEvents: disabled ? \"none\" : \"auto\",\n }}\n data-testid=\"virtual-dpad\"\n >\n {/* D-Pad Container */}\n <div\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n background: \"rgba(0, 0, 0, 0.5)\",\n borderRadius: \"50%\",\n border: `2px solid rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.8)`,\n boxShadow: `0 0 20px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.3)`,\n }}\n role=\"group\"\n aria-label={\n createBilingualLabel(\"방향 패드\", \"Directional Pad\").label\n }\n >\n {/* Directional Buttons */}\n {DIRECTIONS.map((config) => (\n <DPadButton\n key={config.direction}\n config={config}\n active={activeDirection === config.direction}\n focused={focusedDirection === config.direction}\n onStart={(e) => handleStart(e, config.direction)}\n onEnd={handleEnd}\n onKeyDown={handleKeyDown(config.direction)}\n onFocus={() => setFocusedDirection(config.direction)}\n onBlur={() => setFocusedDirection(null)}\n radius={dimensions.radius}\n buttonSize={dimensions.buttonSize}\n />\n ))}\n\n {/* Center Indicator */}\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n width: \"20px\",\n height: \"20px\",\n borderRadius: \"50%\",\n background: activeDirection\n ? `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.9)`\n : `rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.7)`,\n border: \"2px solid #fff\",\n transition: \"transform 0.15s ease, opacity 0.15s ease\",\n boxShadow: activeDirection\n ? `0 0 15px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.8)`\n : \"none\",\n }}\n data-testid=\"dpad-center\"\n />\n </div>\n </div>\n </Html>\n );\n};\n\n/**\n * Memoized VirtualDPad with custom comparison\n * Only re-renders when props change\n */\nexport const VirtualDPad = React.memo(\n VirtualDPadComponent,\n (prevProps, nextProps) => {\n return (\n prevProps.disabled === nextProps.disabled &&\n prevProps.size === nextProps.size &&\n prevProps.bottom === nextProps.bottom &&\n prevProps.left === nextProps.left &&\n prevProps.opacity === nextProps.opacity &&\n prevProps.onMove === nextProps.onMove\n );\n },\n);\n\nVirtualDPad.displayName = \"VirtualDPad\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"VirtualDPad.js","names":[],"sources":["../../../../src/components/shared/mobile/VirtualDPad.tsx"],"sourcesContent":["/**\n * VirtualDPad Component\n *\n * 8-directional virtual D-Pad for mobile touch controls\n * Provides tactile movement control with visual feedback and haptic response\n *\n * WCAG 2.1 Level AA Compliance:\n * - ARIA labels for all 8 directions\n * - Keyboard navigation (Arrow keys)\n * - Visible focus indicators (2px cyan border)\n * - role=\"group\" with descriptive label\n * - 48x48px minimum touch targets\n *\n * @module components/mobile/VirtualDPad\n * @category Mobile Controls\n * @korean 가상 방향 패드\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useState, useMemo } from \"react\";\nimport { KOREAN_COLORS } from \"@/types/constants\";\nimport { triggerHaptic } from \"../../../utils/haptics\";\nimport { getColorRGB } from \"../../../utils/colorHelpers\";\nimport { handleKeyboardNav, getFocusStyle } from \"../../../utils/accessibility\";\nimport { createBilingualLabel } from \"../../../types/AccessibilityTypes\";\nimport { useThrottle } from \"../../../hooks/useThrottle\";\n\n/**\n * 8 directions for movement control\n */\nexport type Direction =\n | \"up\"\n | \"up-right\"\n | \"right\"\n | \"down-right\"\n | \"down\"\n | \"down-left\"\n | \"left\"\n | \"up-left\";\n\n/**\n * Event type for D-Pad interactions\n */\nexport type DPadEventType = \"start\" | \"end\";\n\n/**\n * Props for VirtualDPad component\n */\nexport interface VirtualDPadProps {\n /** Callback when direction changes */\n readonly onMove: (\n direction: Direction | null,\n eventType: DPadEventType,\n ) => void;\n /** Whether D-Pad is disabled */\n readonly disabled?: boolean;\n /** Size of the D-Pad in pixels (default: 140) */\n readonly size?: number;\n /** Position from bottom in pixels (default: 34 for safe area) */\n readonly bottom?: number;\n /** Position from left in pixels (default: 20) */\n readonly left?: number;\n /** Opacity of the D-Pad (default: 0.8) */\n readonly opacity?: number;\n}\n\n/**\n * Direction configuration for D-Pad buttons\n */\ninterface DirectionConfig {\n readonly direction: Direction;\n readonly angle: number; // Angle in degrees for positioning\n readonly korean: string; // Korean label\n readonly englishLabel: string; // English label for ARIA\n}\n\n/**\n * 8-directional configuration\n * Arranged clockwise starting from up (0°)\n */\nconst DIRECTIONS: readonly DirectionConfig[] = [\n { direction: \"up\", angle: 0, korean: \"↑\", englishLabel: \"Up\" },\n { direction: \"up-right\", angle: 45, korean: \"↗\", englishLabel: \"Up Right\" },\n { direction: \"right\", angle: 90, korean: \"→\", englishLabel: \"Right\" },\n {\n direction: \"down-right\",\n angle: 135,\n korean: \"↘\",\n englishLabel: \"Down Right\",\n },\n { direction: \"down\", angle: 180, korean: \"↓\", englishLabel: \"Down\" },\n {\n direction: \"down-left\",\n angle: 225,\n korean: \"↙\",\n englishLabel: \"Down Left\",\n },\n { direction: \"left\", angle: 270, korean: \"←\", englishLabel: \"Left\" },\n { direction: \"up-left\", angle: 315, korean: \"↖\", englishLabel: \"Up Left\" },\n] as const;\n\n/**\n * Individual D-Pad button component\n */\ninterface DPadButtonProps {\n readonly config: DirectionConfig;\n readonly active: boolean;\n readonly focused: boolean;\n readonly onStart: (e: React.TouchEvent | React.MouseEvent) => void;\n readonly onEnd: (e: React.TouchEvent | React.MouseEvent) => void;\n readonly onKeyDown: (e: React.KeyboardEvent) => void;\n readonly onFocus: () => void;\n readonly onBlur: () => void;\n readonly radius: number; // Radius for button positioning\n readonly buttonSize: number;\n}\n\n/**\n * D-Pad button positioned around the center\n * Memoized to prevent unnecessary re-renders\n */\nconst DPadButton = React.memo<DPadButtonProps>(\n ({\n config,\n active,\n focused,\n onStart,\n onEnd,\n onKeyDown,\n onFocus,\n onBlur,\n radius,\n buttonSize,\n }) => {\n // Calculate position using polar coordinates\n const radian = (config.angle - 90) * (Math.PI / 180); // -90 to start from top\n const x = Math.cos(radian) * radius;\n const y = Math.sin(radian) * radius;\n\n // Extract RGB colors using shared utility\n const goldColor = getColorRGB(KOREAN_COLORS.ACCENT_GOLD);\n const primaryColor = getColorRGB(KOREAN_COLORS.PRIMARY_CYAN);\n\n const ariaLabel = createBilingualLabel(\n `이동 ${config.korean}`,\n `Move ${config.englishLabel}`,\n ).label;\n\n return (\n <button\n onTouchStart={onStart}\n onTouchEnd={onEnd}\n onMouseDown={onStart}\n onMouseUp={onEnd}\n onMouseLeave={onEnd}\n onKeyDown={onKeyDown}\n onFocus={onFocus}\n onBlur={onBlur}\n style={{\n position: \"absolute\",\n left: `calc(50% + ${x}px - ${buttonSize / 2}px)`,\n top: `calc(50% + ${y}px - ${buttonSize / 2}px)`,\n width: `${buttonSize}px`,\n height: `${buttonSize}px`,\n borderRadius: \"50%\",\n background: active\n ? `rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.9)`\n : \"rgba(0, 0, 0, 0.5)\",\n border: focused\n ? `3px solid rgb(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b})`\n : `2px solid rgba(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b}, ${active ? 1 : 0.6})`,\n fontSize: \"20px\",\n color: \"#fff\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n cursor: \"pointer\",\n userSelect: \"none\",\n touchAction: \"none\",\n transition: \"transform 0.2s ease, opacity 0.2s ease\",\n transform: active ? \"scale(1.1)\" : \"scale(1)\",\n boxShadow: focused\n ? `0 0 0 4px rgba(${primaryColor.r}, ${primaryColor.g}, ${primaryColor.b}, 0.5), 0 0 15px rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.8)`\n : active\n ? `0 0 15px rgba(${goldColor.r}, ${goldColor.g}, ${goldColor.b}, 0.8)`\n : \"none\",\n ...getFocusStyle(focused),\n }}\n aria-label={ariaLabel}\n aria-pressed={active}\n role=\"button\"\n tabIndex={0}\n data-testid={`dpad-button-${config.direction}`}\n >\n {config.korean}\n </button>\n );\n },\n (prevProps, nextProps) => {\n // Only re-render if active or focused state changes\n // config.direction comparison removed as config is a stable reference\n return (\n prevProps.active === nextProps.active &&\n prevProps.focused === nextProps.focused\n );\n },\n);\n\nDPadButton.displayName = \"DPadButton\";\n\n/**\n * VirtualDPad Component\n *\n * 8-directional virtual D-Pad for touch-based movement control\n * Features:\n * - 8 directional buttons arranged in a circle\n * - Visual feedback on active direction\n * - Haptic feedback on touch\n * - Korean theming with cyberpunk aesthetics\n * - 48x48px minimum touch targets (improved from iOS 44px guideline)\n * - 140x140px default size for better mobile usability\n *\n * Usage in Combat/Training:\n * - Provides tactical positioning and footwork\n * - Alternative to keyboard WASD controls\n * - Essential for mobile gameplay\n *\n * @example\n * ```tsx\n * <VirtualDPad\n * onMove={(direction, eventType) => {\n * if (eventType === 'start' && direction) {\n * handleMovement(direction);\n * } else if (eventType === 'end') {\n * stopMovement();\n * }\n * }}\n * disabled={isPaused}\n * size={140}\n * bottom={34}\n * />\n * ```\n *\n * @public\n * @korean 가상방향패드\n */\nconst VirtualDPadComponent: React.FC<VirtualDPadProps> = ({\n onMove,\n disabled = false,\n size = 140,\n bottom = 34,\n left = 20,\n opacity = 0.8,\n}) => {\n const [activeDirection, setActiveDirection] = useState<Direction | null>(\n null,\n );\n const [focusedDirection, setFocusedDirection] = useState<Direction | null>(\n null,\n );\n\n // Throttle onMove callbacks to ~60fps for performance\n const throttledOnMove = useThrottle(onMove, 16);\n\n /**\n * Handle touch or mouse start on a direction button\n * Throttled for performance\n */\n const handleStart = useCallback(\n (e: React.TouchEvent | React.MouseEvent, direction: Direction) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n setActiveDirection(direction);\n throttledOnMove(direction, \"start\");\n triggerHaptic(\"light\");\n },\n [disabled, throttledOnMove],\n );\n\n /**\n * Handle touch or mouse end\n * Throttled for performance\n */\n const handleEnd = useCallback(\n (e: React.TouchEvent | React.MouseEvent) => {\n if (disabled) return;\n e.preventDefault();\n e.stopPropagation();\n\n setActiveDirection(null);\n throttledOnMove(null, \"end\");\n },\n [disabled, throttledOnMove],\n );\n\n /**\n * Handle keyboard navigation for D-Pad\n */\n const handleKeyDown = useCallback(\n (direction: Direction) => (e: React.KeyboardEvent) => {\n if (disabled) return;\n handleKeyboardNav(e.nativeEvent, {\n onActivate: () => {\n setActiveDirection(direction);\n throttledOnMove(direction, \"start\");\n triggerHaptic(\"light\");\n // Release after brief delay\n setTimeout(() => {\n setActiveDirection(null);\n throttledOnMove(null, \"end\");\n }, 150);\n },\n });\n },\n [disabled, throttledOnMove],\n );\n\n // Memoize calculated dimensions to avoid recalculation\n const dimensions = useMemo(\n () => ({\n buttonSize: Math.max(48, size * 0.3),\n radius: (size - Math.max(48, size * 0.3)) / 2,\n }),\n [size],\n );\n\n // Memoize color values\n const colors = useMemo(\n () => ({\n primary: getColorRGB(KOREAN_COLORS.PRIMARY_CYAN),\n gold: getColorRGB(KOREAN_COLORS.ACCENT_GOLD),\n }),\n [],\n );\n\n return (\n <Html fullscreen>\n <div\n style={{\n position: \"absolute\",\n bottom: `${bottom}px`,\n left: `${left}px`,\n width: `${size}px`,\n height: `${size}px`,\n opacity: disabled ? 0.3 : opacity,\n pointerEvents: disabled ? \"none\" : \"auto\",\n }}\n data-testid=\"virtual-dpad\"\n >\n {/* D-Pad Container */}\n <div\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n background: \"rgba(0, 0, 0, 0.5)\",\n borderRadius: \"50%\",\n border: `2px solid rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.8)`,\n boxShadow: `0 0 20px rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.3)`,\n }}\n role=\"group\"\n aria-label={\n createBilingualLabel(\"방향 패드\", \"Directional Pad\").label\n }\n >\n {/* Directional Buttons */}\n {DIRECTIONS.map((config) => (\n <DPadButton\n key={config.direction}\n config={config}\n active={activeDirection === config.direction}\n focused={focusedDirection === config.direction}\n onStart={(e) => handleStart(e, config.direction)}\n onEnd={handleEnd}\n onKeyDown={handleKeyDown(config.direction)}\n onFocus={() => setFocusedDirection(config.direction)}\n onBlur={() => setFocusedDirection(null)}\n radius={dimensions.radius}\n buttonSize={dimensions.buttonSize}\n />\n ))}\n\n {/* Center Indicator */}\n <div\n style={{\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n transform: \"translate(-50%, -50%)\",\n width: \"20px\",\n height: \"20px\",\n borderRadius: \"50%\",\n background: activeDirection\n ? `rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.9)`\n : `rgba(${colors.primary.r}, ${colors.primary.g}, ${colors.primary.b}, 0.7)`,\n border: \"2px solid #fff\",\n transition: \"transform 0.15s ease, opacity 0.15s ease\",\n boxShadow: activeDirection\n ? `0 0 15px rgba(${colors.gold.r}, ${colors.gold.g}, ${colors.gold.b}, 0.8)`\n : \"none\",\n }}\n data-testid=\"dpad-center\"\n />\n </div>\n </div>\n </Html>\n );\n};\n\n/**\n * Memoized VirtualDPad with custom comparison\n * Only re-renders when props change\n */\nexport const VirtualDPad = React.memo(\n VirtualDPadComponent,\n (prevProps, nextProps) => {\n return (\n prevProps.disabled === nextProps.disabled &&\n prevProps.size === nextProps.size &&\n prevProps.bottom === nextProps.bottom &&\n prevProps.left === nextProps.left &&\n prevProps.opacity === nextProps.opacity &&\n prevProps.onMove === nextProps.onMove\n );\n },\n);\n\nVirtualDPad.displayName = \"VirtualDPad\";\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BodySurface.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BodySurface.tsx"],"sourcesContent":["/**\n * Body Surface component for realistic humanoid skin/flesh rendering\n *\n * **Purpose**: Provides continuous body surface layer between bones and clothing\n * to create organic, human-like appearance instead of robotic segmented look.\n *\n * **Features**:\n * - Continuous skin layer covering neck, torso, shoulders, arms, and legs\n * - Archetype-specific skin tones for visual variety\n * - Proper body thickness scaling based on muscle and fat mass\n * - Double-sided rendering (THREE.DoubleSide) for complete 360° coverage and gap prevention\n * - Smooth tapering for realistic proportions\n * - Enhanced material with subsurface scattering and clearcoat\n * - High-quality geometry with increased segment counts\n * - Shoulder joints for smooth transitions\n *\n * **Rendering Order**: Bones → Muscles (optional) → Body Surface → Clothing\n *\n * @module components/three/BodySurface\n * @category 3D Components\n * @korean 신체표면컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n PECTORALS_RADIUS,\n CORE_RADIUS,\n BICEP_RADIUS,\n FOREARM_RADIUS,\n QUAD_RADIUS,\n CALF_RADIUS,\n} from \"../../../../constants/bodyDimensions\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport { getArchetypeSkinTone } from \"../../../../utils/colorUtils\";\n\n/**\n * Props for BodySurface component\n *\n * @korean 신체표면속성\n */\nexport interface BodySurfaceProps {\n /**\n * Name of the bone this body surface attaches to\n * @korean 뼈이름\n */\n readonly boneName: string;\n\n /**\n * Player archetype for skin tone\n * @korean 플레이어원형\n */\n readonly archetype: PlayerArchetype;\n\n /**\n * Physical attributes for body sizing\n * @korean 신체속성\n */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n\n /**\n * Distance from camera for LOD optimization\n * @korean 카메라거리\n */\n readonly cameraDistance?: number;\n}\n\n/**\n * Body surface segment configuration\n *\n * @korean 신체표면세그먼트\n */\ninterface BodySurfaceSegment {\n readonly geometry: THREE.BufferGeometry;\n readonly localOffset: THREE.Vector3;\n readonly localRotation: THREE.Euler;\n}\n\n/**\n * Calculate body thickness multiplier with reasonable limits\n *\n * Uses linear scaling instead of square root to prevent excessive inflation\n * for heavy characters. Caps maximum thickness at 1.20x to maintain realism.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n * @korean 신체두께계산\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n // Linear scaling with limits (not square root which causes excessive inflation)\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n // Base 0.85, muscle adds up to +0.15, fat adds up to +0.20\n // Thin character (28kg muscle, 10kg fat): 0.85 + (-0.030) + (-0.033) ≈ 0.787\n // Average (35kg muscle, 12kg fat): 0.85 + 0 + 0 = 0.85\n // Heavy (48kg muscle, 20kg fat): 0.85 + 0.056 + 0.133 ≈ 1.039\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n // Cap at 1.20x maximum to prevent \"michelin man\" effect\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Determine segment count based on camera distance for LOD\n *\n * @param cameraDistance - Distance from camera\n * @returns Segment count for geometry\n * @korean LOD세그먼트수\n */\nconst getLODSegmentCount = (cameraDistance: number): number => {\n if (cameraDistance < 5) {\n return 20; // High detail for close-ups\n } else if (cameraDistance < 10) {\n return 16; // Medium detail for normal distance\n } else {\n return 12; // Low detail for far distance\n }\n};\n\n/**\n * Get body surface segments for a specific bone\n *\n * Creates continuous skin geometry appropriate for each body part.\n * Implements LOD (Level of Detail) based on camera distance for performance.\n *\n * @param boneName - Name of the bone\n * @param physicalAttributes - Physical attributes for scaling\n * @param cameraDistance - Distance from camera for LOD\n * @returns Array of body surface segments\n * @korean 신체표면세그먼트가져오기\n */\nconst getBodySurfaceForBone = (\n boneName: string,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n cameraDistance: number = 10,\n): BodySurfaceSegment[] => {\n const segments: BodySurfaceSegment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n // Get appropriate segment count based on distance\n const segmentCount = getLODSegmentCount(cameraDistance);\n\n // Scaling factors for different body parts\n const torsoScale = physicalAttributes.torsoLength / 59; // Reference: 59cm torso\n const armScale = physicalAttributes.armLength / 77; // Reference: 77cm arms\n const legScale = physicalAttributes.legLength / 96; // Reference: 96cm legs\n\n switch (boneName) {\n case \"neck\": {\n // Neck cylinder - smooth connection between head and torso with LOD\n const neckRadius = 0.06 * bodyThickness;\n const neckLength = 0.11 * bodyThickness;\n // Main neck cylinder\n segments.push({\n geometry: new THREE.CylinderGeometry(\n neckRadius * 0.9, // Slightly narrower at top (under jaw)\n neckRadius * 1.2, // Wider at base (base of neck)\n neckLength,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -neckLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Neck base flare - smooth transition to shoulders/torso\n segments.push({\n geometry: new THREE.CylinderGeometry(\n neckRadius * 1.2, // Match neck bottom\n neckRadius * 1.6, // Flare out to trapezius area\n neckLength * 0.3,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, -neckLength * 0.75, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"head\": {\n // Head / skull sphere - Face3D renders features on top of this base\n // Slightly elongated sphere for realistic cranium shape\n const headRadius = 0.095 * bodyThickness; // ~19cm head width\n const headHeight = headRadius * 1.15; // Slightly taller than wide\n\n // Main cranium sphere\n segments.push({\n geometry: new THREE.SphereGeometry(\n headRadius,\n segmentCount,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, headHeight * 0.15, 0), // Slightly above bone origin\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Jaw/chin area - smaller sphere below to fill out the jaw line\n segments.push({\n geometry: new THREE.SphereGeometry(\n headRadius * 0.7,\n Math.floor(segmentCount * 0.75),\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(\n 0,\n -headHeight * 0.25,\n headRadius * 0.15,\n ),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_upper\": {\n // Upper torso / chest area - wider for shoulders\n const width =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.9;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.3;\n const depth = PECTORALS_RADIUS * 2 * bodyThickness * 0.95;\n\n segments.push({\n geometry: new THREE.BoxGeometry(\n width,\n height,\n depth,\n Math.max(2, Math.round(segmentCount * 0.2)),\n Math.max(2, Math.round(segmentCount * 0.2)),\n Math.max(2, Math.round(segmentCount * 0.2)),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_middle\": {\n // Main torso - box covering chest and abs\n const width = (physicalAttributes.shoulderWidth / 100) * bodyThickness;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.35;\n const depth = PECTORALS_RADIUS * 2 * bodyThickness; // Front to back depth\n\n // Use LOD-aware segment counts (slightly higher than other regions) to keep torso shading smooth:\n // - Torso is frequently closest to the camera and used for breathing / impact motion.\n // - Vital point overlays and skin highlights rely on smoother curvature in this region.\n // - We still respect the global LOD segmentCount so distant torsos reduce complexity consistently.\n const torsoSegmentsX = Math.max(2, Math.round(segmentCount * 0.2));\n const torsoSegmentsY = Math.max(3, Math.round(segmentCount * 0.3));\n const torsoSegmentsZ = Math.max(2, Math.round(segmentCount * 0.2));\n\n segments.push({\n geometry: new THREE.BoxGeometry(\n width,\n height,\n depth,\n torsoSegmentsX,\n torsoSegmentsY,\n torsoSegmentsZ,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_lower\": {\n // Lower torso / lumbar area - tapers from chest to pelvis\n const widthTop =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.95;\n const widthBottom =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.85;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.3;\n\n // Use tapered cylinder for natural waist shape\n segments.push({\n geometry: new THREE.CylinderGeometry(\n widthTop * 0.5,\n widthBottom * 0.5,\n height,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"pelvis\": {\n // Pelvis/hip area - wider for hip bones, connecting to legs\n const width =\n (physicalAttributes.shoulderWidth / 100) * 0.85 * bodyThickness;\n const height = 0.15;\n const depth = CORE_RADIUS * 2 * bodyThickness;\n\n segments.push({\n geometry: new THREE.BoxGeometry(width, height, depth, 3, 2, 3),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"shoulder_L\":\n case \"shoulder_R\": {\n // Shoulder joint - full sphere for smooth, rounded shoulder with LOD\n const shoulderRadius = BICEP_RADIUS * bodyThickness * 1.4;\n\n segments.push({\n geometry: new THREE.SphereGeometry(\n shoulderRadius,\n segmentCount,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"upper_arm_L\":\n case \"upper_arm_R\": {\n // Upper arm - tapered cylinder (bicep area) with LOD\n const radiusTop = BICEP_RADIUS * bodyThickness * 1.1; // Wider at shoulder\n const radiusBottom = BICEP_RADIUS * bodyThickness * 0.9; // Narrower at elbow\n const length = (physicalAttributes.armLength / 100) * armScale * 0.45;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"forearm_L\":\n case \"forearm_R\": {\n // Forearm - tapered cylinder with LOD\n const radiusTop = FOREARM_RADIUS * bodyThickness * 1.0; // Wider at elbow\n const radiusBottom = FOREARM_RADIUS * bodyThickness * 0.8; // Less narrow - connects to wrist smoothly\n const length = (physicalAttributes.armLength / 100) * armScale * 0.4;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"thigh_L\":\n case \"thigh_R\": {\n // Thigh - tapered cylinder (quad area) with LOD\n const radiusTop = QUAD_RADIUS * bodyThickness * 1.3; // Wider at hip for smooth connection\n const radiusBottom = QUAD_RADIUS * bodyThickness * 0.95; // Narrower at knee\n const length = (physicalAttributes.legLength / 100) * legScale * 0.45;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"shin_L\":\n case \"shin_R\": {\n // Shin/calf - tapered cylinder with LOD\n const radiusTop = CALF_RADIUS * bodyThickness * 1.0; // Wider at knee\n const radiusBottom = CALF_RADIUS * bodyThickness * 0.8; // Less taper - connects to ankle\n const length = (physicalAttributes.legLength / 100) * legScale * 0.42;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"elbow_L\":\n case \"elbow_R\": {\n // Elbow joint sphere - bridges upper arm and forearm\n const elbowRadius = BICEP_RADIUS * bodyThickness * 0.95;\n segments.push({\n geometry: new THREE.SphereGeometry(\n elbowRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"wrist_L\":\n case \"wrist_R\": {\n // Wrist joint - tapered cylinder connecting forearm to hand\n // No rotation needed - cylinder Y axis already aligns with forearm direction\n const wristRadiusTop = FOREARM_RADIUS * bodyThickness * 0.75;\n const wristRadiusBottom = FOREARM_RADIUS * bodyThickness * 0.6;\n const wristLength = 0.035 * bodyThickness;\n segments.push({\n geometry: new THREE.CylinderGeometry(\n wristRadiusTop,\n wristRadiusBottom,\n wristLength,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, -wristLength * 0.3, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"hand_L\":\n case \"hand_R\": {\n // Wrist-to-hand bridge sphere - fills gap between wrist skin and Hand3D component\n const handBridgeRadius = FOREARM_RADIUS * bodyThickness * 0.55;\n segments.push({\n geometry: new THREE.SphereGeometry(\n handBridgeRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"knee_L\":\n case \"knee_R\": {\n // Knee joint sphere - bridges thigh and shin\n const kneeRadius = QUAD_RADIUS * bodyThickness * 0.9;\n segments.push({\n geometry: new THREE.SphereGeometry(\n kneeRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Front kneecap bump\n const kneecapRadius = kneeRadius * 0.5;\n segments.push({\n geometry: new THREE.SphereGeometry(\n kneecapRadius,\n Math.floor(segmentCount * 0.5),\n Math.floor(segmentCount * 0.5),\n ),\n localOffset: new THREE.Vector3(0, 0, kneeRadius * 0.6),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"hip_L\":\n case \"hip_R\": {\n // Hip joint sphere - connects pelvis to thigh smoothly\n const hipRadius = QUAD_RADIUS * bodyThickness * 1.1;\n segments.push({\n geometry: new THREE.SphereGeometry(\n hipRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, -hipRadius * 0.3, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"foot_L\":\n case \"foot_R\": {\n // Ankle bridge sphere - connects shin body surface to Foot3D component\n const ankleRadius = CALF_RADIUS * bodyThickness * 0.75;\n segments.push({\n geometry: new THREE.SphereGeometry(\n ankleRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n // Shoulders already handled by shoulder_L/R cases\n // Hand detail (fingers) uses specialized Hand3D component\n // Foot detail (toes) uses specialized Foot3D component\n // Head uses Face3D component\n }\n\n return segments;\n};\n\n/**\n * BodySurface Component\n *\n * Renders realistic body surface (skin/flesh) attached to a specific bone.\n * Creates organic, human-like appearance by providing continuous body coverage.\n *\n * @example\n * ```tsx\n * <BodySurface\n * boneName=\"spine_middle\"\n * archetype={PlayerArchetype.MUSA}\n * physicalAttributes={musaPhysicalAttrs}\n * />\n * ```\n *\n * @korean 신체표면컴포넌트\n */\nexport const BodySurface: React.FC<BodySurfaceProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n cameraDistance = 10,\n}) => {\n // Default physical attributes if not provided\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 77,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n // Get body surface segments for this bone with LOD\n const segments = useMemo(\n () => getBodySurfaceForBone(boneName, attrs, cameraDistance),\n [boneName, attrs, cameraDistance],\n );\n\n // Get archetype-specific skin tone\n const skinTone = useMemo(() => getArchetypeSkinTone(archetype), [archetype]);\n\n /**\n * Create skin material with realistic properties\n *\n * Uses MeshPhysicalMaterial for enhanced realism:\n * - Skin tone color from archetype\n * - Subsurface scattering with subtle transmission for realistic skin translucency\n * - Roughness: 0.65 (slightly rough skin texture)\n * - Metalness: 0.0 (skin is not metallic)\n * - Clearcoat for natural skin sheen\n * - Sheen for skin surface properties\n * - Subtle emissive for alive appearance\n * - Double-sided: true (render both inside and outside)\n *\n * Material properties are intentionally different from Face3D/Hand3D/Foot3D to capture\n * body-specific skin characteristics:\n * - transmission: 0.08 (extremities use 0) – BodySurface is the only skin material with\n * non-zero transmission, to model subsurface scattering on larger, less directly lit\n * body areas.\n * - thickness: 0.5 (extremities use 0.1) – torso/limb skin is treated as thicker than\n * hands, feet, and face, which appear optically thinner.\n * - clearcoat: 0.15 (extremities use 0.3) – extremities are rendered slightly glossier\n * due to being more exposed to direct light, while the main body surface is softer.\n *\n * @korean 피부재료생성\n */\n const material = useMemo(() => {\n return new THREE.MeshPhysicalMaterial({\n color: skinTone,\n roughness: 0.65, // Slightly rough for realistic skin\n metalness: 0.0, // Skin is not metallic\n\n // Subsurface scattering for realistic skin translucency\n transmission: 0.08, // Small non-zero transmission for subtle skin translucency\n thickness: 0.5, // Moderate thickness for subsurface scattering\n ior: 1.4, // Index of refraction for human skin\n\n // Clearcoat for natural skin sheen (subtle)\n clearcoat: 0.15,\n clearcoatRoughness: 0.8,\n\n // Sheen for skin surface properties (consistent with Hand3D, Foot3D)\n sheen: 0.1,\n sheenRoughness: 0.8,\n\n // Subtle emissive for alive appearance (consistent with other skin components)\n emissive: new THREE.Color(skinTone),\n emissiveIntensity: 0.02,\n\n // Reflectivity for realistic appearance\n reflectivity: 0.1,\n\n side: THREE.DoubleSide, // Render both sides for complete body coverage and gap prevention\n flatShading: false, // Smooth shading for organic look\n });\n }, [skinTone]);\n\n // Cleanup material on unmount\n useEffect(() => {\n return () => {\n material.dispose();\n };\n }, [material]);\n\n // Cleanup geometries when segments change or on unmount\n useEffect(() => {\n return () => {\n segments.forEach((segment) => {\n segment.geometry.dispose();\n });\n };\n }, [segments]);\n\n if (segments.length === 0) {\n return null;\n }\n\n return (\n <>\n {segments.map((segment, index) => (\n <mesh\n key={`body-surface-${boneName}-${index}`}\n geometry={segment.geometry}\n material={material}\n position={segment.localOffset.toArray()}\n rotation={[\n segment.localRotation.x,\n segment.localRotation.y,\n segment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`body-surface-${boneName}`}\n />\n ))}\n </>\n );\n};\n\nexport default BodySurface;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BodySurface.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BodySurface.tsx"],"sourcesContent":["/**\n * Body Surface component for realistic humanoid skin/flesh rendering\n *\n * **Purpose**: Provides continuous body surface layer between bones and clothing\n * to create organic, human-like appearance instead of robotic segmented look.\n *\n * **Features**:\n * - Continuous skin layer covering neck, torso, shoulders, arms, and legs\n * - Archetype-specific skin tones for visual variety\n * - Proper body thickness scaling based on muscle and fat mass\n * - Double-sided rendering (THREE.DoubleSide) for complete 360° coverage and gap prevention\n * - Smooth tapering for realistic proportions\n * - Enhanced material with subsurface scattering and clearcoat\n * - High-quality geometry with increased segment counts\n * - Shoulder joints for smooth transitions\n *\n * **Rendering Order**: Bones → Muscles (optional) → Body Surface → Clothing\n *\n * @module components/three/BodySurface\n * @category 3D Components\n * @korean 신체표면컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n PECTORALS_RADIUS,\n CORE_RADIUS,\n BICEP_RADIUS,\n FOREARM_RADIUS,\n QUAD_RADIUS,\n CALF_RADIUS,\n} from \"../../../../constants/bodyDimensions\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport { getArchetypeSkinTone } from \"../../../../utils/colorUtils\";\n\n/**\n * Props for BodySurface component\n *\n * @korean 신체표면속성\n */\nexport interface BodySurfaceProps {\n /**\n * Name of the bone this body surface attaches to\n * @korean 뼈이름\n */\n readonly boneName: string;\n\n /**\n * Player archetype for skin tone\n * @korean 플레이어원형\n */\n readonly archetype: PlayerArchetype;\n\n /**\n * Physical attributes for body sizing\n * @korean 신체속성\n */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n\n /**\n * Distance from camera for LOD optimization\n * @korean 카메라거리\n */\n readonly cameraDistance?: number;\n}\n\n/**\n * Body surface segment configuration\n *\n * @korean 신체표면세그먼트\n */\ninterface BodySurfaceSegment {\n readonly geometry: THREE.BufferGeometry;\n readonly localOffset: THREE.Vector3;\n readonly localRotation: THREE.Euler;\n}\n\n/**\n * Calculate body thickness multiplier with reasonable limits\n *\n * Uses linear scaling instead of square root to prevent excessive inflation\n * for heavy characters. Caps maximum thickness at 1.20x to maintain realism.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n * @korean 신체두께계산\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n // Linear scaling with limits (not square root which causes excessive inflation)\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n // Base 0.85, muscle adds up to +0.15, fat adds up to +0.20\n // Thin character (28kg muscle, 10kg fat): 0.85 + (-0.030) + (-0.033) ≈ 0.787\n // Average (35kg muscle, 12kg fat): 0.85 + 0 + 0 = 0.85\n // Heavy (48kg muscle, 20kg fat): 0.85 + 0.056 + 0.133 ≈ 1.039\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n // Cap at 1.20x maximum to prevent \"michelin man\" effect\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Determine segment count based on camera distance for LOD\n *\n * @param cameraDistance - Distance from camera\n * @returns Segment count for geometry\n * @korean LOD세그먼트수\n */\nconst getLODSegmentCount = (cameraDistance: number): number => {\n if (cameraDistance < 5) {\n return 20; // High detail for close-ups\n } else if (cameraDistance < 10) {\n return 16; // Medium detail for normal distance\n } else {\n return 12; // Low detail for far distance\n }\n};\n\n/**\n * Get body surface segments for a specific bone\n *\n * Creates continuous skin geometry appropriate for each body part.\n * Implements LOD (Level of Detail) based on camera distance for performance.\n *\n * @param boneName - Name of the bone\n * @param physicalAttributes - Physical attributes for scaling\n * @param cameraDistance - Distance from camera for LOD\n * @returns Array of body surface segments\n * @korean 신체표면세그먼트가져오기\n */\nconst getBodySurfaceForBone = (\n boneName: string,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n cameraDistance: number = 10,\n): BodySurfaceSegment[] => {\n const segments: BodySurfaceSegment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n // Get appropriate segment count based on distance\n const segmentCount = getLODSegmentCount(cameraDistance);\n\n // Scaling factors for different body parts\n const torsoScale = physicalAttributes.torsoLength / 59; // Reference: 59cm torso\n const armScale = physicalAttributes.armLength / 77; // Reference: 77cm arms\n const legScale = physicalAttributes.legLength / 96; // Reference: 96cm legs\n\n switch (boneName) {\n case \"neck\": {\n // Neck cylinder - smooth connection between head and torso with LOD\n const neckRadius = 0.06 * bodyThickness;\n const neckLength = 0.11 * bodyThickness;\n // Main neck cylinder\n segments.push({\n geometry: new THREE.CylinderGeometry(\n neckRadius * 0.9, // Slightly narrower at top (under jaw)\n neckRadius * 1.2, // Wider at base (base of neck)\n neckLength,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -neckLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Neck base flare - smooth transition to shoulders/torso\n segments.push({\n geometry: new THREE.CylinderGeometry(\n neckRadius * 1.2, // Match neck bottom\n neckRadius * 1.6, // Flare out to trapezius area\n neckLength * 0.3,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, -neckLength * 0.75, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"head\": {\n // Head / skull sphere - Face3D renders features on top of this base\n // Slightly elongated sphere for realistic cranium shape\n const headRadius = 0.095 * bodyThickness; // ~19cm head width\n const headHeight = headRadius * 1.15; // Slightly taller than wide\n\n // Main cranium sphere\n segments.push({\n geometry: new THREE.SphereGeometry(\n headRadius,\n segmentCount,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, headHeight * 0.15, 0), // Slightly above bone origin\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Jaw/chin area - smaller sphere below to fill out the jaw line\n segments.push({\n geometry: new THREE.SphereGeometry(\n headRadius * 0.7,\n Math.floor(segmentCount * 0.75),\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(\n 0,\n -headHeight * 0.25,\n headRadius * 0.15,\n ),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_upper\": {\n // Upper torso / chest area - wider for shoulders\n const width =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.9;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.3;\n const depth = PECTORALS_RADIUS * 2 * bodyThickness * 0.95;\n\n segments.push({\n geometry: new THREE.BoxGeometry(\n width,\n height,\n depth,\n Math.max(2, Math.round(segmentCount * 0.2)),\n Math.max(2, Math.round(segmentCount * 0.2)),\n Math.max(2, Math.round(segmentCount * 0.2)),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_middle\": {\n // Main torso - box covering chest and abs\n const width = (physicalAttributes.shoulderWidth / 100) * bodyThickness;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.35;\n const depth = PECTORALS_RADIUS * 2 * bodyThickness; // Front to back depth\n\n // Use LOD-aware segment counts (slightly higher than other regions) to keep torso shading smooth:\n // - Torso is frequently closest to the camera and used for breathing / impact motion.\n // - Vital point overlays and skin highlights rely on smoother curvature in this region.\n // - We still respect the global LOD segmentCount so distant torsos reduce complexity consistently.\n const torsoSegmentsX = Math.max(2, Math.round(segmentCount * 0.2));\n const torsoSegmentsY = Math.max(3, Math.round(segmentCount * 0.3));\n const torsoSegmentsZ = Math.max(2, Math.round(segmentCount * 0.2));\n\n segments.push({\n geometry: new THREE.BoxGeometry(\n width,\n height,\n depth,\n torsoSegmentsX,\n torsoSegmentsY,\n torsoSegmentsZ,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"spine_lower\": {\n // Lower torso / lumbar area - tapers from chest to pelvis\n const widthTop =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.95;\n const widthBottom =\n (physicalAttributes.shoulderWidth / 100) * bodyThickness * 0.85;\n const height = (physicalAttributes.torsoLength / 100) * torsoScale * 0.3;\n\n // Use tapered cylinder for natural waist shape\n segments.push({\n geometry: new THREE.CylinderGeometry(\n widthTop * 0.5,\n widthBottom * 0.5,\n height,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"pelvis\": {\n // Pelvis/hip area - wider for hip bones, connecting to legs\n const width =\n (physicalAttributes.shoulderWidth / 100) * 0.85 * bodyThickness;\n const height = 0.15;\n const depth = CORE_RADIUS * 2 * bodyThickness;\n\n segments.push({\n geometry: new THREE.BoxGeometry(width, height, depth, 3, 2, 3),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"shoulder_L\":\n case \"shoulder_R\": {\n // Shoulder joint - full sphere for smooth, rounded shoulder with LOD\n const shoulderRadius = BICEP_RADIUS * bodyThickness * 1.4;\n\n segments.push({\n geometry: new THREE.SphereGeometry(\n shoulderRadius,\n segmentCount,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"upper_arm_L\":\n case \"upper_arm_R\": {\n // Upper arm - tapered cylinder (bicep area) with LOD\n const radiusTop = BICEP_RADIUS * bodyThickness * 1.1; // Wider at shoulder\n const radiusBottom = BICEP_RADIUS * bodyThickness * 0.9; // Narrower at elbow\n const length = (physicalAttributes.armLength / 100) * armScale * 0.45;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"forearm_L\":\n case \"forearm_R\": {\n // Forearm - tapered cylinder with LOD\n const radiusTop = FOREARM_RADIUS * bodyThickness * 1.0; // Wider at elbow\n const radiusBottom = FOREARM_RADIUS * bodyThickness * 0.8; // Less narrow - connects to wrist smoothly\n const length = (physicalAttributes.armLength / 100) * armScale * 0.4;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"thigh_L\":\n case \"thigh_R\": {\n // Thigh - tapered cylinder (quad area) with LOD\n const radiusTop = QUAD_RADIUS * bodyThickness * 1.3; // Wider at hip for smooth connection\n const radiusBottom = QUAD_RADIUS * bodyThickness * 0.95; // Narrower at knee\n const length = (physicalAttributes.legLength / 100) * legScale * 0.45;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"shin_L\":\n case \"shin_R\": {\n // Shin/calf - tapered cylinder with LOD\n const radiusTop = CALF_RADIUS * bodyThickness * 1.0; // Wider at knee\n const radiusBottom = CALF_RADIUS * bodyThickness * 0.8; // Less taper - connects to ankle\n const length = (physicalAttributes.legLength / 100) * legScale * 0.42;\n\n segments.push({\n geometry: new THREE.CylinderGeometry(\n radiusTop,\n radiusBottom,\n length,\n segmentCount, // LOD-based segment count\n ),\n localOffset: new THREE.Vector3(0, -length * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"elbow_L\":\n case \"elbow_R\": {\n // Elbow joint sphere - bridges upper arm and forearm\n const elbowRadius = BICEP_RADIUS * bodyThickness * 0.95;\n segments.push({\n geometry: new THREE.SphereGeometry(\n elbowRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"wrist_L\":\n case \"wrist_R\": {\n // Wrist joint - tapered cylinder connecting forearm to hand\n // No rotation needed - cylinder Y axis already aligns with forearm direction\n const wristRadiusTop = FOREARM_RADIUS * bodyThickness * 0.75;\n const wristRadiusBottom = FOREARM_RADIUS * bodyThickness * 0.6;\n const wristLength = 0.035 * bodyThickness;\n segments.push({\n geometry: new THREE.CylinderGeometry(\n wristRadiusTop,\n wristRadiusBottom,\n wristLength,\n segmentCount,\n ),\n localOffset: new THREE.Vector3(0, -wristLength * 0.3, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"hand_L\":\n case \"hand_R\": {\n // Wrist-to-hand bridge sphere - fills gap between wrist skin and Hand3D component\n const handBridgeRadius = FOREARM_RADIUS * bodyThickness * 0.55;\n segments.push({\n geometry: new THREE.SphereGeometry(\n handBridgeRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"knee_L\":\n case \"knee_R\": {\n // Knee joint sphere - bridges thigh and shin\n const kneeRadius = QUAD_RADIUS * bodyThickness * 0.9;\n segments.push({\n geometry: new THREE.SphereGeometry(\n kneeRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n // Front kneecap bump\n const kneecapRadius = kneeRadius * 0.5;\n segments.push({\n geometry: new THREE.SphereGeometry(\n kneecapRadius,\n Math.floor(segmentCount * 0.5),\n Math.floor(segmentCount * 0.5),\n ),\n localOffset: new THREE.Vector3(0, 0, kneeRadius * 0.6),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"hip_L\":\n case \"hip_R\": {\n // Hip joint sphere - connects pelvis to thigh smoothly\n const hipRadius = QUAD_RADIUS * bodyThickness * 1.1;\n segments.push({\n geometry: new THREE.SphereGeometry(\n hipRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, -hipRadius * 0.3, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n case \"foot_L\":\n case \"foot_R\": {\n // Ankle bridge sphere - connects shin body surface to Foot3D component\n const ankleRadius = CALF_RADIUS * bodyThickness * 0.75;\n segments.push({\n geometry: new THREE.SphereGeometry(\n ankleRadius,\n segmentCount,\n Math.floor(segmentCount * 0.75),\n ),\n localOffset: new THREE.Vector3(0, 0, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n });\n break;\n }\n\n // Shoulders already handled by shoulder_L/R cases\n // Hand detail (fingers) uses specialized Hand3D component\n // Foot detail (toes) uses specialized Foot3D component\n // Head uses Face3D component\n }\n\n return segments;\n};\n\n/**\n * BodySurface Component\n *\n * Renders realistic body surface (skin/flesh) attached to a specific bone.\n * Creates organic, human-like appearance by providing continuous body coverage.\n *\n * @example\n * ```tsx\n * <BodySurface\n * boneName=\"spine_middle\"\n * archetype={PlayerArchetype.MUSA}\n * physicalAttributes={musaPhysicalAttrs}\n * />\n * ```\n *\n * @korean 신체표면컴포넌트\n */\nexport const BodySurface: React.FC<BodySurfaceProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n cameraDistance = 10,\n}) => {\n // Default physical attributes if not provided\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 77,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n // Get body surface segments for this bone with LOD\n const segments = useMemo(\n () => getBodySurfaceForBone(boneName, attrs, cameraDistance),\n [boneName, attrs, cameraDistance],\n );\n\n // Get archetype-specific skin tone\n const skinTone = useMemo(() => getArchetypeSkinTone(archetype), [archetype]);\n\n /**\n * Create skin material with realistic properties\n *\n * Uses MeshPhysicalMaterial for enhanced realism:\n * - Skin tone color from archetype\n * - Subsurface scattering with subtle transmission for realistic skin translucency\n * - Roughness: 0.65 (slightly rough skin texture)\n * - Metalness: 0.0 (skin is not metallic)\n * - Clearcoat for natural skin sheen\n * - Sheen for skin surface properties\n * - Subtle emissive for alive appearance\n * - Double-sided: true (render both inside and outside)\n *\n * Material properties are intentionally different from Face3D/Hand3D/Foot3D to capture\n * body-specific skin characteristics:\n * - transmission: 0.08 (extremities use 0) – BodySurface is the only skin material with\n * non-zero transmission, to model subsurface scattering on larger, less directly lit\n * body areas.\n * - thickness: 0.5 (extremities use 0.1) – torso/limb skin is treated as thicker than\n * hands, feet, and face, which appear optically thinner.\n * - clearcoat: 0.15 (extremities use 0.3) – extremities are rendered slightly glossier\n * due to being more exposed to direct light, while the main body surface is softer.\n *\n * @korean 피부재료생성\n */\n const material = useMemo(() => {\n return new THREE.MeshPhysicalMaterial({\n color: skinTone,\n roughness: 0.65, // Slightly rough for realistic skin\n metalness: 0.0, // Skin is not metallic\n\n // Subsurface scattering for realistic skin translucency\n transmission: 0.08, // Small non-zero transmission for subtle skin translucency\n thickness: 0.5, // Moderate thickness for subsurface scattering\n ior: 1.4, // Index of refraction for human skin\n\n // Clearcoat for natural skin sheen (subtle)\n clearcoat: 0.15,\n clearcoatRoughness: 0.8,\n\n // Sheen for skin surface properties (consistent with Hand3D, Foot3D)\n sheen: 0.1,\n sheenRoughness: 0.8,\n\n // Subtle emissive for alive appearance (consistent with other skin components)\n emissive: new THREE.Color(skinTone),\n emissiveIntensity: 0.02,\n\n // Reflectivity for realistic appearance\n reflectivity: 0.1,\n\n side: THREE.DoubleSide, // Render both sides for complete body coverage and gap prevention\n flatShading: false, // Smooth shading for organic look\n });\n }, [skinTone]);\n\n // Cleanup material on unmount\n useEffect(() => {\n return () => {\n material.dispose();\n };\n }, [material]);\n\n // Cleanup geometries when segments change or on unmount\n useEffect(() => {\n return () => {\n segments.forEach((segment) => {\n segment.geometry.dispose();\n });\n };\n }, [segments]);\n\n if (segments.length === 0) {\n return null;\n }\n\n return (\n <>\n {segments.map((segment, index) => (\n <mesh\n key={`body-surface-${boneName}-${index}`}\n geometry={segment.geometry}\n material={material}\n position={segment.localOffset.toArray()}\n rotation={[\n segment.localRotation.x,\n segment.localRotation.y,\n segment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`body-surface-${boneName}`}\n />\n ))}\n </>\n );\n};\n\nexport default BodySurface;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BoneAttachedMuscles.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneAttachedMuscles.tsx"],"sourcesContent":["/* eslint-disable react-refresh/only-export-components */\n/**\n * Bone-attached muscle system for realistic muscle movement with skeleton\n *\n * Muscles are rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n *\n * @module components/three/BoneAttachedMuscles\n * @category 3D Components\n * @korean 뼈부착근육시스템\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport {\n ABS_LENGTH,\n ABS_RADIUS,\n BICEP_LENGTH,\n BICEP_RADIUS,\n CALF_LENGTH,\n CALF_RADIUS,\n CORE_LENGTH,\n CORE_RADIUS,\n ERECTOR_SPINAE_LENGTH,\n ERECTOR_SPINAE_RADIUS,\n FOREARM_LENGTH,\n FOREARM_RADIUS,\n GLUTE_LENGTH,\n GLUTE_RADIUS,\n HAMSTRING_LENGTH,\n HAMSTRING_RADIUS,\n HIP_FLEXOR_LENGTH,\n HIP_FLEXOR_RADIUS,\n LAT_LENGTH,\n LAT_RADIUS,\n OBLIQUES_LENGTH,\n OBLIQUES_RADIUS,\n PECTORALS_LENGTH,\n PECTORALS_RADIUS,\n QUAD_LENGTH,\n QUAD_RADIUS,\n SHOULDER_LENGTH,\n SHOULDER_RADIUS,\n TRAPEZIUS_LENGTH,\n TRAPEZIUS_RADIUS,\n TRICEP_LENGTH,\n TRICEP_RADIUS,\n} from \"../../../../constants/bodyDimensions\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { MuscleGroupName } from \"../../../../types/muscle\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../../../../types/muscle\";\n\n/**\n * Muscle attachment configuration\n *\n * Defines how a muscle is positioned relative to its parent bone.\n * Position is in local bone space, so muscles move with the bone.\n *\n * @korean 근육부착설정\n */\nexport interface MuscleAttachment {\n /** Muscle group name */\n readonly name: MuscleGroupName;\n /** Korean name for UI */\n readonly korean: string;\n /** English name for UI */\n readonly english: string;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Base scale when relaxed */\n readonly baseScale: THREE.Vector3;\n /** Max scale when fully flexed */\n readonly maxFlexScale: THREE.Vector3;\n /** Capsule geometry radius */\n readonly radius: number;\n /** Capsule geometry length */\n readonly length: number;\n}\n\n/**\n * Mapping of bone names to their attached muscles\n *\n * Each bone can have multiple muscles attached to it.\n * Positions are relative to the bone's local coordinate system.\n *\n * @korean 뼈근육매핑\n */\nexport const BONE_MUSCLE_MAP: Record<string, MuscleAttachment[]> = {\n // Shoulders - deltoid muscles\n shoulder_L: [\n {\n name: \"SHOULDER_L\",\n korean: \"왼쪽어깨\",\n english: \"Left Shoulder\",\n localOffset: new THREE.Vector3(-0.04, 0, 0),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.15, 1.15),\n radius: SHOULDER_RADIUS,\n length: SHOULDER_LENGTH,\n },\n ],\n shoulder_R: [\n {\n name: \"SHOULDER_R\",\n korean: \"오른쪽어깨\",\n english: \"Right Shoulder\",\n localOffset: new THREE.Vector3(0.04, 0, 0),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.15, 1.15),\n radius: SHOULDER_RADIUS,\n length: SHOULDER_LENGTH,\n },\n ],\n\n // Upper arms - biceps and triceps\n upper_arm_L: [\n {\n name: \"BICEP_L\",\n korean: \"왼쪽이두근\",\n english: \"Left Bicep\",\n localOffset: new THREE.Vector3(-0.06, 0, 0.03),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.1, 1.2),\n radius: BICEP_RADIUS,\n length: BICEP_LENGTH,\n },\n {\n name: \"TRICEP_L\",\n korean: \"왼쪽삼두근\",\n english: \"Left Tricep\",\n localOffset: new THREE.Vector3(-0.06, 0, -0.03),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: TRICEP_RADIUS,\n length: TRICEP_LENGTH,\n },\n ],\n upper_arm_R: [\n {\n name: \"BICEP_R\",\n korean: \"오른쪽이두근\",\n english: \"Right Bicep\",\n localOffset: new THREE.Vector3(0.06, 0, 0.03),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.1, 1.2),\n radius: BICEP_RADIUS,\n length: BICEP_LENGTH,\n },\n {\n name: \"TRICEP_R\",\n korean: \"오른쪽삼두근\",\n english: \"Right Tricep\",\n localOffset: new THREE.Vector3(0.06, 0, -0.03),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: TRICEP_RADIUS,\n length: TRICEP_LENGTH,\n },\n ],\n\n // Forearms\n forearm_L: [\n {\n name: \"FOREARM_L\",\n korean: \"왼쪽전완근\",\n english: \"Left Forearm\",\n localOffset: new THREE.Vector3(-0.05, 0, 0),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.05, 1.1),\n radius: FOREARM_RADIUS,\n length: FOREARM_LENGTH,\n },\n ],\n forearm_R: [\n {\n name: \"FOREARM_R\",\n korean: \"오른쪽전완근\",\n english: \"Right Forearm\",\n localOffset: new THREE.Vector3(0.05, 0, 0),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.05, 1.1),\n radius: FOREARM_RADIUS,\n length: FOREARM_LENGTH,\n },\n ],\n\n // Spine - chest/core muscles attach to spine_middle\n spine_middle: [\n {\n name: \"PECTORALS\",\n korean: \"대흉근\",\n english: \"Pectorals\",\n localOffset: new THREE.Vector3(0, 0.08, 0.14),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.15),\n radius: PECTORALS_RADIUS,\n length: PECTORALS_LENGTH,\n },\n {\n name: \"CORE\",\n korean: \"코어\",\n english: \"Core\",\n localOffset: new THREE.Vector3(0, -0.04, 0.06),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: CORE_RADIUS,\n length: CORE_LENGTH,\n },\n {\n name: \"ABS\",\n korean: \"복근\",\n english: \"Abdominals\",\n localOffset: new THREE.Vector3(0, -0.18, 0.12),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ABS_RADIUS,\n length: ABS_LENGTH,\n },\n {\n name: \"OBLIQUES\",\n korean: \"복사근\",\n english: \"Obliques\",\n localOffset: new THREE.Vector3(0, -0.1, 0.16),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: OBLIQUES_RADIUS,\n length: OBLIQUES_LENGTH,\n },\n ],\n\n // Pelvis - central hip/waist muscles\n pelvis: [\n {\n name: \"HIP_FLEXOR_L\",\n korean: \"왼쪽고관절굴근\",\n english: \"Left Hip Flexor\",\n localOffset: new THREE.Vector3(-0.05, 0.02, 0.03),\n localRotation: new THREE.Euler(0.2, 0, 0.15),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: HIP_FLEXOR_RADIUS,\n length: HIP_FLEXOR_LENGTH,\n },\n {\n name: \"HIP_FLEXOR_R\",\n korean: \"오른쪽고관절굴근\",\n english: \"Right Hip Flexor\",\n localOffset: new THREE.Vector3(0.05, 0.02, 0.03),\n localRotation: new THREE.Euler(0.2, 0, -0.15),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: HIP_FLEXOR_RADIUS,\n length: HIP_FLEXOR_LENGTH,\n },\n {\n name: \"LOWER_ABS\",\n korean: \"하복근\",\n english: \"Lower Abdominals\",\n localOffset: new THREE.Vector3(0, 0.01, 0.05),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: 0.05, // Lower abs - slightly larger than hip flexor\n length: 0.14,\n },\n ],\n\n // Spine lower - lower back muscles for torso definition\n spine_lower: [\n {\n name: \"ERECTOR_SPINAE_L\",\n korean: \"왼쪽척추기립근\",\n english: \"Left Erector Spinae\",\n localOffset: new THREE.Vector3(-0.03, 0.03, -0.04),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ERECTOR_SPINAE_RADIUS,\n length: ERECTOR_SPINAE_LENGTH,\n },\n {\n name: \"ERECTOR_SPINAE_R\",\n korean: \"오른쪽척추기립근\",\n english: \"Right Erector Spinae\",\n localOffset: new THREE.Vector3(0.03, 0.03, -0.04),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ERECTOR_SPINAE_RADIUS,\n length: ERECTOR_SPINAE_LENGTH,\n },\n ],\n\n // Spine upper - lats and traps for V-shaped back\n spine_upper: [\n {\n name: \"LAT_L\",\n korean: \"왼쪽광배근\",\n english: \"Left Latissimus\",\n localOffset: new THREE.Vector3(-0.07, -0.01, -0.03),\n localRotation: new THREE.Euler(0, 0.2, 0.3),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.1),\n radius: LAT_RADIUS,\n length: LAT_LENGTH,\n },\n {\n name: \"LAT_R\",\n korean: \"오른쪽광배근\",\n english: \"Right Latissimus\",\n localOffset: new THREE.Vector3(0.07, -0.01, -0.03),\n localRotation: new THREE.Euler(0, -0.2, -0.3),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.1),\n radius: LAT_RADIUS,\n length: LAT_LENGTH,\n },\n {\n name: \"TRAPEZIUS\",\n korean: \"승모근\",\n english: \"Trapezius\",\n localOffset: new THREE.Vector3(0, 0.05, -0.04),\n localRotation: new THREE.Euler(-0.25, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.1),\n radius: TRAPEZIUS_RADIUS,\n length: TRAPEZIUS_LENGTH,\n },\n {\n name: \"RHOMBOID\",\n korean: \"능형근\",\n english: \"Rhomboid\",\n localOffset: new THREE.Vector3(0, 0.01, -0.05),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.1),\n radius: 0.04, // Rhomboid - slightly smaller than trapezius\n length: 0.14,\n },\n ],\n\n // Hips - glutes attach here\n hip_L: [\n {\n name: \"GLUTE_L\",\n korean: \"왼쪽둔근\",\n english: \"Left Glute\",\n localOffset: new THREE.Vector3(-0.03, -0.02, -0.05),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.15),\n radius: GLUTE_RADIUS,\n length: GLUTE_LENGTH,\n },\n ],\n hip_R: [\n {\n name: \"GLUTE_R\",\n korean: \"오른쪽둔근\",\n english: \"Right Glute\",\n localOffset: new THREE.Vector3(0.03, -0.02, -0.05),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.15),\n radius: GLUTE_RADIUS,\n length: GLUTE_LENGTH,\n },\n ],\n\n // Thighs - quads and hamstrings\n thigh_L: [\n {\n name: \"QUAD_L\",\n korean: \"왼쪽대퇴사두근\",\n english: \"Left Quadriceps\",\n localOffset: new THREE.Vector3(0, -0.08, 0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: QUAD_RADIUS,\n length: QUAD_LENGTH,\n },\n {\n name: \"HAMSTRING_L\",\n korean: \"왼쪽햄스트링\",\n english: \"Left Hamstring\",\n localOffset: new THREE.Vector3(0, -0.08, -0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.12, 1.1, 1.12),\n radius: HAMSTRING_RADIUS,\n length: HAMSTRING_LENGTH,\n },\n ],\n thigh_R: [\n {\n name: \"QUAD_R\",\n korean: \"오른쪽대퇴사두근\",\n english: \"Right Quadriceps\",\n localOffset: new THREE.Vector3(0, -0.08, 0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: QUAD_RADIUS,\n length: QUAD_LENGTH,\n },\n {\n name: \"HAMSTRING_R\",\n korean: \"오른쪽햄스트링\",\n english: \"Right Hamstring\",\n localOffset: new THREE.Vector3(0, -0.08, -0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.12, 1.1, 1.12),\n radius: HAMSTRING_RADIUS,\n length: HAMSTRING_LENGTH,\n },\n ],\n\n // Shins - calves\n shin_L: [\n {\n name: \"CALF_L\",\n korean: \"왼쪽종아리\",\n english: \"Left Calf\",\n localOffset: new THREE.Vector3(0, -0.04, -0.01),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.08, 1.1),\n radius: CALF_RADIUS,\n length: CALF_LENGTH,\n },\n ],\n shin_R: [\n {\n name: \"CALF_R\",\n korean: \"오른쪽종아리\",\n english: \"Right Calf\",\n localOffset: new THREE.Vector3(0, -0.04, -0.01),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.08, 1.1),\n radius: CALF_RADIUS,\n length: CALF_LENGTH,\n },\n ],\n};\n\n// Import centralized constants\nimport {\n MIN_MUSCLE_SCALE,\n MUSCLE_AMPLIFICATION_BASE,\n MUSCLE_AMPLIFICATION_EXPONENT,\n MUSCLE_GEOMETRY_NORMALIZATION,\n REFERENCE_MUSCLE_MASS,\n} from \"../../../../constants/bodyRenderingConstants\";\n\n/**\n * Calculate muscle scale factor based on muscle mass with proportional scaling.\n *\n * Linear scaling for realistic visual differences:\n * - 28kg (Hacker) → 0.84 scale (lean, defined)\n * - 30kg (Amsalja) → 0.89 scale (lean athlete)\n * - 32kg (Jeongbo) → 0.93 scale (fit operative)\n * - 35kg (Musa) → 1.0 scale (reference baseline)\n * - 48kg (Jojik) → 1.30 scale (massive, powerful)\n *\n * @param muscleMass - Muscle mass in kilograms (archetype range: 28-48kg)\n * @returns Scale factor for muscle geometry\n *\n * @korean 근육크기계산\n */\nexport const calculateMuscleScaleFactor = (muscleMass: number): number => {\n const massRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const deviation = massRatio - 1.0;\n\n // Exponential curve for dramatic differences\n const exponentialDeviation =\n Math.sign(deviation) *\n Math.pow(Math.abs(deviation), MUSCLE_AMPLIFICATION_EXPONENT);\n\n return Math.max(\n MIN_MUSCLE_SCALE,\n 1.0 + exponentialDeviation * MUSCLE_AMPLIFICATION_BASE,\n );\n};\n\n/**\n * Calculate fat layer opacity with expanded range for better distinction.\n *\n * Previous: 0.1-0.7 opacity (insufficient contrast)\n * New: 0.05-0.85 opacity (dramatic difference)\n *\n * @param fatMass - Fat mass in kilograms (archetype range: 10-22kg)\n * @returns Opacity value for fat layer (0.05-0.85)\n *\n * @korean 지방층투명도계산\n */\nexport const calculateFatLayerOpacity = (fatMass: number): number => {\n const minFat = 10; // Amsalja minimum\n const maxFat = 22; // Jojik maximum\n const normalizedFat = (fatMass - minFat) / (maxFat - minFat);\n\n // Expanded range: 0.05 (lean) to 0.85 (heavy)\n return Math.max(0.05, Math.min(0.85, 0.05 + normalizedFat * 0.8));\n};\n\n/**\n * Calculate fat layer thickness with non-linear scaling.\n *\n * Previous: 0.05-0.45 linear\n * New: 0.02-0.60 exponential (skinny = very thin, heavy = very thick)\n *\n * @param fatMass - Fat mass in kilograms (archetype range: 10-22kg)\n * @returns Scale increase for fat layer (0.02-0.60)\n *\n * @korean 지방층두께계산\n */\nexport const calculateFatLayerThickness = (fatMass: number): number => {\n const minFat = 10;\n const maxFat = 22;\n const normalizedFat = (fatMass - minFat) / (maxFat - minFat);\n\n // Exponential curve for fat thickness\n const exponentialFat = Math.pow(normalizedFat, 1.5);\n\n return Math.max(0.02, Math.min(0.6, 0.02 + exponentialFat * 0.58));\n};\n\n/**\n * Props for BoneAttachedMuscle component\n *\n * @korean 뼈부착근육속성\n */\nexport interface BoneAttachedMuscleProps {\n /** Muscle attachment configuration */\n readonly attachment: MuscleAttachment;\n /** Tension level (0-1) for muscle flex */\n readonly tension: number;\n /** Whether muscle is shaking (exhausted state) */\n readonly isShaking: boolean;\n /** Muscle scale factor based on archetype */\n readonly muscleScaleFactor: number;\n /** Fat layer opacity */\n readonly fatLayerOpacity: number;\n /** Fat layer thickness multiplier */\n readonly fatLayerThickness: number;\n}\n\n/**\n * Single bone-attached muscle component\n *\n * Renders a muscle mesh that follows its parent bone's transformations.\n * Includes dynamic scaling based on tension and archetype.\n *\n * @korean 뼈부착근육컴포넌트\n */\nexport const BoneAttachedMuscle: React.FC<BoneAttachedMuscleProps> = ({\n attachment,\n tension,\n isShaking,\n muscleScaleFactor,\n fatLayerOpacity,\n fatLayerThickness,\n}) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const fatMeshRef = useRef<THREE.Mesh>(null);\n\n // Round tension to reduce unnecessary re-renders\n const roundedTension = Math.round(tension * 100) / 100;\n\n // Calculate current scale based on tension\n const currentScale = useMemo(() => {\n const t = Math.max(0, Math.min(1, roundedTension));\n const lerp = (start: number, end: number, factor: number) =>\n start + (end - start) * factor;\n\n return new THREE.Vector3(\n lerp(attachment.baseScale.x, attachment.maxFlexScale.x, t) *\n muscleScaleFactor,\n lerp(attachment.baseScale.y, attachment.maxFlexScale.y, t) *\n muscleScaleFactor,\n lerp(attachment.baseScale.z, attachment.maxFlexScale.z, t) *\n muscleScaleFactor,\n );\n }, [attachment, roundedTension, muscleScaleFactor]);\n\n // Fat layer scale\n const fatScale = useMemo(() => {\n return new THREE.Vector3(\n currentScale.x * (1 + fatLayerThickness),\n currentScale.y * (1 + fatLayerThickness),\n currentScale.z * (1 + fatLayerThickness),\n );\n }, [currentScale, fatLayerThickness]);\n\n // Muscle color based on tension and exhaustion\n const muscleColor = useMemo(() => {\n if (isShaking) {\n return KOREAN_COLORS.MUSCLE_EXHAUSTED;\n } else if (roundedTension > 0.7) {\n return KOREAN_COLORS.MUSCLE_FLEXED;\n }\n return KOREAN_COLORS.MUSCLE_TONE;\n }, [roundedTension, isShaking]);\n\n // Shaking animation at 60fps\n useFrame((state) => {\n if (!meshRef.current) return;\n\n if (!isShaking) {\n meshRef.current.rotation.z = attachment.localRotation.z;\n return;\n }\n\n // Shaking frequency: 20Hz\n const shake = Math.sin(state.clock.elapsedTime * 20 * Math.PI * 2) * 0.02;\n meshRef.current.rotation.z = attachment.localRotation.z + shake;\n if (fatMeshRef.current) {\n fatMeshRef.current.rotation.z = attachment.localRotation.z + shake;\n }\n });\n\n return (\n <group\n position={\n attachment.localOffset\n .toArray()\n .map((v) => v * MUSCLE_GEOMETRY_NORMALIZATION) as [\n number,\n number,\n number,\n ]\n }\n rotation={[attachment.localRotation.x, attachment.localRotation.y, 0]}\n >\n {/* Main muscle mesh */}\n <mesh\n ref={meshRef}\n rotation={[0, 0, attachment.localRotation.z]}\n scale={currentScale.toArray()}\n castShadow\n receiveShadow\n name={`muscle-${attachment.name}`}\n >\n <capsuleGeometry\n args={[\n attachment.radius * MUSCLE_GEOMETRY_NORMALIZATION,\n attachment.length * MUSCLE_GEOMETRY_NORMALIZATION,\n 8,\n 16,\n ]}\n />\n <meshPhysicalMaterial\n color={muscleColor}\n metalness={0.02}\n roughness={0.85}\n clearcoat={0.08}\n clearcoatRoughness={0.6}\n envMapIntensity={0.3}\n sheen={0.15}\n sheenRoughness={0.8}\n sheenColor={muscleColor}\n />\n </mesh>\n\n {/* Fat layer (only visible when fat mass is significant) */}\n {fatLayerOpacity > 0.05 && (\n <mesh\n ref={fatMeshRef}\n rotation={[0, 0, attachment.localRotation.z]}\n scale={fatScale.toArray()}\n castShadow\n receiveShadow\n name={`fat-layer-${attachment.name}`}\n >\n <capsuleGeometry\n args={[\n attachment.radius * MUSCLE_GEOMETRY_NORMALIZATION,\n attachment.length * MUSCLE_GEOMETRY_NORMALIZATION,\n 8,\n 16,\n ]}\n />\n <meshStandardMaterial\n color={KOREAN_COLORS.SKIN_TONE}\n metalness={0.05}\n roughness={0.95}\n transparent={true}\n opacity={fatLayerOpacity}\n depthWrite={false}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Props for BoneMuscles component (renders all muscles for a bone)\n *\n * @korean 뼈근육들속성\n */\nexport interface BoneMusclesProps {\n /** Bone name to look up muscles for */\n readonly boneName: string;\n /** Map of muscle name to tension level */\n readonly muscleStates: Map<string, number>;\n /** Whether character is exhausted (triggers shaking) */\n readonly isExhausted: boolean;\n /** Physical attributes for scaling */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n };\n}\n\n/**\n * Renders all muscles attached to a specific bone\n *\n * Looks up muscle attachments by bone name and renders each one.\n * Muscles inherit the bone's transformations automatically.\n *\n * @korean 뼈근육들컴포넌트\n */\nexport const BoneMuscles: React.FC<BoneMusclesProps> = ({\n boneName,\n muscleStates,\n isExhausted,\n physicalAttributes,\n}) => {\n // Get muscle attachments for this bone\n const attachments = BONE_MUSCLE_MAP[boneName];\n\n // Calculate scaling factors\n const muscleScaleFactor = useMemo(() => {\n if (!physicalAttributes) return 1.0;\n return calculateMuscleScaleFactor(physicalAttributes.muscleMass);\n }, [physicalAttributes]);\n\n const fatLayerOpacity = useMemo(() => {\n if (!physicalAttributes) return 0.0;\n return calculateFatLayerOpacity(physicalAttributes.fatMass);\n }, [physicalAttributes]);\n\n const fatLayerThickness = useMemo(() => {\n if (!physicalAttributes) return 0.0;\n return calculateFatLayerThickness(physicalAttributes.fatMass);\n }, [physicalAttributes]);\n\n // No muscles for this bone\n if (!attachments || attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment) => {\n const tension = muscleStates.get(attachment.name) ?? 0;\n const isShaking =\n isExhausted &&\n tension > DEFAULT_MUSCLE_CONFIG.shakingTensionThreshold;\n\n return (\n <BoneAttachedMuscle\n key={attachment.name}\n attachment={attachment}\n tension={tension}\n isShaking={isShaking}\n muscleScaleFactor={muscleScaleFactor}\n fatLayerOpacity={fatLayerOpacity}\n fatLayerThickness={fatLayerThickness}\n />\n );\n })}\n </>\n );\n};\n\nexport default BoneMuscles;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BoneAttachedMuscles.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneAttachedMuscles.tsx"],"sourcesContent":["/* eslint-disable react-refresh/only-export-components */\n/**\n * Bone-attached muscle system for realistic muscle movement with skeleton\n *\n * Muscles are rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n *\n * @module components/three/BoneAttachedMuscles\n * @category 3D Components\n * @korean 뼈부착근육시스템\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport {\n ABS_LENGTH,\n ABS_RADIUS,\n BICEP_LENGTH,\n BICEP_RADIUS,\n CALF_LENGTH,\n CALF_RADIUS,\n CORE_LENGTH,\n CORE_RADIUS,\n ERECTOR_SPINAE_LENGTH,\n ERECTOR_SPINAE_RADIUS,\n FOREARM_LENGTH,\n FOREARM_RADIUS,\n GLUTE_LENGTH,\n GLUTE_RADIUS,\n HAMSTRING_LENGTH,\n HAMSTRING_RADIUS,\n HIP_FLEXOR_LENGTH,\n HIP_FLEXOR_RADIUS,\n LAT_LENGTH,\n LAT_RADIUS,\n OBLIQUES_LENGTH,\n OBLIQUES_RADIUS,\n PECTORALS_LENGTH,\n PECTORALS_RADIUS,\n QUAD_LENGTH,\n QUAD_RADIUS,\n SHOULDER_LENGTH,\n SHOULDER_RADIUS,\n TRAPEZIUS_LENGTH,\n TRAPEZIUS_RADIUS,\n TRICEP_LENGTH,\n TRICEP_RADIUS,\n} from \"../../../../constants/bodyDimensions\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { MuscleGroupName } from \"../../../../types/muscle\";\nimport { DEFAULT_MUSCLE_CONFIG } from \"../../../../types/muscle\";\n\n/**\n * Muscle attachment configuration\n *\n * Defines how a muscle is positioned relative to its parent bone.\n * Position is in local bone space, so muscles move with the bone.\n *\n * @korean 근육부착설정\n */\nexport interface MuscleAttachment {\n /** Muscle group name */\n readonly name: MuscleGroupName;\n /** Korean name for UI */\n readonly korean: string;\n /** English name for UI */\n readonly english: string;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Base scale when relaxed */\n readonly baseScale: THREE.Vector3;\n /** Max scale when fully flexed */\n readonly maxFlexScale: THREE.Vector3;\n /** Capsule geometry radius */\n readonly radius: number;\n /** Capsule geometry length */\n readonly length: number;\n}\n\n/**\n * Mapping of bone names to their attached muscles\n *\n * Each bone can have multiple muscles attached to it.\n * Positions are relative to the bone's local coordinate system.\n *\n * @korean 뼈근육매핑\n */\nexport const BONE_MUSCLE_MAP: Record<string, MuscleAttachment[]> = {\n // Shoulders - deltoid muscles\n shoulder_L: [\n {\n name: \"SHOULDER_L\",\n korean: \"왼쪽어깨\",\n english: \"Left Shoulder\",\n localOffset: new THREE.Vector3(-0.04, 0, 0),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.15, 1.15),\n radius: SHOULDER_RADIUS,\n length: SHOULDER_LENGTH,\n },\n ],\n shoulder_R: [\n {\n name: \"SHOULDER_R\",\n korean: \"오른쪽어깨\",\n english: \"Right Shoulder\",\n localOffset: new THREE.Vector3(0.04, 0, 0),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.15, 1.15),\n radius: SHOULDER_RADIUS,\n length: SHOULDER_LENGTH,\n },\n ],\n\n // Upper arms - biceps and triceps\n upper_arm_L: [\n {\n name: \"BICEP_L\",\n korean: \"왼쪽이두근\",\n english: \"Left Bicep\",\n localOffset: new THREE.Vector3(-0.06, 0, 0.03),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.1, 1.2),\n radius: BICEP_RADIUS,\n length: BICEP_LENGTH,\n },\n {\n name: \"TRICEP_L\",\n korean: \"왼쪽삼두근\",\n english: \"Left Tricep\",\n localOffset: new THREE.Vector3(-0.06, 0, -0.03),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: TRICEP_RADIUS,\n length: TRICEP_LENGTH,\n },\n ],\n upper_arm_R: [\n {\n name: \"BICEP_R\",\n korean: \"오른쪽이두근\",\n english: \"Right Bicep\",\n localOffset: new THREE.Vector3(0.06, 0, 0.03),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.2, 1.1, 1.2),\n radius: BICEP_RADIUS,\n length: BICEP_LENGTH,\n },\n {\n name: \"TRICEP_R\",\n korean: \"오른쪽삼두근\",\n english: \"Right Tricep\",\n localOffset: new THREE.Vector3(0.06, 0, -0.03),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: TRICEP_RADIUS,\n length: TRICEP_LENGTH,\n },\n ],\n\n // Forearms\n forearm_L: [\n {\n name: \"FOREARM_L\",\n korean: \"왼쪽전완근\",\n english: \"Left Forearm\",\n localOffset: new THREE.Vector3(-0.05, 0, 0),\n localRotation: new THREE.Euler(0, 0, Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.05, 1.1),\n radius: FOREARM_RADIUS,\n length: FOREARM_LENGTH,\n },\n ],\n forearm_R: [\n {\n name: \"FOREARM_R\",\n korean: \"오른쪽전완근\",\n english: \"Right Forearm\",\n localOffset: new THREE.Vector3(0.05, 0, 0),\n localRotation: new THREE.Euler(0, 0, -Math.PI / 2),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.05, 1.1),\n radius: FOREARM_RADIUS,\n length: FOREARM_LENGTH,\n },\n ],\n\n // Spine - chest/core muscles attach to spine_middle\n spine_middle: [\n {\n name: \"PECTORALS\",\n korean: \"대흉근\",\n english: \"Pectorals\",\n localOffset: new THREE.Vector3(0, 0.08, 0.14),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.15),\n radius: PECTORALS_RADIUS,\n length: PECTORALS_LENGTH,\n },\n {\n name: \"CORE\",\n korean: \"코어\",\n english: \"Core\",\n localOffset: new THREE.Vector3(0, -0.04, 0.06),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: CORE_RADIUS,\n length: CORE_LENGTH,\n },\n {\n name: \"ABS\",\n korean: \"복근\",\n english: \"Abdominals\",\n localOffset: new THREE.Vector3(0, -0.18, 0.12),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ABS_RADIUS,\n length: ABS_LENGTH,\n },\n {\n name: \"OBLIQUES\",\n korean: \"복사근\",\n english: \"Obliques\",\n localOffset: new THREE.Vector3(0, -0.1, 0.16),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: OBLIQUES_RADIUS,\n length: OBLIQUES_LENGTH,\n },\n ],\n\n // Pelvis - central hip/waist muscles\n pelvis: [\n {\n name: \"HIP_FLEXOR_L\",\n korean: \"왼쪽고관절굴근\",\n english: \"Left Hip Flexor\",\n localOffset: new THREE.Vector3(-0.05, 0.02, 0.03),\n localRotation: new THREE.Euler(0.2, 0, 0.15),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: HIP_FLEXOR_RADIUS,\n length: HIP_FLEXOR_LENGTH,\n },\n {\n name: \"HIP_FLEXOR_R\",\n korean: \"오른쪽고관절굴근\",\n english: \"Right Hip Flexor\",\n localOffset: new THREE.Vector3(0.05, 0.02, 0.03),\n localRotation: new THREE.Euler(0.2, 0, -0.15),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: HIP_FLEXOR_RADIUS,\n length: HIP_FLEXOR_LENGTH,\n },\n {\n name: \"LOWER_ABS\",\n korean: \"하복근\",\n english: \"Lower Abdominals\",\n localOffset: new THREE.Vector3(0, 0.01, 0.05),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: 0.05, // Lower abs - slightly larger than hip flexor\n length: 0.14,\n },\n ],\n\n // Spine lower - lower back muscles for torso definition\n spine_lower: [\n {\n name: \"ERECTOR_SPINAE_L\",\n korean: \"왼쪽척추기립근\",\n english: \"Left Erector Spinae\",\n localOffset: new THREE.Vector3(-0.03, 0.03, -0.04),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ERECTOR_SPINAE_RADIUS,\n length: ERECTOR_SPINAE_LENGTH,\n },\n {\n name: \"ERECTOR_SPINAE_R\",\n korean: \"오른쪽척추기립근\",\n english: \"Right Erector Spinae\",\n localOffset: new THREE.Vector3(0.03, 0.03, -0.04),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.1, 1.1),\n radius: ERECTOR_SPINAE_RADIUS,\n length: ERECTOR_SPINAE_LENGTH,\n },\n ],\n\n // Spine upper - lats and traps for V-shaped back\n spine_upper: [\n {\n name: \"LAT_L\",\n korean: \"왼쪽광배근\",\n english: \"Left Latissimus\",\n localOffset: new THREE.Vector3(-0.07, -0.01, -0.03),\n localRotation: new THREE.Euler(0, 0.2, 0.3),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.1),\n radius: LAT_RADIUS,\n length: LAT_LENGTH,\n },\n {\n name: \"LAT_R\",\n korean: \"오른쪽광배근\",\n english: \"Right Latissimus\",\n localOffset: new THREE.Vector3(0.07, -0.01, -0.03),\n localRotation: new THREE.Euler(0, -0.2, -0.3),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.2, 1.1),\n radius: LAT_RADIUS,\n length: LAT_LENGTH,\n },\n {\n name: \"TRAPEZIUS\",\n korean: \"승모근\",\n english: \"Trapezius\",\n localOffset: new THREE.Vector3(0, 0.05, -0.04),\n localRotation: new THREE.Euler(-0.25, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.1),\n radius: TRAPEZIUS_RADIUS,\n length: TRAPEZIUS_LENGTH,\n },\n {\n name: \"RHOMBOID\",\n korean: \"능형근\",\n english: \"Rhomboid\",\n localOffset: new THREE.Vector3(0, 0.01, -0.05),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.1),\n radius: 0.04, // Rhomboid - slightly smaller than trapezius\n length: 0.14,\n },\n ],\n\n // Hips - glutes attach here\n hip_L: [\n {\n name: \"GLUTE_L\",\n korean: \"왼쪽둔근\",\n english: \"Left Glute\",\n localOffset: new THREE.Vector3(-0.03, -0.02, -0.05),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.15),\n radius: GLUTE_RADIUS,\n length: GLUTE_LENGTH,\n },\n ],\n hip_R: [\n {\n name: \"GLUTE_R\",\n korean: \"오른쪽둔근\",\n english: \"Right Glute\",\n localOffset: new THREE.Vector3(0.03, -0.02, -0.05),\n localRotation: new THREE.Euler(Math.PI / 2, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.15, 1.15),\n radius: GLUTE_RADIUS,\n length: GLUTE_LENGTH,\n },\n ],\n\n // Thighs - quads and hamstrings\n thigh_L: [\n {\n name: \"QUAD_L\",\n korean: \"왼쪽대퇴사두근\",\n english: \"Left Quadriceps\",\n localOffset: new THREE.Vector3(0, -0.08, 0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: QUAD_RADIUS,\n length: QUAD_LENGTH,\n },\n {\n name: \"HAMSTRING_L\",\n korean: \"왼쪽햄스트링\",\n english: \"Left Hamstring\",\n localOffset: new THREE.Vector3(0, -0.08, -0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.12, 1.1, 1.12),\n radius: HAMSTRING_RADIUS,\n length: HAMSTRING_LENGTH,\n },\n ],\n thigh_R: [\n {\n name: \"QUAD_R\",\n korean: \"오른쪽대퇴사두근\",\n english: \"Right Quadriceps\",\n localOffset: new THREE.Vector3(0, -0.08, 0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.15, 1.1, 1.15),\n radius: QUAD_RADIUS,\n length: QUAD_LENGTH,\n },\n {\n name: \"HAMSTRING_R\",\n korean: \"오른쪽햄스트링\",\n english: \"Right Hamstring\",\n localOffset: new THREE.Vector3(0, -0.08, -0.03),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.12, 1.1, 1.12),\n radius: HAMSTRING_RADIUS,\n length: HAMSTRING_LENGTH,\n },\n ],\n\n // Shins - calves\n shin_L: [\n {\n name: \"CALF_L\",\n korean: \"왼쪽종아리\",\n english: \"Left Calf\",\n localOffset: new THREE.Vector3(0, -0.04, -0.01),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.08, 1.1),\n radius: CALF_RADIUS,\n length: CALF_LENGTH,\n },\n ],\n shin_R: [\n {\n name: \"CALF_R\",\n korean: \"오른쪽종아리\",\n english: \"Right Calf\",\n localOffset: new THREE.Vector3(0, -0.04, -0.01),\n localRotation: new THREE.Euler(0, 0, 0),\n baseScale: new THREE.Vector3(1.0, 1.0, 1.0),\n maxFlexScale: new THREE.Vector3(1.1, 1.08, 1.1),\n radius: CALF_RADIUS,\n length: CALF_LENGTH,\n },\n ],\n};\n\n// Import centralized constants\nimport {\n MIN_MUSCLE_SCALE,\n MUSCLE_AMPLIFICATION_BASE,\n MUSCLE_AMPLIFICATION_EXPONENT,\n MUSCLE_GEOMETRY_NORMALIZATION,\n REFERENCE_MUSCLE_MASS,\n} from \"../../../../constants/bodyRenderingConstants\";\n\n/**\n * Calculate muscle scale factor based on muscle mass with proportional scaling.\n *\n * Linear scaling for realistic visual differences:\n * - 28kg (Hacker) → 0.84 scale (lean, defined)\n * - 30kg (Amsalja) → 0.89 scale (lean athlete)\n * - 32kg (Jeongbo) → 0.93 scale (fit operative)\n * - 35kg (Musa) → 1.0 scale (reference baseline)\n * - 48kg (Jojik) → 1.30 scale (massive, powerful)\n *\n * @param muscleMass - Muscle mass in kilograms (archetype range: 28-48kg)\n * @returns Scale factor for muscle geometry\n *\n * @korean 근육크기계산\n */\nexport const calculateMuscleScaleFactor = (muscleMass: number): number => {\n const massRatio = muscleMass / REFERENCE_MUSCLE_MASS;\n const deviation = massRatio - 1.0;\n\n // Exponential curve for dramatic differences\n const exponentialDeviation =\n Math.sign(deviation) *\n Math.pow(Math.abs(deviation), MUSCLE_AMPLIFICATION_EXPONENT);\n\n return Math.max(\n MIN_MUSCLE_SCALE,\n 1.0 + exponentialDeviation * MUSCLE_AMPLIFICATION_BASE,\n );\n};\n\n/**\n * Calculate fat layer opacity with expanded range for better distinction.\n *\n * Previous: 0.1-0.7 opacity (insufficient contrast)\n * New: 0.05-0.85 opacity (dramatic difference)\n *\n * @param fatMass - Fat mass in kilograms (archetype range: 10-22kg)\n * @returns Opacity value for fat layer (0.05-0.85)\n *\n * @korean 지방층투명도계산\n */\nexport const calculateFatLayerOpacity = (fatMass: number): number => {\n const minFat = 10; // Amsalja minimum\n const maxFat = 22; // Jojik maximum\n const normalizedFat = (fatMass - minFat) / (maxFat - minFat);\n\n // Expanded range: 0.05 (lean) to 0.85 (heavy)\n return Math.max(0.05, Math.min(0.85, 0.05 + normalizedFat * 0.8));\n};\n\n/**\n * Calculate fat layer thickness with non-linear scaling.\n *\n * Previous: 0.05-0.45 linear\n * New: 0.02-0.60 exponential (skinny = very thin, heavy = very thick)\n *\n * @param fatMass - Fat mass in kilograms (archetype range: 10-22kg)\n * @returns Scale increase for fat layer (0.02-0.60)\n *\n * @korean 지방층두께계산\n */\nexport const calculateFatLayerThickness = (fatMass: number): number => {\n const minFat = 10;\n const maxFat = 22;\n const normalizedFat = (fatMass - minFat) / (maxFat - minFat);\n\n // Exponential curve for fat thickness\n const exponentialFat = Math.pow(normalizedFat, 1.5);\n\n return Math.max(0.02, Math.min(0.6, 0.02 + exponentialFat * 0.58));\n};\n\n/**\n * Props for BoneAttachedMuscle component\n *\n * @korean 뼈부착근육속성\n */\nexport interface BoneAttachedMuscleProps {\n /** Muscle attachment configuration */\n readonly attachment: MuscleAttachment;\n /** Tension level (0-1) for muscle flex */\n readonly tension: number;\n /** Whether muscle is shaking (exhausted state) */\n readonly isShaking: boolean;\n /** Muscle scale factor based on archetype */\n readonly muscleScaleFactor: number;\n /** Fat layer opacity */\n readonly fatLayerOpacity: number;\n /** Fat layer thickness multiplier */\n readonly fatLayerThickness: number;\n}\n\n/**\n * Single bone-attached muscle component\n *\n * Renders a muscle mesh that follows its parent bone's transformations.\n * Includes dynamic scaling based on tension and archetype.\n *\n * @korean 뼈부착근육컴포넌트\n */\nexport const BoneAttachedMuscle: React.FC<BoneAttachedMuscleProps> = ({\n attachment,\n tension,\n isShaking,\n muscleScaleFactor,\n fatLayerOpacity,\n fatLayerThickness,\n}) => {\n const meshRef = useRef<THREE.Mesh>(null);\n const fatMeshRef = useRef<THREE.Mesh>(null);\n\n // Round tension to reduce unnecessary re-renders\n const roundedTension = Math.round(tension * 100) / 100;\n\n // Calculate current scale based on tension\n const currentScale = useMemo(() => {\n const t = Math.max(0, Math.min(1, roundedTension));\n const lerp = (start: number, end: number, factor: number) =>\n start + (end - start) * factor;\n\n return new THREE.Vector3(\n lerp(attachment.baseScale.x, attachment.maxFlexScale.x, t) *\n muscleScaleFactor,\n lerp(attachment.baseScale.y, attachment.maxFlexScale.y, t) *\n muscleScaleFactor,\n lerp(attachment.baseScale.z, attachment.maxFlexScale.z, t) *\n muscleScaleFactor,\n );\n }, [attachment, roundedTension, muscleScaleFactor]);\n\n // Fat layer scale\n const fatScale = useMemo(() => {\n return new THREE.Vector3(\n currentScale.x * (1 + fatLayerThickness),\n currentScale.y * (1 + fatLayerThickness),\n currentScale.z * (1 + fatLayerThickness),\n );\n }, [currentScale, fatLayerThickness]);\n\n // Muscle color based on tension and exhaustion\n const muscleColor = useMemo(() => {\n if (isShaking) {\n return KOREAN_COLORS.MUSCLE_EXHAUSTED;\n } else if (roundedTension > 0.7) {\n return KOREAN_COLORS.MUSCLE_FLEXED;\n }\n return KOREAN_COLORS.MUSCLE_TONE;\n }, [roundedTension, isShaking]);\n\n // Shaking animation at 60fps\n useFrame((state) => {\n if (!meshRef.current) return;\n\n if (!isShaking) {\n meshRef.current.rotation.z = attachment.localRotation.z;\n return;\n }\n\n // Shaking frequency: 20Hz\n const shake = Math.sin(state.clock.elapsedTime * 20 * Math.PI * 2) * 0.02;\n meshRef.current.rotation.z = attachment.localRotation.z + shake;\n if (fatMeshRef.current) {\n fatMeshRef.current.rotation.z = attachment.localRotation.z + shake;\n }\n });\n\n return (\n <group\n position={\n attachment.localOffset\n .toArray()\n .map((v) => v * MUSCLE_GEOMETRY_NORMALIZATION) as [\n number,\n number,\n number,\n ]\n }\n rotation={[attachment.localRotation.x, attachment.localRotation.y, 0]}\n >\n {/* Main muscle mesh */}\n <mesh\n ref={meshRef}\n rotation={[0, 0, attachment.localRotation.z]}\n scale={currentScale.toArray()}\n castShadow\n receiveShadow\n name={`muscle-${attachment.name}`}\n >\n <capsuleGeometry\n args={[\n attachment.radius * MUSCLE_GEOMETRY_NORMALIZATION,\n attachment.length * MUSCLE_GEOMETRY_NORMALIZATION,\n 8,\n 16,\n ]}\n />\n <meshPhysicalMaterial\n color={muscleColor}\n metalness={0.02}\n roughness={0.85}\n clearcoat={0.08}\n clearcoatRoughness={0.6}\n envMapIntensity={0.3}\n sheen={0.15}\n sheenRoughness={0.8}\n sheenColor={muscleColor}\n />\n </mesh>\n\n {/* Fat layer (only visible when fat mass is significant) */}\n {fatLayerOpacity > 0.05 && (\n <mesh\n ref={fatMeshRef}\n rotation={[0, 0, attachment.localRotation.z]}\n scale={fatScale.toArray()}\n castShadow\n receiveShadow\n name={`fat-layer-${attachment.name}`}\n >\n <capsuleGeometry\n args={[\n attachment.radius * MUSCLE_GEOMETRY_NORMALIZATION,\n attachment.length * MUSCLE_GEOMETRY_NORMALIZATION,\n 8,\n 16,\n ]}\n />\n <meshStandardMaterial\n color={KOREAN_COLORS.SKIN_TONE}\n metalness={0.05}\n roughness={0.95}\n transparent={true}\n opacity={fatLayerOpacity}\n depthWrite={false}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Props for BoneMuscles component (renders all muscles for a bone)\n *\n * @korean 뼈근육들속성\n */\nexport interface BoneMusclesProps {\n /** Bone name to look up muscles for */\n readonly boneName: string;\n /** Map of muscle name to tension level */\n readonly muscleStates: Map<string, number>;\n /** Whether character is exhausted (triggers shaking) */\n readonly isExhausted: boolean;\n /** Physical attributes for scaling */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n };\n}\n\n/**\n * Renders all muscles attached to a specific bone\n *\n * Looks up muscle attachments by bone name and renders each one.\n * Muscles inherit the bone's transformations automatically.\n *\n * @korean 뼈근육들컴포넌트\n */\nexport const BoneMuscles: React.FC<BoneMusclesProps> = ({\n boneName,\n muscleStates,\n isExhausted,\n physicalAttributes,\n}) => {\n // Get muscle attachments for this bone\n const attachments = BONE_MUSCLE_MAP[boneName];\n\n // Calculate scaling factors\n const muscleScaleFactor = useMemo(() => {\n if (!physicalAttributes) return 1.0;\n return calculateMuscleScaleFactor(physicalAttributes.muscleMass);\n }, [physicalAttributes]);\n\n const fatLayerOpacity = useMemo(() => {\n if (!physicalAttributes) return 0.0;\n return calculateFatLayerOpacity(physicalAttributes.fatMass);\n }, [physicalAttributes]);\n\n const fatLayerThickness = useMemo(() => {\n if (!physicalAttributes) return 0.0;\n return calculateFatLayerThickness(physicalAttributes.fatMass);\n }, [physicalAttributes]);\n\n // No muscles for this bone\n if (!attachments || attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment) => {\n const tension = muscleStates.get(attachment.name) ?? 0;\n const isShaking =\n isExhausted &&\n tension > DEFAULT_MUSCLE_CONFIG.shakingTensionThreshold;\n\n return (\n <BoneAttachedMuscle\n key={attachment.name}\n attachment={attachment}\n tension={tension}\n isShaking={isShaking}\n muscleScaleFactor={muscleScaleFactor}\n fatLayerOpacity={fatLayerOpacity}\n fatLayerThickness={fatLayerThickness}\n />\n );\n })}\n </>\n );\n};\n\nexport default BoneMuscles;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BoneClothing.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneClothing.tsx"],"sourcesContent":["/**\n * Bone-attached clothing system for realistic clothing movement with skeleton\n *\n * **Enhanced Features (v0.6.23+)**:\n * - **Subsurface Scattering**: Realistic fabric translucency with light transmission\n * - **Physical Materials**: Advanced PBR materials with clearcoat and reflectivity\n * - **Body Thickness Scaling**: Accurate clothing sizing based on muscle/fat mass\n * - **Archetype Styling**: Unique clothing sets for all 5 player archetypes\n *\n * Clothing is rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n * This mirrors the BoneMuscles approach for consistent rendering.\n *\n * **Visual Quality**:\n * - Transmission: 0.05 (minimal light passing through fabric)\n * - Thickness: 0.2 (thin fabric simulation)\n * - IOR: 1.4 (realistic fabric index of refraction)\n * - Clearcoat: 0.3 (surface depth and sheen)\n * - Double-sided rendering for cloth folding\n *\n * **Performance**:\n * - Geometry disposal on unmount (prevents memory leaks)\n * - Material cleanup system\n * - Optimized attachment calculations with useMemo\n *\n * @module components/three/BoneClothing\n * @category 3D Components\n * @korean 뼈부착의류시스템\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BICEP_RADIUS,\n CALF_RADIUS,\n CLOTHING_THICKNESS_FITTED,\n CORE_RADIUS,\n FOREARM_RADIUS,\n PECTORALS_RADIUS,\n QUAD_RADIUS,\n calculateClothingRadius,\n} from \"../../../../constants/bodyDimensions\";\nimport { getArchetypeClothing } from \"../../../../data/archetypeClothing\";\nimport type {\n ClothingItem,\n ClothingMaterial,\n} from \"../../../../types/clothing\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport {\n FABRIC_PRESETS,\n generateFabricTextureSet,\n type FabricTextureSet,\n} from \"../../../../utils/fabricTextures\";\n\n/**\n * Clothing attachment configuration for a bone\n *\n * Defines how clothing is positioned relative to its parent bone.\n * Position is in local bone space, so clothing moves with the bone.\n *\n * @korean 의류부착설정\n */\nexport interface ClothingAttachment {\n /** Geometry for this clothing piece */\n readonly geometry: THREE.BufferGeometry;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Color of the clothing */\n readonly color: number;\n /** Emissive color for glow effects */\n readonly emissiveColor?: number;\n /** Emissive intensity */\n readonly emissiveIntensity?: number;\n /** Metalness for material */\n readonly metalness?: number;\n /** Roughness for material */\n readonly roughness?: number;\n /** Clothing item ID */\n readonly itemId: string;\n}\n\n/**\n * Props for BoneClothing component\n */\nexport interface BoneClothingProps {\n /** Name of the bone this clothing attaches to */\n readonly boneName: string;\n /** Player archetype for clothing style */\n readonly archetype: PlayerArchetype;\n /** Physical attributes for sizing */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n}\n\n/**\n * Calculate body thickness multiplier based on muscle mass and fat mass\n *\n * Uses linear scaling with reasonable limits to prevent \"michelin man\" effect.\n * This matches the BodySurface calculation for consistency.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n // Linear scaling with limits (not square root which causes excessive inflation)\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n // Base 0.85, muscle adds up to +0.15, fat adds up to +0.20\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n // Cap at 1.20x maximum to prevent \"michelin man\" effect\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Get fabric preset based on clothing material type\n */\nconst getFabricPreset = (\n material: ClothingMaterial,\n): keyof typeof FABRIC_PRESETS => {\n switch (material) {\n case \"fabric\":\n return \"dobok\";\n case \"tactical\":\n case \"synthetic\":\n return \"tactical\";\n case \"leather\":\n case \"armored\":\n return \"leather\";\n case \"cybernetic\":\n return \"silk\"; // Shiny synthetic look\n default:\n return \"dobok\";\n }\n};\n\n/**\n * Convert number color to hex string\n */\nconst numberToHex = (color: number): string => {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n};\n\n/**\n * Get clothing attachments for a specific bone\n */\nconst getClothingForBone = (\n boneName: string,\n archetype: PlayerArchetype,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const clothingSet = getArchetypeClothing(archetype);\n const attachments: ClothingAttachment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n // Scaling factors\n const torsoScale = physicalAttributes.torsoLength / 59;\n const legScale = physicalAttributes.legLength / 96;\n\n for (const item of clothingSet.items) {\n const attachmentsForItem = getAttachmentsForItem(\n item,\n boneName,\n bodyThickness,\n torsoScale,\n legScale,\n physicalAttributes,\n );\n attachments.push(...attachmentsForItem);\n }\n\n return attachments;\n};\n\n/**\n * Generate clothing attachments for a specific item on a bone\n */\nconst getAttachmentsForItem = (\n item: ClothingItem,\n boneName: string,\n bodyThickness: number,\n torsoScale: number,\n legScale: number,\n physicalAttributes: {\n shoulderWidth: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const fitScaleMap: Record<string, number> = {\n tight: 1.08,\n fitted: 1.15,\n loose: 1.25,\n oversized: 1.4,\n };\n const fitScale = fitScaleMap[item.fit] ?? 1.15;\n const attachments: ClothingAttachment[] = [];\n\n switch (item.type) {\n case \"torso\":\n // Main torso on spine_middle - wraps fully around body\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * torsoScale * 1.2; // Using base torsoLength\n // Clothing wraps around the torso using CylinderGeometry (full 360°)\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n // Torso radius: average of shoulder width and pectorals depth\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n PECTORALS_RADIUS * bodyThickness) *\n 0.5;\n const clothingRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied above\n clothingThickness,\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Slightly narrower at top (chest taper)\n clothingRadius * 1.05, // Slightly wider at bottom (waist)\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n\n // Sleeves\n if (boneName === \"upper_arm_L\" || boneName === \"upper_arm_R\") {\n // Uses centralized BICEP_RADIUS from bodyDimensions\n // Clothing wraps around muscle, so offset outward\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n BICEP_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const upperArmLength = (physicalAttributes.armLength / 100) * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.05, // Slightly larger at top\n clothingRadius * 0.95, // Tapers slightly\n upperArmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -upperArmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-upper-${boneName}`,\n });\n }\n\n if (boneName === \"forearm_L\" || boneName === \"forearm_R\") {\n // Uses centralized FOREARM_RADIUS from bodyDimensions\n // Clothing must be OUTSIDE, so use larger radius\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 0.8;\n const clothingRadius = calculateClothingRadius(\n FOREARM_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const forearmLength = (physicalAttributes.armLength / 100) * 0.4;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.98, // Slightly smaller at top\n clothingRadius * 0.85, // Tapers toward wrist\n forearmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -forearmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-forearm-${boneName}`,\n });\n }\n break;\n\n case \"pants\":\n // Thigh segments\n if (boneName === \"thigh_L\" || boneName === \"thigh_R\") {\n // Uses centralized QUAD_RADIUS from bodyDimensions\n // Clothing wraps around muscle, so offset outward\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 1.3;\n const clothingRadius = calculateClothingRadius(\n QUAD_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const thighLength =\n (physicalAttributes.legLength / 100) * legScale * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.08, // Larger at hip\n clothingRadius * 0.92, // Tapers toward knee\n thighLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -thighLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-thigh-${boneName}`,\n });\n }\n\n // Shin segments\n if (boneName === \"shin_L\" || boneName === \"shin_R\") {\n // Uses centralized CALF_RADIUS from bodyDimensions\n // Clothing must be OUTSIDE, so use larger radius\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n CALF_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const shinLength =\n (physicalAttributes.legLength / 100) * legScale * 0.42;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Larger at knee\n clothingRadius * 0.75, // Tapers toward ankle\n shinLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -shinLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-shin-${boneName}`,\n });\n }\n break;\n\n case \"belt\":\n if (boneName === \"pelvis\") {\n // Belt wraps fully around waist using CylinderGeometry\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const waistRadius = CORE_RADIUS * bodyThickness;\n const beltRadius = calculateClothingRadius(\n waistRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.005, // Extra so belt sits outside clothing\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n beltRadius,\n beltRadius,\n 0.06, // Belt height\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n\n case \"vest\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * 0.75 * torsoScale;\n // Vest wraps fully around torso using CylinderGeometry\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n // Vest sits slightly outside the torso clothing layer\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n (PECTORALS_RADIUS + 0.01) * bodyThickness) *\n 0.5;\n const vestRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.008, // Extra gap for layering over shirt\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n vestRadius * 0.97, // Slightly narrower at top\n vestRadius * 1.03, // Slightly wider at bottom\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n }\n\n return attachments;\n};\n\n/**\n * BoneClothing Component\n *\n * Renders clothing attached to a specific bone.\n * Clothing inherits bone transformations automatically through the scene graph.\n *\n * @korean 뼈부착의류\n */\nexport const BoneClothing: React.FC<BoneClothingProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n}) => {\n // Default physical attributes if not provided - memoized to prevent new object on every render\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 62,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n // Get clothing attachments for this bone\n const attachments = useMemo(\n () => getClothingForBone(boneName, archetype, attrs),\n [boneName, archetype, attrs],\n );\n\n // Get clothing set for fabric texture generation\n const clothingSet = useMemo(\n () => getArchetypeClothing(archetype),\n [archetype],\n );\n\n /**\n * Generate fabric textures for realistic dobok (도복) rendering\n *\n * Creates procedural weave patterns, normal maps, and roughness maps\n * for enhanced cloth realism without external image assets.\n *\n * @korean 직물텍스처생성\n */\n const fabricTextures = useMemo(() => {\n // Generate textures for each unique clothing item\n const textureMap = new Map<string, FabricTextureSet>();\n\n for (const item of clothingSet.items) {\n if (!textureMap.has(item.material)) {\n const preset = getFabricPreset(item.material);\n const colorHex = numberToHex(item.colorPrimary);\n textureMap.set(\n item.material,\n generateFabricTextureSet(colorHex, preset),\n );\n }\n }\n\n return textureMap;\n }, [clothingSet]);\n\n // Cleanup fabric textures on unmount\n useEffect(() => {\n return () => {\n fabricTextures.forEach((textureSet) => textureSet.dispose());\n };\n }, [fabricTextures]);\n\n /**\n * Create materials with advanced physical properties for realistic cloth rendering\n *\n * Enhanced with:\n * - **Fabric textures**: Procedural weave patterns for dobok authenticity\n * - **Normal maps**: Surface detail for thread texture visibility\n * - **Roughness maps**: Realistic light scattering on fabric surface\n * - Subsurface scattering: Light transmission through fabric for realism\n * - Clearcoat: Surface depth and sheen for material quality\n * - Double-sided rendering: Proper display when cloth folds\n * - Physical properties: IOR, reflectivity for authentic appearance\n *\n * @korean 향상된물리재료생성\n */\n const materials = useMemo(() => {\n return attachments.map((attachment) => {\n // Find the clothing item for this attachment to get its material type\n const clothingItem = clothingSet.items.find((item) =>\n attachment.itemId.startsWith(item.id),\n );\n const materialType = clothingItem?.material ?? \"fabric\";\n const textureSet = fabricTextures.get(materialType);\n\n // Create normal scale safely (Vector2 may not be available in test environments)\n let normalScale: THREE.Vector2 | undefined;\n try {\n normalScale = new THREE.Vector2(0.3, 0.3);\n } catch {\n normalScale = undefined;\n }\n\n const mat = new THREE.MeshPhysicalMaterial({\n color: attachment.color,\n // Apply fabric texture maps for realistic dobok appearance\n map: textureSet?.colorMap ?? null,\n normalMap: textureSet?.normalMap ?? null,\n normalScale, // Subtle normal effect\n roughnessMap: textureSet?.roughnessMap ?? null,\n emissive: attachment.emissiveColor ?? 0x000000,\n emissiveIntensity: attachment.emissiveIntensity ?? 0,\n metalness: attachment.metalness ?? 0.1, // Lower metalness for fabric\n roughness: attachment.roughness ?? 0.8, // Higher roughness for cloth\n // Enhanced cloth realism with clearcoat for depth\n clearcoat: 0.2,\n clearcoatRoughness: 0.6,\n // Subsurface scattering for realistic fabric translucency\n // Subtle effect for cloth materials (not skin)\n transmission: 0.03, // Minimal light transmission through fabric\n thickness: 0.15, // Thin fabric thickness\n ior: 1.4, // Index of refraction for fabric (lower than glass)\n // Enable proper reflections\n reflectivity: 0.2,\n // Double-sided rendering for cloth that may fold\n side: THREE.DoubleSide,\n // Improved shading for folds\n flatShading: false,\n });\n return mat;\n });\n }, [attachments, clothingSet, fabricTextures]);\n\n // Cleanup materials on unmount\n useEffect(() => {\n return () => {\n materials.forEach((mat) => mat.dispose());\n };\n }, [materials]);\n\n // Cleanup geometries on unmount\n useEffect(() => {\n return () => {\n attachments.forEach((attachment) => {\n attachment.geometry.dispose();\n });\n };\n }, [attachments]);\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment, index) => (\n <mesh\n key={attachment.itemId}\n geometry={attachment.geometry}\n material={materials[index]}\n position={attachment.localOffset.toArray()}\n rotation={[\n attachment.localRotation.x,\n attachment.localRotation.y,\n attachment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`clothing-${attachment.itemId}`}\n />\n ))}\n </>\n );\n};\n\nexport default BoneClothing;\n"],"mappings":"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"}
1
+ {"version":3,"file":"BoneClothing.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneClothing.tsx"],"sourcesContent":["/**\n * Bone-attached clothing system for realistic clothing movement with skeleton\n *\n * **Enhanced Features (v0.6.23+)**:\n * - **Subsurface Scattering**: Realistic fabric translucency with light transmission\n * - **Physical Materials**: Advanced PBR materials with clearcoat and reflectivity\n * - **Body Thickness Scaling**: Accurate clothing sizing based on muscle/fat mass\n * - **Archetype Styling**: Unique clothing sets for all 5 player archetypes\n *\n * Clothing is rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n * This mirrors the BoneMuscles approach for consistent rendering.\n *\n * **Visual Quality**:\n * - Transmission: 0.05 (minimal light passing through fabric)\n * - Thickness: 0.2 (thin fabric simulation)\n * - IOR: 1.4 (realistic fabric index of refraction)\n * - Clearcoat: 0.3 (surface depth and sheen)\n * - Double-sided rendering for cloth folding\n *\n * **Performance**:\n * - Geometry disposal on unmount (prevents memory leaks)\n * - Material cleanup system\n * - Optimized attachment calculations with useMemo\n *\n * @module components/three/BoneClothing\n * @category 3D Components\n * @korean 뼈부착의류시스템\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BICEP_RADIUS,\n CALF_RADIUS,\n CLOTHING_THICKNESS_FITTED,\n CORE_RADIUS,\n FOREARM_RADIUS,\n PECTORALS_RADIUS,\n QUAD_RADIUS,\n calculateClothingRadius,\n} from \"../../../../constants/bodyDimensions\";\nimport { getArchetypeClothing } from \"../../../../data/archetypeClothing\";\nimport type {\n ClothingItem,\n ClothingMaterial,\n} from \"../../../../types/clothing\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport {\n FABRIC_PRESETS,\n generateFabricTextureSet,\n type FabricTextureSet,\n} from \"../../../../utils/fabricTextures\";\n\n/**\n * Clothing attachment configuration for a bone\n *\n * Defines how clothing is positioned relative to its parent bone.\n * Position is in local bone space, so clothing moves with the bone.\n *\n * @korean 의류부착설정\n */\nexport interface ClothingAttachment {\n /** Geometry for this clothing piece */\n readonly geometry: THREE.BufferGeometry;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Color of the clothing */\n readonly color: number;\n /** Emissive color for glow effects */\n readonly emissiveColor?: number;\n /** Emissive intensity */\n readonly emissiveIntensity?: number;\n /** Metalness for material */\n readonly metalness?: number;\n /** Roughness for material */\n readonly roughness?: number;\n /** Clothing item ID */\n readonly itemId: string;\n}\n\n/**\n * Props for BoneClothing component\n */\nexport interface BoneClothingProps {\n /** Name of the bone this clothing attaches to */\n readonly boneName: string;\n /** Player archetype for clothing style */\n readonly archetype: PlayerArchetype;\n /** Physical attributes for sizing */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n}\n\n/**\n * Calculate body thickness multiplier based on muscle mass and fat mass\n *\n * Uses linear scaling with reasonable limits to prevent \"michelin man\" effect.\n * This matches the BodySurface calculation for consistency.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n // Linear scaling with limits (not square root which causes excessive inflation)\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n // Base 0.85, muscle adds up to +0.15, fat adds up to +0.20\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n // Cap at 1.20x maximum to prevent \"michelin man\" effect\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Get fabric preset based on clothing material type\n */\nconst getFabricPreset = (\n material: ClothingMaterial,\n): keyof typeof FABRIC_PRESETS => {\n switch (material) {\n case \"fabric\":\n return \"dobok\";\n case \"tactical\":\n case \"synthetic\":\n return \"tactical\";\n case \"leather\":\n case \"armored\":\n return \"leather\";\n case \"cybernetic\":\n return \"silk\"; // Shiny synthetic look\n default:\n return \"dobok\";\n }\n};\n\n/**\n * Convert number color to hex string\n */\nconst numberToHex = (color: number): string => {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n};\n\n/**\n * Get clothing attachments for a specific bone\n */\nconst getClothingForBone = (\n boneName: string,\n archetype: PlayerArchetype,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const clothingSet = getArchetypeClothing(archetype);\n const attachments: ClothingAttachment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n // Scaling factors\n const torsoScale = physicalAttributes.torsoLength / 59;\n const legScale = physicalAttributes.legLength / 96;\n\n for (const item of clothingSet.items) {\n const attachmentsForItem = getAttachmentsForItem(\n item,\n boneName,\n bodyThickness,\n torsoScale,\n legScale,\n physicalAttributes,\n );\n attachments.push(...attachmentsForItem);\n }\n\n return attachments;\n};\n\n/**\n * Generate clothing attachments for a specific item on a bone\n */\nconst getAttachmentsForItem = (\n item: ClothingItem,\n boneName: string,\n bodyThickness: number,\n torsoScale: number,\n legScale: number,\n physicalAttributes: {\n shoulderWidth: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const fitScaleMap: Record<string, number> = {\n tight: 1.08,\n fitted: 1.15,\n loose: 1.25,\n oversized: 1.4,\n };\n const fitScale = fitScaleMap[item.fit] ?? 1.15;\n const attachments: ClothingAttachment[] = [];\n\n switch (item.type) {\n case \"torso\":\n // Main torso on spine_middle - wraps fully around body\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * torsoScale * 1.2; // Using base torsoLength\n // Clothing wraps around the torso using CylinderGeometry (full 360°)\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n // Torso radius: average of shoulder width and pectorals depth\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n PECTORALS_RADIUS * bodyThickness) *\n 0.5;\n const clothingRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied above\n clothingThickness,\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Slightly narrower at top (chest taper)\n clothingRadius * 1.05, // Slightly wider at bottom (waist)\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n\n // Sleeves\n if (boneName === \"upper_arm_L\" || boneName === \"upper_arm_R\") {\n // Uses centralized BICEP_RADIUS from bodyDimensions\n // Clothing wraps around muscle, so offset outward\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n BICEP_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const upperArmLength = (physicalAttributes.armLength / 100) * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.05, // Slightly larger at top\n clothingRadius * 0.95, // Tapers slightly\n upperArmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -upperArmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-upper-${boneName}`,\n });\n }\n\n if (boneName === \"forearm_L\" || boneName === \"forearm_R\") {\n // Uses centralized FOREARM_RADIUS from bodyDimensions\n // Clothing must be OUTSIDE, so use larger radius\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 0.8;\n const clothingRadius = calculateClothingRadius(\n FOREARM_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const forearmLength = (physicalAttributes.armLength / 100) * 0.4;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.98, // Slightly smaller at top\n clothingRadius * 0.85, // Tapers toward wrist\n forearmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -forearmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-forearm-${boneName}`,\n });\n }\n break;\n\n case \"pants\":\n // Thigh segments\n if (boneName === \"thigh_L\" || boneName === \"thigh_R\") {\n // Uses centralized QUAD_RADIUS from bodyDimensions\n // Clothing wraps around muscle, so offset outward\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 1.3;\n const clothingRadius = calculateClothingRadius(\n QUAD_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const thighLength =\n (physicalAttributes.legLength / 100) * legScale * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.08, // Larger at hip\n clothingRadius * 0.92, // Tapers toward knee\n thighLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -thighLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-thigh-${boneName}`,\n });\n }\n\n // Shin segments\n if (boneName === \"shin_L\" || boneName === \"shin_R\") {\n // Uses centralized CALF_RADIUS from bodyDimensions\n // Clothing must be OUTSIDE, so use larger radius\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n CALF_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const shinLength =\n (physicalAttributes.legLength / 100) * legScale * 0.42;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Larger at knee\n clothingRadius * 0.75, // Tapers toward ankle\n shinLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -shinLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-shin-${boneName}`,\n });\n }\n break;\n\n case \"belt\":\n if (boneName === \"pelvis\") {\n // Belt wraps fully around waist using CylinderGeometry\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const waistRadius = CORE_RADIUS * bodyThickness;\n const beltRadius = calculateClothingRadius(\n waistRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.005, // Extra so belt sits outside clothing\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n beltRadius,\n beltRadius,\n 0.06, // Belt height\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n\n case \"vest\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * 0.75 * torsoScale;\n // Vest wraps fully around torso using CylinderGeometry\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n // Vest sits slightly outside the torso clothing layer\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n (PECTORALS_RADIUS + 0.01) * bodyThickness) *\n 0.5;\n const vestRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.008, // Extra gap for layering over shirt\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n vestRadius * 0.97, // Slightly narrower at top\n vestRadius * 1.03, // Slightly wider at bottom\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n }\n\n return attachments;\n};\n\n/**\n * BoneClothing Component\n *\n * Renders clothing attached to a specific bone.\n * Clothing inherits bone transformations automatically through the scene graph.\n *\n * @korean 뼈부착의류\n */\nexport const BoneClothing: React.FC<BoneClothingProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n}) => {\n // Default physical attributes if not provided - memoized to prevent new object on every render\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 62,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n // Get clothing attachments for this bone\n const attachments = useMemo(\n () => getClothingForBone(boneName, archetype, attrs),\n [boneName, archetype, attrs],\n );\n\n // Get clothing set for fabric texture generation\n const clothingSet = useMemo(\n () => getArchetypeClothing(archetype),\n [archetype],\n );\n\n /**\n * Generate fabric textures for realistic dobok (도복) rendering\n *\n * Creates procedural weave patterns, normal maps, and roughness maps\n * for enhanced cloth realism without external image assets.\n *\n * @korean 직물텍스처생성\n */\n const fabricTextures = useMemo(() => {\n // Generate textures for each unique clothing item\n const textureMap = new Map<string, FabricTextureSet>();\n\n for (const item of clothingSet.items) {\n if (!textureMap.has(item.material)) {\n const preset = getFabricPreset(item.material);\n const colorHex = numberToHex(item.colorPrimary);\n textureMap.set(\n item.material,\n generateFabricTextureSet(colorHex, preset),\n );\n }\n }\n\n return textureMap;\n }, [clothingSet]);\n\n // Cleanup fabric textures on unmount\n useEffect(() => {\n return () => {\n fabricTextures.forEach((textureSet) => textureSet.dispose());\n };\n }, [fabricTextures]);\n\n /**\n * Create materials with advanced physical properties for realistic cloth rendering\n *\n * Enhanced with:\n * - **Fabric textures**: Procedural weave patterns for dobok authenticity\n * - **Normal maps**: Surface detail for thread texture visibility\n * - **Roughness maps**: Realistic light scattering on fabric surface\n * - Subsurface scattering: Light transmission through fabric for realism\n * - Clearcoat: Surface depth and sheen for material quality\n * - Double-sided rendering: Proper display when cloth folds\n * - Physical properties: IOR, reflectivity for authentic appearance\n *\n * @korean 향상된물리재료생성\n */\n const materials = useMemo(() => {\n return attachments.map((attachment) => {\n // Find the clothing item for this attachment to get its material type\n const clothingItem = clothingSet.items.find((item) =>\n attachment.itemId.startsWith(item.id),\n );\n const materialType = clothingItem?.material ?? \"fabric\";\n const textureSet = fabricTextures.get(materialType);\n\n // Create normal scale safely (Vector2 may not be available in test environments)\n let normalScale: THREE.Vector2 | undefined;\n try {\n normalScale = new THREE.Vector2(0.3, 0.3);\n } catch {\n normalScale = undefined;\n }\n\n const mat = new THREE.MeshPhysicalMaterial({\n color: attachment.color,\n // Apply fabric texture maps for realistic dobok appearance\n map: textureSet?.colorMap ?? null,\n normalMap: textureSet?.normalMap ?? null,\n normalScale, // Subtle normal effect\n roughnessMap: textureSet?.roughnessMap ?? null,\n emissive: attachment.emissiveColor ?? 0x000000,\n emissiveIntensity: attachment.emissiveIntensity ?? 0,\n metalness: attachment.metalness ?? 0.1, // Lower metalness for fabric\n roughness: attachment.roughness ?? 0.8, // Higher roughness for cloth\n // Enhanced cloth realism with clearcoat for depth\n clearcoat: 0.2,\n clearcoatRoughness: 0.6,\n // Subsurface scattering for realistic fabric translucency\n // Subtle effect for cloth materials (not skin)\n transmission: 0.03, // Minimal light transmission through fabric\n thickness: 0.15, // Thin fabric thickness\n ior: 1.4, // Index of refraction for fabric (lower than glass)\n // Enable proper reflections\n reflectivity: 0.2,\n // Double-sided rendering for cloth that may fold\n side: THREE.DoubleSide,\n // Improved shading for folds\n flatShading: false,\n });\n return mat;\n });\n }, [attachments, clothingSet, fabricTextures]);\n\n // Cleanup materials on unmount\n useEffect(() => {\n return () => {\n materials.forEach((mat) => mat.dispose());\n };\n }, [materials]);\n\n // Cleanup geometries on unmount\n useEffect(() => {\n return () => {\n attachments.forEach((attachment) => {\n attachment.geometry.dispose();\n });\n };\n }, [attachments]);\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment, index) => (\n <mesh\n key={attachment.itemId}\n geometry={attachment.geometry}\n material={materials[index]}\n position={attachment.localOffset.toArray()}\n rotation={[\n attachment.localRotation.x,\n attachment.localRotation.y,\n attachment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`clothing-${attachment.itemId}`}\n />\n ))}\n </>\n );\n};\n\nexport default BoneClothing;\n"],"mappings":"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"}