Standalone app, cook the world :)

Get technical support about the C++ source code and about Lua scripts for maps, entities, GUIs, the console, materials, etc. Also covered are the Cafu libraries and APIs, as well as compiling, linking, and the build system.
minosdis
Posts:24
Joined:2008-08-09, 05:42
Standalone app, cook the world :)

Post by minosdis » 2008-09-05, 14:44

I'm getting an odd linker error in this app,

Code: Select all

cfsCoreLib.lib(MapFile.obj) : error LNK2001: unresolved external symbol "public: static double const MapT::RoundEpsilon" (?RoundEpsilon@MapT@@2NB)
D:\Dev\Ca3DE-src-r626\Projects\Ca3D-Engine\CaCook\Release\CaCook.exe : fatal error LNK1120: 1 unresolved externals
I have everything linked that I should (plus some probably), so I'm not really sure where its coming from. I do know that if I comment out

Code: Select all

MFBrushes =MapFileEntityT(TP);
The error goes away, so I'm guessing somewhere in the MapFileEntityT heirarchy I've missed a library I need. Any ideas?

EDIT:
On a side note, I'm quite confused about how brushes are stored. It is stored as an array of MapFilePlaneT, which is defined,

Code: Select all

    struct MapFilePlaneT
    {
     // ArrayT<VectorT> Points;
        Plane3dT        Plane;
        MaterialT*      Material;       ///< The planes material.
        Vector3dT       U;
        Vector3dT       V;
        double          ShiftU;
        double          ShiftV;
    };
The plane has only normal vector and a dist (not sure what the dist is...). I'm not really sure what the U and V are (or ShiftU/V for that matter). Only thing I can think is a projection of 3d onto 2d space.
Basically, what I'm looking for is the points of a brush. Actually, more ideally, the points of each triangle making up the brush. Unfortunately I cant find this information stored like this =/
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-05, 18:24

Dear MinosDis,
minosdis wrote:

Code: Select all

cfsCoreLib.lib(MapFile.obj) : error LNK2001: unresolved external symbol "public: static double const MapT::RoundEpsilon" (?RoundEpsilon@MapT@@2NB)
D:\Dev\Ca3DE-src-r626\Projects\Ca3D-Engine\CaCook\Release\CaCook.exe : fatal error LNK1120: 1 unresolved externals
That symbol (MapT::RoundEpsilon) is defined in file Ca3D-Engine/Common/World.cpp, so you just have to add that the your list of sources when linking your app. If you're using scons for compiling and linking, you can see in Ca3D-Engine/SConscript in line 7 how we do it for the other programs.

On a side note, I'm quite confused about how brushes are stored. It is stored as an array of MapFilePlaneT, which is defined,

Code: Select all

    struct MapFilePlaneT
    {
     // ArrayT<VectorT> Points;
        Plane3dT        Plane;
        MaterialT*      Material;       ///< The planes material.
        Vector3dT       U;
        Vector3dT       V;
        double          ShiftU;
        double          ShiftV;
    };
The plane has only normal vector and a dist (not sure what the dist is...). I'm not really sure what the U and V are (or ShiftU/V for that matter). Only thing I can think is a projection of 3d onto 2d space.
Basically, what I'm looking for is the points of a brush. Actually, more ideally, the points of each triangle making up the brush. Unfortunately I cant find this information stored like this =/
The planes normal vector describes its orientation in space, the dist is its offset (along the normal vector) from the world origin. Once you get used to that representation, it comes quite natural and many things can easily be computed from it (see e.g. the PlaneT<T> methods for examples). Wikipedia also has some information about planes (pretty mathematical, though).

You are right with your guess about the U's and V's: They are for texture mapping and describe how the texture is computed for this brush surface.
I've updated the documentation for the class accordingly, it will be contained in the next source code release:

Code: Select all

    /// This struct describes a plane (and thus one side) of a map brush.
    /// The members U, V, ShiftU and ShiftV together define the planar projection
    /// for computing the (u, v) texture coordinates at the vertices of the brush.
    struct MapFilePlaneT
    {
     // ArrayT<VectorT> Points;
        Plane3dT        Plane;
        MaterialT*      Material;       ///< The planes material.
        Vector3dT       U;              ///< The first  span vector of the texture projection plane.
        Vector3dT       V;              ///< The second span vector of the texture projection plane.
        double          ShiftU;         ///< Texture "scroll offset" in direction of U.
        double          ShiftV;         ///< Texture "scroll offset" in direction of V.
    };
For computing the individual vertices for a brush, see function ComputeBrushPolys() in Ca3D-Engine/CaBSP/LoadWorld.cpp, it calls Polygon3T<double>::Complete() for the job.
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-05, 23:16

Well, I'm a bit stumped at this point. I've got the code done, as far as functionality goes. Unfortunately, I'm getting a bunch of linker errors still (now about 10 all from PhysX, down from 120 earlier). For the mean time, I've commented out the physx stuff and am focusing on getting the Ca3D side working. I'm getting a runtime exception at this line in MapFile.cpp,

Code: Select all

        MFPlane.Material=MaterialManager->GetMaterial(MaterialName);
According to the debugger, the MaterialManager object cannot be evaluated. I'm not sure if I need to initialize it or anything? In case your interested, here is the code to the project:

CaCook.cpp

Code: Select all

// CaCook.cpp : 'Cook's world geometry into binary files readable by PhysX for high speed collision detection.
// by Phil Clark

#include <string.h>
#include <iostream>

#include "ConsoleCommands/ConsoleStdout.hpp"
#include "TextParser/TextParser.hpp"
#include "MapFile.hpp"

#include <NxTriangleMeshDesc.h> 
#include <NxCooking.h>
//#include "UserStream.hpp"

using namespace std;
using namespace cf;

static ConsoleStdoutT ConsoleStdout(true);      // Enable auto-flushing the stdout stream for CaBSP.
ConsoleI* Console=&ConsoleStdout;
class NxTriangleMeshDesc;


int main(int argc, char *argv[])
{
	if ( argc < 3 ) {
		cout << "Usage:" << endl
			 << "CaCook.exe <InputFileName> <OutputFileName>" << endl;
		return 0;
	}
	char inputfilename[255], outputfilename[255];
	strcpy_s(inputfilename, argv[1]);
	strcpy_s(outputfilename, argv[2]);
	cout << " Input File:" << inputfilename  << endl
		 << "Output File:" << outputfilename << endl;
    
	//Make sure to initialize the cooking library.
    NxInitCooking();

cout << "1";
    NxTriangleMeshDesc WorldDesc;
	//MapFileEntityT     *MFBrushes;
    ArrayT<MapFileEntityT> MFEntityList;
	MapFileBrushT      CurrentBrush;
	NxU32              WORLD_NBVERTICES;
	NxU32              WORLD_NBFACES;

cout << "2";
	TextParserT TP(inputfilename, "()");
	if (TP.IsAtEOF()) {
		cout << "Unable to open map file.";
		return 0;
	}
cout << "3";
    try
    {
        while (!TP.IsAtEOF())
        {
            cout << "0";
            MFEntityList.PushBack(MapFileEntityT(TP));
            cout << "_";
        }
    }
    catch (const TextParserT::ParseError&)
    {
        cout << "Problem with parsing the map near byte " << TP.GetReadPosByte() << "(" << TP.GetReadPosPercent()*100.0 << ") of the file." << endl;
    } 
cout << "4";    
    // Check for any 'func_group' brushes, add them to worldspawn
    for (unsigned long EntityNr = 1; EntityNr < MFEntityList.Size(); EntityNr++)
    {
        if (MFEntityList[EntityNr].MFProperties["classname"]=="func_group")
        {
            // Copy all brushes of this entity into the 'worldspawn' entity.
            for (unsigned long BrushNr=0; BrushNr<MFEntityList[EntityNr].MFBrushes.Size(); BrushNr++)
                MFEntityList[0].MFBrushes.PushBack(MFEntityList[EntityNr].MFBrushes[BrushNr]);

            // Copy all bezier patches of this entity into the 'worldspawn' entity.
            for (unsigned long BPNr=0; BPNr<MFEntityList[EntityNr].MFPatches.Size(); BPNr++)
                MFEntityList[0].MFPatches.PushBack(MFEntityList[EntityNr].MFPatches[BPNr]);

            // Delete this entity.
            MFEntityList[EntityNr]=MFEntityList[MFEntityList.Size()-1];
            MFEntityList.DeleteBack();
            EntityNr--;
        }
    }
    
    // Recursivly count number of faces and vertices in the world
    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++)
        for (unsigned long FaceNr=0; FaceNr<MFEntityList[0].MFBrushes[BrushNr].MFPlanes.Size(); FaceNr++)
            WORLD_NBFACES++;
    WORLD_NBVERTICES = WORLD_NBFACES * 3;

