All files / components/shared/three/optimization LODSystem.tsx

65.21% Statements 15/23
33.33% Branches 6/18
60% Functions 3/5
65.21% Lines 15/23

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                                                                                    2x                 2x                                                                           2x                                                                                                                   2x                                                             5x 5x         5x                                   13x   4x   4x   5x                             11x   4x   3x   4x          
/* eslint-disable react-refresh/only-export-components */
/**
 * LODSystem - Level of Detail optimization for Three.js objects
 *
 * Reduces polygon count and detail based on distance from camera.
 * Implements 3-tier LOD system (high/medium/low) for optimal performance.
 *
 * Features:
 * - Distance-based detail switching
 * - Smooth transitions between LOD levels
 * - Configurable distance thresholds
 * - Works with any Three.js component
 * - Korean martial arts character optimization
 *
 * Performance Impact:
 * - Reduces polygon count by 50-75% for distant objects
 * - Maintains visual quality for near objects
 * - Target: <100 draw calls per frame
 *
 * @module components/shared/three/optimization/LODSystem
 * @category Performance Optimization
 * @korean LOD시스템
 */
 
import { Detailed } from "@react-three/drei";
import React from "react";
 
/**
 * LOD distance thresholds (in Three.js units)
 */
export interface LODDistances {
  /** Distance for high detail (closest) */
  readonly high: number;
  /** Distance for medium detail */
  readonly medium: number;
  /** Low detail used beyond medium distance */
}
 
/**
 * Default LOD distances optimized for Black Trigram combat
 * Arena is approximately 16x8 units, camera at [0, 5, 10]
 */
export const DEFAULT_LOD_DISTANCES: LODDistances = {
  high: 0, // 0-12 units (close range)
  medium: 12, // 12-20 units (mid range)
  // Beyond 20 units uses low detail
};
 
/**
 * Mobile LOD distances (more aggressive)
 */
export const MOBILE_LOD_DISTANCES: LODDistances = {
  high: 0, // 0-8 units (very close)
  medium: 8, // 8-15 units (mid range)
  // Beyond 15 units uses low detail
};
 
/**
 * Props for LODCharacter component
 */
export interface LODCharacterProps {
  /** High detail component (0-high distance) */
  readonly highDetail: React.ReactNode;
  /** Medium detail component (high-medium distance) */
  readonly mediumDetail: React.ReactNode;
  /** Low detail component (beyond medium distance) */
  readonly lowDetail: React.ReactNode;
  /** Custom LOD distances (optional) */
  readonly distances?: LODDistances;
  /** Whether to use mobile-optimized distances */
  readonly isMobile?: boolean;
}
 
/**
 * LOD-enabled character component
 *
 * Automatically switches between detail levels based on camera distance.
 * Uses @react-three/drei's Detailed component for smooth transitions.
 *
 * @example
 * ```tsx
 * <LODCharacter
 *   highDetail={<Player3DHighDetail />}
 *   mediumDetail={<Player3DMediumDetail />}
 *   lowDetail={<Player3DLowDetail />}
 *   isMobile={isMobile}
 * />
 * ```
 */
export const LODCharacter: React.FC<LODCharacterProps> = ({
  highDetail,
  mediumDetail,
  lowDetail,
  distances,
  isMobile = false,
}) => {
  // Use mobile distances if on mobile, otherwise use defaults
  const lodDistances =
    distances ?? (isMobile ? MOBILE_LOD_DISTANCES : DEFAULT_LOD_DISTANCES);
 
  // Detailed component requires array of distances
  const distanceArray = [
    lodDistances.high,
    lodDistances.medium,
    Infinity, // Low detail for all distances beyond medium
  ];
 
  return (
    <Detailed distances={distanceArray}>
      <group>{highDetail}</group>
      <group>{mediumDetail}</group>
      <group>{lowDetail}</group>
    </Detailed>
  );
};
 
/**
 * Props for LODEffect component
 */
export interface LODEffectProps {
  /** Effect to render at different detail levels */
  readonly children: (
    detailLevel: "high" | "medium" | "low",
  ) => React.ReactNode;
  /** Custom LOD distances (optional) */
  readonly distances?: LODDistances;
  /** Whether to use mobile-optimized distances */
  readonly isMobile?: boolean;
}
 
/**
 * LOD-enabled effect component
 *
 * Allows render function to adjust effect quality based on distance.
 * Useful for particle effects, visual effects, etc.
 *
 * @example
 * ```tsx
 * <LODEffect isMobile={isMobile}>
 *   {(detail) => (
 *     <ParticleSystem
 *       particleCount={detail === 'high' ? 100 : detail === 'medium' ? 50 : 20}
 *     />
 *   )}
 * </LODEffect>
 * ```
 */
export const LODEffect: React.FC<LODEffectProps> = ({
  children,
  distances,
  isMobile = false,
}) => {
  const lodDistances =
    distances ?? (isMobile ? MOBILE_LOD_DISTANCES : DEFAULT_LOD_DISTANCES);
 
  const distanceArray = [lodDistances.high, lodDistances.medium, Infinity];
 
  return (
    <Detailed distances={distanceArray}>
      <group>{children("high")}</group>
      <group>{children("medium")}</group>
      <group>{children("low")}</group>
    </Detailed>
  );
};
 
/**
 * Calculate appropriate LOD distances based on arena size
 *
 * @param arenaWidth - Arena width in units
 * @param arenaDepth - Arena depth in units
 * @returns Calculated LOD distances
 */
export function calculateLODDistances(
  arenaWidth: number,
  arenaDepth: number,
): LODDistances {
  // High detail: within 40% of arena diagonal
  const diagonal = Math.sqrt(arenaWidth * arenaWidth + arenaDepth * arenaDepth);
  const highDistance = diagonal * 0.4;
 
  // Medium detail: within 70% of arena diagonal (not used directly, implicit in structure)
  // const mediumDistance = diagonal * 0.7;
 
  return {
    high: 0,
    medium: highDistance,
    // Low detail beyond mediumDistance
  };
}
 
/**
 * Get recommended particle count based on LOD level
 *
 * @param baseCount - Base particle count at high detail
 * @param detailLevel - Current LOD level
 * @returns Adjusted particle count
 */
export function getLODParticleCount(
  baseCount: number,
  detailLevel: "high" | "medium" | "low",
): number {
  switch (detailLevel) {
    case "high":
      return baseCount;
    case "medium":
      return Math.floor(baseCount * 0.6); // 60% of base
    case "low":
      return Math.floor(baseCount * 0.3); // 30% of base
    default:
      return baseCount;
  }
}
 
/**
 * Get recommended shadow quality based on LOD level
 *
 * @param detailLevel - Current LOD level
 * @returns Shadow map size
 */
export function getLODShadowQuality(
  detailLevel: "high" | "medium" | "low",
): number {
  switch (detailLevel) {
    case "high":
      return 2048;
    case "medium":
      return 1024;
    case "low":
      return 512;
    default:
      return 1024;
  }
}