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.depthTestAgainstTerrain为true。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);
效果如下
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);
把这段代码添加到项目中,看下效果:
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);
});
实现的效果:
4. 总结
这里实现的是一个简易的例子,在官网案例3D Tiles Feature Picking中有更丰富的功能,包括鼠标左击后,高亮选中的建筑物体。还有手动去更新infobox的内容,其中还用到了Cesium.PostProcessStageLibrary.createEdgeDetectionStage()函数,这个函数用于强调物体的边缘,使得在复杂场景中物体更易于识别。可以自行尝试看看。