three.js實現世界地圖城市遷徙圖

概況如下:

1、THREE.CylinderGeometryTHREE.SphereGeometry繪製地圖上的標記;
2、THREE.CanvasTexture用於加載canvas繪製的字體;
3、THREE.ShapeMeshLine用於實現平面地圖;
4、THREE.ExtrudeGeometry用於將繪製的平面地圖沿Z軸拉伸,出現3d效果;
5、THREE.CubicBezierCurve3用於繪製軌跡曲線;

效果圖如下:在這裏插入圖片描述
預覽地址:three.js實現世界地圖城市遷徙圖

初始化場景、相機、渲染器,設置相機位置。

// 初始化場景
var scene = new THREE.Scene();
// 初始化相機,第一個參數爲攝像機視錐體垂直視野角度,第二個參數爲攝像機視錐體長寬比,
// 第三個參數爲攝像機視錐體近端面,第四個參數爲攝像機視錐體遠端面
var camera = new THREE.PerspectiveCamera(20, dom.clientWidth / dom.clientHeight, 1, 100000);
// 設置相機位置,對應參數分別表示x,y,z位置
camera.position.set(0, 0, 400);
var renderer = new THREE.WebGLRenderer({
	  alpha: true,
	  antialias: true
});

設置場景窗口尺寸,並且初始化控制器,窗口尺寸默認與瀏覽器窗口尺寸保持一致,最後將渲染器加載到dom中。

// 設置窗口尺寸,第一個參數爲寬度,第二個參數爲高度
renderer.setSize(dom.clientWidth, dom.clientHeight);
// 初始化控制器
var orbitcontrols = new THREE.OrbitControls(camera,renderer.domElement);
// 將渲染器加載到dom中
dom.appendChild(renderer.domElement);

繪製平面地圖方法,通過THREE.Shape來實現。

// 繪製地圖函數
var drawShape = function (pos) {
	var shape = new THREE.Shape();
	// 計算平均每格佔比
	var average = getAverage();
	shape.moveTo(pos[0][0], pos[0][1]);
	pos.forEach(function (item) {
		shape.lineTo(item[0], item[1]);
	})
	return shape;
}

ExturdeGeometry配置參數。

// ExturdeGeometry配置參數
var options = {
	depth: zHeight, // 定義圖形拉伸的深度,默認100
	steps: 0, // 拉伸面方向分爲多少級,默認爲1
	bevelEnabled: true, // 表示是否有斜角,默認爲true
	bevelThickness: 0, // 斜角的深度,默認爲6
	bevelSize: 0, // 表示斜角的高度,高度會疊加到正常高度
	bebelSegments: 0, // 斜角的分段數,分段數越高越平滑,默認爲1
	curveSegments: 0 // 拉伸體沿深度方向分爲多少段,默認爲1
}

將平面地圖拉伸,模擬出現3d效果,通過THREE.ExtrudeGeometry來實現。

// 將shape轉換爲ExtrudeGeometry
var transition3d = function (shapeObj, identify) {
	var geometry = new THREE.ExtrudeGeometry(shapeObj, options);
    var material1 = new THREE.MeshBasicMaterial({
    	color: faceColor
    });
    var material2 = new THREE.MeshBasicMaterial({
    	color: sideColor
    });
    // 繪製地圖
    shapeGeometryObj['shapeGeometry' + identify] = new THREE.Mesh(geometry, [material1, material2]);
    // 將地圖加入場景
    scene.add(shapeGeometryObj['shapeGeometry' + identify])
}

繪製世界地圖參數方法

// 計算繪製地圖參數函數
var drawShapeOptionFun = function () {
	// 繪製世界地圖
	worldGeometry.features.forEach(function (worldItem, worldItemIndex) {
		var length = worldItem.geometry.coordinates.length;
		var multipleBool = length > 1 ? true : false;
		worldItem.geometry.coordinates.forEach(function (worldChildItem, worldChildItemIndex) {
			if (multipleBool) {
				// 值界可以使用的經緯度信息
				if (worldChildItem.length && worldChildItem[0].length == 2) {
					transition3d(drawShape(worldChildItem), '' + worldItemIndex + worldChildItemIndex);
				}
				// 需要轉換纔可以使用的經緯度信息
				if (worldChildItem.length && worldChildItem[0].length > 2) {
					worldChildItem.forEach(function (countryItem, countryItenIndex) {
						transition3d(drawShape(countryItem), '' + worldItemIndex + worldChildItemIndex + countryItenIndex);
					})
				}
			} else {
				var countryPos = null;
				if (worldChildItem.length > 1) {
					countryPos = worldChildItem;
				} else {
					countryPos = worldChildItem[0];
				}
				if (countryPos) {
					transition3d(drawShape(countryPos), '' + worldItemIndex + worldChildItemIndex);
				}
			}
		})
	})
}

通過canvas實現說明文字方法。

// canvas實現文字函數
var getCanvasFont = function (w, h, textValue, fontColor) {
	var canvas = document.createElement('canvas');
	canvas.width = w;
	canvas.height = h;
	var ctx = canvas.getContext('2d');
	ctx.fillStyle = textBackground;
	ctx.fillRect(0, 0, w, h);
	ctx.font = h + "px '微軟雅黑'";
	ctx.textAlign = 'center';
	ctx.textBaseline = 'middle';
	ctx.fillStyle = fontColor;
	ctx.fillText(textValue, w / 2, h / 2);
	$('body').append(canvas)
	return canvas;
}

繪製城市標記方法。

