COLLADA DOM Viewer

原文http://3dgep.com/?p=959

 

COLLADA Viewer

The COLLADA Viewer project is provided with the COLLADA API and it can be used to view COLLADA documents that match the XML schema of the DOM you are loading. Let’s see how this works.

The main.cpp File

The only source file in the viewer project is the mainPC.cpp file. It provides a basic implementation of a COLLADA document viewer for the PC. It creates a COLLADA render class object, loads the COLLADA document, handles user input, and renders the COLLADA object to the screen.

Includes

The first thing we see in this source file (after the copyright notice) are the headers.

main.cpp
10
11
12
13
14
#include <windows.h>
#include <zmouse.h>
#include <stdio.h>
#include <gl\gl.h>
#include <gl\glu.h>

Since this is a windows program, we will include the standard windows header file. The “zmouse.h” header provides functionality to handle events generated by the mouse wheel. The “stdio.h” header is also included and the OpenGL headers and OpenGL utility header files.

main.cpp
17
18
19
20
21
#include "Crt/CrtMatrix.h"
#include "Crt/CrtNode.h"
#include "Crt/CrtScene.h"
#include "Crt/CrtRender.h"
#include "Crt/CrtWin.h"

The next group of includes loads the headers from the rt project included with the COLLADA DOM project. The more interesting header here is the “CrtRender.h” file which includes a reference render implementation in OpenGL for the COLLADA viewer.

main.cpp
22
23
#include "dae.h"
#include "dae/daeErrorHandler.h"

Next, the headers that define the “Digital Asset Exchange” C++ runtime classes that will be used to actually load the COLLADA documents into the program. An error handler definition is also included to register our custom error handling routines in to the COLLADA runtime. The default error handler will simply print errors to the stdout output stream defined in “stdio.h” header file.

main.cpp
26
27
28
#include <cfxLoader.h>
#include <cfxEffect.h>
#include <cfxMaterial.h>

The fx library project is used to load, and manipulate shader effects using the Cg framework. The “cfx” prefix implies these files are part of the fx library. These headers provide functions and classes that can be used to load and manipulate Cg shader programs.

main.cpp
31
#include <IL/il.h>

The DevIL is an open source cross-platform image loading library that will be used to load standard image formats. It’s functionality is provided by the single header “il.h”.

 

Globals Variables and Forward Declarations

A few global variables and function forward declarations are used by the main program.

main.cpp
39
40
41
42
HDC         hDC=NULL;   // GDI Device Context
HGLRC       hRC=NULL;   // Rendering Context
HWND        hWnd=NULL;  // Holds Our Window Handle
HINSTANCE   hInstance;  // Application Instance

These are the standard global variables used by the windows program.

  • HDC hDC: The handle to the window’s draw context, or device context. This will be used by OpenGL to render the graphics onto.
  • HGLRC hRC: A handle to the OpenGL render context.
  • HWND hWnd: A handle to the main window of our application.
  • HINSTANCE hInstance: A handle to the main application instance.

The creation of the main window and OpenGL context objects will be described later in the article.

main.cpp
45
46
47
48
49
50
bool    active=TRUE;
bool    fullscreen=TRUE;
bool    togglewireframe=TRUE;
bool    togglehiearchy=TRUE;
bool    togglelighting=TRUE;
int     togglecullingface=0;

A few global variables that used to control the main logic of the viewer.

main.cpp
52
53
54
55
56
57
LRESULT CALLBACK WndProc( HWND , UINT , WPARAM , LPARAM );
BOOL CreateGLWindow(CrtChar* title, CrtInt32 width, CrtInt32 height, CrtInt32 bits, bool fullscreenflag);
GLvoid DestroyGLWindow(GLvoid);
CrtInt32 DrawGLScene(GLvoid);
CrtInt32 InitGL(GLvoid);
GLvoid ResizeGLScreen(GLsizei width, GLsizei height);

A few functions are forward declared here.

  • WndProc: Declare the function signature for the main windows processor.
  • CreateGLWindow: Function declaration to create the main window handle.
  • DestroyGLWindow: This function will destroy the main window handle and release the OpenGL context.
  • DrawGLScene: The render method will render the contents of the COLLADA viewer.
  • InitGL: Initialize the OpenGL states that will be used throughout the application.
  • ResizeGLScreen: This method will be invoked when the screen size changes.
main.cpp
89
90
91
92
93
94
95
CrtFloat    MouseRotateSpeed = 0.75f;
CrtFloat    MouseWheelSpeed = 0.02f;
CrtFloat    MouseTranslateSpeed = 0.1f;
CrtFloat    KeyboardRotateSpeed = 10.0f;
#define     RUN_SPEED   500.0f
#define     WALK_SPEED  100.0f
CrtFloat    KeyboardTranslateSpeed = WALK_SPEED;

The variables determine the speed of the movement of the input devices (mouse, keyboard).

main.cpp
99
100
101
102
103
104
105
106
void AdjustUISpeed(CrtFloat multiplier)
{
     MouseRotateSpeed        *= multiplier;
     MouseWheelSpeed         *= multiplier;
     MouseTranslateSpeed     *= multiplier;
     KeyboardRotateSpeed     *= multiplier;
     KeyboardTranslateSpeed  *= multiplier;
}

The AdjustUISpeed is provided to scale the speed of the user input for the parameters that were previously declared.

main.cpp
96
CrtRender   _CrtRender;

The CrtRender class is defined in the rt library and is used to load and display the COLLADA scene files.

main.cpp
112
113
bool    keys[256];   // Used to track which keys are held down, the index is the windows
bool    sAnimationEnable = true ;

The keys array is used to store the state of the keyboard keys and will be used in our input handler to manipulate the user input variables. We will also store a boolean to toggle the animation of the animated scene objects.

 

The ProcessInput Method

The main method that will be used to control user input and to manipulate our view parameters is the ProcessInput method.

