在他们网站上显示的示例中,正在使用的布料质地为正方形,如您所见here。
但是,在示例页面中,它的高度显示为拉伸,如下面的屏幕截图所示。禁用风时,您会很容易注意到这一点。
如何保持正确的宽高比,为什么在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 );
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,z,mass ) {
this.position = new THREE.Vector3();
this.previous = new THREE.Vector3();
this.original = new THREE.Vector3();
this.a = new THREE.Vector3( 0,0 ); // acceleration
this.mass = mass;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
// init
clothFunction( x,this.position ); // position
clothFunction( x,this.previous ); // previous
clothFunction( x,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 );
};
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,MASS )
);
}
}
// Structural
for ( v = 0; v
particles[ index( u,v ) ],particles[ index( u,v + 1 ) ],restDistance
] );
constraints.push( [
particles[ index( u,particles[ index( u + 1,restDistance
] );
}
}
for ( u = w,v = 0; v
particles[ index( u,restDistance
] );
}
for ( v = h,u = 0; u
particles[ index( u,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
// particles[index(u,v)],// particles[index(u+1,v+1)],// diagonalDist
// ]);
// constraints.push([
// particles[index(u+1,// particles[index(u,// 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
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
particle.addForce( gravity );
particle.integrate( TIMESTEP_SQ );
}
// Start Constraints
cOnstraints= cloth.constraints;
il = constraints.length;
for ( i = 0; 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,il = particles.length; i
var pos = particle.position;
diff.subVectors( pos,ballPosition );
if ( diff.length()
diff.normalize().multiplyScalar( ballSize );
pos.copy( ballPosition ).add( diff );
}
}
} else {
sphere.visible = false;
}
// Floor Constraints
for ( particles = cloth.particles,il = particles.length; i
pos = particle.position;
if ( pos.y <- 250 ) {
pos.y = - 250;
}
}
// Pin Constraints
for ( i = 0,il = pins.length; 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,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 );
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 ),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
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 );
}