blacktrigram 0.7.97 → 0.7.99
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/AudioManager.js +2 -4
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/components/screens/combat/CombatScreen3D.js +8 -9
- 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 +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 +2 -1
- package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.js +3 -1
- package/lib/components/screens/combat/components/effects/InternalDamage3D.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 +16 -15
- 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.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.map +1 -1
- package/lib/components/screens/combat/components/hud/CombatRightHUD.js.map +1 -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/PlayerStateOverlayHtml.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/InputBufferDisplay.js.map +1 -1
- package/lib/components/screens/combat/components/indicators/TechniqueNameDisplay.js.map +1 -1
- package/lib/components/screens/combat/hooks/useAICombat.js +0 -1
- package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatActions.js +7 -6
- package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js +3 -1
- package/lib/components/screens/combat/hooks/useCombatLayout.js.map +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 +7 -6
- 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 +34 -30
- 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/endscreen/EndScreen3D.js +2 -2
- package/lib/components/screens/endscreen/EndScreen3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/DefeatAnimation3D.js +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 +2 -2
- package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js +2 -1
- package/lib/components/screens/endscreen/components/WinnerDisplayOverlayHtml.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +2 -4
- 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.map +1 -1
- package/lib/components/screens/training/TrainingScreen3D.js +10 -9
- package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
- package/lib/components/screens/training/components/AnatomyControlsOverlayHtml.js +14 -13
- 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 +8 -7
- package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js +15 -13
- package/lib/components/screens/training/components/VitalPointTrainingOverlayHtml.js.map +1 -1
- 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 +3 -2
- 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/useTrainingActions.js +1 -2
- package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js +3 -1
- package/lib/components/screens/training/hooks/useTrainingLayout.js.map +1 -1
- package/lib/components/shared/base/BaseButton.js.map +1 -1
- package/lib/components/shared/base/BaseButtonOverlayHtml.js +59 -53
- package/lib/components/shared/base/BaseButtonOverlayHtml.js.map +1 -1
- package/lib/components/shared/base/BasePanel.js +19 -18
- package/lib/components/shared/base/BasePanel.js.map +1 -1
- package/lib/components/shared/base/BaseText.js.map +1 -1
- package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
- package/lib/components/shared/mobile/GestureRecognizerPure.js +22 -18
- package/lib/components/shared/mobile/GestureRecognizerPure.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/VirtualDPad.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js +4 -5
- package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
- package/lib/components/shared/three/anatomy/BoneClothing.js +29 -24
- 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 +3 -2
- package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
- package/lib/components/shared/three/effects/DamageNumbers.js +2 -1
- package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
- package/lib/components/shared/three/effects/HitEffects3D.js +3 -2
- 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 +6 -5
- package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
- package/lib/components/shared/three/indicators/GuardIndicator.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.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/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 +3 -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/MenuList.js.map +1 -1
- package/lib/components/shared/three/ui/PlayerHUD.js +2 -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 +25 -24
- package/lib/components/shared/three/ui/TechniqueBar.js.map +1 -1
- package/lib/components/shared/three/ui/TechniqueCard.js +1 -3
- 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 +2 -1
- package/lib/components/shared/ui/BackButton.js.map +1 -1
- package/lib/components/shared/ui/BaseHUDContainer.js +69 -68
- 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/hooks/useKeyboardControls.js +0 -1
- package/lib/hooks/useKeyboardControls.js.map +1 -1
- package/lib/hooks/useSkeletalAnimation.js +12 -3
- package/lib/hooks/useSkeletalAnimation.js.map +1 -1
- package/lib/systems/CombatSystem.js +6 -2
- package/lib/systems/CombatSystem.js.map +1 -1
- package/lib/systems/PlayerEffectManager.js +0 -1
- package/lib/systems/PlayerEffectManager.js.map +1 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/DecisionTree.js +10 -21
- package/lib/systems/ai/DecisionTree.js.map +1 -1
- package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
- package/lib/systems/animation/systems/AdvancedJointMovements.js +1 -3
- package/lib/systems/animation/systems/AdvancedJointMovements.js.map +1 -1
- package/lib/systems/animation/systems/MuscleActivation.js +3 -4
- package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
- package/lib/systems/bodypart/MovementPenaltySystem.js.map +1 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js +2 -1
- package/lib/systems/bodypart/PlayerInjuryTrackingManager.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/GrappleSystem.js +3 -9
- package/lib/systems/combat/GrappleSystem.js.map +1 -1
- package/lib/systems/combat/LimbExposureSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js +1 -1
- package/lib/systems/movement/InjuryMovementModifier.js.map +1 -1
- package/lib/systems/physics/CoordinateMapper.js.map +1 -1
- package/lib/systems/vitalpoint/DamageCalculator.js +8 -5
- package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/utils/accessibility.js +0 -1
- package/lib/utils/accessibility.js.map +1 -1
- package/lib/utils/effectUtils.js +1 -3
- package/lib/utils/effectUtils.js.map +1 -1
- package/lib/utils/inputSystem.js +0 -2
- package/lib/utils/inputSystem.js.map +1 -1
- package/lib/utils/koreanThemeHelpers.js +6 -3
- package/lib/utils/koreanThemeHelpers.js.map +1 -1
- package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
- package/lib/utils/playerUtils.js +4 -2
- package/lib/utils/playerUtils.js.map +1 -1
- package/package.json +6 -6
|
@@ -104,7 +104,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
104
104
|
if (boneName === "spine_middle") {
|
|
105
105
|
const height = 59 / 100 * torsoScale * 1.2;
|
|
106
106
|
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;
|
|
107
|
-
const
|
|
107
|
+
const torsoRadius = (physicalAttributes.shoulderWidth / 100 * .5 * bodyThickness + PECTORALS_RADIUS * bodyThickness) * .5;
|
|
108
|
+
const clothingRadius = calculateClothingRadius(torsoRadius, 1, clothingThickness);
|
|
108
109
|
attachments.push({
|
|
109
110
|
geometry: new THREE.CylinderGeometry(clothingRadius * .95, clothingRadius * 1.05, height, 16),
|
|
110
111
|
localOffset: new THREE.Vector3(0, 0, 0),
|
|
@@ -118,7 +119,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
118
119
|
});
|
|
119
120
|
}
|
|
120
121
|
if (boneName === "upper_arm_L" || boneName === "upper_arm_R") {
|
|
121
|
-
const
|
|
122
|
+
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;
|
|
123
|
+
const clothingRadius = calculateClothingRadius(BICEP_RADIUS, bodyThickness, clothingThickness);
|
|
122
124
|
const upperArmLength = physicalAttributes.armLength / 100 * .45;
|
|
123
125
|
attachments.push({
|
|
124
126
|
geometry: new THREE.CylinderGeometry(clothingRadius * 1.05, clothingRadius * .95, upperArmLength, 12),
|
|
@@ -133,7 +135,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
137
|
if (boneName === "forearm_L" || boneName === "forearm_R") {
|
|
136
|
-
const
|
|
138
|
+
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * .8;
|
|
139
|
+
const clothingRadius = calculateClothingRadius(FOREARM_RADIUS, bodyThickness, clothingThickness);
|
|
137
140
|
const forearmLength = physicalAttributes.armLength / 100 * .4;
|
|
138
141
|
attachments.push({
|
|
139
142
|
geometry: new THREE.CylinderGeometry(clothingRadius * .98, clothingRadius * .85, forearmLength, 12),
|
|
@@ -150,7 +153,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
150
153
|
break;
|
|
151
154
|
case "pants":
|
|
152
155
|
if (boneName === "thigh_L" || boneName === "thigh_R") {
|
|
153
|
-
const
|
|
156
|
+
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 1.3;
|
|
157
|
+
const clothingRadius = calculateClothingRadius(QUAD_RADIUS, bodyThickness, clothingThickness);
|
|
154
158
|
const thighLength = physicalAttributes.legLength / 100 * legScale * .45;
|
|
155
159
|
attachments.push({
|
|
156
160
|
geometry: new THREE.CylinderGeometry(clothingRadius * 1.08, clothingRadius * .92, thighLength, 16),
|
|
@@ -165,7 +169,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
165
169
|
});
|
|
166
170
|
}
|
|
167
171
|
if (boneName === "shin_L" || boneName === "shin_R") {
|
|
168
|
-
const
|
|
172
|
+
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;
|
|
173
|
+
const clothingRadius = calculateClothingRadius(CALF_RADIUS, bodyThickness, clothingThickness);
|
|
169
174
|
const shinLength = physicalAttributes.legLength / 100 * legScale * .42;
|
|
170
175
|
attachments.push({
|
|
171
176
|
geometry: new THREE.CylinderGeometry(clothingRadius * .95, clothingRadius * .75, shinLength, 16),
|
|
@@ -183,7 +188,8 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
183
188
|
case "belt":
|
|
184
189
|
if (boneName === "pelvis") {
|
|
185
190
|
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;
|
|
186
|
-
const
|
|
191
|
+
const waistRadius = CORE_RADIUS * bodyThickness;
|
|
192
|
+
const beltRadius = calculateClothingRadius(waistRadius, 1, clothingThickness + .005);
|
|
187
193
|
attachments.push({
|
|
188
194
|
geometry: new THREE.CylinderGeometry(beltRadius, beltRadius, .06, 16),
|
|
189
195
|
localOffset: new THREE.Vector3(0, 0, 0),
|
|
@@ -197,24 +203,23 @@ var getAttachmentsForItem = (item, boneName, bodyThickness, torsoScale, legScale
|
|
|
197
203
|
});
|
|
198
204
|
}
|
|
199
205
|
break;
|
|
200
|
-
case "vest":
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
break;
|
|
206
|
+
case "vest": if (boneName === "spine_middle") {
|
|
207
|
+
const height = .4425 * torsoScale;
|
|
208
|
+
const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;
|
|
209
|
+
const torsoRadius = (physicalAttributes.shoulderWidth / 100 * .5 * bodyThickness + (PECTORALS_RADIUS + .01) * bodyThickness) * .5;
|
|
210
|
+
const vestRadius = calculateClothingRadius(torsoRadius, 1, clothingThickness + .008);
|
|
211
|
+
attachments.push({
|
|
212
|
+
geometry: new THREE.CylinderGeometry(vestRadius * .97, vestRadius * 1.03, height, 16),
|
|
213
|
+
localOffset: new THREE.Vector3(0, 0, 0),
|
|
214
|
+
localRotation: new THREE.Euler(0, 0, 0),
|
|
215
|
+
color: item.colorPrimary,
|
|
216
|
+
emissiveColor: item.colorEmissive,
|
|
217
|
+
emissiveIntensity: item.emissiveIntensity,
|
|
218
|
+
metalness: item.metalness,
|
|
219
|
+
roughness: item.roughness,
|
|
220
|
+
itemId: item.id
|
|
221
|
+
});
|
|
222
|
+
}
|
|
218
223
|
}
|
|
219
224
|
return attachments;
|
|
220
225
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BoneClothing.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneClothing.tsx"],"sourcesContent":["/**\n * Bone-attached clothing system for realistic clothing movement with skeleton\n *\n * **Enhanced Features (v0.6.23+)**:\n * - **Subsurface Scattering**: Realistic fabric translucency with light transmission\n * - **Physical Materials**: Advanced PBR materials with clearcoat and reflectivity\n * - **Body Thickness Scaling**: Accurate clothing sizing based on muscle/fat mass\n * - **Archetype Styling**: Unique clothing sets for all 5 player archetypes\n *\n * Clothing is rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n * This mirrors the BoneMuscles approach for consistent rendering.\n *\n * **Visual Quality**:\n * - Transmission: 0.05 (minimal light passing through fabric)\n * - Thickness: 0.2 (thin fabric simulation)\n * - IOR: 1.4 (realistic fabric index of refraction)\n * - Clearcoat: 0.3 (surface depth and sheen)\n * - Double-sided rendering for cloth folding\n *\n * **Performance**:\n * - Geometry disposal on unmount (prevents memory leaks)\n * - Material cleanup system\n * - Optimized attachment calculations with useMemo\n *\n * @module components/three/BoneClothing\n * @category 3D Components\n * @korean 뼈부착의류시스템\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BICEP_RADIUS,\n CALF_RADIUS,\n CLOTHING_THICKNESS_FITTED,\n CORE_RADIUS,\n FOREARM_RADIUS,\n PECTORALS_RADIUS,\n QUAD_RADIUS,\n calculateClothingRadius,\n} from \"../../../../constants/bodyDimensions\";\nimport { getArchetypeClothing } from \"../../../../data/archetypeClothing\";\nimport type {\n ClothingItem,\n ClothingMaterial,\n} from \"../../../../types/clothing\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport {\n FABRIC_PRESETS,\n generateFabricTextureSet,\n type FabricTextureSet,\n} from \"../../../../utils/fabricTextures\";\n\n/**\n * Clothing attachment configuration for a bone\n *\n * Defines how clothing is positioned relative to its parent bone.\n * Position is in local bone space, so clothing moves with the bone.\n *\n * @korean 의류부착설정\n */\nexport interface ClothingAttachment {\n /** Geometry for this clothing piece */\n readonly geometry: THREE.BufferGeometry;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Color of the clothing */\n readonly color: number;\n /** Emissive color for glow effects */\n readonly emissiveColor?: number;\n /** Emissive intensity */\n readonly emissiveIntensity?: number;\n /** Metalness for material */\n readonly metalness?: number;\n /** Roughness for material */\n readonly roughness?: number;\n /** Clothing item ID */\n readonly itemId: string;\n}\n\n/**\n * Props for BoneClothing component\n */\nexport interface BoneClothingProps {\n /** Name of the bone this clothing attaches to */\n readonly boneName: string;\n /** Player archetype for clothing style */\n readonly archetype: PlayerArchetype;\n /** Physical attributes for sizing */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n}\n\n/**\n * Calculate body thickness multiplier based on muscle mass and fat mass\n *\n * Uses linear scaling with reasonable limits to prevent \"michelin man\" effect.\n * This matches the BodySurface calculation for consistency.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Get fabric preset based on clothing material type\n */\nconst getFabricPreset = (\n material: ClothingMaterial,\n): keyof typeof FABRIC_PRESETS => {\n switch (material) {\n case \"fabric\":\n return \"dobok\";\n case \"tactical\":\n case \"synthetic\":\n return \"tactical\";\n case \"leather\":\n case \"armored\":\n return \"leather\";\n case \"cybernetic\":\n return \"silk\"; // Shiny synthetic look\n default:\n return \"dobok\";\n }\n};\n\n/**\n * Convert number color to hex string\n */\nconst numberToHex = (color: number): string => {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n};\n\n/**\n * Get clothing attachments for a specific bone\n */\nconst getClothingForBone = (\n boneName: string,\n archetype: PlayerArchetype,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const clothingSet = getArchetypeClothing(archetype);\n const attachments: ClothingAttachment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n const torsoScale = physicalAttributes.torsoLength / 59;\n const legScale = physicalAttributes.legLength / 96;\n\n for (const item of clothingSet.items) {\n const attachmentsForItem = getAttachmentsForItem(\n item,\n boneName,\n bodyThickness,\n torsoScale,\n legScale,\n physicalAttributes,\n );\n attachments.push(...attachmentsForItem);\n }\n\n return attachments;\n};\n\n/**\n * Generate clothing attachments for a specific item on a bone\n */\nconst getAttachmentsForItem = (\n item: ClothingItem,\n boneName: string,\n bodyThickness: number,\n torsoScale: number,\n legScale: number,\n physicalAttributes: {\n shoulderWidth: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const fitScaleMap: Record<string, number> = {\n tight: 1.08,\n fitted: 1.15,\n loose: 1.25,\n oversized: 1.4,\n };\n const fitScale = fitScaleMap[item.fit] ?? 1.15;\n const attachments: ClothingAttachment[] = [];\n\n switch (item.type) {\n case \"torso\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * torsoScale * 1.2; // Using base torsoLength\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n PECTORALS_RADIUS * bodyThickness) *\n 0.5;\n const clothingRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied above\n clothingThickness,\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Slightly narrower at top (chest taper)\n clothingRadius * 1.05, // Slightly wider at bottom (waist)\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n\n if (boneName === \"upper_arm_L\" || boneName === \"upper_arm_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n BICEP_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const upperArmLength = (physicalAttributes.armLength / 100) * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.05, // Slightly larger at top\n clothingRadius * 0.95, // Tapers slightly\n upperArmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -upperArmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-upper-${boneName}`,\n });\n }\n\n if (boneName === \"forearm_L\" || boneName === \"forearm_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 0.8;\n const clothingRadius = calculateClothingRadius(\n FOREARM_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const forearmLength = (physicalAttributes.armLength / 100) * 0.4;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.98, // Slightly smaller at top\n clothingRadius * 0.85, // Tapers toward wrist\n forearmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -forearmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-forearm-${boneName}`,\n });\n }\n break;\n\n case \"pants\":\n if (boneName === \"thigh_L\" || boneName === \"thigh_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 1.3;\n const clothingRadius = calculateClothingRadius(\n QUAD_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const thighLength =\n (physicalAttributes.legLength / 100) * legScale * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.08, // Larger at hip\n clothingRadius * 0.92, // Tapers toward knee\n thighLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -thighLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-thigh-${boneName}`,\n });\n }\n\n if (boneName === \"shin_L\" || boneName === \"shin_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n CALF_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const shinLength =\n (physicalAttributes.legLength / 100) * legScale * 0.42;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Larger at knee\n clothingRadius * 0.75, // Tapers toward ankle\n shinLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -shinLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-shin-${boneName}`,\n });\n }\n break;\n\n case \"belt\":\n if (boneName === \"pelvis\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const waistRadius = CORE_RADIUS * bodyThickness;\n const beltRadius = calculateClothingRadius(\n waistRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.005, // Extra so belt sits outside clothing\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n beltRadius,\n beltRadius,\n 0.06, // Belt height\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n\n case \"vest\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * 0.75 * torsoScale;\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n (PECTORALS_RADIUS + 0.01) * bodyThickness) *\n 0.5;\n const vestRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.008, // Extra gap for layering over shirt\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n vestRadius * 0.97, // Slightly narrower at top\n vestRadius * 1.03, // Slightly wider at bottom\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n }\n\n return attachments;\n};\n\n/**\n * BoneClothing Component\n *\n * Renders clothing attached to a specific bone.\n * Clothing inherits bone transformations automatically through the scene graph.\n *\n * @korean 뼈부착의류\n */\nexport const BoneClothing: React.FC<BoneClothingProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n}) => {\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 62,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n const attachments = useMemo(\n () => getClothingForBone(boneName, archetype, attrs),\n [boneName, archetype, attrs],\n );\n\n const clothingSet = useMemo(\n () => getArchetypeClothing(archetype),\n [archetype],\n );\n\n /**\n * Generate fabric textures for realistic dobok (도복) rendering\n *\n * Creates procedural weave patterns, normal maps, and roughness maps\n * for enhanced cloth realism without external image assets.\n *\n * @korean 직물텍스처생성\n */\n const fabricTextures = useMemo(() => {\n const textureMap = new Map<string, FabricTextureSet>();\n\n for (const item of clothingSet.items) {\n if (!textureMap.has(item.material)) {\n const preset = getFabricPreset(item.material);\n const colorHex = numberToHex(item.colorPrimary);\n textureMap.set(\n item.material,\n generateFabricTextureSet(colorHex, preset),\n );\n }\n }\n\n return textureMap;\n }, [clothingSet]);\n\n useEffect(() => {\n return () => {\n fabricTextures.forEach((textureSet) => textureSet.dispose());\n };\n }, [fabricTextures]);\n\n /**\n * Create materials with advanced physical properties for realistic cloth rendering\n *\n * Enhanced with:\n * - **Fabric textures**: Procedural weave patterns for dobok authenticity\n * - **Normal maps**: Surface detail for thread texture visibility\n * - **Roughness maps**: Realistic light scattering on fabric surface\n * - Subsurface scattering: Light transmission through fabric for realism\n * - Clearcoat: Surface depth and sheen for material quality\n * - Double-sided rendering: Proper display when cloth folds\n * - Physical properties: IOR, reflectivity for authentic appearance\n *\n * @korean 향상된물리재료생성\n */\n const materials = useMemo(() => {\n return attachments.map((attachment) => {\n const clothingItem = clothingSet.items.find((item) =>\n attachment.itemId.startsWith(item.id),\n );\n const materialType = clothingItem?.material ?? \"fabric\";\n const textureSet = fabricTextures.get(materialType);\n\n let normalScale: THREE.Vector2 | undefined;\n try {\n normalScale = new THREE.Vector2(0.3, 0.3);\n } catch {\n normalScale = undefined;\n }\n\n const mat = new THREE.MeshPhysicalMaterial({\n color: attachment.color,\n map: textureSet?.colorMap ?? null,\n normalMap: textureSet?.normalMap ?? null,\n normalScale, // Subtle normal effect\n roughnessMap: textureSet?.roughnessMap ?? null,\n emissive: attachment.emissiveColor ?? 0x000000,\n emissiveIntensity: attachment.emissiveIntensity ?? 0,\n metalness: attachment.metalness ?? 0.1, // Lower metalness for fabric\n roughness: attachment.roughness ?? 0.8, // Higher roughness for cloth\n clearcoat: 0.2,\n clearcoatRoughness: 0.6,\n transmission: 0.03, // Minimal light transmission through fabric\n thickness: 0.15, // Thin fabric thickness\n ior: 1.4, // Index of refraction for fabric (lower than glass)\n reflectivity: 0.2,\n side: THREE.DoubleSide,\n flatShading: false,\n });\n return mat;\n });\n }, [attachments, clothingSet, fabricTextures]);\n\n useEffect(() => {\n return () => {\n materials.forEach((mat) => mat.dispose());\n };\n }, [materials]);\n\n useEffect(() => {\n return () => {\n attachments.forEach((attachment) => {\n attachment.geometry.dispose();\n });\n };\n }, [attachments]);\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment, index) => (\n <mesh\n key={attachment.itemId}\n geometry={attachment.geometry}\n material={materials[index]}\n position={attachment.localOffset.toArray()}\n rotation={[\n attachment.localRotation.x,\n attachment.localRotation.y,\n attachment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`clothing-${attachment.itemId}`}\n />\n ))}\n </>\n );\n};\n\nexport default BoneClothing;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BoneClothing.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneClothing.tsx"],"sourcesContent":["/**\n * Bone-attached clothing system for realistic clothing movement with skeleton\n *\n * **Enhanced Features (v0.6.23+)**:\n * - **Subsurface Scattering**: Realistic fabric translucency with light transmission\n * - **Physical Materials**: Advanced PBR materials with clearcoat and reflectivity\n * - **Body Thickness Scaling**: Accurate clothing sizing based on muscle/fat mass\n * - **Archetype Styling**: Unique clothing sets for all 5 player archetypes\n *\n * Clothing is rendered as children of their parent bones, inheriting bone\n * transformations automatically for proper movement during animation.\n * This mirrors the BoneMuscles approach for consistent rendering.\n *\n * **Visual Quality**:\n * - Transmission: 0.05 (minimal light passing through fabric)\n * - Thickness: 0.2 (thin fabric simulation)\n * - IOR: 1.4 (realistic fabric index of refraction)\n * - Clearcoat: 0.3 (surface depth and sheen)\n * - Double-sided rendering for cloth folding\n *\n * **Performance**:\n * - Geometry disposal on unmount (prevents memory leaks)\n * - Material cleanup system\n * - Optimized attachment calculations with useMemo\n *\n * @module components/three/BoneClothing\n * @category 3D Components\n * @korean 뼈부착의류시스템\n */\n\nimport React, { useEffect, useMemo } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BICEP_RADIUS,\n CALF_RADIUS,\n CLOTHING_THICKNESS_FITTED,\n CORE_RADIUS,\n FOREARM_RADIUS,\n PECTORALS_RADIUS,\n QUAD_RADIUS,\n calculateClothingRadius,\n} from \"../../../../constants/bodyDimensions\";\nimport { getArchetypeClothing } from \"../../../../data/archetypeClothing\";\nimport type {\n ClothingItem,\n ClothingMaterial,\n} from \"../../../../types/clothing\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport {\n FABRIC_PRESETS,\n generateFabricTextureSet,\n type FabricTextureSet,\n} from \"../../../../utils/fabricTextures\";\n\n/**\n * Clothing attachment configuration for a bone\n *\n * Defines how clothing is positioned relative to its parent bone.\n * Position is in local bone space, so clothing moves with the bone.\n *\n * @korean 의류부착설정\n */\nexport interface ClothingAttachment {\n /** Geometry for this clothing piece */\n readonly geometry: THREE.BufferGeometry;\n /** Local position offset from bone origin */\n readonly localOffset: THREE.Vector3;\n /** Local rotation relative to bone */\n readonly localRotation: THREE.Euler;\n /** Color of the clothing */\n readonly color: number;\n /** Emissive color for glow effects */\n readonly emissiveColor?: number;\n /** Emissive intensity */\n readonly emissiveIntensity?: number;\n /** Metalness for material */\n readonly metalness?: number;\n /** Roughness for material */\n readonly roughness?: number;\n /** Clothing item ID */\n readonly itemId: string;\n}\n\n/**\n * Props for BoneClothing component\n */\nexport interface BoneClothingProps {\n /** Name of the bone this clothing attaches to */\n readonly boneName: string;\n /** Player archetype for clothing style */\n readonly archetype: PlayerArchetype;\n /** Physical attributes for sizing */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth: number;\n readonly torsoLength: number;\n readonly armLength: number;\n readonly legLength: number;\n };\n}\n\n/**\n * Calculate body thickness multiplier based on muscle mass and fat mass\n *\n * Uses linear scaling with reasonable limits to prevent \"michelin man\" effect.\n * This matches the BodySurface calculation for consistency.\n *\n * @param muscleMass - Muscle mass in kg\n * @param fatMass - Fat mass in kg\n * @returns Body thickness multiplier (0.75 - 1.20)\n */\nconst calculateBodyThickness = (\n muscleMass: number,\n fatMass: number,\n): number => {\n const referenceMuscle = 35; // Reference: athletic build\n const referenceFat = 12; // Reference: low body fat\n\n const muscleRatio = muscleMass / referenceMuscle;\n const fatRatio = fatMass / referenceFat;\n\n const muscleContribution = (muscleRatio - 1.0) * 0.15;\n const fatContribution = (fatRatio - 1.0) * 0.2;\n\n return Math.max(\n 0.75,\n Math.min(1.2, 0.85 + muscleContribution + fatContribution),\n );\n};\n\n/**\n * Get fabric preset based on clothing material type\n */\nconst getFabricPreset = (\n material: ClothingMaterial,\n): keyof typeof FABRIC_PRESETS => {\n switch (material) {\n case \"fabric\":\n return \"dobok\";\n case \"tactical\":\n case \"synthetic\":\n return \"tactical\";\n case \"leather\":\n case \"armored\":\n return \"leather\";\n case \"cybernetic\":\n return \"silk\"; // Shiny synthetic look\n default:\n return \"dobok\";\n }\n};\n\n/**\n * Convert number color to hex string\n */\nconst numberToHex = (color: number): string => {\n return `#${color.toString(16).padStart(6, \"0\")}`;\n};\n\n/**\n * Get clothing attachments for a specific bone\n */\nconst getClothingForBone = (\n boneName: string,\n archetype: PlayerArchetype,\n physicalAttributes: {\n muscleMass: number;\n fatMass: number;\n shoulderWidth: number;\n torsoLength: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const clothingSet = getArchetypeClothing(archetype);\n const attachments: ClothingAttachment[] = [];\n\n const bodyThickness = calculateBodyThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n\n const torsoScale = physicalAttributes.torsoLength / 59;\n const legScale = physicalAttributes.legLength / 96;\n\n for (const item of clothingSet.items) {\n const attachmentsForItem = getAttachmentsForItem(\n item,\n boneName,\n bodyThickness,\n torsoScale,\n legScale,\n physicalAttributes,\n );\n attachments.push(...attachmentsForItem);\n }\n\n return attachments;\n};\n\n/**\n * Generate clothing attachments for a specific item on a bone\n */\nconst getAttachmentsForItem = (\n item: ClothingItem,\n boneName: string,\n bodyThickness: number,\n torsoScale: number,\n legScale: number,\n physicalAttributes: {\n shoulderWidth: number;\n armLength: number;\n legLength: number;\n },\n): ClothingAttachment[] => {\n const fitScaleMap: Record<string, number> = {\n tight: 1.08,\n fitted: 1.15,\n loose: 1.25,\n oversized: 1.4,\n };\n const fitScale = fitScaleMap[item.fit] ?? 1.15;\n const attachments: ClothingAttachment[] = [];\n\n switch (item.type) {\n case \"torso\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * torsoScale * 1.2; // Using base torsoLength\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n PECTORALS_RADIUS * bodyThickness) *\n 0.5;\n const clothingRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied above\n clothingThickness,\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Slightly narrower at top (chest taper)\n clothingRadius * 1.05, // Slightly wider at bottom (waist)\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n\n if (boneName === \"upper_arm_L\" || boneName === \"upper_arm_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n BICEP_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const upperArmLength = (physicalAttributes.armLength / 100) * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.05, // Slightly larger at top\n clothingRadius * 0.95, // Tapers slightly\n upperArmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -upperArmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-upper-${boneName}`,\n });\n }\n\n if (boneName === \"forearm_L\" || boneName === \"forearm_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 0.8;\n const clothingRadius = calculateClothingRadius(\n FOREARM_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const forearmLength = (physicalAttributes.armLength / 100) * 0.4;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.98, // Slightly smaller at top\n clothingRadius * 0.85, // Tapers toward wrist\n forearmLength,\n 12,\n ),\n localOffset: new THREE.Vector3(0, -forearmLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-sleeve-forearm-${boneName}`,\n });\n }\n break;\n\n case \"pants\":\n if (boneName === \"thigh_L\" || boneName === \"thigh_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale * 1.3;\n const clothingRadius = calculateClothingRadius(\n QUAD_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const thighLength =\n (physicalAttributes.legLength / 100) * legScale * 0.45;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 1.08, // Larger at hip\n clothingRadius * 0.92, // Tapers toward knee\n thighLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -thighLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-thigh-${boneName}`,\n });\n }\n\n if (boneName === \"shin_L\" || boneName === \"shin_R\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const clothingRadius = calculateClothingRadius(\n CALF_RADIUS,\n bodyThickness,\n clothingThickness,\n );\n const shinLength =\n (physicalAttributes.legLength / 100) * legScale * 0.42;\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n clothingRadius * 0.95, // Larger at knee\n clothingRadius * 0.75, // Tapers toward ankle\n shinLength,\n 16,\n ),\n localOffset: new THREE.Vector3(0, -shinLength * 0.4, 0),\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: `${item.id}-shin-${boneName}`,\n });\n }\n break;\n\n case \"belt\":\n if (boneName === \"pelvis\") {\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const waistRadius = CORE_RADIUS * bodyThickness;\n const beltRadius = calculateClothingRadius(\n waistRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.005, // Extra so belt sits outside clothing\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n beltRadius,\n beltRadius,\n 0.06, // Belt height\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n\n case \"vest\":\n if (boneName === \"spine_middle\") {\n const height = (59 / 100) * 0.75 * torsoScale;\n const clothingThickness = CLOTHING_THICKNESS_FITTED * fitScale;\n const torsoRadius =\n ((physicalAttributes.shoulderWidth / 100) * 0.5 * bodyThickness +\n (PECTORALS_RADIUS + 0.01) * bodyThickness) *\n 0.5;\n const vestRadius = calculateClothingRadius(\n torsoRadius,\n 1.0, // bodyThickness already applied\n clothingThickness + 0.008, // Extra gap for layering over shirt\n );\n attachments.push({\n geometry: new THREE.CylinderGeometry(\n vestRadius * 0.97, // Slightly narrower at top\n vestRadius * 1.03, // Slightly wider at bottom\n height,\n 16, // Smooth cylinder\n ),\n localOffset: new THREE.Vector3(0, 0, 0), // Centered on bone\n localRotation: new THREE.Euler(0, 0, 0),\n color: item.colorPrimary,\n emissiveColor: item.colorEmissive,\n emissiveIntensity: item.emissiveIntensity,\n metalness: item.metalness,\n roughness: item.roughness,\n itemId: item.id,\n });\n }\n break;\n }\n\n return attachments;\n};\n\n/**\n * BoneClothing Component\n *\n * Renders clothing attached to a specific bone.\n * Clothing inherits bone transformations automatically through the scene graph.\n *\n * @korean 뼈부착의류\n */\nexport const BoneClothing: React.FC<BoneClothingProps> = ({\n boneName,\n archetype,\n physicalAttributes,\n}) => {\n const attrs = useMemo(\n () =>\n physicalAttributes ?? {\n muscleMass: 35,\n fatMass: 12,\n shoulderWidth: 45,\n torsoLength: 59,\n armLength: 62,\n legLength: 96,\n },\n [physicalAttributes],\n );\n\n const attachments = useMemo(\n () => getClothingForBone(boneName, archetype, attrs),\n [boneName, archetype, attrs],\n );\n\n const clothingSet = useMemo(\n () => getArchetypeClothing(archetype),\n [archetype],\n );\n\n /**\n * Generate fabric textures for realistic dobok (도복) rendering\n *\n * Creates procedural weave patterns, normal maps, and roughness maps\n * for enhanced cloth realism without external image assets.\n *\n * @korean 직물텍스처생성\n */\n const fabricTextures = useMemo(() => {\n const textureMap = new Map<string, FabricTextureSet>();\n\n for (const item of clothingSet.items) {\n if (!textureMap.has(item.material)) {\n const preset = getFabricPreset(item.material);\n const colorHex = numberToHex(item.colorPrimary);\n textureMap.set(\n item.material,\n generateFabricTextureSet(colorHex, preset),\n );\n }\n }\n\n return textureMap;\n }, [clothingSet]);\n\n useEffect(() => {\n return () => {\n fabricTextures.forEach((textureSet) => textureSet.dispose());\n };\n }, [fabricTextures]);\n\n /**\n * Create materials with advanced physical properties for realistic cloth rendering\n *\n * Enhanced with:\n * - **Fabric textures**: Procedural weave patterns for dobok authenticity\n * - **Normal maps**: Surface detail for thread texture visibility\n * - **Roughness maps**: Realistic light scattering on fabric surface\n * - Subsurface scattering: Light transmission through fabric for realism\n * - Clearcoat: Surface depth and sheen for material quality\n * - Double-sided rendering: Proper display when cloth folds\n * - Physical properties: IOR, reflectivity for authentic appearance\n *\n * @korean 향상된물리재료생성\n */\n const materials = useMemo(() => {\n return attachments.map((attachment) => {\n const clothingItem = clothingSet.items.find((item) =>\n attachment.itemId.startsWith(item.id),\n );\n const materialType = clothingItem?.material ?? \"fabric\";\n const textureSet = fabricTextures.get(materialType);\n\n let normalScale: THREE.Vector2 | undefined;\n try {\n normalScale = new THREE.Vector2(0.3, 0.3);\n } catch {\n normalScale = undefined;\n }\n\n const mat = new THREE.MeshPhysicalMaterial({\n color: attachment.color,\n map: textureSet?.colorMap ?? null,\n normalMap: textureSet?.normalMap ?? null,\n normalScale, // Subtle normal effect\n roughnessMap: textureSet?.roughnessMap ?? null,\n emissive: attachment.emissiveColor ?? 0x000000,\n emissiveIntensity: attachment.emissiveIntensity ?? 0,\n metalness: attachment.metalness ?? 0.1, // Lower metalness for fabric\n roughness: attachment.roughness ?? 0.8, // Higher roughness for cloth\n clearcoat: 0.2,\n clearcoatRoughness: 0.6,\n transmission: 0.03, // Minimal light transmission through fabric\n thickness: 0.15, // Thin fabric thickness\n ior: 1.4, // Index of refraction for fabric (lower than glass)\n reflectivity: 0.2,\n side: THREE.DoubleSide,\n flatShading: false,\n });\n return mat;\n });\n }, [attachments, clothingSet, fabricTextures]);\n\n useEffect(() => {\n return () => {\n materials.forEach((mat) => mat.dispose());\n };\n }, [materials]);\n\n useEffect(() => {\n return () => {\n attachments.forEach((attachment) => {\n attachment.geometry.dispose();\n });\n };\n }, [attachments]);\n\n if (attachments.length === 0) {\n return null;\n }\n\n return (\n <>\n {attachments.map((attachment, index) => (\n <mesh\n key={attachment.itemId}\n geometry={attachment.geometry}\n material={materials[index]}\n position={attachment.localOffset.toArray()}\n rotation={[\n attachment.localRotation.x,\n attachment.localRotation.y,\n attachment.localRotation.z,\n ]}\n castShadow\n receiveShadow\n name={`clothing-${attachment.itemId}`}\n />\n ))}\n </>\n );\n};\n\nexport default BoneClothing;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BoneRenderer.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneRenderer.tsx"],"sourcesContent":["/**\n * BoneRenderer component for visualizing skeletal hierarchy\n *\n * Renders bone hierarchy as connected capsule meshes with proper transformations.\n * Recursively renders parent-child bone relationships for complete skeleton visualization.\n *\n * @module components/three/BoneRenderer\n * @category 3D Components\n * @korean 뼈렌더러컴포넌트\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BASE_BONE_RADIUS_RATIO,\n calculateBoneThickness,\n} from \"../../../../constants/bodyRenderingConstants\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type {\n FacialDamageState,\n FacialExpression,\n} from \"../../../../types/facial\";\nimport { DEFAULT_FACIAL_DAMAGE } from \"../../../../types/facial\";\nimport type { HandAnimationState } from \"../../../../types/hand-animation\";\nimport type { Bone, SkeletalRig } from \"../../../../types/skeletal\";\nimport { BoneMuscles } from \"./BoneAttachedMuscles\";\nimport { BoneClothing } from \"./BoneClothing\";\nimport { BodySurface } from \"./BodySurface\";\nimport Face3D from \"./Face3D\";\nimport Foot3D from \"./Foot3D\";\nimport Hand3D from \"./Hand3D\";\n\n/**\n * Props for BoneRenderer component\n *\n * @category Component Props\n * @korean 뼈렌더러속성\n */\nexport interface BoneRendererProps {\n /**\n * Skeletal rig to render\n * @korean 골격\n */\n readonly rig: SkeletalRig;\n\n /**\n * Bone color\n * @korean 뼈색상\n */\n readonly color?: number;\n\n /**\n * Whether to show bones (for debugging)\n * @korean 뼈표시여부\n */\n readonly showBones?: boolean;\n\n /**\n * Render mode: 'solid' for solid meshes, 'debug' for wireframe\n * @korean 렌더모드\n */\n readonly renderMode?: \"solid\" | \"debug\";\n\n /**\n * Left hand animation state\n * @korean 왼손애니메이션상태\n */\n readonly leftHandState?: HandAnimationState;\n\n /**\n * Right hand animation state\n * @korean 오른손애니메이션상태\n */\n readonly rightHandState?: HandAnimationState;\n\n /**\n * Distance from camera for LOD\n * @korean 카메라거리\n */\n readonly cameraDistance?: number;\n\n /**\n * Current facial expression\n * @korean 얼굴표정\n */\n readonly facialExpression?: FacialExpression;\n\n /**\n * Facial damage state\n * @korean 얼굴손상\n */\n readonly facialDamage?: FacialDamageState;\n\n /**\n * Opponent position for eye tracking\n * @korean 상대위치\n */\n readonly opponentPosition?: THREE.Vector3;\n\n /**\n * Enable facial expressions rendering\n * @korean 얼굴표정렌더링\n */\n readonly enableFacialExpressions?: boolean;\n\n /**\n * Enable eye tracking\n * @korean 눈추적활성화\n */\n readonly enableEyeTracking?: boolean;\n\n /**\n * Physical attributes for bone thickness scaling\n * @korean 신체속성\n */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth?: number;\n readonly torsoLength?: number;\n readonly armLength?: number;\n readonly legLength?: number;\n };\n\n /**\n * Player archetype for clothing style\n * @korean 플레이어원형\n */\n readonly archetype?: PlayerArchetype;\n\n /**\n * Muscle tension states for bone-attached muscles\n * Map of muscle group name to tension level (0-1)\n * @korean 근육상태들\n */\n readonly muscleStates?: Map<string, number>;\n\n /**\n * Whether player is exhausted (triggers muscle shaking)\n * @korean 피로여부\n */\n readonly isExhausted?: boolean;\n}\n\n/**\n * Single bone renderer with transformation\n *\n * Renders a single bone as a capsule connecting to its parent.\n *\n * @param bone - Bone to render\n * @param color - Bone color\n * @param renderMode - Render mode\n * @param leftHandState - Left hand animation state\n * @param rightHandState - Right hand animation state\n * @param cameraDistance - Distance from camera\n * @param boneThicknessMultiplier - Thickness multiplier for bone radius\n * @korean 단일뼈렌더러\n */\nconst SingleBone: React.FC<{\n readonly bone: Bone;\n readonly color: number;\n readonly renderMode: \"solid\" | \"debug\";\n readonly leftHandState?: HandAnimationState;\n readonly rightHandState?: HandAnimationState;\n readonly cameraDistance?: number;\n readonly facialExpression?: FacialExpression;\n readonly facialDamage?: FacialDamageState;\n readonly opponentPosition?: THREE.Vector3;\n readonly enableFacialExpressions?: boolean;\n readonly enableEyeTracking?: boolean;\n readonly boneThicknessMultiplier?: number;\n readonly muscleStates?: Map<string, number>;\n readonly isExhausted?: boolean;\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth?: number;\n readonly torsoLength?: number;\n readonly armLength?: number;\n readonly legLength?: number;\n };\n readonly archetype?: PlayerArchetype;\n}> = ({\n bone,\n color,\n renderMode,\n leftHandState,\n rightHandState,\n cameraDistance = 10,\n facialExpression,\n facialDamage,\n opponentPosition,\n enableFacialExpressions = false,\n enableEyeTracking = true,\n boneThicknessMultiplier = 1.0,\n muscleStates,\n isExhausted = false,\n physicalAttributes,\n archetype,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame(() => {\n if (groupRef.current) {\n groupRef.current.rotation.set(\n bone.rotation.x,\n bone.rotation.y,\n bone.rotation.z,\n );\n groupRef.current.position.set(\n bone.position.x,\n bone.position.y,\n bone.position.z,\n );\n }\n });\n\n const boneTransform = useMemo(() => {\n const length = bone.length;\n const capsuleDefaultDirection = new THREE.Vector3(0, 1, 0);\n\n let rotation = new THREE.Euler(0, 0, 0);\n let offset = new THREE.Vector3(0, -length / 2, 0);\n\n if (bone.parent) {\n const x = bone.position.x ?? 0;\n const y = bone.position.y ?? 0;\n const z = bone.position.z ?? 0;\n\n const positionLength = Math.sqrt(x * x + y * y + z * z);\n if (positionLength > 0.001) {\n const targetX = x / positionLength;\n const targetY = y / positionLength;\n const targetZ = z / positionLength;\n const target = new THREE.Vector3(targetX, targetY, targetZ);\n\n const quaternion = new THREE.Quaternion().setFromUnitVectors(\n capsuleDefaultDirection,\n target,\n );\n rotation = new THREE.Euler().setFromQuaternion(quaternion);\n\n offset = new THREE.Vector3(\n (-targetX * length) / 2,\n (-targetY * length) / 2,\n (-targetZ * length) / 2,\n );\n }\n }\n\n return { length, rotation, offset };\n }, [bone]);\n\n const hasMuscles = muscleStates !== undefined && muscleStates.size > 0;\n\n return (\n <group\n ref={groupRef}\n scale={bone.scale.toArray()}\n name={`bone-${bone.name}`}\n >\n {/* Bone capsule connecting to parent - ONLY render if no muscles (muscles provide body shape) */}\n {renderMode === \"solid\" && !hasMuscles ? (\n <mesh\n position={boneTransform.offset.toArray()}\n rotation={[\n boneTransform.rotation.x,\n boneTransform.rotation.y,\n boneTransform.rotation.z,\n ]}\n castShadow\n receiveShadow\n >\n <capsuleGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n boneThicknessMultiplier, // Radius scaled by thickness\n boneTransform.length, // Length unchanged\n 4,\n 8,\n ]}\n />\n <meshPhysicalMaterial\n color={color}\n metalness={0.5}\n roughness={0.4}\n clearcoat={1.0}\n clearcoatRoughness={0.1}\n envMapIntensity={1.0}\n />\n </mesh>\n ) : renderMode === \"debug\" ? (\n <mesh\n position={boneTransform.offset.toArray()}\n rotation={[\n boneTransform.rotation.x,\n boneTransform.rotation.y,\n boneTransform.rotation.z,\n ]}\n >\n <capsuleGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n boneThicknessMultiplier, // Radius scaled by thickness\n boneTransform.length, // Length unchanged\n 4,\n 8,\n ]}\n />\n <meshBasicMaterial\n color={color}\n wireframe={true}\n transparent={true}\n opacity={0.5}\n />\n </mesh>\n ) : null}\n\n {/* Joint sphere at bone position */}\n {renderMode === \"debug\" && (\n <mesh>\n <sphereGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n 1.2 *\n boneThicknessMultiplier,\n 8,\n 8,\n ]}\n />\n <meshBasicMaterial color={KOREAN_COLORS.PRIMARY_CYAN} />\n </mesh>\n )}\n\n {/* Render bone-attached muscles */}\n {renderMode === \"solid\" && muscleStates && (\n <BoneMuscles\n boneName={bone.name}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n />\n )}\n\n {/* Render body surface (skin/flesh layer) - provides continuous humanoid appearance */}\n {renderMode === \"solid\" && archetype && physicalAttributes && (\n <BodySurface\n boneName={bone.name}\n archetype={archetype}\n cameraDistance={cameraDistance}\n physicalAttributes={{\n muscleMass: physicalAttributes.muscleMass,\n fatMass: physicalAttributes.fatMass,\n shoulderWidth: physicalAttributes.shoulderWidth ?? 45,\n torsoLength: physicalAttributes.torsoLength ?? 59,\n armLength: physicalAttributes.armLength ?? 77,\n legLength: physicalAttributes.legLength ?? 96,\n }}\n />\n )}\n\n {/* Render bone-attached clothing */}\n {renderMode === \"solid\" && archetype && physicalAttributes && (\n <BoneClothing\n boneName={bone.name}\n archetype={archetype}\n physicalAttributes={{\n muscleMass: physicalAttributes.muscleMass,\n fatMass: physicalAttributes.fatMass,\n shoulderWidth: physicalAttributes.shoulderWidth ?? 45,\n torsoLength: physicalAttributes.torsoLength ?? 59,\n armLength: physicalAttributes.armLength ?? 77,\n legLength: physicalAttributes.legLength ?? 96,\n }}\n />\n )}\n\n {/* Render children recursively */}\n {bone.children.map((childBone) => (\n <SingleBone\n key={childBone.name}\n bone={childBone}\n color={color}\n renderMode={renderMode}\n leftHandState={leftHandState}\n rightHandState={rightHandState}\n cameraDistance={cameraDistance}\n facialExpression={facialExpression}\n facialDamage={facialDamage}\n opponentPosition={opponentPosition}\n enableFacialExpressions={enableFacialExpressions}\n enableEyeTracking={enableEyeTracking}\n boneThicknessMultiplier={boneThicknessMultiplier}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n archetype={archetype}\n />\n ))}\n\n {/* Add hands at hand bones with animation state */}\n {bone.name === \"hand_L\" && leftHandState && (\n <Hand3D\n side=\"left\"\n pose={leftHandState.currentPose}\n fingerCurl={leftHandState.currentFingerCurl}\n distanceFromCamera={cameraDistance}\n wristRotation={leftHandState.currentWristRotation}\n isHighlighted={leftHandState.isHighlighted}\n highlightMode={leftHandState.highlightMode}\n skinColor={color}\n scale={1.0}\n />\n )}\n {bone.name === \"hand_R\" && rightHandState && (\n <Hand3D\n side=\"right\"\n pose={rightHandState.currentPose}\n fingerCurl={rightHandState.currentFingerCurl}\n distanceFromCamera={cameraDistance}\n wristRotation={rightHandState.currentWristRotation}\n isHighlighted={rightHandState.isHighlighted}\n highlightMode={rightHandState.highlightMode}\n skinColor={color}\n scale={1.0}\n />\n )}\n\n {/* Add feet at foot bones with archetype scaling */}\n {bone.name === \"foot_L\" && (\n <Foot3D\n side=\"left\"\n skinColor={color}\n scale={boneThicknessMultiplier}\n isHighlighted={false}\n />\n )}\n {bone.name === \"foot_R\" && (\n <Foot3D\n side=\"right\"\n skinColor={color}\n scale={boneThicknessMultiplier}\n isHighlighted={false}\n />\n )}\n\n {/* Add facial features at head bone */}\n {bone.name === \"head\" && enableFacialExpressions && facialExpression && (\n <Face3D\n expression={facialExpression}\n damage={facialDamage ?? DEFAULT_FACIAL_DAMAGE}\n opponentPosition={opponentPosition ?? new THREE.Vector3(5, 2, 0)}\n headRotation={bone.rotation.clone()}\n enableEyeTracking={enableEyeTracking}\n enableDamageVisuals={true}\n isMobile={cameraDistance > 15}\n skinColor={color}\n />\n )}\n </group>\n );\n};\n\n/**\n * BoneRenderer component\n *\n * Renders complete skeletal rig with recursive bone hierarchy.\n *\n * @example\n * ```tsx\n * const rig = createHumanoidRig();\n * <BoneRenderer rig={rig} color={0xFF6B6B} renderMode=\"solid\" />\n * ```\n *\n * @korean 뼈렌더러컴포넌트\n */\nexport const BoneRenderer: React.FC<BoneRendererProps> = ({\n rig,\n color = KOREAN_COLORS.ACCENT_RED,\n showBones = true,\n renderMode = \"solid\",\n leftHandState,\n rightHandState,\n cameraDistance = 10,\n facialExpression,\n facialDamage,\n opponentPosition,\n enableFacialExpressions = false, // Default false to avoid breaking existing tests\n enableEyeTracking = true,\n physicalAttributes,\n muscleStates,\n isExhausted = false,\n archetype,\n}) => {\n const boneThicknessMultiplier = useMemo(() => {\n if (!physicalAttributes) return 1.0;\n return calculateBoneThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n }, [physicalAttributes]);\n\n if (!showBones) {\n return null;\n }\n\n return (\n <group name=\"bone-renderer\">\n <SingleBone\n bone={rig.root}\n color={color}\n renderMode={renderMode}\n leftHandState={leftHandState}\n rightHandState={rightHandState}\n cameraDistance={cameraDistance}\n facialExpression={facialExpression}\n facialDamage={facialDamage}\n opponentPosition={opponentPosition}\n enableFacialExpressions={enableFacialExpressions}\n enableEyeTracking={enableEyeTracking}\n boneThicknessMultiplier={boneThicknessMultiplier}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n archetype={archetype}\n />\n </group>\n );\n};\n\nexport default BoneRenderer;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BoneRenderer.js","names":[],"sources":["../../../../../src/components/shared/three/anatomy/BoneRenderer.tsx"],"sourcesContent":["/**\n * BoneRenderer component for visualizing skeletal hierarchy\n *\n * Renders bone hierarchy as connected capsule meshes with proper transformations.\n * Recursively renders parent-child bone relationships for complete skeleton visualization.\n *\n * @module components/three/BoneRenderer\n * @category 3D Components\n * @korean 뼈렌더러컴포넌트\n */\n\nimport { useFrame } from \"@react-three/fiber\";\nimport React, { useMemo, useRef } from \"react\";\nimport * as THREE from \"three\";\nimport {\n BASE_BONE_RADIUS_RATIO,\n calculateBoneThickness,\n} from \"../../../../constants/bodyRenderingConstants\";\nimport type { PlayerArchetype } from \"../../../../types/common\";\nimport { KOREAN_COLORS } from \"../../../../types/constants\";\nimport type {\n FacialDamageState,\n FacialExpression,\n} from \"../../../../types/facial\";\nimport { DEFAULT_FACIAL_DAMAGE } from \"../../../../types/facial\";\nimport type { HandAnimationState } from \"../../../../types/hand-animation\";\nimport type { Bone, SkeletalRig } from \"../../../../types/skeletal\";\nimport { BoneMuscles } from \"./BoneAttachedMuscles\";\nimport { BoneClothing } from \"./BoneClothing\";\nimport { BodySurface } from \"./BodySurface\";\nimport Face3D from \"./Face3D\";\nimport Foot3D from \"./Foot3D\";\nimport Hand3D from \"./Hand3D\";\n\n/**\n * Props for BoneRenderer component\n *\n * @category Component Props\n * @korean 뼈렌더러속성\n */\nexport interface BoneRendererProps {\n /**\n * Skeletal rig to render\n * @korean 골격\n */\n readonly rig: SkeletalRig;\n\n /**\n * Bone color\n * @korean 뼈색상\n */\n readonly color?: number;\n\n /**\n * Whether to show bones (for debugging)\n * @korean 뼈표시여부\n */\n readonly showBones?: boolean;\n\n /**\n * Render mode: 'solid' for solid meshes, 'debug' for wireframe\n * @korean 렌더모드\n */\n readonly renderMode?: \"solid\" | \"debug\";\n\n /**\n * Left hand animation state\n * @korean 왼손애니메이션상태\n */\n readonly leftHandState?: HandAnimationState;\n\n /**\n * Right hand animation state\n * @korean 오른손애니메이션상태\n */\n readonly rightHandState?: HandAnimationState;\n\n /**\n * Distance from camera for LOD\n * @korean 카메라거리\n */\n readonly cameraDistance?: number;\n\n /**\n * Current facial expression\n * @korean 얼굴표정\n */\n readonly facialExpression?: FacialExpression;\n\n /**\n * Facial damage state\n * @korean 얼굴손상\n */\n readonly facialDamage?: FacialDamageState;\n\n /**\n * Opponent position for eye tracking\n * @korean 상대위치\n */\n readonly opponentPosition?: THREE.Vector3;\n\n /**\n * Enable facial expressions rendering\n * @korean 얼굴표정렌더링\n */\n readonly enableFacialExpressions?: boolean;\n\n /**\n * Enable eye tracking\n * @korean 눈추적활성화\n */\n readonly enableEyeTracking?: boolean;\n\n /**\n * Physical attributes for bone thickness scaling\n * @korean 신체속성\n */\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth?: number;\n readonly torsoLength?: number;\n readonly armLength?: number;\n readonly legLength?: number;\n };\n\n /**\n * Player archetype for clothing style\n * @korean 플레이어원형\n */\n readonly archetype?: PlayerArchetype;\n\n /**\n * Muscle tension states for bone-attached muscles\n * Map of muscle group name to tension level (0-1)\n * @korean 근육상태들\n */\n readonly muscleStates?: Map<string, number>;\n\n /**\n * Whether player is exhausted (triggers muscle shaking)\n * @korean 피로여부\n */\n readonly isExhausted?: boolean;\n}\n\n/**\n * Single bone renderer with transformation\n *\n * Renders a single bone as a capsule connecting to its parent.\n *\n * @param bone - Bone to render\n * @param color - Bone color\n * @param renderMode - Render mode\n * @param leftHandState - Left hand animation state\n * @param rightHandState - Right hand animation state\n * @param cameraDistance - Distance from camera\n * @param boneThicknessMultiplier - Thickness multiplier for bone radius\n * @korean 단일뼈렌더러\n */\nconst SingleBone: React.FC<{\n readonly bone: Bone;\n readonly color: number;\n readonly renderMode: \"solid\" | \"debug\";\n readonly leftHandState?: HandAnimationState;\n readonly rightHandState?: HandAnimationState;\n readonly cameraDistance?: number;\n readonly facialExpression?: FacialExpression;\n readonly facialDamage?: FacialDamageState;\n readonly opponentPosition?: THREE.Vector3;\n readonly enableFacialExpressions?: boolean;\n readonly enableEyeTracking?: boolean;\n readonly boneThicknessMultiplier?: number;\n readonly muscleStates?: Map<string, number>;\n readonly isExhausted?: boolean;\n readonly physicalAttributes?: {\n readonly muscleMass: number;\n readonly fatMass: number;\n readonly shoulderWidth?: number;\n readonly torsoLength?: number;\n readonly armLength?: number;\n readonly legLength?: number;\n };\n readonly archetype?: PlayerArchetype;\n}> = ({\n bone,\n color,\n renderMode,\n leftHandState,\n rightHandState,\n cameraDistance = 10,\n facialExpression,\n facialDamage,\n opponentPosition,\n enableFacialExpressions = false,\n enableEyeTracking = true,\n boneThicknessMultiplier = 1.0,\n muscleStates,\n isExhausted = false,\n physicalAttributes,\n archetype,\n}) => {\n const groupRef = useRef<THREE.Group>(null);\n\n useFrame(() => {\n if (groupRef.current) {\n groupRef.current.rotation.set(\n bone.rotation.x,\n bone.rotation.y,\n bone.rotation.z,\n );\n groupRef.current.position.set(\n bone.position.x,\n bone.position.y,\n bone.position.z,\n );\n }\n });\n\n const boneTransform = useMemo(() => {\n const length = bone.length;\n const capsuleDefaultDirection = new THREE.Vector3(0, 1, 0);\n\n let rotation = new THREE.Euler(0, 0, 0);\n let offset = new THREE.Vector3(0, -length / 2, 0);\n\n if (bone.parent) {\n const x = bone.position.x ?? 0;\n const y = bone.position.y ?? 0;\n const z = bone.position.z ?? 0;\n\n const positionLength = Math.sqrt(x * x + y * y + z * z);\n if (positionLength > 0.001) {\n const targetX = x / positionLength;\n const targetY = y / positionLength;\n const targetZ = z / positionLength;\n const target = new THREE.Vector3(targetX, targetY, targetZ);\n\n const quaternion = new THREE.Quaternion().setFromUnitVectors(\n capsuleDefaultDirection,\n target,\n );\n rotation = new THREE.Euler().setFromQuaternion(quaternion);\n\n offset = new THREE.Vector3(\n (-targetX * length) / 2,\n (-targetY * length) / 2,\n (-targetZ * length) / 2,\n );\n }\n }\n\n return { length, rotation, offset };\n }, [bone]);\n\n const hasMuscles = muscleStates !== undefined && muscleStates.size > 0;\n\n return (\n <group\n ref={groupRef}\n scale={bone.scale.toArray()}\n name={`bone-${bone.name}`}\n >\n {/* Bone capsule connecting to parent - ONLY render if no muscles (muscles provide body shape) */}\n {renderMode === \"solid\" && !hasMuscles ? (\n <mesh\n position={boneTransform.offset.toArray()}\n rotation={[\n boneTransform.rotation.x,\n boneTransform.rotation.y,\n boneTransform.rotation.z,\n ]}\n castShadow\n receiveShadow\n >\n <capsuleGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n boneThicknessMultiplier, // Radius scaled by thickness\n boneTransform.length, // Length unchanged\n 4,\n 8,\n ]}\n />\n <meshPhysicalMaterial\n color={color}\n metalness={0.5}\n roughness={0.4}\n clearcoat={1.0}\n clearcoatRoughness={0.1}\n envMapIntensity={1.0}\n />\n </mesh>\n ) : renderMode === \"debug\" ? (\n <mesh\n position={boneTransform.offset.toArray()}\n rotation={[\n boneTransform.rotation.x,\n boneTransform.rotation.y,\n boneTransform.rotation.z,\n ]}\n >\n <capsuleGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n boneThicknessMultiplier, // Radius scaled by thickness\n boneTransform.length, // Length unchanged\n 4,\n 8,\n ]}\n />\n <meshBasicMaterial\n color={color}\n wireframe={true}\n transparent={true}\n opacity={0.5}\n />\n </mesh>\n ) : null}\n\n {/* Joint sphere at bone position */}\n {renderMode === \"debug\" && (\n <mesh>\n <sphereGeometry\n args={[\n boneTransform.length *\n BASE_BONE_RADIUS_RATIO *\n 1.2 *\n boneThicknessMultiplier,\n 8,\n 8,\n ]}\n />\n <meshBasicMaterial color={KOREAN_COLORS.PRIMARY_CYAN} />\n </mesh>\n )}\n\n {/* Render bone-attached muscles */}\n {renderMode === \"solid\" && muscleStates && (\n <BoneMuscles\n boneName={bone.name}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n />\n )}\n\n {/* Render body surface (skin/flesh layer) - provides continuous humanoid appearance */}\n {renderMode === \"solid\" && archetype && physicalAttributes && (\n <BodySurface\n boneName={bone.name}\n archetype={archetype}\n cameraDistance={cameraDistance}\n physicalAttributes={{\n muscleMass: physicalAttributes.muscleMass,\n fatMass: physicalAttributes.fatMass,\n shoulderWidth: physicalAttributes.shoulderWidth ?? 45,\n torsoLength: physicalAttributes.torsoLength ?? 59,\n armLength: physicalAttributes.armLength ?? 77,\n legLength: physicalAttributes.legLength ?? 96,\n }}\n />\n )}\n\n {/* Render bone-attached clothing */}\n {renderMode === \"solid\" && archetype && physicalAttributes && (\n <BoneClothing\n boneName={bone.name}\n archetype={archetype}\n physicalAttributes={{\n muscleMass: physicalAttributes.muscleMass,\n fatMass: physicalAttributes.fatMass,\n shoulderWidth: physicalAttributes.shoulderWidth ?? 45,\n torsoLength: physicalAttributes.torsoLength ?? 59,\n armLength: physicalAttributes.armLength ?? 77,\n legLength: physicalAttributes.legLength ?? 96,\n }}\n />\n )}\n\n {/* Render children recursively */}\n {bone.children.map((childBone) => (\n <SingleBone\n key={childBone.name}\n bone={childBone}\n color={color}\n renderMode={renderMode}\n leftHandState={leftHandState}\n rightHandState={rightHandState}\n cameraDistance={cameraDistance}\n facialExpression={facialExpression}\n facialDamage={facialDamage}\n opponentPosition={opponentPosition}\n enableFacialExpressions={enableFacialExpressions}\n enableEyeTracking={enableEyeTracking}\n boneThicknessMultiplier={boneThicknessMultiplier}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n archetype={archetype}\n />\n ))}\n\n {/* Add hands at hand bones with animation state */}\n {bone.name === \"hand_L\" && leftHandState && (\n <Hand3D\n side=\"left\"\n pose={leftHandState.currentPose}\n fingerCurl={leftHandState.currentFingerCurl}\n distanceFromCamera={cameraDistance}\n wristRotation={leftHandState.currentWristRotation}\n isHighlighted={leftHandState.isHighlighted}\n highlightMode={leftHandState.highlightMode}\n skinColor={color}\n scale={1.0}\n />\n )}\n {bone.name === \"hand_R\" && rightHandState && (\n <Hand3D\n side=\"right\"\n pose={rightHandState.currentPose}\n fingerCurl={rightHandState.currentFingerCurl}\n distanceFromCamera={cameraDistance}\n wristRotation={rightHandState.currentWristRotation}\n isHighlighted={rightHandState.isHighlighted}\n highlightMode={rightHandState.highlightMode}\n skinColor={color}\n scale={1.0}\n />\n )}\n\n {/* Add feet at foot bones with archetype scaling */}\n {bone.name === \"foot_L\" && (\n <Foot3D\n side=\"left\"\n skinColor={color}\n scale={boneThicknessMultiplier}\n isHighlighted={false}\n />\n )}\n {bone.name === \"foot_R\" && (\n <Foot3D\n side=\"right\"\n skinColor={color}\n scale={boneThicknessMultiplier}\n isHighlighted={false}\n />\n )}\n\n {/* Add facial features at head bone */}\n {bone.name === \"head\" && enableFacialExpressions && facialExpression && (\n <Face3D\n expression={facialExpression}\n damage={facialDamage ?? DEFAULT_FACIAL_DAMAGE}\n opponentPosition={opponentPosition ?? new THREE.Vector3(5, 2, 0)}\n headRotation={bone.rotation.clone()}\n enableEyeTracking={enableEyeTracking}\n enableDamageVisuals={true}\n isMobile={cameraDistance > 15}\n skinColor={color}\n />\n )}\n </group>\n );\n};\n\n/**\n * BoneRenderer component\n *\n * Renders complete skeletal rig with recursive bone hierarchy.\n *\n * @example\n * ```tsx\n * const rig = createHumanoidRig();\n * <BoneRenderer rig={rig} color={0xFF6B6B} renderMode=\"solid\" />\n * ```\n *\n * @korean 뼈렌더러컴포넌트\n */\nexport const BoneRenderer: React.FC<BoneRendererProps> = ({\n rig,\n color = KOREAN_COLORS.ACCENT_RED,\n showBones = true,\n renderMode = \"solid\",\n leftHandState,\n rightHandState,\n cameraDistance = 10,\n facialExpression,\n facialDamage,\n opponentPosition,\n enableFacialExpressions = false, // Default false to avoid breaking existing tests\n enableEyeTracking = true,\n physicalAttributes,\n muscleStates,\n isExhausted = false,\n archetype,\n}) => {\n const boneThicknessMultiplier = useMemo(() => {\n if (!physicalAttributes) return 1.0;\n return calculateBoneThickness(\n physicalAttributes.muscleMass,\n physicalAttributes.fatMass,\n );\n }, [physicalAttributes]);\n\n if (!showBones) {\n return null;\n }\n\n return (\n <group name=\"bone-renderer\">\n <SingleBone\n bone={rig.root}\n color={color}\n renderMode={renderMode}\n leftHandState={leftHandState}\n rightHandState={rightHandState}\n cameraDistance={cameraDistance}\n facialExpression={facialExpression}\n facialDamage={facialDamage}\n opponentPosition={opponentPosition}\n enableFacialExpressions={enableFacialExpressions}\n enableEyeTracking={enableEyeTracking}\n boneThicknessMultiplier={boneThicknessMultiplier}\n muscleStates={muscleStates}\n isExhausted={isExhausted}\n physicalAttributes={physicalAttributes}\n archetype={archetype}\n />\n </group>\n );\n};\n\nexport default BoneRenderer;\n"],"mappings":"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"}
|
|
@@ -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;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;
|
|
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;EAAxB,UAAA;GAEE,qBAAC,QAAD;IACE,UAAU;KAAC;KAAG;KAAG;IAAC;IAClB,YAAA;IACA,eAAA;IACA,MAAM,qBAAqB;IAJ7B,UAAA,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;IARjC,UAAA,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;IATrB,UAAA,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;IATrB,UAAA,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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"}
|
|
@@ -121,9 +121,10 @@ var SingleFeedback = ({ feedback, isMobile, arenaBounds, animationDuration }) =>
|
|
|
121
121
|
* ```
|
|
122
122
|
*/
|
|
123
123
|
var ActionFeedback = ({ feedbacks, isMobile = false, arenaBounds = DEFAULT_PHYSICS_ARENA_BOUNDS, animationDuration = 1200 }) => {
|
|
124
|
+
const visibleFeedbacks = useMemo(() => [...feedbacks], [feedbacks]);
|
|
124
125
|
return /* @__PURE__ */ jsx("group", {
|
|
125
126
|
"data-testid": "action-feedback-container",
|
|
126
|
-
children:
|
|
127
|
+
children: visibleFeedbacks.map((feedback) => /* @__PURE__ */ jsx(SingleFeedback, {
|
|
127
128
|
feedback,
|
|
128
129
|
isMobile,
|
|
129
130
|
arenaBounds,
|
|
@@ -163,7 +164,7 @@ var TechniqueName = ({ korean, english, isMobile = false, duration = 2e3, onComp
|
|
|
163
164
|
setOpacity(1);
|
|
164
165
|
setScale(1);
|
|
165
166
|
} else {
|
|
166
|
-
const fadeOutProgress = (progress - FADE_OUT_THRESHOLD) /
|
|
167
|
+
const fadeOutProgress = (progress - FADE_OUT_THRESHOLD) / .19999999999999996;
|
|
167
168
|
setOpacity(1 - fadeOutProgress);
|
|
168
169
|
setScale(1 + fadeOutProgress * .2);
|
|
169
170
|
}
|