cout << "5";
	WorldDesc.numVertices   = WORLD_NBVERTICES;   // Number of vertices in mesh.
    WorldDesc.numTriangles  = WORLD_NBFACES;     // Number of triangles(3 indices per triangle)
cout << "6";
    NxVec3 *gWorldVertices;
	NxU32  *gWorldTriangles;
	gWorldVertices  = new NxVec3[WORLD_NBVERTICES];    // Array of all the vertices in the world 
	gWorldTriangles = new NxU32[WORLD_NBFACES*3];  // Array of all the triangles' points in the world
cout << "7";
    WorldDesc.pointStrideBytes    = sizeof(NxVec3);  // Number of bytes from one vertex to the next.
    WorldDesc.triangleStrideBytes = 3*sizeof(NxU32); // Number of bytes from one triangle to the next.

	cout << "Total Faces: "     << WORLD_NBFACES    << endl 
         << "Total Vertices: "  << WORLD_NBVERTICES << endl
	     << "Saving Points..."  << endl;

	for (unsigned int i = 0; i < MFEntityList[0].MFBrushes.Size(); i++) // Step through a brush
		{
            CurrentBrush = MFEntityList[0].MFBrushes[i];
			cout << "B[";
			for (unsigned int j = 0; j < CurrentBrush.MFPlanes.Size(); j++)// Step through a plane (triangle, face)
			{ 
				for (int k = 0; k < 3; k++) // Step through a point (vertice), stores it in the gWorldVertices array
				{
					cout << "P(" << CurrentBrush.MFPlanes[j].Points[k].x << ",";
                    gWorldVertices[j*k].x  = CurrentBrush.MFPlanes[j].Points[k].x;
                    gWorldTriangles[j*k+0] = CurrentBrush.MFPlanes[j].Points[k].x;

                    cout << CurrentBrush.MFPlanes[j].Points[k].y << ",";
                    gWorldVertices[j*k].y  = CurrentBrush.MFPlanes[j].Points[k].y;
                    gWorldTriangles[j*k+0] = CurrentBrush.MFPlanes[j].Points[k].y;

                    cout << CurrentBrush.MFPlanes[j].Points[k].z << ") ";
                    gWorldVertices[j*k].z  = CurrentBrush.MFPlanes[j].Points[k].z;
                    gWorldTriangles[j*k+0] = CurrentBrush.MFPlanes[j].Points[k].z;
				}
			cout << "]  ";
			}
		}

	WorldDesc.points = &gWorldVertices;
    WorldDesc.triangles = &gWorldTriangles;
    WorldDesc.flags = 0;

    /*
    ** The cooked version of the mesh is written to UserStream, which is an implementation of NxStream.
    ** Alternatively MemoryReadBuffer/MemoryWriteBuffer could be used to store the cooked version of the 
    ** mesh in memory. See SampleCommonCode for their implementation.
    */

//    NxCookTriangleMesh(WorldDesc, UserStream(outputfilename, false));

    //release cooking related resources.
	delete [] gWorldVertices;
	delete [] gWorldTriangles;
    NxCloseCooking();
	cout << "Done cooking mesh.";
	return 1;
}
And a required class, UserStream.hpp (currently not included, was giving me a bunch of linker errors)

Code: Select all

#ifndef _CA_COOK_HPP_
#define _CA_COOK_HPP_

#include "NxStream.h"

class UserStream : public NxStream
    {
    public:
                                UserStream(const char* filename, bool load);
    virtual                     ~UserStream();

    virtual     NxU8            readByte()                              const;
    virtual     NxU16           readWord()                              const;
    virtual     NxU32           readDword()                             const;
    virtual     float           readFloat()                             const;
    virtual     double          readDouble()                            const;
    virtual     void            readBuffer(void* buffer, NxU32 size)    const;

    virtual     NxStream&       storeByte(NxU8 b);
    virtual     NxStream&       storeWord(NxU16 w);
    virtual     NxStream&       storeDword(NxU32 d);
    virtual     NxStream&       storeFloat(NxReal f);
    virtual     NxStream&       storeDouble(NxF64 f);
    virtual     NxStream&       storeBuffer(const void* buffer, NxU32 size);

                FILE*           fp;
    };


UserStream::UserStream(const char* filename, bool load) : fp(NULL)
    {
    fp = fopen(filename, load ? "rb" : "wb");
    }

UserStream::~UserStream()
    {
    if(fp)  fclose(fp);
    }

// Loading API
NxU8 UserStream::readByte() const
    {
    NxU8 b;
    size_t r = fread(&b, sizeof(NxU8), 1, fp);
    NX_ASSERT(r);
    return b;
    }


//...

void UserStream::readBuffer(void* buffer, NxU32 size)   const
    {
    size_t w = fread(buffer, size, 1, fp);
    NX_ASSERT(w);
    }

// Saving API
NxStream& UserStream::storeByte(NxU8 b)
    {
    size_t w = fwrite(&b, sizeof(NxU8), 1, fp);
    NX_ASSERT(w);
    return *this;
    }

NxStream& UserStream::storeWord(NxU16 w)
    {
    size_t ww = fwrite(&w, sizeof(NxU16), 1, fp);
    NX_ASSERT(ww);
    return *this;
    }

//...

NxStream& UserStream::storeBuffer(const void* buffer, NxU32 size)
    {
    size_t w = fwrite(buffer, size, 1, fp);
    NX_ASSERT(w);
    return *this;
    }
#endif         /*_CA_COOK_HPP_*/
Note, I changed the Points array in MapFile.hpp

Code: Select all

    struct MapFilePlaneT
    {
        Vector3dT       Points[3];
Which requires a minor change in MapFile.cpp:

Code: Select all

        Vector3dT     Points[3];

        for (char PointNr=0; PointNr<3; PointNr++)
        {
            TP.AssertAndSkipToken("(");
            Points[PointNr].x=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            Points[PointNr].y=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            Points[PointNr].z=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            TP.AssertAndSkipToken(")");
        }

        try
        {
            // Die Reihenfolge der Punkte (im Uhrzeigersinn) ist wichtig, damit die Normalenvektoren stets aus dem Brush HERAUS zeigen!
            MFPlane.Plane=Plane3T<double>(Points[0], Points[1], Points[2], MapT::RoundEpsilon);
        }
Becomes,

Code: Select all

        //Vector3dT     Points[3];

        for (char PointNr=0; PointNr<3; PointNr++)
        {
            TP.AssertAndSkipToken("(");
            MFPlane.Points[PointNr].x=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            MFPlane.Points[PointNr].y=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            MFPlane.Points[PointNr].z=TP.GetNextTokenAsFloat()*CA3DE_SCALE;
            TP.AssertAndSkipToken(")");
        }

        try
        {
            // Die Reihenfolge der Punkte (im Uhrzeigersinn) ist wichtig, damit die Normalenvektoren stets aus dem Brush HERAUS zeigen!
            MFPlane.Plane=Plane3T<double>(MFPlane.Points[0], MFPlane.Points[1], MFPlane.Points[2], MapT::RoundEpsilon);
        }
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-06, 15:43

Hi,
minosdis wrote:[...] I'm getting a runtime exception at this line in MapFile.cpp,

Code: Select all

        MFPlane.Material=MaterialManager->GetMaterial(MaterialName);
According to the debugger, the MaterialManager object cannot be evaluated. I'm not sure if I need to initialize it or anything?
Yes. As with most Ca3DE subsystems, the subsystem is available through an abstract base class (ABC) for which several implementations are available. Sometimes the implementations come from DLLs (Renderer System, Sound System, Game System), sometimes they are linked in the executable.

The existing programs usually init the ABCs either before main(), or near the top of main(). The most complex example is Ca3DE.cpp (Ca3DE.cpp is subject to more code cleanup soon), good starting examples are e.g. CaBSP.cpp.

Just as you guessed, the MatSys must be initialized before use, e.g. as is done in CaBSP.cpp:

Code: Select all

    static MaterialManagerImplT MatManImpl;

    MaterialManager=&MatManImpl;

    if (MaterialManager->RegisterMaterialScriptsInDir(GameDirectory+"/Materials", GameDirectory+"/").Size()==0)
    {
        Console->Print(std::string("\nNo materials found in scripts in \"")+GameDirectory+"/Materials\".\n");
        Error("No materials found.");
    }
I'd recommend to also initialize the File System as shown in CaBSP.cpp as well, because when the MatSys tries to learn the dimensions of the texture images that comprise its materials, it accesses them via the FileSys.

In case your interested, here is the code to the project:
CaCook.cpp
[...]
It seems that you assume that the number of vertices is 3x the number of faces. But be careful: When you compute the PolygonT's for a brush (I didn't see the place where this is done in the code, btw.), the polygons are convex polygons of N vertices each (N is different from polygon to polygon). Thus, a given polygon can easily be decomposed into N-2 triangles, which you should use for the vertex count.
And a required class, UserStream.hpp (currently not included, was giving me a bunch of linker errors)
They don't provide you with a default implementation??

