Focus On 3D Models — 3Д БУМ https://3dbym.ru 3Д принтеры и всё что с ними связано Fri, 08 Nov 2013 15:46:23 +0000 ru-RU hourly 1 https://wordpress.org/?v=5.5.1 Scalar Multiplication and Division https://3dbym.ru/2013/11/scalar-multiplication-and-division/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/scalar-multiplication-and-division/ Just like matrices, you can multiply and divide vectors by scalar values. This is useful when you want to scale the speed of a vehicle up or down, or enlarge a picture. For instance, if you had an object moving… читать далее

The post Scalar Multiplication and Division first appeared on 3Д БУМ.

]]>
Just like matrices, you can multiply and divide vectors by scalar values. This is useful when you want to scale the speed of a vehicle up or down, or enlarge a picture. For instance, if you had an object moving at a certain speed in a certain direction and you wanted to make it go twice as fast in the same direction, you would multiply its movement vector by a scalar. The principle you use is much the same as the one you used for matrices. All you need to do is multiply each component of the vector by the scalar. Check out Figure 1.19 for a visual representation.

The post Scalar Multiplication and Division first appeared on 3Д БУМ.

]]>
Understanding the OBJ Format https://3dbym.ru/2013/11/understanding-the-obj-format/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/understanding-the-obj-format/ As mentioned, the OBJ format is in plain text. If you open it in Notepad or Wordpad, you’ll see lists of vertices and other geometry. Having an ASCII (plain text)-based format rather than a binary one has advantages

Table 4.1… читать далее

The post Understanding the OBJ Format first appeared on 3Д БУМ.

]]>
As mentioned, the OBJ format is in plain text. If you open it in Notepad or Wordpad, you’ll see lists of vertices and other geometry. Having an ASCII (plain text)-based format rather than a binary one has advantages

Table 4.1 Parameters for Building a Format String

Parameter

%s

%c

%d

%f

Use

A %s as a format string means that scanf will look for a string. It will read until it finds a null, space, or newline character.

%c reads a single character. It will read the first charac­ter it sees, regardless of what type it is, even a space or a newline.

Reading in integers uses %d. With integers scanf will read until it finds a space, letter, or symbol that is not part of a number. Integers read with %d can be positive or negative.

%f is used for reading real numbers, particularly floating point values. It will cause scanf to read a number the same way as %d, but it will include decimals.

and disadvantages. Because they are stored in plain text, it is easier for a person to edit the file by hand. This can be good or bad. It is a good thing if you need to tweak small parts of the model, but these innocent tweaks can end in disaster if you accidentally change the way the file is laid out. ASCII formats also tend to take up more disk space than binary — based formats because they require extra space for large values due to their length. One “part” of the geometry is on each line.

There are four types of lines you’re looking for. They are the lines that give you details about a vertex, a texture coordinate, a vertex normal, or a face. There may be other lines for things like curved surfaces and comments as well, but you will not be needing them here.

Each line of the model file starts with one or two letters that tell the program what that line is for. There are four prefixes, one for each of the important types, as described here:

■ v: A letter v followed by a space is a plain vanilla vertex. If this is the case, following a single space will be three floating-point values, each separated by a single space as well.

■ vt: The string vt signifies that the line contains texture coordi­nates. Each texture coordinate is two floats, again separated by one space.

■ vn: vn signifies a vertex normal. Other than the prefix, the line mirrors the vertex lines: three floats.

■ f: An f signifies a face. A face is a set of indexes into the arrays of vertices, texcoords, and normals. However, only the vertex indexes must be present; the other two are optional. Every vertex index should be positive. A negative value in the face structure probably means the file is corrupt because you cannot have a negative array index. It wouldn’t be right to say “retrieve the negative second object,” so you obviously could not retrieve the “negative second” vertex either. The best approach if you find a negative value within a face structure is to simply ignore the face altogether.

■ Anything else: Any other line of prefixes such as g (group), # (comments), p (point), l (line), surf (surface), and curv (curve) should be ignored for now. Although they are part of the for­mat, they are not covered here.

A typical line in the OBJ file that represents a face or triangle and contains only vertices would look something like this:

f 1 2 3

This code says that the faces use the first, second, and third vertex indexes to draw the triangles. The absence of any other sets of indexes indicates that texture coordinates and vertex normals are not needed.

If the faces are textured, but contain no vertex normals, the syntax would be similar to this:

f 1/4 2/5 3/6

This line says that the triangles use vertices 1, 2, and 3 and texture indexes 4, 5, and 6.

Yet another variation of this line could use all three types of vertex data, the vertex, texture coordinate, and normal indexes. A line that does that would look like:

f 1/4/7 2/5/8 3/6/9

The first number is for the vertex itself, the second is for its texture coordinate, and the third is for the vertex normal.

