Cesium(八)交互Interactivity -2高亮当前鼠标悬浮处的建筑

573 阅读4分钟

1. 前言

本文主要讲述在3DTiles覆盖的基础上,与模型进行交互。这里我们使用3D tiles是New York City 3D Buildings,具体如何使用可查看八青妹之前发布的cesium(四)3D Tiles。然后结合上篇文章Cesium(七)交互Interactivity -1实时展示当前鼠标经纬度内容, 将鼠标当前建筑的信息展示在实体的label上。然后看下如何在鼠标悬浮的时候高亮当前的建筑。

2. 悬浮展示建筑信息

上篇文章是悬浮展示经纬度,只要在椭球体上,就会有经纬度的值,所以当时的判断条件是

const cartesian = viewer.camera.pickEllipsoid(
  event.endPosition,
  scene.globe.ellipsoid
);
if (cartesian) {
//在地球上
}

但是,我们这里判断的主体不是椭球体,而是3D tiles上的建筑物。这里使用viewer.scene.pick方法检测鼠标指针位置是否有可选的对象。我们看下官网上是如何做的,省流,这里贴下example(cesium官网/pickTranslucentDepth):

// picking the position of a translucent primitive
viewer.screenSpaceEventHandler.setInputAction(function onLeftClick(movement) {
     const pickedFeature = viewer.scene.pick(movement.position);
     if (!Cesium.defined(pickedFeature)) {
         // nothing picked
         return;
     }
     const worldPosition = viewer.scene.pickPosition(movement.position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

前提是一定要设置viewer.scene.globe.depthTestAgainstTerraintrue。example中pickedFeature将包含鼠标位置下的对象(如果有的话)。

这里要设置label中position的高度,每个建筑模型都有Height这个属性,将这个高度值赋予label,让label在鼠标悬浮上方显示。

const entity = viewer.entities.add({
    label: {
      font: "20px sans-serif",
      fillColor: Cesium.Color.WHITE,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      showBackground: true,
      backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.5),
    },
  });
  const scene = viewer.scene;
  const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);

  handler.setInputAction(function onMouseMove(movement) {
    // 鼠标在地球上的位置
    const cartesian = viewer.camera.pickEllipsoid(
      movement.endPosition,
      scene.globe.ellipsoid
    );
    // 拾取要素
    const pickedFeature = viewer.scene.pick(movement.endPosition);
    if (!Cesium.defined(pickedFeature)) {
      entity.label.show = false;
      return;
    } else {
      entity.label.show = true;
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      entity.position = Cesium.Cartesian3.fromRadians(
        cartographic.longitude,
        cartographic.latitude,
        pickedFeature.getProperty("Height")
      );

      entity.label.text = pickedFeature.getProperty("BIN");
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

效果如下 image.png

3. 悬浮高亮当前建筑

但是鼠标悬浮后看到上方的label并不是很清楚是那个建筑物的BIN值,尤其建筑物拥挤的情况下,更是无法分辨。所以接下来,要实现就功能就是高亮当前鼠标选择的建筑物。

3.1 给所有建筑物增加轮廓颜色

在cesium中,通常使用Cesium.PostProcessStageLibrary.isSilhouetteSupported(viewer.scene) 函数来检查当前的 Cesium 场景是否支持轮廓效果(Silhouette Effect)。如果场景对象中没有几何体材质,返回的值为false。当然图形硬件限制和浏览器不兼容也会返回false

增加轮廓效果少不了Cesium.PostProcessStageLibrary.createSilhouetteStage()函数,这个效果用来增强场景中的对象视觉,使其边缘更加突出,也就是可以在边缘添加颜色使其高亮,设置完颜色后,要添加到后处理阶段才会生效。使用方法:

// 创建轮廓效果的后处理阶段
const silhouetteStage = Cesium.PostProcessStageLibrary.createSilhouetteStage(); 
// 设置轮廓颜色 
silhouetteStage.uniforms.color = Cesium.Color.RED; 
// 将轮廓效果添加到后处理阶段 
viewer.postProcessStages.add(silhouetteStage);

把这段代码添加到项目中,看下效果:

image.png

3.2 悬浮高亮当前的建筑物

首先,我们先设置一下高亮的样式

  const silhouetteBlue = Cesium.PostProcessStageLibrary.createSilhouetteStage();
  silhouetteBlue.uniforms.color = Cesium.Color.BLUE;
  silhouetteBlue.uniforms.length = 0.01;
  silhouetteBlue.selected = [];
  viewer.scene.postProcessStages.add(silhouetteBlue);

注意看,这里 silhouetteBlue.selected = []将selected置空,如果不置空,所有的物体模型边框都将应用上该样式。我们这里是需要将指定的物体选中,也就是只需要高亮当前选中的物体。要跟鼠标悬浮展示建筑信息的事件相结合,在之前已经实现的功能中,我们已经获取到了当前的物体pickedFeature

  const pickedFeature = viewer.scene.pick(movement.endPosition);
  const highlightedFeature = silhouetteBlue.selected[0];
  silhouetteBlue.selected = [pickedFeature];

将其pickedFeature覆盖已选的项目。

完整的代码:

onMounted(async () => {
  // 创建一个 Cesium 场景
  Cesium.Ion.defaultAccessToken =
    "xxxxxxxxx";//自行替换
  const viewer = new Cesium.Viewer("cesiumContainer", {
    terrain: await Cesium.Terrain.fromWorldTerrain(),
  });

  // 开启深度测试,确保地形遮挡住模型
  viewer.scene.globe.depthTestAgainstTerrain = true;

  // 设置相机的初始位置和方向
  const initialPosition = Cesium.Cartesian3.fromDegrees(
    -74.01881302800248,
    40.69114333714821,
    753
  );
  const initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(
    21.27879878293835,
    -21.34390550872461,
    0.0716951918898415
  );
  viewer.scene.camera.setView({
    destination: initialPosition,
    orientation: initialOrientation,
    endTransform: Cesium.Matrix4.IDENTITY,
  });

  // 加载 NYC buildings tileset
  try {
    const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(75343);
    viewer.scene.primitives.add(tileset);
  } catch (error) {
    console.log(`Error loading tileset: ${error}`);
  }

  const entity = viewer.entities.add({
    label: {
      font: "20px sans-serif",
      fillColor: Cesium.Color.WHITE,
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      showBackground: true,
      backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.5),
    },
  });
  const scene = viewer.scene;
  const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);

  const silhouetteBlue = Cesium.PostProcessStageLibrary.createSilhouetteStage();
  silhouetteBlue.uniforms.color = Cesium.Color.BLUE;
  silhouetteBlue.uniforms.length = 0.01;
  silhouetteBlue.selected = [];
  viewer.scene.postProcessStages.add(silhouetteBlue);

  handler.setInputAction(function onMouseMove(movement) {
    // 鼠标在地球上的位置
    const cartesian = viewer.camera.pickEllipsoid(
      movement.endPosition,
      scene.globe.ellipsoid
    );
    // 拾取要素
    const pickedFeature = viewer.scene.pick(movement.endPosition);
    if (!Cesium.defined(pickedFeature)) {
      entity.label.show = false;
      silhouetteBlue.selected =[];
      return;
    } else {
      entity.label.show = true;
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      entity.position = Cesium.Cartesian3.fromRadians(
        cartographic.longitude,
        cartographic.latitude,
        pickedFeature.getProperty("Height")
      );

      entity.label.text = pickedFeature.getProperty("BIN");
      const highlightedFeature = silhouetteBlue.selected[0];
      silhouetteBlue.selected = [pickedFeature];
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
});

实现的效果:

hover.png

4. 总结

这里实现的是一个简易的例子,在官网案例3D Tiles Feature Picking中有更丰富的功能,包括鼠标左击后,高亮选中的建筑物体。还有手动去更新infobox的内容,其中还用到了Cesium.PostProcessStageLibrary.createEdgeDetectionStage()函数,这个函数用于强调物体的边缘,使得在复杂场景中物体更易于识别。可以自行尝试看看。