Last UPDATED on 25/09/2002 to work with current HEAD version of Torque.

STEP 1:
Download Frank Bignones zip file from perso.wanadoo.fr/hysteria/docs/guiCtrls.htm

STEP 2:
Add the additional files to your engine code:
- guiObjectView.cc & guiObjectView.h : place them in your game directory (i.e, /engine/game)
- guiTerView.cc & guiTerView.h : place them in your game directory (i.e, /engine/game) and add them to your Visual Studio Project (right-click on the folders containing the new files, "Add files to folder" - you have to change the file filter to *.* to see the .cc files!)

STEP 3:
There is one little extra utility function we need in the guiObjectView class:
add this to guiObjectView.cc:

void GuiObjectView::setEmpty() {
    if ( mModel ) {
       delete mModel;
       mModel = NULL;
    }
}

ConsoleMethod( GuiObjectView, setEmpty, void, 2, 2, "objectView.setEmpty( )" ) {
    argc;
	GuiObjectView* view = static_cast<GuiObjectView*>( object );
	view->setEmpty();
}
And, of course, the declaration in the header file, guiObjectView.h:
(just put it as the last entry in the public section):
	  void setEmpty();
Then, add the following function to core/resManager.cc:
ResourceObject *
ResManager::createResource (const char * fileName)
{

  StringTableEntry path, file;
  getPaths (fileName, path, file);
  ResourceObject *newRO = dictionary.find (path, file);
  if (newRO)
    return newRO;

  newRO = new ResourceObject;
  newRO->path = path;
  newRO->name = file;
  newRO->lockCount = 0;
  newRO->mInstance = NULL;
  newRO->flags = ResourceObject::Added;
  newRO->next = newRO->prev = NULL;
  newRO->nextResource = resourceList.nextResource;
  resourceList.nextResource = newRO;
  newRO->prevResource = &resourceList;
  if (newRO->nextResource)
    newRO->nextResource->prevResource = newRO;
  dictionary.insert (newRO, path, file);
  newRO->fileSize = newRO->fileOffset = newRO->compressedFileSize = 0;
  newRO->zipPath = NULL;
  newRO->zipName = NULL;
  newRO->crc = InvalidCRC;

  return newRO;
}
And the function declaration to the resManager.h file, of course (as the first entry in the "public" section e.g.):
   ResourceObject* createResource(const char *);

STEP 4:
DO NOT replace your terrData.cc & terrData.h files like he mentioned in the tutorial - they seem to be outdated and don't work in the latest HEAD!!
Instead, add this function to terrData.cc:


      

bool TerrainBlock::addToClientGraph()
{
   setPosition(Point3F(-squareSize * (BlockSize >> 1), -squareSize * (BlockSize >> 1), 0));
   MaterialPropertyMap* pMatMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
   StringTableEntry fn = mMaterialFileName[0];
   if(!dStrncmp(fn, "terrain.", 8))
      fn += 8;
   char nameBuff[512];
   dStrcpy(nameBuff, mTerrFileName);
   char *p = dStrrchr(nameBuff, '/');
   if (p) p++;
   else p = nameBuff;
   dStrcat(p,fn); 
   mMPMIndex[0] = pMatMap->getIndexFromName(nameBuff);
   
   mObjBox.min.set(-1e8, -1e8, -1e8);
   mObjBox.max.set( 1e8,  1e8,  1e8);
   resetWorldBox();
   setRenderTransform(mObjToWorld);

      if(mDetailTextureName && mDetailTextureName[0]) 
         mDetailTextureHandle = TextureHandle(mDetailTextureName, DetailTexture);
      
      lightMap = new GBitmap(LightmapSize, LightmapSize, false, GBitmap::RGB5551);

      if (!buildMaterialMap())
         return false;

      mTextureCallbackKey = TextureManager::registerEventCallback(terrainTextureEventCB, U32(this));

      mDynLightTexture = TextureHandle("special/lightFalloffMono", BitmapTexture, true);

      if (dglDoesSupportVertexBuffer())
         mVertexBuffer = glAllocateVertexBufferEXT(VertexBufferSize,GL_V12MTVFMT_EXT,true);
      else
         mVertexBuffer = -1;

   if(!unpackEmptySquares())
      return(false);

   return true;
}



