1042 lines
30 KiB
C++
Executable File
1042 lines
30 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// 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<StdMat *> materials;
|
|
Vector<U32> 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<Point3F> 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<Point3F> 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) {}
|