lines.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /* ------------------------------------------------------------
  2. Copyright 2016 Esri
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at:
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. --------------------------------------------------------------- */
  13. define([
  14. 'esri/core/declare',
  15. 'esri/geometry/Point',
  16. 'esri/geometry/SpatialReference',
  17. 'esri/views/3d/externalRenderers',
  18. 'esri/geometry/support/webMercatorUtils',
  19. ], function(declare, Point, SpatialReference, externalRenderers, webMercatorUtils) {
  20. // Enforce strict mode
  21. 'use strict';
  22. // Constants
  23. var THREE = window.THREE;
  24. var RADIUS = 6378137;
  25. var OFFSET = 5000;
  26. // var COLOR = 0xffffff;
  27. var COLOR = 0xef6c00; //0x00ffff;
  28. var REST = 75; //ms
  29. // var REST = 200;
  30. const RandomColors = [
  31. // '#1A93D3',
  32. // '#B5C334',
  33. // '#C1232B',
  34. // '#E87C25',
  35. // '#27727B',
  36. // '#FE8463',
  37. // '#9BCA63',
  38. // '#FAD860',
  39. // '#F3A43B',
  40. // '#60C0DD',
  41. // '#D7504B',
  42. // '#C6E579',
  43. // '#F4E001',
  44. // '#F0805A',
  45. // '#26C0C0',
  46. // '#c23531',
  47. '#03f0ff',
  48. '#03f0ff',
  49. // '#2f4554',
  50. // '#61a0a8',
  51. // '#d48265',
  52. // '#91c7ae',
  53. // '#749f83',
  54. // '#ca8622',
  55. // '#bda29a',
  56. // '#6e7074',
  57. // '#546570',
  58. // '#c4ccd3',
  59. ];
  60. var colorindex = 0;
  61. function getColor() {
  62. if (colorindex === 0) {
  63. colorindex = 1;
  64. } else {
  65. colorindex = 0;
  66. }
  67. return RandomColors[colorindex].replace('#', '0x');
  68. }
  69. return declare([], {
  70. constructor: function(view, tracks) {
  71. this.view = view;
  72. this.tracks = tracks;
  73. this.index = 0;
  74. this.max = 0;
  75. this.meshLines = [];
  76. this.refresh = Date.now();
  77. },
  78. setup: function(context) {
  79. // Create the THREE.js webgl renderer
  80. this.renderer = new THREE.WebGLRenderer({
  81. context: context.gl,
  82. premultipliedAlpha: false,
  83. });
  84. this.renderer.setPixelRatio(window.devicePixelRatio);
  85. this.renderer.setViewport(0, 0, this.view.width, this.view.height);
  86. // Make sure it does not clear anything before rendering
  87. this.renderer.autoClear = false;
  88. this.renderer.autoClearDepth = false;
  89. this.renderer.autoClearColor = false;
  90. this.renderer.autoClearStencil = false;
  91. // The ArcGIS JS API renders to custom offscreen buffers, and not to the default framebuffers.
  92. // We have to inject this bit of code into the three.js runtime in order for it to bind those
  93. // buffers instead of the default ones.
  94. var originalSetRenderTarget = this.renderer.setRenderTarget.bind(this.renderer);
  95. this.renderer.setRenderTarget = function(target) {
  96. originalSetRenderTarget(target);
  97. if (target == null) {
  98. context.bindRenderTarget();
  99. }
  100. };
  101. this.scene = new THREE.Scene();
  102. // setup the camera
  103. this.camera = new THREE.PerspectiveCamera();
  104. // Create lights that will resemble the sun light lighting that we do internally
  105. this._createLights();
  106. // Create objects and add them to the scene
  107. this._createObjects();
  108. // Set starting geometries
  109. this._updateObjects();
  110. // cleanup after ourselfs
  111. context.resetWebGLState();
  112. },
  113. render: function(context) {
  114. // Make sure to reset the internal THREE.js state
  115. this.renderer.resetGLState();
  116. // Update the THREE.js camera so it's synchronized to our camera
  117. this._updateCamera(context);
  118. // Update the THREE.js lights so it's synchronized to our light
  119. this._updateLights(context);
  120. // Advance current geometry
  121. this._updateObjects(context);
  122. // Render the scene
  123. this.renderer.render(this.scene, this.camera);
  124. // Immediately request a new redraw
  125. externalRenderers.requestRender(this.view);
  126. // cleanup
  127. context.resetWebGLState();
  128. },
  129. dispose: function(content) {},
  130. _createObjects: function() {
  131. //
  132. var scope = this;
  133. var transform = new THREE.Matrix4();
  134. scope.tracks.forEach(function(track) {
  135. // Smooth and densify line
  136. var curve = new THREE.SplineCurve(
  137. track.map(function(e) {
  138. const lonlat = webMercatorUtils.xyToLngLat(e[0], e[1]);
  139. return new THREE.Vector2(lonlat[0], lonlat[1]);
  140. })
  141. );
  142. // var smooth = curve.getSpacedPoints(curve.points.length * 2);
  143. var smooth = curve.getSpacedPoints(35);
  144. // Convert vectors to Esri webgl cartesian
  145. var smooth3d = smooth.map(function(e) {
  146. // // Convert lat/long to radians
  147. // var lon = (e.x * Math.PI) / 180 - Math.PI;
  148. // var lat = (e.y * Math.PI) / 180 - Math.PI / 2;
  149. // // Create vector to current
  150. // var q = new THREE.Vector3(RADIUS + OFFSET, 0, 0);
  151. // q.applyAxisAngle(new THREE.Vector3(0, 1, 0), lat);
  152. // q.applyAxisAngle(new THREE.Vector3(0, 0, 1), lon);
  153. // // Return vector
  154. // return new THREE.Vector3(q.x, q.y, q.z);
  155. const mercatorPoint = webMercatorUtils.geographicToWebMercator(new Point({
  156. x: e.x,
  157. y: e.y,
  158. z: 10
  159. }));
  160. transform.fromArray(
  161. externalRenderers.renderCoordinateTransformAt(
  162. scope.view,
  163. [mercatorPoint.x, mercatorPoint.y, mercatorPoint.z],
  164. SpatialReference.WebMercator,
  165. new Array(16)
  166. )
  167. );
  168. return new THREE.Vector3(
  169. transform.elements[12],
  170. transform.elements[13],
  171. transform.elements[14]
  172. );
  173. });
  174. // Get the length of the longest track
  175. scope.max = Math.max(scope.max, smooth3d.length);
  176. // Create a random offset. Used to stagger animations.
  177. var offset = Math.floor(Math.random() * (smooth3d.length - 1));
  178. var color1 = Number(getColor());
  179. var resolution = new THREE.Vector2(window.innerWidth, window.innerHeight);
  180. for (var i = 0; i < smooth3d.length - 1; i++) {
  181. // Create line geometry
  182. var geometry = new THREE.Geometry();
  183. geometry.vertices = [smooth3d[i], smooth3d[i + 1]];
  184. // Create line material
  185. // var material = new THREE.LineBasicMaterial({
  186. // color: COLOR,
  187. // opacity: 0,
  188. // transparent: true
  189. // });
  190. // Create line.
  191. // var line = new THREE.Line(geometry, material);
  192. // line.visible = false;
  193. // line.flag = i;
  194. // line.offset = offset;
  195. var g = new MeshLine();
  196. g.setGeometry(geometry);
  197. var line = new window.THREE.Mesh(
  198. g.geometry,
  199. new MeshLineMaterial({
  200. useMap: false,
  201. color: new THREE.Color(color1),
  202. opacity: 0,
  203. transparent: true,
  204. dashArray: 0,
  205. resolution: resolution,
  206. sizeAttenuation: false,
  207. lineWidth: 15, //10
  208. near: scope.camera.near,
  209. far: scope.camera.far,
  210. })
  211. );
  212. line.visible = false;
  213. line.flag = i;
  214. line.offset = offset;
  215. // Add line
  216. scope.scene.add(line);
  217. scope.meshLines.push(line);
  218. }
  219. });
  220. // Clear tracks array
  221. this.tracks = null;
  222. },
  223. _createLights: function() {
  224. // setup scene lighting
  225. this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
  226. this.scene.add(this.ambient);
  227. this.sun = new THREE.DirectionalLight(0xffffff, 0.5);
  228. this.scene.add(this.sun);
  229. },
  230. _updateCamera: function(context) {
  231. // update camera parameters
  232. ///////////////////////////////////////////////////////////////////////////////////
  233. var cam = context.camera;
  234. this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
  235. this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
  236. this.camera.lookAt(new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2]));
  237. // Projection matrix can be copied directly
  238. this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
  239. },
  240. _updateLights: function(context) {
  241. var l = context.sunLight;
  242. this.sun.position.set(l.direction[0], l.direction[1], l.direction[2]);
  243. this.sun.intensity = l.diffuse.intensity;
  244. this.sun.color = new THREE.Color(l.diffuse.color[0], l.diffuse.color[1], l.diffuse.color[2]);
  245. this.ambient.intensity = l.ambient.intensity;
  246. this.ambient.color = new THREE.Color(
  247. l.ambient.color[0],
  248. l.ambient.color[1],
  249. l.ambient.color[2]
  250. );
  251. },
  252. _updateObjects: function(context) {
  253. // Only update shapes every ~75ms
  254. var now = Date.now();
  255. if (now - this.refresh < REST) {
  256. return;
  257. }
  258. this.refresh = now;
  259. // Loop for every shape
  260. var scope = this;
  261. this.meshLines.forEach(function(e) {
  262. // Ignore other shapes
  263. // Create a new offset index
  264. var index = scope.index + e.offset;
  265. if (index > scope.max) {
  266. index -= scope.max;
  267. }
  268. // Show or hide a line segment
  269. if (e.flag - index >= 0 && e.flag - index <= 10) {
  270. // Slowly fade in a new line.
  271. var fade = 1;
  272. switch (index) {
  273. case 0:
  274. fade = 0.1;
  275. break;
  276. case 1:
  277. fade = 0.2;
  278. break;
  279. case 2:
  280. fade = 0.5;
  281. break;
  282. }
  283. // if (index > 0 && index < 1) {
  284. // fade = 0.1;
  285. // } else if (index > 1 && index < 2) {
  286. // fade = 0.2;
  287. // } else if (index > 2 && index < 3) {
  288. // fade = 0.5;
  289. // }
  290. // Show the line segment.
  291. e.material.opacity = (fade * (e.flag - index)) / 10;
  292. e.visible = true;
  293. } else {
  294. // Hide the line segment.
  295. e.visible = false;
  296. e.material.opacity = 0;
  297. }
  298. });
  299. // Increment the drawing index
  300. this.index++;
  301. // this.index += 0.2;
  302. if (this.index >= this.max) {
  303. this.index = 0;
  304. }
  305. },
  306. });
  307. });