需求:mapbox基于geojson数据生成的二维面虽然可以通过fill-pattern属性去使用图片填充,但是效果并不好(放大缩小时贴图会重新渲染),因此想使用threebox基于geojson数据生成二维面叠加到mapbox地图上,并使用纹理贴图。
threebox库地址如下:
https://github.com/jscastro76/threebox
在研究了threebox目前的代码以后,发现只定义了line、extrusion、tube、Object3D等,首先尝试使用extrusion进行加载,将高度设为0感觉可以实现所需效果,代码如下:
let material = new THREE.MeshPhongMaterial({
color: 0x3E4E6F,
side: THREE.DoubleSide
});
let loader = new THREE.FileLoader();
loader.load('../data/staticRoad.json', (result) => {
let res = JSON.parse(result)
res.features.forEach((data) => {
let polygon = turf.polygon(data.geometry.coordinates)
let center = turf.center(polygon).geometry.coordinates
let extrusion = tb.extrusion({
coordinates: data.geometry.coordinates,
geometryOptions: { curveSegments: 1, bevelEnabled: false, depth: 0 },
materials: material
});
extrusion.setCoords([center[0], center[1], 0]);
tb.add(extrusion)
})
})
将depth设为0发现确实可以实现纯色填充,然后替换纹理材质
let texture = new THREE.TextureLoader().load("images/road.png");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10);
let material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide
});
这样子虽然实现了效果,加载效果如下图,但是由于geojson数据较大加载太慢了(猜测还是因为构建的是三维的extrusion),响应时间在十几秒甚至几十秒,于是放弃了这种做法。
查看了threejs相关文档,发现在threejs中,一般使用ShapeGeometry构建二维面,因此想到在threebox中使用ShapeGeometry构建二维面。
尝试代码:
// 纹理加载器
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load("images/road.png");
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(10, 10);
let picMaterial = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide
});
let loader = new THREE.FileLoader();
loader.load('../data/staticRoad.json', function (result) {
let res = JSON.parse(result)
res.features.forEach((data) => {
let coordinates = data.geometry.coordinates[0]
let shape = new THREE.Shape();
let coords = tb.projectToWorld([coordinates[0][0], coordinates[0][1]])
shape.moveTo(coords.x, coords.y);
for (let i = 1; i < coordinates.length; i++) {
let point = tb.projectToWorld([coordinates[i][0], coordinates[i][1]])
shape.lineTo(point.x, point.y);
}
const geometry = new THREE.ShapeGeometry(shape);
const mesh = new THREE.Mesh(geometry, picMaterial);
tb.add(mesh)
})
});
使用tb.projectToWorld将经纬度转换成世界坐标,虽然加载速度提升很明显,但是这样加载出来以后在放大时会导致面闪动,且纹理贴图紊乱,效果十分不好。
在各种尝试之后发现,为了避免放大面时的闪动和贴图紊乱问题,改变ShapeGeometry构建方式。首先计算所加载面的绝对中心点并转换成世界坐标,再分别将每个坐标点转换成世界坐标并计算出和绝对中心点的偏差值,根据偏差值来构建面,最终将面移动到中心点位置。代码如下:
// 纹理加载器
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load("images/road.png");
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(10, 10);
let material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide
});
let loader = new THREE.FileLoader();
loader.load('../data/staticRoad.json', function (result) {
let res = JSON.parse(result)
res.features.forEach((data) => {
let coordinates = data.geometry.coordinates[0]
let polygon = turf.polygon([coordinates]);
var shape = new THREE.Shape();
let center = turf.center(polygon).geometry.coordinates;
let centerWorld = tb.projectToWorld([center[0], center[1]]);
for(let i = 0;i<coordinates.length;i++){
let point = tb.projectToWorld([coordinates[i][0], coordinates[i][1]]);
if(i === 0 ){
shape.moveTo(point.x-centerWorld.x, point.y-centerWorld.y);
}else{
shape.lineTo(point.x-centerWorld.x, point.y-centerWorld.y);
}
}
let geometry = new THREE.ShapeGeometry(shape);
let mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(centerWorld)
})
});
注意计算偏差值时用该点坐标减去中心点坐标。
大功告成,加载速度完美~