webgl_geometry_extrude_shapes2.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - geometry - extrude shapes from geodata</title>
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  7. <style>
  8. body {
  9. font-family: Monospace;
  10. background-color: #f0f0f0;
  11. margin: 0px;
  12. overflow: hidden;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. <canvas id="debug" style="position:absolute; left:100px"></canvas>
  18. <script>
  19. 'use strict';
  20. </script>
  21. <script type="text/javascript" src="../build/three.min.js"></script>
  22. <script src="js/libs/stats.min.js"></script>
  23. <script>
  24. // From d3-threeD.js
  25. /* This Source Code Form is subject to the terms of the Mozilla Public
  26. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  27. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  28. function d3threeD(exports) {
  29. const DEGS_TO_RADS = Math.PI / 180, UNIT_SIZE = 100;
  30. const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, MINUS = 45;
  31. exports.transformSVGPath =
  32. function transformSVGPath(pathStr) {
  33. var path = new THREE.Shape();
  34. var idx = 1, len = pathStr.length, activeCmd,
  35. x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null,
  36. x1 = 0, x2 = 0, y1 = 0, y2 = 0,
  37. rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy;
  38. function eatNum() {
  39. var sidx, c, isFloat = false, s;
  40. // eat delims
  41. while (idx < len) {
  42. c = pathStr.charCodeAt(idx);
  43. if (c !== COMMA && c !== SPACE)
  44. break;
  45. idx++;
  46. }
  47. if (c === MINUS)
  48. sidx = idx++;
  49. else
  50. sidx = idx;
  51. // eat number
  52. while (idx < len) {
  53. c = pathStr.charCodeAt(idx);
  54. if (DIGIT_0 <= c && c <= DIGIT_9) {
  55. idx++;
  56. continue;
  57. }
  58. else if (c === PERIOD) {
  59. idx++;
  60. isFloat = true;
  61. continue;
  62. }
  63. s = pathStr.substring(sidx, idx);
  64. return isFloat ? parseFloat(s) : parseInt(s);
  65. }
  66. s = pathStr.substring(sidx);
  67. return isFloat ? parseFloat(s) : parseInt(s);
  68. }
  69. function nextIsNum() {
  70. var c;
  71. // do permanently eat any delims...
  72. while (idx < len) {
  73. c = pathStr.charCodeAt(idx);
  74. if (c !== COMMA && c !== SPACE)
  75. break;
  76. idx++;
  77. }
  78. c = pathStr.charCodeAt(idx);
  79. return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9));
  80. }
  81. var canRepeat;
  82. activeCmd = pathStr[0];
  83. while (idx <= len) {
  84. canRepeat = true;
  85. switch (activeCmd) {
  86. // moveto commands, become lineto's if repeated
  87. case 'M':
  88. x = eatNum();
  89. y = eatNum();
  90. path.moveTo(x, y);
  91. activeCmd = 'L';
  92. firstX = x;
  93. firstY = y;
  94. break;
  95. case 'm':
  96. x += eatNum();
  97. y += eatNum();
  98. path.moveTo(x, y);
  99. activeCmd = 'l';
  100. firstX = x;
  101. firstY = y;
  102. break;
  103. case 'Z':
  104. case 'z':
  105. canRepeat = false;
  106. if (x !== firstX || y !== firstY)
  107. path.lineTo(firstX, firstY);
  108. break;
  109. // - lines!
  110. case 'L':
  111. case 'H':
  112. case 'V':
  113. nx = (activeCmd === 'V') ? x : eatNum();
  114. ny = (activeCmd === 'H') ? y : eatNum();
  115. path.lineTo(nx, ny);
  116. x = nx;
  117. y = ny;
  118. break;
  119. case 'l':
  120. case 'h':
  121. case 'v':
  122. nx = (activeCmd === 'v') ? x : (x + eatNum());
  123. ny = (activeCmd === 'h') ? y : (y + eatNum());
  124. path.lineTo(nx, ny);
  125. x = nx;
  126. y = ny;
  127. break;
  128. // - cubic bezier
  129. case 'C':
  130. x1 = eatNum(); y1 = eatNum();
  131. case 'S':
  132. if (activeCmd === 'S') {
  133. x1 = 2 * x - x2; y1 = 2 * y - y2;
  134. }
  135. x2 = eatNum();
  136. y2 = eatNum();
  137. nx = eatNum();
  138. ny = eatNum();
  139. path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
  140. x = nx; y = ny;
  141. break;
  142. case 'c':
  143. x1 = x + eatNum();
  144. y1 = y + eatNum();
  145. case 's':
  146. if (activeCmd === 's') {
  147. x1 = 2 * x - x2;
  148. y1 = 2 * y - y2;
  149. }
  150. x2 = x + eatNum();
  151. y2 = y + eatNum();
  152. nx = x + eatNum();
  153. ny = y + eatNum();
  154. path.bezierCurveTo(x1, y1, x2, y2, nx, ny);
  155. x = nx; y = ny;
  156. break;
  157. // - quadratic bezier
  158. case 'Q':
  159. x1 = eatNum(); y1 = eatNum();
  160. case 'T':
  161. if (activeCmd === 'T') {
  162. x1 = 2 * x - x1;
  163. y1 = 2 * y - y1;
  164. }
  165. nx = eatNum();
  166. ny = eatNum();
  167. path.quadraticCurveTo(x1, y1, nx, ny);
  168. x = nx;
  169. y = ny;
  170. break;
  171. case 'q':
  172. x1 = x + eatNum();
  173. y1 = y + eatNum();
  174. case 't':
  175. if (activeCmd === 't') {
  176. x1 = 2 * x - x1;
  177. y1 = 2 * y - y1;
  178. }
  179. nx = x + eatNum();
  180. ny = y + eatNum();
  181. path.quadraticCurveTo(x1, y1, nx, ny);
  182. x = nx; y = ny;
  183. break;
  184. // - elliptical arc
  185. case 'A':
  186. rx = eatNum();
  187. ry = eatNum();
  188. xar = eatNum() * DEGS_TO_RADS;
  189. laf = eatNum();
  190. sf = eatNum();
  191. nx = eatNum();
  192. ny = eatNum();
  193. if (rx !== ry) {
  194. console.warn("Forcing elliptical arc to be a circular one :(",
  195. rx, ry);
  196. }
  197. // SVG implementation notes does all the math for us! woo!
  198. // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
  199. // step1, using x1 as x1'
  200. x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2;
  201. y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2;
  202. // step 2, using x2 as cx'
  203. var norm = Math.sqrt(
  204. (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) /
  205. (rx*rx * y1*y1 + ry*ry * x1*x1));
  206. if (laf === sf)
  207. norm = -norm;
  208. x2 = norm * rx * y1 / ry;
  209. y2 = norm * -ry * x1 / rx;
  210. // step 3
  211. cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2;
  212. cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2;
  213. var u = new THREE.Vector2(1, 0),
  214. v = new THREE.Vector2((x1 - x2) / rx,
  215. (y1 - y2) / ry);
  216. var startAng = Math.acos(u.dot(v) / u.length() / v.length());
  217. if (u.x * v.y - u.y * v.x < 0)
  218. startAng = -startAng;
  219. // we can reuse 'v' from start angle as our 'u' for delta angle
  220. u.x = (-x1 - x2) / rx;
  221. u.y = (-y1 - y2) / ry;
  222. var deltaAng = Math.acos(v.dot(u) / v.length() / u.length());
  223. // This normalization ends up making our curves fail to triangulate...
  224. if (v.x * u.y - v.y * u.x < 0)
  225. deltaAng = -deltaAng;
  226. if (!sf && deltaAng > 0)
  227. deltaAng -= Math.PI * 2;
  228. if (sf && deltaAng < 0)
  229. deltaAng += Math.PI * 2;
  230. path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf);
  231. x = nx;
  232. y = ny;
  233. break;
  234. default:
  235. throw new Error("weird path command: " + activeCmd);
  236. }
  237. // just reissue the command
  238. if (canRepeat && nextIsNum())
  239. continue;
  240. activeCmd = pathStr[idx++];
  241. }
  242. return path;
  243. }
  244. }
  245. var $d3g = {};
  246. d3threeD($d3g);
  247. </script>
  248. <script>
  249. /// Part from g0v/twgeojson
  250. /// Graphic Engine and Geo Data Init Functions
  251. var addGeoObject = function( group, svgObject ) {
  252. var i,j, len, len1;
  253. var path, mesh, color, material, amount, simpleShapes, simpleShape, shape3d, x, toAdd, results = [];
  254. var thePaths = svgObject.paths;
  255. var theAmounts = svgObject.amounts;
  256. var theColors = svgObject.colors;
  257. var theCenter = svgObject.center;
  258. len = thePaths.length;
  259. for (i = 0; i < len; ++i) {
  260. path = $d3g.transformSVGPath( thePaths[i] );
  261. color = new THREE.Color( theColors[i] );
  262. material = new THREE.MeshLambertMaterial({
  263. color: color,
  264. ambient: color,
  265. emissive: color,
  266. });
  267. amount = theAmounts[i];
  268. simpleShapes = path.toShapes(true);
  269. len1 = simpleShapes.length;
  270. for (j = 0; j < len1; ++j) {
  271. simpleShape = simpleShapes[j];
  272. shape3d = simpleShape.extrude({
  273. amount: amount,
  274. bevelEnabled: false
  275. });
  276. mesh = new THREE.Mesh(shape3d, material);
  277. mesh.rotation.x = Math.PI;
  278. mesh.translateZ( - amount - 1);
  279. mesh.translateX( - theCenter.x);
  280. mesh.translateY( - theCenter.y);
  281. group.add(mesh);
  282. }
  283. }
  284. };
  285. var init3d = function(){
  286. var near = 1.0;
  287. var far = 10000.0;
  288. /// Global : renderer
  289. renderer = new THREE.WebGLRenderer( { antialias: true } );
  290. renderer.setClearColor( 0xb0b0b0 );
  291. renderer.setSize( window.innerWidth, window.innerHeight );
  292. /// Global : scene
  293. scene = new THREE.Scene();
  294. /// Global : camera
  295. camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, near, far );
  296. camera.position.set( 0, 0, 200 );
  297. /// Global : group
  298. group = new THREE.Group();
  299. scene.add( group );
  300. /// direct light
  301. var light = new THREE.DirectionalLight( 0x404040 );
  302. light.position.set( 0.75, 0.75, 1.0 ).normalize();
  303. scene.add( light );
  304. /// ambient light
  305. var ambientLight = new THREE.AmbientLight(0x404040);
  306. scene.add( ambientLight );
  307. /// backgroup grids
  308. var plane = new THREE.Mesh(
  309. new THREE.PlaneGeometry(160, 160, 20, 20),
  310. new THREE.MeshBasicMaterial({ color: 0x7f7f7f, wireframe: true }));
  311. plane.rotation.x = Math.PI;
  312. group.add( plane );
  313. var obj = initSVGObject();
  314. addGeoObject( group, obj );
  315. };
  316. var initSVGObject = function() {
  317. var obj = {};
  318. /// The geo data from Taipei City, Keelung City, Taipei County in SVG form
  319. obj.paths = [
  320. /// Taipei City
  321. "M366.2182,108.9780 L368.0329,110.3682 L367.5922,112.4411 L369.9258,116.0311 L368.9827,117.3543 " +
  322. "L371.5686,119.8491 L370.5599,121.7206 L372.9314,124.8009 L368.8889,126.7603 L369.2695,130.7622 " +
  323. "L366.1499,130.3388 L363.4698,128.1161 L362.9256,125.6018 L360.8153,126.4025 L360.2968,124.3588 " +
  324. "L361.9519,121.1623 L360.4475,118.7162 L358.1163,117.8678 L358.7094,115.7577 L361.6243,112.4576 Z",
  325. /// Keelung City
  326. "M380.2689,113.3850 L383.5604,114.2370 L383.7404,114.2386 L385.4082,115.6247 L384.9725,117.4631 " +
  327. "L381.6681,117.9439 L383.0209,121.0914 L379.4649,122.7061 L373.4987,118.8487 L372.0980,114.7589 " +
  328. "L377.9716,112.0707 Z",
  329. /// Taipei County
  330. "M359.4486,155.6690 L357.0422,152.7420 L355.1688,148.0173 L357.1186,145.8045 L354.1323,141.2242 " +
  331. "L351.1807,141.6609 L348.9387,140.5372 L349.5415,137.8396 L347.5174,136.1694 L347.6299,129.2327 " +
  332. "L351.4192,128.8067 L354.2518,125.3113 L352.5805,121.8038 L349.3190,120.9429 L344.3277,116.7676 " +
  333. "L350.9772,115.1221 L354.5759,112.5371 L354.5667,110.6949 L357.4098,105.7489 L362.3963,101.8443 " +
  334. "L364.4415,101.0819 L364.5314,101.0828 L364.6209,101.1230 L364.7698,101.2029 L368.1221,101.5115 " +
  335. "L371.7216,104.1338 L372.2958,106.7261 L375.5949,109.6971 L377.0415,108.8875 L377.0737,108.6526 " +
  336. "L377.4037,108.6165 L376.8840,109.7091 L376.7323,109.9037 L377.9416,112.0705 L371.7970,114.8736 " +
  337. "L374.0935,119.4031 L380.7848,122.7963 L382.6529,121.9897 L381.5792,117.8256 L385.0339,117.3069 " +
  338. "L385.4082,115.6247 L388.7014,116.3969 L389.8697,116.6024 L390.0206,116.4860 L391.0396,116.6118 " +
  339. "L394.6665,116.9929 L394.4694,119.2255 L394.3172,119.4987 L395.3792,121.8977 L395.2728,124.0526 " +
  340. "L397.2123,125.6350 L401.1709,126.2516 L401.2612,126.2130 L401.4086,126.6060 L400.1992,127.7733 " +
  341. "L399.7769,128.0446 L399.6247,128.3179 L398.1779,129.0521 L394.2418,129.2969 L388.7324,130.9385 " +
  342. "L389.2782,134.0003 L383.7237,137.0111 L381.7445,139.9336 L379.7001,139.9546 L376.1539,143.0580 " +
  343. "L371.3022,144.1094 L368.6009,146.5914 L368.7361,151.1399 L363.6153,154.4980 " +
  344. /// Taipei County hole.
  345. "M363.4600,128.3904 L366.6300,130.3829 L369.3732,129.3913 L369.5603,125.6695 L374.3989,125.1677 " +
  346. "L370.8412,123.6440 L371.0684,118.8252 L369.0431,117.3157 L369.6882,115.7936 L367.8578,112.8749 " +
  347. "L368.1217,110.4867 L366.5152,109.2554 L361.9554,112.3435 L358.1163,117.8678 L361.7218,120.2192 " +
  348. "L360.7261,126.3232 L362.8064,125.5221 Z"];
  349. obj.amounts = [ 19, 20, 21 ];
  350. obj.colors = [ 0xC07000, 0xC08000, 0xC0A000 ];
  351. obj.center = { x:365, y:125 };
  352. return obj;
  353. };
  354. </script>
  355. <script>
  356. /// Events from extrude shapes example
  357. function onWindowResize() {
  358. windowHalfX = window.innerWidth / 2;
  359. windowHalfY = window.innerHeight / 2;
  360. camera.aspect = window.innerWidth / window.innerHeight;
  361. camera.updateProjectionMatrix();
  362. renderer.setSize( window.innerWidth, window.innerHeight );
  363. };
  364. function onDocumentMouseDown( event ) {
  365. event.preventDefault();
  366. document.addEventListener( 'mousemove', onDocumentMouseMove, false );
  367. document.addEventListener( 'mouseup', onDocumentMouseUp, false );
  368. document.addEventListener( 'mouseout', onDocumentMouseOut, false );
  369. mouseXOnMouseDown = event.clientX - windowHalfX;
  370. targetRotationOnMouseDown = targetRotation;
  371. };
  372. function onDocumentMouseMove( event ) {
  373. mouseX = event.clientX - windowHalfX;
  374. targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
  375. };
  376. function onDocumentMouseUp( event ) {
  377. document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
  378. document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
  379. document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
  380. };
  381. function onDocumentMouseOut( event ) {
  382. document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
  383. document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
  384. document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
  385. };
  386. function onDocumentTouchStart( event ) {
  387. if ( event.touches.length == 1 ) {
  388. event.preventDefault();
  389. mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
  390. targetRotationOnMouseDown = targetRotation;
  391. }
  392. };
  393. function onDocumentTouchMove( event ) {
  394. if ( event.touches.length == 1 ) {
  395. event.preventDefault();
  396. mouseX = event.touches[ 0 ].pageX - windowHalfX;
  397. targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
  398. }
  399. };
  400. function animate() {
  401. /// compatibility : http://caniuse.com/requestanimationframe
  402. requestAnimationFrame( animate );
  403. render();
  404. stats.update();
  405. };
  406. function render() {
  407. group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
  408. renderer.render( scene, camera );
  409. };
  410. </script>
  411. <script>
  412. /// Main
  413. var renderer, stats;
  414. var scene, camera, group;
  415. var targetRotation = 0;
  416. var targetRotationOnMouseDown = 0;
  417. var mouseX = 0;
  418. var mouseXOnMouseDown = 0;
  419. var windowHalfX = window.innerWidth / 2;
  420. var windowHalfY = window.innerHeight / 2;
  421. var container = document.createElement( 'div' );
  422. document.body.appendChild( container );
  423. var info = document.createElement( 'div' );
  424. info.style.position = 'absolute';
  425. info.style.top = '10px';
  426. info.style.width = '100%';
  427. info.style.textAlign = 'center';
  428. info.innerHTML = 'Shapes Extrusion via Geo Data<br/>Drag to spin';
  429. container.appendChild( info );
  430. init3d();
  431. container.appendChild( renderer.domElement );
  432. stats = new Stats();
  433. stats.domElement.style.position = 'absolute';
  434. stats.domElement.style.top = '0px';
  435. container.appendChild( stats.domElement );
  436. document.addEventListener( 'mousedown', onDocumentMouseDown, false );
  437. document.addEventListener( 'touchstart', onDocumentTouchStart, false );
  438. document.addEventListener( 'touchmove', onDocumentTouchMove, false );
  439. window.addEventListener( 'resize', onWindowResize, false );
  440. animate();
  441. </script>
  442. </body>
  443. </html>