main.cpp
117
118
void ProcessInput( bool keys[] )
{

The ProcessInput processes the keys array which stores the pressed state of the keyboard keys.

main.cpp
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
if (keys[ 'E' ] && amplitudeGlobalParameter)
{
     float value;
     cgGetParameterValuefc(amplitudeGlobalParameter, 1, &value);
     value += 0.1f;
     cgSetParameter1f(amplitudeGlobalParameter, value);
     keys[ 'E' ] = false ;
}
if (keys[ 'R' ] && amplitudeGlobalParameter)
{
     float value;
     cgGetParameterValuefc(amplitudeGlobalParameter,1, &value);
     value -= 0.1f;
     cgSetParameter1f(amplitudeGlobalParameter, value);
     keys[ 'R' ] = false ;
}

The ‘E’ and ‘R’ keys are used to manipulate the Cg parameter reference by amplitudeGlobalParameter. This parameter is bound to a variable in the Cg program that is used by the programmable rendering pipeline controlled by the Cg API.

main.cpp
136
137
138
139
140
if (keys[VK_TAB] )
{
     _CrtRender.SetNextCamera();
     keys[VK_TAB] = false ;
}

Pressing the TAB key, the renderer will move between the different cameras defined in the COLLADA file. The COLLADA schema provides a definition for a camera element and each COLLADA scene file must have at least 1 camera definition if the document defines a library_cameras element.

main.cpp
143
144
145
146
147
148
149
150
151
152
153
154
if ( keys[ 'M' ] )
{
     // Speed up UI by 25%
     AdjustUISpeed(1.25f);
     keys[ 'M' ] = false ;
}
if ( keys[ 'N' ] )
{
     // Slow down UI by 25%
     AdjustUISpeed(0.75f);  // Go 25% slower
     keys[ 'N' ] = false ;
}

The ‘M’ and ‘N’ keys are used to adjust the speed at which the viewer will react to user input. For this, it will use the AdjustUISpeed function previously defined.

main.cpp
155
156
157
158
159
160
161
162
163
164
165
if (keys[ 'Q' ])
{
     if (togglewireframe) {
         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
         togglewireframe = FALSE;
     } else {
         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
         togglewireframe = TRUE;
     }
     keys[ 'Q' ] = false ;
}

The ‘Q’ key is used to switch between wireframe (GL_LINE), and filled (GL_FILL) polygon rendering mode.

main.cpp
167
168
169
170
171
172
173
174
175
176
177
if (keys[ 'K' ])
{
     if (togglehiearchy) {
         _CrtRender.SetShowHiearchy(CrtTrue);
         togglehiearchy = FALSE;
     } else {
         _CrtRender.SetShowHiearchy(CrtFalse);
         togglehiearchy = TRUE;
     }
     keys[ 'K' ] = false ;
}

The ‘K’ key is used to toggle the display of the hierarchy of nodes in the COLLADA scene. A scene can consists of multiple nodes that are used to represent render-able objects in the scene. Nodes can be connected together in a hierarchical structure like a tree. This method will enable lines to be drawn between the connected nodes in this structure. This is useful for debugging animation issues in your animated scene objects.

main.cpp
178
179
180
181
182
183
184
185
186
187
188
if (keys[ 'L' ])
{
     if (togglelighting) {
         glDisable(GL_LIGHTING);
         togglelighting = FALSE;
     } else {
         glEnable(GL_LIGHTING);
         togglelighting = TRUE;
     }
     keys[ 'L' ] = false ;
}

The ‘L’ key is used to toggle lighting mode of the fixed-function pipeline in OpenGL.

main.cpp
190
191
192
193
194
195
196
197
198
199
200
201
if (keys[ 'P' ] )
{
     if (sAnimationEnable) {
         _CrtRender.SetAnimationPaused( CrtTrue );
         sAnimationEnable = false ;
     }
     else {
         _CrtRender.SetAnimationPaused( CrtFalse );
         sAnimationEnable = true ;
     }
     keys[ 'P' ] = false ;
}

The ‘P’ key is used to toggle the updating of the animations in the scene.

main.cpp
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
if (keys[VK_F1])
{
     _CrtRender.Destroy();
     DestroyGLWindow();
     fullscreen=!fullscreen;
     // Recreate Our OpenGL Window
     if (!CreateGLWindow( "Collada Viewer for PC" , _CrtRender.GetScreenWidth(), _CrtRender.GetScreenHeight(),32,fullscreen))
     {
         exit (1);
     }
     if ( !_CrtRender.Load( cleaned_file_name ))
     {
         exit (0);
     }
 
     keys[VK_F1] = false ;
}

The ‘F1′ key is used to toggle full-screen rendering. To switch between windowed and full-screen rendering mode, the entire application window needs to be destroyed and recreated. Since the OpenGL context will also be destroyed when you do this, the COLLADA scene will need to be reloaded so that the textures and vertex buffers are recreated in graphics memory in the newly created OpenGL context. This is a pretty brute-force implementation and the wise programmer will definitely want to handle the switching between window modes in a much more graceful way.

The next set of keys will handle the movement of the camera. The standard ‘W’, ‘A’, ‘S’, ‘D’ keys are used to move the camera forward, left, back, and right and in addition to those, the ‘space’ and ‘X’ keys are used to move the camera up and down respectively.

main.cpp
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
if (keys[ 'S' ])
{
     // UI code to move the camera closer
     _CrtRender.ActiveInstanceCamera->MoveTransform(_CrtRender.GetAnimDelta() * KeyboardTranslateSpeed *0.5f, 0.0f, 0.0f);
}
 
if (keys[ 'W' ])
{
     // UI code to move the camera farther away
     _CrtRender.ActiveInstanceCamera->MoveTransform(- _CrtRender.GetAnimDelta() * KeyboardTranslateSpeed * 0.5f, 0.0f, 0.0f);
}
 
if (keys[VK_SPACE])
{
     // UI code to move the camera farther up
     _CrtRender.ActiveInstanceCamera->MoveTransform(0.0f, 0.0f, _CrtRender.GetAnimDelta() * KeyboardTranslateSpeed);
}
 
if (keys[ 'X' ])
{
     // UI code to move the camera farther down
     _CrtRender.ActiveInstanceCamera->MoveTransform(0.0f, 0.0f, - _CrtRender.GetAnimDelta() * KeyboardTranslateSpeed);
}
 
if (keys[ 'D' ])
{
     // UI code to move the camera farther right
     _CrtRender.ActiveInstanceCamera->MoveTransform(0.0f, - _CrtRender.GetAnimDelta() * KeyboardTranslateSpeed, 0.0f);
}
 
if (keys[ 'A' ])
{
     // UI code to move the camera farther left
     _CrtRender.ActiveInstanceCamera->MoveTransform(0.0f, _CrtRender.GetAnimDelta() * KeyboardTranslateSpeed, 0.0f);
}

The ‘S’ and ‘W’ keys will translate the active camera forward and backward along the X-axis, the ‘space’ and ‘X’ keys will translate the camera up and down the Z-axis and the and the ‘D’ and ‘A’ keys will translate the camera left, and right in the Y-axis.

main.cpp
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
if (keys[ 'F' ])
{
     if (togglecullingface == 0)
     { // turn it front
         glEnable( GL_CULL_FACE );
         glCullFace(GL_FRONT);
         togglecullingface = 1;
     } else if (togglecullingface == 1)
     { // turn it both
         glDisable( GL_CULL_FACE );
         togglecullingface = 2;
     } else
     { // turn it back
         glEnable( GL_CULL_FACE );
         glCullFace(GL_BACK);
         togglecullingface = 0;
     }
     keys[ 'F' ] = false ;
}

The ‘F’ key is used to flip between front-face culling, back-face culling, and disable culling mode. This can be used to debug the winding order of your vertices in your mesh. If the winding order is not correct, the model will appear to be inside-out when culling is enabled.

 

The WndProc Method

The WndProc method is the window processor method that is associated with our main window handle. We will look at each message this method handles in turn.

main.cpp
285
286
LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM  wParam, LPARAM  lParam)
{

The signature for the standard windows processor takes four parameters.

  • HWND hWnd: The handle to the window that is associated to this windows processor method.
  • UINT uMsg: The message that is to be processed.
  • WPARAM wParam: Parameters that describe the event that generated the message.
  • LPARAM lParam: Additional message parameters that describe the event that generated the message.

Generally, we’ll use a switch-statement to handle the particular message.

main.cpp
288
289
switch (uMsg)
{

There are more messages being handled than I will show here. I will only focus on the important messages that control the view or application state.

main.cpp
315
316
317
318
319
case WM_CLOSE:
     {
         PostQuitMessage(0);
         return 0;
     }

The WM_CLOSE windows message is generated when the user presses the big red cross in the top-right side of the window frame. Doing this will cause a WM_QUIT message to be posted to the current window eventually causing our application to exit.

main.cpp
321
322
323
324
325
326
327
328
329
330
case WM_KEYDOWN:
     {
         // We only want to know which keys are down, so if this was an auto-repeat, ignore it
         if (!(HIWORD(lParam) & KF_REPEAT))
         {
             // Remember which keys are being held down
             keys[wParam] = TRUE;
         }
         return 0;
     }

A WM_KEYDOWN message is sent to the currently focused window when the user presses a key on the keyboard. In this case, we’ll just set a flag in our keys array and let the key states get handled in the ProcessInput method described above.

main.cpp
332
333
334
335
336
case WM_KEYUP:
     {
         keys[wParam] = FALSE;
         return 0;
     }

The WM_KEYUP message is sent when the user releases a key on the keyboard. In this case, we’ll just clear the state of the key from our keys array.

main.cpp
338
339
340
341
342
case WM_SIZE:
     {
         ResizeGLScreen(LOWORD(lParam),HIWORD(lParam));
         return 0;
     }

The WM_SIZE message is sent if the window frame is resized. Resizing our renderable drawing surface is handled in the ResizeGLScreen method.

main.cpp
343
344
345
346
347
348
349
350
351
352
case WM_MOUSEWHEEL:
     {
         if (_CrtRender.ActiveInstanceCamera)
         {
             float gcWheelDelta = ( short ) HIWORD(wParam);
             _CrtRender.ZoomIn((CrtFloat) (-gcWheelDelta * MouseWheelSpeed));
 
             return 0;
         }
     }

Moving the middle mouse wheel will cause WM_MOUSEWHEEL event to be generated. In this case we will zoom the currently active camera.

main.cpp
353
354
355
356
357
358
case WM_MBUTTONDOWN:
     {
         // Change camera
         _CrtRender.SetNextCamera();
         return 0;
     }

Pressing the middle mouse button will case a WM_MBUTTONDOWN message to be posted to the active windows message queue. In this case we will switch to the next camera defined in the COLLADA scene (if there is more than one).

main.cpp
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
case WM_MOUSEMOVE:
     {
         // UI code to move camera in response to mouse movement.
         static float lastx = 0, lasty = 0;
         static int   lastLeft = 0, lastRight = 0, lastMiddle = 0;
         // Retrieve mouse screen position and button state
         float x=( float ) ( short )LOWORD(lParam);
         float y=( float ) ( short )HIWORD(lParam);
 
         bool leftButtonDown=((wParam & MK_LBUTTON)  !=0);
         bool middleButtonDown=((wParam & MK_MBUTTON)  !=0);
         bool rightButtonDown=((wParam & MK_RBUTTON) !=0);
         // Handle rotations if left button was pressed
         if (leftButtonDown)
         {
             if (lastLeft && _CrtRender.ActiveInstanceCamera)
             {
                 _CrtRender.ActiveInstanceCamera->SetPanAndTilt((lastx - x) * MouseRotateSpeed, (lasty - y) * MouseRotateSpeed);
                 lastx = x;
                 lasty = y;
             }
             else
             {
                 // Remember where the mouse was when it first went down.
                 lastLeft = true ;
                 lastx = x;
                 lasty = y;
                 return 0;
             }
         }
         else
         {
             lastLeft = false ;
         }
         if (middleButtonDown)
         {
             if (lastMiddle && _CrtRender.ActiveInstanceCamera)
             {
                 _CrtRender.ActiveInstanceCamera->MoveOrbit((lastx - x) * MouseTranslateSpeed, - (lasty - y) * MouseTranslateSpeed);
                 lastx = x;
                 lasty = y;
             }
             else
             {
                 // Remember where the mouse was when it first went down.
                 lastMiddle = true ;
                 lastx = x;
                 lasty = y;
                 return 0;
             }
         }
         if (rightButtonDown)
         {
             // Was the mouse previously down?
             if (lastRight && _CrtRender.ActiveInstanceCamera)
             {
                 _CrtRender.ActiveInstanceCamera->MoveOrbit((lastx - x) * MouseTranslateSpeed, - (lasty - y) * MouseTranslateSpeed);
                 lastx = x;
                 lasty = y;
             }
             else
             {
                 // Remember that the button was down, and where it went down
                 lastRight = true ;
                 lastx = x;
                 lasty = y;
                 return 0;
             }
         }
         else
         {
             lastRight = false ;
         }
         return 0;
     }
} // closes the switch-statement (switch (uMsg))

Whenever the mouse is moved over the client area of the active window, the WM_MOUSEMOVE message will be generated. The lParam parameter will store the mouse’s X, and Y coordinates relative to the upper-left corner of the window in screen pixels and the wParam will store the state of the virtual keys (Alt, Shift, and Control) and the current state of the mouse buttons (Left Button, Middle Button, Right Button).

In this code we see that the left mouse button is used to rotate the camera around the origin of the orbit while both the middle mouse button and the right mouse button will move the center position of the orbit along the X, Y plane described by the movement of the mouse.

main.cpp
437
438
     return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

And finally, any messages that are not handled are passed to the default window processor.

 

The WinMain Method

The WinMain method is the main entry point for our application. This is where all the application will start and end it’s existence.

main.cpp
445
446
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
{

The WinMain method takes several arguments.

  • HINSTANCE hInstance: This is the instance handle to our application.
  • HINSTANCE hPrevInstance: The handle to the previously running instance of our application. This value can pretty much be ignored. It is almost always NULL.
  • LPSTR lpCmdLine: The command-line that was passed to the application, excluding the the program name. If you need to retrieve the entire command line, you can use the GetCommandLine function.
  • int nCmdShow: Controls how the applications main window is to be shown.

Before we can start loading and displaying things, we need to initialize a few subsystems.

main.cpp
451
ilInit();

The DevIL image library that will be used to load graphics resources first needs to be initialized before we can use it.

main.cpp
454
455
MSG     msg;
BOOL    done=FALSE;

A few function-scoped variables are declared to store the incoming window messages and a boolean flag that is used to determine when our application can be quit.

main.cpp
471
472
473
// Set the default screen size
_CrtRender.SetScreenWidth( 640 );
_CrtRender.SetScreenHeight( 480 );
main.cpp
475
476
477
478
479
// Create an OpenGL Window
if (!CreateGLWindow( "Collada Viewer for PC" , _CrtRender.GetScreenWidth(), _CrtRender.GetScreenHeight(),32,fullscreen))
{
     return 0;
}

We will then attempt to create a window and associate an OpenGL context with it. If the window creation method fails, the application will immediately quit.

I will not go into extensive detail about the contents of the CreateGLWindow method. If you would like to follow a tutorial that shows how to setup a window and associate an OpenGL context with it, I would recommend you read the first set of tutorials on the NeHe Productions website now hosted by GameDev.net ( http://nehe.gamedev.net/).
main.cpp
490
_CrtRender.Init();

Before we can start rendering, we will setup the COLLADA viewer. This will initialize the render to an initial state as well as initialize the Cg runtime.

main.cpp
496
497
_CrtRender.SetUsingVBOs( CrtTrue );
_CrtRender.SetUsingNormalMaps( CrtTrue );

We will also tell the render system that we want to use OpenGL vertex buffer objects and Normal Maps.

WARNING: This next upcoming code block is really ugly and can be cleaned up by simply using boost::filesystem to extract the different parts of the file path and generate a generic path that can be used by COLLADA DOM. The dae implementation has a mechanism for converting these file paths to URI’s and back anyways, so I’m not really sure why the original author wanted to do it this way. I keep it here for completeness and so you can hopefully reproduce this code in your own projects without creating frustrating questions like “how did they generate that filename being used on line 546!”.

main.cpp
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
// We might get a windows-style path on the command line, this can mess up the DOM which expects
// all paths to be URI's.  This block of code does some conversion to try and make the input
// compliant without breaking the ability to accept a properly formatted URI.  Right now this only
// displays the first filename
char
     file[512],
     *in = lpCmdLine,
     *out = file;
*out = NULL;
// If the first character is a ", skip it (filenames with spaces in them are quoted)
if (*in == '\"' )
{
     in++;
}
if (*(in+1) == ':' )
{
     // Second character is a :, assume we have a path with a drive letter and add a slash at the beginning
     *(out++) = '/' ;
}
int i;
for (i =0; i<512; i++)     {         // If we hit a null or a quote, stop copying.  This will get just the first filename.         if(*in == NULL || *in == '\"')             break;         // Copy while swapping backslashes for forward ones         if(*in == '\\')         {             *out = '/';         }         else         {             *out = *in;         }         in++;         out++;     }     // Should throw an error if i>= 512, but we don't have error dialongs in the code yet so just let it try to load and fail
if (i < 511)
     *out = NULL;

This code will basically convert the windows back-slashes to forward slashes and ensures if the code starts with a disk specifier (like C: for example), the resulting path is preceded with a ‘/’ character.

Now that we have a path in a format that the COLLADA loader likes, we can load the COLLADA file.

main.cpp
546
547
548
549
550
551
cleaned_file_name = file;
// Load the file name provided on the command line
if ( !_CrtRender.Load( cleaned_file_name ))
{
     exit (0);
}

We’ll just pass the name of the file we want to load to the COLLADA render class object. Later I will explain in more detail what is happening in that function, but for now lets just trust that the file is loaded into our renderer.

You may notice that there is a large part of the code missing in the next section. The COLLADA viewer that I am detailing here has some logic that will parse the UI meta data for the Cg effects that are defined in the COLLADA scene. These parameters are not being shown on screen in this viewer so they are redundant but it can be used as an example of parsing this data from the COLLADA scene. Since this is not important to create the COLLADA viewer, I will omit it from this article.

After we have created the main application window and loaded the COLLADA scene, we will start processing the window messages until the user requests to quit the application by pressing the escape key, or by pressing the big red cross in the top-right hand corner of the window frame.

main.cpp
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
while (!done)
{
     if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
     {
         if (msg.message==WM_QUIT)
         {
             done=TRUE;
         }
         else
         {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
         }
     }
     else
     {
         // Draw The Scene.  Watch For ESC Key And Quit Messages From DrawGLScene()
         if ((active && !DrawGLScene()) || keys[VK_ESCAPE])  // Active?  Was There A Quit Received?
         {
             done=TRUE;
         }
         else
         {
             SwapBuffers(hDC);
             ProcessInput( keys );
         }
     }
}

This is a pretty standard windows message pump so I won’t go into much detail here. If you would like a more detailed view of the window message pump, you can read my previous article on setting up a DirectX application. You can just skip down to the section titled “The Run Method”.

If the user presses the escape key, the done flag will get set to true which will cause the message loop to complete and the application will quit.

main.cpp
716
717
718
719
720
721
722
     _CrtRender.Destroy();
  
     // Shutdown
     ilShutDown();
     DestroyGLWindow();
     return ( int )(msg.wParam);
}

Before the application quits, we will first destroy the COLLADA scene, shutdown the DevIL image library and destroy the main window handle.

 

The ResizeGL Method

The ResizeGL method is responsible for setting up the correct parameters for the viewport and projection matrix so that the scene objects don’t look stretched or squished on screen.

main.cpp
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
GLvoid ResizeGLScreen(GLsizei width, GLsizei height)
{
     // Prevent A Divide By Zero By
     if (height==0)
     {
         height=1;
     }
  
     glViewport(0,0,width,height);                       
  
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();                                   
  
     // Calculate The Aspect Ratio Of The Window
     gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
  
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
  
     // Reset the renderer's screen size to the new size
     _CrtRender.SetScreenWidth( width);
     _CrtRender.SetScreenHeight( height);
}

The ResizeGL is pretty much the boiler-plate resize method for OpenGL. The only exception this method has is to the standard resize method is the last two lines that resize our COLLADA scene renderer.

 

The InitGL Method

The purpose of the InitGL method is to initialize the state of the OpenGL context.

main.cpp
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
CrtInt32 InitGL(GLvoid)
{
     glEnable(GL_TEXTURE_2D);
     glShadeModel(GL_SMOOTH);
     glClearColor(.9f, 0.9f, .9f, 1.f);
     glClearDepth(1.0f);
     glEnable(GL_DEPTH_TEST);
     glDepthFunc(GL_LEQUAL);
     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  
  
     glEnable(GL_LIGHT0);
     glEnable(GL_LIGHTING);
  
     glEnable( GL_CULL_FACE );
     glCullFace( GL_BACK ); 
  
     return TRUE;
}

The InitGL method just sets some initial parameters for the OpenGL context. 2D texturing is enabled, smooth blending between vertices is enabled, the background clear color is set to an off-white, depth testing is enabled, a single light is enabled in the scene with default attributes, and back-face culling is enabled.

 

The DrawGLScene Method

The DrawGLScene method is the main rendering function. This method will be called whenever there are no messages to be processed on the window’s message queue.

main.cpp
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
CrtInt32 DrawGLScene(GLvoid)
{
  
     // Clear The Screen And The Depth Buffer
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glLoadIdentity();                                   
  
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();                                   
  
     CrtMaterial mat; 
  
     mat.Ambient = CrtColor3f( 1,1,1 );
     mat.Diffuse = CrtColor3f( 1,1,1 ); 
  
     glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE,  (GLfloat *)&mat.Diffuse );
     glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, (GLfloat *)&mat.Ambient );
     glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat *)&mat.Specular );
     glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, (GLfloat )mat.Shininess ); 
  
     _CrtRender.Render();
     return TRUE;
}

