marsgis / mars3d

【Mars3D平台 】主仓库,包含所有开源仓库清单导航
http://mars3d.cn
Apache License 2.0
969 stars 136 forks source link

请问能否获取到各类事件实例并调整事件参数或取消事件? #22

Closed FirokOtaku closed 1 year ago

FirokOtaku commented 1 year ago

现有需求限制摄像机视角锁定在固定经纬度 / 固定朝向范围 / 固定高度范围.

尝试监听如下方法:

发现传入 fun 内的参数不是 undefined 就是一个 number 类型值, 无法直接调整目标相机视角等参数. 另外在这些事件的监听器内尝试调用 map.camera.setView 等 API 均效果不佳, 会产生闪屏 / 视角错位等错误.

请问有无方法可以在监听器方法内获取事件实例? 或有无方法获取到即将提交到事件总线的事件实例? 另外能否修改获取到的事件实例成员参数以调整本次事件过程?

muyao1987 commented 1 year ago

在事件内的event中可能没有相关值,是ceisum本身的回调,可以取 map.camera内值或调用map相关方法 即可获取相关参数

FirokOtaku commented 1 year ago

另外在这些事件的监听器内尝试调用 map.camera.setView 等 API 均效果不佳, 会产生闪屏 / 视角错位等错误

当前视角参数的获取没有问题, 但是在监听器方法内直接操作 API 会导致视角错位.

function onCameraChanged(event)
{
    const { lat, lng, alt, heading, pitch } = Map.getCameraView()
    const paramView = {
        destination: Cesium.Cartesian3.fromDegrees(lng, lat, alt),
        orientation: { heading, pitch }
    }
    Map.camera.setView(paramView) // 把当前的经纬度等参数原样写回都会错位
}
map.on(mars3d.EventType.cameraChanged, onCameraChanged, this)
muyao1987 commented 1 year ago

有具体需求不,我分析下如果写合适

FirokOtaku commented 1 year ago

现有需求限制摄像机视角锁定在固定经纬度 / 固定朝向范围 / 固定高度范围

这就是具体需求.

这边的应用场景是用来显示雕像 / 告示牌这样的模型, 相机视角会始终以模型为中心 (朝向这个模型), 另外不论如何用户拖动, 都只允许从正面 180 度 (或者其它自定义的) 范围查看模型.

muyao1987 commented 1 year ago

可能需要从鼠标交互事件入手,而不是相机变动事件

下面代码是片段性的关键逻辑,可以参考下

 setPitchRange(max, min = -90) { 
    map.scene.screenSpaceCameraController.inertiaSpin = 0
    map.scene.screenSpaceCameraController.inertiaTranslate = 0

    map.on(mars3d.EventType.mouseDown, this._setPitchRange_rightDownHandler, this)
    map.on(mars3d.EventType.mouseUp, this._setPitchRange_rightUpHandler, this)
  }

  _setPitchRange_rightDownHandler(evnet) { 
    map.on(mars3d.EventType.mouseMove, this._setPitchRange_mouseMoveHandler, this)
    map.on(mars3d.EventType.cameraChanged, this._setPitchRange_cameraChangedHandler, this)
  }

  _setPitchRange_rightUpHandler(evnet) {
    map.scene.screenSpaceCameraController.enableTilt = true
    map.off(mars3d.EventType.mouseMove, this._setPitchRange_mouseMoveHandler, this)
    map.off(mars3d.EventType.cameraChanged, this._setPitchRange_cameraChangedHandler, this)
  }

  _setPitchRange_mouseMoveHandler(evnet) {
    let enableTilt = true
    // const isUp = evnet.endPosition.y < evnet.startPosition.y
    // if (isUp && map.camera.pitch > this._pitch_max) {
    //   enableTilt = false
    // } else if (!isUp && map.camera.pitch < this._pitch_min) {
    //   enableTilt = false
    // } else {
    //   enableTilt = true
    // }
    map.scene.screenSpaceCameraController.enableTilt = enableTilt
  }

  _setPitchRange_cameraChangedHandler(evnet) {
    // if (map.scene.mode !== Cesium.SceneMode.SCENE3D) {
    //   return
    // }
    // if (map.camera.positionCartographic.height > map.scene.screenSpaceCameraController.minimumCollisionTerrainHeight) {
    //   return
    // }

    // let pitch = map.camera.pitch
    if (pitch > this._pitch_max || pitch < this._pitch_min) {
      // map.scene.screenSpaceCameraController.enableTilt = false

      // // clamp the pitch
      // if (pitch > this._pitch_max) {
      //   pitch = this._pitch_max
      // } else if (pitch < this._pitch_min) {
      //   pitch = this._pitch_min
      // }

      // const destination = Cesium.Cartesian3.fromRadians(
      //   map.camera.positionCartographic.longitude,
      //   map.camera.positionCartographic.latitude,
      //   Math.max(map.camera.positionCartographic.height, this._pitch_minHeight)
      // )

      map.camera.cancelFlight()
      map.camera.setView({
        destination: destination,
        orientation: {
          pitch: pitch
        }
      })
      map.scene.screenSpaceCameraController.enableTilt = true
    }
  }
