3dMap.ts 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  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. /** 为 tooltip 设置标题,该标题的设置过程依赖 ../dialog-modal 中的结构 */
  430. setTooltipTitle(title: string) {
  431. const obj = this.detailModalCSS2Obj as CSS2DObject;
  432. const ele = obj.element.getElementsByClassName('modal-title')[0];
  433. if (!ele) return;
  434. ele.innerHTML = title;
  435. }
  436. async setTag(locationInfo: any[]) {
  437. const loader = new FontLoader();
  438. const font = await loader.loadAsync('/font/STSong_Regular.json');
  439. const locationTexture = new THREE.TextureLoader().load('/texture/location4.png');
  440. const colors = gradientColors('#FFDE00', '#F500FF', locationInfo.length, 1);
  441. locationInfo.forEach((item, index) => {
  442. const locationGroup = new THREE.Group();
  443. const [x, y] = this.projection(item['value']);
  444. const geometry = new THREE.CylinderGeometry(0.1, 0.0, 0.3, 3);
  445. if (Math.random() * 10 > 5) {
  446. const material = new THREE.MeshPhongMaterial({
  447. // ShaderMaterial
  448. // color: colors[index],
  449. color: 0xff1f00,
  450. side: THREE.DoubleSide,
  451. // blending: THREE.AdditiveBlending,
  452. transparent: true,
  453. opacity: 0.8,
  454. });
  455. const cylinder = new THREE.Mesh(geometry, material);
  456. cylinder.position.set(x, -y, this.mapConfig.deep + 0.08);
  457. cylinder.rotateX(Math.PI / 2);
  458. cylinder.scale.set(0.4, 0.4, 0.4);
  459. cylinder['userData']['code'] = item['code'];
  460. this.locationCylinderMeshArr.push(cylinder);
  461. locationGroup.add(cylinder);
  462. }
  463. const circleGeometry = new THREE.PlaneGeometry(0.06, 0.06);
  464. const circleMaterial = new THREE.MeshBasicMaterial({ map: locationTexture, side: THREE.DoubleSide, transparent: true });
  465. const plane = new THREE.Mesh(circleGeometry, circleMaterial);
  466. // plane['userData']['code'] = item['code'];
  467. plane.position.set(x, -y, this.mapConfig.deep + 0.02);
  468. plane.renderOrder = 999;
  469. locationGroup.add(plane);
  470. // 地名
  471. const textGeometry = new TextGeometry(item['name'], {
  472. font: font,
  473. size: 0.05, //字体大小
  474. height: 0.005, //字体高度curveSegments: 12,
  475. bevelEnabled: true,
  476. bevelSize: 0.001,
  477. bevelOffset: 0,
  478. bevelSegments: 0,
  479. });
  480. const textMesh = new THREE.Mesh(textGeometry, this.fontMaterial0);
  481. textMesh.name = item['code'];
  482. textMesh.scale.set(0.5, 0.5, 0.5);
  483. textMesh.position.set(x - 0.1, -y + 0.01, this.mapConfig.deep + 0.035);
  484. textMesh.rotateX(-Math.PI / 3);
  485. textMesh.rotateZ(-Math.PI);
  486. this.locationNameArr.push(textMesh);
  487. locationGroup.add(textMesh);
  488. textGeometry.computeBoundingBox();
  489. const { max, min } = textGeometry.boundingBox;
  490. const boxGeometry = new THREE.PlaneGeometry(Math.abs(max.x - min.x), Math.abs(max.y - min.y));
  491. const boxMesh = new THREE.Mesh(boxGeometry, this.fontMaterial2);
  492. boxMesh.rotateX(-Math.PI / 3);
  493. boxMesh.rotateZ(-Math.PI);
  494. boxMesh.position.set(x - Math.abs(max.x - min.x) + 0.018, -y + 0.01, this.mapConfig.deep + 0.03);
  495. boxMesh.renderOrder = 1;
  496. boxMesh['userData'] = item;
  497. this.locationNameBgArr.push(boxMesh);
  498. locationGroup.add(boxMesh);
  499. this.locationCylinderGroup.add(locationGroup);
  500. this.scene?.add(this.locationCylinderGroup);
  501. });
  502. }
  503. animationLocation() {
  504. this.locationCylinderMeshArr.forEach((mesh) => {
  505. // console.log(mesh);
  506. mesh.rotation.y -= 0.05;
  507. });
  508. }
  509. makeTextSprite(message, parameters) {
  510. if (parameters === undefined) parameters = {};
  511. const fontface = parameters['fontface'];
  512. const fontsize = parameters['fontsize'];
  513. const fontColor = parameters['fontColor'];
  514. const borderThickness = parameters['borderThickness'];
  515. const borderColor = parameters['borderColor'];
  516. const backgroundColor = parameters['backgroundColor'];
  517. // var spriteAlignment = THREE.SpriteAlignment.topLeft;
  518. const canvas = document.createElement('canvas');
  519. const context = <CanvasRenderingContext2D>canvas.getContext('2d');
  520. context.font = 'Bold ' + fontsize + 'px ' + fontface;
  521. // get size data (height depends only on font size)
  522. const metrics = context?.measureText(message);
  523. const textWidth = metrics?.width;
  524. // background color
  525. context.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')';
  526. // border color
  527. context.strokeStyle = 'rgba(' + borderColor.r + ',' + borderColor.g + ',' + borderColor.b + ',' + borderColor.a + ')';
  528. context.lineWidth = borderThickness;
  529. // const painting = {
  530. // width: textWidth * 1.4 + borderThickness * 2,
  531. // height: fontsize * 1.4 + borderThickness * 2,
  532. // round: parameters['round'],
  533. // };
  534. const painting = {
  535. width: textWidth,
  536. height: fontsize * 1.4 + borderThickness * 2,
  537. round: parameters['round'],
  538. };
  539. // 1.4 is extra height factor for text below baseline: g,j,p,q.
  540. // context.fillRect(0, 0, painting.width, painting.height)
  541. this.roundRect(context, borderThickness / 2, borderThickness / 2, painting.width, painting.height, painting.round);
  542. // text color
  543. context.fillStyle = 'rgba(' + fontColor.r + ',' + fontColor.g + ',' + fontColor.b + ',' + fontColor.a + ')';
  544. context.textAlign = 'center';
  545. context.textBaseline = 'middle';
  546. context.fillText(message, painting.width / 2, painting.height / 2);
  547. // canvas contents will be used for a texture
  548. const texture = new THREE.Texture(canvas);
  549. texture.needsUpdate = true;
  550. const spriteMaterial = new THREE.SpriteMaterial({
  551. map: texture,
  552. depthTest: false, // 解决精灵谍影问题
  553. // blending: THREE.AdditiveBlending,
  554. // transparent: true,
  555. // alignment: spriteAlignment
  556. });
  557. const sprite = new THREE.Sprite(spriteMaterial);
  558. // sprite.scale.set(1, 1, 1);
  559. return sprite;
  560. }
  561. // 城市 - 名称显示 - 样式
  562. roundRect(ctx, x, y, w, h, r) {
  563. ctx.beginPath();
  564. ctx.moveTo(x + r, y);
  565. ctx.lineTo(x + w - r, y);
  566. ctx.quadraticCurveTo(x + w, y, x + w, y + r);
  567. ctx.lineTo(x + w, y + h - r);
  568. ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
  569. ctx.lineTo(x + r, y + h);
  570. ctx.quadraticCurveTo(x, y + h, x, y + h - r);
  571. ctx.lineTo(x, y + r);
  572. ctx.quadraticCurveTo(x, y, x + r, y);
  573. ctx.closePath();
  574. ctx.fill();
  575. ctx.stroke();
  576. }
  577. // 地球贴图纹理
  578. setEarth() {
  579. const geometry = new THREE.PlaneGeometry(15.0, 15.0);
  580. const texture = new THREE.TextureLoader().load('/texture/earth.jpg');
  581. const bumpTexture = new THREE.TextureLoader().load('/texture/earth.jpg');
  582. texture.encoding = THREE.sRGBEncoding;
  583. const material = new THREE.MeshPhongMaterial({
  584. map: texture, // 贴图
  585. bumpMap: bumpTexture,
  586. // normalMap: bumpTexture,
  587. bumpScale: 1,
  588. // specularMap: bumpTexture,
  589. // specular: 0xffffff,
  590. // shininess: 1,
  591. // color: '#000000',
  592. side: THREE.FrontSide,
  593. });
  594. const earthPlane = new THREE.Mesh(geometry, material);
  595. this.scene?.add(earthPlane);
  596. }
  597. // 射线
  598. setRaycaster() {
  599. const _self = this;
  600. let intersects = <any[]>[];
  601. const eventAction = (event, object3DArr) => {
  602. this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  603. this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  604. (this.raycaster as THREE.Raycaster).setFromCamera(this.mouse, this.camera as THREE.Camera);
  605. intersects = this.raycaster?.intersectObjects(object3DArr, true) as THREE.Intersection[];
  606. };
  607. const onMouseMove = (event) => {
  608. this.locationNameArr.forEach((item: THREE.Mesh) => {
  609. if (item.material == this.fontMaterial1) {
  610. item.material = this.fontMaterial0;
  611. item.scale.set(0.5, 0.5, 0.5);
  612. }
  613. });
  614. eventAction(event, this.locationNameBgArr);
  615. if (this.detailModalCSS2Obj && !this.detailModalCSS2Obj.visible && intersects.length > 0 && this.detailModalCSS2Obj) {
  616. const intersectObj = intersects[0].object as THREE.Mesh;
  617. const point = intersects[0].point;
  618. this.detailModalCSS2Obj.position.set(point.x, point.y, point.z);
  619. this.detailModalCSS2Obj.applyMatrix4(this.scene.matrix);
  620. if (intersectObj['userData'] && intersectObj['userData']['code']) {
  621. const textMesh = this.locationCylinderGroup.getObjectByName(intersectObj['userData']['code']) as THREE.Mesh;
  622. if (textMesh) {
  623. textMesh.material = this.fontMaterial1;
  624. textMesh.scale.set(1, 1, 1);
  625. }
  626. }
  627. }
  628. };
  629. // 点击地图事件
  630. const onClick = (event: Event) => {
  631. event.preventDefault();
  632. eventAction(event, [...this.provinceArr, ...this.locationNameBgArr]);
  633. if (new Date().getTime() - this.playerStartClickTime < 400) {
  634. // 双击
  635. if (intersects.length > 0) {
  636. const point = intersects[0].point;
  637. const boxMaxY = new THREE.Box3().setFromObject(intersects[0].object).max.y;
  638. this.mapClickAnimation(point, boxMaxY);
  639. }
  640. } else {
  641. if (intersects.length > 0 && this.detailModalCSS2Obj) {
  642. const detailMesh = intersects.find((item) => item?.object['userData'] && item.object['userData']['code']);
  643. if (detailMesh) {
  644. // 发送消息
  645. this.detailModalCSS2Obj.visible = true;
  646. this.setTooltipTitle(detailMesh.object.userData.name);
  647. }
  648. }
  649. }
  650. console.log(this.camera, this.controller);
  651. this.playerStartClickTime = new Date().getTime();
  652. };
  653. window.addEventListener('mousemove', onMouseMove, false);
  654. window.addEventListener('click', onClick, false);
  655. }
  656. mapClickAnimation(pos, boxMaxY) {
  657. const _self = this;
  658. const distance = boxMaxY;
  659. const angel = 90;
  660. if (!this.controller || !this.camera) return;
  661. //关闭控制器
  662. this.controller.enabled = false;
  663. const begin = {
  664. x: this.camera.position.x,
  665. y: this.camera.position.y,
  666. z: this.camera.position.z,
  667. x1: this.controller.target.x,
  668. y1: this.controller.target.y,
  669. z1: this.controller.target.z,
  670. };
  671. const end = {
  672. x: pos.x,
  673. y: pos.y + Math.cos(angel) * distance,
  674. z: pos.z + Math.sin(angel) * distance,
  675. x1: pos.x,
  676. y1: pos.y,
  677. z1: pos.z,
  678. };
  679. gsap.fromTo(
  680. begin,
  681. { ...begin },
  682. {
  683. ...end,
  684. time: 500,
  685. onUpdate(obj) {
  686. if (_self.camera && _self.controller) {
  687. _self.camera.position.x = obj.x;
  688. _self.camera.position.y = obj.y;
  689. _self.camera.position.z = obj.z;
  690. _self.controller.target.set(obj.x1, obj.y1, obj.z1);
  691. // 控制器更新
  692. _self.controller.update();
  693. }
  694. },
  695. onUpdateParams: [begin],
  696. onComplete() {
  697. if (_self.controller) _self.controller.enabled = true;
  698. },
  699. }
  700. );
  701. }
  702. resetCameraTween() {
  703. // eslint-disable-next-line @typescript-eslint/no-this-alias
  704. const self = this;
  705. if (this.controller && this.camera && this.cameraPosArr) {
  706. //关闭控制器
  707. this.controller.enabled = false;
  708. const begin = {
  709. x: this.camera.position.x,
  710. y: this.camera.position.y,
  711. z: this.camera.position.z,
  712. x1: this.controller.target.x,
  713. y1: this.controller.target.y,
  714. z1: this.controller.target.z,
  715. };
  716. const end = {
  717. x: 0.20986936323918032,
  718. y: -0.7192585185669363,
  719. z: 1.3178315065115611,
  720. x1: 0.1943010027503315,
  721. y1: 0.6831035409655225,
  722. z1: -0.551271738125278,
  723. };
  724. gsap.fromTo(
  725. begin,
  726. { ...begin },
  727. {
  728. ...end,
  729. duration: 1,
  730. ease: 'easeOutBounce',
  731. onUpdate(obj) {
  732. self.camera.position.x = obj.x;
  733. self.camera.position.y = obj.y;
  734. self.camera.position.z = obj.z;
  735. self.controller.target.x = obj.x1;
  736. self.controller.target.y = obj.y1;
  737. self.controller.target.z = obj.z1;
  738. // self.camera.lookAt(0, 0, 0);
  739. // 控制器更新
  740. self.controller.update();
  741. },
  742. onUpdateParams: [begin],
  743. onComplete() {
  744. self.controller.enabled = true;
  745. },
  746. }
  747. );
  748. }
  749. }
  750. createSideShaderMaterial(material) {
  751. material.onBeforeCompile = function (shader) {
  752. // console.log(shader.fragmentShader);
  753. shader.vertexShader = shader.vertexShader.replace(
  754. `void main() {`,
  755. `
  756. varying vec4 vPosition;
  757. void main() {
  758. `
  759. );
  760. shader.vertexShader = shader.vertexShader.replace(
  761. `#include <fog_vertex>`,
  762. `#include <fog_vertex>
  763. vPosition=modelMatrix * vec4( transformed, 1.0 );
  764. `
  765. );
  766. shader.fragmentShader = shader.fragmentShader.replace(
  767. `void main() {`,
  768. `varying vec4 vPosition;
  769. void main() {`
  770. );
  771. shader.fragmentShader = shader.fragmentShader.replace(
  772. `#include <specularmap_fragment>`,
  773. `
  774. #include <specularmap_fragment>
  775. float z = vPosition.z;
  776. float s = step(5.0,z);
  777. vec3 bottomColor = vec3(.0,1.0,1.0);
  778. diffuseColor.rgb = mix(bottomColor,diffuseColor.rgb,s);
  779. float r = abs(z * (1.0 - s * 2.0) + s * 4.0) ;
  780. diffuseColor.rgb *= pow(r, 0.5 + 2.0 * s);
  781. `
  782. );
  783. };
  784. return material;
  785. }
  786. setMaterial() {
  787. const locationShape = this.map.getObjectByName('locationShape');
  788. if (!locationShape) return;
  789. locationShape.geometry.computeBoundingBox();
  790. const { max, min } = locationShape.geometry.boundingBox;
  791. const distance = max.y - min.y + 2;
  792. locationShape.material[0].onBeforeCompile = (shader) => {
  793. shader.uniforms.uDistance = {
  794. value: distance,
  795. };
  796. shader.uniforms.uTopColor = {
  797. value: new THREE.Color(0xaaaeff),
  798. };
  799. this.addGrad(shader);
  800. };
  801. }
  802. addGrad = (shader) => {
  803. shader.vertexShader = shader.vertexShader.replace(
  804. '#include <common>',
  805. `
  806. varying vec3 vPosition;
  807. #include <common>
  808. `
  809. );
  810. shader.vertexShader = shader.vertexShader.replace(
  811. '#include <fog_vertex>',
  812. `
  813. #include <fog_vertex>
  814. vPosition = position;
  815. `
  816. );
  817. shader.fragmentShader = shader.fragmentShader.replace(
  818. '#include <common>',
  819. `
  820. uniform vec3 uTopColor;
  821. uniform float uDistance;
  822. varying vec3 vPosition;
  823. #include <common>
  824. `
  825. );
  826. shader.fragmentShader = shader.fragmentShader.replace(
  827. '#include <dithering_fragment>',
  828. `
  829. #include <dithering_fragment>
  830. vec4 distGradColor = gl_FragColor;
  831. // 设置混合的百分比
  832. float gradMix = (vPosition.y + (uDistance / 2.0)) / uDistance / 5.0;
  833. // 设置混合颜色
  834. vec3 colorMix = mix(distGradColor.xyz, uTopColor, gradMix);
  835. gl_FragColor = vec4(colorMix, 1.0);
  836. `
  837. );
  838. };
  839. // 动画 - 城市边缘流光
  840. animationCityEdgeLight() {
  841. if (this.mapEdgeLightObj.lightOpacitys && this.mapEdgeLightObj.mapEdgePoints) {
  842. if (this.mapEdgeLightObj.lightCurrentPos > this.mapEdgeLightObj.mapEdgePoints.length) {
  843. this.mapEdgeLightObj.lightCurrentPos = 0;
  844. }
  845. this.mapEdgeLightObj.lightCurrentPos += this.mapEdgeLightObj.lightSpeed;
  846. for (let i = 0; i < this.mapEdgeLightObj.lightSpeed; i++) {
  847. this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos - i) % this.mapEdgeLightObj.mapEdgePoints.length] = 0;
  848. }
  849. for (let i = 0; i < 100; i++) {
  850. this.mapEdgeLightObj.lightOpacitys[(this.mapEdgeLightObj.lightCurrentPos + i) % this.mapEdgeLightObj.mapEdgePoints.length] =
  851. i / 50 > 2 ? 2 : i / 50;
  852. }
  853. if (this.mapEdgeLightObj.lightOpacityGeometry) {
  854. this.mapEdgeLightObj.lightOpacityGeometry.attributes.aOpacity.needsUpdate = true;
  855. }
  856. }
  857. }
  858. // 动画
  859. animate() {
  860. requestAnimationFrame(this.animate.bind(this));
  861. this.animationCityEdgeLight();
  862. this.animationLocation();
  863. this.controller?.update();
  864. this.camera?.updateProjectionMatrix();
  865. this.renderer?.render(this.scene, this.camera);
  866. this.labelRenderer?.render(this.scene, this.camera);
  867. }
  868. }
  869. export default earthtMap;