переделана логика загрузки модели, замена страницы Объекты на другой внешний вид, добавление в меню пункта Объекты
This commit is contained in:
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user