There is only one more variation: vertex and vertex normals, but no texture coordinates. It looks like this:

f 1//4 2//5 3//6

Two slashes separate the numbers instead of one; this indicates that the second number is a vertex normal, not a texture coordinate.

The fact that the faces don’t need to contain all of the information can be very useful. There is no reason an object that needs no lighting or texturing should contain that information. By simply leaving the unneeded information out, the file size is reduced considerably.

Let’s now move on to some code that shows you how to load the file into something you can work with.

The post Understanding the OBJ Format first appeared on 3Д БУМ.

]]>
Focus On 3D Models https://3dbym.ru/2013/11/focus-on-3d-models/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/focus-on-3d-models/ Evan Pipho

F

irst off, thanks to my parents for letting me do this; without their support and putting up with not seeing me most of the summer this would have never been possible.
A big thanks to Trent Polack… читать далее

The post Focus On 3D Models first appeared on 3Д БУМ.

]]>

Evan Pipho

F

irst off, thanks to my parents for letting me do this; without their support and putting up with not seeing me most of the summer this would have never been possible.

A big thanks to Trent Polack for helping me secure this book deal and for being an awesome friend and programmer. Without you I would have never made it to where I am today.

I can’t forget all of the great people over at gamedev. net, flipcode. com, and their associated chat rooms. You guys have been invaluable in helping me sort through problems, squash bugs, and are just great friends. There are too many to name, but a few who have proved to be invaluable have been Nicholas Cooper, Sean Kent, Denis Lukianov, Ron Penton, and Henrik Stuart.

To my friend and lifesaver Amy for forcefully dragging me away from my computer and out of my room every so often; it kept me from going insane.

Thanks to all of my other friends who put up with not seeing me much at all during the summer.

Last, but definitely not least, a huge thanks to the people over at Premier Press, especially Mitzi for sticking with me the whole time I was writing. Your support has been terrific, even through computer, Internet, and communications problems. All of the editors who worked on this have been great; all of you have taught me many valuable lessons as I have worked on my first book. I hope to work with all of you again.

This page intentionally left blank

Evan Pipho has always been interested in computers and electronics.

As a young child, he learned to operate his dad’s IBM PC, playing games, and experimenting with the many programs it contained.

While playing with his dad’s Windows machine, he started to look at languages such as QBASIC and C and to pursue his life-long interest in game development. He had decided a long time ago that he wanted to pursue game development, so he dug right in. After some classes at the nearby community college and several maddening months in front of the computer, he was hooked!

You can visit the author’s forums at http://www. codershq. com. Click the Forums link. If you prefer private email to public forums, you can contact him at evan@codershq. com.

This page intentionally left blank

The post Focus On 3D Models first appeared on 3Д БУМ.

]]>
Calculating the Conjugate of a Quaternion https://3dbym.ru/2013/11/calculating-the-conjugate-of-a-quaternion/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/calculating-the-conjugate-of-a-quaternion/ The conjugate of a quaternion is used for operations such as rotation of a quaternion by another or rotation of a vector by a quaternion. This is especially useful when you are transforming lighting normals or other operations whereby translation… читать далее

The post Calculating the Conjugate of a Quaternion first appeared on 3Д БУМ.

]]>
The conjugate of a quaternion is used for operations such as rotation of a quaternion by another or rotation of a vector by a quaternion. This is especially useful when you are transforming lighting normals or other operations whereby translation is not needed.

The conjugate is very easy to calculate, requiring only that you negate the vector component of the quaternion. Therefore, if you have the quaternion q = [ n, v], where n is a scalar and v is a vector, the conjugate of q (denoted by the symbol ~) would be ~q = [n, — v]. You will learn how to use the conjugate of a quaternion to rotate other quaternions and vectors in the next section.

The post Calculating the Conjugate of a Quaternion first appeared on 3Д БУМ.

]]>
Taking Position https://3dbym.ru/2013/11/taking-position/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/taking-position/ Using the information you have already read, you could try to imple­ment skeletal animation. However, you haven’t learned about how the parent joints actually affect the child joints. Simply using the keyframes would cause every joint to move independently of… читать далее

The post Taking Position first appeared on 3Д БУМ.

]]>
Using the information you have already read, you could try to imple­ment skeletal animation. However, you haven’t learned about how the parent joints actually affect the child joints. Simply using the keyframes would cause every joint to move independently of the rest, probably producing a strange, contorted mess.

This section talks about how to change this so that the joints work together. The first thing you do is build a transformation matrix for each point using the data from the current rotation and translation keyframes. A transformation matrix can be built by first generating the three rotation matrices and translation matrix as shown in Chapter 1. Multiplying the three together will produce a final transformation matrix. Alternatively you can use the SetRotation and SetTranslation functions in the matrix classes to avoid having to build and multiply the matrices yourself. This matrix is called the relative matrix.

