修改了动画逻辑 在动画结束的时候 删除掉新增的动画实体

This commit is contained in:
huangchenhao 2025-11-29 19:17:05 +08:00
parent 539f1b16db
commit dd74af0930
3 changed files with 265 additions and 132 deletions

View File

@ -302,8 +302,15 @@ export function useEntityAnimation() {
* @returns {Cesium.Entity}
*/
const createMovingEntity = (viewer, pathCoordinates, config, startTime, stopTime) => {
try {
const positionProperty = new Cesium.SampledPositionProperty()
const numberOfPoints = pathCoordinates.length
if (numberOfPoints === 0) {
console.warn('[useEntityAnimation] 路径坐标为空')
return null
}
const timeInterval = config.duration / (numberOfPoints - 1)
pathCoordinates.forEach((coord, index) => {
@ -324,9 +331,8 @@ export function useEntityAnimation() {
}, false)
const icon = config.type === 'device' ? deviceIcon : soldierIcon
const trailColor = config.type === 'device' ? Cesium.Color.ORANGE : Cesium.Color.CYAN
return viewer.entities.add({
const entity = viewer.entities.add({
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({ start: startTime, stop: stopTime })
]),
@ -342,17 +348,6 @@ export function useEntityAnimation() {
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scaleByDistance: new Cesium.NearFarScalar(1000, 1.5, 50000, 0.8)
},
// path: {
// resolution: 1,
// material: new Cesium.PolylineGlowMaterialProperty({
// glowPower: 0.4,
// taperPower: 0.5,
// color: trailColor
// }),
// width: 8,
// leadTime: 0,
// trailTime: config.duration
// },
properties: {
type: config.type === 'device' ? 'animatedDevice' : 'animatedSoldier',
name: config.name,
@ -360,7 +355,16 @@ export function useEntityAnimation() {
isAnimating: true
}
})
console.log(`[useEntityAnimation] 创建${config.type === 'device' ? '设备' : '人员'}实体: ${config.name}`)
return entity
} catch (error) {
console.error('[useEntityAnimation] 创建移动实体失败:', error)
return null
}
}
/**
* 启动多组移动动画设备组 + 人员组2
@ -373,6 +377,9 @@ export function useEntityAnimation() {
return []
}
// 先停止所有现有动画
stopAllMovements(viewer)
const config = {
duration: options.duration ?? 60
}
@ -382,17 +389,15 @@ export function useEntityAnimation() {
const startTime = Cesium.JulianDate.now()
const stopTime = Cesium.JulianDate.addSeconds(startTime, config.duration, new Cesium.JulianDate())
// 配置 viewer 时钟
viewer.clock.startTime = startTime.clone()
viewer.clock.stopTime = stopTime.clone()
viewer.clock.currentTime = startTime.clone()
viewer.clock.clockRange = Cesium.ClockRange.CLAMPED // 动画到达终点后停止,不循环
viewer.clock.clockRange = Cesium.ClockRange.CLAMPED
viewer.clock.multiplier = 1
viewer.clock.shouldAnimate = true
// 清除之前的动画实体
animatedEntities.value.forEach(entity => {
if (entity) viewer.entities.remove(entity)
})
// 确保实体数组是空的
animatedEntities.value = []
// 创建设备组移动实体
@ -400,31 +405,47 @@ export function useEntityAnimation() {
duration: config.duration,
type: 'device',
name: '应急设备车',
department: '应急装备队'
department: '应急装备队',
disablePulse: options.disablePulse
}, startTime, stopTime)
if (deviceEntity) {
animatedEntities.value.push(deviceEntity)
}
// 创建人员组2移动实体
const personnel2Entity = createMovingEntity(viewer, PERSONNEL_PATH_COORDINATES_2, {
duration: config.duration,
type: 'soldier',
name: '应急救援队员',
department: '应急救援队'
department: '应急救援队',
disablePulse: options.disablePulse
}, startTime, stopTime)
if (personnel2Entity) {
animatedEntities.value.push(personnel2Entity)
}
isAnimating.value = true
// 设置超时清理
const timeoutId = setTimeout(() => {
console.log('[useEntityAnimation] 多组动画超时,强制清理')
stopAllMovements(viewer)
}, (config.duration + 5) * 1000) // 动画时长+5秒缓冲
const removeListener = viewer.clock.onStop.addEventListener(() => {
console.log('[useEntityAnimation] 多组动画已结束')
clearTimeout(timeoutId)
isAnimating.value = false
removeListener()
})
console.log('[useEntityAnimation] 已启动 2 组额外移动动画')
console.log('[useEntityAnimation] 已启动 2 组移动动画')
return animatedEntities.value
}
/**
* 停止所有移动动画
* @param {Cesium.Viewer} viewer
@ -432,24 +453,63 @@ export function useEntityAnimation() {
const stopAllMovements = (viewer) => {
if (!viewer) return
console.log('[useEntityAnimation] 停止所有移动动画')
// 停止时钟动画
viewer.clock.shouldAnimate = false
// 取消相机跟随
if (viewer.trackedEntity) {
viewer.trackedEntity = undefined
}
// 停止所有脉冲效果
stopAllPulses(viewer)
// 移除单个动画实体
if (animatedEntity.value) {
try {
viewer.entities.remove(animatedEntity.value)
} catch (e) {
console.warn('[useEntityAnimation] 移除单个实体时出错:', e)
}
animatedEntity.value = null
}
// 移除所有动画实体
animatedEntities.value.forEach(entity => {
if (entity) viewer.entities.remove(entity)
if (entity) {
try {
viewer.entities.remove(entity)
} catch (e) {
console.warn('[useEntityAnimation] 移除实体时出错:', e)
}
}
})
animatedEntities.value = []
isAnimating.value = false
console.log('[useEntityAnimation] 已停止所有移动动画')
// 额外清理:移除所有类型为 animatedSoldier 和 animatedDevice 的实体
const entitiesToRemove = []
viewer.entities.values.forEach(entity => {
const entityType = entity.properties?.type?.getValue?.()
if (entityType === 'animatedSoldier' || entityType === 'animatedDevice') {
entitiesToRemove.push(entity)
}
})
entitiesToRemove.forEach(entity => {
try {
viewer.entities.remove(entity)
console.log('[useEntityAnimation] 清理残留实体:', entity.properties?.name?.getValue?.() || '未知')
} catch (e) {
console.warn('[useEntityAnimation] 清理残留实体时出错:', e)
}
})
isAnimating.value = false
console.log('[useEntityAnimation] 已停止所有移动动画,并清理残留实体')
}
/**
* 根据动态路线创建动画实体
@ -633,15 +693,26 @@ export function useEntityAnimation() {
// 监听时钟,检测动画完成
const clockListener = viewer.clock.onTick.addEventListener((clock) => {
checkAnimationsCompletion(viewer, clock.currentTime, onEntityComplete)
checkAnimationsCompletion(viewer, clock.currentTime, (routeId, entity) => {
// 调用原有的路线清除回调
if (options.onEntityComplete) {
options.onEntityComplete(routeId)
}
// 注释掉自动停止时钟的逻辑,让时钟继续运行
// 原因:时钟停止会导致 Cesium 停止更新实体位置,导致前面完成的实体消失
// if (areAllAnimationsCompleted()) {
// console.log('[useEntityAnimation] 所有动画已完成,停止时钟')
// viewer.clock.shouldAnimate = false
// clockListener() // 移除监听器
// }
// 延迟移除实体
setTimeout(() => {
if (viewer && !viewer.isDestroyed() && entity) {
viewer.entities.remove(entity)
console.log(`[useEntityAnimation] 动画完成,已移除实体`)
// 从动画实体列表中移除
const index = animatedEntities.value.indexOf(entity)
if (index > -1) {
animatedEntities.value.splice(index, 1)
}
}
}, 1000) // 延迟1秒移除
})
})
// 监听动画结束
@ -652,16 +723,68 @@ export function useEntityAnimation() {
clockListener() // 移除 tick 监听器
})
// 新增60秒超时强制清除所有实体
const timeoutId = setTimeout(() => {
console.log('[useEntityAnimation] 60秒超时强制清除所有动画实体')
forceCleanupAllAnimations(viewer, options.onEntityComplete)
}, 60000) // 60秒超时
console.log(`[useEntityAnimation] 成功启动 ${animatedEntities.value.length} 个动画实体`)
return animatedEntities.value
}
/**
* 检查动画是否完成如果完成则停止脉冲并清除路线
* 强制清理所有动画实体和路线
* @param {Cesium.Viewer} viewer
* @param {Function} onEntityComplete - 路线清除回调
*/
const forceCleanupAllAnimations = (viewer, onEntityComplete) => {
if (!viewer) return
// 停止时钟
viewer.clock.shouldAnimate = false
// 停止所有脉冲
stopAllPulses(viewer)
// 移除所有实体并触发回调
animatedEntities.value.forEach(entity => {
if (!entity || !entity.properties) return
const routeId = entity.properties.routeId?.getValue()
// 触发路线清除回调
if (onEntityComplete && typeof onEntityComplete === 'function' && routeId) {
console.log(`[useEntityAnimation] 超时清除路线: ${routeId}`)
onEntityComplete(routeId)
}
// 移除实体
if (viewer && !viewer.isDestroyed()) {
viewer.entities.remove(entity)
}
})
// 清空实体列表
animatedEntities.value = []
// 清除单个动画实体
if (animatedEntity.value) {
viewer.entities.remove(animatedEntity.value)
animatedEntity.value = null
}
isAnimating.value = false
console.log('[useEntityAnimation] 强制清理完成')
}
/**
* 检查动画是否完成如果完成则停止脉冲清除路线并移除实体
* @param {Cesium.Viewer} viewer
* @param {Cesium.JulianDate} currentTime
* @param {Function} onEntityComplete - 完成回调 (routeId) => void
* @param {Function} onEntityComplete - 完成回调 (routeId, entity) => void
*/
const checkAnimationsCompletion = (viewer, currentTime, onEntityComplete) => {
animatedEntities.value.forEach(entity => {
@ -698,17 +821,25 @@ export function useEntityAnimation() {
// 2. 标记为已完成(使用 ConstantProperty
entity.properties.isCompleted = new Cesium.ConstantProperty(true)
// 3. 触发回调清除对应路线
// 3. 触发回调清除对应路线,并传递实体信息以便移除
if (onEntityComplete && typeof onEntityComplete === 'function') {
console.log(`[useEntityAnimation] 触发 onEntityComplete 回调routeId: ${routeId}`)
onEntityComplete(routeId)
onEntityComplete(routeId, entity) // 传递实体信息
} else {
console.warn(`[useEntityAnimation] onEntityComplete 回调不存在或不是函数`)
// 如果没有回调,直接移除实体
setTimeout(() => {
if (viewer && !viewer.isDestroyed()) {
viewer.entities.remove(entity)
console.log(`[useEntityAnimation] 已自动移除实体: ${entityName}`)
}
}, 1000) // 延迟1秒移除确保动画完全结束
}
}
})
}
/**
* 停止所有实体的脉冲效果
* @param {Cesium.Viewer} viewer
@ -762,7 +893,9 @@ export function useEntityAnimation() {
startMultipleAnimationsWithRoutes,
// 新增:脉冲控制方法
checkAnimationsCompletion,
stopAllPulses
stopAllPulses,
// 新增:强制清理方法
forceCleanupAllAnimations
}
}

View File

@ -32,45 +32,45 @@ export function useSimulatedMarkers() {
const newMarkers = []
// 1. Create 2 center markers (static, no animation)
const centerPersonnelCoords = applyRandomOffset(disasterCenter, 10, 30)
const centerPersonnel = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(centerPersonnelCoords.lon, centerPersonnelCoords.lat, 0),
billboard: {
image: soldierIcon,
width: 36,
height: 40,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
properties: new Cesium.PropertyBag({
type: 'centerPersonnel',
name: '应急人员',
isSimulated: true,
isStatic: true
})
})
newMarkers.push(centerPersonnel)
// const centerPersonnelCoords = applyRandomOffset(disasterCenter, 10, 30)
// const centerPersonnel = viewer.entities.add({
// position: Cesium.Cartesian3.fromDegrees(centerPersonnelCoords.lon, centerPersonnelCoords.lat, 0),
// billboard: {
// image: soldierIcon,
// width: 36,
// height: 40,
// verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
// disableDepthTestDistance: Number.POSITIVE_INFINITY
// },
// properties: new Cesium.PropertyBag({
// type: 'centerPersonnel',
// name: '应急人员',
// isSimulated: true,
// isStatic: true
// })
// })
// newMarkers.push(centerPersonnel)
const centerEquipmentCoords = applyRandomOffset(disasterCenter, 10, 30)
const centerEquipment = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(centerEquipmentCoords.lon, centerEquipmentCoords.lat, 0),
billboard: {
image: deviceIcon,
width: 36,
height: 40,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
properties: new Cesium.PropertyBag({
type: 'centerEquipment',
name: '应急装备',
isSimulated: true,
isStatic: true
})
})
newMarkers.push(centerEquipment)
// const centerEquipmentCoords = applyRandomOffset(disasterCenter, 10, 30)
// const centerEquipment = viewer.entities.add({
// position: Cesium.Cartesian3.fromDegrees(centerEquipmentCoords.lon, centerEquipmentCoords.lat, 0),
// billboard: {
// image: deviceIcon,
// width: 36,
// height: 40,
// verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
// disableDepthTestDistance: Number.POSITIVE_INFINITY
// },
// properties: new Cesium.PropertyBag({
// type: 'centerEquipment',
// name: '应急装备',
// isSimulated: true,
// isStatic: true
// })
// })
// newMarkers.push(centerEquipment)
// 2. Create 4 emergency point markers (will be animated later)
emergencyPoints.slice(0, 2).forEach((point, index) => {

View File

@ -41,7 +41,7 @@
v-if="isCompareMode"
class="situational-awareness__sync-btn"
@click="toggleCameraSync"
:text="isCameraSyncEnabled ? '同步' : '不同步'"
:text="isCameraSyncEnabled ? '同步灾前灾后实景' : '未同步灾前灾后实景'"
>
</ActionButton>
<!-- 灾前现场实景标签 - 在中间分割线左侧 -->
@ -1824,10 +1824,10 @@ provide("triggerJump", (duration = 5, height = 30) => {
.situational-awareness__sync-btn {
position: absolute;
left: 40%;
top: 133px;
left: 32%;
top: calc(var(--sa-header-height));
height: 31px;
transform: translateX(calc(-100% - vw(10)));
pointer-events: auto; //
}
}