Files
rltn/test-collision.html

406 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест системы коллизий интерьеров</title>
<style>
body {
margin: 0;
padding: 0;
background: #000;
color: #fff;
font-family: Arial, sans-serif;
overflow: hidden;
}
#gameContainer {
width: 100vw;
height: 100vh;
position: relative;
}
#debugInfo {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
font-size: 12px;
z-index: 1000;
}
#instructions {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
font-size: 12px;
z-index: 1000;
max-width: 300px;
}
</style>
</head>
<body>
<div id="gameContainer"></div>
<div id="debugInfo">
<div>Статус: <span id="status">Загрузка...</span></div>
<div>Коллайдеры: <span id="colliders">0</span></div>
<div>Позиция игрока: <span id="position">0, 0, 0</span></div>
<div>В интерьере: <span id="inInterior">false</span></div>
</div>
<div id="instructions">
<h3>Управление:</h3>
<p><strong>WASD</strong> - движение</p>
<p><strong>Мышь</strong> - поворот камеры (в интерьере)</p>
<p><strong>Клик по объекту</strong> - вход в интерьер</p>
<p><strong>Escape</strong> - выход из интерьера</p>
<br>
<p><strong>Тест коллизий:</strong></p>
<p>В интерьере игрок не должен проходить сквозь стены и объекты</p>
</div>
<script type="module">
import * as THREE from 'https://unpkg.com/three@0.158.0/build/three.module.js';
// Простая система коллизий для тестирования
class SimpleCollisionTest {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.player = null;
this.isInInterior = false;
this.interiorColliders = [];
this.moveInput = { forward: false, backward: false, left: false, right: false };
this.init();
}
init() {
// Настройка рендерера
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setClearColor(0x87CEEB);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('gameContainer').appendChild(this.renderer.domElement);
// Создаем простую сцену
this.createScene();
// Создаем игрока
this.createPlayer();
// Настраиваем обработчики событий
this.setupEventListeners();
// Запускаем игровой цикл
this.animate();
this.updateDebugInfo();
}
createScene() {
// Освещение
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = true;
this.scene.add(directionalLight);
// Создаем простой интерьер для тестирования
this.createTestInterior();
}
createTestInterior() {
const interiorGroup = new THREE.Group();
// Стены
const wallGeometry = new THREE.BoxGeometry(0.2, 3, 10);
const wallMaterial = new THREE.MeshLambertMaterial({ color: 0x8B4513 });
// Левая стена
const leftWall = new THREE.Mesh(wallGeometry, wallMaterial);
leftWall.position.set(-5, 1.5, 0);
leftWall.castShadow = true;
interiorGroup.add(leftWall);
// Правая стена
const rightWall = new THREE.Mesh(wallGeometry, wallMaterial);
rightWall.position.set(5, 1.5, 0);
rightWall.castShadow = true;
interiorGroup.add(rightWall);
// Задняя стена
const backWallGeometry = new THREE.BoxGeometry(10, 3, 0.2);
const backWall = new THREE.Mesh(backWallGeometry, wallMaterial);
backWall.position.set(0, 1.5, -5);
backWall.castShadow = true;
interiorGroup.add(backWall);
// Передняя стена (с проходом)
const frontWall1 = new THREE.Mesh(new THREE.BoxGeometry(4, 3, 0.2), wallMaterial);
frontWall1.position.set(-3, 1.5, 5);
frontWall1.castShadow = true;
interiorGroup.add(frontWall1);
const frontWall2 = new THREE.Mesh(new THREE.BoxGeometry(4, 3, 0.2), wallMaterial);
frontWall2.position.set(3, 1.5, 5);
frontWall2.castShadow = true;
interiorGroup.add(frontWall2);
// Пол
const floorGeometry = new THREE.BoxGeometry(10, 0.1, 10);
const floorMaterial = new THREE.MeshLambertMaterial({ color: 0x654321 });
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.set(0, 0, 0);
floor.receiveShadow = true;
interiorGroup.add(floor);
// Потолок
const ceiling = new THREE.Mesh(floorGeometry, new THREE.MeshLambertMaterial({ color: 0xFFFFFF }));
ceiling.position.set(0, 3, 0);
interiorGroup.add(ceiling);
// Объекты в интерьере
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshLambertMaterial({ color: 0xFF0000 });
const box1 = new THREE.Mesh(boxGeometry, boxMaterial);
box1.position.set(-2, 0.5, -2);
box1.castShadow = true;
interiorGroup.add(box1);
const box2 = new THREE.Mesh(boxGeometry, boxMaterial);
box2.position.set(2, 0.5, 2);
box2.castShadow = true;
interiorGroup.add(box2);
// Собираем коллайдеры
this.interiorColliders = [];
interiorGroup.traverse((child) => {
if (child.isMesh && child.geometry) {
this.interiorColliders.push(child);
}
});
this.scene.add(interiorGroup);
this.interiorGroup = interiorGroup;
console.log('Создано коллайдеров:', this.interiorColliders.length);
}
createPlayer() {
const playerGeometry = new THREE.BoxGeometry(0.6, 1.6, 0.6);
const playerMaterial = new THREE.MeshLambertMaterial({ color: 0x0000FF });
this.player = new THREE.Mesh(playerGeometry, playerMaterial);
this.player.position.set(0, 0.8, 0);
this.scene.add(this.player);
// Устанавливаем камеру на уровне глаз игрока
this.camera.position.set(0, 1.6, 0);
this.camera.lookAt(0, 1.6, -1);
}
setupEventListeners() {
// Клавиатура
document.addEventListener('keydown', (event) => {
switch(event.code) {
case 'KeyW':
this.moveInput.forward = true;
break;
case 'KeyS':
this.moveInput.backward = true;
break;
case 'KeyA':
this.moveInput.left = true;
break;
case 'KeyD':
this.moveInput.right = true;
break;
case 'Escape':
this.exitInterior();
break;
}
});
document.addEventListener('keyup', (event) => {
switch(event.code) {
case 'KeyW':
this.moveInput.forward = false;
break;
case 'KeyS':
this.moveInput.backward = false;
break;
case 'KeyA':
this.moveInput.left = false;
break;
case 'KeyD':
this.moveInput.right = false;
break;
}
});
// Мышь для поворота камеры в интерьере
document.addEventListener('mousemove', (event) => {
if (this.isInInterior) {
this.camera.rotation.y -= event.movementX * 0.002;
this.camera.rotation.x -= event.movementY * 0.002;
this.camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.camera.rotation.x));
}
});
// Клик для входа в интерьер
this.renderer.domElement.addEventListener('click', () => {
if (!this.isInInterior) {
this.enterInterior();
}
});
// Изменение размера окна
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
}
enterInterior() {
console.log('Вход в интерьер');
this.isInInterior = true;
this.player.visible = false;
// Запрашиваем pointer lock
this.renderer.domElement.requestPointerLock();
this.updateDebugInfo();
}
exitInterior() {
console.log('Выход из интерьера');
this.isInInterior = false;
this.player.visible = true;
// Выходим из pointer lock
document.exitPointerLock();
this.updateDebugInfo();
}
checkCollision(testPosition) {
const playerRadius = 0.3;
const playerHeight = 1.6;
// Создаем AABB для игрока
const playerBox = new THREE.Box3();
const playerMin = new THREE.Vector3(
testPosition.x - playerRadius,
testPosition.y,
testPosition.z - playerRadius
);
const playerMax = new THREE.Vector3(
testPosition.x + playerRadius,
testPosition.y + playerHeight,
testPosition.z + playerRadius
);
playerBox.setFromPoints([playerMin, playerMax]);
// Проверяем столкновения с коллайдерами
for (const collider of this.interiorColliders) {
if (!collider.geometry || !collider.visible) continue;
collider.updateMatrixWorld(true);
const colliderBox = new THREE.Box3();
colliderBox.setFromObject(collider);
if (playerBox.intersectsBox(colliderBox)) {
return true;
}
}
return false;
}
updatePlayer(deltaTime) {
if (!this.isInInterior) return;
const speed = 3.0;
const moveDistance = speed * deltaTime;
// Используем простые направления вместо кватернионов
const forward = new THREE.Vector3(0, 0, -1);
const right = new THREE.Vector3(1, 0, 0);
// Поворачиваем направления в соответствии с поворотом камеры
forward.applyEuler(new THREE.Euler(0, this.camera.rotation.y, 0));
right.applyEuler(new THREE.Euler(0, this.camera.rotation.y, 0));
let moveVector = new THREE.Vector3();
if (this.moveInput.forward) moveVector.add(forward);
if (this.moveInput.backward) moveVector.add(forward.clone().multiplyScalar(-1));
if (this.moveInput.left) moveVector.add(right.clone().multiplyScalar(-1));
if (this.moveInput.right) moveVector.add(right);
if (moveVector.length() > 0) {
moveVector.normalize().multiplyScalar(moveDistance);
// Проверяем коллизии по осям отдельно
let safePosition = this.camera.position.clone();
// Проверяем движение по X
if (Math.abs(moveVector.x) > 0.001) {
const xTestPosition = safePosition.clone();
xTestPosition.x += moveVector.x;
if (!this.checkCollision(xTestPosition)) {
safePosition.x = xTestPosition.x;
}
}
// Проверяем движение по Z
if (Math.abs(moveVector.z) > 0.001) {
const zTestPosition = safePosition.clone();
zTestPosition.z += moveVector.z;
if (!this.checkCollision(zTestPosition)) {
safePosition.z = zTestPosition.z;
}
}
this.camera.position.copy(safePosition);
}
}
updateDebugInfo() {
document.getElementById('status').textContent = this.isInInterior ? 'В интерьере' : 'Вне интерьера';
document.getElementById('colliders').textContent = this.interiorColliders.length;
document.getElementById('position').textContent =
`${this.camera.position.x.toFixed(2)}, ${this.camera.position.y.toFixed(2)}, ${this.camera.position.z.toFixed(2)}`;
document.getElementById('inInterior').textContent = this.isInInterior;
}
animate() {
requestAnimationFrame(() => this.animate());
const deltaTime = 0.016; // Примерно 60 FPS
this.updatePlayer(deltaTime);
this.updateDebugInfo();
this.renderer.render(this.scene, this.camera);
}
}
// Запускаем тест
const test = new SimpleCollisionTest();
</script>
</body>
</html>