第一次接觸threeJS,說實(shí)話,挺腦瓜子疼的!
功能:3D地球(紋理貼圖),地球上添加標(biāo)記點(diǎn)(經(jīng)緯度),點(diǎn)擊標(biāo)記點(diǎn)彈出對(duì)應(yīng)的信息框,地球入場(chǎng)動(dòng)畫,相機(jī)移動(dòng)動(dòng)畫等。
先開效果圖吧
一:添加必要的依賴
yarn add three
yarn add tween
import * as THREE from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
import * as TWEEN from "tween"
二:組件代碼
<template>
<div style="width: 100%; height: 100%;">
<div id="layerMain">
<div>{{ countryName }}</div>
<div class="shape"></div>
</div>
<div ref="mapId" style="width: 100%; height: 100%;"></div>
</div>
</template>
<script>
import * as THREE from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
import * as TWEEN from "tween"
import map_img from '../../assets/images/map.jpg'
import map_wl from '../../assets/images/wl.png'
let camera, scene, controls, mesh;
let group = new THREE.Group();
let radius = 70;
let fov = 100;
export default {
name: 'index',
data () {
return {
mapDom: null,
renderer: null,
animationType: true, // 地球入場(chǎng)動(dòng)畫
rotationY: true, // 地球自動(dòng)旋轉(zhuǎn)
meshAnimateType: false, // 標(biāo)記點(diǎn)動(dòng)畫
lonlat: { x: 0, y: 0, z: 200 },
countryName: null, // 數(shù)據(jù)
}
},
mounted () {
this.info ()
},
methods: {
// 初始化
info () {
this.infoThree ()
this.infoBall ()
this.infoRender ()
this.renderer.domElement.addEventListener("click", this.infoMouse)
},
// 基本配置
infoThree () {
// 場(chǎng)景
scene = new THREE.Scene()
// 渲染
this.renderer = new THREE.WebGLRenderer({
antialias: true,
})
this.mapDom = this.$refs.mapId
this.renderer.setSize(this.mapDom.clientWidth, this.mapDom.clientHeight)
this.renderer.setClearColor(0x000, 0)
this.mapDom.appendChild(this.renderer.domElement)
// 相機(jī)
camera = new THREE.PerspectiveCamera(
fov,
this.mapDom.clientWidth / this.mapDom.clientHeight,
1,
1000
)
camera.position.set(0, 0, 200)
camera.lookAt(0, 0, 0)
// 鼠標(biāo)
this.infoOrbitControls()
},
// 重新渲染
infoRender() {
this.renderer.clear()
// 地球入場(chǎng)動(dòng)畫
if (this.animationType) this.ballAnimation()
// 地球旋轉(zhuǎn)
if (this.rotationY) this.ballRotationY()
// 標(biāo)記點(diǎn)動(dòng)畫
if (this.meshAnimateType) this.meshAnimate()
this.renderer.render(scene, camera)
requestAnimationFrame(this.infoRender)
TWEEN.update()
},
// 鼠標(biāo)
infoOrbitControls() {
controls = new OrbitControls(camera, this.renderer.domElement)
controls.enableDamping = true
controls.enableZoom = true
controls.autoRotate = false
controls.autoRotateSpeed = 2
controls.enablePan = true
},
// 地球
infoBall() {
// 紋理貼圖
let textureLoader = new THREE.TextureLoader();
textureLoader.load(map_img, function (texture) {
// 創(chuàng)建球
let geometry = new THREE.SphereGeometry(radius, 100, 100);
let material = new THREE.MeshBasicMaterial({
map: texture, //設(shè)置顏色貼圖屬性值
});
//網(wǎng)格模型對(duì)象Mesh
mesh = new THREE.Mesh(geometry, material);
// 唯一標(biāo)識(shí)
mesh.name = "ballMain";
// 添加到場(chǎng)景中
scene.add(mesh);
});
},
// 地球入場(chǎng)動(dòng)畫
ballAnimation() {
fov -= 0.6
if (fov <= 45) {
this.animationType = false
camera.position.set(0, 0, 200)
camera.lookAt(0, 0, 0)
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)
}
},
// 地球自動(dòng)旋轉(zhuǎn)
ballRotationY() {
scene.rotation.y += 0.003
},
// 添加紋理標(biāo)記點(diǎn)
infoMark(item) {
console.log(group)
let cityGeometry = new THREE.PlaneBufferGeometry(1, 1) //默認(rèn)在XOY平面上
let textureLoader = new THREE.TextureLoader()
let texture = textureLoader.load(map_wl)
let cityWaveMaterial = new THREE.MeshBasicMaterial({
color: item.color,
map: texture,
transparent: true,
opacity: 0,
side: THREE.DoubleSide
})
let mesh = new THREE.Mesh(cityGeometry, cityWaveMaterial)
const coord = this.lon2xyz(radius * 1.01, item.lon, item.lat)
mesh.scale.set(2, 2, 2)
// 唯一標(biāo)識(shí)
mesh.name = item.name
mesh.privateType = 'mark'
mesh.position.set(coord.x, coord.y, coord.z)
const coordVec3 = new THREE.Vector3(
coord.x,
coord.y,
coord.z
).normalize()
const meshNormal = new THREE.Vector3(0, 0, 1)
mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3)
if (scene.getObjectByName(item.name) === undefined) {
group.add(mesh)
//網(wǎng)格模型添加到場(chǎng)景中
scene.add(group)
this.meshAnimateType = true
}
},
// 標(biāo)記點(diǎn)動(dòng)畫
meshAnimate() {
for (let i = 0; i < group.children.length; i++) {
if (group.children[i].privateType === "mark") {
// 添加初始隨機(jī)數(shù),防止動(dòng)畫同步
group.children[i].material.opacity += Math.random() * 0.05
group.children[i].scale.set(
group.children[i].material.opacity + 7,
group.children[i].material.opacity + 7,
group.children[i].material.opacity + 7
)
if (group.children[i].scale.x >= 9) {
group.children[i].material.opacity = 0
}
}
}
},
// 移動(dòng)相機(jī)
cameraPos (objList) {
this.frameDivClose ()
let layerObj = scene.getObjectByName(objList.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 ( ()=> {
this.retrievalLayer (objList.name)
})
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start()
this.lonlat = camera.position
// 彈窗面板賦值
this.countryName = objList.name
} else {
console.log('圖層數(shù)據(jù)已被全部刪除,請(qǐng)重新刷新界面,或者重新調(diào)用數(shù)據(jù)初始化方法: this.infoMap ()')
alert('圖層數(shù)據(jù)已被全部刪除,請(qǐng)重新刷新界面,或者重新調(diào)用數(shù)據(jù)初始化方法: this.infoMap ()')
}
},
// 檢索指定的圖層
retrievalLayer (name) {
let layerObj = scene.getObjectByName(name)
this.infoDiv(layerObj.position.x, layerObj.position.y, layerObj.position.z)
},
// 鼠標(biāo)事件(點(diǎn)擊標(biāo)記的點(diǎn)的事件)
infoMouse(event) {
event.preventDefault();
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
// 通過鼠標(biāo)點(diǎn)擊位置,計(jì)算出 raycaster 所需點(diǎn)的位置,以屏幕為中心點(diǎn),范圍 -1 到 1
let getBoundingClientRect = this.mapDom.getBoundingClientRect();
mouse.x =
((event.clientX - getBoundingClientRect.left) /
this.mapDom.offsetWidth) *
2 -
1;
mouse.y =
-(
(event.clientY - getBoundingClientRect.top) /
this.mapDom.offsetHeight
) *
2 +
1;
//通過鼠標(biāo)點(diǎn)擊的位置(二維坐標(biāo))和當(dāng)前相機(jī)的矩陣計(jì)算出射線位置
raycaster.setFromCamera(mouse, camera);
// 獲取與射線相交的對(duì)象數(shù)組,其中的元素按照距離排序,越近的越靠前
let intersects = raycaster.intersectObjects(scene.children);
// 點(diǎn)擊對(duì)象的處理
for (let i = 0; i < intersects.length; i++) {
if (intersects[i].object.name !== 'ballMain') {
// 彈窗面板賦值
this.countryName = intersects[i].object.name
let objList = {
name: intersects[i].object.name
}
this.cameraPos (objList)
return false
} else {
// 開啟自動(dòng)旋轉(zhuǎn)
this.rotationY = true
this.frameDivClose ()
}
}
},
// 標(biāo)簽
infoDiv(pointx, pointy, pointz) {
// 坐標(biāo)轉(zhuǎn)換
let world_vector = new THREE.Vector3(
pointx,
pointy,
pointz
)
let vector = world_vector.project(camera)
let halfWidth = this.mapDom.offsetWidth / 2,
halfHeight = this.mapDom.offsetHeight / 2
let x = Math.round(vector.x * halfWidth + halfWidth)
let y = Math.round(-vector.y * halfHeight + halfHeight)
//創(chuàng)建div容器
let moonDiv = document.getElementById("layerMain")
moonDiv.style.display = "block"
moonDiv.style.left = x - 150 + "px"
moonDiv.style.top = y - 180 + "px"
},
// 關(guān)閉標(biāo)簽
frameDivClose() {
let divHtml = document.getElementById("layerMain")
divHtml.style.display = "none"
},
// 添加光柱
infoColumn (item) {
const material = new THREE.MeshBasicMaterial({
color: item.color,
transparent: true,
opacity: .9,
side: THREE.DoubleSide
})
const coord = this.lon2xyz(radius * 1.01, item.lon, item.lat)
const coordVec3 = new THREE.Vector3(coord.x, coord.y, coord.z).normalize()
const geometry = new THREE.CylinderGeometry(0.2, 2.8, 30)
const mesh = new THREE.Mesh(geometry, material)
mesh.name = item.name
mesh.privateType = 'column'
mesh.position.set(coord.x, coord.y, coord.z)
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), coordVec3)
group.add(mesh)
scene.add(group)
},
// 刪除所有標(biāo)記點(diǎn)
delAll () {
this.frameDivClose ()
group.traverse((item) => {
if (item.type === 'Mesh') {
item.geometry.dispose()
item.material.dispose()
}
})
scene.remove(group)
// 刪除group中的children
if (group.children && group.children.length > 0) {
let i = 0
for (i; i < group.children.length; i++) {
group.remove(group.children[i])
}
}
},
// 刪除指定標(biāo)記點(diǎn)
delMark (item) {
this.frameDivClose ()
let layerObj = scene.getObjectByName(item.name)
group.remove(layerObj)
},
// 經(jīng)緯度轉(zhuǎn)坐標(biāo)
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 }
},
}
}
</script>
<style lang="scss">
#layerMain {
position: absolute;
width: 300px;
height: 160px;
line-height: 160px;
text-align: center;
color: white;
display: none;
background-color: rgba(34,34,35,.6);
.shape {
position: absolute;
margin: auto;
left: 0;
right: 0;
width: 0;
height: 0;
bottom: -40px;
border: 20px solid transparent;
border-top-color: rgba(34,34,35,.6);
}
}
</style>
三:父組件中的代碼
<template>
<div class="home">
<div class="rightMain">
<div class="title">跳轉(zhuǎn)操作</div>
<div class="cont">
<ul>
<li v-for="(item, index) in objList" @click="cameraPos(item)">{{ item.name }}</li>
</ul>
</div>
<div class="title">其他操作</div>
<div class="cont">
<ul>
<li @click="rotationChange">{{ rotation }}</li>
<li @click="columnChange">添加光柱</li>
<li @click="delAllChange">刪除所有</li>
<li @click="delMarkChange">刪除美國標(biāo)記點(diǎn)</li>
</ul>
</div>
<div class="title">重置操作</div>
<div class="cont">
<ul>
<li @click="reset">初始化數(shù)據(jù)</li>
</ul>
</div>
</div>
<threeIndex ref="threeMapId"></threeIndex>
</div>
</template>
<script>
import threeIndex from '../components/three/Index'
export default {
name: 'HomeView',
components: {
threeIndex
},
data () {
return {
objList: [
{ lon: 116.358976, lat: 39.803282, name: "中國", color: '#1FFBC6' },
{ lon: 139.812263, lat: 35.677294, name: "日本", color: '#A41FE8'},
{ lon: 77.198596, lat: 28.575136, name: "印度", color: '#E8BB1F' },
{ lon: -77.02238, lat: 38.900042, name: "美國", color: '#E81F56' },
{ lon: 31.266092, lat: 30.085626, name: "埃及", color: '#1FFBC6' },
{ lon: 103.813654, lat: 1.291125, name: '新加坡', color: '#E8BB1F' },
{ lon: -47.930912, lat: -15.781949, name: '巴西', color: '#A41FE8' },
{ lon: 149.130214, lat: -35.318235, name: '澳大利亞', color: '#E81F56' }
],
objList_2: [
{ lon: 116.358976, lat: 39.803282, name: "中國column", color: '#1FFBC6' },
{ lon: 139.812263, lat: 35.677294, name: "日本column", color: '#A41FE8'},
{ lon: 77.198596, lat: 28.575136, name: "印度column", color: '#E8BB1F' },
{ lon: -77.02238, lat: 38.900042, name: "美國column", color: '#E81F56' },
{ lon: 31.266092, lat: 30.085626, name: "埃及column", color: '#1FFBC6' },
{ lon: 103.813654, lat: 1.291125, name: '新加坡column', color: '#E8BB1F' },
{ lon: -47.930912, lat: -15.781949, name: '巴西column', color: '#A41FE8' },
{ lon: 149.130214, lat: -35.318235, name: '澳大利亞column', color: '#E81F56' }
],
rotation: '關(guān)閉旋轉(zhuǎn)'
}
},
mounted () {
this.infoMap ()
},
methods: {
// 重置
reset () {
this.infoMap ()
},
// 地球添加標(biāo)記點(diǎn)
infoMap() {
for (let i = 0; i < this.objList.length; i++) {
this.$refs.threeMapId.infoMark(this.objList[i]);
}
},
// 移動(dòng)相機(jī)
cameraPos(item) {
this.$refs.threeMapId.cameraPos(item);
},
// 開啟或關(guān)閉地球自動(dòng)旋轉(zhuǎn)
rotationChange () {
this.$refs.threeMapId.rotationY = !this.$refs.threeMapId.rotationY
this.rotation = this.$refs.threeMapId.rotationY === true?'關(guān)閉旋轉(zhuǎn)':'開啟旋轉(zhuǎn)'
this.$refs.threeMapId.frameDivClose()
},
// 添加光柱infoColumn
columnChange () {
for (let i = 0; i < this.objList_2.length; i++) {
this.$refs.threeMapId.infoColumn(this.objList_2[i]);
}
},
// 刪除所有標(biāo)記點(diǎn)
delAllChange () {
for (let i = 0; i < this.objList.length; i++) {
this.$refs.threeMapId.delAll(this.objList[i]);
}
for (let i = 0; i < this.objList_2.length; i++) {
this.$refs.threeMapId.delAll(this.objList_2[i]);
}
},
// 刪除指定標(biāo)記點(diǎn)
delMarkChange () {
let item = {
name: '美國'
}
this.$refs.threeMapId.delMark(item)
},
}
}
</script>
<style lang="scss">
.home {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-image: url("../assets/images/back.png");
background-size: 100% 130%;
.rightMain {
position: absolute;
right: 0;
width: 300px;
height: 100%;
z-index: 100;
padding: 10px;
box-sizing: border-box;
color: white;
background-color: rgba(255,255,255, .2);
.title {
width: 100%;
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.cont {
height: 150px;
ul {
padding: 0;
margin-bottom: 0;
li {
list-style: none;
float: left;
width: 33.33%;
padding: 10px 0;
text-align: center;
cursor: pointer;
font-size: 14px;
&:hover {
color: aquamarine;
}
}
}
}
}
}
</style>
四:項(xiàng)目gitee地址文章來源:http://www.zghlxwxcb.cn/news/detail-523294.html
mythree: 基于three的3D地球案例文章來源地址http://www.zghlxwxcb.cn/news/detail-523294.html
到了這里,關(guān)于基于ThreeJS的3D地球的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!