FirokOtaku commented 1 year ago

稍微加了点逻辑, 传给 map.camera.setView 的参数里已经对 pitch 等值限制了范围, 但是看起来不是很好用. 测试的时候视角还是会超出预定范围. 好的时候只超出范围一点点, 坏的时候视角整个就飞到不知道什么角度了. 有时候 pitch 会变成正数, 变成从地底朝上看; 另外相机视角离地高度也没法没法限制最小离地距离. 有点奇怪. 是我哪里理解有误吗?

function range(value = NaN, min = NaN, max = NaN)
{
    value = +value
    if(isNaN(value)) return NaN
    min = +min
    max = +max
    if(!isNaN(min) && value <= min) return min
    if(!isNaN(max) && value >= max) return max
    return value
}

let cameraRange = {
    pitchMin: -90,
    pitchMax: -10,
    headingMin: 90,
    headingMax: 180,
    altMin: 300,
    altMax: 500,
}
function setCameraRange({ pitchMin, pitchMax, headingMin, headingMax, altMin, altMax, })
{
    map.scene.screenSpaceCameraController.inertiaSpin = 0
    map.scene.screenSpaceCameraController.inertiaTranslate = 0
    if(pitchMin != null) cameraRange.pitchMin = pitchMin
    if(pitchMax != null) cameraRange.pitchMax = pitchMax
    if(headingMin != null) cameraRange.headingMin = headingMin
    if(headingMax != null) cameraRange.headingMax = headingMax
    if(altMin != null) cameraRange.altMin = altMin
    if(altMax != null) cameraRange.altMax = altMax
}

function onMouseDown()
{
    map.on(mars3d.EventType.mouseMove, onMouseMove, this)
    map.on(mars3d.EventType.cameraChanged, onCameraChange, this)
}
function onMouseUp()
{
    map.scene.screenSpaceCameraController.enableTilt = true
    map.off(mars3d.EventType.mouseMove, onMouseMove, this)
    map.off(mars3d.EventType.cameraChanged, onCameraChange, this)
}

// 用来在 UI 显示当前值的测试变量
const LNG = ref(0), LAT = ref(0), HEADING = ref(0), PITCH = ref(0), ALT = ref(0)