Note, I changed the Points array in MapFile.hpp

Code: Select all

    struct MapFilePlaneT
    {
        Vector3dT       Points[3];
[...]
I see, but please note that these Points are not the corner points of a triangle. They are instead only three arbitrary (non-colinear) points in the desired plane. This is also why this member used to be commented out: These points are not needed any more once the plane member has been computed. You actually have to call PolygonT::Complete() in order to obtain the polygons for each side of the brush, then decompose those polygons into triangles for feeding them to the NxCook* functions. (Maybe the NxCooking can also take polygons rather than triangles directly?)
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-08, 05:16

Okay, well, I've made some adjustments following your suggestions :) Fixed all my linker errors and the runtime seg faults, etc. I'm able to load a cmap successfully, and I now realize what you were saying about the points. Just arbitrary points on the planes, so stole some stuff from CaBSP, mainly ComputeBrushPolys(), Error() and I might end up using the MapFileSanityCheck() eventually. However, I'm getting some interesting stuff from ComputeBrushPolys. Heres a sample of it's output:
D:\Dev\Ca3DE-src-r626\Projects\Ca3D-Engine>cacook bprockb.cmap bprockb.bin
Input File:Games/DeathMatch/Maps/bprockb.cmap
Output File:Games/DeathMatch/Worlds/bprockb.bin
Registering archive "Games/DeathMatch/Textures/TechDemo.zip".
Registering archive "Games/DeathMatch/Textures/SkyDomes.zip".

(0, 0, 1), 6019.8 Textures/Frank/metal_bord1a
(0, 0, -1), -5689.6 Textures/Frank/metal_bord1a
(1, 0, 0), 2819.4 Textures/Frank/metal_bord1a
(-1, 0, 0), -2489.2 Textures/Frank/metal_bord1a
(0, -1, 0), 32512 Textures/Frank/metal_bord1a
(0, 1, 0), -31546.8 Textures/Frank/metal_bord1a

Plane (0, 0, 1), 5842, Vertices
Plane (0, 0, -1), -5791.20038757324, Vertices
Plane (1, 0, 0), 2489.2, Vertices (2489.2, -32283.4000000004, 5791.20038757306)
(2489.2, -32334.1999999993, 5791.20038757306) (2489.2, -32334.1999999993, 5842)
(2489.2, -32283.4, 5842)
Plane (-1, 0, 0), -2463.8, Vertices
Plane (0, -1, 0), 32334.2, Vertices
Plane (0, 1, 0), -32283.4, Vertices
Plane (0, 0, 1), 6019.8, Vertices
Plane (0, 0, -1), -5689.6, Vertices
Plane (1, 0, 0), 2819.4, Vertices
Plane (-1, 0, 0), -2489.2, Vertices (2489.2, -32334.1999999993, 5791.20038757306
) (2489.2, -32283.4, 5791.20038757306) (2489.2, -32283.4000000004, 5842) (2489.2
, -32334.1999999993, 5842)
Plane (0, -1, 0), 32512, Vertices
Plane (0, 1, 0), -31546.8, Vertices

FATAL ERROR: Entity #0, brush #1: polygon #0 is invalid.
Program aborted.
I get a similar error (Same Entity, Brush, Polygon) no matter what cmap I try and load. I'm using if if(!BrushPolys[MFPlaneNr].IsValid(2, 10)) in the loop checking for validity in ComputeBrushPolys, if that makes a difference. Heres the full source if your interested (no more userstream class, btw, found one included with the SDK)

Code: Select all

// CaCook.cpp : 'Cooks world geometry into binary files readable by PhysX for high speed collision detection.
// by Phil Clark

#include <string.h>
#include <iostream>
#include "utils.hpp"
#include "MapFile.hpp"
#include "FileSys/FileManImpl.hpp"
#include "ConsoleCommands/ConsoleStdout.hpp"
#include "TextParser/TextParser.hpp"
#include "MaterialSystem/Material.hpp"
#include "MaterialSystem/MaterialManager.hpp"
#include "MaterialSystem/MaterialManagerImpl.hpp"

#include "NxPhysics.h"
#include "NxCooking.h"
#include "NXU_Helper.h"
#include "NXU_Streaming.h"

using namespace std;
using namespace cf;

#define MAX_VERTICES  100000
#define MAX_TRIANGLES 100000
//bool addEntireScene(NxuPhysicsCollection &, NxScene &, const char *,const char *, NXU::NXU_userNotify * = 0);
//class NxTriangleMeshDesc;


int main(int argc, char *argv[])
{
	if ( argc < 3 ) {
		cout << "Usage:" << endl
			 << "CaCook.exe <InputFileName> <OutputFileName>" << endl;
		return 0;
	}
	char inputfilename[255], outputfilename[255];
    sprintf(inputfilename, "Games/DeathMatch/Maps/%s", argv[1]);
    sprintf(outputfilename, "Games/DeathMatch/Worlds/%s", argv[2]);

	cout << " Input File:" << inputfilename  << endl
		 << "Output File:" << outputfilename << endl;
    ///////////////////////////////////////////////////////////////
    //Ca3D Initializations
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
    static MaterialManagerImplT MatManImpl;
    MaterialManager = &MatManImpl;
    if (MaterialManager->RegisterMaterialScriptsInDir("Games/DeathMatch/Materials", "Games/DeathMatch/").Size()==0)
    {
        Console->Print(std::string("\nNo materials found in scripts in \"Games/DeathMatch/Materials\".\n"));
        cout << "No materials found.";
        exit(0);
    }
    ArrayT<MapFileEntityT> MFEntityList;
	MapFileBrushT      *CurrentBrush;

    ///////////////////////////////////////////////////////////////
    //PhysX Initializations
    static NxPhysicsSDK*	   gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);
    static NxCookingInterface *gCooking    = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
    gCooking->NxInitCooking();
    if(!gPhysicsSDK) { cout << "Wrong SDK DLL version?"; exit(0); }
    NxScene                *gScene;     //
    NxSceneDesc             sceneDesc;  // Physics 'scene' descriptor
    NxActor                *gActor;     // Actor
    NxActorDesc             actorDesc;  // Actor desc
    NxTriangleMeshDesc      WorldDesc;  // World geometry
    NxTriangleMeshShapeDesc WorldShapeDesc;    // This is getting obscene....
    NXU::MemoryWriteBuffer  buffer;
	unsigned int            WORLD_NBVERTICES;  // Number of vertices
	unsigned int            WORLD_NBFACES;     // Number of faces


    ///////////////////////////////////////////////////////////////
    //Load the CMAP
    TextParserT TP(inputfilename, "()");
	if (TP.IsAtEOF()) {
		cout << "Unable to open map file.";
		return 0;
	}

    try
    {
        while (!TP.IsAtEOF())
        {
            MFEntityList.PushBack(MapFileEntityT(TP));
        }
    }
    catch (const TextParserT::ParseError&)
    {
        cout << "Problem with parsing the map near byte " << TP.GetReadPosByte() << "(" << TP.GetReadPosPercent()*100.0 << ") of the file." << endl;
        exit(0);
    } 
  
    // Check for any 'func_group' brushes, add them to worldspawn
    for (unsigned long EntityNr = 1; EntityNr < MFEntityList.Size(); EntityNr++)
    {
        if (MFEntityList[EntityNr].MFProperties["classname"]=="func_group")
        {
            // Copy all brushes of this entity into the 'worldspawn' entity.
            for (unsigned long BrushNr=0; BrushNr<MFEntityList[EntityNr].MFBrushes.Size(); BrushNr++)
                MFEntityList[0].MFBrushes.PushBack(MFEntityList[EntityNr].MFBrushes[BrushNr]);

            // Copy all bezier patches of this entity into the 'worldspawn' entity.
            for (unsigned long BPNr=0; BPNr<MFEntityList[EntityNr].MFPatches.Size(); BPNr++)
                MFEntityList[0].MFPatches.PushBack(MFEntityList[EntityNr].MFPatches[BPNr]);

            // Delete this entity.
            MFEntityList[EntityNr]=MFEntityList[MFEntityList.Size()-1];
            MFEntityList.DeleteBack();
            EntityNr--;
        }
    }
    

    ArrayT< Polygon3T<double> > BrushPolys;
    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++) // Step through a brush
	{
        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);
        for (unsigned int PolyNr = 0; PolyNr < BrushPolys.Size(); PolyNr++) 
        {
            for (unsigned int VerticeNr =0; VerticeNr < BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                WORLD_NBVERTICES++;
            }
        WORLD_NBFACES++;
        }
    }
    NxVec3 *gWorldVertices   = (NxVec3*)malloc(WORLD_NBVERTICES*sizeof(NxVec3));    // Array of vertices
	NxU32  *gWorldTriangles  = (NxU32*)malloc(WORLD_NBFACES*sizeof(NxU32));  // Array of triangles
    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++) // Step through a brush
	{
        cout << "B[";        
        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);
        for (unsigned int PolyNr = 0; PolyNr < BrushPolys.Size(); PolyNr++) 
        {
            for (unsigned int VerticeNr =0; VerticeNr < BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                cout << "P(" << BrushPolys[PolyNr].Vertices[VerticeNr].x << ",";
                gWorldVertices[ PolyNr*VerticeNr].x   = (int)BrushPolys[PolyNr].Vertices[VerticeNr].x;
                gWorldTriangles[PolyNr*VerticeNr + 0] = BrushPolys[PolyNr].Vertices[VerticeNr].x;

                cout << BrushPolys[PolyNr].Vertices[VerticeNr].y << ",";
                gWorldVertices[ PolyNr*VerticeNr].y   = BrushPolys[PolyNr].Vertices[VerticeNr].y;
                gWorldTriangles[PolyNr*VerticeNr + 1] = BrushPolys[PolyNr].Vertices[VerticeNr].y;

                cout << BrushPolys[PolyNr].Vertices[VerticeNr].z << ") ";
                gWorldVertices[ PolyNr*VerticeNr].z   = BrushPolys[PolyNr].Vertices[VerticeNr].z;
                gWorldTriangles[PolyNr*VerticeNr + 2] = BrushPolys[PolyNr].Vertices[VerticeNr].z;
            }
        }
        cout << "]  ";
    }

	cout << endl
         << "Total Faces: "     << WORLD_NBFACES    << endl 
         << "Total Vertices: "  << WORLD_NBVERTICES << endl
	     << "Saving Points..."  << endl;

    WorldDesc.numVertices   = WORLD_NBVERTICES;      // Number of vertices in mesh.
    WorldDesc.numTriangles  = WORLD_NBFACES;         // Number of triangles(3 indices per triangle)

	gWorldVertices  = new NxVec3[WORLD_NBVERTICES];  // Array of all the vertices in the world 
	gWorldTriangles = new NxU32[WORLD_NBFACES*3];    // Array of all the triangles' points in the world

    WorldDesc.pointStrideBytes    = sizeof(NxVec3);  // Number of bytes from one vertex to the next.
    WorldDesc.triangleStrideBytes = 3*sizeof(NxU32); // Number of bytes from one triangle to the next.

	WorldDesc.points    = &gWorldVertices;
    WorldDesc.triangles = &gWorldTriangles;
    WorldDesc.flags     = 0;

    // Cook the mesh to memory, and read it back again.
    bool status = gCooking->NxCookTriangleMesh(WorldDesc, buffer);
    WorldShapeDesc.meshData = gPhysicsSDK->createTriangleMesh(NXU::MemoryReadBuffer(buffer.data));

    // Add the shape desc to an actor desc, then add the actor (from desc) to the scene
    actorDesc.shapes.pushBack(&WorldShapeDesc);
    gActor = gScene->createActor(actorDesc);

    // Create collection for the scene and save it to the outputfile
    NXU::NxuPhysicsCollection *collection = NXU::createCollection ();
    NXU::addEntireScene(*collection, *gScene) ;
    if ( NXU::saveCollection(collection, outputfilename, NXU::FT_BINARY, true, true) )
        cout << endl << "Done cooking mesh.";
    else
        cout << endl << "Error cooking mesh.";

    // Release all out resources
	delete [] gWorldVertices;
	delete [] gWorldTriangles;

    NxCloseCooking();
    NXU::releasePersistentMemory();
    gPhysicsSDK->release();
	return 1;
}
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-08, 09:24

