threebox加载基于geojson三维面数据

3 篇文章 0 订阅
1 篇文章 0 订阅

继上一期使用threebox加载geojson二维面数据,新需求是加载三维面。

因threejs的ShapeGeometry只支持x,y坐标,无z值,因此研究方向为BufferGeometry。

 

BufferGeometry主要通过绘制小三角形来构成所需的图形,需构建一个position数组来存储所有点的坐标,然后通过setAttribute和setIndex方法来实现BufferGeometry的绘制。

setAttribute方法用于设置顶点属性。它接受三个参数:属性名称、BufferAttribute对象和每个元素的大小。属性名称可以是位置(position)、法线(normal)、颜色(color)等。BufferAttribute对象包含实际的属性数据,可以通过将数组传递给它的构造函数来创建。每个元素的大小表示每个属性元素所占用的字节数,例如位置属性通常是3个浮点数(x、y、z),所以大小为3。

setIndex方法用于设置索引属性,用于定义构成几何体的三角形面。它接受一个BufferAttribute对象作为参数,其中包含了索引数据。索引数据是一个无符号整数数组,表示顶点的索引顺序。

根据BufferGeometry的特性,研究重点应该在如何将带有高度属性的geojson面数据划分成一个个小三角形。首先想到了turf.js的tesselate方法,代码如下:

    let coordinates = [
      [120.63821315568913, 31.426714841676258, 10],
      [120.6383673932828, 31.42698392461608, 10],
      [120.63771903773284, 31.427314807183862, 20],
      [120.63724293643935, 31.427577060156693, 10],
      [120.63710134038462, 31.42739036386199, 10],
      [120.63758972602409, 31.427099356271654, 20],
      [120.63821315568913, 31.426714841676258, 10]
    ]
    let newArray = []
    let newArray2 = []
    let newArray3 = []
    coordinates.forEach((v) => {
      // 转成three.js的坐标
      newArray.push([tb.projectToWorld([v[0], v[1], v[2]]).x, tb.projectToWorld([v[0], v[1], v[2]]).y])
      // 保留原始二维坐标
      newArray2.push([v[0], v[1]])
    })

    let polygon = turf.polygon([newArray]);
    let polygon2 = turf.polygon([newArray2]);
    let center = turf.center(polygon2).geometry.coordinates;// polygon的中心点
    let center2 = tb.projectToWorld([center[0],center[1]]);// polygon的中心点转three.js坐标

    coordinates.forEach((v) => {
      // 构建position数组
      newArray3.push(tb.projectToWorld([v[0], v[1], v[2]]).x - center2.x, tb.projectToWorld([v[0], v[1], v[2]]).y - center2.y, tb.projectToWorld([v[0], v[1], v[2]]).z)
    })

    // 使用turf.js的tesselate分割
    let triangles = turf.tesselate(polygon);
    const indexArray = []
    triangles.features.forEach((item) => {
      for (let i = 0; i < 3; i++) {
        // 寻找点对应的index
        let index = newArray.findIndex(v => v[0] === item.geometry.coordinates[0][i][0] && v[1] === item.geometry.coordinates[0][i][1])
        indexArray.push(index)
      }
    })

    let geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(newArray3, 3));
    geometry.setIndex(indexArray);
    let material = new THREE.MeshBasicMaterial({
      color: 0xff00ff,
      side: THREE.DoubleSide, //两面可见
    }); //材质对象Material
    //合成对象
    let mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    mesh.position.copy(center2)

这样加载以后存在问题,如图:

 打印查看indexArray数组,发现原因是使用turf.js的tesselate构造的三角形不太正确,tesselate构造的三角形index数组为:[5, 0, 1, 2, 3, 4, 4, 5, 1, 1, 2, 4],但是正确的应该是[0,1,2,0,2,5,2,3,5,3,4,5]之类的,错误地构建了4,5,1和1,2,4两个三角形。

 于是重新找分割方法,尝试使用threejs-earcut方法进行分割,代码如下:

    //三维顶点数据
    let arr = [
      10, 10, 5,
      20, 10, 10,
      30, 10, 5,
      30, 20, 5,
      20, 20, 10,
      10, 20, 5,
    ]
    let geometry = new THREE.BufferGeometry();
    let vertices = new Float32Array(arr);
    // 创建属性缓冲区对象
    let attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
    // 设置几何体attributes属性的位置position属性
    geometry.attributes.position = attribue
    // 三角形顶点索引计算
    let trianglesIndex = earcut(arr, null, 3);
    // Uint16Array类型数组创建顶点索引数据
    let indexes = new Uint16Array(trianglesIndex)
    // 索引数据赋值给几何体的index属性
    geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组
    // 不执行computeVertexNormals,没有顶点法向量数据
    geometry.computeVertexNormals(); //不计算法线,表面比较暗,计算了比较亮,
    //材质对象
    var material = new THREE.MeshLambertMaterial({
      color: 0x0000ff, //三角面颜色
      side: THREE.DoubleSide, //两面可见
    });
    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    let center = tb.projectToWorld([120.63773436683371, 31.427145950916476]);
    mesh.position.copy(center)

构建了一个类似的形状放到指定位置,如下图所示,发现还是构建的三角形有问题,同使用turf.js的tesselate构造的三角形。

 于是又重新找分割三角形的方法,找到d3.js的Delaunay三角形构建方法。代码如下:

    let coordinates = [
      [120.63821315568913, 31.426714841676258, 10],
      [120.6383673932828, 31.42698392461608, 10],
      [120.63771903773284, 31.427314807183862, 20],
      [120.63724293643935, 31.427577060156693, 10],
      [120.63710134038462, 31.42739036386199, 10],
      [120.63758972602409, 31.427099356271654, 20],
      [120.63821315568913, 31.426714841676258, 10]
    ]
    let newArray = []
    let points = []
    coordinates.forEach((v) => {
      newArray.push([v[0], v[1]])
    })

    // 创建Delaunay对象
    const delaunay = d3.Delaunay.from(newArray);

    // 获取三角形的索引数组
    const triangles = delaunay.triangles;
    let polygon = turf.polygon([newArray]);
    const array2 = []
    // 将三角形索引数组转化为三角形坐标数组
    for (let i = 0; i < triangles.length; i += 3) {
      array2.push(triangles[i], triangles[i + 1], triangles[i + 2])
    }

    let center = turf.center(polygon).geometry.coordinates;
    let center2 = tb.projectToWorld([center[0], center[1]]);
    coordinates.forEach((v) => {
      points.push(center2.x - tb.projectToWorld([v[0], v[1], v[2]]).x, center2.y - tb.projectToWorld([v[0], v[1], v[2]]).y, tb.projectToWorld([v[0], v[1], v[2]]).z)
    })

    let geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
    geometry.setIndex(array2);
    let material2 = new THREE.MeshBasicMaterial({
      color: 0xff00ff,
      side: THREE.DoubleSide, //两面可见
      wireframe: false
    }); //材质对象Material
    material2.transparent = true;
    material2.opacity = 0.5
    //合成对象
    let mesh = new THREE.Mesh(geometry, material2); //网格模型对象Mesh
    mesh.position.copy(center2)

这次能够加载出来正确的形状,但是多构建了两个面,即1,2,3和0,4,5两个面。

加载效果如下:

 

经测试发现,d3.js的Delaunay三角形构建方法在不同情况下构建面的形式不同,如左下图会构建六个三角形,右边会构建四个三角形。

 其他数据经测试也可以使用d3.js的Delaunay三角形构建方法加载。

 本次研究到此,如果有更好的方法可以解决多构建面的情况欢迎评论探讨!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值