Сохранение соотношения сторон для моделирования ткани three.js

В примере, показанном на их веб-сайте, текстура ткани, которая используется, представляет собой квадрат, как вы можете видеть здесь.

Однако на странице примера он отображается растянутым по высоте, как вы можете видеть на скриншоте ниже. Вы можете легко это заметить, если отключите ветер.

Как сохранить правильное соотношение сторон и почему оно не соблюдается в примере three.js?

Текстура квадратного изображения с неправильным соотношением сторон

Полный код ниже:

            var params = {
                enableWind: true,
                showBall: false,
                tooglePins: togglePins
            };
            var DAMPING = 0.03;
            var DRAG = 1 - DAMPING;
            var MASS = 0.1;
            var restDistance = 25;
            var xSegs = 10;
            var ySegs = 10;
            var clothFunction = plane( restDistance * xSegs, restDistance * ySegs );
            var cloth = new Cloth( xSegs, ySegs );
            var GRAVITY = 981 * 1.4;
            var gravity = new THREE.Vector3( 0, - GRAVITY, 0 ).multiplyScalar( MASS );
            var TIMESTEP = 18 / 1000;
            var TIMESTEP_SQ = TIMESTEP * TIMESTEP;
            var pins = [];
            var windForce = new THREE.Vector3( 0, 0, 0 );
            var ballPosition = new THREE.Vector3( 0, - 45, 0 );
            var ballSize = 60; //40
            var tmpForce = new THREE.Vector3();
            var lastTime;
            function plane( width, height ) {
                return function ( u, v, target ) {
                    var x = ( u - 0.5 ) * width;
                    var y = ( v + 0.5 ) * height;
                    var z = 0;
                    target.set( x, y, z );
                };
            }
            function Particle( x, y, z, mass ) {
                this.position = new THREE.Vector3();
                this.previous = new THREE.Vector3();
                this.original = new THREE.Vector3();
                this.a = new THREE.Vector3( 0, 0, 0 ); // acceleration
                this.mass = mass;
                this.invMass = 1 / mass;
                this.tmp = new THREE.Vector3();
                this.tmp2 = new THREE.Vector3();
                // init
                clothFunction( x, y, this.position ); // position
                clothFunction( x, y, this.previous ); // previous
                clothFunction( x, y, this.original );
            }
            // Force -> Acceleration
            Particle.prototype.addForce = function ( force ) {
                this.a.add(
                    this.tmp2.copy( force ).multiplyScalar( this.invMass )
                );
            };
            // Performs Verlet integration
            Particle.prototype.integrate = function ( timesq ) {
                var newPos = this.tmp.subVectors( this.position, this.previous );
                newPos.multiplyScalar( DRAG ).add( this.position );
                newPos.add( this.a.multiplyScalar( timesq ) );
                this.tmp = this.previous;
                this.previous = this.position;
                this.position = newPos;
                this.a.set( 0, 0, 0 );
            };
            var diff = new THREE.Vector3();
            function satisfyConstraints( p1, p2, distance ) {
                diff.subVectors( p2.position, p1.position );
                var currentDist = diff.length();
                if ( currentDist === 0 ) return; // prevents division by 0
                var correction = diff.multiplyScalar( 1 - distance / currentDist );
                var correctionHalf = correction.multiplyScalar( 0.5 );
                p1.position.add( correctionHalf );
                p2.position.sub( correctionHalf );
            }
            function Cloth( w, h ) {
                w = w || 10;
                h = h || 10;
                this.w = w;
                this.h = h;
                var particles = [];
                var constraints = [];
                var u, v;
                // Create particles
                for ( v = 0; v <= h; v ++ ) {
                    for ( u = 0; u <= w; u ++ ) {
                        particles.push(
                            new Particle( u / w, v / h, 0, MASS )
                        );
                    }
                }
                // Structural
                for ( v = 0; v < h; v ++ ) {
                    for ( u = 0; u < w; u ++ ) {
                        constraints.push( [
                            particles[ index( u, v ) ],
                            particles[ index( u, v + 1 ) ],
                            restDistance
                        ] );
                        constraints.push( [
                            particles[ index( u, v ) ],
                            particles[ index( u + 1, v ) ],
                            restDistance
                        ] );
                    }
                }
                for ( u = w, v = 0; v < h; v ++ ) {
                    constraints.push( [
                        particles[ index( u, v ) ],
                        particles[ index( u, v + 1 ) ],
                        restDistance
                    ] );
                }
                for ( v = h, u = 0; u < w; u ++ ) {
                    constraints.push( [
                        particles[ index( u, v ) ],
                        particles[ index( u + 1, v ) ],
                        restDistance
                    ] );
                }
                // While many systems use shear and bend springs,
                // the relaxed constraints model seems to be just fine
                // using structural springs.
                // Shear
                // var diagonalDist = Math.sqrt(restDistance * restDistance * 2);
                // for (v=0;v<h;v++) {
                //  for (u=0;u<w;u++) {
                //      constraints.push([
                //          particles[index(u, v)],
                //          particles[index(u+1, v+1)],
                //          diagonalDist
                //      ]);
                //      constraints.push([
                //          particles[index(u+1, v)],
                //          particles[index(u, v+1)],
                //          diagonalDist
                //      ]);
                //  }
                // }
                this.particles = particles;
                this.constraints = constraints;
                function index( u, v ) {
                    return u + v * ( w + 1 );
                }
                this.index = index;
            }
            function simulate( time ) {
                if ( ! lastTime ) {
                    lastTime = time;
                    return;
                }
                var i, j, il, particles, particle, constraints, constraint;
                // Aerodynamics forces
                if ( params.enableWind ) {
                    var indx;
                    var normal = new THREE.Vector3();
                    var indices = clothGeometry.index;
                    var normals = clothGeometry.attributes.normal;
                    particles = cloth.particles;
                    for ( i = 0, il = indices.count; i < il; i += 3 ) {
                        for ( j = 0; j < 3; j ++ ) {
                            indx = indices.getX( i + j );
                            normal.fromBufferAttribute( normals, indx );
                            tmpForce.copy( normal ).normalize().multiplyScalar( normal.dot( windForce ) );
                            particles[ indx ].addForce( tmpForce );
                        }
                    }
                }
                for ( particles = cloth.particles, i = 0, il = particles.length; i < il; i ++ ) {
                    particle = particles[ i ];
                    particle.addForce( gravity );
                    particle.integrate( TIMESTEP_SQ );
                }
                // Start Constraints
                constraints = cloth.constraints;
                il = constraints.length;
                for ( i = 0; i < il; i ++ ) {
                    constraint = constraints[ i ];
                    satisfyConstraints( constraint[ 0 ], constraint[ 1 ], constraint[ 2 ] );
                }
                // Ball Constraints
                ballPosition.z = - Math.sin( Date.now() / 600 ) * 90; //+ 40;
                ballPosition.x = Math.cos( Date.now() / 400 ) * 70;
                if ( params.showBall ) {
                    sphere.visible = true;
                    for ( particles = cloth.particles, i = 0, il = particles.length; i < il; i ++ ) {
                        particle = particles[ i ];
                        var pos = particle.position;
                        diff.subVectors( pos, ballPosition );
                        if ( diff.length() < ballSize ) {
                            // collided
                            diff.normalize().multiplyScalar( ballSize );
                            pos.copy( ballPosition ).add( diff );
                        }
                    }
                } else {
                    sphere.visible = false;
                }
                // Floor Constraints
                for ( particles = cloth.particles, i = 0, il = particles.length; i < il; i ++ ) {
                    particle = particles[ i ];
                    pos = particle.position;
                    if ( pos.y < - 250 ) {
                        pos.y = - 250;
                    }
                }
                // Pin Constraints
                for ( i = 0, il = pins.length; i < il; i ++ ) {
                    var xy = pins[ i ];
                    var p = particles[ xy ];
                    p.position.copy( p.original );
                    p.previous.copy( p.original );
                }
            }
            /* testing cloth simulation */
            var pinsFormation = [];
            var pins = [ 6 ];
            pinsFormation.push( pins );
            pins = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
            pinsFormation.push( pins );
            pins = [ 0 ];
            pinsFormation.push( pins );
            pins = []; // cut the rope ;)
            pinsFormation.push( pins );
            pins = [ 0, cloth.w ]; // classic 2 pins
            pinsFormation.push( pins );
            pins = pinsFormation[ 1 ];
            function togglePins() {
                pins = pinsFormation[ ~ ~ ( Math.random() * pinsFormation.length ) ];
            }
            var container, stats;
            var camera, scene, renderer;
            var clothGeometry;
            var sphere;
            var object;
            init();
            animate();
            function init() {
                container = document.createElement( 'div' );
                document.body.appendChild( container );
                // scene
                scene = new THREE.Scene();
                scene.background = new THREE.Color( 0xcce0ff );
                scene.fog = new THREE.Fog( 0xcce0ff, 500, 10000 );
                // camera
                camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 10000 );
                camera.position.set( 1000, 50, 1500 );
                // lights
                scene.add( new THREE.AmbientLight( 0x666666 ) );
                var light = new THREE.DirectionalLight( 0xdfebff, 1 );
                light.position.set( 50, 200, 100 );
                light.position.multiplyScalar( 1.3 );
                light.castShadow = true;
                light.shadow.mapSize.width = 1024;
                light.shadow.mapSize.height = 1024;
                var d = 300;
                light.shadow.camera.left = - d;
                light.shadow.camera.right = d;
                light.shadow.camera.top = d;
                light.shadow.camera.bottom = - d;
                light.shadow.camera.far = 1000;
                scene.add( light );
                // cloth material
                var loader = new THREE.TextureLoader();
                var clothTexture = loader.load( 'textures/patterns/circuit_pattern.png' );
                clothTexture.anisotropy = 16;
                var clothMaterial = new THREE.MeshLambertMaterial( {
                    map: clothTexture,
                    side: THREE.DoubleSide,
                    alphaTest: 0.5
                } );
                // cloth geometry
                clothGeometry = new THREE.ParametricBufferGeometry( clothFunction, cloth.w, cloth.h );
                // cloth mesh
                object = new THREE.Mesh( clothGeometry, clothMaterial );
                object.position.set( 0, 0, 0 );
                object.castShadow = true;
                scene.add( object );
                object.customDepthMaterial = new THREE.MeshDepthMaterial( {
                    depthPacking: THREE.RGBADepthPacking,
                    map: clothTexture,
                    alphaTest: 0.5
                } );
                // sphere
                var ballGeo = new THREE.SphereBufferGeometry( ballSize, 32, 16 );
                var ballMaterial = new THREE.MeshLambertMaterial();
                sphere = new THREE.Mesh( ballGeo, ballMaterial );
                sphere.castShadow = true;
                sphere.receiveShadow = true;
                sphere.visible = false;
                scene.add( sphere );
                // ground
                var groundTexture = loader.load( 'textures/terrain/grasslight-big.jpg' );
                groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
                groundTexture.repeat.set( 25, 25 );
                groundTexture.anisotropy = 16;
                var groundMaterial = new THREE.MeshLambertMaterial( { map: groundTexture } );
                var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 20000, 20000 ), groundMaterial );
                mesh.position.y = - 250;
                mesh.rotation.x = - Math.PI / 2;
                mesh.receiveShadow = true;
                scene.add( mesh );
                // poles
                var poleGeo = new THREE.BoxBufferGeometry( 5, 375, 5 );
                var poleMat = new THREE.MeshLambertMaterial();
                var mesh = new THREE.Mesh( poleGeo, poleMat );
                mesh.position.x = - 125;
                mesh.position.y = - 62;
                mesh.receiveShadow = true;
                mesh.castShadow = true;
                scene.add( mesh );
                var mesh = new THREE.Mesh( poleGeo, poleMat );
                mesh.position.x = 125;
                mesh.position.y = - 62;
                mesh.receiveShadow = true;
                mesh.castShadow = true;
                scene.add( mesh );
                var mesh = new THREE.Mesh( new THREE.BoxBufferGeometry( 255, 5, 5 ), poleMat );
                mesh.position.y = - 250 + ( 750 / 2 );
                mesh.position.x = 0;
                mesh.receiveShadow = true;
                mesh.castShadow = true;
                scene.add( mesh );
                var gg = new THREE.BoxBufferGeometry( 10, 10, 10 );
                var mesh = new THREE.Mesh( gg, poleMat );
                mesh.position.y = - 250;
                mesh.position.x = 125;
                mesh.receiveShadow = true;
                mesh.castShadow = true;
                scene.add( mesh );
                var mesh = new THREE.Mesh( gg, poleMat );
                mesh.position.y = - 250;
                mesh.position.x = - 125;
                mesh.receiveShadow = true;
                mesh.castShadow = true;
                scene.add( mesh );
                // renderer
                renderer = new THREE.WebGLRenderer( { antialias: true } );
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                container.appendChild( renderer.domElement );
                renderer.gammaInput = true;
                renderer.gammaOutput = true;
                renderer.shadowMap.enabled = true;
                // controls
                var controls = new OrbitControls( camera, renderer.domElement );
                controls.maxPolarAngle = Math.PI * 0.5;
                controls.minDistance = 1000;
                controls.maxDistance = 5000;
                // performance monitor
                stats = new Stats();
                container.appendChild( stats.dom );
                //
                window.addEventListener( 'resize', onWindowResize, false );
                //
                var gui = new GUI();
                gui.add( params, 'enableWind' );
                gui.add( params, 'showBall' );
                gui.add( params, 'tooglePins' );
            }
            //
            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );
            }
            //
            function animate() {
                requestAnimationFrame( animate );
                var time = Date.now();
                var windStrength = Math.cos( time / 7000 ) * 20 + 40;
                windForce.set( Math.sin( time / 2000 ), Math.cos( time / 3000 ), Math.sin( time / 1000 ) );
                windForce.normalize();
                windForce.multiplyScalar( windStrength );
                simulate( time );
                render();
                stats.update();
            }
            function render() {
                var p = cloth.particles;
                for ( var i = 0, il = p.length; i < il; i ++ ) {
                    var v = p[ i ].position;
                    clothGeometry.attributes.position.setXYZ( i, v.x, v.y, v.z );
                }
                clothGeometry.attributes.position.needsUpdate = true;
                clothGeometry.computeVertexNormals();
                sphere.position.copy( ballPosition );
                renderer.render( scene, camera );
            }

0 ответов

Другие вопросы по тегам