Dear MinosDis,

I think that the error with the invalid polygons is because ComputeBrushPolys() expects an empty array (BrushPolys) with each call. That is, you should move the array declaration into the loop, like this:

Code: Select all

    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++) // Step through a brush
    {
        ArrayT< Polygon3T<double> > BrushPolys;

        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);

        // [...]
    }
I'd also suggest to remove the WORLD_NBVERTICES and WORLD_NBFACES, and replace

Code: Select all

    NxVec3 *gWorldVertices   = (NxVec3*)malloc(WORLD_NBVERTICES*sizeof(NxVec3));    // Array of vertices
   NxU32  *gWorldTriangles  = (NxU32*)malloc(WORLD_NBFACES*sizeof(NxU32));  // Array of triangles
with

Code: Select all

    ArrayT<NxVec3> gWorldVertices;
    ArrayT<NxU32>  gWorldTriangles;
You'd not have to call delete or free() on them manually, that's automatically done by the destructors (note btw. that you cannot call delete[] on something alloc'ed with malloc() anyway, you'd have to use free() instead). You'd use the arrays e.g. like

Code: Select all

NxVec3 xyz=...;
gWorldVertices.PushBack(xyz);    // add xyz to end of array
gWorldVertices.PushBackEmpty();   // Add another NxVec3 at the end, uninitialized
NxVec3& last=gWorldVertices[gWorldVertices.Size()-1];
last.x=123.4;    // Manipulate element added with PushBackEmpty() above().
Of course you could also use std::vector<> instead, in new programs I'd even prefer this over our in-house ArrayT<> (which is however nice and fast as well).

The biggest problem with this is probably to replace lines

Code: Select all

   WorldDesc.points    = &gWorldVertices;
    WorldDesc.triangles = &gWorldTriangles;
properly. I have not tried, but you might need something like

Code: Select all

    VerticesPtr=&gWorldVertices[0];
    WorldTrisPtr=&gWorldTriangles[0];

   WorldDesc.points    = &VerticesPtr;
    WorldDesc.triangles = &WorldTrisPtr;
or maybe even

Code: Select all

   WorldDesc.points    = &gWorldVertices[0];
    WorldDesc.triangles = &gWorldTriangles[0];
works, depending on the type of the members of WorldDesc.

Also, in the second loop, the gWorldTriangles array should be filled with indices rather than coordinates, shouldn't it? I'll post a loop that I think works right this afternoon. :up:
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-08, 13:58

Hey,

