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

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

View File

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

View File

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