бновление от 2025-09-19 для ветки 19sepTest

This commit is contained in:
2025-09-19 19:02:51 +03:00
parent bb58adb1a3
commit 261e8a8b63
23 changed files with 4982 additions and 83 deletions

149
COLLIDER_CONFIGURATION.md Normal file
View File

@@ -0,0 +1,149 @@
# Конфигурация системы коллайдеров
## Обзор изменений
Система коллайдеров была обновлена для обеспечения полного покрытия объектов коллизиями. Теперь коллайдеры создаются с увеличенными размерами, чтобы покрывать весь объект, а не только его часть.
## Основные изменения
### 1. Конфигурация коллайдеров
```javascript
const COLLIDER_CONFIG = {
sizeMultiplier: 2.0, // Коэффициент увеличения размеров (100% больше)
debugMode: false, // Режим отладки для визуализации
minSize: 0.5, // Минимальный размер коллайдера
maxSize: 50.0, // Максимальный размер коллайдера
adaptiveScaling: true // Адаптивное масштабирование
};
```
### 2. Обновленная функция создания коллайдеров
- `createVisualColliderFromModel()` теперь использует увеличенные размеры для JSON данных
- Приоритет отдается реальным размерам мешей из 3D модели
- Fallback к JSON данным с коэффициентом увеличения
### 3. Адаптивное масштабирование
Система автоматически определяет оптимальный коэффициент масштабирования на основе размеров объекта:
- **Маленькие объекты** (< 1.0): коэффициент 3.0x или больше
- **Средние объекты** (1.0-5.0): стандартный коэффициент 2.0x
- **Большие объекты** (> 5.0): уменьшенный коэффициент 1.6x
### 4. Динамическая настройка
Добавлены расширенные функции для настройки коллайдеров:
```javascript
// Изменить коэффициент размера коллайдеров
window.updateColliderSize(3.0); // Увеличить на 200%
// Переключить адаптивное масштабирование
window.toggleAdaptiveScaling();
// Установить ограничения размеров
window.setColliderLimits(1.0, 20.0); // мин: 1.0, макс: 20.0
// Переключить режим отладки
window.toggleColliderDebug();
// Получить текущую конфигурацию
console.log(window.colliderConfig);
// Протестировать коллизии
window.testCollisions();
// Быстрый тест всех функций
window.quickTest();
// Управление цветом и прозрачностью коллайдеров
window.setColliderColor(0, 1, 0); // Зеленый цвет (RGB 0-1)
window.setColliderOpacity(0.5); // Полупрозрачность (0-1)
window.randomizeColliderColors(); // Случайные цвета
// Управление цветом и прозрачностью объектов интерьера
window.setInteriorObjectColor(1, 0, 0); // Красный цвет объектов (RGB 0-1)
window.setInteriorObjectOpacity(0.7); // Полупрозрачность объектов (0-1)
```
## Использование
### Базовое использование
Коллайдеры автоматически загружаются из `colliders_city_1.json` с увеличенными размерами.
### Настройка размеров
Если коллайдеры слишком большие или маленькие, можно изменить коэффициент:
```javascript
// В консоли браузера
window.updateColliderSize(1.0); // Оригинальные размеры
window.updateColliderSize(1.2); // +20% (по умолчанию)
window.updateColliderSize(1.5); // +50%
```
### Отладка
Для визуализации коллайдеров включите режим отладки:
```javascript
window.colliderConfig.debugMode = true;
```
## Технические детали
### Алгоритм создания коллайдеров
1. Поиск соответствующего меша в 3D модели по позиции
2. Если меш найден - использование его реальных размеров
3. Если меш не найден - использование JSON данных с коэффициентом увеличения
4. Создание Box3 для проверки коллизий
5. Создание визуального представления
### Проверка коллизий
- Используется AABB (Axis-Aligned Bounding Box) для эффективной проверки
- Игрок представлен как цилиндр с радиусом 0.35 и высотой 1.6
- Проверка пересечения Box3 объектов
## Файлы конфигурации
### colliders_city_1.json
```json
{
"colliders": [
{
"type": "box",
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0 },
"scale": { "x": 2, "y": 2, "z": 2 },
"color": { "r": 1.0, "g": 0.0, "b": 0.0 },
"opacity": 0.3
}
]
}
```
**Параметры:**
- `position`, `rotation`, `scale` - стандартные параметры трансформации
- `color` - RGB цвет в диапазоне 0.0-1.0 (опционально)
- `opacity` - прозрачность в диапазоне 0.0-1.0 (опционально)
Размеры в JSON будут автоматически увеличены на коэффициент `sizeMultiplier`.
## Рекомендации
1. **Начальная настройка**: Используйте коэффициент 1.2 (20% увеличение)
2. **Тонкая настройка**: Тестируйте разные значения от 1.0 до 1.5
3. **Отладка**: Включите `debugMode` для визуальной проверки коллайдеров
4. **Производительность**: Большие коэффициенты могут снизить производительность
## Устранение неполадок
### Коллайдеры слишком маленькие
```javascript
window.updateColliderSize(1.3); // Увеличить коэффициент
```
### Коллайдеры слишком большие
```javascript
window.updateColliderSize(1.1); // Уменьшить коэффициент
```
### Проблемы с производительностью
```javascript
window.updateColliderSize(1.0); // Вернуться к оригинальным размерам
```

196
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@readyplayerme/visage": "^6.10.0",
"bcrypt": "^5.1.1",
"compression": "^1.7.4",
"concurrently": "^8.2.2",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
@@ -5841,6 +5842,84 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/concurrently": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
"dependencies": {
"chalk": "^4.1.2",
"date-fns": "^2.30.0",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"spawn-command": "0.0.2",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"bin": {
"conc": "dist/bin/concurrently.js",
"concurrently": "dist/bin/concurrently.js"
},
"engines": {
"node": "^14.13.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
}
},
"node_modules/concurrently/node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/concurrently/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/concurrently/node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/concurrently/node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
},
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@@ -6446,6 +6525,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -14618,6 +14712,14 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -15417,6 +15519,11 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
},
"node_modules/spawn-command": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ=="
},
"node_modules/spdy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -16479,6 +16586,14 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"bin": {
"tree-kill": "cli.js"
}
},
"node_modules/troika-three-text": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.1.tgz",
@@ -22270,6 +22385,61 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"concurrently": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
"integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
"requires": {
"chalk": "^4.1.2",
"date-fns": "^2.30.0",
"lodash": "^4.17.21",
"rxjs": "^7.8.1",
"shell-quote": "^1.8.1",
"spawn-command": "0.0.2",
"supports-color": "^8.1.1",
"tree-kill": "^1.2.2",
"yargs": "^17.7.2"
},
"dependencies": {
"cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
}
},
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"requires": {
"has-flag": "^4.0.0"
}
},
"yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"requires": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
}
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
}
}
},
"confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@@ -22667,6 +22837,14 @@
"is-data-view": "^1.0.1"
}
},
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"requires": {
"@babel/runtime": "^7.21.0"
}
},
"debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -28343,6 +28521,14 @@
"queue-microtask": "^1.2.2"
}
},
"rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"requires": {
"tslib": "^2.1.0"
}
},
"safe-array-concat": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
@@ -28904,6 +29090,11 @@
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"spawn-command": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
"integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ=="
},
"spdy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -29704,6 +29895,11 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
},
"troika-three-text": {
"version": "0.49.1",
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.1.tgz",

View File

@@ -16,7 +16,81 @@
"x": 3.8820412781839853,
"y": 1.9275391013076184,
"z": 0.020423580261430187
}
},
"color": {
"r": 0.2,
"g": 1,
"b": 0.1
},
"opacity": 1
},
{
"type": "box",
"position": {
"x": -19.682621880668748,
"y": -98.44103033988638,
"z": -71.00143351764237
},
"rotation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": {
"x": 0.021,
"y": 1.9,
"z": 3.8
},
"color": {
"r": 0.2,
"g": 1,
"b": 0.1
},
"opacity": 1
},
{
"type": "box",
"position": {
"x": -16.32989632517387,
"y": -98.91158756288065,
"z": -66.6280016541835
},
"rotation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": {
"x": 1,
"y": 2,
"z": 1
},
"color": {
"r": 0.2,
"g": 1,
"b": 0.1
},
"opacity": 1
},
{
"type": "box",
"position": {
"x": 3.1476200534247045,
"y": -0.5512983469988768,
"z": -0.4395577458346196
},
"rotation": {
"x": 0,
"y": 0,
"z": 0
},
"scale": {
"x": 0.5301919909798507,
"y": 2.40369930235243007,
"z": 1.2663514332721553
},
"color": {},
"opacity": 0.3
}
]
}

