/** * Parametric Surfaces Geometry * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html */ import { BufferGeometry, Float32BufferAttribute, Vector3 } from 'three'; class ParametricGeometry extends BufferGeometry { constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) { super(); this.type = 'ParametricGeometry'; this.parameters = { func: func, slices: slices, stacks: stacks }; // buffers const indices = []; const vertices = []; const normals = []; const uvs = []; const EPS = 0.00001; const normal = new Vector3(); const p0 = new Vector3(), p1 = new Vector3(); const pu = new Vector3(), pv = new Vector3(); // generate vertices, normals and uvs const sliceCount = slices + 1; for ( let i = 0; i <= stacks; i ++ ) { const v = i / stacks; for ( let j = 0; j <= slices; j ++ ) { const u = j / slices; // vertex func( u, v, p0 ); vertices.push( p0.x, p0.y, p0.z ); // normal // approximate tangent vectors via finite differences if ( u - EPS >= 0 ) { func( u - EPS, v, p1 ); pu.subVectors( p0, p1 ); } else { func( u + EPS, v, p1 ); pu.subVectors( p1, p0 ); } if ( v - EPS >= 0 ) { func( u, v - EPS, p1 ); pv.subVectors( p0, p1 ); } else { func( u, v + EPS, p1 ); pv.subVectors( p1, p0 ); } // cross product of tangent vectors returns surface normal normal.crossVectors( pu, pv ).normalize(); normals.push( normal.x, normal.y, normal.z ); // uv uvs.push( u, v ); } } // generate indices for ( let i = 0; i < stacks; i ++ ) { for ( let j = 0; j < slices; j ++ ) { const a = i * sliceCount + j; const b = i * sliceCount + j + 1; const c = ( i + 1 ) * sliceCount + j + 1; const d = ( i + 1 ) * sliceCount + j; // faces one and two indices.push( a, b, d ); indices.push( b, c, d ); } } // build geometry this.setIndex( indices ); this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } } export { ParametricGeometry };