Before we render any scene with OpenGL, we always want to clear the entire contents of the color buffer and the depth buffer. We do that with the glClear method.

The current matrix transformation is reset by using the glLoadIdentity method after the matrix state has been set to GL_MODELVIEW.

Then in this particular implementation, a default material state is specified with a ambient and diffuse parameters set to white.

We then forward the rest of the rendering logic to the reference COLLADA viewer renderer.

 

The DestroyGLWindow Method

When we are finished with our main window handle, we want to make sure our resources get cleaned up and the OpenGL context is released.

main.cpp
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
GLvoid DestroyGLWindow(GLvoid)
{
     if (fullscreen)
     {
         ChangeDisplaySettings(NULL,0);
         ShowCursor(TRUE);
     }
  
     if (hRC)
     {
         if (!wglMakeCurrent(NULL,NULL))
         {
             MessageBox(NULL, "Release Of DC And RC Failed." , "SHUTDOWN ERROR" ,MB_OK | MB_ICONINFORMATION);
         }
  
         if (!wglDeleteContext(hRC))
         {
             MessageBox(NULL, "Release Rendering Context Failed." , "SHUTDOWN ERROR" ,MB_OK | MB_ICONINFORMATION);
         }
         hRC=NULL;
     }
  
     if (hDC && !ReleaseDC(hWnd,hDC))
     {
         MessageBox(NULL, "Release Device Context Failed." , "SHUTDOWN ERROR" ,MB_OK | MB_ICONINFORMATION);
         hDC=NULL;
     }
  
     if (hWnd && !DestroyWindow(hWnd))
     {
         MessageBox(NULL, "Could Not Release hWnd." , "SHUTDOWN ERROR" ,MB_OK | MB_ICONINFORMATION);
         hWnd=NULL;
     }
  
     if (!UnregisterClass( "OpenGL" ,hInstance))
     {
         MessageBox(NULL, "Could Not Unregister Class." , "SHUTDOWN ERROR" ,MB_OK | MB_ICONINFORMATION);
         hInstance=NULL;
     }
}

