最近整理了下手上的代码,以下是对模型压平的说明。
原理是使用了customShader来重新设置了模型的着色器,通过修改模型顶点的坐标来实现了压平。
废话不多说,下面上代码:
/*** @class* @description 3dtiles模型压平*/
class Flat {/*** * @param {Cesium.Cesium3DTileset} tileset 三维模型* @param {Object} opt * @param {Number} opt.flatHeight 压平高度 */constructor(tileset, opt) {if (!tileset) return;this.tileset = tileset;this.opt = opt || {};this.flatHeight = this.opt.flatHeight || 0;this.center = tileset.boundingSphere.center.clone();this.matrix = Cesium.Transforms.eastNorthUpToFixedFrame(this.center.clone());this.localMatrix = Cesium.Matrix4.inverse(this.matrix, new Cesium.Matrix4());// 多面的坐标数组this.regionList = [];// 多个面坐标转为局部模型坐标this.localPositionsArr = [];}/*** 添加压平面* @param {Object} attr 参数* @param {Cesium.Cartesian3[]} attr.positions 压平面坐标* @param {Number} attr.height 压平深度,当前不支持单独设置* @param {Number} attr.id 唯一标识*/addRegion(attr) {let { positions, height, id } = attr || {};// this.flatHeight = height;if (!id) id = (new Date()).getTime() + "" + Number(Math.random() * 1000).toFixed(0);this.regionList.push(attr);for (let i = 0; i < this.regionList.length; i++) {let item = this.regionList[i];const positions = item.positions;let localCoor = this.cartesiansToLocal(positions);this.localPositionsArr.push(localCoor);}const funstr = this.getIsinPolygonFun(this.localPositionsArr);let str = ``;for (let i = 0; i < this.localPositionsArr.length; i++) {const coors = this.localPositionsArr[i];const n = coors.length;let instr = ``;coors.forEach((coordinate, index) => {instr += `points_${n}[${index}] = vec2(${coordinate[0]}, ${coordinate[1]});\n`;})str += `${instr}if(isPointInPolygon_${n}(position2D)){vec4 tileset_local_position_transformed = vec4(tileset_local_position.x, tileset_local_position.y, ground_z, 1.0);vec4 model_local_position_transformed = czm_inverseModel * u_tileset_localToWorldMatrix * tileset_local_position_transformed;vsOutput.positionMC.xy = model_local_position_transformed.xy;vsOutput.positionMC.z = model_local_position_transformed.z+ modelMC.z*0.002;return;}`;}this.updateShader(funstr, str);}/*** 根据id删除压平的面* @param {String} id 唯一标识*/removeRegionById(id) {if (!id) return;this.regionList = this.regionList.filter((attr) => {return attr.id != id;})this.localPositionsArr = [];for (let i = 0; i < this.regionList.length; i++) {let item = this.regionList[i];const positions = item.positions;let localCoor = this.cartesiansToLocal(positions);this.localPositionsArr.push(localCoor);}const funstr = this.getIsinPolygonFun(this.localPositionsArr);let str = ``;for (let i = 0; i < this.localPositionsArr.length; i++) {const coors = this.localPositionsArr[i];const n = coors.length;let instr = ``;coors.forEach((coordinate, index) => {instr += `points_${n}[${index}] = vec2(${coordinate[0]}, ${coordinate[1]});\n`;})str += `${instr}if(isPointInPolygon_${n}(position2D)){vec4 tileset_local_position_transformed = vec4(tileset_local_position.x, tileset_local_position.y, ground_z, 1.0);vec4 model_local_position_transformed = czm_inverseModel * u_tileset_localToWorldMatrix * tileset_local_position_transformed;vsOutput.positionMC.xy = model_local_position_transformed.xy;vsOutput.positionMC.z = model_local_position_transformed.z+ modelMC.z*0.002;return;}`;}this.updateShader(funstr, str);}/*** 销毁*/destroy() {this.tileset.customShader = undefined;}/*** 根据数组长度,构建 判断点是否在面内 的压平函数*/getIsinPolygonFun(polygons) {let pmap = polygons.map((polygon) => polygon.length);let uniqueArray = this.getUniqueArray(pmap);let str = ``;uniqueArray.forEach(length => {str += `vec2 points_${length}[${length}];bool isPointInPolygon_${length}(vec2 point){int nCross = 0; // 交点数const int n = ${length}; for(int i = 0; i < n; i++){vec2 p1 = points_${length}[i];vec2 p2 = points_${length}[int(mod(float(i+1),float(n)))];if(p1[1] == p2[1]){continue;}if(point[1] < min(p1[1], p2[1])){continue;}if(point[1] >= max(p1[1], p2[1])){continue;}float x = p1[0] + ((point[1] - p1[1]) * (p2[0] - p1[0])) / (p2[1] - p1[1]);if(x > point[0]){nCross++;}}return int(mod(float(nCross), float(2))) == 1;}`})return str}updateShader(vtx1, vtx2) {let flatCustomShader = new Cesium.CustomShader({uniforms: {u_tileset_localToWorldMatrix: {type: Cesium.UniformType.MAT4,value: this.matrix,},u_tileset_worldToLocalMatrix: {type: Cesium.UniformType.MAT4,value: this.localMatrix,},u_flatHeight: {type: Cesium.UniformType.FLOAT,value: this.flatHeight,},},vertexShaderText: `// 所有isPointInPolygon函数${vtx1}void vertexMain(VertexInput vsInput, inout czm_modelVertexOutput vsOutput){vec3 modelMC = vsInput.attributes.positionMC;vec4 model_local_position = vec4(modelMC.x, modelMC.y, modelMC.z, 1.0);vec4 tileset_local_position = u_tileset_worldToLocalMatrix * czm_model * model_local_position;vec2 position2D = vec2(tileset_local_position.x,tileset_local_position.y);float ground_z = 0.0 + u_flatHeight;// 多个多边形区域${vtx2}}`,});this.tileset.customShader = flatCustomShader;}// 数组去重,不能处理嵌套的数组getUniqueArray = (arr) => {return arr.filter(function (item, index, arr) {//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素return arr.indexOf(item, 0) === index;});}// 世界坐标转数组局部坐标cartesiansToLocal(positions) {let arr = [];for (let i = 0; i < positions.length; i++) {let position = positions[i];let localp = Cesium.Matrix4.multiplyByPoint(this.localMatrix,position.clone(),new Cesium.Cartesian3())arr.push([localp.x, localp.y]);}return arr;}}export default Flat;
调用方式:
let flatTool = new Flat(tileset, {flatHeight: -30});flatTool.addRegion({positions : positions,id : new Date().getTime()});
以下是仓库地址:
CesiumExp-tilesetFlat: 3dtiles模型压平