I'll give that a shot with the BrushPolys array. About the gWorldVertices and gWorldTriangles, I originally wanted to use the in house ArrayT, but given the example from the PhysX manual,
They want a pointer to a NxVec3 standard array. Not knowing a whole lot about how datatypes are stored in memory, I would worry the point data either wouldnt be stored in the first block, or would have other data mixed in. Heres a (slightly incomplete) cooking example from the docs,

Code: Select all

// Build vertex normals (useful for rendering)
meshNormals = new NxVec3[MESH_NBVERTICES];
NxBuildSmoothNormals(MESH_NBFACES, MESH_NBVERTICES, (const NxVec3*)gVertices, (const NxU32*)gTriangles, NULL, meshNormals, true);


// Build thr triangle mesh.
NxTriangleMeshDesc meshDesc;
meshDesc.numVertices                = MESH_NBVERTICES;
meshDesc.numTriangles               = MESH_NBFACES;
meshDesc.pointStrideBytes           = sizeof(NxVec3);
meshDesc.triangleStrideBytes        = 3*sizeof(NxU32);
meshDesc.points                         = gVertices;
meshDesc.triangles                      = gTriangles;                           
meshDesc.flags                           = 0;

//cooking is a slow process and should generally be done offline.
NxCookingInterface *gCooking = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
gCooking->NxInitCooking();
gCooking->NxCookTriangleMesh(meshDesc, UserStream("tempfile.bin", false));

//Create the mesh from the cooked data.
triangleMesh = gPhysicsSDK->createTriangleMesh(UserStream("tempfile.bin", true));
I don't think we have to pay attention to the first block except that looking up the function. Looking up the points and triangles members in the docs, I also found:
const void* NxSimpleTriangleMesh::points
Pointer to first vertex point.
Caller may add pointStrideBytes bytes to the pointer to access the next point.
const void* NxSimpleTriangleMesh::triangles
Pointer to first triangle.
Caller may add triangleStrideBytes bytes to the pointer to access the next triangle.
These are triplets of 0 based indices: vert0 vert1 vert2 vert0 vert1 vert2 vert0 vert1 vert2 ...
where vertex is either a 32 or 16 bit unsigned integer. There are numTriangles*3 indices.
This is declared as a void pointer because it is actually either an NxU16 or a NxU32 pointer.
And I guess you're right about the triangles member being for indices not the actual points. I'm not entirely sure what they mean by indices in this context however.

EDIT: I rewrote the loop using vectors, seems to work. Will need to do a little testing.

Code: Select all

    vector<NxVec3> gWorldVertices;
    vector<NxU32>  gWorldTriangles;
    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++) // Step through a brush
	{
        cout << "B[";
        ArrayT< Polygon3T<double> > BrushPolys;
        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);
        for (unsigned int PolyNr = 0; PolyNr < BrushPolys.Size(); PolyNr++) 
        {
            for (unsigned int VerticeNr =0; VerticeNr < BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                NxVec3 tempVec;

                cout << "P(" << BrushPolys[PolyNr].Vertices[VerticeNr].x << ",";
                tempVec.x = BrushPolys[PolyNr].Vertices[VerticeNr].x;
                gWorldTriangles.push_back(BrushPolys[PolyNr].Vertices[VerticeNr].x);

                cout << BrushPolys[PolyNr].Vertices[VerticeNr].y << ",";
                tempVec.x = BrushPolys[PolyNr].Vertices[VerticeNr].y;
                gWorldTriangles.push_back(BrushPolys[PolyNr].Vertices[VerticeNr].y);

                cout << BrushPolys[PolyNr].Vertices[VerticeNr].z << ") ";
                tempVec.x = BrushPolys[PolyNr].Vertices[VerticeNr].z;
                gWorldTriangles.push_back(BrushPolys[PolyNr].Vertices[VerticeNr].z);

                gWorldVertices.push_back(tempVec);
            }
        }
        cout << "]  ";
    }
That being said, I still don't think I'm getting the correct points. Heres the first brushe that the loop prints out (reformatted for readability) for a cmap I made that only contains one hollow cube:
B[
P( 3251.2, 3251.2, 1625.6)
P( 3251.2,-1625.6, 1625.6)
P(-1625.6,-1625.6, 1625.6)
P(-1625.6, 3251.2, 1625.6)
P(-1625.6,-1625.6, 812.8)
P(-1625.6, 3251.2, 812.8)
P(-1625.6, 3251.2, 1625.6)
P(-1625.6,-1625.6, 1625.6)
P( 3251.2, 3251.2, 812.8)
P( 3251.2,-1625.6, 812.8)
P( 3251.2,-1625.6, 1625.6)
P( 3251.2, 3251.2, 1625.6)
P(-1625.6, 3251.2, 812.8)
P( 3251.2, 3251.2, 812.8)
P( 3251.2, 3251.2, 1625.6)
P(-1625.6, 3251.2, 1625.6)
P( 3251.2,-1625.6, 812.8)
P(-1625.6,-1625.6, 812.8)
P(-1625.6,-1625.6, 1625.6)
P( 3251.2,-1625.6, 1625.6)
P(-1625.6, 3251.2, 812.8)
P(-1625.6,-1625.6, 812.8)
P( 3251.2,-1625.6, 812.8)
P( 3251.2, 3251.2, 812.8)
]
And heres the brush from the cmap,
{ // Brush 0
( -64 -64 64 ) ( -64 128.015625 64 ) ( 128 128 64 ) "Textures/Frank/marble" ( 2 0 0 0 ( 128 0 0 ) ( 0 -16 0 ) )
( -64 128 32 ) ( -64 128 64 ) ( -64 -64 64 ) "Textures/Frank/marble" ( 2 0 0 0 ( 0 128 0 ) ( 0 0 -16 ) )
( 128 -64 32 ) ( 128 -64 64 ) ( 128 128.015625 64 ) "Textures/Frank/marble" ( 2 0 0 0 ( 0 128 0 ) ( 0 0 -16 ) )
( 128 128 32 ) ( 128 128 64 ) ( -64 128 64 ) "Textures/Frank/marble" ( 2 0 0 0 ( 128 0 0 ) ( 0 0 -16 ) )
( -64 -64 32 ) ( -64 -64 64 ) ( 128 -64 64 ) "Textures/Frank/marble" ( 2 0 0 0 ( 128 0 0 ) ( 0 0 -16 ) )
( -64 128 32 ) ( -64 -64 32 ) ( 128 -64 32 ) "Textures/Frank/marble" ( 2 0 0 0 ( 128 0 0 ) ( 0 -16 0 ) )
}
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-08, 17:27

Hi MinosDis,
They want a pointer to a NxVec3 standard array. Not knowing a whole lot about how datatypes are stored in memory, I would worry the point data either wouldnt be stored in the first block, or would have other data mixed in.
Data that is stored in a std::vector<> or ArrayT<> is guaranteed to be laid out like the same data alloc'ed with malloc(), that is, passing &myArray[0] to a function that expects an array alloc'ed with malloc() is valid.

As promised, I've rewritten your inner loop:

Code: Select all

    vector<NxVec3> gWorldVertices;
    vector<NxU32>  gWorldTriangles;

    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++)
    {
        ArrayT< Polygon3T<double> > BrushPolys;

        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);

        for (unsigned int PolyNr=0; PolyNr<BrushPolys.Size(); PolyNr++)
        {
            const unsigned int FirstIndex=gWorldVertices.size();

            // Just add all vertices of the polygon to gWorldVertices.
            // (When polygons share vertices removing the redundancy is not taken into account.)
            for (unsigned int VerticeNr=0; VerticeNr<BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                NxVec3 tempVec;

                tempVec.x=BrushPolys[PolyNr].Vertices[VerticeNr].x;
                tempVec.y=BrushPolys[PolyNr].Vertices[VerticeNr].y;
                tempVec.z=BrushPolys[PolyNr].Vertices[VerticeNr].z;

                gWorldVertices.push_back(tempVec);
            }

            // Add the right indices for the V-2 triangles to gWorldTriangles.
            for (unsigned int VerticeNr=0; VerticeNr+2<BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                // FIXME / Check: Is the triangle orientation right (same as in Ca3DE)?
                //                Swap the last two lines otherwise.
                gWorldTriangles.push_back(FirstIndex);      // Always the first.
                gWorldTriangles.push_back(FirstIndex+VerticeNr+1);
                gWorldTriangles.push_back(FirstIndex+VerticeNr+2);
            }
        }
    }
