环境
- ThreeJS 107版本
- three.min.js
- OrbitControls.js
- THREE.MeshLine.js
说明
迁徙图参考了网上大大们的方法做的,但是效果不太理想,迁徙飞行效果原理是生成50个小球循环飞,数据量一大有点卡,需要优化。
解决方案
-
创建球的过程参见"ThreeJS制作地球"
-
创建点位group,考虑后面会做删除功能,所以把所有的实体都以group组为单位添加,后续方便做删除
// 标记点组合
var marking = new THREE.Group();
- 根据数据,在地球上添加飞出、飞入的点位,并且绘制贝塞尔曲线
var groupLines = new THREE.Group();
for (var i = 0; i < _flyData.length; i++) {
index = i;
for (var j = 0; j < _flyData[i].length; j++) {
var ballPosFrom, ballPosTo;
// 创建标记点球体
var ballFrom = new THREE.Mesh(new THREE.SphereGeometry(0.5, 30, 30), new THREE.MeshBasicMaterial({
color: ballColors[index % 3]//'#1bb4b0'
}));
// 获取标记点坐标
ballPosFrom = this.getPosition(getPositionByName(_flyData[i][j][0].name)[1][0] + 90, getPositionByName(_flyData[i][j][0].name)[1][1], 30);
ballFrom.position.set(ballPosFrom.x, ballPosFrom.y, ballPosFrom.z);
marking.add(ballFrom);
// 创建标记点球体
var ballTo = new THREE.Mesh(new THREE.SphereGeometry(0.5, 30, 30), new THREE.MeshBasicMaterial({
color: ballColors[index % 3]//'#1bb4b0'
}));
// 获取标记点坐标
ballPosTo = this.getPosition(getPositionByName(_flyData[i][j][1].name)[1][0] + 90, getPositionByName(_flyData[i][j][1].name)[1][1], 30);
ballTo.position.set(ballPosTo.x, ballPosTo.y, ballPosTo.z);
marking.add(ballTo);
// 添加飞线
var line = addLine(ballFrom.position, ballTo.position);//迁徙方向,第一个参数是起始方向
groupLines.add(line.lineMesh);
animateDots.push(line.curve.getPoints(150));
}
}
scene.add(marking);
scene.add(groupLines);
- 将经纬度转换成球上坐标
//经纬度转球坐标
this.getPosition = function (_longitude, _latitude, _radius) {
var lg = THREE.Math.degToRad(_longitude);
var lt = THREE.Math.degToRad(_latitude);
var temp = _radius * Math.cos(lt);
var x = temp * Math.sin(lg);
var y = _radius * Math.sin(lt);
var z = temp * Math.cos(lg);
return {
x: x,
y: y,
z: z
}
}
- 绘制贝塞尔曲线
function animationLine() {
aGroup.children.forEach(function (elem, index) {
var _index = parseInt(index / 50);
var index2 = index - 50 * _index;
var _vIndex = 0;
if (firstBool) {
_vIndex = vIndex - index2 % 50 >= 0 ? vIndex - index2 % 50 : 0;
} else {
_vIndex = vIndex - index2 % 50 >= 0 ? vIndex - index2 % 50 : 150 + vIndex - index2;
}
var v = animateDots[_index][_vIndex];
elem.position.set(v.x, v.y, v.z);
})
vIndex++;
if (vIndex > 150) {
vIndex = 0;
}
if (vIndex == 150 && firstBool) {
firstBool = false;
}
requestAnimationFrame(animationLine);
}
// 计算球体上两个点的中点
function getVCenter(v1, v2) {
var v = v1.add(v2);
return v.divideScalar(2);
}
// 计算球体两点向量固定长度的点
function getLenVcetor(v1, v2, len) {
var v1v2Len = v1.distanceTo(v2);
return v1.lerp(v2, len / v1v2Len);
}
// 添加轨迹函数
function addLine(v0, v3) {
var angleRate = _style.angleRate ? _style.angleRate : 0.5;
var angle = (v0.angleTo(v3) * 180) / Math.PI;
var aLen = angle * angleRate * (1 - angle / (Math.PI * 90));
var hLen = angle * angle * 1.2 * (1 - angle / (Math.PI * 90));
var p0 = new THREE.Vector3(0, 0, 0);
// 法线向量
var rayLine = new THREE.Ray(p0, getVCenter(v0.clone(), v3.clone()));
// 顶点坐标
var vtop = rayLine.at(hLen / rayLine.at(1).distanceTo(p0));
// 控制点坐标
var v1 = getLenVcetor(v0.clone(), vtop, aLen);
var v2 = getLenVcetor(v3.clone(), vtop, aLen);
// 绘制贝塞尔曲线
var curve = new THREE.CubicBezierCurve3(v0, v1, v2, v3);
var geometry = new THREE.Geometry();
geometry.vertices = curve.getPoints(100);
var line = new MeshLine();
line.setGeometry(geometry);
var material = new MeshLineMaterial({
color: lineColors[index % 3],
lineWidth: _style.lineWidth ? _style.lineWidth : 0.1,
transparent: true,
opacity: 1
})
return {
curve: curve,
lineMesh: new THREE.Mesh(line.geometry, material)
}
}
- 构造循环动画的小球
// 线上滑动的小球
var aGroup = new THREE.Group();
for (var i = 0; i < animateDots.length; i++) {
for (var j = 0; j < 50; j++) {
var aGeo = new THREE.SphereGeometry(0.2, 10, 10);
var aMaterial = new THREE.MeshBasicMaterial({
color: lineColors[index % 3],//"rgb(27, 180, 176)",
transparent: true,
opacity: 1 - j * 0.02
})
var aMesh = new THREE.Mesh(aGeo, aMaterial);
aGroup.add(aMesh);
}
}