The first few lines here will ensure that if we are in full-screen mode, we will change back to windowed mode so that our desktop is restored.

On lines 807-819 the OpenGL render context will be deleted. Then we’ll release the device context for the main window and on line 827, the main application window will be destroyed. And finally on line 833, the class that was registered to create our main window handle is unregistered. This is shown by the highlighted lines.

There is one method not being shown here, and that’s the CreateGLWindow method. This method is very long (over about 200 lines of code) and it is pretty much identical to the code shown in the NeHe productions tutorial on GameDev. If you would like to see this function described in detail, I would recommend you follow the article there [http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=01]

 

The CrtRender.cpp File

Now that we’ve seen how to setup the main application, let’s look at how the CrtRender class loads and stores the COLLADA scene.

 

The Load Method

The Load method is responsible for loading the COLLADA scene from the document for use at runtime. Let’s see how this is done.

CrtRender.cpp
99
100
101
102
103
104
105
106
107
CrtScene * CrtRender::Load( const CrtChar * fileName, const CrtChar * basePath )
{
     UnLoad();
  
     // Create a new scene and name it the same as the file being read.
  
     CrtScene * scene = CrtNew(CrtScene);
     scene->SetName( fileName );
     scene->SetDocURI( fileName );

The Load method accepts two parameters:

  • const CrtChar * fileName: The generic file path to the COLLADA document we want to load.
  • const CrtChar * basePath: The base file path to the file. If fileName is a relative file path, this would be the path that fileName is relative to. This value can actually be NULL even if fileName is a relative path.

The UnLoad method is called to make sure we don’t have any dangling memory to a scene that may already have been loaded.

A new CrtScene instance is created and it’s name is set to the name of the file that is being loaded.

The CrtRender class must also set a few initial variables and initial states.

CrtRender.cpp
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// set the base path if there is one
if ( basePath )
     SetInitialPath( basePath );         
 
// in case of multithreaded loading
Loading = CrtTrue; 
 
if ( UseVBOs && UseRender )
     CheckForVBOs();
 
//CrtPrint(" Loading file %s \n", fileName); 
 
// Setup the type of renderer and initialize Cg if necessary (we may need the context for loading)
// !!!GAC this code used to come after the load, but now the load needs a Cg context.
if ( UseRender )
{
     // try and initialize cg if we can as set the default shaders
     if ( UseCg )
         InitCg(); 
 
     if ( UseShadows )
         InitShadowMap();
}
else
{
     UseCg = CrtFalse;
     UseVBOs = CrtFalse;
     UseShadows = CrtFalse;
}

If a base path was provided, a few initial paths relative to the base path are initialized and the CheckForVBOs method will check to see if the OpenGL extensions for VBOs are available.

On line 127 if Cg is enabled, the Cg runtime will be initialized and on line 130, the shadow map functionality will be initialized if shadow maps are enabled.

The next thing this method does is to actually load the COLLADA document.

CrtRender.cpp
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Load the COLLADA document
if ( basePath )
{
     // If we've been supplied with a basePath to go with the file name, put the
     // filename and base path together to make the name of the file to load.
     CrtChar newPath[CRT_MAX_NAME_SIZE];
     CrtCpy( newPath, BasePath );
     CrtCat( newPath, fileName );
     if ( !scene->Load( newPath ))
     {
         CrtPrint( " Failed to read scene \n" );
         CrtDelete( scene );
         Loading = CrtFalse;
         return NULL;
     }
}
else
{
     // If there's no base path, assume fileName is a full path and load it.
     if ( !scene->Load( ( char *)fileName ) )
     {
         CrtPrint( " Failed to read scene \n" );
         CrtDelete( scene );
         return NULL;
     }
}
 
CrtPrint( " Done Loading %s \n" , fileName);
// in case of multithreaded loading
Loading = CrtFalse;

To load the COLLADA document, if the basePath variable is available, it is used to make an absolute path to the COLLADA document and the absolute path is used to load the doument, otherwise the fileName parameter is used as-is to load the scene. In either case, the actual loading of the document is passed to the CrtScene. The CrtScene class is the basis of every COLLADA document. The COLLADA scene element stores the instances of both the visual scene and the physics scene which is used to describe the renderable objects and the physics objects that are defined in the scene.

Each COLLADA document contains a single root element of type <COLLADA>. The <COLLADA> root element has a single <scene> element and multiple library elements.

  • <library_animations>: A container for <animation> elements.
  • <library_animation_clips>: A container for <animation_clip> elements.
  • <library_cameras>: A container for <camera> elements.
  • <library_controllers>: A container for <controller> elements.
  • <library_effects>: A container for <effect> elements.
  • <library_force_fields>: A container for <force_field> elements.
  • <library_geometries>: A container for <geometry> elements.
  • <library_images>: A container for <image> elements.
  • <library_lights>: A container for <light> elements.
  • <library_materials>: A container for <material> elements.
  • <library_nodes>: A container for <node> elements.
  • <library_physics_materials>: A container for <physics_material> elements.
  • <library_physics_models>: A container for <physics_model> elements.
  • <library_visual_scenes>: A container for <visual_scene> elements.

The <scene> element contains references to an <instance_visual_scene> element that refers to a <visual_scene> element in the <library_visual_scenes> element of the <COLLADA> root element.

Let’s now investigate how the <visual_scene> element is loaded.

 

The CrtScene.cpp File

The CrtScene class is the container class for all the COLLADA scene elements. The CrtScene class will load the run-time classes from the COLLADA DOM database so that these objects can be updated and rendered in the application.

 

The Load Method

The Load method is where the COLLADA DOM will be loaded and the runtime objects will be created for use by the application.

CrtScene.cpp
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
CrtBool CrtScene::Load( CrtChar * LFileName )
{
  
     //if ( LFileName == NULL )
     //  return CrtFalse;
     CrtChar * nameOnly = CrtGetAfterPath(LFileName);
  
     // Instantiate the reference implementation
     m_collada = new DAE;
  
     CrtPrint( "COLLADA_DOM Load Started %s\n" , LFileName);
     // load with full path
     CrtInt res = m_collada->load(LFileName);
     if (res != DAE_OK)
     {
         CrtPrint( "Error loading the COLLADA file %s make sure it is COLLADA 1.4 or greater\n" , LFileName );
         delete m_collada;
         m_collada = 0;
         return CrtFalse;
     }

The Load method takes a single parameter, the file path to the document to be loaded. The nameOnly variable on line 168 stores just the name of the file without the preceding path. The nameOnly variable will be used to identify the DOM later on in the load method.

On line 171, a new DAE object is created. This is the core interface object that will be used to parse the COLLADA document and be used by the application to build our runtime classes.

On line 175, the full COLLADA document is loaded into memory. I will not go into more detail in about the DAE::load method in this article. This is the class that your loader should be using to load COLLADA documents into memory. It is basically a glorified XML loader and parser that is used to store the XML data in a Document Object Model (DOM) that can be used by your program at runtime.

If the loading went okay, then the DAE::load method will return DAE_OK and we can then use the DAE object to create our runtime classes.

There are two primary COLLADA schema versions since it’s standardization: 1.4 and 1.5. Because the DOM is built against these exact schemas, it isn’t very flexible regarding the layout of the schema. If you try to load a 1.5 document with a DOM that was built against the 1.4 schema, the loading will fail. Both the document and the DOM must be the same version otherwise loading will fail.
CrtScene.cpp
192
193
194
195
196
197
198
199
200
201
202
domCOLLADA *dom = m_collada->getDom(nameOnly);
if ( !dom )
     dom = m_collada->getDom(LFileName);
if ( !dom )
{
     CrtPrint( "COLLADA File loaded to the dom, but query for the dom assets failed \n" );
     CrtPrint( "COLLADA Load Aborted! \n" );
     delete m_collada;
     m_collada = 0;
     return CrtFalse;
}

Every element in the COLLADA schema has a corresponding element in the DOM. The root element of every COLLADA document is the <COLLADA> element. The corresponding DOM class is called domCOLLADA. The COLLADA root object is accessible from the DAE and once we have a reference to a valid domCOLLADA object, we can build the runtime objects.

CrtScene.cpp
204
205
206
207
208
209
210
211
212
213
214
215
216
217
     CrtPrint( "Begin Conditioning\n" );
     //  ret = kmzcleanup(collada, true);
     //  if (ret) CrtPrint("kmzcleanup complete\n");
  
#ifndef _WIN32
     CrtInt ret = 0;
     ret = triangulate(m_collada);
#endif
  
     //  if (ret) CrtPrint("triangulate complete\n");
     //  ret = deindexer(collada);
     //  if (ret) CrtPrint("deindexer complete\n");
  
     CrtPrint( "Finish Conditioning\n" );

After the DOM is loaded the contents of the DOM can be “conditioned” to suite the needs of the platform we are targeting. Conditioning is the process of manipulating the incoming data into a format that is better suited for our particular needs. In this way, the digital artists can create assets that are suited for the highest platform that is being targeted (usually PC) and the document conditioning stage can prepare the textures, models, and binary effect files that are better suited for the target platform (PSP or DS?). In this case, on any non-windows platform, the polygon meshes will be converted to triangle fans using the triangulate method.

CrtScene.cpp
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Need to now get the asset tag which will determine what vector x y or z is up.  Typically y or z.
if ( dom->getAsset()->getUp_axis() )
{
     domAsset::domUp_axis * up = dom->getAsset()->getUp_axis();
     switch ( up->getValue() )
     {
     case UPAXISTYPE_X_UP:
         CrtPrint( "  X Axis is Up axis! default camera is adjusted\n" );
         _CrtRender.SetUpAxis(eCrtXUp);
         break ;
     case UPAXISTYPE_Y_UP:
         CrtPrint( "  Y Axis is Up axis!n" );
         _CrtRender.SetUpAxis(eCrtYUp);
         break ;
     case UPAXISTYPE_Z_UP:
         CrtPrint( "  Z Axis is Up axis! default camera is adjusted\n" );
         _CrtRender.SetUpAxis(eCrtZUp);
         break ;
     default :
 
         break ;
     }
}

Since different modeling packages will interpret different axes as being the up axis, the COLLADA document will also store the intended up axis so that the loading program can adjust it’s camera settings accordingly.

CrtScene.cpp
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
// Load all the image libraries
for ( CrtUInt i = 0; i < dom->getLibrary_images_array().getCount(); i++)
{
     ReadImageLibrary( dom->getLibrary_images_array()[i] );
}
 
// Load all the effect libraries
//Check for a binary file
CrtChar *cfxBinFilename = ReadCfxBinaryFilename( dom->getExtra_array() );
CrtBool success = CrtFalse;
if ( cfxBinFilename != NULL )
{
     cfxLoader::setBinaryLoadRemotePath( BasePath );
     success = (CrtBool) cfxLoader::loadMaterialsAndEffectsFromBinFile(cfxBinFilename, cfxMaterials, cfxEffects, cgContext);
     assert (success);
}
else
{
     success = (CrtBool) cfxLoader::loadMaterialsAndEffects(m_collada, cfxMaterials, cfxEffects, cgContext);
     assert (success);
}
 
for ( CrtUInt i = 0; i < dom->getLibrary_effects_array().getCount(); i++)
{
     ReadEffectLibrary( dom->getLibrary_effects_array()[i] );
}
 
// Load all the material libraries
for ( CrtUInt i = 0; i < dom->getLibrary_materials_array().getCount(); i++)
{
     ReadMaterialLibrary( dom->getLibrary_materials_array()[i] );
}
 
// Load all the animation libraries
for ( CrtUInt i = 0; i < dom->getLibrary_animations_array().getCount(); i++)
{
     ReadAnimationLibrary( dom->getLibrary_animations_array()[i] );
}

Like I mentioned previously, the <COLLADA> root element stores a single <scene> element and several library elements. The scene will load the different libraries from the DOM using the appropriate Read*Library methods.

CrtScene.cpp
311
312
313
314
315
316
317
318
319
// Find the scene we want
domCOLLADA::domSceneRef domScene = dom->getScene();
daeElement* defaultScene = NULL;
if (domScene)
     if (domScene->getInstance_visual_scene())
         if (domScene->getInstance_visual_scene())
             defaultScene = domScene->getInstance_visual_scene()->getUrl().getElement();
if (defaultScene)
     ReadScene( (domVisual_scene *)defaultScene );

If there is a valid <visual_scene> element inside the <scene> element, the scene will be read using the ReadScene method.

I will skip the rest of the CrtScene::Load and carry on to the CrtScene::ReadScene for the sake of simplicity. Since this article is about loading the geometry from the COLLADA document.

 

The ReadScene Method

The ReadScene method is where the renderable geometry and animation data will be read.

CrtSceneRead.cpp
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
CrtBool CrtScene::ReadScene( domVisual_sceneRef scene )
{
     // create the scene root
     SceneRoot = CrtNew(CrtNode);
     CrtAssert( "No memory\n" , SceneRoot!=NULL);
     // get the scene name
     if ( scene->getName() )
         SceneRoot->SetName( scene->getName() );
     if ( scene->getID() )
         SceneRoot->SetId( scene->getID() ); 
  
     CrtPrint( " CrtScene::Reading Collada Scene %s\n" , scene->getName());     
  
     // recurse through the scene, read and add nodes
     for ( CrtUInt i = 0; i < scene->getNode_array().getCount(); i++)
     {
         CrtNode * node = ReadNode( scene->getNode_array()[i], SceneRoot );
         if (node)
         {
             Nodes[node->GetId()] = node;
         }
     }
     Nodes[SceneRoot->GetId()] = SceneRoot;

If the COLLADA document has a visual scene, then it must also have at least one <node> element that will describe geometric elements along with a transformation that will orient that object in space. Nodes are read in the ReadNode method. If the node has been read in, it is then added to the CrtScene::Nodes container which is a std::map which stores it’s keys as type std::string and it’s values as type CrtNode*.

 

The ReadNode Method

The ReadNode method will read the transformation (or transformations if more than one) that is associated with the node and also read the geometry information that represents the visual model of the node.

CrtSceneRead.cpp
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
CrtNode * CrtScene::ReadNode( domNodeRef node, CrtNode * parentNode )
{
     CrtNode * findnode = GetNode(node->getId(), NULL);
     if (findnode) return findnode;
     CrtPrint( " CrtScene::Reading Scene Node %s \n" , node->getId() );
  
     CrtNode * crtNode = CrtNew( CrtNode );
     // Create a new node and initialize it with name and parent pointer
     CrtAssert( "No memory\n" , crtNode!=NULL);
     if ( node->getName() ) crtNode->SetName( node->getName() );
     if ( node->getId() ) crtNode->SetId( node->getId() );
     if ( node->getSid() ) crtNode->SetSid( node->getSid() ); 
  
//  crtNode->SetDocURI( node->getDocumentURI()->getURI() );
     crtNode->SetParent( parentNode ); 
  
     // future support method that will support any rot/trans/scale matrix combination
     ReadNodeTranforms( crtNode, node, parentNode);
     // Process Instance Geometries
     for (CrtUInt i = 0; i < node->getInstance_geometry_array().getCount(); i++)
     {
         CrtInstanceGeometry * instanceGeometry = ReadInstanceGeometry(node->getInstance_geometry_array()[i]);
         if ( instanceGeometry == NULL ) continue ;
         instanceGeometry->Parent = crtNode;
         crtNode->InstanceGeometries.push_back(instanceGeometry);
         GeometryInstances.push_back(instanceGeometry);
     }

If the node has already been read before, the GetNode method will simply return a pointer to that node, otherwise a new node will be created and it’s transformation and geometry will be read-in.

The ReadNodeTransforms method will read and assign the transforms that are associated with the node. The node can have any number of transforms associated with it and they will all be combined before the node is rendered. Types of transform elements that the node can have are:

  • <lookat>: Contains a position and orientation transformation suitable for aiming a camera.
  • <matrix>: Describes transformations that embody mathematical changes to points within a coordinate system or the coordinate system itself.
  • <rotate>: Specifies how to rotate an object around an axis.  This element contains a list of four floating-point values, similar to rotations in the OpenGL specification.  These values are organized into a column vector specifying the axis of rotation followed by an angle in degrees.
  • <scale>: Specify how to change an object’s size.
  • <skew>: Specify how to deform an object along one axis.
  • <translate>: Changes the position of an object in a local coordinate system.

I will not go into more detail about the ReadTransform method, but it will basically parse the transform elements associated with the node and add the transform to the node’s Transforms array.

On line 2159, the node’s instance geometries are read-in. If the CrtInstanceGeometry object is valid, it is added to the nodes InstanceGeometries array and the scene’s GeometryInstances array so that it isn’t loaded multiple times.

 

The ReadInstanceGeometry Method

The ReadInstanceGeometry method will resolve the reference to the actual geometry element in the DOM and read the geometry data into a buffer object that can be used to render this node.

CrtSceneRead.cpp
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
CrtInstanceGeometry *CrtScene::ReadInstanceGeometry( domInstance_geometryRef lib)
{
  
     // Find the <geometry> the <instance_geometry> is pointing at (there can be only one)
     // by searching the Geometries list in the scene.
//  domInstance_geometry *instanceGeometry = node->getInstance_geometry_array()[i];
     xsAnyURI & urltype  = lib->getUrl();
//  const char * url    = urltype.getID(); //TODO: We might not need this
     urltype.resolveElement();
     domElement * element = (domElement* ) urltype.getElement();
     if (element==NULL) // this instance geometry is not found skip to the next one
         return NULL;
  
     CrtGeometry * geo = ReadGeometry((domGeometry* ) element);
     if (geo==NULL)          // couldn't find from existing pool of geometries
         return NULL;
  
     CrtInstanceGeometry *newCrtInstanceGeometry = CrtNew(CrtInstanceGeometry);
     CrtAssert( "No memory\n" , newCrtInstanceGeometry!=NULL);
     newCrtInstanceGeometry->AbstractGeometry = geo;

The ReadInstanceGeometry method takes a reference to the instance geometry object in the DOM and returns a CrtInstanceGeometry pointer that contains the actual CrtGeometry object that is used to render the model’s mesh.

On line 2069, the geometry associated with the geometry instance is read-in and if the CrtGeometry object is valid, it is associated with a new CrtInstanceGeometry and is returned to the caller.

 

The ReadGeometry Method

The ReadGeometry method basically has one purpose. To parse the domGeometry object from the DOM and add the resulting geometry to the scene’s Geometries array.

CrtSceneRead.cpp
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
CrtGeometry *CrtScene::ReadGeometry( domGeometryRef lib)
{
     if (lib->getId()==NULL) return NULL;
     if ( !_CrtRender.GetLoadGeometry() )
         return NULL; 
  
     CrtGeometry * geometry = GetGeometry(lib->getID(), lib->getDocumentURI()->getURI());
     if (geometry)   // geometry is found
         return geometry;
  
     ParseGeometry(newGeo, lib);
  
     Geometries.push_back(newGeo);
     return newGeo;
};
I have edited the above source code from the original source found in “CrtSceneRead.cpp”. I have removed redundant code that was not useful to show how the data is loaded. As a result the line-numbers won’t match up with the original code.

The first thing the ReadGeometry method does is check to see if the geometry has already been added to the scene’s Geometries array using the GetGeometry method. If it was found, that geometry reference will be returned.

If this geometry object hasn’t been parsed yet, a new CrtGeometry object is created, parsed, and added to the scene’s Geometries array.

 

The ParseGeometry Method

The <geometry> element can contain exactly one of the following elements:

  • <convex_mesh>: Contains or refers to information sufficient to describe basic geometric meshes.
  • <mesh>: Describes basic geometric meshes using vertex and primitive information.
  • <spline>: Describes a multisegment spline with control vertex (CV) and segment information.

In this particular implementation, every domGeometry object is assumed to have a domMesh element.  The other two elements (<convex_mesh> and <spline>) are ignored.

CrtSceneRead.cpp
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
CrtVoid CrtScene::ParseGeometry(CrtGeometry * newGeo, domGeometry * dom_geometry)
{
     domMesh         *meshElement        = dom_geometry->getMesh();
     newGeo->SetName( dom_geometry->getId() );
     newGeo->SetDocURI( dom_geometry->getDocumentURI()->getURI() ); 
  
     //not sure if we should get primitives by groups or by whatever comes first, I think it shouldn't matter, let's confirm later.
     CrtUInt numPolylistGroups = (CrtUInt)meshElement->getPolylist_array().getCount();
     for (CrtUInt i=0; i< numPolylistGroups; i++)     {         CrtPolyGroup *newprimitives = BuildPolygons(meshElement->getPolylist_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numPolygonGroups = (CrtUInt)meshElement->getPolygons_array().getCount();
     for (CrtUInt i=0; i< numPolygonGroups; i++)     {         CrtPolyGroup *newprimitives = BuildPolygons(meshElement->getPolygons_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numTriangleGroups = (CrtUInt)meshElement->getTriangles_array().getCount();
     for (CrtUInt i=0; i< numTriangleGroups; i++)     {         CrtPolyGroup *newprimitives = BuildTriangles(meshElement->getTriangles_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numTriStripsGroups = (CrtUInt)meshElement->getTristrips_array().getCount();
     for (CrtUInt i=0; i< numTriStripsGroups ; i++)     {         CrtPolyGroup *newprimitives = BuildTriStrips(meshElement->getTristrips_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numTriFansGroups = (CrtUInt)meshElement->getTrifans_array().getCount();
     for (CrtUInt i=0; i< numTriFansGroups ; i++)     {         CrtPolyGroup *newprimitives = BuildTriFans(meshElement->getTrifans_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numLinesGroups = (CrtUInt)meshElement->getLines_array().getCount();
     for (CrtUInt i=0; i< numLinesGroups ; i++)     {         CrtPolyGroup *newprimitives = BuildLines(meshElement->getLines_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }
  
     CrtUInt numLineStripsGroups = (CrtUInt)meshElement->getLinestrips_array().getCount();
     for (CrtUInt i=0; i< numLineStripsGroups ; i++)     {         CrtPolyGroup *newprimitives = BuildLineStrips(meshElement->getLinestrips_array()[i], newGeo);
         newGeo->Groups.push_back(newprimitives);
     }

A <mesh> element can contain the following child elements:

  • <source>: Provides the bulk of the mesh’s vertex data.
  • <vertices>: Describes the mesh-vertex attributes.

The <mesh> element must contain one or more <source> elements and exactly one <vertices> element that describes the position (identity) of the vertices comprising the mesh.

The <mesh> element can also have zero or more of the following primitive elements:

  • <lines>: Line primitives.
  • <linestrips>: Line-strip primitives.
  • <polygons>: Contains polygon primitives which may contain holes.
  • <polylist>: Contains polygon primitives that cannot contain holes.
  • <triangles>: Contains triangle primitives.
  • <trifans>: Contains triangle-fan primitives.
  • <tristrips>: Contains triangle-strip primitives.

The ParseGeometry will search through these primitive lists and build the primitive geometry groups for the <mesh> element.

After the primitive data has been created based on the data in the <mesh> element’s primitive lists, the Vertex Buffer Objects (VBO’s) will be created that will be used at runtime to render the geometry.

CrtSceneRead.cpp
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
     if (_CrtRender.UsingVBOs())
     {
         for (CrtUInt i=0; iGroups.size() ; i++)
             newGeo->Groups[i]->SetVBOs();
  
         newGeo->VBOIDs[eGeoPoints] = _CrtRender.GenerateVBO();
         _CrtRender.CopyVBOData(GL_ARRAY_BUFFER, newGeo->VBOIDs[eGeoPoints],newGeo->Points, newGeo->vertexcount*3* sizeof (CrtFloat));
  
         newGeo->VBOIDs[eGeoNormals] = _CrtRender.GenerateVBO();
         _CrtRender.CopyVBOData(GL_ARRAY_BUFFER, newGeo->VBOIDs[eGeoNormals],newGeo->Normals, newGeo->vertexcount*3* sizeof (CrtFloat));
  
         newGeo->VBOIDs[eGeoTexCoord0] = _CrtRender.GenerateVBO();
         _CrtRender.CopyVBOData(GL_ARRAY_BUFFER, newGeo->VBOIDs[eGeoTexCoord0],newGeo->TexCoords[0], newGeo->vertexcount*2* sizeof (CrtFloat));
     }
  
}

I will not go into too much detail about the BuildPrimitives methods. They basically read the element’s vertex data from the DOM and create vertex lists and index lists as needed to build the VBO’s later.

 

Conclusion

That almost covers all the functionality that is required to load the geometry of a COLLADA scene.

So far, I’ve neglected the fact that some models in the scene may have an animation associated with them. In the next article, I will review how the animation data is loaded into the model view and updated on the CPU to animate the model.

 

References

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值