webgl_geometry_extrude_shapes2.html 13 KB

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