在cesium中实现地形的压平功能
1.功能分析
cesium中有地形功能,能看到凹凸不平的山丘,所以有些时候模型和地形会存在重叠的情况,这时为了突出模型需要把地形中多余的部分进行压平处理。
实现步骤一般为选择一块区域,设置压平高度,实现压平
2.基本架构设计
先封装实现区域选择的操作
ts
import {
CallbackProperty,
Cartesian3,
Cartographic,
Color,
defined,
Entity,
Math,
PolygonHierarchy,
ScreenSpaceEventHandler,
ScreenSpaceEventType
} from 'cesium';
class PlanishOperate {
private app: any;
private viewerCesium: any;
private handler: ScreenSpaceEventHandler | undefined;
private pointArray: Array<number>;
private pointEntityArray: Array<any>;
private polygonEntity: Entity | null;
public planishHeight: number | null;
constructor(app: any) {
this.app = app;
this.viewerCesium = this.app.getViewerCesium();
this.pointArray = [];
this.pointEntityArray = [];
this.polygonEntity = null;
this.planishHeight = null;
}
/**
* 设置压平高度
* @param height 高度值
*/
public setPlanishHeight(height: number) {
this.planishHeight = height;
}
/**
* 开启绘制区域
* @param successCallback 绘制成功后的回调函数
*/
public startDrawArea(successCallback: Function) {
if (!this.handler) {
// 构造屏幕空间事件句柄,用于后面的鼠标交互
this.handler = new ScreenSpaceEventHandler(this.viewerCesium.scene.canvas);
}
// 绑定鼠标左键点击事件
this.handler.setInputAction((e: any) => this.innerLeftClickAction(e), ScreenSpaceEventType.LEFT_CLICK);
// 绑定鼠标移动事件
this.handler.setInputAction((e: any) => this.innerMouseMoveAction(e), ScreenSpaceEventType.MOUSE_MOVE);
// 绑定鼠标右键点击事件
this.handler.setInputAction(
(e: any) => this.innerRightClickAction(e, successCallback),
ScreenSpaceEventType.RIGHT_CLICK
);
}
/**
* 关闭绘制区域
*/
public endDrawArea() {
this.handler?.removeInputAction(ScreenSpaceEventType.LEFT_CLICK);
this.handler?.removeInputAction(ScreenSpaceEventType.MOUSE_MOVE);
this.handler?.removeInputAction(ScreenSpaceEventType.RIGHT_CLICK);
}
// 右键结束区域绘制
public innerRightClickAction(e: any, successCallback: Function) {
this.pointArray.pop();
this.pointArray.pop();
this.pointArray.push(this.pointArray[0]);
this.pointArray.push(this.pointArray[1]);
if (successCallback) successCallback(this.pointArray, this.planishHeight);
this.cancelDrawArea();
}
// 左键开始区域绘制
private innerLeftClickAction(movement: any) {
let ray = this.viewerCesium.camera.getPickRay(movement.position);
let cartesian = this.viewerCesium.scene.globe.pick(ray, this.viewerCesium.scene);
// 是否获取到空间坐标
if (defined(cartesian)) {
this.drawPoint(cartesian);
let cartographic = Cartographic.fromCartesian(cartesian);
// 弧度转为经纬度
let lon = Math.toDegrees(cartographic.longitude);
let lat = Math.toDegrees(cartographic.latitude);
this.pointArray.push(lon);
this.pointArray.push(lat);
}
if (this.pointArray.length === 6) {
let polygonEntity = new Entity({
name: '拍平区域面对象',
polygon: {
hierarchy: new CallbackProperty(() => {
return new PolygonHierarchy(Cartesian3.fromDegreesArray(this.pointArray));
}, false),
material: Color.RED.withAlpha(0.5)
}
});
this.viewerCesium.entities.add(polygonEntity);
this.polygonEntity = polygonEntity;
}
}
// 绘制点
private drawPoint(position: Cartesian3) {
let pointEntity = new Entity({
name: '拍平区域点对象',
position: position,
point: {
color: Color.SKYBLUE,
pixelSize: 10,
outlineColor: Color.YELLOW,
outlineWidth: 3,
disableDepthTestDistance: Number.POSITIVE_INFINITY
}
});
this.viewerCesium.entities.add(pointEntity);
this.pointEntityArray.push(pointEntity);
}
// 鼠标移动绘制区域面
private innerMouseMoveAction(movement: any) {
let ray = this.viewerCesium.camera.getPickRay(movement.endPosition);
let cartesian = this.viewerCesium.scene.globe.pick(ray, this.viewerCesium.scene);
// 是否获取到空间坐标
if (defined(cartesian)) {
let cartographic = Cartographic.fromCartesian(cartesian);
// 弧度转为经纬度
let lon = Math.toDegrees(cartographic.longitude);
let lat = Math.toDegrees(cartographic.latitude);
this.pointArray.pop();
this.pointArray.pop();
this.pointArray.push(lon);
this.pointArray.push(lat);
}
}
/**
* 取消区域绘制
*/
public cancelDrawArea() {
this.viewerCesium.entities.remove(this.polygonEntity);
this.polygonEntity = null;
this.pointEntityArray.forEach((item) => {
this.viewerCesium.entities.remove(item);
});
this.pointEntityArray = [];
this.pointArray = [];
this.endDrawArea();
}
}
export { PlanishOperate };
封装地形压平的数据项
ts
class PlanishArea {
// 平整区域名称
name: string;
/** uuid */
uuid: string;
// 平整区域点数组
area: Array<number>;
// 平整高度
height: number | null;
show: boolean;
constructor() {
this.name = '区域';
this.uuid = this.createUUID();
this.area = [];
this.height = null;
this.show = true;
}
private createUUID() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4();
}
}
class PlanishOptions {
// 版本号
version: string;
// 自定义场地平整数据
customPlanishArr: PlanishArea[];
constructor() {
this.version = '1.0';
this.customPlanishArr = [];
}
public copyData(PlanishOptions: PlanishOptions) {
if (!PlanishOptions) return;
this.version = PlanishOptions.version;
this.customPlanishArr = PlanishOptions.customPlanishArr;
}
}
export { PlanishOptions, PlanishArea };
封装业务代码操作
ts
import { PlanishOperate } from './PlanishOperate';
import { PlanishArea, PlanishOptions } from './PlanishOptions';
class PlanishManager {
planishOperate: PlanishOperate;
planishOptions: PlanishOptions;
viewerCesium: any;
constructor(app: any) {
super(app);
this.planishOperate = new PlanishOperate(app);
this.planishOptions = new PlanishOptions();
this.viewerCesium = this.app.getViewerCesium();
}
protected doGetSceneData(): any {
return this.planishOptions;
}
protected doSetSceneData(sceneData: PlanishOptions): void {
this.planishOptions.copyData(sceneData);
this.addTerrainEditsDataArr(this.planishOptions.customPlanishArr);
}
/**
* 开启绘制区域
* @param successCallback 绘制成功后的回调函数
*/
public startDrawArea(successCallback: Function) {
// 调用PlanishOperate操作器里的绘制区域
this.planishOperate.startDrawArea((areaData: Array<any>, height: number) => {
// 绘制区域成功后在回调里进行业务操作
let num = this.planishOptions.customPlanishArr.length + 1;
let name = '区域' + num;
let planishArea = new PlanishArea();
planishArea.name = name;
planishArea.area = areaData;
planishArea.height = height;
this.planishOptions.customPlanishArr.push(planishArea);
this.savePlanish(planishArea.uuid, areaData, height);
if (successCallback) successCallback(planishArea);
});
}
/**
* 进行地形压平处理
* @param uuid 唯一标识符
* @param areaData 压平区域数据
* @param height 压平高度
*/
public savePlanish(uuid: string, areaData: Array<any>, height: number) {
let terrainProvider = this.viewerCesium.scene.terrainProvider;
// 进行压平处理
terrainProvider.addTerrainEditsData(uuid, areaData, height);
this.viewerCesium.scene.globe._surface.invalidateAllTiles();
}
/**
* 地形压平批量处理
* @param arr PlanishArea类型的数组
*/
public addTerrainEditsDataArr(arr: PlanishArea[]) {
let terrainProvider = this.viewerCesium.scene.terrainProvider;
// 进行压平处理
terrainProvider.addTerrainEditsDataArr(arr);
this.viewerCesium.scene.globe._surface.invalidateAllTiles();
}
/**
* 删除压平区域
* @param uuid 压平区域的uuid
* @param successFunc 删除成功后的回调
*/
public removePlanish(uuid: string, successFunc: Function) {
for (let i = 0; i < this.planishOptions.customPlanishArr.length; i++) {
let item = this.planishOptions.customPlanishArr[i];
if (item.uuid === uuid) {
this.planishOptions.customPlanishArr.splice(i, 1);
let terrainProvider = this.viewerCesium.scene.terrainProvider;
// 删除对应的压平区域
terrainProvider.removeTerrainEditsData(uuid);
this.viewerCesium.scene.globe._surface.invalidateAllTiles();
successFunc();
}
}
}
/**
* 编辑压平区域高度
* @param uuid 压平区域的uuid
* @param heightValue 高度值
* @param successFunc 编辑成功后的回调
*/
public editPlanishHeight(uuid: string, heightValue: number, successFunc: Function) {
for (let i = 0; i < this.planishOptions.customPlanishArr.length; i++) {
let item = this.planishOptions.customPlanishArr[i];
if (item.uuid === uuid) {
item.height = heightValue;
let terrainProvider = this.viewerCesium.scene.terrainProvider;
// 设置对应的压平区域高度
terrainProvider.setTerrainEditsHeight(item.uuid, heightValue);
this.viewerCesium.scene.globe._surface.invalidateAllTiles();
successFunc();
}
}
}
/**
* 编辑压平区域的显隐
* @param uuid 压平区域的uuid
* @param visible 显隐值
* @param successFunc 编辑成功后的回调
*/
public editPlanishVisible(uuid: string, visible: boolean, successFunc: Function) {
for (let i = 0; i < this.planishOptions.customPlanishArr.length; i++) {
let item = this.planishOptions.customPlanishArr[i];
if (item.uuid === uuid) {
item.show = visible;
let terrainProvider = this.viewerCesium.scene.terrainProvider;
// 设置对应的压平区域显隐
terrainProvider.setTerrainEditsVisible(item.uuid, visible);
this.viewerCesium.scene.globe._surface.invalidateAllTiles();
successFunc();
}
}
}
/**
* 取消区域绘制
*/
public cancelPlanish() {
this.planishOperate.cancelDrawArea();
}
/**
* 设置压平区域高度
* @param height 压平高度
*/
public setPlanishHeight(height: number) {
this.planishOperate.setPlanishHeight(height);
}
}
export { PlanishManager };
3.核心代码实现
压平的核心实现在terrainProvider
ts
this.viewerCesium.scene.terrainProvider = createWorldTerrain(); //cesium默认的世界地形图
要实现地形压平就需要在地形加载的时候执行到特殊区域时,对这块区域的地形顶点数据进行处理,设置顶点高度到指定的压平高度
所以修改一下createWorldTerrain.js的源码,修改内部new CesiumTerrainProvider为new CesiumTerrainProviderEdit
实现CesiumTerrainProviderEdit
ts
import { CesiumTerrainProvider, Math as CesiumMath } from 'cesium';
import * as turf from '@turf/turf';
import { LoopSubdivision } from '../../gis-modules/gisCommon/loopSubdivision.js';
/**
* 对CesiumTerrainProvider进行扩展,修改地形数据,实现场地平整功能
*/
const MAX_SHORT = 32767;
class CesiumTerrainProviderEdit extends CesiumTerrainProvider {
constructor({ url, modelEdits }) {
super({ url });
// 存储多边形压平区域的数组
this._polygonEdits = [];
}
/**
* 过滤地形瓦片,如果这块地形瓦片在压平区域内则进行压平处理
* @param {*} x 瓦片的x
* @param {*} y 瓦片的y
* @param {*} level 瓦片的层级
* @param {*} terrainData 瓦片的地形数据
* @returns
*/
filterTerrainTile(x, y, level, terrainData) {
if (this._polygonEdits.length === 0) {
return terrainData;
}
// 获取对应瓦片的矩形数据
const tileRectangle = this.tilingScheme.tileXYToRectangle(x, y, level);
const tilePolygon = rectangleToPolygon(tileRectangle);
// 通过truf提供的算法来筛选压平区域(booleanOverlap 判断是否重叠;booleanContains 判断是否包含)
const relevantEdits = this._polygonEdits.filter(
(edit) =>
edit.show && (turf.booleanOverlap(edit.polygon, tilePolygon) || turf.booleanContains(edit.polygon, tilePolygon))
);
if (relevantEdits.length === 0) {
return terrainData;
}
if (level >= 18) {
subdivisionTerrainTile(terrainData);
}
// 地形瓦片细分好后,修改地形
let modifyData = modifyTerrainTile(terrainData, tileRectangle, relevantEdits);
return modifyData;
}
/**
* 添加地形压平区域数据
* @param {*} uuid 压平区域的uuid
* @param {*} area 压平区域数据
* @param {*} height 压平高度
* @returns
*/
addTerrainEditsData(uuid, area, height) {
let arr = [];
for (let i = 0; i < area.length; i += 2) {
if (area[i]) {
arr.push([area[i], area[i + 1]]);
}
}
for (let p = 0; p < this._polygonEdits.length; p++) {
if (this._polygonEdits[p].uuid === uuid) {
return;
}
}
if (arr.length === 0) return;
// 把多边形压平区域的数据对象存储起来,用于后续地形处理
this._polygonEdits.push({
uuid: uuid,
show: true,
polygon: turf.polygon([arr]),
height: height
});
}
/**
* 业务需要封装根据压平区域数据的数组添加(内部实现与addTerrainEditsData类似)
* @param {*} dataArr 压平区域数据的对象数组
* @returns
*/
addTerrainEditsDataArr(dataArr) {
for (let d = 0; d < dataArr.length; d++) {
let item = dataArr[d];
let arr = [];
for (let i = 0; i < dataArr[d].area.length; i += 2) {
if (dataArr[d].area[i]) {
arr.push([item.area[i], item.area[i + 1]]);
}
}
for (let p = 0; p < this._polygonEdits.length; p++) {
if (this._polygonEdits[p].uuid === item.uuid) {
return;
}
}
if (arr.length === 0) return;
this._polygonEdits.push({
uuid: item.uuid,
show: item.show,
polygon: turf.polygon([arr]),
height: item.height
});
}
}
/**
* 删除压平区域
* @param {*} uuid 压平区域的uuid
*/
removeTerrainEditsData(uuid) {
for (let i = 0; i < this._polygonEdits.length; i++) {
if (this._polygonEdits[i].uuid === uuid) {
this._polygonEdits.splice(i, 1);
}
}
}
/**
* 设置压平区域的显隐
* @param {*} uuid 压平区域的uuid
* @param {*} visible 显隐值
*/
setTerrainEditsVisible(uuid, visible) {
for (let i = 0; i < this._polygonEdits.length; i++) {
if (this._polygonEdits[i].uuid === uuid) {
this._polygonEdits[i].show = visible;
}
}
}
/**
* 设置压平区域高度
* @param {*} uuid 压平区域的uuid
* @param {*} height 压平高度
*/
setTerrainEditsHeight(uuid, height) {
for (let i = 0; i < this._polygonEdits.length; i++) {
if (this._polygonEdits[i].uuid === uuid) {
this._polygonEdits[i].height = height;
}
}
}
}
/**
*
* @param {*} tileRectangle 经度和纬度坐标的二维区域
* @returns
*/
function rectangleToPolygon(tileRectangle) {
// 将弧度转换为度数
let westD = CesiumMath.toDegrees(tileRectangle.west);
let southD = CesiumMath.toDegrees(tileRectangle.south);
let eastD = CesiumMath.toDegrees(tileRectangle.east);
let northD = CesiumMath.toDegrees(tileRectangle.north);
// 使用turf.js进行构造多边形
let polygon = turf.polygon([
[
[westD, northD],
[eastD, northD],
[eastD, southD],
[westD, southD],
[westD, northD]
]
]);
return polygon;
}
/**
* 细分地形瓦片(进行细分的目的是解决压平的多边形区域的边缘处粗糙问题)
* @param {*} terrainData 瓦片的地形数据
*/
function subdivisionTerrainTile(terrainData) {
// 对地形数据中的position和index进行处理,改成便于网格化细分的数据
positionToNonIndexed(terrainData);
// 进行网格化细分(LoopSubdivision是我封装好的网格化细分算法)
let quantizedVerticesNew = LoopSubdivision.modify(terrainData._quantizedVertices, 4, 2500);
terrainData._quantizedVertices = quantizedVerticesNew;
let indicesLength = quantizedVerticesNew.length / 3;
let newIndices = new Uint16Array(indicesLength);
for (let i = 0; i < indicesLength; i++) {
newIndices[i] = i;
}
terrainData._indices = newIndices;
}
/**
* 将postion与index对应的这种优化方式改为position与index无索引优化的方式
* @param {*} terrainData 瓦片的地形数据
*/
function positionToNonIndexed(terrainData) {
// position与index的这种索引优化可以复用position数据,减少内存,但是在后续进行网格化细分的时候不好计算处理,所以先进行这步处理。
let quantizedVertices = terrainData._quantizedVertices;
let indices = terrainData._indices;
let itemSize = 3;
let indicesLength = indices.length;
let quanLength = quantizedVertices.length / 3;
const quantizedVerticesNew = new Uint16Array(indicesLength * itemSize);
const indicesNew = new Uint16Array(indicesLength);
for (let i = 0, len = indicesLength; i < len; i++) {
let index = indices[i];
quantizedVerticesNew[i] = quantizedVertices[index];
quantizedVerticesNew[i + indicesLength] = quantizedVertices[index + quanLength];
quantizedVerticesNew[i + indicesLength * 2] = quantizedVertices[index + quanLength * 2];
indicesNew[i] = i;
}
let westIndices = changeWsenIndices(terrainData._westIndices, quantizedVertices, quantizedVerticesNew);
let southIndices = changeWsenIndices(terrainData._southIndices, quantizedVertices, quantizedVerticesNew);
let eastIndices = changeWsenIndices(terrainData._eastIndices, quantizedVertices, quantizedVerticesNew);
let northIndices = changeWsenIndices(terrainData._northIndices, quantizedVertices, quantizedVerticesNew);
terrainData._quantizedVertices = quantizedVerticesNew;
terrainData._indices = indicesNew;
terrainData._westIndices = westIndices;
terrainData._southIndices = southIndices;
terrainData._eastIndices = eastIndices;
terrainData._northIndices = northIndices;
}
function changeWsenIndices(indices, quantizedVertices, quantizedVerticesNew) {
let quanLength = quantizedVertices.length / 3;
let quanNewLength = quantizedVerticesNew.length / 3;
let indicesNew = [];
for (let w = 0, wl = indices.length; w < wl; w++) {
let index = indices[w];
let p1 = quantizedVertices[index];
let p2 = quantizedVertices[index + quanLength];
let p3 = quantizedVertices[index + quanLength * 2];
for (let p = 0; p < quanNewLength; p++) {
if (
p1 === quantizedVerticesNew[p] &&
p2 === quantizedVerticesNew[p + quanNewLength] &&
p3 === quantizedVerticesNew[p + quanNewLength * 2]
) {
indicesNew.push(p);
break;
}
}
}
return indicesNew;
}
/**
* 对瓦片数据进行修改,实现压平(对瓦片和压平区域进行范围判断处理,范围内的顶点修改高度值)
* @param {*} terrainData 瓦片的地形数据
* @param {*} tileRectangle 经度和纬度坐标的二维区域
* @param {*} relevantEdits 要压平的区域
* @returns
*/
function modifyTerrainTile(terrainData, tileRectangle, relevantEdits) {
const data = terrainData;
const minimumHeight = data._minimumHeight;
const maximumHeight = data._maximumHeight;
const quantizedVertices = data._quantizedVertices;
const vertexCount = quantizedVertices.length / 3;
const positions = [];
for (let i = 0; i < vertexCount; i++) {
const rawU = quantizedVertices[i];
const rawV = quantizedVertices[i + vertexCount];
const rawH = quantizedVertices[i + vertexCount * 2];
const u = rawU / MAX_SHORT;
const v = rawV / MAX_SHORT;
const longitude = CesiumMath.toDegrees(CesiumMath.lerp(tileRectangle.west, tileRectangle.east, u));
const latitude = CesiumMath.toDegrees(CesiumMath.lerp(tileRectangle.south, tileRectangle.north, v));
let height = CesiumMath.lerp(minimumHeight, maximumHeight, rawH / MAX_SHORT);
const currentPoint = turf.point([longitude, latitude]);
const relevantEdit = relevantEdits.find((edit) => turf.booleanPointInPolygon(currentPoint, edit.polygon));
if (relevantEdit) {
if (relevantEdit.height !== null) {
height = relevantEdit.height;
}
}
positions.push([longitude, latitude, height]);
}
const heights = positions.map((p) => p[2]);
const newMinHeight = Math.min(...heights);
const newMaxHeight = Math.max(...heights);
if (newMaxHeight - newMinHeight === 0) {
positions.forEach((p, i) => {
const relativeHeight = Math.round(CesiumMath.lerp(0, MAX_SHORT, (p[2] - newMinHeight) / 1));
quantizedVertices[i + positions.length * 2] = relativeHeight;
});
} else {
positions.forEach((p, i) => {
const relativeHeight = Math.round(
CesiumMath.lerp(0, MAX_SHORT, (p[2] - newMinHeight) / (newMaxHeight - newMinHeight))
);
quantizedVertices[i + positions.length * 2] = relativeHeight;
});
}
data._minimumHeight = newMinHeight;
data._maximumHeight = newMaxHeight;
return data;
}
export default CesiumTerrainProviderEdit;
// https://community.cesium.com/t/terrain-editing-on-the-fly/9695/6
由于地形瓦片上的顶点数据比较粗糙,压平的时候在范围的边缘处锯齿化严重,所以需要对地形上的顶点进行细分,分成更多的顶点来减少误差
实现地形网格化细分算法LoopSubdivision
ts
class Vector3 {
constructor(x, y, z) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
set(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
copy(v) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
return this;
}
add(v) {
this.x += v.x;
this.y += v.y;
this.z += v.z;
return this;
}
divideScalar(scalar) {
return this.multiplyScalar(1 / scalar);
}
multiplyScalar(scalar) {
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
return this;
}
}
const _vector0 = new Vector3();
const _vector1 = new Vector3();
const _vector2 = new Vector3();
const _vec0to1 = new Vector3();
const _vec1to2 = new Vector3();
const _vec2to0 = new Vector3();
class LoopSubdivision {
/**
* 对顶点的三角形进行细分
* @param {*} positionArray 顶点数据
* @param {*} iterations 递归细分次数
* @param {*} maxTriangles 细分的最大三角形数量
* @returns
*/
static modify(positionArray, iterations = 1, maxTriangles = Infinity) {
let modifiedPosition = [...positionArray];
for (let i = 0; i < iterations; i++) {
let currentTriangles = modifiedPosition.length / 3;
if (currentTriangles < maxTriangles) {
let subdividedPosition = LoopSubdivision.flatAttribute(modifiedPosition);
modifiedPosition = [];
modifiedPosition = subdividedPosition;
}
}
return modifiedPosition;
}
/**
* 一个三角形细分成四个三角形
* @param {*} positionArray
* @returns
*/
static flatAttribute(positionArray) {
const vertexCount = positionArray.length / 3;
const arrayLength = vertexCount * 3 * 4;
const newPositionArray = new Float32Array(arrayLength);
let index = 0;
for (let i = 0; i < vertexCount; i += 3) {
let x0 = i + 0;
let y0 = i + 0 + vertexCount;
let z0 = i + 0 + vertexCount * 2;
_vector0.set(positionArray[x0], positionArray[y0], positionArray[z0]);
let x1 = i + 1;
let y1 = i + 1 + vertexCount;
let z1 = i + 1 + vertexCount * 2;
_vector1.set(positionArray[x1], positionArray[y1], positionArray[z1]);
let x2 = i + 2;
let y2 = i + 2 + vertexCount;
let z2 = i + 2 + vertexCount * 2;
_vector2.set(positionArray[x2], positionArray[y2], positionArray[z2]);
// /\
// / \
// /_ _ \
// /\ /\
// / \ / \
// /_ _ \/_ _ \
// 取出三个顶点后,每两个顶点之间取中间点
_vec0to1.copy(_vector0).add(_vector1).divideScalar(2.0);
_vec1to2.copy(_vector1).add(_vector2).divideScalar(2.0);
_vec2to0.copy(_vector2).add(_vector0).divideScalar(2.0);
// 构造三角形
setTriangle(newPositionArray, index, _vector0, _vec0to1, _vec2to0);
index += 3;
setTriangle(newPositionArray, index, _vector1, _vec1to2, _vec0to1);
index += 3;
setTriangle(newPositionArray, index, _vector2, _vec2to0, _vec1to2);
index += 3;
setTriangle(newPositionArray, index, _vec0to1, _vec1to2, _vec2to0);
index += 3;
}
return newPositionArray;
}
}
function setTriangle(positions, index, vec0, vec1, vec2) {
let vertexCount = positions.length / 3;
positions[index + 0] = vec0.x;
positions[index + 0 + vertexCount] = vec0.y;
positions[index + 0 + vertexCount * 2] = vec0.z;
positions[index + 1] = vec1.x;
positions[index + 1 + vertexCount] = vec1.y;
positions[index + 1 + vertexCount * 2] = vec1.z;
positions[index + 2] = vec2.x;
positions[index + 2 + vertexCount] = vec2.y;
positions[index + 2 + vertexCount * 2] = vec2.z;
}
export { LoopSubdivision };
最后还需修改一处位置,在GlobeSurfaceTile.js中加载地形时,调用我们新增的函数,对地形数据进行过滤压平