Let’s take a look at the basics

The demo address

preface

In this chapter we will make a drag and drop, start with the autobiography, click on the city can move the earth demo

The first thing you need to know is what three.js is. To put it simply, Three.js is an excellent WebGL open source framework, three(3D) + JS (javaScript)– learn about Three.js.

WebGL is a set of specifications for implementing 3D effects in browsers.

There are three important concepts in three.js that need to be understood first:

  • There is a scene.
  • Camera
  • Renderer

1, the scene

A scene is a very important concept in the WebGL world, it is the container that holds all objects. It’s easy to create a new Scene in three.js: New three. Scene. The code is as follows:

const scene = new THREE.Scene(); // There is only one scenarioCopy the code

2, camera

Const camera = new THREE. PerspectiveCamera (75, window. InnerWidth/window. InnerHeight, 0.1, 1000).Copy the code

PerspectiveCamera(FOV, aspect, Near, far).

  • Fov (Number): indicates the Angle of elevation
  • Aspect (Number): length to width ratio of the sectional plane, usually the length to width ratio of the canvas.
  • Near (Number): indicates the distance of the near plane
  • Far (Number): distance from far

3, the renderer

The renderer determines what elements of the page the rendered results should be drawn on, and how they should be drawn.

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
Copy the code

Start drawing the Earth

1. Instantiate a Scence object that holds our Earth entity.

function initScene() {
    scene = new THREE.Scene();
    scene.opacity = 0;
    scene.transparent = true;
}
Copy the code

2. Prepare to set up the camera position and adjust the Angle. In three.js, the right hand coordinate system is used

Const camera = new THREE. PerspectiveCamera (75, window. InnerWidth/window. InnerHeight, 0.1, 1000).Copy the code

This is a perspective camera. The bigger the view, the bigger the scene, the smaller the object in the middle is relative to the scene.

3. Renderer

