2026-01-26 17:21:20 +08:00
|
|
|
import React, { useMemo, useRef } from 'react';
|
|
|
|
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
|
|
|
import * as THREE from 'three';
|
|
|
|
|
|
|
|
|
|
export const LOD_LEVELS = {
|
|
|
|
|
HIGH: 0,
|
|
|
|
|
MEDIUM: 1,
|
2026-02-10 11:04:01 +08:00
|
|
|
LOW: 2,
|
2026-01-26 17:21:20 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const LOD_DISTANCES = {
|
|
|
|
|
HIGH: 5,
|
2026-02-10 11:04:01 +08:00
|
|
|
MEDIUM: 10,
|
2026-01-26 17:21:20 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createSimplifiedDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusColor) => {
|
|
|
|
|
const depth = rackDepth || 1.0;
|
|
|
|
|
const dHeight = device.height || device.u_height || 1;
|
|
|
|
|
const height = dHeight * uHeight;
|
|
|
|
|
const chassisWidth = 0.44;
|
|
|
|
|
const chassisDepth = 0.8;
|
|
|
|
|
const panelWidth = 0.4826;
|
|
|
|
|
const panelDepth = 0.02;
|
|
|
|
|
const frontZ = depth / 2 - 0.02;
|
|
|
|
|
const halfHeight = height / 2;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<group>
|
|
|
|
|
<mesh position={[0, 0, frontZ - panelDepth / 2]}>
|
|
|
|
|
<boxGeometry args={[panelWidth, height - 0.002, panelDepth]} />
|
|
|
|
|
<meshStandardMaterial color={deviceColor} roughness={0.8} metalness={0.1} />
|
|
|
|
|
</mesh>
|
|
|
|
|
<mesh position={[0, 0, frontZ - panelDepth - chassisDepth / 2]}>
|
|
|
|
|
<boxGeometry args={[chassisWidth, height - 0.002, chassisDepth]} />
|
|
|
|
|
<meshStandardMaterial color="#333333" roughness={0.9} metalness={0.3} />
|
|
|
|
|
</mesh>
|
2026-02-10 11:04:01 +08:00
|
|
|
<mesh position={[panelWidth / 2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
2026-01-26 17:21:20 +08:00
|
|
|
<circleGeometry args={[0.006, 16]} />
|
|
|
|
|
<meshBasicMaterial color={statusColor} toneMapped={false} />
|
|
|
|
|
</mesh>
|
|
|
|
|
</group>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const createMediumDeviceMesh = (device, uHeight, rackDepth, deviceColor, statusColor) => {
|
|
|
|
|
const depth = rackDepth || 1.0;
|
|
|
|
|
const dHeight = device.height || device.u_height || 1;
|
|
|
|
|
const height = dHeight * uHeight;
|
|
|
|
|
const chassisWidth = 0.44;
|
|
|
|
|
const chassisDepth = 0.8;
|
|
|
|
|
const panelWidth = 0.4826;
|
|
|
|
|
const panelDepth = 0.02;
|
|
|
|
|
const frontZ = depth / 2 - 0.02;
|
|
|
|
|
const is2U = height > 0.08;
|
|
|
|
|
const halfHeight = height / 2;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<group>
|
|
|
|
|
<mesh position={[0, 0, frontZ + 0.0055]}>
|
|
|
|
|
<boxGeometry args={[0.44, height - 0.004, 0.002]} />
|
|
|
|
|
<meshStandardMaterial color="#1e293b" roughness={0.7} metalness={0.5} />
|
|
|
|
|
</mesh>
|
|
|
|
|
<mesh position={[0, 0, frontZ - panelDepth / 2]}>
|
|
|
|
|
<boxGeometry args={[panelWidth, height - 0.002, panelDepth]} />
|
|
|
|
|
<meshStandardMaterial color={deviceColor} roughness={0.8} metalness={0.1} />
|
|
|
|
|
</mesh>
|
|
|
|
|
<mesh position={[0, 0, frontZ - panelDepth - chassisDepth / 2]}>
|
2026-02-10 11:04:01 +08:00
|
|
|
<boxGeometry args={[chassisWidth, height - 0.002, chassisDepth]} />
|
|
|
|
|
<meshStandardMaterial color="#333333" roughness={0.9} metalness={0.3} />
|
|
|
|
|
</mesh>
|
2026-01-26 17:21:20 +08:00
|
|
|
<group position={[-0.18, 0, frontZ + 0.006]}>
|
|
|
|
|
<mesh position={[-0.02, 0, 0]}>
|
|
|
|
|
<boxGeometry args={[0.04, height - 0.01, 0.002]} />
|
|
|
|
|
<meshStandardMaterial color="#000000" roughness={0.2} />
|
|
|
|
|
</mesh>
|
|
|
|
|
<mesh position={[0, 0.01, 0.002]}>
|
|
|
|
|
<boxGeometry args={[0.012, 0.01, 0.002]} />
|
|
|
|
|
<meshStandardMaterial color="#cbd5e1" />
|
|
|
|
|
</mesh>
|
|
|
|
|
</group>
|
|
|
|
|
<group position={[0.05, 0, frontZ + 0.006]}>
|
|
|
|
|
{Array.from({ length: is2U ? 4 : 2 }).map((_, i) => (
|
2026-01-29 13:09:43 +08:00
|
|
|
<mesh key={`drive-bay-${device.id || 'unknown'}-${i}`} position={[(i - 1) * 0.08, 0, 0]}>
|
2026-01-26 17:21:20 +08:00
|
|
|
<boxGeometry args={[0.06, 0.03, 0.004]} />
|
|
|
|
|
<meshStandardMaterial color="#334155" roughness={0.6} />
|
|
|
|
|
</mesh>
|
|
|
|
|
))}
|
|
|
|
|
</group>
|
2026-02-10 11:04:01 +08:00
|
|
|
<mesh position={[panelWidth / 2 - 0.03, halfHeight - 0.02, frontZ + 0.01]}>
|
2026-01-26 17:21:20 +08:00
|
|
|
<circleGeometry args={[0.006, 16]} />
|
|
|
|
|
<meshBasicMaterial color={statusColor} toneMapped={false} />
|
|
|
|
|
</mesh>
|
|
|
|
|
</group>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-10 11:04:01 +08:00
|
|
|
const LODManager = ({
|
|
|
|
|
device,
|
|
|
|
|
uHeight,
|
|
|
|
|
rackDepth,
|
|
|
|
|
position,
|
|
|
|
|
deviceColor,
|
|
|
|
|
statusColor,
|
2026-01-26 17:21:20 +08:00
|
|
|
children,
|
2026-02-10 11:04:01 +08:00
|
|
|
level = LOD_LEVELS.HIGH,
|
2026-01-26 17:21:20 +08:00
|
|
|
}) => {
|
|
|
|
|
const groupRef = useRef();
|
2026-01-29 13:09:43 +08:00
|
|
|
const highDetailRef = useRef();
|
|
|
|
|
const mediumDetailRef = useRef();
|
|
|
|
|
const lowDetailRef = useRef();
|
2026-01-26 17:21:20 +08:00
|
|
|
const { camera } = useThree();
|
2026-01-29 13:09:43 +08:00
|
|
|
const lodLevelRef = useRef(LOD_LEVELS.HIGH);
|
2026-01-26 17:21:20 +08:00
|
|
|
const distanceRef = useRef(0);
|
2026-04-07 17:05:19 +08:00
|
|
|
const prevDistanceRef = useRef(null);
|
2026-01-29 13:09:43 +08:00
|
|
|
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
|
2026-01-26 17:21:20 +08:00
|
|
|
|
|
|
|
|
useFrame(() => {
|
2026-01-29 13:09:43 +08:00
|
|
|
if (!groupRef.current) return;
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-01-29 13:09:43 +08:00
|
|
|
const distance = camera.position.distanceTo(groupRef.current.position);
|
2026-04-07 17:05:19 +08:00
|
|
|
|
|
|
|
|
// 仅当距离变化超过阈值(10%)时才检查
|
|
|
|
|
if (prevDistanceRef.current !== null) {
|
|
|
|
|
const changeRatio = Math.abs(distance - prevDistanceRef.current) / (prevDistanceRef.current || 1);
|
|
|
|
|
if (changeRatio < 0.1) return;
|
|
|
|
|
}
|
|
|
|
|
prevDistanceRef.current = distance;
|
2026-01-29 13:09:43 +08:00
|
|
|
distanceRef.current = distance;
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-04-07 17:05:19 +08:00
|
|
|
const highThreshold = LOD_DISTANCES.HIGH;
|
|
|
|
|
const mediumThreshold = LOD_DISTANCES.MEDIUM;
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-01-29 13:09:43 +08:00
|
|
|
let newLevel = LOD_LEVELS.HIGH;
|
|
|
|
|
if (distance > mediumThreshold) {
|
|
|
|
|
newLevel = LOD_LEVELS.LOW;
|
|
|
|
|
} else if (distance > highThreshold) {
|
|
|
|
|
newLevel = LOD_LEVELS.MEDIUM;
|
|
|
|
|
}
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-01-29 13:09:43 +08:00
|
|
|
if (newLevel !== lodLevelRef.current) {
|
|
|
|
|
lodLevelRef.current = newLevel;
|
|
|
|
|
if (highDetailRef.current) {
|
|
|
|
|
highDetailRef.current.visible = newLevel === LOD_LEVELS.HIGH;
|
2026-01-26 17:21:20 +08:00
|
|
|
}
|
2026-01-29 13:09:43 +08:00
|
|
|
if (mediumDetailRef.current) {
|
|
|
|
|
mediumDetailRef.current.visible = newLevel === LOD_LEVELS.MEDIUM;
|
|
|
|
|
}
|
|
|
|
|
if (lowDetailRef.current) {
|
|
|
|
|
lowDetailRef.current.visible = newLevel === LOD_LEVELS.LOW;
|
|
|
|
|
}
|
2026-04-07 17:05:19 +08:00
|
|
|
forceUpdate();
|
2026-01-26 17:21:20 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (level !== LOD_LEVELS.HIGH) {
|
|
|
|
|
return children;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<group ref={groupRef} position={position}>
|
2026-01-29 13:09:43 +08:00
|
|
|
{/* 高细节模型 - 默认显示 */}
|
|
|
|
|
<group ref={highDetailRef} visible={true}>
|
|
|
|
|
{children}
|
|
|
|
|
</group>
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-01-29 13:09:43 +08:00
|
|
|
{/* 中等细节模型 */}
|
|
|
|
|
<group ref={mediumDetailRef} visible={false}>
|
|
|
|
|
{createMediumDeviceMesh(device, uHeight, rackDepth, deviceColor, statusColor)}
|
|
|
|
|
</group>
|
2026-02-10 11:04:01 +08:00
|
|
|
|
2026-01-29 13:09:43 +08:00
|
|
|
{/* 低细节模型 */}
|
|
|
|
|
<group ref={lowDetailRef} visible={false}>
|
|
|
|
|
{createSimplifiedDeviceMesh(device, uHeight, rackDepth, deviceColor, statusColor)}
|
|
|
|
|
</group>
|
2026-01-26 17:21:20 +08:00
|
|
|
</group>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default LODManager;
|