I didn't try it out, but I think that it is (more) correct like this, at least it implements the proper triangulation of the polygons and fills the indices into gWorldTriangles properly. However, please note that I've not looked into the PhysiX API nor read any related documentation - depending on the details, more changes might be required. ;)
That being said, I still don't think I'm getting the correct points. Heres the first brushe that the loop prints out (reformatted for readability) for a cmap I made that only contains one hollow cube:
Note that the actual numbers are scaled by constant CA3DE_SCALE=25.4 in MapFile.cpp. That is, numbers in cmap files are in inches, after loading they are in millimeters, so I think that your output is right. (In addition, you could group the vertices output per polygon, so that it's easier to see which vertices belong to an individual polygon.)
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-09, 04:56

thanks for the snippet :)
I think that should work well. Thanks for clearing up the indices part also. I'm still not perfectly clear on what they are, but your implementation looks similar to what the physx docs describe, so I'll trust you on that one ;)

Unfortunately, I've been avoiding the elefant in the room. Ca3d works with meshes, which are convex polygons of N vertices. PhysX works with triangles. So now that we've got the correct vertices showing up, I've got to implement an algorithm to triangulate the PolygonT (which I'm guessing are equivilent to faces of a polyhedron (polychoron even?) I'm thinking about using Seidel's Algorithm (http://www.cs.unc.edu/~dm/CODE/GEM/chapter.html) or something like Ear Cutting (http://cgm.cs.mcgill.ca/~godfried/teach ... _ears.html). I didnt really want to do any computational algorithms in this program. Initially I figured it would be fairly straightforward. Oh well. I think after this is done, adding entities to the scene should be easy, and so should updating their positions from the physics engine. I have a feeling this will be the toughest part. ::knock on wood::

EDIT:
Well, I quickly wrote something up tonite that I think might work. It does make a number of key assumptions about the polygons in Ca3D, but I think they are valid. Let me know what you think :)

Code: Select all

///////////////////////////////////////////////////////////
// Triangulation Tools
//
// Assumptions:
// 1. All polygons in Ca3D are Convex
// 2. There are no holes in polygons
//
// Goal:
// Break down a polygon of N vertices into N-2 triangles
// 

// Triangulates a polygon, returning an array of triangles (an array of 3 Vectors)
// NOTE: this function does not account for duplicate vertices. This will be done in the main program.
ArrayT< ArrayT<VectorT> > triangulate(Polygon3T<double> poly)
{
    ArrayT< ArrayT<VectorT> > triangles;
    
    // First check to see if this is already a triangle, if so format the data correctly and leave.
    if (poly.Vertices.Size() == 3) 
    {
        ArrayT<VectorT> singleTriangle;
        singleTriangle.PushBack(poly.Vertices[0]);
        singleTriangle.PushBack(poly.Vertices[1]);
        singleTriangle.PushBack(poly.Vertices[2]);

        triangles.PushBack(singleTriangle);
        return triangles;
    }
    
    Polygon3T<double> newPoly = poly;
    Polygon3T<double> oldPoly;
    bool done = false;
    int i = 0;
    while (!done)
    {
        ArrayT<VectorT> tempTriangle;
        VectorT tempVec_i;
        VectorT tempVec_iminus1;
        VectorT tempVec_iplus1;
        unsigned int indice0, indice1, indice2;
        
        indice0 = i;
        tempVec_i       = newPoly.Vertices[indice0];
        tempTriangle.PushBack(tempVec_i);
        
        indice1 = (i == 0) ? newPoly.Vertices.Size() : i;
        tempVec_iminus1 = newPoly.Vertices[indice1];
        tempTriangle.PushBack(tempVec_iminus1);
        
        indice2 = (i == newPoly.Vertices.Size()) ? 0 : i;
        tempVec_iplus1  = newPoly.Vertices[indice2];
        tempTriangle.PushBack(tempVec_iplus1);

        triangles.PushBack(tempTriangle);
        
        // Fill the new polygon
        oldPoly = newPoly;
        newPoly.Vertices.Clear();
        for (unsigned int VertNr = 0; VertNr < oldPoly.Vertices.Size(); VertNr++)
            if ( (VertNr != indice0) && (VertNr != indice1) && (VertNr != indice2) )
                newPoly.Vertices.PushBack(oldPoly.Vertices[VertNr]);

        i++;
        if (i == newPoly.Vertices.Size()) i=0;

        if (newPoly.Vertices.Size() == 3) done = true;
    }
}
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-09, 10:41

oh. erm. Maybe I should have expressed it more clearly: My loop above already handles the triangulation. 8)

This is because the polygons are indeed convex. I just realize that the definition of a polygon is still given in German language (comment at the top in Libs/Math3D/Polygon.hpp) - I will fix that for the next version.

When a polygon is convex, its triangulation is always easy, e.g with the "triangle fan" technique: A nice image and description is at Microsoft MSDN.
That is, choosing one point (e.g. the first one at index 0, "v1"), then "walking around the contour" always works with convex polygons. This is what my second inner loop above does. More complicated triangulation algorithms are only necessary when the polygons are non-convex.

Btw., the indices in gWorldTriangles are just the numbers that say where in gWorldVertices a vertex is found. That is, if

Code: Select all

gWorldTriangles[300]=34;
gWorldTriangles[301]=97;
gWorldTriangles[302]=123;
this just means that the 100-th triangle in the world is defined by vertices 34, 97 and 123 in gWorldVertices.

Another tip: Don't pass "big" data structures and classes by value. Pass them by pointer or better by reference. That is, write

Code: Select all

ArrayT< ArrayT<VectorT> > triangulate(Polygon3T<double>& poly)
(Note the & sign, it makes a huge difference.) Passing by value implies a possibly very expensive copy operation, whereas passing by reference or pointer only copies a pointer.
The same trick cannot be used with the return type, though.
For completeness, in the example above, you'd even add a const:

Code: Select all

ArrayT< ArrayT<VectorT> > triangulate(const Polygon3T<double>& poly)
Btw., http://www.parashift.com/c++-faq-lite/ is a great resource for learning the fine points about C++ (the online version is good, but the printed book is even better). When I once bought the book, I initially found it too hard and full of "Do!"s and "Don't!"s, but in hindsight, it's an excellent learning. Scott Meyers books ("Effective C++, 3rd edition") are very good as well. ;)
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-09, 12:37

Hrmm, well that actually kind of depresses me that your loop is doing the triangulation. Partially because I spent an hour or so writing that function but mainly because I was hoping that was the reason I was getting runtime errors (in __lock_fhandle(), osfinfo.c) ;)

I guess I actually have to figure out why I'm getting the error now :(
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-09, 16:01

minosdis wrote:Hrmm, well that actually kind of depresses me that your loop is doing the triangulation.
Don't worry, everyone (including me) throws away tons of code from time to time. ;)
Partially because I spent an hour or so writing that function but mainly because I was hoping that was the reason I was getting runtime errors (in __lock_fhandle(), osfinfo.c) ;)
Are you familiar with using a debugger? Usually a stack trace is very helpful in such situations.
Iirc, the free versions of Microsoft Visual C++ do not come with a JIT debugger, but you can download Microsofts free WinDbg, see this chapter for more details.

If you use a Visual Studio debugger already, you probably don't need or want WinDbg. The JIT feature must also be enabled explicitly (but then it's very useful), I'll have to look this up again and will then update the documentation accordingly.
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-09, 20:04

Are you familiar with using a debugger? Usually a stack trace is very helpful in such situations.
Iirc, the free versions of Microsoft Visual C++ do not come with a JIT debugger, but you can download Microsofts free WinDbg, see this chapter for more details.
I've been using the debugger that comes with MSVC++, however I don't really understand what the stack trace is telling me. Mainly I just look at which line the program is crashing on, and the various watches I've set up trying to find the bad pointer. Unfortunately this error is comming from some random lib file (osfinfo.c) in __lock_fhandle(), a file and function I'm completely unfamiliar with. I'm not even sure really where that function is getting called from. I'm pretty sure its getting called by the PhysX side, from within gCooking->NxCookTriangleMesh(). This is what lead me to believe the input I was giving it (the mesh descriptor and a memory buffer object (derives from NxStream)) I think I may post a snippet on the PhysX forum over at NVidia and see if they can come up with anything =/
User avatar
Carsten
Site Admin
Posts:2170
Joined:2004-08-19, 13:46
Location:Germany
Contact:

