/** * GdDecoPreview — превью декораций ландшафта (10 эпох × 10 = 100). * Маршрут: /admin-preview/gd-deco * Multi-select: до 5 моделей на эпоху. Выбор → kubikon3d_savegame * (project_id=295, namespace='gd_deco_choices'). data = { 1: ['d1_v1','d1_v3',...] }. */ import React, { useEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../auth/AuthContext.jsx'; import { jwtDecode } from 'jwt-decode'; import axios from 'axios'; import { STORYS_addres } from '../api/API'; import { Engine } from '@babylonjs/core/Engines/engine'; import { Scene } from '@babylonjs/core/scene'; import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; import { HemisphericLight } from '@babylonjs/core/Lights/hemisphericLight'; import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight'; import { Vector3, Color3, Color4 } from '@babylonjs/core/Maths/math'; import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder'; import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial'; import { DECO_CATALOG, EPOCH_INFO, getDecoByEpoch } from './gdDeco/decoFactories'; const CHOICES_PID = 295; const CHOICES_NS = 'gd_deco_choices'; const MAX_PER_EPOCH = 5; function getUserId() { try { const t = localStorage.getItem('Authorization'); if (!t) return 0; return Number(jwtDecode(t).id) || 0; } catch (e) { return 0; } } const api = axios.create({ baseURL: STORYS_addres, timeout: 15000 }); api.interceptors.request.use((cfg) => { try { const token = localStorage.getItem('Authorization'); if (token) cfg.headers.Authorization = token; } catch (e) {} return cfg; }); function DecoCard({ deco, isChosen, onToggle, disabled }) { const wrapRef = useRef(null); const canvasRef = useRef(null); const [isVisible, setIsVisible] = useState(false); useEffect(() => { if (!wrapRef.current) return; const io = new IntersectionObserver((entries) => { for (const e of entries) setIsVisible(e.isIntersecting); }, { rootMargin: '200px', threshold: 0.01 }); io.observe(wrapRef.current); return () => io.disconnect(); }, []); useEffect(() => { if (!isVisible || !canvasRef.current) return; let engine = null, scene = null; try { engine = new Engine(canvasRef.current, true, { stencil: false, antialias: true }); scene = new Scene(engine); scene.clearColor = new Color4(0.05, 0.07, 0.12, 1); const camera = new ArcRotateCamera('cam', -Math.PI / 2.5, Math.PI / 2.7, 5, new Vector3(0, 1.2, 0), scene); camera.attachControl(canvasRef.current, false); camera.minZ = 0.1; camera.maxZ = 50; new HemisphericLight('hemi', new Vector3(0, 1, 0), scene).intensity = 0.7; const sun = new DirectionalLight('sun', new Vector3(-0.5, -1, -0.3), scene); sun.intensity = 0.8; const floor = MeshBuilder.CreateGround('floor', { width: 4, height: 4 }, scene); const fmat = new StandardMaterial('fmat', scene); fmat.diffuseColor = new Color3(0.18, 0.22, 0.20); floor.material = fmat; const handle = deco.make(scene, `prev_${deco.id}`); if (handle && handle.root) handle.root.position.y = 0; scene.onBeforeRenderObservable.add(() => { if (handle && handle.root) handle.root.rotation.y += 0.008; }); engine.runRenderLoop(() => scene.render()); return () => { try { engine && engine.stopRenderLoop(); } catch (e) {} try { handle && handle.dispose && handle.dispose(); } catch (e) {} try { scene && scene.dispose(); } catch (e) {} try { engine && engine.dispose(); } catch (e) {} }; } catch (e) { console.warn('[DecoCard]', e); return () => {}; } }, [isVisible, deco]); return (
{deco.id}