| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <title>three.js webgpu - loft geometry</title>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
- <meta property="og:title" content="three.js webgpu - loft geometry">
- <meta property="og:type" content="website">
- <meta property="og:url" content="https://threejs.org/examples/webgpu_geometry_loft.html">
- <meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_geometry_loft.jpg">
- <link type="text/css" rel="stylesheet" href="example.css">
- </head>
- <body>
- <div id="info">
- <a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
- <div class="title-wrapper">
- <a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Loft Geometry</span>
- </div>
- <small>
- Surfaces generated through cross sections, textured procedurally with TSL.
- </small>
- </div>
- <script type="importmap">
- {
- "imports": {
- "three": "../build/three.webgpu.js",
- "three/webgpu": "../build/three.webgpu.js",
- "three/tsl": "../build/three.tsl.js",
- "three/addons/": "./jsm/"
- }
- }
- </script>
- <script type="module">
- import * as THREE from 'three/webgpu';
- 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';
- import { Inspector } from 'three/addons/inspector/Inspector.js';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
- import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
- import { LoftGeometry } from 'three/addons/geometries/LoftGeometry.js';
- let group, camera, scene, renderer;
- init();
- async function init() {
- renderer = new THREE.WebGPURenderer( { antialias: true } );
- renderer.setPixelRatio( window.devicePixelRatio );
- renderer.setSize( window.innerWidth, window.innerHeight );
- renderer.setAnimationLoop( animate );
- renderer.toneMapping = THREE.NeutralToneMapping;
- renderer.shadowMap.enabled = true;
- renderer.shadowMap.type = THREE.PCFShadowMap;
- renderer.inspector = new Inspector();
- document.body.appendChild( renderer.domElement );
- await renderer.init();
- const pmremGenerator = new THREE.PMREMGenerator( renderer );
- scene = new THREE.Scene();
- scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
- scene.environmentIntensity = 0.4;
- // a vignette in the background
- const background = screenUV.distance( .5 ).mix( color( 0x5d5d84 ), color( 0x2e2e44 ) );
- scene.backgroundNode = background;
- // camera
- camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
- camera.position.set( 0, 15, 40 );
- // controls
- const controls = new OrbitControls( camera, renderer.domElement );
- controls.minDistance = 15;
- controls.maxDistance = 50; // stay inside the curtain
- controls.target.set( 0, - 3, 0 );
- controls.update();
- // everything stands on a rotating floor. all surface detail is
- // procedural: the loft uvs run along the loft ( uv().x ) and around
- // the sections ( uv().y ), so patterns can follow the geometry
- group = new THREE.Group();
- scene.add( group );
- // floor: large, softly mottled, running under the curtain so its
- // edge is never seen
- const floorMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 1 } );
- floorMaterial.colorNode = color( 0x555577 ).mul( mx_noise_float( positionLocal.mul( 0.4 ) ).mul( 0.1 ).add( 0.95 ) );
- const floor = new THREE.Mesh(
- new THREE.CircleGeometry( 58, 64 ).rotateX( - Math.PI / 2 ),
- floorMaterial
- );
- floor.position.y = - 5.01;
- floor.receiveShadow = true;
- group.add( floor );
- // a theater curtain encircles the exhibition
- const curtainMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.9, side: THREE.DoubleSide } );
- curtainMaterial.colorNode = color( 0x86222e ).mul( mx_noise_float( vec3( uv().y.mul( 300 ), uv().x.mul( 6 ), 0 ) ).mul( 0.08 ).add( 0.96 ) );
- const curtain = new THREE.Mesh( createCurtainGeometry(), curtainMaterial );
- group.add( curtain );
- // light
- const light = new THREE.DirectionalLight( 0xffffff, 3 );
- light.position.set( 18, 30, 12 );
- light.castShadow = true;
- light.shadow.camera.left = - 60;
- light.shadow.camera.right = 60;
- light.shadow.camera.top = 60;
- light.shadow.camera.bottom = - 60;
- light.shadow.camera.far = 110;
- light.shadow.mapSize.set( 4096, 4096 );
- light.shadow.bias = - 0.0005;
- scene.add( light );
- // pedestals: polished marble. the veins meander and branch along the
- // zero crossings of a domain warped fractal noise — a sharp dark
- // core inside a soft halo — over a gently clouded white base
- const p = positionLocal.mul( 0.9 );
- const vein = mx_fractal_noise_float( p.add( mx_fractal_noise_float( p.mul( 0.4 ), 3 ).mul( 2 ) ), 4 ).abs().oneMinus();
- const fine = mx_fractal_noise_float( p.mul( 3 ).add( 11 ), 3 ).abs().oneMinus();
- const veining = vein.pow( 4 ).mul( 0.3 ).add( vein.pow( 12 ).mul( 0.7 ) ).add( fine.pow( 14 ).mul( 0.2 ) );
- const clouds = mx_noise_float( p.mul( 0.5 ) ).mul( 0.5 ).add( 0.5 );
- const pedestalMaterial = new THREE.MeshStandardNodeMaterial();
- pedestalMaterial.colorNode = mix( mix( color( 0xf4f4f7 ), color( 0xeeeef2 ), clouds ), color( 0xd0d0d5 ), veining );
- pedestalMaterial.roughnessNode = veining.mul( 0.14 ).add( 0.07 );
- pedestalMaterial.envMapIntensity = 1.5;
- function addPedestal( x, z, radius, height ) {
- const pedestal = new THREE.Mesh( createPedestalGeometry( radius, height ), pedestalMaterial );
- pedestal.position.set( x, - 5, z );
- pedestal.rotation.y = x * 0.7 + z * 1.3; // so the marbling differs per pedestal
- group.add( pedestal );
- return - 5 + height; // the top of the pedestal
- }
- // a coffee set: a cup and a saucer lofted from the bottom center, up
- // one side of the wall and back down the inside, and a handle swept
- // along a spline. the porcelain glaze has a faint waviness
- const porcelain = new THREE.MeshStandardNodeMaterial();
- porcelain.roughnessNode = mx_noise_float( positionLocal.mul( 6 ) ).mul( 0.08 ).add( 0.2 );
- porcelain.normalNode = bumpMap( mx_noise_float( positionLocal.mul( 2 ) ).mul( 0.05 ) );
- const coffee = new THREE.Group();
- coffee.position.y = addPedestal( 0, 0, 3.6, 2.2 );
- coffee.scale.setScalar( 0.75 );
- group.add( coffee );
- const saucer = new THREE.Mesh( createSaucerGeometry(), porcelain );
- coffee.add( saucer );
- const cup = new THREE.Mesh( createCupGeometry(), porcelain );
- cup.position.y = 0.3;
- coffee.add( cup );
- const handle = new THREE.Mesh( createHandleGeometry(), porcelain );
- handle.position.y = 0.3;
- handle.rotation.y = 0.15;
- coffee.add( handle );
- // the coffee has a lazy swirl on its surface
- const liquidMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.08 } );
- liquidMaterial.colorNode = mix( color( 0x2b1a12 ), color( 0x4a2c1a ), mx_noise_float( positionLocal.mul( 1.5 ) ).mul( 0.5 ).add( 0.5 ) );
- const liquid = new THREE.Mesh(
- new THREE.CircleGeometry( 2, 48 ).rotateX( - Math.PI / 2 ),
- liquidMaterial
- );
- liquid.position.y = 3.6;
- coffee.add( liquid );
- // a vase: circular sections with a varying radius. the glaze pools
- // in throwing rings along the profile
- const vaseRings = sin( uv().x.mul( 160 ) );
- const vaseMaterial = new THREE.MeshStandardNodeMaterial( { side: THREE.DoubleSide } );
- vaseMaterial.colorNode = mix( color( 0x2e6f9e ), color( 0x82b8d8 ), mx_noise_float( positionLocal.mul( 1.2 ) ).mul( 0.5 ).add( 0.5 ) );
- vaseMaterial.roughnessNode = vaseRings.mul( 0.08 ).add( 0.3 );
- vaseMaterial.normalNode = bumpMap( vaseRings.mul( 0.012 ) );
- const vase = new THREE.Mesh( createVaseGeometry(), vaseMaterial );
- vase.position.set( - 10.5, addPedestal( - 10.5, - 10.5, 3.6, 1.1 ), - 10.5 );
- group.add( vase );
- // a seashell: circular sections that grow while sweeping along a
- // logarithmic spiral, with growth bands and fine ridges along it
- const shellMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.5, side: THREE.DoubleSide } );
- shellMaterial.colorNode = mix( color( 0xc9a87f ), color( 0xf2e6d8 ), mx_noise_float( vec3( uv().x.mul( 24 ), 0, 0 ) ).mul( 0.5 ).add( 0.5 ) );
- shellMaterial.normalNode = bumpMap( sin( uv().x.mul( 480 ) ).mul( 0.02 ) );
- const shell = new THREE.Mesh( createShellGeometry(), shellMaterial );
- shell.position.set( 10.5, addPedestal( 10.5, - 10.5, 3.6, 1.1 ) + 1.92, - 10.5 );
- shell.rotation.y = - Math.PI / 2;
- shell.scale.setScalar( 0.8 );
- group.add( shell );
- // a twisted star: non-circular sections that rotate and scale along
- // the loft, in sandy terracotta
- const starMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.6 } );
- starMaterial.colorNode = color( 0xcc5544 ).mul( mx_noise_float( positionLocal.mul( 3 ) ).mul( 0.12 ).add( 0.94 ) );
- starMaterial.normalNode = bumpMap( mx_noise_float( positionLocal.mul( 50 ) ).mul( 0.008 ) );
- const star = new THREE.Mesh( createStarGeometry(), starMaterial );
- star.position.set( - 10.5, addPedestal( - 10.5, 10.5, 3.6, 1.1 ), 10.5 );
- star.scale.setScalar( 0.8 );
- group.add( star );
- // a ribbon: open two-point sections ( closed: false ), in gold
- // brushed along its length
- const brush = mx_noise_float( vec3( uv().x.mul( 6 ), uv().y.mul( 160 ), 0 ) );
- const ribbonMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xffcc44, metalness: 1, side: THREE.DoubleSide } );
- ribbonMaterial.roughnessNode = brush.mul( 0.08 ).add( 0.1 );
- ribbonMaterial.normalNode = bumpMap( brush.mul( 0.004 ) );
- ribbonMaterial.envMapIntensity = 2.5;
- const ribbon = new THREE.Mesh( createRibbonGeometry(), ribbonMaterial );
- ribbon.position.set( 10.5, addPedestal( 10.5, 10.5, 3.6, 1.1 ), 10.5 );
- group.add( ribbon );
- // a toothpaste tube: circular sections that morph into a flat
- // crimped seam, with stripes printed around the body
- const tubeU = uv().x;
- const tealStripe = smoothstep( 0.48, 0.5, tubeU ).sub( smoothstep( 0.6, 0.62, tubeU ) );
- const redStripe = smoothstep( 0.66, 0.68, tubeU ).sub( smoothstep( 0.72, 0.74, tubeU ) );
- const tubeMaterial = new THREE.MeshStandardNodeMaterial( { roughness: 0.25 } );
- tubeMaterial.colorNode = mix( mix( color( 0xf2f2f2 ), color( 0x2aa6b8 ), tealStripe ), color( 0xd0543a ), redStripe );
- const tube = new THREE.Mesh( createToothpasteGeometry(), tubeMaterial );
- tube.position.set( 7.8, addPedestal( 7.8, 0, 2, 1.6 ), 0 );
- tube.rotation.y = 0.5;
- group.add( tube );
- // a pumpkin: lobed sections around a squashed profile. the shading
- // follows the same crease function as the geometry, so the narrow
- // creases are darker and rougher than the broad lobes
- const lobe = cos( uv().y.mul( Math.PI * 7 ) ).abs().pow( 0.35 );
- const pumpkinMaterial = new THREE.MeshStandardNodeMaterial();
- pumpkinMaterial.colorNode = mix(
- mix( color( 0x9c4f16 ), color( 0xe6913d ), lobe ),
- color( 0x8a7a2e ), // greener around the stem
- smoothstep( 0.88, 1, uv().x ).mul( 0.6 )
- );
- pumpkinMaterial.roughnessNode = float( 0.7 ).sub( lobe.mul( 0.2 ) );
- pumpkinMaterial.normalNode = bumpMap( mx_noise_float( vec3( uv().y.mul( 120 ), uv().x.mul( 5 ), 0 ) ).mul( 0.01 ) );
- const pumpkinY = addPedestal( 0, 7.8, 2, 1.6 );
- const pumpkin = new THREE.Mesh( createPumpkinGeometry(), pumpkinMaterial );
- pumpkin.position.set( 0, pumpkinY, 7.8 );
- group.add( pumpkin );
- const pumpkinStemMaterial = new THREE.MeshStandardNodeMaterial( { color: 0x667744 } );
- pumpkinStemMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 20 ) ).mul( 0.2 ).add( 0.7 );
- const pumpkinStem = new THREE.Mesh( createPumpkinStemGeometry(), pumpkinStemMaterial );
- pumpkinStem.position.set( 0, pumpkinY, 7.8 );
- group.add( pumpkinStem );
- // a mushroom: a cap that folds under its own rim. the cap uvs run
- // from under the rim ( u 0 ) over the edge to the top center ( u 1 ),
- // so the red dome with its raised warts and the pale underside with
- // its radial gills can share one material
- const capU = uv().x;
- const dome = smoothstep( 0.42, 0.58, capU );
- const warts = smoothstep( 0.18, 0.38, mx_worley_noise_float( positionLocal.mul( 2.4 ) ) ).oneMinus().mul( dome );
- const gills = sin( uv().y.mul( Math.PI * 120 ) ).mul( 0.5 ).add( 0.5 ).mul( dome.oneMinus() );
- const capMaterial = new THREE.MeshStandardNodeMaterial();
- capMaterial.colorNode = mix(
- mix( color( 0xe8dcc4 ), color( 0xbfae8e ), gills ),
- mix( color( 0xa32d20 ), color( 0xf2e9d8 ), warts ),
- dome
- );
- capMaterial.roughnessNode = float( 0.55 ).sub( dome.mul( 0.2 ) ).add( warts.mul( 0.25 ) );
- capMaterial.normalNode = bumpMap( warts.mul( 0.08 ).sub( gills.mul( 0.015 ) ) );
- const mushroomY = addPedestal( - 7.8, 0, 2, 1.6 );
- const mushroomCap = new THREE.Mesh( createMushroomCapGeometry(), capMaterial );
- mushroomCap.position.set( - 7.8, mushroomY, 0 );
- group.add( mushroomCap );
- const mushroomStemMaterial = new THREE.MeshStandardNodeMaterial();
- mushroomStemMaterial.colorNode = color( 0xe5d5b5 ).mul( mx_noise_float( vec3( uv().y.mul( 24 ), uv().x.mul( 2 ), 0 ) ).mul( 0.1 ).add( 0.94 ) );
- mushroomStemMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 12 ) ).mul( 0.15 ).add( 0.55 );
- const mushroomStem = new THREE.Mesh( createMushroomStemGeometry(), mushroomStemMaterial );
- mushroomStem.position.set( - 7.8, mushroomY, 0 );
- group.add( mushroomStem );
- // a goblet: a foot, a thin stem and a bowl with folded back walls,
- // in hammered copper
- const dents = mx_worley_noise_float( positionLocal.mul( 5 ) );
- const gobletMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xb87333, metalness: 1 } );
- gobletMaterial.roughnessNode = dents.mul( 0.18 ).add( 0.12 );
- gobletMaterial.normalNode = bumpMap( dents.mul( 0.1 ) );
- gobletMaterial.envMapIntensity = 2;
- const goblet = new THREE.Mesh( createGobletGeometry(), gobletMaterial );
- goblet.position.set( 0, addPedestal( 0, - 7.8, 2, 1.6 ), - 7.8 );
- group.add( goblet );
- // a rope barrier around the exhibition: brass stanchions, and a
- // twisted cord sagging between them
- const brassMaterial = new THREE.MeshStandardNodeMaterial( { color: 0xc9a86a, metalness: 1 } );
- brassMaterial.roughnessNode = mx_noise_float( positionLocal.mul( 10 ) ).mul( 0.06 ).add( 0.16 );
- brassMaterial.envMapIntensity = 2;
- const ropeMaterial = new THREE.MeshStandardNodeMaterial( { color: 0x8a2433, roughness: 0.65 } );
- ropeMaterial.normalNode = bumpMap( sin( uv().x.mul( 200 ).add( uv().y.mul( Math.PI * 2 ) ) ).mul( 0.015 ) ); // the twist of the cord
- const posts = 14;
- const barrierRadius = 20;
- const stanchionGeometry = createStanchionGeometry();
- const ropeGeometry = createRopeGeometry( 2 * barrierRadius * Math.sin( Math.PI / posts ) );
- for ( let i = 0; i < posts; i ++ ) {
- const angle = ( i + 0.5 ) / posts * Math.PI * 2;
- const post = new THREE.Mesh( stanchionGeometry, brassMaterial );
- post.position.set( Math.sin( angle ) * barrierRadius, - 5, Math.cos( angle ) * barrierRadius );
- group.add( post );
- const mid = angle + Math.PI / posts;
- const midRadius = barrierRadius * Math.cos( Math.PI / posts );
- const rope = new THREE.Mesh( ropeGeometry, ropeMaterial );
- rope.position.set( Math.sin( mid ) * midRadius, - 5 + 2.05, Math.cos( mid ) * midRadius );
- rope.rotation.y = mid;
- group.add( rope );
- }
- // shadows
- group.traverse( ( child ) => {
- if ( child.isMesh ) child.castShadow = child.receiveShadow = true;
- } );
- floor.castShadow = false;
- liquid.castShadow = false;
- // every loft remembers the sections it was skinned through in
- // geometry.parameters, so a skeleton of rings can be rebuilt from
- // the meshes themselves
- const skeleton = new THREE.Group();
- skeleton.visible = false;
- group.add( skeleton );
- const lofts = [];
- group.traverse( ( child ) => {
- if ( child.isMesh && child.geometry.type === 'LoftGeometry' ) lofts.push( child );
- } );
- const lineMaterial = new THREE.LineBasicMaterial( { color: 0xaaccee } );
- group.updateMatrixWorld( true );
- for ( const mesh of lofts ) {
- const { sections, closed } = mesh.geometry.parameters;
- const step = Math.max( 1, Math.round( sections.length / 20 ) );
- const positions = [];
- function addRing( ring ) {
- const segments = closed ? ring.length : ring.length - 1;
- for ( let j = 0; j < segments; j ++ ) {
- const a = ring[ j ], b = ring[ ( j + 1 ) % ring.length ];
- positions.push( a.x, a.y, a.z, b.x, b.y, b.z );
- }
- }
- for ( let i = 0; i < sections.length; i += step ) addRing( sections[ i ] );
- if ( ( sections.length - 1 ) % step !== 0 ) addRing( sections[ sections.length - 1 ] );
- const geometry = new THREE.BufferGeometry();
- geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
- const lines = new THREE.LineSegments( geometry, lineMaterial );
- lines.applyMatrix4( mesh.matrixWorld );
- skeleton.add( lines );
- }
- // parameters
- const gui = renderer.inspector.createParameters( 'Parameters' );
- gui.add( { sections: false }, 'sections' ).onChange( ( value ) => {
- skeleton.visible = value;
- liquid.visible = ! value;
- for ( const mesh of lofts ) mesh.visible = ! value;
- } );
- gui.add( { wireframe: false }, 'wireframe' ).onChange( ( value ) => {
- group.traverse( ( child ) => {
- if ( child.isMesh ) {
- child.material.wireframe = value;
- child.material.needsUpdate = true;
- }
- } );
- } );
- //
- window.addEventListener( 'resize', onWindowResize );
- }
- // revolves a smoothed 2d profile ( x = radius, y = height ) into
- // circular sections, like a lathe
- function createRevolvedSections( profile, divisions, segments ) {
- const points = new THREE.SplineCurve( profile ).getPoints( divisions );
- const sections = [];
- for ( let i = 0; i <= divisions; i ++ ) {
- const point = points[ i ];
- const ring = [];
- for ( let j = 0; j < segments; j ++ ) {
- const angle = j / segments * Math.PI * 2;
- ring.push( new THREE.Vector3( Math.sin( angle ) * point.x, point.y, Math.cos( angle ) * point.x ) );
- }
- sections.push( ring );
- }
- return sections;
- }
- function createPedestalGeometry( radius, height ) {
- // a stepped plinth, a tapered shaft and a cornice; revolved without
- // smoothing, one ring per profile point. doubled points split the
- // vertex normals, keeping those turnings crisp
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( radius * 1.06, 0 ),
- new THREE.Vector2( radius * 1.06, height * 0.1 ),
- new THREE.Vector2( radius * 1.06, height * 0.1 ),
- new THREE.Vector2( radius * 0.98, height * 0.16 ),
- new THREE.Vector2( radius * 0.94, height * 0.55 ),
- new THREE.Vector2( radius * 0.97, height * 0.84 ),
- new THREE.Vector2( radius * 1.04, height * 0.88 ),
- new THREE.Vector2( radius * 1.04, height * 0.97 ),
- new THREE.Vector2( radius * 1.04, height * 0.97 ),
- new THREE.Vector2( radius * 0.98, height ),
- new THREE.Vector2( radius * 0.98, height ),
- new THREE.Vector2( radius * 0.5, height - 0.004 ),
- new THREE.Vector2( 0.2, height )
- ];
- const sections = [];
- for ( const point of profile ) {
- const ring = [];
- for ( let j = 0; j < 48; j ++ ) {
- const angle = j / 48 * Math.PI * 2;
- ring.push( new THREE.Vector3( Math.sin( angle ) * point.x, point.y, Math.cos( angle ) * point.x ) );
- }
- sections.push( ring );
- }
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createCupGeometry() {
- // from the bottom center, up the egg shaped outer wall, over the lip
- // and back down the inner wall
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( 0.7, 0.04 ),
- new THREE.Vector2( 1.05, 0.1 ),
- new THREE.Vector2( 1.75, 0.55 ),
- new THREE.Vector2( 2.25, 1.45 ),
- new THREE.Vector2( 2.36, 2.2 ),
- new THREE.Vector2( 2.3, 3.1 ),
- new THREE.Vector2( 2.22, 3.82 ),
- new THREE.Vector2( 2.18, 3.95 ),
- new THREE.Vector2( 2.06, 3.8 ),
- new THREE.Vector2( 2.18, 2.2 ),
- new THREE.Vector2( 1.5, 0.75 ),
- new THREE.Vector2( 0.9, 0.55 ),
- new THREE.Vector2( 0.2, 0.62 )
- ];
- const sections = createRevolvedSections( profile, 120, 64 );
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createSaucerGeometry() {
- // from the bottom center, out along the underside, around the thin
- // rim and back across the gently dished top
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( 1.3, 0.08 ),
- new THREE.Vector2( 2.6, 0.35 ),
- new THREE.Vector2( 3.7, 0.8 ),
- new THREE.Vector2( 4.2, 1 ),
- new THREE.Vector2( 3.4, 0.68 ),
- new THREE.Vector2( 2, 0.3 ),
- new THREE.Vector2( 1, 0.18 ),
- new THREE.Vector2( 0.2, 0.26 )
- ];
- const sections = createRevolvedSections( profile, 120, 64 );
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createHandleGeometry() {
- // an ear shaped loop swept along a spline, slightly tapering; the
- // ends slim down so they stay buried inside the thin cup wall
- const path = new THREE.SplineCurve( [
- new THREE.Vector2( 2.2, 3.3 ),
- new THREE.Vector2( 2.9, 3.45 ),
- new THREE.Vector2( 3.6, 2.85 ),
- new THREE.Vector2( 3.65, 1.95 ),
- new THREE.Vector2( 3, 1.2 ),
- new THREE.Vector2( 1.78, 1.05 )
- ] );
- const divisions = 60;
- const points = path.getPoints( divisions );
- const sections = [];
- for ( let i = 0; i <= divisions; i ++ ) {
- const t = i / divisions;
- const point = points[ i ];
- const tangent = path.getTangent( t );
- const scale = ( 1 - 0.25 * t )
- * ( 0.28 + 0.72 * THREE.MathUtils.smoothstep( t, 0, 0.12 ) )
- * ( 0.28 + 0.72 * ( 1 - THREE.MathUtils.smoothstep( t, 0.88, 1 ) ) );
- const a = 0.22 * scale; // in the plane of the loop
- const b = 0.27 * scale; // across the loop
- const ring = [];
- for ( let j = 0; j < 16; j ++ ) {
- const phi = j / 16 * Math.PI * 2;
- const radial = a * Math.cos( phi );
- ring.push( new THREE.Vector3(
- point.x - radial * tangent.y,
- point.y + radial * tangent.x,
- b * Math.sin( phi )
- ) );
- }
- sections.push( ring );
- }
- return new LoftGeometry( sections );
- }
- function createVaseGeometry() {
- // a full belly, a slender waist and a flared lip
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( 1.05, 0.05 ),
- new THREE.Vector2( 1.5, 0.3 ),
- new THREE.Vector2( 2.1, 1.4 ),
- new THREE.Vector2( 2.2, 2.3 ),
- new THREE.Vector2( 1.8, 3.6 ),
- new THREE.Vector2( 1.2, 4.8 ),
- new THREE.Vector2( 0.85, 5.8 ),
- new THREE.Vector2( 0.72, 6.6 ),
- new THREE.Vector2( 0.8, 7.3 ),
- new THREE.Vector2( 1.1, 7.9 ),
- new THREE.Vector2( 1.3, 8.2 )
- ];
- const sections = createRevolvedSections( profile, 100, 48 );
- return new LoftGeometry( sections, { capStart: true } );
- }
- function createShellGeometry() {
- const turns = 3;
- const growth = 0.18;
- const scale = Math.exp( growth * turns * Math.PI * 2 );
- const sections = [];
- for ( let i = 0; i <= 150; i ++ ) {
- const t = i / 150;
- const angle = turns * Math.PI * 2 * t;
- const e = Math.exp( growth * angle ) / scale;
- const pathRadius = 3 * e;
- const sectionRadius = 2.4 * e;
- const sin = Math.sin( angle );
- const cos = Math.cos( angle );
- const points = [];
- for ( let j = 0; j < 32; j ++ ) {
- const phi = j / 32 * Math.PI * 2;
- const r = pathRadius + sectionRadius * Math.cos( phi );
- points.push( new THREE.Vector3( r * sin, 4.5 * ( 1 - e ) + sectionRadius * Math.sin( phi ), r * cos ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections );
- }
- function createStarGeometry() {
- const sections = [];
- for ( let i = 0; i <= 60; i ++ ) {
- const t = i / 60;
- const twist = t * Math.PI / 3;
- const scale = 1 - 0.35 * Math.sin( t * Math.PI );
- const points = [];
- for ( let j = 0; j < 96; j ++ ) {
- const angle = j / 96 * Math.PI * 2;
- const radius = ( 2.4 + 0.7 * Math.cos( 5 * angle ) ) * scale;
- points.push( new THREE.Vector3( Math.sin( angle + twist ) * radius, t * 10, Math.cos( angle + twist ) * radius ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createRibbonGeometry() {
- const sections = [];
- for ( let i = 0; i <= 120; i ++ ) {
- const t = i / 120;
- const angle = t * Math.PI * 2 * 2.5;
- const sin = Math.sin( angle );
- const cos = Math.cos( angle );
- sections.push( [
- new THREE.Vector3( 3 * sin, t * 7.5, 3 * cos ),
- new THREE.Vector3( 3 * sin, t * 7.5 + 2, 3 * cos )
- ] );
- }
- return new LoftGeometry( sections, { closed: false } );
- }
- function createToothpasteGeometry() {
- // a cap, a shoulder, and a body whose circular sections flatten
- // into a wide crimped seam at the top
- const sections = [];
- for ( let i = 0; i <= 80; i ++ ) {
- const t = i / 80;
- const radius = 0.5 + 0.28 * THREE.MathUtils.smoothstep( t, 0.08, 0.2 );
- const crimp = THREE.MathUtils.smoothstep( t, 0.3, 0.95 );
- const width = radius * ( 1 - crimp ) + 1.15 * crimp;
- const depth = radius * ( 1 - crimp ) + 0.05 * crimp;
- const points = [];
- for ( let j = 0; j < 48; j ++ ) {
- const angle = j / 48 * Math.PI * 2;
- points.push( new THREE.Vector3( Math.sin( angle ) * width, t * 4.2, Math.cos( angle ) * depth ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createPumpkinGeometry() {
- // a squashed sphere with broad lobes split by narrow creases, and
- // a sunken hollow around the stem
- const sections = [];
- for ( let i = 0; i <= 60; i ++ ) {
- const t = i / 60;
- const angle = Math.PI * ( 0.03 + 0.94 * t );
- const radius = 1.85 * Math.pow( Math.sin( angle ), 0.62 );
- const creases = 0.15 * Math.sin( Math.PI * t );
- const y = 2.05 * t - 0.75 * THREE.MathUtils.smoothstep( t, 0.8, 1 );
- const points = [];
- for ( let j = 0; j < 96; j ++ ) {
- const theta = j / 96 * Math.PI * 2;
- const lobe = Math.pow( Math.abs( Math.cos( 3.5 * theta ) ), 0.35 );
- const r = radius * ( 1 - creases + creases * lobe );
- points.push( new THREE.Vector3( Math.sin( theta ) * r, y, Math.cos( theta ) * r ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createPumpkinStemGeometry() {
- // a ribbed stalk, flared at its base, that rises out of the hollow
- // and leans over
- const sections = [];
- for ( let i = 0; i <= 30; i ++ ) {
- const t = i / 30;
- const radius = 0.2 - 0.09 * t + 0.14 * Math.pow( 1 - t, 4 );
- const lean = 0.45 * t * t;
- const points = [];
- for ( let j = 0; j < 32; j ++ ) {
- const angle = j / 32 * Math.PI * 2;
- const r = radius * ( 0.92 + 0.13 * Math.pow( Math.abs( Math.cos( 2.5 * angle ) ), 0.5 ) );
- points.push( new THREE.Vector3( lean + Math.sin( angle ) * r, 1.3 + 1.15 * t, Math.cos( angle ) * r ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections, { capEnd: true } );
- }
- function createMushroomCapGeometry() {
- // from under the rim, around the edge and over the dome
- const profile = [
- new THREE.Vector2( 0.35, 2.02 ),
- new THREE.Vector2( 1.1, 2 ),
- new THREE.Vector2( 1.65, 2.15 ),
- new THREE.Vector2( 1.78, 2.4 ),
- new THREE.Vector2( 1.5, 2.85 ),
- new THREE.Vector2( 0.95, 3.18 ),
- new THREE.Vector2( 0.2, 3.32 )
- ];
- const sections = createRevolvedSections( profile, 80, 48 );
- return new LoftGeometry( sections, { capEnd: true } );
- }
- function createMushroomStemGeometry() {
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( 0.55, 0.05 ),
- new THREE.Vector2( 0.45, 0.9 ),
- new THREE.Vector2( 0.4, 1.7 ),
- new THREE.Vector2( 0.42, 2.3 )
- ];
- const sections = createRevolvedSections( profile, 60, 32 );
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createGobletGeometry() {
- // a foot, a thin stem, and a bowl that folds back down inside
- const profile = [
- new THREE.Vector2( 0.2, 0 ),
- new THREE.Vector2( 1.25, 0.05 ),
- new THREE.Vector2( 1.35, 0.2 ),
- new THREE.Vector2( 0.6, 0.5 ),
- new THREE.Vector2( 0.28, 0.9 ),
- new THREE.Vector2( 0.24, 1.7 ),
- new THREE.Vector2( 0.7, 2.15 ),
- new THREE.Vector2( 1.15, 2.8 ),
- new THREE.Vector2( 1.28, 3.5 ),
- new THREE.Vector2( 1.27, 3.62 ),
- new THREE.Vector2( 1.16, 3.5 ),
- new THREE.Vector2( 0.95, 2.85 ),
- new THREE.Vector2( 0.45, 2.25 ),
- new THREE.Vector2( 0.2, 2.32 )
- ];
- const sections = createRevolvedSections( profile, 140, 48 );
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createStanchionGeometry() {
- // a flat disc base, a slender pole and a small ball finial
- const profile = [
- new THREE.Vector2( 0.16, 0 ),
- new THREE.Vector2( 0.42, 0.04 ),
- new THREE.Vector2( 0.46, 0.12 ),
- new THREE.Vector2( 0.28, 0.22 ),
- new THREE.Vector2( 0.08, 0.38 ),
- new THREE.Vector2( 0.06, 1 ),
- new THREE.Vector2( 0.06, 1.85 ),
- new THREE.Vector2( 0.11, 1.95 ),
- new THREE.Vector2( 0.19, 2.08 ),
- new THREE.Vector2( 0.2, 2.2 ),
- new THREE.Vector2( 0.11, 2.3 ),
- new THREE.Vector2( 0.04, 2.34 )
- ];
- const sections = createRevolvedSections( profile, 80, 24 );
- return new LoftGeometry( sections, { capStart: true, capEnd: true } );
- }
- function createRopeGeometry( length ) {
- // a cord sagging between two stanchions, with both ends buried in
- // their poles
- const sag = 0.9;
- const sections = [];
- for ( let i = 0; i <= 40; i ++ ) {
- const t = i / 40;
- const x = ( t - 0.5 ) * length;
- const y = - sag * 4 * t * ( 1 - t );
- // the in plane tangent orients the rings along the curve
- const tx = length;
- const ty = - sag * 4 * ( 1 - 2 * t );
- const tl = Math.sqrt( tx * tx + ty * ty );
- const points = [];
- for ( let j = 0; j < 16; j ++ ) {
- const phi = j / 16 * Math.PI * 2;
- const radial = 0.08 * Math.cos( phi );
- points.push( new THREE.Vector3(
- x - radial * ty / tl,
- y + radial * tx / tl,
- 0.08 * Math.sin( phi )
- ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections );
- }
- function createCurtainGeometry() {
- // rows of pleated rings hanging from above; the folds deepen and
- // drift sideways as they fall
- const sections = [];
- for ( let i = 0; i <= 30; i ++ ) {
- const t = i / 30;
- const y = - 5 + 25 * t;
- const points = [];
- for ( let j = 0; j < 480; j ++ ) {
- const s = j / 480;
- const folds = ( 1.2 - 0.5 * t ) * Math.sin( s * Math.PI * 2 * 48 + t * 2 );
- const sway = 0.5 * Math.sin( s * Math.PI * 2 * 5 + t * 3 );
- const theta = s * Math.PI * 2;
- const r = 55 + folds + sway;
- points.push( new THREE.Vector3( Math.sin( theta ) * r, y, Math.cos( theta ) * r ) );
- }
- sections.push( points );
- }
- return new LoftGeometry( sections );
- }
- function onWindowResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize( window.innerWidth, window.innerHeight );
- }
- function animate() {
- group.rotation.y += 0.001;
- renderer.render( scene, camera );
- }
- </script>
- </body>
- </html>
|