/** 繪製標記函數
 * pos表示經緯度信息
 * textValue表示標記內容
 * fontColor表示標記字體顏色
 * fontSize表示字體大小
**/
var drawMarkingFont = function (option, markingIndex) {
	var average = getAverage();
	var cityX = option.pos[0];
	var cityY = option.pos[1];
	var markingGroup = new THREE.Group();
	// 圓錐體
	var cylinder = new THREE.Mesh(
		new THREE.CylinderGeometry(circularRadio, 0, circularHeight, 50, 50, false),
		new THREE.MeshBasicMaterial({
			color: markingColor
		})
	)
	// 球體
	var ball = new THREE.Mesh(
		new THREE.SphereGeometry(circularRadio, 30, 30),
		new THREE.MeshBasicMaterial({
			color: markingColor
		})
	)
	ball.position.set(cityX, cityY, circularHeight + zHeight);
	cylinder.position.set(cityX, cityY, circularHeight / 2 + zHeight);
	cylinder.rotation.x = 1.5;
	// 添加文字說明
	var textLength = option.textValue.split('').length;
	var texture = new THREE.CanvasTexture(getCanvasFont(textLength * option.fontSize * average, option.fontSize * average, option.textValue, option.fontColor));
	var fontMesh = new THREE.Sprite(
		new THREE.SpriteMaterial({
			map: texture
		})
	)
	fontMesh.scale.x = option.fontSize / average * textLength;
	fontMesh.scale.y = option.fontSize / average;
	// 定義提示文字顯示位置
	fontMesh.position.set(cityX, cityY, circularHeight + circularRadio / 2 + zHeight / 2 + option.fontSize / average + 0.5);
	markingGroup.add(ball);
	markingGroup.add(cylinder);
	markingGroup.add(fontMesh);
	markingObj['markingGroup' + markingIndex] = markingGroup;
	scene.add(markingGroup);
}

城市遷徙線條繪製。

// 繪製遷徙線條函數
var drawMetapLine = function (v0, v3) {
	var v1 = {};
	v1.x = (v0.x + v3.x) / 2;
	v1.y = (v0.y + v3.y) / 2;
	v1.z = 6;
	// 繪製貝塞爾曲線
	var curve = new THREE.CubicBezierCurve3(v0, v1, v1, v3);
	var geometry = new THREE.Geometry();
	geometry.vertices = curve.getPoints(100);
	var line = new MeshLine();
	line.setGeometry(geometry);
	var material = new MeshLineMaterial({
		color: meshLineColor,
		lineWidth: lineWidth
	})
	return {
		curve: curve,
		lineMesh: new THREE.Mesh(line.geometry, material)
	}
}

繪製遷徙圖方法。

// 繪製遷徙圖
var drawMetap = function () {
	var average = getAverage();
	var beijing = {x: 116.4551, y: 40.2539, z: zHeight};
	var lundun = {x: 0.5, y: 51.3, z: zHeight};
	// 經緯度信息
	var metapArray = [];
	// 組裝線條連接經緯度信息
	markingPos.marking.forEach(function (markingItem) {
    	metapArray.push({
    		x: markingItem.pos[0],
    		y: markingItem.pos[1],
    		z: zHeight
    	})
    })
	// 線條集合
	var animateDots = [];
	// 存放線條對象集合
	var groupLines = new THREE.Group();
	// 繪製遷徙線條
	metapArray.forEach(function (metapItem, metapIndex) {
		if (metapIndex > 0) {
			var line = drawMetapLine(metapArray[0], metapItem);
	    	groupLines.add(line.lineMesh);
	    	animateDots.push(line.curve.getPoints(metapNum));
		}
	})
	// 添加遷徙線條到場景中
	scene.add(groupLines);
	// 添加線上滑動的物質
	var aGroup = new THREE.Group();
	for (var i = 0; i < animateDots.length; i ++) {
		for (var j = 0; j < markingNum; j ++) {
			var aGeo = new THREE.SphereGeometry(dotWidth, 10, 10);
			var aMater = new THREE.MeshPhongMaterial({
				color: markingColor,
				transparent: true,
				opacity: 1 - j * 1 / markingNum
			})
			var aMesh = new THREE.Mesh(aGeo, aMater);
			aGroup.add(aMesh);
		}
	}
	var vIndex = 0;
	// 表示第一次循環運行
	var firstBool = true;
	function animationLine() {
		aGroup.children.forEach(function (elem, index) {
			var _index = parseInt(index / markingNum);
			// 保證當前數組與遷徙軌跡匹配
			var index2 = index - _index * markingNum;
			var _vIndex = 0;
			if (firstBool) {
				_vIndex = vIndex - index2 % markingNum >= 0 ? vIndex - index2 % markingNum : 0;
			} else {
				_vIndex = vIndex - index2 % markingNum >= 0 ? vIndex - index2 % markingNum : 150 + vIndex - index2;
			}
			var v = animateDots[_index][_vIndex];
			elem.position.set(v.x, v.y, v.z);
		})
		vIndex ++;
		if (vIndex > metapNum) {
			vIndex = 0;
		}
		if (vIndex == 150 && firstBool) {
			firstBool = false;
		}
		requestAnimationFrame(animationLine);
	}
	scene.add(aGroup);
	animationLine();
}

世界地圖城市遷徙通過position值來實現位置的確認,動畫使用requestAnimationFrame來實現。

// 執行函數
var render = function () {
	scene.rotation.x = -0.8;
    renderer.render(scene, camera);
    orbitcontrols.update();
    requestAnimationFrame(render);
}
發佈了19 篇原創文章 · 獲贊 4 · 訪問量 4295
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章