переделана логика загрузки модели, замена страницы Объекты на другой внешний вид, добавление в меню пункта Объекты

This commit is contained in:
2026-02-03 19:00:02 +03:00
parent 458222817e
commit 5e58f6ef76
24 changed files with 3514 additions and 1161 deletions

View File

@@ -20,6 +20,7 @@ import {
PointerEventTypes,
PointerInfo,
Matrix,
Ray,
} from '@babylonjs/core'
import '@babylonjs/loaders'
@@ -55,6 +56,8 @@ export interface ModelViewerProps {
onSensorPick?: (sensorId: string | null) => void
highlightAllSensors?: boolean
sensorStatusMap?: Record<string, string>
showStats?: boolean
onToggleStats?: () => void
}
const ModelViewer: React.FC<ModelViewerProps> = ({
@@ -68,6 +71,8 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
onSensorPick,
highlightAllSensors,
sensorStatusMap,
showStats = false,
onToggleStats,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const engineRef = useRef<Nullable<Engine>>(null)
@@ -340,7 +345,8 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
let engine: Engine
try {
engine = new Engine(canvas, true, { stencil: true })
// Оптимизация: используем FXAA вместо MSAA для снижения нагрузки на GPU
engine = new Engine(canvas, false, { stencil: true }) // false = отключаем MSAA
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
const message = `WebGL недоступен: ${errorMessage}`
@@ -362,6 +368,9 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
sceneRef.current = scene
scene.clearColor = new Color4(0.1, 0.1, 0.15, 1)
// Оптимизация: включаем FXAA (более легковесное сглаживание)
scene.imageProcessingConfiguration.fxaaEnabled = true
const camera = new ArcRotateCamera('camera', 0, Math.PI / 3, 20, Vector3.Zero(), scene)
camera.attachControl(canvas, true)
@@ -689,22 +698,26 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
const maxDimension = Math.max(size.x, size.y, size.z)
const targetRadius = Math.max(camera.lowerRadiusLimit ?? 2, maxDimension * 1.5)
// Вычисляем оптимальные углы камеры для видимости датчика
// Позиционируем камеру спереди датчика с небольшим наклоном сверху
const directionToCamera = camera.position.subtract(center).normalize()
// Простое позиционирование камеры - всегда поворачиваемся к датчику
console.log('[ModelViewer] Calculating camera direction to sensor')
// Вычисляем целевые углы alpha и beta
// Вычисляем направление от текущей позиции камеры к датчику
const directionToSensor = center.subtract(camera.position).normalize()
// Преобразуем в сферические координаты
// alpha - горизонтальный угол (вокруг оси Y)
// beta - вертикальный угол (наклон)
let targetAlpha = Math.atan2(directionToCamera.x, directionToCamera.z)
let targetBeta = Math.acos(directionToCamera.y)
let targetAlpha = Math.atan2(directionToSensor.x, directionToSensor.z)
// Если датчик за стеной, позиционируем камеру спереди
// Используем направление от центра сцены к датчику
const sceneCenter = Vector3.Zero()
const directionFromSceneCenter = center.subtract(sceneCenter).normalize()
targetAlpha = Math.atan2(directionFromSceneCenter.x, directionFromSceneCenter.z) + Math.PI
targetBeta = Math.PI / 3 // 60 градусов - смотрим немного сверху
// beta - вертикальный угол (от вертикали)
// Используем оптимальный угол 60° для обзора
let targetBeta = Math.PI / 3 // 60°
console.log('[ModelViewer] Calculated camera direction:', {
alpha: (targetAlpha * 180 / Math.PI).toFixed(1) + '°',
beta: (targetBeta * 180 / Math.PI).toFixed(1) + '°',
sensorPosition: { x: center.x.toFixed(2), y: center.y.toFixed(2), z: center.z.toFixed(2) },
cameraPosition: { x: camera.position.x.toFixed(2), y: camera.position.y.toFixed(2), z: camera.position.z.toFixed(2) }
})
// Нормализуем alpha в диапазон [-PI, PI]
while (targetAlpha > Math.PI) targetAlpha -= 2 * Math.PI
@@ -715,6 +728,25 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
scene.stopAnimation(camera)
// Логирование перед анимацией
console.log('[ModelViewer] Starting camera animation:', {
sensorId,
from: {
target: { x: camera.target.x.toFixed(2), y: camera.target.y.toFixed(2), z: camera.target.z.toFixed(2) },
radius: camera.radius.toFixed(2),
alpha: (camera.alpha * 180 / Math.PI).toFixed(1) + '°',
beta: (camera.beta * 180 / Math.PI).toFixed(1) + '°'
},
to: {
target: { x: center.x.toFixed(2), y: center.y.toFixed(2), z: center.z.toFixed(2) },
radius: targetRadius.toFixed(2),
alpha: (targetAlpha * 180 / Math.PI).toFixed(1) + '°',
beta: (targetBeta * 180 / Math.PI).toFixed(1) + '°'
},
alphaChange: ((targetAlpha - camera.alpha) * 180 / Math.PI).toFixed(1) + '°',
betaChange: ((targetBeta - camera.beta) * 180 / Math.PI).toFixed(1) + '°'
})
const ease = new CubicEase()
ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
const frameRate = 60
@@ -891,7 +923,49 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
panActive={panActive}
onToggleSensorHighlights={useNavigationStore.getState().toggleSensorHighlights}
sensorHighlightsActive={useNavigationStore.getState().showSensorHighlights}
onToggleStats={onToggleStats}
statsActive={showStats}
/>
{/* Блок статистики датчиков */}
{showStats && sensorStatusMap && (
<div className="absolute top-24 left-8 z-[60] pointer-events-none">
<div className="bg-[#161824] rounded-[15px] border border-white/10 shadow-[0_8px_32px_rgba(0,0,0,0.3)] py-3 px-4">
<div className="flex flex-col gap-2 text-gray-300">
{/* Всего датчиков */}
<div className="flex items-center justify-between gap-4">
<span className="text-xs">Всего:</span>
<span className="text-sm font-semibold">{Object.keys(sensorStatusMap).length}</span>
</div>
<div className="h-px bg-white/10"></div>
{/* Нормальный */}
<div className="flex items-center justify-between gap-4">
<span className="text-xs">Норма:</span>
<span className="text-sm font-semibold">
{Object.values(sensorStatusMap).filter(s => s === '#4ade80' || s.toLowerCase() === 'normal').length}
</span>
</div>
{/* Предупреждение */}
<div className="flex items-center justify-between gap-4">
<span className="text-xs">Предупр.:</span>
<span className="text-sm font-semibold">
{Object.values(sensorStatusMap).filter(s => s === '#fb923c' || s.toLowerCase() === 'warning').length}
</span>
</div>
{/* Критический */}
<div className="flex items-center justify-between gap-4">
<span className="text-xs">Критич.:</span>
<span className="text-sm font-semibold">
{Object.values(sensorStatusMap).filter(s => s === '#ef4444' || s.toLowerCase() === 'critical').length}
</span>
</div>
</div>
</div>
</div>
)}
</>
)}
{/* UPDATED: Interactive overlay circles with hover effects */}