原文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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
96
|
CrtRender _CrtRender;
|
The CrtRender class is defined in the rt library and is used to load and display the COLLADA scene files.
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.
117
118
|
void
ProcessInput(
bool
keys[] )
{
|
The ProcessInput processes the keys array which stores the pressed state of the keyboard keys.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
451
|
ilInit();
|
The DevIL image library that will be used to load graphics resources first needs to be initialized before we can use it.
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.
471
472
473
|
// Set the default screen size
_CrtRender.SetScreenWidth( 640 );
_CrtRender.SetScreenHeight( 480 );
|
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.
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.
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!”.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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;
};
|
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.
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.
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