|
|
@@ -16,6 +16,9 @@ import { renderMarker } from './markerAssets.js'
|
|
|
* 色值真源仍是 tokens.css;本表只在读不到计算样式时兜底(脱浏览器回归、样式未挂载)。
|
|
|
* 刻意不与 mapService 共享常量:那两个文件带有其它在制品改动,本票不触碰它们。
|
|
|
*/
|
|
|
+/** 设备图标相对设备主体的世界高度偏移(不受 group 缩放影响)。 */
|
|
|
+const ICON_OFFSET_ABOVE_DEVICE = 0.62
|
|
|
+
|
|
|
const TONE_FALLBACK = Object.freeze({
|
|
|
lv1: '#ff3b52',
|
|
|
lv2: '#ffb020',
|
|
|
@@ -226,7 +229,10 @@ function buildNeighborhood(plan) {
|
|
|
const icon = new THREE.Sprite(new THREE.SpriteMaterial({ transparent: true, depthTest: false, depthWrite: false, sizeAttenuation: true }))
|
|
|
icon.name = 'device-icon'
|
|
|
icon.center.set(0.5, 0)
|
|
|
- icon.position.set(0, 0.72, 0)
|
|
|
+ // 图标挂在设备正上方一个**稳定的世界距离**,必须除掉 group.scale:
|
|
|
+ // 否则 group 的 3.5~7 倍缩放会把它顶到设备上方 3~5 个世界单位处
|
|
|
+ // (实测 NDC y 达 1.63~1.80,整个图标落在画面上沿之外,拉多远都看不全)。
|
|
|
+ icon.position.set(0, ICON_OFFSET_ABOVE_DEVICE / deviceScale, 0)
|
|
|
icon.renderOrder = 30
|
|
|
icon.userData.device = device
|
|
|
icon.visible = false
|
|
|
@@ -312,8 +318,12 @@ export function mountPipeEnhancement(host, plan, { rendererFactory = defaultRend
|
|
|
* 与「到期精确到位」,而不是只能验证终态。
|
|
|
*/
|
|
|
const FLIGHT_MS = 620
|
|
|
- /** 定位终点距离倍率:1.3 太贴脸,1.8 留出邻域上下文(维护者 2026-10-23 反馈)。 */
|
|
|
- const FOCUS_DISTANCE_FACTOR = 1.8
|
|
|
+ /** 定位构图:图标顶端不超过这个 NDC 高度(1=画面上沿),留出上方空间。 */
|
|
|
+ const FOCUS_ICON_TOP_NDC = 0.4
|
|
|
+ /** 设备底端不越过这个 NDC 高度(负值=画面下沿),保证设备完整。 */
|
|
|
+ const FOCUS_BODY_BOTTOM_NDC = 0.9
|
|
|
+ /** 解析距离的安全系数:再多留一点上下文,避免刚好贴边。 */
|
|
|
+ const FOCUS_SAFETY = 1.18
|
|
|
const nowImpl = typeof now === 'function'
|
|
|
? now
|
|
|
: () => (typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now())
|
|
|
@@ -557,11 +567,29 @@ export function mountPipeEnhancement(host, plan, { rendererFactory = defaultRend
|
|
|
// 图标按屏幕像素恒定尺寸缩放,飞行途中会变大变小,用整体盒算中心会让镜头「飘」。
|
|
|
const anchor = device.getObjectByName('device-body') || device
|
|
|
const bounds = new THREE.Box3().setFromObject(anchor).getBoundingSphere(new THREE.Sphere())
|
|
|
- // 终点距离:把设备包围球刚好塞满视野再往后退一截(维护者 2026-10-23:
|
|
|
- // 「最终移动到的终点离设备有点太近了,可以稍微拉远一点点」)。
|
|
|
- // 1.3 会把设备顶到画面边缘,改为 1.8(约 +38%)留出周围管网上下文。
|
|
|
- const fitDistance = bounds.radius / Math.sin(THREE.MathUtils.degToRad(camera.fov / 2))
|
|
|
- const toDistance = Math.max(0.8, fitDistance * FOCUS_DISTANCE_FACTOR)
|
|
|
+ /**
|
|
|
+ * 终点距离按「**设备与图标都要完整可见**」解析求出,不再用固定倍率猜。
|
|
|
+ *
|
|
|
+ * 维护者 2026-10-23:「还需要拉远一点,需要能展示设备及点位图标的全貌。」
|
|
|
+ * 图标是**屏幕恒定像素**的(约 33px 高),头顶在设备上方 `ICON_OFFSET_ABOVE_DEVICE`;
|
|
|
+ * 只按设备包围球算距离会让图标顶出画面上沿(实测 NDC y 曾达 1.63~1.80)。
|
|
|
+ *
|
|
|
+ * 设 tanHalf=tan(fov/2)、H=视口高、iconPx=图标屏显高、o=图标偏移、r=设备半径:
|
|
|
+ * 图标顶端 NDC ≈ (o + 2·d·tanHalf·iconPx/H) / (d·tanHalf) ≤ ICON_TOP_NDC
|
|
|
+ * → d ≥ o / (tanHalf · (ICON_TOP_NDC − 2·iconPx/H))
|
|
|
+ * 设备底端 NDC ≈ r / (d·tanHalf) ≤ BODY_BOTTOM_NDC → d ≥ r / (BODY_BOTTOM_NDC · tanHalf)
|
|
|
+ * 取两者较大值再乘安全系数。这样任意视口尺寸都能适配,不是写死的倍数。
|
|
|
+ */
|
|
|
+ const tanHalf = Math.tan(THREE.MathUtils.degToRad(camera.fov / 2))
|
|
|
+ const viewHeight = host.clientHeight || 220
|
|
|
+ const iconNode = device.getObjectByName('device-icon')
|
|
|
+ const iconWidthPx = iconNode?.userData?.iconWidth || 26.7
|
|
|
+ const iconAspect = iconNode?.userData?.iconAspect || 1.2566
|
|
|
+ const iconPx = iconWidthPx * iconAspect
|
|
|
+ const iconSpan = 2 * iconPx / viewHeight
|
|
|
+ const distanceForIcon = ICON_OFFSET_ABOVE_DEVICE / Math.max(0.01, tanHalf * (FOCUS_ICON_TOP_NDC - iconSpan))
|
|
|
+ const distanceForBody = bounds.radius / (FOCUS_BODY_BOTTOM_NDC * tanHalf)
|
|
|
+ const toDistance = Math.max(0.8, Math.max(distanceForIcon, distanceForBody) * FOCUS_SAFETY)
|
|
|
// 平滑飞行定位(不再瞬移);返回 true 表示已受理,飞行在后续帧推进。
|
|
|
startFlight(bounds.center, toDistance)
|
|
|
return true
|