View File

@@ -1 +1 @@
{"time":"2025-08-08T00:17:13.976Z","lastReal":1757399069805}
{"time":"2025-10-30T05:20:24.552Z","lastReal":1758297743627}

View File

@@ -1666,6 +1666,57 @@ app.get('/api/cities', authenticate, async (req, res) => {
}
});
// API endpoint для сохранения коллайдеров города
app.post('/api/colliders/city/:cityId', authenticate, async (req, res) => {
const cityId = req.params.cityId;
const collidersData = req.body;
try {
// Сохраняем коллайдеры в JSON файл
const fileName = `colliders_city_${cityId}.json`;
const filePath = pathLib.join(__dirname, 'public', fileName);
// Создаем директорию если не существует
await fs.promises.mkdir(pathLib.dirname(filePath), { recursive: true });
// Записываем данные в файл
await fs.promises.writeFile(filePath, JSON.stringify(collidersData, null, 2), 'utf8');
console.log(`Коллайдеры для города ${cityId} сохранены в ${fileName}`);
res.json({ success: true, message: 'Коллайдеры сохранены успешно' });
} catch (error) {
console.error('Ошибка сохранения коллайдеров:', error);
res.status(500).json({ error: 'Ошибка сохранения коллайдеров' });
}
});
// API endpoint для получения коллайдеров города
app.get('/api/colliders/city/:cityId', authenticate, async (req, res) => {
const cityId = req.params.cityId;
try {
const fileName = `colliders_city_${cityId}.json`;
const filePath = pathLib.join(__dirname, 'public', fileName);
// Проверяем существование файла
try {
await fs.promises.access(filePath);
} catch (error) {
// Файл не существует, возвращаем пустой массив
return res.json({ colliders: [] });
}
// Читаем файл
const fileContent = await fs.promises.readFile(filePath, 'utf8');
const data = JSON.parse(fileContent);
res.json(data);
} catch (error) {
console.error('Ошибка чтения коллайдеров:', error);
res.status(500).json({ error: 'Ошибка чтения коллайдеров' });
}
});
app.use((req, res) => {
res.sendFile(pathLib.join(__dirname, 'build', 'index.html'));
});

View File

