- discuss cleaning up some of the utility functions,
- I'm going to finally admit that I really should have made MEMORY a class a long time ago, and
- I'm going to discuss a variety of other changes I've made to the code as I improve my knowledge of C++.
I'm tired of making some of these changes piecemeal. This is especially true with respect to the code passing unsigned char variables when what it's really passing are bool values. The files SDK/common/utils.cpp and SDK/common/memory.cpp implement a number of such functions. I realized my reasons for postponing this work weren't sensible, and with Chapters 5 and 6 not introducing any new modules I decided to quit procrastinating. In the utils.cpp and utils.h files I:
- changed the third argument to get_file_extension() from unsigned char to const bool,
- changed the return types of the functions point_in_frustum(), and box_in_frustum() to bool.
- added a new enumerated type InFrustum,
- changed the return type of the functions sphere_intersect_frustum(), and box_intersect_frustum() to be the enumerated type InFrustum,
- replaced various while loops with for loops,
- added comments to note various portions of the code which aren't well documented so they can be found for later study, and
- added a comment to point out a future strategy of using the vec4 dot product computation to make the code more readable.
The changes to use bool parameter types and return values necessitated changes in the OBJ, SOUND, and TEXTURE modules. Any place in these three modules where get_file_extension() is called was changed to pass a bool value by changing the use of 0, or 1 to false, or true, respectively. But you knew that already from previous posts, right?
The functions point_in_frustum(), box_in_frustum(), sphere_intersect_frustum() and box_intersect_frustum() are never used in any of the other modules, or in the sample programs so there are no changes which need to be propagated as a result of their respective changes.
To see the use of the vector dot product to simplify the code search for the function vec4_dot_vec4() in the file SDK/common/utils.cpp. The old code used a somewhat complicated method of multiplying the frustum vector with the location vector. The complication was caused by the frustum vector being of type vec4, and the location vector being of type vec3. Without getting into the theory of the mathematics of 3D graphics transformations the 4D frustum vector describes a plane in homogeneous space. The simple way of converting a 3D location (vec3) into its equivalent location in homogeneous space is to add a 1 (one) as the vector's fourth component. You should be able to quickly prove to yourself that the dot product of the frustum vector and the location vector described in homogeneous space computes the same value as the original code. Finally/Obviously, the function vec4_dot_vec4() computes the dot product of two 4D vectors (vec4). If you look through the rest of the file you'll see many places this simplification can be made. Resist the temptation to manually fix the code. Later on I'll have more tools in place, and those tools (I hope) will make the task much easier.
Moving on to SDK/common/memory.cpp I was simply going to change the definition of mopen() to use const bool as the type of its second argument. But, as I was poking around, I changed my mind about leaving the MEMORY data type as a C-type struct, and so I changed MEMORY into a full C++ class. Most of the changes are unremarkable. The mopen() function was the basis for the constructor, the mclose() function was the basis for the destructor, and the functions mread(), and minsert() became the class methods read(), and insert(), respectively.
The one new wrinkle now that MEMORY is a full class is that I needed to worry about whether the compiler was going to implicitly create a copy constructor, and an assignment operator behind my back, and then, even worse, use them implicitly, and introduce bugs. Why would this, potentially, introduce bugs? Because the MEMORY data type has a pointer to dynamically allocated memory. If the code makes a copy of a MEMORY object (which is what will happen using a copy constructor, or an assignment operator), the programmer needs to start making decisions about how dynamic memory is handled for this object class. For example:
- When making a copy of a MEMORY object does the new object point to same dynamically allocated space as the object it was copied from?
- If two MEMORY objects point to the same dynamically allocated memory space how does the code coordinate the releasing of the dynamic memory space with the destruction of the MEMORY objects?
- Does the new MEMORY object have its own private copy?
Before moving on I should add that I probably should have added a private default constructor for the MEMORY class for the same reasons I added private versions of the copy constructor, and the assignment operator. And I should re-examine the modules which have already been converted to classes, and decide on a case-by-case basis whether they also need to have private default constructors, copy constructors, and/or assignment operators.
It's a fairly simple process to change the various calls to mopen() to use "new MEMORY(…)", and so on. This change, however, does have a ripple effect. The functions FONT_load(), MD5_load_mesh(), and MD5_load_action(), and the class methods OBJ::load_mtl(), one of the OBJ constructors, one of the PROGRAM constructors, PROGRAM::load_gfx(), PROGRAM::build(), one of the TEXTURE constructors, and TEXTURE::build() pass an argument to the MEMORY constructor which they, in turn, received as arguments from the code which invoked them. The corresponding arguments to each of these functions, and/or class methods needed to be changed to be of type const bool. Likewise, up through the call stack. Every place where one of these methods was called I changed the code to pass a bool value. In most cases, this involved replacing the integers 0 (zero), and 1 (one), with false, and true, respectively. But you've seen me do this several times before; sorry if it's getting tedious.
As always, I keep finding ways to clean my code up. One of these is that I changed the PROGRAM::init() method to test if the pointer to the name string is NULL. If it is, the method clears the name field of this to all zeroes. Otherwise, like before, the method tests to make sure that the name string will fit in the space allotted by the PROGRAM class, and, if possible, copies the string into the name field.
The last clean-up in this release is to change the copy constructors for OBJMESH, OBJTRIANGLELIST, and OBJMATERIAL to use initializer lists. Again, I should probably go back through the other classes I've implemented to see where initializer lists could be used for the copy constructors.
In my next post I will be talking about the code from Chapter 7 (duh!). Chapter 7, like Chapters 5 and 6, doesn't introduce any new modules by the author so I will discuss a small optimization which can be made.
No comments:
Post a Comment