How to Create a Basic Application
How to Create a Basic Application. In this tutorial, you will create a basic application using high level classes from Delta3D. Then you learn how to load a 3D model and setup a basic motion model.Last Update: 2006-03-06 for Delta3D 1.2.0.
Note: This tutorial is out of date and not updated anymore. If you landed here, you’re in the wrong place. Head over to the Wiki Tutorials to see the updated version.
First, add a header file (.h) to store your classes. Do this by right clicking on “Header Files” in the VS Solution Explorer and select:
Add -> New Item -> Header File (.h)
The Header File
Open your header file, in this case HelloWorld.h and the following lines to include and allow the use of classes from dtCore and dtABC that we will need. Our example class will derive from dtABC::Application, so we need dtABC/application.h. It will also contain members using the dtCore::RefPtr template, so dtCore/refptr.h must be included as well. Then forward declare the rest of the classes.
#include <dtABC/application.h>
#include <dtCore/refptr.h>
// Forward declarations to keep compile-time down
namespace dtCore
{
class Object;
class OrbitMotionModel;
}
*Note – Building in debug you will need to include ‘dtCored.lib’ ‘dtABCd.lib’ ‘osgd.lib’ ‘osgDBd.lib’ in .net under “Linker – > Input -> Additional Dependency libs” in your project settings. For Basic character animation tutorial you will also need ‘dtChard.lib’.
All of the Delta3D libraries are compiled with the preprocessor define SIGSLOT_PURE_ISO. Make sure to add this symbol to your apps project settings as well (C/C++ -> Preprocessor).
Next, let’s declare a sub-class called HelloWorld that derives from dtABC::Application. The Application class is the base level class for most applications. It contains the basic components required for displaying a window, creating a scene, and recieving input. By convention, sub-classes of Application take a reference to a string which is the filename for the application’s config file. Let’s also declare an override of the function virtual void Config(), we’ll use that to setup our scene. We’ll also need two data members: something to contain our 3D model and a motion motion to control the camera.
NOTE! The destructor of this class must be protected. Classes deriving from dtCore::Base must be reference counted. This means they are always allocated on the heap (the protected destructor enforces this) and stored in a special template called dtCore::RefPtr. This template will manage the lifetime of your object and takes care of deleting the appropriate memory when no one is using it anymore. See Don Burns’ great article on referencing counting for more info. We will also store our data members inside RefPtrs.
Here’s the full class declaration:
class HelloWorld : public dtABC::Application
{
public:
HelloWorld( const std::string& configFilename );
protected:
// Destructors for subclasses of dtCore::Base must have a protected
// destructor. Otherwise use of RefPtrs will cause some serious
// problems if the objects are allocated on the stack.
virtual ~HelloWorld();
public:
// Override this function to setup your scene.
virtual void Config();
private:
// dtCore::RefPtr is a template for a smart pointer that takes
// care of reference counting for objects allocated on the heap.
// It is good practice to store all objects that derive from
// dtCore::Base inside a RefPtr.
dtCore::RefPtr<dtCore::Object> mText;
dtCore::RefPtr<dtCore::OrbitMotionModel> mOrbitMotionModel;
};
How to Create a Basic Application
The Implementation File
Let’s move onto the implementation for our application. Here we’ll need to implement three functions: the constructor, destructor, and Config(). Since we are building the whole application into one implementation file, we’ll also need a main().
Now, create a file called HelloWorld.cpp file, and add the following code. These headers will bring in our class declaration, plus the interface for several Delta3D classes that we will use.
#include "HelloWorld.h"
#include <dtCore/globals.h>
#include <dtCore/object.h>
#include <dtCore/orbitmotionmodel.h>
Next, let’s implement the constructor.
HelloWorld::HelloWorld( const std::string& configFilename ) :
dtABC::Application( configFilename ),
mText(0),
mOrbitMotionModel(0)
{
// Generating a default config file if the one passed in is not there.
if( osgDB::findDataFile(configFilename).empty() )
{
GenerateDefaultConfigFile();
}
}
As mentioned earlier, sub-classes of dtABC::Application usually take a config file. This file is normally passed directly to the constructor of the dtABC::Applicaiton base class. It is a simple XML file that sets up basic window parameters (like size, position, and window title). Of course, we’ll initailize the rest of our data members pointers to 0. In the body of the constructor, we’ll account for the case of a missing config file. Basically, it will check for the file on disk, and if it is not present, it will generate a default one for you. Then you can modify it yourself for future runs of the program.
Next is the destructor. There is nothing special we want to do here, so it should be empty.
HelloWorld::~HelloWorld()
{
}
Importing a 3D Model and Setting a Camera
Loading a 3D Model
Now let’s setup out scene. By convention, the Config() function is used for this step:
void HelloWorld::Config()
{
// Load up models here
}
There are two thing we want to accomplish here. First, load up a 3D model displaying the text “HelloWorld”. Second, attach a motion model to our camera so we can move it based on mouse input. The particular mesh we’ll be using here is called HelloWorld.flt. Here is the code necessary to load it up.
// Allocate a dtCore::Object. This class will be your basic container
// for 3D meshes.
mText = new dtCore::Object("Text");
// Load the model file, in this case a OpenFligth model (.flt)
mText->LoadFile("HelloWorld.flt");
// Add the Object to the scene. Since mText is a RefPtr, we must
// pull the internal point out to pass it to the Scene.
GetScene()->AddDrawable( mText.get() );
The three steps in more detail are:
- Allocate memory to contain a dtCore::Object. The constructor takes a string which will serve as a name for this particular mesh.
- Load the mesh from the HelloWorld.flt file on disk.
- Add the newly loaded mesh into our scene. Object will not get rendered until they added to the scene.
Next, we need to setup our camera. Even with a mesh loaded and properly added to the scene, we will not be able to see it unless the camera is placed in front of it. Add the following code to Config to setup the camera.
// Adjust the Camera position by instantiating a transform object to
// store the camera position and attitude.
dtCore:: Transform camPos;
camPos.SetLookAt( 0.0f, -100.0f, 20.0f, // Position
0.0f, 0.0f, 0.0f, // Look At
0.0f, 0.0f, 1.0f); // Up Vector
GetCamera()->SetTransform(&camPos);
This code will point the camera to face at the the point ( 0.0, 0.0, 0.0 ) in world space from a position of ( 0.0, -100.0, 20.0 ), giving us a nice view of the mesh.
Finally in Config, let’s add a little interactivity to our example. Delta3D has a few stock motion models that can be used to move things in the scene based on user input. One of the most basic is OrbitMotionModel which gives the standard 3D “world-in-hand” type of control. It is simple to use:
// Setting a motion model for the camera
mOrbitMotionModel = new dtCore::OrbitMotionModel( GetKeyboard(), GetMouse() );
// Setting the camera as a target for the motion model. The object (the hello
// world 3D text) will be static at 0,0,0 and the camera will move using
// the right clicked mouse.
mOrbitMotionModel->SetTarget( GetCamera() );
Now your app will have the ability to move the camera in response to using the mouse. Left-clicking and dragging will rotate the world, middle-clicking and dragging will zoom in and out, and right-clicking and dragging will translate the camera. Here’s the full Config implementation:
void HelloWorld::Config()
{
GetWindow()->SetWindowTitle("HelloWorld");
// Allocate a dtCore::Object. This class will be your basic container
// for 3D meshes.
// you do not have to call delete on this.
mText = new dtCore::Object("Text");
// Load the model file, in this case a OpenFligth model (.flt)
mText->LoadFile("HelloWorld.flt");
// Add the Object to the scene. Since mText is a RefPtr, we must
// pull the internal point out to pass it to the Scene.
GetScene()->AddDrawable( mText.get() );
// Adjust the Camera position by instantiating a transform object to
// store the camera position and attitude.
dtCore:: Transform camPos;
camPos.SetLookAt( 0.0f, -100.0f, 20.0f, // Position
0.0f, 0.0f, 0.0f, // Look At
0.0f, 0.0f, 1.0f); // Up Vector
GetCamera()->SetTransform(&camPos);
// Setting a motion model for the camera
// you do not have to call delete on this.
mOrbitMotionModel = new dtCore::OrbitMotionModel( GetKeyboard(), GetMouse() );
// Setting the camera as a target for the motion model. The object (the hello
// world 3D text) will be static at 0,0,0 and the camera will move using
// the right clicked mouse.
};
Last but not least, let’s implement the main function. The application will begin and end its life here.
int main()
{
// Setup the data file search paths for the config file and the models files.
// This is best done in main prior to configuring app. That way the paths
// are ensured to be correct when loading data.
dtCore::SetDataFilePathList( ".;" + dtCore::GetDeltaDataPathList() );
// Instantiate the application and look for the config file
// You do not have to call delete on this.
dtCore::RefPtr<HelloWorld> app = new HelloWorld( "config.xml" );
// Configure the application
app->Config();
// Run the simulation loop
app->Run();
return 0;
}
Conceptually, the main doesn’t do much. The main() function’s main function (pardon the pun!) is to allocate a new instance of our example class HelloWorld, configure it, and start the simluation. The line before the new application is important:
dtCore::SetDataFilePathList( ".;" + dtCore::GetDeltaDataPathList() );
Anytime Delta3D is told to search for a file on disk (e.g. loading a config file or a mesh) it will use whatever paths you set here. It is good practice to keep all your runtime data in a single directory called data, so you can set it once and forget about it. In this application our data is in the same directory as our application, so we need to add the directory “.”. The entire string is semi-colon delimted on Windows and colon delimted on *nix platforms. The second path added (dtCore::GetDeltaDataPathList()) is the value of the environment variable DELTA_DATA. Delta3D has certain data file that it needed in order to run. Ensuring that this string is in the search path let’s the engine find them.