#include "interiorMapRes.h" #include "dgl/gBitmap.h" #include "console/consoleTypes.h" #include "console/console.h" #include "dgl/dgl.h" #include "collision/convexBrush.h" InteriorMapResource::InteriorMapResource() { mBrushFormat = Unknown; mTexGensCalced = false; mNextBrushID = 0; mWorldSpawn = NULL; mBrushScale = 1.0f; } InteriorMapResource::~InteriorMapResource() { } bool InteriorMapResource::load(const char* filename) { // Open our file and read it into a buffer FileStream* pStream = new FileStream; if (pStream->open(filename, FileStream::Read) == false) { Con::errorf("Unable to open %s", filename); delete pStream; return false; } read(*pStream); // Done with actual file pStream->close(); delete pStream; return true; } bool InteriorMapResource::read(Stream& stream) { // Parse that sucker Tokenizer toker; toker.openFile(&stream); parseMap(&toker); for (U32 i = 0; i < mBrushes.size(); i++) mBrushes[i]->processBrush(); // Assign IDs for (U32 i = 0; i < mBrushes.size(); i++) mBrushes[i]->mID = getNextBrushID(); for (U32 i = 0; i < mBrushes.size(); i++) { if (mBrushes[i]->mStatus != ConvexBrush::Good) Con::errorf(mBrushes[i]->mDebugInfo); } return true; } bool InteriorMapResource::write(Stream& stream) { char buffer[10240]; dSprintf(buffer, 10240, "// This map has been written by the Torque Constructor\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, "// For more information see http://www.garagegames.com\r\n"); stream.write(dStrlen(buffer), buffer); stream.write(2, "\r\n"); // Start the worldspawn dump dSprintf(buffer, 10240, "{\r\n"); stream.write(dStrlen(buffer), buffer); // Write out the properties of worldspawn if (mWorldSpawn) { for (U32 i = 0; i < mWorldSpawn->properties.size(); i++) { dSprintf(buffer, 10240, " \"%s\" \"%s\"\r\n", mWorldSpawn->properties[i].name, mWorldSpawn->properties[i].value); stream.write(dStrlen(buffer), buffer); } } else // Write out some default properties { dSprintf(buffer, 10240, " \"classname\" \"worldspawn\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"detail_number\" \"0\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"min_pixels\" \"250\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"geometry_scale\" \"32.0\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"light_geometry_scale\" \"32.0\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"ambient_color\" \"0 0 0\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"emergency_ambient_color\" \"0 0 0\"\r\n"); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " \"mapversion\" \"220\"\r\n"); stream.write(dStrlen(buffer), buffer); } // Dump the structural brushes into the worldspawn for (U32 i = 0; i < mBrushes.size(); i++) { if (mBrushes[i]->mType != Structural) continue; // Don't write any deleted brushes if (mBrushes[i]->mStatus == ConvexBrush::Deleted) continue; writeBrush(i, stream); } // Finish up the worldspawn dump dSprintf(buffer, 10240, "}\r\n"); stream.write(dStrlen(buffer), buffer); // Dump the rest of the entities for (U32 i = 0; i < mEntities.size(); i++) { if (dStricmp("worldspawn", mEntities[i]->classname) == 0) continue; // Begin the entity dump dSprintf(buffer, 10240, "{\r\n"); stream.write(dStrlen(buffer), buffer); // Write out the entity properties for (U32 j = 0; j < mEntities[i]->properties.size(); j++) { dSprintf(buffer, 10240, " \"%s\" \"%s\"\r\n", mEntities[i]->properties[j].name, mEntities[i]->properties[j].value); stream.write(dStrlen(buffer), buffer); } // Dump any brushes that belong to this entity for (U32 j = 0; j < mBrushes.size(); j++) { // We already dumped the Structural brushes with worldspawn if (mBrushes[j]->mType == Structural) continue; // Skip any that don't belong to this entity if (mBrushes[j]->mOwner != mEntities[i]) continue; // Don't write any deleted brushes if (mBrushes[j]->mStatus == ConvexBrush::Deleted) continue; writeBrush(j, stream); } // Finish the entity dump dSprintf(buffer, 10240, "}\r\n"); stream.write(dStrlen(buffer), buffer); } return true; } bool InteriorMapResource::writeBrush(U32 brushIndex, Stream& stream) { char buffer[10240]; if (mBrushes[brushIndex]->mFaces.mPolyList.size() > 3) { dSprintf(buffer, 10240, "\r\n // Brush %d\r\n", brushIndex); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " {\r\n"); stream.write(dStrlen(buffer), buffer); for (U32 j = 0; j < mBrushes[brushIndex]->mFaces.mPolyList.size(); j++) { // MDFFIX: Really shouldn't rely on pre-calced points..should generate them from plane if (mBrushes[brushIndex]->mFaces.mPolyList[j].vertexCount > 2) { // MDFFIX: Only testing first 3 verts...should really loop until find a valid triangle U32 i0 = mBrushes[brushIndex]->mFaces.mIndexList[mBrushes[brushIndex]->mFaces.mPolyList[j].vertexStart]; U32 i1 = mBrushes[brushIndex]->mFaces.mIndexList[mBrushes[brushIndex]->mFaces.mPolyList[j].vertexStart + 1]; U32 i2 = mBrushes[brushIndex]->mFaces.mIndexList[mBrushes[brushIndex]->mFaces.mPolyList[j].vertexStart + 2]; // Snag the points Point3F p0 = mBrushes[brushIndex]->mFaces.mVertexList[i0]; Point3F p1 = mBrushes[brushIndex]->mFaces.mVertexList[i1]; Point3F p2 = mBrushes[brushIndex]->mFaces.mVertexList[i2]; // Multiply by the brush transform Point3F tp0, tp1, tp2; MatrixF mat = mBrushes[brushIndex]->mTransform; mat.mulP(p0, &tp0); mat.mulP(p1, &tp1); mat.mulP(p2, &tp2); // Scale it by our transform scale tp0.convolve(mBrushes[brushIndex]->mScale); tp1.convolve(mBrushes[brushIndex]->mScale); tp2.convolve(mBrushes[brushIndex]->mScale); // Now scale it up by the brush scale tp0 *= mBrushScale; tp1 *= mBrushScale; tp2 *= mBrushScale; F32 rot = mBrushes[brushIndex]->mTexInfos[j].rot; F32 texScale[2]; F32 texDiv[2]; PlaneF texGens[2]; texScale[0] = mBrushes[brushIndex]->mTexInfos[j].scale[0]; texScale[1] = mBrushes[brushIndex]->mTexInfos[j].scale[1]; texDiv[0] = mBrushes[brushIndex]->mTexInfos[j].texDiv[0]; texDiv[1] = mBrushes[brushIndex]->mTexInfos[j].texDiv[1]; texGens[0] = mBrushes[brushIndex]->mTexInfos[j].texGens[0]; texGens[1] = mBrushes[brushIndex]->mTexInfos[j].texGens[1]; //Con::warnf("preconditioned texGens[0](%g, %g, %g, %g) texGens[1](%g, %g, %g, %g)", // texGens[0].x, texGens[0].y, texGens[0].z, texGens[0].d, // texGens[1].x, texGens[1].y, texGens[1].z, texGens[1].d); // Divide out the brushscale texGens[0] /= mBrushScale; texGens[1] /= mBrushScale; // Multiply by our texture sizes texGens[0] *= texDiv[0]; texGens[0].d *= texDiv[0]; texGens[1] *= texDiv[1]; texGens[1].d *= texDiv[1]; // Multiply by texgen scale texGens[0] *= texScale[0]; texGens[1] *= texScale[1]; // Normalize axii and d F32 mag[2]; mag[0] = 1.0f / texGens[0].len(); mag[1] = 1.0f / texGens[1].len(); texGens[0].normalize(); texGens[1].normalize(); texGens[0].d *= mag[0]; texGens[1].d *= mag[1]; //Con::warnf("conditioned texGens[0](%g, %g, %g, %g) texGens[1](%g, %g, %g, %g)", // texGens[0].x, texGens[0].y, texGens[0].z, texGens[0].d, // texGens[1].x, texGens[1].y, texGens[1].z, texGens[1].d); if (validatePlane(p0, p1, p2)) { // ( -2 4 -158 ) ( -2 -252 -158 ) ( 254 4 -158 ) concrete_1 [ 1.00000 0.00000 0.00000 1.00000 ] [ 0.00000 -1.00000 0.00000 -2.00000 ] 0 2.00000 -2.00000 // First the verts specifying the plane dSprintf(buffer, 10240, " ( %g %g %g ) ( %g %g %g ) ( %g %g %g )", tp0.x, tp0.y, tp0.z, tp1.x, tp1.y, tp1.z, tp2.x, tp2.y, tp2.z); stream.write(dStrlen(buffer), buffer); // Then the texture dSprintf(buffer, 10240, " %s", mMaterials[mBrushes[brushIndex]->mFaces.mPolyList[j].material]); stream.write(dStrlen(buffer), buffer); // Now the texgens dSprintf(buffer, 10240, " [ %g %g %g %g ]", texGens[0].x, texGens[0].y, texGens[0].z, texGens[0].d); stream.write(dStrlen(buffer), buffer); dSprintf(buffer, 10240, " [ %g %g %g %g ]", texGens[1].x, texGens[1].y, texGens[1].z, texGens[1].d); stream.write(dStrlen(buffer), buffer); // And last the rotation and scaling dSprintf(buffer, 10240, " %g %g %g\r\n", rot, texScale[0], texScale[1]); stream.write(dStrlen(buffer), buffer); } } } dSprintf(buffer, 10240, " }\r\n"); stream.write(dStrlen(buffer), buffer); } return true; } bool InteriorMapResource::parseMap(Tokenizer* toker) { // Find a worldspawn entity and then find a brush toker->reset(); while (toker->advanceToken(true)) { // Search for the beginning of an entity while (toker->getToken()[0] != '{') { if (!toker->advanceToken(true)) break; } // Check to see if we are at the end of the file or at // the beginning of an entity if (toker->getToken()[0] == '{') { // Found the start of an entity...parse it parseEntity(toker); } // Unable to support the newest Quake 3 format if (mBrushFormat == QuakeNew) { Con::errorf("The latest Quake 3 format is incompatible with this version of map2dif. Please use an older format."); return false; } // parseEntity *should* return us at the end of the entity - i.e } // If it doesn't then loop through till we find the end while (toker->getToken()[0] != '}') { if (!toker->advanceToken(true)) break; } } //for (U32 i = 0; i < mEntities.size(); i++) //{ // Con::errorf("Entity[%d] %s", i, mEntities[i]->classname); // for (U32 j = 0; j < mEntities[i]->properties.size(); j++) // Con::printf(" %s %s", mEntities[i]->properties[j].name, mEntities[i]->properties[j].value); //} // If we don't have a worldspawn entity then create one if (!mWorldSpawn) { // Add a worldspawn entity mWorldSpawn = new Entity; mEntities.push_back(mWorldSpawn); mWorldSpawn->classname = StringTable->insert("worldspawn"); // Fill in the default properties InteriorMapResource::Property prop; prop.name = StringTable->insert("classname"); prop.value = StringTable->insert("worldspawn"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("detail_number"); prop.value = StringTable->insert("0"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("min_pixels"); prop.value = StringTable->insert("250"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("geometry_scale"); prop.value = StringTable->insert("1.0"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("light_geometry_scale"); prop.value = StringTable->insert("32.0"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("ambient_color"); prop.value = StringTable->insert("0 0 0"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("emergency_ambient_color"); prop.value = StringTable->insert("0 0 0"); mWorldSpawn->properties.push_back(prop); prop.name = StringTable->insert("mapversion"); prop.value = StringTable->insert("220"); mWorldSpawn->properties.push_back(prop); } return true; } bool InteriorMapResource::parsePatch(Tokenizer* toker) { // For now we aren't parsing patches so just skip to the end of the entity U32 bcnt = 1; while (bcnt > 0) { toker->advanceToken(true); if (toker->getToken()[0] == '{') bcnt++; if (toker->getToken()[0] == '}') bcnt--; } return true; } bool InteriorMapResource::parseBrush(Tokenizer* toker, BrushType type) { mBrushes.increment(); mBrushes.last() = new ConvexBrush; mBrushes.last()->mType = type; mBrushes.last()->mOwner = mEntities.last(); U32 bcnt = 1; while (bcnt > 0) { toker->advanceToken(true); if (toker->getToken()[0] == '{') bcnt++; if (toker->getToken()[0] == '}') bcnt--; // Look for the beginning of a brush if (toker->getToken()[0] == '(') parsePlane(toker); // Unable to support the newest Quake 3 format if (mBrushFormat == QuakeNew) return false; } return true; } bool InteriorMapResource::parseEntity(Tokenizer* toker) { // In the .map format entities have this format: // { // "property1 name" "property1 value" // "property2 name" "property2 value" // ... // { // brush1 (optional - has to have at least 4 planes to be valid) // // // // // ... // } // { // brush2 (optional - has to have at least 4 planes to be valid) // // // // // ... // } // ... // { // patch1 (optional - bezier patches in the q3 format) // patchDef2 // { // // ( dimensions ) // ( // ( ) // ( ) // ... // ) // } // } // { // patch2 (optional - bezier patches in the q3 format) // patchDef2 // { // // ( dimensions ) // ( // ( ) // ( ) // ... // ) // } // } // ... // } // Sanity check if (toker->getToken()[0] != '{') { Con::errorf("InteriorMapResource::parseEntity() not at the beginning of an entity - token is %s line %d", toker->getToken(), toker->getCurrentLine()); return false; } // Add an entity to the entities list mEntities.increment(); mEntities.last() = new Entity; Entity* ent = mEntities.last(); ent->classname = StringTable->insert("unknown"); // We are at brace level 1 U32 bcnt = 1; // Loop through till we hit the ending brace while (bcnt > 0) { toker->advanceToken(true); if (toker->endOfFile()) return true; //const char* tok = toker->getToken(); if (toker->getToken()[0] == '{') bcnt++; if (toker->getToken()[0] == '}') bcnt--; // Properties are on the first brace level if (bcnt == 1 && toker->getToken()[0] != '}') { ent->properties.increment(); ent->properties.last().name = StringTable->insert(toker->getToken()); toker->advanceToken(false); ent->properties.last().value = StringTable->insert(toker->getToken()); // Store the classname if we find it (just makes it easier to look up entities if (dStricmp(ent->properties.last().name, "classname") == 0) ent->classname = ent->properties.last().value; } else if (bcnt == 2 && toker->getToken()[0] == '{') // We have found either a brush or a patch { // Determine whether this is a patch or a brush int sub = -1; toker->advanceToken(true); if (toker->tokenICmp("patchDef2")) sub = 0; else if (toker->tokenICmp("brushDef") || toker->getToken()[0] == '(') sub = 1; else Con::errorf("InteriorMapResource::parseEntity() unknown brush entity - token is %s line %d", toker->getToken(), toker->getCurrentLine()); // Regress to the beginning of the subobject toker->regressToken(true); // Sanity check if (toker->getToken()[0] != '{') Con::errorf("InteriorMapResource::parseEntity() failed to return to the beginning of a subobject - token is %s line %d", toker->getToken(), toker->getCurrentLine()); else { if (sub == 0) parsePatch(toker); else if (dStricmp(ent->classname, "worldspawn") == 0) parseBrush(toker, Structural); else if (dStricmp(ent->classname, "detail") == 0) parseBrush(toker, Detail); else if (dStricmp(ent->classname, "collision") == 0) parseBrush(toker, Collision); else if (dStricmp(ent->classname, "portal") == 0) parseBrush(toker, Portal); else if (dStricmp(ent->classname, "trigger") == 0) parseBrush(toker, Trigger); else Con::errorf("InteriorMapResource::parseEntity() unknown entity %s with brushes", ent->classname); // Unable to support the newest Quake 3 format if (mBrushFormat == QuakeNew) return false; // Loop to the end of the subobject while (toker->getToken()[0] != '}' && !toker->endOfFile()) toker->advanceToken(true); // Should return at the end of the subobject if (toker->getToken()[0] == '}') bcnt--; else { Con::errorf("InteriorMapResource::parseEntity() failure to parse to the end of a subobject"); Con::errorf("InteriorMapResource::parseEntity() entity is %s token is %s line %d", ent->classname, toker->getToken(), toker->getCurrentLine()); } } } } // See if this entiry is our worldspawn if (dStricmp(ent->classname, "worldspawn") == 0) { mWorldSpawn = ent; // See if we have a brush scale char* scale = mWorldSpawn->getValue("geometry_scale"); if (scale) mBrushScale = dAtof(scale); } // Sanity check if (toker->getToken()[0] != '}') { Con::errorf("InteriorMapResource::parseEntity() not at the end of an entity - token is %s line %d", toker->getToken(), toker->getCurrentLine()); return false; } return true; } bool InteriorMapResource::validatePlane(Point3F k, Point3F j, Point3F l) { F32 ax = k.x-j.x; F32 ay = k.y-j.y; F32 az = k.z-j.z; F32 bx = l.x-j.x; F32 by = l.y-j.y; F32 bz = l.z-j.z; F32 x = ay*bz - az*by; F32 y = az*bx - ax*bz; F32 z = ax*by - ay*bx; F32 squared = x*x + y*y + z*z; if (squared == 0.0f) return false; else return true; } bool InteriorMapResource::parsePlane(Tokenizer* toker) { // We start at the first ( F32 x, y, z; Point3F p0, p1, p2; // All of the formats share this in common toker->advanceToken(false); x = dAtof(toker->getToken()); toker->advanceToken(false); y = dAtof(toker->getToken()); toker->advanceToken(false); z = dAtof(toker->getToken()); p0 = Point3F(x, y, z); // Brings us to end of the first () toker->advanceToken(false); // Brings us to the beginning of the second () toker->advanceToken(true); toker->advanceToken(false); x = dAtof(toker->getToken()); toker->advanceToken(false); y = dAtof(toker->getToken()); toker->advanceToken(false); z = dAtof(toker->getToken()); p1 = Point3F(x, y, z); // Brings us to end of the second () toker->advanceToken(false); // Brings us to the beginning of the third () toker->advanceToken(true); toker->advanceToken(false); x = dAtof(toker->getToken()); toker->advanceToken(false); y = dAtof(toker->getToken()); toker->advanceToken(false); z = dAtof(toker->getToken()); p2 = Point3F(x, y, z); // Brings us to end of the third () toker->advanceToken(false); // So now we have enough to calculate our plane PlaneF normal; // Check to make sure that none of the points are the same if (!validatePlane(p0, p1, p2)) { normal.set(1.0f, 0.0f, 0.0f); mBrushes.last()->mStatus = ConvexBrush::Malformed; mBrushes.last()->mDebugInfo = StringTable->insert("This brush was exported improperly with badly formed plane points. Please validate the brushes in your modelling tool before export."); } else normal.set(p0, p1, p2); // Here is where things start to split up by format // If we find a ( here then the map is in the newest // Quake 3 format. Otherwise we are in Quake/Valve format // which further splits // This the data that needs to be returned U32 texIndex; PlaneF texGens[2]; F32 scale[2] = {1.0f, 1.0f }; // If we don't know what format we are using see if it is // the newest Quake 3 format. if (mBrushFormat == Unknown) { toker->advanceToken(false); if (toker->getToken()[0] == '(') mBrushFormat = QuakeNew; // Move back to the beginning of the texgen fields toker->regressToken(true); } if (mBrushFormat == QuakeNew) { // The newest Quake 3 format is not supported at this time // b/c its texgens are incompatible with map2dif's return false; //parseQuakeNew(toker, texIndex, texGens); } else parseQuakeValve(toker, normal, texIndex, texGens, scale); // Now that we have our data add it to the brush mBrushes.last()->addFace(normal, texGens, scale, texIndex); // ( 64 -64 -64 ) ( -64 -64 -64 ) ( -64 -64 64 ) WALL_PANEL01 [ 1 0 0 64 ] [ 0 0 -1 64 ] 0 1 1 //Con::warnf("( %g %g %g ) ( %g %g %g ) ( %g %g %g ) %s [ %g %g %g %g ] [ %g %g %g %g ] 0 %g %g", // p0.x, p0.y, p0.z, // p1.x, p1.y, p1.z, // p2.x, p2.y, p2.z, // mMaterials[texIndex], // texGens[0].x, texGens[0].y, texGens[0].z, texGens[0].d, // texGens[1].x, texGens[1].y, texGens[1].z, texGens[1].d, // scale[0], scale[1]); return true; } bool InteriorMapResource::parseQuakeValve(Tokenizer* toker, VectorF normal, U32& tdx, PlaneF* texGens, F32* scale) { // Brings us to the texture name toker->advanceToken(false); tdx = addTexture((char*)toker->getToken()); // Ok here is where things start to differ between the formats // If we haven't determined the format yet then check for a '[' if (mBrushFormat == Unknown) { toker->advanceToken(false); if (toker->getToken()[0] == '[') mBrushFormat = Valve220; else mBrushFormat = QuakeOld; // Move back to the beginning of the texgen fields toker->regressToken(true); } if (mBrushFormat == Valve220) parseValve220TexGens(toker, texGens, scale); else if (mBrushFormat == QuakeOld) parseQuakeTexGens(toker, normal, texGens, scale); return true; } bool InteriorMapResource::parseQuakeNew(Tokenizer* toker, U32& tdx, PlaneF* texGens, F32* scale) { // Brings us to the beginning of the outer () toker->advanceToken(false); // Brings us to the beginning of the next () toker->advanceToken(false); // MDFFIX:: figure out what these are toker->advanceToken(false); toker->advanceToken(false); toker->advanceToken(false); // Brings us to the end of () toker->advanceToken(false); // Brings us to the beginning of the next () toker->advanceToken(false); // MDFFIX:: figure out what these are toker->advanceToken(false); toker->advanceToken(false); toker->advanceToken(false); // Brings us to the end of () toker->advanceToken(false); // Brings us to the end of the outer () toker->advanceToken(false); // Brings us to the texture toker->advanceToken(false); tdx = addTexture((char*)toker->getToken()); // MDFFIX:: figure out what these are toker->advanceToken(false); toker->advanceToken(false); toker->advanceToken(false); return true; } bool InteriorMapResource::parseValve220TexGens(Tokenizer* toker, PlaneF* texGens, F32* scale) { // Brings us to the opening of the first [] toker->advanceToken(false); toker->advanceToken(false); texGens[0].x = dAtof(toker->getToken()); toker->advanceToken(false); texGens[0].y = dAtof(toker->getToken()); toker->advanceToken(false); texGens[0].z = dAtof(toker->getToken()); toker->advanceToken(false); texGens[0].d = dAtof(toker->getToken()); // Takes us to the close of the first [] toker->advanceToken(false); // Brings us to the opening of the second [] toker->advanceToken(false); toker->advanceToken(false); texGens[1].x = dAtof(toker->getToken()); toker->advanceToken(false); texGens[1].y = dAtof(toker->getToken()); toker->advanceToken(false); texGens[1].z = dAtof(toker->getToken()); toker->advanceToken(false); texGens[1].d = dAtof(toker->getToken()); // Takes us to the close of the second [] toker->advanceToken(false); // Skip rotation toker->advanceToken(false); // Scale toker->advanceToken(false); scale[0] = dAtof(toker->getToken()); toker->advanceToken(false); scale[1] = dAtof(toker->getToken()); texGens[0].x /= scale[0]; texGens[0].y /= scale[0]; texGens[0].z /= scale[0]; texGens[1].x /= scale[1]; texGens[1].y /= scale[1]; texGens[1].z /= scale[1]; return true; } F32 baseaxis[18][3] = { {0,0,1}, {1,0,0}, {0,-1,0}, // floor {0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling {1,0,0}, {0,1,0}, {0,0,-1}, // west wall {-1,0,0}, {0,1,0}, {0,0,-1}, // east wall {0,1,0}, {1,0,0}, {0,0,-1}, // south wall {0,-1,0}, {1,0,0}, {0,0,-1} // north wall }; void textureAxisFromPlane(const VectorF& normal, F32* xv, F32* yv) { int bestaxis; F32 dot,best; int i; best = 0; bestaxis = 0; for (i=0 ; i<6 ; i++) { dot = mDot (normal, VectorF (baseaxis[i*3][0], baseaxis[i*3][1], baseaxis[i*3][2])); if (dot > best) { best = dot; bestaxis = i; } } xv[0] = baseaxis [bestaxis * 3 + 1][0]; xv[1] = baseaxis [bestaxis * 3 + 1][1]; xv[2] = baseaxis [bestaxis * 3 + 1][2]; yv[0] = baseaxis [bestaxis * 3 + 2][0]; yv[1] = baseaxis [bestaxis * 3 + 2][1]; yv[2] = baseaxis [bestaxis * 3 + 2][2]; } void quakeTextureVecs(const VectorF& normal, F32 offsetX, F32 offsetY, F32 rotate, F32 scaleX, F32 scaleY, PlaneF* u, PlaneF *v) { F32 vecs[2][3]; int sv, tv; F32 ang, sinv, cosv; F32 ns, nt; int i, j; textureAxisFromPlane(normal, vecs[0], vecs[1]); ang = rotate / 180 * M_PI; sinv = sin(ang); cosv = cos(ang); if (vecs[0][0] != 0.0) sv = 0; else if (vecs[0][1] != 0.0) sv = 1; else sv = 2; if (vecs[1][0] != 0.0) tv = 0; else if (vecs[1][1] != 0.0) tv = 1; else tv = 2; for (i=0 ; i<2 ; i++) { ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; vecs[i][sv] = ns; vecs[i][tv] = nt; } u->x = vecs[0][0] / scaleX; u->y = vecs[0][1] / scaleX; u->z = vecs[0][2] / scaleX; u->d = offsetX; v->x = vecs[1][0] / scaleY; v->y = vecs[1][1] / scaleY; v->z = vecs[1][2] / scaleY; v->d = offsetY; } bool InteriorMapResource::parseQuakeTexGens(Tokenizer* toker, VectorF normal, PlaneF* texGens, F32* scale) { toker->advanceToken(false); F32 shiftU = dAtof(toker->getToken()); toker->advanceToken(false); F32 shiftV = dAtof(toker->getToken()); toker->advanceToken(false); F32 rot = dAtof(toker->getToken()); toker->advanceToken(false); scale[0] = dAtof(toker->getToken()); toker->advanceToken(false); scale[1] = dAtof(toker->getToken()); // Make sure the normal is normalized normal.normalize(); quakeTextureVecs (normal, shiftU, shiftV, rot, scale[0], scale[1], &texGens[0], &texGens[1]); return true; } U32 InteriorMapResource::addTexture(char* texture) { S32 idx = getTextureIndex(texture); if (idx == -1) { idx = mMaterials.size(); mMaterials.increment(); mMaterials.last() = StringTable->insert(texture); } return idx; } S32 InteriorMapResource::getTextureIndex(char* texture) { S32 idx = -1; for (U32 i = 0; i < mMaterials.size(); i++) { if (dStricmp(texture, mMaterials[i]) == 0) { idx = i; break; } } return idx; } char* InteriorMapResource::getTextureName(U32 texIdx) { if (mMaterials.size() == 0 || texIdx > mMaterials.size() - 1) { Con::warnf("InteriorMapResource::getTextureName(): texture index is outside of range"); return "error"; } return (char*)mMaterials[texIdx]; } ResourceInstance* constructInteriorMAP(Stream& stream) { InteriorMapResource* pResource = new InteriorMapResource; if (pResource->read(stream) == true) return pResource; else { delete pResource; return NULL; } }