@@ -11,6 +11,7 @@ import RequireProfile from './components/RequireProfile';
import MapEditor from './pages/MapEditor';
import InteriorEditor from './pages/InteriorEditor';
import CollisionEditor from './pages/CollisionEditor';
import EnhancedCollisionEditor from './pages/EnhancedCollisionEditor';
export default function App() {
const [isAuth, setIsAuth] = useState(!!localStorage.getItem('token'));
@@ -89,6 +90,16 @@ export default function App() {
: <Navigate to="/login" replace/>
}
/>
<Route
path="/enhanced-collision-editor"
element={
isAuth
? <RequireProfile>
<EnhancedCollisionEditor />
</RequireProfile>
: <Navigate to="/login" replace/>
}
/>
{/* всё остальное */}
<Route path="*" element={<Navigate to="/" replace />} />

File diff suppressed because it is too large Load Diff

View File

@@ -100,6 +100,9 @@ export class CameraManager {
this.fpPitch = 0;
this.fpCamera.updateProjectionMatrix();
// Запрашиваем pointer lock для управления мышью
this.requestPointerLock();
}
/**
@@ -108,6 +111,27 @@ export class CameraManager {
switchToOrthoCamera() {
this.currentCamera = this.orthoCamera;
this.fpPitch = 0;
// Выходим из pointer lock
this.exitPointerLock();
}
/**
* Запрос pointer lock для управления мышью
*/
requestPointerLock() {
if (document.body.requestPointerLock) {
document.body.requestPointerLock();
}
}
/**
* Выход из pointer lock
*/
exitPointerLock() {
if (document.exitPointerLock) {
document.exitPointerLock();
}
}
/**

View File

@@ -0,0 +1,300 @@
import * as THREE from 'three';
/**
* Менеджер коллизий
* Отвечает за проверку столкновений игрока с объектами интерьера
*/
export class CollisionManager {
constructor(sceneManager) {
this.sceneManager = sceneManager;
this.playerRadius = 0.35; // Радиус игрока
this.playerHeight = 1.6; // Высота игрока
this.collisionCache = new Map(); // Кэш для оптимизации
this.cacheTimeout = 100; // Время жизни кэша в мс
this.lastCacheUpdate = 0;
this.init();
}
/**
* Инициализация менеджера коллизий
*/
init() {
console.log('CollisionManager инициализирован');
}
/**
* Проверка коллизий при движении игрока в интерьере
* @param {THREE.Vector3} currentPosition - Текущая позиция игрока
* @param {THREE.Vector3} targetPosition - Целевая позиция игрока
* @param {THREE.Vector3} direction - Направление движения
* @param {number} deltaTime - Время между кадрами
* @returns {THREE.Vector3} - Безопасная позиция игрока
*/
checkInteriorCollisions(currentPosition, targetPosition, direction, deltaTime) {
if (!this.sceneManager.interiorColliders || this.sceneManager.interiorColliders.length === 0) {
return targetPosition;
}
// Обновляем кэш коллизий если нужно
this.updateCollisionCache();
// Получаем коллайдеры из кэша
const colliders = this.collisionCache.get('interiorColliders') || [];
if (colliders.length === 0) {
return targetPosition;
}
// Проверяем коллизии по осям отдельно для более плавного движения
let safePosition = currentPosition.clone();
// Проверяем движение по X
if (Math.abs(direction.x) > 0.001) {
const xTestPosition = safePosition.clone();
xTestPosition.x = targetPosition.x;
if (!this.checkPlayerCollision(xTestPosition, colliders)) {
safePosition.x = targetPosition.x;
}
}
// Проверяем движение по Z
if (Math.abs(direction.z) > 0.001) {
const zTestPosition = safePosition.clone();
zTestPosition.z = targetPosition.z;
if (!this.checkPlayerCollision(zTestPosition, colliders)) {
safePosition.z = targetPosition.z;
}
}
return safePosition;
}
/**
* Проверка столкновения игрока с коллайдерами
* @param {THREE.Vector3} position - Позиция игрока
* @param {Array} colliders - Массив коллайдеров
* @returns {boolean} - true если есть столкновение
*/
checkPlayerCollision(position, colliders) {
// Создаем AABB для игрока
const playerBox = new THREE.Box3();
const playerMin = new THREE.Vector3(
position.x - this.playerRadius,
position.y,
position.z - this.playerRadius
);
const playerMax = new THREE.Vector3(
position.x + this.playerRadius,
position.y + this.playerHeight,
position.z + this.playerRadius
);
playerBox.setFromPoints([playerMin, playerMax]);
// Проверяем столкновения с каждым коллайдером
for (const collider of colliders) {
if (!collider.geometry || !collider.visible) continue;
// Пропускаем интерактивные объекты
if (collider.userData && (collider.userData.interactable || collider.userData.payload)) {
continue;
}
// Пропускаем сферы (хит-зоны)
if (collider.geometry.type === 'SphereGeometry') {
continue;
}
try {
// Обновляем мировую матрицу коллайдера
collider.updateMatrixWorld(true);
// Создаем AABB для коллайдера
const colliderBox = new THREE.Box3();
colliderBox.setFromObject(collider);
// Проверяем пересечение
if (playerBox.intersectsBox(colliderBox)) {
return true;
}
} catch (error) {
console.warn('Ошибка при проверке коллизии:', error);
continue;
}
}
return false;
}
/**
* Обновление кэша коллизий
*/
updateCollisionCache() {
const now = Date.now();
// Обновляем кэш только если прошло достаточно времени
if (now - this.lastCacheUpdate < this.cacheTimeout) {
return;
}
this.lastCacheUpdate = now;
// Очищаем старый кэш
this.collisionCache.clear();
// Собираем коллайдеры интерьера
const interiorColliders = this.sceneManager.interiorColliders || [];
const validColliders = [];
// Фильтруем и валидируем коллайдеры
for (const collider of interiorColliders) {
if (this.isValidCollider(collider)) {
validColliders.push(collider);
}
}
// Если коллайдеров мало, попробуем собрать их из группы интерьера
if (validColliders.length === 0 && this.sceneManager.interiorGroup) {
this.collectCollidersFromInteriorGroup(validColliders);
}
this.collisionCache.set('interiorColliders', validColliders);
}
/**
* Проверка валидности коллайдера
* @param {THREE.Object3D} collider - Объект для проверки
* @returns {boolean} - true если коллайдер валиден
*/
isValidCollider(collider) {
if (!collider || !collider.isMesh) return false;
if (!collider.geometry) return false;
if (!collider.visible) return false;
// Пропускаем интерактивные объекты
if (collider.userData && (collider.userData.interactable || collider.userData.payload)) {
return false;
}
// Пропускаем сферы (хит-зоны)
if (collider.geometry.type === 'SphereGeometry') {
return false;
}
return true;
}
/**
* Сбор коллайдеров из группы интерьера
* @param {Array} colliders - Массив для добавления коллайдеров
*/
collectCollidersFromInteriorGroup(colliders) {
if (!this.sceneManager.interiorGroup) return;
try {
this.sceneManager.interiorGroup.updateMatrixWorld(true);
this.sceneManager.interiorGroup.traverse((child) => {
if (this.isValidCollider(child)) {
colliders.push(child);
}
});
} catch (error) {
console.warn('Ошибка при сборе коллайдеров из группы интерьера:', error);
}
}
/**
* Проверка коллизий с интерактивными объектами
* @param {THREE.Vector3} position - Позиция игрока
* @returns {Object|null} - Данные интерактивного объекта или null
*/
checkInteriorInteractions(position) {
const interactables = this.sceneManager.interiorInteractables || [];
for (const interactable of interactables) {
if (!interactable.geometry || !interactable.visible) continue;
try {
interactable.updateMatrixWorld(true);
// Создаем сферу вокруг интерактивного объекта
const interactableBox = new THREE.Box3();
interactableBox.setFromObject(interactable);
// Создаем AABB для игрока
const playerBox = new THREE.Box3();
const playerMin = new THREE.Vector3(
position.x - this.playerRadius,
position.y,
position.z - this.playerRadius
);
const playerMax = new THREE.Vector3(
position.x + this.playerRadius,
position.y + this.playerHeight,
position.z + this.playerRadius
);
playerBox.setFromPoints([playerMin, playerMax]);
if (playerBox.intersectsBox(interactableBox)) {
return interactable.userData.payload || { type: 'interactable' };
}
} catch (error) {
console.warn('Ошибка при проверке взаимодействия:', error);
continue;
}
}
return null;
}
/**
* Получение безопасной позиции для телепортации
* @param {THREE.Vector3} targetPosition - Целевая позиция
* @returns {THREE.Vector3} - Безопасная позиция
*/
getSafeTeleportPosition(targetPosition) {
const colliders = this.collisionCache.get('interiorColliders') || [];
if (colliders.length === 0) {
return targetPosition.clone();
}
// Проверяем целевую позицию
if (!this.checkPlayerCollision(targetPosition, colliders)) {
return targetPosition.clone();
}
// Ищем ближайшую безопасную позицию
const searchRadius = 2.0;
const searchSteps = 8;
for (let radius = 0.5; radius <= searchRadius; radius += 0.5) {
for (let step = 0; step < searchSteps; step++) {
const angle = (step / searchSteps) * Math.PI * 2;
const testPosition = new THREE.Vector3(
targetPosition.x + Math.cos(angle) * radius,
targetPosition.y,
targetPosition.z + Math.sin(angle) * radius
);
if (!this.checkPlayerCollision(testPosition, colliders)) {
return testPosition;
}
}
}
// Если не нашли безопасную позицию, возвращаем исходную
return targetPosition.clone();
}
/**
* Очистка ресурсов
*/
dispose() {
this.collisionCache.clear();
console.log('CollisionManager очищен');
}
}

View File

@@ -4,6 +4,7 @@ import { CameraManager } from './CameraManager.js';
import { PlayerManager } from './PlayerManager.js';
import { RendererManager } from './RendererManager.js';
import { InteriorManager } from './InteriorManager.js';
import { CollisionManager } from './CollisionManager.js';
/**
* Основной класс игры
@@ -18,7 +19,8 @@ export class GameCore {
// Инициализация модулей
this.sceneManager = new SceneManager();
this.cameraManager = new CameraManager();
this.playerManager = new PlayerManager(this.sceneManager);
this.collisionManager = new CollisionManager(this.sceneManager);
this.playerManager = new PlayerManager(this.sceneManager, this.collisionManager);
this.rendererManager = new RendererManager(container);
this.interiorManager = new InteriorManager(this.sceneManager);
@@ -287,6 +289,7 @@ export class GameCore {
async enterInterior(interiorId) {
try {
this.isInInterior = true;
this.playerManager.setInInterior(true);
// Сохраняем позицию игрока
const playerPosition = this.playerManager.getPlayerPosition();
@@ -305,6 +308,7 @@ export class GameCore {
} catch (error) {
console.error('Ошибка входа в интерьер:', error);
this.isInInterior = false;
this.playerManager.setInInterior(false);
}
}
@@ -316,6 +320,7 @@ export class GameCore {
try {
this.isInInterior = false;
this.playerManager.setInInterior(false);
// Восстанавливаем позицию игрока
this.playerManager.restorePosition();
@@ -413,6 +418,13 @@ export class GameCore {
return this.interiorManager;
}
/**
* Получение менеджера коллизий
*/
getCollisionManager() {
return this.collisionManager;
}
/**
* Очистка ресурсов
*/
@@ -425,6 +437,7 @@ export class GameCore {
this.playerManager.dispose();
this.rendererManager.dispose();
this.interiorManager.dispose();
this.collisionManager.dispose();
// Удаляем обработчики событий
document.removeEventListener('keydown', this.handleKeyDown.bind(this));

View File

@@ -5,13 +5,16 @@ import * as THREE from 'three';
* Отвечает за создание, управление и анимацию игрока
*/
export class PlayerManager {
constructor(sceneManager) {
constructor(sceneManager, collisionManager = null) {
this.sceneManager = sceneManager;
this.collisionManager = collisionManager;
this.player = null;
this.mixer = null;
this.moveSpeed = 2.5;
this.interiorMoveSpeed = 3.0; // Скорость движения в интерьере
this.savedPosition = new THREE.Vector3();
this.remotePlayers = {};
this.isInInterior = false;
this.init();
}
@@ -51,7 +54,7 @@ export class PlayerManager {
movePlayer(direction, deltaTime) {
if (!this.player) return;
const moveDistance = this.moveSpeed * deltaTime;
const moveDistance = this.isInInterior ? this.interiorMoveSpeed * deltaTime : this.moveSpeed * deltaTime;
const moveVector = new THREE.Vector3();
if (direction.forward) moveVector.z -= moveDistance;
@@ -59,7 +62,20 @@ export class PlayerManager {
if (direction.left) moveVector.x -= moveDistance;
if (direction.right) moveVector.x += moveDistance;
this.player.position.add(moveVector);
// Если игрок в интерьере, используем систему коллизий
if (this.isInInterior && this.collisionManager) {
const targetPosition = this.player.position.clone().add(moveVector);
const safePosition = this.collisionManager.checkInteriorCollisions(
this.player.position,
targetPosition,
moveVector,
deltaTime
);
this.player.position.copy(safePosition);
} else {
// Обычное движение без коллизий
this.player.position.add(moveVector);
}
// Поворачиваем игрока в направлении движения
if (moveVector.length() > 0) {
@@ -74,12 +90,51 @@ export class PlayerManager {
teleportPlayer(position, rotation = null) {
if (!this.player) return;
this.player.position.copy(position);
// Если игрок в интерьере, проверяем безопасность позиции
if (this.isInInterior && this.collisionManager) {
const safePosition = this.collisionManager.getSafeTeleportPosition(position);
this.player.position.copy(safePosition);
} else {
this.player.position.copy(position);
}
if (rotation !== null) {
this.player.rotation.y = rotation;
}
}
/**
* Сохранение позиции игрока
*/
savePosition() {
if (this.player) {
this.savedPosition.copy(this.player.position);
}
}
/**
* Восстановление позиции игрока
*/
restorePosition() {
if (this.player && this.savedPosition.length() > 0) {
this.player.position.copy(this.savedPosition);
}
}
/**
* Установка состояния интерьера
*/
setInInterior(inInterior) {
this.isInInterior = inInterior;
}
/**
* Получение поворота игрока
*/
getPlayerRotation() {
return this.player ? this.player.rotation.clone() : new THREE.Euler();
}
/**
* Получение позиции игрока
*/

View File

@@ -10,6 +10,7 @@
- **SceneManager.js** - Управление 3D сценой
- **CameraManager.js** - Управление камерами
- **PlayerManager.js** - Управление игроком
- **CollisionManager.js** - Система коллизий для интерьеров
### 2. Rendering (Рендеринг)
- **RendererManager.js** - Управление рендерером
@@ -38,6 +39,28 @@
- **CollisionDetection.js** - Обнаружение коллизий
- **AnimationManager.js** - Управление анимациями
## Система коллизий для интерьеров
### CollisionManager.js
Модуль отвечает за проверку столкновений игрока с объектами интерьера при движении от первого лица.
**Основные функции:**
- `checkInteriorCollisions()` - проверка коллизий при движении
- `checkPlayerCollision()` - проверка столкновения с конкретным объектом
- `getSafeTeleportPosition()` - поиск безопасной позиции для телепортации
- `checkInteriorInteractions()` - проверка взаимодействий с интерактивными объектами
**Особенности:**
- Использует AABB (Axis-Aligned Bounding Box) для быстрой проверки коллизий
- Кэширование коллайдеров для оптимизации производительности
- Раздельная проверка по осям X и Z для плавного движения
- Автоматическое исключение интерактивных объектов и хит-зон
**Интеграция:**
- Работает совместно с `PlayerManager` для ограничения движения
- Использует данные из `SceneManager` о коллайдерах интерьера
- Поддерживает различные типы геометрии (Box, Sphere, Custom)
## Принципы модулизации
1. **Единая ответственность** - каждый модуль отвечает за одну область функциональности

File diff suppressed because it is too large Load Diff

70
src/test-collision.js Normal file
View File

@@ -0,0 +1,70 @@
import { GameCore } from './modules/GameCore.js';
/**
* Тестовый класс для демонстрации системы коллизий в интерьерах
*/
class CollisionTest {
constructor() {
this.gameCore = null;
this.container = null;
}
/**
* Инициализация теста
*/
async init() {
// Создаем контейнер для игры
this.container = document.createElement('div');
this.container.style.width = '100vw';
this.container.style.height = '100vh';
this.container.style.position = 'fixed';
this.container.style.top = '0';
this.container.style.left = '0';
document.body.appendChild(this.container);
// Инициализируем игровое ядро
this.gameCore = new GameCore(this.container);
console.log('Тест системы коллизий инициализирован');
console.log('Управление:');
console.log('- WASD или стрелки для движения');
console.log('- Мышь для поворота камеры в интерьере');
console.log('- Клик по объектам города для входа в интерьер');
}
/**
* Запуск теста
*/
start() {
console.log('Тест запущен');
}
/**
* Остановка теста
*/
stop() {
if (this.gameCore) {
this.gameCore.dispose();
}
if (this.container && this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
console.log('Тест остановлен');
}
}
// Автоматический запуск теста при загрузке страницы
if (typeof window !== 'undefined') {
window.addEventListener('load', async () => {
const test = new CollisionTest();
await test.init();
test.start();
// Обработка закрытия страницы
window.addEventListener('beforeunload', () => {
test.stop();
});
});
}
export { CollisionTest };

View File

@@ -0,0 +1,131 @@
// Тест расширенного функционала редактора коллизий
// Файл: test-advanced-collision-editor.js
console.log('🚀 Тестирование расширенного функционала редактора коллизий');
console.log('');
console.log('✨ Новые возможности:');
console.log('');
console.log('1. 📐 Управление параметрами коллайдера:');
console.log(' - Позиция (X, Y, Z) - точное позиционирование');
console.log(' - Поворот (X, Y, Z) - в радианах');
console.log(' - Масштаб (X, Y, Z) - изменение размера');
console.log(' - Кнопка "Применить параметры" для обновления');
console.log('');
console.log('2. 🔄 Дублирование коллайдеров:');
console.log(' - Кнопка "Дублировать коллайдер"');
console.log(' - Копирует все параметры (позиция, поворот, масштаб, цвет, прозрачность)');
console.log(' - Создает копию со смещением на 2 единицы');
console.log(' - Автоматически выбирает новый коллайдер');
console.log('');
console.log('3. 🗑️ Удаление коллайдеров:');
console.log(' - Кнопка "Удалить коллайдер"');
console.log(' - Удаляет выбранный коллайдер из сцены');
console.log(' - Очищает TransformControls');
console.log(' - Обновляет список коллайдеров');
console.log('');
console.log('4. 🚀 Телепорт к интерьерам:');
console.log(' - Секция "Телепорт к интерьерам"');
console.log(' - Список всех доступных интерьеров');
console.log(' - Показывает координаты каждого интерьера');
console.log(' - Мгновенная телепортация камеры');
console.log(' - Автоматическое наведение на интерьер');
console.log('');
console.log('🎮 Как использовать:');
console.log('');
console.log('📐 Управление параметрами:');
console.log('1. Выберите коллайдер кликом');
console.log('2. В секции "Параметры трансформации" введите новые значения');
console.log('3. Нажмите "Применить параметры"');
console.log('4. Коллайдер обновится в реальном времени');
console.log('');
console.log('🔄 Дублирование:');
console.log('1. Выберите коллайдер для копирования');
console.log('2. Нажмите "Дублировать коллайдер"');
console.log('3. Новый коллайдер появится рядом с оригиналом');
console.log('4. Новый коллайдер автоматически выберется');
console.log('');
console.log('🗑️ Удаление:');
console.log('1. Выберите коллайдер для удаления');
console.log('2. Нажмите "Удалить коллайдер"');
console.log('3. Коллайдер исчезнет из сцены');
console.log('');
console.log('🚀 Телепорт:');
console.log('1. В секции "Телепорт к интерьерам" выберите интерьер');
console.log('2. Камера мгновенно переместится к интерьеру');
console.log('3. Камера автоматически наведется на интерьер');
console.log('');
console.log('🔧 Технические детали:');
console.log('');
console.log('📐 Параметры трансформации:');
console.log('- Позиция: точные координаты в 3D пространстве');
console.log('- Поворот: углы в радианах (0 = 0°, π/2 = 90°, π = 180°)');
console.log('- Масштаб: множители размера (1.0 = оригинальный размер)');
console.log('- Валидация: минимальный масштаб 0.1');
console.log('- Шаг: 0.1 для точной настройки');
console.log('');
console.log('🔄 Дублирование:');
console.log('- Копирует геометрию (box/circle/capsule)');
console.log('- Копирует материал и цвет');
console.log('- Копирует все трансформации');
console.log('- Смещение: +2 по X и Z для избежания наложения');
console.log('- Автоматическое обновление TransformControls');
console.log('');
console.log('🗑️ Удаление:');
console.log('- Удаление из Three.js сцены');
console.log('- Удаление из массива collidersRef');
console.log('- Отключение TransformControls');
console.log('- Сброс выбранного объекта');
console.log('');
console.log('🚀 Телепорт:');
console.log('- Загрузка интерьеров через API /api/interiors');
console.log('- Позиционирование камеры: (pos_x, pos_y + 10, pos_z)');
console.log('- Наведение OrbitControls на интерьер');
console.log('- Прокручиваемый список интерьеров');
console.log('- Отображение координат для каждого интерьера');
console.log('');
console.log('🎯 Преимущества:');
console.log('');
console.log('✅ Точность:');
console.log('- Числовые поля для точного ввода параметров');
console.log('- Поддержка десятичных значений');
console.log('- Валидация входных данных');
console.log('');
console.log('✅ Удобство:');
console.log('- Автоматическое обновление при выборе коллайдера');
console.log('- Мгновенная обратная связь');
console.log('- Интуитивный интерфейс');
console.log('');
console.log('✅ Производительность:');
console.log('- Эффективное дублирование объектов');
console.log('- Оптимизированное удаление');
console.log('- Быстрая телепортация');
console.log('');
console.log('✅ Функциональность:');
console.log('- Полный контроль над коллайдерами');
console.log('- Быстрое перемещение по сцене');
console.log('- Удобное создание похожих объектов');
console.log('');
console.log('🚀 Готово к тестированию!');
console.log('Откройте: http://localhost:4000/enhanced-collision-editor');

73
test-camera-controls.js Normal file
View File

@@ -0,0 +1,73 @@
// Тест улучшенного управления камерой в редакторе коллизий
// Файл: test-camera-controls.js
console.log('🎮 Тестирование улучшенного управления камерой');
console.log('');
console.log('📋 Доступные функции:');
console.log('1. Управление клавиатурой:');
console.log(' - W / ↑ - Движение вперед');
console.log(' - S / ↓ - Движение назад');
console.log(' - A / ← - Движение влево');
console.log(' - D / → - Движение вправо');
console.log(' - Q / PageUp - Движение вверх');
console.log(' - E / PageDown - Движение вниз');
console.log(' - R - Сброс позиции камеры');
console.log('');
console.log('2. Управление мышью:');
console.log(' - Левая кнопка + движение - Поворот камеры');
console.log(' - Правая кнопка + движение - Панорамирование');
console.log(' - Колесо мыши - Приближение/отдаление');
console.log('');
console.log('3. Настройки в интерфейсе:');
console.log(' - Слайдер скорости движения (1-20)');
console.log(' - Кнопка "Сброс камеры"');
console.log(' - Кнопка "Вид сверху"');
console.log(' - Кнопка "Вид сбоку"');
console.log('');
console.log('4. Улучшенные OrbitControls:');
console.log(' - Плавное движение с демпфированием');
console.log(' - Настраиваемая скорость панорамирования');
console.log(' - Настраиваемая скорость приближения');
console.log(' - Настраиваемая скорость поворота');
console.log(' - Ограничения расстояния (5-500 единиц)');
console.log('');
console.log('🚀 Как протестировать:');
console.log('1. Откройте редактор: http://localhost:4000/enhanced-collision-editor');
console.log('2. Попробуйте управление клавиатурой (WASD + стрелки)');
console.log('3. Используйте Q/E для движения вверх/вниз');
console.log('4. Нажмите R для сброса позиции');
console.log('5. Попробуйте кнопки быстрого перемещения');
console.log('6. Измените скорость движения слайдером');
console.log('');
console.log('✨ Особенности:');
console.log('- Движение камеры синхронизировано с OrbitControls');
console.log('- Поддержка множественных клавиш одновременно');
console.log('- Плавное движение без рывков');
console.log('- Информация о позиции камеры в реальном времени');
console.log('- Совместимость с существующим функционалом редактора');
console.log('');
console.log('🎯 Рекомендации по использованию:');
console.log('- Используйте WASD для точного позиционирования');
console.log('- Стрелки клавиатуры для альтернативного управления');
console.log('- Q/E для вертикального движения');
console.log('- R для быстрого возврата к исходной позиции');
console.log('- Кнопки видов для быстрого переключения ракурсов');
console.log('- Настройте скорость под свои предпочтения');
console.log('');
console.log('🔧 Технические детали:');
console.log('- Используется requestAnimationFrame для плавности');
console.log('- Обработчики keydown/keyup для отзывчивости');
console.log('- Векторная математика для корректного движения');
console.log('- Синхронизация с target OrbitControls');
console.log('- Автоматическая очистка обработчиков событий');
console.log('');
console.log('✅ Готово к тестированию!');

53
test-collider-creation.js Normal file
View File

@@ -0,0 +1,53 @@
// Тест улучшенного создания коллайдеров в редакторе
// Файл: test-collider-creation.js
console.log('🎯 Тестирование улучшенного создания коллайдеров');
console.log('');
console.log('✨ Что было исправлено:');
console.log('1. Коллайдеры теперь создаются ПЕРЕД камерой, а не в начале координат');
console.log('2. Добавлена настройка расстояния создания (5-50 единиц)');
console.log('3. Добавлен предварительный просмотр позиции коллайдера');
console.log('4. Предварительный просмотр обновляется в реальном времени');
console.log('');
console.log('🎮 Как использовать:');
console.log('1. Откройте редактор: http://localhost:4000/enhanced-collision-editor');
console.log('2. Настройте расстояние создания коллайдера слайдером');
console.log('3. Включите "Показать предварительный просмотр"');
console.log('4. Перемещайте камеру - предварительный просмотр следует за ней');
console.log('5. Нажмите "Создать коллайдер" - он появится перед камерой');
console.log('');
console.log('🔧 Технические детали:');
console.log('- Используется camera.getWorldDirection() для определения направления');
console.log('- Позиция вычисляется: camera.position + direction * distance');
console.log('- Y координата устанавливается на уровне земли (0) или выше');
console.log('- Предварительный просмотр отображается как wireframe с половинной прозрачностью');
console.log('- Предварительный просмотр автоматически удаляется после создания коллайдера');
console.log('');
console.log('⚙️ Настройки:');
console.log('- Расстояние: 5-50 единиц (по умолчанию 10)');
console.log('- Предварительный просмотр: включен/выключен');
console.log('- Тип коллайдера: коробка/цилиндр/капсула');
console.log('- Цвет и прозрачность применяются к предварительному просмотру');
console.log('');
console.log('🎯 Преимущества:');
console.log('- Точное позиционирование коллайдеров');
console.log('- Визуальная обратная связь перед созданием');
console.log('- Настраиваемое расстояние для разных ситуаций');
console.log('- Интуитивное управление');
console.log('- Экономия времени на позиционировании');
console.log('');
console.log('🚀 Рекомендации:');
console.log('- Используйте предварительный просмотр для точного размещения');
console.log('- Настройте расстояние в зависимости от размера объектов');
console.log('- Перемещайте камеру для оптимального угла обзора');
console.log('- Используйте разные типы коллайдеров для разных объектов');
console.log('');
console.log('✅ Готово к тестированию!');

View File

@@ -0,0 +1,141 @@
// Тест отладки проблемы с удалением коллайдеров
// Файл: test-collider-deletion-debug.js
console.log('🔍 Отладка проблемы с удалением коллайдеров');
console.log('');
console.log('❓ Проблема:');
console.log('Созданные и дублированные коллайдеры не удаляются, а старые удаляются');
console.log('');
console.log('🔧 Добавленная отладка:');
console.log('');
console.log('1. 📊 Отладочная информация в функции удаления:');
console.log(' - Логирование выбранного коллайдера');
console.log(' - Подсчет коллайдеров до и после удаления');
console.log(' - Проверка успешности удаления');
console.log('');
console.log('2. 📊 Отладочная информация в функциях создания:');
console.log(' - Логирование создания нового коллайдера');
console.log(' - Подсчет общего количества коллайдеров');
console.log(' - Проверка добавления в массив');
console.log('');
console.log('3. 📊 Отладочная информация в функции дублирования:');
console.log(' - Логирование дублирования коллайдера');
console.log(' - Подсчет общего количества коллайдеров');
console.log(' - Проверка добавления в массив');
console.log('');
console.log('4. 🎯 Отладочная информация в функции выбора:');
console.log(' - Логирование выбранного коллайдера');
console.log(' - Подсчет коллайдеров в массиве');
console.log(' - Проверка корректности выбора');
console.log('');
console.log('5. 🔍 Функция отладки коллайдеров:');
console.log(' - Кнопка "🔍 Отладка коллайдеров"');
console.log(' - Показывает все коллайдеры в массиве');
console.log(' - Отображает их свойства и userData');
console.log('');
console.log('🧪 Как тестировать:');
console.log('');
console.log('1. 📦 Создание коллайдера:');
console.log(' - Нажмите "Создать коллайдер"');
console.log(' - Проверьте консоль: должно появиться "✅ Создан коллайдер"');
console.log(' - Проверьте счетчик: "📊 Всего коллайдеров: X"');
console.log('');
console.log('2. 🔄 Дублирование коллайдера:');
console.log(' - Выберите коллайдер');
console.log(' - Нажмите "Дублировать коллайдер"');
console.log(' - Проверьте консоль: должно появиться "✅ Коллайдер дублирован"');
console.log(' - Проверьте счетчик: количество должно увеличиться');
console.log('');
console.log('3. 🎯 Выбор коллайдера:');
console.log(' - Кликните по коллайдеру');
console.log(' - Проверьте консоль: должно появиться "🎯 Выбран коллайдер"');
console.log(' - Проверьте счетчик: "📊 Всего коллайдеров в массиве: X"');
console.log('');
console.log('4. 🗑️ Удаление коллайдера:');
console.log(' - Выберите коллайдер');
console.log(' - Нажмите "Удалить коллайдер"');
console.log(' - Проверьте консоль:');
console.log(' * "🗑️ Удаляем коллайдер: [объект]"');
console.log(' * "📊 Всего коллайдеров до удаления: X"');
console.log(' * "📊 Коллайдеров до: X, после: Y"');
console.log(' * "✅ Коллайдер успешно удален"');
console.log('');
console.log('5. 🔍 Отладка коллайдеров:');
console.log(' - Нажмите кнопку "🔍 Отладка коллайдеров"');
console.log(' - Проверьте консоль:');
console.log(' * "🔍 Отладка коллайдеров:"');
console.log(' * "📊 Всего коллайдеров: X"');
console.log(' * "🎯 Выбранный коллайдер: [объект или null]"');
console.log(' * "📦 Коллайдер 0: {mesh, data, position, userData}"');
console.log('');
console.log('🔍 Возможные причины проблемы:');
console.log('');
console.log('1. 🔗 Проблема с ссылками:');
console.log(' - Коллайдер может быть добавлен в сцену, но не в массив');
console.log(' - Или наоборот: в массив, но не в сцену');
console.log(' - Проверьте логи создания/дублирования');
console.log('');
console.log('2. 🎯 Проблема с выбором:');
console.log(' - Raycaster может не находить новые коллайдеры');
console.log(' - Проверьте логи выбора коллайдера');
console.log('');
console.log('3. 🗑️ Проблема с удалением:');
console.log(' - Фильтрация массива может работать неправильно');
console.log(' - Проверьте логи удаления');
console.log('');
console.log('4. 🔄 Проблема с состоянием:');
console.log(' - React состояние может не обновляться');
console.log(' - Проверьте, обновляется ли selected');
console.log('');
console.log('📋 Чек-лист для диагностики:');
console.log('');
console.log('✅ Создание:');
console.log('- [ ] В консоли появляется "✅ Создан коллайдер"');
console.log('- [ ] Счетчик коллайдеров увеличивается');
console.log('- [ ] Коллайдер виден в сцене');
console.log('- [ ] Коллайдер можно выбрать');
console.log('');
console.log('✅ Дублирование:');
console.log('- [ ] В консоли появляется "✅ Коллайдер дублирован"');
console.log('- [ ] Счетчик коллайдеров увеличивается');
console.log('- [ ] Новый коллайдер виден в сцене');
console.log('- [ ] Новый коллайдер можно выбрать');
console.log('');
console.log('✅ Выбор:');
console.log('- [ ] В консоли появляется "🎯 Выбран коллайдер"');
console.log('- [ ] TransformControls активируется');
console.log('- [ ] Параметры обновляются в UI');
console.log('');
console.log('✅ Удаление:');
console.log('- [ ] В консоли появляется "🗑️ Удаляем коллайдер"');
console.log('- [ ] Счетчик коллайдеров уменьшается');
console.log('- [ ] Коллайдер исчезает из сцены');
console.log('- [ ] TransformControls отключается');
console.log('');
console.log('🚀 Готово к тестированию!');
console.log('Откройте: http://localhost:4000/enhanced-collision-editor');
console.log('Откройте консоль браузера для просмотра логов');

97
test-colliders.js Normal file
View File

@@ -0,0 +1,97 @@
// Тестовый скрипт для проверки размеров коллайдеров
// Запустите этот код в консоли браузера для тестирования
console.log('🧪 Тестирование системы коллайдеров');
// Проверяем текущую конфигурацию
console.log('Текущая конфигурация:', window.colliderConfig);
// Тестируем разные коэффициенты
const testMultipliers = [1.0, 2.0, 3.0, 4.0, 5.0];
console.log('📊 Тестируем разные коэффициенты:');
testMultipliers.forEach(multiplier => {
console.log(`\n--- Тест с коэффициентом ${multiplier} ---`);
window.updateColliderSize(multiplier);
// Ждем немного для обновления
setTimeout(() => {
console.log('Размеры коллайдеров обновлены');
window.testCollisions();
}, 100);
});
// Функция для быстрого тестирования
window.quickTest = () => {
console.log('🚀 Быстрый тест коллайдеров и объектов интерьера');
// Включаем режим отладки
window.toggleColliderDebug();
// Тестируем цвета коллайдеров
setTimeout(() => {
console.log('Тестируем синий цвет коллайдеров...');
window.setColliderColor(0, 0, 1); // Синий
}, 1000);
setTimeout(() => {
console.log('Тестируем зеленый цвет коллайдеров...');
window.setColliderColor(0, 1, 0); // Зеленый
}, 2000);
// Тестируем цвета объектов интерьера
setTimeout(() => {
console.log('Тестируем красный цвет объектов интерьера...');
window.setInteriorObjectColor(1, 0, 0); // Красный
}, 3000);
setTimeout(() => {
console.log('Тестируем желтый цвет объектов интерьера...');
window.setInteriorObjectColor(1, 1, 0); // Желтый
}, 4000);
// Тестируем прозрачность
setTimeout(() => {
console.log('Тестируем полупрозрачность объектов интерьера...');
window.setInteriorObjectOpacity(0.5);
}, 5000);
setTimeout(() => {
console.log('Тестируем полную прозрачность объектов интерьера...');
window.setInteriorObjectOpacity(0.1);
}, 6000);
// Тестируем случайные цвета коллайдеров
setTimeout(() => {
console.log('Тестируем случайные цвета коллайдеров...');
window.randomizeColliderColors();
}, 7000);
// Возвращаем стандартные настройки
setTimeout(() => {
console.log('Возвращаем стандартные настройки...');
window.setColliderColor(1, 0, 0); // Красный коллайдер
window.setInteriorObjectColor(1, 1, 1); // Белый объект
window.setInteriorObjectOpacity(1.0); // Полная непрозрачность
}, 8000);
};
console.log('✅ Тестовые функции загружены!');
console.log('Используйте window.quickTest() для быстрого тестирования');
console.log('Используйте window.updateColliderSize(коэффициент) для изменения размера');
console.log('Используйте window.toggleColliderDebug() для включения/выключения визуализации');
console.log('');
console.log('🔧 Функции диагностики:');
console.log('window.debugInteriorObjects() - диагностика объектов интерьера');
console.log('window.setInteriorObjectColor(r,g,b) - цвет объектов интерьера');
console.log('window.setAllObjectsColor(r,g,b) - цвет ВСЕХ объектов в сцене');
console.log('');
console.log('🎯 Целевые функции (только объекты из JSON):');
console.log('window.setColliderObjectsColor(r,g,b) - цвет только объектов из JSON коллайдеров');
console.log('window.applyJsonColorsToObjects() - применить цвета и прозрачность из JSON');
console.log('');
console.log('🎨 Примеры использования:');
console.log('window.applyJsonColorsToObjects() - применить настройки из JSON');
console.log('window.setColliderObjectsColor(1, 0, 0) - красный цвет объектов из JSON');
console.log('window.setColliderObjectsColor(0, 1, 0) - зеленый цвет объектов из JSON');
console.log('window.setAllObjectsColor(0, 0, 1) - синий цвет всех объектов (для сравнения)');

View File

@@ -0,0 +1,129 @@
// Тест исправлений редактора коллизий
// Файл: test-collision-editor-fixes.js
console.log('🔧 Тестирование исправлений редактора коллизий');
console.log('');
console.log('✅ Исправленные проблемы:');
console.log('');
console.log('1. 🔄 Дублирование коллайдера:');
console.log(' ПРОБЛЕМА: У нового коллайдера была видна только рамка, внутри пустота');
console.log(' РЕШЕНИЕ: Исправлено создание материала при дублировании');
console.log(' - Создается новый MeshBasicMaterial с правильными параметрами');
console.log(' - Копируется цвет и прозрачность из оригинального коллайдера');
console.log(' - Создается новый LineBasicMaterial для рамки');
console.log(' - Материал правильно применяется к геометрии');
console.log('');
console.log('2. 📐 Применение параметров трансформации:');
console.log(' ПРОБЛЕМА: При нажатии "Применить параметры" ничего не происходило');
console.log(' РЕШЕНИЕ: Добавлено обновление TransformControls');
console.log(' - Вызывается transformRef.current.updateMatrixWorld()');
console.log(' - TransformControls корректно отображает изменения');
console.log(' - Параметры применяются в реальном времени');
console.log('');
console.log('3. 🎛️ Переключение режимов TransformControls:');
console.log(' НОВАЯ ФУНКЦИЯ: Добавлено переключение между осями');
console.log(' - Кнопки "Перемещение", "Поворот", "Масштаб"');
console.log(' - Визуальная индикация активного режима');
console.log(' - Функция switchTransformMode() для управления');
console.log(' - Автоматическое обновление TransformControls');
console.log('');
console.log('4. 📏 Высота создания коллайдера:');
console.log(' ПРОБЛЕМА: Коллайдер создавался на высоте 0, а не на высоте камеры');
console.log(' РЕШЕНИЕ: Используется высота камеры для создания');
console.log(' - position.y = camera.position.y - 1');
console.log(' - Коллайдер создается на уровне камеры или немного ниже');
console.log(' - Работает корректно в отрицательных координатах');
console.log('');
console.log('🎮 Как использовать исправления:');
console.log('');
console.log('🔄 Дублирование:');
console.log('1. Выберите коллайдер');
console.log('2. Нажмите "Дублировать коллайдер"');
console.log('3. Новый коллайдер появится с правильным материалом');
console.log('4. Цвет и прозрачность будут скопированы');
console.log('');
console.log('📐 Применение параметров:');
console.log('1. Выберите коллайдер');
console.log('2. Измените значения в полях позиции/поворота/масштаба');
console.log('3. Нажмите "Применить параметры"');
console.log('4. Коллайдер обновится и TransformControls покажет изменения');
console.log('');
console.log('🎛️ Режимы трансформации:');
console.log('1. Выберите коллайдер');
console.log('2. Нажмите одну из кнопок: "Перемещение", "Поворот", "Масштаб"');
console.log('3. Активная кнопка подсветится зеленым');
console.log('4. TransformControls переключится в соответствующий режим');
console.log('5. Теперь можно перетаскивать оси для трансформации');
console.log('');
console.log('📏 Создание на высоте камеры:');
console.log('1. Переместите камеру в нужное место');
console.log('2. Нажмите "Создать коллайдер"');
console.log('3. Коллайдер появится перед камерой на её высоте');
console.log('4. Работает даже в отрицательных координатах Y');
console.log('');
console.log('🔧 Технические детали исправлений:');
console.log('');
console.log('🔄 Дублирование материала:');
console.log('- Создается новый MeshBasicMaterial вместо клонирования');
console.log('- Правильно копируется цвет: selected.material.color.clone()');
console.log('- Правильно копируется прозрачность: selected.material.opacity');
console.log('- Создается новый LineBasicMaterial для рамки');
console.log('- Материал применяется к новой геометрии');
console.log('');
console.log('📐 Обновление TransformControls:');
console.log('- Добавлен вызов updateMatrixWorld() после изменения параметров');
console.log('- TransformControls корректно отображает новые значения');
console.log('- Оси обновляются в реальном времени');
console.log('- Поддерживается все три режима трансформации');
console.log('');
console.log('🎛️ Переключение режимов:');
console.log('- Состояние transformMode для отслеживания текущего режима');
console.log('- Функция switchTransformMode() для переключения');
console.log('- Вызов transformRef.current.setMode(mode)');
console.log('- Визуальная индикация активного режима');
console.log('');
console.log('📏 Высота создания:');
console.log('- Используется camera.position.y вместо принудительного 0');
console.log('- position.y = camera.position.y - 1 для небольшого смещения');
console.log('- Работает в любых координатах (положительных и отрицательных)');
console.log('- Коллайдер создается на уровне камеры');
console.log('');
console.log('🎯 Преимущества исправлений:');
console.log('');
console.log('✅ Надежность:');
console.log('- Дублирование работает корректно');
console.log('- Параметры применяются без ошибок');
console.log('- Высота создания предсказуема');
console.log('');
console.log('✅ Удобство:');
console.log('- Визуальное переключение режимов');
console.log('- Интуитивные кнопки управления');
console.log('- Мгновенная обратная связь');
console.log('');
console.log('✅ Функциональность:');
console.log('- Полный контроль над трансформацией');
console.log('- Корректное дублирование объектов');
console.log('- Работа в любых координатах');
console.log('');
console.log('🚀 Все исправления готовы к тестированию!');
console.log('Откройте: http://localhost:4000/enhanced-collision-editor');

405
test-collision.html Normal file
View File

@@ -0,0 +1,405 @@
<!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>

View File

@@ -0,0 +1,135 @@
// Тест исправления дублирования параметров трансформации
// Файл: test-duplicate-transform-fix.js
console.log('🔧 Исправление дублирования параметров трансформации');
console.log('');
console.log('❓ Проблема:');
console.log('При дублировании коллайдера параметры трансформации не передавались новому объекту');
console.log('');
console.log('✅ Исправление:');
console.log('');
console.log('1. 📐 Копирование параметров трансформации:');
console.log(' - Позиция: mesh.position.copy(selected.position)');
console.log(' - Поворот: mesh.rotation.copy(selected.rotation)');
console.log(' - Масштаб: mesh.scale.copy(selected.scale)');
console.log(' - Смещение: +2 по X и Z для избежания наложения');
console.log('');
console.log('2. 🎛️ Обновление UI параметров:');
console.log(' - setColliderPosition() с новыми координатами');
console.log(' - setColliderRotation() с новыми углами');
console.log(' - setColliderScale() с новыми размерами');
console.log(' - UI автоматически отображает параметры нового коллайдера');
console.log('');
console.log('3. 📊 Улучшенная отладка:');
console.log(' - Логирование скопированных параметров трансформации');
console.log(' - Отображение позиции, поворота и масштаба');
console.log(' - Проверка корректности копирования');
console.log('');
console.log('🎮 Как тестировать исправление:');
console.log('');
console.log('1. 📦 Создайте коллайдер:');
console.log(' - Нажмите "Создать коллайдер"');
console.log(' - Коллайдер появится перед камерой');
console.log('');
console.log('2. 🔧 Измените параметры:');
console.log(' - Выберите коллайдер');
console.log(' - Измените позицию, поворот или масштаб');
console.log(' - Нажмите "Применить параметры"');
console.log(' - Коллайдер обновится');
console.log('');
console.log('3. 🔄 Дублируйте коллайдер:');
console.log(' - Убедитесь, что коллайдер выбран');
console.log(' - Нажмите "Дублировать коллайдер"');
console.log(' - Проверьте консоль:');
console.log(' * "✅ Коллайдер дублирован"');
console.log(' * "📐 Параметры трансформации скопированы:"');
console.log(' * Позиция, поворот, масштаб нового коллайдера');
console.log('');
console.log('4. ✅ Проверьте результат:');
console.log(' - Новый коллайдер должен иметь те же параметры');
console.log(' - Плюс смещение на 2 единицы по X и Z');
console.log(' - UI должен показать параметры нового коллайдера');
console.log(' - TransformControls должен быть прикреплен к новому коллайдеру');
console.log('');
console.log('🔍 Технические детали:');
console.log('');
console.log('📐 Копирование трансформации:');
console.log('- mesh.position.copy(selected.position) - копирует позицию');
console.log('- mesh.position.add(new THREE.Vector3(2, 0, 2)) - добавляет смещение');
console.log('- mesh.rotation.copy(selected.rotation) - копирует поворот');
console.log('- mesh.scale.copy(selected.scale) - копирует масштаб');
console.log('');
console.log('🎛️ Обновление UI:');
console.log('- setColliderPosition() - обновляет поля позиции в UI');
console.log('- setColliderRotation() - обновляет поля поворота в UI');
console.log('- setColliderScale() - обновляет поля масштаба в UI');
console.log('- UI автоматически синхронизируется с новым коллайдером');
console.log('');
console.log('📊 Отладочная информация:');
console.log('- Логирование всех скопированных параметров');
console.log('- Отображение точных значений позиции, поворота, масштаба');
console.log('- Проверка корректности копирования');
console.log('');
console.log('🎯 Преимущества исправления:');
console.log('');
console.log('✅ Полнота копирования:');
console.log('- Все параметры трансформации копируются');
console.log('- Новый коллайдер идентичен оригиналу');
console.log('- Только позиция смещается для избежания наложения');
console.log('');
console.log('✅ Удобство использования:');
console.log('- UI показывает параметры нового коллайдера');
console.log('- Можно сразу редактировать параметры');
console.log('- TransformControls готов к работе');
console.log('');
console.log('✅ Предсказуемость:');
console.log('- Поведение дублирования предсказуемо');
console.log('- Все параметры сохраняются');
console.log('- Смещение всегда одинаковое');
console.log('');
console.log('🧪 Тестовые сценарии:');
console.log('');
console.log('1. 🔄 Дублирование с поворотом:');
console.log(' - Создайте коллайдер');
console.log(' - Поверните его (например, на 45°)');
console.log(' - Дублируйте');
console.log(' - Новый коллайдер должен быть повернут на тот же угол');
console.log('');
console.log('2. 🔄 Дублирование с масштабом:');
console.log(' - Создайте коллайдер');
console.log(' - Увеличьте масштаб (например, в 2 раза)');
console.log(' - Дублируйте');
console.log(' - Новый коллайдер должен иметь тот же масштаб');
console.log('');
console.log('3. 🔄 Дублирование с позицией:');
console.log(' - Создайте коллайдер');
console.log(' - Переместите его в другое место');
console.log(' - Дублируйте');
console.log(' - Новый коллайдер должен быть рядом (смещение +2, +2)');
console.log('');
console.log('🚀 Исправление готово к тестированию!');
console.log('Откройте: http://localhost:4000/enhanced-collision-editor');
console.log('Проверьте дублирование коллайдеров с различными параметрами');

297
test-interior-api.html Normal file
View File

@@ -0,0 +1,297 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест API интерьера</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #f0f0f0;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.test-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.result {
background: #f9f9f9;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
white-space: pre-wrap;
font-family: monospace;
font-size: 12px;
}
.error {
background: #ffe6e6;
color: #d00;
}
.success {
background: #e6ffe6;
color: #060;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #0056b3;
}
input {
padding: 8px;
margin: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
</style>
</head>
<body>
<div class="container">
<h1>Тест API интерьера</h1>
<div class="test-section">
<h3>1. Проверка токена</h3>
<button onclick="checkToken()">Проверить токен</button>
<div id="tokenResult" class="result"></div>
</div>
<div class="test-section">
<h3>2. Тест API интерьера</h3>
<input type="number" id="interiorId" placeholder="ID интерьера" value="101">
<button onclick="testInteriorAPI()">Тестировать API</button>
<div id="apiResult" class="result"></div>
</div>
<div class="test-section">
<h3>3. Тест загрузки GLB</h3>
<input type="text" id="glbUrl" placeholder="URL GLB файла">
<button onclick="testGLBLoad()">Тестировать GLB</button>
<div id="glbResult" class="result"></div>
</div>
<div class="test-section">
<h3>4. Полный тест интерьера</h3>
<button onclick="fullInteriorTest()">Полный тест</button>
<div id="fullResult" class="result"></div>
</div>
</div>
<script>
function log(elementId, message, isError = false) {
const element = document.getElementById(elementId);
const timestamp = new Date().toLocaleTimeString();
const className = isError ? 'error' : 'success';
element.innerHTML += `<div class="${className}">[${timestamp}] ${message}</div>`;
}
function clear(elementId) {
document.getElementById(elementId).innerHTML = '';
}
async function checkToken() {
clear('tokenResult');
log('tokenResult', 'Проверяем токен...');
const token = localStorage.getItem('token');
if (!token) {
log('tokenResult', 'Токен не найден! Попробуйте войти в основную игру сначала.', true);
log('tokenResult', 'Или введите токен вручную:', false);
const manualToken = prompt('Введите токен из localStorage основной игры:');
if (manualToken) {
localStorage.setItem('token', manualToken);
log('tokenResult', 'Токен сохранен, повторяем проверку...');
setTimeout(checkToken, 1000);
}
return;
}
log('tokenResult', `Токен найден: ${token.substring(0, 20)}...`);
// Проверяем валидность токена
try {
const response = await fetch('/api/auth/me', {
headers: { Authorization: `Bearer ${token}` },
credentials: 'include'
});
if (response.ok) {
const userData = await response.json();
log('tokenResult', `Токен валиден. Пользователь: ${userData.firstName} ${userData.lastName}`);
} else {
log('tokenResult', `Токен невалиден: ${response.status}`, true);
}
} catch (error) {
log('tokenResult', `Ошибка проверки токена: ${error.message}`, true);
}
}
async function testInteriorAPI() {
clear('apiResult');
const interiorId = document.getElementById('interiorId').value;
log('apiResult', `Тестируем API для интерьера ${interiorId}...`);
const token = localStorage.getItem('token');
if (!token) {
log('apiResult', 'Токен не найден!', true);
return;
}
try {
log('apiResult', 'Запрашиваем определение интерьера...');
const response = await fetch(`/api/interiors/${interiorId}/definition`, {
headers: { Authorization: `Bearer ${token}` },
credentials: 'include',
cache: 'no-cache'
});
log('apiResult', `Ответ сервера: ${response.status} ${response.statusText}`);
if (!response.ok) {
const errorText = await response.text();
log('apiResult', `Ошибка: ${errorText}`, true);
return;
}
const data = await response.json();
log('apiResult', `Данные получены: ${JSON.stringify(data, null, 2)}`);
if (data.glb) {
const glbUrl = window.location.origin + data.glb;
log('apiResult', `GLB URL: ${glbUrl}`);
// Проверяем доступность GLB файла
try {
const headResponse = await fetch(glbUrl, { method: 'HEAD', cache: 'no-cache' });
log('apiResult', `GLB файл доступен: ${headResponse.status}`);
} catch (error) {
log('apiResult', `GLB файл недоступен: ${error.message}`, true);
}
}
} catch (error) {
log('apiResult', `Ошибка API: ${error.message}`, true);
}
}
async function testGLBLoad() {
clear('glbResult');
const glbUrl = document.getElementById('glbUrl').value;
if (!glbUrl) {
log('glbResult', 'URL GLB не указан!', true);
return;
}
log('glbResult', `Тестируем загрузку GLB: ${glbUrl}`);
try {
// Проверяем доступность файла
const headResponse = await fetch(glbUrl, { method: 'HEAD', cache: 'no-cache' });
log('glbResult', `Файл доступен: ${headResponse.status}`);
if (headResponse.ok) {
// Пробуем загрузить как ArrayBuffer
const response = await fetch(glbUrl, { cache: 'no-cache' });
const arrayBuffer = await response.arrayBuffer();
log('glbResult', `GLB загружен: ${arrayBuffer.byteLength} байт`);
}
} catch (error) {
log('glbResult', `Ошибка загрузки GLB: ${error.message}`, true);
}
}
async function fullInteriorTest() {
clear('fullResult');
const interiorId = document.getElementById('interiorId').value;
log('fullResult', `Полный тест интерьера ${interiorId}...`);
// 1. Проверяем токен
const token = localStorage.getItem('token');
if (!token) {
log('fullResult', 'Токен не найден!', true);
return;
}
// 2. Получаем определение интерьера
try {
log('fullResult', 'Шаг 1: Получаем определение интерьера...');
const defResponse = await fetch(`/api/interiors/${interiorId}/definition`, {
headers: { Authorization: `Bearer ${token}` },
credentials: 'include',
cache: 'no-cache'
});
if (!defResponse.ok) {
log('fullResult', `Ошибка получения определения: ${defResponse.status}`, true);
return;
}
const { glb, objects } = await defResponse.json();
log('fullResult', `Определение получено. GLB: ${glb}, Объектов: ${objects ? objects.length : 0}`);
// 3. Проверяем GLB файл
const glbUrl = window.location.origin + glb;
log('fullResult', `Шаг 2: Проверяем GLB файл: ${glbUrl}`);
const headResponse = await fetch(glbUrl, { method: 'HEAD', cache: 'no-cache' });
if (!headResponse.ok) {
log('fullResult', `GLB файл недоступен: ${headResponse.status}`, true);
return;
}
log('fullResult', `GLB файл доступен: ${headResponse.status}`);
// 4. Пробуем загрузить GLB
log('fullResult', 'Шаг 3: Загружаем GLB файл...');
const glbResponse = await fetch(glbUrl, { cache: 'no-cache' });
const arrayBuffer = await glbResponse.arrayBuffer();
log('fullResult', `GLB загружен: ${arrayBuffer.byteLength} байт`);
// 5. Проверяем объекты интерьера
if (objects && objects.length > 0) {
log('fullResult', `Шаг 4: Проверяем объекты интерьера (${objects.length} шт.)...`);
for (let i = 0; i < Math.min(3, objects.length); i++) {
const obj = objects[i];
if (obj.model_url) {
const objUrl = window.location.origin + obj.model_url;
try {
const objResponse = await fetch(objUrl, { method: 'HEAD', cache: 'no-cache' });
log('fullResult', `Объект ${i + 1}: ${objResponse.status} - ${obj.model_url}`);
} catch (error) {
log('fullResult', `Объект ${i + 1}: Ошибка - ${obj.model_url}`, true);
}
}
}
}
log('fullResult', 'Тест завершен успешно!');
} catch (error) {
log('fullResult', `Ошибка теста: ${error.message}`, true);
}
}
// Автоматически проверяем токен при загрузке
window.addEventListener('load', () => {
checkToken();
});
</script>
</body>
</html>