/*************************************************************************** File: SimCloth.cpp Created: 01/22/02 Author: Maxim Garber Computer Science Department University of North Carolina - Chapel Hill garber@cs.unc.edu Description: Cloth simulation class ----------------------------------------------------------------------------. Copyright 2002 Maxim Garber *****************************************************************************/ #include "SimCloth.h" #include #include #include // open GL headers #include #include "GL\gl.h" #include "GL\glu.h" #include "tga.h" /**************************************************************************** Function : _ReadParameterFile Description: Reads in cloth parameters from a data file ****************************************************************************/ void SimCloth::_ReadParameterFile(const char* file_name) { FILE* fp = fopen(file_name,"r"); if (fp==NULL) { printf("ERROR: unable to open ObjFile [%s]!\n",file_name); exit(1); } char string[50]; /////////////////////////////////////////////////////////////////////////// // Read Parameters // fscanf(fp, "%s", &string); if(strcmp(string, "[cloth_parameters]") != 0) { printf("ERROR: invalid ObjFile format [%s]!\n",file_name); exit(1); } // number of rows fscanf(fp, "%s", &string); fscanf(fp, "%d", &_rows); // number of cols fscanf(fp, "%s", &string); fscanf(fp, "%d", &_cols); // alocate memory _pControlPoints = new float[_rows*_cols*3]; _pOldControlPoints = new float[_rows*_cols*3]; _pIsFree = new unsigned char[_rows*_cols]; _pNormals = new float[_rows*_cols*3]; // initialize isFree flags unsigned char *is = _pIsFree; for(unsigned int i=0; i<_rows*_cols; i++, is++) *is = (unsigned char) 1; // read next attribute fscanf(fp, "%s", &string); while(strcmp(string, "[end]") != 0) { // center point if(strcmp(string, "center:") == 0) for(unsigned int i = 0; i < 3; i++) fscanf(fp, "%f", &_centerPoint[i]); // normal axis if(strcmp(string, "nomal_axis:") == 0) { char axis[3]; fscanf(fp, "%s", &axis); // sign if(strchr(axis, '-') != NULL) _reverseNormals = true; // axis if(strchr(axis, 'X') != NULL) {_hIndex = 2; _vIndex = 1;} else if(strchr(axis, 'Y') != NULL) {_hIndex = 0; _vIndex = 2;} else if(strchr(axis, 'Z') != NULL) {_hIndex = 0; _vIndex = 1;} } // cloth width if(strcmp(string, "width:") == 0) fscanf(fp, "%f", &_width); // cloth height if(strcmp(string, "height:") == 0) fscanf(fp, "%f", &_height); // pin vertex if(strcmp(string, "pinned:") == 0) { unsigned int row=0, col=0; fscanf(fp, "%d", &col); fscanf(fp, "%d", &row); SetIsFree(col, row, 0); } // color if(strcmp(string, "color:") == 0) { unsigned int HexColor, RedMask=0xff0000, GreenMask=0xff00, BlueMask=0xff; fscanf(fp,"%x",&HexColor); _color[0] = (unsigned char)((HexColor & RedMask) >> 16); _color[1] = (unsigned char)((HexColor & GreenMask) >> 8); _color[2] = (unsigned char)(HexColor & BlueMask); } // cloth texture if(strcmp(string, "texture:") == 0){ fscanf(fp, "%s", &_texFile); _textured = true; } if(strcmp(string, "tiles_wide:") == 0) fscanf(fp, "%d", &_texTileWidth); if(strcmp(string, "tiles_high:") == 0) fscanf(fp, "%d", &_texTileHeight); // physical simulation if(strcmp(string, "inverse_mass:") == 0) fscanf(fp, "%f", &_pointInverseMass); if(strcmp(string, "damping_factor:") == 0) fscanf(fp, "%f", &_dampingFactor); if(strcmp(string, "gravity:") == 0) for(unsigned int i = 0; i < 3; i++) fscanf(fp, "%f", &_gravity[i]); if(strcmp(string, "num_iterations:") == 0) fscanf(fp, "%d", &_numIterations); // read next attribute fscanf(fp, "%s", &string); } } /***************************************************************************** FUNCTION : SimCloth DESCRIPTION: Initializes a rectangular cloth in x, y plane with specified dimensions *****************************************************************************/ SimCloth::SimCloth(const char* file_name) { // set default cloth parameters Set3fv(_centerPoint, 0.0f, 0.0f, 0.0f); _hIndex = 0; _vIndex = 1; _width = 0.0f; _height = 0.0f; _dampingFactor = 0.95f; Set3fv(_gravity, 0.0f, -9.8f, 0.0f); _numIterations = 2; _reverseNormals = false; _textured = NULL; _ReadParameterFile(file_name); // check input parameters if(_rows < 2 || _cols < 2 || _height <= 0 || _width <= 0) { printf("Invalid Cloth Initialization"); exit(0); } // calculate constriant strut lengths _horizStrutLength = _width / (_cols-1.0f); _vertStrutLength = _height / (_rows-1.0f); _diagStrutLength = (float) sqrt(_horizStrutLength*_horizStrutLength + _vertStrutLength * _vertStrutLength); _horizStrutLength2 = _horizStrutLength*_horizStrutLength; _vertStrutLength2 = _vertStrutLength*_vertStrutLength; _diagStrutLength2 = _diagStrutLength*_diagStrutLength; // initialize all the control points float* pt = _pControlPoints; unsigned int row = 0, col = 0; float point[3] = {_centerPoint[0], _centerPoint[1], _centerPoint[2]}; point[_hIndex] -= _width/2; point[_vIndex] += _height/2; for(; row<_rows; pt+=3, col++) { Copy3fv(pt, point); point[_hIndex] += _horizStrutLength; if(col == _cols-1) { col = -1; row++; point[_hIndex] = _centerPoint[_hIndex] - _width/2; point[_vIndex] -= _vertStrutLength; } } if(_textured) { // initialize texture glEnable(GL_TEXTURE_2D); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); _pTexture = NULL; LoadTGA(_texFile, _pTexture, _texWidth, _texHeight, _texChannels); glGenTextures(1, &_texName); // bind the texture glBindTexture(GL_TEXTURE_2D, _texName); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); if(_texChannels == 4) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _texWidth, _texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, _pTexture); else if(_texChannels == 3) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, _texWidth, _texHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, _pTexture); } _InitializeSimulation(); } /***************************************************************************** FUNCTION : ~SimCloth DESCRIPTION: Deletes all allocated memory *****************************************************************************/ SimCloth::~SimCloth() { // delete each row of control points delete(_pControlPoints); delete(_pOldControlPoints); delete(_pIsFree); delete(_pNormals); _pControlPoints = NULL; _pOldControlPoints = NULL; _pIsFree = NULL; _pNormals = NULL; } /***************************************************************************** FUNCTION : Draw DESCRIPTION: Draws the cloth polygons as a tri-strip with vertex normals for lighting *****************************************************************************/ void SimCloth::Draw() const { ///////////////////////////////////////////////////////////// // Draw the cloth if(_textured) { // texture coords glEnable(GL_TEXTURE_2D); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBindTexture(GL_TEXTURE_2D, _texName); } glColor3ubv(_color); // normalize normal vectors glEnable(GL_NORMALIZE); float *pt = _pControlPoints; // current control point float *nextpt = _pControlPoints + 3*_cols; // control point one row down. float *n = _pNormals; // normal vector float *nextn = _pNormals + 3*_cols; // next normal vector unsigned int row = 0, col = 0; for(; row<_rows-1; pt+=3, nextpt+=3, n+=3, nextn+=3, col++) { // start of a new row if(col == 0) glBegin(GL_TRIANGLE_STRIP); glNormal3fv(n); glTexCoord2f(_texTileWidth*(float)(col) /_cols, _texTileHeight*(float)(row )/_rows); glVertex3fv(pt); glNormal3fv(nextn); glTexCoord2f(_texTileWidth*(float)(col) /_cols, _texTileHeight*(float)(row+1) /_rows); glVertex3fv(nextpt); // increment to next row down if(col == _cols-1) { col = -1; row++; glEnd(); } } // diable texture and mormalization glDisable(GL_NORMALIZE); if(_textured) glDisable(GL_TEXTURE_2D); /////////////////////////////////////////////////// // draw the cloths shadow glColor4f(0.0f, 0.0f, 0.0f, 0.3f); glNormal3f(0.0f, 1.0f, 0.0f); pt = _pControlPoints; // current control point nextpt = _pControlPoints + 3*_cols; // control point one row down. n = _pNormals; // normal vector nextn = _pNormals + 3*_cols; // next normal vector for(row = 0, col = 0; row<_rows-1; pt+=3, nextpt+=3, n+=3, nextn+=3, col++) { // start of a new row if(col == 0) glBegin(GL_TRIANGLE_STRIP); glVertex3f(*pt, 0.01f, *(pt+2)); glVertex3f(*nextpt, 0.01f, *(nextpt+2)); // increment to next row down if(col == _cols-1) { col = -1; row++; glEnd(); } } } /***************************************************************************** FUNCTION : Update DESCRIPTION: Updates cloth control points for the elapsed timestep under all forces considered. *****************************************************************************/ void SimCloth::Update(float timeStep) { _timeStep = timeStep; _Integrate(); _SatisfyConstraints(); // prepare to render _ComputeNormals(); } /***************************************************************************** FUNCTION : mInitializeSimulation DESCRIPTION: Initilializes simulation values; *****************************************************************************/ void SimCloth::_InitializeSimulation() { _timeStep = 0.0f; unsigned int i; // set previous control points equal to current control points float* pt = _pControlPoints; float* oldpt = _pOldControlPoints; for(i=0; i < _rows*_cols; pt+=3, oldpt+=3, i++) Copy3fv(oldpt, pt); // set normals to render _ComputeNormals(); } /***************************************************************************** FUNCTION : mIntegrate DESCRIPTION: Uses Verlet integration to update cloth particle positions due to applied forces. *****************************************************************************/ void SimCloth::_Integrate() { float timeStep2 = _timeStep*_timeStep; // pointers for traversal float *pt = _pControlPoints, *oldpt = _pOldControlPoints; unsigned char *isfree = _pIsFree; // temporary vectors float temp[3], velocity[3], force[3]; ScalarMult3fv(force, _gravity, timeStep2); // iterate through all control points for(unsigned int i=0; i < _rows*_cols; pt+=3, oldpt+=3, isfree++, i++) { if(*isfree) { // save current control point location Copy3fv(temp, pt); // update control point by the formula // x += (x - old_x)*dampingFactor + force*timeStep^2 Subtract3fv(velocity, pt, oldpt); ScalarMult3fv(velocity, velocity, _dampingFactor); Add3fv(pt, pt, velocity); Add3fv(pt, pt, force); // store old control point location Copy3fv(oldpt, temp); } } } /***************************************************************************** FUNCTION : mSatisfyConstraints DESCRIPTION: Uses Jacobi relaxation to satisfy constriants using a maximum number of iterations; *****************************************************************************/ void SimCloth::_SatisfyConstraints() { float delta[3]; for(unsigned int iter = 0; iter < _numIterations; iter++) { // interate through control points float *pt = _pControlPoints; // current control point float *nextpt = _pControlPoints + 3*_cols; // control point one row down. unsigned char *isfree = _pIsFree; // current isFree flag unsigned char *nextisfree = _pIsFree + _cols; // isFree flag one row down unsigned int row = 0, col = 0; for(; row<_rows; pt+=3, nextpt+=3, isfree++, nextisfree++, col++) { /*************************************************************** Collision Detection ***************************************************************/ // collision with floor if(pt[1] < 0.0f) pt[1] = 0.0f; // collision with spheres std::list::iterator iter = _pSpheres.begin(); float difference; for(; iter != _pSpheres.end(); iter++) { Subtract3fv(delta, pt, (*iter)->GetCenter()); difference = (*iter)->GetRadius() - Length3fv(delta); if(difference > 0) { // we have a collision project objects to resolve collision float massFactor = _pointInverseMass+(*iter)->GetInverseMass(); Normalize3fv(delta); // bahavior depends on which object is free to move char freeValue = (isfree ? 1 : 0) + ((*iter)->GetIsFree() ? 2 : 0); switch(freeValue) { case 0: // neither is free break; case 1: // only point is free ScalarMult3fv(delta, delta, difference); Add3fv(pt, pt, delta); break; case 2: // only sphere is free ScalarMult3fv(delta, delta, -difference); (*iter)->MoveCenter(delta); break; case 3: // both are free { float pointDelta[3], sphereDelta[3]; // move point ScalarMult3fv(pointDelta, delta, difference*_pointInverseMass/massFactor); Add3fv(pt, pt, pointDelta); // move sphere ScalarMult3fv(sphereDelta, delta, (-difference)*(*iter)->GetInverseMass()/massFactor); (*iter)->MoveCenter(sphereDelta); break; } } } } /*************************************************************** Cloth Mesh Constraints with sqrt approximation The constraint between xi and xj is satisfied by the formula: delta = [(xj - xi) * strutLength^2 / (|(xj - xi)|^2 + strutLenth^2)] - 1/2 xi -= delta xj += delta ***************************************************************/ if(col < _cols-1) { // enforce horizontal strut constriants Subtract3fv(delta, pt+3, pt); float temp = _horizStrutLength2 / (LengthSquared3fv(delta)+ _horizStrutLength2) - 0.5f; ScalarMult3fv(delta, delta, temp); if(*isfree) Subtract3fv(pt, pt, delta); if(*(isfree+1))Add3fv (pt+3, pt+3, delta); } if(row < _rows-1) { // enforce vertical strut constriants Subtract3fv(delta, nextpt, pt); float temp = _vertStrutLength2 / (LengthSquared3fv(delta)+ _vertStrutLength2) - 0.5f; ScalarMult3fv(delta, delta, temp); if(*isfree) Subtract3fv(pt, pt, delta); if(*nextisfree) Add3fv (nextpt, nextpt, delta); } if(row < _rows-1 && col < _cols-1) { // enforce diagonal strut constriants Subtract3fv(delta, nextpt+3, pt); float temp = _diagStrutLength2/(LengthSquared3fv(delta)+_diagStrutLength2) - 0.5f; ScalarMult3fv(delta, delta, temp); if(*isfree) Subtract3fv(pt, pt, delta); if(*(nextisfree+1)) Add3fv(nextpt+3, nextpt+3, delta); } // increment to next row down if(col == _cols-1) { col = -1; row++; } } } } void SimCloth::ChangeTightness(float mutiplier) { _horizStrutLength *= mutiplier; _vertStrutLength *= mutiplier; _diagStrutLength *= mutiplier; _horizStrutLength2 = _horizStrutLength*_horizStrutLength; _vertStrutLength2 = _vertStrutLength*_vertStrutLength; _diagStrutLength2 = _diagStrutLength*_diagStrutLength; } void SimCloth::_ComputeNormals() { float *pt = _pControlPoints; // current control point float *nextpt = _pControlPoints + 3*_cols; // control point one row down. float *n = _pNormals; // current normal vector float *nextn = _pNormals + 3*_cols; // next normal vector unsigned int row=0, col=0; // set all normals to zero unsigned int size=_rows*_cols*3; for(unsigned int i=0; i