美文网首页网页前端后台技巧(CSS+HTML)视觉艺术让前端飞
web前端入门到实战:threejs 绘制地球、飞机、轨迹

web前端入门到实战:threejs 绘制地球、飞机、轨迹

作者: 大前端世界 | 来源:发表于2019-11-25 16:47 被阅读0次

    首先我们来看下要实现的效果

    这个缩小后的图片,下面我们来看下近距离的动态效果。。

    效果比较简陋,需要后期再处理。。。

    下面进入主题,代码篇。。

    HTML部分:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>全球航班</title>
    
        <style>
            html{overflow: hidden;}
            body { margin: 0;}
        </style>
    
        <script src="js/jquery.min.js"></script>
    </head>
    <body>
        <!-- 地国 -->
        <div id="zh_globe_container"></div> <!-- 容器 -->
    
        <script src="js/threejs/Detector.js"></script> <!-- webGL浏览器支持检测 -->
        <script src="js/threejs/three.min.js"></script> <!-- 核心js -->
        <script src="js/threejs/stats.min.js"></script> <!-- 性能测试 -->
        <script src="js/threejs/OrbitControls.js"></script> <!-- 地球控制 -->
        <script src="js/socketio-1.4.5.js"></script> <!-- socket -->
    
        <script src="js/globe.js"></script> <!--  -->
    </body>
    </html>
    web前端开发学习Q-q-u-n: 731771211,分享学习的方法和需要注意的小细节,不停更新最新的教程和学习方法(详细的前端项目实战教学视频,PDF)
    

    JS部分(globe.js)

    1、实现地球
    地球贴图(可以在网上下载)

    // 地球
    function globe() {
        var globeTextureLoader = new THREE.TextureLoader();
        globeTextureLoader.load('images/textures/earth.jpg', function (texture) {
            var globeGgeometry = new THREE.SphereGeometry(200, 100, 100);
            var globeMaterial = new THREE.MeshStandardMaterial({map: texture});
            var globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial);
            group.add(globeMesh);
            group.rotation.x = THREE.Math.degToRad(35);
            group.rotation.y = THREE.Math.degToRad(170);
        });
    }
    

    2、添加球面光源(这里使用的是半球光)

    // 光
    function lights() {
        var hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x333333, 2);
        hemisphereLight.position.x = 0;
        hemisphereLight.position.y = 0;
        hemisphereLight.position.z = -200;
        group.add(hemisphereLight);
    }
    

    3、添加星点

    // 星点
    function stars() {
        var starsGeometry = new THREE.Geometry();
        for (var i = 0; i < 2000; i ++) {
            var starVector = new THREE.Vector3(
                THREE.Math.randFloatSpread(2000),
                THREE.Math.randFloatSpread(2000),
                THREE.Math.randFloatSpread(2000)
            );
            starsGeometry.vertices.push(starVector);
        }
        var starsMaterial = new THREE.PointsMaterial({color: 0x888888})
        var starsPoint = new THREE.Points(starsGeometry, starsMaterial);
        group.add(starsPoint);
    }
    

    4、添加飞机

    这里需要我们把 经纬度坐标 转成 xyz 坐标

    // 获取position
    function getPosition(lng, lat, alt) {
        var phi = (90-lat)*(Math.PI/180),
            theta = (lng+180)*(Math.PI/180),
            radius = alt+200,
            x = -(radius * Math.sin(phi) * Math.cos(theta)),
            z = (radius * Math.sin(phi) * Math.sin(theta)),
            y = (radius * Math.cos(phi));
        return {x: x, y: y, z: z};
    }
    

    画飞机

    // 飞机形状(不想画的,可以下载个 飞机模型 使用加载器加载进来)
    var planeShape = new THREE.Shape();
    planeShape.moveTo( 0, 0);
    planeShape.lineTo(0.2, -0.2);
    planeShape.lineTo(0.2, -1.3);
    planeShape.lineTo(1.6,-2.7);
    planeShape.lineTo(1.6,-3);
    planeShape.lineTo(0.2, -2.1);
    planeShape.lineTo(0.2, -3);
    planeShape.lineTo(0.5, -3.4);
    planeShape.lineTo(0.5, -3.7);
    planeShape.lineTo(0, -3.3);
    planeShape.lineTo(-0.5, -3.7);
    planeShape.lineTo(-0.5, -3.4);
    planeShape.lineTo(-0.2, -3);
    planeShape.lineTo(-0.2, -2.1);
    planeShape.lineTo(-1.6,-3);
    planeShape.lineTo(-1.6,-2.7);
    planeShape.lineTo(-0.2, -1.3);
    planeShape.lineTo(-0.2, -0.2);
    var planeGeometry = new THREE.ShapeGeometry(planeShape);
    // 飞机材质
    var planeMaterial = new THREE.MeshPhongMaterial({color: 0x0FB4DD, side: THREE.DoubleSide, depthTest: true});
    web前端开发学习Q-q-u-n: 731771211,分享学习的方法和需要注意的小细节,不停更新最新的教程和学习方法(详细的前端项目实战教学视频,PDF)
    

    depthTest作用是能否透过球体看到飞机,如果是false则旋转到球体另一面也能看到飞机

    添加飞机

    // 添加飞机
    function addPlane(item) {
        if(item.anum && item.lng && item.lat) {
            var plane = new THREE.Mesh(planeGeometry, planeMaterial);
            // 旋转
            plane.rotation.z = THREE.Math.degToRad(item.ang);
            // 定位
            var position = getPosition(item.lng, item.lat, 5);
            plane.position.set(position.x, position.y, position.z);
            // 显示/隐藏
            // plane.visible = false;
            // 保存
            planeMarkers[item.anum] = plane;
            // 添加到场景
            group.add(plane);
            // 绘制历史轨迹
            drawHistoryTrack(item.anum);
        }
    }
    

    绘制轨迹(使用socket来获取的飞行轨迹经纬度坐标点)

    // 时间段
    var curTime = Date.parse(new Date())/1000;
    var depTime = curTime - 30*60;
    // 轨迹线质
    var trackMaterial = new THREE.LineBasicMaterial({color : 0x1B94B1});
    // 绘制历史轨迹
    function drawHistoryTrack(anum) {
        socket.emit("fullPath", anum, depTime, curTime, function(status, data){
            if(status) {
                var dLength = data.length;
                if(dLength>=2) {
                    var trackCoordArr = [];
                    for(var i=0; i<dLength; i++) {
                        if(data[i].lng && data[i].alt) {
                            trackCoordArr.push({lng: data[i].lng, lat: data[i].lat});
                        }
                    }
    
                    var tcaLength = trackCoordArr.length;
                    if(tcaLength>=2) {
                        var tcaHalfLength = Math.ceil(tcaLength/2),
                            vertexArr = [];
    
                        // 这里只取了三个点(起点、中点、终点)
                        var p1 = getPosition(trackCoordArr[0].lng, trackCoordArr[0].lat, 0),
                            p2 = getPosition(trackCoordArr[tcaHalfLength].lng, trackCoordArr[tcaHalfLength].lat, tcaLength*0.01),
                            p3 = getPosition(trackCoordArr[tcaLength-1].lng, trackCoordArr[tcaLength-1].lat, 0);
    
                        var trackCurve = new THREE.CatmullRomCurve3([
                            new THREE.Vector3(p1.x, p1.y, p1.z),
                            new THREE.Vector3(p2.x, p2.y, p2.z),
                            new THREE.Vector3(p3.x, p3.y, p3.z)
                        ]);
    
                        var trackGeometry = new THREE.Geometry(),
                            verticesArr = trackCurve.getPoints(tcaLength);
    
                        trackGeometry.vertices = verticesArr;
    
                        var trackLine = new THREE.Line(trackGeometry, trackMaterial);
                        group.add(trackLine);
    
                        // 动画点
                        addLightPoint(p1, tcaLength, verticesArr);
                    }
                }
            }
        });
    }
    

    如果要绘制所有点,且头尾是在球面上的曲线,则需要两次循环

    var tcaRemainLength = tcaLength-tcaHalfLength
    for(var j=0; j<tcaHalfLength; j++) { // 前一半
        var p1 = getPosition(trackCoordArr[j].lng, trackCoordArr[j].lat, j*0.05);
        vertexArr.push(new THREE.Vector3(p1.x, p1.y, p1.z));    
    }
    for(var k=tcaRemainLength; k>0; k--) { // 后一半
        var p2 = getPosition(trackCoordArr[tcaLength-k].lng, trackCoordArr[tcaLength-k].lat, k*0.05);
        vertexArr.push(new THREE.Vector3(p2.x, p2.y, p2.z));    
    }
    
    var trackCurve = new THREE.CatmullRomCurve3(vertexArr);
    

    这个部分看看就行了。。

    光点动画

    // 点动画
    var pointGeometry = new THREE.SphereGeometry(0.2, 20, 20);
    var pointMaterial = new THREE.MeshBasicMaterial({color: 0x40E0D0});
    function addLightPoint(pos, coordsNum ,verArr) {    
        var pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
        pointMesh.position.set(pos.x, pos.y, pos.z);
        group.add(pointMesh);
    
        var index = 0;
        function pointAnimate() {
            index++;
            if(index>coordsNum) {
                index = 0;
            }
            pointMesh.position.set(verArr[index].x, verArr[index].y, verArr[index].z);
            requestAnimationFrame(pointAnimate);
        }
        pointAnimate();
    }
    

    这个点使用的是sphere,,当然也可以用顶点来实现,如下

    var geometry = new THREE.Geometry();
    geometry.vertices.push(new THREE.Vector3(0, 0, 0))
    geometry.colors.push(new THREE.Color(0xffffff));
    
    var material = new THREE.PointsMaterial({size: 1, vertexColors: THREE.VertexColors, opacity: 0.75, sizeAttenuation: true, transparent: true});
    var point = new THREE.Points(geometry, material);
    point.position.set(x, y, z);
    group.add(point);
    

    另外不想用光点动画的话,也可以用线动画,实现原理是不断更新顶点坐标,如下

    var curveGeometry = new THREE.Geometry(); 
    var curveData = new THREE.CatmullRomCurve3(verArr.slice(0, 10));  
    curveGeometry.vertices = curveData.getPoints(10);
    
    var curveMaterial = new THREE.LineBasicMaterial({color: 0x40E0D0});
    var curveLine = new THREE.Line(curveGeometry, curveMaterial);
    group.add(curveLine);
    
    var index = 0;
    function lineAnimate() {
        index++;
        if(index>coordsNum-10) {
            index = 0;
        }
        var offsetData = verArr.slice(index, 10+index);
        if(offsetData.length > 0) {
            curveData = new THREE.CatmullRomCurve3(offsetData);  
               curveLine.geometry.vertices = curveData.getPoints(10);
            curveLine.geometry.verticesNeedUpdate = true;
        }
        requestAnimationFrame(lineAnimate);
    }
    lineAnimate();
    

    最后就是布置场景和事件了

    // 初始化
    function init() {
        container = document.getElementById('zh_globe_container');
    
        scene = new THREE.Scene();
        var bgTexture = new THREE.TextureLoader().load("images/textures/starfield.jpg");
        scene.background = bgTexture;
    
        camera = new THREE.PerspectiveCamera(50, winWth/winHgt, 1, 2000);
        camera.up.x = 0;
        camera.up.y = 1;
        camera.up.z = 0;
        camera.position.x = 0;
        camera.position.y = 0;
        camera.position.z = 400;
        camera.lookAt(0,0,0);
    
        group = new THREE.Group();
        scene.add(group);
    
        // 地球    
        globe();
    
        // 飞机
        plane();
    
        // 星点
        stars();
    
        // 半球光
        lights();
    
        // 渲染器
        renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(winWth, winHgt);
        container.appendChild(renderer.domElement);
    
        // 盘旋控制
        var orbitControl = new THREE.OrbitControls(camera, renderer.domElement);
        orbitControl.minDistrance = 20;
        orbitControl.maxDistrance = 50;
        orbitControl.maxPolarAngle = Math.PI/2;
    
        // 性能测试
        stats = new Stats();
        container.appendChild(stats.dom);
    
        // resize事件
        window.addEventListener('resize', onWindowResize, false);
    }
    
    // 窗口大小改变
    function onWindowResize() {
        camera.aspect = window.innerWidth/window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }
    
    // 渲染
    function render() {
        group.rotation.y -= 0.0005;
        renderer.render(scene, camera);
    }
    
    // 动画
    function animate() {
        requestAnimationFrame(animate);
        render();
        stats.update();
    }
    
    init();
    animate();    
    

    完整代码:

    web前端开发学习Q-q-u-n: 731771211,分享学习的方法和需要注意的小细节,不停更新最新的教程和学习方法(详细的前端项目实战教学视频,PDF)
    var log = console.log.bind(console);
    
    var globeObj = (function() {
        'use strict';
    
        // 判断浏览器是否支持webgl
        if(!Detector.webgl) Detector.addGetWebGLMessage();
    
        var container, stats;
        var camera, scene, renderer;
        var group;
        var mouseX = 0, mouseY = 0;
        var winWth = window.innerWidth, winHgt = window.innerHeight;
    
        // 获取position
        function getPosition(lng, lat, alt) {
            var phi = (90-lat)*(Math.PI/180),
                theta = (lng+180)*(Math.PI/180),
                radius = alt+200,
                x = -(radius * Math.sin(phi) * Math.cos(theta)),
                z = (radius * Math.sin(phi) * Math.sin(theta)),
                y = (radius * Math.cos(phi));
            return {x: x, y: y, z: z};
        }
    
        // 飞机
        function plane() {
            var socket = io('https://loc.variflight.com/*****此处接口地址不能给了', {transports: ['websocket']});
    
            var clientBounds = [52.793056,72.427908,2.970897,135.181814];
    
            // 连接
            socket.on('connect', function() {
                socket.emit("sub", clientBounds, -1, '', function(){});
            });
    
            // 飞机标记
            var planeMarkers = {};
    
            // 飞机形状
            var planeShape = new THREE.Shape();
            planeShape.moveTo( 0, 0);
            planeShape.lineTo(0.2, -0.2);
            planeShape.lineTo(0.2, -1.3);
            planeShape.lineTo(1.6,-2.7);
            planeShape.lineTo(1.6,-3);
            planeShape.lineTo(0.2, -2.1);
            planeShape.lineTo(0.2, -3);
            planeShape.lineTo(0.5, -3.4);
            planeShape.lineTo(0.5, -3.7);
            planeShape.lineTo(0, -3.3);
            planeShape.lineTo(-0.5, -3.7);
            planeShape.lineTo(-0.5, -3.4);
            planeShape.lineTo(-0.2, -3);
            planeShape.lineTo(-0.2, -2.1);
            planeShape.lineTo(-1.6,-3);
            planeShape.lineTo(-1.6,-2.7);
            planeShape.lineTo(-0.2, -1.3);
            planeShape.lineTo(-0.2, -0.2);
            var planeGeometry = new THREE.ShapeGeometry(planeShape);
            // 飞机材质
            var planeMaterial = new THREE.MeshPhongMaterial({color: 0x0FB4DD, side: THREE.DoubleSide, depthTest: true});
            // 添加飞机
            function addPlane(item) {
                if(item.anum && item.lng && item.lat) {
                    var plane = new THREE.Mesh(planeGeometry, planeMaterial);
                    // 旋转
                    plane.rotation.z = THREE.Math.degToRad(item.ang);
                    // 定位
                    var position = getPosition(item.lng, item.lat, 5);
                    plane.position.set(position.x, position.y, position.z);
                    // 显示/隐藏
                    // plane.visible = false;
                    // 保存
                    planeMarkers[item.anum] = plane;
                    // 添加到场景
                    group.add(plane);
                    // 绘制历史轨迹
                    drawHistoryTrack(item.anum);
                }
            }
    
            // 时间段
            var curTime = Date.parse(new Date())/1000;
            var depTime = curTime - 30*60;
            // 轨迹线质
            var trackMaterial = new THREE.LineBasicMaterial({color : 0x1B94B1});
            // 绘制历史轨迹
            function drawHistoryTrack(anum) {
                socket.emit("fullPath", anum, depTime, curTime, function(status, data){
                    if(status) {
                        var dLength = data.length;
                        if(dLength>=2) {
                            var trackCoordArr = [];
                            for(var i=0; i<dLength; i++) {
                                if(data[i].lng && data[i].alt) {
                                    trackCoordArr.push({lng: data[i].lng, lat: data[i].lat});
                                }
                            }
    
                            var tcaLength = trackCoordArr.length;
                            if(tcaLength>=2) {
                                var tcaHalfLength = Math.ceil(tcaLength/2),
                                    tcaRemainLength = tcaLength-tcaHalfLength,
                                    vertexArr = [];
    
                                /* 所有点
                                for(var j=0; j<tcaHalfLength; j++) {
                                    var p1 = getPosition(trackCoordArr[j].lng, trackCoordArr[j].lat, j*0.05);
                                    vertexArr.push(new THREE.Vector3(p1.x, p1.y, p1.z));    
                                }
                                for(var k=tcaRemainLength; k>0; k--) {
                                    var p2 = getPosition(trackCoordArr[tcaLength-k].lng, trackCoordArr[tcaLength-k].lat, k*0.05);
                                    vertexArr.push(new THREE.Vector3(p2.x, p2.y, p2.z));    
                                }
    
                                var trackCurve = new THREE.CatmullRomCurve3(vertexArr);
                                */
    
                                // 三个点
                                var p1 = getPosition(trackCoordArr[0].lng, trackCoordArr[0].lat, 0),
                                    p2 = getPosition(trackCoordArr[tcaHalfLength].lng, trackCoordArr[tcaHalfLength].lat, tcaLength*0.01),
                                    p3 = getPosition(trackCoordArr[tcaLength-1].lng, trackCoordArr[tcaLength-1].lat, 0);
    
                                var trackCurve = new THREE.CatmullRomCurve3([
                                    new THREE.Vector3(p1.x, p1.y, p1.z),
                                    new THREE.Vector3(p2.x, p2.y, p2.z),
                                    new THREE.Vector3(p3.x, p3.y, p3.z)
                                ]);
    
                                var trackGeometry = new THREE.Geometry(),
                                    verticesArr = trackCurve.getPoints(tcaLength);
    
                                trackGeometry.vertices = verticesArr;
    
                                var trackLine = new THREE.Line(trackGeometry, trackMaterial);
                                group.add(trackLine);
    
                                // 动画点
                                addLightPoint(p1, tcaLength, verticesArr);
                            }
                        }
                    }
                });
            }
    
            // 点动画
            var pointGeometry = new THREE.SphereGeometry(0.2, 20, 20);
            var pointMaterial = new THREE.MeshBasicMaterial({color: 0x40E0D0});
            function addLightPoint(pos, coordsNum ,verArr) {
                var pointMesh = new THREE.Mesh(pointGeometry, pointMaterial);
                pointMesh.position.set(pos.x, pos.y, pos.z);
                group.add(pointMesh);
    
                var index = 0;
                function pointAnimate() {
                    index++;
                    if(index>coordsNum) {
                        index = 0;
                    }
                    pointMesh.position.set(verArr[index].x, verArr[index].y, verArr[index].z);
                    requestAnimationFrame(pointAnimate);
                }
                pointAnimate();
    
                /*var curveGeometry = new THREE.Geometry(); 
                var curveData = new THREE.CatmullRomCurve3(verArr.slice(0, 10));  
                curveGeometry.vertices = curveData.getPoints(10);
    
                var curveMaterial = new THREE.LineBasicMaterial({color: 0x40E0D0});
                var curveLine = new THREE.Line(curveGeometry, curveMaterial);
                group.add(curveLine);
    
                var index = 0;
                function lineAnimate() {
                    index++;
                    if(index>coordsNum-10) {
                        index = 0;
                    }
                    var offsetData = verArr.slice(index, 10+index);
                    if(offsetData.length > 0) {
                        curveData = new THREE.CatmullRomCurve3(offsetData);  
                           curveLine.geometry.vertices = curveData.getPoints(10);
                        curveLine.geometry.verticesNeedUpdate = true;
                    }
                    requestAnimationFrame(lineAnimate);
                }
                lineAnimate();*/
            }
    
            // 监听数据(添加并更新)
            socket.on('~', function(res) {
                if($.isEmptyObject(planeMarkers)) {
                    $.each(res, function(i, item) {
                        addPlane(item);
                    });
                } else {
                    $.each(res, function(i, item) {
                        if(planeMarkers[item.anum]) {
                            if(item.lng && item.lat) {
                                var pos = getPosition(item.lng, item.lat, 5);
                                planeMarkers[item.anum].position.set(pos.x, pos.y, pos.z);
                            }
                        } else {
                            addPlane(item);
                        }
                    });
                }
            });
        }
    
        // 地球
        function globe() {
            var globeTextureLoader = new THREE.TextureLoader();
            globeTextureLoader.load('images/textures/earth.jpg', function (texture) {
                var globeGgeometry = new THREE.SphereGeometry(200, 100, 100);
                var globeMaterial = new THREE.MeshStandardMaterial({map: texture});
                var globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial);
                group.add(globeMesh);
                group.rotation.x = THREE.Math.degToRad(35);
                group.rotation.y = THREE.Math.degToRad(170);
            });
        }
    
        // 星点
        function stars() {
            var starsGeometry = new THREE.Geometry();
            for (var i = 0; i < 2000; i ++) {
                var starVector = new THREE.Vector3(
                    THREE.Math.randFloatSpread(2000),
                    THREE.Math.randFloatSpread(2000),
                    THREE.Math.randFloatSpread(2000)
                );
                starsGeometry.vertices.push(starVector);
            }
            var starsMaterial = new THREE.PointsMaterial({color: 0x888888})
            var starsPoint = new THREE.Points(starsGeometry, starsMaterial);
            group.add(starsPoint);
        }
    
        // 光
        function lights() {
            var hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x333333, 2);
            hemisphereLight.position.x = 0;
            hemisphereLight.position.y = 0;
            hemisphereLight.position.z = -200;
            group.add(hemisphereLight);
        }
    
        // 初始化
        function init() {
            container = document.getElementById('zh_globe_container');
    
            scene = new THREE.Scene();
            var bgTexture = new THREE.TextureLoader().load("images/textures/starfield.jpg");
            scene.background = bgTexture;
    
            camera = new THREE.PerspectiveCamera(50, winWth/winHgt, 1, 2000);
            camera.up.x = 0;
            camera.up.y = 1;
            camera.up.z = 0;
            camera.position.x = 0;
            camera.position.y = 0;
            camera.position.z = 400;
            camera.lookAt(0,0,0);
    
            group = new THREE.Group();
            scene.add(group);
    
            // 地球    
            globe();
    
            // 飞机
            plane();
    
            // 星点
            stars();
    
            // 半球光
            lights();
    
            // 渲染器
            renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(winWth, winHgt);
            container.appendChild(renderer.domElement);
    
            // 盘旋控制
            var orbitControl = new THREE.OrbitControls(camera, renderer.domElement);
            orbitControl.minDistrance = 20;
            orbitControl.maxDistrance = 50;
            orbitControl.maxPolarAngle = Math.PI/2;
    
            // 性能测试
            stats = new Stats();
            container.appendChild(stats.dom);
    
            // resize事件
            window.addEventListener('resize', onWindowResize, false);
        }
    
        // 窗口大小改变
        function onWindowResize() {
            camera.aspect = window.innerWidth/window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }
    
        // 渲染
        function render() {
            group.rotation.y -= 0.0005;
            renderer.render(scene, camera);
        }
    
        // 动画
        function animate() {
            requestAnimationFrame(animate);
            render();
            stats.update();
        }
    
        init();
        animate();    
    })();
    

    场景背景图


    相关文章

      网友评论

        本文标题:web前端入门到实战:threejs 绘制地球、飞机、轨迹

        本文链接:https://www.haomeiwen.com/subject/earuictx.html