Microsoft Corporation
October 6, 1997
A COM interface defined in Direct3D® Retained Mode as based on IDirect3DRMObject has the characteristics of all objects defined for the system. You can pass any kind of object to a function that takes a IDirect3DRMObject as one of its parameters. Here is a list of interfaces that can be used as IDirect3DRMObjects:
DECLARE_INTERFACE_(IDirect3DRMObject, IUnknown)
DECLARE_INTERFACE_(IDirect3DRMVisual, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMDevice, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMViewport, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMFace, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMLight, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMWrap, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMMaterial, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMAnimation, IDirect3DRMObject)
DECLARE_INTERFACE_(IDirect3DRMAnimationSet, IDirect3DRMObject)
These are some of the interfaces to IDirect3DRMVisual:
DECLARE_INTERFACE_(IDirect3DRMMesh, IDirect3DRMVisual)
DECLARE_INTERFACE_(IDirect3DRMMeshBuilder, IDirect3DRMVisual)
DECLARE_INTERFACE_(IDirect3DRMTexture, Direct3DRMVisual)
Functions that work at the IDirect3DRMObject level can operate on all of the above types of objects. Here is a list of those types of functions:
AddDestroyCallback
DeleteDestroyCallback
SetAppData
GetAppData
SetName
GetName
GetClassName
Clone
This method of being able to use multiple types of objects in the same function is very helpful when programming a Retained Mode application. In Windows®, you can store extra bytes with a specific window handle. In Retained Mode, you can store 32 bits of application-specific data in any kind of Retained Mode object. This means that you can store information with each individual mesh of your application, or you can go to a higher level and store this information with each individual device in your application.
An IDirect3DRMDevice is simply an output device that Retained Mode renders to. Think of an IDirect3DRMFrame as being an invisible frame of reference. Think of it as a plane that objects can be placed on by reference. An IDirect3DRMMesh is a visual object, which is made up of a set of polygons. To display a visual mesh object, you could load (or create) a mesh, then create an IDirect3DRMFrame with CreateFrame() and add the mesh to the frame as a visual object with AddVisual().
For more information on objects in Retained Mode, go to the API reference, located at www.microsoft.com/directx/dxm/help/d3drm/ref/Iface_intro.htm, and click on IDirect3DRMObject.
You must create a Windows device if you want Retained Mode to render to your window. You can do this with the IDirect3DRM::CreateDeviceFromClipper() function. Inside of the callback procedure for the window that you are creating an IDirect3DRMDevice for, you need to process the WM_PAINT and WM_ACTIVATE messages and call the IDirect3DRMWinDevice::HandlePaint() and IDirect3DRMWinDevice::HandleActivate() functions respectively. For example, here is one way you might add code to your application to do this:
case WM_ACTIVATE:
{
LPDIRECT3DRMWINDEVICE windev;
if (SUCCEEDED(m_lpDev->QueryInterface(IID_IDirect3DRMWinDevice, (void **) &windev)))
{
if (FAILED(windev->HandleActivate((WORD)uParam)))
{
AfxMessageBox("Failed to handle WM_ACTIVATE");
}
windev->Release();
}
else
{
AfxMessageBox("Failed to create Windows device to handle WM_ACTIVATE");
}
break;
}
case WM_PAINT:
{
RECT r;
PAINTSTRUCT ps;
if (GetUpdateRect(hWnd, &r, FALSE))
{
BeginPaint(hWnd, &ps);
WinDevice->HandlePaint(ps.hdc);
EndPaint(hWnd, &ps);
}
break;
}
You will want to create a Retained Mode object with Direct3DRMCreate(). Here is an example of how you might do this:
CRl4mfcView::CRl4mfcView()
{
m_lpDev = NULL;
CRl4mfcApp* pApp = (CRl4mfcApp*)AfxGetApp();
pApp->m_pView = this;
if (FAILED(Direct3DRMCreate(&m_lprm)))
AfxMessageBox("Create3DRM Failed");
}
You will want to call IDirect3DRM::CreateDeviceFromClipper() right after you have created your Window and a DirectDraw clipper that is not associated with a DirectDraw COM object. You can create this clipper with DirectDrawCreateClipper(). Then you will want to set color ramps, the number of texture colors, and a few other things dependent on pixel depth with SetShades, SetDefaultTextureShades, and SetDither. The above functions need to be called with parameters dependent on the number of bits-per-pixel your device supports. After you have done that, you need to create a frame for your whole Retained Mode scene, a camera (viewing) frame for that scene, and a viewport for it with the CreateFrame and CreateViewport functions. Finally, you need to build your Retained Mode scene with Retained Mode functions.
Here is an example of how you might implement the above:
{
RECT r;
int bpp;
HDC hdc;
::GetClientRect(win, &r);
DirectDrawCreateClipper(0, &m_lpDDClipper, NULL);
if (FAILED(m_lpDDClipper->SetHWnd(0, win)))
{
m_lpDDClipper->Release();
return;
}
m_lprm->CreateDeviceFromClipper(m_lpDDClipper,
FindDevice(D3DCOLOR_MONO), r.right, r.bottom, &m_lpDev);
hdc = ::GetDC(win);
bpp = ::GetDeviceCaps(hdc, BITSPIXEL);
::ReleaseDC(win, hdc);
switch (bpp)
{
case 1:
m_lpDev->SetShades(4);
m_lprm->SetDefaultTextureShades(4);
break;
case 16:
m_lpDev->SetShades(32);
m_lprm->SetDefaultTextureColors(64);
m_lprm->SetDefaultTextureShades(32);
m_lpDev->SetDither(FALSE);
break;
case 24:
m_lpDev->SetShades(256);
m_lprm->SetDefaultTextureColors(64);
m_lprm->SetDefaultTextureShades(256);
m_lpDev->SetDither(FALSE);
break;
default:
m_lpDev->SetDither(FALSE);
}
CreateScene();
m_lprm->CreateViewport(m_lpDev, m_camera, 0, 0,
m_lpDev->GetWidth(),
m_lpDev->GetHeight(), &m_view);
m_view->SetBack(D3DVAL(5000.0));
}
The FindDevice() function called above can be found in "viewer.cpp" of the Viewersample shipped with Microsoft® DirectX® 2. The CreateScene() function called above is a function you should write that will initialize the objects, lights, textures, and so on of your scene.
To inform Retained Mode that you want it to render and update your scene, you can call the following set of functions:
BOOL CRl4mfcApp::OnIdle(LONG lCount)
{
CWinApp::OnIdle(lCount);
if (m_pView)
{
CRl4mfcView * view = (CRl4mfcView *)m_pView;
view->Render();
}
return TRUE;
}
void CRl4mfcView::Render()
{
m_scene->Move(D3DVALUE(1.0));
m_view->Clear();
m_view->Render(m_scene);
m_lpDev->Update();
}
The IDirect3DRMMeshBuilder::Load and IDirect3DTexture::Load functions can be used to load meshes and textures that are in the .XOF and .PPM formats, respectively.
An IDirect3DRMFrame provides a frame of reference that objects can be placed in. Visual objects are placed in a scene by taking their positions and orientations from an IDirect3DRMFrame. So, if you have a set of visuals that you want to move together around a scene, you would call IDirect3DRMFrame::AddVisual to add the visuals to the frame. Now, when the frame is moved, all of the visuals within the frame are moved along with it. For a complete listing of methods for the Frame interface, refer to the API help document.
The API reference shows a lot of functions implemented to manipulate frames. Every Retained Mode scene has what is called the root frame. This frame does not have a parent and could be created like this, for example:
scene = win->CreateFrame(NULL);
To create children of the root frame, you would pass the root frame (scene) to the CreateFrame() function. Child frames orient and position themselves to the parent frame and when the parent frame moves or reorients itself, all of the children move and reorient themselves along with it. Think of the root frame as being the "world" coordinate system. A child will inherit the velocity and rotation of its parent and move along with it. Frame velocity is set with the SetVelocity function and frame rotation is set with the SetRotation function. The frame's objects will move to their new locations and will rotate correspondingly before each scene is rendered. If you do not want your objects to move or rotate, then these values should be set to zero, which is the default value. A frame will keep on rotating and moving as long as these values are nonzero.
You can call the AddChild function to change a frame's parent at any time. You can call AddVisual to add a frame as a visual to another frame. This way, you can use a given hierarchy of frames many different times throughout a scene.
An IDirect3DRMMesh is a visual, three-dimensional object made up of a set of faces. A face is defined in Retained Mode as an IDirect3DRMFace object and contains vertices and normals which define a face in a set of faces that make up a mesh.
You can set the color, texture, and material properties for a single face in a mesh by using the SetColor, SetTexture, and SetMaterial functions. You can set the color, texture, and material properties for a complete mesh (all faces in the mesh) by using the SetColor, SetTexture, and SetMaterial functions.
One way to create a mesh is to create each individual face yourself as a set of polygonal points and add each face to the mesh individually. Retained Mode will calculate the normal vector of each face for you by using the points in the face. You need to add each vertex to a face in clockwise order if you want the normal to come out towards you as you look at the face. You can create an empty face with IDirect3DRMMeshBuilder::CreateFace, then add vertices to that face with IDirect3DRMFace::AddVertex. When you are done building the face, you can add it to the mesh builder with IDirect3DRMMeshBuilder::AddFace.
Another, perhaps more efficient, method of building a mesh of faces is to use the IDirect3DRMMeshBuilder::AddFaces function. This function looks like this:
HRESULT AddFaces(DWORD dwVertexCount, D3DVECTOR *
lpD3DVertices, DWORD normalCount, D3DVECTOR
*lpNormals, DWORD *lpFaceData,
LPDIRECT3DRMFACEARRAY* lplpD3DRMFaceArray);
The dwVertexCount parameter specifies the number of vertices that the lpD3DVertices array will contain and the normalCount parameter specifies the number of normal vectors that the lpNormals array will contain. For every possible vertex point and for every possible normal vector in your mesh, you must create these two arrays. Every possible vertex point should be placed in the lpD3Dvertices array and every possible normal vector should be placed in the lpNormals array. Now the lplpD3DRMFaceArray array will contain data for every single face in your mesh. You need to define a normal vector for every single vertex in every face using the indices into the lpD3DVertices array and the lpNormals array. For every face in the lplpD3DRMFaceArray array, you have to first specify the number of vertices in the face. Next, you need to follow that number with pairs of integers that specify an index into the lpD3DVertices array and an index into the lpNormals array. So, if you had four indices in your face, then you should have eight integers following that number to define the face. If that is the last face in the mesh, then you need to place a zero after the last number.
A mesh is a visual object that is made up of a set of polygonal faces. A mesh defines a set of vertices and a set of faces (the faces are defined in terms of the vertices and normals of the mesh). Changing a vertex or normal that is used by several faces will change the appearance of all faces sharing it.
The vertices of a mesh define the positions of faces in the mesh, and they can also be used to define two-dimensional coordinates within a texture map.
You can manipulate meshes in RealityLab by using two COM interfaces: IDirect3DRMMesh and IDirect3DRMMeshBuilder. IDirect3DRMMesh is very fast and should be used whenever a mesh is subject to frequent changes. Morphing is a good example of a natural use for the IDirect3DRMMesh interface. IDirect3DRMMeshBuilder is built on top of the IDirect3DRMMesh interface. Although the IDirect3DRMMeshBuilder interface is a convenient way to perform operations on individual faces and vertices, the system must convert a MeshBuilder object into a Mesh object before rendering it. For meshes that do not change or that change infrequently, this conversion has a negligible impact on performance.
You use the IDirect3DRMMesh methods to create groups of vertices and faces and then assign such characteristics as materials and textures to the group as a unit. The IDirect3DRMMesh::AddGroup method retrieves a group identifier that is used to identify the group in subsequent calls. If you need a vertex to be shared by two faces that have different colors, you must duplicate the vertex in two different groups, with the same vertex coordinates in each group.
The IDirect3DRMMeshBuilder and IDirect3DRMMesh interfaces allow you to have faces with more than three sides. They also automatically split a mesh into multiple buffers if, for example, the card you are rendering to has a limit of 64 kilobytes (KB) and your mesh is larger than that size. These features set the Mesh and MeshBuilder API apart from the Direct3D API.
Vertices and faces can be added individually to a mesh using the IDirect3DRMMeshBuilder::AddVertex, IDirect3DRMMeshBuilder::AddFace, and IDirect3DRMMeshBuilder::AddFaces methods. The vertex normals are used when lighting in the Gouraud and Phong shading models to give a smooth look to a polygonal object. The application can set normals (which should be unit vectors) or normals can be calculated by averaging the face normals of the surrounding faces using IDirect3DRMMeshBuilder::GenerateNormals.
Individual color, texture and material properties can be defined for each face in the mesh, or for all faces in the mesh at once, using IDirect3DRMMesh::SetGroupColour, IDirect3DRMMesh::SetGroupTexture, and IDirect3DRMMesh::SetGroupMaterial.
For a mesh to be rendered it must first be added to a frame using IDirect3DRMFrame::AddVisual. One mesh can be added to multiple frames to create multiple instances of that mesh.
A texture in Retained Mode is defined as an IDirect3DRMTexture object. With a texture, you can map patterns, such as bitmap pictures, onto the surfaces of objects, such as faces and meshes. You can add a texture to a mesh with IDirect3DRMMeshBuilder::SetTexture and add one to a face in a mesh with IDirect3DRMFace::SetTexture. If you have a mesh in a file in the .PPM file format, you can load it with the IDirect3DTexture::Load function.
You can set the number of colors used to render a texture using the IDirect3DRMTexture::SetColors function.
You will want to use the IDirect3DRM::CreateWrap and IDirect3DRMWrap::Apply to create and assign a wrapping function that can be used to assign texture coordinates to faces and/or meshes.
A shadow can be produced to give a more realistic effect to your application. A shadow object can be created with the IDirect3DRM::CreateShadow function.
Before creating and using a shadow, you will first want to define a light source to produce light that will, in turn, produce shadows.
After you have added a light source to your scene for use in casting shadows, you need to create the shadow with the IDirect3DRM::CreateShadow function. Here is how this function is defined:
HRESULT CreateShadow(LPDIRECT3DRMVISUAL lpVisual,
LPDIRECT3DRMLIGHT lpLight, D3DVALUE px, D3DVALUE py,
D3DVALUE pz, D3DVALUE nx, D3DVALUE ny, D3DVALUE nz,
LPDIRECT3DRMVISUAL * lplpShadow);
When you have all of these 3-D objects running around in three-dimensional space, you need to define how your 3-D scene will be rendered into your two-dimensional window. The viewport, defined as IDirect3DRMViewport, defines a rectangular area on your IDirect3DRMDevice where objects should be rendered. Take a moment to read the section "IDirect3DRMViewport" in API reference, located at www.microsoft.com/directx/dxm/help/d3drm/ref/Iface_intro.htm.
When rendering to a Window, you need to make sure to change the size of your IDirect3DRMViewport when the size of the window changes. When your "Retained Mode" window receives a WM_SIZE message, you need to destroy the old viewport and create a new one that is the new size of your window.
void CRl4mfcView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (m_lpDev)
{
int width = cx;
int height = cy;
if (width && height)
{
int view_width = m_view->GetWidth();
int view_height = m_view->GetHeight();
int dev_width = m_lpDev->GetWidth();
int dev_height = m_lpDev->GetHeight();
if (view_width == width && view_height == height)
return;
if (width <= dev_width && height <= dev_height) {
m_view->Release();
m_lprm->CreateViewport(m_lpDev, m_camera, 0,
0, width, height, &m_view);
m_view->SetBack(D3DVALUE(400.0));
}
int old_dither = m_lpDev->GetDither();
D3DRMRENDERQUALITY old_quality = m_lpDev->GetQuality();
int old_shades = m_lpDev->GetShades();
m_view->Release();
m_lpDev->Release();
m_lprm->CreateDeviceFromClipper(m_lpDDClipper,
FindDevice(D3DCOLOR_MONO), width, height, &m_lpDev);
m_lpDev->SetDither(old_dither);
m_lpDev->SetQuality(old_quality);
m_lpDev->SetShades(old_shades);
width = m_lpDev->GetWidth();
height = m_lpDev->GetHeight();
m_lprm->CreateViewport(m_lpDev, m_camera,
0, 0, width, height, &m_view);
m_view->SetBack(D3DVALUE(400.0));
}
}
}