基于threejs实现中国地图轮廓动画 您所在的位置:网站首页 threejs描边 基于threejs实现中国地图轮廓动画

基于threejs实现中国地图轮廓动画

2023-09-26 07:00| 来源: 网络整理| 查看: 265

请添加图片描述请添加图片描述背景

目前项目的中国地图是echarts画的,现在这想再次基础上增加一个中国地图描边动画。

分析

因为echart 使用geo 坐标画上去的,我们可以根绝中国地图坐标画点,然后定时去移动这些点。

这里使用threejs 的点材质去帧动画移动。

geojson版本

threejs 基础场景不过多介绍,具体看代码,只写下核心部分。

步骤:

中国地图轮廓geojson 获取点坐标。(百度和阿里都有提供,可以自己搜很多。)使用卡墨托投影方法,将经纬坐标转成平面根绝点轮廓图采样出亮点控制亮点亮度和移动

核心代码:

import { BufferGeometry, Object3D, FileLoader, BufferAttribute, ShaderMaterial, Color, Points, LineBasicMaterial, Line, Vector3, ColorRepresentation } from "three"; import * as d3 from "d3-geo"; import coordinates from "./data/china"; const projection = d3 .geoMercator() .center([116.412318, 39.909843]) .translate([0, 0]); const linePinots:any[] = []; const countryLine = (tintColor:ColorRepresentation,outLineColor?:ColorRepresentation)=>{ let positions:Float32Array; let opacitys:Float32Array; const opacityGeometry = new BufferGeometry(); // 中国边界 const chinaLines = new Object3D(); // 点数据 coordinates.forEach((coordinate:any) => { // coordinate 多边形数据 coordinate.forEach((rows:any) => { const line = lineDraw(rows, outLineColor ?? '0xffffff'); chinaLines.add(line); }); }); positions = new Float32Array(linePinots.flat(1)); // 设置顶点 opacityGeometry.setAttribute("position", new BufferAttribute(positions, 3)); // 设置 粒子透明度为 0 opacitys = new Float32Array(positions.length).map(() => 0); opacityGeometry.setAttribute("aOpacity", new BufferAttribute(opacitys, 1)); // 控制 颜色和粒子大小 const params = { pointSize: 2.0, pointColor: tintColor } const vertexShader = ` attribute float aOpacity; uniform float uSize; varying float vOpacity; void main(){ gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); gl_PointSize = uSize; vOpacity=aOpacity; } ` const fragmentShader = ` varying float vOpacity; uniform vec3 uColor; float invert(float n){ return 1.-n; } void main(){ if(vOpacity { const projectionRow = projection(row); if (!projectionRow) { return } let x = projectionRow[0] let y = projectionRow[1] // 创建三维点 pointsArray.push(new Vector3(x, -y, 0)); linePinots.push([x, -y, 0]); }); // 放入多个点 lineGeometry.setFromPoints(pointsArray); const lineMaterial = new LineBasicMaterial({ color: color, }); return new Line(lineGeometry, lineMaterial); } export { countryLine };

全部代码: demo

遇到问题

geojson 版本我们需要将提供的经纬度坐标点转场成平面,各平台算法不同,投影失真情况不同,所以一些情况地图会失真无法重合。

我们刚才使用卡墨托投影转换,也会失真并且和echarts 地图轮廓对不上,所以想起其他方案。

我们利用svg路径来取点,UI提供的svg地图轮廓肯定是一致的。

SVG版本

设计思路:

加载svg 取所有的点根绝点来创建threejs 亮光点移动动画

核心代码:

import * as THREE from 'three'; import { initRender } from './render'; import { initScene } from './scene'; import { initCamera } from './camera'; // import { countryLine } from "./countryPolygon"; import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js'; export interface OutLineConfig { outline: boolean; outlineColor?: THREE.ColorRepresentation; tintColor: THREE.ColorRepresentation; speed: number; tintLength?: number; tintPointSize?: number; } class MapOutline { private parentDom: HTMLElement; private width: number; private height: number; private renderer: THREE.WebGLRenderer; private scene: THREE.Scene; private camera: THREE.PerspectiveCamera; private opacitys: Float32Array | null = null; private linePinots: any[] = []; private opacityGeometry: THREE.BufferGeometry | null = null; private currentPos = 0; public constructor( containerId: string, public config: OutLineConfig = { outline: false, speed: 3, tintColor: '#008792' }, ) { this.parentDom = document.getElementById(containerId)!; this.width = this.parentDom.offsetWidth; this.height = this.parentDom.offsetHeight; this.renderer = initRender(this.width, this.height); this.parentDom?.appendChild(this.renderer.domElement); this.scene = initScene(); this.camera = initCamera(this.width, this.height); } public render = () => { const loader = new SVGLoader(); loader.load('./chinaLine.svg', (data) => { const paths = data.paths; const group = new THREE.Group(); group.scale.multiplyScalar(0.34); group.position.x = -117; group.position.y = 90; group.scale.y *= -1; let allPoints: any[] = []; let pointsMesh: THREE.Points | null = null; // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < paths.length; i++) { const path = paths[i]; const strokeColor = path.userData?.style.stroke; const material = new THREE.MeshBasicMaterial({ color: 'red', opacity: path.userData?.style.strokeOpacity, transparent: path.userData?.style.strokeOpacity < 1, side: THREE.DoubleSide, depthWrite: false, }); for (let j = 0, jl = path.subPaths.length; j < jl; j++) { const subPath = path.subPaths[j]; let subPoints = subPath.getPoints(); allPoints = allPoints.concat(subPoints); const geometry = SVGLoader.pointsToStroke(subPath.getPoints(), path.userData?.style); if (geometry) { const mesh = new THREE.Mesh(geometry, material); group.add(mesh); } } allPoints = allPoints.map((item) => { item.z = 0; return item; }); for (const point of allPoints) { this.linePinots.push(point.x * 0.34 - 117, -point.y * 0.34 + 90, point.z); } this.opacityGeometry = new THREE.BufferGeometry(); this.opacitys = new Float32Array(this.linePinots.length).map(() => 0); this.opacityGeometry.setAttribute('position', new THREE.Float32BufferAttribute(this.linePinots, 3)); this.opacityGeometry.setAttribute('aOpacity', new THREE.BufferAttribute(this.opacitys, 1)); // 控制 颜色和粒子大小 const params = { pointSize: 5.0, pointColor: 'DarkOrange', }; const vertexShader = ` attribute float aOpacity; uniform float uSize; varying float vOpacity; void main(){ gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); gl_PointSize = uSize; vOpacity=aOpacity; } `; const fragmentShader = ` varying float vOpacity; uniform vec3 uColor; float invert(float n){ return 1.-n; } void main(){ if(vOpacity { if (this.linePinots && this.opacitys) { // console.log(this.currentPos); if (this.currentPos > 1600) { this.currentPos = 0; } this.currentPos += this.config.speed; for (let i = 0; i < this.config.speed; i++) { this.opacitys[(this.currentPos - i) % this.linePinots.length] = 0; } for (let i = 0; i < 100; i++) { // console.log((this.currentPos + i) % this.linePinots.length); this.opacitys[(this.currentPos + i) % this.linePinots.length] = i / 50 > 2 ? 2 : i / 50; } if (this.opacityGeometry) { this.opacityGeometry.attributes.aOpacity.needsUpdate = true; } } this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.animate); }; } export default MapOutline;


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有