Sample code to create the sound interface appears in the following: LPDIRECTSOUND8 m_pDSound = 0; // Create IDirectSound using the primary sound device hr = DirectSoundCreate8 NULL,
Trang 1HRESULT WINAPI DirectSoundCreate8(
LPCGUID lpcGuid,
LPDIRECTSOUND8 * ppDS,
LPUNKNOWN pUnkOuter
);
lpcGuid A pointer to a GUID that describes the device you wish to create While you can
enumerate all of the sound devices with DirectSoundEnumerate, generally there is only one sound card on a machine To get the default device (which is what you want, usually), set this to NULL
ppDS A pointer to an LPDIRECTSOUND8 interface pointer that will be filled with a valid
interface pointer if the function succeeds
pUnkOuter Used for COM aggregation; leave this as NULL
Sample code to create the sound interface appears in the following:
LPDIRECTSOUND8 m_pDSound = 0;
// Create IDirectSound using the primary sound device
hr = DirectSoundCreate8( NULL, &m_pDSound, NULL );
if( FAILED( hr ) )
{
// Handle critical error
}
Setting the Cooperative Level
After you acquire the interface pointer, the next step is to declare how cooperative you intend on being Just like DirectInput, this is done using the SetCooperativeLevel command
Trang 2hwnd Handle to the window to be associated with the DirectSound object This should be the
primary window
dwLevel One of the following flags, describing the desired cooperative level
DSSCL_EXCLUSIVE—Grab exclusive control of the sound device When the application has focus, it is the only audible application
DSSCL_NORMAL—Smoothest, yet most restrictive cooperative level The primary format cannot be changed This is the cooperative level the sound layer uses
DSSCL_PRIORITY—Like DDSCL_NORMAL except the primary format may be changed
DSSCL_WRITEPRIMARY—This is the highest possible priority for an application
to have It can't play any secondary buffers, and it has the ability to manually mangle the bits of the primary buffer Only for the extremely hardcore!
This code will be changing the primary format of the sound buffer, so I'll go ahead and set this to
DSSCL_PRIORITY Sample code to do this appears in the following:
// pDSound is a valid LPDIRECTSOUND8 object
HRESULT hr = pDSound->SetCooperativeLevel( hWnd, DSSCL_PRIORITY );
if( FAILED( hr ) )
{
/* handle error */
}
Grabbing the Primary Buffer
Since the sound layer sets the cooperative level's priority, it can do some crazy things like change the format of the primary buffer Generally it's best to set the primary buffer to the same format that all of your secondary buffers will be in; this makes the mixer's job easier, as it doesn't have to resample any sound effects to be able to mix them into the primary buffer You can imagine what would happen if you tried to play a 22 KHz sound effect in a 44 KHz buffer without resampling: You would run out of samples twice as soon as you would expect, and the sound effect would have sort of a chipmunkish quality to it
To change the format of the primary buffer, you just need to grab it using CreateSoundBuffer, fill out a new format description, and set it using the SetFormat() method on the primary buffer Listing 4.2 has code that sets the primary format to 22 KHz, 16-bit stereo
Listing 4.2: Sample code to change the format of the primary buffer
Trang 3// pDSound is a valid LPDIRECTSOUND object
LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
wfx.nBlockAlign = wfx.wBitsPerSample/8* wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
Trang 4* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
LPDIRECTSOUNDBUFFER8 m_pPrimary; // primary mixer
static cSoundLayer* m_pGlobalSLayer;
Trang 6* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Desc: Sample application for Direct3D
*
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information ******************************************************************/
LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
// Create IDirectSound using the primary sound device
hr = DirectSoundCreate8( NULL, &m_pDSound, NULL );
if( FAILED( hr ) )
{
Trang 7throw cGameError( "DirectSoundCreate failed!" );
}
// Set coop level to DSSCL_PRIORITY
hr = m_pDSound->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if( FAILED( hr ) )
wfx.nBlockAlign = wfx.wBitsPerSample/8* wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
Trang 8if( FAILED( hr = pDSBPrimary->SetFormat(&wfx) ) )
The cSound Class
To help facilitate the creation and playback of secondary buffers, I constructed an encapsulation class called cSound A cSound object can be constructed either from a filename or from another cSound object The copy constructor uses a ref-counting map so that all cSounds based on the same WAV file use the same CWaveSoundRead object The overhead of the map could have been avoided if the CWaveSoundRead code was changed to accommodate the needed functionality, but I felt it was better
to leave the code unchanged from the DirectX SDK
Without any further ado, let's just dive into the code The details of how this code works isn't terribly interesting but have a look through it anyway to get accustomed to it
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
Trang 9* Multiple sounds that use the same
* file shouldn't reread it, they should
* share the CWSR object This map
* implements rudimentary reference counting
* I would have just changed CWaveSoundRead,
* but I wanted to keep it unchanged from the
Trang 10cSound& operator=( const cSound &in );
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information ******************************************************************/
#include "stdafx.h"
#include "WavRead.h"
#include "Sound.h"
using std::map;
Trang 11map< CWaveSoundRead*, int > cSound::m_waveMap;
cSound::cSound( char* filename )
{
m_pWaveSoundRead = NULL;
m_pBuffer = NULL;
// Create a new wave file class
m_pWaveSoundRead = new CWaveSoundRead();
m_waveMap[ m_pWaveSoundRead]=1;
// Load the wave file
if( FAILED( m_pWaveSoundRead->Open( filename))) {
throw cGameError("couldn't open file!"); }
Trang 12* Destroy the old object
Trang 13* Set up the DirectSound surface The size of the sound file
* and the format of the data can be retrieved from the wave
* sound object Besides that, we only set the STATIC flag,
* so that the driver isn't restricted in setting up the
// Create the sound buffer
hr = Sound()->GetDSound()->CreateSoundBuffer( &dsbd, &pTempBuffer, NULL );
Trang 14pTempBuffer->QueryInterface( IID_IDirectSoundBuffer8, (void**)&m_pBuffer );
Trang 15/**
* Chances are, we got here because the app /just/
* started, and DirectSound hasn't given us any
* control yet Just spin until we can restore
uchar* pbWavData; // Pointer to actual wav data
uint cbWavSize; // Size of data
void* pbData = NULL;
void* pbData2 = NULL;
ulong dwLength;
ulong dwLength2;
/**
Trang 16* How big the wav file is
pbWavData = new uchar[ nWaveFileSize ];
if( NULL == pbWavData )
Trang 170, m_bufferSize, &pbData, &dwLength,
memcpy( pbData, pbWavData, m_bufferSize );
m_pBuffer->Unlock( pbData, m_bufferSize, NULL, 0 );
Trang 18DWORD dwLooped = bLoop ? DSBPLAY_LOOPING : 0L;
if( FAILED( hr = m_pBuffer->Play( 0, 0, dwLooped)))
Application: DirectSound Sample
Adrian, the lead author of the DirectX 7.0 version of this book, has a few interesting hobbies As part of
an ongoing effort, he does extracurricular activities that actually have nothing to do with programming One of them is an a cappella group that he sings bass in One of his jobs in the a cappella group is to take care of some of the vocal percussion
A cappella music can't have any sort of accompaniment, so any percussion needs to be done with the human voice This has spawned an entire subculture of vocal percussionists, each trying to make that
Trang 19perfect snare sound or cymbal crash using only their mouths The DirectSound sample for this chapter was created using Adrian's unique vocal abilities
When you load the file DSSAMPLE from the companion files, you're presented with a small window that lists six different vocal percussion sounds The keys 1 through 6 play each of the sounds, and you can press multiple keys simultaneously to play multiple sounds
You'll note that I didn't show you a DirectInput sample, because I figured it would be better to roll DirectSound and DirectInput into one sample DirectInput is used to capture the keystrokes With some practice you can get a pretty swank beat going The code behind the sample appears in Listing 4.7
Listing 4.7: The vocal percussion DirectSound sample app
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
Trang 20public:
void PlaySound( int num );
//========== - cApplication
virtual void DoFrame( float timeDelta );
virtual void SceneInit();
virtual void KeyUp( int key );
virtual void KeyDown( int key );
Trang 22* for a sound that isn't currently playing
DP("spawning a new sound\n");
cSound* pNew = new cSound( *m_sounds[num][0] );
string help;
help += "DirectSound Sample application\n";
help += "Vocal Percussion with Adrian Perez\n";
Trang 23help += " [1]: Keg drum\n";
help += " [2]: Crash 1\n";
help += " [3]: Crash 2\n";
help += " [4]: Bass drum\n";
help += " [5]: Snare drum\n";
// Tell Direct3D we are done rendering
Trang 26And that, ladies and gentlemen, is DirectSound You should be able to build on top of the code I have shown you in this chapter to create the perfect acoustic accompaniment to your own projects without too much difficulty
Now it's time to focus on the 3D mathematics that you need to start exploring the next dimension with Direct3D What is the matrix? You're about to find out…
When you really get down to it, using 3D graphics is an exercise in math Some of the math can be intensely interesting; some of it can be seriously dull It all depends on the eye of the beholder Love it
or hate it, however, you still have to learn it A solid foundation in math is a requirement if you want to be
a successful 3D coder Don't worry, though; I'll try to keep this chapter as interesting as possible
Points
Let's start with the most basic of basic primitives: the 3D point, or vector Points are paramount in 3D
graphics The vertices of objects, the objects' locations and directions, and their velocities/forces are all described with 3D points Three-dimensional objects have width, height, and depth, which are
represented with the shorthand components x (width), y (height), and z (depth) Points and vectors,
when used in equations, are referred to as vector quantities, while regular numbers are referred to as
scalar quantities In this book I'll refer to 3D vectors with lower-case, boldface letters (examples would
be v and p) The three components are written separated by commas like this: <x,y,z> They are also
represented as a single row matrix (or, equivalently, a transposed single column matrix) If you're
unfamiliar with the concept of matrices, have patience: I'll get there soon At the right is an example of how points are represented using matrix notation:
Note
In the book I use the terms "point" and "vector" interchangeably They loosely mean the same thing A point is a location in 3D space, and a vector is a line that goes from the origin to a location in 3D space For all the math that I'm discussing in this book, they can be used interchangeably
Here are a few examples of three-dimensional points It's difficult to represent the dimensions on paper,
so please be tolerant of the illustrations used in this book
Trang 27Figure 5.1: Examples of 3D vectors
3D points are graphed in a way analogous to the 2D Cartesian coordinate system There are three principal axes stretching off into infinity in both directions These are the x, y, and z axes They meet at
the origin, a specific point that represents the center of the current coordinate system (typically you have
several coordinate systems to worry about, but I'll leave this until later) The coordinates of the origin are, of course, <0,0,0>
Which way do the axes point? In some systems (for example, some 3D modelers like 3D Studio Max), x increases to the right, y increases forward (into the page), and z increases up These directions are all dependent on the orientation of the viewer of the scene My choice of axes direction is the one used in most 3D games: x increases to the right, y increases up, and z increases forward, into the monitor (or away from you if that makes it clearer)
Note
This book uses a left-handed coordinate space, where x increases to the right, y increases up, and z increases forward (into the screen) In right-handed coordinate systems, z increases coming out of the screen
A point always exists some distance away from the origin of the coordinate space; this quantity is called
the magnitude of the vector (or, more intuitively, the length of the vector) To compute the magnitude of
vectors in 2D, you use the Pythagorean theorem:
Luckily, the Pythagorean theorem extends into 3D to measure the length of 3D vectors by simply adding the extra z component into the equation You can see that the 2D Pythagorean equation is simply a special case of the 3D equation where the z-distance from the origin is zero
Trang 28There is a shorthand notation used to denote the magnitude of a vector when used in more complex equations The notation is the vector surrounded on both sides by double vertical lines The equation for
vector length, given a vector v with components x, y, and z is:
A special type of vector is one that has a length of 1 This type of vector is called a unit vector Each unit vector touches a point on what is called the unit sphere, a conceptual sphere with radius 1, situated at
the origin
It's often the case that you want a unit-length version of a given vector For example, the unit-length
version n of a given vector m would be:
For simplicity's sake, however, I'll introduce some shorthand notation The same equation can be
represented by putting a bar over m to signify the unit-length version:
There are three specific unit vectors that represent the directions along the three primary axes: i
<1,0,0>, j <0,1,0>, and k <0,0,1>
Figure 5.2: The i, j, and k vectors
Many physics texts use the i, j, and k vectors as primitives to describe other 3D vectors, so it is worth mentioning it here Any point in 3D can be represented as a linear combination of the i, j, and k vectors
You can define any vector as a sum of the scalar components with the three principal vectors For
example, if you had the 3D vector a = <3,5,2>, you could represent it like this:
Trang 29This trait will become more important later on, when I discuss matrices and the spaces they represent
Aside
While it isn't really pertinent to the level of expertise you need to reach in this book, the concept of a linear combination is important when talking about spaces and
transformations
Given n vectors b0 bn−1, any vector v is a linear combination of the set of the vectors if
the following equation can be satisfied:
where k0 kn−1 are scalars
That is, if you want to get to v, you can start at the origin and walk along any or all of the vectors some amount and reach v
You can say the set of b vectors is linearly independent if no single b vector is a linear
combination of the others
The point3 Structure
It is always useful to design a class to encapsulate a generic 3D point The class name I use is point3 Unlike most of the other classes you have seen so far, the intent of the point3 structure is to act as a mathematical primitive like float or int The 3 suffix denotes the dimension of the point I'll also define 2D and 4D versions of points, which are named point2 and point4, respectively
Listing 5.1: The point3 structure (defined in point3.h)
Trang 30
// Default constructor
point3(){}
// Construct a point with 3 given inputs
point3( float X, float Y, floatZ):
Aside
The non-default constructor uses initialization lists C++ classes should use these whenever possible They clarify the intent of the code to the compiler, which lets it do its job better (it has a better chance to inline the code, and the code will end up being considerably more efficient, especially for complex structures)
Finally, you may wonder why I'm choosing floats (32 bits/4 bytes) instead of doubles (64 bits/8 bytes) or
long doubles (80 bits/10 bytes) Well, I could just implement the point as a template class, but there are
too many other interactions with other classes to complicate the code that much Using it as a template
in a way defeats the concept of using the point as a generic primitive, especially since there is a space
of only three types I would use
Doubles and long doubles are slower than floats, about twice as slow for things like divides (19 versus
39 cycles), and on top of that they require twice the space The added precision really isn't important unless you really need a wide range of precision Within a few years worlds will be big enough and model resolution will be fine enough that you may need to employ larger floating-point resolutions to get the job done Until then I'd suggest sticking with traditional floats
Basic point3 Functions
Trang 31The point3 structure is pretty lame right now All it can do is construct structures! To spice it up, I'll add some member functions to help perform some basic operations on 3D points, and explain what they are used for
Assign
Setting a point to a certain value is a common task It could be done explicitly in three lines, setting the
x, y, and z values separately However, for simplicity's sake, it's easier to set them all at once, with a single function call This is also better than just creating a new variable on the stack with a point3 constructor; it's more efficient to reuse stack variables whenever possible The code to do this appears
in Listing 5.2
Listing 5.2: point3::Assign
// Reassign a point without making a temporary structure
inline void point3::Assign( float X, float Y, float Z )
Mag and MagSquared
The function Mag uses the 3D version of the Pythagorean theorem mentioned previously to calculate the length of the point structure (the distance from the point to the origin) The code appears in Listing 5.3
Trang 32Sometimes you want the squared distance (for example, when calculating the attenuation factor for a point-source light) Rather than computing the expensive square root and squaring it, you can avoid the cost and simply make an extra function to do it for you, which appears in Listing 5.4
Dist is a static function that calculates the distance between two point structures Conceptually, it finds
the vector that connects them (which is the vector b–a) and computes its length The code appears in
Listing 5.6
Listing 5.6: point3::Dist
inline static float point3::Dist( const point3 &a, const point3 &b )
Trang 33Addition/Subtraction
Vector addition and subtraction are useful in moving points around in 3D Conceptually, adding a vector
to another moves the location of the first vector in the direction of the second Figure 5.3 shows what the result of vector addition looks like, and Figure 5.4 shows the result of vector subtraction
Figure 5.3: Vector addition example
Trang 34Figure 5.4: Vector subtraction example
In many respects, vector addition/subtraction is incredibly similar to the normal scalar addition that I'm sure you know and love For example, if you wanted to find the average location of a set of vectors, you simply add them together and divide the result by the number of vectors added, which is, of course, the same averaging formula used for scalars
The code for adding/subtracting vectors is equally similar to their scalar cousins: Simply add (or
subtract) each component together separately I'll give the + and − operators; in the code you'll find the += and −= operators
Listing 5.7: Addition and subtraction operators for point3
inline point3 operator+(point3 const &a, point3 const &b)
Trang 35Figure 5.5: Multiplying/dividing vectors by scalars
Doing this in code is easy enough; just multiply (or divide) each component in the vector by the provided scalar Listing 5.8 has the * and / operators; the *= and /= operators are defined in the header Note that
I defined two multiplicative operators; one for vector * scalar and another for scalar * vector
Listing 5.8: Scalar multiplication/division operators for point3
inline point3 operator*(point3 const &a, float const &b)
{
return point3