Next, you need to calculate what is called the absolute matrix. The abso­lute matrix is the joint’s relative matrix multiplied by its parent’s abso­lute matrix. The absolute matrix tells you the joint’s absolute transfor­mation. This includes its relative transformations, as well as all of the transformations any joints before it in the hierarchy have made. This is what allows other joints to move as a result of moving a joint farther up in the line. Consider, for example, how your elbow moves when you move your shoulder. This begs the question: how do you calculate the

very first absolute matrix? Keep in mind that the root joint has no parent. Therefore, its absolute matrix is the same as its relative matrix.

Taking Position

If you traverse down the joints in the right order, calculating the absolute matrices as you go, every joint will have its parent’s transfor­mations, and its parent’s parent’s transformations and so on. Figure 5.6 shows what happens when you take into account all previous transformations before transforming a joint.

Taking Position

Rotating A joint with children affects the children, as well as the original joint

Figure 5.6 Traversing down the joints, taking into account all previous transformations. Notice that even though only one joint is told to move, the ones below it follow, much like moving your hip joint and having your knee and ankle follow as well.

What if you do not store the joint’s parent index, but rather the child indexes? This is no problem. The set of indexes you have access to right away depends on the model format. Some formats such as MS3D give you the parent index for each joint, whereas others give you the child indexes. Using child indexes requires a slightly different ap­proach than using parent indexes, but is really not any harder. You start at the root joint again. After calculating the root joint’s transfor­mation matrix, you push a new matrix onto the stack with a command such as glPushMatrix. This creates a new copy of the world matrix, which is the matrix that all geometry is transformed by before being dis­played. Now, multiply your new matrix by the root joint’s local matrix.

The resulting world matrix positions everything so when the next bone is drawn, the transformation of the parent joint is taken into consider­ation. For example, the hip of the character may be rotated a certain amount. Because the knee and ankle joints are children of the hip joint, they will also be rotated.

The drawing function is recursive so, as each joint is drawn, it calls the drawing functions of its children. Each child calls the rendering function of its children, and so on. Only when a terminal joint is reached (one with no children), is the matrix stack reset using a command such as glPopMatrix. For example, when drawing the leg of a character, new matrices can be pushed on the stack for the knee, angle, and foot joints. But when it is time to start on the arm, you want to pop back to the original position. Otherwise, whenever you moved the leg, the arm would move as well.

Figure 5.7 shows a diagram of a recursive rendering function.

Taking Position

Figure 5.7 Rendering joints that are stored with child indexes instead of parent indexes

The post Taking Position first appeared on 3Д БУМ.

]]>
Scalar Multiplication https://3dbym.ru/2013/11/scalar-multiplication/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/scalar-multiplication/ There are two types of matrix multiplication: matrix times matrix and matrix times scalar. Before you get into multiplying matrices by other matrices, you should perform scalar multiplication because it is much easier. A scalar is simply a number such… читать далее

The post Scalar Multiplication first appeared on 3Д БУМ.

]]>
There are two types of matrix multiplication: matrix times matrix and matrix times scalar. Before you get into multiplying matrices by other matrices, you should perform scalar multiplication because it is much easier. A scalar is simply a number such as 10, 3, or 13,142. Multiplying or dividing a matrix by a scalar is quite easy. As always, you start with the matrix and a scalar value. All you need to do to multiply the whole matrix by your scalar is take each element, multiply it by the scalar, and place it in the resulting matrix. The same principle applies for scalar division. This operation is illustrated in Figure 1.5. A good use of this technique again goes back to the example used in the addition and subtraction section.

Say your group of units takes a wrong turn and plows across a pit full of radioactive slime. This radioactive slime has the special property of removing half of the health from each unit in the group. Using scalar multiplication or division, you can perform this operation in one shot. Simply multiply the hitpoints matrix by 0.5 or divide it by 2.

~3

5

o"

3×4

5×4

0x4

12

20

o’

4

4

8

4×4

4×4

8×4

=

16

16

32

9

6

1

9×4

6×4

1×4

36

24

4

Figure 1.5 Multiplying a matrix by a scalar (a constant number).

Scalar multiplication will scale every element in the matrix by the same value.

The post Scalar Multiplication first appeared on 3Д БУМ.

]]>
Understanding the FILE * Functions https://3dbym.ru/2013/11/understanding-the-file-functions/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/understanding-the-file-functions/ One of the most important parts of model loading is just getting the file into memory. One of the ways to do this is to use a set of functions from the standard C library, collectively known as the FILE… читать далее

The post Understanding the FILE * Functions first appeared on 3Д БУМ.

]]>
One of the most important parts of model loading is just getting the file into memory. One of the ways to do this is to use a set of functions from the standard C library, collectively known as the FILE * functions. You have probably heard of them, perhaps even used them before.

