webgpu_geometry_loft.html 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>three.js webgpu - loft geometry</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. <meta property="og:title" content="three.js webgpu - loft geometry">
  8. <meta property="og:type" content="website">
  9. <meta property="og:url" content="https://threejs.org/examples/webgpu_geometry_loft.html">
  10. <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_geometry_loft.jpg">
  11. <link type="text/css" rel="stylesheet" href="example.css">
  12. </head>
  13. <body>
  14. <div id="info">
  15. <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
  16. <div class="title-wrapper">
  17. <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Loft Geometry</span>
  18. </div>
  19. <small>
  20. Surfaces generated through cross sections, textured procedurally with TSL.
  21. </small>
  22. </div>
  23. <script type="importmap">
  24. {
  25. "imports": {
  26. "three": "../build/three.webgpu.js",
  27. "three/webgpu": "../build/three.webgpu.js",
  28. "three/tsl": "../build/three.tsl.js",
  29. "three/addons/": "./jsm/"
  30. }
  31. }
  32. </script>
  33. <script type="module">
  34. import * as THREE from 'three/webgpu';
  35. import { bumpMap, color, cos, float, mix, mx_fractal_noise_float, mx_noise_float, mx_worley_noise_float, positionLocal, screenUV, sin, smoothstep, uv, vec3 } from 'three/tsl';
  36. import { Inspector } from 'three/addons/inspector/Inspector.js';
  37. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  38. import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
  39. import { LoftGeometry } from 'three/addons/geometries/LoftGeometry.js';
  40. let group, camera, scene, renderer;
  41. init();
  42. async function init() {
  43. renderer = new THREE.WebGPURenderer( { antialias: true } );
  44. renderer.setPixelRatio( window.devicePixelRatio );
  45. renderer.setSize( window.innerWidth, window.innerHeight );
  46. renderer.setAnimationLoop( animate );
  47. renderer.toneMapping = THREE.NeutralToneMapping;
  48. renderer.shadowMap.enabled = true;
  49. renderer.shadowMap.type = THREE.PCFShadowMap;
  50. renderer.inspector = new Inspector();
  51. document.body.appendChild( renderer.domElement );
  52. await renderer.init();
  53. const pmremGenerator = new THREE.PMREMGenerator( renderer );
  54. scene = new THREE.Scene();
  55. scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
  56. scene.environmentIntensity = 0.4;
  57. // a vignette in the background
  58. const background = screenUV.distance( .5 ).mix( color( 0x5d5d84 ), color( 0x2e2e44 ) );
  59. scene.backgroundNode = background;
  60. // camera
  61. camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
  62. camera.position.set( 0, 15, 40 );
  63. // controls
  64. const controls = new OrbitControls( camera, renderer.domElement );
  65. controls.minDistance = 15;
  66. controls.maxDistance = 50; // stay inside the curtain
  67. controls.target.set( 0, - 3, 0 );
  68. controls.update();
  69. // everything stands on a rotating floor. all surface detail is
  70. // procedural: the loft uvs run along the loft ( uv().x ) and around
  71. // the sections ( uv().y ), so patterns can follow the geometry
  72. group = new THREE.Group();
  73. scene.add( group );
  74. // floor: large, softly mottled, running under the curtain so its
  75. // edge is never seen
  76. const floorMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 1 } );
  77. floorMaterial.colorNode = color( 0x555577 ).mul( mx_noise_float( positionLocal.mul( 0.4 ) ).mul( 0.1 ).add( 0.95 ) );
  78. const floor = new THREE.Mesh(
  79. new THREE.CircleGeometry( 58, 64 ).rotateX( - Math.PI / 2 ),
  80. floorMaterial
  81. );
  82. floor.position.y = - 5.01;
  83. floor.receiveShadow = true;
  84. group.add( floor );
  85. // a theater curtain encircles the exhibition
  86. const curtainMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.9, side: THREE.DoubleSide } );
  87. curtainMaterial.colorNode = color( 0x86222e ).mul( mx_noise_float( vec3( uv().y.mul( 300 ), uv().x.mul( 6 ), 0 ) ).mul( 0.08 ).add( 0.96 ) );
  88. const curtain = new THREE.Mesh( createCurtainGeometry(), curtainMaterial );
  89. group.add( curtain );
  90. // light
  91. const light = new THREE.DirectionalLight( 0xffffff, 3 );
  92. light.position.set( 18, 30, 12 );
  93. light.castShadow = true;
  94. light.shadow.camera.left = - 60;
  95. light.shadow.camera.right = 60;
  96. light.shadow.camera.top = 60;
  97. light.shadow.camera.bottom = - 60;
  98. light.shadow.camera.far = 110;
  99. light.shadow.mapSize.set( 4096, 4096 );
  100. light.shadow.bias = - 0.0005;
  101. scene.add( light );
  102. // pedestals: polished marble. the veins meander and branch along the
  103. // zero crossings of a domain warped fractal noise — a sharp dark
  104. // core inside a soft halo — over a gently clouded white base
  105. const p = positionLocal.mul( 0.9 );
  106. const vein = mx_fractal_noise_float( p.add( mx_fractal_noise_float( p.mul( 0.4 ), 3 ).mul( 2 ) ), 4 ).abs().oneMinus();
  107. const fine = mx_fractal_noise_float( p.mul( 3 ).add( 11 ), 3 ).abs().oneMinus();
  108. const veining = vein.pow( 4 ).mul( 0.3 ).add( vein.pow( 12 ).mul( 0.7 ) ).add( fine.pow( 14 ).mul( 0.2 ) );
  109. const clouds = mx_noise_float( p.mul( 0.5 ) ).mul( 0.5 ).add( 0.5 );
  110. const pedestalMaterial = new THREE.MeshStandardNodeMaterial();
  111. pedestalMaterial.colorNode = mix( mix( color( 0xf4f4f7 ), color( 0xeeeef2 ), clouds ), color( 0xd0d0d5 ), veining );
  112. pedestalMaterial.roughnessNode = veining.mul( 0.14 ).add( 0.07 );
  113. pedestalMaterial.envMapIntensity = 1.5;
  114. function addPedestal( x, z, radius, height ) {
  115. const pedestal = new THREE.Mesh( createPedestalGeometry( radius, height ), pedestalMaterial );
  116. pedestal.position.set( x, - 5, z );
  117. pedestal.rotation.y = x * 0.7 + z * 1.3; // so the marbling differs per pedestal
  118. group.add( pedestal );
  119. return - 5 + height; // the top of the pedestal
  120. }
  121. // a coffee set: a cup and a saucer lofted from the bottom center, up
  122. // one side of the wall and back down the inside, and a handle swept
  123. // along a spline. the porcelain glaze has a faint waviness
  124. const porcelain = new THREE.MeshStandardNodeMaterial();
  125. porcelain.roughnessNode = mx_noise_float( positionLocal.mul( 6 ) ).mul( 0.08 ).add( 0.2 );
  126. porcelain.normalNode = bumpMap( mx_noise_float( positionLocal.mul( 2 ) ).mul( 0.05 ) );
  127. const coffee = new THREE.Group();
  128. coffee.position.y = addPedestal( 0, 0, 3.6, 2.2 );
  129. coffee.scale.setScalar( 0.75 );
  130. group.add( coffee );
  131. const saucer = new THREE.Mesh( createSaucerGeometry(), porcelain );
  132. coffee.add( saucer );
  133. const cup = new THREE.Mesh( createCupGeometry(), porcelain );
  134. cup.position.y = 0.3;
  135. coffee.add( cup );
  136. const handle = new THREE.Mesh( createHandleGeometry(), porcelain );
  137. handle.position.y = 0.3;
  138. handle.rotation.y = 0.15;
  139. coffee.add( handle );
  140. // the coffee has a lazy swirl on its surface
  141. const liquidMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.08 } );
  142. liquidMaterial.colorNode = mix( color( 0x2b1a12 ), color( 0x4a2c1a ), mx_noise_float( positionLocal.mul( 1.5 ) ).mul( 0.5 ).add( 0.5 ) );
  143. const liquid = new THREE.Mesh(
  144. new THREE.CircleGeometry( 2, 48 ).rotateX( - Math.PI / 2 ),
  145. liquidMaterial
  146. );
  147. liquid.position.y = 3.6;
  148. coffee.add( liquid );
  149. // a vase: circular sections with a varying radius. the glaze pools
  150. // in throwing rings along the profile
  151. const vaseRings = sin( uv().x.mul( 160 ) );
  152. const vaseMaterial = new THREE.MeshStandardNodeMaterial( { side: THREE.DoubleSide } );
  153. vaseMaterial.colorNode = mix( color( 0x2e6f9e ), color( 0x82b8d8 ), mx_noise_float( positionLocal.mul( 1.2 ) ).mul( 0.5 ).add( 0.5 ) );
  154. vaseMaterial.roughnessNode = vaseRings.mul( 0.08 ).add( 0.3 );
  155. vaseMaterial.normalNode = bumpMap( vaseRings.mul( 0.012 ) );
  156. const vase = new THREE.Mesh( createVaseGeometry(), vaseMaterial );
  157. vase.position.set( - 10.5, addPedestal( - 10.5, - 10.5, 3.6, 1.1 ), - 10.5 );
  158. group.add( vase );
  159. // a seashell: circular sections that grow while sweeping along a
  160. // logarithmic spiral, with growth bands and fine ridges along it
  161. const shellMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.5, side: THREE.DoubleSide } );
  162. shellMaterial.colorNode = mix( color( 0xc9a87f ), color( 0xf2e6d8 ), mx_noise_float( vec3( uv().x.mul( 24 ), 0, 0 ) ).mul( 0.5 ).add( 0.5 ) );
  163. shellMaterial.normalNode = bumpMap( sin( uv().x.mul( 480 ) ).mul( 0.02 ) );
  164. const shell = new THREE.Mesh( createShellGeometry(), shellMaterial );
  165. shell.position.set( 10.5, addPedestal( 10.5, - 10.5, 3.6, 1.1 ) + 1.92, - 10.5 );
  166. shell.rotation.y = - Math.PI / 2;
  167. shell.scale.setScalar( 0.8 );
  168. group.add( shell );
  169. // a twisted star: non-circular sections that rotate and scale along
  170. // the loft, in sandy terracotta
  171. const starMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.6 } );
  172. starMaterial.colorNode = color( 0xcc5544 ).mul( mx_noise_float( positionLocal.mul( 3 ) ).mul( 0.12 ).add( 0.94 ) );
  173. starMaterial.normalNode = bumpMap( mx_noise_float( positionLocal.mul( 50 ) ).mul( 0.008 ) );
  174. const star = new THREE.Mesh( createStarGeometry(), starMaterial );
  175. star.position.set( - 10.5, addPedestal( - 10.5, 10.5, 3.6, 1.1 ), 10.5 );
  176. star.scale.setScalar( 0.8 );
  177. group.add( star );
  178. // a ribbon: open two-point sections ( closed: false ), in gold
  179. // brushed along its length
  180. const brush = mx_noise_float( vec3( uv().x.mul( 6 ), uv().y.mul( 160 ), 0 ) );
  181. const ribbonMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xffcc44, metalness: 1, side: THREE.DoubleSide } );
  182. ribbonMaterial.roughnessNode = brush.mul( 0.08 ).add( 0.1 );
  183. ribbonMaterial.normalNode = bumpMap( brush.mul( 0.004 ) );
  184. ribbonMaterial.envMapIntensity = 2.5;
  185. const ribbon = new THREE.Mesh( createRibbonGeometry(), ribbonMaterial );
  186. ribbon.position.set( 10.5, addPedestal( 10.5, 10.5, 3.6, 1.1 ), 10.5 );
  187. group.add( ribbon );
  188. // a toothpaste tube: circular sections that morph into a flat
  189. // crimped seam, with stripes printed around the body
  190. const tubeU = uv().x;
  191. const tealStripe = smoothstep( 0.48, 0.5, tubeU ).sub( smoothstep( 0.6, 0.62, tubeU ) );
  192. const redStripe = smoothstep( 0.66, 0.68, tubeU ).sub( smoothstep( 0.72, 0.74, tubeU ) );
  193. const tubeMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.25 } );
  194. tubeMaterial.colorNode = mix( mix( color( 0xf2f2f2 ), color( 0x2aa6b8 ), tealStripe ), color( 0xd0543a ), redStripe );
  195. const tube = new THREE.Mesh( createToothpasteGeometry(), tubeMaterial );
  196. tube.position.set( 7.8, addPedestal( 7.8, 0, 2, 1.6 ), 0 );
  197. tube.rotation.y = 0.5;
  198. group.add( tube );
  199. // a pumpkin: lobed sections around a squashed profile. the shading
  200. // follows the same crease function as the geometry, so the narrow
  201. // creases are darker and rougher than the broad lobes
  202. const lobe = cos( uv().y.mul( Math.PI * 7 ) ).abs().pow( 0.35 );
  203. const pumpkinMaterial = new THREE.MeshStandardNodeMaterial();
  204. pumpkinMaterial.colorNode = mix(
  205. mix( color( 0x9c4f16 ), color( 0xe6913d ), lobe ),
  206. color( 0x8a7a2e ), // greener around the stem
  207. smoothstep( 0.88, 1, uv().x ).mul( 0.6 )
  208. );
  209. pumpkinMaterial.roughnessNode = float( 0.7 ).sub( lobe.mul( 0.2 ) );
  210. pumpkinMaterial.normalNode = bumpMap( mx_noise_float( vec3( uv().y.mul( 120 ), uv().x.mul( 5 ), 0 ) ).mul( 0.01 ) );
  211. const pumpkinY = addPedestal( 0, 7.8, 2, 1.6 );
  212. const pumpkin = new THREE.Mesh( createPumpkinGeometry(), pumpkinMaterial );
  213. pumpkin.position.set( 0, pumpkinY, 7.8 );
  214. group.add( pumpkin );
  215. const pumpkinStemMaterial = new THREE.MeshStandardNodeMaterial( { color: 0x667744 } );
  216. pumpkinStemMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 20 ) ).mul( 0.2 ).add( 0.7 );
  217. const pumpkinStem = new THREE.Mesh( createPumpkinStemGeometry(), pumpkinStemMaterial );
  218. pumpkinStem.position.set( 0, pumpkinY, 7.8 );
  219. group.add( pumpkinStem );
  220. // a mushroom: a cap that folds under its own rim. the cap uvs run
  221. // from under the rim ( u 0 ) over the edge to the top center ( u 1 ),
  222. // so the red dome with its raised warts and the pale underside with
  223. // its radial gills can share one material
  224. const capU = uv().x;
  225. const dome = smoothstep( 0.42, 0.58, capU );
  226. const warts = smoothstep( 0.18, 0.38, mx_worley_noise_float( positionLocal.mul( 2.4 ) ) ).oneMinus().mul( dome );
  227. const gills = sin( uv().y.mul( Math.PI * 120 ) ).mul( 0.5 ).add( 0.5 ).mul( dome.oneMinus() );
  228. const capMaterial = new THREE.MeshStandardNodeMaterial();
  229. capMaterial.colorNode = mix(
  230. mix( color( 0xe8dcc4 ), color( 0xbfae8e ), gills ),
  231. mix( color( 0xa32d20 ), color( 0xf2e9d8 ), warts ),
  232. dome
  233. );
  234. capMaterial.roughnessNode = float( 0.55 ).sub( dome.mul( 0.2 ) ).add( warts.mul( 0.25 ) );
  235. capMaterial.normalNode = bumpMap( warts.mul( 0.08 ).sub( gills.mul( 0.015 ) ) );
  236. const mushroomY = addPedestal( - 7.8, 0, 2, 1.6 );
  237. const mushroomCap = new THREE.Mesh( createMushroomCapGeometry(), capMaterial );
  238. mushroomCap.position.set( - 7.8, mushroomY, 0 );
  239. group.add( mushroomCap );
  240. const mushroomStemMaterial = new THREE.MeshStandardNodeMaterial();
  241. mushroomStemMaterial.colorNode = color( 0xe5d5b5 ).mul( mx_noise_float( vec3( uv().y.mul( 24 ), uv().x.mul( 2 ), 0 ) ).mul( 0.1 ).add( 0.94 ) );
  242. mushroomStemMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 12 ) ).mul( 0.15 ).add( 0.55 );
  243. const mushroomStem = new THREE.Mesh( createMushroomStemGeometry(), mushroomStemMaterial );
  244. mushroomStem.position.set( - 7.8, mushroomY, 0 );
  245. group.add( mushroomStem );
  246. // a goblet: a foot, a thin stem and a bowl with folded back walls,
  247. // in hammered copper
  248. const dents = mx_worley_noise_float( positionLocal.mul( 5 ) );
  249. const gobletMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xb87333, metalness: 1 } );
  250. gobletMaterial.roughnessNode = dents.mul( 0.18 ).add( 0.12 );
  251. gobletMaterial.normalNode = bumpMap( dents.mul( 0.1 ) );
  252. gobletMaterial.envMapIntensity = 2;
  253. const goblet = new THREE.Mesh( createGobletGeometry(), gobletMaterial );
  254. goblet.position.set( 0, addPedestal( 0, - 7.8, 2, 1.6 ), - 7.8 );
  255. group.add( goblet );
  256. // a rope barrier around the exhibition: brass stanchions, and a
  257. // twisted cord sagging between them
  258. const brassMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xc9a86a, metalness: 1 } );
  259. brassMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 10 ) ).mul( 0.06 ).add( 0.16 );
  260. brassMaterial.envMapIntensity = 2;
  261. const ropeMaterial = new THREE.MeshStandardNodeMaterial( { color: 0x8a2433, roughness: 0.65 } );
  262. ropeMaterial.normalNode = bumpMap( sin( uv().x.mul( 200 ).add( uv().y.mul( Math.PI * 2 ) ) ).mul( 0.015 ) ); // the twist of the cord
  263. const posts = 14;
  264. const barrierRadius = 20;
  265. const stanchionGeometry = createStanchionGeometry();
  266. const ropeGeometry = createRopeGeometry( 2 * barrierRadius * Math.sin( Math.PI / posts ) );
  267. for ( let i = 0; i < posts; i ++ ) {
  268. const angle = ( i + 0.5 ) / posts * Math.PI * 2;
  269. const post = new THREE.Mesh( stanchionGeometry, brassMaterial );
  270. post.position.set( Math.sin( angle ) * barrierRadius, - 5, Math.cos( angle ) * barrierRadius );
  271. group.add( post );
  272. const mid = angle + Math.PI / posts;
  273. const midRadius = barrierRadius * Math.cos( Math.PI / posts );
  274. const rope = new THREE.Mesh( ropeGeometry, ropeMaterial );
  275. rope.position.set( Math.sin( mid ) * midRadius, - 5 + 2.05, Math.cos( mid ) * midRadius );
  276. rope.rotation.y = mid;
  277. group.add( rope );
  278. }
  279. // shadows
  280. group.traverse( ( child ) => {
  281. if ( child.isMesh ) child.castShadow = child.receiveShadow = true;
  282. } );
  283. floor.castShadow = false;
  284. liquid.castShadow = false;
  285. // every loft remembers the sections it was skinned through in
  286. // geometry.parameters, so a skeleton of rings can be rebuilt from
  287. // the meshes themselves
  288. const skeleton = new THREE.Group();
  289. skeleton.visible = false;
  290. group.add( skeleton );
  291. const lofts = [];
  292. group.traverse( ( child ) => {
  293. if ( child.isMesh && child.geometry.type === 'LoftGeometry' ) lofts.push( child );
  294. } );
  295. const lineMaterial = new THREE.LineBasicMaterial( { color: 0xaaccee } );
  296. group.updateMatrixWorld( true );
  297. for ( const mesh of lofts ) {
  298. const { sections, closed } = mesh.geometry.parameters;
  299. const step = Math.max( 1, Math.round( sections.length / 20 ) );
  300. const positions = [];
  301. function addRing( ring ) {
  302. const segments = closed ? ring.length : ring.length - 1;
  303. for ( let j = 0; j < segments; j ++ ) {
  304. const a = ring[ j ], b = ring[ ( j + 1 ) % ring.length ];
  305. positions.push( a.x, a.y, a.z, b.x, b.y, b.z );
  306. }
  307. }
  308. for ( let i = 0; i < sections.length; i += step ) addRing( sections[ i ] );
  309. if ( ( sections.length - 1 ) % step !== 0 ) addRing( sections[ sections.length - 1 ] );
  310. const geometry = new THREE.BufferGeometry();
  311. geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
  312. const lines = new THREE.LineSegments( geometry, lineMaterial );
  313. lines.applyMatrix4( mesh.matrixWorld );
  314. skeleton.add( lines );
  315. }
  316. // parameters
  317. const gui = renderer.inspector.createParameters( 'Parameters' );
  318. gui.add( { sections: false }, 'sections' ).onChange( ( value ) => {
  319. skeleton.visible = value;
  320. liquid.visible = ! value;
  321. for ( const mesh of lofts ) mesh.visible = ! value;
  322. } );
  323. gui.add( { wireframe: false }, 'wireframe' ).onChange( ( value ) => {
  324. group.traverse( ( child ) => {
  325. if ( child.isMesh ) {
  326. child.material.wireframe = value;
  327. child.material.needsUpdate = true;
  328. }
  329. } );
  330. } );
  331. //
  332. window.addEventListener( 'resize', onWindowResize );
  333. }
  334. // revolves a smoothed 2d profile ( x = radius, y = height ) into
  335. // circular sections, like a lathe
  336. function createRevolvedSections( profile, divisions, segments ) {
  337. const points = new THREE.SplineCurve( profile ).getPoints( divisions );
  338. const sections = [];
  339. for ( let i = 0; i <= divisions; i ++ ) {
  340. const point = points[ i ];
  341. const ring = [];
  342. for ( let j = 0; j < segments; j ++ ) {
  343. const angle = j / segments * Math.PI * 2;
  344. ring.push( new THREE.Vector3( Math.sin( angle ) * point.x, point.y, Math.cos( angle ) * point.x ) );
  345. }
  346. sections.push( ring );
  347. }
  348. return sections;
  349. }
  350. function createPedestalGeometry( radius, height ) {
  351. // a stepped plinth, a tapered shaft and a cornice; revolved without
  352. // smoothing, one ring per profile point. doubled points split the
  353. // vertex normals, keeping those turnings crisp
  354. const profile = [
  355. new THREE.Vector2( 0.2, 0 ),
  356. new THREE.Vector2( radius * 1.06, 0 ),
  357. new THREE.Vector2( radius * 1.06, height * 0.1 ),
  358. new THREE.Vector2( radius * 1.06, height * 0.1 ),
  359. new THREE.Vector2( radius * 0.98, height * 0.16 ),
  360. new THREE.Vector2( radius * 0.94, height * 0.55 ),
  361. new THREE.Vector2( radius * 0.97, height * 0.84 ),
  362. new THREE.Vector2( radius * 1.04, height * 0.88 ),
  363. new THREE.Vector2( radius * 1.04, height * 0.97 ),
  364. new THREE.Vector2( radius * 1.04, height * 0.97 ),
  365. new THREE.Vector2( radius * 0.98, height ),
  366. new THREE.Vector2( radius * 0.98, height ),
  367. new THREE.Vector2( radius * 0.5, height - 0.004 ),
  368. new THREE.Vector2( 0.2, height )
  369. ];
  370. const sections = [];
  371. for ( const point of profile ) {
  372. const ring = [];
  373. for ( let j = 0; j < 48; j ++ ) {
  374. const angle = j / 48 * Math.PI * 2;
  375. ring.push( new THREE.Vector3( Math.sin( angle ) * point.x, point.y, Math.cos( angle ) * point.x ) );
  376. }
  377. sections.push( ring );
  378. }
  379. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  380. }
  381. function createCupGeometry() {
  382. // from the bottom center, up the egg shaped outer wall, over the lip
  383. // and back down the inner wall
  384. const profile = [
  385. new THREE.Vector2( 0.2, 0 ),
  386. new THREE.Vector2( 0.7, 0.04 ),
  387. new THREE.Vector2( 1.05, 0.1 ),
  388. new THREE.Vector2( 1.75, 0.55 ),
  389. new THREE.Vector2( 2.25, 1.45 ),
  390. new THREE.Vector2( 2.36, 2.2 ),
  391. new THREE.Vector2( 2.3, 3.1 ),
  392. new THREE.Vector2( 2.22, 3.82 ),
  393. new THREE.Vector2( 2.18, 3.95 ),
  394. new THREE.Vector2( 2.06, 3.8 ),
  395. new THREE.Vector2( 2.18, 2.2 ),
  396. new THREE.Vector2( 1.5, 0.75 ),
  397. new THREE.Vector2( 0.9, 0.55 ),
  398. new THREE.Vector2( 0.2, 0.62 )
  399. ];
  400. const sections = createRevolvedSections( profile, 120, 64 );
  401. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  402. }
  403. function createSaucerGeometry() {
  404. // from the bottom center, out along the underside, around the thin
  405. // rim and back across the gently dished top
  406. const profile = [
  407. new THREE.Vector2( 0.2, 0 ),
  408. new THREE.Vector2( 1.3, 0.08 ),
  409. new THREE.Vector2( 2.6, 0.35 ),
  410. new THREE.Vector2( 3.7, 0.8 ),
  411. new THREE.Vector2( 4.2, 1 ),
  412. new THREE.Vector2( 3.4, 0.68 ),
  413. new THREE.Vector2( 2, 0.3 ),
  414. new THREE.Vector2( 1, 0.18 ),
  415. new THREE.Vector2( 0.2, 0.26 )
  416. ];
  417. const sections = createRevolvedSections( profile, 120, 64 );
  418. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  419. }
  420. function createHandleGeometry() {
  421. // an ear shaped loop swept along a spline, slightly tapering; the
  422. // ends slim down so they stay buried inside the thin cup wall
  423. const path = new THREE.SplineCurve( [
  424. new THREE.Vector2( 2.2, 3.3 ),
  425. new THREE.Vector2( 2.9, 3.45 ),
  426. new THREE.Vector2( 3.6, 2.85 ),
  427. new THREE.Vector2( 3.65, 1.95 ),
  428. new THREE.Vector2( 3, 1.2 ),
  429. new THREE.Vector2( 1.78, 1.05 )
  430. ] );
  431. const divisions = 60;
  432. const points = path.getPoints( divisions );
  433. const sections = [];
  434. for ( let i = 0; i <= divisions; i ++ ) {
  435. const t = i / divisions;
  436. const point = points[ i ];
  437. const tangent = path.getTangent( t );
  438. const scale = ( 1 - 0.25 * t )
  439. * ( 0.28 + 0.72 * THREE.MathUtils.smoothstep( t, 0, 0.12 ) )
  440. * ( 0.28 + 0.72 * ( 1 - THREE.MathUtils.smoothstep( t, 0.88, 1 ) ) );
  441. const a = 0.22 * scale; // in the plane of the loop
  442. const b = 0.27 * scale; // across the loop
  443. const ring = [];
  444. for ( let j = 0; j < 16; j ++ ) {
  445. const phi = j / 16 * Math.PI * 2;
  446. const radial = a * Math.cos( phi );
  447. ring.push( new THREE.Vector3(
  448. point.x - radial * tangent.y,
  449. point.y + radial * tangent.x,
  450. b * Math.sin( phi )
  451. ) );
  452. }
  453. sections.push( ring );
  454. }
  455. return new LoftGeometry( sections );
  456. }
  457. function createVaseGeometry() {
  458. // a full belly, a slender waist and a flared lip
  459. const profile = [
  460. new THREE.Vector2( 0.2, 0 ),
  461. new THREE.Vector2( 1.05, 0.05 ),
  462. new THREE.Vector2( 1.5, 0.3 ),
  463. new THREE.Vector2( 2.1, 1.4 ),
  464. new THREE.Vector2( 2.2, 2.3 ),
  465. new THREE.Vector2( 1.8, 3.6 ),
  466. new THREE.Vector2( 1.2, 4.8 ),
  467. new THREE.Vector2( 0.85, 5.8 ),
  468. new THREE.Vector2( 0.72, 6.6 ),
  469. new THREE.Vector2( 0.8, 7.3 ),
  470. new THREE.Vector2( 1.1, 7.9 ),
  471. new THREE.Vector2( 1.3, 8.2 )
  472. ];
  473. const sections = createRevolvedSections( profile, 100, 48 );
  474. return new LoftGeometry( sections, { capStart: true } );
  475. }
  476. function createShellGeometry() {
  477. const turns = 3;
  478. const growth = 0.18;
  479. const scale = Math.exp( growth * turns * Math.PI * 2 );
  480. const sections = [];
  481. for ( let i = 0; i <= 150; i ++ ) {
  482. const t = i / 150;
  483. const angle = turns * Math.PI * 2 * t;
  484. const e = Math.exp( growth * angle ) / scale;
  485. const pathRadius = 3 * e;
  486. const sectionRadius = 2.4 * e;
  487. const sin = Math.sin( angle );
  488. const cos = Math.cos( angle );
  489. const points = [];
  490. for ( let j = 0; j < 32; j ++ ) {
  491. const phi = j / 32 * Math.PI * 2;
  492. const r = pathRadius + sectionRadius * Math.cos( phi );
  493. points.push( new THREE.Vector3( r * sin, 4.5 * ( 1 - e ) + sectionRadius * Math.sin( phi ), r * cos ) );
  494. }
  495. sections.push( points );
  496. }
  497. return new LoftGeometry( sections );
  498. }
  499. function createStarGeometry() {
  500. const sections = [];
  501. for ( let i = 0; i <= 60; i ++ ) {
  502. const t = i / 60;
  503. const twist = t * Math.PI / 3;
  504. const scale = 1 - 0.35 * Math.sin( t * Math.PI );
  505. const points = [];
  506. for ( let j = 0; j < 96; j ++ ) {
  507. const angle = j / 96 * Math.PI * 2;
  508. const radius = ( 2.4 + 0.7 * Math.cos( 5 * angle ) ) * scale;
  509. points.push( new THREE.Vector3( Math.sin( angle + twist ) * radius, t * 10, Math.cos( angle + twist ) * radius ) );
  510. }
  511. sections.push( points );
  512. }
  513. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  514. }
  515. function createRibbonGeometry() {
  516. const sections = [];
  517. for ( let i = 0; i <= 120; i ++ ) {
  518. const t = i / 120;
  519. const angle = t * Math.PI * 2 * 2.5;
  520. const sin = Math.sin( angle );
  521. const cos = Math.cos( angle );
  522. sections.push( [
  523. new THREE.Vector3( 3 * sin, t * 7.5, 3 * cos ),
  524. new THREE.Vector3( 3 * sin, t * 7.5 + 2, 3 * cos )
  525. ] );
  526. }
  527. return new LoftGeometry( sections, { closed: false } );
  528. }
  529. function createToothpasteGeometry() {
  530. // a cap, a shoulder, and a body whose circular sections flatten
  531. // into a wide crimped seam at the top
  532. const sections = [];
  533. for ( let i = 0; i <= 80; i ++ ) {
  534. const t = i / 80;
  535. const radius = 0.5 + 0.28 * THREE.MathUtils.smoothstep( t, 0.08, 0.2 );
  536. const crimp = THREE.MathUtils.smoothstep( t, 0.3, 0.95 );
  537. const width = radius * ( 1 - crimp ) + 1.15 * crimp;
  538. const depth = radius * ( 1 - crimp ) + 0.05 * crimp;
  539. const points = [];
  540. for ( let j = 0; j < 48; j ++ ) {
  541. const angle = j / 48 * Math.PI * 2;
  542. points.push( new THREE.Vector3( Math.sin( angle ) * width, t * 4.2, Math.cos( angle ) * depth ) );
  543. }
  544. sections.push( points );
  545. }
  546. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  547. }
  548. function createPumpkinGeometry() {
  549. // a squashed sphere with broad lobes split by narrow creases, and
  550. // a sunken hollow around the stem
  551. const sections = [];
  552. for ( let i = 0; i <= 60; i ++ ) {
  553. const t = i / 60;
  554. const angle = Math.PI * ( 0.03 + 0.94 * t );
  555. const radius = 1.85 * Math.pow( Math.sin( angle ), 0.62 );
  556. const creases = 0.15 * Math.sin( Math.PI * t );
  557. const y = 2.05 * t - 0.75 * THREE.MathUtils.smoothstep( t, 0.8, 1 );
  558. const points = [];
  559. for ( let j = 0; j < 96; j ++ ) {
  560. const theta = j / 96 * Math.PI * 2;
  561. const lobe = Math.pow( Math.abs( Math.cos( 3.5 * theta ) ), 0.35 );
  562. const r = radius * ( 1 - creases + creases * lobe );
  563. points.push( new THREE.Vector3( Math.sin( theta ) * r, y, Math.cos( theta ) * r ) );
  564. }
  565. sections.push( points );
  566. }
  567. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  568. }
  569. function createPumpkinStemGeometry() {
  570. // a ribbed stalk, flared at its base, that rises out of the hollow
  571. // and leans over
  572. const sections = [];
  573. for ( let i = 0; i <= 30; i ++ ) {
  574. const t = i / 30;
  575. const radius = 0.2 - 0.09 * t + 0.14 * Math.pow( 1 - t, 4 );
  576. const lean = 0.45 * t * t;
  577. const points = [];
  578. for ( let j = 0; j < 32; j ++ ) {
  579. const angle = j / 32 * Math.PI * 2;
  580. const r = radius * ( 0.92 + 0.13 * Math.pow( Math.abs( Math.cos( 2.5 * angle ) ), 0.5 ) );
  581. points.push( new THREE.Vector3( lean + Math.sin( angle ) * r, 1.3 + 1.15 * t, Math.cos( angle ) * r ) );
  582. }
  583. sections.push( points );
  584. }
  585. return new LoftGeometry( sections, { capEnd: true } );
  586. }
  587. function createMushroomCapGeometry() {
  588. // from under the rim, around the edge and over the dome
  589. const profile = [
  590. new THREE.Vector2( 0.35, 2.02 ),
  591. new THREE.Vector2( 1.1, 2 ),
  592. new THREE.Vector2( 1.65, 2.15 ),
  593. new THREE.Vector2( 1.78, 2.4 ),
  594. new THREE.Vector2( 1.5, 2.85 ),
  595. new THREE.Vector2( 0.95, 3.18 ),
  596. new THREE.Vector2( 0.2, 3.32 )
  597. ];
  598. const sections = createRevolvedSections( profile, 80, 48 );
  599. return new LoftGeometry( sections, { capEnd: true } );
  600. }
  601. function createMushroomStemGeometry() {
  602. const profile = [
  603. new THREE.Vector2( 0.2, 0 ),
  604. new THREE.Vector2( 0.55, 0.05 ),
  605. new THREE.Vector2( 0.45, 0.9 ),
  606. new THREE.Vector2( 0.4, 1.7 ),
  607. new THREE.Vector2( 0.42, 2.3 )
  608. ];
  609. const sections = createRevolvedSections( profile, 60, 32 );
  610. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  611. }
  612. function createGobletGeometry() {
  613. // a foot, a thin stem, and a bowl that folds back down inside
  614. const profile = [
  615. new THREE.Vector2( 0.2, 0 ),
  616. new THREE.Vector2( 1.25, 0.05 ),
  617. new THREE.Vector2( 1.35, 0.2 ),
  618. new THREE.Vector2( 0.6, 0.5 ),
  619. new THREE.Vector2( 0.28, 0.9 ),
  620. new THREE.Vector2( 0.24, 1.7 ),
  621. new THREE.Vector2( 0.7, 2.15 ),
  622. new THREE.Vector2( 1.15, 2.8 ),
  623. new THREE.Vector2( 1.28, 3.5 ),
  624. new THREE.Vector2( 1.27, 3.62 ),
  625. new THREE.Vector2( 1.16, 3.5 ),
  626. new THREE.Vector2( 0.95, 2.85 ),
  627. new THREE.Vector2( 0.45, 2.25 ),
  628. new THREE.Vector2( 0.2, 2.32 )
  629. ];
  630. const sections = createRevolvedSections( profile, 140, 48 );
  631. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  632. }
  633. function createStanchionGeometry() {
  634. // a flat disc base, a slender pole and a small ball finial
  635. const profile = [
  636. new THREE.Vector2( 0.16, 0 ),
  637. new THREE.Vector2( 0.42, 0.04 ),
  638. new THREE.Vector2( 0.46, 0.12 ),
  639. new THREE.Vector2( 0.28, 0.22 ),
  640. new THREE.Vector2( 0.08, 0.38 ),
  641. new THREE.Vector2( 0.06, 1 ),
  642. new THREE.Vector2( 0.06, 1.85 ),
  643. new THREE.Vector2( 0.11, 1.95 ),
  644. new THREE.Vector2( 0.19, 2.08 ),
  645. new THREE.Vector2( 0.2, 2.2 ),
  646. new THREE.Vector2( 0.11, 2.3 ),
  647. new THREE.Vector2( 0.04, 2.34 )
  648. ];
  649. const sections = createRevolvedSections( profile, 80, 24 );
  650. return new LoftGeometry( sections, { capStart: true, capEnd: true } );
  651. }
  652. function createRopeGeometry( length ) {
  653. // a cord sagging between two stanchions, with both ends buried in
  654. // their poles
  655. const sag = 0.9;
  656. const sections = [];
  657. for ( let i = 0; i <= 40; i ++ ) {
  658. const t = i / 40;
  659. const x = ( t - 0.5 ) * length;
  660. const y = - sag * 4 * t * ( 1 - t );
  661. // the in plane tangent orients the rings along the curve
  662. const tx = length;
  663. const ty = - sag * 4 * ( 1 - 2 * t );
  664. const tl = Math.sqrt( tx * tx + ty * ty );
  665. const points = [];
  666. for ( let j = 0; j < 16; j ++ ) {
  667. const phi = j / 16 * Math.PI * 2;
  668. const radial = 0.08 * Math.cos( phi );
  669. points.push( new THREE.Vector3(
  670. x - radial * ty / tl,
  671. y + radial * tx / tl,
  672. 0.08 * Math.sin( phi )
  673. ) );
  674. }
  675. sections.push( points );
  676. }
  677. return new LoftGeometry( sections );
  678. }
  679. function createCurtainGeometry() {
  680. // rows of pleated rings hanging from above; the folds deepen and
  681. // drift sideways as they fall
  682. const sections = [];
  683. for ( let i = 0; i <= 30; i ++ ) {
  684. const t = i / 30;
  685. const y = - 5 + 25 * t;
  686. const points = [];
  687. for ( let j = 0; j < 480; j ++ ) {
  688. const s = j / 480;
  689. const folds = ( 1.2 - 0.5 * t ) * Math.sin( s * Math.PI * 2 * 48 + t * 2 );
  690. const sway = 0.5 * Math.sin( s * Math.PI * 2 * 5 + t * 3 );
  691. const theta = s * Math.PI * 2;
  692. const r = 55 + folds + sway;
  693. points.push( new THREE.Vector3( Math.sin( theta ) * r, y, Math.cos( theta ) * r ) );
  694. }
  695. sections.push( points );
  696. }
  697. return new LoftGeometry( sections );
  698. }
  699. function onWindowResize() {
  700. camera.aspect = window.innerWidth / window.innerHeight;
  701. camera.updateProjectionMatrix();
  702. renderer.setSize( window.innerWidth, window.innerHeight );
  703. }
  704. function animate() {
  705. group.rotation.y += 0.001;
  706. renderer.render( scene, camera );
  707. }
  708. </script>
  709. </body>
  710. </html>
粤ICP备19079148号