Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | 9x 9x 69x 67x 2x 9x 483x 483x 483x 483x 483x 483x 9x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 69x 160x 160x 160x 72x 72x 3x 69x 69x 2x 69x 69x 160x | /**
* useGuardPoseOverlay - Shared hook for stance guard pose application
*
* Manages guard pose overlay application on top of base animations.
* Reduces code duplication in skeletal animation components.
*
* @module hooks/useGuardPoseOverlay
* @category Hooks
* @korean 방어자세오버레이훅
*/
import { useRef } from "react";
import { getGuardPoseForStance } from "../systems/animation";
import type { StanceLaterality } from "../systems/trigram/types";
import { TrigramStance } from "../types/common";
import type { PlayerAnimation } from "../types/player-visual";
import type { SkeletalRig } from "../types/skeletal";
import * as THREE from "three";
/**
* Blend factor for torso rotation during guard overlay
*
* This value is multiplied by the main `blendFactor` argument used for the
* stance guard overlay. For example, when `blendFactor` is 1.0 (full guard),
* the effective torso guard influence becomes `1.0 * 0.8 = 0.8`, allowing
* approximately 20% of the base animation (walk/idle) torso movement to show
* through. Keep this lower than 1.0 to preserve some natural torso motion
* while still maintaining a visible guard posture.
*
* @korean 방어자세가몸통에적용되는비율을줄이는추가스케일계수
*/
const TORSO_BLEND_FACTOR = 0.8;
/**
* Get dynamic guard blend factor for natural movement while maintaining stance character
* @param animation - Current animation state
* @returns Blend factor (0.0 = no guard, 1.0 = full guard)
* @korean 동적방어블렌드계수가져오기
*/
const getGuardBlendFactor = (animation: PlayerAnimation): number => {
switch (animation) {
case "idle":
case "block":
case "counter":
case "stance_change":
return 1.0; // Full guard - maximum stance visibility when stationary/defensive
case "walk":
case "step_forward":
case "step_back":
case "step_left":
case "step_right":
case "step_forward_left":
case "step_forward_right":
case "step_back_left":
case "step_back_right":
return 0.7; // Partial guard - balanced movement with stance character
case "attack":
case "defend":
case "hit":
case "death":
case "technique_execute":
return 0.0; // No guard - technique animations have full control
default:
return 1.0; // Default to full guard for unknown animations
}
};
/**
* Helper function to apply bone rotation with lerp blending
* Reduces code duplication for limb positioning
*
* @param rig - Skeletal rig
* @param boneName - Name of bone to rotate
* @param targetRotation - Target rotation
* @param blend - Blend factor (0.0-1.0)
* @korean 뼈회전적용
*/
const applyBoneRotation = (
rig: SkeletalRig,
boneName: string,
targetRotation: THREE.Euler,
blend: number
): void => {
const bone = rig.bones.get(boneName);
Iif (!bone) return;
const current = bone.rotation;
current.x = THREE.MathUtils.lerp(current.x, targetRotation.x, blend);
current.y = THREE.MathUtils.lerp(current.y, targetRotation.y, blend);
current.z = THREE.MathUtils.lerp(current.z, targetRotation.z, blend);
};
/**
* Apply stance guard pose overlay on top of base animation
*
* Blends guard arm positions with base animation (idle/walk) to maintain
* guard pose during movement. Only affects upper body (arms, torso) while
* allowing legs to animate normally.
*
* PERFORMANCE: Directly modifies existing Euler rotation components
* to avoid extra Euler object cloning while still using component-wise interpolation.
*
* @param rig - Skeletal rig to apply overlay to
* @param stance - Current trigram stance
* @param breathingPhase - Breathing phase 0.0-1.0 for scale oscillation
* @param laterality - Stance laterality (left or right foot forward)
* @param blendFactor - How much guard pose to blend (0=base animation, 1=full guard)
*
* @korean 자세방어포즈오버레이적용
*/
export const applyStanceGuardOverlay = (
rig: SkeletalRig,
stance: TrigramStance | string,
breathingPhase: number,
laterality: StanceLaterality = "right",
blendFactor: number = 1.0
): void => {
const guardPose = getGuardPoseForStance(stance as TrigramStance, laterality);
Iif (!guardPose) return;
// Blend left arm rotations with current pose
const leftShoulder = rig.bones.get("shoulder_L");
Eif (leftShoulder) {
const current = leftShoulder.rotation;
const target = guardPose.leftArm.shoulder;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
const leftElbow = rig.bones.get("elbow_L");
Eif (leftElbow) {
const current = leftElbow.rotation;
const target = guardPose.leftArm.elbow;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
const leftWrist = rig.bones.get("wrist_L");
Eif (leftWrist) {
const current = leftWrist.rotation;
const target = guardPose.leftArm.wrist;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
// Blend right arm rotations with current pose
const rightShoulder = rig.bones.get("shoulder_R");
Eif (rightShoulder) {
const current = rightShoulder.rotation;
const target = guardPose.rightArm.shoulder;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
const rightElbow = rig.bones.get("elbow_R");
Eif (rightElbow) {
const current = rightElbow.rotation;
const target = guardPose.rightArm.elbow;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
const rightWrist = rig.bones.get("wrist_R");
Eif (rightWrist) {
const current = rightWrist.rotation;
const target = guardPose.rightArm.wrist;
current.x = THREE.MathUtils.lerp(current.x, target.x, blendFactor);
current.y = THREE.MathUtils.lerp(current.y, target.y, blendFactor);
current.z = THREE.MathUtils.lerp(current.z, target.z, blendFactor);
}
// Blend torso rotation with current pose
const spine = rig.bones.get("spine_upper");
Eif (spine) {
const current = spine.rotation;
const target = guardPose.torso;
const torsoBlend = blendFactor * TORSO_BLEND_FACTOR;
current.x = THREE.MathUtils.lerp(current.x, target.x, torsoBlend);
current.y = THREE.MathUtils.lerp(current.y, target.y, torsoBlend);
current.z = THREE.MathUtils.lerp(current.z, target.z, torsoBlend);
}
// Blend leg rotations for authentic stance positioning
applyBoneRotation(rig, "hip_L", guardPose.leftLeg.hip, blendFactor);
applyBoneRotation(rig, "knee_L", guardPose.leftLeg.knee, blendFactor);
applyBoneRotation(rig, "foot_L", guardPose.leftLeg.ankle, blendFactor);
applyBoneRotation(rig, "hip_R", guardPose.rightLeg.hip, blendFactor);
applyBoneRotation(rig, "knee_R", guardPose.rightLeg.knee, blendFactor);
applyBoneRotation(rig, "foot_R", guardPose.rightLeg.ankle, blendFactor);
// Blend pelvis rotation for proper stance base
applyBoneRotation(rig, "pelvis", guardPose.pelvis, blendFactor);
// Apply breathing animation scale (chest/shoulder expansion)
const breathingScale = THREE.MathUtils.lerp(
guardPose.breathingRange.min,
guardPose.breathingRange.max,
(Math.sin(breathingPhase * Math.PI * 2) + 1) / 2 // Sine wave 0-1
);
// Apply breathing to upper torso
const chest = rig.bones.get("spine_middle");
Eif (chest) {
chest.scale.setScalar(breathingScale);
}
const neck = rig.bones.get("neck");
Eif (neck) {
neck.scale.y = breathingScale;
}
};
/**
* Options for useGuardPoseOverlay hook
* @korean 방어자세오버레이훅옵션
*/
export interface UseGuardPoseOverlayOptions {
/** Current stance */
readonly stance: TrigramStance | string;
/** Stance laterality (left or right foot forward) */
readonly laterality?: StanceLaterality;
/** Current animation name */
readonly currentAnimation: PlayerAnimation;
}
/**
* Return type for useGuardPoseOverlay hook
* @korean 방어자세오버레이훅반환타입
*/
export interface UseGuardPoseOverlayReturn {
/** Apply guard pose overlay to rig (call in useFrame) */
readonly applyGuardOverlay: (rig: SkeletalRig, delta: number) => void;
}
/**
* useGuardPoseOverlay hook
*
* Manages guard pose overlay application on top of base animations.
* Handles breathing animation and dynamic blend factors based on movement.
*
* @param options - Guard pose options
* @returns Guard overlay application function
*
* @example
* ```tsx
* const { applyGuardOverlay } = useGuardPoseOverlay({
* stance: "geon",
* laterality: "right",
* currentAnimation: "idle",
* });
*
* // In useFrame callback (after base animation is applied)
* useFrame((_, delta) => {
* // Apply base animation first
* updateRigAnimation(rig, delta);
* // Then apply guard overlay
* applyGuardOverlay(rig, delta);
* });
* ```
*
* @korean 방어자세오버레이훅
*/
export function useGuardPoseOverlay(
options: UseGuardPoseOverlayOptions
): UseGuardPoseOverlayReturn {
const { stance, laterality = "right", currentAnimation } = options;
// Breathing phase ref for guard poses (0-1 cycle)
const breathingPhaseRef = useRef(0);
// Apply guard pose overlay (called at 60fps in useFrame)
const applyGuardOverlay = (rig: SkeletalRig, delta: number): void => {
// Only apply guard for animations that allow it
const shouldApplyGuard =
currentAnimation !== "attack" &&
currentAnimation !== "defend" &&
currentAnimation !== "hit" &&
currentAnimation !== "death";
if (!shouldApplyGuard) {
return;
}
// Update breathing phase for guard poses
breathingPhaseRef.current += delta * 0.5; // 0.5 Hz = 2 seconds per breath cycle
if (breathingPhaseRef.current > 1.0) {
breathingPhaseRef.current -= 1.0;
}
// Get dynamic blend factor based on animation type
const blendFactor = getGuardBlendFactor(currentAnimation);
// Apply guard pose overlay
applyStanceGuardOverlay(
rig,
stance,
breathingPhaseRef.current,
laterality,
blendFactor
);
};
return {
applyGuardOverlay,
};
}
|