This section is intended to give you a basic overview of the most impor­tant functions and datatypes used for I/O. The first thing you need to do before you can use FILE * I/O is include the appropriate header. You will need <stdio. h> for C programming or <cstdio> for C++.

The first and most important part of the FILE* I/O is not a function at all. Rather, it is a datatype called FILE. FILE is always used as a pointer (FILE *) and holds exactly what it sounds like, a pointer to the file called a file pointer. You need a separate file pointer for each file you want to have open; each file pointer can only point to a single file at a time.

So now you have a datatype that points to the file, but how do you use it? The first thing you need to do is open a file. The function you want here is fopen(). fopen takes just two parameters. The first is a constant string (const char *) that contains the file name of the file you desire to open, the second, also a constant string (const char *), is the mode you want to open the file in. The mode parameter can take many forms, depending on what you want to do with the file. There are modes for reading, and writing, and binary and text files.

How do you determine which mode to use? Simple, just check Tables 3.1 and 3.2 for a list of all the mode strings that can be used with fopen. The mode string will consist of one part from Table 3.1, which tells fopen which type of access you want, whether it be reading, writing,

~ГГ^Э-

appending, or a combination. The second part of the mode string comes from Table 3.2. The second part, also known as the translation mode, tells fopen whether to look at it as a binary or text file.

The fopen function will return the file pointer (FILE *) for your file.

This file pointer will be used whenever you work with the file, whether it is to read data from it or write new data back. Here are a few ex­amples of opening a file in different modes:

FILE * f = fopen("file. txt", "w+t");

This will create and open the file called file. txt for reading and writing in text mode. Anything currently contained within file. txt will be destroyed.

FILE * f = fopen("file. bin", "rb");

Table 3.1 Access Modes for fopen()

Mode String Use

"r" Opens the file for reading only. If the file does not exist, a

new one will not be created and the call to fopen will fail.

"w" Opens a blank file for writing. If the target file contains

information, it is wiped out. Be careful when using this mode; you could wipe out a file.

"a" Opens a file for writing, much the same as the "w"

mode. However, "a" will not destroy the data in the file. Any new data will be added to the end of the file, also known as appending the data.

Opens an existing file for reading and writing. As with "r", the file must exist or fopen will fail.

Works the same as "r+" with two major differences. If the file does not exist, fopen will create a blank one. If the file does exist, all data will be erased. Another function to be careful with.

Opens the file for both reading and appending, or writing to the end of the file.

r+"

"w+"

"a+"

Table 3.2 Translation Modes for fopen()

Mode String Use

"t"

"b"

“t” stands for text mode. In text mode, each byte of the file will be treated as its own character. You will gener­ally use this format if the file was or needs to be human-readable.

“b” is for binary. A file opened in binary mode will be treated as raw data and is generally not human-readable.

This will open the file file. bin for reading only, in binary mode. This is the most common mode used when loading and using 3D models within your games for several reasons. First, most model files are in binary form (but not all of them; the next chapter contains a file format that is text-based). Second, you rarely need to write to a model file when working with it in your game. Generally, you only need to load and use, rather than modify, the data it contains. Don’t forget to determine whether your file pointer is valid (not zero) before you try to use it.

Now that you have an open file, you need to be able to retrieve data from it. When working with model files, I like to read in the whole file as a chunk of unformatted data, and then sort it out later. To do this

you use fread().

fread is used for just what it sounds like: file reading. This function reads raw, unformatted data into an array. This is a quick and easy way to get data from the file into memory. The fopen function takes four param­eters, as follows:

■ The first parameter is a pointer to a buffer in which to store the new data.

■ The second and third parameters will tell fread how much data you want. These parameters contain the number of bytes in a single “item,” or chunk, of data, and the number of items you would like to read, respectively. Be sure your buffer is large enough to hold all of your data; nothing is worse than overwrit­ing an array and losing data.

■ The fourth and final parameter is a file pointer. The parameter should contain the file pointer that you created when you opened the file using fopen. You can check fread to make sure it read everything. The fread function returns the number of items actually read. If this return value and the third parameter of fread are equal, all the data was read successfully.

Here is a snippet of code that would read 100 bytes of unformatted data from one of the files you opened previously.

byte Buffer[100]; fread(Buffer, 1, 100, f);

This will store 100 bytes of data (one byte per item * 100 items) read from the file that f points to, in Buffer, an array of 100 bytes.

Just as fread is used for reading unformatted data from a file, fwrite is used for writing unformatted data. The parameters for fwrite are exactly the same as fread. The first parameter is the buffer containing the data to write to the file, the second and third parameters hold the sizes of the data to be written, and the fourth is a file pointer so fwrite knows which file to output the data to. Here is code to take the data you just read in (now stored in Buffer) and write it back to the file.

