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

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

效果

火箭

全部代码在最后

起步

  • 模型控制,第一步当然是需要一个合适的模型,去cesium官网实例中找到了一个合适的模型,并且还顺带了一些模型操作方法。
    cesium火箭发射,模型控制,模型动画,模型移动_第1张图片

  • 搜索关键字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,
        })
    );

模型的组成

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

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

cesium火箭发射,模型控制,模型动画,模型移动_第2张图片

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

模型操作

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

  • 火箭点火后自然就要移动,添加模型的平滑移动,模型的平滑移动我之前的文章有。
  • 原理就是不停的移动模型位置,以及模型的姿态,只要移动的距离足够小看起来就是平滑的。
 Cesium.Transforms.headingPitchRollToFixedFrame(
        showPath[activeIndex], //当前坐标点Cesium.Cartesian3
        hpRoll,//姿态
        Cesium.Ellipsoid.WGS84,
        fixedFrameTransform,
        rocketPrimitive.modelMatrix //模型当前的世界矩阵
    );
  • 利用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
}

  • 可以平滑的移动了。

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

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)
   }
})

cesium火箭发射,模型控制,模型动画,模型移动_第4张图片

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

  • 比如火焰喷射要有真实感
  • 火箭转向时 喷射头偏转,等等
<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>

你可能感兴趣的:(Vue3,Cesium,TS,javascript,typescript,前端)