优化小车平滑行驶
This commit is contained in:
parent
a5317cb848
commit
01088e3c9d
67
.kiro/specs/vehicle-smooth-movement/requirements.md
Normal file
67
.kiro/specs/vehicle-smooth-movement/requirements.md
Normal file
@ -0,0 +1,67 @@
|
||||
# 车辆平滑移动优化需求文档
|
||||
|
||||
## 介绍
|
||||
|
||||
当前地图上的车辆移动存在"平滑行驶一小段然后停顿再平滑行驶"的问题,需要优化为完全平滑的连续行驶效果。系统每秒接收一次车辆位置数据,但当前的动画实现在数据间隔期间会出现停顿现象,影响用户体验。
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望地图上的车辆能够完全平滑地移动,这样我可以更直观地监控车辆的实时状态。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 系统接收到车辆位置更新数据 THEN 车辆应该平滑地从当前位置移动到新位置
|
||||
2. WHEN 两次位置数据之间存在时间间隔 THEN 车辆应该继续保持平滑移动而不是停顿
|
||||
3. WHEN 车辆速度发生变化 THEN 移动速度应该平滑过渡而不是突然改变
|
||||
|
||||
### 需求 2
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望车辆的移动轨迹能够预测性地延续,这样即使在数据传输延迟时也能保持视觉连续性。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 超过预设时间没有接收到新的位置数据 THEN 系统应该基于历史轨迹预测车辆移动方向
|
||||
2. WHEN 接收到新的位置数据 THEN 预测轨迹应该平滑地调整到实际位置
|
||||
3. WHEN 车辆停止移动 THEN 预测移动应该逐渐减速直至停止
|
||||
|
||||
### 需求 3
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望车辆的旋转角度变化也能平滑过渡,这样车辆转向时看起来更自然。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 车辆heading角度发生变化 THEN 车辆图标应该平滑旋转到新角度
|
||||
2. WHEN 角度变化较大时 THEN 应该选择最短旋转路径进行过渡
|
||||
3. WHEN 车辆移动方向与heading不一致时 THEN 应该优先使用实际移动方向进行旋转
|
||||
|
||||
### 需求 4
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望动画系统能够自适应不同的数据接收频率,这样无论网络状况如何都能保持平滑效果。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 数据接收频率为每秒1次 THEN 动画应该在1秒内平滑完成位置过渡
|
||||
2. WHEN 数据接收出现延迟 THEN 动画应该自动调整插值时间以保持平滑
|
||||
3. WHEN 数据接收频率变化 THEN 动画参数应该动态调整以适应新频率
|
||||
|
||||
### 需求 5
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望车辆标签能够始终跟随车辆图标移动,这样我可以持续看到车辆的实时信息。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 车辆图标移动时 THEN 车辆标签应该同步移动
|
||||
2. WHEN 车辆处于动画移动状态 THEN 标签位置应该实时更新
|
||||
3. WHEN 地图缩放或平移时 THEN 标签应该保持正确的相对位置
|
||||
|
||||
### 需求 6
|
||||
|
||||
**用户故事:** 作为系统操作员,我希望动画系统具有良好的性能表现,这样即使有多辆车同时移动也不会影响系统响应速度。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN 同时有多辆车进行动画移动 THEN 系统帧率应该保持在30fps以上
|
||||
2. WHEN 动画运行时 THEN CPU使用率不应该显著增加
|
||||
3. WHEN 车辆数量增加时 THEN 动画性能应该保持稳定
|
||||
@ -19,6 +19,15 @@ export function exportCarRunInfo(query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 批量查询车辆轨迹
|
||||
export function batchQueryVehicleTrajectory(data) {
|
||||
return request({
|
||||
url: '/system/vehicle_location/trajectory/batch',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车辆ID查询车辆运动信息列表
|
||||
export function listCarRunInfoByVehicleId(vehicleId, query) {
|
||||
return request({
|
||||
@ -28,6 +37,42 @@ export function listCarRunInfoByVehicleId(vehicleId, query) {
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车辆ID查询车辆轨迹
|
||||
export function getVehicleTrajectory(vehicleId, query) {
|
||||
return request({
|
||||
url: '/system/vehicle_location/trajectory/' + vehicleId,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车辆ID查询轨迹统计信息
|
||||
export function getTrajectoryStatistics(vehicleId, query) {
|
||||
return request({
|
||||
url: '/system/vehicle_location/trajectory/' + vehicleId + '/statistics',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车辆ID查询轨迹回放数据
|
||||
export function getTrajectoryPlayback(vehicleId, query) {
|
||||
return request({
|
||||
url: '/system/vehicle_location/trajectory/' + vehicleId + '/playback',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车辆ID查询区域内轨迹
|
||||
export function getAreaTrajectory(vehicleId, query) {
|
||||
return request({
|
||||
url: '/system/vehicle_location/trajectory/' + vehicleId + '/area',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 根据车牌号查询车辆运动信息列表
|
||||
export function listCarRunInfoByLicensePlate(licensePlate, query) {
|
||||
return request({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<!-- TrackPlayback.vue(轨迹回放页面UI还原) -->
|
||||
<!-- TrackPlayback.vue(轨迹回放页面) -->
|
||||
<template>
|
||||
<div class="track-playback-content">
|
||||
<!-- 左侧搜索+列表 -->
|
||||
|
||||
@ -22,21 +22,15 @@ const vehicleMotionHistory = ref({}); // 存储车辆移动历史
|
||||
const PREDICTION_THRESHOLD = 50; // 从100ms降低到50ms,更早开始预测
|
||||
const MAX_PREDICTION_TIME = 15000; // 从10000ms增加到15000ms,延长预测时间
|
||||
|
||||
// 物理模拟参数
|
||||
const ACCELERATION = 0.15; // 加速度系数
|
||||
const DECELERATION = 0.25; // 减速度系数
|
||||
const INERTIA_FACTOR = 0.99; // 从0.985增加到0.99,进一步增强惯性效果,减少抖动
|
||||
const PREDICTION_STRENGTH = 3; // 从4降低到3,进一步减少预测激进性,避免过度预测造成抖动
|
||||
const SPEED_SMOOTHING = 0.98; // 从0.97增加到0.98,进一步增强速度平滑效果
|
||||
const POSITION_SMOOTHING = 0.06; // 从0.08降低到0.06,进一步减小单次位置变化幅度,增强平滑度
|
||||
const MIN_MOVE_THRESHOLD = 0.000005; // 从0.00001降低到0.000005,确保更平滑的微小移动
|
||||
const CONTINUOUS_MOVEMENT = true; // 保持启用连续移动模式
|
||||
const MIN_SPEED = 0.5; // 从1.0降低到0.5,进一步降低最低保持速度,减少抖动
|
||||
const PREDICTION_DECAY_RATE = 0.9995; // 从0.999增加到0.9995,进一步减缓预测衰减
|
||||
const PATH_PREDICTION_ENABLED = true; // 保持启用路径预测
|
||||
const PATH_PREDICTION_POINTS = 15; // 从20降低到15,减少计算量
|
||||
const CONTINUOUS_MOVEMENT_THRESHOLD = 200; // 从300ms降低到200ms,更快触发连续移动,减少停顿感
|
||||
const POSITION_UPDATE_THRESHOLD = 3; // 从5降低到3,进一步减少位置更新阈值
|
||||
// 物理模拟参数 - 优化为完全平滑移动
|
||||
const SPEED_SMOOTHING = 0.3; // 大幅降低速度平滑系数,让速度快速响应
|
||||
const POSITION_SMOOTHING = 1.0; // 设为1.0,每帧直接移动到理想位置,确保完全平滑
|
||||
const MIN_MOVE_THRESHOLD = 0.0000001; // 极小的移动阈值
|
||||
const CONTINUOUS_MOVEMENT = true; // 启用连续移动模式
|
||||
const MIN_SPEED = 1.0; // 适中的最低保持速度
|
||||
const PREDICTION_DECAY_RATE = 0.99; // 更快的预测衰减
|
||||
const CONTINUOUS_MOVEMENT_THRESHOLD = 50; // 大幅降低阈值,几乎立即触发连续移动
|
||||
const INTERPOLATION_STEPS = 60; // 每秒60帧的插值步数
|
||||
|
||||
// 缓动函数 - 平滑的加减速
|
||||
function easeInOutQuad(t) {
|
||||
@ -78,7 +72,7 @@ function startAnimationLoop() {
|
||||
animationFrameId.value = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// 更新车辆动画
|
||||
// 更新车辆动画 - 简化版本,专注于平滑移动
|
||||
function updateVehicleAnimations(deltaTime) {
|
||||
if (!props.vehicleSource || !props.map) return;
|
||||
|
||||
@ -91,176 +85,91 @@ function updateVehicleAnimations(deltaTime) {
|
||||
const animData = vehicleAnimations.value[id];
|
||||
if (!animData) return;
|
||||
|
||||
// 确保车辆总是在移动,即使没有新的位置更新
|
||||
// 确保车辆持续移动
|
||||
ensureContinuousMovement(animData, vehicle, currentTime, deltaTime);
|
||||
|
||||
const timeSinceLastUpdate = currentTime - animData.lastUpdated;
|
||||
|
||||
// 1. 计算目标位置(基于预测)
|
||||
let targetPosition = [...animData.targetPosition];
|
||||
|
||||
// 平滑速度过渡 - 当前速度逐渐接近目标速度
|
||||
// 平滑速度过渡
|
||||
if (animData.currentSpeed === undefined) {
|
||||
animData.currentSpeed = animData.speed;
|
||||
} else {
|
||||
// 使用更平滑的速度过渡
|
||||
const speedSmoothingFactor = vehicle.speedViolation ? 0.98 : SPEED_SMOOTHING;
|
||||
animData.currentSpeed = animData.currentSpeed * speedSmoothingFactor +
|
||||
animData.speed * (1 - speedSmoothingFactor);
|
||||
animData.currentSpeed = animData.currentSpeed * SPEED_SMOOTHING +
|
||||
animData.speed * (1 - SPEED_SMOOTHING);
|
||||
}
|
||||
|
||||
let currentSpeed = animData.currentSpeed;
|
||||
let currentSpeed = Math.max(animData.currentSpeed, MIN_SPEED);
|
||||
|
||||
// 根据时间应用不同程度的预测
|
||||
if (timeSinceLastUpdate > PREDICTION_THRESHOLD && animData.predictionVector) {
|
||||
// 计算预测因子,随时间增长但有上限
|
||||
const predictTime = Math.min(timeSinceLastUpdate, MAX_PREDICTION_TIME);
|
||||
const predictFactor = (predictTime - PREDICTION_THRESHOLD) / 1000 * 0.6; // 从0.8降低到0.6,减少预测激进性
|
||||
|
||||
// 应用预测向量,考虑当前速度
|
||||
const speedFactor = Math.min(1.0, currentSpeed / 30); // 从1.2/25降低到1.0/30,减少高速预测
|
||||
|
||||
// 计算预测位置
|
||||
const predictedPosition = [
|
||||
animData.targetPosition[0] + animData.predictionVector[0] * predictFactor * speedFactor,
|
||||
animData.targetPosition[1] + animData.predictionVector[1] * predictFactor * speedFactor
|
||||
];
|
||||
|
||||
// 平滑过渡到预测位置
|
||||
targetPosition = predictedPosition;
|
||||
|
||||
// 预测时逐渐降低速度,但不要完全停止
|
||||
if (predictTime > 2000) {
|
||||
// 长时间无更新,缓慢降低速度,但保持最低速度
|
||||
const slowdownFactor = Math.max(0.6, 1 - (predictTime - 2000) / 15000); // 从0.5/10000调整为0.6/15000,减缓减速率
|
||||
currentSpeed = Math.max(currentSpeed * slowdownFactor, MIN_SPEED); // 确保最低速度不为0
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 计算当前位置到目标位置的向量
|
||||
const dx = targetPosition[0] - animData.position[0];
|
||||
const dy = targetPosition[1] - animData.position[1];
|
||||
// 计算到目标位置的向量
|
||||
const dx = animData.targetPosition[0] - animData.position[0];
|
||||
const dy = animData.targetPosition[1] - animData.position[1];
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// 忽略过小的位置更新,减少抖动
|
||||
if (distance < MIN_MOVE_THRESHOLD && !CONTINUOUS_MOVEMENT) {
|
||||
return;
|
||||
}
|
||||
// 计算基于速度和时间的理想移动距离
|
||||
const idealDistance = (currentSpeed * 1000 / 3600) * (deltaTime / 1000);
|
||||
|
||||
// 3. 如果距离足够远或启用了连续移动,应用平滑移动
|
||||
if (distance > MIN_MOVE_THRESHOLD || CONTINUOUS_MOVEMENT) {
|
||||
// 计算理想移动距离(基于速度和时间)
|
||||
const idealDistance = (currentSpeed * 1000 / 3600) * (deltaTime / 1000);
|
||||
|
||||
// 根据距离动态调整移动比例
|
||||
let moveRatio;
|
||||
|
||||
// 超速车辆使用更平滑的移动
|
||||
const positionSmoothingFactor = vehicle.speedViolation ? POSITION_SMOOTHING * 0.7 : POSITION_SMOOTHING;
|
||||
|
||||
if (distance < idealDistance * 1.2) { // 从1.5降低到1.2,更早开始减速
|
||||
// 接近目标时减速(使用更强的缓动)
|
||||
moveRatio = Math.min(positionSmoothingFactor * 1.2, idealDistance / distance); // 从1.5降低到1.2
|
||||
moveRatio = easeOutQuint(moveRatio);
|
||||
// 始终移动,确保完全平滑 - 最终优化版本
|
||||
let moveDistance;
|
||||
|
||||
// 简化移动逻辑,确保始终平滑移动
|
||||
if (distance > 0.0001) {
|
||||
// 有明确目标时,计算合适的移动距离
|
||||
if (distance > idealDistance) {
|
||||
// 距离较远,按理想速度移动
|
||||
moveDistance = idealDistance;
|
||||
} else {
|
||||
// 正常行驶时使用标准缓动
|
||||
moveRatio = Math.min(positionSmoothingFactor, idealDistance / distance);
|
||||
moveRatio = easeOutCubic(moveRatio);
|
||||
// 距离较近,使用更激进的移动策略确保快速到达
|
||||
// 在16ms内(约60fps)移动更多距离,避免停顿
|
||||
moveDistance = Math.max(distance * 0.5, idealDistance * 0.3);
|
||||
}
|
||||
} else {
|
||||
// 已到达目标或距离极小,使用预测向量保持移动
|
||||
moveDistance = idealDistance * 0.8;
|
||||
}
|
||||
|
||||
// 计算移动方向和更新位置
|
||||
if (distance > 0.0001) {
|
||||
const moveRatio = Math.min(moveDistance / distance, 1.0);
|
||||
|
||||
// 直接更新位置
|
||||
animData.position[0] += dx * moveRatio;
|
||||
animData.position[1] += dy * moveRatio;
|
||||
} else if (animData.predictionVector) {
|
||||
// 使用预测向量继续移动
|
||||
const predLen = Math.sqrt(
|
||||
animData.predictionVector[0] * animData.predictionVector[0] +
|
||||
animData.predictionVector[1] * animData.predictionVector[1]
|
||||
);
|
||||
|
||||
if (predLen > 0) {
|
||||
animData.position[0] += (animData.predictionVector[0] / predLen) * moveDistance;
|
||||
animData.position[1] += (animData.predictionVector[1] / predLen) * moveDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新地图上的要素位置
|
||||
const feature = props.vehicleSource.getFeatureById(id);
|
||||
if (feature) {
|
||||
feature.getGeometry().setCoordinates(animData.position);
|
||||
|
||||
// 更新车辆对象中的位置信息
|
||||
if (props.vehicles[id]) {
|
||||
props.vehicles[id].position = animData.position;
|
||||
}
|
||||
|
||||
// 修复逻辑:确保最小移动和惯性移动是互斥的
|
||||
// 如果距离很小且有预测向量,使用预测向量进行微小移动
|
||||
if (distance < MIN_MOVE_THRESHOLD && animData.predictionVector) {
|
||||
// 使用预测向量作为移动方向,但使用很小的移动量
|
||||
const minMoveAmount = (currentSpeed * 1000 / 3600) * (deltaTime / 1000) * 0.05; // 从0.1降低到0.05,减小微小移动幅度
|
||||
// 更新车辆样式
|
||||
if (props.getVehicleStyle) {
|
||||
const currentHeading = vehicle ? vehicle.heading : 0;
|
||||
|
||||
// 归一化预测向量
|
||||
const predLen = Math.sqrt(
|
||||
animData.predictionVector[0] * animData.predictionVector[0] +
|
||||
animData.predictionVector[1] * animData.predictionVector[1]
|
||||
);
|
||||
|
||||
if (predLen > 0) {
|
||||
const normPredX = animData.predictionVector[0] / predLen;
|
||||
const normPredY = animData.predictionVector[1] / predLen;
|
||||
|
||||
// 应用最小移动量
|
||||
animData.position[0] += normPredX * minMoveAmount;
|
||||
animData.position[1] += normPredY * minMoveAmount;
|
||||
|
||||
// 保存本次移动向量用于下一帧惯性计算
|
||||
animData.lastDx = normPredX * minMoveAmount;
|
||||
animData.lastDy = normPredY * minMoveAmount;
|
||||
}
|
||||
}
|
||||
// 距离足够大时,应用惯性移动
|
||||
else {
|
||||
if (animData.lastDx !== undefined && animData.lastDy !== undefined) {
|
||||
// 超速车辆使用更强的惯性
|
||||
const inertiaFactor = vehicle.speedViolation ? INERTIA_FACTOR * 1.05 : INERTIA_FACTOR; // 从1.1降低到1.05,减少超速车辆惯性过强
|
||||
|
||||
const inertiaX = animData.lastDx * inertiaFactor;
|
||||
const inertiaY = animData.lastDy * inertiaFactor;
|
||||
|
||||
// 混合新方向和惯性方向
|
||||
const newDx = dx * moveRatio;
|
||||
const newDy = dy * moveRatio;
|
||||
|
||||
// 根据速度和距离调整惯性影响
|
||||
const inertiaWeight = Math.min(0.75, currentSpeed / 80) * // 从0.85/70降低到0.75/80,减少惯性权重
|
||||
Math.min(0.9, distance / 10); // 从1.0/8调整为0.9/10,减少短距离惯性
|
||||
|
||||
// 计算新位置(混合惯性和目标方向)
|
||||
const nextX = animData.position[0] + newDx * (1 - inertiaWeight) + inertiaX * inertiaWeight;
|
||||
const nextY = animData.position[1] + newDy * (1 - inertiaWeight) + inertiaY * inertiaWeight;
|
||||
|
||||
// 应用位置更新
|
||||
animData.position[0] = nextX;
|
||||
animData.position[1] = nextY;
|
||||
|
||||
// 保存本次移动向量用于下一帧惯性计算
|
||||
animData.lastDx = nextX - animData.position[0];
|
||||
animData.lastDy = nextY - animData.position[1];
|
||||
} else {
|
||||
// 首次移动,没有惯性
|
||||
animData.position[0] += dx * moveRatio;
|
||||
animData.position[1] += dy * moveRatio;
|
||||
|
||||
// 初始化移动向量
|
||||
animData.lastDx = dx * moveRatio;
|
||||
animData.lastDy = dy * moveRatio;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新地图上的要素位置
|
||||
const feature = props.vehicleSource.getFeatureById(id);
|
||||
if (feature) {
|
||||
feature.getGeometry().setCoordinates(animData.position);
|
||||
|
||||
// 更新车辆对象中的位置信息,确保标签能够正确跟随
|
||||
if (props.vehicles[id]) {
|
||||
props.vehicles[id].position = animData.position;
|
||||
}
|
||||
|
||||
// 更新车辆样式,传递正确的heading值
|
||||
if (feature && props.getVehicleStyle) {
|
||||
// 获取车辆的当前heading值
|
||||
const vehicle = props.vehicles[id];
|
||||
const currentHeading = vehicle ? vehicle.heading : 0;
|
||||
|
||||
// 只在heading发生变化时才更新样式,避免频繁更新导致闪烁
|
||||
if (!animData.lastHeading || Math.abs(animData.lastHeading - currentHeading) > 1) {
|
||||
feature.setStyle(props.getVehicleStyle(id, currentSpeed, currentHeading));
|
||||
animData.lastHeading = currentHeading;
|
||||
}
|
||||
if (!animData.lastHeading || Math.abs(animData.lastHeading - currentHeading) > 1) {
|
||||
feature.setStyle(props.getVehicleStyle(id, currentSpeed, currentHeading));
|
||||
animData.lastHeading = currentHeading;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前速度用于下一帧
|
||||
// 保存当前速度
|
||||
animData.currentSpeed = currentSpeed;
|
||||
|
||||
// 更新历史轨迹
|
||||
// 简化的历史轨迹更新
|
||||
updateVehiclePathHistory(id, animData, currentTime);
|
||||
});
|
||||
}
|
||||
@ -282,8 +191,8 @@ function updateVehiclePathHistory(id, animData, currentTime) {
|
||||
speed: animData.currentSpeed
|
||||
});
|
||||
|
||||
// 增加历史轨迹长度,提供更多数据点用于预测
|
||||
if (animData.pathHistory.length > PATH_PREDICTION_POINTS + 4) {
|
||||
// 保持最多10个历史点
|
||||
if (animData.pathHistory.length > 10) {
|
||||
animData.pathHistory.shift();
|
||||
}
|
||||
|
||||
@ -291,92 +200,46 @@ function updateVehiclePathHistory(id, animData, currentTime) {
|
||||
}
|
||||
}
|
||||
|
||||
// 确保车辆持续移动,即使没有新的位置更新
|
||||
// 确保车辆持续移动,即使没有新的位置更新 - 简化版本
|
||||
function ensureContinuousMovement(animData, vehicle, currentTime, deltaTime) {
|
||||
// 检查上次更新时间,使用更低的阈值
|
||||
const timeSinceLastUpdate = currentTime - animData.lastUpdated;
|
||||
|
||||
// 如果超过阈值没有位置更新,且车辆当前速度大于0
|
||||
// 如果超过阈值没有位置更新,创建简单的预测移动
|
||||
if (timeSinceLastUpdate > CONTINUOUS_MOVEMENT_THRESHOLD && animData.currentSpeed > 0) {
|
||||
// 如果启用了路径预测,使用历史轨迹预测未来路径
|
||||
if (PATH_PREDICTION_ENABLED && animData.pathHistory && animData.pathHistory.length >= 3) {
|
||||
const predictedPath = predictFuturePath(animData);
|
||||
if (predictedPath) {
|
||||
// 平滑过渡到预测向量,而不是直接替换
|
||||
if (!animData.predictionVector) {
|
||||
animData.predictionVector = predictedPath.vector;
|
||||
} else {
|
||||
// 平滑混合当前预测向量和新预测向量,使用更平滑的过渡
|
||||
animData.predictionVector = [
|
||||
animData.predictionVector[0] * 0.8 + predictedPath.vector[0] * 0.2, // 从0.7/0.3调整为0.8/0.2
|
||||
animData.predictionVector[1] * 0.8 + predictedPath.vector[1] * 0.2
|
||||
];
|
||||
}
|
||||
|
||||
// 平滑过渡到预测位置,使用更平滑的过渡
|
||||
animData.targetPosition = [
|
||||
animData.targetPosition[0] * 0.8 + predictedPath.position[0] * 0.2, // 从0.7/0.3调整为0.8/0.2
|
||||
animData.targetPosition[1] * 0.8 + predictedPath.position[1] * 0.2
|
||||
];
|
||||
}
|
||||
}
|
||||
// 如果没有预测向量或预测向量太小,创建一个基于历史数据的预测向量
|
||||
else if (!animData.predictionVector ||
|
||||
(Math.abs(animData.predictionVector[0]) < 0.001 && Math.abs(animData.predictionVector[1]) < 0.001)) {
|
||||
|
||||
// 使用最近两个历史点计算预测向量
|
||||
// 简单的预测:基于最后的移动方向继续移动
|
||||
if (!animData.predictionVector) {
|
||||
// 如果没有预测向量,使用历史轨迹的最后方向
|
||||
if (animData.pathHistory && animData.pathHistory.length >= 2) {
|
||||
const latestPoints = animData.pathHistory.slice(-2);
|
||||
const dx = latestPoints[1].position[0] - latestPoints[0].position[0];
|
||||
const dy = latestPoints[1].position[1] - latestPoints[0].position[1];
|
||||
const len = Math.sqrt(dx*dx + dy*dy);
|
||||
const latest = animData.pathHistory[animData.pathHistory.length - 1];
|
||||
const previous = animData.pathHistory[animData.pathHistory.length - 2];
|
||||
|
||||
if (len > 0.001) {
|
||||
const normalizedDx = dx / len;
|
||||
const normalizedDy = dy / len;
|
||||
const predX = normalizedDx * animData.currentSpeed * 0.18;
|
||||
const predY = normalizedDy * animData.currentSpeed * 0.18;
|
||||
animData.predictionVector = [predX, predY];
|
||||
|
||||
// 更新目标位置为当前位置加上预测向量
|
||||
animData.targetPosition = [
|
||||
animData.position[0] + predX * 15,
|
||||
animData.position[1] + predY * 15
|
||||
];
|
||||
const dx = latest.position[0] - previous.position[0];
|
||||
const dy = latest.position[1] - previous.position[1];
|
||||
const len = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (len > 0) {
|
||||
// 创建单位方向向量
|
||||
animData.predictionVector = [dx / len, dy / len];
|
||||
} else {
|
||||
// 如果历史点距离太近,使用默认的前进向量
|
||||
animData.predictionVector = [0, animData.currentSpeed * 0.18];
|
||||
animData.targetPosition = [
|
||||
animData.position[0],
|
||||
animData.position[1] + animData.currentSpeed * 0.18 * 15
|
||||
];
|
||||
// 默认向前移动
|
||||
animData.predictionVector = [0, 1];
|
||||
}
|
||||
} else {
|
||||
// 没有足够的历史数据,使用默认的前进向量
|
||||
animData.predictionVector = [0, animData.currentSpeed * 0.18];
|
||||
animData.targetPosition = [
|
||||
animData.position[0],
|
||||
animData.position[1] + animData.currentSpeed * 0.18 * 15
|
||||
];
|
||||
// 默认向前移动
|
||||
animData.predictionVector = [0, 1];
|
||||
}
|
||||
} else {
|
||||
// 预测向量存在但需要缓慢衰减,避免突然停止
|
||||
// 使用更高的衰减率
|
||||
animData.predictionVector = [
|
||||
animData.predictionVector[0] * PREDICTION_DECAY_RATE,
|
||||
animData.predictionVector[1] * PREDICTION_DECAY_RATE
|
||||
];
|
||||
|
||||
// 更新目标位置
|
||||
animData.targetPosition = [
|
||||
animData.position[0] + animData.predictionVector[0] * 15, // 从12增加到15
|
||||
animData.position[1] + animData.predictionVector[1] * 15
|
||||
];
|
||||
}
|
||||
|
||||
// 缓慢降低速度,但保持更高的最低速度
|
||||
if (!animData.lastSpeedReduction || currentTime - animData.lastSpeedReduction > 1000) {
|
||||
animData.speed = Math.max(animData.speed * 0.98, MIN_SPEED); // 从0.97增加到0.98,进一步减缓速度降低率
|
||||
// 基于预测向量更新目标位置
|
||||
const moveDistance = (animData.currentSpeed * 1000 / 3600) * 1.0; // 1秒的移动距离
|
||||
animData.targetPosition = [
|
||||
animData.position[0] + animData.predictionVector[0] * moveDistance,
|
||||
animData.position[1] + animData.predictionVector[1] * moveDistance
|
||||
];
|
||||
|
||||
// 缓慢降低速度,但保持最低速度
|
||||
if (!animData.lastSpeedReduction || currentTime - animData.lastSpeedReduction > 2000) {
|
||||
animData.speed = Math.max(animData.speed * 0.95, MIN_SPEED);
|
||||
animData.lastSpeedReduction = currentTime;
|
||||
}
|
||||
}
|
||||
@ -481,124 +344,48 @@ function initVehicleAnimation(id, coordinates, heading, speed) {
|
||||
vehicleMotionHistory.value[id] = [];
|
||||
}
|
||||
|
||||
// 更新车辆动画目标
|
||||
// 更新车辆动画目标 - 简化版本,专注于平滑过渡
|
||||
function updateVehicleAnimationTarget(id, coordinates, heading, speed) {
|
||||
const animData = vehicleAnimations.value[id] || {};
|
||||
const now = Date.now();
|
||||
|
||||
// 记录历史轨迹点
|
||||
vehicleMotionHistory.value[id] = vehicleMotionHistory.value[id] || [];
|
||||
vehicleMotionHistory.value[id].push({
|
||||
time: now,
|
||||
position: coordinates,
|
||||
heading: heading, // 保存正确的heading值
|
||||
speed: speed
|
||||
});
|
||||
|
||||
// 增加历史点数量
|
||||
if (vehicleMotionHistory.value[id].length > 10) { // 从8增加到10
|
||||
vehicleMotionHistory.value[id].shift();
|
||||
}
|
||||
|
||||
// 计算预测向量 - 使用更复杂的算法
|
||||
// 简化的预测向量计算
|
||||
let predictionVector = [0, 0];
|
||||
|
||||
if (vehicleMotionHistory.value[id].length >= 2) {
|
||||
// 基本预测 - 使用最近两点
|
||||
const history = vehicleMotionHistory.value[id];
|
||||
const latest = history[history.length - 1];
|
||||
const previous = history[history.length - 2];
|
||||
// 如果有当前位置,计算移动方向
|
||||
if (animData.position) {
|
||||
const dx = coordinates[0] - animData.position[0];
|
||||
const dy = coordinates[1] - animData.position[1];
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
const dt = Math.max(1, latest.time - previous.time);
|
||||
const dx = (latest.position[0] - previous.position[0]);
|
||||
const dy = (latest.position[1] - previous.position[1]);
|
||||
|
||||
// 基础预测向量 - 考虑时间间隔
|
||||
const baseSpeed = Math.sqrt(dx*dx + dy*dy) * (1000 / dt); // 单位时间内的移动距离
|
||||
const normalizedDx = baseSpeed > 0 ? dx / baseSpeed : 0;
|
||||
const normalizedDy = baseSpeed > 0 ? dy / baseSpeed : 0;
|
||||
|
||||
// 减小预测强度
|
||||
predictionVector = [
|
||||
normalizedDx * (PREDICTION_STRENGTH * 0.9) * baseSpeed,
|
||||
normalizedDy * (PREDICTION_STRENGTH * 0.9) * baseSpeed
|
||||
];
|
||||
|
||||
// 如果有更多历史点,使用加权平均提高预测准确性
|
||||
if (vehicleMotionHistory.value[id].length >= 3) {
|
||||
// 使用多个历史点计算平均移动趋势
|
||||
let totalWeight = 0;
|
||||
let weightedDx = 0;
|
||||
let weightedDy = 0;
|
||||
|
||||
// 从最近到最远遍历历史点,使用更多的历史点
|
||||
for (let i = history.length - 2; i >= 0 && i >= history.length - 7; i--) { // 从5增加到7
|
||||
const curr = history[i+1];
|
||||
const prev = history[i];
|
||||
|
||||
const pointDt = Math.max(1, curr.time - prev.time);
|
||||
const pointDx = curr.position[0] - prev.position[0];
|
||||
const pointDy = curr.position[1] - prev.position[1];
|
||||
|
||||
// 使用指数衰减权重
|
||||
const weight = Math.pow(0.85, (history.length - i - 1));
|
||||
totalWeight += weight;
|
||||
|
||||
weightedDx += pointDx * weight;
|
||||
weightedDy += pointDy * weight;
|
||||
}
|
||||
|
||||
if (totalWeight > 0) {
|
||||
// 归一化加权向量
|
||||
weightedDx /= totalWeight;
|
||||
weightedDy /= totalWeight;
|
||||
|
||||
// 混合最新向量和加权历史向量,增加历史向量的权重
|
||||
predictionVector = [
|
||||
predictionVector[0] * 0.6 + weightedDx * PREDICTION_STRENGTH * 0.4, // 从0.7/0.3调整为0.6/0.4
|
||||
predictionVector[1] * 0.6 + weightedDy * PREDICTION_STRENGTH * 0.4
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 根据速度调整预测强度,使用更平滑的曲线
|
||||
const speedFactor = Math.min(1.8, Math.pow(speed / 15, 0.8)); // 使用幂函数使曲线更平滑
|
||||
predictionVector = [
|
||||
predictionVector[0] * speedFactor,
|
||||
predictionVector[1] * speedFactor
|
||||
];
|
||||
|
||||
// 如果车辆几乎静止,减弱预测但保持最低预测强度
|
||||
if (speed < 2) {
|
||||
predictionVector = [
|
||||
predictionVector[0] * 0.4, // 从0.3增加到0.4
|
||||
predictionVector[1] * 0.4
|
||||
];
|
||||
if (distance > 0.0001) {
|
||||
// 创建单位方向向量
|
||||
predictionVector = [dx / distance, dy / distance];
|
||||
}
|
||||
}
|
||||
|
||||
// 保存速度历史用于平滑过渡,增加历史点
|
||||
// 保存速度历史用于平滑过渡
|
||||
if (!animData.speedHistory) {
|
||||
animData.speedHistory = [speed, speed, speed, speed]; // 增加一个点
|
||||
animData.speedHistory = [speed, speed, speed];
|
||||
} else {
|
||||
animData.speedHistory.push(speed);
|
||||
if (animData.speedHistory.length > 4) { // 从3增加到4
|
||||
if (animData.speedHistory.length > 3) {
|
||||
animData.speedHistory.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算平滑速度(避免速度突变)
|
||||
// 计算平滑速度
|
||||
const smoothedSpeed = animData.speedHistory.reduce((sum, s) => sum + s, 0) /
|
||||
animData.speedHistory.length;
|
||||
|
||||
// 更新动画数据 - 保留当前位置,只更新目标位置
|
||||
// 更新动画数据
|
||||
vehicleAnimations.value[id] = {
|
||||
...animData,
|
||||
targetPosition: coordinates,
|
||||
targetHeading: heading, // 保存目标heading值
|
||||
heading: animData.heading || heading, // 保留当前heading,如果不存在则使用新的heading
|
||||
targetHeading: heading,
|
||||
heading: animData.heading || heading,
|
||||
position: animData.position || coordinates,
|
||||
speed: smoothedSpeed, // 使用平滑后的速度
|
||||
speed: smoothedSpeed,
|
||||
lastUpdated: now,
|
||||
predictionVector,
|
||||
speedHistory: animData.speedHistory
|
||||
|
||||
Loading…
Reference in New Issue
Block a user