fwrite(Buffer, 1, 100, f);

Sometimes you need to read or write formatted data to a file. In these cases, fread and fwrite will not work. You will need to look toward functions such as fscanf and fprintf for formatted input and output.

These functions are used just like their text counterparts scanf/sscanf and printf/sprintf. Other functions are in place to read or write single variables to files such as fputc/fgetc (single characters) and fputs/fgets (strings).

All of this is very useful, but what if you need to write or read data to or from other parts of the file? You simply use the fseek() function. fseek will move your file pointer to a new place in the file. The fseek func­tion takes three variables. The first parameter takes the file pointer you want to manipulate. The second argument takes the number of bytes you want to move, relative to the origin. The origin point is given by the third parameter. The origin can be one of three constant values:

■ SEEK_BEGIN places the origin at the beginning of the file. In this case, fseek will move the specified number of bytes from the start of the file.

■ The final constant for the origin is SEEK_SET. SEEK_SET will tell fseek to move the specified number of bytes from the current location in the file. After fseek is called, you may return to reading and writing your file. This time, the file is coming from or going to a new location.

When you are done reading or writing from a file, you need to close it. Failing to close a file leaves some memory allocated, thus creating a memory leak in your program. To close a file you opened with fopen, you use fclose. fclose() takes a single parameter, the file pointer of the file you want to close. You can also use the _fcloseall() to close all open files; it takes no parameters at all and returns the number of files it successfully closed. Here, you see the simple code necessary to close the file f once you are done working with it.

fclose(f);

Well, there you have it. A short introduction to file I/O using FILE *. This should be plenty to get you started loading your own 3D models. If you want to know more, or would like to learn about other methods of file I/O, I would suggest picking up a book on C or C++. Any good C/C++ book will contain a section pertaining to file I/O, whether covering FILE * or another method.

With that out of the way, and a review of file I/O complete, you are ready to move onto the hard part—deciphering the MD2 format.

The post Understanding the FILE * Functions first appeared on 3Д БУМ.

]]>
STL Vector Primer https://3dbym.ru/2013/11/stl-vector-primer/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/stl-vector-primer/ —By 5ean Kent

A

s you have been reading through this book, you have no doubt heard plenty about the mathematical construct known as a vector. Well, here you are going to be introduced to another kind of vector, a… читать далее

The post STL Vector Primer first appeared on 3Д БУМ.

]]>
—By 5ean Kent

A

s you have been reading through this book, you have no doubt heard plenty about the mathematical construct known as a vector. Well, here you are going to be introduced to another kind of vector, a

data storage vector.

The Standard Template Library (STL) is a collection of container classes, iterator classes, and other utility classes and functions. A container is just that, it contains data of a user-specified type. The nice thing about STL containers is that they clean up after themselves, meaning that any memory they allocate, they also free. However, this doesn’t include any memory you allocated. Iterators are a type of class that points to the data contained within a container class. An iterator has the capability to move through the elements inside of the con­tainer, giving you access to the elements that the iterator points to.

The STL Vector

The STL vector class (from now on just called a vector) is a container object that stores its elements in a linear arrangement, allowing for fast insertions and deletions at the end of the vector, and slower insertions and deletions in the middle or at the beginning.

When using the function vector<>::end, the return value will be the element after the last element in the vector. You will find that this “past the end” theme is repeated throughout STL.

The Basics of Using Vectors

The first thing you need to do to use a vector is to include the header file that it is contained in:

#include <vector>

You will notice that there is no. h extension on it, which is not a typo. STL headers in all of the implementations that I have used do not

have an extension. The next thing you need to do is to declare a vector; in this case, you are declaring a vector of integers:

std::vector<int> vec; // Declares a vector that will contain integers.

So what is up with this std:: thing? Well, std is a namespace, which I won’t go into much, other than to say that for a C++ program, all you need to do to avoid using this std:: is to add this line after the include:

using namespace std;

However, in a C++ program where you have multiple files, including headers and such, it is generally a good idea to just stick to using the std:: extension to prevent including namespaces unintentionally.

All right, so far you have included and declared a vector that will contain integers. Now you will insert some data into it. Inserting data at the end of a vector is easy; you just use the push_back function. The following code will insert the numbers 0-9 at the end of the vector:

for(int i=0; i < 10; i++)

vec. push_back(i); // Inserts i onto the end of the vector

To remove data from the end of a vector, you simply use the pop_back function. The pop_back function takes no arguments, and returns void. So, if you wanted to remove that last nine from the vector, you would do the following:

vec. pop_back(); // Removes the 9 from the end of the vector

Now that you have some data stored in your vector, you need to learn how to access the data. To do so, you use a little thing called an iterator. An iterator at its most basic form is just a class that gives you access to the data stored in a container on an element-by-element basis. You can use iterators for inserting, deleting, searching, and sorting the data stored within said container. So let’s create an iterator in order to walk through your data and print it:

