<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>First-Person Shooter Game with Enhanced Collision Detection</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #87ceeb; 
            cursor: crosshair; 
        }
        canvas {
            display: block;
        }
        #instructions {
            position: absolute;
            top: 50%;
            width: 100%;
            text-align: center;
            color: white;
            font-family: Arial, sans-serif;
            font-size: 24px;
            user-select: none;
            cursor: pointer;
            transform: translateY(-50%);
            z-index: 1; /* Ensure it's above the canvas */
        }
        /* ---------- Scope Overlay Styles ---------- */
        #scope {
            position: absolute;
            top: 50%;
            left: 50%;
            width: 40px;    /* Smaller size for minimalism */
            height: 40px;   /* Smaller size for minimalism */
            margin-left: -20px; /* Half of width to center */
            margin-top: -20px;  /* Half of height to center */
            pointer-events: none; /* Allow clicks to pass through */
            z-index: 2; /* Above instructions */
        }
        /* Crosshair Horizontal Line */
        #scope .crosshair-horizontal {
            position: absolute;
            top: 50%;
            left: 0;
            width: 100%;
            height: 2px; /* Thin line */
            background: rgba(255, 255, 255, 0.7);
            transform: translateY(-50%);
        }
        /* Crosshair Vertical Line */
        #scope .crosshair-vertical {
            position: absolute;
            top: 0;
            left: 50%;
            width: 2px; /* Thin line */
            height: 100%;
            background: rgba(255, 255, 255, 0.7);
            transform: translateX(-50%);
        }
    </style>
    <!-- Import Map to resolve 'three' and 'tween.js' modules -->
    <script type="importmap">
    {
        "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three@0.152.0/build/three.module.js",
            "tween.js": "https://unpkg.com/@tweenjs/tween.js@18.6.4/dist/tween.esm.js"
        }
    }
    </script>
</head>
<body>

<div id="instructions">
    Нажмите для игры<br>
    W/A/S/D для движения | Мышь для обзора | Клик для стрельбы<br>
    Пробел для прыжка | Ctrl для приседания | Shift для бега
</div>

<!-- Scope Overlay -->
<div id="scope">
    <div class="crosshair-horizontal"></div>
    <div class="crosshair-vertical"></div>
</div>