function onMouseMove(event)
{
    const { heading, pitch } = map.getCameraView()
    const mcp = map.camera.positionCartographic
    const lng = mcp.longitude, lat = mcp.latitude, alt = mcp.height
    LNG.value = lng
    LAT.value = lat
    ALT.value = alt
    HEADING.value = heading
    PITCH.value = pitch

    const { pitchMin, pitchMax, headingMin, headingMax, altMin, altMax } = cameraRange

    let enableUpDown
    if (event.endPosition.y < event.startPosition.y)
        enableUpDown = !(
            (!isNaN(pitchMax) && pitch > pitchMax) ||
            (!isNaN(altMax) && alt > altMax)
        )
    else if (event.endPosition.y > event.startPosition.y)
        enableUpDown = !(
            (!isNaN(pitchMin) && pitch < pitchMin) ||
            (!isNaN(altMin) && alt < altMin)
        )
    else
        enableUpDown = true

    let enableLeftRight
    if(event.endPosition.x > event.startPosition.x)
        enableLeftRight = !(
            (!isNaN(headingMax) && heading > headingMax)
        )
    else if(event.endPosition.x < event.startPosition.x)
        enableLeftRight = !(
            (!isNaN(headingMin) && heading < headingMin)
        )
    else
        enableLeftRight = true

    map.scene.screenSpaceCameraController.enableTilt = enableUpDown && enableLeftRight
}

function onCameraChange(event)
{
    const { pitchMax, pitchMin, headingMax, headingMin, altMin, altMax } = cameraRange
    let { pitch, heading } = map.camera
    if (!map.scene.screenSpaceCameraController.enableTilt)
    {
        pitch = range(pitch, pitchMin, pitchMax)

        map.camera.cancelFlight()
        map.camera.setView({
            destination: Cesium.Cartesian3.fromRadians(
                map.camera.positionCartographic.longitude,
                map.camera.positionCartographic.latitude,
                range(map.camera.positionCartographic.height, altMin, altMax)
            ),
            orientation: {
                pitch: range(pitch, pitchMin, pitchMax),
                heading: range(heading, headingMin, headingMax),
            }
        })
        map.scene.screenSpaceCameraController.enableTilt = true
    }
}

onMounted(async () => {
    map.scene.screenSpaceCameraController.inertiaSpin = 0
    map.scene.screenSpaceCameraController.inertiaTranslate = 0
    map.on(mars3d.EventType.mouseDown, onMouseDown, this)
    map.on(mars3d.EventType.mouseUp, onMouseUp, this)
})

onMounted(async () => {
    let mapOptions = {
        scene: {
            shadows: false, //是否启用日照阴影
            removeDblClick: true, //是否移除Cesium默认的双击事件

            //以下是Cesium.Viewer所支持的options【控件相关的写在另外的control属性中】
            sceneMode: 3, //3等价于Cesium.SceneMode.SCENE3D,

            //以下是Cesium.Scene对象相关参数
            showSun: false, //是否显示太阳
            showMoon: false, //是否显示月亮
            showSkyBox: true, //是否显示天空盒
            showSkyAtmosphere: false, //是否显示地球大气层外光圈
            fog: false, //是否启用雾化效果
            fxaa: true, //是否启用抗锯齿

            //以下是Cesium.Globe对象相关参数
            globe: {
                depthTestAgainstTerrain: false, //是否启用深度监测
                baseColor: '#546a53', //地球默认背景色
                showGroundAtmosphere: false, //是否在地球上绘制的地面大气
                enableLighting: false //是否显示昼夜区域
            },
            //以下是Cesium.ScreenSpaceCameraController对象相关参数
            cameraController: {
                zoomFactor: 5, //鼠标滚轮放大的步长参数
                minimumZoomDistance: 50, //地球放大的最小值(以米为单位)
                maximumZoomDistance: 100, //地球缩小的最大值(以米为单位)
                minimumCollisionTerrainHeight: 100,
                enableRotate: true , //2D和3D视图下,是否允许用户旋转相机
                enableTranslate: false, //2D和哥伦布视图下,是否允许用户平移地图
                enableTilt: true, // 3D和哥伦布视图下,是否允许用户倾斜相机
                enableZoom: true, // 是否允许 用户放大和缩小视图
                enableCollisionDetection: true //是否允许 地形相机的碰撞检测
            },
        },
        control: {
            baseLayerPicker: false, //basemaps底图切换按钮
            homeButton: true, //视角复位按钮
            sceneModePicker: false, //二三维切换按钮
            navigationHelpButton: false, //帮助按钮
            fullscreenButton: false, //全屏按钮
        },
        basemaps: []
    }

    map = new mars3d.Map(
        'mars3dContainer',
        mapOptions,
    )
    map.fixedLight = true
    map = markRaw(map)

    const tiles3dLayer = new mars3d.layer.TilesetLayer({
        name: "县城社区",
        url: "//data.mars3d.cn/3dtiles/qx-shequ/tileset.json",
        position: { alt: 11.5 },
        maximumScreenSpaceError: 1,
        maximumMemoryUsage: 1024,
        dynamicScreenSpaceError: true,
        cullWithChildrenBounds: false,
        skipLevelOfDetail: true,
        preferLeaves: true,
        center: { lat: 28.439577, lng: 119.476925, alt: 229, heading: 57, pitch: -29 },

        enableDebugWireframe: true, // 是否可以进行三角网的切换显示
        flyTo: true
    })
    map.addLayer(tiles3dLayer)
    tiles3dLayer.flyTo().finally(() => {})

    map.on(mars3d.EventType.mouseDown, onMouseDown, this)
    map.on(mars3d.EventType.mouseUp, onMouseUp, this)
    window.map = map
})
FirokOtaku commented 1 year ago