std::vector<int>::iterator l, endi;

endi = vec. end(); // Returns the element "Past the end" of the // last element

for(l = vec. begin(); // Returns the first element in the vector

l!= endi; // Checks to make sure that we are not at the end ++l) // Moves l to the next element, using ++l instead of l++ is // faster because you don’t create a copy

}

Not that difficult, eh? To insert data into a vector at any point, you must use the vector<>::insert() function. A warning, though: If you find that you are doing a lot of insertions and deletions anywhere except at the end of a vector, it is advisable that you look into one of the other container classes instead. Insertions at points other than the end of a vector are more expensive than at the end. To insert at, say, the beginning of a vector, you must first obtain an iterator pointing to the beginning, and then you merely call the insert function with the iterator, as well as the data you want to insert.

std::vector<int>::iterator l = vec. end(); while(l!= vec. begin())

{

—l;

vec. insert(vec. begin(), *l) // Insert at the beginning

// the element contained in l

}

There are two ways to remove data from a vector at points other than the end. One is to use the vector<>::erase function and the other is to use the remove functions. The difference between them is that erase will remove and resize the vector, whereas remove will preserve the relative placements of the elements, but will remove the specified elements.

// Using remove:

std::vector<int>::iterator newend;

// Searches the vector for all 7’s and removes them newend = std::remove ( vec. begin( ), vec. end( ), 7 );

cout << "Vector vec with value 7 removed is [ " ; for ( l = vec. begin( ) ; l!= vec. end( ) ; l++ ) cout << *l << " "; cout << "]." << endl;

// To change the sequence size, use erase

The

^ v1-1 ■"_^ ^ ‘ lJ

vec. erase (newend, vec. end( ) );

cout << "Vector vec resized with value 7 removed is [ " ; for ( l = vec. begin( ) ; Iter1 != l. end( ) ; l++ ) cout << *l << " "; cout << "]." << endl;

The next section of code removes all occurrences of 7 and resizes the vector.

// Using just erase: vec. erase(vec. begin(), vec. end(), 7);

The complete set of the remove functions includes remove_if, remove_copy, and remove_copy_if. The remove_copy and remove_copy_if functions create a new range of values, copying only those values that are not part of the value specified. The remove_copy_if and remove_if functions take a function argument called a binary predicate, which is a true-false function that will return true for those elements that match the predicate, and false for all others. These functions then remove all elements that meet the binary predicate.

bool gt6( int val )

{

return (val > 6); //returns true if greater than 6

}

std::vector<int> v2;

newend = std::remove_copy_if ( vec. begin( ), vec. end( ), v2.begin( ), gt6 ); // copies to a new vector containing all values // less than or equal 6

Sorting

At some point, you will likely want to sort your vector so that you may do cool things like use a binary search to find elements. Sorting a vector is a fairly simple process; you just use the sort function. The sort functions are included using the algorithm header.

#include <algorithm>

// The following code will sort your vector of ints in ascending order std::sort(vec. begin(), vec. end());

The sort algorithm can also take a function argument that will be used to determine whether an item should be moved. STL has several of these functions already; you just need to include the <functional> header.

#include <functional>

#include <algorithm>

//sorts using the greater function object ( descending order ) std::sort(vec. begin(), vec. end(), greater<int>());

The greater<int>() part is actually a function object, which is basically an overloaded operator() that’s contained within a structure or a class.

Searching

There are a few methods for searching a vector; however, this section covers only the use of the find functions as well as the binary search functions. The find functions work on both sorted and unsorted vectors. They work by comparing every element to the value being searched for. The binary search functions require a sorted vector, but they take significantly less time to find the element.

The find function has four versions. They are find, find_end, find_first_of, and find_if. The find function finds the first occurrence of an element. The find_if function finds the first occurrence of an element that meets a specified condition. The other two functions,

find_end and find_first_of, aren’t covered here.

To use find, simply call it with the range you want to search and the element you want to find. So, to find a number within a vector of integers, you would do the following:

#include <algorithm>

// Starts at the beginning of the vector, and proceeds to the end

// Till it finds a 7, or returns the element past the end if it doesn’t

std::vector<int>::iterator i;

i = find(vec. begin(), vec. end(), 7);

if(i!= vec. end()) // We found it

To use the find_if function, you simply do the following: #include <algorithm>

bool IsGreaterThan5( int val ) {

return (val > 5);

}

std::vector<int>::iterator i;

//Will find the first element that is greater than five i = find_if(vec. begin(), vec. end(), IsGreaterThan5); if(i!= vec. end()) // if we found it

. // Do stuff

