All files / hooks useDistanceCulling.ts

94.11% Statements 16/17
100% Branches 5/5
66.66% Functions 2/3
100% Lines 15/15

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                                                                                                                                  1x       19x   19x   19x   18x       17x 17x 17x     17x 17x 17x 17x 17x     17x     19x                  
/**
 * useDistanceCulling - Distance-based culling hook for HTML overlays
 *
 * Optimizes performance by culling distant HTML overlays that are
 * beyond the camera's effective view distance. Helps maintain 60fps
 * by reducing unnecessary DOM rendering.
 *
 * @module hooks/useDistanceCulling
 * @category Performance
 * @korean 거리컬링훅
 */
 
import { useThree } from "@react-three/fiber";
import { useMemo } from "react";
 
/**
 * Distance culling hook options
 */
export interface DistanceCullingOptions {
  /**
   * Maximum distance in meters before culling
   * @default 20
   * @korean 최대거리
   */
  readonly cullDistance?: number;
 
  /**
   * Whether culling is enabled
   * @default true
   * @korean 컬링활성화
   */
  readonly enabled?: boolean;
}
 
/**
 * useDistanceCulling hook
 *
 * Calculates whether an overlay should be rendered based on
 * distance from camera. Returns false if object is beyond cull distance.
 *
 * @param position - World position of the overlay [x, y, z]
 * @param options - Culling configuration options
 * @returns boolean - true if should render, false if should cull
 *
 * @example
 * ```tsx
 * const isVisible = useDistanceCulling([5, 0, 0], { cullDistance: 20 });
 * if (!isVisible) return null;
 * ```
 *
 * @performance
 * - Uses useMemo to prevent recalculation on every frame
 * - Distance calculation only runs when camera or position changes
 * - Manually calculates squared distance to avoid expensive sqrt operation
 * - Reduces DOM rendering for distant overlays
 *
 * @note Camera position dependency
 * This hook tracks individual camera position components (x, y, z) in the dependency array.
 * While this causes recalculation on camera movement, it's necessary for accurate culling.
 * The useMemo still prevents redundant calculations within the same frame. For games with
 * very frequent camera updates, consider implementing a threshold-based approach (only update
 * when camera moves more than a certain distance) or debouncing at the component level.
 *
 * @korean 거리컬링훅사용
 */
export const useDistanceCulling = (
  position: readonly [number, number, number] | [number, number, number],
  options: DistanceCullingOptions = {},
): boolean => {
  const { cullDistance = 20, enabled = true } = options;
 
  const camera = useThree((state) => state.camera);
 
  const isVisible = useMemo(() => {
    // If culling disabled, always render
    if (!enabled) return true;
 
    // Calculate distance from camera to overlay position
    // Extract camera position components to avoid dependency issues
    const camX = camera.position.x;
    const camY = camera.position.y;
    const camZ = camera.position.z;
    
    // Calculate squared distance manually to avoid expensive sqrt operation
    const dx = position[0] - camX;
    const dy = position[1] - camY;
    const dz = position[2] - camZ;
    const distanceSquared = dx * dx + dy * dy + dz * dz;
    const cullDistanceSquared = cullDistance * cullDistance;
 
    // Return true if within cull distance, false otherwise
    return distanceSquared <= cullDistanceSquared;
  }, [camera.position.x, camera.position.y, camera.position.z, position, cullDistance, enabled]);
 
  return isVisible;
};
 
// Note: useDistanceCullingWithThreshold was removed because it didn't actually implement
// hysteresis and was identical to useDistanceCulling. True hysteresis requires useState
// to track previous visibility state and apply different show/hide thresholds.
// TODO: Implement proper hysteresis in the future if needed.
 
export default useDistanceCulling;