Re: Standalone app, cook the world :)

Post by Carsten » 2008-09-10, 13:43

Dear MinosDis,

a stack trace just shows you the function (or method) call chain at the time of a crash or debugger interrupt. This normally makes it easy to identify where own code causes crashes deeper in libraries. It's also explained in this Wikipedia article, though only briefly.
Of course, in the presence of bad pointers, stack traces alone are often not sufficient to locate the cause for a crash.

If you continue to have problems, don't hesitate to post your (full) code here (or send to me via email) - I'm sure we will get this fixed. :up:
Best regards,
Carsten
minosdis
Posts:24
Joined:2008-08-09, 05:42

Re: Standalone app, cook the world :)

Post by minosdis » 2008-09-10, 14:29

Well, I went ahead and posted to the nvidia forums, hopefully someone there will see what I'm doing wrong :)
I'll post the full source here, maybe if you get a chance you could try it out?

CaCook.cpp

Code: Select all

// CaCook.cpp : 'Cook's world geometry into binary files readable by PhysX for high speed collision detection.
#define debug 1

#include <iostream>
#include <vector>
#include "utils.hpp"
#include "MapFile.hpp"
#include "ConsoleCommands/ConsoleStdout.hpp"
#include "TextParser/TextParser.hpp"
#include "MaterialSystem/MaterialManagerImpl.hpp"


#include "NxCooking.h"
#include "NXU_Helper.h"
#include "NXU_Streaming.h"

using namespace std;
using namespace cf;

int main(int argc, char *argv[])
{
	if ( argc < 3 ) {
		cout << "Usage:" << endl
			 << "CaCook.exe <InputFileName> <OutputFileName>" << endl;
		return 0;
	}
	char inputfilename[255], outputfilename[255];
    sprintf(inputfilename, "Games/DeathMatch/Maps/%s", argv[1]);
    sprintf(outputfilename, "Games/DeathMatch/Worlds/%s", argv[2]);

	cout << " Input File:" << inputfilename  << endl
		 << "Output File:" << outputfilename << endl;

    ///////////////////////////////////////////////////////////////
    //Ca3D Initializations
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_LOCAL_PATH, "./", "");
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/TechDemo.zip", "Games/DeathMatch/Textures/TechDemo/", "Ca3DE");
    FileSys::FileMan->MountFileSystem(cf::FileSys::FS_TYPE_ZIP_ARCHIVE, "Games/DeathMatch/Textures/SkyDomes.zip", "Games/DeathMatch/Textures/SkyDomes/", "Ca3DE");
    static MaterialManagerImplT MatManImpl;
    MaterialManager = &MatManImpl;
    if (MaterialManager->RegisterMaterialScriptsInDir("Games/DeathMatch/Materials", "Games/DeathMatch/").Size()==0)
        Error("No materials found in scripts in \"Games/DeathMatch/Materials\"");
    ArrayT<MapFileEntityT> MFEntityList;
	MapFileBrushT      *CurrentBrush;

    ///////////////////////////////////////////////////////////////
    //PhysX Initializations
    static NxPhysicsSDK*	   gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);
    if (!gPhysicsSDK) Error("Failed to load PhysX SDK");
    else Console->Print("Loaded PhysX SDK\n");

    static NxCookingInterface *gCooking    = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
    if (!gCooking->NxInitCooking()) Error("Failed to load NxCooking");
    else Console->Print("Loaded NxCooking\n");

    NxScene                 *gScene;     //
    NxSceneDesc              sceneDesc;  // Physics 'scene' descriptor
    NxActor                 *gActor;     // Actor
    NxActorDesc              actorDesc;  // Actor desc
    NxTriangleMeshDesc       WorldDesc;  // World geometry
    NxTriangleMeshShapeDesc  WorldShapeDesc;    // This is getting obscene....
    NXU::MemoryWriteBuffer   buffer;
	unsigned int             WORLD_NBVERTICES =0;  // Number of vertices
	unsigned int             WORLD_NBFACES =0;     // Number of faces


    ///////////////////////////////////////////////////////////////
    //Load the CMAP
    TextParserT TP(inputfilename, "()");
	if (TP.IsAtEOF())
        Error("Unable to open map file.");
    try
    {
        while (!TP.IsAtEOF())
        {
            MFEntityList.PushBack(MapFileEntityT(TP));
        }
    }
    catch (const TextParserT::ParseError&)
    {
        Error("Problem with parsing the map near byte %lu (%.3f%%) of the file.", TP.GetReadPosByte(), TP.GetReadPosPercent()*100.0);
    } 
  
    // Check for any 'func_group' brushes, add them to worldspawn
    for (unsigned long EntityNr = 1; EntityNr < MFEntityList.Size(); EntityNr++)
    {
        if (MFEntityList[EntityNr].MFProperties["classname"]=="func_group")
        {
            // Copy all brushes of this entity into the 'worldspawn' entity.
            for (unsigned long BrushNr=0; BrushNr<MFEntityList[EntityNr].MFBrushes.Size(); BrushNr++)
                MFEntityList[0].MFBrushes.PushBack(MFEntityList[EntityNr].MFBrushes[BrushNr]);

            // Copy all bezier patches of this entity into the 'worldspawn' entity.
            for (unsigned long BPNr=0; BPNr<MFEntityList[EntityNr].MFPatches.Size(); BPNr++)
                MFEntityList[0].MFPatches.PushBack(MFEntityList[EntityNr].MFPatches[BPNr]);

            // Delete this entity.
            MFEntityList[EntityNr]=MFEntityList[MFEntityList.Size()-1];
            MFEntityList.DeleteBack();
            EntityNr--;
        }
    }


    ArrayT<NxVec3> gWorldVertices;
    ArrayT<NxU32>  gWorldTriangles;

    for (unsigned long BrushNr=0; BrushNr<MFEntityList[0].MFBrushes.Size(); BrushNr++)
    {
        ArrayT< Polygon3T<double> > BrushPolys;
        
        ComputeBrushPolys(MFEntityList[0].MFBrushes[BrushNr], BrushPolys, 0, BrushNr);

        for (unsigned int PolyNr=0; PolyNr<BrushPolys.Size(); PolyNr++)
        {
            unsigned int NumVerts    = 0;
            unsigned int FirstIndex  = 0;
            unsigned int SecondIndex = 0;
            unsigned int ThirdIndex  = 0;
            if(debug)cout << "Poly[" << endl;

            // Just add all vertices of the polygon to gWorldVertices.
            // (When polygons share vertices removing the redundancy is not taken into account.)
            for (unsigned int VerticeNr=0; VerticeNr<BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                NxVec3 tempVec;
                NumVerts++;
                if (FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[VerticeNr]) == -1) 
                {
                    tempVec.x=BrushPolys[PolyNr].Vertices[VerticeNr].x;
                    tempVec.y=BrushPolys[PolyNr].Vertices[VerticeNr].y;
                    tempVec.z=BrushPolys[PolyNr].Vertices[VerticeNr].z;
                    WORLD_NBVERTICES++;
                    gWorldVertices.PushBack(tempVec);

                    if(debug)cout << "    V(" << tempVec.x
                                  << ", "  << tempVec.y
                                  << ", "  << tempVec.z 
                                  << ")"  << endl;
                         
                }
            }

            // Add the right indices for the V-2 triangles to gWorldTriangles.
            for (unsigned int VerticeNr=0; VerticeNr+2<BrushPolys[PolyNr].Vertices.Size(); VerticeNr++)
            {
                // FIXME / Check: Is the triangle orientation right (same as in Ca3DE)?
                //                Swap the last two lines otherwise.
                if (FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[0]) != -1) // if vert is already defined
                    FirstIndex  = FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[0]);
                if (FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[VerticeNr+1]) != -1) // if vert is already defined
                    SecondIndex = FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[VerticeNr+1]);
                if (FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[VerticeNr+1]) != -1) // if vert is already defined
                    ThirdIndex  = FindIndex(gWorldVertices, BrushPolys[PolyNr].Vertices[VerticeNr+2]);

                gWorldTriangles.PushBack(static_cast<NxU32>(FirstIndex) );      // Always the first.
                gWorldTriangles.PushBack(static_cast<NxU32>(SecondIndex) );
                gWorldTriangles.PushBack(static_cast<NxU32>(ThirdIndex) );
                if(debug)cout << "    T(" << FirstIndex
                              << ", "      << SecondIndex
                              << ", "      << ThirdIndex
                              << ")"      << endl;
                WORLD_NBFACES++;
            }
            if(debug)cout << "]" << endl;
        }
    }

	cout << endl
         << "Total Triangles: "     << WORLD_NBFACES    << endl 
         << "Total Vertices: "  << WORLD_NBVERTICES << endl
	     << "Saving Points..."  << endl;

    WorldDesc.numVertices   = WORLD_NBVERTICES;      // Number of vertices in mesh.
    WorldDesc.numTriangles  = WORLD_NBFACES;         // Number of triangles(3 indices per triangle)

    WorldDesc.pointStrideBytes    = sizeof(NxVec3);  // Number of bytes from one vertex to the next.
    WorldDesc.triangleStrideBytes = 3*sizeof(NxU32); // Number of bytes from one triangle to the next.

	WorldDesc.points    = &gWorldVertices;
    WorldDesc.triangles = &gWorldTriangles;
    WorldDesc.flags     = 0;
    if (!WorldDesc.isValid()) Error("Descriptor invalid");
   
    // Cook the mesh to memory, and read it back again.
    bool status = gCooking->NxCookTriangleMesh(WorldDesc,buffer);

