Building a Morphed Mesh through Manipulation
Directly manipulating a mesh's vertex buffers is probably the easiest way to
work with morphing. For this method you'll need a third mesh that contains the
final coordinates of each vertex after morphing; it's this third mesh that
you'll render.
To create the third mesh, which I call the resulting morphed mesh, you can
clone the source mesh and be on your way.
// Declare third mesh to use for resulting morphed mesh
ID3DXMesh *pResultMesh = NULL;
// Clone the mesh using the source mesh pSourceMesh
pSourceMesh−>CloneMeshFVF(0, pSourceMesh−>GetFVF(), pDevice,&pResultMesh);
After you've created the resulting morphed mesh (pResultMesh), you can begin
processing the morphing animation by locking the source, target, and resulting
morphed mesh's vertex buffers. Before you do that however, you need to declare a
generic vertex structure that contains only the vertex coordinates, which you'll
use to lock and access each vertex buffer.
typedef struct {
D3DXVECTOR3 vecPos;
} sGenericVertex;
Also, because each vertex buffer contains vertices of varying sizes (for
example, the source might use normals whereas the target doesn't), you need to
calculate the size of the vertex structure used for each mesh's vertices. You
can do so using the D3DXGetFVFVertexSize function.
// pSourceMesh = source mesh object
// pTargetMesh = target mesh object
// pResultMesh = resulting morphed mesh object
DWORD SourceSize = D3DXGetFVFVertexSize(pSourceMesh−>GetFVF());
DWORD TargetSize = D3DXGetFVFVertexSize(pTargetMesh−>GetFVF());
DWORD ResultSize = D3DXGetFVFVertexSize(pResultMesh−>GetFVF());
Now you can lock the vertex buffers and assign the pointers to them.
// Declare vertex pointers
char *pSourcePtr, *pTargetPtr, *pResultPtr;
pSourceMesh−>LockVertexBuffer (D3DLOCK_READONLY, (void**)&pSourcePtr);
pTargetMesh−>LockVertexBuffer (D3DLOCK_READONLY, (void**)&pTargetPtr);
pResultMesh−>LockVertexBuffer (0, (void**)&pResultPtr);
Notice how I assigned a few char * pointers to the vertex buffers instead of
using the generic vertex structure? You need to do that because the vertices in
the buffers could be of any size, remember? Whenever you need to access a
vertex, you cast the pointer to the generic vertex structure and access the
data. To go to the next vertex in the list, add the size of the vertex structure
to the pointer. Get it? If not, don't worry−the upcoming code will help you make
sense of it all.
After you've locked the buffers you can begin iterating through all vertices,
grabbing the coordinates and using the calculations from the previous section to
calculate the morphed vertex positions. Assuming that the length of the
animation is stored in Length and the current time you are using is stored in
Time, the following code will illustrate how to perform the calculations:
// Length = FLOAT with length of animation in milliseconds
// Time = FLOAT with time in animation to use
// Calculate a scalar value to use for calculations
float Scalar = Time / Length;
// Loop through all vertices
for(DWORD i=0;i<pSourceMesh−>GetNumVertices();i++)
{
// Cast vertex buffer pointers to a generic vertex structure
sGenericVertex *pSourceVertex = (sGenericVertex*)pSourcePtr;
sGenericVertex *pTargetVertex = (sGenericVertex*)pTargetPtr;
sGenericVertex *pResultVertex = (sGenericVertex*)pResultPtr;
// Get source coordinates and scale them
D3DXVECTOR3 vecSource = pSourceVertex−>vecPos;
vecSource *= (1.0f − Scalar);
// Get target coordinates and scale them
D3DXVECTOR3 vecTarget = pTargetVertex−>vecPos;
vecTarget *= Scalar;
// Store summed coordinates in resulting morphed mesh
pResultVertex−>vecPos = vecSource + vecTarget;
// Go to next vertices in each buffer and continue loop
pSourcePtr += SourceSize;
pTargetPtr += TargetSize;
pResultPtr += ResultSize;
}
Up to this point I've skipped over the topic of vertex normals because
normals are identical to vertex coordinates in that you use scalar and inversed
scalar values on the normals to perform the same calculations as you do for the
vertex coordinates.
In the preceding code, you can calculate the morphing normal values by first
seeing whether the mesh uses normals. If so, during the loop of all vertices you
grab the normals from both the source and target vertices, multiply by the
scalar and inversed scalar, and store the results. Take another look at the code
to see how to do that:
// Length = FLOAT with length of animation in milliseconds
// Time = FLOAT with time in animation to use
// Calculate a scalar value to use for calculations
float Scalar = Time / Length;
// Set a flag if using normals
BOOL UseNormals = FALSE;
if(pSourceMesh−>GetFVF() & D3DFVF_NORMAL && pTargetMesh−>GetFVF() & D3DFVF_NORMAL)
UseNormals = TRUE;
// Loop through all vertices
for(DWORD i=0;i<pSourceMesh−>GetNumVertices();i++)
{
// Cast vertex buffer pointers to a generic vertex structure
sGenericVertex *pSourceVertex = (sGenericVertex*)pSourcePtr;
sGenericVertex *pTargetVertex = (sGenericVertex*)pTargetPtr;
sGenericVertex *pResultVertex = (sGenericVertex*)pResultPtr;
// Get source coordinates and scale them
D3DXVECTOR3 vecSource = pSourceVertex−>vecPos;
vecSource *= (1.0f − Scalar);
// Get target coordinates and scale them
D3DXVECTOR3 vecTarget = pTargetVertex−>vecPos;
vecTarget *= Scalar;
// Store summed coordinates in resulting morphed mesh
pResultVertex−>vecPos = vecSource + vecTarget;
// Process normals if flagged
if(UseNormals == TRUE)
{
// Adjust generic vertex structure pointers to access
// normals, which are next vector after coordinates.
pSourceVertex++; pTargetVertex++; pResultVertex++;
// Get normals and apply scalar and inversed scalar values
D3DXVECTOR3 vecSource = pSourceVertex−>vecPos;
vecSource *= (1.0f − Scalar);
D3DXVECTOR3 vecTarget = pTargetVertex−>vecPos;
vecTarget *= Scalar;
pResultVertex−>vecPos = vecSource + vecTarget;
}
// Go to next vertices in each buffer and continue loop
pSourcePtr += SourceSize;
pTargetPtr += TargetSize;
pResultPtr += ResultSize;
}
Everything looks great! All you need to do now is unlock the vertex buffers
and render the resulting mesh! I'll skip the code to unlock the buffers and get
right to the good part−rendering the meshes.
Drawing Morphed Meshes
If you're building the morphing meshes by directly manipulating the resulting
mesh's vertex buffer, as shown in the previous section, then rendering the
morphing mesh is the same as for any other ID3DXMesh object you've been using.
For example, you can loop through each material in the mesh, set the material
and texture, and then draw the currently iterated subset. No need to show any
code here−it's just simple mesh rendering.
On the other hand, if you want to move past the basics and start playing with
real power, you can create your own vertex shader to render the morphing meshes
for you. Take my word for it−this is something you'll definitely want to do.
Using a vertex shader means you have one less mesh to deal with because the
resulting mesh object is no longer needed; the speed increase is well worth a
little extra effort.
Before you can move on to using a vertex shader, however, you need to figure
out how to render the mesh's subsets yourself.
Dissecting the Subsets
To draw the morphing mesh, you need to set the source mesh's vertex stream as
well as the target mesh's stream. Also, you need to set only the source mesh's
indices. At that point, it's only a matter of scanning through every subset and
rendering the polygons related to each subset.
Wait a second! How do you render the subsets yourself? By duplicating what
the ID3DXMesh::DrawSubset function does, that's how! The DrawSubset function
works in one of two ways. The first method, which you use if your mesh has not
been optimized to use an attribute table, is to scan the entire list of
attributes and render those batches of polygons belonging to the same subset.
This method
can be a little slow because it renders multimaterial meshes in small batches of
polygons.
The second method, which is used after you optimize the mesh to use an
attribute table, works by scanning the built attribute table to determine which
grouped faces are drawn all in one shot. That is, all faces that belong to the
same subset are grouped together beforehand and rendered in one call to
DrawPrimitive or DrawIndexedPrimitive. That seems like the way to go!
To use the second method of rendering, you need to first optimize your source
mesh. You can (and should) do this when you load the mesh. It's a safe habit to
optimize all meshes you load using the ID3DXMesh::OptimizeInPlace function, as
shown in the following bit of code:
// pMesh = just−loaded mesh
pMesh−>OptimizeInPlace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
Once the mesh is optimized, you can query the ID3DXMesh object for the
attribute table it is using. The attribute table is of the data type
D3DXATTRIBUTERANGE, which is defined as follows:
typedef struct_D3DXATTRIBUTERANGE {
DWORD AttribId;
DWORD FaceStart;
DWORD FaceCount;
DWORD VertexStart;
DWORD VertexCount;
} D3DXATTRIBUTERANGE;
The first variable, AttribId is the subset number that the structure
represents. For each material in your mesh, you have one D3DXATTRIBUTERANGE
structure with the AttribId set to match the subset number.
Next come FaceStart and FaceCount. You use these two variables to determine
which polygon faces belong to the subset. Here's where the optimization comes in
handy−all faces belonging to the same subset are grouped together in the index
buffer. FaceStart represents the first face in the index buffer belonging to the
subset, whereas FaceCount represents the number of polygon faces to render using
that subset.
Last, you see VertexStart and VertexCount, which, much like FaceStart and
FaceCount, determine which vertices are used during the call to render the
polygons. VertexStart represents the first vertex in the vertex buffer to use
for a subset, and VertexCount represents the number of vertices you can render
in one call. When you optimize a mesh based on vertices, you'll notice that all
vertices are packed in
the buffer to reduce the number of vertices used in a call to render a subset.
For each subset in your mesh you must have a matching D3DXATTRIBUTERANGE
structure. Therefore, a mesh using three materials will have three attribute
structures. After you've optimized a mesh (using ID3DXMesh::OptimizeInPlace ),
you can get the attribute table by first querying the mesh object for the number
of attribute structures using the ID3DXMesh::GetAttributeTable function, as
shown
here:
// Get the number of attributes in the table
DWORD NumAttributes;
pMesh−>GetAttributeTable(NULL, &NumAttributes);
At this point, you only need to allocate a number of D3DXATTRIBUTERANGE
objects and call the GetAttributeTable function again, this time supplying a
pointer to your array of attribute objects.
// Allocate memory for the attribute table and query
for the table data
D3DXATTRIBUTERANGE *pAttributes;
pAttributes = new D3DXATTRIBUTERANGE[NumAttributes];
pMesh−>GetAttributeTable(pAttributes, NumAttributes);
Cool! After you've got the attribute data, you can pretty much render the
subsets by scanning through each attribute table object and using the specified
data in each in a call to DrawIndexedPrimitive. In fact, do that now by first
grabbing the mesh's vertex buffer and index buffer pointers.
// Get the vertex buffer interface
IDirect3DVertexBuffer9 *pVB;
pMesh−>GetVertexBuffer(&pVB);
// Get the index buffer interface
IDirect3DIndexBuffer9 *pIB;
pMesh−>GetIndexBuffer(&pIB);
Now that you have both buffer pointers, go ahead and set up your streams,
vertex shader, and vertex element declaration, and loop through each subset,
setting the texture and then rendering the polygons.
// Set the vertex shader and declaration
pDevice−>SetFVF(NULL); // Clear FVF usage
pDevice−>SetVertexShader(pShader);
pDevice−>SetVertexDeclaration(pDecl);
// Set the streams
pDevice−>SetStreamSource(0, pVB, 0, pMesh−>GetNumBytesPerVertex());
pDevice−>SetIndices(pIB);
// Go through each subset
for(DWORD i=0;i<NumAttributes;i++)
{
// Get the material id#
DWORD MatID = pAttributes[i];
// Set the texture of the subset
pDevice−>SetTexture(0, pTexture[AttribID]);
// Render the polygons using the table
pDevice−>DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
pAttributes[i].VertexStart,
pAttributes[i].VertexCount,
pAttributes[i].FaceStart * 3,
pAttributes[i].FaceCount);
}
After you've rendered the subsets you can free the vertex buffer and index
buffer interfaces you obtained.
pVB−>Release(); pVB = NULL;
pIB−>Release(); pIB = NULL;
When you're done with the attribute table, make sure to free that memory as
well.
delete [] pAttributes; pAttributes = NULL;
All right, now you're getting somewhere! Now that you know how to render the
subsets yourself, it's time to move on to using a vertex shader.
Check Out the Demos
This project demonstrates building morphing meshes by directly manipulating a
mesh's vertex buffer.
Figure 8.4: The animated dolphin jumps over morphing sea waves!
WinMain.cpp:
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include "Direct3D.h"
// A generic coordinate vertex structure
struct sVertex
{
D3DXVECTOR3 pos;
};
// Background vertex structure, fvf, and texture.
struct sBackdropVertex
{
float x, y, z, rhw;
float u, v;
};
#define BACKDROP_FVF (D3DFVF_XYZRHW | D3DFVF_TEX1)
// Structure to contain a morphing mesh
struct sMorphMesh
{
D3DXMESHCONTAINER_EX* source_mesh;
D3DXMESHCONTAINER_EX* target_mesh;
D3DXMESHCONTAINER_EX* result_mesh;
long normal_offset;
DWORD vertex_pitch;
sMorphMesh()
{
ZeroMemory(this, sizeof(*this));
}
~sMorphMesh()
{
delete source_mesh; source_mesh = NULL;
delete target_mesh; target_mesh = NULL;
delete result_mesh; result_mesh = NULL;
}
};
//////////////////////////////////////////////////////////////////////////////////////////////
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
IDirect3DVertexBuffer9* g_backdrop_vb;
IDirect3DTexture9* g_backdrop_texture;
sMorphMesh* g_water_morph_mesh;
sMorphMesh* g_dolphin_morph_mesh;
const char CLASS_NAME[] = "MorphClass";
const char CAPTION[] = "Morphing Demo";
////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
bool do_init(HWND hwnd);
void do_shutdown();
void do_frame();
// Function to load a group of meshes for morphing
bool load_morphing_mesh(sMorphMesh* morph_mesh,
const char* source_mesh_file,
const char* target_mesh_file,
const char* texture_path);
// Function to build a resulting morphes mesh
void build_morph_mesh(sMorphMesh* morph_mesh, float scalar);
//////////////////////////////////////////////////////////////////////////////////////////////
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR, int cmd_show)
{
CoInitialize(NULL); // Initialize the COM system
// Create the window class here and register it
WNDCLASSEX win_class;
win_class.cbSize = sizeof(win_class);
win_class.style = CS_CLASSDC;
win_class.lpfnWndProc = window_proc;
win_class.cbClsExtra = 0;
win_class.cbWndExtra = 0;
win_class.hInstance = inst;
win_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
win_class.hCursor = LoadCursor(NULL, IDC_ARROW);
win_class.hbrBackground = NULL;
win_class.lpszMenuName = NULL;
win_class.lpszClassName = CLASS_NAME;
win_class.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if(!RegisterClassEx(&win_class))
return -1;
// Create the main window
HWND hwnd = CreateWindow(CLASS_NAME, CAPTION, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
0, 0, 640, 480, NULL, NULL, inst, NULL);
if(hwnd == NULL)
return -1;
ShowWindow(hwnd, cmd_show);
UpdateWindow(hwnd);
// Call init function and enter message pump
if(do_init(hwnd))
{
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
// Start message pump, waiting for user to exit
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
do_frame(); // Render a single frame
}
}
do_shutdown();
UnregisterClass(CLASS_NAME, inst);
CoUninitialize();
return 0;
}
LRESULT FAR PASCAL window_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Only handle window destruction messages
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
DestroyWindow(hwnd);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
bool do_init(HWND hwnd)
{
init_d3d(&g_d3d, &g_device, hwnd, false, false);
g_water_morph_mesh = new sMorphMesh;
if(! load_morphing_mesh(g_water_morph_mesh, "..\\Data\\Water1.x", "..\\Data\\Water2.x", "..\\Data\\"))
return false;
g_dolphin_morph_mesh = new sMorphMesh;
if(! load_morphing_mesh(g_dolphin_morph_mesh, "..\\Data\\Dolphin1.x", "..\\Data\\Dolphin3.x", "..\\Data\\"))
return false;
// create the backdrop
sBackdropVertex backdrop_verts[4] = {
{ 0.0f, 0.0, 1.0, 1.0f, 0.0f, 0.0f },
{ 640.0f, 0.0, 1.0, 1.0f, 1.0f, 0.0f },
{ 0.0f, 480.0, 1.0, 1.0f, 0.0f, 1.0f },
{ 640.0f, 480.0, 1.0, 1.0f, 1.0f, 1.0f }
};
g_device->CreateVertexBuffer(sizeof(backdrop_verts), D3DUSAGE_WRITEONLY, BACKDROP_FVF, D3DPOOL_DEFAULT,
&g_backdrop_vb, NULL);
char* ptr;
g_backdrop_vb->Lock(0, 0, (void**)&ptr, 0);
memcpy(ptr, backdrop_verts, sizeof(backdrop_verts));
g_backdrop_vb->Unlock();
D3DXCreateTextureFromFile(g_device, "..\\Data\\Sky.bmp", &g_backdrop_texture);
// Create and enable a directional light
D3DLIGHT9 light;
ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_DIRECTIONAL;
light.Diffuse.r = light.Diffuse.g = light.Diffuse.b = light.Diffuse.a = 1.0f;
light.Direction = D3DXVECTOR3(0.0f, -1.0f, 0.0f);
g_device->SetLight(0, &light);
g_device->LightEnable(0, TRUE);
// start playing an ocean sound
PlaySound("..\\Data\\Ocean.wav", NULL, SND_ASYNC | SND_LOOP);
return true;
}
void do_shutdown()
{
// Stop playing an ocean sound
PlaySound(NULL, NULL, 0);
// release backdrop data
release_com(g_backdrop_vb);
release_com(g_backdrop_texture);
// free mesh data
delete g_water_morph_mesh; g_water_morph_mesh = NULL;
delete g_dolphin_morph_mesh; g_dolphin_morph_mesh = NULL;
// release D3D objects
release_com(g_device);
release_com(g_d3d);
}
void do_frame()
{
static float dolphin_x_pos = 0.0f, dolphin_z_pos = 256.0f;
// build the water morphing mesh using a time-based sine-wave scalar value
float water_scalar = (sin(timeGetTime() * 0.001f) + 1.0f) * 0.5f;
build_morph_mesh(g_water_morph_mesh, water_scalar);
// build the dolphin morphing mesh using a time-based scalar value
float dolphin_time_factor = (timeGetTime() % 501) / 250.0f;
float dolphin_scalar = (dolphin_time_factor <= 1.0f) ? dolphin_time_factor : (2.0f - dolphin_time_factor);
build_morph_mesh(g_dolphin_morph_mesh, dolphin_scalar);
// calculate the angle of the dolphin's movement and reposition the dolphin if it's far enough underwater.
float dolphin_angle = (timeGetTime() % 6280) / 1000.0f * 3.0f;
if(sin(dolphin_angle) < -0.7f)
{
dolphin_x_pos = (float)(rand() % 1400) - 700.0f;
dolphin_z_pos = (float)(rand() % 1500);
}
// create and set the view transformation
D3DXMATRIX mat_view;
D3DXVECTOR3 eye(0.0f, 170.0f, -1000.0f);
D3DXVECTOR3 at(0.0f, 150.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up);
g_device->SetTransform(D3DTS_VIEW, &mat_view);
// clear the device and start drawing the scene
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(0, 0, 0, 255), 1.0f, 0);
g_device->BeginScene();
// draw the backdrop
g_device->SetFVF(BACKDROP_FVF);
g_device->SetStreamSource(0, g_backdrop_vb, 0, sizeof(sBackdropVertex));
g_device->SetTexture(0, g_backdrop_texture);
g_device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
// set identity matrix for world transformation
D3DXMATRIX mat_world;
D3DXMatrixIdentity(&mat_world);
g_device->SetTransform(D3DTS_WORLD, &mat_world);
g_device->SetRenderState(D3DRS_LIGHTING, TRUE);
draw_mesh(g_water_morph_mesh->result_mesh);
// draw the jumping dolphin
D3DXMatrixRotationZ(&mat_world, dolphin_angle - 1.57f);
mat_world._41 = dolphin_x_pos + cos(dolphin_angle) * 256.0f;
mat_world._42 = sin(dolphin_angle) * 512.0f;
mat_world._43 = dolphin_z_pos;
g_device->SetTransform(D3DTS_WORLD, &mat_world);
draw_mesh(g_dolphin_morph_mesh->result_mesh);
g_device->SetRenderState(D3DRS_LIGHTING, FALSE);
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
bool load_morphing_mesh(sMorphMesh* morph_mesh,
const char* source_mesh_file,
const char* target_mesh_file,
const char* texture_path)
{
if(FAILED(load_mesh(&morph_mesh->source_mesh, g_device, source_mesh_file, texture_path, 0, D3DXMESH_SYSTEMMEM)))
return false;
if(FAILED(load_mesh(&morph_mesh->target_mesh, g_device, target_mesh_file, texture_path, 0, D3DXMESH_SYSTEMMEM)))
return false;
// reload the source mesh as a resulting morphed mesh container
if(FAILED(load_mesh(&morph_mesh->result_mesh, g_device, source_mesh_file, texture_path, 0, D3DXMESH_SYSTEMMEM)))
return false;
DWORD mesh_fvf = morph_mesh->source_mesh->MeshData.pMesh->GetFVF();
// determin if source mesh uses normals and calculate offset
if(mesh_fvf & D3DFVF_NORMAL)
morph_mesh->normal_offset = 3 * sizeof(float);
else
morph_mesh->normal_offset = 0;
morph_mesh->vertex_pitch = D3DXGetFVFVertexSize(mesh_fvf);
return true;
}
void build_morph_mesh(sMorphMesh* morph_mesh, float scalar)
{
char* source_ptr;
char* target_ptr;
char* result_ptr;
morph_mesh->source_mesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&source_ptr);
morph_mesh->target_mesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&target_ptr);
morph_mesh->result_mesh->MeshData.pMesh->LockVertexBuffer(0, (void**)&result_ptr);
DWORD num_verts = morph_mesh->source_mesh->MeshData.pMesh->GetNumVertices();
// go through each vertex and interpolate coordinates
for(DWORD i = 0; i < num_verts; i++)
{
sVertex* source_vert = (sVertex*) source_ptr;
sVertex* target_vert = (sVertex*) target_ptr;
sVertex* result_vert = (sVertex*) result_ptr;
D3DXVECTOR3 source_pos = source_vert->pos;
source_pos *= (1.0f - scalar);
D3DXVECTOR3 target_pos = target_vert->pos;
target_pos *= scalar;
result_vert->pos = source_pos + target_pos;
// handle interpolation of normals
if(morph_mesh->normal_offset)
{
sVertex* source_normal = (sVertex*) &source_ptr[morph_mesh->normal_offset];
sVertex* target_normal = (sVertex*) &target_ptr[morph_mesh->normal_offset];
sVertex* result_normal = (sVertex*) &result_ptr[morph_mesh->normal_offset];
D3DXVECTOR3 source_pos = source_normal->pos;
source_pos *= (1.0f - scalar);
D3DXVECTOR3 target_pos = target_normal->pos;
target_pos *= scalar;
result_normal->pos = source_pos + target_pos;
}
// goto next vertex
source_ptr += morph_mesh->vertex_pitch;
target_ptr += morph_mesh->vertex_pitch;
result_ptr += morph_mesh->vertex_pitch;
}
morph_mesh->source_mesh->MeshData.pMesh->UnlockVertexBuffer();
morph_mesh->target_mesh->MeshData.pMesh->UnlockVertexBuffer();
morph_mesh->result_mesh->MeshData.pMesh->UnlockVertexBuffer();
}
download source file