| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962 |
- import * as THREE from 'three';
- import * as d3 from 'd3';
- import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
- import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
- import gsap from 'gsap';
- import { gradientColors } from '/@/utils/threejs/util';
- // import * as dat from 'dat.gui';
- import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
- import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
- // const gui = new dat.GUI();
- // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
- class earthtMap {
- container: HTMLCanvasElement | null; //canvas 容器
- labelRenderer: CSS2DRenderer | null = null;
- camera: THREE.PerspectiveCamera | null = null; // 相机
- scene: THREE.Scene | null = null;
- renderer: THREE.WebGLRenderer | null = null;
- controller: OrbitControls | null = null;
- map = new THREE.Object3D();
- provinceArr = <THREE.Mesh[]>[];
- locationCylinderMeshArr = <THREE.Mesh[]>[];
- locationCylinderGroup = new THREE.Object3D();
- locationNameBgArr = <THREE.Mesh[]>[];
- locationNameArr = <THREE.Mesh[]>[];
- raycaster = new THREE.Raycaster();
- mouse = new THREE.Vector2();
- tooltip: HTMLElement | null; //canvas 容器
- detailModalCSS2Obj: CSS2DObject | null = null;
- mapConfig = {
- deep: 0.2,
- };
- projection = d3.geoMercator().center([109.741193, 42.290162]).scale(90).translate([-1, -6]); // 根据地球贴图做轻微调整
- cameraPosArr = [{ x: 0, y: -2.0, z: 4.8 }];
- mapEdgeLightObj = {
- mapEdgePoints: <number[][]>[],
- lightOpacityGeometry: new THREE.BufferGeometry(), // 单独把geometry提出来,动画用
- lightSpeed: 3, // 边缘流光参数
- lightCurrentPos: 0,
- lightOpacitys: null,
- };
- fontMaterial3 = new THREE.MeshBasicMaterial({ color: 0x00dbff, transparent: true, opacity: 0.5 });
- fontMaterial0 = new THREE.MeshBasicMaterial({ color: 0xffffff });
- fontMaterial1 = new THREE.MeshBasicMaterial({ color: 0xf7ff00 });
- fontMaterial2 = new THREE.MeshBasicMaterial({ color: 0xf7ff00, transparent: true, opacity: 0, depthTest: true, depthWrite: true });
- playerStartClickTime = new Date().getTime();
- constructor(canvasSelector, tooltip) {
- this.container = document.getElementById(canvasSelector) as HTMLCanvasElement;
- this.tooltip = document.getElementById(tooltip) as HTMLElement;
- }
- init() {
- this.setScene();
- this.setCamera();
- this.setRenderer(); // 创建渲染器对象
- this.setController(); // 创建控件对象
- this.loadMapData();
- this.loadMapData1();
- this.addCompass();
- this.setEarth();
- this.setLight();
- this.setRaycaster();
- // this.animate();
- }
- setScene() {
- // 创建场景对象Scene
- this.scene = new THREE.Scene();
- this.scene.rotateZ(Math.PI);
- }
- setCamera() {
- // 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
- this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 500);
- this.camera.position.set(0.3909238439719087, -16.078364444489004, 3.748569090055743); // 0, -5, 1
- this.camera.lookAt(this.scene.position);
- // this.camera.lookAt(new THREE.Vector3(0, 1, 0)); // 0, 0, 0
- }
- setRenderer() {
- if (!this.container) return;
- this.renderer = new THREE.WebGLRenderer({
- antialias: true,
- // alpha: true,
- // precision: 'mediump',
- // logarithmicDepthBuffer: true, // 是否使用对数深度缓存
- });
- this.renderer.sortObjects = true;
- this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
- this.renderer.setPixelRatio(window.devicePixelRatio);
- // this.renderer.sortObjects = false; // 是否需要对对象排序
- this.container.appendChild(this.renderer.domElement);
- this.renderer.outputColorSpace = THREE.SRGBColorSpace;
- // this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
- this.labelRenderer = new CSS2DRenderer();
- this.labelRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
- this.labelRenderer.domElement.style.position = 'absolute';
- this.labelRenderer.domElement.style.top = '0';
- this.labelRenderer.domElement.style.pointerEvents = 'none';
- this.container.appendChild(this.labelRenderer.domElement);
- }
- setController() {
- if (this.camera && this.renderer && this.renderer.domElement) {
- this.controller = new OrbitControls(this.camera, this.renderer.domElement);
- // this.controller.minDistance = 2;
- // this.controller.minDistance = 0.5;
- // this.controller.maxDistance = 35.5; // 5.5
- // 阻尼(惯性)
- // this.controller.enableDamping = true;
- // this.controller.dampingFactor = 0.04;
- this.controller.minAzimuthAngle = -0.1;
- this.controller.maxAzimuthAngle = 0.1;
- this.controller.minPolarAngle = 0.01;
- this.controller.maxPolarAngle = Math.PI - 0.01;
- // 修改相机的lookAt是不会影响THREE.OrbitControls的target的
- // this.controller.target = new THREE.Vector3(-0.9915, -1.5075, -1.8432);
- this.controller.target = new THREE.Vector3(0, 0, -1);
- }
- }
- // 辅助线
- addHelper() {
- // let helper = new THREE.CameraHelper(this.camera);
- // this.scene.add(helper);
- //轴辅助 (每一个轴的长度)
- const axisHelper = new THREE.AxisHelper(150); // 红线是X轴,绿线是Y轴,蓝线是Z轴
- // this.scene.add(axisHelper);
- const gridHelper = new THREE.GridHelper(100, 30, 0x2c2c2c, 0x888888);
- // this.scene.add(gridHelper);
- }
- setLight() {
- const ambientLight = new THREE.AmbientLight(0x999999, 2);
- this.scene?.add(ambientLight);
- // // 平行光
- const directionalLight = new THREE.DirectionalLight(0xffffff, 2.0);
- this.scene?.add(directionalLight);
- // 聚光光源 - 照模型
- const spotLight = new THREE.SpotLight(0xffffff, 2);
- spotLight.position.set(0.62, 10.0, 2.78);
- spotLight.castShadow = true;
- this.scene?.add(spotLight);
- // 聚光光源辅助线
- // const spotLightHelper = new THREE.SpotLightHelper(spotLight);
- // this.scene?.add(spotLightHelper);
- // 点光源 - 照模型
- const test = new THREE.PointLight('#ffffff', 1.2, 20);
- test.position.set(-0.39, -0.93, 0.64);
- this.scene?.add(test);
- // const testHelperMap = new THREE.PointLightHelper(test);
- // this.scene?.add(testHelperMap);
- // 点光源 - 蓝色照地球
- const pointLightMap = new THREE.PointLight('#4161ff', 1.4, 20);
- pointLightMap.position.set(-0.18, -1.19, 1.5);
- this.scene?.add(pointLightMap);
- // const spotLightHelperMap = new THREE.PointLightHelper(pointLightMap);
- // this.scene?.add(spotLightHelperMap);
- }
- // 加载地图数据
- loadMapData() {
- const loader = new THREE.FileLoader();
- loader.load('/json/vent3.json', async (data: string) => {
- const jsondata = JSON.parse(data);
- await this.addMapGeometry(jsondata);
- });
- }
- loadMapData1() {
- const loader = new THREE.FileLoader();
- loader.load('/json/vent2.json', (data: string) => {
- const jsondata = JSON.parse(data);
- this.addMapGeometry1(jsondata);
- });
- }
- addCompass() {}
- addMapGeometry1(jsondata) {
- // 墨卡托投影转换
- jsondata.features.forEach((elem) => {
- // 定一个省份3D对象
- const province = new THREE.Object3D();
- // 每个的 坐标 数组
- const coordinates = elem.geometry.coordinates;
- // 循环坐标数组
- coordinates.forEach((multiPolygon) => {
- multiPolygon.forEach((polygon) => {
- const shape = new THREE.Shape();
- const lineMaterial = new THREE.LineBasicMaterial({
- color: '#A2A2A2',
- transparent: true,
- opacity: 0.3,
- linewidth: 1,
- linecap: 'round', //ignored by WebGLRenderer
- linejoin: 'round', //ignored by WebGLRenderer
- });
- // const lineGeometry = new THREE.Geometry();
- // 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);
- // lineGeometry.vertices.push(new THREE.Vector3(x, -y, 3));
- // }
- const lineGeometry = new THREE.BufferGeometry();
- const pointsArray = <any[]>[];
- for (let i = 0; i < polygon.length; i++) {
- const [x, y] = this.projection(polygon[i]);
- if (i === 0) {
- shape.moveTo(x, -y);
- }
- shape.lineTo(x, -y);
- pointsArray.push(new THREE.Vector3(x, -y, 0.1));
- }
- // console.log(pointsArray);
- lineGeometry.setFromPoints(pointsArray);
- const extrudeSettings = {
- depth: 0.01,
- bevelEnabled: false, // 对挤出的形状应用是否斜角
- };
- const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
- const material = new THREE.MeshPhongMaterial({
- color: '#3962DE',
- transparent: true,
- opacity: 0.1,
- side: THREE.FrontSide,
- // depthTest: true,
- });
- const material1 = new THREE.MeshLambertMaterial({
- color: '#3BB2ED',
- transparent: true,
- opacity: 0.4,
- side: THREE.FrontSide,
- // wireframe: true
- });
- const mesh = new THREE.Mesh(geometry, [material, material1]);
- const line = new THREE.Line(lineGeometry, lineMaterial);
- // console.log(elem.properties);
- province.add(mesh);
- province.add(line);
- });
- });
- // province.scale.set(5, 5, 0);
- // province.position.set(0, 0, 0);
- // console.log(province);
- this.scene?.add(province);
- });
- }
- // 地图模型
- async addMapGeometry(jsondata) {
- const _self = this;
- const loader = new FontLoader();
- const font = await loader.loadAsync('/font/STSong_Regular.json');
- jsondata.features.forEach((elem) => {
- // 定一个省份3D对象
- const province = new THREE.Object3D();
- // 每个的 坐标 数组
- const coordinates = elem.geometry.coordinates;
- // 循环坐标数组
- coordinates.forEach((multiPolygon) => {
- multiPolygon.forEach(async (polygon) => {
- const shape = new THREE.Shape();
- const lineMaterial = new THREE.LineBasicMaterial({
- color: '#3EFFED',
- // linewidth: 1,
- linecap: 'round', //ignored by WebGLRenderer
- linejoin: 'round', //ignored by WebGLRenderer
- });
- // const lineGeometry = new THREE.Geometry();
- // 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);
- // lineGeometry.vertices.push(new THREE.Vector3(x, -y, 3));
- // }
- const lineGeometry = new THREE.BufferGeometry();
- const pointsArray = <any[]>[];
- for (let i = 0; i < polygon.length; i++) {
- const [x, y] = this.projection(polygon[i]);
- if (i === 0) {
- shape.moveTo(x, -y);
- }
- shape.lineTo(x, -y);
- pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
- // 做边缘流光效果,把所有点保存下来
- this.mapEdgeLightObj.mapEdgePoints.push([x, -y, this.mapConfig.deep]);
- }
- // console.log(pointsArray);
- lineGeometry.setFromPoints(pointsArray);
- const extrudeSettings = {
- depth: this.mapConfig.deep,
- bevelEnabled: true, // 对挤出的形状应用是否斜角
- bevelSegments: 0,
- bevelThickness: 0.1,
- };
- const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
- const material = new THREE.MeshPhongMaterial({
- color: '#3962DE',
- transparent: true,
- opacity: 0.4,
- side: THREE.FrontSide,
- // depthTest: true,
- });
- let material1 = new THREE.MeshBasicMaterial({
- polygonOffset: true,
- polygonOffsetFactor: 1,
- polygonOffsetUnits: 1,
- metalness: 1,
- roughness: 1,
- color: '#3EC9FF',
- transparent: true,
- opacity: 0.6,
- side: THREE.FrontSide,
- // wireframe: true
- });
- material1 = this.createSideShaderMaterial(material1);
- const mesh = new THREE.Mesh(geometry, [material, material1]);
- mesh.name = 'locationShape';
- const line = new THREE.Line(lineGeometry, lineMaterial);
- // 将省份的属性 加进来
- province['properties'] = elem.properties;
- // 将城市信息放到模型中,后续做动画用
- if (elem.properties.centroid) {
- const [x, y] = this.projection(elem.properties.centroid); // uv映射坐标
- province['properties']['_centroid'] = [x, y];
- const textGeometry = new TextGeometry(province['properties']['name'], {
- font: font,
- size: 0.05, //字体大小
- height: 0.005, //字体高度curveSegments: 12,
- bevelEnabled: true,
- bevelSize: 0.001,
- bevelOffset: 0,
- bevelSegments: 0,
- });
- const textMesh = new THREE.Mesh(textGeometry, this.fontMaterial3);
- textMesh.scale.set(1, 1, 1);
- textMesh.position.set(x, -y, this.mapConfig.deep + 0.01);
- textMesh.rotateZ(-Math.PI);
- province.add(textMesh);
- }
- this.provinceArr.push(mesh);
- // console.log(elem.properties);
- province.add(mesh);
- province.add(line);
- });
- });
- // province.scale.set(5, 5, 0);
- // province.position.set(0, 0, 0);
- // console.log(province);
- this.map?.add(province);
- });
- this.setMapEdgeLight();
- this.scene?.add(this.map);
- setTimeout(() => {
- _self.resetCameraTween();
- // _self.setMaterial();
- }, 1500);
- }
- setMapEdgeLight() {
- // console.log(this.mapEdgeLightObj.mapEdgePoints);
- const positions = new Float32Array(this.mapEdgeLightObj.mapEdgePoints.flat(1)); // 数组深度遍历扁平化
- // console.log(positions);
- this.mapEdgeLightObj.lightOpacityGeometry = new THREE.BufferGeometry();
- // 设置顶点
- this.mapEdgeLightObj.lightOpacityGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
- // 设置 粒子透明度为 0
- this.mapEdgeLightObj.lightOpacitys = new Float32Array(positions.length).map(() => 0);
- this.mapEdgeLightObj.lightOpacityGeometry.setAttribute('aOpacity', new THREE.BufferAttribute(this.mapEdgeLightObj.lightOpacitys, 1));
- // 顶点着色器
- 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 <=0.2){
- discard;
- }
- vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
- vec2 cUv=2.*uv-1.;
- vec4 color=vec4(1./length(cUv));
- color*=vOpacity;
- color.rgb*=uColor;
- gl_FragColor=color;
- }
- `;
- const material = new THREE.ShaderMaterial({
- vertexShader: vertexShader,
- fragmentShader: fragmentShader,
- transparent: true, // 设置透明
- // blending: THREE.AdditiveBlending,
- uniforms: {
- uSize: {
- value: 5.0,
- },
- uColor: {
- value: new THREE.Color('#FAFFB7'), // 光点颜色 fffb85
- },
- },
- });
- // material.blending = THREE.AdditiveBlending;
- const opacityPointsMesh = new THREE.Points(this.mapEdgeLightObj.lightOpacityGeometry, material);
- this.scene?.add(opacityPointsMesh);
- }
- setTooltip(detailModalId) {
- const _self = this;
- const tooltipDom = document.getElementById(detailModalId) as HTMLElement;
- this.detailModalCSS2Obj = new CSS2DObject(tooltipDom);
- tooltipDom.addEventListener('click', (event: Event) => {
- event.stopPropagation();
- });
- this.detailModalCSS2Obj.visible = false;
- this.scene?.add(this.detailModalCSS2Obj);
- const elements = tooltipDom.getElementsByClassName('close');
- if (elements[0]) {
- this.detailModalCSS2Obj.element.style.pointerEvents = 'auto';
- elements[0].addEventListener('click', (event: Event) => {
- event.stopPropagation();
- console.log('---------------->', event, _self);
- _self.detailModalCSS2Obj.visible = false;
- });
- }
- }
- /** 为 tooltip 设置标题,该标题的设置过程依赖 ../dialog-modal 中的结构 */
- setTooltipTitle(title: string) {
- const obj = this.detailModalCSS2Obj as CSS2DObject;
- const ele = obj.element.getElementsByClassName('modal-title')[0];
- if (!ele) return;
- ele.innerHTML = title;
- }
- async setTag(locationInfo: any[]) {
- const loader = new FontLoader();
- const font = await loader.loadAsync('/font/STSong_Regular.json');
- const locationTexture = new THREE.TextureLoader().load('/texture/location4.png');
- const colors = gradientColors('#FFDE00', '#F500FF', locationInfo.length, 1);
- locationInfo.forEach((item, index) => {
- const locationGroup = new THREE.Group();
- const [x, y] = this.projection(item['value']);
- const geometry = new THREE.CylinderGeometry(0.1, 0.0, 0.3, 3);
- if (Math.random() * 10 > 5) {
- const material = new THREE.MeshPhongMaterial({
- // ShaderMaterial
- // color: colors[index],
- color: 0xff1f00,
- side: THREE.DoubleSide,
- // blending: THREE.AdditiveBlending,
- transparent: true,
- opacity: 0.8,
- });
- const cylinder = new THREE.Mesh(geometry, material);
- cylinder.position.set(x, -y, this.mapConfig.deep + 0.08);
- cylinder.rotateX(Math.PI / 2);
- cylinder.scale.set(0.4, 0.4, 0.4);
- cylinder['userData']['code'] = item['code'];
- this.locationCylinderMeshArr.push(cylinder);
- locationGroup.add(cylinder);
- }
- const circleGeometry = new THREE.PlaneGeometry(0.06, 0.06);
- const circleMaterial = new THREE.MeshBasicMaterial({ map: locationTexture, side: THREE.DoubleSide, transparent: true });
- const plane = new THREE.Mesh(circleGeometry, circleMaterial);
- // plane['userData']['code'] = item['code'];
- plane.position.set(x, -y, this.mapConfig.deep + 0.02);
- plane.renderOrder = 999;
- locationGroup.add(plane);
- // 地名
- const textGeometry = new TextGeometry(item['name'], {
- font: font,
- size: 0.05, //字体大小
- height: 0.005, //字体高度curveSegments: 12,
- bevelEnabled: true,
- bevelSize: 0.001,
- bevelOffset: 0,
- bevelSegments: 0,
- });
- const textMesh = new THREE.Mesh(textGeometry, this.fontMaterial0);
- textMesh.name = item['code'];
- textMesh.scale.set(0.5, 0.5, 0.5);
- textMesh.position.set(x - 0.1, -y + 0.01, this.mapConfig.deep + 0.035);
- textMesh.rotateX(-Math.PI / 3);
- textMesh.rotateZ(-Math.PI);
- this.locationNameArr.push(textMesh);
- locationGroup.add(textMesh);
- textGeometry.computeBoundingBox();
- const { max, min } = textGeometry.boundingBox;
- const boxGeometry = new THREE.PlaneGeometry(Math.abs(max.x - min.x), Math.abs(max.y - min.y));
- const boxMesh = new THREE.Mesh(boxGeometry, this.fontMaterial2);
- boxMesh.rotateX(-Math.PI / 3);
- boxMesh.rotateZ(-Math.PI);
- boxMesh.position.set(x - Math.abs(max.x - min.x) + 0.018, -y + 0.01, this.mapConfig.deep + 0.03);
- boxMesh.renderOrder = 1;
- boxMesh['userData'] = item;
- this.locationNameBgArr.push(boxMesh);
- locationGroup.add(boxMesh);
- this.locationCylinderGroup.add(locationGroup);
- this.scene?.add(this.locationCylinderGroup);
- });
- }
- animationLocation() {
- this.locationCylinderMeshArr.forEach((mesh) => {
- // console.log(mesh);
- mesh.rotation.y -= 0.05;
- });
- }
- makeTextSprite(message, parameters) {
- if (parameters === undefined) parameters = {};
- const fontface = parameters['fontface'];
- const fontsize = parameters['fontsize'];
- const fontColor = parameters['fontColor'];
- const borderThickness = parameters['borderThickness'];
- const borderColor = parameters['borderColor'];
- const backgroundColor = parameters['backgroundColor'];
- // var spriteAlignment = THREE.SpriteAlignment.topLeft;
- const canvas = document.createElement('canvas');
- const context = <CanvasRenderingContext2D>canvas.getContext('2d');
- context.font = 'Bold ' + fontsize + 'px ' + fontface;
- // get size data (height depends only on font size)
- const metrics = context?.measureText(message);
- const textWidth = metrics?.width;
- // background color
- context.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')';
- // border color
- context.strokeStyle = 'rgba(' + borderColor.r + ',' + borderColor.g + ',' + borderColor.b + ',' + borderColor.a + ')';
- context.lineWidth = borderThickness;
- // const painting = {
- // width: textWidth * 1.4 + borderThickness * 2,
- // height: fontsize * 1.4 + borderThickness * 2,
- // round: parameters['round'],
- // };
- const painting = {
- width: textWidth,
- height: fontsize * 1.4 + borderThickness * 2,
- round: parameters['round'],
- };
- // 1.4 is extra height factor for text below baseline: g,j,p,q.
- // context.fillRect(0, 0, painting.width, painting.height)
- this.roundRect(context, borderThickness / 2, borderThickness / 2, painting.width, painting.height, painting.round);
- // text color
- context.fillStyle = 'rgba(' + fontColor.r + ',' + fontColor.g + ',' + fontColor.b + ',' + fontColor.a + ')';
- context.textAlign = 'center';
- context.textBaseline = 'middle';
- context.fillText(message, painting.width / 2, painting.height / 2);
- // canvas contents will be used for a texture
- const texture = new THREE.Texture(canvas);
- texture.needsUpdate = true;
- const spriteMaterial = new THREE.SpriteMaterial({
- map: texture,
- depthTest: false, // 解决精灵谍影问题
- // blending: THREE.AdditiveBlending,
- // transparent: true,
- // alignment: spriteAlignment
- });
- const sprite = new THREE.Sprite(spriteMaterial);
- // sprite.scale.set(1, 1, 1);
- return sprite;
- }
- // 城市 - 名称显示 - 样式
- roundRect(ctx, x, y, w, h, r) {
- ctx.beginPath();
- ctx.moveTo(x + r, y);
- ctx.lineTo(x + w - r, y);
- ctx.quadraticCurveTo(x + w, y, x + w, y + r);
- ctx.lineTo(x + w, y + h - r);
- ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
- ctx.lineTo(x + r, y + h);
- ctx.quadraticCurveTo(x, y + h, x, y + h - r);
- ctx.lineTo(x, y + r);
- ctx.quadraticCurveTo(x, y, x + r, y);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- }
- // 地球贴图纹理
- setEarth() {
- const geometry = new THREE.PlaneGeometry(15.0, 15.0);
- const texture = new THREE.TextureLoader().load('/texture/earth.jpg');
- const bumpTexture = new THREE.TextureLoader().load('/texture/earth.jpg');
- texture.encoding = THREE.sRGBEncoding;
- const material = new THREE.MeshPhongMaterial({
- map: texture, // 贴图
- bumpMap: bumpTexture,
- // normalMap: bumpTexture,
- bumpScale: 1,
- // specularMap: bumpTexture,
- // specular: 0xffffff,
- // shininess: 1,
- // color: '#000000',
- side: THREE.FrontSide,
- });
- const earthPlane = new THREE.Mesh(geometry, material);
- this.scene?.add(earthPlane);
- }
- // 射线
- setRaycaster() {
- const _self = this;
- let intersects = <any[]>[];
- const eventAction = (event, object3DArr) => {
- this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
- this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
- (this.raycaster as THREE.Raycaster).setFromCamera(this.mouse, this.camera as THREE.Camera);
- intersects = this.raycaster?.intersectObjects(object3DArr, true) as THREE.Intersection[];
- };
- const onMouseMove = (event) => {
- this.locationNameArr.forEach((item: THREE.Mesh) => {
- if (item.material == this.fontMaterial1) {
- item.material = this.fontMaterial0;
- item.scale.set(0.5, 0.5, 0.5);
- }
- });
- eventAction(event, this.locationNameBgArr);
- if (this.detailModalCSS2Obj && !this.detailModalCSS2Obj.visible && intersects.length > 0 && this.detailModalCSS2Obj) {
- const intersectObj = intersects[0].object as THREE.Mesh;
- const point = intersects[0].point;
- this.detailModalCSS2Obj.position.set(point.x, point.y, point.z);
- this.detailModalCSS2Obj.applyMatrix4(this.scene.matrix);
- if (intersectObj['userData'] && intersectObj['userData']['code']) {
- const textMesh = this.locationCylinderGroup.getObjectByName(intersectObj['userData']['code']) as THREE.Mesh;
- if (textMesh) {
- textMesh.material = this.fontMaterial1;
- textMesh.scale.set(1, 1, 1);
- }
- }
- }
- };
- // 点击地图事件
- const onClick = (event: Event) => {
- event.preventDefault();
- eventAction(event, [...this.provinceArr, ...this.locationNameBgArr]);
- if (new Date().getTime() - this.playerStartClickTime < 400) {
- // 双击
- if (intersects.length > 0) {
- const point = intersects[0].point;
- const boxMaxY = new THREE.Box3().setFromObject(intersects[0].object).max.y;
- this.mapClickAnimation(point, boxMaxY);
- }
- } else {
- if (intersects.length > 0 && this.detailModalCSS2Obj) {
- const detailMesh = intersects.find((item) => item?.object['userData'] && item.object['userData']['code']);
- if (detailMesh) {
- // 发送消息
- this.detailModalCSS2Obj.visible = true;
- this.setTooltipTitle(detailMesh.object.userData.name);
- }
- }
- }
- console.log(this.camera, this.controller);
- this.playerStartClickTime = new Date().getTime();
- };
- window.addEventListener('mousemove', onMouseMove, false);
- window.addEventListener('click', onClick, false);
- }
- mapClickAnimation(pos, boxMaxY) {
- const _self = this;
- const distance = boxMaxY;
- const angel = 90;
- if (!this.controller || !this.camera) return;
- //关闭控制器
- this.controller.enabled = false;
- const begin = {
- x: this.camera.position.x,
- y: this.camera.position.y,
- z: this.camera.position.z,
- x1: this.controller.target.x,
- y1: this.controller.target.y,
- z1: this.controller.target.z,
- };
- const end = {
- x: pos.x,
- y: pos.y + Math.cos(angel) * distance,
- z: pos.z + Math.sin(angel) * distance,
- x1: pos.x,
- y1: pos.y,
- z1: pos.z,
- };
- gsap.fromTo(
- begin,
- { ...begin },
- {
- ...end,
- time: 500,
- onUpdate(obj) {
- if (_self.camera && _self.controller) {
- _self.camera.position.x = obj.x;
- _self.camera.position.y = obj.y;
- _self.camera.position.z = obj.z;
- _self.controller.target.set(obj.x1, obj.y1, obj.z1);
- // 控制器更新
- _self.controller.update();
- }
- },
- onUpdateParams: [begin],
- onComplete() {
- if (_self.controller) _self.controller.enabled = true;
- },
- }
- );
- }
- resetCameraTween() {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- if (this.controller && this.camera && this.cameraPosArr) {
- //关闭控制器
- this.controller.enabled = false;
- const begin = {
- x: this.camera.position.x,
- y: this.camera.position.y,
- z: this.camera.position.z,
- x1: this.controller.target.x,
- y1: this.controller.target.y,
- z1: this.controller.target.z,
- };
- const end = {
- x: 0.20986936323918032,
- y: -0.7192585185669363,
- z: 1.3178315065115611,
- x1: 0.1943010027503315,
- y1: 0.6831035409655225,
- z1: -0.551271738125278,
- };
- gsap.fromTo(
- begin,
- { ...begin },
- {
- ...end,
- duration: 1,
- ease: 'easeOutBounce',
- onUpdate(obj) {
- self.camera.position.x = obj.x;
- self.camera.position.y = obj.y;
- self.camera.position.z = obj.z;
- self.controller.target.x = obj.x1;
- self.controller.target.y = obj.y1;
- self.controller.target.z = obj.z1;
- // self.camera.lookAt(0, 0, 0);
- // 控制器更新
- self.controller.update();
- },
- onUpdateParams: [begin],
- onComplete() {
- self.controller.enabled = true;
- },
- }
- );
- }
- }
- createSideShaderMaterial(material) {
- material.onBeforeCompile = function (shader) {
- // console.log(shader.fragmentShader);
- shader.vertexShader = shader.vertexShader.replace(
- `void main() {`,
- `
- varying vec4 vPosition;
- void main() {
- `
- );
- shader.vertexShader = shader.vertexShader.replace(
- `#include <fog_vertex>`,
- `#include <fog_vertex>
- vPosition=modelMatrix * vec4( transformed, 1.0 );
- `
- );
- shader.fragmentShader = shader.fragmentShader.replace(
- `void main() {`,
- `varying vec4 vPosition;
- void main() {`
- );
- shader.fragmentShader = shader.fragmentShader.replace(
- `#include <specularmap_fragment>`,
- `
- #include <specularmap_fragment>
- float z = vPosition.z;
- float s = step(5.0,z);
- vec3 bottomColor = vec3(.0,1.0,1.0);
-
- diffuseColor.rgb = mix(bottomColor,diffuseColor.rgb,s);
- float r = abs(z * (1.0 - s * 2.0) + s * 4.0) ;
- diffuseColor.rgb *= pow(r, 0.5 + 2.0 * s);
- `
- );
- };
- return material;
- }
- setMaterial() {
- const locationShape = this.map.getObjectByName('locationShape');
- if (!locationShape) return;
- locationShape.geometry.computeBoundingBox();
- const { max, min } = locationShape.geometry.boundingBox;
- const distance = max.y - min.y + 2;
- locationShape.material[0].onBeforeCompile = (shader) => {
- shader.uniforms.uDistance = {
- value: distance,
- };
- shader.uniforms.uTopColor = {
- value: new THREE.Color(0xaaaeff),
- };
- this.addGrad(shader);
- };
- }
- addGrad = (shader) => {
- shader.vertexShader = shader.vertexShader.replace(
- '#include <common>',
- `
- varying vec3 vPosition;
- #include <common>
- `
- );
- shader.vertexShader = shader.vertexShader.replace(
- '#include <fog_vertex>',
- `
- #include <fog_vertex>
- vPosition = position;
- `
- );
- shader.fragmentShader = shader.fragmentShader.replace(
- '#include <common>',
- `
- uniform vec3 uTopColor;
- uniform float uDistance;
- varying vec3 vPosition;
- #include <common>
- `
- );
- shader.fragmentShader = shader.fragmentShader.replace(
- '#include <dithering_fragment>',
- `
- #include <dithering_fragment>
- vec4 distGradColor = gl_FragColor;
- // 设置混合的百分比
- float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0;
- // 设置混合颜色
- vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix);
- gl_FragColor = vec4(colorMix, 1.0);
-
- `
- );
- };
- // 动画 - 城市边缘流光
- animationCityEdgeLight() {
- if (this.mapEdgeLightObj.lightOpacitys && this.mapEdgeLightObj.mapEdgePoints) {
- if (this.mapEdgeLightObj.lightCurrentPos > this.mapEdgeLightObj.mapEdgePoints.length) {
- this.mapEdgeLightObj.lightCurrentPos = 0;
- }
- this.mapEdgeLightObj.lightCurrentPos += this.mapEdgeLightObj.lightSpeed;
- for (let i = 0; i < this.mapEdgeLightObj.lightSpeed; i++) {
- this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos - i) % this.mapEdgeLightObj.mapEdgePoints.length] = 0;
- }
- for (let i = 0; i < 100; i++) {
- this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos + i) % this.mapEdgeLightObj.mapEdgePoints.length] =
- i / 50 > 2 ? 2 : i / 50;
- }
- if (this.mapEdgeLightObj.lightOpacityGeometry) {
- this.mapEdgeLightObj.lightOpacityGeometry.attributes.aOpacity.needsUpdate = true;
- }
- }
- }
- // 动画
- animate() {
- requestAnimationFrame(this.animate.bind(this));
- this.animationCityEdgeLight();
- this.animationLocation();
- this.controller?.update();
- this.camera?.updateProjectionMatrix();
- this.renderer?.render(this.scene, this.camera);
- this.labelRenderer?.render(this.scene, this.camera);
- }
- }
- export default earthtMap;
|