带你入门three.js——从0到1实现一个3d可视化地图

发布时间 2023-11-07 15:16:05作者: 7c89
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()