//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "max2mapExporter/exporter.h" #include "platformWin32/winConsole.h" #include "dgl/materialList.h" #include "dgl/gBitmap.h" // static data static HINSTANCE gInstance; static PluginDescription gPluginDesc; static Interface * gInterface; static EditGeometry * gWorkingGeometry; static CollisionDetail * gCollisionDetail; static MapProperties gMapProperties; static const S32 gcProgramVersion = 69; static const F32 gcCenterOffsetScale = 0.1f; static const F32 gcMinCenterOffset = 2.f; static const F32 gcMaxCenterOffset = 200.f; static const F32 gcPlaneNormThresh = 0.999f; static const F32 gcPlaneDistThresh = 0.001f; // ------------------------------------------------------------------ namespace // helper functions { // Point3F toPoint3F(const Point3 & pnt){return(Point3F(pnt.x, pnt.y, pnt.z));} // returns (a mod b) F32 floatMod(F32 a, F32 b) { F32 multiple = mFabs(a / b); F32 remainder = multiple - mFloor(multiple); return(a < 0 ? -(remainder * b) : (remainder * b)); } // void writeString(FileStream & file, const char * str) { file.write(dStrlen(str), str); } //------------------------------------------------------------------------------ // from Numerical Recipes in C #define idx(i,j) (i*4 + j) void gaussjInverse(MatrixF & mat) { U32 indxc[4]; U32 indxr[4]; U32 ipiv[4]; F32 * a = (F32*)mat; S32 i, icol, irow, j, k, l, ll; F32 big, dum, pivinv, temp; for(j = 0; j < 4; j++) ipiv[j] = 0; for(i = 0; i < 4; i++) { big = 0.f; for(j = 0; j < 4; j++) if(ipiv[j] != 1) for(k = 0; k < 4; k++) { if(ipiv[k] == 0) { if(mFabs(a[idx(j,k)]) >= big) { big = mFabs(a[idx(j,k)]); irow = j; icol = k; } } else if(ipiv[k] > 1) AssertFatal(0, "Doh!"); } ++(ipiv[icol]); if(irow != icol) { for(l = 0; l < 4; l++) { F32 tmp = a[idx(irow, l)]; a[idx(irow, l)] = a[idx(icol, l)]; a[idx(icol, l)] = tmp; } } indxr[i] = irow; indxc[i] = icol; if(a[idx(icol, icol)] == 0.0) AssertFatal(0, "Doh!"); pivinv = 1.0 / a[idx(icol, icol)]; a[idx(icol, icol)] = 1.f; for(l = 0; l < 4; l++) a[idx(icol, l)] *= pivinv; for(ll = 0; ll < 4; ll++) if(ll != icol) { dum = a[idx(ll, icol)]; a[idx(ll, icol)] = 0.f; for(l = 0; l < 4; l++) a[idx(ll, l)] -= a[idx(icol, l)] * dum; } } for(l = 3; l >= 0; l--) { if(indxr[l] != indxc[l]) for(k = 0; k < 4; k++) { F32 tmp = a[idx(k, indxr[l])]; a[idx(k, indxr[l])] = a[idx(k, indxc[l])]; a[idx(k, indxc[l])] = tmp; } } } // size of the material Point2I getMaterialSize(Mtl * material) { Texmap * texMap = material->GetSubTexmap(ID_DI); Bitmap * bitmap = ((BitmapTex*)texMap)->GetBitmap(0); Point2I size(bitmap->Width(), bitmap->Height()); return(size); } // get the base name of the material const char * getMaterialName(Mtl * material) { Texmap * texMap = material->GetSubTexmap(ID_DI); char buf[1024]; dSprintf(buf, sizeof(buf), ((BitmapTex*)texMap)->GetMapName()); // just get the base name... char * cur = dStrrchr(buf, '.'); if(!cur) return(0); *cur = '\0'; cur--; while(1) { // end of filebase? if(*cur == '\\' || *cur == '/') { cur++; break; } // bad char? if(*cur == ' ') return(0); // only base? if(cur == buf) break; cur--; } // good length? U32 len = dStrlen(cur); if(!len || len > 15) return(0); return(StringTable->insert(cur)); } // bool isValidMaterial(StdMat * material) { // grab the diffuse map Texmap * texMap = material->GetSubTexmap(ID_DI); // valid name? if(!getMaterialName(material)) return(false); // make sure a bitmap if(texMap && texMap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0)) { Bitmap * bitmap = ((BitmapTex*)texMap)->GetBitmap(0); // make sure has a name if(!((BitmapTex*)texMap)->GetMapName()) return(false); // make sure a valid bitmap width/height if(bitmap->Width() < 0x10 || bitmap->Height() < 0x10) return(false); if(bitmap->Width() & 0x07 || bitmap->Height() & 0x07) return(false); return(true); } return(false); } }; // ------------------------------------------------------------------ // MapProperties // ------------------------------------------------------------------ MapProperties::MapProperties() { mWadName = 0; mGeometryScale = 64.f; mLightingScale = 64.f; mAmbientColor.set(0,0,0); mEmergencyAmbientColor.set(0,0,0); } // ------------------------------------------------------------------ // DetailEnumerator // ------------------------------------------------------------------ DetailEnumerator::DetailEnumerator() { } DetailEnumerator::~DetailEnumerator() { for(U32 i = 0; i < mDetails.size(); i++) delete mDetails[i]; } // ------------------------------------------------------------------ // enumerates the scene creating details and assigning SceneObjects to them void DetailEnumerator::enumScene(IScene * scene) { scene->EnumTree(this); } // ------------------------------------------------------------------ int DetailEnumerator::callback(INode * node) { const char * name = node->GetName(); if(node->IsHidden()) return(TREE_CONTINUE); Object * obj = node->EvalWorldState(gInterface->GetTime()).obj; if(!obj->IsRenderable()) return(TREE_CONTINUE); if(!obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID,0))) return(TREE_CONTINUE); // get the detail this belongs in EditGeometry * detail = getNodeDetail(node); if(!detail) { // collision hull? if(!isCollisionHullObject(node)) return(TREE_CONTINUE); } TriObject * triObj = (TriObject*)obj->ConvertToType(gInterface->GetTime(), Class_ID(TRIOBJ_CLASS_ID, 0)); // ownership of the TriObject will be transfered if(detail) detail->addSceneObject(new SceneObject(TriObjRef(triObj, triObj != obj), node)); else mCollisionDetail.addConvexHullObject(new ConvexHullObject(TriObjRef(triObj, triObj != obj), node)); return(TREE_CONTINUE); } // ------------------------------------------------------------------ EditGeometry * DetailEnumerator::getNodeDetail(INode * node) { // search for a detail node while(!node->IsRootNode()) { char buf[80]; dSprintf(buf, sizeof(buf), "%s", node->GetName()); if(!dStrnicmp(buf, "detail_", 7)) return(createDetail(dAtoi(buf+7))); node = node->GetParentNode(); } return(0); } bool DetailEnumerator::isCollisionHullObject(INode * node) { while(!node->IsRootNode()) { if(!dStrnicmp(node->GetName() ? node->GetName() : "", "collision", 9)) return(true); node = node->GetParentNode(); } return(false); } // ------------------------------------------------------------------ EditGeometry * DetailEnumerator::createDetail(U32 minPixels) { // search for it for(U32 i = 0; i < mDetails.size(); i++) if(mDetails[i]->mMinPixels == minPixels) return(mDetails[i]); // create a new one EditGeometry * detail = new EditGeometry; detail->mMinPixels = minPixels; mDetails.push_back(detail); return(detail); } // ------------------------------------------------------------------ static S32 QSORT_CALLBACK detailCompare(const void * a, const void * b) { return(((S32)(*(EditGeometry**)b)->mMinPixels) - ((S32)(*(EditGeometry**)a)->mMinPixels)); } void DetailEnumerator::sortDetails() { dQsort((void*)&mDetails[0], mDetails.size(), sizeof(EditGeometry*), detailCompare); } bool DetailEnumerator::sequentialDetails() { for(U32 i = 0; i < (mDetails.size() - 1); i++) if(mDetails[i]->mMinPixels <= mDetails[i+1]->mMinPixels) return(false); return(true); } // ------------------------------------------------------------------ // Class TriObjRef // ------------------------------------------------------------------ TriObjRef::TriObjRef(TriObject * obj, bool ownObject) : mRef(obj), mOwnObject(ownObject) { } TriObjRef::TriObjRef(TriObjRef & rhs) { mOwnObject = rhs.mOwnObject; mRef = rhs.mRef; rhs.mOwnObject = false; } TriObjRef::~TriObjRef() { if(mOwnObject) delete mRef; } // ------------------------------------------------------------------ // Class ObjectBase // ------------------------------------------------------------------ ObjectBase::ObjectBase(TriObjRef objRef, INode * node) : mTriObjRef(objRef), mNode(node) { } // ------------------------------------------------------------------ // Class SceneObject // ------------------------------------------------------------------ SceneObject::SceneObject(TriObjRef objRef, INode * node) : ObjectBase(objRef, node) { } // ------------------------------------------------------------------ bool SceneObject::process() { // do materials first Mtl * material = mNode->GetMtl(); if(!material) return(false); Vector materials; Vector materialIDs; // single material if(material->ClassID() == Class_ID(DMTL_CLASS_ID, 0)) { if(!isValidMaterial((StdMat*)material)) return(false); materials.push_back((StdMat*)material); materialIDs.push_back(gWorkingGeometry->insertMaterial(material)); } // multiple materials if(material->ClassID() == Class_ID(MULTI_CLASS_ID, 0)) { U32 numMat = material->NumSubMtls(); for(U32 i = 0; i < numMat; i++) { Mtl * subMat = material->GetSubMtl(i); // check it if(subMat->ClassID() != Class_ID(DMTL_CLASS_ID, 0)) return(false); if(!isValidMaterial((StdMat*)subMat)) return(false); materials.push_back((StdMat*)subMat); materialIDs.push_back(gWorkingGeometry->insertMaterial(subMat)); } } if(!materials.size()) return(false); // get the mesh and walk the faces Mesh & mesh = mTriObjRef->GetMesh(); Matrix3 nodeTM = mNode->GetNodeTM(gInterface->GetTime()); Point3 offset = mNode->GetObjOffsetPos(); // create all the surfaces from the mesh for(U32 i = 0; i < mesh.numFaces; i++) { TVFace & texFace = mesh.tvFace[i]; Face & face = mesh.faces[i]; U32 matID = face.getMatID() % materials.size(); // create the surface and add to the current detail Surface surface; dMemset(&surface, 0, sizeof(Surface)); surface.materialIndex = materialIDs[matID]; // 'Face' is a triangular surface... surface.winding.numIndices = 3; Vector textureCoords; // insert the vertices for(U32 j = 0; j < surface.winding.numIndices; j++) { Point3 maxPnt = nodeTM.PointTransform(mesh.getVert(face.v[j])); maxPnt += offset; // scale it maxPnt *= gMapProperties.mGeometryScale; surface.winding.indices[j] = gWorkingGeometry->insertPoint(toPoint3F(maxPnt)); // get the texture coordinate const UVVert & texVert = mesh.getTVert(texFace.t[j]); textureCoords.push_back(toPoint3F(texVert)); textureCoords[j].z = 0; } // insert the plane - normal to empty PlaneF plane(gWorkingGeometry->getPoint(surface.winding.indices[2]), gWorkingGeometry->getPoint(surface.winding.indices[1]), gWorkingGeometry->getPoint(surface.winding.indices[0])); surface.planeIndex = gWorkingGeometry->insertPlane(plane); // now do the texture stuff... //--------------------------------------- // get some info BitmapTex * bitmapTex = (BitmapTex*)materials[matID]->GetSubTexmap(ID_DI); Bitmap * bitmap = bitmapTex->GetBitmap(gInterface->GetTime()); // grab the points for easy access Vector mappedCoords; mappedCoords.setSize(surface.winding.numIndices); for(U32 l = 0; l < surface.winding.numIndices; l++) { const Point3F & pnt = gWorkingGeometry->getPoint(surface.winding.indices[l]); mappedCoords[l] = pnt; } MatrixF mat; Point4F sb, tb; sb.w = tb.w = 0; for(U32 row = 0; row < 3; row++) { mat.setRow(row, mappedCoords[row]); ((F32*)sb)[row] = textureCoords[row].x; ((F32*)tb)[row] = textureCoords[row].y; } Point3F norm; Point3F na = mappedCoords[1] - mappedCoords[0]; Point3F nb = mappedCoords[2] - mappedCoords[1]; na.normalize(); nb.normalize(); mCross(na, nb, &norm); mat.setRow(3, norm); mat.setColumn(3, Point4F(1,1,1,0)); // inverse gaussjInverse(mat); mat.mul(sb); mat.mul(tb); // set the gen's surface.texGenX.x = sb.x; surface.texGenX.y = sb.y; surface.texGenX.z = sb.z; surface.texGenX.d = sb.w; surface.texGenY.x = -tb.x; surface.texGenY.y = -tb.y; surface.texGenY.z = -tb.z; surface.texGenY.d = -tb.w; gWorkingGeometry->mSurfaces.push_back(surface); } return(true); } // ------------------------------------------------------------------ // Class ConvexHullObject // ------------------------------------------------------------------ ConvexHullObject::ConvexHullObject(TriObjRef objRef, INode * node) : ObjectBase(objRef, node) { mIsConvex = false; } void ConvexHullObject::process() { AssertFatal(!mPlanes.size(), "ConvexHullObject::process: already processed"); // grab all the planes Mesh & mesh = mTriObjRef->GetMesh(); Matrix3 nodeTM = mNode->GetNodeTM(gInterface->GetTime()); Point3 offset = mNode->GetObjOffsetPos(); // create all the surfaces from the mesh for(U32 i = 0; i < mesh.numFaces; i++) { Face & face = mesh.faces[i]; Point3F pnts[3]; for(U32 j = 0; j < 3; j++) { Point3 maxPnt = nodeTM.PointTransform(mesh.getVert(face.v[j])); maxPnt += offset; maxPnt *= gMapProperties.mGeometryScale; pnts[j] = toPoint3F(maxPnt); } PlaneF plane(pnts[0], pnts[1], pnts[2]); // dont insert duplicate planes... bool found = false; for(S32 k = mPlanes.size() - 1; (k >= 0) && !found; k--) { if((mDot(plane, mPlanes[k]) > gcPlaneNormThresh) && (mFabs(plane.d - mPlanes[k].d) < gcPlaneDistThresh)) found = true; } if(!found) { mPlanes.push_back(plane); mPoints.push_back(pnts[0]); mPoints.push_back(pnts[1]); mPoints.push_back(pnts[2]); } } // converting into a MNMesh does not correctly solve convex problem.. so we // are to assume that this is convex if(mPlanes.size() >= 4) mIsConvex = true; } // ------------------------------------------------------------------ // Class CollisionDetail // ------------------------------------------------------------------ CollisionDetail::~CollisionDetail() { for(S32 i = mConvexHulls.size() - 1; i >= 0; i--) delete mConvexHulls[i]; } void CollisionDetail::processConvexHullObjects() { for(S32 i = mConvexHulls.size() - 1; i >= 0; i--) mConvexHulls[i]->process(); } void CollisionDetail::export(FileStream & file) { if(!mConvexHulls.size()) return; writeString(file, "{\n"); writeString(file, "\"classname\" \"collision\"\n"); char buf[1024]; for(U32 i = 0; i < mConvexHulls.size(); i++) { ConvexHullObject * hull = mConvexHulls[i]; if(!hull->mIsConvex) continue; writeString(file, "{\n"); for(U32 j = 0; j < hull->mPlanes.size(); j++) { U32 k = (j * 3); dSprintf(buf, sizeof(buf), "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) NULL [ 1 0 0 0 ] [ 0 1 0 0 ] 0 1 1\n", (S32)hull->mPoints[k+2].x, (S32)hull->mPoints[k+2].y, (S32)hull->mPoints[k+2].z, (S32)hull->mPoints[k+1].x, (S32)hull->mPoints[k+1].y, (S32)hull->mPoints[k+1].z, (S32)hull->mPoints[k].x, (S32)hull->mPoints[k].y, (S32)hull->mPoints[k].z); writeString(file, buf); } writeString(file, "}\n"); } writeString(file, "}\n"); } // ------------------------------------------------------------------ // DLL Entry point // ------------------------------------------------------------------ BOOL WINAPI DllMain(HINSTANCE hInst, ULONG dwReason, LPVOID pReserved) { static bool initialized = false; if(!initialized) { initialized = true; _StringTable::create(); } gInstance = hInst; return(TRUE); } DLLEXPORT int LibNumberClasses() { return(1); } DLLEXPORT ClassDesc * LibClassDesc(int index) { if(index != 0) return(0); return(&gPluginDesc); } DLLEXPORT const TCHAR * LibDescription() { return(_T("WorldCraft 3.3 Map Exporter")); } DLLEXPORT ULONG LibVersion() { return(VERSION_3DSMAX); } //------------------------------------------------------------------------------ // Class Detail //------------------------------------------------------------------------------ EditGeometry::EditGeometry() { } EditGeometry::~EditGeometry() { // remove the scene info for(U32 i = 0; i < mSceneObjects.size(); i++) delete mSceneObjects[i]; } void EditGeometry::processSceneObjects() { // have each object create it's surfaces... for(U32 i = 0; i < mSceneObjects.size(); i++) mSceneObjects[i]->process(); } //------------------------------------------------------------------------------ U32 EditGeometry::insertMaterial(Mtl * material) { const char * matName = getMaterialName(material); for(U32 i = 0; i < mMaterialNames.size(); i++) if(!dStricmp(matName, mMaterialNames[i])) return(i); // insert the name and size mMaterialNames.push_back(matName); mMaterialSizes.push_back(getMaterialSize(material)); return(mMaterialNames.size() - 1); } U32 EditGeometry::insertPoint(const Point3F & pnt) { for(U32 i = 0; i < mPoints.size(); i++) if(mPoints[i] == pnt) return(i); mPoints.push_back(pnt); return(mPoints.size() - 1); } U32 EditGeometry::insertPlane(const PlaneF & plane) { for(U32 i = 0; i < mPlanes.size(); i++) if(mPlanes[i] == plane) return(i); mPlanes.push_back(plane); return(mPlanes.size() - 1); } //------------------------------------------------------------------------------ Point3F EditGeometry::getSurfaceCenter(Surface & surface) { AssertFatal(surface.winding.numIndices, "EditGeometry::getSurfaceCenter: invalid surface"); Point3F center(0,0,0); for(U32 i = 0; i < surface.winding.numIndices; i++) center += getPoint(surface.winding.indices[i]); center /= surface.winding.numIndices; return(center); } //------------------------------------------------------------------------------ void EditGeometry::exportToMap(const char * fileName, U32 detailNum) { AssertISV(false, "This plugin IS BROKEN. It is UNSUPPORTED. Use it at YOUR OWN RISK. You should really be using QUARK, not this tool."); FileStream file; file.open(fileName, FileStream::Write); char buf[1024]; writeString(file, "{\n"); writeString(file, "\"classname\" \"worldspawn\"\n"); writeString(file, "\"mapversion\" \"220\"\n"); // wadname dSprintf(buf, sizeof(buf), "\"wad\" \"%s\"\n", gMapProperties.mWadName); writeString(file, buf); // geometry scale dSprintf(buf, sizeof(buf), "\"geometry_scale\" \"%f\"\n", gMapProperties.mGeometryScale); writeString(file, buf); // lighting scale dSprintf(buf, sizeof(buf), "\"light_geometry_scale\" \"%f\"\n", gMapProperties.mLightingScale); writeString(file, buf); // ambient color dSprintf(buf, sizeof(buf), "\"ambient_color\", \"%d %d %d\"\n", gMapProperties.mAmbientColor.red, gMapProperties.mAmbientColor.green, gMapProperties.mAmbientColor.blue); writeString(file, buf); // emergency lighting color dSprintf(buf, sizeof(buf), "\"emergency_ambient_color\", \"%d %d %d\"\n", gMapProperties.mEmergencyAmbientColor.red, gMapProperties.mEmergencyAmbientColor.green, gMapProperties.mEmergencyAmbientColor.blue); writeString(file, buf); // minpixels dSprintf(buf, sizeof(buf), "\"min_pixels\" \"%d\"\n", mMinPixels); writeString(file, buf); // detailNum dSprintf(buf, sizeof(buf), "\"detail_number\" \"%d\"\n", detailNum); writeString(file, buf); // create a brush per face.. for(U32 i = 0; i < mSurfaces.size(); i++) { Surface & surface = mSurfaces[i]; writeString(file, "{\n"); char buf[1024]; // surface plane.. Point3F pnts[3]; for(U32 j = 0; j < 3; j++) pnts[j] = getPoint(surface.winding.indices[j]); // unscale texgens surface.texGenX.x *= mMaterialSizes[surface.materialIndex].x; surface.texGenX.y *= mMaterialSizes[surface.materialIndex].x; surface.texGenX.z *= mMaterialSizes[surface.materialIndex].x; surface.texGenX.d *= mMaterialSizes[surface.materialIndex].x; surface.texGenY.x *= mMaterialSizes[surface.materialIndex].y; surface.texGenY.y *= mMaterialSizes[surface.materialIndex].y; surface.texGenY.z *= mMaterialSizes[surface.materialIndex].y; surface.texGenY.d *= mMaterialSizes[surface.materialIndex].y; // write out this plane dSprintf(buf, sizeof(buf), "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) %s [ %f %f %f %f ] [ %f %f %f %f ] 0 1 1\n", (S32)pnts[2].x, (S32)pnts[2].y, (S32)pnts[2].z, (S32)pnts[1].x, (S32)pnts[1].y, (S32)pnts[1].z, (S32)pnts[0].x, (S32)pnts[0].y, (S32)pnts[0].z, getMaterial(surface.materialIndex), surface.texGenX.x, surface.texGenX.y, surface.texGenX.z, surface.texGenX.d, surface.texGenY.x, surface.texGenY.y, surface.texGenY.z, surface.texGenY.d); writeString(file, buf); PlaneF plane = getPlane(surface.planeIndex); // get the surface area Point3F areaNorm(0.f,0.f,0.f); Point3F tmp; mCross(pnts[0], pnts[1], &tmp); areaNorm += tmp; mCross(pnts[1], pnts[2], &tmp); areaNorm += tmp; mCross(pnts[2], pnts[0], &tmp); areaNorm += tmp; F32 len = mSqrt(mFabs(mDot(plane, areaNorm) * 0.5f)); // null bounding planes Point3F center = getSurfaceCenter(surface); center -= (plane * mClampF(len * gcCenterOffsetScale, gcMinCenterOffset, gcMaxCenterOffset)); // write out the null planes for(U32 k = 0; k < surface.winding.numIndices; k++) { U32 l = (k+1) % surface.winding.numIndices; pnts[0] = getPoint(surface.winding.indices[l]); pnts[1] = getPoint(surface.winding.indices[k]); pnts[2] = center; // bunk texture info (null surfaces) dSprintf(buf, sizeof(buf), "( %d %d %d ) ( %d %d %d ) ( %d %d %d ) NULL [ 1 0 0 0 ] [ 0 1 0 0 ] 0 1 1\n", (S32)pnts[2].x, (S32)pnts[2].y, (S32)pnts[2].z, (S32)pnts[1].x, (S32)pnts[1].y, (S32)pnts[1].z, (S32)pnts[0].x, (S32)pnts[0].y, (S32)pnts[0].z); writeString(file, buf); } writeString(file, "}\n"); } // wad file writeString(file, "}\n"); // the collision info is dumped on highest detail if(!detailNum) { gCollisionDetail->processConvexHullObjects(); gCollisionDetail->export(file); } file.close(); } //------------------------------------------------------------------------------ // Class Exporter //------------------------------------------------------------------------------ const TCHAR * Exporter::Ext(int index) { if(index != 0) return(_T("")); return(_T("map")); } //------------------------------------------------------------------------------ const char * Exporter::generateMapName(const char * baseName, U32 detail, U32 numDetails) { if(numDetails == 1) return(baseName); // work on a copy char buf[1024]; dSprintf(buf, sizeof(buf), baseName); char * cur = dStrrchr(buf, '.'); if(!cur) return(0); char ext[16]; dSprintf(ext, sizeof(ext), "_%d.map", detail); *cur = '\0'; // fixup the name dStrcat(buf, ext); return(StringTable->insert(buf)); } bool Exporter::getMapProperties() { bool gotLightingScale = false; // walk and grab anything we like for(U32 i = 0; i < gInterface->GetNumProperties(PROPSET_USERDEFINED); i++) { const PROPSPEC * propSpec = gInterface->GetPropertySpec(PROPSET_USERDEFINED, i); const PROPVARIANT * propVar = gInterface->GetPropertyVariant(PROPSET_USERDEFINED, i); if(propVar->vt != VT_LPWSTR && propVar->vt != VT_LPSTR) continue; char key[1024]; char value[1024]; _tcscpy(key, TSTR(propSpec->lpwstr)); _tcscpy(value, propVar->vt == VT_LPWSTR ? TSTR(propVar->pwszVal) : TSTR(propVar->pszVal)); // if(!dStricmp(key, "wad")) { char buf[256]; dSprintf(buf, sizeof(buf), "\\%s.wad", value); gMapProperties.mWadName = StringTable->insert(buf); } else if(!dStricmp(key, "geometry_scale")) { dSscanf(value, "%f", &gMapProperties.mGeometryScale); } else if(!dStricmp(key, "lighting_scale")) { gotLightingScale = true; dSscanf(value, "%f", &gMapProperties.mLightingScale); } else if(!dStricmp(key, "ambient_color")) { S32 r, g, b; dSscanf(value, "%d %d %d", &r, &g, &b); gMapProperties.mAmbientColor.red = U8(r); gMapProperties.mAmbientColor.green = U8(g); gMapProperties.mAmbientColor.blue = U8(b); } else if(!dStricmp(key, "emergency_ambient_color")) { S32 r, g, b; dSscanf(value, "%d %d %d", &r, &g, &b); gMapProperties.mEmergencyAmbientColor.red = U8(r); gMapProperties.mEmergencyAmbientColor.green = U8(g); gMapProperties.mEmergencyAmbientColor.blue = U8(b); } } // if there was no lighting scale, then set to the geometry scale if(!gotLightingScale) gMapProperties.mLightingScale = gMapProperties.mLightingScale; return(gMapProperties.mWadName ? true : false); } //------------------------------------------------------------------------------ int Exporter::DoExport(const TCHAR * name, ExpInterface * expIFace, Interface * iFace, BOOL suppressPrompts, DWORD options) { suppressPrompts;options; MessageBox(NULL, "This exporter is useful for exporting ONLY simple convex shapes.\n\n" "If you try more complicated shapes it will not work.\n\n" "Even if it does work you probably don't want to use the output.\n\n" "You use it at your own risk! You should really be using Quark, not this tool! " "Max is fundamentally unsuited to creating .maps and this tool does NOTHING to change that! " "If this doesn't work DON'T ASK US FOR HELP. This is UNSUPPORTED and UNMAINTAINED SOFTWARE." , "Exporter WARNING!", MB_OK); // setup globals gInterface = iFace; mDetailEnum.enumScene(expIFace->theScene); gCollisionDetail = mDetailEnum.getCollisionDetail(); if(!getMapProperties()) return(-1); // has detail levels? if(!mDetailEnum.numDetails()) { MessageBox(NULL, "No details found to export", "Export failed!", MB_OK); return(-1); } // sort them mDetailEnum.sortDetails(); if(!mDetailEnum.sequentialDetails()) { MessageBox(NULL, "Details' minPixels are not sequential or are not unique!", "Export failed!", MB_OK); return(-1); } // work with each of the details U32 i; for(i = 0; i < mDetailEnum.numDetails(); i++) { gWorkingGeometry = mDetailEnum.getDetail(i); gWorkingGeometry->processSceneObjects(); const char * mapName = generateMapName(name, i, mDetailEnum.numDetails()); if(!mapName) { MessageBox(NULL, "Failed to generate mapname", "Export failed!", MB_OK); return(-1); } // multiple detail levels? gWorkingGeometry->exportToMap(mapName, i); } return(-1); } //------------------------------------------------------------------------------ // Resolve external's //------------------------------------------------------------------------------ U32 GameAddTaggedString(const char *) {return(0);} void GameReactivate() {} void GameDeactivate(bool) {}