//---html (angular)---
<!-- 3D地球 -->
<div #mapId id="mapIdBox" class="mapBox" style="width: 800px; height: 800px;"></div>
//---ts---
import {Component, ElementRef, ViewChild } from '@angular/core';
//引入three相关
import * as THREE from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
import * as TWEEN from "tween"
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
// 引入点阵数据
import mapPoints from "/assets/js/mapPoints/mapPoints";
let glRender; // webgl渲染器
let camera; // 摄像机
let scene; // 场景,一个大容器,可以理解为html中的body
let meshGroup; // 所有Mesh的容器,后面所有Mesh都会放在这里面,方便我们管理,可理解为一个div
let controls; // 轨道控制器,实现整体场景的控制
let globeRadius = 55;
let fov = 100;
@Component({
selector: 'testThreeEartyh',
templateUrl: './testThreeEartyh.component.html',
styleUrls: ['./testThreeEartyh.component.less'],
})
export class IndexCenterComponent {
constructor(){}
@ViewChild('mapId', { static: true }) mapId: ElementRef;
mapDom = null
globeSegments = 100; // 球体面数,数量越大越光滑,性能消耗越大
animationType = true // 地球入场动画
rotationY = true // 地球自动旋转
meshAnimateType = false // 标记点动画
lonlat = { x: 0, y: 0, z: 200 }
roratTimer = null;
//标记点数据
objList = [
{ lon: 116.358976, lat: 39.803282, name: "中国", color: '#F03022' },
]
ngAfterViewInit(){
let that = this;
that.info ()
// 添加标记点
this.infoMap()
}
ngOnDestroy(){
this.rotationY = false
this.ballRotationY()
clearInterval(this.roratTimer);
}
// 初始化
info () {
let that = this
this.infoThree()
glRender.domElement.addEventListener("click", this.infoMouse)
glRender.domElement.addEventListener("mouseover", function(){//鼠标移入地球停止旋转
that.rotationY = false
that.ballRotationY()
})
glRender.domElement.addEventListener("mouseout", function(){//鼠标移出地球开始旋转
that.rotationY = true
that.ballRotationY()
})
}
// 基本配置
infoThree () {
let that = this;
// 场景
scene = new THREE.Scene();
meshGroup = new THREE.Group()
// 渲染
glRender = new THREE.WebGLRenderer({
antialias: true,//抗锯齿
alpha: true,//是否透明
})
this.mapDom = this.mapId.nativeElement;
glRender.setSize(this.mapDom.clientWidth, this.mapDom.clientHeight)
glRender.setClearColor(0x000, 0)//第二个参数来设置透明度0是完全透明,1是不透明
this.mapDom.appendChild(glRender.domElement)
// 相机
camera = new THREE.PerspectiveCamera(
fov,
this.mapDom.clientWidth / this.mapDom.clientHeight,
1,
1000
)
camera.position.set(0, 0, 200)
camera.lookAt(0, 0, 0)
// 点阵
// this.createMapPoints()
// 创建地球
this.infoBall()
// 鼠标
this.infoOrbitControls()
// 轮廓描边
this.initStrockMap(this.variableObj.mapDataObj.chinaMapFeatures)
// 添加标记点
// this.infoMap()
}
/**
* 导入纹理
* @param path
* @returns {Promise}
*/
loadTexture(path){
return new Promise((resolve, reject)=>{
let loader = new THREE.TextureLoader();
loader.load(path, texture => {
resolve(texture);
} , ()=>{} , ()=>{reject('fail')});
});
}
// 地球纹理贴图
async infoBall() {
let that = this;
// //创建球体
let earthTexture = await that.loadTexture('/earth_atmos_2048_2.jpg');
let specularMap = await that.loadTexture( '/earth_lights_2048.png' )
let normalMap = await that.loadTexture( '/earth_normal_2048.jpg' )
let earthGeometry = new THREE.SphereGeometry( globeRadius, that.globeSegments, that.globeSegments );
var textureLoader = new THREE.TextureLoader();
// 加载光照贴图
var textureLight = textureLoader.load('/earth_clouds_2048.png');
// let earthMaterial = new THREE.MeshBasicMaterial( { map: earthTexture, overdraw: 0.5, transparent: true,opacity: 0.6,} );//无光照
let earthMaterial = new THREE.MeshPhongMaterial( { //有光照
color: 0xffffff,
map: earthTexture,
specularMap: specularMap,
normalMap: normalMap,
shininess: 15,
normalScale: new THREE.Vector2( 0.85, - 0.85 ),
overdraw: 0.5,
transparent: true,
opacity: 0.9,
emissive: 0x2eadf4, // 设置发光颜色
emissiveIntensity: 0.8, // 设置发光强度
lightMap:textureLight,// 设置光照贴图
} );
let earthMesh = new THREE.Mesh( earthGeometry, earthMaterial );
earthMesh.castShadow = true; // 投射阴影
meshGroup.add( earthMesh );
//地球的云层
let cloudTexture = await that.loadTexture('/earth_clouds_2048.png');
let cloudGeometry = new THREE.SphereGeometry( globeRadius+1, that.globeSegments, that.globeSegments );
// let cloudMaterial = new THREE.MeshBasicMaterial( { map: cloudTexture, overdraw: 0.5, transparent: true,side: THREE.DoubleSide} );//无光照
let cloudMaterial = new THREE.MeshPhongMaterial( { //有光照
map: cloudTexture,
side: THREE.DoubleSide,
shininess: 15,
normalScale: new THREE.Vector2( 0.85, - 0.85 ),
overdraw: 0.5,
transparent: true,
emissive: 0x2eadf4, // 设置发光颜色
emissiveIntensity: 0.8 // 设置发光强度
} );
let cloudMesh = new THREE.Mesh( cloudGeometry, cloudMaterial );
cloudMesh.castShadow = true; // 投射阴影
meshGroup.add( cloudMesh );
// 创建光并开启投射阴影
var dirLight = new THREE.DirectionalLight( 0xffffff );
dirLight.position.set( 90, 0, 200 ).normalize();
const targetPosition = new THREE.Vector3(0, 0, 0);
dirLight.target.position.copy(targetPosition);
dirLight.castShadow = true;
scene.add(dirLight);
// 重新渲染
// this.infoRender()
this.roratTimer = setInterval(this.infoRender.bind(this), 17);//屏刷新频率是 60HZ ==> 也就是每秒60次. ==> 相当于1000毫秒60次 = 16.67ms一次。也就是说每16.67毫秒刷新一次是浏览器显示的最大刷新频率。我们一般设置16或者17 接近这个频率。
}
// 重新渲染
infoRender() {
glRender.clear()
// 地球入场动画
if (this.animationType) this.ballAnimation()
// 地球旋转
if (this.rotationY) this.ballRotationY()
// 标记点动画
if (this.meshAnimateType) this.meshAnimate()
glRender.render(scene, camera)
// requestAnimationFrame(this.infoRender.bind(this))
TWEEN.update()
}
// 鼠标
infoOrbitControls() {
controls = new OrbitControls(camera, glRender.domElement)
controls.enableDamping = true
controls.enableZoom = true
controls.autoRotate = false
controls.autoRotateSpeed = 2
controls.enablePan = true
}
/**
* 生成点状世界地图方法
*/
createMapPoints() {
// 点的基本材质.
const material = new THREE.MeshBasicMaterial({
// color: "#AAA",
color: "rgba(170,170,170,0.1)",
});
const sphere = [];
// 循环遍历所有点将2维坐标映射到3维坐标
for (let point of mapPoints.points) {
/**
* 我们需要获取2维点的数组,循环遍历并将每个点转换为其3维位置。这是执行转换的功能。根据您创建的模板投影的大小,您可能需要调整前几个变量
*/
let x,y,z;
const globeWidth = 4098 / 2;
const globeHeight = 2050 / 2;
let latitude = ((point['x'] - globeWidth) / globeWidth) * -180;
let longitude = ((point['y'] - globeHeight) / globeHeight) * -90;
latitude = (latitude * Math.PI) / 180;
longitude = (longitude * Math.PI) / 180;
const radius = Math.cos(longitude) * globeRadius;
x = Math.cos(latitude) * radius;
y = Math.sin(longitude) * globeRadius;
z = Math.sin(latitude) * radius;
if (x && y && z) {
// 生成点阵
const pingGeometry = new THREE.SphereGeometry(0.4, 5, 5);
pingGeometry.translate(x, y, z);
sphere.push(pingGeometry);
}
}
// 合并所有点阵生成一个mesh对象
const earthMapPoints = new THREE.Mesh(
BufferGeometryUtils.mergeBufferGeometries(sphere),
material
);
// 加入到mesh容器中
meshGroup.add(earthMapPoints);
}
/**
*经纬度转坐标(THREE.Vector3方式)
*lng:经度
*lat:纬度
*radius:地球半径
*/
lglt2xyz(lng, lat, radius) {
const theta = (90 + lng) * (Math.PI / 180)
const phi = (90 - lat) * (Math.PI / 180)
return (new THREE.Vector3()).setFromSpherical(new THREE.Spherical(radius, phi, theta))
}
// 添加中国省份轮廓线
initStrockMap( chinaJson ) {
// 遍历省份构建模型
chinaJson.features.forEach( elem => {
// 新建一个省份容器:用来存放省份对应的模型和轮廓线
const province = new THREE.Object3D();
const coordinates = elem.geometry.coordinates;
coordinates.forEach( multiPolygon => {
multiPolygon.forEach( polygon => {
const lineMaterial = new THREE.LineBasicMaterial( { color: 0xffffff } ); //0x3BFA9E
const positions = [];
const linGeometry = new THREE.BufferGeometry();
for (let i = 0; i < polygon.length; i ++) {
var pos = this.lglt2xyz( polygon[i][0], polygon[i][1], globeRadius );
positions.push( pos.x, pos.y, pos.z );
}
linGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
const line = new THREE.Line( linGeometry, lineMaterial );
province.add( line );
} );
} );
meshGroup.add( province );
} );
scene.add( meshGroup );
}
// 地球入场动画
ballAnimation() {
fov -= 0.8
if (fov <= 45) {
this.animationType = false
this.infoOrbitControls()
} else {
camera = new THREE.PerspectiveCamera(
fov,
this.mapDom.clientWidth / this.mapDom.clientHeight,
1,
1000
);
}
camera.position.set(0, 0, 200)
camera.lookAt(0, 0, 0)
}
// 地球自动旋转
ballRotationY() {
if(this.rotationY){
meshGroup.rotateY(0.03)//每次绕y轴旋转0.0005弧度
}else{
meshGroup.rotateY(0)
}
}
// 地球添加标记点
infoMap() {
// for (let i = 0; i < this.objList.length; i++) {
for (let i = 0; i < 1000; i++) {
this.infoMark(this.objList[i]);
}
}
// 添加纹理标记点
infoMark(item) {
let cityGeometry = new THREE.PlaneGeometry(1, 1) //默认在XOY平面上
let textureLoader = new THREE.TextureLoader()
let texture;
texture = textureLoader.load('/redflag.png')
let cityWaveMaterial = new THREE.MeshBasicMaterial({
color: item.color,
map: texture,
transparent: true,
opacity: 1,
side: THREE.DoubleSide
})
let mesh = new THREE.Mesh(cityGeometry, cityWaveMaterial)
const coord = this.lon2xyz(globeRadius * 1.03, item.lon, item.lat)
mesh.scale.set(2, 2, 2)
// 唯一标识
mesh.name = item.name
mesh.privateType = 'mark'
mesh.position.set(coord.x, coord.y, coord.z)
const coordVec3 = new THREE.Vector3(
coord.x, 0, coord.z
).normalize()
const meshNormal = new THREE.Vector3(0, 0, 1)
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
// if (scene.getObjectByName(item.name) === undefined) {
meshGroup.add(mesh)
//网格模型添加到场景中
scene.add(meshGroup)
this.meshAnimateType = false
// }
}
// 标记点动画
meshAnimate() {
for (let i = 0; i < meshGroup.children.length; i++) {
if (meshGroup.children[i].privateType === "mark") {
// 添加初始随机数,防止动画同步
meshGroup.children[i].material.opacity += Math.random() * 0.05
meshGroup.children[i].scale.set(
meshGroup.children[i].material.opacity + 7,
meshGroup.children[i].material.opacity + 7,
meshGroup.children[i].material.opacity + 7
)
if (meshGroup.children[i].scale.x >= 9) {
meshGroup.children[i].material.opacity = 0
}
}
}
}
// 移动相机
cameraPos(val) {
// this.frameDivClose ()
let layerObj = scene.getObjectByName(val.name)
if (layerObj) {
scene.rotation.y = 0
this.rotationY = false
new TWEEN.Tween( { x: this.lonlat.x, y: this.lonlat.y, z: this.lonlat.z } )
.to( { x: layerObj.position.x * 2.8, y: layerObj.position.y * 2.8, z: layerObj.position.z * 2.8}, 1500 )
.onUpdate( function () {
camera.position.x = this.x
camera.position.y = this.y
camera.position.z = this.z
camera.lookAt(0, 0, 0)
})
.onComplete ( ()=> { })
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start()
this.lonlat = camera.position
} else {
console.log('图层数据已被全部删除,请重新刷新界面,或者重新调用数据初始化方法: this.infoMap ()')
}
}
// 鼠标事件(点击标记的点的事件)
//@ts-ignore
infoMouse(event) {
console.log(event)
let that = this
let elementCanvas = document.getElementById('mapIdBox')
event.preventDefault();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
let getBoundingClientRect = elementCanvas.getBoundingClientRect();
mouse.x = ((event.clientX - getBoundingClientRect.left) / elementCanvas.offsetWidth) * 2 - 1;
mouse.y = -((event.clientY - getBoundingClientRect.top) / elementCanvas.offsetHeight) * 2 + 1;
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, camera);
// 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
let intersects = raycaster.intersectObjects(scene.children);
// 点击对象的处理
for (let i = 0; i < intersects.length; i++) {
if (intersects[i].object.name !== 'ballMain') {
let objListObj = {
name: intersects[i].object.name
}
if(objListObj.name != ""){
that.cameraPos(objListObj)
}
return false
} else {
// 开启自动旋转
this.rotationY = true
}
}
}
// 经纬度转坐标(js方式)
lon2xyz(R, longitude, latitude) {
const lon = (Number(longitude) + 90) * (Math.PI / 180)
const lat = Number(latitude) * (Math.PI / 180)
const x = R * Math.cos(lat) * Math.sin(lon)
const y = R * Math.sin(lat)
const z = R * Math.cos(lon) * Math.cos(lat)
return { x, y, z }
}
}
效果图:文章来源:https://www.toymoban.com/news/detail-854127.html
文章来源地址https://www.toymoban.com/news/detail-854127.html
到了这里,关于threeJs实现3D地球-旋转-自定义贴图-透明发光的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!