好吧, 想了想, 完全换了一种写法, 禁用 mars3d 所有的视角移动选项,

cameraController: {
    enableRotate: false , //2D和3D视图下,是否允许用户旋转相机
    enableTranslate: false, //2D和哥伦布视图下,是否允许用户平移地图
    enableTilt: false, // 3D和哥伦布视图下,是否允许用户倾斜相机
    enableZoom: false, // 是否允许 用户放大和缩小视图
    enableCollisionDetection: false //是否允许 地形相机的碰撞检测
}

然后监听 mouseUp, mouseDown, mouseMove 事件, 手动根据鼠标移动的偏移角度用三角函数算相机的空间位置和朝向, 把算出来的东西 map.setCameraView 到 mars3d 里, 大致实现需要用的功能了.

新问题来了, 翻了半天 wheel 事件实例的字段, 没找到滚轮滚动值 (判断滚轮向上 / 向下滚动), 镜头视角缩放功能少一个常见的快捷键.

所以说传入监听器的事件实例能不能把原生 Web 事件实例加上啊, 哪怕不允许直接访问原生 MouseEvent 对象, 把里面字段复制一份让监听器方法能读到, 有些事能好办很多.

FirokOtaku commented 1 year ago

另外请问有没有对 各类型事件触发的时候会传给监听器什么结构的事件实例参数 的描述文档?

现有文档 (Event, Global#EventType, BaseClass#fire) 里没找到相关信息. 现在只知道发生 xx 事件之后 xx 监听器方法就会被调用, 不知道调用的时候会传给监听器方法什么参数, 还得现翻 F12 控制台挨个字段瞅.

muyao1987 commented 1 year ago

cameraController

建议看看ScreenSpaceCameraController类代码,是cesium对鼠标交互控制相机的所有逻辑。

muyao1987 commented 1 year ago

另外请问有没有对 各类型事件触发的时候会传给监听器什么结构的事件实例参数 的描述文档?

现有文档 (Event, Global#EventType, BaseClass#fire) 里没找到相关信息. 现在只知道发生 xx 事件之后 xx 监听器方法就会被调用, 不知道调用的时候会传给监听器方法什么参数, 还得现翻 F12 控制台挨个字段瞅.

目前还没有,需要F12打印event后看下值。

FirokOtaku commented 1 year ago

目前还没有,需要F12打印event后看下值。

好的