Lib3d Programmers Documentation ------------------------------- The documentation currently takes the form of a large scale annotation of a working program. There are currently quite a few include files to add to the top of your program. You will need most of these at least in the places where you create or modify the overall structure of your 3d hierarchy. > #include "Visual.H" > #include "Model.H" > #include "ModelBuilder.H" > #include "World.H" > #include "Camera.H" > #include "Light.H" > #include "SmoothPipeline.H" You'll need this to use 'cout' and the like. > #include > main() > { In designing lib3d, I have tried to minimize the exposure of client applications to the underlying hardware and/or windowing system. The following line is the extent to which exposure remains: > Visual *visual = Visual::create(Device::create(300, 250, 8)); Two things have happened. Firstly we attempt to create a Device - an object providing framebuffer access - for the current environment. We have specified that that Device should be at least 300x250 and be at least 8 bits deep (256 colour or better). There are currently only two possible Devices, one typically for console X displays (XShmDevice), and one for X displays on other computers or X Terminals (XImageDevice). XShmDevice is a good deal faster and will be used if possible. The XDevice classes are the only places where lib3d touches X directly, and it is a minimal point of contact. This means that ports to other environments and operating systems should be very simple, both for lib3d and programs that use it. If an SVGA device were to become available, this program would not require any changes to take advantage of it. Indeed, if lib3d was linked as a dynamic library, the program shouldn't even need to be recompiled. The second thing which has occurred is that we have created an appropriate Visual - an object which performs drawing to the device and manages the z-buffer - for the Device that was created. There are currently visuals for 8, 16 and 32 bit displays. Visuals are independent of windowing system as they write directly to a memory mapped buffer provided by the Device object. It is also the task of the Device object to move the contents of the buffer to the screen. > ifstream in("teapot.nff"); > if (in.bad()) { > cerr << "Couldn't open teapot.nff" << endl; > exit(-1); > } The hierarchy of objects, lights and cameras maintained by every lib3d program is rooted at a World object. When you render the hierarchy, you will ask the World object to do that for you. The position and orientation of every object is defined (ultimately) to relative to the World object. > World world; So at this point we have a hierarchy that looks like this: World This is the starting point. We will now create a model (a visible 3d polyhedron object) from the teapot.nff file opened above. This currently acheived with the help of an object of class ModelBuilder. This is a utility class, which really just serves to keep the noise down in the Model class itself. All model creation occurs through this class, even if you want to create one polygon by polygon. You can think of this as analogous to the case of a commercial renderer with a proprietry file format and some ad-hoc conversion utilities bundled into the package. > ModelBuilder ob; > ob.startModel(); > ob.readNFF( in ); > Model *model = ob.endModel(); So now we have a teapot model and an empty hierarchy. To connect the two, we ask the World object to add the teapot to its list of descendents. > world.adopt(model); Now the hierarchy looks like this: World | +---- Model (teapot.nff) To display the contents of the hierarcy, we must define a point of view - a Camera. We put the camera into the hierarcy, too. This has powerful implications as we will discuss later. > Camera *camera = new Camera(world); Now the hierarchy looks like this: World | +---- Model (teapot.nff) | +---- Camera Define the camera to have a near clipping plane at z=3, a far clipping plane at z=100, a field of view of 15 degrees, and an aspect ratio of 1:1. > camera->setParameters(3,100,15,1); Tell the world that this is the camera to use when rendering. This allows us to switch between several cameras implanted in diferent places in the hierarcy at appropriate moments. For example one camera could be located in the driver's position in a car, and another in a trailing helicopter for cut-away shots. > world.setActiveCamera( *camera ); Next, a light to clarify the shape of objects. > Light *light = new Light(world); Predictably, the hierarchy now looks like this: World | +---- Model (teapot.nff) | +---- Camera | +---- Light This is really the minimal setup. In any real application or game, the hierarcy is going to be considerably more complex than this. Particularly, cameras are likely to be descendents of player's ships/cars/characters so that the point of view will move automatically whenever the position or orientation of the player is changed. Here we set the direction and ambient and diffuse colours of the light. > Vector3 colour(1,1,1); > Vector3 direction(0,1,.5); > light->setParameters(colour, colour, direction); And turn the light on. > world.registerLight( *light ); If you want to use Gouraud shading for your teapot, you need to explicitly override the default pipeline, which performs flat shading. Models can and should share pipelines, so you should never create more than one of any type of pipeline. There are currently two pipelines, FlatPipeline and SmoothPipeline. If you try to render any models that don't have a pipeline explicitly stated, a single additional FlatPipeline will be created. > Pipeline *pipeline = new SmoothPipeline; > model->usePipeline(*pipeline); Now we are all set up with our hierarchy. All that is left to do is move the bits around and take photos of them. > Matrix34 tmp; > Matrix34 scale; > Matrix34 transform; This is the incremental rotation to be applied each frame. > scale.setRotation(4, 0,1,0); This is necessary to get the teapot facing upwards... > transform.setRotation(-90, 1,0,0); Currently the camera and teapot are coincident at the origin in world space. Move the camera back a bit so we can see the teapot. The transformation matrix in every case specifies the mapping between the object's 3d coordinate system and that of its parent. Tranformations are inherited down the hierarchy, so if you move, scale, shear or rotate a parent object, all of the children are likewise affected. > tmp.setTranslation(0,-1,-10); > camera->setTransform(tmp); Loop 1000 times: > for ( int i = 0 ; i < 1000 ; i++ ) { Add a small rotation to the teapot's translation matrix. > transform.premul(scale); > model->setTransform(transform); Render the hierarchy onto the back buffer. > world.renderHierarchy( *visual ); Move the image to the screen and clear the back buffer. > visual->swapBuffers(); > } All done. > delete visual; > } From here on, you are entering undocumented territory.