3dMap.ts 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. import * as THREE from 'three';
  2. import * as d3 from 'd3';
  3. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
  4. import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
  5. import gsap from 'gsap';
  6. import { gradientColors } from '/@/utils/threejs/util';
  7. // import * as dat from 'dat.gui';
  8. import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
  9. import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
  10. // const gui = new dat.GUI();
  11. // gui.domElement.style = 'position:absolute;top:100px;left:10px;z-index:99999999999999';
  12. class earthtMap {
  13. container: HTMLCanvasElement | null; //canvas 容器
  14. labelRenderer: CSS2DRenderer | null = null;
  15. camera: THREE.PerspectiveCamera | null = null; // 相机
  16. scene: THREE.Scene | null = null;
  17. renderer: THREE.WebGLRenderer | null = null;
  18. controller: OrbitControls | null = null;
  19. map = new THREE.Object3D();
  20. provinceArr = <THREE.Mesh[]>[];
  21. locationCylinderMeshArr = <THREE.Mesh[]>[];
  22. locationCylinderGroup = new THREE.Object3D();
  23. locationNameBgArr = <THREE.Mesh[]>[];
  24. locationNameArr = <THREE.Mesh[]>[];
  25. raycaster = new THREE.Raycaster();
  26. mouse = new THREE.Vector2();
  27. tooltip: HTMLElement | null; //canvas 容器
  28. detailModalCSS2Obj: CSS2DObject | null = null;
  29. mapConfig = {
  30. deep: 0.2,
  31. };
  32. projection = d3.geoMercator().center([109.741193, 42.290162]).scale(90).translate([-1, -6]); // 根据地球贴图做轻微调整
  33. cameraPosArr = [{ x: 0, y: -2.0, z: 4.8 }];
  34. mapEdgeLightObj = {
  35. mapEdgePoints: <number[][]>[],
  36. lightOpacityGeometry: new THREE.BufferGeometry(), // 单独把geometry提出来,动画用
  37. lightSpeed: 3, // 边缘流光参数
  38. lightCurrentPos: 0,
  39. lightOpacitys: null,
  40. };
  41. fontMaterial3 = new THREE.MeshBasicMaterial({ color: 0x00dbff, transparent: true, opacity: 0.5 });
  42. fontMaterial0 = new THREE.MeshBasicMaterial({ color: 0xffffff });
  43. fontMaterial1 = new THREE.MeshBasicMaterial({ color: 0xf7ff00 });
  44. fontMaterial2 = new THREE.MeshBasicMaterial({ color: 0xf7ff00, transparent: true, opacity: 0, depthTest: true, depthWrite: true });
  45. playerStartClickTime = new Date().getTime();
  46. constructor(canvasSelector, tooltip) {
  47. this.container = document.getElementById(canvasSelector) as HTMLCanvasElement;
  48. this.tooltip = document.getElementById(tooltip) as HTMLElement;
  49. }
  50. init() {
  51. this.setScene();
  52. this.setCamera();
  53. this.setRenderer(); // 创建渲染器对象
  54. this.setController(); // 创建控件对象
  55. this.loadMapData();
  56. this.loadMapData1();
  57. this.addCompass();
  58. this.setEarth();
  59. this.setLight();
  60. this.setRaycaster();
  61. // this.animate();
  62. }
  63. setScene() {
  64. // 创建场景对象Scene
  65. this.scene = new THREE.Scene();
  66. this.scene.rotateZ(Math.PI);
  67. }
  68. setCamera() {
  69. // 第二参数就是 长度和宽度比 默认采用浏览器 返回以像素为单位的窗口的内部宽度和高度
  70. this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 500);
  71. this.camera.position.set(0.3909238439719087, -16.078364444489004, 3.748569090055743); // 0, -5, 1
  72. this.camera.lookAt(this.scene.position);
  73. // this.camera.lookAt(new THREE.Vector3(0, 1, 0)); // 0, 0, 0
  74. }
  75. setRenderer() {
  76. if (!this.container) return;
  77. this.renderer = new THREE.WebGLRenderer({
  78. antialias: true,
  79. // alpha: true,
  80. // precision: 'mediump',
  81. // logarithmicDepthBuffer: true, // 是否使用对数深度缓存
  82. });
  83. this.renderer.sortObjects = true;
  84. this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
  85. this.renderer.setPixelRatio(window.devicePixelRatio);
  86. // this.renderer.sortObjects = false; // 是否需要对对象排序
  87. this.container.appendChild(this.renderer.domElement);
  88. this.renderer.outputColorSpace = THREE.SRGBColorSpace;
  89. // this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
  90. this.labelRenderer = new CSS2DRenderer();
  91. this.labelRenderer.setSize(this.container.clientWidth, this.container.clientHeight);
  92. this.labelRenderer.domElement.style.position = 'absolute';
  93. this.labelRenderer.domElement.style.top = '0';
  94. this.labelRenderer.domElement.style.pointerEvents = 'none';
  95. this.container.appendChild(this.labelRenderer.domElement);
  96. }
  97. setController() {
  98. if (this.camera && this.renderer && this.renderer.domElement) {
  99. this.controller = new OrbitControls(this.camera, this.renderer.domElement);
  100. // this.controller.minDistance = 2;
  101. // this.controller.minDistance = 0.5;
  102. // this.controller.maxDistance = 35.5; // 5.5
  103. // 阻尼(惯性)
  104. // this.controller.enableDamping = true;
  105. // this.controller.dampingFactor = 0.04;
  106. this.controller.minAzimuthAngle = -0.1;
  107. this.controller.maxAzimuthAngle = 0.1;
  108. this.controller.minPolarAngle = 0.01;
  109. this.controller.maxPolarAngle = Math.PI - 0.01;
  110. // 修改相机的lookAt是不会影响THREE.OrbitControls的target的
  111. // this.controller.target = new THREE.Vector3(-0.9915, -1.5075, -1.8432);
  112. this.controller.target = new THREE.Vector3(0, 0, -1);
  113. }
  114. }
  115. // 辅助线
  116. addHelper() {
  117. // let helper = new THREE.CameraHelper(this.camera);
  118. // this.scene.add(helper);
  119. //轴辅助 (每一个轴的长度)
  120. const axisHelper = new THREE.AxisHelper(150); // 红线是X轴,绿线是Y轴,蓝线是Z轴
  121. // this.scene.add(axisHelper);
  122. const gridHelper = new THREE.GridHelper(100, 30, 0x2c2c2c, 0x888888);
  123. // this.scene.add(gridHelper);
  124. }
  125. setLight() {
  126. const ambientLight = new THREE.AmbientLight(0x999999, 2);
  127. this.scene?.add(ambientLight);
  128. // // 平行光
  129. const directionalLight = new THREE.DirectionalLight(0xffffff, 2.0);
  130. this.scene?.add(directionalLight);
  131. // 聚光光源 - 照模型
  132. const spotLight = new THREE.SpotLight(0xffffff, 2);
  133. spotLight.position.set(0.62, 10.0, 2.78);
  134. spotLight.castShadow = true;
  135. this.scene?.add(spotLight);
  136. // 聚光光源辅助线
  137. // const spotLightHelper = new THREE.SpotLightHelper(spotLight);
  138. // this.scene?.add(spotLightHelper);
  139. // 点光源 - 照模型
  140. const test = new THREE.PointLight('#ffffff', 1.2, 20);
  141. test.position.set(-0.39, -0.93, 0.64);
  142. this.scene?.add(test);
  143. // const testHelperMap = new THREE.PointLightHelper(test);
  144. // this.scene?.add(testHelperMap);
  145. // 点光源 - 蓝色照地球
  146. const pointLightMap = new THREE.PointLight('#4161ff', 1.4, 20);
  147. pointLightMap.position.set(-0.18, -1.19, 1.5);
  148. this.scene?.add(pointLightMap);
  149. // const spotLightHelperMap = new THREE.PointLightHelper(pointLightMap);
  150. // this.scene?.add(spotLightHelperMap);
  151. }
  152. // 加载地图数据
  153. loadMapData() {
  154. const loader = new THREE.FileLoader();
  155. loader.load('/json/vent3.json', async (data: string) => {
  156. const jsondata = JSON.parse(data);
  157. await this.addMapGeometry(jsondata);
  158. });
  159. }
  160. loadMapData1() {
  161. const loader = new THREE.FileLoader();
  162. loader.load('/json/vent2.json', (data: string) => {
  163. const jsondata = JSON.parse(data);
  164. this.addMapGeometry1(jsondata);
  165. });
  166. }
  167. addCompass() {}
  168. addMapGeometry1(jsondata) {
  169. // 墨卡托投影转换
  170. jsondata.features.forEach((elem) => {
  171. // 定一个省份3D对象
  172. const province = new THREE.Object3D();
  173. // 每个的 坐标 数组
  174. const coordinates = elem.geometry.coordinates;
  175. // 循环坐标数组
  176. coordinates.forEach((multiPolygon) => {
  177. multiPolygon.forEach((polygon) => {
  178. const shape = new THREE.Shape();
  179. const lineMaterial = new THREE.LineBasicMaterial({
  180. color: '#A2A2A2',
  181. transparent: true,
  182. opacity: 0.3,
  183. linewidth: 1,
  184. linecap: 'round', //ignored by WebGLRenderer
  185. linejoin: 'round', //ignored by WebGLRenderer
  186. });
  187. // const lineGeometry = new THREE.Geometry();
  188. // for (let i = 0; i < polygon.length; i++) {
  189. // const [x, y] = projection(polygon[i]);
  190. // if (i === 0) {
  191. // shape.moveTo(x, -y);
  192. // }
  193. // shape.lineTo(x, -y);
  194. // lineGeometry.vertices.push(new THREE.Vector3(x, -y, 3));
  195. // }
  196. const lineGeometry = new THREE.BufferGeometry();
  197. const pointsArray = <any[]>[];
  198. for (let i = 0; i < polygon.length; i++) {
  199. const [x, y] = this.projection(polygon[i]);
  200. if (i === 0) {
  201. shape.moveTo(x, -y);
  202. }
  203. shape.lineTo(x, -y);
  204. pointsArray.push(new THREE.Vector3(x, -y, 0.1));
  205. }
  206. // console.log(pointsArray);
  207. lineGeometry.setFromPoints(pointsArray);
  208. const extrudeSettings = {
  209. depth: 0.01,
  210. bevelEnabled: false, // 对挤出的形状应用是否斜角
  211. };
  212. const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
  213. const material = new THREE.MeshPhongMaterial({
  214. color: '#3962DE',
  215. transparent: true,
  216. opacity: 0.1,
  217. side: THREE.FrontSide,
  218. // depthTest: true,
  219. });
  220. const material1 = new THREE.MeshLambertMaterial({
  221. color: '#3BB2ED',
  222. transparent: true,
  223. opacity: 0.4,
  224. side: THREE.FrontSide,
  225. // wireframe: true
  226. });
  227. const mesh = new THREE.Mesh(geometry, [material, material1]);
  228. const line = new THREE.Line(lineGeometry, lineMaterial);
  229. // console.log(elem.properties);
  230. province.add(mesh);
  231. province.add(line);
  232. });
  233. });
  234. // province.scale.set(5, 5, 0);
  235. // province.position.set(0, 0, 0);
  236. // console.log(province);
  237. this.scene?.add(province);
  238. });
  239. }
  240. // 地图模型
  241. async addMapGeometry(jsondata) {
  242. const _self = this;
  243. const loader = new FontLoader();
  244. const font = await loader.loadAsync('/font/STSong_Regular.json');
  245. jsondata.features.forEach((elem) => {
  246. // 定一个省份3D对象
  247. const province = new THREE.Object3D();
  248. // 每个的 坐标 数组
  249. const coordinates = elem.geometry.coordinates;
  250. // 循环坐标数组
  251. coordinates.forEach((multiPolygon) => {
  252. multiPolygon.forEach(async (polygon) => {
  253. const shape = new THREE.Shape();
  254. const lineMaterial = new THREE.LineBasicMaterial({
  255. color: '#3EFFED',
  256. // linewidth: 1,
  257. linecap: 'round', //ignored by WebGLRenderer
  258. linejoin: 'round', //ignored by WebGLRenderer
  259. });
  260. // const lineGeometry = new THREE.Geometry();
  261. // for (let i = 0; i < polygon.length; i++) {
  262. // const [x, y] = projection(polygon[i]);
  263. // if (i === 0) {
  264. // shape.moveTo(x, -y);
  265. // }
  266. // shape.lineTo(x, -y);
  267. // lineGeometry.vertices.push(new THREE.Vector3(x, -y, 3));
  268. // }
  269. const lineGeometry = new THREE.BufferGeometry();
  270. const pointsArray = <any[]>[];
  271. for (let i = 0; i < polygon.length; i++) {
  272. const [x, y] = this.projection(polygon[i]);
  273. if (i === 0) {
  274. shape.moveTo(x, -y);
  275. }
  276. shape.lineTo(x, -y);
  277. pointsArray.push(new THREE.Vector3(x, -y, this.mapConfig.deep));
  278. // 做边缘流光效果,把所有点保存下来
  279. this.mapEdgeLightObj.mapEdgePoints.push([x, -y, this.mapConfig.deep]);
  280. }
  281. // console.log(pointsArray);
  282. lineGeometry.setFromPoints(pointsArray);
  283. const extrudeSettings = {
  284. depth: this.mapConfig.deep,
  285. bevelEnabled: true, // 对挤出的形状应用是否斜角
  286. bevelSegments: 0,
  287. bevelThickness: 0.1,
  288. };
  289. const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
  290. const material = new THREE.MeshPhongMaterial({
  291. color: '#3962DE',
  292. transparent: true,
  293. opacity: 0.4,
  294. side: THREE.FrontSide,
  295. // depthTest: true,
  296. });
  297. let material1 = new THREE.MeshBasicMaterial({
  298. polygonOffset: true,
  299. polygonOffsetFactor: 1,
  300. polygonOffsetUnits: 1,
  301. metalness: 1,
  302. roughness: 1,
  303. color: '#3EC9FF',
  304. transparent: true,
  305. opacity: 0.6,
  306. side: THREE.FrontSide,
  307. // wireframe: true
  308. });
  309. material1 = this.createSideShaderMaterial(material1);
  310. const mesh = new THREE.Mesh(geometry, [material, material1]);
  311. mesh.name = 'locationShape';
  312. const line = new THREE.Line(lineGeometry, lineMaterial);
  313. // 将省份的属性 加进来
  314. province['properties'] = elem.properties;
  315. // 将城市信息放到模型中,后续做动画用
  316. if (elem.properties.centroid) {
  317. const [x, y] = this.projection(elem.properties.centroid); // uv映射坐标
  318. province['properties']['_centroid'] = [x, y];
  319. const textGeometry = new TextGeometry(province['properties']['name'], {
  320. font: font,
  321. size: 0.05, //字体大小
  322. height: 0.005, //字体高度curveSegments: 12,
  323. bevelEnabled: true,
  324. bevelSize: 0.001,
  325. bevelOffset: 0,
  326. bevelSegments: 0,
  327. });
  328. const textMesh = new THREE.Mesh(textGeometry, this.fontMaterial3);
  329. textMesh.scale.set(1, 1, 1);
  330. textMesh.position.set(x, -y, this.mapConfig.deep + 0.01);
  331. textMesh.rotateZ(-Math.PI);
  332. province.add(textMesh);
  333. }
  334. this.provinceArr.push(mesh);
  335. // console.log(elem.properties);
  336. province.add(mesh);
  337. province.add(line);
  338. });
  339. });
  340. // province.scale.set(5, 5, 0);
  341. // province.position.set(0, 0, 0);
  342. // console.log(province);
  343. this.map?.add(province);
  344. });
  345. this.setMapEdgeLight();
  346. this.scene?.add(this.map);
  347. setTimeout(() => {
  348. _self.resetCameraTween();
  349. // _self.setMaterial();
  350. }, 1500);
  351. }
  352. setMapEdgeLight() {
  353. // console.log(this.mapEdgeLightObj.mapEdgePoints);
  354. const positions = new Float32Array(this.mapEdgeLightObj.mapEdgePoints.flat(1)); // 数组深度遍历扁平化
  355. // console.log(positions);
  356. this.mapEdgeLightObj.lightOpacityGeometry = new THREE.BufferGeometry();
  357. // 设置顶点
  358. this.mapEdgeLightObj.lightOpacityGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  359. // 设置 粒子透明度为 0
  360. this.mapEdgeLightObj.lightOpacitys = new Float32Array(positions.length).map(() => 0);
  361. this.mapEdgeLightObj.lightOpacityGeometry.setAttribute('aOpacity', new THREE.BufferAttribute(this.mapEdgeLightObj.lightOpacitys, 1));
  362. // 顶点着色器
  363. const vertexShader = `
  364. attribute float aOpacity;
  365. uniform float uSize;
  366. varying float vOpacity;
  367. void main(){
  368. gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
  369. gl_PointSize = uSize;
  370. vOpacity=aOpacity;
  371. }
  372. `;
  373. // 片段着色器
  374. const fragmentShader = `
  375. varying float vOpacity;
  376. uniform vec3 uColor;
  377. float invert(float n){
  378. return 1.-n;
  379. }
  380. void main(){
  381. if(vOpacity <=0.2){
  382. discard;
  383. }
  384. vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
  385. vec2 cUv=2.*uv-1.;
  386. vec4 color=vec4(1./length(cUv));
  387. color*=vOpacity;
  388. color.rgb*=uColor;
  389. gl_FragColor=color;
  390. }
  391. `;
  392. const material = new THREE.ShaderMaterial({
  393. vertexShader: vertexShader,
  394. fragmentShader: fragmentShader,
  395. transparent: true, // 设置透明
  396. // blending: THREE.AdditiveBlending,
  397. uniforms: {
  398. uSize: {
  399. value: 5.0,
  400. },
  401. uColor: {
  402. value: new THREE.Color('#FAFFB7'), // 光点颜色 fffb85
  403. },
  404. },
  405. });
  406. // material.blending = THREE.AdditiveBlending;
  407. const opacityPointsMesh = new THREE.Points(this.mapEdgeLightObj.lightOpacityGeometry, material);
  408. this.scene?.add(opacityPointsMesh);
  409. }
  410. setTooltip(detailModalId) {
  411. const _self = this;
  412. const tooltipDom = document.getElementById(detailModalId) as HTMLElement;
  413. this.detailModalCSS2Obj = new CSS2DObject(tooltipDom);
  414. tooltipDom.addEventListener('click', (event: Event) => {
  415. event.stopPropagation();
  416. });
  417. this.detailModalCSS2Obj.visible = false;
  418. this.scene?.add(this.detailModalCSS2Obj);
  419. const elements = tooltipDom.getElementsByClassName('close');
  420. if (elements[0]) {
  421. this.detailModalCSS2Obj.element.style.pointerEvents = 'auto';
  422. elements[0].addEventListener('click', (event: Event) => {
  423. event.stopPropagation();
  424. console.log('---------------->', event, _self);
  425. _self.detailModalCSS2Obj.visible = false;
  426. });
  427. }
  428. }
  429. async setTag(locationInfo: any[]) {
  430. const loader = new FontLoader();
  431. const font = await loader.loadAsync('/font/STSong_Regular.json');
  432. const locationTexture = new THREE.TextureLoader().load('/texture/location4.png');
  433. const colors = gradientColors('#FFDE00', '#F500FF', locationInfo.length, 1);
  434. locationInfo.forEach((item, index) => {
  435. const locationGroup = new THREE.Group();
  436. const [x, y] = this.projection(item['value']);
  437. const geometry = new THREE.CylinderGeometry(0.1, 0.0, 0.3, 3);
  438. if (Math.random() * 10 > 5) {
  439. const material = new THREE.MeshPhongMaterial({
  440. // ShaderMaterial
  441. // color: colors[index],
  442. color: 0xff1f00,
  443. side: THREE.DoubleSide,
  444. // blending: THREE.AdditiveBlending,
  445. transparent: true,
  446. opacity: 0.8,
  447. });
  448. const cylinder = new THREE.Mesh(geometry, material);
  449. cylinder.position.set(x, -y, this.mapConfig.deep + 0.08);
  450. cylinder.rotateX(Math.PI / 2);
  451. cylinder.scale.set(0.4, 0.4, 0.4);
  452. cylinder['userData']['code'] = item['code'];
  453. this.locationCylinderMeshArr.push(cylinder);
  454. locationGroup.add(cylinder);
  455. }
  456. const circleGeometry = new THREE.PlaneGeometry(0.06, 0.06);
  457. const circleMaterial = new THREE.MeshBasicMaterial({ map: locationTexture, side: THREE.DoubleSide, transparent: true });
  458. const plane = new THREE.Mesh(circleGeometry, circleMaterial);
  459. // plane['userData']['code'] = item['code'];
  460. plane.position.set(x, -y, this.mapConfig.deep + 0.02);
  461. plane.renderOrder = 999;
  462. locationGroup.add(plane);
  463. // 地名
  464. const textGeometry = new TextGeometry(item['name'], {
  465. font: font,
  466. size: 0.05, //字体大小
  467. height: 0.005, //字体高度curveSegments: 12,
  468. bevelEnabled: true,
  469. bevelSize: 0.001,
  470. bevelOffset: 0,
  471. bevelSegments: 0,
  472. });
  473. const textMesh = new THREE.Mesh(textGeometry, this.fontMaterial0);
  474. textMesh.name = item['code'];
  475. textMesh.scale.set(0.5, 0.5, 0.5);
  476. textMesh.position.set(x - 0.1, -y + 0.01, this.mapConfig.deep + 0.035);
  477. textMesh.rotateX(-Math.PI / 3);
  478. textMesh.rotateZ(-Math.PI);
  479. this.locationNameArr.push(textMesh);
  480. locationGroup.add(textMesh);
  481. textGeometry.computeBoundingBox();
  482. const { max, min } = textGeometry.boundingBox;
  483. const boxGeometry = new THREE.PlaneGeometry(Math.abs(max.x - min.x), Math.abs(max.y - min.y));
  484. const boxMesh = new THREE.Mesh(boxGeometry, this.fontMaterial2);
  485. boxMesh.rotateX(-Math.PI / 3);
  486. boxMesh.rotateZ(-Math.PI);
  487. boxMesh.position.set(x - Math.abs(max.x - min.x) + 0.018, -y + 0.01, this.mapConfig.deep + 0.03);
  488. boxMesh.renderOrder = 1;
  489. boxMesh['userData']['code'] = item['code'];
  490. this.locationNameBgArr.push(boxMesh);
  491. locationGroup.add(boxMesh);
  492. this.locationCylinderGroup.add(locationGroup);
  493. this.scene?.add(this.locationCylinderGroup);
  494. });
  495. }
  496. animationLocation() {
  497. this.locationCylinderMeshArr.forEach((mesh) => {
  498. // console.log(mesh);
  499. mesh.rotation.y -= 0.05;
  500. });
  501. }
  502. makeTextSprite(message, parameters) {
  503. if (parameters === undefined) parameters = {};
  504. const fontface = parameters['fontface'];
  505. const fontsize = parameters['fontsize'];
  506. const fontColor = parameters['fontColor'];
  507. const borderThickness = parameters['borderThickness'];
  508. const borderColor = parameters['borderColor'];
  509. const backgroundColor = parameters['backgroundColor'];
  510. // var spriteAlignment = THREE.SpriteAlignment.topLeft;
  511. const canvas = document.createElement('canvas');
  512. const context = <CanvasRenderingContext2D>canvas.getContext('2d');
  513. context.font = 'Bold ' + fontsize + 'px ' + fontface;
  514. // get size data (height depends only on font size)
  515. const metrics = context?.measureText(message);
  516. const textWidth = metrics?.width;
  517. // background color
  518. context.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')';
  519. // border color
  520. context.strokeStyle = 'rgba(' + borderColor.r + ',' + borderColor.g + ',' + borderColor.b + ',' + borderColor.a + ')';
  521. context.lineWidth = borderThickness;
  522. // const painting = {
  523. // width: textWidth * 1.4 + borderThickness * 2,
  524. // height: fontsize * 1.4 + borderThickness * 2,
  525. // round: parameters['round'],
  526. // };
  527. const painting = {
  528. width: textWidth,
  529. height: fontsize * 1.4 + borderThickness * 2,
  530. round: parameters['round'],
  531. };
  532. // 1.4 is extra height factor for text below baseline: g,j,p,q.
  533. // context.fillRect(0, 0, painting.width, painting.height)
  534. this.roundRect(context, borderThickness / 2, borderThickness / 2, painting.width, painting.height, painting.round);
  535. // text color
  536. context.fillStyle = 'rgba(' + fontColor.r + ',' + fontColor.g + ',' + fontColor.b + ',' + fontColor.a + ')';
  537. context.textAlign = 'center';
  538. context.textBaseline = 'middle';
  539. context.fillText(message, painting.width / 2, painting.height / 2);
  540. // canvas contents will be used for a texture
  541. const texture = new THREE.Texture(canvas);
  542. texture.needsUpdate = true;
  543. const spriteMaterial = new THREE.SpriteMaterial({
  544. map: texture,
  545. depthTest: false, // 解决精灵谍影问题
  546. // blending: THREE.AdditiveBlending,
  547. // transparent: true,
  548. // alignment: spriteAlignment
  549. });
  550. const sprite = new THREE.Sprite(spriteMaterial);
  551. // sprite.scale.set(1, 1, 1);
  552. return sprite;
  553. }
  554. // 城市 - 名称显示 - 样式
  555. roundRect(ctx, x, y, w, h, r) {
  556. ctx.beginPath();
  557. ctx.moveTo(x + r, y);
  558. ctx.lineTo(x + w - r, y);
  559. ctx.quadraticCurveTo(x + w, y, x + w, y + r);
  560. ctx.lineTo(x + w, y + h - r);
  561. ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
  562. ctx.lineTo(x + r, y + h);
  563. ctx.quadraticCurveTo(x, y + h, x, y + h - r);
  564. ctx.lineTo(x, y + r);
  565. ctx.quadraticCurveTo(x, y, x + r, y);
  566. ctx.closePath();
  567. ctx.fill();
  568. ctx.stroke();
  569. }
  570. // 地球贴图纹理
  571. setEarth() {
  572. const geometry = new THREE.PlaneGeometry(15.0, 15.0);
  573. const texture = new THREE.TextureLoader().load('/texture/earth.jpg');
  574. const bumpTexture = new THREE.TextureLoader().load('/texture/earth.jpg');
  575. texture.encoding = THREE.sRGBEncoding;
  576. const material = new THREE.MeshPhongMaterial({
  577. map: texture, // 贴图
  578. bumpMap: bumpTexture,
  579. // normalMap: bumpTexture,
  580. bumpScale: 1,
  581. // specularMap: bumpTexture,
  582. // specular: 0xffffff,
  583. // shininess: 1,
  584. // color: '#000000',
  585. side: THREE.FrontSide,
  586. });
  587. const earthPlane = new THREE.Mesh(geometry, material);
  588. this.scene?.add(earthPlane);
  589. }
  590. // 射线
  591. setRaycaster() {
  592. const _self = this;
  593. let intersects = <any[]>[];
  594. const eventAction = (event, object3DArr) => {
  595. this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  596. this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  597. (this.raycaster as THREE.Raycaster).setFromCamera(this.mouse, this.camera as THREE.Camera);
  598. intersects = this.raycaster?.intersectObjects(object3DArr, true) as THREE.Intersection[];
  599. };
  600. const onMouseMove = (event) => {
  601. this.locationNameArr.forEach((item: THREE.Mesh) => {
  602. if (item.material == this.fontMaterial1) {
  603. item.material = this.fontMaterial0;
  604. item.scale.set(0.5, 0.5, 0.5);
  605. }
  606. });
  607. eventAction(event, this.locationNameBgArr);
  608. if (this.detailModalCSS2Obj && !this.detailModalCSS2Obj.visible && intersects.length > 0 && this.detailModalCSS2Obj) {
  609. const intersectObj = intersects[0].object as THREE.Mesh;
  610. const point = intersects[0].point;
  611. this.detailModalCSS2Obj.position.set(point.x, point.y, point.z);
  612. this.detailModalCSS2Obj.applyMatrix4(this.scene.matrix);
  613. if (intersectObj['userData'] && intersectObj['userData']['code']) {
  614. const textMesh = this.locationCylinderGroup.getObjectByName(intersectObj['userData']['code']) as THREE.Mesh;
  615. if (textMesh) {
  616. textMesh.material = this.fontMaterial1;
  617. textMesh.scale.set(1, 1, 1);
  618. }
  619. }
  620. }
  621. };
  622. // 点击地图事件
  623. const onClick = (event: Event) => {
  624. event.preventDefault();
  625. eventAction(event, [...this.provinceArr, ...this.locationNameBgArr]);
  626. if (new Date().getTime() - this.playerStartClickTime < 400) {
  627. // 双击
  628. if (intersects.length > 0) {
  629. const point = intersects[0].point;
  630. const boxMaxY = new THREE.Box3().setFromObject(intersects[0].object).max.y;
  631. this.mapClickAnimation(point, boxMaxY);
  632. }
  633. } else {
  634. if (intersects.length > 0 && this.detailModalCSS2Obj) {
  635. const detailMesh = intersects.find((item) => item?.object['userData'] && item.object['userData']['code']);
  636. if (detailMesh) {
  637. // 发送消息
  638. this.detailModalCSS2Obj.visible = true;
  639. }
  640. }
  641. }
  642. console.log(this.camera, this.controller);
  643. this.playerStartClickTime = new Date().getTime();
  644. };
  645. window.addEventListener('mousemove', onMouseMove, false);
  646. window.addEventListener('click', onClick, false);
  647. }
  648. mapClickAnimation(pos, boxMaxY) {
  649. const _self = this;
  650. const distance = boxMaxY;
  651. const angel = 90;
  652. if (!this.controller || !this.camera) return;
  653. //关闭控制器
  654. this.controller.enabled = false;
  655. const begin = {
  656. x: this.camera.position.x,
  657. y: this.camera.position.y,
  658. z: this.camera.position.z,
  659. x1: this.controller.target.x,
  660. y1: this.controller.target.y,
  661. z1: this.controller.target.z,
  662. };
  663. const end = {
  664. x: pos.x,
  665. y: pos.y + Math.cos(angel) * distance,
  666. z: pos.z + Math.sin(angel) * distance,
  667. x1: pos.x,
  668. y1: pos.y,
  669. z1: pos.z,
  670. };
  671. gsap.fromTo(
  672. begin,
  673. { ...begin },
  674. {
  675. ...end,
  676. time: 500,
  677. onUpdate(obj) {
  678. if (_self.camera && _self.controller) {
  679. _self.camera.position.x = obj.x;
  680. _self.camera.position.y = obj.y;
  681. _self.camera.position.z = obj.z;
  682. _self.controller.target.set(obj.x1, obj.y1, obj.z1);
  683. // 控制器更新
  684. _self.controller.update();
  685. }
  686. },
  687. onUpdateParams: [begin],
  688. onComplete() {
  689. if (_self.controller) _self.controller.enabled = true;
  690. },
  691. }
  692. );
  693. }
  694. resetCameraTween() {
  695. // eslint-disable-next-line @typescript-eslint/no-this-alias
  696. const self = this;
  697. if (this.controller && this.camera && this.cameraPosArr) {
  698. //关闭控制器
  699. this.controller.enabled = false;
  700. const begin = {
  701. x: this.camera.position.x,
  702. y: this.camera.position.y,
  703. z: this.camera.position.z,
  704. x1: this.controller.target.x,
  705. y1: this.controller.target.y,
  706. z1: this.controller.target.z,
  707. };
  708. const end = {
  709. x: 0.20986936323918032,
  710. y: -0.7192585185669363,
  711. z: 1.3178315065115611,
  712. x1: 0.1943010027503315,
  713. y1: 0.6831035409655225,
  714. z1: -0.551271738125278,
  715. };
  716. gsap.fromTo(
  717. begin,
  718. { ...begin },
  719. {
  720. ...end,
  721. duration: 1,
  722. ease: 'easeOutBounce',
  723. onUpdate(obj) {
  724. self.camera.position.x = obj.x;
  725. self.camera.position.y = obj.y;
  726. self.camera.position.z = obj.z;
  727. self.controller.target.x = obj.x1;
  728. self.controller.target.y = obj.y1;
  729. self.controller.target.z = obj.z1;
  730. // self.camera.lookAt(0, 0, 0);
  731. // 控制器更新
  732. self.controller.update();
  733. },
  734. onUpdateParams: [begin],
  735. onComplete() {
  736. self.controller.enabled = true;
  737. },
  738. }
  739. );
  740. }
  741. }
  742. createSideShaderMaterial(material) {
  743. material.onBeforeCompile = function (shader) {
  744. // console.log(shader.fragmentShader);
  745. shader.vertexShader = shader.vertexShader.replace(
  746. `void main() {`,
  747. `
  748. varying vec4 vPosition;
  749. void main() {
  750. `
  751. );
  752. shader.vertexShader = shader.vertexShader.replace(
  753. `#include <fog_vertex>`,
  754. `#include <fog_vertex>
  755. vPosition=modelMatrix * vec4( transformed, 1.0 );
  756. `
  757. );
  758. shader.fragmentShader = shader.fragmentShader.replace(
  759. `void main() {`,
  760. `varying vec4 vPosition;
  761. void main() {`
  762. );
  763. shader.fragmentShader = shader.fragmentShader.replace(
  764. `#include <specularmap_fragment>`,
  765. `
  766. #include <specularmap_fragment>
  767. float z = vPosition.z;
  768. float s = step(5.0,z);
  769. vec3 bottomColor = vec3(.0,1.0,1.0);
  770. diffuseColor.rgb = mix(bottomColor,diffuseColor.rgb,s);
  771. float r = abs(z * (1.0 - s * 2.0) + s * 4.0) ;
  772. diffuseColor.rgb *= pow(r, 0.5 + 2.0 * s);
  773. `
  774. );
  775. };
  776. return material;
  777. }
  778. setMaterial() {
  779. const locationShape = this.map.getObjectByName('locationShape');
  780. if (!locationShape) return;
  781. locationShape.geometry.computeBoundingBox();
  782. const { max, min } = locationShape.geometry.boundingBox;
  783. const distance = max.y - min.y + 2;
  784. locationShape.material[0].onBeforeCompile = (shader) => {
  785. shader.uniforms.uDistance = {
  786. value: distance,
  787. };
  788. shader.uniforms.uTopColor = {
  789. value: new THREE.Color(0xaaaeff),
  790. };
  791. this.addGrad(shader);
  792. };
  793. }
  794. addGrad = (shader) => {
  795. shader.vertexShader = shader.vertexShader.replace(
  796. '#include <common>',
  797. `
  798. varying vec3 vPosition;
  799. #include <common>
  800. `
  801. );
  802. shader.vertexShader = shader.vertexShader.replace(
  803. '#include <fog_vertex>',
  804. `
  805. #include <fog_vertex>
  806. vPosition = position;
  807. `
  808. );
  809. shader.fragmentShader = shader.fragmentShader.replace(
  810. '#include <common>',
  811. `
  812. uniform vec3 uTopColor;
  813. uniform float uDistance;
  814. varying vec3 vPosition;
  815. #include <common>
  816. `
  817. );
  818. shader.fragmentShader = shader.fragmentShader.replace(
  819. '#include <dithering_fragment>',
  820. `
  821. #include <dithering_fragment>
  822. vec4 distGradColor = gl_FragColor;
  823. // 设置混合的百分比
  824. float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0;
  825. // 设置混合颜色
  826. vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix);
  827. gl_FragColor = vec4(colorMix, 1.0);
  828. `
  829. );
  830. };
  831. // 动画 - 城市边缘流光
  832. animationCityEdgeLight() {
  833. if (this.mapEdgeLightObj.lightOpacitys && this.mapEdgeLightObj.mapEdgePoints) {
  834. if (this.mapEdgeLightObj.lightCurrentPos > this.mapEdgeLightObj.mapEdgePoints.length) {
  835. this.mapEdgeLightObj.lightCurrentPos = 0;
  836. }
  837. this.mapEdgeLightObj.lightCurrentPos += this.mapEdgeLightObj.lightSpeed;
  838. for (let i = 0; i < this.mapEdgeLightObj.lightSpeed; i++) {
  839. this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos - i) % this.mapEdgeLightObj.mapEdgePoints.length] = 0;
  840. }
  841. for (let i = 0; i < 100; i++) {
  842. this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos + i) % this.mapEdgeLightObj.mapEdgePoints.length] =
  843. i / 50 > 2 ? 2 : i / 50;
  844. }
  845. if (this.mapEdgeLightObj.lightOpacityGeometry) {
  846. this.mapEdgeLightObj.lightOpacityGeometry.attributes.aOpacity.needsUpdate = true;
  847. }
  848. }
  849. }
  850. // 动画
  851. animate() {
  852. requestAnimationFrame(this.animate.bind(this));
  853. this.animationCityEdgeLight();
  854. this.animationLocation();
  855. this.controller?.update();
  856. this.camera?.updateProjectionMatrix();
  857. this.renderer?.render(this.scene, this.camera);
  858. this.labelRenderer?.render(this.scene, this.camera);
  859. }
  860. }
  861. export default earthtMap;