I've added it just before void TerrainBlock::onRemove().

STEP 5:
Add this piece in terrData.h


      

public:
bool addToClientGraph();



e.g. right after
void unpackUpdate(NetConnection *, BitStream *stream);

STEP 6:
compile your engine (CLEAN build would be a good thing :-) !!

STEP 7:
Now for the scripting, I've put all the script for the GUI in one file named "terrainPreviewGui.gui", and added
exec("./ui/terrainPreviewGui.gui");
to fps/client/init.cs.
Then I call it from mainMenuGui.cs instead of the normal startMissionGui:

//command = "Canvas.setContent(startMissionGui);";
command = "Canvas.setContent(terrainPreviewGui);";

STEP 8:
In order to switch players/cars in a mod-specific way, I set a global variable in this new GUI file named
"$playerCSFile" and in the "game.cs" file of the actual mod I call:
//exec("./player.cs");
exec($playerCSFile);

or, for the car mod
//exec("./car.cs");
exec($playerCSFile);

respectively.

Furthermore, a variable $currentUserMod is set in "example/main.cs", which simply saves the recent mod.
I use it in terrainPreviewGui to set the paths to search for terrain and dts files:
(starting around line 62)


      

case "-mod":
$argUsed[$i]++;
if ($hasNextArg)
{
// save mod for global use - beffy 03/24/02
$currentUserMod = $nextArg;



So, now here is the complete "terrainPreviewGui.gui" code (should be almost self explanatory - you can also download it here):
UPDATE 25/09/02: Note that the function "localConnect($pref::Player::Name);" has been replaced with

   %conn = new GameConnection(ServerConnection);
   RootGroup.add(ServerConnection);
   %conn.setConnectArgs($pref::Player::Name);
   %conn.setJoinPassword($Client::Password);
   %conn.connectLocal();
to work with the current HEAD.
Also, a default mod is now set in "onWake()", because otherwise the GUI crashes in the HEAD version if no "mod" is set on the command line:
   if($currentUserMod $= "")
	   $currentUserMod = $userMods;
Finally, the "newMission.ter" file is excluded from the terrain file loading to prevent problems:
	  // 25/09/02: ignore "newMission.ter" also
      if (strStr(%file, "CVS/") == -1 && strStr(%file, "/newMission") == -1)


//--- OBJECT WRITE BEGIN ---
new GuiChunkedBitmapCtrl(TerrainPreviewGui) {
   profile = "GuiDefaultProfile";
   horizSizing = "right";
   vertSizing = "bottom";
   position = "0 0";
   extent = "780 580";
   minExtent = "8 8";
   visible = "1";
   helpTag = "0";

   new GuiControl() {
      profile = "GuiWindowProfile";
      horizSizing = "center";
      vertSizing = "center";
      position = "60 74";
      extent = "710 400";
      minExtent = "8 8";
      visible = "1";
      helpTag = "0";


      new GuiButtonCtrl(PrevModelButton) {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "top";
         position = "495 310";
         extent = "80 23";
         minExtent = "8 8";
         visible = "0";
         command = "TerrainPreviewGui::prevModel();";
         helpTag = "1";
         text = "<< Back";
      };
      new GuiButtonCtrl(NextModelButton) {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "top";
         position = "590 310";
         extent = "80 23";
         minExtent = "8 8";
         visible = "1";
         command = "TerrainPreviewGui::nextModel();";
         helpTag = "0";
         text = "Next >>";
      };
      new GuiTextCtrl(PlayerSelectTextCtrl) {
         profile = "GuiTextProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "485 25";
         extent = "88 20";
         minExtent = "8 8";
         visible = "1";
         text = "Select Model:";
         helpTag = "0";
         maxLength = "255";
      };
      // place to render the selected item
      new GuiObjectView(ToRKDisplayWindow) {
         profile = "GuiDefaultProfile";
         horizSizing = "relative";
         vertSizing = "relative";
         position = "485 64";
         extent = "230 220";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
      };


      new GuiTerView(MM_ShowTerrain) {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "210 93";
         extent = "240 240";
         minExtent = "8 8";
         visible = "1";
         modal = "0";
         helpTag = "0";
      };

      new GuiTextCtrl(MissionTextCtrl) {
         profile = "GuiTextProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "210 4";
         extent = "88 20";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
         maxLength = "255";
      };
      new GuiTextCtrl(TerrainTextCtrl) {
         profile = "GuiTextProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "210 25";
         extent = "88 20";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
         maxLength = "255";
      };

      new GuiButtonCtrl() {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "top";
         position = "10 184";
         extent = "127 23";
         minExtent = "8 8";
         visible = "1";
         command = "TerrainPreviewGui::StartMission();";
         helpTag = "0";
         text = "Go!";
      };

      new GuiButtonCtrl() {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "top";
         position = "10 212";
         extent = "127 23";
         minExtent = "8 8";
         visible = "1";
         //command = "TerrainPreviewGui::selectNextTerrain();";
         command = "TerrainPreviewGui::previewTerrain();";
         helpTag = "0";
         text = "Preview";
      };

      new GuiButtonCtrl() {
         profile = "GuiButtonProfile";
         horizSizing = "right";
         vertSizing = "top";
         position = "10 240";
         extent = "127 23";
         minExtent = "8 8";
         visible = "1";
         command = "Canvas.getContent().exit();";
         helpTag = "0";
         text = "<< Back";
      };
      new GuiCheckBoxCtrl(ML_isMultiplayer) {
         profile = "GuiCheckBoxProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "12 303";
         extent = "147 23";
         minExtent = "8 8";
         visible = "1";
         variable = "pref::HostMultiPlayer";
         helpTag = "0";
         text = "Multiplayer Mission";
         maxLength = "255";
      };
      new GuiTextEditCtrl() {
         profile = "GuiTextEditProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "12 30";
         extent = "164 16";
         minExtent = "8 8";
         visible = "1";
         variable = "pref::Player::Name";
         helpTag = "0";
         maxLength = "255";
         historySize = "0";
         password = "0";
         tabComplete = "0";
      };
      new GuiTextCtrl() {
         profile = "GuiTextProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "12 11";
         extent = "79 20";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
         text = "Player Name:";
         maxLength = "255";
      };

      new GuiScrollCtrl() {
         profile = "GuiScrollProfile";
         horizSizing = "right";
         vertSizing = "bottom";
         position = "12 55";
         extent = "164 100";
         minExtent = "8 8";
         visible = "1";
         helpTag = "0";
         willFirstRespond = "1";
         hScrollBar = "dynamic";
         vScrollBar = "alwaysOn";
         constantThumbHeight = "0";
         defaultLineHeight = "15";
         childMargin = "0 0";

         new GuiTextListCtrl(ToRK_missionList) {
            profile = "GuiTextArrayProfile";
            horizSizing = "right";
            vertSizing = "bottom";
            position = "0 0";
            extent = "150 40";
            minExtent = "8 8";
            visible = "1";
            helpTag = "0";
            enumerate = "0";
            resizeCell = "1";
            columns = "0";
            fitParentWidth = "1";
            clipColumnText = "0";
            noDuplicates = "false";
         };
      };
   };
};
//--- OBJECT WRITE END ---

//----------------------------------------
function TerrainPreviewGui::onWake()
{
   //reset vars
   $numDTS = 0;
   $currentDTS = 0;
   $playerCSFile = "";
   $currTerrainId = 0;
   $terrCounter = 0;
   // 25/09/02: set default mod
   if($currentUserMod $= "")
	   $currentUserMod = $userMods;
   // clear Gui elements
   ToRK_missionList.clear();
   ToRKDisplayWindow.setEmpty();
   MM_ShowTerrain.detachTerrain();
   // search *.ter files and init the mission list
   TerrainPreviewGui::loadTerrains();
   TerrainPreviewGui::loadPlayerShapes($currentUserMod);
   // display first model in array
   TerrainPreviewGui::displayDTSItem();
}

//----------------------------------------
function TerrainPreviewGui::cancel(%this)
{
}

//----------------------------------------
function TerrainPreviewGui::exit(%this)
{
   // cleanup
   MM_ShowTerrain.detachTerrain();
   ToRKDisplayWindow.setEmpty();
   Canvas.setContent(mainMenuGui);
}

//--------------------------------------------------------------------
//  Search *.ter files, fill arrays and display all missions in list
//--------------------------------------------------------------------
function TerrainPreviewGui::loadTerrains()
{
   //$currentUserMod is set in "example/main.cs"...
   %filespec = $currentUserMod @ "/data" @ "/*.ter";
   %missionPath = $currentUserMod @ "/data/missions";
   // a little hack to get the mod directory
   //%delpos = strpos($Client::MissionFileSpec, "/*");
   //%modDir = getSubStr($Client::MissionFileSpec, 0, %delpos);

   for(%file = findFirstFile(%filespec); %file !$= ""; %file = findNextFile(%filespec))
   {
      // ignore CVS folder...
	  // 25/09/02: ignore "newMission.ter" also
      if (strStr(%file, "CVS/") == -1 && strStr(%file, "/newMission") == -1)
      {
         //echo("count: " @ getFileCount("*.ter"));
         $terrFiles[$terrCounter] = filePath(%file) @ "/" @ fileName(%file);
         $missionFiles[$terrCounter] = %missionPath @ "/" @ fileBase(%file) @ ".mis";
         echo("File " @ $terrCounter @ ": " @ $terrFiles[$terrCounter]);
         ToRK_missionList.addRow($terrCounter, getMissionDisplayName($missionFiles[$terrCounter]) @ "\t" @ %file );
         $terrCounter++;
      }
   }
   ToRK_missionList.sort(0);
   ToRK_missionList.setSelectedRow(0);
   ToRK_missionList.scrollVisible(0);
}

//--------------------------------------------------------------------
//  Preview selected mission terrain
//--------------------------------------------------------------------
function TerrainPreviewGui::previewTerrain()
{
   %id = ToRK_missionList.getSelectedId();
   TerrainTextCtrl.setText("Terrain file: " @ $terrFiles[%id]);
   MissionTextCtrl.setText("Mission: " @ getMissionDisplayName($missionFiles[%id]));
   MM_ShowTerrain.detachTerrain();
   MM_ShowTerrain.attachTerrain($terrFiles[%id]);
}

//--------------------------------------------------------------------
//  Return the next terrain in cycle
//--------------------------------------------------------------------
function TerrainPreviewGui::cycleTerrains()
{
   if($currTerrainId++ == $terrCounter)
   {
      $currTerrainId = 0;
   }
   TerrainTextCtrl.setText("Terrain file: " @ $terrFiles[$currTerrainId]);
   MissionTextCtrl.setText("Mission: " @ getMissionDisplayName($missionFiles[$currTerrainId]));
   return $terrFiles[$currTerrainId];
}
//--------------------------------------------------------------------
//  Select the next terrain in cycle
//--------------------------------------------------------------------
function TerrainPreviewGui::selectNextTerrain()
{
   MM_ShowTerrain.detachTerrain();
   MM_ShowTerrain.attachTerrain(TerrainPreviewGui::cycleTerrains());
}
//--------------------------------------------------------------------
//  Start the selected mission
//--------------------------------------------------------------------
function TerrainPreviewGui::StartMission()
{
   MM_ShowTerrain.detachTerrain();
   %mission = $missionFiles[ToRK_missionList.getSelectedId()];
   // used in game.cs to load the appropriate player script
   $playerCSFile = $playerFiles[$currentDTS];

   if ($pref::HostMultiPlayer)
      %serverType = "MultiPlayer";
   else
      %serverType = "SinglePlayer";

   createServer(%serverType, %mission);
   // 25/09/02: changed for latest HEAD
   %conn = new GameConnection(ServerConnection);
   RootGroup.add(ServerConnection);
   %conn.setConnectArgs($pref::Player::Name);
   %conn.setJoinPassword($Client::Password);
   %conn.connectLocal();
}
//--------------------------------------------------------------------
//  Parse mission files for mission name
//--------------------------------------------------------------------
function getMissionDisplayName( %missionFile )
{
   %file = new FileObject();

   %MissionInfoObject = "";

   if ( %file.openForRead( %missionFile ) ) {
		%inInfoBlock = false;

		while ( !%file.isEOF() ) {
			%line = %file.readLine();
			%line = trim( %line );

			if( %line $= "new ScriptObject(MissionInfo) {" )
				%inInfoBlock = true;
			else if( %inInfoBlock && %line $= "};" ) {
				%inInfoBlock = false;
				%MissionInfoObject = %MissionInfoObject @ %line;
				break;
			}

			if( %inInfoBlock )
			   %MissionInfoObject = %MissionInfoObject @ %line @ " ";
		}

		%file.close();
	}
	%MissionInfoObject = "%MissionInfoObject = " @ %MissionInfoObject;
	eval( %MissionInfoObject );

   %file.delete();

   if( %MissionInfoObject.name !$= "" )
      return %MissionInfoObject.name;
   else
      return fileBase(%missionFile);
}


//--------------------------------------------------------------------
//  initialize arrays with shape- and scriptpaths
//--------------------------------------------------------------------
function TerrainPreviewGui::loadPlayerShapes(%mod)
{
   if(%mod $= "fps")
   {
      $playerShapes[0] = %mod @ "/data/shapes/player/player.dts";
      $playerFiles[0] = %mod @ "/server/scripts/player.cs";
      $numDTS++;
   }
   else if(%mod $= "racing")
   {
      $playerShapes[2] = %mod @ "/data/shapes/car/car.dts";
      $playerFiles[2] = %mod @ "/server/scripts/car.cs";
      $numDTS++;
   }
   else
   {
   		error("Unknown mod name!");
   }
}

//--------------------------------------------------------------------
//  display current *.dts
//--------------------------------------------------------------------
function TerrainPreviewGui::displayDTSItem()
{
   PrevModelButton.visible = "1";
   NextModelButton.visible = "1";
   ToRKDisplayWindow.setEmpty();
   %file = $playerShapes[$currentDTS];
   echo("Loading " @ %file);
   ToRKDisplayWindow.setModel(%file , "");
}


//--------------------------------------------------------------------
//  go back one dts
//--------------------------------------------------------------------
function TerrainPreviewGui::prevModel()
{
   if($currentDTS-- < 0)
   {
      $currentDTS = $numDTS-1;
   }
   echo($currentDTS);
   TerrainPreviewGui::displayDTSItem();
}

//--------------------------------------------------------------------
//  show next dts
//--------------------------------------------------------------------
function TerrainPreviewGui::nextModel()
{
   if($currentDTS++ >= $numDTS)
   {
      $currentDTS = 0;
   }
   echo($currentDTS);
   TerrainPreviewGui::displayDTSItem();
}



Here is a screenshot of it.

STEP 9: Preview, select and enjoy! :-)