環境
- 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);
}
}
附上效果圖