The binary search functions require a sorted vector, but can take significantly less time to find the element you are searching for. The binary search functions are binary_search, lower_bound, upper_bound, and equal_range. The binary_search function returns true if the element searched for exists. The lower_bound function finds the first occurrence of an element within a vector, or the position it would be at if it ex­isted. The upper_bound function finds the element past the last occur­rence of the element searched for within a vector, or where it would be if the element searched for existed. The equal_range function is just a combination of the lower_bound and upper_bound functions.

To use the binary search functions, you simply pass it the range of sorted elements you want to search and the element you want to find.

#include <algorithm>

std::vector<int> vec; for(int j = 0; j < 10; j++)

{

vec. push_back(j); // Insert 2 copies of the number at the end. vec. push_back(j);

}

sort(vec. begin(), vec. end()); // Sort the vector

bool found = binary_search(vec. begin(), vec. end(), 7); //Is there a 7? assert(found == true); //should be std::vector<int>::iterator k, l;

k = lower_bound(vec. begin(), vec. end(), 5); // Find the first five

l = upper_bound(vec. begin(), vec. end(), 5); // Find the item past the five

assert(*k == 5); // should be the first five

assert(*l == 6); // Should equal the element after the last five

Using Your Own Objects

So far, you have just been using vectors containing integers. Although integers are fine and dandy, you will probably want to use your own user-defined types with vectors. This section covers a few issues about using your own objects with vectors.

The first thing you need to do before you can store an object in a vector is define that object. An object that is going to be stored in a vector should include a copy constructor, because copy constructors are used when moving objects around. An overloaded assignment operator can also be helpful.

class MyObject { int age int height; //in CM public:

MyObject() {}

MyObject( int a, int h) : age(a), height(h) {}

// Copy Constructor MyObject(const MyObject& a) : age(a. age), height(a. height) {} void SetAge(int a) { age = a; } void SetHeight(int a) { height = a; } int GetAge() { return age; } int GetHeight() { return height; }

//Overloaded assignment operator MyObject& operator=(const MyObject& r) { age = r. age; height = r. height; return *this; }

};

There is the basic object. To tell a vector to use it, you simply replace the int portion of the vector with MyObject:

std::vector<MyObject> vec; //vector to hold MyObject types

Now comes the tricky part—storing a MyObject inside of the vector.

To do so, you call push_back with a MyObject, like so:

// Stick 10 random MyObject’s into the vector for(int j = 0; j < 10; j++)

{

vec. push_back(MyObject(rand()%20+1, rand()%120 + 1));

}

To sort the objects, you must either overload the < operator or supply the sort function with another function. You will notice in the example code that you are passing everything by reference. This avoids a copy and thus saves you memory and time. This example sorts the vector by age:

#include <algorithm>

bool LesserAge( MyObject& l, MyObject &r) {

return (l. GetAge() < r. GetAge());

}

//or:

bool operator<( MyObject& l, MyObject &r) {

return (l. GetAge() < r. GetAge());

}

sort(vec. begin(), vec. end(), LesserAge); // Sorts by age sort(vec. begin(), vec. end()); // Sorts by age using <

Because the find function uses the == operator, you must overload it to work with your class. This is a simple operation:

#include <algorithm>

bool operator==(MyObject l, MyObject r) {

if(l. GetAge() != r. GetAge()) return false; if(l. GetHeight() != r. GetHeight()) return false; return true;

}

std::vector<MyObject>::iterator j;

j = std::find(vec. begin(), vec. end(), MyObject(10, 120)); if(j!= vec. end()) //We found it

The equality operator (== operator) is used in comparisons to deter­mine whether one element equals another. However, it will only work on C++ defined types. To get around this limitation, you can overload it to accept other types. In this case, it is overloaded to accept MyObject types and to compare them based on both age and height. However,

you could just as easily compare based on age alone, thus allowing for levels of equality.

Pointers

One of the disadvantages of storing an object inside of a vector is that whenever it gets moved, it has to copy the entire object to its new location. For small vectors, this may be possible, but when you start to get larger vectors, it becomes unacceptable. The way to avoid this is to use pointers. A pointer is quite a bit smaller than most objects, so moving them around takes a lot less time. However, because pointers use memory you have allocated, you must also remember to free that memory when you are done.

Declaring a vector to hold pointers to objects is fairly simple; you just replace the MyObject portion with the appropriate conversion:

std::vector<MyObject*> vec;

To put something into the vector, all you really have to do, from the last code, is add the new operator:

// Stick 10 random MyObject’s into the vector for(int j = 0; j < 10; j++)

{

vec. push_back( new MyObject(rand()%20+1, rand()%120 + 1));

}

Sorting the vector is a bit different, because the sort operator will use a binary predicate that you specify or the < operator by default. Because a pointer is just an integer, the < operator will sort by the memory address and not the contents of the MyObject.

#include <algorithm>

