/** * GdPortalArch — рендер 3D-арок поверх портал-примитивов (gd_ship, gd_cube, …). * * Работа: * 1. attach(scene, scene3d) пробегает по primitiveManager.instances. * 2. Для каждого primitive с typeDef.kind === 'gd_portal': * - Читает выбор юзера: gd_portal_choices[gdMode] (например 'ship_v3'). * - Если выбора нет — берёт дефолт `${gdMode}_v1`. * - Спавнит арку из PORTAL_CATALOG в той же позиции. * - Скрывает оригинальный mesh (visible=false), но НЕ удаляет — GdLevelManager * читает позицию из primitiveManager и обрабатывает коллизию. * * Загрузка выбора: * GET /api/v1/storys/kubikon3d/savegame/295//gd_portal_choices * data = { cube: 'cube_v5', ship: 'ship_v5', ... } * * Дефолты (если нет выбора): * все группы = `${gdMode}_v1` (классический). */ import { Vector3, TransformNode } from '@babylonjs/core'; import { PORTAL_CATALOG } from '../../admin-preview/gdPortals/portalFactories'; import { STORYS_addres as STORYS_BASE } from '../../api/API'; // project_id выбора порталов = 295 (sandbox-проект, где юзер выбирал) const PORTAL_CHOICES_PID = 295; const PORTAL_CHOICES_NS = 'gd_portal_choices'; const DEFAULT_PORTAL_BY_MODE = { cube: 'cube_v1', ship: 'ship_v1', ball: 'ball_v1', ufo: 'ufo_v1', wave: 'wave_v1', robot: 'robot_v1', }; export class GdPortalArch { constructor() { this.scene = null; this._scene3d = null; this._instances = []; // [{ portalPrimId, handle }] this._choices = null; } /** * Привязать к сцене. * userId — для загрузки выбора порталов из savegame (project_id=295, ns=gd_portal_choices). * Если userId не передан или загрузка не удалась — используются дефолты `${mode}_v1`. */ async attach(scene, scene3d, userId = null) { if (!scene || !scene3d) return; this.scene = scene; this._scene3d = scene3d; this._choices = {}; if (userId) { try { const url = `${STORYS_BASE}/kubikon3d/savegame/${PORTAL_CHOICES_PID}/${userId}/${PORTAL_CHOICES_NS}`; const r = await fetch(url, { headers: { Authorization: localStorage.getItem('Authorization') || '' }, }); if (r.ok) { const j = await r.json(); this._choices = j?.data || {}; } } catch (e) { console.warn('[GdPortalArch] не загрузил выбор:', e); } } // Откладываем — примитивы могут быть не созданы в момент enterPlayMode let attempts = 0; const tryRender = () => { attempts++; const pm = scene3d.primitiveManager; if (!pm || !pm.instances) { if (attempts < 10) setTimeout(tryRender, 200); return; } this._renderAll(); }; tryRender(); } _renderAll() { const pm = this._scene3d.primitiveManager; if (!pm || !pm.instances) return; let rendered = 0; for (const data of pm.instances.values()) { // type у primitive — строка вроде 'gd_ship', 'gd_cube'. // У этих типов в PrimitiveTypes.js kind = 'gd_portal' и есть gdMode. // Импортировать getPrimitiveType из engine — там есть. // Но проще проверить по type-строке: const portalType = data.type || ''; const m = portalType.match(/^gd_(\w+)$/); if (!m) continue; const gdMode = m[1]; // 'cube', 'ship', … if (!DEFAULT_PORTAL_BY_MODE[gdMode]) continue; // не порталовый const chosenId = (this._choices && this._choices[gdMode]) || DEFAULT_PORTAL_BY_MODE[gdMode]; const factory = PORTAL_CATALOG.find(p => p.id === chosenId); if (!factory) { console.warn('[GdPortalArch] не найдена фабрика:', chosenId); continue; } try { const handle = factory.make(this.scene, `portal_inst_${data.id}`); if (!handle || !handle.root) continue; // Низ колонн на y=0; ставим на y=1 (поверх пола), как GdStartArch handle.root.position = new Vector3(data.x, 1, data.z); // Поворот по Y 90° — фабрики строят арку проёмом вдоль X (колонны на ±X). // Игрок бежит по +X — арка должна стоять поперёк, колонны на ±Z, проём вдоль X. handle.root.rotation.y = Math.PI / 2; // Скрыть оригинальный primitive-меш (GdLevelManager всё ещё видит data.x для коллизии) if (data.mesh) { data.mesh.setEnabled(false); } this._instances.push({ portalPrimId: data.id, handle }); rendered++; } catch (e) { console.warn('[GdPortalArch] ошибка при создании арки:', chosenId, e); } } console.log(`[GdPortalArch] отрисовано ${rendered} порталов`); } dispose() { for (const inst of this._instances) { try { inst.handle && inst.handle.dispose && inst.handle.dispose(); } catch (e) {} } this._instances = []; this.scene = null; this._scene3d = null; } }