blacktrigram 0.7.47 → 0.7.49
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.
- package/lib/App2.js.map +1 -1
- package/lib/audio/AudioAssetLoader.js.map +1 -1
- package/lib/audio/AudioAssetRegistry.js.map +1 -1
- package/lib/audio/AudioCache.js.map +1 -1
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/AudioMonitor.js.map +1 -1
- package/lib/audio/AudioPool.js.map +1 -1
- package/lib/audio/AudioProvider.js.map +1 -1
- package/lib/audio/AudioUtils.js.map +1 -1
- package/lib/audio/BoneImpactAudioMap.js.map +1 -1
- package/lib/audio/VariantSelector.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.d.ts.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +29 -25
- package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatButtons.js.map +1 -1
- package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
- package/lib/components/screens/combat/components/controls/ControlsGuide.js.map +1 -1
- package/lib/components/screens/combat/components/controls/KeyboardHints.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
- package/lib/components/screens/combat/components/controls/PauseMenuButton.js.map +1 -1
- package/lib/components/screens/combat/components/controls/QuickSettings.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodLossOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ConsciousnessBlur.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/PainVignette.js.map +1 -1
- package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/TraumaOverlay3D.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/MatchCountdown.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundDisplayStatus.js.map +1 -1
- package/lib/components/screens/combat/components/feedback/RoundStartAnnouncementOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js +2 -2
- package/lib/components/screens/combat/components/hud/CombatBottomHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatLeftHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js +1 -1
- package/lib/components/screens/combat/components/hud/CombatPortraitStatusStrip.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.d.ts.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js +2 -1
- package/lib/components/screens/combat/components/hud/CombatTopHUD.js.map +1 -1
- package/lib/components/screens/combat/components/hud/DifficultyIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
- package/lib/components/screens/combat/components/hud/MobileControlsWrapper.js.map +1 -1
- package/lib/components/screens/combat/components/hud/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/StaminaWarning.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.d.ts.map +1 -1
- package/lib/components/screens/combat/helpers/AnimationUpdater.js +4 -2
- package/lib/components/screens/combat/helpers/AnimationUpdater.js.map +1 -1
- package/lib/components/screens/combat/helpers/combatHelpers.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatAudio.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.d.ts.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +11 -5
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatState.js.map +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js +1 -1
- package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
- package/lib/components/screens/controls/components/ControlBindingsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/GamepadVisualization3D.js.map +1 -1
- package/lib/components/screens/controls/components/InteractiveControlDemoOverlayHtml.js.map +1 -1
- package/lib/components/screens/controls/components/Key3D.js.map +1 -1
- package/lib/components/screens/controls/components/VisualKeyboard3D.js.map +1 -1
- package/lib/components/screens/controls/constants/ControlsConstants.js.map +1 -1
- package/lib/components/screens/controls/hooks/useControlsState.js.map +1 -1
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/NavigationButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/PerformanceRatingOverlayHtml.js.map +1 -1
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
- package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeCardOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js +1 -1
- package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.d.ts.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +3 -11
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
- package/lib/components/screens/training/components/FootPlacementMarkers3D.js.map +1 -1
- package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingControlsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
- package/lib/components/screens/training/components/TrainingFeedbackOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.d.ts.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js +2 -2
- package/lib/components/screens/training/components/hud/TrainingBottomHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingLeftHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingRightHUD.js.map +1 -1
- package/lib/components/screens/training/components/hud/TrainingTopHUD.js.map +1 -1
- package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts +1 -0
- package/lib/components/screens/training/hooks/useTrainingActions.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingActions.js +6 -4
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.d.ts.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +11 -5
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingState.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/base/useKoreanTheme.js.map +1 -1
- package/lib/components/shared/debug/PerformanceDebugOverlayHtml.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
- package/lib/components/shared/mobile/HapticController.js.map +1 -1
- package/lib/components/shared/mobile/MobileControlsPure.js.map +1 -1
- package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
- package/lib/components/shared/mobile/TouchOptimizer.js.map +1 -1
- package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BodySurface.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
- package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Foot3D.js.map +1 -1
- package/lib/components/shared/three/anatomy/Hand3D.js.map +1 -1
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
- package/lib/components/shared/three/effects/PlayerStateIndicators.js.map +1 -1
- package/lib/components/shared/three/effects/StanceSymbol3D.js.map +1 -1
- package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
- package/lib/components/shared/three/indicators/HapticFeedback.js.map +1 -1
- package/lib/components/shared/three/indicators/StanceChangeIndicator.js.map +1 -1
- package/lib/components/shared/three/models/Player3DWithTransitions.js.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.d.ts.map +1 -1
- package/lib/components/shared/three/models/SkeletalPlayer3D.js +7 -5
- package/lib/components/shared/three/models/SkeletalPlayer3D.js.map +1 -1
- package/lib/components/shared/three/optimization/AdaptiveQuality.js.map +1 -1
- package/lib/components/shared/three/scene/AtmosphericParticles3D.js.map +1 -1
- package/lib/components/shared/three/scene/BackgroundScene3D.js.map +1 -1
- package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
- package/lib/components/shared/three/scene/KoreanSignage3D.js.map +1 -1
- package/lib/components/shared/three/ui/ArchetypeCard.js.map +1 -1
- package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
- package/lib/components/shared/three/ui/BreathingIndicator2.js.map +1 -1
- package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
- package/lib/components/shared/three/ui/ComboCounter.js.map +1 -1
- package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanButton.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanPanel.js.map +1 -1
- package/lib/components/shared/three/ui/KoreanText.js.map +1 -1
- package/lib/components/shared/three/ui/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
- package/lib/components/shared/three/ui/ProgressBar.js.map +1 -1
- package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
- package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
- package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js.map +1 -1
- package/lib/components/shared/ui/CombatTimer.js.map +1 -1
- package/lib/components/shared/ui/ErrorModal.js.map +1 -1
- package/lib/components/shared/ui/LoadingState.js.map +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/components/shared/ui/SplashScreen.js.map +1 -1
- package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
- package/lib/components/shared/ui/VolumeControl.js.map +1 -1
- package/lib/components/shared/ui/shared/ConfirmDialog.js.map +1 -1
- package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
- package/lib/constants/bodyDimensions.js.map +1 -1
- package/lib/constants/bodyRenderingConstants.js.map +1 -1
- package/lib/data/archetypeClothing.js.map +1 -1
- package/lib/data/archetypePhysicalAttributes.js.map +1 -1
- package/lib/data/techniqueMappings.js.map +1 -1
- package/lib/data/techniques.js.map +1 -1
- package/lib/hooks/useActionFeedback.js.map +1 -1
- package/lib/hooks/useBalanceAnimations.js.map +1 -1
- package/lib/hooks/useCombatTimer.js.map +1 -1
- package/lib/hooks/useDebounce.js.map +1 -1
- package/lib/hooks/useHUDLayout.d.ts.map +1 -1
- package/lib/hooks/useHUDLayout.js +3 -2
- package/lib/hooks/useHUDLayout.js.map +1 -1
- package/lib/hooks/useHandPoseTransitions.js.map +1 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useMatchCountdown.js.map +1 -1
- package/lib/hooks/useMuscleActivation.js.map +1 -1
- package/lib/hooks/usePauseMenu.js.map +1 -1
- package/lib/hooks/usePlayerAnimation.js.map +1 -1
- package/lib/hooks/useResponsiveLayout.js.map +1 -1
- package/lib/hooks/useRoundTransition.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.d.ts.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +1 -1
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/hooks/useTechniqueSelection.js.map +1 -1
- package/lib/hooks/useThrottle.js.map +1 -1
- package/lib/hooks/useTouchControls.js.map +1 -1
- package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
- package/lib/hooks/useWindowSize.js.map +1 -1
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/EffectCalculator.js.map +1 -1
- package/lib/systems/LayoutSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ResponsiveScaling.js.map +1 -1
- package/lib/systems/TrigramSystem.js.map +1 -1
- package/lib/systems/VitalPointSystem.js.map +1 -1
- package/lib/systems/ai/AIPersonality.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js +16 -16
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
- package/lib/systems/ai/ComboSystem.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/ai/TrainingAI.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/AnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/HandPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/HandPoses.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
- package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts +6 -0
- package/lib/systems/animation/builders/KickPhaseApplicator.d.ts.map +1 -1
- package/lib/systems/animation/builders/KickPhaseApplicator.js +16 -9
- package/lib/systems/animation/builders/KickPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/KoreanGuardPositions.d.ts +4 -4
- package/lib/systems/animation/builders/KoreanGuardPositions.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js +5 -5
- package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts +112 -71
- package/lib/systems/animation/builders/MartialArtsConstants.d.ts.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js +113 -72
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/builders/MartialPoseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/PunchPhaseApplicator.js.map +1 -1
- package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
- package/lib/systems/animation/builders/TrigramGuardApplicator.js.map +1 -1
- package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/EnhancedElbowKneeAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GamStanceAnimations.js +21 -0
- package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js +34 -2
- package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts +9 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.d.ts.map +1 -1
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +288 -0
- package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -0
- package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/KickAnimations.d.ts +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js +2 -2
- package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js +14 -1
- package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.d.ts +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
- package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/RecoveryAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceGuardPoses.d.ts +6 -6
- package/lib/systems/animation/catalogs/StanceGuardPoses.js +36 -36
- package/lib/systems/animation/catalogs/StanceGuardPoses.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
- package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
- package/lib/systems/animation/constants/AnatomicalLimits.js.map +1 -1
- package/lib/systems/animation/core/AnimationHitTiming.js.map +1 -1
- package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
- package/lib/systems/animation/core/AnimationPriority.js +15 -15
- package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.d.ts +30 -0
- package/lib/systems/animation/core/AnimationRegistry.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationRegistry.js +74 -12
- package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js +16 -16
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.d.ts.map +1 -1
- package/lib/systems/animation/core/AnimationTransitions.js +34 -0
- package/lib/systems/animation/core/AnimationTransitions.js.map +1 -1
- package/lib/systems/animation/core/LateralityTransform.js.map +1 -1
- package/lib/systems/animation/core/RecoveryPhaseEnhancer.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
- package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
- package/lib/systems/animation/core/index.d.ts +1 -1
- package/lib/systems/animation/core/index.d.ts.map +1 -1
- package/lib/systems/animation/core/types.d.ts +24 -0
- package/lib/systems/animation/core/types.d.ts.map +1 -1
- package/lib/systems/animation/core/types.js +27 -11
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/BodyFacingSystem.js.map +1 -1
- package/lib/systems/animation/systems/FacialExpressions.js.map +1 -1
- package/lib/systems/animation/systems/FallAnimations.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
- package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
- package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
- package/lib/systems/bodypart/CombatInjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryIntegration.js.map +1 -1
- package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js +19 -19
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/breathing/feedback.js.map +1 -1
- package/lib/systems/breathing/integration.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js +19 -19
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js +17 -17
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js +24 -24
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/FallIntegration.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js +21 -21
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/combat/TrainingCombatSystem.js.map +1 -1
- package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
- package/lib/systems/combat/typeGuards.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/movement/helpers/AccelerationUpdater.js.map +1 -1
- package/lib/systems/movement/helpers/accelerationUtils.js.map +1 -1
- package/lib/systems/movement/integration.js.map +1 -1
- package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
- package/lib/systems/physics/CollisionDetection.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
- package/lib/systems/physics/MovementPhysics.js.map +1 -1
- package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js +6 -6
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/systems/trigram/KoreanCulture.js.map +1 -1
- package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
- package/lib/systems/trigram/StanceManager.js.map +1 -1
- package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
- package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
- package/lib/systems/trigram/techniques/DarkOpsTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GamTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GanTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GeonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/GonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/JinTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/LiTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/SonTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TaeTechniques.js.map +1 -1
- package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
- package/lib/systems/trigram/techniques/index.js.map +1 -1
- package/lib/systems/trigram/types/GonTechniqueExtensions.js.map +1 -1
- package/lib/systems/trigram/types.js.map +1 -1
- package/lib/systems/types.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/systems/vitalpoint/HitDetection.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
- package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
- package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
- package/lib/systems/vitalpoint/VitalPointsData.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/LayoutTypes.js.map +1 -1
- package/lib/types/PhysicsTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/constants/animations.js.map +1 -1
- package/lib/types/constants/colors.js.map +1 -1
- package/lib/types/constants/designSystem.js.map +1 -1
- package/lib/types/constants/index.js.map +1 -1
- package/lib/types/constants/layout.d.ts +21 -0
- package/lib/types/constants/layout.d.ts.map +1 -1
- package/lib/types/constants/layout.js +22 -1
- package/lib/types/constants/layout.js.map +1 -1
- package/lib/types/constants/performance.js.map +1 -1
- package/lib/types/constants/typography.js.map +1 -1
- package/lib/types/constants/ui.js.map +1 -1
- package/lib/types/facial.js +19 -19
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/muscle.js.map +1 -1
- package/lib/types/physics.js.map +1 -1
- package/lib/types/physicsConstants.js.map +1 -1
- package/lib/types/player-visual.d.ts +1 -1
- package/lib/types/player-visual.d.ts.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/arenaWorldDimensions.js.map +1 -1
- package/lib/utils/assetConfig.js.map +1 -1
- package/lib/utils/characterScaling.js.map +1 -1
- package/lib/utils/colorHelpers.js.map +1 -1
- package/lib/utils/colorUtils.js.map +1 -1
- package/lib/utils/combatReadiness.js.map +1 -1
- package/lib/utils/controlMapping.js.map +1 -1
- package/lib/utils/deviceDetection.js +6 -7
- package/lib/utils/deviceDetection.js.map +1 -1
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/fabricTextures.js.map +1 -1
- package/lib/utils/hapticFeedback.js.map +1 -1
- package/lib/utils/haptics.js.map +1 -1
- package/lib/utils/htmlOverlayHelpers.js.map +1 -1
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/math.js.map +1 -1
- package/lib/utils/mobileLayoutHelpers.js.map +1 -1
- package/lib/utils/mobileUIUtils.js.map +1 -1
- package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/performance/usePerformanceMonitor.js.map +1 -1
- package/lib/utils/performanceOptimization.js.map +1 -1
- package/lib/utils/player3DHelpers.js.map +1 -1
- package/lib/utils/playerUtils.js.map +1 -1
- package/lib/utils/responsiveLayout.js.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.d.ts +7 -0
- package/lib/utils/responsiveLayoutHelpers.d.ts.map +1 -1
- package/lib/utils/responsiveLayoutHelpers.js +16 -2
- package/lib/utils/responsiveLayoutHelpers.js.map +1 -1
- package/lib/utils/responsiveOrientationConstants.js.map +1 -1
- package/lib/utils/safeAreaUtils.js.map +1 -1
- package/lib/utils/sharedPhysicsConfig.js.map +1 -1
- package/lib/utils/skeletonScaling.js.map +1 -1
- package/lib/utils/stanceHelpers.js.map +1 -1
- package/lib/utils/threeObjectPool.js.map +1 -1
- package/lib/utils/visualEffects.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Face3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Face3D.tsx"],"sourcesContent":["/**\n * Face3D component with realistic facial features\n *\n * Renders facial features with expressions, eye tracking, and damage visualization:\n * - Eyes with pupils that track opponent\n * - Mouth that opens/closes based on expression\n * - Nose geometry\n * - Damage effects (bruises, swelling, bleeding)\n *\n * @module components/three/Face3D\n * @category 3D Components\n * @korean 얼굴3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport {\n EYE_OPENNESS,\n MOUTH_OPENNESS,\n type EyeProps,\n type Face3DProps,\n type MouthProps,\n} from \"../../../../types/facial\";\nimport { mixColors } from \"../../../../utils/colorHelpers\";\n\n/**\n * Head position offset from bone center\n * Since Face3D is rendered inside the head bone group,\n * this is a small offset to position the face correctly\n * @korean 머리위치오프셋\n */\nconst HEAD_POSITION_OFFSET = 0.1;\n\n/**\n * Eye component with pupil tracking and swelling\n *\n * Renders eye with adjustable openness, pupil tracking, and swelling effects.\n *\n * @param props - Eye component props\n * @returns Eye 3D mesh group\n *\n * @korean 눈컴포넌트\n */\nconst Eye: React.FC<EyeProps> = ({\n position,\n expression,\n lookDirection,\n swelling,\n side,\n}) => {\n const eyeOpenness = useMemo(() => {\n return EYE_OPENNESS[expression];\n }, [expression]);\n\n const pupilOffset = useMemo(() => {\n const normalized = lookDirection.clone().normalize();\n\n const maxOffset = 0.015;\n const x = normalized.x * maxOffset;\n const y = -normalized.y * maxOffset * 0.5; // Less vertical movement\n\n return new THREE.Vector3(x, y, 0.03);\n }, [lookDirection]);\n\n const swellingColor = 0x663366;\n\n return (\n <group position={position} name={`eye-${side}`}>\n {/* Eye white (sclera) */}\n <mesh scale={[1, eyeOpenness, 1]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshPhysicalMaterial\n color={0xffffff}\n roughness={0.1}\n clearcoat={1.0}\n clearcoatRoughness={0.1}\n />\n </mesh>\n\n {/* Pupil (tracks opponent) */}\n {eyeOpenness > 0.1 && (\n <mesh position={pupilOffset}>\n <sphereGeometry args={[0.015, 8, 8]} />\n <meshPhysicalMaterial\n color={0x000000}\n roughness={0.2}\n clearcoat={0.8}\n />\n </mesh>\n )}\n\n {/* Swelling indicator */}\n {swelling > 0 && (\n <mesh scale={[1 + swelling * 0.5, 1 + swelling * 0.5, 1]}>\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshStandardMaterial\n color={swellingColor}\n transparent\n opacity={swelling * 0.6}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Mouth component with expression-based openness and bleeding\n *\n * Renders mouth that opens/closes based on expression.\n *\n * @param props - Mouth component props\n * @returns Mouth 3D mesh group\n *\n * @korean 입컴포넌트\n */\nconst Mouth: React.FC<MouthProps> = ({ position, expression, bleeding }) => {\n const mouthOpenness = useMemo(() => {\n return MOUTH_OPENNESS[expression];\n }, [expression]);\n\n const bloodColor = KOREAN_COLORS.ACCENT_RED;\n\n return (\n <group position={position} name=\"mouth\">\n {/* Mouth line */}\n <mesh scale={[0.08, mouthOpenness * 0.04 + 0.005, 0.01]}>\n <boxGeometry args={[1, 1, 1]} />\n <meshPhysicalMaterial color={0x330000} roughness={0.5} />\n </mesh>\n\n {/* Blood effect */}\n {bleeding > 0 && (\n <>\n {/* Blood on lip */}\n <mesh position={[0, -0.01, 0]}>\n <sphereGeometry args={[0.015, 8, 8]} />\n <meshPhysicalMaterial\n color={bloodColor}\n roughness={0.2}\n clearcoat={1.0}\n transparent\n opacity={bleeding * 0.9}\n />\n </mesh>\n\n {/* Blood drip */}\n {bleeding > 0.5 && (\n <mesh position={[0, -0.03, 0]} scale={[0.5, 1, 0.5]}>\n <cylinderGeometry args={[0.005, 0.008, 0.04, 8]} />\n <meshPhysicalMaterial\n color={bloodColor}\n roughness={0.2}\n clearcoat={1.0}\n transparent\n opacity={bleeding * 0.7}\n />\n </mesh>\n )}\n </>\n )}\n </group>\n );\n};\n\n/**\n * Nose component\n *\n * Simple geometric nose.\n *\n * @param position - Nose position\n * @param skinColor - Skin tone color\n * @param bleeding - Bleeding intensity (0-1)\n * @returns Nose 3D mesh\n *\n * @korean 코컴포넌트\n */\nconst Nose: React.FC<{\n position: [number, number, number];\n skinColor: number;\n bleeding: number;\n}> = ({ position, skinColor, bleeding }) => {\n const bloodColor = KOREAN_COLORS.ACCENT_RED;\n\n const noseMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: skinColor,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n transmission: 0,\n thickness: 0.05,\n ior: 1.4,\n sheen: 0.1,\n sheenRoughness: 0.8,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [skinColor]\n );\n\n useEffect(() => {\n return () => {\n noseMaterial.dispose();\n };\n }, [noseMaterial]);\n\n return (\n <group position={position} name=\"nose\">\n {/* Nose */}\n <mesh rotation={[Math.PI, 0, 0]}>\n <coneGeometry args={[0.03, 0.06, 8]} />\n <primitive object={noseMaterial} attach=\"material\" />\n </mesh>\n\n {/* Blood from nose */}\n {bleeding > 0 && (\n <mesh position={[0, -0.03, 0.01]}>\n <sphereGeometry args={[0.01, 6, 6]} />\n <meshBasicMaterial\n color={bloodColor}\n transparent\n opacity={bleeding * 0.8}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Face3D component\n *\n * Main facial features component with head sphere, eyes, mouth, nose,\n * and damage visualization.\n *\n * @param props - Face3D props\n * @returns Face 3D mesh group\n *\n * @example\n * ```tsx\n * <Face3D\n * expression={FacialExpression.PAINED}\n * damage={damageState}\n * opponentPosition={new THREE.Vector3(5, 2, 0)}\n * headRotation={new THREE.Euler(0, 0, 0)}\n * enableEyeTracking={true}\n * />\n * ```\n *\n * @korean 얼굴3D\n */\nexport const Face3D: React.FC<Face3DProps> = ({\n expression,\n damage,\n opponentPosition,\n headRotation,\n enableEyeTracking = true,\n enableDamageVisuals = true,\n isMobile = false,\n skinColor = 0xffdbac, // Default skin tone\n}) => {\n const eyeDirection = useMemo(() => {\n if (!enableEyeTracking) {\n return new THREE.Vector3(0, 0, 1); // Look forward\n }\n\n if (\n !opponentPosition ||\n typeof opponentPosition.x !== \"number\" ||\n typeof opponentPosition.y !== \"number\" ||\n typeof opponentPosition.z !== \"number\"\n ) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n \"Face3D: opponentPosition is not a valid THREE.Vector3; defaulting eye direction forward.\"\n );\n }\n return new THREE.Vector3(0, 0, 1);\n }\n\n const headPos = new THREE.Vector3(0, HEAD_POSITION_OFFSET, 0);\n const dir = new THREE.Vector3(\n opponentPosition.x - headPos.x,\n opponentPosition.y - headPos.y,\n opponentPosition.z - headPos.z\n );\n dir.normalize();\n\n return dir;\n }, [opponentPosition, enableEyeTracking]);\n\n const bruiseIntensity = useMemo(() => {\n if (!enableDamageVisuals) return 0;\n\n const avgBruise =\n (damage.leftCheekBruise +\n damage.rightCheekBruise +\n damage.foreheadBruise +\n damage.jawBruise) /\n 4;\n return avgBruise;\n }, [damage, enableDamageVisuals]);\n\n const headColor = useMemo(() => {\n if (bruiseIntensity === 0) return skinColor;\n\n const bruiseColor = 0x663366;\n return mixColors(skinColor, bruiseColor, bruiseIntensity * 0.3);\n }, [skinColor, bruiseIntensity]);\n\n const damageTexture = null;\n\n const headMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: headColor,\n map: damageTexture,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n envMapIntensity: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.15, // Facial skin has more sheen\n sheenRoughness: 0.7,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [headColor, damageTexture]\n );\n\n const earMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: headColor,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n transmission: 0,\n thickness: 0.05,\n ior: 1.4,\n sheen: 0.1,\n sheenRoughness: 0.8,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [headColor]\n );\n\n useEffect(() => {\n return () => {\n headMaterial.dispose();\n earMaterial.dispose();\n };\n }, [headMaterial, earMaterial]);\n\n return (\n <group\n position={[0, HEAD_POSITION_OFFSET, 0]}\n rotation={headRotation}\n name=\"face3d\"\n >\n {/* Head sphere */}\n <mesh>\n <sphereGeometry args={[0.2, isMobile ? 12 : 16, isMobile ? 12 : 16]} />\n <primitive object={headMaterial} attach=\"material\" />\n </mesh>\n\n {/* Left eye */}\n <Eye\n position={[-0.08, 0.05, 0.15]}\n expression={expression}\n lookDirection={eyeDirection}\n swelling={enableDamageVisuals ? damage.leftEyeSwelling : 0}\n side=\"left\"\n />\n\n {/* Right eye */}\n <Eye\n position={[0.08, 0.05, 0.15]}\n expression={expression}\n lookDirection={eyeDirection}\n swelling={enableDamageVisuals ? damage.rightEyeSwelling : 0}\n side=\"right\"\n />\n\n {/* Mouth */}\n <Mouth\n position={[0, -0.05, 0.15]}\n expression={expression}\n bleeding={enableDamageVisuals ? damage.mouthBleeding : 0}\n />\n\n {/* Nose */}\n <Nose\n position={[0, 0.02, 0.18]}\n skinColor={skinColor}\n bleeding={enableDamageVisuals ? damage.noseBleeding : 0}\n />\n\n {/* Ears (simple geometric shapes) */}\n {!isMobile && (\n <>\n {/* Left ear */}\n <mesh position={[-0.2, 0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <primitive object={earMaterial} attach=\"material\" />\n </mesh>\n\n {/* Right ear */}\n <mesh position={[0.2, 0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <primitive object={earMaterial} attach=\"material\" />\n </mesh>\n </>\n )}\n </group>\n );\n};\n\nexport default Face3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Face3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Face3D.tsx"],"sourcesContent":["/**\n * Face3D component with realistic facial features\n *\n * Renders facial features with expressions, eye tracking, and damage visualization:\n * - Eyes with pupils that track opponent\n * - Mouth that opens/closes based on expression\n * - Nose geometry\n * - Damage effects (bruises, swelling, bleeding)\n *\n * @module components/three/Face3D\n * @category 3D Components\n * @korean 얼굴3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport {\n EYE_OPENNESS,\n MOUTH_OPENNESS,\n type EyeProps,\n type Face3DProps,\n type MouthProps,\n} from \"../../../../types/facial\";\nimport { mixColors } from \"../../../../utils/colorHelpers\";\n\n/**\n * Head position offset from bone center\n * Since Face3D is rendered inside the head bone group,\n * this is a small offset to position the face correctly\n * @korean 머리위치오프셋\n */\nconst HEAD_POSITION_OFFSET = 0.1;\n\n/**\n * Eye component with pupil tracking and swelling\n *\n * Renders eye with adjustable openness, pupil tracking, and swelling effects.\n *\n * @param props - Eye component props\n * @returns Eye 3D mesh group\n *\n * @korean 눈컴포넌트\n */\nconst Eye: React.FC<EyeProps> = ({\n position,\n expression,\n lookDirection,\n swelling,\n side,\n}) => {\n const eyeOpenness = useMemo(() => {\n return EYE_OPENNESS[expression];\n }, [expression]);\n\n const pupilOffset = useMemo(() => {\n const normalized = lookDirection.clone().normalize();\n\n const maxOffset = 0.015;\n const x = normalized.x * maxOffset;\n const y = -normalized.y * maxOffset * 0.5; // Less vertical movement\n\n return new THREE.Vector3(x, y, 0.03);\n }, [lookDirection]);\n\n const swellingColor = 0x663366;\n\n return (\n <group position={position} name={`eye-${side}`}>\n {/* Eye white (sclera) */}\n <mesh scale={[1, eyeOpenness, 1]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshPhysicalMaterial\n color={0xffffff}\n roughness={0.1}\n clearcoat={1.0}\n clearcoatRoughness={0.1}\n />\n </mesh>\n\n {/* Pupil (tracks opponent) */}\n {eyeOpenness > 0.1 && (\n <mesh position={pupilOffset}>\n <sphereGeometry args={[0.015, 8, 8]} />\n <meshPhysicalMaterial\n color={0x000000}\n roughness={0.2}\n clearcoat={0.8}\n />\n </mesh>\n )}\n\n {/* Swelling indicator */}\n {swelling > 0 && (\n <mesh scale={[1 + swelling * 0.5, 1 + swelling * 0.5, 1]}>\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshStandardMaterial\n color={swellingColor}\n transparent\n opacity={swelling * 0.6}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Mouth component with expression-based openness and bleeding\n *\n * Renders mouth that opens/closes based on expression.\n *\n * @param props - Mouth component props\n * @returns Mouth 3D mesh group\n *\n * @korean 입컴포넌트\n */\nconst Mouth: React.FC<MouthProps> = ({ position, expression, bleeding }) => {\n const mouthOpenness = useMemo(() => {\n return MOUTH_OPENNESS[expression];\n }, [expression]);\n\n const bloodColor = KOREAN_COLORS.ACCENT_RED;\n\n return (\n <group position={position} name=\"mouth\">\n {/* Mouth line */}\n <mesh scale={[0.08, mouthOpenness * 0.04 + 0.005, 0.01]}>\n <boxGeometry args={[1, 1, 1]} />\n <meshPhysicalMaterial color={0x330000} roughness={0.5} />\n </mesh>\n\n {/* Blood effect */}\n {bleeding > 0 && (\n <>\n {/* Blood on lip */}\n <mesh position={[0, -0.01, 0]}>\n <sphereGeometry args={[0.015, 8, 8]} />\n <meshPhysicalMaterial\n color={bloodColor}\n roughness={0.2}\n clearcoat={1.0}\n transparent\n opacity={bleeding * 0.9}\n />\n </mesh>\n\n {/* Blood drip */}\n {bleeding > 0.5 && (\n <mesh position={[0, -0.03, 0]} scale={[0.5, 1, 0.5]}>\n <cylinderGeometry args={[0.005, 0.008, 0.04, 8]} />\n <meshPhysicalMaterial\n color={bloodColor}\n roughness={0.2}\n clearcoat={1.0}\n transparent\n opacity={bleeding * 0.7}\n />\n </mesh>\n )}\n </>\n )}\n </group>\n );\n};\n\n/**\n * Nose component\n *\n * Simple geometric nose.\n *\n * @param position - Nose position\n * @param skinColor - Skin tone color\n * @param bleeding - Bleeding intensity (0-1)\n * @returns Nose 3D mesh\n *\n * @korean 코컴포넌트\n */\nconst Nose: React.FC<{\n position: [number, number, number];\n skinColor: number;\n bleeding: number;\n}> = ({ position, skinColor, bleeding }) => {\n const bloodColor = KOREAN_COLORS.ACCENT_RED;\n\n const noseMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: skinColor,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n transmission: 0,\n thickness: 0.05,\n ior: 1.4,\n sheen: 0.1,\n sheenRoughness: 0.8,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [skinColor]\n );\n\n useEffect(() => {\n return () => {\n noseMaterial.dispose();\n };\n }, [noseMaterial]);\n\n return (\n <group position={position} name=\"nose\">\n {/* Nose */}\n <mesh rotation={[Math.PI, 0, 0]}>\n <coneGeometry args={[0.03, 0.06, 8]} />\n <primitive object={noseMaterial} attach=\"material\" />\n </mesh>\n\n {/* Blood from nose */}\n {bleeding > 0 && (\n <mesh position={[0, -0.03, 0.01]}>\n <sphereGeometry args={[0.01, 6, 6]} />\n <meshBasicMaterial\n color={bloodColor}\n transparent\n opacity={bleeding * 0.8}\n />\n </mesh>\n )}\n </group>\n );\n};\n\n/**\n * Face3D component\n *\n * Main facial features component with head sphere, eyes, mouth, nose,\n * and damage visualization.\n *\n * @param props - Face3D props\n * @returns Face 3D mesh group\n *\n * @example\n * ```tsx\n * <Face3D\n * expression={FacialExpression.PAINED}\n * damage={damageState}\n * opponentPosition={new THREE.Vector3(5, 2, 0)}\n * headRotation={new THREE.Euler(0, 0, 0)}\n * enableEyeTracking={true}\n * />\n * ```\n *\n * @korean 얼굴3D\n */\nexport const Face3D: React.FC<Face3DProps> = ({\n expression,\n damage,\n opponentPosition,\n headRotation,\n enableEyeTracking = true,\n enableDamageVisuals = true,\n isMobile = false,\n skinColor = 0xffdbac, // Default skin tone\n}) => {\n const eyeDirection = useMemo(() => {\n if (!enableEyeTracking) {\n return new THREE.Vector3(0, 0, 1); // Look forward\n }\n\n if (\n !opponentPosition ||\n typeof opponentPosition.x !== \"number\" ||\n typeof opponentPosition.y !== \"number\" ||\n typeof opponentPosition.z !== \"number\"\n ) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\n \"Face3D: opponentPosition is not a valid THREE.Vector3; defaulting eye direction forward.\"\n );\n }\n return new THREE.Vector3(0, 0, 1);\n }\n\n const headPos = new THREE.Vector3(0, HEAD_POSITION_OFFSET, 0);\n const dir = new THREE.Vector3(\n opponentPosition.x - headPos.x,\n opponentPosition.y - headPos.y,\n opponentPosition.z - headPos.z\n );\n dir.normalize();\n\n return dir;\n }, [opponentPosition, enableEyeTracking]);\n\n const bruiseIntensity = useMemo(() => {\n if (!enableDamageVisuals) return 0;\n\n const avgBruise =\n (damage.leftCheekBruise +\n damage.rightCheekBruise +\n damage.foreheadBruise +\n damage.jawBruise) /\n 4;\n return avgBruise;\n }, [damage, enableDamageVisuals]);\n\n const headColor = useMemo(() => {\n if (bruiseIntensity === 0) return skinColor;\n\n const bruiseColor = 0x663366;\n return mixColors(skinColor, bruiseColor, bruiseIntensity * 0.3);\n }, [skinColor, bruiseIntensity]);\n\n const damageTexture = null;\n\n const headMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: headColor,\n map: damageTexture,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n envMapIntensity: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.15, // Facial skin has more sheen\n sheenRoughness: 0.7,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [headColor, damageTexture]\n );\n\n const earMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: headColor,\n roughness: 0.6,\n metalness: 0,\n clearcoat: 0.3,\n clearcoatRoughness: 0.6,\n transmission: 0,\n thickness: 0.05,\n ior: 1.4,\n sheen: 0.1,\n sheenRoughness: 0.8,\n emissive: new THREE.Color(0xff6040),\n emissiveIntensity: 0.02,\n }),\n [headColor]\n );\n\n useEffect(() => {\n return () => {\n headMaterial.dispose();\n earMaterial.dispose();\n };\n }, [headMaterial, earMaterial]);\n\n return (\n <group\n position={[0, HEAD_POSITION_OFFSET, 0]}\n rotation={headRotation}\n name=\"face3d\"\n >\n {/* Head sphere */}\n <mesh>\n <sphereGeometry args={[0.2, isMobile ? 12 : 16, isMobile ? 12 : 16]} />\n <primitive object={headMaterial} attach=\"material\" />\n </mesh>\n\n {/* Left eye */}\n <Eye\n position={[-0.08, 0.05, 0.15]}\n expression={expression}\n lookDirection={eyeDirection}\n swelling={enableDamageVisuals ? damage.leftEyeSwelling : 0}\n side=\"left\"\n />\n\n {/* Right eye */}\n <Eye\n position={[0.08, 0.05, 0.15]}\n expression={expression}\n lookDirection={eyeDirection}\n swelling={enableDamageVisuals ? damage.rightEyeSwelling : 0}\n side=\"right\"\n />\n\n {/* Mouth */}\n <Mouth\n position={[0, -0.05, 0.15]}\n expression={expression}\n bleeding={enableDamageVisuals ? damage.mouthBleeding : 0}\n />\n\n {/* Nose */}\n <Nose\n position={[0, 0.02, 0.18]}\n skinColor={skinColor}\n bleeding={enableDamageVisuals ? damage.noseBleeding : 0}\n />\n\n {/* Ears (simple geometric shapes) */}\n {!isMobile && (\n <>\n {/* Left ear */}\n <mesh position={[-0.2, 0, 0]} rotation={[0, 0, Math.PI / 6]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <primitive object={earMaterial} attach=\"material\" />\n </mesh>\n\n {/* Right ear */}\n <mesh position={[0.2, 0, 0]} rotation={[0, 0, -Math.PI / 6]}>\n <sphereGeometry args={[0.04, 8, 8]} />\n <primitive object={earMaterial} attach=\"material\" />\n </mesh>\n </>\n )}\n </group>\n );\n};\n\nexport default Face3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Foot3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Foot3D.tsx"],"sourcesContent":["/**\n * Foot3D component with anatomically accurate foot geometry\n *\n * Renders detailed 3D foot with proper dimensions for martial arts stances\n * and kicks. Supports left/right feet with Korean skin tone coloring.\n *\n * Implements anatomically correct foot proportions:\n * - Length: ~26-29cm (varies by archetype)\n * - Width: ~10cm at widest point\n * - Height: ~8cm at ankle\n *\n * @module components/three/Foot3D\n * @category 3D Components\n * @korean 발3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Props for Foot3D component\n *\n * @korean 발3D속성\n */\nexport interface Foot3DProps {\n /**\n * Foot side (left or right)\n * @korean 발쪽\n */\n readonly side: \"left\" | \"right\";\n\n /**\n * Base skin color\n * @korean 피부색\n */\n readonly skinColor?: number;\n\n /**\n * Scale multiplier (based on archetype physical attributes)\n * @korean 크기배율\n */\n readonly scale?: number;\n\n /**\n * Whether foot is highlighted (e.g., during kicks)\n * @korean 표시여부\n */\n readonly isHighlighted?: boolean;\n}\n\n/**\n * Foot3D Component\n *\n * Complete foot geometry with anatomically correct dimensions suitable\n * for Korean martial arts stance visualization and kick animations.\n *\n * Design notes:\n * - Main foot body is box-shaped with rounded edges\n * - Toe area is slightly elevated and separated\n * - Heel is wider than toe area for stability\n * - Dimensions scale with archetype (Amsalja: smaller, Jojik: larger)\n *\n * @example\n * ```tsx\n * <Foot3D\n * side=\"right\"\n * skinColor={0xffdbac}\n * scale={1.0}\n * isHighlighted={false}\n * />\n * ```\n *\n * @korean 발3D컴포넌트\n */\nexport const Foot3D: React.FC<Foot3DProps> = ({\n side,\n skinColor = 0xffdbac,\n scale = 1.0,\n isHighlighted = false,\n}) => {\n const footDimensions = useMemo(() => {\n const footLength = 0.26 * scale; // 26cm base length\n const footWidth = 0.1 * scale; // 10cm width\n const footHeight = 0.08 * scale; // 8cm height at ankle\n\n const toeLength = footLength * 0.3;\n const toeWidth = footWidth * 0.9; // Slightly narrower than heel\n const toeHeight = footHeight * 0.6; // Lower profile\n\n const heelLength = footLength * 0.7;\n\n return {\n footLength,\n footWidth,\n footHeight,\n toeLength,\n toeWidth,\n toeHeight,\n heelLength,\n };\n }, [scale]);\n\n const footColor = useMemo(() => {\n if (isHighlighted) {\n return KOREAN_COLORS.ACCENT_GOLD;\n }\n return skinColor;\n }, [isHighlighted, skinColor]);\n\n const skinMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: footColor,\n metalness: 0,\n roughness: 0.8,\n clearcoat: 0.3,\n clearcoatRoughness: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.1, // Subtle skin sheen\n sheenRoughness: 0.8,\n emissive: new THREE.Color(footColor),\n emissiveIntensity: isHighlighted ? 0.3 : 0.02,\n }),\n [footColor, isHighlighted],\n );\n\n useEffect(() => {\n return () => {\n skinMaterial.dispose();\n };\n }, [skinMaterial]);\n\n return (\n <group name={`foot-3d-${side}`}>\n {/* Ankle connector - larger sphere that bridges shin body surface to foot */}\n <mesh\n position={[0, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-ankle-bridge-${side}`}\n >\n <sphereGeometry args={[footDimensions.footHeight * 0.6, 10, 10]} />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Ankle-to-heel transition cylinder */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.2,\n footDimensions.heelLength * 0.1,\n ]}\n castShadow\n receiveShadow\n name={`foot-ankle-transition-${side}`}\n >\n <cylinderGeometry\n args={[\n footDimensions.footHeight * 0.5, // Top radius matches ankle sphere\n footDimensions.footWidth * 0.45, // Bottom matches heel width\n footDimensions.footHeight * 0.4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Main heel/midfoot body - capsule for smooth organic shape */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.4,\n footDimensions.heelLength * 0.2,\n ]}\n rotation={[Math.PI / 2, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-heel-${side}`}\n >\n <capsuleGeometry\n args={[\n footDimensions.footWidth * 0.45, // Radius\n footDimensions.heelLength * 0.5, // Length (shorter than full heel - caps add length)\n 4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Toe area - capsule for rounded toe shape */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.35 + footDimensions.toeHeight * 0.2,\n footDimensions.heelLength * 0.7 + footDimensions.toeLength / 2,\n ]}\n rotation={[Math.PI / 2, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-toes-${side}`}\n >\n <capsuleGeometry\n args={[\n footDimensions.toeWidth * 0.35, // Narrower radius for tapered toes\n footDimensions.toeLength * 0.4, // Shorter length\n 4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n </group>\n );\n};\n\nexport default Foot3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAa,UAAiC,EAC5C,MACA,YAAY,UACZ,QAAQ,GACR,gBAAgB,YACZ;CACJ,MAAM,iBAAiB,cAAc;EACnC,MAAM,aAAa,MAAO;EAC1B,MAAM,YAAY,KAAM;EACxB,MAAM,aAAa,MAAO;EAQ1B,OAAO;GACL;GACA;GACA;GACA,WAVgB,aAAa;GAW7B,UAVe,YAAY;GAW3B,WAVgB,aAAa;GAW7B,YATiB,aAAa;
|
|
1
|
+
{"version":3,"file":"Foot3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Foot3D.tsx"],"sourcesContent":["/**\n * Foot3D component with anatomically accurate foot geometry\n *\n * Renders detailed 3D foot with proper dimensions for martial arts stances\n * and kicks. Supports left/right feet with Korean skin tone coloring.\n *\n * Implements anatomically correct foot proportions:\n * - Length: ~26-29cm (varies by archetype)\n * - Width: ~10cm at widest point\n * - Height: ~8cm at ankle\n *\n * @module components/three/Foot3D\n * @category 3D Components\n * @korean 발3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\n\n/**\n * Props for Foot3D component\n *\n * @korean 발3D속성\n */\nexport interface Foot3DProps {\n /**\n * Foot side (left or right)\n * @korean 발쪽\n */\n readonly side: \"left\" | \"right\";\n\n /**\n * Base skin color\n * @korean 피부색\n */\n readonly skinColor?: number;\n\n /**\n * Scale multiplier (based on archetype physical attributes)\n * @korean 크기배율\n */\n readonly scale?: number;\n\n /**\n * Whether foot is highlighted (e.g., during kicks)\n * @korean 표시여부\n */\n readonly isHighlighted?: boolean;\n}\n\n/**\n * Foot3D Component\n *\n * Complete foot geometry with anatomically correct dimensions suitable\n * for Korean martial arts stance visualization and kick animations.\n *\n * Design notes:\n * - Main foot body is box-shaped with rounded edges\n * - Toe area is slightly elevated and separated\n * - Heel is wider than toe area for stability\n * - Dimensions scale with archetype (Amsalja: smaller, Jojik: larger)\n *\n * @example\n * ```tsx\n * <Foot3D\n * side=\"right\"\n * skinColor={0xffdbac}\n * scale={1.0}\n * isHighlighted={false}\n * />\n * ```\n *\n * @korean 발3D컴포넌트\n */\nexport const Foot3D: React.FC<Foot3DProps> = ({\n side,\n skinColor = 0xffdbac,\n scale = 1.0,\n isHighlighted = false,\n}) => {\n const footDimensions = useMemo(() => {\n const footLength = 0.26 * scale; // 26cm base length\n const footWidth = 0.1 * scale; // 10cm width\n const footHeight = 0.08 * scale; // 8cm height at ankle\n\n const toeLength = footLength * 0.3;\n const toeWidth = footWidth * 0.9; // Slightly narrower than heel\n const toeHeight = footHeight * 0.6; // Lower profile\n\n const heelLength = footLength * 0.7;\n\n return {\n footLength,\n footWidth,\n footHeight,\n toeLength,\n toeWidth,\n toeHeight,\n heelLength,\n };\n }, [scale]);\n\n const footColor = useMemo(() => {\n if (isHighlighted) {\n return KOREAN_COLORS.ACCENT_GOLD;\n }\n return skinColor;\n }, [isHighlighted, skinColor]);\n\n const skinMaterial = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: footColor,\n metalness: 0,\n roughness: 0.8,\n clearcoat: 0.3,\n clearcoatRoughness: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.1, // Subtle skin sheen\n sheenRoughness: 0.8,\n emissive: new THREE.Color(footColor),\n emissiveIntensity: isHighlighted ? 0.3 : 0.02,\n }),\n [footColor, isHighlighted],\n );\n\n useEffect(() => {\n return () => {\n skinMaterial.dispose();\n };\n }, [skinMaterial]);\n\n return (\n <group name={`foot-3d-${side}`}>\n {/* Ankle connector - larger sphere that bridges shin body surface to foot */}\n <mesh\n position={[0, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-ankle-bridge-${side}`}\n >\n <sphereGeometry args={[footDimensions.footHeight * 0.6, 10, 10]} />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Ankle-to-heel transition cylinder */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.2,\n footDimensions.heelLength * 0.1,\n ]}\n castShadow\n receiveShadow\n name={`foot-ankle-transition-${side}`}\n >\n <cylinderGeometry\n args={[\n footDimensions.footHeight * 0.5, // Top radius matches ankle sphere\n footDimensions.footWidth * 0.45, // Bottom matches heel width\n footDimensions.footHeight * 0.4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Main heel/midfoot body - capsule for smooth organic shape */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.4,\n footDimensions.heelLength * 0.2,\n ]}\n rotation={[Math.PI / 2, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-heel-${side}`}\n >\n <capsuleGeometry\n args={[\n footDimensions.footWidth * 0.45, // Radius\n footDimensions.heelLength * 0.5, // Length (shorter than full heel - caps add length)\n 4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n\n {/* Toe area - capsule for rounded toe shape */}\n <mesh\n position={[\n 0,\n -footDimensions.footHeight * 0.35 + footDimensions.toeHeight * 0.2,\n footDimensions.heelLength * 0.7 + footDimensions.toeLength / 2,\n ]}\n rotation={[Math.PI / 2, 0, 0]}\n castShadow\n receiveShadow\n name={`foot-toes-${side}`}\n >\n <capsuleGeometry\n args={[\n footDimensions.toeWidth * 0.35, // Narrower radius for tapered toes\n footDimensions.toeLength * 0.4, // Shorter length\n 4,\n 8,\n ]}\n />\n <primitive object={skinMaterial} attach=\"material\" />\n </mesh>\n </group>\n );\n};\n\nexport default Foot3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAa,UAAiC,EAC5C,MACA,YAAY,UACZ,QAAQ,GACR,gBAAgB,YACZ;CACJ,MAAM,iBAAiB,cAAc;EACnC,MAAM,aAAa,MAAO;EAC1B,MAAM,YAAY,KAAM;EACxB,MAAM,aAAa,MAAO;EAQ1B,OAAO;GACL;GACA;GACA;GACA,WAVgB,aAAa;GAW7B,UAVe,YAAY;GAW3B,WAVgB,aAAa;GAW7B,YATiB,aAAa;EAUhC;CACF,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,YAAY,cAAc;EAC9B,IAAI,eACF,OAAO,cAAc;EAEvB,OAAO;CACT,GAAG,CAAC,eAAe,SAAS,CAAC;CAE7B,MAAM,eAAe,cAEjB,IAAI,MAAM,qBAAqB;EAC7B,OAAO;EACP,WAAW;EACX,WAAW;EACX,WAAW;EACX,oBAAoB;EACpB,cAAc;EACd,WAAW;EACX,KAAK;EACL,OAAO;EACP,gBAAgB;EAChB,UAAU,IAAI,MAAM,MAAM,SAAS;EACnC,mBAAmB,gBAAgB,KAAM;CAC3C,CAAC,GACH,CAAC,WAAW,aAAa,CAC3B;CAEA,gBAAgB;EACd,aAAa;GACX,aAAa,QAAQ;EACvB;CACF,GAAG,CAAC,YAAY,CAAC;CAEjB,OACE,qBAAC,SAAD;EAAO,MAAM,WAAW;YAAxB;GAEE,qBAAC,QAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,YAAA;IACA,eAAA;IACA,MAAM,qBAAqB;cAJ7B,CAME,oBAAC,kBAAD,EAAgB,MAAM;KAAC,eAAe,aAAa;KAAK;KAAI;IAAE,EAAI,CAAA,GAClE,oBAAC,aAAD;KAAW,QAAQ;KAAc,QAAO;IAAY,CAAA,CAChD;;GAGN,qBAAC,QAAD;IACE,UAAU;KACR;KACA,CAAC,eAAe,aAAa;KAC7B,eAAe,aAAa;IAC9B;IACA,YAAA;IACA,eAAA;IACA,MAAM,yBAAyB;cARjC,CAUE,oBAAC,oBAAD,EACE,MAAM;KACJ,eAAe,aAAa;KAC5B,eAAe,YAAY;KAC3B,eAAe,aAAa;KAC5B;IACF,EACD,CAAA,GACD,oBAAC,aAAD;KAAW,QAAQ;KAAc,QAAO;IAAY,CAAA,CAChD;;GAGN,qBAAC,QAAD;IACE,UAAU;KACR;KACA,CAAC,eAAe,aAAa;KAC7B,eAAe,aAAa;IAC9B;IACA,UAAU;KAAC,KAAK,KAAK;KAAG;KAAG;IAAC;IAC5B,YAAA;IACA,eAAA;IACA,MAAM,aAAa;cATrB,CAWE,oBAAC,mBAAD,EACE,MAAM;KACJ,eAAe,YAAY;KAC3B,eAAe,aAAa;KAC5B;KACA;IACF,EACD,CAAA,GACD,oBAAC,aAAD;KAAW,QAAQ;KAAc,QAAO;IAAY,CAAA,CAChD;;GAGN,qBAAC,QAAD;IACE,UAAU;KACR;KACA,CAAC,eAAe,aAAa,MAAO,eAAe,YAAY;KAC/D,eAAe,aAAa,KAAM,eAAe,YAAY;IAC/D;IACA,UAAU;KAAC,KAAK,KAAK;KAAG;KAAG;IAAC;IAC5B,YAAA;IACA,eAAA;IACA,MAAM,aAAa;cATrB,CAWE,oBAAC,mBAAD,EACE,MAAM;KACJ,eAAe,WAAW;KAC1B,eAAe,YAAY;KAC3B;KACA;IACF,EACD,CAAA,GACD,oBAAC,aAAD;KAAW,QAAQ;KAAc,QAAO;IAAY,CAAA,CAChD;;EACD;;AAEX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hand3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Hand3D.tsx"],"sourcesContent":["/**\n * Hand3D component with finger geometry for martial arts techniques\n *\n * Renders detailed hand with palm and 5 fingers, supporting multiple\n * Korean martial arts hand poses (Fist, Knife-hand, Spear-hand, etc.).\n *\n * Implements LOD (Level of Detail) for performance optimization:\n * - High detail (<5 units): Full finger bones (4 segments per finger)\n * - Medium detail (5-15 units): Simplified fingers (3 segments)\n * - Low detail (>15 units): No finger detail (hand as single unit)\n *\n * @module components/three/Hand3D\n * @category 3D Components\n * @korean 손3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type {\n FingerCurl,\n HandLODConfig,\n HandPoseType,\n HandSide,\n} from \"../../../../types/hand-animation\";\n\n/**\n * Props for Hand3D component\n *\n * @korean 손3D속성\n */\nexport interface Hand3DProps {\n /**\n * Hand side (left or right)\n * @korean 손쪽\n */\n readonly side: HandSide;\n\n /**\n * Current hand pose\n * @korean 현재손자세\n */\n readonly pose: HandPoseType;\n\n /**\n * Finger curl amounts (0-1 per finger)\n * @korean 손가락구부림\n */\n readonly fingerCurl: FingerCurl;\n\n /**\n * Distance from camera for LOD\n * @korean 카메라거리\n */\n readonly distanceFromCamera: number;\n\n /**\n * Wrist rotation\n * @korean 손목회전\n */\n readonly wristRotation: THREE.Euler;\n\n /**\n * Whether hand is highlighted for vital point targeting\n * @korean 표시여부\n */\n readonly isHighlighted?: boolean;\n\n /**\n * Highlight mode for striking surfaces\n * @korean 표시모드\n */\n readonly highlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\"\n | null;\n\n /**\n * Base skin color\n * @korean 피부색\n */\n readonly skinColor?: number;\n\n /**\n * Scale multiplier\n * @korean 크기배율\n */\n readonly scale?: number;\n}\n\n/**\n * Determine LOD config based on camera distance\n *\n * @param distance - Distance from camera\n * @returns LOD configuration\n * @korean LOD설정결정\n */\nconst getLODConfig = (distance: number): HandLODConfig => {\n if (distance < 5) {\n return {\n detailLevel: \"high\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: true,\n fingerSegments: 4,\n };\n } else if (distance < 15) {\n return {\n detailLevel: \"medium\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: true,\n fingerSegments: 3,\n };\n } else {\n return {\n detailLevel: \"low\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: false,\n fingerSegments: 0,\n };\n }\n};\n\n/**\n * Finger segment component\n *\n * Renders a single finger segment (proximal, intermediate, or distal phalanx).\n *\n * @korean 손가락세그먼트컴포넌트\n */\ninterface FingerSegmentProps {\n readonly position: [number, number, number];\n readonly rotation: [number, number, number];\n readonly length: number;\n readonly radius: number;\n readonly color: number;\n}\n\nconst FingerSegment: React.FC<FingerSegmentProps> = ({\n position,\n rotation,\n length,\n radius,\n color,\n}) => {\n const material = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: color,\n metalness: 0,\n roughness: 0.6,\n clearcoat: 0.3,\n clearcoatRoughness: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.1, // Subtle skin sheen\n sheenRoughness: 0.8,\n emissive: new THREE.Color(color),\n emissiveIntensity: 0.02,\n }),\n [color],\n );\n\n useEffect(() => {\n return () => {\n material.dispose();\n };\n }, [material]);\n\n return (\n <mesh position={position} rotation={rotation} castShadow receiveShadow>\n <capsuleGeometry args={[radius, length, 4, 8]} />\n <primitive object={material} attach=\"material\" />\n </mesh>\n );\n};\n\n/**\n * Single finger component with curl animation\n *\n * @korean 손가락컴포넌트\n */\ninterface FingerProps {\n readonly fingerName: string;\n readonly basePosition: [number, number, number];\n readonly curl: number;\n readonly segments: number;\n readonly isHighlighted: boolean;\n readonly skinColor: number;\n}\n\nconst Finger: React.FC<FingerProps> = ({\n fingerName,\n basePosition,\n curl,\n segments,\n isHighlighted,\n skinColor,\n}) => {\n const dimensions = useMemo(() => {\n const baseLength =\n fingerName === \"thumb\"\n ? 0.025 // Thumb proximal is shorter\n : fingerName === \"pinky\"\n ? 0.022 // Pinky is shorter\n : 0.03; // Other fingers\n\n const baseRadius =\n fingerName === \"pinky\"\n ? 0.006 // Pinky is thinner\n : fingerName === \"thumb\"\n ? 0.009 // Thumb is thicker\n : 0.007;\n\n return {\n proximalLength: baseLength,\n intermediateLength: baseLength * 0.75,\n distalLength: baseLength * 0.55,\n radius: baseRadius,\n };\n }, [fingerName]);\n\n const PROXIMAL_CURL_FACTOR = 0.5; // Proximal joint bends less (0-90 degrees)\n const INTERMEDIATE_CURL_FACTOR = 0.7; // Middle joint bends moderately (0-126 degrees)\n const DISTAL_CURL_FACTOR = 1.0; // Distal joint bends most (0-180 degrees)\n\n const proximalCurl = curl * PROXIMAL_CURL_FACTOR * Math.PI;\n const intermediateCurl = curl * INTERMEDIATE_CURL_FACTOR * Math.PI;\n const distalCurl = curl * DISTAL_CURL_FACTOR * Math.PI;\n\n const highlightColor = isHighlighted ? KOREAN_COLORS.ACCENT_RED : skinColor;\n\n return (\n <group position={basePosition}>\n {/* Proximal phalanx (knuckle joint) */}\n <FingerSegment\n position={[0, dimensions.proximalLength / 2, 0]}\n rotation={[0, 0, proximalCurl]}\n length={dimensions.proximalLength}\n radius={dimensions.radius}\n color={highlightColor}\n />\n\n {/* Intermediate phalanx (middle joint) - skip for thumb or low detail */}\n {segments >= 4 && fingerName !== \"thumb\" && (\n <FingerSegment\n position={[\n 0,\n dimensions.proximalLength + dimensions.intermediateLength / 2,\n 0,\n ]}\n rotation={[0, 0, intermediateCurl]}\n length={dimensions.intermediateLength}\n radius={dimensions.radius * 0.9}\n color={highlightColor}\n />\n )}\n\n {/* Distal phalanx (fingertip) */}\n <FingerSegment\n position={[\n 0,\n dimensions.proximalLength +\n (fingerName !== \"thumb\" && segments >= 4\n ? dimensions.intermediateLength\n : 0) +\n dimensions.distalLength / 2,\n 0,\n ]}\n rotation={[0, 0, distalCurl]}\n length={dimensions.distalLength}\n radius={dimensions.radius * 0.7}\n color={highlightColor}\n />\n </group>\n );\n};\n\n/**\n * Hand3D Component\n *\n * Complete hand with palm and 5 fingers, supporting Korean martial arts\n * hand poses with LOD optimization for performance.\n *\n * @example\n * ```tsx\n * <Hand3D\n * side=\"right\"\n * pose={HandPoseType.FIST}\n * fingerCurl={{\n * thumb: 0.8,\n * index: 1.0,\n * middle: 1.0,\n * ring: 1.0,\n * pinky: 1.0,\n * }}\n * distanceFromCamera={3}\n * wristRotation={new THREE.Euler(0, 0, 0)}\n * isHighlighted={false}\n * highlightMode=\"knuckles\"\n * />\n * ```\n *\n * @korean 손3D컴포넌트\n */\nexport const Hand3D: React.FC<Hand3DProps> = ({\n side,\n fingerCurl,\n distanceFromCamera,\n wristRotation,\n isHighlighted = false,\n highlightMode = null,\n skinColor = 0xffdbac,\n scale = 1.0,\n}) => {\n const lodConfig = useMemo(\n () => getLODConfig(distanceFromCamera),\n [distanceFromCamera],\n );\n\n const sideMultiplier = side === \"left\" ? -1 : 1;\n\n const palmWidth = 0.085 * scale; // 8.5cm palm width\n const palmLength = 0.095 * scale; // 9.5cm palm/metacarpal length\n const palmThickness = 0.025 * scale; // 2.5cm palm thickness\n\n const highlightKnuckles = highlightMode === \"knuckles\";\n const highlightPalm = highlightMode === \"palm\";\n const highlightKnifeEdge = highlightMode === \"knife_edge\";\n const highlightFingertips = highlightMode === \"fingertips\";\n\n const palmColor =\n isHighlighted && highlightPalm ? KOREAN_COLORS.ACCENT_GOLD : skinColor;\n\n return (\n <group\n rotation={[wristRotation.x, wristRotation.y, wristRotation.z]}\n name={`hand-3d-${side}`}\n >\n {/* Wrist connector - smooth sphere bridging forearm to palm */}\n <mesh\n position={[0, -palmLength * 0.45, 0]}\n castShadow\n receiveShadow\n name={`hand-wrist-bridge-${side}`}\n >\n <sphereGeometry args={[palmWidth * 0.35, 8, 8]} />\n <meshPhysicalMaterial\n color={skinColor}\n metalness={0}\n roughness={0.6}\n clearcoat={0.3}\n clearcoatRoughness={0.5}\n transmission={0}\n thickness={0.1}\n ior={1.4}\n sheen={0.1}\n sheenRoughness={0.8}\n emissive={new THREE.Color(0xff6040)}\n emissiveIntensity={0.02}\n />\n </mesh>\n\n {/* Palm */}\n <mesh castShadow receiveShadow name={`hand-palm-${side}`}>\n <boxGeometry args={[palmWidth, palmLength, palmThickness]} />\n <meshPhysicalMaterial\n color={palmColor}\n metalness={0}\n roughness={0.6}\n clearcoat={0.3}\n clearcoatRoughness={0.5}\n transmission={0}\n thickness={0.1}\n ior={1.4} // Index of refraction for skin\n sheen={0.1} // Subtle skin sheen\n sheenRoughness={0.8}\n emissive={new THREE.Color(0xff6040)}\n emissiveIntensity={0.02}\n />\n </mesh>\n\n {/* Knife edge highlight (pinky side of hand) - emissive highlight for visibility */}\n {isHighlighted && highlightKnifeEdge && (\n <mesh\n position={[(-palmWidth / 2) * sideMultiplier, 0, 0]}\n castShadow\n receiveShadow\n name={`hand-knife-edge-${side}`}\n >\n <boxGeometry args={[0.005, palmLength, palmThickness]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n metalness={0.8}\n roughness={0.2}\n clearcoat={0.8}\n clearcoatRoughness={0.1}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={2.0}\n transmission={0}\n thickness={0.05}\n />\n </mesh>\n )}\n\n {/* Render fingers based on LOD */}\n {lodConfig.renderFingers && (\n <>\n {/* Thumb - offset toward palm center and forward */}\n <Finger\n fingerName=\"thumb\"\n basePosition={[\n 0.015 * sideMultiplier * scale,\n palmLength / 2,\n palmThickness / 2,\n ]}\n curl={fingerCurl.thumb}\n segments={3} // Thumb has no intermediate phalanx\n isHighlighted={isHighlighted && highlightFingertips}\n skinColor={skinColor}\n />\n\n {/* Index finger */}\n <Finger\n fingerName=\"index\"\n basePosition={[0.015 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.index}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Middle finger */}\n <Finger\n fingerName=\"middle\"\n basePosition={[0.005 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.middle}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Ring finger */}\n <Finger\n fingerName=\"ring\"\n basePosition={[-0.005 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.ring}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Pinky finger */}\n <Finger\n fingerName=\"pinky\"\n basePosition={[-0.015 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.pinky}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n </>\n )}\n\n {/* Low detail: just palm, no fingers */}\n {!lodConfig.renderFingers && (\n <mesh\n position={[0, palmLength / 2, 0]}\n castShadow\n receiveShadow\n name={`hand-simple-${side}`}\n >\n <capsuleGeometry args={[palmWidth / 2, palmLength * 0.3, 4, 8]} />\n <meshStandardMaterial\n color={skinColor}\n metalness={0.1}\n roughness={0.8}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Hand3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Hand3D.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/Hand3D.tsx"],"sourcesContent":["/**\n * Hand3D component with finger geometry for martial arts techniques\n *\n * Renders detailed hand with palm and 5 fingers, supporting multiple\n * Korean martial arts hand poses (Fist, Knife-hand, Spear-hand, etc.).\n *\n * Implements LOD (Level of Detail) for performance optimization:\n * - High detail (<5 units): Full finger bones (4 segments per finger)\n * - Medium detail (5-15 units): Simplified fingers (3 segments)\n * - Low detail (>15 units): No finger detail (hand as single unit)\n *\n * @module components/three/Hand3D\n * @category 3D Components\n * @korean 손3D컴포넌트\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type {\n FingerCurl,\n HandLODConfig,\n HandPoseType,\n HandSide,\n} from \"../../../../types/hand-animation\";\n\n/**\n * Props for Hand3D component\n *\n * @korean 손3D속성\n */\nexport interface Hand3DProps {\n /**\n * Hand side (left or right)\n * @korean 손쪽\n */\n readonly side: HandSide;\n\n /**\n * Current hand pose\n * @korean 현재손자세\n */\n readonly pose: HandPoseType;\n\n /**\n * Finger curl amounts (0-1 per finger)\n * @korean 손가락구부림\n */\n readonly fingerCurl: FingerCurl;\n\n /**\n * Distance from camera for LOD\n * @korean 카메라거리\n */\n readonly distanceFromCamera: number;\n\n /**\n * Wrist rotation\n * @korean 손목회전\n */\n readonly wristRotation: THREE.Euler;\n\n /**\n * Whether hand is highlighted for vital point targeting\n * @korean 표시여부\n */\n readonly isHighlighted?: boolean;\n\n /**\n * Highlight mode for striking surfaces\n * @korean 표시모드\n */\n readonly highlightMode?:\n | \"none\"\n | \"knuckles\"\n | \"palm\"\n | \"knife_edge\"\n | \"fingertips\"\n | null;\n\n /**\n * Base skin color\n * @korean 피부색\n */\n readonly skinColor?: number;\n\n /**\n * Scale multiplier\n * @korean 크기배율\n */\n readonly scale?: number;\n}\n\n/**\n * Determine LOD config based on camera distance\n *\n * @param distance - Distance from camera\n * @returns LOD configuration\n * @korean LOD설정결정\n */\nconst getLODConfig = (distance: number): HandLODConfig => {\n if (distance < 5) {\n return {\n detailLevel: \"high\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: true,\n fingerSegments: 4,\n };\n } else if (distance < 15) {\n return {\n detailLevel: \"medium\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: true,\n fingerSegments: 3,\n };\n } else {\n return {\n detailLevel: \"low\",\n distanceThresholds: { high: 5, medium: 15, low: 100 },\n renderFingers: false,\n fingerSegments: 0,\n };\n }\n};\n\n/**\n * Finger segment component\n *\n * Renders a single finger segment (proximal, intermediate, or distal phalanx).\n *\n * @korean 손가락세그먼트컴포넌트\n */\ninterface FingerSegmentProps {\n readonly position: [number, number, number];\n readonly rotation: [number, number, number];\n readonly length: number;\n readonly radius: number;\n readonly color: number;\n}\n\nconst FingerSegment: React.FC<FingerSegmentProps> = ({\n position,\n rotation,\n length,\n radius,\n color,\n}) => {\n const material = useMemo(\n () =>\n new THREE.MeshPhysicalMaterial({\n color: color,\n metalness: 0,\n roughness: 0.6,\n clearcoat: 0.3,\n clearcoatRoughness: 0.5,\n transmission: 0,\n thickness: 0.1,\n ior: 1.4, // Index of refraction for skin\n sheen: 0.1, // Subtle skin sheen\n sheenRoughness: 0.8,\n emissive: new THREE.Color(color),\n emissiveIntensity: 0.02,\n }),\n [color],\n );\n\n useEffect(() => {\n return () => {\n material.dispose();\n };\n }, [material]);\n\n return (\n <mesh position={position} rotation={rotation} castShadow receiveShadow>\n <capsuleGeometry args={[radius, length, 4, 8]} />\n <primitive object={material} attach=\"material\" />\n </mesh>\n );\n};\n\n/**\n * Single finger component with curl animation\n *\n * @korean 손가락컴포넌트\n */\ninterface FingerProps {\n readonly fingerName: string;\n readonly basePosition: [number, number, number];\n readonly curl: number;\n readonly segments: number;\n readonly isHighlighted: boolean;\n readonly skinColor: number;\n}\n\nconst Finger: React.FC<FingerProps> = ({\n fingerName,\n basePosition,\n curl,\n segments,\n isHighlighted,\n skinColor,\n}) => {\n const dimensions = useMemo(() => {\n const baseLength =\n fingerName === \"thumb\"\n ? 0.025 // Thumb proximal is shorter\n : fingerName === \"pinky\"\n ? 0.022 // Pinky is shorter\n : 0.03; // Other fingers\n\n const baseRadius =\n fingerName === \"pinky\"\n ? 0.006 // Pinky is thinner\n : fingerName === \"thumb\"\n ? 0.009 // Thumb is thicker\n : 0.007;\n\n return {\n proximalLength: baseLength,\n intermediateLength: baseLength * 0.75,\n distalLength: baseLength * 0.55,\n radius: baseRadius,\n };\n }, [fingerName]);\n\n const PROXIMAL_CURL_FACTOR = 0.5; // Proximal joint bends less (0-90 degrees)\n const INTERMEDIATE_CURL_FACTOR = 0.7; // Middle joint bends moderately (0-126 degrees)\n const DISTAL_CURL_FACTOR = 1.0; // Distal joint bends most (0-180 degrees)\n\n const proximalCurl = curl * PROXIMAL_CURL_FACTOR * Math.PI;\n const intermediateCurl = curl * INTERMEDIATE_CURL_FACTOR * Math.PI;\n const distalCurl = curl * DISTAL_CURL_FACTOR * Math.PI;\n\n const highlightColor = isHighlighted ? KOREAN_COLORS.ACCENT_RED : skinColor;\n\n return (\n <group position={basePosition}>\n {/* Proximal phalanx (knuckle joint) */}\n <FingerSegment\n position={[0, dimensions.proximalLength / 2, 0]}\n rotation={[0, 0, proximalCurl]}\n length={dimensions.proximalLength}\n radius={dimensions.radius}\n color={highlightColor}\n />\n\n {/* Intermediate phalanx (middle joint) - skip for thumb or low detail */}\n {segments >= 4 && fingerName !== \"thumb\" && (\n <FingerSegment\n position={[\n 0,\n dimensions.proximalLength + dimensions.intermediateLength / 2,\n 0,\n ]}\n rotation={[0, 0, intermediateCurl]}\n length={dimensions.intermediateLength}\n radius={dimensions.radius * 0.9}\n color={highlightColor}\n />\n )}\n\n {/* Distal phalanx (fingertip) */}\n <FingerSegment\n position={[\n 0,\n dimensions.proximalLength +\n (fingerName !== \"thumb\" && segments >= 4\n ? dimensions.intermediateLength\n : 0) +\n dimensions.distalLength / 2,\n 0,\n ]}\n rotation={[0, 0, distalCurl]}\n length={dimensions.distalLength}\n radius={dimensions.radius * 0.7}\n color={highlightColor}\n />\n </group>\n );\n};\n\n/**\n * Hand3D Component\n *\n * Complete hand with palm and 5 fingers, supporting Korean martial arts\n * hand poses with LOD optimization for performance.\n *\n * @example\n * ```tsx\n * <Hand3D\n * side=\"right\"\n * pose={HandPoseType.FIST}\n * fingerCurl={{\n * thumb: 0.8,\n * index: 1.0,\n * middle: 1.0,\n * ring: 1.0,\n * pinky: 1.0,\n * }}\n * distanceFromCamera={3}\n * wristRotation={new THREE.Euler(0, 0, 0)}\n * isHighlighted={false}\n * highlightMode=\"knuckles\"\n * />\n * ```\n *\n * @korean 손3D컴포넌트\n */\nexport const Hand3D: React.FC<Hand3DProps> = ({\n side,\n fingerCurl,\n distanceFromCamera,\n wristRotation,\n isHighlighted = false,\n highlightMode = null,\n skinColor = 0xffdbac,\n scale = 1.0,\n}) => {\n const lodConfig = useMemo(\n () => getLODConfig(distanceFromCamera),\n [distanceFromCamera],\n );\n\n const sideMultiplier = side === \"left\" ? -1 : 1;\n\n const palmWidth = 0.085 * scale; // 8.5cm palm width\n const palmLength = 0.095 * scale; // 9.5cm palm/metacarpal length\n const palmThickness = 0.025 * scale; // 2.5cm palm thickness\n\n const highlightKnuckles = highlightMode === \"knuckles\";\n const highlightPalm = highlightMode === \"palm\";\n const highlightKnifeEdge = highlightMode === \"knife_edge\";\n const highlightFingertips = highlightMode === \"fingertips\";\n\n const palmColor =\n isHighlighted && highlightPalm ? KOREAN_COLORS.ACCENT_GOLD : skinColor;\n\n return (\n <group\n rotation={[wristRotation.x, wristRotation.y, wristRotation.z]}\n name={`hand-3d-${side}`}\n >\n {/* Wrist connector - smooth sphere bridging forearm to palm */}\n <mesh\n position={[0, -palmLength * 0.45, 0]}\n castShadow\n receiveShadow\n name={`hand-wrist-bridge-${side}`}\n >\n <sphereGeometry args={[palmWidth * 0.35, 8, 8]} />\n <meshPhysicalMaterial\n color={skinColor}\n metalness={0}\n roughness={0.6}\n clearcoat={0.3}\n clearcoatRoughness={0.5}\n transmission={0}\n thickness={0.1}\n ior={1.4}\n sheen={0.1}\n sheenRoughness={0.8}\n emissive={new THREE.Color(0xff6040)}\n emissiveIntensity={0.02}\n />\n </mesh>\n\n {/* Palm */}\n <mesh castShadow receiveShadow name={`hand-palm-${side}`}>\n <boxGeometry args={[palmWidth, palmLength, palmThickness]} />\n <meshPhysicalMaterial\n color={palmColor}\n metalness={0}\n roughness={0.6}\n clearcoat={0.3}\n clearcoatRoughness={0.5}\n transmission={0}\n thickness={0.1}\n ior={1.4} // Index of refraction for skin\n sheen={0.1} // Subtle skin sheen\n sheenRoughness={0.8}\n emissive={new THREE.Color(0xff6040)}\n emissiveIntensity={0.02}\n />\n </mesh>\n\n {/* Knife edge highlight (pinky side of hand) - emissive highlight for visibility */}\n {isHighlighted && highlightKnifeEdge && (\n <mesh\n position={[(-palmWidth / 2) * sideMultiplier, 0, 0]}\n castShadow\n receiveShadow\n name={`hand-knife-edge-${side}`}\n >\n <boxGeometry args={[0.005, palmLength, palmThickness]} />\n <meshPhysicalMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n metalness={0.8}\n roughness={0.2}\n clearcoat={0.8}\n clearcoatRoughness={0.1}\n emissive={KOREAN_COLORS.ACCENT_GOLD}\n emissiveIntensity={2.0}\n transmission={0}\n thickness={0.05}\n />\n </mesh>\n )}\n\n {/* Render fingers based on LOD */}\n {lodConfig.renderFingers && (\n <>\n {/* Thumb - offset toward palm center and forward */}\n <Finger\n fingerName=\"thumb\"\n basePosition={[\n 0.015 * sideMultiplier * scale,\n palmLength / 2,\n palmThickness / 2,\n ]}\n curl={fingerCurl.thumb}\n segments={3} // Thumb has no intermediate phalanx\n isHighlighted={isHighlighted && highlightFingertips}\n skinColor={skinColor}\n />\n\n {/* Index finger */}\n <Finger\n fingerName=\"index\"\n basePosition={[0.015 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.index}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Middle finger */}\n <Finger\n fingerName=\"middle\"\n basePosition={[0.005 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.middle}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Ring finger */}\n <Finger\n fingerName=\"ring\"\n basePosition={[-0.005 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.ring}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n\n {/* Pinky finger */}\n <Finger\n fingerName=\"pinky\"\n basePosition={[-0.015 * sideMultiplier * scale, palmLength / 2, 0]}\n curl={fingerCurl.pinky}\n segments={lodConfig.fingerSegments}\n isHighlighted={\n isHighlighted && (highlightFingertips || highlightKnuckles)\n }\n skinColor={skinColor}\n />\n </>\n )}\n\n {/* Low detail: just palm, no fingers */}\n {!lodConfig.renderFingers && (\n <mesh\n position={[0, palmLength / 2, 0]}\n castShadow\n receiveShadow\n name={`hand-simple-${side}`}\n >\n <capsuleGeometry args={[palmWidth / 2, palmLength * 0.3, 4, 8]} />\n <meshStandardMaterial\n color={skinColor}\n metalness={0.1}\n roughness={0.8}\n />\n </mesh>\n )}\n </group>\n );\n};\n\nexport default Hand3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionFeedback.js","names":[],"sources":["../../../../../src/components/shared/three/effects/ActionFeedback.tsx"],"sourcesContent":["/**\n * ActionFeedback - Combat action feedback display component\n *\n * Displays action indicators like \"Perfect!\", \"Critical!\", \"Blocked\", \"Dodged\",\n * and technique names with Korean-English bilingual text.\n *\n * Uses Html overlay from @react-three/drei for rendering within 3D scenes.\n *\n * @module components/shared/three/effects/ActionFeedback\n * @category Shared Effects\n * @korean 액션피드백\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport {\n ActionFeedback as ActionFeedbackData,\n ActionFeedbackType,\n} from \"../../../../hooks/useActionFeedback\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/** Fade in completes at 20% of total duration */\nconst FADE_IN_THRESHOLD = 0.2;\n/** Fade out begins at 80% of total duration */\nconst FADE_OUT_THRESHOLD = 0.8;\n\n/**\n * Props for the ActionFeedback component\n */\nexport interface ActionFeedbackProps {\n /** Array of action feedbacks to display */\n readonly feedbacks: readonly ActionFeedbackData[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Arena bounds for 3D positioning (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n /** Duration of animation in ms (default: 1200) */\n readonly animationDuration?: number;\n}\n\n/**\n * Props for technique name display\n */\nexport interface TechniqueNameProps {\n /** Korean technique name */\n readonly korean: string;\n /** English technique name */\n readonly english: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Animation duration in ms */\n readonly duration?: number;\n /** Callback when animation completes */\n readonly onComplete?: () => void;\n}\n\n/**\n * Get color based on feedback type\n */\nfunction getFeedbackColor(type: ActionFeedbackType): string {\n switch (type) {\n case \"perfect\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case \"critical\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n case \"blocked\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN);\n case \"dodged\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GREEN);\n case \"technique\":\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n case \"combo_milestone\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n default:\n return hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY);\n }\n}\n\n/**\n * Get glow color based on feedback type\n */\nfunction getGlowColor(type: ActionFeedbackType): string {\n switch (type) {\n case \"perfect\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n case \"critical\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n case \"blocked\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_CYAN, 0.6);\n case \"dodged\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6);\n case \"technique\":\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n case \"combo_milestone\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n default:\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.4);\n }\n}\n\n/**\n * Individual action feedback display\n */\ninterface SingleFeedbackProps {\n readonly feedback: ActionFeedbackData;\n readonly isMobile: boolean;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly animationDuration: number;\n}\n\nconst SingleFeedback: React.FC<SingleFeedbackProps> = ({\n feedback,\n isMobile,\n arenaBounds,\n animationDuration,\n}) => {\n const [progress, setProgress] = useState(0);\n const startTimeRef = useRef(feedback.timestamp);\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, feedback.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, feedback.position.y));\n \n const x = clampedX; // Meter position X\n const y = 2.5 + progress * 1.5; // Float upward\n const z = clampedZ; // Meter position Z (depth)\n const position3D: [number, number, number] = [x, y, z];\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const newProgress = Math.min(elapsed / animationDuration, 1);\n setProgress(newProgress);\n });\n\n if (progress >= 1) return null;\n\n const opacity = 1 - progress;\n const scale = 1 + (progress < 0.2 ? progress * 2 : (1 - progress) * 0.5);\n const fontSize = isMobile ? 18 : 24;\n const color = getFeedbackColor(feedback.type);\n const glowColor = getGlowColor(feedback.type);\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid={`action-feedback-${feedback.id}`}\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n opacity,\n transform: `scale(${scale})`,\n textShadow: `\n 0 0 10px ${glowColor},\n 0 0 20px ${glowColor},\n 2px 2px 4px rgba(0, 0, 0, 0.9)\n `,\n whiteSpace: \"nowrap\",\n userSelect: \"none\",\n textAlign: \"center\",\n }}\n >\n {feedback.textKorean} | {feedback.text}\n </div>\n </Html>\n );\n};\n\n/**\n * ActionFeedback Component\n *\n * Renders multiple action feedback indicators in the 3D scene.\n * Each indicator floats upward and fades out over time.\n *\n * @example\n * ```tsx\n * <ActionFeedback\n * feedbacks={actionFeedbacks}\n * isMobile={isMobile}\n * arenaBounds={arenaBounds}\n * />\n * ```\n */\nexport const ActionFeedback: React.FC<ActionFeedbackProps> = ({\n feedbacks,\n isMobile = false,\n arenaBounds = DEFAULT_PHYSICS_ARENA_BOUNDS,\n animationDuration = 1200,\n}) => {\n const visibleFeedbacks = useMemo(() => [...feedbacks], [feedbacks]);\n\n return (\n <group data-testid=\"action-feedback-container\">\n {visibleFeedbacks.map((feedback) => (\n <SingleFeedback\n key={feedback.id}\n feedback={feedback}\n isMobile={isMobile}\n arenaBounds={arenaBounds}\n animationDuration={animationDuration}\n />\n ))}\n </group>\n );\n};\n\n/**\n * TechniqueName Component\n *\n * Displays the current technique name in Korean and English.\n * Appears at the center of the screen with a dramatic animation.\n *\n * @example\n * ```tsx\n * <TechniqueName\n * korean=\"천둥벽력\"\n * english=\"Thunder Strike\"\n * isMobile={isMobile}\n * duration={2000}\n * />\n * ```\n */\nexport const TechniqueName: React.FC<TechniqueNameProps> = ({\n korean,\n english,\n isMobile = false,\n duration = 2000,\n onComplete,\n}) => {\n const [opacity, setOpacity] = useState(0);\n const [scale, setScale] = useState(0.5);\n const [startTime] = useState(() => Date.now());\n const startTimeRef = useRef(startTime);\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const progress = Math.min(elapsed / duration, 1);\n\n if (progress < FADE_IN_THRESHOLD) {\n const fadeInProgress = progress / FADE_IN_THRESHOLD;\n setOpacity(fadeInProgress);\n setScale(0.5 + fadeInProgress * 0.5);\n } else if (progress < FADE_OUT_THRESHOLD) {\n setOpacity(1);\n setScale(1);\n } else {\n const fadeOutProgress =\n (progress - FADE_OUT_THRESHOLD) / (1 - FADE_OUT_THRESHOLD);\n setOpacity(1 - fadeOutProgress);\n setScale(1 + fadeOutProgress * 0.2);\n }\n\n if (progress >= 1 && onComplete) {\n onComplete();\n }\n });\n\n const position3D: [number, number, number] = [0, 3.5, 0];\n\n const mainFontSize = isMobile ? 28 : 42;\n const subFontSize = isMobile ? 16 : 24;\n const color = hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n const glowColor = hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid=\"technique-name\"\n style={{\n textAlign: \"center\",\n opacity,\n transform: `scale(${scale})`,\n transition: \"transform 0.1s ease-out\",\n }}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: `${mainFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: `\n 0 0 15px ${glowColor},\n 0 0 30px ${glowColor},\n 3px 3px 6px rgba(0, 0, 0, 0.9)\n `,\n letterSpacing: \"4px\",\n }}\n >\n {korean}\n </div>\n\n {/* Divider */}\n <div\n style={{\n width: \"60px\",\n height: \"2px\",\n background: `linear-gradient(90deg, transparent, ${color}, transparent)`,\n margin: \"8px auto\",\n }}\n />\n\n {/* English name */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textShadow: \"2px 2px 4px rgba(0, 0, 0, 0.8)\",\n letterSpacing: \"2px\",\n textTransform: \"uppercase\",\n }}\n >\n {english}\n </div>\n </div>\n </Html>\n );\n};\n\nexport default ActionFeedback;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionFeedback.js","names":[],"sources":["../../../../../src/components/shared/three/effects/ActionFeedback.tsx"],"sourcesContent":["/**\n * ActionFeedback - Combat action feedback display component\n *\n * Displays action indicators like \"Perfect!\", \"Critical!\", \"Blocked\", \"Dodged\",\n * and technique names with Korean-English bilingual text.\n *\n * Uses Html overlay from @react-three/drei for rendering within 3D scenes.\n *\n * @module components/shared/three/effects/ActionFeedback\n * @category Shared Effects\n * @korean 액션피드백\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport {\n ActionFeedback as ActionFeedbackData,\n ActionFeedbackType,\n} from \"../../../../hooks/useActionFeedback\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\n\n/** Fade in completes at 20% of total duration */\nconst FADE_IN_THRESHOLD = 0.2;\n/** Fade out begins at 80% of total duration */\nconst FADE_OUT_THRESHOLD = 0.8;\n\n/**\n * Props for the ActionFeedback component\n */\nexport interface ActionFeedbackProps {\n /** Array of action feedbacks to display */\n readonly feedbacks: readonly ActionFeedbackData[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Arena bounds for 3D positioning (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n /** Duration of animation in ms (default: 1200) */\n readonly animationDuration?: number;\n}\n\n/**\n * Props for technique name display\n */\nexport interface TechniqueNameProps {\n /** Korean technique name */\n readonly korean: string;\n /** English technique name */\n readonly english: string;\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Animation duration in ms */\n readonly duration?: number;\n /** Callback when animation completes */\n readonly onComplete?: () => void;\n}\n\n/**\n * Get color based on feedback type\n */\nfunction getFeedbackColor(type: ActionFeedbackType): string {\n switch (type) {\n case \"perfect\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case \"critical\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n case \"blocked\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN);\n case \"dodged\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GREEN);\n case \"technique\":\n return hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n case \"combo_milestone\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n default:\n return hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY);\n }\n}\n\n/**\n * Get glow color based on feedback type\n */\nfunction getGlowColor(type: ActionFeedbackType): string {\n switch (type) {\n case \"perfect\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n case \"critical\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n case \"blocked\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_CYAN, 0.6);\n case \"dodged\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GREEN, 0.6);\n case \"technique\":\n return hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n case \"combo_milestone\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n default:\n return hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.4);\n }\n}\n\n/**\n * Individual action feedback display\n */\ninterface SingleFeedbackProps {\n readonly feedback: ActionFeedbackData;\n readonly isMobile: boolean;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly animationDuration: number;\n}\n\nconst SingleFeedback: React.FC<SingleFeedbackProps> = ({\n feedback,\n isMobile,\n arenaBounds,\n animationDuration,\n}) => {\n const [progress, setProgress] = useState(0);\n const startTimeRef = useRef(feedback.timestamp);\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, feedback.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, feedback.position.y));\n \n const x = clampedX; // Meter position X\n const y = 2.5 + progress * 1.5; // Float upward\n const z = clampedZ; // Meter position Z (depth)\n const position3D: [number, number, number] = [x, y, z];\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const newProgress = Math.min(elapsed / animationDuration, 1);\n setProgress(newProgress);\n });\n\n if (progress >= 1) return null;\n\n const opacity = 1 - progress;\n const scale = 1 + (progress < 0.2 ? progress * 2 : (1 - progress) * 0.5);\n const fontSize = isMobile ? 18 : 24;\n const color = getFeedbackColor(feedback.type);\n const glowColor = getGlowColor(feedback.type);\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid={`action-feedback-${feedback.id}`}\n style={{\n fontSize: `${fontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n opacity,\n transform: `scale(${scale})`,\n textShadow: `\n 0 0 10px ${glowColor},\n 0 0 20px ${glowColor},\n 2px 2px 4px rgba(0, 0, 0, 0.9)\n `,\n whiteSpace: \"nowrap\",\n userSelect: \"none\",\n textAlign: \"center\",\n }}\n >\n {feedback.textKorean} | {feedback.text}\n </div>\n </Html>\n );\n};\n\n/**\n * ActionFeedback Component\n *\n * Renders multiple action feedback indicators in the 3D scene.\n * Each indicator floats upward and fades out over time.\n *\n * @example\n * ```tsx\n * <ActionFeedback\n * feedbacks={actionFeedbacks}\n * isMobile={isMobile}\n * arenaBounds={arenaBounds}\n * />\n * ```\n */\nexport const ActionFeedback: React.FC<ActionFeedbackProps> = ({\n feedbacks,\n isMobile = false,\n arenaBounds = DEFAULT_PHYSICS_ARENA_BOUNDS,\n animationDuration = 1200,\n}) => {\n const visibleFeedbacks = useMemo(() => [...feedbacks], [feedbacks]);\n\n return (\n <group data-testid=\"action-feedback-container\">\n {visibleFeedbacks.map((feedback) => (\n <SingleFeedback\n key={feedback.id}\n feedback={feedback}\n isMobile={isMobile}\n arenaBounds={arenaBounds}\n animationDuration={animationDuration}\n />\n ))}\n </group>\n );\n};\n\n/**\n * TechniqueName Component\n *\n * Displays the current technique name in Korean and English.\n * Appears at the center of the screen with a dramatic animation.\n *\n * @example\n * ```tsx\n * <TechniqueName\n * korean=\"천둥벽력\"\n * english=\"Thunder Strike\"\n * isMobile={isMobile}\n * duration={2000}\n * />\n * ```\n */\nexport const TechniqueName: React.FC<TechniqueNameProps> = ({\n korean,\n english,\n isMobile = false,\n duration = 2000,\n onComplete,\n}) => {\n const [opacity, setOpacity] = useState(0);\n const [scale, setScale] = useState(0.5);\n const [startTime] = useState(() => Date.now());\n const startTimeRef = useRef(startTime);\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const progress = Math.min(elapsed / duration, 1);\n\n if (progress < FADE_IN_THRESHOLD) {\n const fadeInProgress = progress / FADE_IN_THRESHOLD;\n setOpacity(fadeInProgress);\n setScale(0.5 + fadeInProgress * 0.5);\n } else if (progress < FADE_OUT_THRESHOLD) {\n setOpacity(1);\n setScale(1);\n } else {\n const fadeOutProgress =\n (progress - FADE_OUT_THRESHOLD) / (1 - FADE_OUT_THRESHOLD);\n setOpacity(1 - fadeOutProgress);\n setScale(1 + fadeOutProgress * 0.2);\n }\n\n if (progress >= 1 && onComplete) {\n onComplete();\n }\n });\n\n const position3D: [number, number, number] = [0, 3.5, 0];\n\n const mainFontSize = isMobile ? 28 : 42;\n const subFontSize = isMobile ? 16 : 24;\n const color = hexColorToCSS(KOREAN_COLORS.SECONDARY_MAGENTA);\n const glowColor = hexToRgbaString(KOREAN_COLORS.SECONDARY_MAGENTA, 0.8);\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid=\"technique-name\"\n style={{\n textAlign: \"center\",\n opacity,\n transform: `scale(${scale})`,\n transition: \"transform 0.1s ease-out\",\n }}\n >\n {/* Korean name */}\n <div\n style={{\n fontSize: `${mainFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color,\n textShadow: `\n 0 0 15px ${glowColor},\n 0 0 30px ${glowColor},\n 3px 3px 6px rgba(0, 0, 0, 0.9)\n `,\n letterSpacing: \"4px\",\n }}\n >\n {korean}\n </div>\n\n {/* Divider */}\n <div\n style={{\n width: \"60px\",\n height: \"2px\",\n background: `linear-gradient(90deg, transparent, ${color}, transparent)`,\n margin: \"8px auto\",\n }}\n />\n\n {/* English name */}\n <div\n style={{\n fontSize: `${subFontSize}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n textShadow: \"2px 2px 4px rgba(0, 0, 0, 0.8)\",\n letterSpacing: \"2px\",\n textTransform: \"uppercase\",\n }}\n >\n {english}\n </div>\n </div>\n </Html>\n );\n};\n\nexport default ActionFeedback;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DamageNumbers.js","names":[],"sources":["../../../../../src/components/shared/three/effects/DamageNumbers.tsx"],"sourcesContent":["/**\n * DamageNumbers - Floating damage number display component\n *\n * Displays floating damage numbers that animate upward and fade out.\n * Color-coded based on damage type: normal (cyan), critical (gold), vital (red).\n *\n * Uses Html overlays from @react-three/drei for rendering within 3D scenes.\n * Performance optimized with React.memo to reduce unnecessary re-renders.\n *\n * @module components/shared/three/effects/DamageNumbers\n * @category Shared Effects\n * @korean 피해숫자\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport { DamageNumber, DamageType } from \"../../../../hooks/useActionFeedback\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { withGPUAcceleration } from \"../../../../utils/performanceOptimization\";\n\n/**\n * Props for the DamageNumbers component\n */\nexport interface DamageNumbersProps {\n /** Array of damage numbers to display */\n readonly damages: readonly DamageNumber[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Arena bounds for 3D positioning (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n /** Duration of animation in ms (default: 1500) */\n readonly animationDuration?: number;\n}\n\n/**\n * Get color based on damage type\n */\nfunction getDamageColor(type: DamageType): string {\n switch (type) {\n case \"critical\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case \"vital\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n case \"normal\":\n default:\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }\n}\n\n/**\n * Get glow color based on damage type\n */\nfunction getGlowColor(type: DamageType): string {\n switch (type) {\n case \"critical\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n case \"vital\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n case \"normal\":\n default:\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8);\n }\n}\n\n/**\n * Individual damage number display\n * Memoized to prevent unnecessary re-renders\n */\ninterface SingleDamageNumberProps {\n readonly damage: DamageNumber;\n readonly isMobile: boolean;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly animationDuration: number;\n}\n\nconst SingleDamageNumber = React.memo<SingleDamageNumberProps>(({\n damage,\n isMobile,\n arenaBounds,\n animationDuration,\n}) => {\n const [progress, setProgress] = useState(0);\n const startTimeRef = useRef(damage.timestamp);\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, damage.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, damage.position.y));\n \n const x = clampedX; // Meter position X\n const y = 2 + progress * 2; // Float upward\n const z = clampedZ; // Meter position Z (depth)\n const position3D: [number, number, number] = [x, y, z];\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const newProgress = Math.min(elapsed / animationDuration, 1);\n setProgress(newProgress);\n });\n\n if (progress >= 1) return null;\n\n const opacity = 1 - progress;\n const scale = 1 + progress * 0.3; // Slight scale up during animation\n const fontSize = isMobile ? 20 : 28;\n const getCriticalBonus = (): number => {\n if (damage.type === \"critical\") return 8;\n if (damage.type === \"vital\") return 4;\n return 0;\n };\n const criticalBonus = getCriticalBonus();\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid={`damage-${damage.id}`}\n style={withGPUAcceleration({\n fontSize: `${fontSize + criticalBonus}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: getDamageColor(damage.type),\n opacity,\n transform: `scale(${scale})`,\n textShadow: `\n 0 0 10px ${getGlowColor(damage.type)},\n 0 0 20px ${getGlowColor(damage.type)},\n 2px 2px 4px rgba(0, 0, 0, 0.8)\n `,\n whiteSpace: \"nowrap\",\n userSelect: \"none\",\n })}\n >\n {damage.damage}\n {damage.type === \"critical\" && \"!\"}\n {damage.type === \"vital\" && \"!!\"}\n </div>\n </Html>\n );\n}, (prevProps, nextProps) => {\n const prevArena = prevProps.arenaBounds;\n const nextArena = nextProps.arenaBounds;\n\n const sameArenaBounds =\n prevArena?.x === nextArena?.x &&\n prevArena?.y === nextArena?.y &&\n prevArena?.width === nextArena?.width &&\n prevArena?.height === nextArena?.height &&\n prevArena?.worldWidthMeters === nextArena?.worldWidthMeters &&\n prevArena?.worldDepthMeters === nextArena?.worldDepthMeters;\n\n return (\n prevProps.damage.id === nextProps.damage.id &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.animationDuration === nextProps.animationDuration &&\n sameArenaBounds\n );\n});\n\nSingleDamageNumber.displayName = \"SingleDamageNumber\";\n\n/**\n * DamageNumbers Component\n *\n * Renders multiple floating damage numbers in the 3D scene.\n * Each number floats upward and fades out over time.\n * Performance optimized with React.memo.\n *\n * @example\n * ```tsx\n * <DamageNumbers\n * damages={damageNumbers}\n * isMobile={isMobile}\n * arenaBounds={arenaBounds}\n * />\n * ```\n */\nconst DamageNumbersComponent: React.FC<DamageNumbersProps> = ({\n damages,\n isMobile = false,\n arenaBounds = DEFAULT_PHYSICS_ARENA_BOUNDS,\n animationDuration = 1500,\n}) => {\n const visibleDamages = useMemo(() => [...damages], [damages]);\n\n return (\n <group data-testid=\"damage-numbers-container\">\n {visibleDamages.map((damage) => (\n <SingleDamageNumber\n key={damage.id}\n damage={damage}\n isMobile={isMobile}\n arenaBounds={arenaBounds}\n animationDuration={animationDuration}\n />\n ))}\n </group>\n );\n};\n\n/**\n * Memoized DamageNumbers with custom comparison\n * Only re-renders when damage array changes\n */\nexport const DamageNumbers = React.memo(\n DamageNumbersComponent,\n (prevProps, nextProps) => {\n if (prevProps.damages.length !== nextProps.damages.length) {\n return false;\n }\n \n for (let i = 0; i < prevProps.damages.length; i++) {\n if (prevProps.damages[i].id !== nextProps.damages[i].id) {\n return false;\n }\n }\n \n return (\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.animationDuration === nextProps.animationDuration &&\n prevProps.arenaBounds?.x === nextProps.arenaBounds?.x &&\n prevProps.arenaBounds?.y === nextProps.arenaBounds?.y &&\n prevProps.arenaBounds?.width === nextProps.arenaBounds?.width &&\n prevProps.arenaBounds?.height === nextProps.arenaBounds?.height\n );\n }\n);\n\nDamageNumbers.displayName = \"DamageNumbers\";\n\nexport default DamageNumbers;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DamageNumbers.js","names":[],"sources":["../../../../../src/components/shared/three/effects/DamageNumbers.tsx"],"sourcesContent":["/**\n * DamageNumbers - Floating damage number display component\n *\n * Displays floating damage numbers that animate upward and fade out.\n * Color-coded based on damage type: normal (cyan), critical (gold), vital (red).\n *\n * Uses Html overlays from @react-three/drei for rendering within 3D scenes.\n * Performance optimized with React.memo to reduce unnecessary re-renders.\n *\n * @module components/shared/three/effects/DamageNumbers\n * @category Shared Effects\n * @korean 피해숫자\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef, useState } from \"react\";\nimport { DamageNumber, DamageType } from \"../../../../hooks/useActionFeedback\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport { withGPUAcceleration } from \"../../../../utils/performanceOptimization\";\n\n/**\n * Props for the DamageNumbers component\n */\nexport interface DamageNumbersProps {\n /** Array of damage numbers to display */\n readonly damages: readonly DamageNumber[];\n /** Whether to use mobile-optimized sizing */\n readonly isMobile?: boolean;\n /** Arena bounds for 3D positioning (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n /** Duration of animation in ms (default: 1500) */\n readonly animationDuration?: number;\n}\n\n/**\n * Get color based on damage type\n */\nfunction getDamageColor(type: DamageType): string {\n switch (type) {\n case \"critical\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case \"vital\":\n return hexColorToCSS(KOREAN_COLORS.ACCENT_RED);\n case \"normal\":\n default:\n return hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN);\n }\n}\n\n/**\n * Get glow color based on damage type\n */\nfunction getGlowColor(type: DamageType): string {\n switch (type) {\n case \"critical\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_GOLD, 0.8);\n case \"vital\":\n return hexToRgbaString(KOREAN_COLORS.ACCENT_RED, 0.8);\n case \"normal\":\n default:\n return hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.8);\n }\n}\n\n/**\n * Individual damage number display\n * Memoized to prevent unnecessary re-renders\n */\ninterface SingleDamageNumberProps {\n readonly damage: DamageNumber;\n readonly isMobile: boolean;\n readonly arenaBounds: PhysicsArenaBounds;\n readonly animationDuration: number;\n}\n\nconst SingleDamageNumber = React.memo<SingleDamageNumberProps>(({\n damage,\n isMobile,\n arenaBounds,\n animationDuration,\n}) => {\n const [progress, setProgress] = useState(0);\n const startTimeRef = useRef(damage.timestamp);\n\n const halfWidth = arenaBounds.worldWidthMeters / 2;\n const halfDepth = arenaBounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, damage.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, damage.position.y));\n \n const x = clampedX; // Meter position X\n const y = 2 + progress * 2; // Float upward\n const z = clampedZ; // Meter position Z (depth)\n const position3D: [number, number, number] = [x, y, z];\n\n useFrame(() => {\n const elapsed = Date.now() - startTimeRef.current;\n const newProgress = Math.min(elapsed / animationDuration, 1);\n setProgress(newProgress);\n });\n\n if (progress >= 1) return null;\n\n const opacity = 1 - progress;\n const scale = 1 + progress * 0.3; // Slight scale up during animation\n const fontSize = isMobile ? 20 : 28;\n const getCriticalBonus = (): number => {\n if (damage.type === \"critical\") return 8;\n if (damage.type === \"vital\") return 4;\n return 0;\n };\n const criticalBonus = getCriticalBonus();\n\n return (\n <Html\n position={position3D}\n center\n distanceFactor={10}\n style={{ pointerEvents: \"none\" }}\n >\n <div\n data-testid={`damage-${damage.id}`}\n style={withGPUAcceleration({\n fontSize: `${fontSize + criticalBonus}px`,\n fontWeight: \"bold\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: getDamageColor(damage.type),\n opacity,\n transform: `scale(${scale})`,\n textShadow: `\n 0 0 10px ${getGlowColor(damage.type)},\n 0 0 20px ${getGlowColor(damage.type)},\n 2px 2px 4px rgba(0, 0, 0, 0.8)\n `,\n whiteSpace: \"nowrap\",\n userSelect: \"none\",\n })}\n >\n {damage.damage}\n {damage.type === \"critical\" && \"!\"}\n {damage.type === \"vital\" && \"!!\"}\n </div>\n </Html>\n );\n}, (prevProps, nextProps) => {\n const prevArena = prevProps.arenaBounds;\n const nextArena = nextProps.arenaBounds;\n\n const sameArenaBounds =\n prevArena?.x === nextArena?.x &&\n prevArena?.y === nextArena?.y &&\n prevArena?.width === nextArena?.width &&\n prevArena?.height === nextArena?.height &&\n prevArena?.worldWidthMeters === nextArena?.worldWidthMeters &&\n prevArena?.worldDepthMeters === nextArena?.worldDepthMeters;\n\n return (\n prevProps.damage.id === nextProps.damage.id &&\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.animationDuration === nextProps.animationDuration &&\n sameArenaBounds\n );\n});\n\nSingleDamageNumber.displayName = \"SingleDamageNumber\";\n\n/**\n * DamageNumbers Component\n *\n * Renders multiple floating damage numbers in the 3D scene.\n * Each number floats upward and fades out over time.\n * Performance optimized with React.memo.\n *\n * @example\n * ```tsx\n * <DamageNumbers\n * damages={damageNumbers}\n * isMobile={isMobile}\n * arenaBounds={arenaBounds}\n * />\n * ```\n */\nconst DamageNumbersComponent: React.FC<DamageNumbersProps> = ({\n damages,\n isMobile = false,\n arenaBounds = DEFAULT_PHYSICS_ARENA_BOUNDS,\n animationDuration = 1500,\n}) => {\n const visibleDamages = useMemo(() => [...damages], [damages]);\n\n return (\n <group data-testid=\"damage-numbers-container\">\n {visibleDamages.map((damage) => (\n <SingleDamageNumber\n key={damage.id}\n damage={damage}\n isMobile={isMobile}\n arenaBounds={arenaBounds}\n animationDuration={animationDuration}\n />\n ))}\n </group>\n );\n};\n\n/**\n * Memoized DamageNumbers with custom comparison\n * Only re-renders when damage array changes\n */\nexport const DamageNumbers = React.memo(\n DamageNumbersComponent,\n (prevProps, nextProps) => {\n if (prevProps.damages.length !== nextProps.damages.length) {\n return false;\n }\n \n for (let i = 0; i < prevProps.damages.length; i++) {\n if (prevProps.damages[i].id !== nextProps.damages[i].id) {\n return false;\n }\n }\n \n return (\n prevProps.isMobile === nextProps.isMobile &&\n prevProps.animationDuration === nextProps.animationDuration &&\n prevProps.arenaBounds?.x === nextProps.arenaBounds?.x &&\n prevProps.arenaBounds?.y === nextProps.arenaBounds?.y &&\n prevProps.arenaBounds?.width === nextProps.arenaBounds?.width &&\n prevProps.arenaBounds?.height === nextProps.arenaBounds?.height\n );\n }\n);\n\nDamageNumbers.displayName = \"DamageNumbers\";\n\nexport default DamageNumbers;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAS,eAAe,MAA0B;CAChD,QAAQ,MAAR;EACE,KAAK,YACH,OAAO,cAAc,cAAc,WAAW;EAChD,KAAK,SACH,OAAO,cAAc,cAAc,UAAU;EAE/C,SACE,OAAO,cAAc,cAAc,YAAY;CACnD;AACF;;;;AAKA,SAAS,aAAa,MAA0B;CAC9C,QAAQ,MAAR;EACE,KAAK,YACH,OAAO,gBAAgB,cAAc,aAAa,EAAG;EACvD,KAAK,SACH,OAAO,gBAAgB,cAAc,YAAY,EAAG;EAEtD,SACE,OAAO,gBAAgB,cAAc,cAAc,EAAG;CAC1D;AACF;AAaA,IAAM,qBAAqB,MAAM,MAA+B,EAC9D,QACA,UACA,aACA,wBACI;CACJ,MAAM,CAAC,UAAU,eAAe,SAAS,CAAC;CAC1C,MAAM,eAAe,OAAO,OAAO,SAAS;CAE5C,MAAM,YAAY,YAAY,mBAAmB;CACjD,MAAM,YAAY,YAAY,mBAAmB;CAEjD,MAAM,WAAW,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,OAAO,SAAS,CAAC,CAAC;CAC5E,MAAM,WAAW,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC,WAAW,OAAO,SAAS,CAAC,CAAC;CAK5E,MAAM,aAAuC;EAAC;EAFpC,IAAI,WAAW;EAE2B;CAAC;CAErD,eAAe;EACb,MAAM,UAAU,KAAK,IAAI,IAAI,aAAa;EAE1C,YADoB,KAAK,IAAI,UAAU,mBAAmB,CAC9C,CAAW;CACzB,CAAC;CAED,IAAI,YAAY,GAAG,OAAO;CAE1B,MAAM,UAAU,IAAI;CACpB,MAAM,QAAQ,IAAI,WAAW;CAC7B,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,yBAAiC;EACrC,IAAI,OAAO,SAAS,YAAY,OAAO;EACvC,IAAI,OAAO,SAAS,SAAS,OAAO;EACpC,OAAO;CACT;CACA,MAAM,gBAAgB,iBAAiB;CAEvC,OACE,oBAAC,MAAD;EACE,UAAU;EACV,QAAA;EACA,gBAAgB;EAChB,OAAO,EAAE,eAAe,OAAO;YAE/B,qBAAC,OAAD;GACE,eAAa,UAAU,OAAO;GAC9B,OAAO,oBAAoB;IACzB,UAAU,GAAG,WAAW,cAAc;IACtC,YAAY;IACZ,YAAY,YAAY;IACxB,OAAO,eAAe,OAAO,IAAI;IACjC;IACA,WAAW,SAAS,MAAM;IAC1B,YAAY;uBACC,aAAa,OAAO,IAAI,EAAE;uBAC1B,aAAa,OAAO,IAAI,EAAE;;;IAGvC,YAAY;IACZ,YAAY;GACd,CAAC;aAhBH;IAkBG,OAAO;IACP,OAAO,SAAS,cAAc;IAC9B,OAAO,SAAS,WAAW;GACzB;;CACD,CAAA;AAEV,IAAI,WAAW,cAAc;CAC3B,MAAM,YAAY,UAAU;CAC5B,MAAM,YAAY,UAAU;CAE5B,MAAM,kBACJ,WAAW,MAAM,WAAW,KAC5B,WAAW,MAAM,WAAW,KAC5B,WAAW,UAAU,WAAW,SAChC,WAAW,WAAW,WAAW,UACjC,WAAW,qBAAqB,WAAW,oBAC3C,WAAW,qBAAqB,WAAW;CAE7C,OACE,UAAU,OAAO,OAAO,UAAU,OAAO,MACzC,UAAU,aAAa,UAAU,YACjC,UAAU,sBAAsB,UAAU,qBAC1C;AAEJ,CAAC;AAED,mBAAmB,cAAc;;;;;;;;;;;;;;;;;AAkBjC,IAAM,0BAAwD,EAC5D,SACA,WAAW,OACX,cAAc,8BACd,oBAAoB,WAChB;CAGJ,OACE,oBAAC,SAAD;EAAO,eAAY;YAHE,cAAc,CAAC,GAAG,OAAO,GAAG,CAAC,OAAO,CAItD,EAAe,KAAK,WACnB,oBAAC,oBAAD;GAEU;GACE;GACG;GACM;EACpB,GALM,OAAO,EAKb,CACF;CACI,CAAA;AAEX;;;;;AAMA,IAAa,gBAAgB,MAAM,KACjC,yBACC,WAAW,cAAc;CACxB,IAAI,UAAU,QAAQ,WAAW,UAAU,QAAQ,QACjD,OAAO;CAGT,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,QAAQ,KAC5C,IAAI,UAAU,QAAQ,GAAG,OAAO,UAAU,QAAQ,GAAG,IACnD,OAAO;CAIX,OACE,UAAU,aAAa,UAAU,YACjC,UAAU,sBAAsB,UAAU,qBAC1C,UAAU,aAAa,MAAM,UAAU,aAAa,KACpD,UAAU,aAAa,MAAM,UAAU,aAAa,KACpD,UAAU,aAAa,UAAU,UAAU,aAAa,SACxD,UAAU,aAAa,WAAW,UAAU,aAAa;AAE7D,CACF;AAEA,cAAc,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HitEffects3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/HitEffects3D.tsx"],"sourcesContent":["/**\n * HitEffects3D - Three.js particle effects for combat\n *\n * Maintains Korean theming and visual feedback for combat actions\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { HitEffect } from \"../../../../systems\";\nimport { HitEffectType } from \"../../../../systems/effects\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\n\n/**\n * Props for the HitEffects3D component.\n * Controls which effects are displayed and callbacks for effect lifecycle.\n */\nexport interface HitEffects3DProps {\n /** Array of active hit effects to render in the scene */\n readonly effects: HitEffect[];\n /** Callback invoked when an effect completes its duration */\n readonly onEffectComplete?: (effectId: string) => void;\n /** Arena bounds for accurate coordinate conversion (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n}\n\ninterface ActiveEffect extends HitEffect {\n progress: number;\n}\n\n/**\n * Individual Hit Effect Component\n * Renders a single effect with Three.js primitives\n */\nconst HitEffectVisual: React.FC<{\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n arenaBounds?: PhysicsArenaBounds;\n}> = ({ effect, effectRef, arenaBounds }) => {\n const groupRef = useRef<THREE.Group>(null);\n const alphaRef = useRef(1);\n\n const position3D: [number, number, number] = useMemo(() => {\n if (!effect.position) return [0, 1, 0];\n\n const bounds = arenaBounds ?? DEFAULT_PHYSICS_ARENA_BOUNDS;\n \n const halfWidth = bounds.worldWidthMeters / 2;\n const halfDepth = bounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, effect.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, effect.position.y));\n \n const x = clampedX; // Meter position X\n const y = 1.5; // Mid-height for effects\n const z = clampedZ; // Meter position Z (depth)\n\n return [x, y, z];\n }, [effect.position, arenaBounds]);\n\n useFrame(() => {\n if (!groupRef.current || !effectRef.current) return;\n\n const progress = effectRef.current.progress;\n alphaRef.current = 1 - progress;\n\n let sparkIndex = 0;\n groupRef.current.traverse((object) => {\n if (object instanceof THREE.Mesh && object.material instanceof THREE.MeshBasicMaterial) {\n const baseOpacity = object.material.userData.baseOpacity ?? 1;\n object.material.opacity = alphaRef.current * baseOpacity;\n \n if (effect.type === HitEffectType.BLOCK && object.geometry instanceof THREE.SphereGeometry) {\n if (object.geometry.parameters?.radius === 0.05) { // Spark particles have radius 0.05\n const i = sparkIndex++;\n if (i < 3) { // Only update the 3 spark particles\n object.position.y = Math.sin((1 - alphaRef.current) * Math.PI) * 0.3;\n }\n }\n }\n }\n });\n\n if (\n effect.type === HitEffectType.COUNTER ||\n effect.type === HitEffectType.VITAL_POINT_STRIKE\n ) {\n groupRef.current.rotation.y += 0.1;\n }\n\n if (effect.type === HitEffectType.CRITICAL_HIT) {\n const pulse =\n 1 + Math.sin(effectRef.current.progress * Math.PI * 4) * 0.2;\n groupRef.current.scale.set(pulse, pulse, pulse);\n }\n });\n\n switch (effect.type) {\n case HitEffectType.HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Impact flash sphere */}\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Expanding ring */}\n <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry\n args={[0.3 * effect.intensity, 0.35 * effect.intensity, 32]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.CRITICAL_HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Large impact sphere */}\n <mesh>\n <sphereGeometry args={[0.5 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.7}\n userData={{ baseOpacity: 0.7 }}\n />\n </mesh>\n {/* Star burst lines */}\n {[0, 1, 2, 3].map((i) => {\n const angle = (i * Math.PI) / 2;\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.3, 0, Math.sin(angle) * 0.3]}\n rotation={[0, angle, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.BLOCK:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Shield arc */}\n <mesh rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry\n args={[0.4 * effect.intensity, 0.05, 8, 16, Math.PI]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Spark particles - positions will be updated in useFrame */}\n {[0, 1, 2].map((i) => (\n <mesh\n key={i}\n position={[\n (i - 1) * 0.2,\n 0,\n 0,\n ]}\n >\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.MISS:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Swish trail lines */}\n {[0, 1].map((i) => (\n <mesh\n key={i}\n position={[(i - 0.5) * 0.2, i * 0.1, 0]}\n rotation={[0, 0, (i - 0.5) * 0.3]}\n >\n <boxGeometry args={[0.6, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.TEXT_TERTIARY}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.VITAL_POINT_STRIKE:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Pulsing sphere */}\n <mesh>\n <sphereGeometry args={[0.35 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Concentric rings */}\n {[0.2, 0.3, 0.4].map((radius, i) => (\n <mesh key={i} rotation={[-Math.PI / 2, 0, 0]}>\n <ringGeometry args={[radius, radius + 0.02, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n {/* Crosshair */}\n <mesh position={[0, 0, 0]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n <mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.PARRY:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Deflection arc */}\n <mesh>\n <torusGeometry\n args={[0.35 * effect.intensity, 0.05, 8, 16, Math.PI / 2]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Sparks */}\n {[0, 1, 2].map((i) => {\n const angle = (Math.PI / 6) * (i - 1);\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.4, Math.sin(angle) * 0.4, 0]}\n >\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.COUNTER:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Spinning energy blades */}\n {[0, 1, 2, 3].map((i) => (\n <mesh\n key={i}\n rotation={[0, (i * Math.PI) / 2, 0]}\n position={[0, 0, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.GENERAL_DAMAGE:\n case HitEffectType.STATUS_EFFECT:\n default:\n return (\n <group ref={groupRef} position={position3D}>\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GREEN}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n </group>\n );\n }\n};\n\n/**\n * HitEffects3D Component\n * Manages all active hit effects in the combat scene\n * Uses refs to avoid triggering React re-renders at 60fps\n */\nexport const HitEffects3D: React.FC<HitEffects3DProps> = ({\n effects,\n onEffectComplete,\n arenaBounds,\n}) => {\n const effectRefsMap = useRef<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n const completedEffectsRef = useRef<Set<string>>(new Set());\n\n const [effectRefsSnapshot, setEffectRefsSnapshot] = useState<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n\n useEffect(() => {\n const currentIdSet = new Set(effects.map((e) => e.id));\n effectRefsMap.current.forEach((_ref, id) => {\n if (!currentIdSet.has(id)) {\n effectRefsMap.current.delete(id);\n completedEffectsRef.current.delete(id);\n }\n });\n\n effects.forEach((effect) => {\n if (!effectRefsMap.current.has(effect.id)) {\n effectRefsMap.current.set(effect.id, {\n current: { ...effect, progress: 0 },\n });\n }\n });\n\n setEffectRefsSnapshot(new Map(effectRefsMap.current));\n }, [effects]);\n\n useFrame(() => {\n const now = Date.now();\n\n effectRefsMap.current.forEach((ref, id) => {\n if (!ref.current) return;\n\n const progress = Math.min(\n (now - ref.current.startTime) / ref.current.duration,\n 1\n );\n ref.current.progress = progress;\n\n const isExpired = progress >= 1;\n if (\n isExpired &&\n onEffectComplete &&\n !completedEffectsRef.current.has(id)\n ) {\n completedEffectsRef.current.add(id);\n onEffectComplete(id);\n }\n });\n });\n\n const effectsToRender = useMemo(() => {\n return effects\n .map((effect) => {\n const effectRef = effectRefsSnapshot.get(effect.id);\n return { effect, effectRef };\n })\n .filter(\n (\n item\n ): item is {\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n } => item.effectRef !== undefined\n );\n }, [effects, effectRefsSnapshot]);\n\n return (\n <group>\n {effectsToRender.map(({ effect, effectRef }) => {\n return (\n <HitEffectVisual\n key={effect.id}\n effect={effect}\n effectRef={effectRef}\n arenaBounds={arenaBounds}\n />\n );\n })}\n </group>\n );\n};\n\nexport default HitEffects3D;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HitEffects3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/HitEffects3D.tsx"],"sourcesContent":["/**\n * HitEffects3D - Three.js particle effects for combat\n *\n * Maintains Korean theming and visual feedback for combat actions\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useEffect, useMemo, useRef, useState } from \"react\";\nimport * as THREE from \"three\";\nimport { HitEffect } from \"../../../../systems\";\nimport { HitEffectType } from \"../../../../systems/effects\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport { DEFAULT_PHYSICS_ARENA_BOUNDS, type PhysicsArenaBounds } from \"../../../../types/PhysicsTypes\";\n\n/**\n * Props for the HitEffects3D component.\n * Controls which effects are displayed and callbacks for effect lifecycle.\n */\nexport interface HitEffects3DProps {\n /** Array of active hit effects to render in the scene */\n readonly effects: HitEffect[];\n /** Callback invoked when an effect completes its duration */\n readonly onEffectComplete?: (effectId: string) => void;\n /** Arena bounds for accurate coordinate conversion (physics-first with meter dimensions) */\n readonly arenaBounds?: PhysicsArenaBounds;\n}\n\ninterface ActiveEffect extends HitEffect {\n progress: number;\n}\n\n/**\n * Individual Hit Effect Component\n * Renders a single effect with Three.js primitives\n */\nconst HitEffectVisual: React.FC<{\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n arenaBounds?: PhysicsArenaBounds;\n}> = ({ effect, effectRef, arenaBounds }) => {\n const groupRef = useRef<THREE.Group>(null);\n const alphaRef = useRef(1);\n\n const position3D: [number, number, number] = useMemo(() => {\n if (!effect.position) return [0, 1, 0];\n\n const bounds = arenaBounds ?? DEFAULT_PHYSICS_ARENA_BOUNDS;\n \n const halfWidth = bounds.worldWidthMeters / 2;\n const halfDepth = bounds.worldDepthMeters / 2;\n \n const clampedX = Math.min(halfWidth, Math.max(-halfWidth, effect.position.x));\n const clampedZ = Math.min(halfDepth, Math.max(-halfDepth, effect.position.y));\n \n const x = clampedX; // Meter position X\n const y = 1.5; // Mid-height for effects\n const z = clampedZ; // Meter position Z (depth)\n\n return [x, y, z];\n }, [effect.position, arenaBounds]);\n\n useFrame(() => {\n if (!groupRef.current || !effectRef.current) return;\n\n const progress = effectRef.current.progress;\n alphaRef.current = 1 - progress;\n\n let sparkIndex = 0;\n groupRef.current.traverse((object) => {\n if (object instanceof THREE.Mesh && object.material instanceof THREE.MeshBasicMaterial) {\n const baseOpacity = object.material.userData.baseOpacity ?? 1;\n object.material.opacity = alphaRef.current * baseOpacity;\n \n if (effect.type === HitEffectType.BLOCK && object.geometry instanceof THREE.SphereGeometry) {\n if (object.geometry.parameters?.radius === 0.05) { // Spark particles have radius 0.05\n const i = sparkIndex++;\n if (i < 3) { // Only update the 3 spark particles\n object.position.y = Math.sin((1 - alphaRef.current) * Math.PI) * 0.3;\n }\n }\n }\n }\n });\n\n if (\n effect.type === HitEffectType.COUNTER ||\n effect.type === HitEffectType.VITAL_POINT_STRIKE\n ) {\n groupRef.current.rotation.y += 0.1;\n }\n\n if (effect.type === HitEffectType.CRITICAL_HIT) {\n const pulse =\n 1 + Math.sin(effectRef.current.progress * Math.PI * 4) * 0.2;\n groupRef.current.scale.set(pulse, pulse, pulse);\n }\n });\n\n switch (effect.type) {\n case HitEffectType.HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Impact flash sphere */}\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Expanding ring */}\n <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>\n <ringGeometry\n args={[0.3 * effect.intensity, 0.35 * effect.intensity, 32]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.CRITICAL_HIT:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Large impact sphere */}\n <mesh>\n <sphereGeometry args={[0.5 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.7}\n userData={{ baseOpacity: 0.7 }}\n />\n </mesh>\n {/* Star burst lines */}\n {[0, 1, 2, 3].map((i) => {\n const angle = (i * Math.PI) / 2;\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.3, 0, Math.sin(angle) * 0.3]}\n rotation={[0, angle, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_RED}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.BLOCK:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Shield arc */}\n <mesh rotation={[0, 0, Math.PI / 2]}>\n <torusGeometry\n args={[0.4 * effect.intensity, 0.05, 8, 16, Math.PI]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Spark particles - positions will be updated in useFrame */}\n {[0, 1, 2].map((i) => (\n <mesh\n key={i}\n position={[\n (i - 1) * 0.2,\n 0,\n 0,\n ]}\n >\n <sphereGeometry args={[0.05, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_CYAN}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.MISS:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Swish trail lines */}\n {[0, 1].map((i) => (\n <mesh\n key={i}\n position={[(i - 0.5) * 0.2, i * 0.1, 0]}\n rotation={[0, 0, (i - 0.5) * 0.3]}\n >\n <boxGeometry args={[0.6, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.TEXT_TERTIARY}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.VITAL_POINT_STRIKE:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Pulsing sphere */}\n <mesh>\n <sphereGeometry args={[0.35 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n {/* Concentric rings */}\n {[0.2, 0.3, 0.4].map((radius, i) => (\n <mesh key={i} rotation={[-Math.PI / 2, 0, 0]}>\n <ringGeometry args={[radius, radius + 0.02, 32]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={1}\n side={THREE.DoubleSide}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n {/* Crosshair */}\n <mesh position={[0, 0, 0]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n <mesh position={[0, 0, 0]} rotation={[0, 0, Math.PI / 2]}>\n <boxGeometry args={[0.8, 0.02, 0.02]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.SECONDARY_MAGENTA}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n </group>\n );\n\n case HitEffectType.PARRY:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Deflection arc */}\n <mesh>\n <torusGeometry\n args={[0.35 * effect.intensity, 0.05, 8, 16, Math.PI / 2]}\n />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n {/* Sparks */}\n {[0, 1, 2].map((i) => {\n const angle = (Math.PI / 6) * (i - 1);\n return (\n <mesh\n key={i}\n position={[Math.cos(angle) * 0.4, Math.sin(angle) * 0.4, 0]}\n >\n <sphereGeometry args={[0.04, 8, 8]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GOLD}\n transparent\n opacity={0.8}\n userData={{ baseOpacity: 0.8 }}\n />\n </mesh>\n );\n })}\n </group>\n );\n\n case HitEffectType.COUNTER:\n return (\n <group ref={groupRef} position={position3D}>\n {/* Spinning energy blades */}\n {[0, 1, 2, 3].map((i) => (\n <mesh\n key={i}\n rotation={[0, (i * Math.PI) / 2, 0]}\n position={[0, 0, 0]}\n >\n <boxGeometry args={[0.6, 0.05, 0.05]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.PRIMARY_CYAN}\n transparent\n opacity={1}\n userData={{ baseOpacity: 1.0 }}\n />\n </mesh>\n ))}\n </group>\n );\n\n case HitEffectType.GENERAL_DAMAGE:\n case HitEffectType.STATUS_EFFECT:\n default:\n return (\n <group ref={groupRef} position={position3D}>\n <mesh>\n <sphereGeometry args={[0.3 * effect.intensity, 16, 16]} />\n <meshBasicMaterial\n color={KOREAN_COLORS.ACCENT_GREEN}\n transparent\n opacity={0.5}\n userData={{ baseOpacity: 0.5 }}\n />\n </mesh>\n </group>\n );\n }\n};\n\n/**\n * HitEffects3D Component\n * Manages all active hit effects in the combat scene\n * Uses refs to avoid triggering React re-renders at 60fps\n */\nexport const HitEffects3D: React.FC<HitEffects3DProps> = ({\n effects,\n onEffectComplete,\n arenaBounds,\n}) => {\n const effectRefsMap = useRef<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n const completedEffectsRef = useRef<Set<string>>(new Set());\n\n const [effectRefsSnapshot, setEffectRefsSnapshot] = useState<\n Map<string, React.MutableRefObject<ActiveEffect | null>>\n >(new Map());\n\n useEffect(() => {\n const currentIdSet = new Set(effects.map((e) => e.id));\n effectRefsMap.current.forEach((_ref, id) => {\n if (!currentIdSet.has(id)) {\n effectRefsMap.current.delete(id);\n completedEffectsRef.current.delete(id);\n }\n });\n\n effects.forEach((effect) => {\n if (!effectRefsMap.current.has(effect.id)) {\n effectRefsMap.current.set(effect.id, {\n current: { ...effect, progress: 0 },\n });\n }\n });\n\n setEffectRefsSnapshot(new Map(effectRefsMap.current));\n }, [effects]);\n\n useFrame(() => {\n const now = Date.now();\n\n effectRefsMap.current.forEach((ref, id) => {\n if (!ref.current) return;\n\n const progress = Math.min(\n (now - ref.current.startTime) / ref.current.duration,\n 1\n );\n ref.current.progress = progress;\n\n const isExpired = progress >= 1;\n if (\n isExpired &&\n onEffectComplete &&\n !completedEffectsRef.current.has(id)\n ) {\n completedEffectsRef.current.add(id);\n onEffectComplete(id);\n }\n });\n });\n\n const effectsToRender = useMemo(() => {\n return effects\n .map((effect) => {\n const effectRef = effectRefsSnapshot.get(effect.id);\n return { effect, effectRef };\n })\n .filter(\n (\n item\n ): item is {\n effect: HitEffect;\n effectRef: React.MutableRefObject<ActiveEffect | null>;\n } => item.effectRef !== undefined\n );\n }, [effects, effectRefsSnapshot]);\n\n return (\n <group>\n {effectsToRender.map(({ effect, effectRef }) => {\n return (\n <HitEffectVisual\n key={effect.id}\n effect={effect}\n effectRef={effectRef}\n arenaBounds={arenaBounds}\n />\n );\n })}\n </group>\n );\n};\n\nexport default HitEffects3D;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayerStateIndicators.js","names":[],"sources":["../../../../../src/components/shared/three/effects/PlayerStateIndicators.tsx"],"sourcesContent":["/**\n * PlayerStateIndicators - Html overlay for player stats\n * \n * Displays health, stamina, Ki, balance state, and other combat metrics\n * as an Html overlay above the 3D player model.\n * \n * @module components/three/PlayerStateIndicators\n * @category 3D Components\n * @korean 플레이어상태표시기\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { PlayerStateIndicatorsProps, BalanceState } from \"../../../../types/player-visual\";\nimport { toHexColor } from \"../../../../utils/colorHelpers\";\n\n/**\n * Get color for balance state\n * \n * @param balance - Current balance state\n * @returns CSS color string\n * @korean 균형색상가져오기\n */\nconst getBalanceColor = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"#00cc44\"; // Green - ready for combat\n case \"SHAKEN\":\n return \"#ffcc00\"; // Yellow - slightly compromised\n case \"VULNERABLE\":\n return \"#ff8800\"; // Orange - significantly exposed\n case \"HELPLESS\":\n return \"#cc0000\"; // Red - complete vulnerability\n default:\n return \"#00cc44\";\n }\n};\n\n/**\n * Get Korean text for balance state\n * \n * @param balance - Current balance state\n * @returns Korean text\n * @korean 균형한글가져오기\n */\nconst getBalanceText = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"준비완료\";\n case \"SHAKEN\":\n return \"동요상태\";\n case \"VULNERABLE\":\n return \"취약상태\";\n case \"HELPLESS\":\n return \"무력상태\";\n default:\n return \"준비완료\";\n }\n};\n\n/**\n * PlayerStateIndicators Component\n * \n * Renders an Html overlay with health bar, stamina bar, Ki indicator,\n * balance state, and consciousness level.\n * \n * @example\n * ```tsx\n * <PlayerStateIndicators\n * health={85}\n * maxHealth={100}\n * stamina={60}\n * ki={40}\n * balance=\"READY\"\n * consciousness={100}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 플레이어상태표시기컴포넌트\n */\nexport const PlayerStateIndicators: React.FC<PlayerStateIndicatorsProps> = ({\n health,\n maxHealth,\n stamina,\n ki,\n balance,\n consciousness,\n pain = 0,\n bloodLoss = 0,\n isMobile,\n}) => {\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (health / maxHealth) * 100)),\n [health, maxHealth]\n );\n\n const staminaPercent = useMemo(\n () => Math.max(0, Math.min(100, stamina)),\n [stamina]\n );\n\n const kiPercent = useMemo(() => Math.max(0, Math.min(100, ki)), [ki]);\n\n const consciousnessPercent = useMemo(\n () => Math.max(0, Math.min(100, consciousness)),\n [consciousness]\n );\n\n const sizing = useMemo(\n () => ({\n width: isMobile ? \"60px\" : \"80px\",\n barHeight: isMobile ? \"4px\" : \"6px\",\n thinBarHeight: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"8px\" : \"10px\",\n gap: isMobile ? \"2px\" : \"4px\",\n }),\n [isMobile]\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 50) return \"#00ff00\"; // Green\n if (healthPercent > 25) return \"#ffff00\"; // Yellow\n return \"#ff0000\"; // Red\n }, [healthPercent]);\n\n const balanceColor = useMemo(() => getBalanceColor(balance), [balance]);\n const balanceTextKorean = useMemo(() => getBalanceText(balance), [balance]);\n\n return (\n <Html\n position={[0, 2.5, 0]}\n center\n distanceFactor={isMobile ? 15 : 10}\n occlude={false}\n style={{ pointerEvents: \"none\", userSelect: \"none\" }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: sizing.gap,\n minWidth: sizing.width,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid=\"player-state-indicators\"\n >\n {/* Health bar */}\n <div\n style={{\n height: sizing.barHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"health-bar\"\n title={`Health: ${health}/${maxHealth}`}\n >\n <div\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n background: healthColor,\n transition: \"width 0.3s ease, background-color 0.3s ease\",\n }}\n />\n </div>\n\n {/* Stamina bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.ACCENT_GOLD)}`,\n }}\n data-testid=\"stamina-bar\"\n title={`Stamina: ${stamina}%`}\n >\n <div\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.ACCENT_GOLD),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n\n {/* Ki bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"ki-bar\"\n title={`Ki: ${ki}%`}\n >\n <div\n style={{\n width: `${kiPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.PRIMARY_CYAN),\n transition: \"width 0.3s ease\",\n boxShadow:\n kiPercent > 80\n ? `0 0 ${isMobile ? \"4px\" : \"6px\"} ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`\n : \"none\",\n }}\n />\n </div>\n\n {/* Balance state indicator */}\n <div\n style={{\n fontSize: sizing.fontSize,\n color: balanceColor,\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"2px 4px\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: \"2px\",\n }}\n data-testid=\"balance-indicator\"\n title={`Balance: ${balance}`}\n >\n {balanceTextKorean}\n </div>\n\n {/* Consciousness indicator (if below 100%) */}\n {consciousnessPercent < 100 && (\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE)}`,\n }}\n data-testid=\"consciousness-bar\"\n title={`Consciousness: ${consciousness}%`}\n >\n <div\n style={{\n width: `${consciousnessPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n )}\n\n {/* Pain indicator (if above 20%) */}\n {pain > 20 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.PAIN_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"pain-indicator\"\n title={`Pain: ${pain}%`}\n >\n 통증 {Math.round(pain)}%\n </div>\n )}\n\n {/* Blood loss indicator (if above 10%) */}\n {bloodLoss && bloodLoss > 10 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.BLOODLOSS_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"bloodloss-indicator\"\n title={`Blood Loss: ${bloodLoss}%`}\n >\n 출혈 {Math.round(bloodLoss)}%\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nexport default PlayerStateIndicators;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,mBAAmB,YAAkC;CACzD,QAAQ,SAAR;EACE,KAAK,SACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,SACE,OAAO;;;;;;;;;;AAWb,IAAM,kBAAkB,YAAkC;CACxD,QAAQ,SAAR;EACE,KAAK,SACH,OAAO;EACT,KAAK,UACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,SACE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBb,IAAa,yBAA+D,EAC1E,QACA,WACA,SACA,IACA,SACA,eACA,OAAO,GACP,YAAY,GACZ,eACI;CACJ,MAAM,gBAAgB,cACd,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,SAAS,YAAa,IAAI,CAAC,EAC5D,CAAC,QAAQ,UAAU,CACpB;CAED,MAAM,iBAAiB,cACf,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,QAAQ,CAAC,EACzC,CAAC,QAAQ,CACV;CAED,MAAM,YAAY,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC;CAErE,MAAM,uBAAuB,cACrB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,cAAc,CAAC,EAC/C,CAAC,cAAc,CAChB;CAED,MAAM,SAAS,eACN;EACL,OAAO,WAAW,SAAS;EAC3B,WAAW,WAAW,QAAQ;EAC9B,eAAe,WAAW,QAAQ;EAClC,UAAU,WAAW,QAAQ;EAC7B,KAAK,WAAW,QAAQ;EACzB,GACD,CAAC,SAAS,CACX;CAED,MAAM,cAAc,cAAc;EAChC,IAAI,gBAAgB,IAAI,OAAO;EAC/B,IAAI,gBAAgB,IAAI,OAAO;EAC/B,OAAO;IACN,CAAC,cAAc,CAAC;CAEnB,MAAM,eAAe,cAAc,gBAAgB,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACvE,MAAM,oBAAoB,cAAc,eAAe,QAAQ,EAAE,CAAC,QAAQ,CAAC;CAE3E,OACE,oBAAC,MAAD;EACE,UAAU;GAAC;GAAG;GAAK;GAAE;EACrB,QAAA;EACA,gBAAgB,WAAW,KAAK;EAChC,SAAS;EACT,OAAO;GAAE,eAAe;GAAQ,YAAY;GAAQ;YAEpD,qBAAC,OAAD;GACE,OAAO;IACL,SAAS;IACT,eAAe;IACf,KAAK,OAAO;IACZ,UAAU,OAAO;IACjB,YAAY,YAAY;IACzB;GACD,eAAY;aARd;IAWE,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,aAAa;MAC5D;KACD,eAAY;KACZ,OAAO,WAAW,OAAO,GAAG;eAE5B,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,cAAc;MACxB,QAAQ;MACR,YAAY;MACZ,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,YAAY;MAC3D;KACD,eAAY;KACZ,OAAO,YAAY,QAAQ;eAE3B,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,eAAe;MACzB,QAAQ;MACR,YAAY,WAAW,cAAc,YAAY;MACjD,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,aAAa;MAC5D;KACD,eAAY;KACZ,OAAO,OAAO,GAAG;eAEjB,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,UAAU;MACpB,QAAQ;MACR,YAAY,WAAW,cAAc,aAAa;MAClD,YAAY;MACZ,WACE,YAAY,KACR,OAAO,WAAW,QAAQ,MAAM,GAAG,WAAW,cAAc,aAAa,KACzE;MACP,EACD,CAAA;KACE,CAAA;IAGN,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO;MACP,WAAW;MACX,YAAY;MACZ,YAAY;MACZ,SAAS;MACT,YAAY;MACZ,cAAc;MACf;KACD,eAAY;KACZ,OAAO,YAAY;eAElB;KACG,CAAA;IAGL,uBAAuB,OACtB,oBAAC,OAAD;KACE,OAAO;MACL,QAAQ,OAAO;MACf,YAAY;MACZ,cAAc;MACd,UAAU;MACV,QAAQ,aAAa,WAAW,cAAc,qBAAqB;MACpE;KACD,eAAY;KACZ,OAAO,kBAAkB,cAAc;eAEvC,oBAAC,OAAD,EACE,OAAO;MACL,OAAO,GAAG,qBAAqB;MAC/B,QAAQ;MACR,YAAY,WAAW,cAAc,qBAAqB;MAC1D,YAAY;MACb,EACD,CAAA;KACE,CAAA;IAIP,OAAO,MACN,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO,WAAW,cAAc,eAAe;MAC/C,WAAW;MACX,YAAY;MACZ,YAAY;MACb;KACD,eAAY;KACZ,OAAO,SAAS,KAAK;eATvB;MAUC;MACK,KAAK,MAAM,KAAK;MAAC;MACjB;;IAIP,aAAa,YAAY,MACxB,qBAAC,OAAD;KACE,OAAO;MACL,UAAU,OAAO;MACjB,OAAO,WAAW,cAAc,oBAAoB;MACpD,WAAW;MACX,YAAY;MACZ,YAAY;MACb;KACD,eAAY;KACZ,OAAO,eAAe,UAAU;eATlC;MAUC;MACK,KAAK,MAAM,UAAU;MAAC;MACtB;;IAEJ;;EACD,CAAA"}
|
|
1
|
+
{"version":3,"file":"PlayerStateIndicators.js","names":[],"sources":["../../../../../src/components/shared/three/effects/PlayerStateIndicators.tsx"],"sourcesContent":["/**\n * PlayerStateIndicators - Html overlay for player stats\n * \n * Displays health, stamina, Ki, balance state, and other combat metrics\n * as an Html overlay above the 3D player model.\n * \n * @module components/three/PlayerStateIndicators\n * @category 3D Components\n * @korean 플레이어상태표시기\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useMemo } from \"react\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport type { PlayerStateIndicatorsProps, BalanceState } from \"../../../../types/player-visual\";\nimport { toHexColor } from \"../../../../utils/colorHelpers\";\n\n/**\n * Get color for balance state\n * \n * @param balance - Current balance state\n * @returns CSS color string\n * @korean 균형색상가져오기\n */\nconst getBalanceColor = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"#00cc44\"; // Green - ready for combat\n case \"SHAKEN\":\n return \"#ffcc00\"; // Yellow - slightly compromised\n case \"VULNERABLE\":\n return \"#ff8800\"; // Orange - significantly exposed\n case \"HELPLESS\":\n return \"#cc0000\"; // Red - complete vulnerability\n default:\n return \"#00cc44\";\n }\n};\n\n/**\n * Get Korean text for balance state\n * \n * @param balance - Current balance state\n * @returns Korean text\n * @korean 균형한글가져오기\n */\nconst getBalanceText = (balance: BalanceState): string => {\n switch (balance) {\n case \"READY\":\n return \"준비완료\";\n case \"SHAKEN\":\n return \"동요상태\";\n case \"VULNERABLE\":\n return \"취약상태\";\n case \"HELPLESS\":\n return \"무력상태\";\n default:\n return \"준비완료\";\n }\n};\n\n/**\n * PlayerStateIndicators Component\n * \n * Renders an Html overlay with health bar, stamina bar, Ki indicator,\n * balance state, and consciousness level.\n * \n * @example\n * ```tsx\n * <PlayerStateIndicators\n * health={85}\n * maxHealth={100}\n * stamina={60}\n * ki={40}\n * balance=\"READY\"\n * consciousness={100}\n * isMobile={false}\n * />\n * ```\n * \n * @korean 플레이어상태표시기컴포넌트\n */\nexport const PlayerStateIndicators: React.FC<PlayerStateIndicatorsProps> = ({\n health,\n maxHealth,\n stamina,\n ki,\n balance,\n consciousness,\n pain = 0,\n bloodLoss = 0,\n isMobile,\n}) => {\n const healthPercent = useMemo(\n () => Math.max(0, Math.min(100, (health / maxHealth) * 100)),\n [health, maxHealth]\n );\n\n const staminaPercent = useMemo(\n () => Math.max(0, Math.min(100, stamina)),\n [stamina]\n );\n\n const kiPercent = useMemo(() => Math.max(0, Math.min(100, ki)), [ki]);\n\n const consciousnessPercent = useMemo(\n () => Math.max(0, Math.min(100, consciousness)),\n [consciousness]\n );\n\n const sizing = useMemo(\n () => ({\n width: isMobile ? \"60px\" : \"80px\",\n barHeight: isMobile ? \"4px\" : \"6px\",\n thinBarHeight: isMobile ? \"3px\" : \"4px\",\n fontSize: isMobile ? \"8px\" : \"10px\",\n gap: isMobile ? \"2px\" : \"4px\",\n }),\n [isMobile]\n );\n\n const healthColor = useMemo(() => {\n if (healthPercent > 50) return \"#00ff00\"; // Green\n if (healthPercent > 25) return \"#ffff00\"; // Yellow\n return \"#ff0000\"; // Red\n }, [healthPercent]);\n\n const balanceColor = useMemo(() => getBalanceColor(balance), [balance]);\n const balanceTextKorean = useMemo(() => getBalanceText(balance), [balance]);\n\n return (\n <Html\n position={[0, 2.5, 0]}\n center\n distanceFactor={isMobile ? 15 : 10}\n occlude={false}\n style={{ pointerEvents: \"none\", userSelect: \"none\" }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: sizing.gap,\n minWidth: sizing.width,\n fontFamily: FONT_FAMILY.KOREAN,\n }}\n data-testid=\"player-state-indicators\"\n >\n {/* Health bar */}\n <div\n style={{\n height: sizing.barHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"health-bar\"\n title={`Health: ${health}/${maxHealth}`}\n >\n <div\n style={{\n width: `${healthPercent}%`,\n height: \"100%\",\n background: healthColor,\n transition: \"width 0.3s ease, background-color 0.3s ease\",\n }}\n />\n </div>\n\n {/* Stamina bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.ACCENT_GOLD)}`,\n }}\n data-testid=\"stamina-bar\"\n title={`Stamina: ${stamina}%`}\n >\n <div\n style={{\n width: `${staminaPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.ACCENT_GOLD),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n\n {/* Ki bar */}\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`,\n }}\n data-testid=\"ki-bar\"\n title={`Ki: ${ki}%`}\n >\n <div\n style={{\n width: `${kiPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.PRIMARY_CYAN),\n transition: \"width 0.3s ease\",\n boxShadow:\n kiPercent > 80\n ? `0 0 ${isMobile ? \"4px\" : \"6px\"} ${toHexColor(KOREAN_COLORS.PRIMARY_CYAN)}`\n : \"none\",\n }}\n />\n </div>\n\n {/* Balance state indicator */}\n <div\n style={{\n fontSize: sizing.fontSize,\n color: balanceColor,\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n padding: \"2px 4px\",\n background: \"rgba(0,0,0,0.4)\",\n borderRadius: \"2px\",\n }}\n data-testid=\"balance-indicator\"\n title={`Balance: ${balance}`}\n >\n {balanceTextKorean}\n </div>\n\n {/* Consciousness indicator (if below 100%) */}\n {consciousnessPercent < 100 && (\n <div\n style={{\n height: sizing.thinBarHeight,\n background: \"rgba(0,0,0,0.6)\",\n borderRadius: \"2px\",\n overflow: \"hidden\",\n border: `1px solid ${toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE)}`,\n }}\n data-testid=\"consciousness-bar\"\n title={`Consciousness: ${consciousness}%`}\n >\n <div\n style={{\n width: `${consciousnessPercent}%`,\n height: \"100%\",\n background: toHexColor(KOREAN_COLORS.CONSCIOUSNESS_PURPLE),\n transition: \"width 0.3s ease\",\n }}\n />\n </div>\n )}\n\n {/* Pain indicator (if above 20%) */}\n {pain > 20 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.PAIN_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"pain-indicator\"\n title={`Pain: ${pain}%`}\n >\n 통증 {Math.round(pain)}%\n </div>\n )}\n\n {/* Blood loss indicator (if above 10%) */}\n {bloodLoss && bloodLoss > 10 && (\n <div\n style={{\n fontSize: sizing.fontSize,\n color: toHexColor(KOREAN_COLORS.BLOODLOSS_INDICATOR),\n textAlign: \"center\",\n fontWeight: \"bold\",\n textShadow: \"0 0 4px rgba(0,0,0,0.8)\",\n }}\n data-testid=\"bloodloss-indicator\"\n title={`Blood Loss: ${bloodLoss}%`}\n >\n 출혈 {Math.round(bloodLoss)}%\n </div>\n )}\n </div>\n </Html>\n );\n};\n\nexport default PlayerStateIndicators;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StanceSymbol3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceSymbol3D.tsx"],"sourcesContent":["/**\n * StanceSymbol3D - Floating trigram symbol above player\n *\n * Displays the Unicode trigram symbol (☰☱☲☳☴☵☶☷) floating above the player's head,\n * with rotation animation and pulsing glow effect. Provides immediate visual feedback\n * of the current stance to the player and observers.\n *\n * @module components/three/StanceSymbol3D\n * @category 3D Components\n * @korean 자세기호3D\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport {\n getStanceColorHex,\n getStanceKoreanName,\n getTrigramSymbol,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceSymbol3D component\n */\nexport interface StanceSymbol3DProps {\n /** Current trigram stance to display */\n readonly stance: TrigramStance;\n /** Height offset above player (default: 2.5) */\n readonly heightOffset?: number;\n /** Whether symbol should rotate */\n readonly animated?: boolean;\n /** Symbol scale multiplier */\n readonly scale?: number;\n /** Show Korean name below symbol */\n readonly showName?: boolean;\n}\n\n/**\n * Animation constants for stance symbol\n */\nconst ANIMATION_CONSTANTS = {\n ROTATION_SPEED: 0.5,\n BOB_AMPLITUDE: 0.1,\n BOB_FREQUENCY: 2,\n} as const;\n\n/**\n * StanceSymbol3D Component\n *\n * Renders a floating trigram symbol above the player with:\n * - Rotation animation\n * - Pulsing glow effect\n * - Stance-specific coloring\n * - Optional Korean name display\n *\n * Uses Html from @react-three/drei for crisp text rendering that always faces camera.\n *\n * @example\n * ```tsx\n * <StanceSymbol3D\n * stance={TrigramStance.GEON}\n * heightOffset={2.5}\n * animated={true}\n * showName={true}\n * />\n * ```\n */\nexport const StanceSymbol3D: React.FC<StanceSymbol3DProps> = ({\n stance,\n heightOffset = 2.5,\n animated = true,\n scale = 1.0,\n showName = true,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n const symbol = useMemo(() => getTrigramSymbol(stance), [stance]);\n const koreanName = useMemo(() => getStanceKoreanName(stance), [stance]);\n const colorHex = useMemo(() => getStanceColorHex(stance), [stance]);\n\n useFrame((state) => {\n if (!animated || !groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n\n groupRef.current.rotation.y = time * ANIMATION_CONSTANTS.ROTATION_SPEED;\n\n groupRef.current.position.y =\n heightOffset +\n Math.sin(time * ANIMATION_CONSTANTS.BOB_FREQUENCY) *\n ANIMATION_CONSTANTS.BOB_AMPLITUDE;\n });\n\n return (\n <group\n ref={groupRef}\n position={[0, heightOffset, 0]}\n name=\"stance-symbol-3d\"\n >\n {/* Interactive Light Source - illuminates player head/shoulders */}\n <pointLight color={colorHex} intensity={2.5} distance={4} decay={2} />\n\n {/* Trigram symbol with glow effect */}\n <Html\n center\n distanceFactor={10}\n zIndexRange={[100, 0]}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n }}\n >\n {/* Main trigram symbol */}\n <div\n style={{\n fontSize: `${48 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `\n 0 0 10px ${colorHex},\n 0 0 20px ${colorHex},\n 0 0 30px ${colorHex}\n `,\n fontWeight: \"bold\",\n lineHeight: \"1\",\n animation: \"pulse 2s ease-in-out infinite\",\n }}\n data-testid=\"trigram-symbol\"\n >\n {symbol}\n </div>\n\n {/* Korean name below symbol */}\n {showName && (\n <div\n style={{\n fontSize: `${16 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `0 0 5px ${colorHex}`,\n fontWeight: \"bold\",\n letterSpacing: \"2px\",\n }}\n data-testid=\"stance-korean-name\"\n >\n {koreanName}\n </div>\n )}\n </div>\n\n {/* CSS animation for pulse effect */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.8; transform: scale(1.1); }\n }\n `}\n </style>\n </Html>\n </group>\n );\n};\n\nexport default StanceSymbol3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,sBAAsB;CAC1B,gBAAgB;CAChB,eAAe;CACf,eAAe;
|
|
1
|
+
{"version":3,"file":"StanceSymbol3D.js","names":[],"sources":["../../../../../src/components/shared/three/effects/StanceSymbol3D.tsx"],"sourcesContent":["/**\n * StanceSymbol3D - Floating trigram symbol above player\n *\n * Displays the Unicode trigram symbol (☰☱☲☳☴☵☶☷) floating above the player's head,\n * with rotation animation and pulsing glow effect. Provides immediate visual feedback\n * of the current stance to the player and observers.\n *\n * @module components/three/StanceSymbol3D\n * @category 3D Components\n * @korean 자세기호3D\n */\n\nimport { Html } from \"@react-three/drei\";\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport { TrigramStance } from \"../../../../types/common\";\nimport { FONT_FAMILY } from \"../../../../types/constants\";\nimport {\n getStanceColorHex,\n getStanceKoreanName,\n getTrigramSymbol,\n} from \"../../../../utils/stanceHelpers\";\n\n/**\n * Props for StanceSymbol3D component\n */\nexport interface StanceSymbol3DProps {\n /** Current trigram stance to display */\n readonly stance: TrigramStance;\n /** Height offset above player (default: 2.5) */\n readonly heightOffset?: number;\n /** Whether symbol should rotate */\n readonly animated?: boolean;\n /** Symbol scale multiplier */\n readonly scale?: number;\n /** Show Korean name below symbol */\n readonly showName?: boolean;\n}\n\n/**\n * Animation constants for stance symbol\n */\nconst ANIMATION_CONSTANTS = {\n ROTATION_SPEED: 0.5,\n BOB_AMPLITUDE: 0.1,\n BOB_FREQUENCY: 2,\n} as const;\n\n/**\n * StanceSymbol3D Component\n *\n * Renders a floating trigram symbol above the player with:\n * - Rotation animation\n * - Pulsing glow effect\n * - Stance-specific coloring\n * - Optional Korean name display\n *\n * Uses Html from @react-three/drei for crisp text rendering that always faces camera.\n *\n * @example\n * ```tsx\n * <StanceSymbol3D\n * stance={TrigramStance.GEON}\n * heightOffset={2.5}\n * animated={true}\n * showName={true}\n * />\n * ```\n */\nexport const StanceSymbol3D: React.FC<StanceSymbol3DProps> = ({\n stance,\n heightOffset = 2.5,\n animated = true,\n scale = 1.0,\n showName = true,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n const symbol = useMemo(() => getTrigramSymbol(stance), [stance]);\n const koreanName = useMemo(() => getStanceKoreanName(stance), [stance]);\n const colorHex = useMemo(() => getStanceColorHex(stance), [stance]);\n\n useFrame((state) => {\n if (!animated || !groupRef.current) return;\n\n const time = state.clock.elapsedTime;\n\n groupRef.current.rotation.y = time * ANIMATION_CONSTANTS.ROTATION_SPEED;\n\n groupRef.current.position.y =\n heightOffset +\n Math.sin(time * ANIMATION_CONSTANTS.BOB_FREQUENCY) *\n ANIMATION_CONSTANTS.BOB_AMPLITUDE;\n });\n\n return (\n <group\n ref={groupRef}\n position={[0, heightOffset, 0]}\n name=\"stance-symbol-3d\"\n >\n {/* Interactive Light Source - illuminates player head/shoulders */}\n <pointLight color={colorHex} intensity={2.5} distance={4} decay={2} />\n\n {/* Trigram symbol with glow effect */}\n <Html\n center\n distanceFactor={10}\n zIndexRange={[100, 0]}\n style={{\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: \"4px\",\n }}\n >\n {/* Main trigram symbol */}\n <div\n style={{\n fontSize: `${48 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `\n 0 0 10px ${colorHex},\n 0 0 20px ${colorHex},\n 0 0 30px ${colorHex}\n `,\n fontWeight: \"bold\",\n lineHeight: \"1\",\n animation: \"pulse 2s ease-in-out infinite\",\n }}\n data-testid=\"trigram-symbol\"\n >\n {symbol}\n </div>\n\n {/* Korean name below symbol */}\n {showName && (\n <div\n style={{\n fontSize: `${16 * scale}px`,\n fontFamily: FONT_FAMILY.KOREAN,\n color: colorHex,\n textShadow: `0 0 5px ${colorHex}`,\n fontWeight: \"bold\",\n letterSpacing: \"2px\",\n }}\n data-testid=\"stance-korean-name\"\n >\n {koreanName}\n </div>\n )}\n </div>\n\n {/* CSS animation for pulse effect */}\n <style>\n {`\n @keyframes pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.8; transform: scale(1.1); }\n }\n `}\n </style>\n </Html>\n </group>\n );\n};\n\nexport default StanceSymbol3D;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,sBAAsB;CAC1B,gBAAgB;CAChB,eAAe;CACf,eAAe;AACjB;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,kBAAiD,EAC5D,QACA,eAAe,KACf,WAAW,MACX,QAAQ,GACR,WAAW,WACP;CACJ,MAAM,WAAW,OAAoB,IAAI;CAEzC,MAAM,SAAS,cAAc,iBAAiB,MAAM,GAAG,CAAC,MAAM,CAAC;CAC/D,MAAM,aAAa,cAAc,oBAAoB,MAAM,GAAG,CAAC,MAAM,CAAC;CACtE,MAAM,WAAW,cAAc,kBAAkB,MAAM,GAAG,CAAC,MAAM,CAAC;CAElE,UAAU,UAAU;EAClB,IAAI,CAAC,YAAY,CAAC,SAAS,SAAS;EAEpC,MAAM,OAAO,MAAM,MAAM;EAEzB,SAAS,QAAQ,SAAS,IAAI,OAAO,oBAAoB;EAEzD,SAAS,QAAQ,SAAS,IACxB,eACA,KAAK,IAAI,OAAO,oBAAoB,aAAa,IAC/C,oBAAoB;CAC1B,CAAC;CAED,OACE,qBAAC,SAAD;EACE,KAAK;EACL,UAAU;GAAC;GAAG;GAAc;EAAC;EAC7B,MAAK;YAHP,CAME,oBAAC,cAAD;GAAY,OAAO;GAAU,WAAW;GAAK,UAAU;GAAG,OAAO;EAAI,CAAA,GAGrE,qBAAC,MAAD;GACE,QAAA;GACA,gBAAgB;GAChB,aAAa,CAAC,KAAK,CAAC;GACpB,OAAO;IACL,eAAe;IACf,YAAY;GACd;aAPF,CASE,qBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,eAAe;KACf,YAAY;KACZ,KAAK;IACP;cANF,CASE,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,KAAK,MAAM;MACxB,YAAY,YAAY;MACxB,OAAO;MACP,YAAY;2BACC,SAAS;2BACT,SAAS;2BACT,SAAS;;MAEtB,YAAY;MACZ,YAAY;MACZ,WAAW;KACb;KACA,eAAY;eAEX;IACE,CAAA,GAGJ,YACC,oBAAC,OAAD;KACE,OAAO;MACL,UAAU,GAAG,KAAK,MAAM;MACxB,YAAY,YAAY;MACxB,OAAO;MACP,YAAY,WAAW;MACvB,YAAY;MACZ,eAAe;KACjB;KACA,eAAY;eAEX;IACE,CAAA,CAEJ;OAGL,oBAAC,SAAD,EAAA,UACG;;;;;YAMI,CAAA,CACH;IACD;;AAEX"}
|