• cesium火箭发射,模型控制,模型动画,模型移动


    起因:最近想做模型的动画,结果上网查资料,看到网上好多对于模型控制的文章都有限制。决定还是自己研究下。欢迎大家一起探讨,评论留言。

    效果

    火箭

    全部代码在最后

    起步

    • 模型控制,第一步当然是需要一个合适的模型,去cesium官网实例中找到了一个合适的模型,并且还顺带了一些模型操作方法。
      在这里插入图片描述

    • 搜索关键字applyArticulations;模型地址

    • 拿到模型迫不及待的想在自己自己的项目中加载出来

    • 加载方式有两种entityPrimitive, 我个人更喜欢第二种Primitive方式。

    • 对应的参数就不做解释了,文档中都有

    let rocketPrimitive: Cesium.Model
    let position = Cesium.Cartesian3.fromDegrees(104.200403, 30.396231, 600.0);
    const hpRoll = new Cesium.HeadingPitchRoll();
    const fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator("north", "west");
    const rocketPrimitive = viewer.scene.primitives.add(
            Cesium.Model.fromGltf({
                url: "https://assets.agi.com/models/launchvehicle.glb",
                modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame(
                    position,
                    hpRoll,
                    Cesium.Ellipsoid.WGS84,
                    fixedFrameTransform
                ),
                minimumPixelSize: 128,
            })
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    模型的组成

    const articulations = model.sceneGraph._runtimeArticulations;官方的这段代码我用着报错,文档里面没有。也不知道啥问题

    • 直接将加载的模型打印出来看里面具体的结构

    在这里插入图片描述

    发现了nodes,模型里面的关节点,就是模型由哪些部分组成,官网文档中也明确的说了。画红线的那段话翻译出来就是"返回glTF中具有给定名称的节点。这用于修改用户定义动画的节点变换"
    在这里插入图片描述

    模型操作

    • 点火
     rocketPrimitive.setArticulationStage( //对应属性改变参数值
       'SRBFlames Size',
         1
     );
     rocketPrimitive.applyArticulations(); //使得修改的属性生效
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    • 火箭点火后自然就要移动,添加模型的平滑移动,模型的平滑移动我之前的文章有。
    • 原理就是不停的移动模型位置,以及模型的姿态,只要移动的距离足够小看起来就是平滑的。
     Cesium.Transforms.headingPitchRollToFixedFrame(
            showPath[activeIndex], //当前坐标点Cesium.Cartesian3
            hpRoll,//姿态
            Cesium.Ellipsoid.WGS84,
            fixedFrameTransform,
            rocketPrimitive.modelMatrix //模型当前的世界矩阵
        );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 利用viewer.scene.preUpdate.addEventListener //下一帧渲染前回调

    viewer.scene.preUpdate.addEventListener(keepRun)

    • keepRun 就是我们移动的函数
    const keepRun = (scene: Cesium.Scene, time: number) => {
        if (activeIndex >= maxIndex) return
        if (autoDirection && activeIndex > 0 && !showPath[activeIndex - 1].equals(showPath[activeIndex])) { //判断前后两个点是否一样,不一样就调整姿态
            const heading = Helper.getHeading(
                showPath[activeIndex - 1],
                showPath[activeIndex],
            );
            if (heading) hpRoll.heading = heading
            const pitch = Helper.getPitch(
                showPath[activeIndex - 1],
                showPath[activeIndex])
            if (pitch) hpRoll.pitch = pitch
        }
        Cesium.Transforms.headingPitchRollToFixedFrame(
            showPath[activeIndex],
            hpRoll,
            Cesium.Ellipsoid.WGS84,
            fixedFrameTransform,
            rocketPrimitive.modelMatrix
        );
        activeIndex += 1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    • 可以平滑的移动了。

    • 当然火焰的生起也应当平滑,控制模型都需要平滑的操作,直接写个函数控制

    function modelAnimationController(controller: typeModelAnimationController) {
        const { type, initVal, maxVal, fn, step, minVal } = controller
        let num = initVal
        let stopFrame: number
        const max = maxVal || 1
        const min = minVal || -99999
        const duration = step || 0.1
        const render = () => {
            num += duration
            rocketPrimitive.setArticulationStage(
                type,
                num
            );
            rocketPrimitive.applyArticulations();
            stopFrame = requestAnimationFrame(render)
            if (num > max || num <= min) {
                window.cancelAnimationFrame(stopFrame)
                fn && fn()
            }
        }
        render()
    }
    modelAnimationController({
      type: 'SRBFlames Size', initVal: 0, maxVal: 1, step: 0.05, fn: () => {
           viewer.scene.preUpdate.addEventListener(keepRun)
       }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    剩下的都是重复的操作,以及反复调试修改达到最佳

    • 比如火焰喷射要有真实感
    • 火箭转向时 喷射头偏转,等等
    <template>
        <div class="btn-box">
        </div>
        <Map @onViewerLoaded="onViewerLoaded" :options="options">
        </Map>>
    </template>
    <script lang="ts" setup>
    import Map from "@/components/Cesium/lib/Map.vue";
    import * as Cesium from "cesium";
    import { GetPosition } from "@/components/Cesium/utils";
    import { initLayerPromise } from '@/components/Cesium/utils/initLayer'
    import { Helper } from "@/components/Cesium/lib/helper";
    let viewer: Cesium.Viewer
    const options = {}
    let handler: Cesium.ScreenSpaceEventHandler
    const onViewerLoaded = (Viewer: Cesium.Viewer) => {
        viewer = Viewer
        handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    
        initLayerPromise(Viewer, true).then(() => {
            viewer.camera.flyTo({
                destination: Cesium.Cartesian3.fromDegrees(104.200403,
                    30.396231, 2000),
                // destination: Cesium.Cartesian3.fromDegrees(120.38105869, 31.10115627, 3000),
                complete: () => { init() }
            });
            // init()
        })
        const getP = new GetPosition(Viewer);
        getP.getPositionByClick((position: any) => {
            console.log(position);
        });
    };
    let planePrimitive: Cesium.Model
    let position = Cesium.Cartesian3.fromDegrees(104.200403, 30.396231, 600.0);
    const hpRoll = new Cesium.HeadingPitchRoll();
    const fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator("north", "west");
    
    let activeIndex = 0 //插值经纬度索引
    let maxIndex = 0// 最大插值经纬度数组索引
    let autoDirection = true; //自动调整方向
    let path: [number, number, number][] = [] //存在路线数组
    let showPath: Cesium.Cartesian3[] = [] //插值数组
    let camera: Cesium.Camera
    let controller: Cesium.ScreenSpaceCameraController
    let r: number
    const hpRange = new Cesium.HeadingPitchRange();
    let nodes: any[] = []
    const init = () => {
        hpRoll.pitch = 90 * Math.PI / 180;
        planePrimitive = viewer.scene.primitives.add(
            Cesium.Model.fromGltf({
                url: "models/launchvehicle.glb",
                modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame(
                    position,
                    hpRoll,
                    Cesium.Ellipsoid.WGS84,
                    fixedFrameTransform
                ),
                minimumPixelSize: 128,
            })
        );
        const scene = viewer.scene;
        planePrimitive.readyPromise.then((model) => {
            camera = viewer.camera;
            controller = scene.screenSpaceCameraController;
            r =
                2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
            controller.minimumZoomDistance = r * 0.2;
            /**
             modelAnimationController({
                type: 'SRBFlames Size', initVal: 0, maxVal: 1, step: 0.05, fn: () => {
                    modelAnimationController({ type: 'SRBFlames Size', initVal: 1, minVal: 0, step: -0.5 })
                    modelAnimationController({
                        type: 'SRBs Separate', initVal: 0, maxVal: 10, step: 0.5, fn: () => {//一级脱落
                            modelAnimationController({ type: 'SRBs Drop', initVal: 0, minVal: -50, step: -0.5 })
                        }
                    })
                    // modelAnimationController({ type: 'BoosterEngines Yaw', initVal: 0, maxVal: 1, step: 0.1 }) //左右
                    // modelAnimationController({ type: 'BoosterEngines Pitch', initVal: 0, maxVal: 1, step: 0.1 }) //上下
                    modelAnimationController({
                        type: 'BoosterFlames Size', initVal: 0, maxVal: 1, step: 0.1, fn: () => {
                            modelAnimationController({ type: 'Fairing Open', initVal: 0, maxVal: 45, step: 0.5 })
                            modelAnimationController({ type: 'Fairing Separate', initVal: 0, minVal: -10, step: -0.1 })
                            modelAnimationController({ type: 'Fairing Drop', initVal: 0, minVal: -50, step: -0.5, fn: ()=> {
                                //主推进器脱落
                                modelAnimationController({ type: 'Booster MoveZ', initVal: 0, minVal: -50, step: -0.5})
                                modelAnimationController({ type: 'UpperStageFlames Size', initVal: 0, maxVal: 1, step: 0.05, fn:()=> {
                                    modelAnimationController({ type: 'InterstageAdapter MoveZ', initVal: 0, minVal: -50, step: -0.5})
                                }})
                                // modelAnimationController({ type: 'UpperStageEngines Yaw', initVal: 0, maxVal: 1, step: 0.05,})//左右
                                // modelAnimationController({ type: 'UpperStageEngines Pitch', initVal: 0, maxVal: 1, step: 0.05,}) //上下
                            } })
                        }
                    })
                }
            })
             */
            lookAt()
            pickup()
            nodes = planePrimitive.gltf.nodes
            // nodes.forEach(i => {
            //     if (new RegExp(/InterstageAdapter/).test(i.name)) {
            //         planePrimitive.getNode(i.name).show = false
            //     }
            // })
    
            crateLine().then(() => {
                modelAnimationController({
                    type: 'SRBFlames Size', initVal: 0, maxVal: 1, step: 0.05, fn: () => {
                        viewer.scene.preUpdate.addEventListener(keepRun)
                    }
                })
            })
        })
    
    }
    function pickup() {
        handler.setInputAction(function (movement) {
            const pickedObject = viewer.scene.pick(movement.position);
            if (Cesium.defined(pickedObject)) {
                console.log(pickedObject)
            }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    }
    const lookAt = () => {
        const center = Cesium.Matrix4.multiplyByPoint(
            planePrimitive.modelMatrix,
            Cesium.Cartesian3.ZERO,
            new Cesium.Cartesian3()
        );
        const heading = Cesium.Math.toRadians(10.0);
        const pitch = Cesium.Math.toRadians(-5.0);
        camera.lookAt(
            center,
            new Cesium.HeadingPitchRange(heading, pitch, r * 2)
        );
    }
    type typeModelAnimationController = {
        type: string;
        initVal: number;
        maxVal?: number;
        minVal?: number;
        fn?: Function;
        step?: number,
    }
    function modelAnimationController(controller: typeModelAnimationController) {
        const { type, initVal, maxVal, fn, step, minVal } = controller
        let num = initVal
        let stopFrame: number
        const max = maxVal || 1
        const min = minVal || -99999
        const duration = step || 0.1
        const render = () => {
            num += duration
            planePrimitive.setArticulationStage(
                type,
                num
            );
            planePrimitive.applyArticulations();
            stopFrame = requestAnimationFrame(render)
            if (num > max || num <= min) {
                window.cancelAnimationFrame(stopFrame)
                fn && fn()
            }
        }
        render()
    }
    const crateLine = () => {
        const lon = 104.200403, lat = 30.396231, alt = 20600
        for (let index = 1; index < 20; index++) {
            path.push([lon, lat, 600 + 1000 * index])
        }
        const len = 1000
        let lastLat = 0, lastLon = 0, lastAlt = 0, activeLon, activeLat, activeAlt
        for (let index = 0; index < len; index++) {
            activeLon = Number((lon + index * 0.01).toFixed(6))
            activeLat = Number((lat + index * 0.02).toFixed(6))
            activeAlt = alt + index * 1000
            path.push([activeLon, activeLat, activeAlt])
            if (index === len - 1) {
                lastLon = activeLon
                lastLat = activeLat
                lastAlt = activeAlt
            }
        }
        for (let i = 0; i <= 360; i += 1) {
            path.push([lastLon + i, lastLat, lastAlt])
        }
        return new Promise(resolve => {
            getPosition().then(res => {
                showPath = res
                maxIndex = res.length
                const line = viewer.scene.primitives.add(
                    new Cesium.Primitive({
                        geometryInstances: new Cesium.GeometryInstance({
                            geometry: new Cesium.PolylineGeometry({
                                positions: res,
                                width: 3.0,
                                vertexFormat: Cesium.PolylineColorAppearance.VERTEX_FORMAT,
                            }),
                            attributes: {
                                color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.BLUE.withAlpha(.7)),
                            },
                        }),
                        appearance: new Cesium.PolylineColorAppearance(),
                    })
                );
                line.readyPromise.then(() => {
                    resolve('')
                })
            })
        })
    }
    
    const keepRun = (scene: Cesium.Scene, time: number) => {
        if (activeIndex >= maxIndex) return
        console.log(activeIndex)
        if (activeIndex === 1000) {
            modelAnimationController({ type: 'SRBFlames Size', initVal: 1, minVal: 0, step: -0.5 })
            modelAnimationController({
                type: 'SRBs Separate', initVal: 0, maxVal: 10, step: 0.5, fn: () => {//一级脱落
                    modelAnimationController({
                        type: 'SRBs Drop', initVal: 0, minVal: -100, step: -1, fn: () => nodes.forEach(i => {
                            if (new RegExp(/SRB\d/).test(i.name)) {
                                planePrimitive.getNode(i.name).show = false
                            }
                        })
                    })
                }
            })
            modelAnimationController({ type: 'BoosterFlames Size', initVal: 0, maxVal: 1, step: 0.1 }) //主推期开始点火
        }
        if (activeIndex === 3000) {
            modelAnimationController({ type: 'Fairing Open', initVal: 0, maxVal: 45, step: 0.5 })
            modelAnimationController({ type: 'Fairing Separate', initVal: 0, minVal: -10, step: -0.1 })
            modelAnimationController({
                type: 'Fairing Drop', initVal: 0, minVal: -150, step: -1, fn: () => {
                    //主推进器脱落
                    modelAnimationController({ type: 'BoosterFlames Size', initVal: 1, minVal: 0, step: -0.05 })
                    modelAnimationController({
                        type: 'Booster MoveZ', initVal: 0, minVal: -150, step: -1, fn: () => {
                            nodes.forEach(i => {
                                if (new RegExp(/Fairing\d/).test(i.name) || new RegExp(/Booster/).test(i.name)) {
                                    planePrimitive.getNode(i.name).show = false
                                }
                            })
                        }
                    })
                    modelAnimationController({ type: 'UpperStageFlames Size', initVal: 0, maxVal: 1, step: 0.05 })
                }
            })
        }
        if (activeIndex === 3600) {
            modelAnimationController({
                type: 'InterstageAdapter MoveZ', initVal: 0, minVal: -150, step: -1, fn: () => {
                    nodes.forEach(i => {
                        if (new RegExp(/InterstageAdapter/).test(i.name)) {
                            planePrimitive.getNode(i.name).show = false
                        }
                    })
                }
            })
        }
        lookAt()
        if (autoDirection && activeIndex > 0 && !showPath[activeIndex - 1].equals(showPath[activeIndex])) {
            const heading = Helper.getHeading(
                showPath[activeIndex - 1],
                showPath[activeIndex],
            );
            if (heading) hpRoll.heading = heading
            const pitch = Helper.getPitch(
                showPath[activeIndex - 1],
                showPath[activeIndex])
            if (pitch) hpRoll.pitch = pitch
        }
        Cesium.Transforms.headingPitchRollToFixedFrame(
            showPath[activeIndex],
            hpRoll,
            Cesium.Ellipsoid.WGS84,
            fixedFrameTransform,
            planePrimitive.modelMatrix
        );
        activeIndex += 1
    }
    const getPosition = () => {
        //插值 new Cesium.LinearSpline  new Cesium.CatmullRomSpline esium.HermiteSpline.createNaturalCubic
        //let pos = Cesium.Cartesian3.lerp(startP, endP, i / duration, new Cesium.Cartesian3());
        return new Promise((resolve: (value: Cesium.Cartesian3[]) => void) => {
            const points = path.map(i => Cesium.Cartesian3.fromDegrees(...i))
            let times: number[] = []
            for (let index = 0; index < points.length; index++) {
                times.push(index)
            }
            const spline = new Cesium.CatmullRomSpline({
                points,
                times,
            });
            const positions: Cesium.Cartesian3[] = [];
            for (let i = 1; i < times.length; i++) {
                for (let j = 0; j < 100; j++) {
                    const cartesian3 = spline.evaluate(i - 1 + j * 0.01);
                    positions.push(cartesian3);
                }
            }
            resolve(positions)
        })
    }
    </script>
    <style lang="less" scoped>
    .btn-box {
        position: absolute;
        top: 10px;
        z-index: 10;
        width: 500px;
        margin-left: 20px;
    }
    </style>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
  • 相关阅读:
    关于python pytorch 与CUDA版本相关问题
    2023数学建模国赛E题黄河水沙监测数据分析完整代码分析+处理结果+思路文档
    利用京东云Web应用防火墙实现Web入侵防护
    【cf】CodeForces Round 905(Div.3)题解
    【java】【SpringBoot】【二】运维实用篇 SpringBoot工程
    c++ set 容器详解 附例题题解
    二叉树的最近公共祖先LCA
    U盘有病毒插上电脑会感染吗?了解下U盘的病毒传播机制
    【模拟string函数的实现】
    File中文件改名
  • 原文地址:https://blog.csdn.net/qq_41400354/article/details/128017925