All files / components/screens/training/components TrainingArena3D.tsx

6.89% Statements 2/29
0% Branches 0/6
0% Functions 0/14
6.89% Lines 2/29

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                                                  1x                                                                                                                                                   1x                                                                                                    
/**
 * TrainingArena3D - 3D training dojang floor
 *
 * Provides the ground plane for the training area with Korean-themed aesthetics
 */
 
import { useFrame } from "@react-three/fiber";
import React, { useEffect, useMemo, useRef } from "react";
import * as THREE from "three";
import { KOREAN_COLORS } from "../../../../types/constants";
 
/**
 * Props for TrainingArena3D component
 */
export interface TrainingArena3DProps {
  /** Size of the arena. Defaults to 20 */
  readonly size?: number;
  /** Whether to show grid lines. Defaults to true */
  readonly showGrid?: boolean;
}
 
/**
 * TrainingArena3D Component
 * Renders the training dojang floor with Korean aesthetic
 */
export const TrainingArena3D: React.FC<TrainingArena3DProps> = ({
  size = 20,
  showGrid = true,
}) => {
  const gridRef = useRef<THREE.GridHelper>(null);
 
  useFrame(() => {
    if (gridRef.current) {
      gridRef.current.rotation.y += 0.0002;
    }
  });
 
  const floorMaterial = useMemo(() => {
    return new THREE.MeshStandardMaterial({
      color: KOREAN_COLORS.UI_BACKGROUND_MEDIUM,
      metalness: 0.2,
      roughness: 0.8,
      side: THREE.FrontSide,
    });
  }, []);
 
  useEffect(() => {
    return () => {
      floorMaterial.dispose();
    };
  }, [floorMaterial]);
 
  const gridColor = useMemo(
    () => new THREE.Color(KOREAN_COLORS.PRIMARY_CYAN),
    []
  );
  const gridSecondaryColor = useMemo(
    () => new THREE.Color(KOREAN_COLORS.UI_BACKGROUND_DARK),
    []
  );
 
  return (
    <group>
      {/* Main floor plane */}
      <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]} receiveShadow>
        <planeGeometry args={[size, size, 1, 1]} />
        <primitive object={floorMaterial} attach="material" />
      </mesh>
 
      {/* Cyberpunk grid overlay (animated, matching CombatArena3D) */}
      {showGrid && (
        <gridHelper
          ref={gridRef}
          args={[size, 20, gridColor, gridSecondaryColor]}
          position={[0, 0.01, 0]}
        />
      )}
 
      {/* Center marker - Yin Yang inspired (matching CombatArena3D) */}
      <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 0]}>
        <ringGeometry args={[0.8, 1.0, 32]} />
        <meshBasicMaterial
          color={KOREAN_COLORS.ACCENT_GOLD}
          transparent
          opacity={0.6}
          side={THREE.DoubleSide}
        />
      </mesh>
 
      {/* Corner markers for Korean aesthetic */}
      <CornerMarkers size={size} />
    </group>
  );
};
 
/**
 * Corner markers for Korean dojang aesthetic
 * Optimized with memoized geometry and material to prevent recreation
 */
const CornerMarkers: React.FC<{ size: number }> = ({ size }) => {
  const halfSize = size / 2 - 0.5;
 
  const markerPositions: Array<[number, number, number]> = useMemo(
    () => [
      [-halfSize, 0.1, -halfSize], // Front-left
      [halfSize, 0.1, -halfSize], // Front-right
      [-halfSize, 0.1, halfSize], // Back-left
      [halfSize, 0.1, halfSize], // Back-right
    ],
    [halfSize]
  );
 
  const markerGeometry = useMemo(
    () => new THREE.CylinderGeometry(0.2, 0.2, 0.05, 8),
    []
  );
 
  const markerMaterial = useMemo(
    () =>
      new THREE.MeshStandardMaterial({
        color: KOREAN_COLORS.ACCENT_GOLD,
        emissive: KOREAN_COLORS.ACCENT_GOLD,
        emissiveIntensity: 0.3,
        metalness: 0.5,
        roughness: 0.5,
      }),
    []
  );
 
  useEffect(() => {
    return () => {
      markerGeometry.dispose();
      markerMaterial.dispose();
    };
  }, [markerGeometry, markerMaterial]);
 
  return (
    <>
      {markerPositions.map((position, index) => (
        <mesh key={index} position={position}>
          <primitive object={markerGeometry} attach="geometry" />
          <primitive object={markerMaterial} attach="material" />
        </mesh>
      ))}
    </>
  );
};
 
export default TrainingArena3D;