Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
#include "bod_bob_parser.h"#include "../common/strutils.h"#define ISMATERIAL(text) ((strcmp(text, "MATERIAL6")==0 || strcmp(text, "MATERIAL5")==0 || \strcmp(text, "MATERIAL3")==0 || strcmp(text, "MATERIAL")==0))//---------------------------------------------------------------------------------geometry::point2d<double> bod_bob_parser::uv_coord::NULL_COORDS(123456, 78910);//---------------------------------------------------------------------------------bool bod_bob_parser::compile(token_stream& is, obinaryfilestream& os, bool bEmbedded){bob_dom_bob *bob=loadBOB(is, bEmbedded);if(bob)bob->toFile(os);delete bob;return bob!=NULL;}//---------------------------------------------------------------------------------bob_dom_bob * bod_bob_parser::loadBOB(token_stream& is, bool bEmbedded){token *t;bob_dom_bob *bob=new bob_dom_bob(m_settings);bool abort=false;while((t=is.tok()) && t->type==token::t_hdrinfo){if(bob->info.text()!=0)error(is.tok(), S_Warning, "Header info already defined, last value will be used");bob->info.text(t->text);++is;}// first load material definitions, break on first numeric tokenwhile((t=is.tok()) && t->type!=token::t_closeCrBracket){if(t->type!=token::t_text){error(t, S_Error, "Unexpected here: '%s'", t->getText());abort=true; break;}if(isinteger(t->text)) break;if(ISMATERIAL(t->text)){if(!loadMaterial(bob, is)){abort=true; break;}}else{error(t, S_Error, "Unexpected here '%s'", t->getText());abort=true; break;}}// now load the body definition: body size, points, partsif(!abort){m_materials=&(bob->materials);do{if(abort=(loadBody(bob, is)==false)) break;if(bEmbedded){if(is.tok()==NULL){error(is.previous(), S_Error, "Unexpected end of document found while loading embedded BOB - do you miss the '}'?");abort=true; break;}else if(is.tok()->type==token::t_closeCrBracket)break;}else if(is.eof())break;}while(1); // while what? while '}' if embedded or while eof if standalone}if(abort){delete bob;bob=NULL;}++is; // advance past the close bracket (if embedded)return bob;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMatRGB(token_stream& is, bob_material5::rgb& rgb){int vals[3];token *t;for(int i=0; i < 3; i++){t=loadValue(is);if(t==NULL) return false;if(!isinteger(t->text)){error(t, S_Error, "Expected integer value at position %d in RBG", i);return false;}vals[i]=atoi(t->text);}rgb.r=vals[0];rgb.g=vals[1];rgb.b=vals[2];return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMatPair(token_stream& is, bob_material5::pair& pair){int vals[2];for(int i=0; i < 2; i++){if(!loadIntValue(is, vals + i))return false;}pair.value=vals[0];pair.strength=vals[1];return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMatPair(token_stream& is, bob_material6::Small::pair& pair){int i;if((pair.texture=loadString(is, "texture name"))==0) return false;if(strcmp(pair.texture, "NULL")==0){delete pair.texture; pair.texture=0;}if(loadIntValue(is, &i, "texture strength")==false) return false;pair.strength=i;return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::checkImmediatePlacement(const bod_bob_parser::token *token1, const bod_bob_parser::token *token2){if(token1->line!=token2->line || (token1->col + strlen(token1->getText())!=token2->col)){error(token2, S_Error, "'%s' must be placed directly after '%s'", token2->getText(), token1->getText());return false;}elsereturn true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMat(bob_material5 *mat, token_stream &is){do{int i;bob_material5::rgb rgb;bob_material5::pair pair;if(!loadIntValue(is, &i)) break;mat->index=i;if(!loadIntValue(is, &i)) break;mat->textureID=i;if(!loadMatRGB(is, rgb)) break;mat->ambient=rgb;if(!loadMatRGB(is, rgb)) break;mat->diffuse=rgb;if(!loadMatRGB(is, rgb)) break;mat->specular=rgb;if(mat->type!=bob_material::mat1) {if(!loadIntValue(is, &i)) break;mat->transparency=i;if(!loadIntValue(is, &i)) break;mat->selfIllumination=i;if(!loadMatPair(is, pair)) break;mat->shininess=pair;if(!loadIntValue(is, &i)) break;mat->destinationBlend=i!=0;if(!loadIntValue(is, &i)) break;mat->twoSided=i!=0;if(!loadIntValue(is, &i)) break;mat->wireframe=i!=0;if(!loadIntValue(is, &i)) break;mat->textureValue=i;if(!loadMatPair(is, pair)) break;mat->enviromentMap=pair;if(!loadMatPair(is, pair)) break;mat->bumpMap=pair;if(mat->type==bob_material::mat5){if(!loadMatPair(is, pair)) break;mat->lightMap=pair;}}return true;}while(false);return false;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMat(bob_material6 *mat, token_stream &is){int i; bool bRes=false;do{if(!loadIntValue(is, &i)) break;mat->index=i;if(!loadIntValue(is, &i)) break;mat->flags=i;if(mat->flags==bob_material6::Big::flag)bRes=loadMaterial6Big(mat, is);elsebRes=loadMaterial6Small(mat, is);}while(false);return bRes;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMaterial6Small(bob_material6 *mat, token_stream &is){int i;bob_material1::rgb rgb;bob_material1::pair int_pair;bob_material6::Small::pair pair;mat->small=new bob_material6::Small();do{if((mat->small->textureFile=loadString(is, "texture name: "))==0) break;if(strcmp(mat->small->textureFile, "NULL")==0){ delete[] mat->small->textureFile; mat->small->textureFile=0; }if(!loadMatRGB(is, rgb)) break;mat->small->ambient=rgb;if(!loadMatRGB(is, rgb)) break;mat->small->diffuse=rgb;if(!loadMatRGB(is, rgb)) break;mat->small->specular=rgb;if(!loadIntValue(is, &i)) break;mat->small->transparency=i;if(!loadIntValue(is, &i)) break;mat->small->selfIllumination=i;if(!loadMatPair(is, int_pair)) break;mat->small->shininess=int_pair;if(!loadIntValue(is, &i)) break;mat->small->destinationBlend=i!=0;if(!loadIntValue(is, &i)) break;mat->small->twoSided=i!=0;if(!loadIntValue(is, &i)) break;mat->small->wireframe=i!=0;if(!loadIntValue(is, &i)) break;mat->small->textureValue=i;if(!loadMatPair(is, pair)) break;mat->small->enviromentMap=pair;if(!loadMatPair(is, pair)) break;mat->small->bumpMap=pair;if(!loadMatPair(is, pair)) break;mat->small->lightMap=pair;if(!loadMatPair(is, pair)) break;mat->small->map4=pair;if(!loadMatPair(is, pair)) break;mat->small->map5=pair;short flags=0;if(mat->small->destinationBlend) flags&=0x2;if(mat->small->twoSided) flags&=0x10;if(mat->small->wireframe) flags&=0x8;mat->flags=flags;return true;}while(false);return false;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMaterial6Big(bob_material6 *mat, token_stream &is){int count,i;bob_material6::Big *big=new bob_material6::Big();mat->big=big;if(!loadIntValue(is, &i, "Technique idx: ")) return false;big->technique=i;if((big->effect=loadString(is, "Effect name: "))==0) return false;material6_value *val;if(!loadIntValue(is, &count, "Value count: ")) return false;for(i=0; i < count; i++){if((val=loadMat6Value(is))==0) return false;big->values.push_back(val);}return true;}//---------------------------------------------------------------------------------material6_value * bod_bob_parser::loadMat6Value(token_stream& is){bool bRes=false;// name, type, valuechar *name, *strtype;material6_value *val=new material6_value();token_stream::token *typepos;do{if((name=loadString(is, "Property name: "))==0) break;if(name[0]==0) {error(is.tok(), S_Error, "Empty property name");return false;}if((strtype=loadString(is, "Property type: "))==0) break;typepos=is.tok();int type=-1;for(int i=0; i < material6_value::typeNameCount(); i++){if(strcmp(strtype, material6_value::typeName(i))==0) {if(strtype[0]!=0)type=i;break;}}if(type==-1){error(typepos, S_Error, "Unknown property type: \"%s\"", strtype);break;}val->type=(material6_value::Type)type;val->name=name;name=0;double d;switch(val->type){case material6_value::typeBool: // nobreakcase material6_value::typeLong:bRes=loadIntValue(is, &val->val.i, "Property value: ");break;case material6_value::typeString:bRes=((val->val.psz=loadString(is, "Property value: "))!=0);break;case material6_value::typeFloat:bRes=loadDoubleValue(is, &d, "Property value: ");val->val.f=(float)d;break;case material6_value::typeFloat4:for(int i=0; i < 4; i++){if((bRes=loadDoubleValue(is, &d, "Property value"))==false) break;val->val.f4.f[i]=(float)d;}break;default:error(0, S_Error, "Don't know how to output type \"%s\"", strtype);bRes=false;}if(bRes==false)break;bRes=true;}while(0);delete[] name; delete[] strtype;if(bRes==false) {delete val;val=0;}return val;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadMaterial(bob_dom_bob *bob, token_stream& is){bob_material5::materialType type;token *t=is.tok();if(t){if(strcmp(t->getText(), "MATERIAL6")==0)type=bob_material::mat6;else if(strcmp(t->getText(), "MATERIAL5")==0)type=bob_material::mat5;else if(strcmp(t->getText(), "MATERIAL3")==0)type=bob_material::mat3;else if(strcmp(t->getText(), "MATERIAL")==0)type=bob_material5::mat1;}if(t==NULL || t->type!=token::t_text){error(t ? t : is.previous(), S_Error, "Expected material header (MATERIAL, MATERIAL3, MATERIAL5 or MATERIAL6)");return NULL;}t=(++is).tok();if(t==NULL || t->type!=token::t_colon){error(t ? t : is.previous(), S_Error, "Expected ':' after '%s'", is.previous()->getText());return NULL;}if(!checkImmediatePlacement(is.previous(), is.tok()))return NULL;++is;bool bRes;bob_material *mat;switch(type) {case bob_material::mat6:mat=new bob_material6();bRes=loadMat((bob_material6*)mat, is);break;default: // always mat5 because lower mats are not supported in binary formmat=new bob_material5();mat->type=type;bRes=loadMat((bob_material5*)mat, is);break;}if(bRes==false)delete mat;elsebob->materials.push_back(mat);return bRes;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadBody(bob_dom_bob *bob, token_stream& is){static const char *context="Body: ";bob_body *body=bob->bodies.createChild();VertexList list;deleteTempData();loadIntValue(is, &(body->bodySize), context);if(!loadBones(body, is)){bob->bodies.removeChild(--bob->bodies.end());return false;}if(!loadVertices(body, is, list)) {bob->bodies.removeChild(--bob->bodies.end());return false;}if(!loadWeights(body, is)){bob->bodies.removeChild(--bob->bodies.end());return false;}if(m_weights.size() && m_weights.size()!=list.size()){error(0, S_Error, "Number of Weights (%d) does not match number of Points (%d)", m_weights.size(), list.size());bob->bodies.removeChild(--bob->bodies.end());return false;}// move the points to array so we can access them with indexm_vertices.resize(list.size());int i=0;for(VertexList::iterator &it=list.begin(); it!=list.end(); ++it, ++i){m_vertices[i]=*it;}list.clear();// reserve space in the vertex array (some of the vertices will be duplicated)body->vertices.new_vertices.reserve((size_t)(m_vertices.size() * 2));body->vertices.new_vertices.growby((size_t)(m_vertices.size() * 0.25));int endval;do{if(!loadPart(body, is)) {bob->bodies.removeChild(--bob->bodies.end());return false;}loadIntValue(is, &endval);if(endval!=-99)ungetValue(is);}while(is.good() && endval!=-99); // -99 means end of bodyif(endval!=-99){error(is.previous(), S_Error, "Unexpected end of document found while loading Body. Do you miss the '-99'?");bob->bodies.removeChild(--bob->bodies.end());return false;}if(!flagsFromString(is, body->bodyFlags)){bob->bodies.removeChild(--bob->bodies.end());return false;}computeVertexTangents(*body);return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadVertices(bob_body *body, token_stream& is, VertexList& points){bool bEnd;int x, y, z;static const char *context="Vertex: ";do{loadIntValue(is, &x, context);loadIntValue(is, &y, context);if(!loadIntValue(is, &z, context)) return false;if(x==-1 && y==-1 && z==-1) {bEnd=true;break;}/* SameVertices will take care of translating the BOD coords to BOB format */points.push_back(new SameVertices(x, y, z));}while(is.good());if(!bEnd){error(is.previous(), S_Error, "Unexpected end of document found while loading Points. Do you miss the '-1;-1;-1;'?");return false;}return true;}//---------------------------------------------------------------------------------// load part - bunch of faces/uv'sbool bod_bob_parser::loadPart(bob_body *body, token_stream& is){int matIdx;bool bRes=true;static const char *context="Parts: ";bool x3data=false;bob_part *part=new bob_part();loadIntValue(is, &matIdx, context);do{// no longer truth - negative number is direct texture ID of animated texture/*if(matIdx < 0){ error(is.previous(), S_Error, "%sMaterial index must be >= 0", context); bRes=false; break; }*/if(matIdx > 0 && ((size_t)matIdx > m_materials->size() - 1))error(is.previous(), S_Warning, "%sMaterial index out of range: %d", context, matIdx);if((bRes=loadFace(matIdx, *body, *part, is))==false)break;if(is.tok() && is.tok()->type==token::t_openInstrBlock) {x3data = true;if((bRes = loadCollisionBoxData(++is, *part)) == false)break;}loadIntValue(is, &matIdx, context);}while(matIdx!=-99 && is.good()); // -99 means end of partif(bRes && matIdx != -99){error(is.previous(), S_Error, "Unexpected end of document found while loading Part. Do you miss the '-99'?");bRes = false;}if(bRes)bRes = flagsFromString(is, part->flags);// set x3 flag if either we loaded x3 part values or settings::writeX3Data is setif(x3data || m_settings->writeX3Data())part->flags|=bob_part::FLAG_X3;if(bRes)body->parts.push_back(part);elsedelete part;return bRes;};//---------------------------------------------------------------------------------bool bod_bob_parser::flagsFromString(token_stream& is, int& flags){bool bRes;token *t=loadValue(is);flags=0;if(bRes=(t!=NULL)){int len=(int)strlen(t->text);int val=1;for(int i=len-1; i >= 0; i--, val<<=1 ){switch(t->text[i]){case '0':break;case '1':flags|=val;break;default:error(is.previous(), S_Error, "Invalid flags definition. Expected binary number");bRes=false;break;}if(bRes==false) break;}}return bRes;}//---------------------------------------------------------------------------------// load face - actual face and its uv'sbool bod_bob_parser::loadFace(int matIdx, bob_body& body, bob_part& part, token_stream& is){int indexes[3];int magic;int sgbits;double dtexX, dtexY;static const char *context="Part: ";for(int i=0; i < 3; i++){if(loadIntValue(is, indexes + i, context)){if(indexes[i] < 0 || (size_t)indexes[i] >= m_vertices.size()){error(is.previous(), S_Error, "Point index out of range: point %d, index %d", i, indexes[i]);return false;}}elsereturn false;}bool bRes=true;/*original (old) meaning of the 'magic' number (now I'm using flags)magic: -1 means no smoothing and no uv, -9 means no smoothing and uv-17 means smoothing and no uv, -25 means smoothing and uv*/bRes&=loadIntValue(is, &magic, context);if(magic >= 0){error(is.tok(), S_Error, "Faces are written using polygons not triangles - cannot continue");return false;}magic*=-1;if(magic & bob_part::BOD_FLAG_SGBITS)bRes&=loadIntValue(is, &sgbits, context);elsesgbits=0;if(bRes==false) return false;bob_vertex *vertex;bob_face *face=new bob_face();bob_vertex *points_ar[3];for(int i=0; i < 3; i++){if(magic & bob_part::BOD_FLAG_UV){loadDoubleValue(is, &dtexX, context);bRes&=loadDoubleValue(is, &dtexY, context);}else{dtexX=dtexY=0;}/*look (or create) for vertex with same coords and same sgbits*/SameSG_UVs *SGgroup=m_vertices[indexes[i]]->findTextureCoords(sgbits);uv_coord *uv=NULL;/*look for the same UV coords or use anything if no UV is specified*/if(magic & bob_part::BOD_FLAG_UV)uv=SGgroup->findUV(dtexX, dtexY);elseuv=SGgroup->findNullUV();if(uv==NULL){if(magic & bob_part::BOD_FLAG_UV)uv=SGgroup->createUV(dtexX, dtexY);elseuv=SGgroup->createNullUV();vertex=uv->createBOBVertex();uv->vertexIndex=(int)body.vertices.new_vertices.size();body.vertices.new_vertices.push_back(vertex);points_ar[i]=vertex; // this will be used to fetch values found in intruction block// now insert the appropriate weightif(m_weights.size())body.weights.new_weights.push_back(new bob_weight(*m_weights[indexes[i]]));}elsepoints_ar[i]=NULL;face->values[i]=uv->vertexIndex;}face->flags=1; // always 1 (or 0)part.facelist(matIdx)->push_back(face);bRes = loadSpecialValues(points_ar, is);return bRes;}//---------------------------------------------------------------------------------// load special values which may be "hidden" in instruction block after standard part definition// example: /! { 1;2;3; } { 4;5;6; } { 7;8;9; } !/bool bod_bob_parser::loadSpecialValues(bob_vertex *points[3], token_stream& is){bob_vertex dummy_pnt;token *t=is.tok();if(t && t->type==token::t_openInstrBlock){++is;t=is.tok();while(t != NULL && t->type != token::t_closeInstrBlock){if(t == NULL || t->type != token::t_text){error(t ? t : is.previous(), S_Error, "Expected identifier in instruction block.");return false;}if(strcmp(t->text, "N") == 0){if(!loadFaceNormal(is, points))return false;}else if(strcmp(t->text, "XPINFO") == 0){t = (++is).tok();if(t == NULL || t->type != token::t_colon){error(t ? t : is.previous(), S_Error, "Expected ':' after '%s'", is.previous()->getText());return false;}++is;for(int i = 0; i < 3; i++){if(!loadSpecialPointValues(is, points[i]))return false;}}else {error(t, S_Error, "Unexpected identifier in instruction block: '%s'", t);return false;}t=is.tok();}if(t==NULL || t->type!=token::t_closeInstrBlock){error(t ? t : is.previous(), S_Error, "Expected end of instruction block '!/' after '%s'", is.previous());return false;}++is;}return true;}//---------------------------------------------------------------------------------// load one block from extra info in instruction block// example: { 1; 2; 3; }// point can be null!bool bod_bob_parser::loadSpecialPointValues(token_stream& is, bob_vertex *point){static const char *context="Part - Extra point info: ";token *t=is.tok();if(t==NULL || t->type!=token::t_openCrBracket){error(t ? t : is.previous(), S_Error, "Expected beginning of block after '%s'", is.previous()->getText());return false;}++is;double x, y;if(!loadDoubleValue(is, &x, context))return false;if(!loadDoubleValue(is, &y, context))return false;if(point != NULL){point->weirdCoords.x = x;point->weirdCoords.y = y;point->flags|=bob_vertex::FLAG_WEIRD_STUFF;}if(is.tok()==NULL || is.tok()->type!=token::t_closeCrBracket){if(is.tok()==NULL)error(is.previous(), S_Error, "Unexpected end of document found while loading extra point info. Do you miss the '}'?");elseerror(t, S_Error, "Unexpected '%s' found while loading extra point info", t->getText());return false;}++is;return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadBones(bob_body *body, token_stream& is){const token *t=is.tok();static const char *context2="Bones: ";static const char *context="Bone: ";if(!(t && t->type==token::t_text && strcmp(t->text, "BONES")==0))return true;t=(++is).tok();if(t==NULL || t->type!=token::t_colon){error(t ? t : is.previous(), S_Error, "Expected ':' after '%s'", is.previous()->getText());return false;}if(!checkImmediatePlacement(is.previous(), t))return false;int count;if(!loadIntValue(++is, &count, context2))return false;char *name;for(int i=0; i < count; i++){name=loadString(is, context);if(name==NULL) return false;body->bones.push_back(name);}return !is.fail();}//---------------------------------------------------------------------------------// load the weights to temporary array. Weights from this array will then be inflated and added to bodybool bod_bob_parser::loadWeights(bob_body *body, token_stream& is){const token *t=is.tok();static const char *context="Weight: ";static const char *context2="Weights: ";if(!(t && t->type==token::t_text && strcmp(t->text, "WEIGHTS")==0))return true;int count;if(!loadIntValue(++is, &count, context2))return false;bool bRes=true;int v; double d;bob_weight *w;m_weights.resize(count, 0);for(int i=0; i < count; i++){w=new bob_weight();do{if(!loadIntValue(is, &v, context)){ bRes=false; break; }if(v==-1)break;if(v < 0 || (size_t)v >= body->bones.size()){error(is.previous(), S_Error, "Weight: Bone index out of range (%d)", v);{ bRes=false; break; }}if(!loadDoubleValue(is, &d, context)){ bRes=false; break; }bob_weight::value val;val.boneIdx=v;__asm{/*val.boneCoefficient = bob_weight::value::multiplier * d*/fild bob_weight::value::MULTIPLIERfmul dfistp val.boneCoefficient}w->values.push_back(val);}while(!is.fail());if(is.fail()){error(is.previous(), S_Error, "Unexpected end of document found while loading Weights. Do you miss the '-1'?");bRes=false;}if(bRes==false){delete w;break;}m_weights[i]=w;}if(bRes){if(!loadIntValue(is, &v, context2))bRes=false;else if(v!=-1){error(is.previous(), S_Error, "Expected '-1' after end of Weights");bRes=false;}}return bRes;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadCollisionBoxData(token_stream& is, bob_part& part){static char *context="Collision box: ";static char *HDR="COLLISION_BOX";if(is.tok()==NULL || is.tok()->type!=token::t_text || strcmp(is.tok()->text, HDR)!=0) {error(is.tok() ? is.tok() : is.previous(), S_Error, "Expected '%s' here", HDR);return false;}if((++is).tok()==NULL || is.tok()->type!=token::t_colon){error(is.tok() ? is.tok() : is.previous(), S_Error, "Expected ':' after '%s'", HDR);return false;}++is;loadDoubleValue(is, &part.collisionBox.boxOffset.x, "Box offset");loadDoubleValue(is, &part.collisionBox.boxOffset.y, "Box offset");if(!loadDoubleValue(is, &part.collisionBox.boxOffset.z, "Box offset"))return false;if(!loadDoubleValue(is, &part.collisionBox.sphereDiameter, "Sphere diameter"))return false;loadDoubleValue(is, &part.collisionBox.boxSize.x, "Box size");loadDoubleValue(is, &part.collisionBox.boxSize.y, "Box size");if(!loadDoubleValue(is, &part.collisionBox.boxSize.z, "Box size"))return false;part.collisionBox.sphereOffset = part.collisionBox.boxOffset;if(is.tok()==NULL || is.tok()->type!=token::t_closeInstrBlock){error(is.tok() ? is.tok() : is.previous(), S_Error, "Expected '!/' after end of Collision box");return false;}++is;return true;}//---------------------------------------------------------------------------------bool bod_bob_parser::loadFaceNormal(token_stream& is, bob_vertex *points[3]){token *t;t=is.tok();if(t==NULL || t->type!=token::t_text || *t->text!='N'){error(t, S_Error, "Error loading face normal: expecting 'N:'");return false;}t=(++is).tok();if(t==NULL || t->type!=token::t_colon){error(t, S_Error, "Expecting ':' after '%'", is.previous()->getText());return false;}++is;t = is.tok();if(t == NULL || t->type != token::t_openCrBracket){error(t ? t : is.previous(), S_Error, "Expected beginning of block after '%s'", is.previous()->getText());return false;}++is;// buffer for normals - contains either 1 or 3 recordsnormal_vector normals[3];int i;// for each vertexfor(i = 0; i < 3; i++){double d[3];// load the vertex normalfor(int j=0; j < 3; j++){if(!loadDoubleValue(is, d + j, "Face normal: "))return false;}normals[i] = normal_vector(d[0], d[1], d[2]);t = is.tok();// bail out if end of block was reached - case of short (3 numbers) normal blockif(i == 0 && t && t->type == token::t_closeCrBracket)break;}int max = i > 0 ? 2 : 0;for(i = 0; i < 3; i++){if(points[i]!=NULL)points[i]->normalVector = normals[__min(i, max)];}if(t == NULL || t->type != token::t_closeCrBracket){error(t ? t : is.previous(), S_Error, "Expected end of block after '%s'", is.previous()->getText());return false;}++is;return true;}//---------------------------------------------------------------------------------/*computation taken from C4 3D engine (http://www.terathon.com/code/tangent.php)*/void bod_bob_parser::computeVertexTangents(bob_body& body){typedef ext::array<bob_vertex*> vertex_array;bob_vertices::VertexArray &vertices=body.vertices.new_vertices;// stuff for the computationvertex v1, v2, v3; // verticesgeometry::point2d<double> w1, w2, w3; // uv coordsint i1, i2, i3; // vertex indexesvector *tmp_tangents=new vector[vertices.size()];// every part in bodyfor(bob_parts::iterator &part_it=body.parts.begin(); part_it!=body.parts.end(); part_it++){// every face list in a partfor(bob_part::iterator &facelist_it=part_it->begin(); facelist_it!=part_it->end(); facelist_it++){// every face in face listfor(bob_face_list::iterator &face_it=facelist_it->begin(); face_it!=facelist_it->end(); ++face_it){i1=face_it->values[0]; i2=face_it->values[1]; i3=face_it->values[2];v1=*vertices[i1]; v2=*vertices[i2]; v3=*vertices[i3];w1=vertices[i1]->textureCoords; w2=vertices[i2]->textureCoords; w3=vertices[i3]->textureCoords;double x1 = v2.x - v1.x;double x2 = v3.x - v1.x;double y1 = v2.y - v1.y;double y2 = v3.y - v1.y;double z1 = v2.z - v1.z;double z2 = v3.z - v1.z;double s1 = w2.x - w1.x;double s2 = w3.x - w1.x;double t1 = w2.y - w1.y;double t2 = w3.y - w1.y;double cp=s1 * t2 - s2 * t1;if(cp!=0) {double r = 1.0 / cp;vector sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r,(t2 * z1 - t1 * z2) * r);tmp_tangents[i1] += sdir;tmp_tangents[i2] += sdir;tmp_tangents[i3] += sdir;}}}}for(vertex_array::size_type i=0; i < vertices.size(); i++){const vector& n=vertices[(vertex_array::difference_type)i]->normalVector;const vector& t=tmp_tangents[i];if(!t.isZero()){// Gram-Schmidt orthogonalizevertices[(vertex_array::difference_type)i]->tangentVector = (t - n * n.dot(t)).normalize();}}delete[] tmp_tangents;}//---------------------------------------------------------------------------------