bool GreaterAgePtr( MyObject* l, MyObject *r) {

return (l->GetAge() > r->GetAge());

sort(vec. begin(), vec. end(), GreaterAgePtr); // Sorts by age sort(vec. begin(), vec. end()); // Sorts by age using < (memory address)

Searching has the same problem as sorting because it uses the == operator, except in this case there is a way around it:

#include <algorithm>

bool operator==(MyObject *l, MyObject r) {

if(l->GetAge() != r. GetAge()) return false; if(l->GetHeight() != r. GetHeight()) return false; return true;

}

std::vector<MyObject>::iterator j;

= std::find(vec. begin(), vec. end(), MyObject(10, 120)); f(j!= vec. end()) //We found it

Again, you are overloading the equality operator (==) just as you did earlier. However, this time, it can compare a pointer to a MyObject object and compare a MyObject object to itself. After you are done with your vector of pointers, you must free the memory that you allocated. This is a fairly simple process:

#include<algorithm>

}

};

std::for_each(vec. begin(), vec. end(), DeletePtr<MyObject>());

The class DeletePtr with the member function operator () is called a function object. All it does is delete MyObject pointers. If you wanted to, you could make it delete integer pointers by simply changing this line:

for_each(vec. begin(), vec. end(), DeletePtr<MyObject>());

to:

for_each(vec. begin(), vec. end(), DeletePtr<int>());

Simple and easy (and useful too).

Conclusion

If you are wondering about some of the applications of vectors in games, I have an idea. My idea is for a simple scene-graph. If you derive all of your objects from some base object, you could use a vector of pointers to the base object. This would allow you to easily do up­dates, collisions, and rendering. Because you would know that all objects before the current object had already moved and had been collision tested, you wouldn’t have to test against them for your cur­rent object. Also, you could reuse vector elements, such as when a creature dies, so you set its vector element to an empty object, and when you need a new object, just reuse the empty ones.

Finally I would recommend that you do some more research into template programming, especially pertaining to the Standard Tem­plate Library. It has many types of containers, including deques, lists,

sets, multisets, and maps. Each container has its own advantages and disadvantages, so picking the right one is not always easy. There is generally one container that will be better suited for a certain applica­tion than another.

The post STL Vector Primer first appeared on 3Д БУМ.

]]>
Vector Products https://3dbym.ru/2013/11/vector-products/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/vector-products/ Figure 1.19 Scalar multiplication of vectors. A vector multiplied by a scalar will have the same direction as the original, but a different magnitude (length).

Vectors cannot be multiplied together in the traditional sense. Instead of standard multiplication, there are… читать далее

The post Vector Products first appeared on 3Д БУМ.

]]>

Vector Products

Figure 1.19 Scalar multiplication of vectors. A vector multiplied by a scalar will have the same direction as the original, but a different magnitude (length).

Vectors cannot be multiplied together in the traditional sense. Instead of standard multiplication, there are two operations that take its place—the dot product (also known as the scalar product) and the cross product (also known as the vector product).

The post Vector Products first appeared on 3Д БУМ.

]]>
Loading the OBJ Format https://3dbym.ru/2013/11/loading-the-obj-format/ Fri, 08 Nov 2013 15:46:23 +0000 //3dbym.ru/2013/11/loading-the-obj-format/ Because the OBJ does not contain any sort of header, you need some sort of resizable array to hold your vertices, faces, and the other com­ponents. In the implementation shown here, everything is loaded into an STL vector, a kind… читать далее

The post Loading the OBJ Format first appeared on 3Д БУМ.

]]>
Because the OBJ does not contain any sort of header, you need some sort of resizable array to hold your vertices, faces, and the other com­ponents. In the implementation shown here, everything is loaded into an STL vector, a kind of resizable array.

Each data type has its own structure. Vector3 contains two floats and is used to hold a single vertex position, or vertex normal. Vector2 is a lot like its bigger brother, but holds only two values, perfect for texture coordi­nates. Last of all is SObjFace, which contains 12 unsigned integers, three for each vertex indexes, texture coord indexes, and normal indexes. Even if all the face variables are not always used, the storage is still there.

Now you are ready to read in the file. The best way to do this is read in one line at a time, check the prefix, and extract the rest of the values using the sscanf function. Each type of value (vertex data, vertex normal, texture coordinate, and face) has its own arrays to hold it. If the loader finds a line that starts with something that does not signify a recognized chunk, it simply reads the line and discards it.

In the CObj class, there are two Boolean values: m_bHasTexCoords and m_bHasNormals. These variables are set to true if a texture coord (for m_bHasTexCoords) or vertex normal line (for m_bHasNormals) is found in the file. Although this does work, it has a few flaws. If for some reason the faces do not come after all the vertex info, the loader will not know what type of face to read and will default to reading vertices only.

The post Loading the OBJ Format first appeared on 3Д БУМ.

]]>