├── .gitignore
├── .vscode
└── settings.json
├── LICENSE.md
├── README.md
├── assets
├── css
│ └── main.css
└── images
│ ├── github.svg
│ ├── logo.jpg
│ ├── mastodon.svg
│ └── twitter.svg
├── index.html
├── package.json
├── src
├── glsl
│ ├── constraints.frag.js
│ ├── integrate.frag.js
│ ├── mouse.frag.js
│ ├── normals.frag.js
│ ├── through.frag.js
│ └── through.vert.js
├── main.js
├── modules
│ ├── background.js
│ ├── cloth.js
│ ├── fbo.js
│ ├── lights.js
│ ├── materials.js
│ ├── mouse.js
│ └── pre.js
└── textures
│ └── bmpMap.png
└── utils
└── externs.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .DS_Store?
3 | /node_modules
4 | .eslintignore
5 | *.js.map
6 | dev.html
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5501
3 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2018-2023 ScieCode
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## License
5 | This experiment is under MIT License.
6 |
--------------------------------------------------------------------------------
/assets/css/main.css:
--------------------------------------------------------------------------------
1 | /* === [IMPORTS] === */
2 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:600&display=swap');
3 |
4 | /* === [GLOBAL] === */
5 | body {
6 | margin: 0;
7 | position: fixed;
8 | }
9 |
10 | canvas {
11 | display: block;
12 | width: 100%;
13 | height: 100%;
14 | z-index: 1;
15 | }
16 |
17 | #footer {
18 | position: absolute;
19 | display: flex;
20 | align-items: center;
21 | right: 4vw;
22 | bottom: 5vh;
23 | user-select: none;
24 | z-index: 10;
25 | }
26 |
27 | .footers {
28 | font-weight: 600;
29 | display: block;
30 | color: #e4e5e6;
31 | font-size: 14px;
32 | text-rendering: optimizeLegibility;
33 | -webkit-font-smoothing: subpixel-antialiased;
34 | font-family: 'Open Sans', sans-serif;
35 | letter-spacing: 1.1px;
36 | user-select: none;
37 | margin: auto 5px;
38 | z-index: 10;
39 | }
40 |
41 | a.footers {
42 | pointer-events: all;
43 | }
44 |
45 | #twitter {
46 | display: block;
47 | height: 16px;
48 | aspect-ratio: 1;
49 | background-color: #e4e5e6;
50 | mask-image: url(../images/twitter.svg);
51 | mask-repeat: no-repeat;
52 | mask-size: contain;
53 | -webkit-mask-image: url(../images/twitter.svg);
54 | -webkit-mask-repeat: no-repeat;
55 | -webkit-mask-size: contain;
56 | }
57 |
58 | #mastodon {
59 | display: block;
60 | height: 16px;
61 | aspect-ratio: 1;
62 | background-color: #e4e5e6;
63 | mask-image: url(../images/mastodon.svg);
64 | mask-repeat: no-repeat;
65 | mask-size: contain;
66 | -webkit-mask-image: url(../images/mastodon.svg);
67 | -webkit-mask-repeat: no-repeat;
68 | -webkit-mask-size: contain;
69 | }
70 |
71 | #github {
72 | display: block;
73 | height: 16px;
74 | aspect-ratio: 1;
75 | background-color: #e4e5e6;
76 | mask-image: url(../images/github.svg);
77 | mask-repeat: no-repeat;
78 | mask-size: contain;
79 | -webkit-mask-image: url(../images/github.svg);
80 | -webkit-mask-repeat: no-repeat;
81 | -webkit-mask-size: contain;
82 | }
83 |
--------------------------------------------------------------------------------
/assets/images/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/images/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciecode/sheen/0745512693762050041320a5e4cc6e89daf6b415/assets/images/logo.jpg
--------------------------------------------------------------------------------
/assets/images/mastodon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/assets/images/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Sheen
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Sheen",
3 | "version": "1.0.0",
4 | "description": "GPGPU Cloth Simulation",
5 | "main": "src/main.js",
6 | "author": "sciecode",
7 | "license": "MIT",
8 | "homepage": "sciecode.github.io/sheen",
9 | "devDependencies": {
10 | "http-server": "^0.11.1"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/glsl/constraints.frag.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 | precision highp sampler2D;
4 |
5 | uniform vec2 tSize;
6 | uniform float order;
7 | uniform sampler2D tPosition0;
8 | uniform sampler2D tPosition1;
9 |
10 | uniform sampler2D tDistancesA;
11 | uniform sampler2D tDistancesB;
12 |
13 | uniform sampler2D tAdjacentsA;
14 | uniform sampler2D tAdjacentsB;
15 |
16 | // get vec2 tex coordinate from index
17 | vec2 getUV( float id ) {
18 |
19 | vec2 coords = vec2(
20 | floor( mod( ( id + 0.5 ), tSize.x ) ),
21 | floor( ( id + 0.5 ) / tSize.x )
22 | ) + 0.5;
23 |
24 | return coords / tSize;
25 |
26 | }
27 |
28 | // compute offset based on current distance and spring rest distance
29 | vec3 getDisplacement( vec3 point0, vec3 point1, float restDistance ) {
30 |
31 | float curDistance = distance( point0, point1 );
32 | if (curDistance < 0.0001) return vec3(0.0);
33 | return .5 * ( curDistance - restDistance ) * ( point1 - point0 ) / curDistance;
34 |
35 | }
36 |
37 | // pack float16 position into float32
38 | vec3 packPosition( vec2 uv ) {
39 |
40 | return ( texture2D( tPosition0, uv ).xyz + texture2D( tPosition1, uv ).xyz ) / 1024.0;
41 |
42 | }
43 |
44 | vec3 unpackPosition( vec3 pos ) {
45 |
46 | pos *= 1024.0;
47 |
48 | return ( order > 0.0 ) ? floor( pos ) : fract( pos );
49 |
50 | }
51 |
52 | void main() {
53 |
54 | vec3 displacement;
55 | vec2 uv = gl_FragCoord.xy / tSize.xy;
56 |
57 | // indices of adjacent vertices
58 | vec4 adjacentA = texture2D( tAdjacentsA, uv );
59 | vec4 adjacentB = texture2D( tAdjacentsB, uv );
60 |
61 | // distances of adjacent vertices
62 | vec4 distancesA = texture2D( tDistancesA, uv );
63 | vec4 distancesB = texture2D( tDistancesB, uv );
64 |
65 | // vertex position
66 | vec3 p0 = packPosition( uv );
67 |
68 | // adjacent vertices positions
69 | vec3 p1 = packPosition( getUV( adjacentA.x ) );
70 | vec3 p2 = packPosition( getUV( adjacentA.y ) );
71 | vec3 p3 = packPosition( getUV( adjacentA.z ) );
72 | vec3 p4 = packPosition( getUV( adjacentA.w ) );
73 | vec3 p5 = packPosition( getUV( adjacentB.x ) );
74 | vec3 p6 = packPosition( getUV( adjacentB.y ) );
75 |
76 | // spring-based displacement
77 | displacement += getDisplacement( p0, p1, distancesA.x );
78 | displacement += getDisplacement( p0, p2, distancesA.y );
79 | displacement += getDisplacement( p0, p3, distancesA.z );
80 | displacement += getDisplacement( p0, p4, distancesA.w );
81 | displacement += getDisplacement( p0, p5, distancesB.x );
82 | displacement += ( adjacentB.y > 0.0 ) ? getDisplacement( p0, p6, distancesB.y ) : vec3( 0 );
83 |
84 | p0 += displacement / ( ( adjacentB.y > 0.0 ) ? 6.0 : 5.0 );
85 |
86 | gl_FragColor = vec4( unpackPosition( p0 ), 1.0 );
87 |
88 | }
89 | `;
90 |
--------------------------------------------------------------------------------
/src/glsl/integrate.frag.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 | precision highp sampler2D;
4 |
5 | uniform vec2 tSize;
6 | uniform float dt;
7 | uniform float order;
8 | uniform sampler2D tOriginal;
9 | uniform sampler2D tPrevious0;
10 | uniform sampler2D tPrevious1;
11 | uniform sampler2D tPosition0;
12 | uniform sampler2D tPosition1;
13 |
14 | vec3 unpackPosition( vec3 pos ) {
15 |
16 | pos *= 1024.0;
17 |
18 | return ( order > 0.0 ) ? floor( pos ) : fract( pos );
19 |
20 | }
21 |
22 | void main() {
23 |
24 | float dt2 = dt*dt;
25 |
26 | vec2 uv = gl_FragCoord.xy / tSize.xy;
27 |
28 | vec3 org = texture2D( tOriginal, uv ).xyz;
29 | vec3 prv = ( texture2D( tPrevious0, uv ).xyz + texture2D( tPrevious1, uv ).xyz ) / 1024.0;
30 | vec3 pos = ( texture2D( tPosition0, uv ).xyz + texture2D( tPosition1, uv ).xyz ) / 1024.0;
31 |
32 | vec3 accel = 345.*(org-pos);
33 | vec3 vel = (pos-prv)/dt+accel*dt;
34 | vec3 disp = 0.98*vel*dt;
35 |
36 | gl_FragColor = vec4( unpackPosition( pos + disp ), 1.0 );
37 |
38 | }
39 | `;
40 |
--------------------------------------------------------------------------------
/src/glsl/mouse.frag.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 | precision highp sampler2D;
4 |
5 | uniform float vertices[3];
6 | uniform vec3 coordinates[3];
7 |
8 | uniform vec2 tSize;
9 | uniform float order;
10 | uniform sampler2D tPosition0;
11 | uniform sampler2D tPosition1;
12 | uniform sampler2D tOriginal;
13 |
14 | // get vec2 tex coordinate from index
15 | vec2 getUV( float id ) {
16 |
17 | vec2 coords = vec2(
18 | floor( mod( ( id + 0.5 ), tSize.x ) ),
19 | floor( ( id + 0.5 ) / tSize.x )
20 | ) + 0.5;
21 |
22 | return coords / tSize;
23 |
24 | }
25 |
26 | // pack float16 position into float32
27 | vec3 packPosition( vec2 uv ) {
28 |
29 | return ( texture2D( tPosition0, uv ).xyz + texture2D( tPosition1, uv ).xyz ) / 1024.0;
30 |
31 | }
32 |
33 | vec3 unpackPosition( vec3 pos ) {
34 |
35 | pos *= 1024.0;
36 |
37 | return ( order > 0.0 ) ? floor( pos ) : fract( pos );
38 |
39 | }
40 |
41 | void main() {
42 |
43 | vec2 uv = gl_FragCoord.xy / tSize.xy;
44 |
45 | vec3 pos = packPosition( uv );
46 | vec3 org = texture2D( tOriginal, uv ).xyz;
47 |
48 | vec3 ref, diff, proj, offset;
49 |
50 | for ( int i = 0; i < 3; ++i ) {
51 |
52 | if ( vertices[ i ] == - 1.0 ) continue;
53 |
54 | ref = texture2D( tOriginal, getUV( vertices[ i ] ) ).xyz;
55 | offset = coordinates[ i ] - ref;
56 |
57 | if ( distance( org, ref ) <= 0.1 ) {
58 |
59 | diff = ref - org;
60 |
61 | proj = dot( diff, offset ) / dot( offset, offset ) * org;
62 |
63 | pos = org + proj + offset;
64 |
65 | }
66 |
67 | }
68 |
69 | gl_FragColor = vec4( unpackPosition( pos ), 1.0 );
70 |
71 | }
72 | `;
73 |
--------------------------------------------------------------------------------
/src/glsl/normals.frag.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 | precision highp sampler2D;
4 |
5 | uniform vec2 tSize;
6 |
7 | uniform sampler2D tPosition0;
8 | uniform sampler2D tPosition1;
9 |
10 | uniform sampler2D tAdjacentsA;
11 | uniform sampler2D tAdjacentsB;
12 |
13 | // get vec2 tex coordinate from index
14 | vec2 getUV( float id ) {
15 |
16 | vec2 coords = vec2(
17 | floor( mod( ( id + 0.5 ), tSize.x ) ),
18 | floor( ( id + 0.5 ) / tSize.x )
19 | ) + 0.5;
20 |
21 | return coords / tSize;
22 |
23 | }
24 |
25 | // pack float16 position into float32
26 | vec3 packPosition( vec2 uv ) {
27 |
28 | return ( texture2D( tPosition0, uv ).xyz + texture2D( tPosition1, uv ).xyz ) / 1024.0;
29 |
30 | }
31 |
32 | void main () {
33 |
34 | vec3 normal;
35 | vec2 uv = gl_FragCoord.xy / tSize.xy;
36 |
37 | // indices of adjacent vertices
38 | vec4 adjacentA = texture2D( tAdjacentsA, uv );
39 | vec4 adjacentB = texture2D( tAdjacentsB, uv );
40 |
41 | // vertex position
42 | vec3 p0 = ( texture2D( tPosition0, uv ).xyz + texture2D( tPosition1, uv ).xyz ) / 1024.0;
43 |
44 | // adjacent vertices positions
45 | vec3 p1 = packPosition( getUV( adjacentA.x ) );
46 | vec3 p2 = packPosition( getUV( adjacentA.y ) );
47 | vec3 p3 = packPosition( getUV( adjacentA.z ) );
48 | vec3 p4 = packPosition( getUV( adjacentA.w ) );
49 | vec3 p5 = packPosition( getUV( adjacentB.x ) );
50 | vec3 p6 = packPosition( getUV( adjacentB.y ) );
51 |
52 | // compute vertex normal contribution
53 | normal += cross( p1 - p0, p2 - p0 );
54 | normal += cross( p2 - p0, p3 - p0 );
55 | normal += cross( p3 - p0, p4 - p0 );
56 | normal += cross( p4 - p0, p5 - p0 );
57 |
58 | if ( adjacentB.y > 0.0 ) {
59 |
60 | normal += cross( p5 - p0, p6 - p0 );
61 | normal += cross( p6 - p0, p1 - p0 );
62 |
63 | } else {
64 |
65 | normal += cross( p5 - p0, p1 - p0 );
66 |
67 | }
68 |
69 | gl_FragColor = vec4( normalize( normal ), 1.0 );
70 | }
71 | `;
72 |
--------------------------------------------------------------------------------
/src/glsl/through.frag.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 | precision highp sampler2D;
4 |
5 | uniform vec2 tSize;
6 | uniform float order;
7 | uniform sampler2D texture;
8 |
9 | vec4 unpackPosition( vec4 pos ) {
10 |
11 | pos *= 1024.0;
12 |
13 | return ( order > 0.0 ) ? floor( pos ) : fract( pos );
14 |
15 | }
16 |
17 | void main() {
18 |
19 | vec2 uv = gl_FragCoord.xy / tSize.xy;
20 |
21 | gl_FragColor = unpackPosition( texture2D( texture, uv ) );
22 |
23 | }
24 | `;
25 |
--------------------------------------------------------------------------------
/src/glsl/through.vert.js:
--------------------------------------------------------------------------------
1 | export default /* glsl */`
2 | precision highp float;
3 |
4 | attribute vec2 position;
5 |
6 | void main() {
7 |
8 | gl_Position = vec4( position, vec2(1.0) );
9 |
10 | }
11 | `;
12 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import * as BG from './modules/background.js';
2 | import * as CLOTH from './modules/cloth.js';
3 | import * as FBO from './modules/fbo.js';
4 | import * as PRE from './modules/pre.js';
5 | import * as LIGHTS from './modules/lights.js';
6 | import * as MOUSE from './modules/mouse.js';
7 |
8 | let
9 | renderer, camera, scene, lastOrientation;
10 |
11 | function init() {
12 |
13 | // renderer
14 | renderer = new THREE.WebGLRenderer( { antialias: true } );
15 | renderer.setSize( window.innerWidth, window.innerHeight );
16 |
17 | renderer.gammaOutput = true;
18 | renderer.physicallyCorrectLights = true;
19 |
20 | renderer.shadowMap.enabled = true;
21 | renderer.shadowMap.type = THREE.PCFShadowMap;
22 |
23 | document.body.appendChild( renderer.domElement );
24 |
25 | window.addEventListener( 'resize', onResize );
26 |
27 | // scene
28 | scene = new THREE.Scene();
29 | scene.background = new THREE.Color( 0x121312 );
30 |
31 | // camera
32 | camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 4000 );
33 | camera.position.set( 0, - 0.5, - 3.5 );
34 | camera.lookAt( new THREE.Vector3() );
35 |
36 | // pre-calculate geometry information
37 | PRE.calculate();
38 |
39 | // initialization block;
40 | BG.init( scene );
41 | CLOTH.init( scene );
42 |
43 | MOUSE.init( camera, renderer.domElement );
44 | FBO.init( renderer );
45 |
46 | // dispose of calculation data
47 | PRE.dispose();
48 |
49 | // initialize light
50 | LIGHTS.init( scene );
51 |
52 | // start program
53 | animate();
54 |
55 | }
56 |
57 | function animate() {
58 |
59 | if ( window.orientation != lastOrientation ) {
60 |
61 | lastOrientation = window.orientation;
62 | onResize();
63 |
64 | }
65 |
66 | LIGHTS.update();
67 | FBO.update();
68 |
69 | renderer.setRenderTarget( null );
70 | renderer.render( scene, camera );
71 |
72 | requestAnimationFrame( animate );
73 |
74 | }
75 |
76 | function onResize() {
77 |
78 | const w = window.innerWidth;
79 | const h = window.innerHeight;
80 |
81 | camera.aspect = w / h;
82 | camera.updateProjectionMatrix();
83 |
84 | renderer.setSize( w, h );
85 |
86 | };
87 |
88 | init();
89 |
--------------------------------------------------------------------------------
/src/modules/background.js:
--------------------------------------------------------------------------------
1 | function init( scene ) {
2 |
3 | const material = new THREE.MeshPhysicalMaterial( {
4 |
5 | color: 0x393939,
6 | metalness: 0.9,
7 | roughness: 0.4,
8 | dithering: true
9 |
10 | } );
11 |
12 | const geometry = new THREE.PlaneBufferGeometry( 16000, 16000 );
13 |
14 | const object = new THREE.Mesh( geometry, material );
15 | object.receiveShadow = true;
16 | object.rotation.x += Math.PI * 0.9;
17 | object.position.set( 0, - 100, 2000 );
18 |
19 | scene.add( object );
20 |
21 | }
22 |
23 | export { init };
24 |
--------------------------------------------------------------------------------
/src/modules/cloth.js:
--------------------------------------------------------------------------------
1 | import * as PRE from './pre.js';
2 | import * as FBO from './fbo.js';
3 |
4 | let
5 | RESOLUTION,
6 | mesh;
7 |
8 | function init( scene ) {
9 |
10 | RESOLUTION = Math.ceil( Math.sqrt( PRE.vertices.length ) );
11 |
12 | const bmp = new THREE.TextureLoader().load( './src/textures/bmpMap.png' );
13 | bmp.wrapS = THREE.RepeatWrapping;
14 | bmp.wrapT = THREE.RepeatWrapping;
15 | bmp.repeat.set( 2.5, 2.5 );
16 |
17 | const material = new THREE.MeshPhysicalMaterial( {
18 |
19 | color: 0xffda20,
20 | bumpMap: bmp,
21 | bumpScale: 0.012,
22 | metalness: 0.1,
23 | roughness: 0.6,
24 | clearcoat: 0.8,
25 | clearcoatRoughness: 0.35,
26 | sheen: new THREE.Color( 0.2, 0.2, 1 ).multiplyScalar( 1 / 6 ),
27 | dithering: true
28 |
29 | } );
30 |
31 | // update cloth material with computed position and normals
32 | material.onBeforeCompile = function ( shader ) {
33 | shader.uniforms.tPosition0 = { value: FBO.positionRT[ 0 ].texture };
34 | shader.uniforms.tPosition1 = { value: FBO.positionRT[ 1 ].texture };
35 | shader.uniforms.tNormal = { value: FBO.normalsRT.texture };
36 | shader.vertexShader = 'precision highp sampler2D;\nuniform sampler2D tPosition0;\nuniform sampler2D tPosition1;\nuniform sampler2D tNormal;\n' + shader.vertexShader;
37 | shader.vertexShader = shader.vertexShader.replace(
38 | '#include ',
39 | `vec3 transformed = ( texture2D( tPosition0, position.xy ).xyz + texture2D( tPosition1, position.xy ).xyz ) / 1024.0;
40 | vec3 objectNormal = normalize( texture2D( tNormal, position.xy ).xyz );
41 | `
42 | );
43 | shader.vertexShader = shader.vertexShader.replace(
44 | '#include ',
45 | ''
46 | );
47 | };
48 |
49 | // update depth material for correct shadows
50 | const depthMaterial = new THREE.MeshDepthMaterial();
51 | depthMaterial.onBeforeCompile = function ( shader ) {
52 | shader.uniforms.tPosition0 = { value: FBO.positionRT[ 0 ].texture };
53 | shader.uniforms.tPosition1 = { value: FBO.positionRT[ 1 ].texture };
54 | shader.vertexShader = 'precision highp sampler2D;\nuniform sampler2D tPosition0;\nuniform sampler2D tPosition1;\n' + shader.vertexShader;
55 | shader.vertexShader = shader.vertexShader.replace(
56 | '#include ',
57 | `vec3 transformed = ( texture2D( tPosition0, position.xy ).xyz + texture2D( tPosition1, position.xy ).xyz ) / 1024.0;`
58 | );
59 | };
60 |
61 | // fill position with associated texture sampling coordinate
62 | const position = new Float32Array( RESOLUTION * RESOLUTION * 3 );
63 | for ( let i = 0, il = RESOLUTION * RESOLUTION; i < il; i ++ ) {
64 |
65 | const i3 = i * 3;
66 | position[ i3 + 0 ] = ( i % ( RESOLUTION ) ) / ( RESOLUTION ) + 0.5 / ( RESOLUTION );
67 | position[ i3 + 1 ] = ~ ~ ( i / ( RESOLUTION ) ) / ( RESOLUTION ) + 0.5 / ( RESOLUTION );
68 |
69 | }
70 |
71 | const geometry = new THREE.BufferGeometry();
72 | geometry.setIndex( PRE.geometry.index );
73 | geometry.addAttribute( 'position', new THREE.BufferAttribute( position, 3 ) );
74 | geometry.addAttribute( 'uv', PRE.geometry.attributes.uv );
75 |
76 | mesh = new THREE.Mesh( geometry, material );
77 | mesh.customDepthMaterial = depthMaterial;
78 | mesh.castShadow = true;
79 |
80 | scene.add( mesh );
81 |
82 | }
83 |
84 | export { init, mesh };
85 |
--------------------------------------------------------------------------------
/src/modules/fbo.js:
--------------------------------------------------------------------------------
1 | import * as PRE from './pre.js';
2 | import * as MOUSE from './mouse.js';
3 |
4 | import {
5 | constraintsShader,
6 | copyShader,
7 | integrateShader,
8 | mouseShader,
9 | normalsShader
10 | } from './materials.js';
11 |
12 | let
13 | RESOLUTION,
14 | renderer, mesh, targetRT, normalsRT,
15 | originalRT, previousRT, positionRT,
16 | adjacentsRT, distancesRT,
17 | iterations = 5, steps = 6,
18 | prevTime, dt;
19 |
20 | const
21 | tSize = new THREE.Vector2(),
22 | scene = new THREE.Scene(),
23 | camera = new THREE.Camera();
24 |
25 | function init( WebGLRenderer ) {
26 |
27 | // setup
28 | renderer = WebGLRenderer;
29 |
30 | RESOLUTION = Math.ceil( Math.sqrt( PRE.vertices.length ) );
31 | tSize.set( RESOLUTION, RESOLUTION );
32 |
33 | // geometry
34 | const geometry = new THREE.BufferGeometry();
35 | const positions = new Float32Array( [
36 | -1.0, -1.0,
37 | 3.0, -1.0,
38 | -1.0, 3.0
39 | ] );
40 |
41 | geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 2 ) );
42 |
43 | // mesh
44 | mesh = new THREE.Mesh( geometry, copyShader );
45 | mesh.frustumCulled = false;
46 | scene.add( mesh );
47 |
48 | scene.updateMatrixWorld = function() {};
49 |
50 | adjacentsRT = new Array( 2 );
51 | distancesRT = new Array( 2 );
52 | positionRT = new Array( 2 );
53 | previousRT = new Array( 2 );
54 | targetRT = new Array( 2 );
55 |
56 | normalsRT = createRenderTarget();
57 |
58 | // prepare
59 | createPositionTexture();
60 |
61 | // setup relaxed vertices conditions
62 | for ( let i = 0; i < 2; i++ ) {
63 |
64 | createAdjacentsTexture( i );
65 | createDistancesTexture( i );
66 |
67 | positionRT[ i ] = createRenderTarget();
68 | previousRT[ i ] = createRenderTarget();
69 | targetRT[ i ] = createRenderTarget();
70 |
71 | copyTexture( originalRT, positionRT[ i ], !i );
72 | copyTexture( originalRT, previousRT[ i ], !i );
73 |
74 | }
75 |
76 | prevTime = performance.now();
77 |
78 | }
79 |
80 | function copyTexture( input, output, order ) {
81 |
82 | mesh.material = copyShader;
83 | copyShader.uniforms.order.value = ( order ) ? 1 : - 1;
84 | copyShader.uniforms.tSize.value = tSize;
85 | copyShader.uniforms.texture.value = input.texture;
86 |
87 | renderer.setRenderTarget( output );
88 | renderer.render( scene, camera );
89 |
90 | }
91 |
92 | function createRenderTarget( ) {
93 |
94 | return new THREE.WebGLRenderTarget( RESOLUTION, RESOLUTION, {
95 | wrapS: THREE.ClampToEdgeWrapping,
96 | wrapT: THREE.ClampToEdgeWrapping,
97 | minFilter: THREE.NearestFilter,
98 | magFilter: THREE.NearestFilter,
99 | format: THREE.RGBAFormat,
100 | type: THREE.HalfFloatType,
101 | depthTest: false,
102 | depthWrite: false,
103 | depthBuffer: false,
104 | stencilBuffer: false
105 | } );
106 |
107 | }
108 |
109 | function createPositionTexture( ) {
110 |
111 | const data = new Float32Array( RESOLUTION * RESOLUTION * 4 );
112 | const length = PRE.vertices.length;
113 |
114 | for ( let i = 0; i < length; i++ ) {
115 |
116 | const i4 = i * 4;
117 |
118 | data[ i4 + 0 ] = PRE.vertices[ i ].x;
119 | data[ i4 + 1 ] = PRE.vertices[ i ].y;
120 | data[ i4 + 2 ] = PRE.vertices[ i ].z;
121 |
122 | }
123 |
124 | const tmp = {};
125 | tmp.texture = new THREE.DataTexture( data, RESOLUTION, RESOLUTION, THREE.RGBAFormat, THREE.FloatType );
126 | tmp.texture.needsUpdate = true;
127 |
128 | originalRT = tmp;
129 |
130 | }
131 |
132 | function createAdjacentsTexture( k ) {
133 |
134 | const data = new Float32Array( RESOLUTION * RESOLUTION * 4 );
135 | const length = PRE.vertices.length;
136 |
137 | for ( let i = 0; i < length; i++ ) {
138 |
139 | const i4 = i * 4;
140 | const adj = PRE.adjacency[ i ];
141 | const len = PRE.adjacency[ i ].length - 1;
142 |
143 | for ( let j = 0; j < 4; j++ )
144 | data[ i4 + j ] = ( len < k * 4 + j ) ? - 1 : adj[ k * 4 + j ];
145 |
146 | }
147 |
148 | const tmp = {};
149 | tmp.texture = new THREE.DataTexture( data, RESOLUTION, RESOLUTION, THREE.RGBAFormat, THREE.FloatType );
150 | tmp.texture.needsUpdate = true;
151 |
152 | adjacentsRT[ k ] = tmp;
153 |
154 | }
155 |
156 | function createDistancesTexture( k ) {
157 |
158 | const data = new Float32Array( RESOLUTION * RESOLUTION * 4 );
159 | const length = PRE.vertices.length;
160 |
161 | const vert = PRE.vertices;
162 |
163 | for ( let i = 0; i < length; i++ ) {
164 |
165 | const i4 = i * 4;
166 | const adj = PRE.adjacency[ i ];
167 | const len = PRE.adjacency[ i ].length - 1;
168 |
169 | const v = vert[ i ];
170 |
171 | for ( let j = 0; j < 4; j++ )
172 | data[ i4 + j ] = ( len < k * 4 + j ) ? - 1 : v.distanceTo( vert[ adj[ k * 4 + j ] ] );
173 |
174 | }
175 |
176 | const tmp = {};
177 | tmp.texture = new THREE.DataTexture( data, RESOLUTION, RESOLUTION, THREE.RGBAFormat, THREE.FloatType );
178 | tmp.texture.needsUpdate = true;
179 |
180 | distancesRT[ k ] = tmp;
181 |
182 | }
183 |
184 | function integrate() {
185 |
186 | mesh.material = integrateShader;
187 | integrateShader.uniforms.tSize.value = tSize;
188 | integrateShader.uniforms.dt.value = dt;
189 | integrateShader.uniforms.tOriginal.value = originalRT.texture;
190 | integrateShader.uniforms.tPrevious0.value = previousRT[ 0 ].texture;
191 | integrateShader.uniforms.tPrevious1.value = previousRT[ 1 ].texture;
192 | integrateShader.uniforms.tPosition0.value = positionRT[ 0 ].texture;
193 | integrateShader.uniforms.tPosition1.value = positionRT[ 1 ].texture;
194 |
195 | // console.log(dt);
196 |
197 | // integer-part
198 | integrateShader.uniforms.order.value = 1;
199 | renderer.setRenderTarget( targetRT[ 0 ] );
200 | renderer.render( scene, camera );
201 |
202 | // fraction-part
203 | integrateShader.uniforms.order.value = -1;
204 | renderer.setRenderTarget( targetRT[ 1 ] );
205 | renderer.render( scene, camera );
206 |
207 |
208 | // swap framebuffers
209 | let tmp = previousRT[ 0 ];
210 | previousRT[ 0 ] = positionRT[ 0 ];
211 | positionRT[ 0 ] = targetRT[ 0 ];
212 | targetRT[ 0 ] = tmp;
213 |
214 | tmp = previousRT[ 1 ];
215 | previousRT[ 1 ] = positionRT[ 1 ];
216 | positionRT[ 1 ] = targetRT[ 1 ];
217 | targetRT[ 1 ] = tmp;
218 |
219 | }
220 |
221 | function solveConstraints() {
222 |
223 | mesh.material = constraintsShader;
224 | constraintsShader.uniforms.tSize.value = tSize;
225 | constraintsShader.uniforms.tPosition0.value = positionRT[ 0 ].texture;
226 | constraintsShader.uniforms.tPosition1.value = positionRT[ 1 ].texture;
227 | constraintsShader.uniforms.tAdjacentsA.value = adjacentsRT[ 0 ].texture;
228 | constraintsShader.uniforms.tAdjacentsB.value = adjacentsRT[ 1 ].texture;
229 | constraintsShader.uniforms.tDistancesA.value = distancesRT[ 0 ].texture;
230 | constraintsShader.uniforms.tDistancesB.value = distancesRT[ 1 ].texture;
231 |
232 | // integer-part
233 | constraintsShader.uniforms.order.value = 1;
234 | renderer.setRenderTarget( targetRT[ 0 ] );
235 | renderer.render( scene, camera );
236 |
237 | // fraction-part
238 | constraintsShader.uniforms.order.value = -1;
239 | renderer.setRenderTarget( targetRT[ 1 ] );
240 | renderer.render( scene, camera );
241 |
242 |
243 | // swap framebuffers
244 | let tmp = positionRT[ 0 ];
245 | positionRT[ 0 ] = targetRT [ 0 ];
246 | targetRT[ 0 ] = tmp;
247 |
248 | tmp = positionRT[ 1 ];
249 | positionRT[ 1 ] = targetRT [ 1 ];
250 | targetRT[ 1 ] = tmp;
251 |
252 | }
253 |
254 | function mouseOffset() {
255 |
256 | mesh.material = mouseShader;
257 | mouseShader.uniforms.tSize.value = tSize;
258 | mouseShader.uniforms.vertices.value = MOUSE.vertices;
259 | mouseShader.uniforms.coordinates.value = MOUSE.coordinates;
260 | mouseShader.uniforms.tOriginal.value = originalRT.texture;
261 | mouseShader.uniforms.tPosition0.value = positionRT[ 0 ].texture;
262 | mouseShader.uniforms.tPosition1.value = positionRT[ 1 ].texture;
263 |
264 | // integer-part
265 | mouseShader.uniforms.order.value = 1;
266 | renderer.setRenderTarget( targetRT[ 0 ] );
267 | renderer.render( scene, camera );
268 |
269 | // fraction-part
270 | mouseShader.uniforms.order.value = -1;
271 | renderer.setRenderTarget( targetRT[ 1 ] );
272 | renderer.render( scene, camera );
273 |
274 |
275 | // swap framebuffers
276 | let tmp = positionRT[ 0 ];
277 | positionRT[ 0 ] = targetRT [ 0 ];
278 | targetRT[ 0 ] = tmp;
279 |
280 | tmp = positionRT[ 1 ];
281 | positionRT[ 1 ] = targetRT [ 1 ];
282 | targetRT[ 1 ] = tmp;
283 |
284 | }
285 |
286 | function computeVertexNormals( ) {
287 |
288 | mesh.material = normalsShader;
289 | normalsShader.uniforms.tSize.value = tSize;
290 | normalsShader.uniforms.tPosition0.value = positionRT[ 0 ].texture;
291 | normalsShader.uniforms.tPosition1.value = positionRT[ 1 ].texture;
292 | normalsShader.uniforms.tAdjacentsA.value = adjacentsRT[ 0 ].texture;
293 | normalsShader.uniforms.tAdjacentsB.value = adjacentsRT[ 1 ].texture;
294 |
295 | renderer.setRenderTarget( normalsRT );
296 | renderer.render( scene, camera );
297 |
298 | }
299 |
300 | function update() {
301 |
302 | const now = performance.now();
303 | const frame_dt = Math.min(now - prevTime, 33.333)/1000;
304 | dt = frame_dt/steps;
305 | prevTime = now;
306 |
307 | let mouseUpdating = MOUSE.updating();
308 |
309 | for ( let i = 0; i < steps; i++ ) {
310 | integrate();
311 | for ( let j = 0; j < iterations; j++ ) {
312 | if ( mouseUpdating && (i 1 && time < 4 ) {
65 |
66 | updateLights( time );
67 |
68 | } else if ( time > 4 ) {
69 |
70 | updateLights( 4 );
71 |
72 | finished = true;
73 |
74 | }
75 |
76 | }
77 |
78 | export { init, update };
79 |
--------------------------------------------------------------------------------
/src/modules/materials.js:
--------------------------------------------------------------------------------
1 | // shader-import-block
2 | import through_vert from '../glsl/through.vert.js';
3 |
4 | import constraints_frag from '../glsl/constraints.frag.js';
5 | import integrate_frag from '../glsl/integrate.frag.js';
6 | import mouse_frag from '../glsl/mouse.frag.js';
7 | import normals_frag from '../glsl/normals.frag.js';
8 | import through_frag from '../glsl/through.frag.js';
9 |
10 |
11 | // copyToRenderTarget
12 | const copyShader = new THREE.RawShaderMaterial( {
13 | uniforms: {
14 | order: {},
15 | tSize: { type: 'v2' },
16 | texture: { type: 't' }
17 | },
18 | vertexShader: through_vert,
19 | fragmentShader: through_frag,
20 | fog: false,
21 | lights: false,
22 | depthWrite: false,
23 | depthTest: false
24 | });
25 |
26 | // forward-integration
27 | const integrateShader = copyShader.clone();
28 | integrateShader.fragmentShader = integrate_frag;
29 | integrateShader.uniforms = {
30 | dt: { type: 'f' },
31 | tSize: { type: 'v2' },
32 | order: {},
33 | tOriginal: { type: 't' },
34 | tPrevious0: { type: 't' },
35 | tPrevious1: { type: 't' },
36 | tPosition0: { type: 't' },
37 | tPosition1: { type: 't' }
38 | };
39 |
40 | // mouse displacement
41 | const mouseShader = copyShader.clone();
42 | mouseShader.fragmentShader = mouse_frag;
43 | mouseShader.uniforms = {
44 | vertices: { value: null },
45 | coordinates: { type: 'v3' },
46 | order: {},
47 | tSize: { type: 'v2' },
48 | tOriginal: { type: 't' },
49 | tPosition0: { type: 't' },
50 | tPosition1: { type: 't' }
51 | };
52 |
53 | // vertices relaxation
54 | const constraintsShader = copyShader.clone();
55 | constraintsShader.fragmentShader = constraints_frag;
56 | constraintsShader.uniforms = {
57 | tSize: { type: 'v2' },
58 | order: { },
59 | tPosition0: { type: 't' },
60 | tPosition1: { type: 't' },
61 | tAdjacentsA: { type: 't' },
62 | tAdjacentsB: { type: 't' },
63 | tDistancesA: { type: 't' },
64 | tDistancesB: { type: 't' }
65 | };
66 |
67 | // calculate normals
68 | const normalsShader = copyShader.clone();
69 | normalsShader.fragmentShader = normals_frag;
70 | normalsShader.uniforms = {
71 | tSize: { type: 'v2' },
72 | tPosition0: { type: 't' },
73 | tPosition1: { type: 't' },
74 | tAdjacentsA: { type: 't' },
75 | tAdjacentsB: { type: 't' }
76 |
77 | };
78 |
79 | export { copyShader, integrateShader, mouseShader, constraintsShader, normalsShader }
80 |
--------------------------------------------------------------------------------
/src/modules/mouse.js:
--------------------------------------------------------------------------------
1 | import * as PRE from './pre.js';
2 |
3 | let camera;
4 |
5 | const
6 | pointers = {},
7 | vertices = new Array( 3 ),
8 | coordinates = new Array( 3 ),
9 | tmpmouse = new THREE.Vector3(),
10 | mouse3d = new THREE.Vector3(),
11 | raycaster = new THREE.Raycaster(),
12 | plane = new THREE.Plane( undefined, -1.8 ),
13 | sphere = new THREE.Sphere( undefined, 1 );
14 |
15 |
16 | function init( PerspectiveCamera ) {
17 |
18 | camera = PerspectiveCamera;
19 | plane.normal.copy( camera.position ).normalize();
20 |
21 | const canvas = document.getElementsByTagName( 'canvas' )[0];
22 |
23 | canvas.addEventListener('mousemove', onMouseMove );
24 | canvas.addEventListener('mousedown', onMouseDown );
25 | canvas.addEventListener('mouseout', onMouseOut );
26 | canvas.addEventListener('mouseup', onMouseUp );
27 |
28 | canvas.addEventListener('touchmove', onTouchMove, { passive: false } );
29 | canvas.addEventListener('touchstart', onTouchDown, { passive: false } );
30 | canvas.addEventListener('touchend', onTouchUp );
31 |
32 | }
33 |
34 | function updating() {
35 |
36 | let count = 0;
37 | let isUpdating = false;
38 |
39 | for ( let [ key, value ] of Object.entries( pointers ) ) {
40 |
41 | let mouse = value.screenCoordinate;
42 |
43 | raycaster.setFromCamera( mouse, camera );
44 |
45 | if ( value.vertex === undefined &&
46 | raycaster.ray.intersectSphere( sphere, tmpmouse ) != null ) {
47 |
48 | mouse3d.copy( tmpmouse );
49 |
50 | let dist = Infinity;
51 |
52 | for ( let i = 0; i < PRE.vertices.length; ++i ) {
53 |
54 | const tmp = mouse3d.distanceTo( PRE.vertices[ i ] );
55 |
56 | if ( tmp < dist ) {
57 |
58 | dist = tmp;
59 | value.vertex = i;
60 |
61 | }
62 |
63 | }
64 |
65 | }
66 |
67 | if ( value.vertex !== undefined ) {
68 |
69 | isUpdating = true;
70 | raycaster.ray.intersectPlane( plane, tmpmouse );
71 | value.worldCoordinate.copy( tmpmouse );
72 |
73 | vertices[ count ] = value.vertex;
74 | coordinates[ count ] = value.worldCoordinate;
75 |
76 | count++;
77 |
78 | }
79 |
80 | }
81 |
82 | while ( count < 3 ) {
83 |
84 | vertices[ count ] = -1;
85 | coordinates[ count ] = mouse3d;
86 |
87 | count++;
88 |
89 | }
90 |
91 | return ( isUpdating ) ? true : false;
92 |
93 | }
94 |
95 | function onMouseMove( evt ) {
96 |
97 | if ( pointers[ 'mouse' ] !== undefined ) {
98 |
99 | pointers[ 'mouse' ].screenCoordinate.x = ( evt.pageX / window.innerWidth ) * 2 - 1;
100 | pointers[ 'mouse' ].screenCoordinate.y = - ( evt.pageY / window.innerHeight ) * 2 + 1;
101 |
102 | }
103 |
104 | }
105 |
106 | function onMouseDown( evt ) {
107 |
108 | if ( evt.button == 0 ) {
109 |
110 | pointers[ 'mouse' ] = {
111 |
112 | vertex: undefined,
113 | screenCoordinate: new THREE.Vector2(),
114 | worldCoordinate: new THREE.Vector3()
115 |
116 | }
117 |
118 | onMouseMove( evt );
119 |
120 | }
121 |
122 | }
123 |
124 | function onMouseUp( evt ) {
125 |
126 | if ( evt.button == 0 ) {
127 |
128 | delete pointers[ 'mouse' ];
129 |
130 | }
131 |
132 | }
133 |
134 | function onMouseOut() {
135 |
136 | delete pointers[ 'mouse' ];
137 |
138 | }
139 |
140 | function onTouchMove( evt ) {
141 |
142 | evt.preventDefault();
143 |
144 | for ( let i = 0; i < evt.changedTouches.length; ++i ) {
145 |
146 | let touch = evt.changedTouches[ i ];
147 |
148 | pointers[ touch.identifier ].screenCoordinate.x = ( touch.pageX / window.innerWidth ) * 2 - 1;
149 | pointers[ touch.identifier ].screenCoordinate.y = - ( touch.pageY / window.innerHeight ) * 2 + 1;
150 |
151 | }
152 |
153 | }
154 |
155 | function onTouchDown( evt ) {
156 |
157 | for ( let i = 0; i < evt.changedTouches.length; ++i ) {
158 |
159 | let touch = evt.changedTouches[ i ];
160 |
161 | pointers[ touch.identifier ] = {
162 |
163 | vertex: undefined,
164 | screenCoordinate: new THREE.Vector2(),
165 | worldCoordinate: new THREE.Vector3()
166 |
167 | }
168 |
169 | }
170 |
171 | onTouchMove( evt );
172 |
173 | }
174 |
175 | function onTouchUp( evt ) {
176 |
177 | for ( let i = 0; i < evt.changedTouches.length; ++i ) {
178 |
179 | let touch = evt.changedTouches[ i ];
180 |
181 | delete pointers[ touch.identifier ];
182 |
183 | }
184 |
185 | }
186 |
187 | export { init, updating, vertices, coordinates }
188 |
--------------------------------------------------------------------------------
/src/modules/pre.js:
--------------------------------------------------------------------------------
1 |
2 | let
3 | geometry, adjacency, vertices;
4 |
5 | function mergeVertices( geometry, tolerance = 1e-4 ) {
6 |
7 | tolerance = Math.max( tolerance, Number.EPSILON );
8 |
9 | // Generate an index buffer if the geometry doesn't have one, or optimize it
10 | // if it's already available.
11 | const hashToIndex = {};
12 | const indices = geometry.getIndex();
13 | const positions = geometry.getAttribute( 'position' );
14 | const vertexCount = indices ? indices.count : positions.count;
15 |
16 | // next value for triangle indices
17 | let nextIndex = 0;
18 |
19 | // attributes and new attribute arrays
20 | const attributeNames = Object.keys( geometry.attributes );
21 | const tmpAttributes = {};
22 | const tmpMorphAttributes = {};
23 | const newIndices = [];
24 | const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
25 | const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
26 |
27 | // Initialize the arrays, allocating space conservatively. Extra
28 | // space will be trimmed in the last step.
29 | for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
30 |
31 | const name = attributeNames[ i ];
32 | const attr = geometry.attributes[ name ];
33 | tmpAttributes[ name ] = new THREE.BufferAttribute( new attr.array.constructor( attr.count * attr.itemSize ), attr.itemSize, attr.normalized );
34 | const morphAttr = geometry.morphAttributes[ name ];
35 | if ( morphAttr ) {
36 |
37 | tmpMorphAttributes[ name ] = new THREE.BufferAttribute( new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ), morphAttr.itemSize, morphAttr.normalized );
38 |
39 | }
40 |
41 | }
42 |
43 | // convert the error tolerance to an amount of decimal places to truncate to
44 | const decimalShift = Math.log10( 1 / tolerance );
45 | const shiftMultiplier = Math.pow( 10, decimalShift );
46 | for ( let i = 0; i < vertexCount; i ++ ) {
47 |
48 | const index = indices ? indices.getX( i ) : i;
49 |
50 | // Generate a hash for the vertex attributes at the current index 'i'
51 | let hash = '';
52 | for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
53 |
54 | const name = attributeNames[ j ];
55 | const attribute = geometry.getAttribute( name );
56 | const itemSize = attribute.itemSize;
57 | for ( let k = 0; k < itemSize; k ++ ) {
58 |
59 | // double tilde truncates the decimal value
60 | hash += `${~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier )},`;
61 |
62 | }
63 |
64 | }
65 |
66 | // Add another reference to the vertex if it's already
67 | // used by another index
68 | if ( hash in hashToIndex ) {
69 |
70 | newIndices.push( hashToIndex[ hash ] );
71 |
72 | } else {
73 |
74 | // copy data to the new index in the temporary attributes
75 | for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
76 |
77 | const name = attributeNames[ j ];
78 | const attribute = geometry.getAttribute( name );
79 | const morphAttr = geometry.morphAttributes[ name ];
80 | const itemSize = attribute.itemSize;
81 | const newarray = tmpAttributes[ name ];
82 | const newMorphArrays = tmpMorphAttributes[ name ];
83 | for ( let k = 0; k < itemSize; k ++ ) {
84 |
85 | const getterFunc = getters[ k ];
86 | const setterFunc = setters[ k ];
87 | newarray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
88 | if ( morphAttr ) {
89 |
90 | for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) {
91 |
92 | newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttr[ m ][ getterFunc ]( index ) );
93 |
94 | }
95 |
96 | }
97 |
98 | }
99 |
100 | }
101 |
102 | hashToIndex[ hash ] = nextIndex;
103 | newIndices.push( nextIndex );
104 | nextIndex ++;
105 |
106 | }
107 |
108 | }
109 |
110 | // generate result THREE.BufferGeometry
111 | const result = geometry.clone();
112 | for ( const name in geometry.attributes ) {
113 |
114 | const tmpAttribute = tmpAttributes[ name ];
115 | result.setAttribute( name, new THREE.BufferAttribute( tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ), tmpAttribute.itemSize, tmpAttribute.normalized ) );
116 | if ( ! ( name in tmpMorphAttributes ) ) continue;
117 | for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
118 |
119 | const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
120 | result.morphAttributes[ name ][ j ] = new THREE.BufferAttribute( tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ), tmpMorphAttribute.itemSize, tmpMorphAttribute.normalized );
121 |
122 | }
123 |
124 | }
125 |
126 | // indices
127 |
128 | result.setIndex( newIndices );
129 | return result;
130 |
131 | }
132 |
133 | function calculate() {
134 |
135 | const tmp = new THREE.IcosahedronBufferGeometry( 1000, 5 );
136 |
137 | // icosahedron generates non-indexed vertices, we make use of graph adjacency.
138 | geometry = mergeVertices( tmp, 1.2 );
139 |
140 | geometry.scale( 0.00095, 0.00095, 0.00095 );
141 |
142 | populateVertices();
143 |
144 | populateAdjacency();
145 |
146 | }
147 |
148 | function populateVertices() {
149 |
150 | const v0 = new THREE.Vector3();
151 | const position = geometry.attributes.position;
152 |
153 | vertices = new Array();
154 |
155 | for ( let i = 0, il = position.count; i < il; i++ ) {
156 |
157 | v0.fromBufferAttribute( position, i );
158 | vertices.push( v0.clone() );
159 |
160 | }
161 |
162 | }
163 |
164 | function populateAdjacency() {
165 |
166 | const
167 | index = geometry.index,
168 | faces = Array.from( { length: vertices.length }, () => new Array() );
169 |
170 | // compute all faces for set vertex
171 | for ( let i = 0, il = index.count / 3; i < il; i++ ) {
172 |
173 | const
174 | i3 = i * 3,
175 | a = index.getX( i3 + 0 ),
176 | b = index.getX( i3 + 1 ),
177 | c = index.getX( i3 + 2 ),
178 |
179 | face = new THREE.Face3( a, b, c );
180 |
181 | faces[ a ].push( face );
182 | faces[ b ].push( face );
183 | faces[ c ].push( face );
184 |
185 | }
186 |
187 | // support function - find face with winding order ( first ) -> ( next )
188 | function getFace( arr, first, next ) {
189 |
190 | for ( let r = 0; r < arr.length; r++ ) {
191 |
192 | var n = arr[ r ];
193 |
194 | if ( n.a === first && n.b === next ||
195 | n.b === first && n.c === next ||
196 | n.c === first && n.a === next )
197 | return n
198 | }
199 |
200 | console.error( "sheen.error: shouldn't reach here." );
201 | return
202 |
203 | }
204 |
205 | adjacency = Array.from( { length: vertices.length }, () => new Array() );
206 |
207 | // compute sorted adjacency list for every vertex
208 | for ( let r = 0; r < faces.length; r++ ) {
209 |
210 | let n = faces[ r ][ 0 ];
211 |
212 | // cycle in a fan, through all faces of the vertex
213 | while ( true ) {
214 |
215 | if ( n.a == r ) {
216 |
217 | adjacency[ r ].push( n.c );
218 | n = getFace( faces[ r ], r, n.c ); // face with reverse winding order ( a ) -> ( c )
219 |
220 | } else if ( n.b == r ) {
221 |
222 | adjacency[ r ].push( n.a );
223 | n = getFace( faces[ r ], r, n.a ); // face with reverse winding order ( b ) -> ( a )
224 |
225 | } else { // n.c == r
226 |
227 | adjacency[ r ].push( n.b );
228 | n = getFace( faces[ r ], r, n.b ); // face with reverse winding order ( c ) -> ( b )
229 |
230 | }
231 |
232 | // back to the start - end
233 | if ( n == faces[ r ][ 0 ] ) break;
234 |
235 | }
236 |
237 | }
238 |
239 | }
240 |
241 | function dispose() {
242 |
243 | geometry = undefined;
244 | adjacency = undefined;
245 |
246 | }
247 |
248 | export { calculate, dispose, geometry, vertices, adjacency };
249 |
--------------------------------------------------------------------------------
/src/textures/bmpMap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sciecode/sheen/0745512693762050041320a5e4cc6e89daf6b415/src/textures/bmpMap.png
--------------------------------------------------------------------------------
/utils/externs.js:
--------------------------------------------------------------------------------
1 | var THREE;
2 |
--------------------------------------------------------------------------------