Function initThree() {// Instantiate the three. WebGLRenderer object. Renderer = new THREE.WebGLRenderer({antialias: true, // antialiasing alpha: true, // intensity canvas: renderer}); Renderer. setSize(window.innerwidth, window.innerheight); // Set the size of renderer. RenderHelper (renderer.domelement) // set the clear color and opacity. The renderer. SetClearColor (0 x000000, 1.0); }Copy the code

4. Draw the Earth. The earth here is actually a globe with a sticker on it

Function initEarth() {// Instantiate a sphere of radius 200 const earthGeo = new three.spheregeometry (200, 100, 100); const earthMater = new THREE.MeshPhongMaterial({ specular: 0x404040, shininess: 5, map: Textureloader.load ('/earth.jpg'), // specularMap: textureloader.load ('/earth_spec.jpg'), // use the texture bumpMap to create the bumpMap: textureLoader.load('/earth_bump.jpg') }); earthMesh = new THREE.Mesh(earthGeo, earthMater); earthMesh.rotation.y = -correctRotate; earthMesh.name = "earth" scene.add(earthMesh); }Copy the code

5. Earth plus clouds

const cloudsMesh; Var cloudsGeo = new THREE.SphereGeometry(201, 100, 100); // Set opacity of materials. When transparent is set to true, special treatment will be given to materials, which will cause some loss of performance. var cloudsMater = new THREE.MeshPhongMaterial({ alphaMap: new THREE.TextureLoader().load('/clouds.jpg'), transparent: True, opacity: 0.2}); cloudsMesh = new THREE.Mesh(cloudsGeo, cloudsMater); scene.add(cloudsMesh); }Copy the code

6. Light up the world

Function initLight() {// The light is located directly above the scene. The color changes from sky color to ground color. // light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1); Const allLight = new THREE.AmbientLight(0xFFFFFF); // Const allLight = new THREE. allLight.position.set(100, 100, 200); // Light = new THREE.DirectionalLight(0xffffbb, 1); light.position.set(-11, 3, 1); Const sun = new THREE.SpotLight(0x393939, 2.5); sun.position.set(-15, 10, 21); scene.add(allLight, light, sun); }Copy the code

7. Coordinate

/** * LNG: longitude *lat: dimension *radius: earth radius */ function Point(LNG, lat, // Convert latitude and longitude to three const lg = three.math.degtorad (LNG), lt = three.math.degtorad (lat); const y = radius * Math.sin(lt); const temp = radius * Math.cos(lt); const x = temp * Math.sin(lg); const z = temp * Math.cos(lg); return {x: x, y: y, z: Sprite = new THREE.Sprite(new) const Sprite = new THREE Three.spritematerial ({map: textureLoader.load('/location.png'), depthWrite: false, // forbid writing depth buffer data}); Const pos = Point(location.coord[1], location.coord[0], 200 * 1.05) sprite.coord = {lg: location.coord[1], lt: location.coord[0]} sprite.position.set(pos.x, pos.y, pos.z); sprite.scale.set(20, 20, 1); Sprite. name = location.name // cityname const spriteText = new three. Sprite(new three. SpriteMaterial({color: 0x000000, map: Textureloader.load (' /i_${(location.name).tolowerCase ()}.png '), depthWrite: false, // Forbid writing depth buffer data}); spriteText.position.set(pos.x, pos.y, pos.z); SpriteText. Scale. The set (50, 50 and 1.5); locationGroup.add(sprite, spriteText); scene.add(locationGroup); })}Copy the code

8. Add click and rotate controls

function startControl() { document.addEventListener('mousedown', onPointClick); // Load controller controls = new OrbitControls(camera, renderer.domElement); } function onPointClick(e) { e.preventDefault(); // The screen coordinates of the mouse click position are converted to the standard coordinates in threejs: -1<x<1, -1<y<1 mouse. X = (e.clientx/window.innerWidth) * 2-1; mouse.y = -(e.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); / / get raycaster intersecting lines and all model set the array of const intersects = raycaster. IntersectObjects (locationGroup. Children, true); If (intersects. Length > 0 &&intersects [0].object.name) {if (city) city.scale.set(20, 20, 1); city = intersects[0].object; RotateToCenter (city)}} function rotateToCenter(city) {// zoom in on city.scale.set(30, 30, 1); Const cityName = city.name; const cityText = document.querySelector(".showCity"); setTimeout(function () { cityText.innerText = cityName; }, 500) // Rotate to center const rotateRad = rotate2Center(city.coord); let finalY = -rotateRad.y; while (earthMesh.rotation.y > 0 && finalY + Math.PI * 2 < earthMesh.rotation.y) finalY += Math.PI * 2; while (earthMesh.rotation.y < 0 && finalY - Math.PI * 2 > earthMesh.rotation.y) finalY -= Math.PI * 2; if (Math.abs(finalY - earthMesh.rotation.y) > Math.PI) { if (finalY > earthMesh.rotation.y) finalY -= Math.PI * 2; else finalY += Math.PI * 2; } const needRotateX = rotateRad.x - earthMesh.rotation.x + controls.object.rotation.x const needRotateY = finalY - earthMesh.rotation.y - correctRotate + controls.object.rotation.y rotateEarth(needRotateX, needRotateY); Function rotate2Center(coord) {return {x: degToRad(coord.lt), y: degToRad(coord.lg)}; Function rotateEarth(intervalX, intervalY) {if (tween) tween.stop();} function rotateEarth(intervalX, intervalY) {if (tween) tween.stop(); tween = new TWEEN.Tween({ rotateY: earthMesh.rotation.y, rotateX: earthMesh.rotation.x, rotateLoc: locationGroup.rotation.y }) .to({ rotateY: earthMesh.rotation.y + intervalY, rotateX: earthMesh.rotation.x + intervalX, rotateLoc: locationGroup.rotation.y + intervalY }, 1000); tween.easing(TWEEN.Easing.Sinusoidal.InOut); const onUpdate = function () { earthMesh.rotation.y = this._object.rotateY; earthMesh.rotation.x = this._object.rotateX; locationGroup.rotation.y = this._object.rotateLoc; locationGroup.rotation.x = this._object.rotateX; } const onComplete = function () { } tween.onUpdate(onUpdate); tween.onComplete(onComplete); tween.start(); } function rotateToCity(cityName) { city = locationGroup.children.find((location) => { return location.name === cityName  }) rotateToCenter(city) }Copy the code

9. Animation and rotation

function animate() { stats.update(); renderer.clear(); EarthMesh. Rotate. + y = 0.05 locationGroup. Rotate. + y = 0.05 the renderer. Render (scene, camera); requestAnimationFrame(() => { TWEEN.update(); if (controls) { controls.update(); animate() } }); }Copy the code

The complete code

city.js

LOCATIONS = [{name: 'SHANGHAI', coord: [30 ° 40', 120° 52'}, {name: // 39° 92' N, 116° 46' E}, {name: 'WASHINGTON', Coord: [39.92, 116.46] // 39° 92' N, 116° 46' E}, {name: 'WASHINGTON', Coord: [39.92, 116.46] [38.91, -77.02] // 38° 91' N, 77° 02' W}, {name: [37.50, 144.58] / / 37 ° 50 'S, 58' 144 ° E}, {name: 'RIO', coord: [22.54, 43.12] / / 22 ° 54 '43 ° 12' S, W}, {name: 'LONDON', COORD: [51.30, 0.5] // 22° 54's, 43° 12' W}, {name: 'ANTARCTICA', COORD: [-90,360]}, {name: 'ANTARCTICA', coORD: [-90,360]}, {name: 'GREENLAND', COORD: [75.930886,-40.253906]}, {name: 'NAMIBIA', COORD: [-22.57,17.086117]}]Copy the code

renderHelper.js

export const renderHelper = (dom) =>{ const el = document.querySelector('.showDemos') if (! el)return el.innerHTML = '' el.appendChild(dom) }Copy the code

earthScreen.js

import * as THREE from "three";
import {renderHelper} from "../renderHelper";
import Stats from "three/examples/jsm/libs/stats.module";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import {LOCATIONS} from "./city";
import {TWEEN} from "three/examples/jsm/libs/tween.module.min";
import {degToRad} from "three/src/math/MathUtils";
import {useEffect} from "react";
import './city.css'

export const EarthScreen = () => {
    let scene, camera, renderer, earthMesh, light, controls, stats,
        tween, city = null
    const mouse = new THREE.Vector2();
    const locationGroup = new THREE.Group();
    const raycaster = new THREE.Raycaster()
    const textureLoader = new THREE.TextureLoader();
    const correctRotate = (Math.PI / 2).toFixed(2)

    useEffect(() => {
        threeStart();
        rotateToCity('SHANGHAI')
    }, [])

    function initScene() {
        scene = new THREE.Scene();
        scene.opacity = 0;
        scene.transparent = true;
    }

    function initCamera() {
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.z = 500;
    }

    // 渲染器
    function initThree() {
        // 实例化 THREE.WebGLRenderer 对象。
        renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: true,
            canvas: renderer
        });
        // 设置 renderer 的大小
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 挂载到准备的 domElement 上
        renderHelper(renderer.domElement)
        // Sets the clear color and opacity.
        renderer.setClearColor(0x000000, 1.0);
    }

    // 帧蘋
    function initStats() {
        stats = new Stats();
        const container = document.querySelector('.showDemos')
        if (!container) return
        container.appendChild(stats.dom)
    }

    // 地球
    function initEarth() {
        // 实例化一个半径为 200 的球体
        const earthGeo = new THREE.SphereGeometry(200, 100, 100);
        const earthMater = new THREE.MeshPhongMaterial({
            specular: 0x404040,
            shininess: 5,
            map: textureLoader.load('/earth.jpg'),
            specularMap: textureLoader.load('/earth_spec.jpg'),
            bumpMap: textureLoader.load('/earth_bump.jpg')
        });
        earthMesh = new THREE.Mesh(earthGeo, earthMater);
        earthMesh.rotation.y = -correctRotate;
        earthMesh.name = "earth"
        scene.add(earthMesh);
    }

    // 光源
    function initLight() {
        // 位于场景正上方的光源,颜色从天空颜色渐变为地面颜色。
        // light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);

        // 环境光
        const allLight = new THREE.AmbientLight(0xFFFFFF);
        allLight.position.set(100, 100, 200);

        // 平行光 位置不同,方向光作用于物体的面也不同,看到的物体各个面的颜色也不一样
        light = new THREE.DirectionalLight(0xffffbb, 1);
        light.position.set(-11, 3, 1);

        const sun = new THREE.SpotLight(0x393939, 2.5);
        sun.position.set(-15, 10, 21);

        scene.add(allLight, light, sun);
    }

    /**
     *lng:经度
     *lat:维度
     *radius:地球半径
     */
    function Point(lng, lat, radius) {
        const lg = THREE.Math.degToRad(lng),
            lt = THREE.Math.degToRad(lat);
        const y = radius * Math.sin(lt);
        const temp = radius * Math.cos(lt);
        const x = temp * Math.sin(lg);
        const z = temp * Math.cos(lg);
        return {x: x, y: y, z: z}
    }

    function createPointMesh() {
        // 打点
        LOCATIONS.forEach(location => {
                //设置坐标
                const sprite = new THREE.Sprite(new THREE.SpriteMaterial(
                    {
                        map: textureLoader.load('/location.png'),
                        depthWrite: false, //禁止写入深度缓冲区数据
                    }));
                const pos = Point(location.coord[1], location.coord[0], 200 * 1.05)
                sprite.coord = {lg: location.coord[1], lt: location.coord[0]}
                sprite.position.set(pos.x, pos.y, pos.z);
                sprite.scale.set(20, 20, 1);
                sprite.name = location.name
                //城市名
                const spriteText = new THREE.Sprite(new THREE.SpriteMaterial(
                    {
                        color: 0x000000,
                        map: textureLoader.load(`/i_${(location.name).toLowerCase()}.png`),
                        depthWrite: false, //禁止写入深度缓冲区数据
                    }));
                spriteText.position.set(pos.x, pos.y, pos.z);
                spriteText.scale.set(50, 50, 1.5);
                locationGroup.add(sprite, spriteText);
                scene.add(locationGroup);
            }
        )
    }

    function rotate2Center(coord) {
        return {x: degToRad(coord.lt), y: degToRad(coord.lg)};
    }

    function rotateEarth(intervalX, intervalY) {
        if (tween) tween.stop();
        tween = new TWEEN.Tween({
            rotateY: earthMesh.rotation.y,
            rotateX: earthMesh.rotation.x,
            rotateLoc: locationGroup.rotation.y
        })
            .to({
                rotateY: earthMesh.rotation.y + intervalY,
                rotateX: earthMesh.rotation.x + intervalX,
                rotateLoc: locationGroup.rotation.y + intervalY
            }, 1000);
        tween.easing(TWEEN.Easing.Sinusoidal.InOut);
        const onUpdate = function () {
            earthMesh.rotation.y = this._object.rotateY;
            earthMesh.rotation.x = this._object.rotateX;
            locationGroup.rotation.y = this._object.rotateLoc;
            locationGroup.rotation.x = this._object.rotateX;
        }
        const onComplete = function () {
        }
        tween.onUpdate(onUpdate);
        tween.onComplete(onComplete);
        tween.start();
    }

    function onPointClick(e) {
        e.preventDefault();
        // 鼠标点击位置的屏幕坐标转换成threejs中的标准坐标-1<x<1, -1<y<1
        mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
        raycaster.setFromCamera(mouse, camera);
        // 获取raycaster直线和所有模型相交的数组集合
        const intersects = raycaster.intersectObjects(locationGroup.children, true);
        if (intersects.length > 0 && intersects[0].object.name) {
            // 只取第一个相交物体
            if (city) city.scale.set(20, 20, 1);
            city = intersects[0].object;
            rotateToCenter(city)
        }
    }

    function rotateToCenter(city) {
        // 放大
        city.scale.set(30, 30, 1);

        // 显示城市名
        const cityName = city.name;
        const cityText = document.querySelector(".showCity");
        setTimeout(function () {
            cityText.innerText = cityName;
        }, 500)

        // 旋转到中心
        const rotateRad = rotate2Center(city.coord);
        let finalY = -rotateRad.y;
        while (earthMesh.rotation.y > 0 && finalY + Math.PI * 2 < earthMesh.rotation.y) finalY += Math.PI * 2;
        while (earthMesh.rotation.y < 0 && finalY - Math.PI * 2 > earthMesh.rotation.y) finalY -= Math.PI * 2;
        if (Math.abs(finalY - earthMesh.rotation.y) > Math.PI) {
            if (finalY > earthMesh.rotation.y) finalY -= Math.PI * 2;
            else finalY += Math.PI * 2;
        }
        const needRotateX = rotateRad.x - earthMesh.rotation.x + controls.object.rotation.x
        const needRotateY = finalY - earthMesh.rotation.y - correctRotate + controls.object.rotation.y
        rotateEarth(needRotateX, needRotateY);
    }

    function rotateToCity(cityName) {
        city = locationGroup.children.find((location) => {
            return location.name === cityName
        })
        rotateToCenter(city)
    }

    function startControl() {
        document.addEventListener('mousedown', onPointClick);
        // 载入控制器
        controls = new OrbitControls(camera, renderer.domElement);
    }

    function threeStart() {
        initThree();
        initScene();
        initCamera();
        initStats();
        initLight();
        initEarth();
        createPointMesh()
        startControl()
        animate();
    }

    function animate() {
        stats.update();
        renderer.clear();
        renderer.render(scene, camera);
        requestAnimationFrame(() => {
            TWEEN.update();
            if (controls) {
                controls.update();
                animate()
            }
        });
    }

    return <div className='showCity'/>
}
Copy the code