/*  
    // I commented this so the program would crash and I could debug it.  Will uncomment once fixed.
    if (status == false) 
    {
        if (!buffer.data) 
            Error("Memory Buffer invalid");
        Error("Unable to cook mesh!");
    }
    else Console->Print("Cooking successfull!");
*/
    NxTriangleMesh *WorldMesh = gPhysicsSDK->createTriangleMesh(NXU::MemoryReadBuffer(buffer.data));
        
    // Add the shape desc to an actor desc, then add the actor (from desc) to the scene
    actorDesc.shapes.pushBack(&WorldShapeDesc);
    gActor = gScene->createActor(actorDesc);

    // Create collection for the scene and save it to the outputfile
    NXU::NxuPhysicsCollection *collection = NXU::createCollection ();
    NXU::addEntireScene(*collection, *gScene) ;
    if ( NXU::saveCollection(collection, outputfilename, NXU::FT_BINARY, true, true) )
        Console->Print("Done cooking mesh.");
    else
        Console->Print("Error cooking mesh.");

    // Release all out resources
    
    NXU::releasePersistentMemory();
    gCooking->NxCloseCooking();
    gPhysicsSDK->release();
    return 1;
}
utils.hpp

Code: Select all

/////////////////////////////////////////////////////////////
// A few utility functions stolen from LoadWorld.cpp 
// Thanks Carsten ;)

#ifndef _UTILS_HPP_
#define _UTILS_HPP_

#include <iostream>
#include <stdarg.h>
#include "ConsoleCommands/ConsoleStdout.hpp"
#include "FileSys/FileManImpl.hpp"
#include "MapFile.hpp"
#include "../Common/World.hpp"
#include "NxVec3.h"

using namespace cf;


///////////////////////////////////////////////////////////////
//Forward Declarations
static ConsoleStdoutT ConsoleStdout(true);      // Enable auto-flushing the stdout stream for CaBSP.
ConsoleI* Console=&ConsoleStdout;

static FileSys::FileManImplT FileManImpl;
FileSys::FileManI* cf::FileSys::FileMan=&FileManImpl;

const double MapT::RoundEpsilon = 2.0;
const double MapT::MinVertexDist=10.0;  // 1 cm

static void Error(const char* ErrorText, ...)
{
    va_list ArgList;
    char    ErrorString[256];

    if (ErrorText!=NULL)
    {
        va_start(ArgList, ErrorText);
            vsnprintf(ErrorString, 256, ErrorText, ArgList);
        va_end(ArgList);

        Console->Print(cf::va("\nFATAL ERROR: %s\n", ErrorString));
    }

    Console->Print("Program aborted.\n\n");
    exit(1);
}

void ComputeBrushPolys(const MapFileBrushT&         MFBrush,    // Brush to be calculated
                       ArrayT< Polygon3T<double> >& BrushPolys, // Polygon array returned
                       const unsigned long          EntityNr,   // ??
                       const unsigned long          BrushNr)    // ??
{
    // Construct the polygons of the Brushes.
    for (unsigned long MFPlaneNr=0; MFPlaneNr<MFBrush.MFPlanes.Size(); MFPlaneNr++)
    {
        BrushPolys.PushBackEmpty();
        BrushPolys[BrushPolys.Size()-1].Plane=MFBrush.MFPlanes[MFPlaneNr].Plane;
    }


    Polygon3T<double>::Complete(BrushPolys, 2);


    // Check the validity of the constructed polygons.
    // An explicit validation is useful and necessary to ensure that we clean input data begin!
    for (unsigned long MFPlaneNr=0; MFPlaneNr<BrushPolys.Size(); MFPlaneNr++)
        if (!BrushPolys[MFPlaneNr].IsValid(2, 10))
        {
            Console->Print("\n");
            for (unsigned long PlaneNr=0; PlaneNr<MFBrush.MFPlanes.Size(); PlaneNr++)
                std::cout << convertToString(MFBrush.MFPlanes[PlaneNr].Plane) << " "
                          << MFBrush.MFPlanes[PlaneNr].Material->Name << "\n";

            Console->Print("\n");
            for (unsigned long PlaneNr=0; PlaneNr<BrushPolys.Size(); PlaneNr++)
            {
                std::cout << "Plane " << convertToString(BrushPolys[PlaneNr].Plane) << ", Vertices ";

                for (unsigned long VertexNr=0; VertexNr<BrushPolys[PlaneNr].Vertices.Size(); VertexNr++)
                    std::cout << convertToString(BrushPolys[PlaneNr].Vertices[VertexNr]) << " ";

                std::cout << "\n";
            }

            Error("Entity #%u, brush #%u: polygon #%u is invalid.", EntityNr, BrushNr, MFPlaneNr);
        }
}

int FindIndex(ArrayT<NxVec3> inpArray, Vector3T<double> toFind)
{
    NxVec3 tempVec;
    tempVec.x = toFind.x;
    tempVec.y = toFind.y;
    tempVec.z = toFind.z;
    for (unsigned int ElementNr = 0; ElementNr < inpArray.Size(); ElementNr++)
        if (inpArray[ElementNr].equals(tempVec, 2))
            return ElementNr;
    return -1;
}



#endif /* _UTILS_HPP_ */
NOTE: The only way I was able to get this to actually build without a ton of linker errors was to #include and link everything in sight. I've also put a ton of files into my project. So, I'll also include the VC++ 9 (2008) project files. You'll also need PhysX 2.8.1 installed (I installed it into \Dev\Ca3DE-src-r626\ExtLibs\PhysX\ so that you would have a dir, \Dev\Ca3DE-src-r626\ExtLibs\PhysX\SDKs\Physics\include)
Heres the links: to get PhysX
http://developer.nvidia.com/object/physx_downloads.html - Downloads
http://developer.download.nvidia.com/Ph ... K_Core.msi - PhysX SDK 2.8.1
http://www.nvidia.com/object/physx_8.06.12_whql.html - PhysX System software (also needed by
end users)
CaCook.vcproj.txt
VC project file.
Project dir is Dev\Ca3DE-src-r626\Projects\Ca3D-Engine\CaCook
EXE should compile to and be run from Dev\Ca3DE-src-r626\Projects\Ca3D-Engine\
(10.27KiB)Downloaded 363 times

Thanks :)
Last edited by minosdis on 2008-09-12, 13:56, edited 1 time in total.
Post Reply

Who is online

Users browsing this forum: No registered users and 5 guests