All files / systems/animation/builders PunchPhaseApplicator.ts

77.14% Statements 27/35
75.51% Branches 37/49
50% Functions 1/2
77.14% Lines 27/35

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                                                                                                                                                                    4008x       4008x 4008x 4008x       4008x   4008x   4008x     4008x 4008x                 4008x 4008x       4008x 2213x       4008x 4008x 2213x               4008x 2213x               4008x                     4008x 4008x 4008x 2103x     4008x 4008x               4008x                 4008x                                        
/**
 * Punch Phase Application Utilities
 *
 * Utilities for applying punch phase poses to keyframes with integrated
 * anatomy awareness (hand poses and highlighting for strikes).
 * 주먹 단계 적용 유틸리티 (해부학 통합)
 *
 * @module systems/animation/PunchPhaseApplicator
 * @korean 주먹단계적용기
 */
 
import { BoneName } from "@/types/skeletal";
import type { HandHighlightMode, KeyframeConfig } from "./KeyframeConfig";
import { PUNCH_PHASES } from "./MartialArtsConstants";
 
/**
 * Interface for punch phases with Korean martial arts biomechanics
 * Includes opposite arm hikite (당기기) for power generation
 */
interface PunchPhase {
  readonly shoulder: readonly [number, number, number];
  readonly elbow: readonly [number, number, number];
  readonly wrist?: readonly [number, number, number];
  readonly spineY?: number;
  readonly pelvisY?: number;
  // Opposite arm for hikite (pulling hand) - 당기기
  readonly oppositeShoulder?: readonly [number, number, number];
  readonly oppositeElbow?: readonly [number, number, number];
  readonly oppositeWrist?: readonly [number, number, number];
}
 
/** Phase name keys */
export type PunchPhaseName = keyof typeof PUNCH_PHASES;
 
/** Punch hand side */
export type PunchSide = "left" | "right";
 
/**
 * Apply punch phase to a KeyframeConfig with Korean martial arts biomechanics
 * and anatomy integration (hand poses, highlighting).
 *
 * Handles common punch phase bones: shoulder, elbow, wrist, spine, pelvis
 * Now includes opposite arm hikite (당기기) and automatic hand pose/highlight
 *
 * @param kf - KeyframeConfig to apply phase to
 * @param phase - Punch phase data from PUNCH_PHASES
 * @param hand - Which hand is punching ("left" | "right")
 * @param options - Configuration including anatomy options
 *
 * @example
 * ```typescript
 * // Apply extension with automatic fist pose and knuckle highlight
 * applyPunchPhaseToConfig(kf, PUNCH_PHASES.EXTENSION, "right", {
 *   handPose: "fist",
 *   handHighlightMode: "knuckles",
 *   includeOppositeArm: true
 * });
 * ```
 *
 * @korean KeyframeConfig에주먹단계적용
 */
export function applyPunchPhaseToConfig(
  kf: KeyframeConfig,
  phase: PunchPhase,
  hand: PunchSide = "right",
  options: {
    readonly includeWrist?: boolean;
    readonly includeSpineMiddle?: boolean;
    readonly includeOppositeArm?: boolean;
    // Anatomy integration
    readonly handPose?: string;
    readonly handHighlightMode?: HandHighlightMode;
    readonly oppositeHandPose?: string;
  } = {}
): void {
  const {
    includeWrist = false,
    includeSpineMiddle = false,
    includeOppositeArm = true,
    handPose,
    handHighlightMode,
    oppositeHandPose,
  } = options;
 
  // Select bones based on punching hand
  const shoulderBone =
    hand === "right" ? BoneName.SHOULDER_R : BoneName.SHOULDER_L;
  const elbowBone = hand === "right" ? BoneName.ELBOW_R : BoneName.ELBOW_L;
  const wristBone = hand === "right" ? BoneName.WRIST_R : BoneName.WRIST_L;
 
  // Opposite hand bones for hikite (당기기)
  const oppositeShoulderBone =
    hand === "right" ? BoneName.SHOULDER_L : BoneName.SHOULDER_R;
  const oppositeElbowBone =
    hand === "right" ? BoneName.ELBOW_L : BoneName.ELBOW_R;
  const oppositeWristBone =
    hand === "right" ? BoneName.WRIST_L : BoneName.WRIST_R;
 
  // Apply punching arm shoulder rotation
  Eif (phase.shoulder) {
    kf.rotate(
      shoulderBone,
      phase.shoulder[0],
      phase.shoulder[1],
      phase.shoulder[2]
    );
  }
 
  // Apply punching arm elbow rotation
  Eif (phase.elbow) {
    kf.rotate(elbowBone, phase.elbow[0], phase.elbow[1], phase.elbow[2]);
  }
 
  // Optional punching arm wrist rotation (for fist rotation)
  if (includeWrist && phase.wrist) {
    kf.rotate(wristBone, phase.wrist[0], phase.wrist[1], phase.wrist[2]);
  }
 
  // Apply opposite arm hikite (당기기 - pulling hand for power generation)
  Eif (includeOppositeArm) {
    if (phase.oppositeShoulder) {
      kf.rotate(
        oppositeShoulderBone,
        phase.oppositeShoulder[0],
        phase.oppositeShoulder[1],
        phase.oppositeShoulder[2]
      );
    }
 
    if (phase.oppositeElbow) {
      kf.rotate(
        oppositeElbowBone,
        phase.oppositeElbow[0],
        phase.oppositeElbow[1],
        phase.oppositeElbow[2]
      );
    }
 
    Iif (includeWrist && phase.oppositeWrist) {
      kf.rotate(
        oppositeWristBone,
        phase.oppositeWrist[0],
        phase.oppositeWrist[1],
        phase.oppositeWrist[2]
      );
    }
  }
 
  // Spine and pelvis Y-axis rotations for hip/shoulder engagement
  Eif (phase.spineY !== undefined) {
    kf.rotate(BoneName.SPINE_UPPER, 0, phase.spineY, 0);
    if (includeSpineMiddle) {
      kf.rotate(BoneName.SPINE_MIDDLE, 0, phase.spineY * 0.7, 0);
    }
  }
  Eif (phase.pelvisY !== undefined) {
    kf.rotate(BoneName.PELVIS, 0, phase.pelvisY, 0);
  }
 
  // ═══════════════════════════════════════════════════════════════════════════
  // ANATOMY INTEGRATION (해부학 통합)
  // ═══════════════════════════════════════════════════════════════════════════
 
  // Set punching hand pose and highlight
  Iif (handPose) {
    if (hand === "right") {
      kf.setRightHandPose(handPose, handHighlightMode);
    } else {
      kf.setLeftHandPose(handPose, handHighlightMode);
    }
  }
 
  // Set opposite hand pose (guard position)
  Iif (oppositeHandPose) {
    if (hand === "right") {
      kf.setLeftHandPose(oppositeHandPose);
    } else {
      kf.setRightHandPose(oppositeHandPose);
    }
  }
}
 
/**
 * Get a punch phase by name
 *
 * @param phaseName - Name of the phase from PUNCH_PHASES
 * @returns The punch phase data
 *
 * @korean 주먹단계가져오기
 */
export function getPunchPhase(phaseName: PunchPhaseName): PunchPhase {
  return PUNCH_PHASES[phaseName];
}