1
0

webgl_geometry_extrude_splines.html 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgl - geometry - spline extrusion</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. background-color: #f0f0f0;
  11. color: #444;
  12. }
  13. a {
  14. color: #08f;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <div id="container"></div>
  20. <div id="info">
  21. <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - spline extrusion examples
  22. </div>
  23. <script type="importmap">
  24. {
  25. "imports": {
  26. "three": "../build/three.module.js",
  27. "three/addons/": "./jsm/"
  28. }
  29. }
  30. </script>
  31. <script type="module">
  32. import * as THREE from 'three';
  33. import Stats from 'three/addons/libs/stats.module.js';
  34. import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
  35. import * as Curves from 'three/addons/curves/CurveExtras.js';
  36. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  37. let container, stats;
  38. let camera, scene, renderer, splineCamera, cameraHelper, cameraEye;
  39. const direction = new THREE.Vector3();
  40. const binormal = new THREE.Vector3();
  41. const normal = new THREE.Vector3();
  42. const position = new THREE.Vector3();
  43. const lookAt = new THREE.Vector3();
  44. const pipeSpline = new THREE.CatmullRomCurve3( [
  45. new THREE.Vector3( 0, 10, - 10 ), new THREE.Vector3( 10, 0, - 10 ),
  46. new THREE.Vector3( 20, 0, 0 ), new THREE.Vector3( 30, 0, 10 ),
  47. new THREE.Vector3( 30, 0, 20 ), new THREE.Vector3( 20, 0, 30 ),
  48. new THREE.Vector3( 10, 0, 30 ), new THREE.Vector3( 0, 0, 30 ),
  49. new THREE.Vector3( - 10, 10, 30 ), new THREE.Vector3( - 10, 20, 30 ),
  50. new THREE.Vector3( 0, 30, 30 ), new THREE.Vector3( 10, 30, 30 ),
  51. new THREE.Vector3( 20, 30, 15 ), new THREE.Vector3( 10, 30, 10 ),
  52. new THREE.Vector3( 0, 30, 10 ), new THREE.Vector3( - 10, 20, 10 ),
  53. new THREE.Vector3( - 10, 10, 10 ), new THREE.Vector3( 0, 0, 10 ),
  54. new THREE.Vector3( 10, - 10, 10 ), new THREE.Vector3( 20, - 15, 10 ),
  55. new THREE.Vector3( 30, - 15, 10 ), new THREE.Vector3( 40, - 15, 10 ),
  56. new THREE.Vector3( 50, - 15, 10 ), new THREE.Vector3( 60, 0, 10 ),
  57. new THREE.Vector3( 70, 0, 0 ), new THREE.Vector3( 80, 0, 0 ),
  58. new THREE.Vector3( 90, 0, 0 ), new THREE.Vector3( 100, 0, 0 )
  59. ] );
  60. const sampleClosedSpline = new THREE.CatmullRomCurve3( [
  61. new THREE.Vector3( 0, - 40, - 40 ),
  62. new THREE.Vector3( 0, 40, - 40 ),
  63. new THREE.Vector3( 0, 140, - 40 ),
  64. new THREE.Vector3( 0, 40, 40 ),
  65. new THREE.Vector3( 0, - 40, 40 )
  66. ] );
  67. sampleClosedSpline.curveType = 'catmullrom';
  68. sampleClosedSpline.closed = true;
  69. // Keep a dictionary of Curve instances
  70. const splines = {
  71. GrannyKnot: new Curves.GrannyKnot(),
  72. HeartCurve: new Curves.HeartCurve( 3.5 ),
  73. VivianiCurve: new Curves.VivianiCurve( 70 ),
  74. KnotCurve: new Curves.KnotCurve(),
  75. HelixCurve: new Curves.HelixCurve(),
  76. TrefoilKnot: new Curves.TrefoilKnot(),
  77. TorusKnot: new Curves.TorusKnot( 20 ),
  78. CinquefoilKnot: new Curves.CinquefoilKnot( 20 ),
  79. TrefoilPolynomialKnot: new Curves.TrefoilPolynomialKnot( 14 ),
  80. FigureEightPolynomialKnot: new Curves.FigureEightPolynomialKnot(),
  81. DecoratedTorusKnot4a: new Curves.DecoratedTorusKnot4a(),
  82. DecoratedTorusKnot4b: new Curves.DecoratedTorusKnot4b(),
  83. DecoratedTorusKnot5a: new Curves.DecoratedTorusKnot5a(),
  84. DecoratedTorusKnot5c: new Curves.DecoratedTorusKnot5c(),
  85. PipeSpline: pipeSpline,
  86. SampleClosedSpline: sampleClosedSpline
  87. };
  88. let parent, tubeGeometry, mesh;
  89. const params = {
  90. spline: 'GrannyKnot',
  91. scale: 4,
  92. extrusionSegments: 100,
  93. radiusSegments: 3,
  94. closed: true,
  95. animationView: false,
  96. lookAhead: false,
  97. cameraHelper: false,
  98. };
  99. const material = new THREE.MeshLambertMaterial( { color: 0xff00ff } );
  100. const wireframeMaterial = new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.3, wireframe: true, transparent: true } );
  101. function addTube() {
  102. if ( mesh !== undefined ) {
  103. parent.remove( mesh );
  104. mesh.geometry.dispose();
  105. }
  106. const extrudePath = splines[ params.spline ];
  107. tubeGeometry = new THREE.TubeGeometry( extrudePath, params.extrusionSegments, 2, params.radiusSegments, params.closed );
  108. addGeometry( tubeGeometry );
  109. setScale();
  110. }
  111. function setScale() {
  112. mesh.scale.set( params.scale, params.scale, params.scale );
  113. }
  114. function addGeometry( geometry ) {
  115. // 3D shape
  116. mesh = new THREE.Mesh( geometry, material );
  117. const wireframe = new THREE.Mesh( geometry, wireframeMaterial );
  118. mesh.add( wireframe );
  119. parent.add( mesh );
  120. }
  121. function animateCamera() {
  122. cameraHelper.visible = params.cameraHelper;
  123. cameraEye.visible = params.cameraHelper;
  124. }
  125. init();
  126. function init() {
  127. container = document.getElementById( 'container' );
  128. // camera
  129. camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.01, 10000 );
  130. camera.position.set( 0, 50, 500 );
  131. // scene
  132. scene = new THREE.Scene();
  133. scene.background = new THREE.Color( 0xf0f0f0 );
  134. // light
  135. scene.add( new THREE.AmbientLight( 0xffffff ) );
  136. const light = new THREE.DirectionalLight( 0xffffff, 1.5 );
  137. light.position.set( 0, 0, 1 );
  138. scene.add( light );
  139. // tube
  140. parent = new THREE.Object3D();
  141. scene.add( parent );
  142. splineCamera = new THREE.PerspectiveCamera( 84, window.innerWidth / window.innerHeight, 0.01, 1000 );
  143. parent.add( splineCamera );
  144. cameraHelper = new THREE.CameraHelper( splineCamera );
  145. scene.add( cameraHelper );
  146. addTube();
  147. // debug camera
  148. cameraEye = new THREE.Mesh( new THREE.SphereGeometry( 5 ), new THREE.MeshBasicMaterial( { color: 0xdddddd } ) );
  149. parent.add( cameraEye );
  150. cameraHelper.visible = params.cameraHelper;
  151. cameraEye.visible = params.cameraHelper;
  152. // renderer
  153. renderer = new THREE.WebGLRenderer( { antialias: true } );
  154. renderer.setPixelRatio( window.devicePixelRatio );
  155. renderer.setSize( window.innerWidth, window.innerHeight );
  156. renderer.setAnimationLoop( animate );
  157. container.appendChild( renderer.domElement );
  158. // stats
  159. stats = new Stats();
  160. container.appendChild( stats.dom );
  161. // dat.GUI
  162. const gui = new GUI( { width: 285 } );
  163. const folderGeometry = gui.addFolder( 'Geometry' );
  164. folderGeometry.add( params, 'spline', Object.keys( splines ) ).onChange( function () {
  165. addTube();
  166. } );
  167. folderGeometry.add( params, 'scale', 2, 10 ).step( 2 ).onChange( function () {
  168. setScale();
  169. } );
  170. folderGeometry.add( params, 'extrusionSegments', 50, 500 ).step( 50 ).onChange( function () {
  171. addTube();
  172. } );
  173. folderGeometry.add( params, 'radiusSegments', 2, 12 ).step( 1 ).onChange( function () {
  174. addTube();
  175. } );
  176. folderGeometry.add( params, 'closed' ).onChange( function () {
  177. addTube();
  178. } );
  179. folderGeometry.open();
  180. const folderCamera = gui.addFolder( 'Camera' );
  181. folderCamera.add( params, 'animationView' ).onChange( function () {
  182. animateCamera();
  183. } );
  184. folderCamera.add( params, 'lookAhead' ).onChange( function () {
  185. animateCamera();
  186. } );
  187. folderCamera.add( params, 'cameraHelper' ).onChange( function () {
  188. animateCamera();
  189. } );
  190. folderCamera.open();
  191. const controls = new OrbitControls( camera, renderer.domElement );
  192. controls.minDistance = 100;
  193. controls.maxDistance = 2000;
  194. window.addEventListener( 'resize', onWindowResize );
  195. }
  196. function onWindowResize() {
  197. camera.aspect = window.innerWidth / window.innerHeight;
  198. camera.updateProjectionMatrix();
  199. renderer.setSize( window.innerWidth, window.innerHeight );
  200. }
  201. //
  202. function animate() {
  203. render();
  204. stats.update();
  205. }
  206. function render() {
  207. // animate camera along spline
  208. const time = Date.now();
  209. const looptime = 20 * 1000;
  210. const t = ( time % looptime ) / looptime;
  211. tubeGeometry.parameters.path.getPointAt( t, position );
  212. position.multiplyScalar( params.scale );
  213. // interpolation
  214. const segments = tubeGeometry.tangents.length;
  215. const pickt = t * segments;
  216. const pick = Math.floor( pickt );
  217. const pickNext = ( pick + 1 ) % segments;
  218. binormal.subVectors( tubeGeometry.binormals[ pickNext ], tubeGeometry.binormals[ pick ] );
  219. binormal.multiplyScalar( pickt - pick ).add( tubeGeometry.binormals[ pick ] );
  220. tubeGeometry.parameters.path.getTangentAt( t, direction );
  221. const offset = 15;
  222. normal.copy( binormal ).cross( direction );
  223. // we move on a offset on its binormal
  224. position.add( normal.clone().multiplyScalar( offset ) );
  225. splineCamera.position.copy( position );
  226. cameraEye.position.copy( position );
  227. // using arclength for stablization in look ahead
  228. tubeGeometry.parameters.path.getPointAt( ( t + 30 / tubeGeometry.parameters.path.getLength() ) % 1, lookAt );
  229. lookAt.multiplyScalar( params.scale );
  230. // camera orientation 2 - up orientation via normal
  231. if ( ! params.lookAhead ) lookAt.copy( position ).add( direction );
  232. splineCamera.matrix.lookAt( splineCamera.position, lookAt, normal );
  233. splineCamera.quaternion.setFromRotationMatrix( splineCamera.matrix );
  234. cameraHelper.update();
  235. renderer.render( scene, params.animationView === true ? splineCamera : camera );
  236. }
  237. </script>
  238. </body>
  239. </html>