<script type="module">
    'use strict';
    
    // Импортируем необходимые модули
    import * as THREE from 'three';
    import { PointerLockControls } from 'https://cdn.jsdelivr.net/npm/three@0.152.0/examples/jsm/controls/PointerLockControls.js';
    import TWEEN from 'tween.js'; // Правильный импорт для TWEEN.js

    // Константы для высоты игрока и радиуса коллайдера
    const standingHeight = 10.0;
    const crouchHeight = 7.0;
    const playerRadius = 1.5; // Уменьшен радиус для предотвращения застревания
    const collisionBuffer = 0.5; // Увеличен буфер для обнаружения столкновений

    // Настройка сцены, камеры и рендерера
    const scene = new THREE.Scene();
    
    // Задаём цвет фона сцены
    scene.background = new THREE.Color(0x87ceeb); // Sky Blue
    
    const camera = new THREE.PerspectiveCamera(
        75, 
        window.innerWidth / window.innerHeight, 
        0.1, 
        1000
    );

    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.shadowMap.enabled = true; // Включаем тени
    document.body.appendChild(renderer.domElement);

    // ---------- Добавление освещения ----------
    // Hemisphere Light для имитации окружающего света
    const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0);
    hemiLight.position.set(0, 200, 0);
    scene.add(hemiLight);

    // Directional Light для имитации солнца
    const sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
    sunLight.position.set(100, 200, 100); // Позиционирование выше и сбоку
    sunLight.castShadow = true;
    sunLight.shadow.camera.top = 200;
    sunLight.shadow.camera.bottom = -100;
    sunLight.shadow.camera.left = -120;
    sunLight.shadow.camera.right = 120;
    scene.add(sunLight);

    // ---------- Добавление солнца ----------
    // Создаём видимое солнце как яркую эмиссионную сферу
    const sunGeometry = new THREE.SphereGeometry(20, 32, 32);
    const sunMaterial = new THREE.MeshStandardMaterial({ 
        color: 0xffff00, 
        emissive: 0xffff00,
        emissiveIntensity: 1
    });
    const sun = new THREE.Mesh(sunGeometry, sunMaterial);
    sun.position.copy(sunLight.position);
    scene.add(sun);

    // ---------- Добавление облаков ----------
    const clouds = [];
    const cloudMaterial = new THREE.MeshLambertMaterial({ 
        color: 0xffffff, 
        transparent: true, 
        opacity: 0.6,
        depthWrite: false
    });

    // Функция для создания одного облака
    function createCloud() {
        const cloudGroup = new THREE.Group();
        const cloudCount = 5; // Количество сфер в облаке

        for (let i = 0; i < cloudCount; i++) {
            const sphereGeometry = new THREE.SphereGeometry(10, 16, 16);
            const sphere = new THREE.Mesh(sphereGeometry, cloudMaterial);
            sphere.position.set(
                Math.random() * 20 - 10, // Позиция по X относительно группы
                Math.random() * 5 - 2.5,  // Позиция по Y относительно группы
                Math.random() * 20 - 10  // Позиция по Z относительно группы
            );
            cloudGroup.add(sphere);
        }
        return cloudGroup;
    }

    // Создаём несколько облаков и размещаем их случайным образом в небе
    for (let i = 0; i < 20; i++) { // Количество облаков
        const cloud = createCloud();
        cloud.position.set(
            Math.random() * 800 - 400, // Позиция по X в мире
            Math.random() * 100 + 50,  // Позиция по Y (высота)
            Math.random() * 800 - 400  // Позиция по Z в мире
        );
        scene.add(cloud);
        clouds.push(cloud);
    }

    // ---------- Добавление зелёной земли ----------
    const groundMaterial = new THREE.MeshLambertMaterial({ 
        color: 0x228B22 // Forest Green
    });

    const groundGeometry = new THREE.PlaneGeometry(1000, 1000);
    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
    ground.rotation.x = - Math.PI / 2; // Поворот для горизонтальной плоскости
    ground.receiveShadow = true; // Разрешаем земле принимать тени
    scene.add(ground);

    // ---------- Добавление деревьев ----------
    const trees = [];
    const treeMaterialTrunk = new THREE.MeshStandardMaterial({ color: 0x8B4513 }); // Коричневый для стволов
    const treeMaterialFoliage = new THREE.MeshStandardMaterial({ color: 0x006400 }); // Тёмно-зелёный для листвы

    // Функция для создания одного дерева
    function createTree() {
        const tree = new THREE.Group();

        // Создание ствола
        const trunkGeometry = new THREE.CylinderGeometry(1, 1, 10, 8);
        const trunk = new THREE.Mesh(trunkGeometry, treeMaterialTrunk);
        trunk.position.y = 5; // Половина высоты ствола
        trunk.castShadow = true;
        trunk.receiveShadow = true;
        tree.add(trunk);

        // Создание листвы
        const foliageGeometry = new THREE.ConeGeometry(5, 20, 8);
        const foliage = new THREE.Mesh(foliageGeometry, treeMaterialFoliage);
        foliage.position.y = 15; // Позиционирование на вершине ствола
        foliage.castShadow = true;
        foliage.receiveShadow = true;
        tree.add(foliage);

        return tree;
    }

    // Создаём несколько деревьев и размещаем их случайным образом на земле
    for (let i = 0; i < 100; i++) { // Количество деревьев
        const tree = createTree();
        tree.position.set(
            Math.random() * 800 - 400, // Позиция по X в мире
            0,                        // Позиция по Y на земле
            Math.random() * 800 - 400  // Позиция по Z в мире
        );
        scene.add(tree);
        trees.push(tree);
    }

    // ---------- Добавление красных кубов как целей ----------
    const boxGeometry = new THREE.BoxGeometry(20, 20, 20);
    const boxMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // Красный цвет
    const targets = [];
    for (let i = 0; i < 20; i++) {
        const box = new THREE.Mesh(boxGeometry, boxMaterial);
        box.position.set(
            Math.random() * 800 - 400,
            10,
            Math.random() * 800 - 400
        );
        box.castShadow = true;
        box.receiveShadow = true;
        scene.add(box);
        targets.push(box);
    }

    // ---------- Добавление дома с окнами и дверями ----------
    function createHouse() {
        const house = new THREE.Group();

        // ----- Стены -----
        const wallsMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 }); // Коричневый для стен
        const wallsGeometry = new THREE.BoxGeometry(100, 60, 100);
        const walls = new THREE.Mesh(wallsGeometry, wallsMaterial);
        walls.castShadow = true;
        walls.receiveShadow = true;
        walls.position.y = 30; // Позиционирование на земле
        house.add(walls);

        // ----- Крыша -----
        const roofMaterial = new THREE.MeshStandardMaterial({ color: 0xB22222 }); // Firebrick для крыши
        const roofGeometry = new THREE.ConeGeometry(70, 30, 4); // Коническая крыша с квадратной базой
        const roof = new THREE.Mesh(roofGeometry, roofMaterial);
        roof.rotation.y = Math.PI / 4; // Поворот для выравнивания с стенами
        roof.position.y = 60; // Позиционирование на вершине стен
        roof.castShadow = true;
        roof.receiveShadow = true;
        house.add(roof);

        // ----- Дверь -----
        const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x654321 }); // Тёмно-коричневый для двери
        const doorGeometry = new THREE.BoxGeometry(20, 35, 5);
        const door = new THREE.Mesh(doorGeometry, doorMaterial);
        door.position.set(0, 17.5, 52.51); // Позиционирование на передней грани стен
        door.castShadow = true;
        door.receiveShadow = true;
        house.add(door);

        // ----- Окна -----
        const windowMaterial = new THREE.MeshStandardMaterial({ 
            color: 0xADD8E6, // Светло-голубой для окон
            transparent: true,
            opacity: 0.6
        });
        const windowGeometry = new THREE.BoxGeometry(15, 15, 2);

        // Передние окна
        const frontWindow = new THREE.Mesh(windowGeometry, windowMaterial);
        frontWindow.position.set(-30, 30, 52.51); // Левое переднее окно
        house.add(frontWindow);

        const frontWindow2 = new THREE.Mesh(windowGeometry, windowMaterial);
        frontWindow2.position.set(30, 30, 52.51); // Правое переднее окно
        house.add(frontWindow2);

        // Задние окна
        const backWindow = new THREE.Mesh(windowGeometry, windowMaterial);
        backWindow.position.set(-30, 30, -52.51); // Левое заднее окно
        house.add(backWindow);

        const backWindow2 = new THREE.Mesh(windowGeometry, windowMaterial);
        backWindow2.position.set(30, 30, -52.51); // Правое заднее окно
        house.add(backWindow2);

        // ----- Боковые окна -----
        const sideWindow = new THREE.Mesh(windowGeometry, windowMaterial);
        sideWindow.position.set(52.51, 30, 30); // Правое боковое окно
        sideWindow.rotation.y = Math.PI / 2;
        house.add(sideWindow);

        const sideWindow2 = new THREE.Mesh(windowGeometry, windowMaterial);
        sideWindow2.position.set(52.51, 30, -30); // Правое боковое окно
        sideWindow2.rotation.y = Math.PI / 2;
        house.add(sideWindow2);

        const sideWindow3 = new THREE.Mesh(windowGeometry, windowMaterial);
        sideWindow3.position.set(-52.51, 30, 30); // Левое боковое окно
        sideWindow3.rotation.y = Math.PI / 2;
        house.add(sideWindow3);

        const sideWindow4 = new THREE.Mesh(windowGeometry, windowMaterial);
        sideWindow4.position.set(-52.51, 30, -30); // Левое боковое окно
        sideWindow4.rotation.y = Math.PI / 2;
        house.add(sideWindow4);

        return house;
    }

    // Создаём и позиционируем дом
    const house = createHouse();
    house.position.set(0, 0, -200); // Позиционирование дома за пределами начальной точки
    scene.add(house);

    // ---------- Настройка Pointer Lock Controls ----------
    const controls = new PointerLockControls(camera, document.body);

    const instructions = document.getElementById('instructions');
    instructions.addEventListener('click', () => {
        controls.lock();
    }, false);

    controls.addEventListener('lock', () => {
        instructions.style.display = 'none';
    });

    controls.addEventListener('unlock', () => {
        instructions.style.display = '';
    });

    scene.add(controls.getObject());

    // ---------- Инициализация позиции игрока ----------
    controls.getObject().position.y = standingHeight;

    // ---------- Переменные для движения ----------
    const move = {
        forward: false,
        backward: false,
        left: false,
        right: false,
        run: false
    };

    let speed = 200.0; // Базовая скорость (единиц в секунду)
    const runMultiplier = 1.75; // Множитель скорости для бега

    // ---------- Переменные для прыжков и гравитации ----------
    const gravity = 2000.0; // Сила гравитации (единиц в секунду в квадрате)
    let isOnGround = true;
    let verticalVelocity = 0.0;

    // ---------- Переменные для приседания ----------
    let isCrouching = false;

    // ---------- Переменные для стрельбы ----------
    let isShooting = false;          // Флаг для непрерывной стрельбы
    const fireRate = 10;             // Скорострельность (патронов в секунду)
    const shootInterval = 1 / fireRate; // Интервал между выстрелами
    let shootTimer = 0;              // Таймер для отслеживания выстрелов

    // ---------- Обработчики событий для нажатий клавиш ----------
    const onKeyDown = function (event) {
        switch (event.code) {
            case 'KeyW':
            case 'ArrowUp':
                move.forward = true;
                break;
            case 'KeyA':
            case 'ArrowLeft':
                move.left = true;
                break;
            case 'KeyS':
            case 'ArrowDown':
                move.backward = true;
                break;
            case 'KeyD':
            case 'ArrowRight':
                move.right = true;
                break;
            case 'Space':
                if (isOnGround && !isCrouching) {
                    verticalVelocity = 600; // Начальная скорость прыжка (вверх)
                    isOnGround = false;
                }
                break;
            case 'ShiftLeft':
            case 'ShiftRight':
                move.run = true;
                break;
            case 'ControlLeft':
            case 'ControlRight':
                toggleCrouch();
                break;
        }
    };

    const onKeyUp = function (event) {
        switch (event.code) {
            case 'KeyW':
            case 'ArrowUp':
                move.forward = false;
                break;
            case 'KeyA':
            case 'ArrowLeft':
                move.left = false;
                break;
            case 'KeyS':
            case 'ArrowDown':
                move.backward = false;
                break;
            case 'KeyD':
            case 'ArrowRight':
                move.right = false;
                break;
            case 'ShiftLeft':
            case 'ShiftRight':
                move.run = false;
                break;
            // Пропускаем обработку 'Control' на keyup, так как используем toggle
        }
    };

    window.addEventListener('keydown', onKeyDown, false);
    window.addEventListener('keyup', onKeyUp, false);

    // ---------- Обработчики мыши для стрельбы ----------
    window.addEventListener('mousedown', (event) => {
        if (event.button === 0 && controls.isLocked === true) { // Левая кнопка мыши
            isShooting = true;
        }
    }, false);

    window.addEventListener('mouseup', (event) => {
        if (event.button === 0 && controls.isLocked === true) { // Левая кнопка мыши
            isShooting = false;
        }
    }, false);

    // ---------- Функция переключения приседания ----------
    function toggleCrouch() {
        if (isCrouching) {
            // Встать
            isCrouching = false;
            // Плавно вернуть камеру к стоячей позиции
            new TWEEN.Tween(controls.getObject().position)
                .to({ y: standingHeight }, 200)
                .easing(TWEEN.Easing.Linear.None)
                .start();
            // Плавно вернуть позицию оружия к стоячей
            new TWEEN.Tween(gun.position)
                .to({ y: -1 }, 200)
                .easing(TWEEN.Easing.Linear.None)
                .start();
        } else {
            // Присесть
            isCrouching = true;
            // Плавно опустить камеру в присед
            new TWEEN.Tween(controls.getObject().position)
                .to({ y: crouchHeight }, 200)
                .easing(TWEEN.Easing.Linear.None)
                .start();
            // Плавно опустить оружие в присед
            new TWEEN.Tween(gun.position)
                .to({ y: -1.5 }, 200)
                .easing(TWEEN.Easing.Linear.None)
                .start();
        }
    }

    // ---------- Массив для пуль ----------
    const bullets = [];
    const bulletSpeed = 1000; // Скорость пули (единиц в секунду)
    const bulletGeometry = new THREE.SphereGeometry(0.5, 8, 8);
    const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 }); // Жёлтый цвет пули

    // ---------- Механика стрельбы ----------
    const shoot = () => {
        const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
        bullet.position.copy(controls.getObject().position);
        bullet.position.y -= 1; // Корректировка позиции пули по Y для выравнивания с оружием

        // Расчёт направления
        const vector = new THREE.Vector3(0, 0, -1);
        vector.applyQuaternion(camera.quaternion);
        bullet.velocity = vector.clone().multiplyScalar(bulletSpeed);

        scene.add(bullet);
        bullets.push(bullet);
    };

    // ---------- Настройка оружия (модель AK-74) ----------
    const gun = new THREE.Group();

    // ----- Приёмник -----
    const receiverGeometry = new THREE.BoxGeometry(1, 0.5, 1.5);
    const receiverMaterial = new THREE.MeshStandardMaterial({ color: 0x444444 });
    const receiver = new THREE.Mesh(receiverGeometry, receiverMaterial);
    receiver.position.set(0, 0, -0.75);
    receiver.castShadow = true;
    receiver.receiveShadow = true;
    gun.add(receiver);

    // ----- Дуло -----
    const barrelGeometry = new THREE.CylinderGeometry(0.1, 0.1, 3, 8);
    const barrelMaterial = new THREE.MeshStandardMaterial({ color: 0x555555 });
    const barrel = new THREE.Mesh(barrelGeometry, barrelMaterial);
    barrel.rotation.x = Math.PI / 2;
    barrel.position.set(0, 0, -2);
    barrel.castShadow = true;
    barrel.receiveShadow = true;
    gun.add(barrel);

    // ----- Ручка -----
    const handguardGeometry = new THREE.BoxGeometry(0.3, 0.3, 1.5);
    const handguardMaterial = new THREE.MeshStandardMaterial({ color: 0x666666 });
    const handguard = new THREE.Mesh(handguardGeometry, handguardMaterial);
    handguard.position.set(0, 0.5, -1.5);
    handguard.castShadow = true;
    handguard.receiveShadow = true;
    gun.add(handguard);

    // ----- Магазин -----
    const magazineGeometry = new THREE.BoxGeometry(0.5, 1, 0.5);
    const magazineMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });
    const magazine = new THREE.Mesh(magazineGeometry, magazineMaterial);
    magazine.position.set(0, -0.75, -0.75);
    magazine.castShadow = true;
    magazine.receiveShadow = true;
    gun.add(magazine);

    // ----- Приклад -----
    const stockGeometry = new THREE.BoxGeometry(0.2, 1, 0.2);
    const stockMaterial = new THREE.MeshStandardMaterial({ color: 0x444444 });
    const stock = new THREE.Mesh(stockGeometry, stockMaterial);
    stock.position.set(-0.6, -0.4, 0.3);
    stock.castShadow = true;
    stock.receiveShadow = true;
    gun.add(stock);

    // ----- Передняя прицелная планка -----
    const frontSightGeometry = new THREE.BoxGeometry(0.05, 0.05, 0.2);
    const frontSightMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
    const frontSight = new THREE.Mesh(frontSightGeometry, frontSightMaterial);
    frontSight.position.set(0, 0.25, -3);
    frontSight.castShadow = true;
    frontSight.receiveShadow = true;
    gun.add(frontSight);

    // ----- Задняя прицелная планка -----
    const rearSightGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.2);
    const rearSightMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
    const rearSight = new THREE.Mesh(rearSightGeometry, rearSightMaterial);
    rearSight.position.set(-0.3, 0.25, -0.5);
    rearSight.castShadow = true;
    rearSight.receiveShadow = true;
    gun.add(rearSight);

    // ----- Пистолетная рукоятка -----
    const gripGeometry = new THREE.BoxGeometry(0.3, 0.5, 0.2);
    const gripMaterial = new THREE.MeshStandardMaterial({ color: 0x222222 });
    const grip = new THREE.Mesh(gripGeometry, gripMaterial);
    grip.position.set(0.3, -0.3, -0.5);
    grip.castShadow = true;
    grip.receiveShadow = true;
    gun.add(grip);

    // Позиционирование оружия перед камерой
    gun.position.set(0.5, -1, -2); // Начальная позиция стоя
    camera.add(gun); // Добавляем оружие в иерархию камеры

    // Включаем тени для частей оружия
    receiver.castShadow = true;
    receiver.receiveShadow = true;
    barrel.castShadow = true;
    barrel.receiveShadow = true;
    handguard.castShadow = true;
    handguard.receiveShadow = true;
    magazine.castShadow = true;
    magazine.receiveShadow = true;
    stock.castShadow = true;
    stock.receiveShadow = true;
    frontSight.castShadow = true;
    frontSight.receiveShadow = true;
    rearSight.castShadow = true;
    rearSight.receiveShadow = true;
    grip.castShadow = true;
    grip.receiveShadow = true;

    // ---------- Анимация отдачи оружия ----------
    let isRecoiling = false;
    const recoilDistance = 0.1; // Насколько оружие отступает при отдаче
    const originalGunPosition = gun.position.clone();
    const originalGunRotationX = gun.rotation.x;

    const recoilAnim = () => {
        if (!isRecoiling) {
            isRecoiling = true;
            // Начало отдачи
            new TWEEN.Tween(gun.position)
                .to({ z: originalGunPosition.z - recoilDistance, y: originalGunPosition.y + 0.5 }, 100)
                .easing(TWEEN.Easing.Linear.None)
                .onComplete(() => {
                    // Возврат к исходной позиции
                    new TWEEN.Tween(gun.position)
                        .to({ z: originalGunPosition.z, y: originalGunPosition.y }, 100)
                        .easing(TWEEN.Easing.Linear.None)
                        .onComplete(() => {
                            isRecoiling = false;
                        })
                        .start();
                })
                .start();
            
            // Опционально: добавление вращения отдачи
            new TWEEN.Tween(gun.rotation)
                .to({ x: originalGunRotationX + 0.05 }, 100)
                .easing(TWEEN.Easing.Linear.None)
                .onComplete(() => {
                    new TWEEN.Tween(gun.rotation)
                        .to({ x: originalGunRotationX }, 100)
                        .easing(TWEEN.Easing.Linear.None)
                        .start();
                })
                .start();
        }
    };

    // ---------- Настройка обнаружения столкновений ----------
    const collidableObjects = [];

    // Функция для добавления объекта в список коллайдеров
    function addCollidable(object) {
        object.updateMatrixWorld(true); // Обновляем мировую матрицу объекта
        const box = new THREE.Box3().setFromObject(object);
        collidableObjects.push(box);
    }

    // Добавляем коллайдеры: деревья, дом, цели
    trees.forEach(tree => addCollidable(tree));
    addCollidable(house);
    targets.forEach(target => addCollidable(target));

    // ---------- Визуализация коллайдеров для отладки ----------
    const debugHelpers = new THREE.Group();
    scene.add(debugHelpers);

    function visualizeBoundingBoxes() {
        collidableObjects.forEach((box, index) => {
            const boxHelper = new THREE.Box3Helper(box, 0x00ff00);
            debugHelpers.add(boxHelper);
        });
    }

    visualizeBoundingBoxes();

    // ----- Toggle Bounding Boxes Visibility -----
    window.addEventListener('keydown', (event) => {
        if (event.code === 'KeyB') { // Нажмите 'B' для переключения
            debugHelpers.visible = !debugHelpers.visible;
        }
    });

    // ---------- Вспомогательная функция 2D обнаружения столкновений ----------
    function circleIntersectsBox2D(x, z, radius, box) {
        // Находим ближайшую точку на боксе к центру круга
        const closestX = Math.max(box.min.x, Math.min(x, box.max.x));
        const closestZ = Math.max(box.min.z, Math.min(z, box.max.z));
        // Вычисляем квадрат расстояния
        const distanceSq = (x - closestX) ** 2 + (z - closestZ) ** 2;
        return distanceSq < (radius * radius);
    }

    // ---------- Инициализация Raycaster ----------
    const raycaster = new THREE.Raycaster();
    const rayOrigin = new THREE.Vector3();
    const rayDirection = new THREE.Vector3();

    // ---------- Главный цикл анимации ----------
    const clockObj = new THREE.Clock();

    function animate(time) {
        requestAnimationFrame(animate);
        let deltaTime = clockObj.getDelta();

        // Обновление анимаций TWEEN
        TWEEN.update(time);

        // ---------- Обработка бега ----------
        let currentSpeed = speed;
        if (move.run && !isCrouching) {
            currentSpeed *= runMultiplier; // Увеличение скорости при беге
        }

        // ---------- Расчёт перемещений ----------
        let moveX = 0;
        let moveZ = 0;

        if (move.forward) moveZ += 1;
        if (move.backward) moveZ -= 1;
        if (move.left) moveX -= 1;
        if (move.right) moveX += 1;

        // Нормализация движения для предотвращения ускорения при диагональном движении
        const movementDirection = new THREE.Vector3(moveX, 0, moveZ).normalize();
        if (movementDirection.length() === 0) {
            // Если нет движения, пропуск перемещения
            movementDirection.set(0, 0, 0);
        }

        const movement = movementDirection.multiplyScalar(currentSpeed * deltaTime);

        // ---------- Расчёт предполагаемой позиции ----------
        const pos = controls.getObject().position.clone();
        const tentativeX = pos.x + movement.x;
        const tentativeZ = pos.z + movement.z;

        // ---------- Проверка столкновений ----------
        let collision = false;

        // ---------- Raycasting для предотвращения проникновения ----------
        // Проверка движения по оси X
        if (movement.x !== 0) {
            rayOrigin.set(pos.x, pos.y, pos.z);
            rayDirection.set(movement.x > 0 ? 1 : -1, 0, 0).normalize();
            raycaster.set(rayOrigin, rayDirection);
            const distance = Math.abs(movement.x);
            const intersections = raycaster.intersectObjects(
                collidableObjects.map(box => box.object || box), // Ensure object references
                false
            );
            if (intersections.length > 0 && intersections[0].distance < distance + collisionBuffer) {
                // Ограничиваем перемещение до точки перед столкновением
                controls.moveRight(
                    (intersections[0].distance - collisionBuffer) * (movement.x > 0 ? 1 : -1)
                );
                movement.x = 0; // Обнуляем дальнейшее движение по X
            } else {
                controls.moveRight(movement.x);
            }
        }

        // Проверка движения по оси Z
        if (movement.z !== 0) {
            rayOrigin.set(pos.x, pos.y, pos.z);
            rayDirection.set(0, 0, movement.z > 0 ? 1 : -1).normalize();
            raycaster.set(rayOrigin, rayDirection);
            const distance = Math.abs(movement.z);
            const intersections = raycaster.intersectObjects(
                collidableObjects.map(box => box.object || box),
                false
            );
            if (intersections.length > 0 && intersections[0].distance < distance + collisionBuffer) {
                // Ограничиваем перемещение до точки перед столкновением
                controls.moveForward(
                    (intersections[0].distance - collisionBuffer) * (movement.z > 0 ? 1 : -1)
                );
                movement.z = 0; // Обнуляем дальнейшее движение по Z
            } else {
                controls.moveForward(movement.z);
            }
        }

        // ---------- Обработка прыжков и гравитации ----------
        if (!isOnGround) {
            verticalVelocity -= gravity * deltaTime; // Применение гравитации
            const newY = controls.getObject().position.y + verticalVelocity * deltaTime;

            if (newY <= standingHeight) {
                controls.getObject().position.y = standingHeight;
                isOnGround = true;
                verticalVelocity = 0;
            } else {
                controls.getObject().position.y = newY;
            }
        } else {
            // Убедимся, что игрок на земле
            if (controls.getObject().position.y < standingHeight) {
                controls.getObject().position.y = standingHeight;
            }
        }

        // Предотвращаем снижение камеры ниже высоты при приседании
        if (isCrouching && controls.getObject().position.y < crouchHeight) {
            controls.getObject().position.y = crouchHeight;
        }

        // ---------- Обработка непрерывной стрельбы ----------
        shootTimer -= deltaTime;
        if (isShooting && shootTimer <= 0) {
            shoot();
            shootTimer = shootInterval;
            recoilAnim(); // Запуск отдачи оружия
        }

        // ---------- Обновление пуль ----------
        for (let i = 0; i < bullets.length; i++) {
            const bullet = bullets[i];
            
            // Перемещение пули по её скорости и времени кадра
            const velocityDelta = bullet.velocity.clone().multiplyScalar(deltaTime);
            bullet.position.add(velocityDelta);

            // Проверка столкновения с целями
            for (let j = 0; j < targets.length; j++) {
                const target = targets[j];
                const distance = bullet.position.distanceTo(target.position);
                if (distance < 10) { // Предполагаемый радиус цели ~10
                    // Удаление цели и пули
                    scene.remove(target);
                    targets.splice(j, 1);
                    collidableObjects.splice(j, 1); // Удаление из коллайдеров

                    scene.remove(bullet);
                    bullets.splice(i, 1);
                    i--;
                    break;
                }
            }

            // Удаление пули, если она слишком далеко
            const distanceFromCamera = bullet.position.distanceTo(controls.getObject().position);
            if (distanceFromCamera > 1000) {
                scene.remove(bullet);
                bullets.splice(i, 1);
                i--;
            }
        }

        renderer.render(scene, camera);
    }

    animate(performance.now());

</script>
</body>
</html>The problem is that the player can go a little bit behind the territory of the box collider, because of which he gets stuck there, fix it so that he automatically stops a little bit BEFORE the box collider.