「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」
简介
在可视化开发中飞线图应该是很常见的一种。为了灵活的使用three.js
,本节我们在3D地图上绘制一个简单的飞线图。
绘制
- 在上一节已经绘制好了一个中国地图,我们直接在此基础上开发。
起始点添加动画
- 在飞线图的起始点都会有醒目的标识。这里实现一个扩散光圈效果。
- 起始点会有很多,我们要创建一个公用方法。
- 先在起始点坐标,绘制一个平面圆和一个平面圆环。在渲染函数中对平面圆环放大和透明。
// 圆环网格对象组
const circleYs = []
/**
* 目标位置
* */
function spotCircle(spot) {
// 圆
const geometry1 = new THREE.CircleGeometry(0.5, 200)
const material1 = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide })
const circle = new THREE.Mesh(geometry1, material1)
// 绘制地图时 y轴取反 这里同步
circle.position.set(spot[0], -spot[1], spot[2] + 0.1)
scene.add(circle)
// 圆环
const geometry2 = new THREE.RingGeometry(0.5, 0.7, 50)
// transparent 设置 true 开启透明
const material2 = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true })
const circleY = new THREE.Mesh(geometry2, material2)\
// 绘制地图时 y轴取反 这里同步
circleY.position.set(spot[0], -spot[1], spot[2] + 0.1)
scene.add(circleY)
circleYs.push(circleY)
}
- 渲染函数中放大圆环同时修改其透明度。
// 渲染函数
function render() {
circleYs.forEach(function (mesh) {
// 目标 圆环放大 并 透明
mesh._s += 0.01
mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s)
if (mesh._s <= 2) {
mesh.material.opacity = 2 - mesh._s
} else {
mesh._s = 1
}
})
}
起始点连线
- 使用
.QuadraticBezierCurve3()
创建三维二次贝塞尔曲线。 - 使用自定义方法
spotCircle()
传入三维坐标,绘制目标点 - 为了线更好看使用
.BufferGeometry()
对线的每一个顶点设置颜色,让线有渐变色。
/**
* 两点链接飞线
* */
function lineConnect(posStart, posEnd) {
// 根据目标坐标设置3D坐标 z轴位置在地图表面
const [x0, y0, z0] = [...posStart, 10.01]
const [x1, y1, z1] = [...posEnd, 10.01]
// 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线
const curve = new THREE.QuadraticBezierCurve3(
new THREE.Vector3(x0, -y0, z0),
new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
new THREE.Vector3(x1, -y1, z1)
)
// 绘制 目标位置
spotCircle([x0, y0, z0])
spotCircle([x1, y1, z1])
const lineGeometry = new THREE.BufferGeometry()
// 获取曲线 上的50个点
var points = curve.getPoints(50)
var positions = []
var colors = []
var color = new THREE.Color()
// 给每个顶点设置演示 实现渐变
for (var j = 0; j < points.length; j++) {
color.setHSL(0.81666 + j, 0.88, 0.715 + j * 0.0025) // 粉色
colors.push(color.r, color.g, color.b)
positions.push(points[j].x, points[j].y, points[j].z)
}
// 放入顶点 和 设置顶点颜色
lineGeometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3, true))
lineGeometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3, true))
const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors, side: THREE.DoubleSide })
const line = new THREE.Line(lineGeometry, material)
return line
}
- 通过上节的
projection()
方法,转换经纬度为3D坐标。
const line = lineConnect(projection([106.557691, 29.559296]), projection([121.495721, 31.236797]))
scene.add(line)
绘制移动物体
- 飞线都有一个从起点到终点的移动物体,这里绘制一个简单的几何体。为了好看你也可以加载一个飞机模型在这使用。
- 移动物体是在飞线上的,这里需要传入三维二次贝塞尔曲线的实例,在渲染函数中使用。
// 移动物体网格对象组
const moveSpots = []
/**
* 线上移动物体
* */
function moveSpot(curve) {
// 线上的移动物体
const aGeo = new THREE.SphereGeometry(0.4, 0.4, 0.4)
const aMater = new THREE.MeshPhongMaterial({ color: 0xff0000, side: THREE.DoubleSide })
const aMesh = new THREE.Mesh(aGeo, aMater)
// 保存曲线实例
aMesh.curve = curve
aMesh._s = 0
scene.add(aMesh)
moveSpots.push(aMesh)
}
- 在渲染函数中修改移动物体的位置。
function render() {
...
moveSpots.forEach(function (mesh) {
mesh._s += 0.006
let tankPosition = new THREE.Vector3()
tankPosition = mesh.curve.getPointAt(mesh._s % 1)
mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z)
})
...
}
getPointAt()
根据弧长在曲线上的位置。必须在范围[0,1]内。
- 这里使用曲线实例的
.getPointAt()
方法,获取[0~1]曲线上点的位置。设置给移动物体让其在线上移动。 - 在
lineConnect()
函数中调用。
function lineConnect(posStart, posEnd) {
...
// 绘制 目标位置
spotCircle([x0, y0, z0])
spotCircle([x1, y1, z1])
moveSpot(curve)
...
}
创建多个飞线
- 公用方法全部创建完毕,多加几条飞线让内容丰富一下。
const line = lineConnect(projection([106.557691, 29.559296]), projection([121.495721, 31.236797]))
scene.add(line)
const line2 = lineConnect(projection([106.557691, 29.559296]), projection([104.006215, 30.650055]))
scene.add(line2)
const line3 = lineConnect(projection([106.557691, 29.559296]), projection([116.396795, 39.93242]))
scene.add(line3)