cesium-path-animation
v0.1.3
Published
适配 Cesium 1.107.2 到 1.128.0 的模型路径动画与相机跟随控制器
Maintainers
Readme
cesium-path-animation
基于 Cesium 1.107.2 到 1.128.0 的模型路径动画与相机控制器。暴露单个类 CesiumPathAnimation,提供 3D 模型加载、路径动画、三种相机跟随模式、粒子管理、模型初始化管理、模型辉光、循环播放等能力。demo 默认使用 demo/demo.gltf,并在海口附近创建两个首尾相连的巡检对象,方便观察多对象同时工作状态。
安装
npm install cesium-path-animation
# cesium 为 peerDependency,需宿主项目自行安装;Node 16 推荐使用 1.107.2
npm install [email protected]快速开始
import { CesiumPathAnimation, CAMERA_MODE, PATH_MODE } from 'cesium-path-animation'
const viewer = new Cesium.Viewer('cesiumContainer')
const anim = new CesiumPathAnimation({
viewer,
model: {
url: '/model/plane.glb',
scale: 20,
brightness: 1,
orientation: { heading: 0, pitch: 0, roll: 0 }
},
path: {
name: '演示路径',
mode: PATH_MODE.LINEAR,
speed: 120,
points: [
{ lon: 110.303, lat: 20.03, height: 1000 },
{ lon: 110.354, lat: 20.035, height: 1200 },
{ lon: 110.401, lat: 20.04, height: 1500 }
]
},
speed: 2,
camera: {
mode: CAMERA_MODE.THIRD_PERSON,
distance: 5000,
angle: { heading: 0, pitch: Cesium.Math.toRadians(-26) },
autoOrbit: true
},
particle: {
enabled: true,
color: '#c7ffbb',
imageSize: 12,
particleLife: 2,
backOffset: 19,
sideOffset: 0
},
glow: { enabled: true, color: '#00ff66', sizeMeters: 180 },
loop: true,
trajectoryVisible: true,
autoplay: true
})
// 后续仍可随时动态调整
anim.setSpeed(3)
anim.setCameraMode(CAMERA_MODE.FIXED)
anim.setLoop(false)构造参数
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| viewer | Cesium.Viewer | 必填,已创建的 Cesium Viewer 实例 |
| console | boolean \| object | 可选,是否显示共享控制台,也可传 { label, id, enabled } |
| model | string \| object | 可选,初始模型;字符串等同模型 URL,对象支持 { url, scale, brightness, orientation, ...ModelGraphicsOptions } |
| path | Array \| object | 可选,初始路径;支持点数组或 { name, mode, speed, closedLoop, points } |
| speed | number | 可选,初始播放倍率 |
| camera | object | 可选,初始相机配置;支持 { mode, distance, angle, autoOrbit, autoOrbitSpeed, fixedTarget } |
| followTarget | Entity \| Model \| object \| function | 可选,相机跟随目标;不传时默认跟随插件内部模型,支持外部 Entity、Cesium.Model、ModelInstanceCollection 指定实例或自定义位置函数 |
| particle | boolean \| object | 可选,初始粒子配置;对象支持 { enabled, color, imageSize, particleLife, backOffset, sideOffset } |
| glow | boolean \| string \| object | 可选,初始辉光配置;字符串等同辉光颜色,对象支持 { enabled, color, sizeMeters } |
| loop | boolean | 可选,是否循环播放 |
| returnMotion | boolean | 可选,是否折返运动 |
| trajectoryVisible | boolean | 可选,是否显示轨迹线 |
| pathEditor | boolean | 可选,是否开启地图路径编辑 |
| autoplay | boolean | 可选,实例化完成后是否立即播放 |
构造参数会在实例化阶段一次性应用;实例创建后,仍可继续使用 setModel、setPath、setSpeed、setCameraMode、setParticleEnabled 等方法动态调整。也可以调用 applyOptions(options) 批量更新同一套配置。
API
| 方法 | 说明 |
| --- | --- |
| applyOptions(options) | 批量应用初始化配置;可在实例化后继续一次性更新模型、路径、相机、跟随目标、粒子、辉光、循环和播放状态 |
| setFollowTarget(target) | 设置相机跟随目标;支持外部 Entity、Cesium.Model、ModelInstanceCollection 指定实例或自定义位置函数,传 null 恢复跟随插件内部模型 |
| setModel(url, options?) | 设置 3D 模型文件路径;会按模型名自动读取本地模型初始化设置 |
| setLocalModelFile(file, options?) | 使用用户本地选择的 .glb / .gltf 模型文件,当前页面立即生效 |
| setModelScale(scale) | 设置模型初始缩放,有效范围 0.001 到 10000;控制台滑块使用换算映射,方便调节极小值;会按模型名自动保存 |
| setModelBrightness(brightness) | 设置模型亮度倍数,有效范围 0.1 到 10,默认 1;会按模型名自动保存。底层写入 Cesium 的 imageBasedLightingFactor 会自动限制在 0 到 1,避免不同 Cesium 版本渲染时报错 |
| setModelOrientation({ heading, pitch, roll }) | 设置模型初始朝向偏移(弧度),修正模型自身朝向与运动方向不一致;会按模型名自动保存 |
| setPath(pointsOrConfig) | 设置当前路径。既支持直接传点数组,也支持传 { name, mode, speed, closedLoop, points } 一次性覆盖整条路径配置 |
| setPathMode(mode) | 设置轨迹模式:PATH_MODE.LINEAR / PATH_MODE.BEZIER |
| addPathPoint(point, time?) | 动态追加一个路径点 |
| setPathSpeed(metersPerSecond) | 设置路径推算速度(米/秒),仅影响缺省时间的点 |
| setSpeed(multiplier) | 设置插件自身播放速度倍率;基于宿主 viewer.clock 的时间推进额外叠加,不改宿主倍率 |
| setCameraMode(mode) | 切换相机视角,见 CAMERA_MODE |
| setCameraDistance(distance) | 设置相机与模型距离(米) |
| setCameraAngle({ heading, pitch }) | 设置相机角度(弧度) |
| play() | 播放动画 |
| pause() | 暂停动画 |
| setParticleEnabled(enabled, options?) | 开启/关闭粒子发射器,默认从路径运动点发射,支持 color / startColor / endColor / imageSize / particleLife / backOffset / sideOffset;backOffset 为前后偏移米数,正数在运动点后方,负数在运动点前方;sideOffset 为左右偏移米数,正数在运动点右侧,负数在运动点左侧;默认粒子生命周期 2 秒 |
| setParticleSize(sizeMeters) | 设置尾部轨迹粒子大小(米),运行中立即生效,控制台也可滑动调节 |
| setParticleLife(seconds) | 设置尾部轨迹粒子生命周期(秒),运行中立即生效,控制台也可滑动调节 |
| setParticleBackOffset(meters) | 设置粒子发射器相对路径运动点的前后偏移(米),正数向运动点后方,负数向运动点前方,0 表示运动点位置;运行中立即生效,控制台也可滑动调节 |
| setParticleSideOffset(meters) | 设置粒子发射器相对路径运动点的左右偏移(米),正数向运动点右侧,负数向运动点左侧,0 表示运动点位置;运行中立即生效,控制台也可滑动调节 |
| setGlowEnabled(enabled, config?) | 开启/关闭呼吸闪烁辉光粒子,默认绿色,最高透明度 80%,支持 CSS 颜色或配置对象 |
| setGlowSize(sizeMeters) | 设置辉光粒子大小(米),控制台也可滑动调节 |
| setLoop(enabled) | 开启/关闭循环播放 |
| setReturnMotion(enabled) | 开启/关闭折返运动,反向段会在模型基础朝向上叠加 180° |
| setTrajectoryVisible(visible) | 显示/隐藏运动轨迹线 |
| setPathEditorEnabled(enabled) | 开启/关闭地图路径编辑,开启后左键点击地图创建点位 |
| setPathDraft(presetOrPoints) | 设置当前路径草稿,支持完整 JSON 或点位数组 |
| closePathLoop() | 首尾相连:记录 closedLoop 标识,按当前轨迹模式连接两端;直线模式补直线闭合段,平滑模式按闭合曲线处理 |
| saveCurrentPath(name) | 保存当前路径到 localStorage |
| loadPathPreset(id) | 从 localStorage 读取路径并运行中切换 |
| exportCurrentPath() | 导出当前路径 JSON 字符串 |
| importPathPreset(json) | 导入路径 JSON 并应用到当前动画 |
| destroy() | 销毁,移除所有监听与场景对象 |
路径点格式
{ lon: 经度, lat: 纬度, height: 高度(米) }路径 JSON 格式
{
"id": "path-1710000000000",
"name": "演示路径",
"version": 1,
"mode": "bezier",
"speed": 120,
"closedLoop": false,
"points": [
{ "lon": 116.39, "lat": 39.9, "height": 300 },
{ "lon": 116.41, "lat": 39.91, "height": 600 }
],
"createdAt": 1710000000000,
"updatedAt": 1710000000000
}路径控制台支持读取 localStorage 中的路径列表,运行中可通过下拉框切换;“新建路径”会创建一条空草稿,第一次点击“保存路径”会弹出控制台风格的名称表单,已保存过的路径再次点击“保存路径”会把当前最新数据覆盖保存到本地,“另存为新路径”始终会弹出名称表单并生成一条新路径。“导出 JSON”会直接下载当前路径 JSON 文件;路径管理器下方的 JSON 输入框始终显示当前路径 JSON,地图点位变化会实时更新该输入框,直接修改输入框里的 JSON 也会实时同步到地图点位。“首尾相连”会把当前路径标记为 closedLoop: true,不再依赖末尾重复首点;直线轨迹会补上尾点到首点的直线闭合段,平滑轨迹会按闭合曲线处理两端切线。编辑模式开启后会自动进入自由镜头,地图可直接左键拖动观察;界面底部会显示半透明操作提示:单击空白区域可添加路径点,右键路径线段可在该位置插入路径点,右键路径点会弹出删除按钮;左键拖动已有路径点或三向轴可调整位置,拖动期间编辑器会临时接管鼠标,松手后恢复地图拖动。
控制台主面板保留播放、视角、相机距离、相机环绕角度、相机俯仰等常用控制;顶部“当前编辑对象”下拉默认以“动画对象 1 / 2 ...”区分多个插件实例,外部传入 console.label 时优先使用自定义名称;下拉框下方提供“本地上传模型”入口,选择 .glb / .gltf 后会立即使用本地模型。多个插件实例会各自维护模型、路径、粒子、辉光和轨迹状态;共享控制台只负责切换当前编辑对象,本地路径列表仍是全局可选列表。
控制台把“当前编辑对象”和“相机跟随对象”分开管理:当前编辑对象决定模型初始化、路径管理、粒子、辉光、轨迹线等设置作用到谁;相机跟随对象决定场景相机现在看谁。多个模型可以同时移动,但相机同一时间只跟随一个对象;选择“自由镜头 / 不跟随”会交还 Cesium 默认相机控制。插件以宿主 viewer.clock.currentTime 作为时间源,不接管宿主倍率;每个插件实例独立维护播放开关和播放速度。宿主 clock 正在推进时,只有插件自身处于播放状态的对象才会运动;宿主 clock 没有推进时,插件播放会临时启动 clock 作为时间源,最后一个由插件启动的动画暂停或销毁后会释放该临时状态。
“路径管理器”位于相机控制下方;“首尾相连”会把当前路径标记为 closedLoop: true,不再依赖末尾重复首点;直线轨迹会补上尾点到首点的直线闭合段,平滑轨迹会按闭合曲线处理两端切线。点位较多时,闭合曲线采样会自动分帧处理,避免点击瞬间阻塞页面。粒子颜色、轨迹粒子大小、粒子生命周期、粒子前后偏移、粒子左右偏移、辉光颜色、辉光大小已收纳到“粒子管理”;粒子跟随路径运动点,不继承模型自身坐标轴、缩放或内置动画偏移。模型初始缩放、模型亮度倍数、初始朝向、初始俯仰已收纳到“模型初始化”。模型缩放有效范围为 0.001 到 10000,无历史初始化设置的新模型默认使用 20 倍;模型亮度有效范围为 0.1 到 10,默认 1 倍;底层 Cesium 环境光照因子会自动限制在 0 到 1,因此高于 1 的亮度值只作为插件侧初始化参数保存,不会直接写入 Cesium 的受限字段。控制台缩放滑块使用换算映射,能同时兼顾极小值和大值调节。模型初始化设置会按模型名保存到 localStorage,同一个模型下次加载时会自动恢复缩放、亮度、朝向、俯仰和滚转;本地上传模型按文件名记忆这些初始化参数,但模型文件本体不会写入 localStorage。
模型初始化本地存储 key 为 cesium-path-animation:model-settings:v1,结构按模型名索引:
{
"plane.glb": {
"version": 1,
"modelName": "plane.glb",
"url": "/model/plane.glb",
"scale": 20,
"brightness": 1,
"heading": 1.5708,
"pitch": 0,
"roll": 0,
"updatedAt": 1710000000000
}
}跟随外部 Cesium 对象
默认情况下,相机会跟随 CesiumPathAnimation 内部创建的模型。如果你的模型已经由业务系统自己绘制,也可以把外部对象作为 followTarget 传进来。此时插件只负责相机跟随、路径控制台、粒子、辉光等能力,不强制接管你的模型绘制方式。
followTarget 的核心不是“模型必须是什么类”,而是“每一帧能不能拿到目标当前位置”。因此除了直接传 Entity、Cesium.Model、ModelInstanceCollection 实例信息,也可以传一个位置解析函数。位置解析函数就是业务系统提供给插件的取位置接口:插件每帧把当前时间和一个可复用的 result 传进去,函数返回目标当前的 Cartesian3。这种方式最适合业务侧自己维护模型池、合批模型、拾取 ID、矩阵缓存的场景。
位置解析函数格式:
(time, result) => Cesium.Cartesian3 | undefinedtime:当前 Cesium 时间,适合读取动态位置。result:插件提供的复用对象,建议把结果写进去再返回,减少每帧创建新对象。- 返回值:目标当前世界坐标;返回
undefined时,本帧不会更新到无效位置。
1. 跟随 Entity 模型
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(110.35, 20.01, 500),
model: { uri: '/model/drone.glb', scale: 10 }
})
const anim = new CesiumPathAnimation({
viewer,
followTarget: entity,
camera: {
mode: CAMERA_MODE.FIXED,
distance: 3000,
angle: { heading: 0, pitch: Cesium.Math.toRadians(-30) }
}
})
// 后续也可以动态切换跟随目标
anim.setFollowTarget(entity)2. 跟随 Cesium.Model 单模型
const model = await Cesium.Model.fromGltfAsync({
url: '/model/drone.glb',
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(110.35, 20.01, 500)
),
scale: 10
})
viewer.scene.primitives.add(model)
const anim = new CesiumPathAnimation({
viewer,
followTarget: model,
camera: {
mode: CAMERA_MODE.THIRD_PERSON,
distance: 3000
}
})如果你使用的是旧版同步创建方式,也可以这样传:
const model = viewer.scene.primitives.add(Cesium.Model.fromGltf({
url: '/model/drone.glb',
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(110.35, 20.01, 500)
),
scale: 10
}))
anim.setFollowTarget(model)3. 跟随 ModelInstanceCollection 合批模型中的某个实例
如果宿主项目能直接拿到 ModelInstanceCollection 对象,并且能确定实例下标,可以直接传 { collection, index }:
const collection = viewer.scene.primitives.add(new Cesium.ModelInstanceCollection({
url: '/model/tree.glb',
instances: [
{
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(110.35, 20.01, 0)
)
},
{
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
Cesium.Cartesian3.fromDegrees(110.36, 20.02, 0)
)
}
]
}))
anim.setFollowTarget({
collection,
index: 1
})但在真实业务里,普通合批模型通常由渲染器统一管理:渲染器保存业务 ID、实例顺序、矩阵缓存,并在 scene.pick 后把 picked.instanceId 转成业务 ID。此时更推荐把“业务 ID 到世界坐标”的位置解析函数传给插件,插件不需要知道你的合批集合内部怎么维护:
const pickedInfo = modelRenderer.resolvePickObject(picked)
if (pickedInfo?.source === 'model-instance') {
anim.setFollowTarget((time, result) => {
const position = modelRenderer.getItemWorldPosition(pickedInfo.id)
return position ? Cesium.Cartesian3.clone(position, result) : undefined
})
}这种方式适合:
- 普通合批模型:
ModelInstanceCollection - 业务模型池:同一个模型资源复用给很多目标
- 拾取后只有业务 ID,没有直接暴露 Cesium 实例
- 模型矩阵由业务系统持续更新
4. 自定义跟随位置函数
如果你的外部对象不是以上三种结构,只要能返回 Cartesian3,也可以直接传函数。建议优先把坐标写入 result 后返回:
const anim = new CesiumPathAnimation({
viewer,
followTarget: (time, result) => {
return Cesium.Cartesian3.fromDegrees(110.35, 20.01, 500, undefined, result)
},
camera: { mode: CAMERA_MODE.FIXED, distance: 3000 }
})多对象同时移动
每创建一个 CesiumPathAnimation,就代表一个独立的动画对象。一个对象包含一个模型、一条当前轨迹、一套粒子/辉光/轨迹线状态。多个对象共用宿主 viewer.clock.currentTime 作为时间源,但播放开关和播放速度由各自实例独立维护;宿主 clock 正在推进时只有调用过 play() 的对象会移动,宿主 clock 没有推进时插件会在播放期间临时启动时间源。
const droneA = new CesiumPathAnimation({
viewer,
console: { label: '无人机 A' }
})
droneA.setModel('/model/drone-a.glb', { scale: 20 })
droneA.setPath({
name: '巡检路线 A',
speed: 120,
points: [
{ lon: 110.303, lat: 20.03, height: 500 },
{ lon: 110.354, lat: 20.035, height: 800 }
]
})
const droneB = new CesiumPathAnimation({
viewer,
console: { label: '无人机 B' }
})
droneB.setModel('/model/drone-b.glb', { scale: 20 })
droneB.setPath({
name: '巡检路线 B',
speed: 90,
points: [
{ lon: 110.285, lat: 19.985, height: 400 },
{ lon: 110.412, lat: 20.002, height: 700 }
]
})
droneA.play()控制台里可以分别选择“当前编辑对象”和“相机跟随对象”:编辑对象用于改模型、路径、粒子和轨迹线;跟随对象用于决定相机看谁。
相机交互
- 随目标固定视角(
FIXED):默认相机模式,默认相机距离 5000 米;普通观察时直接按住鼠标左键拖拽,修改相机围绕目标的环绕角度和俯仰角度;开启轨迹点编辑后会自动进入自由镜头,可直接拖动地图观察。 - 定点固定视角:控制台点击“定点固定视角”后,会弹出与控制台同风格的经纬高表单;确认后相机会切到固定视角并围绕该坐标点观察;点击“恢复随目标固定”可恢复围绕模型当前位置。
- 第三人称跟随(
THIRD_PERSON):普通观察时直接按住鼠标左键拖拽,可在跟随运动方向的基础上调整环绕偏移和俯仰角度;开启轨迹点编辑后会自动进入自由镜头,可直接拖动地图观察。 - 自动环绕:默认开启,默认速度 2°/s;控制台开启“自动环绕”后,仅在随目标固定视角生效;用户 1 秒无鼠标操作时,相机会从静止慢慢加速到设定环绕速度,速度可在控制台调节。
- 第三人称跟随(
THIRD_PERSON)与固定视角跟随:鼠标滚轮控制与目标的距离,按当前视线角度斜率缩放;固定模式最大相机距离 500 万米。 - 自由镜头(
FREE):交还 Cesium 默认相机控制,操作方式保持 Cesium 原样。
运行示例
npm run demo
# 或只启动 demo 目录
npm run demo:package发布包会包含 src、demo 和 README.md;因此安装包里也会带上海口双对象 demo 与默认模型 demo/demo.gltf。
