https://juejin.cn/post/6980983551399788580
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Three.js入门</title>
<style>
#tooltip {
position: fixed;
z-index: 999;
background: white;
padding: 10px;
border-radius: 2px;
visibility: hidden;
user-select: none;
}
#canvas{
position: relative;
z-index: 1;
}
</style>
</head>
<body>
<script src="./script.js" type="module"></script>
<div id="tooltip"></div>
</body>
</html>
import { geoMercator } from "d3-geo";
import floor from "./images/floor_wood.jpeg";
import uv from "./images/uv.png";
// 立方体的顶部纹理
import grass_top from "./images/grass_top.png";
// 立方体的侧边纹理
import grass_side from "./images/grass_side.png";
// 立方体的底部纹理
import grass_bottom from "./images/grass_bottom.png";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 1. 创建渲染器,指定渲染的分辨率和尺寸,然后添加到body中
const renderer = new THREE.WebGLRenderer({
antialias: true, // true/false表示是否开启反锯齿
alpha: true, // true/false 表示是否可以设置背景色透明
precision: 'highp', // highp/mediump/lowp 表示着色精度选择
premultipliedAlpha: false, // true/false 表示是否可以设置像素深度(用来度量图像的分率)
preserveDrawingBuffer: true, // true/false 表示是否保存绘图缓冲
maxLights: 3, // 最大灯光数
stencil: false
});
renderer.pixelRatio = window.devicePixelRatio;
renderer.setSize(window.innerWidth, window.innerHeight);
// document.body.append(renderer.domElement);
class chinaMap {
constructor() {
this.init()
}
init() {
// 第一步新建一个场景
this.scene = new THREE.Scene()
this.setCamera()
this.setRenderer()
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
// this.cube=cube
// this.scene.add(cube)
this.render()
// const controls = new OrbitControls(this.camera, this.renderer.domElement);
// controls.update();
this.loadMapData()
this.addHelper()
this.setController()
this.setRaycaster()
// this.showTip()
this.animate() //不断渲染
}
setController() {
this.controller = new OrbitControls(
this.camera,
document.getElementById('canvas')
)
}
addHelper() {//辅助视图
const helper = new THREE.CameraHelper(this.camera)
this.scene.add(helper)
}
// 加载地图数据
loadMapData() {
const loader = new THREE.FileLoader()
console.log();
this.generateGeometry(require('./china.json'))
// loader.load('china.json', (data) => {
// console.log(data);
// const jsondata = JSON.parse(JSON.stringify(data))
// console.log(jsondata);
// this.generateGeometry(jsondata)
// })
}
//render 方法
render() {
this.renderer.render(this.scene, this.camera)
}
showTip() {
// console.log(this.lastPick);
// 显示省份的信息
if (this.lastPick) {
const properties = this.lastPick.object.parent.properties
this.tooltip.textContent = properties.name
this.tooltip.style.visibility = 'visible'
} else {
this.tooltip.style.visibility = 'hidden'
}
}
setRaycaster() {
this.raycaster = new THREE.Raycaster()
this.mouse = new THREE.Vector2()
this.tooltip = document.getElementById('tooltip')
const onMouseMove = (event) => {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
// 更改div位置
this.tooltip.style.left = event.clientX + 2 + 'px'
this.tooltip.style.top = event.clientY + 2 + 'px'
}
window.addEventListener('mousemove', onMouseMove, false)
}
animate() {
requestAnimationFrame(this.animate.bind(this))
// 通过摄像机和鼠标位置更新射线
this.raycaster.setFromCamera(this.mouse, this.camera)
// 算出射线 与当场景相交的对象有那些
const intersects = this.raycaster.intersectObjects(
this.scene.children,
true
)
// 恢复上一次清空的
if (this.lastPick) {
this.lastPick.object.material[0].color.set('#2defff')
this.lastPick.object.material[1].color.set('#3480C4')
}
this.lastPick = null
this.lastPick = intersects.find(
(item) => item.object.material && item.object.material.length === 2
)
if (this.lastPick) {
this.lastPick.object.material[0].color.set(0xff0000)
this.lastPick.object.material[1].color.set(0xff0000)
}
this.showTip()
this.render()
}
// 新建透视相机
setCamera() {
// 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
this.camera.position.z = 100
}
// 设置渲染器
setRenderer() {
this.renderer = new THREE.WebGLRenderer()
// 设置画布的大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
//这里 其实就是canvas 画布 renderer.domElement
this.renderer.domElement.setAttribute('id','canvas')
document.body.appendChild(this.renderer.domElement)
}
// 设置环境光
setLight() {
this.ambientLight = new THREE.AmbientLight(0xffffff) // 环境光
this.scene.add(ambientLight)
}
generateGeometry(jsondata) {
console.log(jsondata);
// 初始化一个地图对象
this.map = new THREE.Object3D()
// 墨卡托投影转换
const projection = geoMercator()
.center([104.0, 37.5])
.scale(80)
.translate([0, 0])
jsondata.features.forEach((elem) => {
// 定一个省份3D对象
const province = new THREE.Object3D()
province.properties = elem.properties
this.map.add(province)
// 每个的 坐标 数组
const coordinates = elem.geometry.coordinates
console.log(coordinates);
// 循环坐标数组
coordinates.forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
const shape = new THREE.Shape()
const lineMaterial = new THREE.LineBasicMaterial({
color: 'white',
})
const lineGeometry = new THREE.BufferGeometry()
const points = [];
for (let i = 0; i < polygon.length; i++) {
const [x, y] = projection(polygon[i])
if (i === 0) {
shape.moveTo(x, -y)
}
shape.lineTo(x, -y)
points.push(new THREE.Vector3(x, -y, 4.01))
}
// 绑定顶点到空几何体
lineGeometry.setFromPoints(points);
const extrudeSettings = {
depth: 10,
bevelEnabled: false,
}
const geometry = new THREE.ExtrudeGeometry(
shape,
extrudeSettings
)
const material = new THREE.MeshBasicMaterial({
color: '#2defff',
transparent: true,
opacity: 0.6,
})
const material1 = new THREE.MeshBasicMaterial({
color: '#3480C4',
transparent: true,
opacity: 0.5,
})
const mesh = new THREE.Mesh(geometry, [material, material1])
const line = new THREE.Line(lineGeometry, lineMaterial)
province.add(mesh)
province.add(line)
})
})
})
this.scene.add(this.map)
this.render()
}
}
let a = new chinaMap()