• 数据可视化【原创】vue+arcgis+threejs 实现立体光圈闪烁效果


    本文适合对vue,arcgis4.x,threejs,ES6较熟悉的人群食用。

    效果图:

     素材:

     

    主要思路:

    先用arcgis externalRenderers封装了一个ExternalRendererLayer,在里面把arcgis和threejs的context关联,然后再写个子类继承它,这部分类容在上一个帖子里面有讲过。

    子类FlashLayer继承它,并在里面封装了一个excute方法用来执行闪烁,参数包括point和height。先构建一个管道CatmullRomCurve3,在构建环RingGeometry,然后在updateModels里面去更新管道的scale.z,更新环的半径和透明度。

    excute方法:

    复制代码
     1 excute(point, height) {
     2         // let pointList = [
     3         //     [114.31456780904838, 30.55355011036358, 0],
     4         //     [114.31456780904838, 30.55355011036358, 2000]
     5         // ];
     6         let pointList = [
     7             [
     8                 point.longitude,
     9                 point.latitude,
    10                 0
    11             ],
    12             [
    13                 point.longitude,
    14                 point.latitude,
    15                 height
    16             ]
    17         ]
    18 
    19         let linePoints = [];
    20         //确定几何体位置
    21         pointList.forEach((item) => {
    22             var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], item[2]]);
    23             linePoints.push(new THREE.Vector3(renderLinePoints.vector3List.x, renderLinePoints
    24                 .vector3List.y, renderLinePoints.vector3List.z));
    25         })
    26 
    27         const lineImg = require('../../../../public/static/img/line.png')
    28         let lineTexture = new THREE.TextureLoader().load(lineImg)
    29         lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
    30         lineTexture.repeat.set(1, 1)
    31         lineTexture.needsUpdate = true
    32 
    33         let lineMaterial = new THREE.MeshBasicMaterial({
    34             map: lineTexture,
    35             side: THREE.DoubleSide,
    36             transparent: true,
    37             opacity: 1
    38         })
    39 
    40         // CatmullRomCurve3创建一条平滑的三维样条曲线
    41         let curve = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
    42 
    43         // 创建管道
    44         let tubeGeometry = new THREE.TubeGeometry(curve, 1, 16)
    45 
    46         let mesh = new THREE.Mesh(tubeGeometry, lineMaterial);
    47         mesh.name = 'FlashLayer_Line';
    48         mesh.scale.z = 0;
    49         mesh.layers.set(0);
    50         this.group.add(mesh);
    51 
    52         //光圈
    53         const ringUserData = {
    54             width: 300,
    55             innerRadius: 0,
    56             opacity: 1,
    57             opacityFlag: false,
    58             per: 10,
    59             innerMax: 800,
    60             times: 0,
    61             line: mesh
    62         };
    63         const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
    64             ringUserData.width, 32);
    65         const ringMaterial = new THREE.MeshPhongMaterial({
    66             color: COLOR_RING,
    67             emissive: COLOR_RING,
    68             side: THREE.DoubleSide,
    69             flatShading: true,
    70             wireframe: false,
    71             transparent: true,
    72             opacity: ringUserData.opacity
    73         });
    74         const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
    75         const ringPoint = linePoints[1];
    76         console.log(ringPoint);
    77         ringMesh.position.set(ringPoint.x, ringPoint.y, ringPoint.z / 2 + (Math.random() * ringPoint.z / 4 | 0) );
    78         ringMesh.name = 'FlashLayer_Ring';
    79         ringMesh.userData = ringUserData;
    80         ringMesh.layers.set(0);
    81 
    82         this.group.add(ringMesh);
    83     }
    复制代码

     

    updateModels方法(记得一定要销毁哦):

     

    复制代码
      1 updateModels(context) {
      2         super.updateModels(context)
      3 
      4         if (this.group.children.length) {
      5             let rubbish = [];
      6             for (let i = this.group.children.length - 1; i >= 0; i--) {
      7                 const mesh = this.group.children[i];
      8                 if (mesh.name === 'FlashLayer_Line') {
      9                     mesh.material.map.offset.x -= 0.02;
     10                     mesh.scale.z += 0.03;
     11                     if(mesh.scale.z >= 1) {
     12                         mesh.scale.z = 1;
     13                     }
     14                     continue;
     15                 }
     16                 if (mesh.name === 'FlashLayer_Ring') {
     17                     const ringUserData = mesh.userData;
     18                     const per = ringUserData.per;
     19                     ringUserData.innerRadius += per;
     20                     ringUserData.opacity -= 1 / (ringUserData.innerMax / per);
     21 
     22                     if (ringUserData.innerRadius > ringUserData.innerMax) {
     23                         ringUserData.innerRadius = 0;
     24                         ringUserData.opacity = 1;
     25                         ringUserData.times++;
     26                         
     27                         if(ringUserData.times === FLASH_TIMES) {
     28                             rubbish.push(mesh)
     29                             continue;
     30                         }
     31                     }
     32 
     33                     // if(ringUserData.opacityFlag) {
     34                     //     ringUserData.opacity += 1 / 800 / 5;
     35                     // } else {
     36                     //     ringUserData.opacity -= 1 / 800 / 5;
     37                     // }
     38 
     39                     // if(ringUserData.opacity >= 1) {
     40                     //     ringUserData.opacity = 1;
     41                     //     ringUserData.opacityFlag = false;
     42                     // }
     43                     // if(ringUserData.opacity <= 0) {
     44                     //     ringUserData.opacity = 0;
     45                     //     ringUserData.opacityFlag = true;
     46                     // }
     47 
     48                     const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
     49                         ringUserData.width, 32);
     50                     const ringMaterial = new THREE.MeshPhongMaterial({
     51                         color: COLOR_RING,
     52                         emissive: COLOR_RING,
     53                         side: THREE.DoubleSide,
     54                         flatShading: true,
     55                         wireframe: false,
     56                         transparent: true,
     57                         opacity: ringUserData.opacity
     58                     });
     59                     mesh.geometry.dispose();
     60                     mesh.material.dispose();
     61                     mesh.geometry = ringGeometry;
     62                     mesh.material = ringMaterial;
     63                     mesh.rotation.z += 1;
     64 
     65                     continue;
     66                 }
     67             }
     68             
     69             if(rubbish.length) {
     70                 for(let i = 0; i < rubbish.length; i++) {
     71                     let rubbishMesh = rubbish[i];
     72                     let line = rubbishMesh.userData.line;
     73                     this.group.remove(line);
     74                     line.material.dispose();
     75                     line.geometry.dispose();
     76                     // line.dispose();
     77                     line = null;
     78                     this.group.remove(rubbishMesh);
     79                     rubbishMesh.material.dispose();
     80                     rubbishMesh.geometry.dispose();
     81                     // rubbishMesh.dispose();
     82                     rubbishMesh = null;
     83                 }
     84                 rubbish = null;
     85             }
     86 
     87             // if (mesh.scale.z <=0 ){
     88             //     this.animateFlag = true;
     89             // }
     90 
     91             // if(mesh.scale.z >=1 ) {
     92             //     this.animateFlag = false;
     93             // } 
     94 
     95             // if(this.animateFlag) {
     96             //     mesh.scale.z += 0.01;
     97             // } else {
     98             //     mesh.scale.z -= 0.01;
     99             // }
    100         }
    101     }
    复制代码

     

     

    ExternalRendererLayer:

      1 import * as THREE from 'three'
      2 import Stats from 'three/examples/jsm/libs/stats.module.js'
      3 import * as webMercatorUtils from "@arcgis/core/geometry/support/webMercatorUtils"
      4 import * as externalRenderers from "@arcgis/core/views/3d/externalRenderers"
      5 
      6 export default class ExternalRendererLayer {
      7     constructor({
      8         view,
      9         options
     10     }) {
     11         this.view = view
     12         this.options = options
     13 
     14         this.objects = []
     15         this.scene = null
     16         this.camera = null
     17         this.renderer = null
     18         
     19         this.setup();
     20     }
     21     
     22     setup() {
     23         if (process.env.NODE_ENV !== "production") {
     24             const sid = setTimeout(() => {
     25                 clearTimeout(sid)
     26                 //构建帧率查看器
     27                 let stats = new Stats()
     28                 stats.setMode(0)
     29                 stats.domElement.style.position = 'absolute'
     30                 stats.domElement.style.left = '0px'
     31                 stats.domElement.style.top = '0px'
     32                 document.body.appendChild(stats.domElement)
     33                 function render() {
     34                   stats.update()
     35                   requestAnimationFrame(render)
     36                 }
     37                 render()
     38             }, 5000)
     39         }
     40     }
     41 
     42     apply() {
     43         let myExternalRenderer = {
     44             setup: context => {
     45                 this.createSetup(context)
     46             },
     47             render: context => {
     48                 this.createRender(context)
     49             }
     50         }
     51         
     52         externalRenderers.add(this.view, myExternalRenderer);
     53     }
     54 
     55     createSetup(context) {
     56         this.scene = new THREE.Scene(); // 场景
     57         this.camera = new THREE.PerspectiveCamera(); // 相机
     58 
     59         this.setLight();
     60 
     61         // 添加坐标轴辅助工具
     62         const axesHelper = new THREE.AxesHelper(10000000);
     63         this.scene.Helpers = axesHelper;
     64         this.scene.add(axesHelper);
     65 
     66         this.renderer = new THREE.WebGLRenderer({
     67             context: context.gl, // 可用于将渲染器附加到已有的渲染环境(RenderingContext)中
     68             premultipliedAlpha: false, // renderer是否假设颜色有 premultiplied alpha. 默认为true
     69             // antialias: true
     70             // logarithmicDepthBuffer: false
     71             // logarithmicDepthBuffer: true 
     72         });
     73         this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊
     74         this.renderer.setViewport(0, 0, this.view.width, this.view.height); // 视口大小设置
     75         
     76         // 防止Three.js清除ArcGIS JS API提供的缓冲区。
     77         this.renderer.autoClearDepth = false; // 定义renderer是否清除深度缓存
     78         this.renderer.autoClearStencil = false; // 定义renderer是否清除模板缓存
     79         this.renderer.autoClearColor = false; // 定义renderer是否清除颜色缓存
     80         // this.renderer.autoClear = false;
     81         
     82         // ArcGIS JS API渲染自定义离屏缓冲区,而不是默认的帧缓冲区。
     83         // 我们必须将这段代码注入到three.js运行时中,以便绑定这些缓冲区而不是默认的缓冲区。
     84         const originalSetRenderTarget = this.renderer.setRenderTarget.bind(
     85             this.renderer
     86         );
     87         this.renderer.setRenderTarget = target => {
     88             originalSetRenderTarget(target);
     89             if (target == null) {
     90                 // 绑定外部渲染器应该渲染到的颜色和深度缓冲区
     91                 context.bindRenderTarget();
     92             }
     93         };
     94         
     95         this.addModels(context);
     96 
     97         context.resetWebGLState();
     98     }
     99 
    100     createRender(context) {
    101         const cam = context.camera;
    102         this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
    103         this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
    104         this.camera.lookAt(
    105             new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])
    106         );
    107         // this.camera.near = 1;
    108         // this.camera.far = 100;
    109 
    110         // 投影矩阵可以直接复制
    111         this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
    112         
    113         this.updateModels(context);
    114 
    115         this.renderer.state.reset();
    116 
    117         context.bindRenderTarget();
    118 
    119         this.renderer.render(this.scene, this.camera);
    120 
    121         // 请求重绘视图。
    122         externalRenderers.requestRender(this.view);
    123 
    124         // cleanup
    125         context.resetWebGLState();
    126     }
    127     
    128     //经纬度坐标转成三维空间坐标
    129     lngLatToXY(view, points) {
    130     
    131         let vector3List; // 顶点数组
    132     
    133         let pointXYs;
    134     
    135     
    136         // 计算顶点
    137         let transform = new THREE.Matrix4(); // 变换矩阵
    138         let transformation = new Array(16);
    139     
    140         // 将经纬度坐标转换为xy值\
    141         let pointXY = webMercatorUtils.lngLatToXY(points[0], points[1]);
    142     
    143         // 先转换高度为0的点
    144         transform.fromArray(
    145             externalRenderers.renderCoordinateTransformAt(
    146                 view,
    147                 [pointXY[0], pointXY[1], points[
    148                     2]], // 坐标在地面上的点[x值, y值, 高度值]
    149                 view.spatialReference,
    150                 transformation
    151             )
    152         );
    153     
    154         pointXYs = pointXY;
    155     
    156         vector3List =
    157             new THREE.Vector3(
    158                 transform.elements[12],
    159                 transform.elements[13],
    160                 transform.elements[14]
    161             )
    162     
    163         return {
    164             vector3List: vector3List,
    165             pointXYs: pointXYs
    166         };
    167     }
    168     
    169     setLight() {
    170         console.log('setLight')
    171         let ambient = new THREE.AmbientLight(0xffffff, 0.7);
    172         this.scene.add(ambient);
    173         let directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
    174         directionalLight.position.set(100, 300, 200);
    175         this.scene.add(directionalLight);
    176     }
    177     
    178     addModels(context) {
    179         console.log('addModels')
    180     }
    181     
    182     updateModels(context) {
    183         // console.log('updateModels')
    184     }
    185     
    186 }
    View Code

    FlashLayer:

      1 import mapx from '@/utils/mapUtils.js';
      2 import Polygon from "@arcgis/core/geometry/Polygon";
      3 import Point from "@arcgis/core/geometry/Point";
      4 import * as THREE from 'three'
      5 import ExternalRendererLayer from './ExternalRendererLayer.js'
      6 import Graphic from "@arcgis/core/Graphic";
      7 import SpatialReference from '@arcgis/core/geometry/SpatialReference'
      8 import * as externalRenderers from "@arcgis/core/views/3d/externalRenderers"
      9 
     10 const COLOR_RING = 0xff0000;
     11 const FLASH_TIMES = 2;
     12 
     13 export default class FlashLayer extends ExternalRendererLayer {
     14     constructor({
     15         view,
     16         options
     17     }) {
     18         super({
     19             view,
     20             options
     21         })
     22     }
     23 
     24     setup() {
     25         this.group = new THREE.Group();
     26         // this.animateFlag = false;
     27     }
     28 
     29     addModels(context) {
     30         super.addModels(context);
     31         
     32         this.scene.add(this.group);
     33         this.objects.push(this.group);
     34     }
     35 
     36     excute(point, height) {
     37         // let pointList = [
     38         //     [114.31456780904838, 30.55355011036358, 0],
     39         //     [114.31456780904838, 30.55355011036358, 2000]
     40         // ];
     41         let pointList = [
     42             [
     43                 point.longitude,
     44                 point.latitude,
     45                 0
     46             ],
     47             [
     48                 point.longitude,
     49                 point.latitude,
     50                 height
     51             ]
     52         ]
     53 
     54         let linePoints = [];
     55         //确定几何体位置
     56         pointList.forEach((item) => {
     57             var renderLinePoints = this.lngLatToXY(this.view, [item[0], item[1], item[2]]);
     58             linePoints.push(new THREE.Vector3(renderLinePoints.vector3List.x, renderLinePoints
     59                 .vector3List.y, renderLinePoints.vector3List.z));
     60         })
     61 
     62         const lineImg = require('../../../../public/static/img/line.png')
     63         let lineTexture = new THREE.TextureLoader().load(lineImg)
     64         lineTexture.wrapS = lineTexture.wrapT = THREE.RepeatWrapping; //每个都重复
     65         lineTexture.repeat.set(1, 1)
     66         lineTexture.needsUpdate = true
     67 
     68         let lineMaterial = new THREE.MeshBasicMaterial({
     69             map: lineTexture,
     70             side: THREE.DoubleSide,
     71             transparent: true,
     72             opacity: 1
     73         })
     74 
     75         // CatmullRomCurve3创建一条平滑的三维样条曲线
     76         let curve = new THREE.CatmullRomCurve3(linePoints) // 曲线路径
     77 
     78         // 创建管道
     79         let tubeGeometry = new THREE.TubeGeometry(curve, 1, 16)
     80 
     81         let mesh = new THREE.Mesh(tubeGeometry, lineMaterial);
     82         mesh.name = 'FlashLayer_Line';
     83         mesh.scale.z = 0;
     84         mesh.layers.set(0);
     85         this.group.add(mesh);
     86 
     87         //光圈
     88         const ringUserData = {
     89             width: 300,
     90             innerRadius: 0,
     91             opacity: 1,
     92             opacityFlag: false,
     93             per: 10,
     94             innerMax: 800,
     95             times: 0,
     96             line: mesh
     97         };
     98         const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
     99             ringUserData.width, 32);
    100         const ringMaterial = new THREE.MeshPhongMaterial({
    101             color: COLOR_RING,
    102             emissive: COLOR_RING,
    103             side: THREE.DoubleSide,
    104             flatShading: true,
    105             wireframe: false,
    106             transparent: true,
    107             opacity: ringUserData.opacity
    108         });
    109         const ringMesh = new THREE.Mesh(ringGeometry, ringMaterial);
    110         const ringPoint = linePoints[1];
    111         console.log(ringPoint);
    112         ringMesh.position.set(ringPoint.x, ringPoint.y, ringPoint.z / 2 + (Math.random() * ringPoint.z / 4 | 0) );
    113         ringMesh.name = 'FlashLayer_Ring';
    114         ringMesh.userData = ringUserData;
    115         ringMesh.layers.set(0);
    116 
    117         this.group.add(ringMesh);
    118     }
    119 
    120     updateModels(context) {
    121         super.updateModels(context)
    122 
    123         if (this.group.children.length) {
    124             let rubbish = [];
    125             for (let i = this.group.children.length - 1; i >= 0; i--) {
    126                 const mesh = this.group.children[i];
    127                 if (mesh.name === 'FlashLayer_Line') {
    128                     mesh.material.map.offset.x -= 0.02;
    129                     mesh.scale.z += 0.03;
    130                     if(mesh.scale.z >= 1) {
    131                         mesh.scale.z = 1;
    132                     }
    133                     continue;
    134                 }
    135                 if (mesh.name === 'FlashLayer_Ring') {
    136                     const ringUserData = mesh.userData;
    137                     const per = ringUserData.per;
    138                     ringUserData.innerRadius += per;
    139                     ringUserData.opacity -= 1 / (ringUserData.innerMax / per);
    140 
    141                     if (ringUserData.innerRadius > ringUserData.innerMax) {
    142                         ringUserData.innerRadius = 0;
    143                         ringUserData.opacity = 1;
    144                         ringUserData.times++;
    145                         
    146                         if(ringUserData.times === FLASH_TIMES) {
    147                             rubbish.push(mesh)
    148                             continue;
    149                         }
    150                     }
    151 
    152                     // if(ringUserData.opacityFlag) {
    153                     //     ringUserData.opacity += 1 / 800 / 5;
    154                     // } else {
    155                     //     ringUserData.opacity -= 1 / 800 / 5;
    156                     // }
    157 
    158                     // if(ringUserData.opacity >= 1) {
    159                     //     ringUserData.opacity = 1;
    160                     //     ringUserData.opacityFlag = false;
    161                     // }
    162                     // if(ringUserData.opacity <= 0) {
    163                     //     ringUserData.opacity = 0;
    164                     //     ringUserData.opacityFlag = true;
    165                     // }
    166 
    167                     const ringGeometry = new THREE.RingGeometry(ringUserData.innerRadius, ringUserData.innerRadius +
    168                         ringUserData.width, 32);
    169                     const ringMaterial = new THREE.MeshPhongMaterial({
    170                         color: COLOR_RING,
    171                         emissive: COLOR_RING,
    172                         side: THREE.DoubleSide,
    173                         flatShading: true,
    174                         wireframe: false,
    175                         transparent: true,
    176                         opacity: ringUserData.opacity
    177                     });
    178                     mesh.geometry.dispose();
    179                     mesh.material.dispose();
    180                     mesh.geometry = ringGeometry;
    181                     mesh.material = ringMaterial;
    182                     mesh.rotation.z += 1;
    183 
    184                     continue;
    185                 }
    186             }
    187             
    188             if(rubbish.length) {
    189                 for(let i = 0; i < rubbish.length; i++) {
    190                     let rubbishMesh = rubbish[i];
    191                     let line = rubbishMesh.userData.line;
    192                     this.group.remove(line);
    193                     line.material.dispose();
    194                     line.geometry.dispose();
    195                     // line.dispose();
    196                     line = null;
    197                     this.group.remove(rubbishMesh);
    198                     rubbishMesh.material.dispose();
    199                     rubbishMesh.geometry.dispose();
    200                     // rubbishMesh.dispose();
    201                     rubbishMesh = null;
    202                 }
    203                 rubbish = null;
    204             }
    205 
    206             // if (mesh.scale.z <=0 ){
    207             //     this.animateFlag = true;
    208             // }
    209 
    210             // if(mesh.scale.z >=1 ) {
    211             //     this.animateFlag = false;
    212             // } 
    213 
    214             // if(this.animateFlag) {
    215             //     mesh.scale.z += 0.01;
    216             // } else {
    217             //     mesh.scale.z -= 0.01;
    218             // }
    219         }
    220     }
    221 
    222     transparentObject(geometry, material) {
    223         var obj = new THREE.Object3D();
    224         var mesh = new THREE.Mesh(geometry, material);
    225         mesh.material.side = THREE.BackSide; // back faces
    226         mesh.renderOrder = 0;
    227         obj.add(mesh);
    228 
    229         var mesh = new THREE.Mesh(geometry, material.clone());
    230         mesh.material.side = THREE.FrontSide; // front faces
    231         mesh.renderOrder = 1;
    232         obj.add(mesh);
    233         return obj
    234     }
    235 
    236 }
    View Code

    调用案例:点击地图显示光圈闪烁,MapBuilder是我封装的加载底图的类,各位大佬自己换掉,随便加个底图图层

     1 
     4 
     5 
    43 
    44 
    View Code

     

  • 相关阅读:
    MATLAB关系运算
    [项目设计] 从零实现的高并发内存池(四)
    我来告诉你代码重构有什么好处
    C++map容器
    C++面试八股文
    SpringCloud 微服务(三)-分布式事务问题
    使用canvas制作一个无人机旋转特效
    电商客服想偷懒,用这款神器
    使用Python,Xpath获取所有的漫画章节路径,并下载漫画图片生成单个/多个pdf,并进行pdf合并
    20. 有效的括号 --力扣 --JAVA
  • 原文地址:https://www.cnblogs.com/loveFlex/p/17680047.html